From f1640be9e1fe55a2258eea3b3aaa9cee3b7671c9 Mon Sep 17 00:00:00 2001 From: Saeed M Rad Date: Sat, 7 May 2022 18:13:28 +0430 Subject: [PATCH] Use corepack in ci (#210) * Remove unnecessary pnpm version spec * Remove only-allow * Update CI scripts * Add a tsconfig to the ci scripts file * Fix grammar in utils.cjs * Remove full stops at the end of messages * Fix accidentally using utils instead of console * Wrap some stuff in smart quotes * Handle missing Stream.hasColors * Use pnpm exec instead of pnpx * Update CI to use corepack * Use npx to run renovate-config-validator * Rename yml files to yaml * Use normal sized arrows --- .../{bug_report.yml => bug_report.yaml} | 0 ...ature_request.yml => feature_request.yaml} | 0 .../{_config.yml => _config.yaml} | 0 .github/scripts/docs/build.fish | 4 +- .github/scripts/docs/parse-exports.cjs | 208 ++++++++---------- .github/scripts/docs/update-config.cjs | 25 ++- .github/scripts/tsconfig.json | 14 ++ .github/scripts/utils.cjs | 39 ++++ .github/workflows/{ci.yml => ci.yaml} | 16 +- .github/workflows/{docs.yml => docs.yaml} | 11 +- .../{lint-fish.yml => lint-fish.yaml} | 0 .../workflows/{renovate.yml => renovate.yaml} | 4 +- package.json | 4 +- renovate.json | 3 - 14 files changed, 180 insertions(+), 148 deletions(-) rename .github/ISSUE_TEMPLATE/{bug_report.yml => bug_report.yaml} (100%) rename .github/ISSUE_TEMPLATE/{feature_request.yml => feature_request.yaml} (100%) rename .github/pages-template/{_config.yml => _config.yaml} (100%) create mode 100644 .github/scripts/tsconfig.json create mode 100644 .github/scripts/utils.cjs rename .github/workflows/{ci.yml => ci.yaml} (84%) rename .github/workflows/{docs.yml => docs.yaml} (95%) rename .github/workflows/{lint-fish.yml => lint-fish.yaml} (100%) rename .github/workflows/{renovate.yml => renovate.yaml} (76%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yaml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.yml rename to .github/ISSUE_TEMPLATE/bug_report.yaml diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yaml similarity index 100% rename from .github/ISSUE_TEMPLATE/feature_request.yml rename to .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/pages-template/_config.yml b/.github/pages-template/_config.yaml similarity index 100% rename from .github/pages-template/_config.yml rename to .github/pages-template/_config.yaml diff --git a/.github/scripts/docs/build.fish b/.github/scripts/docs/build.fish index ec198a5..ca17d8c 100644 --- a/.github/scripts/docs/build.fish +++ b/.github/scripts/docs/build.fish @@ -24,8 +24,8 @@ for line in (assert node (status dirname)/parse-exports.cjs $dts_files) echo Map $map[1] to $map[2]. assert node (status dirname)/update-config.cjs $map[2] - assert pnpx api-extractor run --local --verbose - assert pnpx api-documenter markdown -i build/api -o build/docs_temp + assert pnpm exec api-extractor run --local --verbose + assert pnpm exec api-documenter markdown -i build/api -o build/docs_temp for file in (find build/docs_temp -type f -name "$placeholder.*") set -l destination (string replace -af $placeholder \ diff --git a/.github/scripts/docs/parse-exports.cjs b/.github/scripts/docs/parse-exports.cjs index fff23ca..9e6ad8b 100644 --- a/.github/scripts/docs/parse-exports.cjs +++ b/.github/scripts/docs/parse-exports.cjs @@ -1,60 +1,106 @@ #!/usr/bin/env node -const { readFile, writeFile } = require('node:fs/promises'); -const process = require('process'); -const hasColors = process.stderr?.hasColors?.() ?? false; -const isInGitHub = Boolean(process.env.GITHUB_ACTIONS); -const logColor = hasColors ? '\x1B[94m' : ''; -const warnColor = isInGitHub ? '::warning::' : hasColors ? '\x1B[93m' : ''; -const errorColor = isInGitHub ? '::error::' : hasColors ? '\x1B[91m' : ''; -const resetColor = hasColors ? '\x1B[m' : ''; - -main(process.argv.slice(2)).catch((error) => { - console.error(error); - process.exit(1); -}); +const fs = require('node:fs'); +const process = require('node:process'); +const utils = require('../utils.cjs'); -function log (...args) { - console.warn(logColor + 'ℹ', ...args, resetColor); -} +const argv = process.argv.slice(2); -function warn (...args) { - console.warn(warnColor + '⚠', ...args, resetColor); -} +try { + const json = JSON.parse(fs.readFileSync('package.json', 'utf8')); -function error (...args) { - console.error(errorColor + '✖', ...args, resetColor); -} + if (!utils.isObject(json)) { + throw new TypeError('package.json is not an object or is null'); + } + + if (typeof json.name !== 'string') { + throw new Error('package.json has no name, or the name is not a string'); + } + + const name = json.name; + const exportsObject = getExportsObject(json.exports) ?? getTypesObject(json.types); -/** @return {value is Record} */ -function isObject (value) { - return typeof value === 'object' && value !== null; + if (exportsObject === undefined) { + throw new Error('No exported types found'); + } + + /** @type {Map} */ + const matches = new Map(argv.map((path) => [path, []])); + + for (let [key, value] of Object.entries(exportsObject)) { + utils.log('New subpath:', key, '→', value); + let isMapped = key.includes('/*'); + + if (isMapped && !value.includes('/*')) { + utils.warn('Pattern is mapped but its value is not'); + key = key.replace('/*', '/index'); + isMapped = false; + } + + const keyBeginning = isMapped ? key.slice(0, key.indexOf('/*')) : key; + const keyEnding = isMapped ? key.slice(key.indexOf('/*') + 2) : ''; + const beginning = isMapped ? value.slice(0, value.indexOf('/*')) : value; + const ending = isMapped ? value.slice(value.indexOf('/*') + 2) : ''; + + utils.log(' Key parts:', keyBeginning, '→', keyEnding); + utils.log(' Value parts:', beginning, '→', ending); + + for (const [path, matched] of matches) { + if (path.startsWith(beginning) && path.endsWith(ending)) { + utils.log(' Path matches:', path); + matched.push(name + + keyBeginning.slice(1) // This removes the dot + + path.slice(beginning.length, path.indexOf(ending)) + + keyEnding); + } + } + } + + for (const [path, matched] of matches) { + if (matched.length === 0) { + utils.log('Path did not match:', path); + } else { + matched.sort((a, b) => a.length - b.length); + console.log(`${matched[0]}\t${path}`); + } + } + + // This overwrites the `package.json`. If you are running this script + // locally, make sure to `git reset` it afterwards. + json.name = 'placeholder_package_name'; + fs.writeFileSync('package.json', JSON.stringify(json), 'utf8'); +} catch (error) { + utils.error(error); + process.exit(1); } /** @param {string} path */ function toRelative (path) { if (path.startsWith('/') || path.startsWith('../')) { - error('Path must start with neither / nor ../:', path); + utils.error('Path must start with neither / nor ../:', path); return; } return path.startsWith('./') ? path : `./${path}`; } +/** + * @param {unknown} exports + */ function getExportsObject (exports) { - if (!isObject(exports)) { - error('"exports" field does not exist or is not an object.'); + if (!utils.isObject(exports)) { + utils.error('"exports" field does not exist or is not an object'); return; } - log('Package has conditional exports.'); + utils.log('Package has conditional exports'); if (exports.docs === null) { - warn('Package is exempted from documentation.'); + utils.warn('Package is exempted from documentation'); return {}; } if (typeof exports.types === 'string') { - log('Package does not use subpaths.'); + utils.log('Package does not use subpaths'); const relative = toRelative(exports.types); @@ -62,7 +108,7 @@ function getExportsObject (exports) { return; } - log(' ⟶ ', relative); + utils.log(' →', relative); return { '.': relative, @@ -72,136 +118,72 @@ function getExportsObject (exports) { const entries = Object.entries(exports); if (entries.length === 0) { - error('The "exports" field is empty.'); + utils.error('The "exports" field is empty'); return; } if (entries.some(([key]) => key !== '.' && !key.startsWith('./'))) { - error('Package does not have any subpaths and the "types" field is missing.'); + utils.error('Package does not have any subpaths and the "types" field is missing'); return; } /** @type {Record} */ - let exportsObject = {}; + const exportsObject = {}; for (const [key, value] of entries) { - if (!isObject(value)) { - warn('Subpath is not an object:', key); + if (!utils.isObject(value)) { + utils.warn('Subpath is not an object:', key); continue; } if (value.docs === null) { - log('Subpath is exempted from documentation:', key); + utils.log('Subpath is exempted from documentation:', key); continue; } if (typeof value.types !== 'string') { - warn('Subpath does not have a "types" field:', key); + utils.warn('Subpath does not have a "types" field:', key); continue; } - log('Subpath found:', key); + utils.log('Subpath found:', key); const relative = toRelative(value.types); if (relative !== undefined) { - log(' ⟶ ', relative); + utils.log(' →', relative); exportsObject[key] = relative; } } if (Object.keys(exportsObject).length === 0) { - warn('No valid subpath found for documentation.'); + utils.warn('No valid subpath found for documentation'); } return exportsObject; } +/** + * @param {unknown} types + */ function getTypesObject (types) { if (typeof types !== 'string') { - error('"types" field does not exist or is not a string.'); + utils.error('"types" field does not exist or is not a string'); return; } - log('Global "types" field found.'); + utils.log('Global "types" field found'); - let relative = toRelative(types); + const relative = toRelative(types); if (relative === undefined) { return; } - log(' ⟶ ', relative); + utils.log(' →', relative); return { '.': relative, }; } - -/** @param {string[]} argv */ -async function main (argv) { - const json = JSON.parse(await readFile('package.json', 'utf8')); - - if (!isObject(json)) { - error('package.json is not an object or is null.'); - return; - } - - if (typeof json.name !== 'string') { - error('package.json has no name, or the name is not a string.'); - return; - } - - const name = json.name; - const exportsObject = getExportsObject(json.exports) ?? getTypesObject(json.types); - - if (exportsObject === undefined) { - error('No exported types found.'); - return; - } - - /** @type {Map} */ - const matches = new Map(argv.map((path) => [path, []])); - - for (let [key, value] of Object.entries(exportsObject)) { - log('New subpath:', key, '→', value); - let isMapped = key.includes('/*'); - - if (isMapped && !value.includes('/*')) { - warn('Pattern is mapped but its value is not.'); - key = key.replace('/*', '/index'); - isMapped = false; - } - - let keyBegining = isMapped ? key.slice(0, key.indexOf('/*')) : key; - let keyEnding = isMapped ? key.slice(key.indexOf('/*') + 2) : ''; - let begining = isMapped ? value.slice(0, value.indexOf('/*')) : value; - let ending = isMapped ? value.slice(value.indexOf('/*') + 2) : ''; - - log(' Key parts:', keyBegining, '→', keyEnding); - log(' Value parts:', begining, '→', ending); - - for (const [path, matched] of matches) { - if (path.startsWith(begining) && path.endsWith(ending)) { - log(' Path matches:', path); - matched.push(name - + keyBegining.slice(1) // This removes the dot - + path.slice(begining.length, path.indexOf(ending)) - + keyEnding); - } - } - } - - for (const [path, matched] of matches) { - if (matched.length === 0) { - log('Path did not match:', path); - } else { - matched.sort((a, b) => a.length - b.length); - console.log(`${matched[0]}\t${path}`); - } - } - - json.name = 'placeholder_package_name'; - await writeFile('package.json', JSON.stringify(json), 'utf8'); -} diff --git a/.github/scripts/docs/update-config.cjs b/.github/scripts/docs/update-config.cjs index 21a1c8e..63c8a72 100644 --- a/.github/scripts/docs/update-config.cjs +++ b/.github/scripts/docs/update-config.cjs @@ -1,19 +1,24 @@ #!/usr/bin/env node -const { readFile, writeFile } = require('node:fs/promises'); -const process = require('process'); +const fs = require('node:fs'); +const process = require('node:process'); +const utils = require('../utils.cjs'); -main(process.argv[2]).catch((error) => { - console.error(error); - process.exit(1); -}); +try { + const entrypoint = process.argv[2]; + + if (!entrypoint) { + throw new Error('No entrypoint was specified'); + } -/** @param {string} entrypoint */ -async function main (entrypoint) { - const json = JSON.parse(await readFile('api-extractor.json', 'utf8')); + const json = JSON.parse(fs.readFileSync('api-extractor.json', 'utf8')); if (json) { json.mainEntryPointFilePath = '/' + entrypoint; } - await writeFile('api-extractor.json', JSON.stringify(json), 'utf8'); + fs.writeFileSync('api-extractor.json', JSON.stringify(json), 'utf8'); + utils.log('Updated api-extractor config'); +} catch (error) { + utils.error(error); + process.exit(1); } diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json new file mode 100644 index 0000000..ad2a5f2 --- /dev/null +++ b/.github/scripts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "noEmit": true, + "checkJs": true + }, + "include": [ + "**/*.js", + "**/*.cjs" + ], + "references": [] +} diff --git a/.github/scripts/utils.cjs b/.github/scripts/utils.cjs new file mode 100644 index 0000000..93f9a19 --- /dev/null +++ b/.github/scripts/utils.cjs @@ -0,0 +1,39 @@ +const process = require('node:process'); + +const isInGitHub = Boolean(process.env.GITHUB_ACTIONS); +const hasColors = process.stderr?.hasColors?.() ?? false; +const logSymbol = hasColors ? '\x1B[94mℹ\x1B[m' : 'ℹ'; +const warnSymbol = isInGitHub ? '::warning::' : hasColors ? '\x1B[93m⚠\x1B[m' : '⚠'; +const errorSymbol = isInGitHub ? '::error::' : hasColors ? '\x1B[91m✖\x1B[m' : '✖'; + +/** @param {unknown[]} args */ +exports.log = (...args) => { + // This uses `console.warn` because it should output to the stderr. + console.warn(logSymbol, ...args); +}; + +/** @param {unknown[]} args */ +exports.warn = (...args) => { + console.warn(warnSymbol, ...args); +}; + +/** @param {unknown[]} args */ +exports.error = (...args) => { + console.error(errorSymbol, ...args); +}; + +/** + * @param {string} name + * @param {string} value + */ +exports.setOutput = (name, value) => { + console.log(`::set-output name=${name}::${value}`); +}; + +/** + * @param {unknown} value + * @return {value is Record} + */ +exports.isObject = (value) => { + return typeof value === 'object' && value !== null; +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yaml similarity index 84% rename from .github/workflows/ci.yml rename to .github/workflows/ci.yaml index 434c776..1285bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yaml @@ -12,13 +12,13 @@ on: - master - releases/** -env: - JOB_NODE_VERSION: 16.x - JOB_PNPM_VERSION: 6 - jobs: lint: name: CI + strategy: + matrix: + node: + - '16' runs-on: ubuntu-latest steps: @@ -28,12 +28,12 @@ jobs: - name: Setup Node.js uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # renovate: tag=v2 with: - node-version: ${{ env.JOB_NODE_VERSION }} + node-version: ${{ matrix.node }} - name: Setup pnpm - uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2 # renovate: tag=v2.0.1 - with: - version: ${{ env.JOB_PNPM_VERSION }} + run: |- + corepack enable + pnpm --version - name: Set pnpm store-dir run: pnpm config set store-dir ~/.pnpm-store diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yaml similarity index 95% rename from .github/workflows/docs.yml rename to .github/workflows/docs.yaml index a62e7db..950504e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yaml @@ -11,9 +11,8 @@ env: GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com JOB_AUTHOR_NAME: ${{ github.event.head_commit.author.name }} JOB_AUTHOR_EMAIL: ${{ github.event.head_commit.author.email }} - JOB_NODE_VERSION: 16.x - JOB_PNPM_VERSION: 6 - JOB_RUBY_VERSION: 2.7 + JOB_NODE_VERSION: '16' + JOB_RUBY_VERSION: '2.7' JOB_COMMIT_ORIGINAL_ID: ${{ github.event.head_commit.id }} JOB_COMMIT_ORIGINAL_URL: ${{ github.event.head_commit.url }} JOB_COMMIT_MESSAGE: |- @@ -42,9 +41,9 @@ jobs: node-version: ${{ env.JOB_NODE_VERSION }} - name: Setup pnpm - uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2 # renovate: tag=v2.0.1 - with: - version: ${{ env.JOB_PNPM_VERSION }} + run: |- + corepack enable + pnpm --version - name: Setup pnpm store-dir run: pnpm config set store-dir ~/.pnpm-store/current/ diff --git a/.github/workflows/lint-fish.yml b/.github/workflows/lint-fish.yaml similarity index 100% rename from .github/workflows/lint-fish.yml rename to .github/workflows/lint-fish.yaml diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yaml similarity index 76% rename from .github/workflows/renovate.yml rename to .github/workflows/renovate.yaml index 021ea7e..4ef9b34 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yaml @@ -25,6 +25,4 @@ jobs: uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # renovate: tag=v2 - name: Validate renovate config - uses: rinchsan/renovate-config-validator@b87b6441b539cd7bcf6e684fb6cac65b81e84085 # renovate: tag=v0.0.10 - with: - pattern: renovate.json + run: npx -y --package renovate -- renovate-config-validator diff --git a/package.json b/package.json index b039a1f..19eaecb 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "changeset": "changeset", "changeset:version": "changeset version", "clean": "rimraf build .nyc_output coverage", - "preinstall": "npx -y only-allow pnpm", "lint": "xo", "lint:fix": "xo --fix", "prepack": "node scripts/prepack.js", @@ -58,8 +57,7 @@ "xo": "0.48.0" }, "engines": { - "node": ">=16", - "pnpm": "6" + "node": ">=16" }, "imports": { "#src/*": "./build/*", diff --git a/renovate.json b/renovate.json index 168d1be..1642c22 100644 --- a/renovate.json +++ b/renovate.json @@ -5,9 +5,6 @@ "helpers:pinGitHubActionDigests", ":ignoreUnstable" ], - "constraints": { - "pnpm": "6" - }, "labels": [ "renovate" ],