├── demo
├── website
├── test
│ ├── mocha.css
│ └── mocha.js
├── demo.ts
└── index.html
├── .github
├── FUNDING.yml
├── workflows
│ └── ci.yml
└── ISSUE_TEMPLATE
│ └── issue.yaml
├── .npmignore
├── .gitignore
├── package.json
├── LICENSE
├── README.md
├── bin
├── packages.js
├── build-readme.js
└── cm.js
└── tsconfig.json
/demo/website:
--------------------------------------------------------------------------------
1 | ../website/output/
--------------------------------------------------------------------------------
/demo/test/mocha.css:
--------------------------------------------------------------------------------
1 | ../../node_modules/mocha/mocha.css
--------------------------------------------------------------------------------
/demo/test/mocha.js:
--------------------------------------------------------------------------------
1 | ../../node_modules/mocha/mocha.js
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: marijn
2 | custom: ['https://www.paypal.com/paypalme/marijnhaverbeke', 'https://marijnhaverbeke.nl/fund/']
3 | github: marijnh
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /*/src/*.js
2 | /*/src/*.ts
3 | !/*/src/*.d.ts
4 | /*/src/*.map
5 | /*/src/README.md
6 | /*/test
7 | /node_modules
8 | /demo
9 | /.travis.yml
10 | .tern-*
11 | /bin
12 | /website
13 |
--------------------------------------------------------------------------------
/demo/demo.ts:
--------------------------------------------------------------------------------
1 | import {EditorView, basicSetup} from "codemirror"
2 | import {javascript} from "@codemirror/lang-javascript"
3 |
4 | ;(window as any).view = new EditorView({
5 | doc: 'console.log("Hello world")',
6 | extensions: [
7 | basicSetup,
8 | javascript(),
9 | ],
10 | parent: document.body
11 | })
12 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
CM6 demo
5 |
6 |
10 |
11 | CM6
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | on: [push, repository_dispatch]
3 | jobs:
4 | build-and-test:
5 | runs-on: ubuntu-latest
6 | name: Build and test
7 | steps:
8 | - uses: styfle/cancel-workflow-action@0.9.1
9 | with:
10 | access_token: ${{ github.token }}
11 |
12 | - uses: actions/checkout@v1
13 |
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: '22.11.0'
17 |
18 | - run: node bin/cm.js install
19 |
20 | - run: npm test
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | package-lock.json
3 | /demo/demo.js*
4 | /demo/demo.d.ts*
5 | /demo/test/test.js*
6 | /website
7 | /buildhelper
8 | /state
9 | /view
10 | /commands
11 | /collab
12 | /language
13 | /language-data
14 | /search
15 | /lint
16 | /autocomplete
17 | /codemirror
18 | /merge
19 | /lsp-client
20 | /lang-javascript
21 | /lang-java
22 | /lang-json
23 | /lang-cpp
24 | /lang-python
25 | /lang-go
26 | /lang-css
27 | /lang-html
28 | /lang-php
29 | /lang-sql
30 | /lang-rust
31 | /lang-xml
32 | /lang-markdown
33 | /lang-lezer
34 | /lang-wast
35 | /lang-angular
36 | /lang-vue
37 | /lang-liquid
38 | /lang-sass
39 | /lang-jinja
40 | /lang-less
41 | /lang-yaml
42 | /legacy-modes
43 | /theme-one-dark
44 | .tern-*
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Development environment for the CodeMirror 6 packages",
3 | "scripts": {
4 | "test": "node bin/cm.js test",
5 | "test-node": "node bin/cm.js test --no-browser",
6 | "prepare": "node bin/cm.js build",
7 | "dev": "node bin/cm.js devserver"
8 | },
9 | "author": "Marijn Haverbeke ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@codemirror/buildhelper": "^1.0.2",
13 | "esmoduleserve": "^0.2.0",
14 | "serve-static": "^1.14.1",
15 | "getdocs-ts": "^1.0.0",
16 | "builddocs": "^1.0.0"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/codemirror/dev.git"
21 | },
22 | "workspaces": [
23 | "*"
24 | ],
25 | "private": true
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue.yaml:
--------------------------------------------------------------------------------
1 | name: Issue Report
2 | description: Report a problem
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: Thanks for reporting an issue. If you have a question or request for support, use the [forum](https://discuss.codemirror.net/), **not** the bug tracker.
7 | - type: textarea
8 | id: descriptin
9 | attributes:
10 | label: Describe the issue
11 | validations:
12 | required: true
13 | - type: input
14 | id: browser
15 | attributes:
16 | label: Browser and platform
17 | description: If there is any chance at all that this is browser or platform related, please let us know which ones you tested. If IME or a virtual keyboard was involved, please mention which.
18 | validations:
19 | required: false
20 | - type: input
21 | id: try
22 | attributes:
23 | label: Reproduction link
24 | description: When practical, it helps a lot if you provide a script that reproduces the issue in [the CodeMirror sandbox](https://codemirror.net/try/) or another online code environment.
25 | validations:
26 | required: false
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (C) 2018 by Marijn Haverbeke , Adrian
4 | Heine , and others
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeMirror
2 |
3 | [](https://github.com/codemirror/codemirror.next/actions)
4 |
5 | This is the central repository for [CodeMirror](https://codemirror.net/). It holds the bug tracker and development scripts.
6 |
7 | If you want to **use** CodeMirror, install the separate packages from npm, and ignore the contents of this repository. If you want to **develop on** CodeMirror, this repository provides scripts to install and work with the various packages.
8 |
9 | To get started, make sure you are running [node.js](https://nodejs.org/) version 16. After cloning the repository, run
10 |
11 | node bin/cm.js install
12 |
13 | to clone the packages that make up the system, install dependencies, and build the packages. At any time you can rebuild packages, either by running `npm run prepare` in their subdirectory, or all at once with
14 |
15 | node bin/cm.js build
16 |
17 | Developing is best done by setting up
18 |
19 | npm run dev
20 |
21 | which starts a server that automatically rebuilds the packages when their code changes and exposes a dev server on port 8090 running the [demo](http://localhost:8090) and [browser tests](http://localhost:8090/test/).
22 |
23 | Please see [the website](https://codemirror.net/) for more information and [docs](https://codemirror.net/docs/ref).
24 |
--------------------------------------------------------------------------------
/bin/packages.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs"), {join} = require("path")
2 |
3 | exports.core = [
4 | "state",
5 | "view",
6 | "language",
7 | "commands",
8 | "search",
9 | "autocomplete",
10 | "lint",
11 | "collab",
12 | "language-data",
13 | "merge",
14 | "lsp-client",
15 | "codemirror",
16 | ]
17 | exports.nonCore = [
18 | "lang-javascript",
19 | "lang-java",
20 | "lang-json",
21 | "lang-cpp",
22 | "lang-php",
23 | "lang-python",
24 | "lang-go",
25 | "lang-css",
26 | "lang-sass",
27 | "lang-html",
28 | "lang-sql",
29 | "lang-rust",
30 | "lang-xml",
31 | "lang-markdown",
32 | "lang-lezer",
33 | "lang-wast",
34 | "lang-angular",
35 | "lang-vue",
36 | "lang-liquid",
37 | "lang-less",
38 | "lang-yaml",
39 | "lang-jinja",
40 | "legacy-modes",
41 | "theme-one-dark"
42 | ]
43 |
44 | exports.all = exports.core.concat(exports.nonCore)
45 |
46 | class Pkg {
47 | constructor(name) {
48 | this.name = name
49 | this.dir = join(__dirname, "..", name)
50 | this.main = null
51 | if (name != "legacy-modes" && fs.existsSync(this.dir)) {
52 | let files = fs.readdirSync(join(this.dir, "src")).filter(f => /^[^.]+\.ts$/.test(f))
53 | let main = files.length == 1 ? files[0] : files.includes("index.ts") ? "index.ts"
54 | : files.includes(name.replace(/^(theme-|lang-)/, "") + ".ts") ? name.replace(/^(theme-|lang-)/, "") + ".ts" : null
55 | if (!main) throw new Error("Couldn't find a main script for " + name)
56 | this.main = join(this.dir, "src", main)
57 | }
58 | }
59 | }
60 | exports.Pkg = Pkg
61 |
62 | exports.loadPackages = function loadPackages() {
63 | let packages = exports.all.map(n => new Pkg(n))
64 | let packageNames = Object.create(null)
65 | for (let p of packages) packageNames[p.name] = p
66 | return {packages, packageNames, buildPackages: packages.filter(p => p.main)}
67 | }
68 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es2018", "dom", "scripthost"],
4 | "types": ["mocha"],
5 | "stripInternal": true,
6 | "typeRoots": ["./node_modules/@types"],
7 | "noUnusedLocals": true,
8 | "strict": true,
9 | "target": "es2018",
10 | "module": "es2020",
11 | "newLine": "lf",
12 | "moduleResolution": "node",
13 | "paths": {
14 | "@codemirror/state": ["./state/src/index.ts"],
15 | "@codemirror/view": ["./view/src/index.ts"],
16 | "@codemirror/commands": ["./commands/src/commands.ts"],
17 | "@codemirror/collab": ["./collab/src/collab.ts"],
18 | "@codemirror/language": ["./language/src/index.ts"],
19 | "@codemirror/language-data": ["./language-data/src/language-data.ts"],
20 | "@codemirror/search": ["./search/src/search.ts"],
21 | "@codemirror/lint": ["./lint/src/lint.ts"],
22 | "@codemirror/autocomplete": ["./autocomplete/src/index.ts"],
23 | "@codemirror/merge": ["./merge/src/index.ts"],
24 | "@codemirror/lsp-client": ["./lsp-client/src/index.ts"],
25 | "codemirror": ["./codemirror/src/codemirror.ts"],
26 | "@codemirror/lang-javascript": ["./lang-javascript/src/index.ts"],
27 | "@codemirror/lang-java": ["./lang-java/src/java.ts"],
28 | "@codemirror/lang-json": ["./lang-json/src/json.ts"],
29 | "@codemirror/lang-cpp": ["./lang-cpp/src/cpp.ts"],
30 | "@codemirror/lang-python": ["./lang-python/src/python.ts"],
31 | "@codemirror/lang-go": ["./lang-go/src/index.ts"],
32 | "@codemirror/lang-css": ["./lang-css/src/css.ts"],
33 | "@codemirror/lang-html": ["./lang-html/src/html.ts"],
34 | "@codemirror/lang-sql": ["./lang-sql/src/sql.ts"],
35 | "@codemirror/lang-rust": ["./lang-rust/src/rust.ts"],
36 | "@codemirror/lang-xml": ["./lang-xml/src/xml.ts"],
37 | "@codemirror/lang-markdown": ["./lang-markdown/src/index.ts"],
38 | "@codemirror/lang-lezer": ["./lang-lezer/src/index.ts"],
39 | "@codemirror/lang-php": ["./lang-php/src/php.ts"],
40 | "@codemirror/lang-wast": ["./lang-wast/src/wast.ts"],
41 | "@codemirror/lang-angular": ["./lang-angular/src/angular.ts"],
42 | "@codemirror/lang-vue": ["./lang-vue/src/vue.ts"],
43 | "@codemirror/lang-liquid": ["./lang-vue/src/liquid.ts"],
44 | "@codemirror/lang-sass": ["./lang-sass/src/sass.ts"],
45 | "@codemirror/lang-less": ["./lang-less/src/less.ts"],
46 | "@codemirror/lang-yaml": ["./lang-yaml/src/yaml.ts"],
47 | "@codemirror/lang-jinja": ["./lang-jinja/src/jinja.ts"],
48 | "@codemirror/theme-one-dark": ["./theme-one-dark/src/one-dark.ts"]
49 | }
50 | },
51 | "include": ["*/src/*.ts", "*/test/*.ts", "demo/demo.ts"],
52 | }
53 |
--------------------------------------------------------------------------------
/bin/build-readme.js:
--------------------------------------------------------------------------------
1 | // Function to build github-proof readmes that contain the package's API
2 | // docs as HTML.
3 |
4 | const {core} = require("./packages")
5 | const {gather, gatherMany} = require("getdocs-ts")
6 | const {build, browserImports} = require("builddocs")
7 | const {join} = require("path"), fs = require("fs")
8 |
9 | exports.buildReadme = function(pkg) {
10 | let imports = [type => {
11 | let sibling = type.typeSource && core.find(name => type.typeSource.startsWith("../" + name + "/"))
12 | if (sibling) return "https://codemirror.net/docs/ref#" + sibling + "." + type.type
13 | }, type => {
14 | if (/\blezer\/tree\b/.test(type.typeSource)) return `https://lezer.codemirror.net/docs/ref/#tree.${type.type}`
15 | if (/\blezer\/common\b/.test(type.typeSource)) return `https://lezer.codemirror.net/docs/ref/#common.${type.type}`
16 | if (/\blezer\/lr\b/.test(type.typeSource)) return `https://lezer.codemirror.net/docs/ref/#lr.${type.type}`
17 | if (/\blezer\/markdown\b/.test(type.typeSource)) return `https://github.com/lezer-parser/markdown#user-content-${type.type.toLowerCase()}`
18 | if (/\bstyle-mod\b/.test(type.typeSource)) return "https://github.com/marijnh/style-mod#documentation"
19 | if (/\bvscode-languageserver-/.test(type.typeSource))
20 | return `https://microsoft.github.io/language-server-protocol/specifications/specification-current#` +
21 | type.type[0].toLowerCase() + type.type.slice(1)
22 | if (type.type == "TextEdit") console.log(type.typeSource, type.type)
23 | }, browserImports]
24 |
25 | let template = fs.readFileSync(join(pkg.dir, pkg.name == "legacy-modes" ? "mode" : "src", "README.md"), "utf8")
26 | let html = ""
27 |
28 | if (pkg.name == "legacy-modes") {
29 | let mods = fs.readdirSync(join(pkg.dir, "mode")).filter(f => /\.d.ts$/.test(f)).map(file => {
30 | let name = /^(.*)\.d\.ts$/.exec(file)[1]
31 | return {name, filename: join(pkg.dir, "mode", file), basedir: pkg.dir}
32 | }), items = gatherMany(mods)
33 | for (let i = 0; i < mods.length; i++) {
34 | let {name} = mods[i]
35 | html += `\n\n` + build({
36 | name: pkg.name,
37 | anchorPrefix: name + ".",
38 | allowUnresolvedTypes: false,
39 | imports
40 | }, items[i])
41 | }
42 | template += "\n$$$"
43 | } else {
44 | let placeholders = /(^|\n)@[^]*@\w+|\n@\w+/.exec(template)
45 | html = build({
46 | mainText: placeholders[0],
47 | name: pkg.name,
48 | anchorPrefix: "",
49 | allowUnresolvedTypes: false,
50 | imports
51 | }, gather({filename: pkg.main, basedir: pkg.dir}))
52 | template = template.slice(0, placeholders.index) + "\n$$$" + template.slice(placeholders.index + placeholders[0].length)
53 | }
54 |
55 | html = html.replace(/<\/?span.*?>/g, "")
56 | .replace(/id="(.*?)"/g, (_, id) => `id="user-content-${id.toLowerCase()}"`)
57 | .replace(/href="#(.*?)"/g, (_, id) => {
58 | let first = /^[^^.]*/.exec(id)[0]
59 | if (core.includes(first)) return `href="https://codemirror.net/docs/ref/#${id}"`
60 | if (first == pkg.name && id.length > first.length) id = id.slice(first.length + 1)
61 | return `href="#user-content-${id.toLowerCase()}"`
62 | })
63 |
64 | return template.replace("$$$", html)
65 | }
66 |
--------------------------------------------------------------------------------
/bin/cm.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // NOTE: Don't require anything from node_modules here, since the
4 | // install script has to be able to run _before_ that exists.
5 | const child = require("child_process"), fs = require("fs"), path = require("path"), {join} = path
6 |
7 | let root = join(__dirname, "..")
8 |
9 | const {loadPackages, nonCore} = require("./packages")
10 |
11 | let {packages, packageNames, buildPackages} = loadPackages()
12 |
13 | function start() {
14 | let command = process.argv[2]
15 | if (command && !["install", "--help"].includes(command)) assertInstalled()
16 | let args = process.argv.slice(3)
17 | let cmdFn = {
18 | packages: listPackages,
19 | status,
20 | build,
21 | devserver,
22 | release,
23 | unreleased,
24 | install,
25 | clean,
26 | commit,
27 | push,
28 | grep,
29 | "build-readme": buildReadme,
30 | test,
31 | run: runCmd,
32 | "--help": () => help(0)
33 | }[command]
34 | if (!cmdFn || cmdFn.length > args.length) help(1)
35 | new Promise(r => r(cmdFn.apply(null, args))).catch(e => error(e))
36 | }
37 |
38 | function help(status) {
39 | console.log(`Usage:
40 | cm install [--ssh] Clone and symlink the packages, install deps, build
41 | cm packages Emit a list of all pkg names
42 | cm status Output git status, when interesting, for packages
43 | cm build Build the bundle files
44 | cm clean Delete files created by the build
45 | cm devserver [--source-map]
46 | Start a dev server on port 8090
47 | cm release [--edit] [--version ]
48 | Create commits to tag a release
49 | cm build-readme Regenerate the readme file for a non-core package
50 | cm commit Run git commit in all packages that have changes
51 | cm push Run git push in packages that have new commits
52 | cm run Run the given command in each of the package dirs
53 | cm test [--no-browser] Run the test suite of all the packages
54 | cm grep Grep through the source code for all packages
55 | cm --help`)
56 | process.exit(status)
57 | }
58 |
59 | function error(err) {
60 | console.error(err)
61 | process.exit(1)
62 | }
63 |
64 | function run(cmd, args, wd = root, { shell = false } = {}) {
65 | return child.execFileSync(cmd, args, {shell, cwd: wd, encoding: "utf8", stdio: ["ignore", "pipe", process.stderr]})
66 | }
67 |
68 | function replace(file, f) {
69 | fs.writeFileSync(file, f(fs.readFileSync(file, "utf8")))
70 | }
71 |
72 | function assertInstalled() {
73 | for (let p of packages) {
74 | if (!fs.existsSync(p.dir)) {
75 | console.error(`module ${p.name} is missing. Did you forget to run 'cm install'?`)
76 | process.exit(1)
77 | }
78 | }
79 | }
80 |
81 | function install(arg = null) {
82 | let base = arg == "--ssh" ? "git@github.com:codemirror/" : "https://github.com/codemirror/"
83 | if (arg && arg != "--ssh") help(1)
84 |
85 | for (let pkg of packages) {
86 | if (fs.existsSync(pkg.dir)) {
87 | console.warn(`Skipping cloning of ${pkg.name} (directory exists)`)
88 | } else {
89 | let origin = base + (pkg.name == "codemirror" ? "basic-setup" : pkg.name) + ".git"
90 | run("git", ["clone", origin, pkg.dir])
91 | }
92 | }
93 |
94 | console.log("Running npm install")
95 | run("npm", ["install"], root, {shell: process.platform == "win32"})
96 | console.log("Building modules")
97 | ;({packages, packageNames, buildPackages} = loadPackages())
98 | build()
99 | }
100 |
101 |
102 | function listPackages() {
103 | console.log(packages.map(p => p.name).join("\n"))
104 | }
105 |
106 | function status() {
107 | for (let pkg of packages) {
108 | let output = run("git", ["status", "-sb"], pkg.dir)
109 | if (output != "## main...origin/main\n")
110 | console.log(`${pkg.name}:\n${output}`)
111 | }
112 | }
113 |
114 | async function build() {
115 | console.info("Building...")
116 | let t0 = Date.now()
117 | await require("@marijn/buildtool").build(buildPackages.map(p => p.main), require("@codemirror/buildhelper/src/options").options)
118 | console.info(`Done in ${((Date.now() - t0) / 1000).toFixed(2)}s`)
119 | }
120 |
121 | function startServer() {
122 | let serve = join(root, "demo")
123 | let moduleserver = new (require("esmoduleserve/moduleserver"))({root: serve, maxDepth: 2})
124 | let serveStatic = require("serve-static")(serve, {
125 | setHeaders(res, path) {
126 | if (/try\/mods\//.test(path)) res.setHeader("Access-Control-Allow-Origin", "*")
127 | }
128 | })
129 | require("http").createServer((req, resp) => {
130 | if (/^\/test\/?($|\?)/.test(req.url)) {
131 | let runTests = require("@marijn/testtool")
132 | let {browserTests} = runTests.gatherTests(buildPackages.map(p => p.dir))
133 | resp.writeHead(200, {"content-type": "text/html"})
134 | resp.end(runTests.testHTML(browserTests.map(f => path.relative(serve, f)), {
135 | html: `CM6 view tests
136 | CM6 view tests
137 | `
138 | }))
139 | } else {
140 | moduleserver.handleRequest(req, resp) || serveStatic(req, resp, _err => {
141 | resp.statusCode = 404
142 | resp.end('Not found')
143 | })
144 | }
145 | }).listen(8090, process.env.OPEN ? undefined : "127.0.0.1")
146 | console.log("Dev server listening on 8090")
147 | }
148 |
149 | function devserver(...args) {
150 | let options = {
151 | sourceMap : args.includes('--source-map'),
152 | ...require("@codemirror/buildhelper/src/options").options
153 | }
154 | require("@marijn/buildtool").watch(buildPackages.map(p => p.main).filter(f => f), [join(root, "demo/demo.ts")], options)
155 | startServer()
156 | }
157 |
158 | function changelog(pkg, since) {
159 | let commits = run("git", ["log", "--format=%B%n", "--reverse", since + "..main"], pkg.dir)
160 | let result = {fix: [], feature: [], breaking: []}
161 | let re = /\n\r?\n(BREAKING|FIX|FEATURE):\s*([^]*?)(?=\r?\n\r?\n|\r?\n?$)/g, match
162 | while (match = re.exec(commits)) result[match[1].toLowerCase()].push(match[2].replace(/\r?\n/g, " "))
163 | return result
164 | }
165 |
166 | function bumpVersion(version, changes) {
167 | let [major, minor, patch] = version.split(".")
168 | if (major == "0") return changes.breaking.length ? `0.${Number(minor) + 1}.0` : `0.${minor}.${Number(patch) + 1}`
169 | if (changes.breaking.length) return `${Number(major) + 1}.0.0`
170 | if (changes.feature.length) return `${major}.${Number(minor) + 1}.0`
171 | if (changes.fix.length) return `${major}.${minor}.${Number(patch) + 1}`
172 | throw new Error("No new release notes!")
173 | }
174 |
175 | function releaseNotes(changes, version) {
176 | let pad = n => n < 10 ? "0" + n : n
177 | let d = new Date, date = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
178 |
179 | let types = {breaking: "Breaking changes", fix: "Bug fixes", feature: "New features"}
180 |
181 | let refTarget = "https://codemirror.net/docs/ref/"
182 | let head = `## ${version} (${date})\n\n`, body = ""
183 | for (let type in types) {
184 | let messages = changes[type]
185 | if (messages.length) body += `### ${types[type]}\n\n`
186 | messages.forEach(message => body += message.replace(/\]\(##/g, "](" + refTarget + "#") + "\n\n")
187 | }
188 | return {head, body}
189 | }
190 |
191 | function setModuleVersion(pkg, version) {
192 | let file = join(pkg.dir, "package.json")
193 | fs.writeFileSync(file, fs.readFileSync(file, "utf8").replace(/"version":\s*".*?"/, `"version": "${version}"`))
194 | }
195 |
196 | function updateDependencyVersion(pkg, version) {
197 | let changed = []
198 | for (let other of packages) if (other != pkg) {
199 | let pkgFile = join(other.dir, "package.json"), text = fs.readFileSync(pkgFile, "utf8")
200 | let updated = text.replace(new RegExp(`("@codemirror/${pkg.name}": ")(.*?)"`, "g"), (_, m) => m + "^" + version + '"')
201 | if (updated != text) {
202 | changed.push(other)
203 | fs.writeFileSync(pkgFile, updated)
204 | run("git", ["add", "package.json"], other.dir)
205 | let lastMsg = run("git", ["log", "-1", "--pretty=%B"], other.dir)
206 | if (/^Bump dependency /.test(lastMsg))
207 | run("git", ["commit", "--amend", "-m", lastMsg.trimEnd() + ", @codemirror/" + pkg.name], other.dir)
208 | else
209 | run("git", ["commit", "-m", "Bump dependency for @codemirror/" + pkg.name], other.dir)
210 | }
211 | }
212 | return changed
213 | }
214 |
215 | function version(pkg) {
216 | return require(join(pkg.dir, "package.json")).version
217 | }
218 |
219 | const mainVersion = /^0.\d+|\d+/
220 |
221 | function release(...args) {
222 | let setVersion, edit = false, pkgName, pkg
223 | for (let i = 0; i < args.length; i++) {
224 | let arg = args[i]
225 | if (arg == "--edit") edit = true
226 | else if (arg == "--version" && i < args.length) setVersion = args[++i]
227 | else if (!pkgName && arg[0] != "-") pkgName = arg
228 | else help(1)
229 | }
230 | if (!pkgName || !(pkg = packageNames[pkgName])) help(1)
231 |
232 | run("git", ["pull"], pkg.dir)
233 |
234 | let {changes, newVersion} = doRelease(pkg, setVersion, {edit})
235 |
236 | // Turned off for now, since this creates a huge mess on accidental
237 | // major version bumps. Maybe add a manual utility for it?
238 | if (false && mainVersion.exec(newVersion)[0] != mainVersion.exec(version(pkg))[0]) {
239 | let updated = updateDependencyVersion(pkg, newVersion)
240 | if (updated.length) console.log(`Updated dependencies in ${updated.map(p => p.name).join(", ")}`)
241 | }
242 | }
243 |
244 | function doRelease(pkg, newVersion, {edit = false, defaultChanges = null}) {
245 | let log = join(pkg.dir, "CHANGELOG.md")
246 | let newPackage = !fs.existsSync(log)
247 |
248 | let currentVersion = version(pkg)
249 | let changes = newPackage ? {fix: [], feature: [], breaking: ["First numbered release."]} : changelog(pkg, currentVersion)
250 | if (defaultChanges && !changes.fix.length && !changes.feature.length && !changes.breaking.length) changes = defaultChanges
251 | if (!newVersion) newVersion = newPackage ? currentVersion : bumpVersion(currentVersion, changes)
252 | console.log(`Creating @codemirror/${pkg.name} ${newVersion}`)
253 |
254 | let notes = releaseNotes(changes, newVersion)
255 | if (edit) notes = editReleaseNotes(notes)
256 |
257 | setModuleVersion(pkg, newVersion)
258 | fs.writeFileSync(log, notes.head + notes.body + (newPackage ? "" : fs.readFileSync(log, "utf8")))
259 | run("git", ["add", "package.json"], pkg.dir)
260 | run("git", ["add", "CHANGELOG.md"], pkg.dir)
261 | run("git", ["commit", "-m", `Mark version ${newVersion}`], pkg.dir)
262 | run("git", ["tag", newVersion, "-m", `Version ${newVersion}\n\n${notes.body}`, "--cleanup=verbatim"], pkg.dir)
263 |
264 | return {changes, newVersion}
265 | }
266 |
267 | function editReleaseNotes(notes) {
268 | let noteFile = join(root, "notes.txt")
269 | fs.writeFileSync(noteFile, notes.head + notes.body)
270 | run(process.env.EDITOR || "emacs", [noteFile])
271 | let edited = fs.readFileSync(noteFile)
272 | fs.unlinkSync(noteFile)
273 | if (!/\S/.test(edited)) process.exit(0)
274 | let split = /^(.*)\n+([^]*)/.exec(edited)
275 | return {head: split[1] + "\n\n", body: split[2]}
276 | }
277 |
278 | function unreleased() {
279 | for (let pkg of packages) {
280 | let ver = version(pkg), changes = changelog(pkg, ver)
281 | if (changes.fix.length || changes.feature.length || changes.breaking.length)
282 | console.log(pkg.name + ":\n\n", releaseNotes(changes, ver).body)
283 | }
284 | }
285 |
286 | function clean() {
287 | for (let pkg of buildPackages)
288 | run("rm", ["-rf", "dist"], pkg.dir)
289 | }
290 |
291 | function commit(...args) {
292 | for (let pkg of packages) {
293 | if (run("git", ["diff"], pkg.dir) || run("git", ["diff", "--cached"], pkg.dir))
294 | console.log(pkg.name + ":\n" + run("git", ["commit"].concat(args), pkg.dir))
295 | }
296 | }
297 |
298 | function push(...args) {
299 | for (let pkg of packages) {
300 | if (/\bahead\b/.test(run("git", ["status", "-sb"], pkg.dir)))
301 | run("git", ["push", ...args], pkg.dir)
302 | }
303 | }
304 |
305 | function grep(pattern) {
306 | let files = [join(root, "demo", "demo.ts")]
307 | function add(dir, ext) {
308 | let list
309 | try { list = fs.readdirSync(dir) }
310 | catch (_) { return }
311 | for (let f of list) if (ext.includes(/^[^.]*(.*)/.exec(f)[1])) {
312 | files.push(path.relative(process.cwd(), join(dir, f)))
313 | }
314 | }
315 | for (let pkg of packages) {
316 | if (pkg.name == "legacy-modes") {
317 | add(join(pkg.dir, "mode"), [".js", ".d.ts"])
318 | } else {
319 | add(join(pkg.dir, "src"), [".ts"])
320 | add(join(pkg.dir, "test"), [".ts"])
321 | }
322 | }
323 | try {
324 | console.log(run("grep", ["--color", "-nH", "-e", pattern].concat(files), process.cwd()))
325 | } catch(e) {
326 | process.exit(1)
327 | }
328 | }
329 |
330 | function runCmd(cmd, ...args) {
331 | for (let pkg of packages) {
332 | console.log(pkg.name + ":")
333 | try {
334 | console.log(run(cmd, args, pkg.dir))
335 | } catch (e) {
336 | console.log(e.toString())
337 | process.exit(1)
338 | }
339 | }
340 | }
341 |
342 | function buildReadme(name) {
343 | if (!nonCore.includes(name)) help(1)
344 | let pkg = packageNames[name]
345 | fs.writeFileSync(join(pkg.dir, "README.md"), require("./build-readme").buildReadme(pkg))
346 | }
347 |
348 | function test(...args) {
349 | let runTests = require("@marijn/testtool")
350 | let {tests, browserTests} = runTests.gatherTests(buildPackages.map(p => p.dir))
351 | let browsers = [], grep, noBrowser = false
352 | for (let i = 0; i < args.length; i++) {
353 | if (args[i] == "--firefox") browsers.push("firefox")
354 | if (args[i] == "--chrome") browser.push("chrome")
355 | if (args[i] == "--no-browser") noBrowser = true
356 | if (args[i] == "--grep") grep = args[++i]
357 | }
358 | if (!browsers.length && !noBrowser) browsers.push("chrome")
359 | runTests.runTests({tests, browserTests, browsers, grep}).then(failed => process.exit(failed ? 1 : 0))
360 | }
361 |
362 | start()
363 |
--------------------------------------------------------------------------------