├── 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 | [![Build Status](https://github.com/codemirror/dev/workflows/main/badge.svg)](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

mode/${name}

\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 | --------------------------------------------------------------------------------