diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 99f194da..0db35d2f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -9,6 +9,8 @@ body: attributes: label: Which packages are affected? options: + # NOTE: Package names are automatically generated. Do not manually edit. + # packages-start - label: "`@eslint/compat`" required: false - label: "`@eslint/config-array`" @@ -21,6 +23,8 @@ body: required: false - label: "`@eslint/plugin-kit`" required: false + # packages-end + - type: textarea attributes: label: Environment diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml index a77de3ea..551c780d 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -8,6 +8,8 @@ body: attributes: label: Which packages would you like to change? options: + # NOTE: Package names are automatically generated. Do not manually edit. + # packages-start - label: "`@eslint/compat`" required: false - label: "`@eslint/config-array`" @@ -20,6 +22,8 @@ body: required: false - label: "`@eslint/plugin-kit`" required: false + # packages-end + - type: textarea attributes: label: What problem do you want to solve? diff --git a/README.md b/README.md index 22fa428f..b91a7ae6 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,18 @@ Monorepo for the rewrite of ESLint. This repository is the home of the following packages: + + + + - [`@eslint/core`](packages/core) - [`@eslint/compat`](packages/compat) - [`@eslint/config-array`](packages/config-array) - [`@eslint/object-schema`](packages/object-schema) - [`@eslint/migrate-config`](packages/migrate-config) + + + diff --git a/package.json b/package.json index 687926a5..1914f7df 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "npm test --workspaces --if-present", "build": "node scripts/build.js", "build:readme": "node tools/update-readme.js", + "build:new-pkg": "node tools/new-pkg.js", "lint": "eslint .", "lint:fix": "eslint --fix .", "fmt": "prettier --write .", @@ -27,6 +28,9 @@ ], "!(*.{js,ts})": "prettier --write --ignore-unknown" }, + "engines": { + "node": ">= 22.3.0" + }, "devDependencies": { "@types/mocha": "^10.0.7", "eslint": "^9.11.1", diff --git a/templates/package/CHANGELOG.md b/templates/package/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/templates/package/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/templates/package/LICENSE b/templates/package/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/templates/package/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/templates/package/README.md b/templates/package/README.md new file mode 100644 index 00000000..b625319e --- /dev/null +++ b/templates/package/README.md @@ -0,0 +1,37 @@ +# @eslint/<%= name %> + +## Description + +<%= description %> + +## Installation + +For Node.js and compatible runtimes: + +```shell +npm install @eslint/<%= name %> +# or +yarn add @eslint/<%= name %> +# or +pnpm install @eslint/<%= name %> +# or +bun install @eslint/<%= name %> +``` + +For Deno: + +```shell +deno add @eslint/<%= name %> +``` + +## Usage + +TODO + +## License + +Apache 2.0 + + + + diff --git a/templates/package/jsr.json b/templates/package/jsr.json new file mode 100644 index 00000000..3c9fa154 --- /dev/null +++ b/templates/package/jsr.json @@ -0,0 +1,16 @@ +{ + "name": "@eslint/<%= name %>", + "version": "0.0.0", + "exports": "./dist/esm/index.js", + "publish": { + "include": [ + "dist/esm/index.js", + "dist/esm/index.d.ts", + "dist/esm/types.ts", + "dist/esm/types.d.ts", + "README.md", + "jsr.json", + "LICENSE" + ] + } +} diff --git a/templates/package/package.json b/templates/package/package.json new file mode 100644 index 00000000..5d270de0 --- /dev/null +++ b/templates/package/package.json @@ -0,0 +1,58 @@ +{ + "name": "@eslint/<%= name %>", + "version": "0.0.0", + "description": "<%= description %>", + "type": "module", + "main": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "exports": { + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + }, + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + } + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "directories": { + "test": "tests" + }, + "scripts": { + "build:cts": "node ../../tools/build-cts.js dist/esm/index.d.ts dist/cjs/index.d.cts", + "build": "rollup -c && tsc -p tsconfig.esm.json && npm run build:cts", + "test:jsr": "npx jsr@latest publish --dry-run", + "test": "mocha tests/*.js", + "test:coverage": "c8 npm test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/eslint/rewrite.git" + }, + "keywords": [ + "eslint" + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/eslint/rewrite/issues" + }, + "homepage": "https://github.com/eslint/rewrite/tree/main/packages/<%= name %>#readme", + "devDependencies": { + "@eslint/core": "^0.10.0", + "c8": "^9.1.0", + "eslint": "^9.11.0", + "mocha": "^10.4.0", + "rollup": "^4.16.2", + "rollup-plugin-copy": "^3.5.0", + "typescript": "^5.4.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } +} diff --git a/templates/package/rollup.config.js b/templates/package/rollup.config.js new file mode 100644 index 00000000..5b813a88 --- /dev/null +++ b/templates/package/rollup.config.js @@ -0,0 +1,24 @@ +import copy from "rollup-plugin-copy"; + +export default { + input: "src/index.js", + output: [ + { + file: "dist/cjs/index.cjs", + format: "cjs", + }, + { + file: "dist/esm/index.js", + format: "esm", + banner: '// @ts-self-types="./index.d.ts"', + }, + ], + plugins: [ + copy({ + targets: [ + { src: "src/types.ts", dest: "dist/cjs", rename: "types.cts" }, + { src: "src/types.ts", dest: "dist/esm" }, + ], + }), + ], +}; diff --git a/templates/package/src/index.js b/templates/package/src/index.js new file mode 100644 index 00000000..0010db6c --- /dev/null +++ b/templates/package/src/index.js @@ -0,0 +1,3 @@ +/** + * @fileoverview Main entrypoint for the package. + */ diff --git a/templates/package/src/types.ts b/templates/package/src/types.ts new file mode 100644 index 00000000..9900e5f0 --- /dev/null +++ b/templates/package/src/types.ts @@ -0,0 +1,3 @@ +/** + * @fileoverview Types for this package. + */ diff --git a/templates/package/tests/index.test.js b/templates/package/tests/index.test.js new file mode 100644 index 00000000..bb5a6436 --- /dev/null +++ b/templates/package/tests/index.test.js @@ -0,0 +1,3 @@ +/** + * @fileoverview Tests for package entrypoint + */ diff --git a/templates/package/tsconfig.esm.json b/templates/package/tsconfig.esm.json new file mode 100644 index 00000000..7ce10923 --- /dev/null +++ b/templates/package/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "files": ["dist/esm/index.js"], + "compilerOptions": { + "strict": false + } +} diff --git a/templates/package/tsconfig.json b/templates/package/tsconfig.json new file mode 100644 index 00000000..779639cf --- /dev/null +++ b/templates/package/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "files": ["src/index.js"], + "compilerOptions": { + "outDir": "dist/esm", + "strict": true + } +} diff --git a/tools/new-pkg.js b/tools/new-pkg.js new file mode 100644 index 00000000..24f993e1 --- /dev/null +++ b/tools/new-pkg.js @@ -0,0 +1,225 @@ +/** + * @fileoverview Script to bootstrap a new package in the monorepo. + * + * node tools/new-pkg.js --name --desc + * + * @author Nicholas C. Zakas + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { readFileSync, readdirSync, writeFileSync, cpSync } from "node:fs"; +import { parseArgs } from "node:util"; + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +/** + * Recursively gets all files in a directory. + * @param {string} dir The directory to search. + * @param {string[]} fileList The list of files found so far. + */ +function getAllFiles(dir) { + const fileList = []; + const files = readdirSync(dir, { withFileTypes: true }); + + files.forEach(file => { + const filePath = `${dir}/${file.name}`; + if (file.isDirectory()) { + fileList.push(...getAllFiles(filePath)); + } else { + fileList.push(filePath); + } + }); + + return fileList; +} + +//----------------------------------------------------------------------------- +// Data +//----------------------------------------------------------------------------- + +const packageNames = readdirSync("./packages"); + +//----------------------------------------------------------------------------- +// Parse CLI +//----------------------------------------------------------------------------- + +const options = { + name: { + type: "string", + }, + desc: { + type: "string", + }, +}; + +const { values } = parseArgs({ args: process.argv.slice(2), options }); + +if (!values.name) { + throw new Error("--name is required."); +} + +if (packageNames.includes(values.name)) { + throw new Error(`Package ${values.name} already exists.`); +} + +if (!values.desc) { + throw new Error("--desc is required."); +} + +packageNames.push(values.name); +packageNames.sort(); + +//----------------------------------------------------------------------------- +// Creating the new directory +//----------------------------------------------------------------------------- + +console.log("Creating new directory..."); + +const templateDirectory = "./templates/package"; +const newDirectory = `./packages/${values.name}`; + +cpSync(templateDirectory, newDirectory, { recursive: true }); + +const allFiles = getAllFiles(newDirectory); + +allFiles.forEach(filePath => { + const content = readFileSync(filePath, "utf8"); + const newContent = content + .replace(/<%=\s*name\s*%>/gu, values.name) + .replace(/<%=\s*description\s*%>/gu, values.desc); + + writeFileSync(filePath, newContent, "utf8"); +}); + +console.log("✅ Created", newDirectory); + +//----------------------------------------------------------------------------- +// Update issue templates +//----------------------------------------------------------------------------- + +console.log("\nUpdating issue templates..."); + +const issueTemplateFiles = readdirSync("./.github/ISSUE_TEMPLATE"); + +issueTemplateFiles.forEach(file => { + const filePath = `./.github/ISSUE_TEMPLATE/${file}`; + const content = readFileSync(filePath, "utf8"); + + if (!content.includes("# packages-start")) { + return; + } + + const lines = content.split(/\r?\n/gu); + + const startIndex = lines.findIndex(line => + line.includes("# packages-start"), + ); + const endIndex = lines.findIndex(line => line.includes("# packages-end")); + const newLines = packageNames.map( + packageName => + `${" ".repeat(14)}- label: "\`@eslint/${packageName}\`"\n${" ".repeat(16)}required: false`, + ); + + lines.splice(startIndex + 1, endIndex - startIndex - 1, ...newLines); + + writeFileSync(filePath, lines.join("\n"), "utf8"); + + console.log("✅ Updated", filePath); +}); + +//----------------------------------------------------------------------------- +// Update README +//----------------------------------------------------------------------------- + +console.log("\nUpdating README..."); + +const readmePath = "./README.md"; +const readmeContent = readFileSync(readmePath, "utf8"); +const readmeLines = readmeContent.split(/\r?\n/gu); +const readmeStartIndex = readmeLines.findIndex(line => + line.includes(""), +); +const readmeEndIndex = readmeLines.findIndex(line => + line.includes(""), +); +const newReadmeLines = packageNames.map( + packageName => `- [\`@eslint/${packageName}\`](./packages/${packageName})`, +); + +readmeLines.splice( + readmeStartIndex + 1, + readmeEndIndex - readmeStartIndex - 1, + ...newReadmeLines, +); + +writeFileSync(readmePath, readmeLines.join("\n"), "utf8"); + +console.log("✅ Updated", readmePath); + +//----------------------------------------------------------------------------- +// Update release-please-manifest.json +//----------------------------------------------------------------------------- + +console.log("\nUpdating release-please-manifest.json..."); + +const manifestPath = "./.release-please-manifest.json"; +const manifestContent = readFileSync(manifestPath, "utf8"); +let manifest = JSON.parse(manifestContent); + +manifest[`packages/${values.name}`] = "0.0.0"; + +// sort manifest keys +manifest = Object.fromEntries( + Object.entries(manifest).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)), +); + +writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8"); + +console.log("✅ Updated", manifestPath); + +//----------------------------------------------------------------------------- +// Update release-please-config.json +//----------------------------------------------------------------------------- + +console.log("\nUpdating release-please-config.json..."); + +const configPath = "./release-please-config.json"; +const configContent = readFileSync(configPath, "utf8"); +const config = JSON.parse(configContent); + +config.packages[`packages/${values.name}`] = { + "release-type": "node", + "extra-files": [ + { + type: "json", + path: "jsr.json", + jsonpath: "$.version", + }, + ], +}; + +// sort the config.packages keys +config.packages = Object.fromEntries( + Object.entries(config.packages).sort(([keyA], [keyB]) => + keyA.localeCompare(keyB), + ), +); + +writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8"); + +console.log("✅ Updated", configPath); + +//----------------------------------------------------------------------------- +// Final notice +//----------------------------------------------------------------------------- + +console.log("\nIMPORTANT!!!!!"); +console.log( + "This script does NOT update the release-please.yml workflow file.", +); +console.log("You must do that manually.");