├── docs ├── .nojekyll ├── 404.html └── index.html ├── .yarn └── plugins │ └── .keep ├── packages ├── dev │ ├── .skip-deno │ ├── src │ │ ├── cjs │ │ │ ├── package.json │ │ │ └── sample.js │ │ ├── rootRust │ │ │ ├── lib.rs │ │ │ └── mod.rs │ │ ├── rootJs │ │ │ ├── testJson.json │ │ │ ├── dynamic.mjs │ │ │ ├── test.json.d.ts │ │ │ ├── augmented.ts │ │ │ ├── JsxChild.tsx │ │ │ ├── Jsx.spec.tsx │ │ │ ├── Clazz.ts │ │ │ ├── Jsx.tsx │ │ │ └── index.ts │ │ ├── detectOther.ts │ │ ├── types.ts │ │ ├── root.ts │ │ ├── packageInfo.ts │ │ ├── rootSrc.spec.ts │ │ ├── index.ts │ │ ├── mod.ts │ │ ├── rootCjs.spec.ts │ │ ├── rootStatic │ │ │ └── kusama.svg │ │ ├── rootTests.ts │ │ └── rootEsm.spec.ts │ ├── scripts │ │ ├── polkadot-exec-rollup.mjs │ │ ├── polkadot-exec-tsc.mjs │ │ ├── polkadot-exec-eslint.mjs │ │ ├── polkadot-exec-webpack.mjs │ │ ├── polkadot-exec-ghrelease.mjs │ │ ├── polkadot-dev-run-node-ts.mjs │ │ ├── polkadot-dev-yarn-only.mjs │ │ ├── polkadot-exec-ghpages.mjs │ │ ├── polkadot-ci-ghact-docs.mjs │ │ ├── polkadot-dev-build-docs.mjs │ │ ├── polkadot-dev-circular.mjs │ │ ├── polkadot-ci-ghpages-force.mjs │ │ ├── polkadot-dev-copy-dir.mjs │ │ ├── polkadot-dev-run-lint.mjs │ │ ├── polkadot-dev-deno-map.mjs │ │ ├── polkadot-dev-clean-build.mjs │ │ ├── polkadot-dev-copy-to.mjs │ │ ├── polkadot-dev-contrib.mjs │ │ ├── polkadot-dev-version.mjs │ │ ├── polkadot-dev-run-test.mjs │ │ ├── polkadot-exec-node-test.mjs │ │ └── polkadot-ci-ghact-build.mjs │ ├── tsconfig.config.json │ ├── tsconfig.scripts.json │ ├── tsconfig.build.json │ ├── tsconfig.spec.json │ ├── config │ │ ├── typedoc.cjs │ │ ├── prettier.cjs │ │ ├── tsconfig.json │ │ ├── rollup.js │ │ ├── eslint.js │ │ └── eslint.rules.js │ └── package.json ├── dev-test │ ├── .skip-deno │ ├── src │ │ ├── globals.cjs │ │ ├── browser.ts │ │ ├── node.ts │ │ ├── index.ts │ │ ├── packageInfo.ts │ │ ├── env │ │ │ ├── lifecycle.ts │ │ │ ├── index.ts │ │ │ ├── browser.spec.ts │ │ │ ├── suite.spec.ts │ │ │ ├── suite.ts │ │ │ ├── jest.ts │ │ │ ├── browser.ts │ │ │ ├── jest.spec.ts │ │ │ ├── expect.spec.ts │ │ │ └── expect.ts │ │ ├── types.ts │ │ ├── globals.d.ts │ │ ├── util.ts │ │ └── util.spec.ts │ ├── tsconfig.build.json │ ├── tsconfig.spec.json │ ├── package.json │ └── README.md └── dev-ts │ ├── .skip-deno │ ├── src │ ├── cached.ts │ ├── packageInfo.ts │ ├── types.ts │ ├── testCached.ts │ ├── index.ts │ ├── common.ts │ ├── loader.ts │ ├── testLoader.ts │ ├── resolver.spec.ts │ ├── tsconfig.ts │ └── resolver.ts │ ├── tsconfig.build.json │ ├── tsconfig.spec.json │ ├── package.json │ └── README.md ├── .npmignore ├── .prettierignore ├── .vscode └── settings.json ├── .prettierrc.cjs ├── .editorconfig ├── eslint.config.js ├── .mailmap ├── .yarnrc.yml ├── tsconfig.eslint.json ├── .gitignore ├── tsconfig.base.json ├── tsconfig.json ├── .github └── workflows │ ├── auto-merge.yml │ ├── pr-any.yml │ ├── auto-approve.yml │ ├── push-master.yml │ └── lock.yml ├── README.md ├── tsconfig.build.json ├── scripts ├── all-bump-berry.sh ├── all-update.sh └── all-deps.js ├── CONTRIBUTORS ├── package.json ├── CONTRIBUTING.md ├── CHANGELOG.md └── LICENSE /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarn/plugins/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/dev/.skip-deno: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/dev-test/.skip-deno: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/dev-ts/.skip-deno: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | docs 4 | src 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | packages 4 | -------------------------------------------------------------------------------- /packages/dev/src/cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "module": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/dev/src/rootRust/lib.rs: -------------------------------------------------------------------------------- 1 | /// this should not appear in the final output 2 | -------------------------------------------------------------------------------- /packages/dev/src/rootRust/mod.rs: -------------------------------------------------------------------------------- 1 | /// this should not appear in the final output 2 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/testJson.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "json": "works" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "eslint.experimental.useFlatConfig": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/dev/src/detectOther.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export default []; 5 | -------------------------------------------------------------------------------- /packages/dev-test/src/globals.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = {}; 5 | -------------------------------------------------------------------------------- /packages/dev/src/cjs/sample.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = { foo: 'bar' }; 5 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = require('@polkadot/dev/config/prettier.cjs'); 5 | -------------------------------------------------------------------------------- /packages/dev/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export type EchoString = string; 5 | 6 | export type BlahType = number; 7 | -------------------------------------------------------------------------------- /packages/dev-test/src/browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { exposeEnv } from './env/index.js'; 5 | 6 | exposeEnv(true); 7 | -------------------------------------------------------------------------------- /packages/dev-test/src/node.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { exposeEnv } from './env/index.js'; 5 | 6 | exposeEnv(false); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=space 4 | indent_size=2 5 | tab_width=2 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | -------------------------------------------------------------------------------- /packages/dev/src/root.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './rootJs/index.js'; 5 | 6 | export const TEST_PURE = /*#__PURE__*/ 'testRoot'; 7 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import baseConfig from '@polkadot/dev/config/eslint'; 5 | 6 | export default [ 7 | ...baseConfig 8 | ]; 9 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jaco 2 | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3 | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Github Actions 4 | -------------------------------------------------------------------------------- /packages/dev-test/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | throw new Error('Use node --require @polkadot/dev-test/{node, browser} depending on the required environment'); 5 | -------------------------------------------------------------------------------- /packages/dev-ts/src/cached.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { loaderOptions } from './common.js'; 5 | 6 | loaderOptions.isCached = true; 7 | 8 | export * from './index.js'; 9 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-rollup.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { execViaNode } from './util.mjs'; 6 | 7 | execViaNode('rollup', 'rollup/dist/bin/rollup'); 8 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-tsc.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { importDirect } from './util.mjs'; 6 | 7 | await importDirect('tsc', 'typescript/lib/tsc.js'); 8 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-eslint.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { importRelative } from './util.mjs'; 6 | 7 | await importRelative('eslint', 'eslint/bin/eslint.js'); 8 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-webpack.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { importDirect } from './util.mjs'; 6 | 7 | await importDirect('webpack', 'webpack-cli/bin/cli.js'); 8 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-ghrelease.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { importRelative } from './util.mjs'; 6 | 7 | await importRelative('gh-release', 'gh-release/bin/cli.js'); 8 | -------------------------------------------------------------------------------- /packages/dev/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/dev', path: 'auto', type: 'auto', version: '0.84.2' }; 7 | -------------------------------------------------------------------------------- /packages/dev/src/rootSrc.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import * as testRoot from './root.js'; 5 | import { runTests } from './rootTests.js'; 6 | 7 | /** This is run against the sources */ 8 | runTests(testRoot); 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | enableImmutableInstalls: false 6 | 7 | enableProgressBars: false 8 | 9 | logFilters: 10 | - code: YN0013 11 | level: discard 12 | 13 | nodeLinker: node-modules 14 | 15 | yarnPath: .yarn/releases/yarn-4.6.0.cjs 16 | -------------------------------------------------------------------------------- /packages/dev-ts/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/dev-ts', path: 'auto', type: 'auto', version: '0.84.2' }; 7 | -------------------------------------------------------------------------------- /packages/dev-test/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/dev-test', path: 'auto', type: 'auto', version: '0.84.2' }; 7 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-run-node-ts.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { execNodeTs, logBin } from './util.mjs'; 6 | 7 | logBin('polkadot-run-node-ts'); 8 | 9 | execNodeTs(process.argv.slice(2).join(' ')); 10 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-yarn-only.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import process from 'node:process'; 6 | 7 | import { exitFatalYarn } from './util.mjs'; 8 | 9 | exitFatalYarn(); 10 | 11 | process.exit(0); 12 | -------------------------------------------------------------------------------- /packages/dev-ts/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export interface TsAlias { 5 | filter: string[]; 6 | isWildcard?: boolean; 7 | path: string; 8 | url: URL; 9 | } 10 | 11 | export interface LoaderOptions { 12 | isCached?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/dynamic.mjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Returns the sum of 2 numbers 6 | * 7 | * @param {number} a 8 | * @param {number} b 9 | * @returns {number} 10 | */ 11 | export function sum (a, b) { 12 | return a + b; 13 | } 14 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/test.json.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** This file should not be in the compiled output */ 5 | 6 | declare module './testJson.json' { 7 | declare let contents: { test: { json: 'works' } }; 8 | export default contents; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dev/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./config", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "config/**/*" 12 | ], 13 | "references": [] 14 | } 15 | -------------------------------------------------------------------------------- /packages/dev/tsconfig.scripts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./scripts", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "scripts/**/*" 12 | ], 13 | "references": [] 14 | } 15 | -------------------------------------------------------------------------------- /packages/dev-ts/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/mod.ts", 10 | "src/**/*.spec.ts" 11 | ], 12 | "include": [ 13 | "src/**/*" 14 | ], 15 | "references": [] 16 | } 17 | -------------------------------------------------------------------------------- /packages/dev-test/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/mod.ts", 10 | "src/**/*.spec.ts" 11 | ], 12 | "include": [ 13 | "src/**/*" 14 | ], 15 | "references": [] 16 | } 17 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/augmented.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit 2 | /* eslint-disable */ 3 | 4 | /** This tests augmentation outputs, e.g. as used in the polkadot-js/api */ 5 | 6 | export interface Something { 7 | bar: string; 8 | foo: string; 9 | } 10 | 11 | declare module '@polkadot/dev/types' { 12 | const blah: string; 13 | } 14 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-ghpages.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { importRelative } from './util.mjs'; 6 | 7 | const ghp = await importRelative('gh-pages', 'gh-pages/bin/gh-pages.js'); 8 | 9 | await ghp.default(process.argv); 10 | 11 | console.log('Published'); 12 | -------------------------------------------------------------------------------- /packages/dev/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/mod.ts", 10 | "**/*.spec.ts", 11 | "**/*.spec.tsx" 12 | ], 13 | "include": [ 14 | "src/**/*", 15 | "src/**/*.json" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages" 5 | }, 6 | "include": [ 7 | "packages/**/src/**/*", 8 | "packages/**/config/**/*", 9 | "packages/**/scripts/**/*", 10 | "scripts/*", 11 | ".prettierrc.cjs", 12 | "eslint.config.js" 13 | ], 14 | "exclude": [ 15 | "**/node_modules/**/*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env.local 3 | .env.development.local 4 | .env.test.local 5 | .env.production.local 6 | .npmrc 7 | .pnp.* 8 | .yarn/* 9 | !.yarn/releases 10 | !.yarn/plugins 11 | !.yarn/sdks 12 | /mod.ts 13 | /import_map.json 14 | build/ 15 | build-*/ 16 | cc-test-reporter 17 | coverage/ 18 | node_modules/ 19 | npm-debug.log* 20 | tmp/ 21 | tsconfig.*buildinfo 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/dev-test/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts" 12 | ], 13 | "references": [ 14 | { "path": "../dev-test/tsconfig.build.json" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@polkadot/dev/config/tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "paths": { 6 | "@polkadot/dev/types": ["dev/src/types.ts"], 7 | "@polkadot/dev/*": ["dev/src/*"], 8 | "@polkadot/dev-test/*": ["dev-test/src/*"], 9 | "@polkadot/dev-ts/*": ["dev-ts/src/*.ts"] 10 | }, 11 | "resolveJsonModule": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/dev/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export {}; 5 | 6 | throw new Error('@polkadot/dev is not meant to be imported via root. Rather if provides a set of shared dependencies, a collection of scripts, base configs and some loaders accessed via the scripts. It is only meant to be used as a shared resource by all @polkadot/* projects'); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages", 5 | "composite": false, 6 | "emitDeclarationOnly": false, 7 | "noEmit": true 8 | }, 9 | "include": [ 10 | "packages/**/src/**/*", 11 | "packages/**/config/**/*", 12 | "packages/**/scripts/**/*" 13 | ], 14 | "exclude": [ 15 | "**/build/**/*", 16 | "**/node_modules/**/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: bot 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | merge: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: jacogr/action-merge@d2d64b4545acd93b0a9575177d3d215ae3f92029 12 | with: 13 | checks: pr (build),pr (docs),pr (lint),pr (test) 14 | labels: -auto 15 | strategy: squash 16 | token: ${{ secrets.GH_PAT_BOT }} 17 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirecting to https://polkadot.js.org/docs/ 8 | 9 | 10 | Redirecting to https://polkadot.js.org/docs/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirecting to https://polkadot.js.org/docs/ 8 | 9 | 10 | Redirecting to https://polkadot.js.org/docs/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/dev-ts/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts" 12 | ], 13 | "references": [ 14 | { "path": "../dev-ts/tsconfig.build.json" }, 15 | { "path": "../dev-test/tsconfig.build.json" } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/dev/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.spec.tsx" 13 | ], 14 | "references": [ 15 | { "path": "../dev/tsconfig.build.json" }, 16 | { "path": "../dev-test/tsconfig.build.json" } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/dev-ts/src/testCached.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Adapted from: https://nodejs.org/api/esm.html#esm_transpiler_loader 5 | // 6 | // NOTE: This assumes the loader implementation for Node.js >= 18 7 | 8 | import { loaderOptions } from './common.js'; 9 | 10 | loaderOptions.isCached = true; 11 | 12 | export { resolve } from './resolver.js'; 13 | export { load } from './testLoader.js'; 14 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/lifecycle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { after, afterEach, before, beforeEach } from 'node:test'; 5 | 6 | /** 7 | * This ensures that the before/after functions are exposed 8 | **/ 9 | export function lifecycle () { 10 | return { 11 | after, 12 | afterAll: after, 13 | afterEach, 14 | before, 15 | beforeAll: before, 16 | beforeEach 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/dev/src/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './index.js'; 5 | 6 | import rollupAlias from '@rollup/plugin-alias'; 7 | import eslint from 'eslint/use-at-your-own-risk'; 8 | import nodeCrypto from 'node:crypto'; 9 | 10 | console.log(' eslint::', typeof eslint !== 'undefined'); 11 | console.log(' nodeCrypto::', typeof nodeCrypto !== 'undefined'); 12 | console.log('rollupAlias::', typeof rollupAlias !== 'undefined'); 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/dev 2 | 3 | A collection of shared CI scripts and development environment (configuration, dependencies) used by all [@polkadot](https://polkadot.js.org) projects. 4 | 5 | Included here - 6 | 7 | - [@polkadot/dev](packages/dev/) Common base configurations for our TypeScript projects 8 | - [Scripts](packages/dev/README.md) 9 | - [@polkadot/dev-test](packages/dev-test/) A Jest-like global environment for usage alongside `node:test` 10 | - [@polkadot/dev-ts](packages/dev-ts/) An ESM loader for TS environments 11 | -------------------------------------------------------------------------------- /packages/dev-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Adapted from: https://nodejs.org/api/esm.html#esm_transpiler_loader 5 | // 6 | // NOTE: This assumes the loader implementation for Node.js >= 18 7 | 8 | import { loaderOptions } from './common.js'; 9 | 10 | loaderOptions.isCached = new URL(import.meta.url).searchParams.get('isCached') === 'true'; 11 | 12 | export { load } from './loader.js'; 13 | export { resolve } from './resolver.js'; 14 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-ci-ghact-docs.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { execPm, GITHUB_REPO, GITHUB_TOKEN_URL, gitSetup, logBin } from './util.mjs'; 6 | 7 | const repo = `${GITHUB_TOKEN_URL}/${GITHUB_REPO}.git`; 8 | 9 | logBin('polkadot-ci-ghact-docs'); 10 | 11 | gitSetup(); 12 | 13 | execPm('run docs'); 14 | execPm(`polkadot-exec-ghpages --dotfiles --repo ${repo} --dist ${process.env['GH_PAGES_SRC']} --dest .`, true); 15 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/JsxChild.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React from 'react'; 5 | 6 | export interface Props { 7 | children?: React.ReactNode; 8 | className?: string; 9 | label?: string; 10 | } 11 | 12 | function Child ({ children, className, label }: Props): React.ReactElement { 13 | return ( 14 |
15 | {label || ''}{children} 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(Child); 21 | -------------------------------------------------------------------------------- /.github/workflows/pr-any.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: [pull_request] 3 | 4 | jobs: 5 | pr: 6 | continue-on-error: true 7 | strategy: 8 | matrix: 9 | step: ['lint', 'test', 'build', 'docs'] 10 | runs-on: ubuntu-latest 11 | env: 12 | YARN_ENABLE_SCRIPTS: false 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 'lts/*' 18 | - name: ${{ matrix.step }} 19 | run: | 20 | yarn install --immutable 21 | yarn ${{ matrix.step }} 22 | -------------------------------------------------------------------------------- /packages/dev/config/typedoc.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = { 5 | exclude: '**/*+(index|e2e|spec|types).ts', 6 | excludeExternals: true, 7 | excludeNotExported: true, 8 | excludePrivate: true, 9 | excludeProtected: true, 10 | hideGenerator: true, 11 | includeDeclarations: false, 12 | module: 'commonjs', 13 | moduleResolution: 'node', 14 | name: 'polkadot{.js}', 15 | out: 'docs', 16 | stripInternal: 'true', 17 | theme: 'markdown' 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: bot 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | approve: 9 | if: "! startsWith(github.event.head_commit.message, '[CI Skip]') && (!github.event.pull_request || github.event.pull_request.head.repo.full_name == github.repository)" 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: jacogr/action-approve@795afd1dd096a2071d7ec98740661af4e853b7da 13 | with: 14 | authors: jacogr, TarikGul 15 | labels: -auto 16 | token: ${{ secrets.GH_PAT_BOT }} 17 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-build-docs.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | 8 | import { copyDirSync, logBin, rimrafSync } from './util.mjs'; 9 | 10 | logBin('polkadot-dev-build-docs'); 11 | 12 | let docRoot = path.join(process.cwd(), 'docs'); 13 | 14 | if (fs.existsSync(docRoot)) { 15 | docRoot = path.join(process.cwd(), 'build-docs'); 16 | 17 | rimrafSync(docRoot); 18 | copyDirSync(path.join(process.cwd(), 'docs'), docRoot); 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true 4 | }, 5 | "files": [], 6 | "references": [ 7 | { "path": "./packages/dev/tsconfig.build.json" }, 8 | { "path": "./packages/dev/tsconfig.config.json" }, 9 | { "path": "./packages/dev/tsconfig.scripts.json" }, 10 | { "path": "./packages/dev/tsconfig.spec.json" }, 11 | { "path": "./packages/dev-test/tsconfig.build.json" }, 12 | { "path": "./packages/dev-test/tsconfig.spec.json" }, 13 | { "path": "./packages/dev-ts/tsconfig.build.json" }, 14 | { "path": "./packages/dev-ts/tsconfig.spec.json" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/dev/src/rootCjs.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type * as testRoot from './root.js'; 5 | 6 | // NOTE We don't use ts-expect-error here since the build folder may or may 7 | // not exist (so the error may or may not be there) 8 | // 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore This should only run against the compiled ouput, where this should exist 11 | import testRootBuild from '../build/cjs/root.js'; 12 | import { runTests } from './rootTests.js'; 13 | 14 | runTests(testRootBuild as unknown as typeof testRoot); 15 | -------------------------------------------------------------------------------- /packages/dev/config/prettier.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = { 5 | arrowParens: 'always', 6 | bracketSpacing: true, 7 | embeddedLanguageFormatting: 'off', 8 | endOfLine: 'lf', 9 | htmlWhitespaceSensitivity: 'ignore', 10 | jsxBracketSameLine: false, 11 | jsxSingleQuote: true, 12 | parser: 'typescript', 13 | printWidth: 2048, 14 | proseWrap: 'preserve', 15 | quoteProps: 'as-needed', 16 | requirePragma: true, // only on those files explicitly asked for 17 | semi: true, 18 | singleQuote: true, 19 | tabWidth: 2, 20 | trailingComma: 'none', 21 | useTabs: false 22 | }; 23 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { browser } from './browser.js'; 5 | import { expect } from './expect.js'; 6 | import { jest } from './jest.js'; 7 | import { lifecycle } from './lifecycle.js'; 8 | import { suite } from './suite.js'; 9 | 10 | /** 11 | * Exposes the jest-y environment via globals. 12 | */ 13 | export function exposeEnv (isBrowser: boolean): void { 14 | [expect, jest, lifecycle, suite, isBrowser && browser].forEach((env) => { 15 | env && Object 16 | .entries(env()) 17 | .forEach(([key, fn]) => { 18 | globalThis[key as 'undefined'] ??= fn; 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/browser.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { browser } from './browser.js'; 5 | 6 | const all = browser(); 7 | 8 | describe('browser', () => { 9 | it('contains window', () => { 10 | expect(all.window).toBeDefined(); 11 | }); 12 | 13 | it('contains a crypto implementation', () => { 14 | expect(all.crypto).toBeTruthy(); 15 | expect(typeof all.crypto.getRandomValues).toBe('function'); 16 | }); 17 | 18 | it('contains the top-level objects', () => { 19 | expect(all.document).toBeDefined(); 20 | expect(all.navigator).toBeDefined(); 21 | }); 22 | 23 | it('contains HTML*Element', () => { 24 | expect(typeof all.HTMLElement).toBe('function'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/Jsx.spec.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import { fireEvent, render, screen } from '@testing-library/react'; 7 | import { strict as assert } from 'node:assert'; 8 | import React from 'react'; 9 | 10 | import Jsx from './Jsx.js'; 11 | 12 | describe('react testing', () => { 13 | it('shows the children when the checkbox is checked', () => { 14 | const testMessage = 'Test Message'; 15 | 16 | render({testMessage}); 17 | 18 | assert.equal(screen.queryByText(testMessage), null); 19 | 20 | fireEvent.click(screen.getByLabelText(/show/i)); 21 | 22 | assert.notEqual(screen.getByText(testMessage), null); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/dev-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/dev/issues", 4 | "description": "An TS -> ESM loader for Node >= 16.12", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/dev/tree/master/packages/dev-ts#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/dev-ts", 11 | "repository": { 12 | "directory": "packages/dev-ts", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/dev.git" 15 | }, 16 | "sideEffects": false, 17 | "type": "module", 18 | "version": "0.84.2", 19 | "main": "./index.js", 20 | "exports": { 21 | "./globals.d.ts": "./src/globals.d.ts" 22 | }, 23 | "dependencies": { 24 | "json5": "^2.2.3", 25 | "tslib": "^2.7.0", 26 | "typescript": "^5.5.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/dev/src/rootStatic/kusama.svg: -------------------------------------------------------------------------------- 1 | kusama-ksm-logo -------------------------------------------------------------------------------- /.github/workflows/push-master.yml: -------------------------------------------------------------------------------- 1 | name: Master 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | master: 9 | if: "! startsWith(github.event.head_commit.message, '[CI Skip]')" 10 | strategy: 11 | matrix: 12 | step: ['build:release'] 13 | runs-on: ubuntu-latest 14 | env: 15 | YARN_ENABLE_SCRIPTS: false 16 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 17 | GH_PAT: ${{ secrets.GH_PAT_BOT }} 18 | GH_RELEASE_GITHUB_API_TOKEN: ${{ secrets.GH_PAT_BOT }} 19 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | token: ${{ secrets.GH_PAT_BOT }} 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 'lts/*' 28 | - name: ${{ matrix.step }} 29 | run: | 30 | yarn install --immutable 31 | yarn ${{ matrix.step }} 32 | -------------------------------------------------------------------------------- /packages/dev-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/dev/issues", 4 | "description": "A basic test-functions-as-global library on top of node:test", 5 | "engines": { 6 | "node": ">=18.14" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/dev/tree/master/packages/dev-test#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/dev-test", 11 | "repository": { 12 | "directory": "packages/dev-test", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/dev.git" 15 | }, 16 | "sideEffects": false, 17 | "type": "module", 18 | "version": "0.84.2", 19 | "main": "./index.js", 20 | "exports": { 21 | "./globals.d.ts": "./src/globals.d.ts" 22 | }, 23 | "dependencies": { 24 | "jsdom": "^24.0.0", 25 | "tslib": "^2.7.0" 26 | }, 27 | "devDependencies": { 28 | "@types/jsdom": "^21.1.6" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '30 1/3 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | env: 11 | YARN_ENABLE_SCRIPTS: false 12 | steps: 13 | - uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836 14 | with: 15 | github-token: ${{ secrets.GH_PAT_BOT }} 16 | issue-inactive-days: '7' 17 | issue-comment: > 18 | This thread has been automatically locked since there has not been 19 | any recent activity after it was closed. Please open a new issue 20 | if you think you have a related problem or query. 21 | pr-inactive-days: '2' 22 | pr-comment: > 23 | This pull request has been automatically locked since there 24 | has not been any recent activity after it was closed. 25 | Please open a new issue for related bugs. 26 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-circular.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // @ts-expect-error For scripts we don't include @types/* definitions 6 | import madge from 'madge'; 7 | 8 | import { exitFatal, logBin } from './util.mjs'; 9 | 10 | logBin('polkadot-dev-circular'); 11 | 12 | const res = await madge('./', { fileExtensions: ['ts', 'tsx'] }); 13 | 14 | /** @type {string[][]} */ 15 | const circular = res.circular(); 16 | 17 | if (!circular.length) { 18 | process.stdout.write('No circular dependency found!\n'); 19 | process.exit(0); 20 | } 21 | 22 | const err = `Failed with ${circular.length} circular dependencies`; 23 | const all = circular 24 | .map((files, idx) => `${(idx + 1).toString().padStart(4)}: ${files.join(' > ')}`) 25 | .join('\n'); 26 | 27 | process.stdout.write(`\n${err}:\n\n${all}\n\n`); 28 | 29 | exitFatal(err); 30 | -------------------------------------------------------------------------------- /packages/dev-test/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export type AnyFn = (...args: any[]) => any; 6 | 7 | export type BaseObj = Record; 8 | 9 | // eslint-disable-next-line @typescript-eslint/ban-types 10 | export type BaseFn = Function; 11 | 12 | export type StubFn = (...args: unknown[]) => unknown; 13 | 14 | // These basically needs to align with the ReturnType 15 | // functions at least for the functionality that we are using: accessing calls & 16 | // managing the mock interface with resets and restores 17 | export type WithMock = F & { 18 | mock: { 19 | calls: { 20 | arguments: unknown[]; 21 | }[]; 22 | 23 | mockImplementation: (fn: AnyFn) => void; 24 | mockImplementationOnce: (fn: AnyFn) => void; 25 | resetCalls: () => void; 26 | restore: () => void; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-ci-ghpages-force.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | 7 | import { execGit, logBin } from './util.mjs'; 8 | 9 | logBin('polkadot-ci-ghpages-force'); 10 | 11 | // ensure we are on master 12 | execGit('checkout master'); 13 | 14 | // checkout latest 15 | execGit('fetch'); 16 | execGit('checkout gh-pages'); 17 | execGit('pull'); 18 | execGit('checkout --orphan gh-pages-temp'); 19 | 20 | // ignore relevant files 21 | fs.writeFileSync('.gitignore', ` 22 | .github/ 23 | .vscode/ 24 | .yarn/ 25 | build/ 26 | coverage/ 27 | node_modules/ 28 | packages/ 29 | test/ 30 | NOTES.md 31 | `); 32 | 33 | // add 34 | execGit('add -A'); 35 | execGit('commit -am "refresh history"'); 36 | 37 | // danger, force new 38 | execGit('branch -D gh-pages'); 39 | execGit('branch -m gh-pages'); 40 | execGit('push -f origin gh-pages'); 41 | 42 | // switch to master 43 | execGit('checkout master'); 44 | -------------------------------------------------------------------------------- /packages/dev-test/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* eslint-disable no-var */ 5 | 6 | import type { expect } from './env/expect.js'; 7 | import type { jest } from './env/jest.js'; 8 | import type { lifecycle } from './env/lifecycle.js'; 9 | import type { suite } from './env/suite.js'; 10 | 11 | type Expect = ReturnType; 12 | 13 | type Jest = ReturnType; 14 | 15 | type Lifecycle = ReturnType; 16 | 17 | type Suite = ReturnType; 18 | 19 | declare global { 20 | var after: Lifecycle['after']; 21 | var afterAll: Lifecycle['afterAll']; 22 | var afterEach: Lifecycle['afterEach']; 23 | var before: Lifecycle['before']; 24 | var beforeAll: Lifecycle['beforeAll']; 25 | var beforeEach: Lifecycle['beforeEach']; 26 | var describe: Suite['describe']; 27 | var expect: Expect['expect']; 28 | var it: Suite['it']; 29 | var jest: Jest['jest']; 30 | } 31 | 32 | export {}; 33 | -------------------------------------------------------------------------------- /scripts/all-bump-berry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2017-2025 @polkadot/dev authors & contributors 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # This scripts updates and aligns the version of yarn berry used. It follows 6 | # the following approach - 7 | # 8 | # 1. Updates the version of yarn berry in the dev project 9 | # 2. Performs an install in dev to upgrade the locks/plugins 10 | # 3. Loops through each of the polkadot-js projects, copying the 11 | # config from dev 12 | 13 | DIRECTORIES=( "wasm" "common" "api" "docs" "ui" "phishing" "extension" "tools" "apps" ) 14 | 15 | # update to latest inside dev 16 | cd dev 17 | echo "*** Updating yarn in dev" 18 | git pull 19 | yarn set version latest 20 | yarn 21 | cd .. 22 | 23 | # update all our existing polkadot-js projects 24 | for PKG in "${DIRECTORIES[@]}"; do 25 | echo "*** Updating yarn in $PKG" 26 | cd $PKG 27 | git pull 28 | rm -rf .yarn/plugins .yarn/releases 29 | cp -R ../dev/.yarn/plugins .yarn 30 | cp -R ../dev/.yarn/releases .yarn 31 | cat ../dev/.yarnrc.yml > .yarnrc.yml 32 | yarn 33 | cd .. 34 | done 35 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | 1382 Jaco Bump deps (#1134) 2 | 19 Tarik Gul Write the Buffer Import to necessary Deno build files (#1156) 3 | 2 Arjun Porwal Fix CI issue (#1159) 4 | 2 Nikos Kontakis Add rollup dynamic import variables plugin (#789) 5 | 1 Alex Saft Fix Vite build bundling error about EISDIR on `new URL('.', import.meta.url)` (#637) 6 | 1 Alex Wang support build when using lerna with on package (#214) 7 | 1 Amaury Martiny Allow .spec.ts files (#143) 8 | 1 Axel Chalon Include dotfiles when pushing to gh-pages (#268) 9 | 1 Ewa Kowalska Add fix flag to eslint script (#419) 10 | 1 Nazar Mokrynskyi Remove redundant babel plugins (#501) 11 | 1 rajk93 build(dev): ignore removing private fields (#1157) 12 | 1 Stefanie Doll Added react-hot-loader to 'dev-react' config (#191) 13 | 1 StefansArya Change bundle configuration to UMD (#557) -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-copy-dir.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { copyDirSync, exitFatal, logBin } from './util.mjs'; 6 | 7 | const argv = process.argv.slice(2); 8 | const args = []; 9 | let cd = ''; 10 | let flatten = false; 11 | 12 | for (let i = 0; i < argv.length; i++) { 13 | switch (argv[i]) { 14 | case '--cd': 15 | cd = argv[++i]; 16 | break; 17 | case '--flatten': 18 | flatten = true; 19 | break; 20 | default: 21 | args.push(argv[i]); 22 | break; 23 | } 24 | } 25 | 26 | const sources = args.slice(0, args.length - 1); 27 | const dest = args[args.length - 1]; 28 | 29 | logBin('polkadot-dev-copy-dir'); 30 | 31 | if (!sources || !dest) { 32 | exitFatal('Expected at least one ... and one argument'); 33 | } 34 | 35 | sources.forEach((src) => 36 | copyDirSync( 37 | cd 38 | ? `${cd}/${src}` 39 | : src, 40 | cd 41 | ? `${cd}/${dest}${flatten ? '' : `/${src}`}` 42 | : `${dest}${flatten ? '' : `/${src}`}` 43 | ) 44 | ); 45 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/Clazz.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export class Clazz { 5 | #something = 123_456_789; 6 | 7 | readonly and: number; 8 | 9 | static staticProperty = 'foobar'; 10 | static staticFunction = (): string|null => Clazz.staticProperty; 11 | 12 | /** 13 | * @param and the number we should and with 14 | */ 15 | constructor (and: number) { 16 | this.and = and; 17 | this.#something = this.#something & and; 18 | } 19 | 20 | get something (): number { 21 | return this.#something; 22 | } 23 | 24 | async doAsync (): Promise { 25 | const res = await new Promise((resolve) => resolve(true)); 26 | 27 | console.log(res); 28 | 29 | return res; 30 | } 31 | 32 | /** 33 | * @description Sets something to something 34 | * @param something The addition 35 | */ 36 | setSomething = (something?: number): number => { 37 | this.#something = (something ?? 123_456) & this.and; 38 | 39 | return this.#something; 40 | }; 41 | 42 | toString (): string { 43 | return `something=${this.#something}`; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/dev-ts/src/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { LoaderOptions } from './types.js'; 5 | 6 | import path from 'node:path'; 7 | import process from 'node:process'; 8 | import { pathToFileURL } from 'node:url'; 9 | 10 | /** The path we are being executed from */ 11 | export const CWD_PATH = process.cwd(); 12 | 13 | /** The cwd path we are being executed from in URL form */ 14 | export const CWD_URL = pathToFileURL(`${CWD_PATH}/`); 15 | 16 | /** The root path to node_modules (assuming it is in the root) */ 17 | export const MOD_PATH = path.join(CWD_PATH, 'node_modules'); 18 | 19 | /** List of allowed extensions for mappings */ 20 | export const EXT_TS_ARRAY = ['.ts', '.tsx']; 21 | 22 | /** RegEx for files that we support via this loader */ 23 | export const EXT_TS_REGEX = /\.tsx?$/; 24 | 25 | /** RegEx for matching JS files (imports map to TS) */ 26 | export const EXT_JS_REGEX = /\.jsx?$/; 27 | 28 | /** RegEx for json files (as actually aliassed in polkadot-js) */ 29 | export const EXT_JSON_REGEX = /\.json$/; 30 | 31 | /** Options for loader config */ 32 | export const loaderOptions: LoaderOptions = {}; 33 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-run-lint.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import process from 'node:process'; 6 | import yargs from 'yargs'; 7 | 8 | import { __dirname, execPm, GITHUB_REPO, logBin } from './util.mjs'; 9 | 10 | const TS_CONFIG_BUILD = true; 11 | 12 | logBin('polkadot-dev-run-lint'); 13 | 14 | // Since yargs can also be a promise, we just relax the type here completely 15 | const argv = await yargs(process.argv.slice(2)) 16 | .options({ 17 | 'skip-eslint': { 18 | description: 'Skips running eslint', 19 | type: 'boolean' 20 | }, 21 | 'skip-tsc': { 22 | description: 'Skips running tsc', 23 | type: 'boolean' 24 | } 25 | }) 26 | .strict() 27 | .argv; 28 | 29 | if (!argv['skip-eslint']) { 30 | // We don't want to run with fix on CI 31 | const extra = GITHUB_REPO 32 | ? '' 33 | : '--fix'; 34 | 35 | execPm(`polkadot-exec-eslint ${extra} ${process.cwd()}`); 36 | } 37 | 38 | if (!argv['skip-tsc']) { 39 | execPm(`polkadot-exec-tsc --noEmit --emitDeclarationOnly false --pretty${TS_CONFIG_BUILD ? ' --project tsconfig.build.json' : ''}`); 40 | } 41 | -------------------------------------------------------------------------------- /packages/dev/config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * There uses the strictest configs as the base 4 | * https://github.com/tsconfig/bases/blob/f674fa6cbca17062ff02511b02872f8729a597ec/bases/strictest.json 5 | */ 6 | "extends": "@tsconfig/strictest/tsconfig.json", 7 | "compilerOptions": { 8 | /** 9 | * Aligns with packages/dev/scripts/polkadot-dev-build-ts & packages/dev-ts/src/loader 10 | * (target here is specifically tied to the minimum supported Node version) 11 | */ 12 | "module": "nodenext", 13 | "moduleResolution": "nodenext", 14 | "target": "es2022", 15 | 16 | /** 17 | * Specific compilation configs for polkadot-js projects as it is used 18 | * (we only compile *.d.ts via the tsc command-line) 19 | */ 20 | "declaration": true, 21 | "emitDeclarationOnly": true, 22 | "jsx": "preserve", 23 | "verbatimModuleSyntax": true, 24 | 25 | /** 26 | * These appear in strictest, however we don't (yet) use them. For the most part it means 27 | * that we actually do have a large number of these lurking (especially on index checks) 28 | */ 29 | "exactOptionalPropertyTypes": false, 30 | "noUncheckedIndexedAccess": false, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/dev-test/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/dev-test 2 | 3 | This is a very basic Jest-compatible environment that could be used alongside tests. The need for this came from replacing Jest with `node --test` without rewriting all assertions. 4 | 5 | It provides the following - 6 | 7 | 1. Browser `window`, `document`, `navigator` (see usage for browser-specific path) 8 | 2. `jest` functions, specifically `spyOn` (not comprehensive, some will error, some witll noop) 9 | 3. `expect` functions (not comprehensive, caters for specific polkadot-js usage) 10 | 11 | 12 | ## Usage 13 | 14 | On thing to note is that `node:test` is still rapidly evolving - this includes the APIs and features. As such this requires at least Node 18.14, however 18.15+ is recommended. 15 | 16 | The entry points are different based on the environment you would like to operate in. For a browser-like environment, 17 | 18 | ``` 19 | node --require @polkadot/dev-test/browser ... 20 | ``` 21 | 22 | or for a basic describe/expect/jest-only global environment 23 | 24 | ``` 25 | node --require @polkadot/dev-test/node ... 26 | ``` 27 | 28 | The `...` above indicates any additional Node options, for instance a full command could be - 29 | 30 | ``` 31 | node --require @polkadot/dev-test/node --test something.test.js 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/Jsx.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Adapted from https://github.com/testing-library/react-testing-library#basic-example 5 | 6 | import type { Props } from './JsxChild.js'; 7 | 8 | import React, { useCallback, useState } from 'react'; 9 | import { styled } from 'styled-components'; 10 | 11 | import Child from './JsxChild.js'; 12 | 13 | function Hidden ({ children, className }: Props): React.ReactElement { 14 | const [isMessageVisible, setMessageVisibility] = useState(false); 15 | 16 | const onShow = useCallback( 17 | (e: React.ChangeEvent) => 18 | setMessageVisibility(e.target.checked), 19 | [] 20 | ); 21 | 22 | return ( 23 | 24 | 25 | 31 | {isMessageVisible && ( 32 | <> 33 | {children} 34 | 35 | 36 | )} 37 | 38 | ); 39 | } 40 | 41 | const StyledDiv = styled.div` 42 | background: red; 43 | `; 44 | 45 | export default React.memo(Hidden); 46 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-deno-map.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | 7 | import { DENO_POL_PRE } from './util.mjs'; 8 | 9 | const [e, i] = fs 10 | .readdirSync('packages') 11 | .filter((p) => fs.existsSync(`packages/${p}/src/mod.ts`)) 12 | .sort() 13 | .reduce((/** @type {[string[], Record]} */ [e, i], p) => { 14 | e.push(`export * as ${p.replace(/-/g, '_')} from '${DENO_POL_PRE}/${p}/mod.ts';`); 15 | i[`${DENO_POL_PRE}/${p}/`] = `./packages/${p}/build-deno/`; 16 | 17 | return [e, i]; 18 | }, [[], {}]); 19 | 20 | if (!fs.existsSync('mod.ts')) { 21 | fs.writeFileSync('mod.ts', `// Copyright 2017-${new Date().getFullYear()} @polkadot/dev authors & contributors\n// SPDX-License-Identifier: Apache-2.0\n\n// auto-generated via polkadot-dev-deno-map, do not edit\n\n// This is a Deno file, so we can allow .ts imports 22 | /* eslint-disable import/extensions */\n\n${e.join('\n')}\n`); 23 | } 24 | 25 | if (fs.existsSync('import_map.in.json')) { 26 | const o = JSON.parse(fs.readFileSync('import_map.in.json', 'utf-8')); 27 | 28 | Object 29 | .entries(o.imports) 30 | .forEach(([k, v]) => { 31 | i[k] = v; 32 | }); 33 | } 34 | 35 | fs.writeFileSync('import_map.json', JSON.stringify({ imports: i }, null, 2)); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/dev/issues", 4 | "engines": { 5 | "node": ">=18.14" 6 | }, 7 | "homepage": "https://github.com/polkadot-js/dev#readme", 8 | "license": "Apache-2.0", 9 | "packageManager": "yarn@4.6.0", 10 | "private": true, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/polkadot-js/dev.git" 14 | }, 15 | "sideEffects": false, 16 | "type": "module", 17 | "version": "0.84.2", 18 | "versions": { 19 | "git": "0.84.2", 20 | "npm": "0.84.2" 21 | }, 22 | "workspaces": [ 23 | "packages/*" 24 | ], 25 | "scripts": { 26 | "build": "polkadot-dev-build-ts", 27 | "build:before": "polkadot-dev-copy-dir --cd packages/dev config scripts build", 28 | "build:release": "polkadot-ci-ghact-build --skip-beta", 29 | "clean": "polkadot-dev-clean-build", 30 | "docs": "polkadot-dev-build-docs", 31 | "lint": "polkadot-dev-run-lint", 32 | "postinstall": "./packages/dev/scripts/polkadot-dev-yarn-only.mjs", 33 | "test": "yarn build && polkadot-dev-run-test --dev-build --env browser" 34 | }, 35 | "devDependencies": { 36 | "@polkadot/dev": "workspace:packages/dev", 37 | "@polkadot/dev-test": "workspace:packages/dev-test", 38 | "@polkadot/dev-ts": "workspace:packages/dev-ts" 39 | }, 40 | "resolutions": { 41 | "typescript": "^5.5.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-clean-build.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | 8 | import { logBin, PATHS_BUILD, rimrafSync } from './util.mjs'; 9 | 10 | const PKGS = path.join(process.cwd(), 'packages'); 11 | const DIRS = PATHS_BUILD.map((d) => `build${d}`); 12 | 13 | logBin('polkadot-dev-clean-build'); 14 | 15 | /** 16 | * @internal 17 | * 18 | * Retrieves all the files containing tsconfig.*.tsbuildinfo contained withing the directory 19 | * 20 | * @param {string} dir 21 | * @returns {string[]} 22 | */ 23 | function getPaths (dir) { 24 | if (!fs.existsSync(dir)) { 25 | return []; 26 | } 27 | 28 | return fs 29 | .readdirSync(dir) 30 | .reduce((all, p) => { 31 | if (p.startsWith('tsconfig.') && p.endsWith('.tsbuildinfo')) { 32 | all.push(path.join(dir, p)); 33 | } 34 | 35 | return all; 36 | }, DIRS.map((p) => path.join(dir, p))); 37 | } 38 | 39 | /** 40 | * @internal 41 | * 42 | * Removes all the specified directories 43 | * 44 | * @param {string[]} dirs 45 | */ 46 | function cleanDirs (dirs) { 47 | dirs.forEach((d) => rimrafSync(d)); 48 | } 49 | 50 | cleanDirs(getPaths(process.cwd())); 51 | 52 | if (fs.existsSync(PKGS)) { 53 | cleanDirs(getPaths(PKGS)); 54 | cleanDirs( 55 | fs 56 | .readdirSync(PKGS) 57 | .map((f) => path.join(PKGS, f)) 58 | .filter((f) => fs.statSync(f).isDirectory()) 59 | .reduce((/** @type {string[]} */ res, d) => res.concat(getPaths(d)), []) 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-copy-to.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | 8 | import { copyDirSync, execPm, exitFatal, logBin, mkdirpSync, rimrafSync } from './util.mjs'; 9 | 10 | const args = process.argv.slice(2); 11 | 12 | logBin('polkadot-dev-copy-to'); 13 | 14 | if (args.length !== 1) { 15 | exitFatal('Expected one argument'); 16 | } 17 | 18 | const dest = path.join(process.cwd(), '..', args[0], 'node_modules'); 19 | 20 | if (!fs.existsSync(dest)) { 21 | exitFatal('Destination node_modules folder does not exist'); 22 | } 23 | 24 | // build to ensure we actually have latest 25 | execPm('build'); 26 | 27 | // map across what is available and copy it 28 | fs 29 | .readdirSync('packages') 30 | .map((dir) => { 31 | const pkgPath = path.join(process.cwd(), 'packages', dir); 32 | 33 | return [pkgPath, path.join(pkgPath, 'package.json')]; 34 | }) 35 | .filter(([, jsonPath]) => fs.existsSync(jsonPath)) 36 | .map(([pkgPath, json]) => [JSON.parse(fs.readFileSync(json, 'utf8')).name, pkgPath]) 37 | .forEach(([name, pkgPath]) => { 38 | console.log(`*** Copying ${name} to ${dest}`); 39 | 40 | const outDest = path.join(dest, name); 41 | 42 | // remove the destination 43 | rimrafSync(outDest); 44 | 45 | // create the root 46 | mkdirpSync(outDest); 47 | 48 | // copy the build output 49 | copyDirSync(path.join(pkgPath, 'build'), outDest); 50 | 51 | // copy node_modules, as available 52 | copyDirSync(path.join(pkgPath, 'node_modules'), path.join(outDest, 'node_modules')); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/suite.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | describe('describe()', () => { 5 | // eslint-disable-next-line jest/no-focused-tests 6 | describe.only('.only', () => { 7 | it('runs this one', () => { 8 | expect(true).toBe(true); 9 | }); 10 | }); 11 | 12 | describe('.skip', () => { 13 | // eslint-disable-next-line jest/no-disabled-tests 14 | describe.skip('.only (.skip)', () => { 15 | it('skips inside .only', () => { 16 | expect(true).toBe(true); 17 | 18 | throw new Error('FATAL: This should not run'); 19 | }); 20 | }); 21 | }); 22 | }); 23 | 24 | describe('it()', () => { 25 | it('has been enhanced', () => { 26 | expect(it.todo).toBeDefined(); 27 | }); 28 | 29 | it('allows promises', async () => { 30 | expect(await Promise.resolve(true)).toBe(true); 31 | }); 32 | 33 | describe('.only', () => { 34 | // eslint-disable-next-line jest/no-focused-tests 35 | it.only('runs this test when .only is used', () => { 36 | expect(true).toBe(true); 37 | }); 38 | 39 | // eslint-disable-next-line jest/no-disabled-tests 40 | it.skip('skips when .skip is used', () => { 41 | expect(true).toBe(true); 42 | 43 | throw new Error('FATAL: This should not run'); 44 | }); 45 | }); 46 | 47 | describe('.skip', () => { 48 | // eslint-disable-next-line jest/no-disabled-tests 49 | it.skip('skips when .skip is used', () => { 50 | expect(true).toBe(true); 51 | 52 | throw new Error('FATAL: This should not run'); 53 | }); 54 | }); 55 | 56 | describe('.todo', () => { 57 | it.todo('marks as a todo when .todo is used', () => { 58 | expect(true).toBe(true); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/suite.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { describe, it } from 'node:test'; 5 | 6 | import { enhanceObj } from '../util.js'; 7 | 8 | interface WrapOpts { 9 | only?: boolean; 10 | skip?: boolean; 11 | todo?: boolean; 12 | } 13 | 14 | type WrapFn = (name: string, options: { only?: boolean; skip?: boolean; timeout?: number; todo?: boolean; }, fn: () => void | Promise) => void | Promise; 15 | 16 | const MINUTE = 60 * 1000; 17 | 18 | /** 19 | * @internal 20 | * 21 | * Wraps either describe or it with relevant .only, .skip, .todo & .each helpers, 22 | * shimming it into a Jest-compatible environment. 23 | * 24 | * @param {} fn 25 | */ 26 | function createWrapper (fn: T, defaultTimeout: number) { 27 | const wrap = (opts: WrapOpts) => (name: string, exec: () => void | Promise, timeout?: number) => fn(name, { ...opts, timeout: (timeout || defaultTimeout) }, exec) as unknown as void; 28 | 29 | // Ensure that we have consistent helpers on the function. These are not consistently 30 | // applied accross all node:test versions, latest has all, so always apply ours. 31 | // Instead of node:test options for e.g. timeout, we provide a Jest-compatible signature 32 | return enhanceObj(wrap({}), { 33 | only: wrap({ only: true }), 34 | skip: wrap({ skip: true }), 35 | todo: wrap({ todo: true }) 36 | }); 37 | } 38 | 39 | /** 40 | * This ensures that the describe and it functions match our actual usages. 41 | * This includes .only, .skip and .todo helpers (.each is not applied) 42 | **/ 43 | export function suite () { 44 | return { 45 | describe: createWrapper(describe, 60 * MINUTE), 46 | it: createWrapper(it, 2 * MINUTE) 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /packages/dev/src/rootJs/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** This should appear as-is in the output with: 1. extension added, 2. augmented.d.ts correct */ 5 | import './augmented.js'; 6 | 7 | /** This import should appear as-in in the ouput (cjs without asserts) */ 8 | import testJson from '@polkadot/dev/rootJs/testJson.json' assert { type: 'json' }; 9 | 10 | /** Double double work, i.e. re-exports */ 11 | export { Clazz } from './Clazz.js'; 12 | 13 | /** Function to ensure that BigInt does not have the Babel Math.pow() transform */ 14 | export function bigIntExp (): bigint { 15 | // 123_456n * 137_858_491_849n 16 | return 123_456_789n * (13n ** 10n); 17 | } 18 | 19 | /** Function to ensure that dynamic imports work */ 20 | export async function dynamic (a: number, b: number): Promise { 21 | // NOTE we go via this path so it points to the same location in both ESM 22 | // and CJS output (a './dynamic' import would be different otherwise) 23 | const { sum } = await import('@polkadot/dev/rootJs/dynamic.mjs'); 24 | 25 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 26 | return sum(a, b); 27 | } 28 | 29 | /** Function to ensure we have json correctly imported */ 30 | export function json (): string { 31 | return testJson.test.json; 32 | } 33 | 34 | /** Check support for the ?? operator */ 35 | export function jsOpExp (a?: number): number { 36 | const defaults = { 37 | a: 42, 38 | b: 43, 39 | c: 44 40 | }; 41 | 42 | return a ?? defaults.a; 43 | } 44 | 45 | /** This is an actual check to ensure PURE is all-happy */ 46 | export const pureOpExp = /*#__PURE__*/ jsOpExp(); 47 | 48 | const fooA = 1; 49 | const fooB = 2; 50 | const fooC = 3; 51 | const fooD = 4; 52 | 53 | export { fooA, fooB, fooC, fooD }; 54 | -------------------------------------------------------------------------------- /packages/dev/src/rootTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | /* global describe, expect, it */ 7 | 8 | import type * as testRoot from './root.js'; 9 | 10 | export function runTests ({ Clazz, TEST_PURE, bigIntExp, dynamic, jsOpExp, json }: typeof testRoot): void { 11 | describe('Clazz', (): void => { 12 | it('has staticProperty', (): void => { 13 | expect(Clazz.staticProperty).toBe('foobar'); 14 | }); 15 | 16 | it('creates an instance with get/set', (): void => { 17 | const c = new Clazz(456); 18 | 19 | expect(c.something).toBe(123_456_789 & 456); 20 | 21 | c.setSomething(123); 22 | 23 | expect(c.something).toBe(123 & 456); 24 | }); 25 | }); 26 | 27 | describe('TEST_PURE', (): void => { 28 | it('should have the correct value', (): void => { 29 | expect(TEST_PURE).toBe('testRoot'); 30 | }); 31 | }); 32 | 33 | describe('dynamic()', (): void => { 34 | it('should allow dynamic import usage', async (): Promise => { 35 | expect(await dynamic(5, 37)).toBe(42); 36 | }); 37 | }); 38 | 39 | describe('bigIntExp()', (): void => { 40 | it('should return the correct value', (): void => { 41 | expect(bigIntExp()).toBe(123_456_789n * 137_858_491_849n); 42 | }); 43 | }); 44 | 45 | describe('jsOpExp', (): void => { 46 | it('handles 0 ?? 42 correctly', (): void => { 47 | expect(jsOpExp(0)).toBe(0); 48 | }); 49 | 50 | it('handles undefined ?? 42 correctly', (): void => { 51 | expect(jsOpExp()).toBe(42); 52 | }); 53 | }); 54 | 55 | describe('json()', (): void => { 56 | it('should return the correct value', (): void => { 57 | expect(json()).toBe('works'); 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /packages/dev-test/src/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { BaseFn, BaseObj, StubFn } from './types.js'; 5 | 6 | /** 7 | * Extends an existing object with the additional function if they 8 | * are not already existing. 9 | */ 10 | export function enhanceObj (obj: T, extra: X) { 11 | Object 12 | .entries(extra as Record) 13 | .forEach(([key, value]) => { 14 | (obj as Record)[key] ??= value; 15 | }); 16 | 17 | return obj as T & Omit; 18 | } 19 | 20 | /** 21 | * @internal 22 | * 23 | * A helper to create a stub object based wite the stub creator supplied 24 | */ 25 | function createStub (keys: N, creator: (key: string) => StubFn) { 26 | return keys.reduce>((obj, key) => { 27 | obj[key] ??= creator(key); 28 | 29 | return obj; 30 | }, {}) as unknown as { [K in N[number]]: StubFn }; 31 | } 32 | 33 | /** 34 | * Extends a given object with the named functions if they do not 35 | * already exist on the object. 36 | * 37 | * @type {StubObjFn} 38 | */ 39 | export function stubObj (objName: string, keys: N, alts?: Record) { 40 | return createStub(keys, (key) => () => { 41 | const alt = alts?.[key]; 42 | 43 | throw new Error(`${objName}.${key} has not been implemented${alt ? ` (Use ${alt} instead)` : ''}`); 44 | }); 45 | } 46 | 47 | /** 48 | * Extends a given object with the named functions if they do not 49 | * already exist on the object. 50 | * 51 | * @type {StubObjFn} 52 | */ 53 | export function warnObj (objName: string, keys: N) { 54 | return createStub(keys, (key) => () => { 55 | console.warn(`${objName}.${key} has been implemented as a noop`); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /scripts/all-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2017-2025 @polkadot/dev authors & contributors 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # This scripts updates all the inter polkadot-js dependencies. To do so it 6 | # creates a list of all the packages available and then loops through the 7 | # package.json files in the various repos, upgrading the dependecies as found. 8 | # 9 | # In this upgrading step is uses the local all-deps.js script 10 | # 11 | # In addition it also cleans old stale local branches, and performs an overall 12 | # dedupe - all maintenence operations. 13 | 14 | DIRECTORIES=( "dev" "wasm" "common" "api" "docs" "ui" "phishing" "extension" "tools" "apps" ) 15 | 16 | for REPO in "${DIRECTORIES[@]}"; do 17 | if [ "$REPO" != "" ] && [ -d "./$REPO/.git" ]; then 18 | echo "" 19 | echo "*** Removing branches in $REPO" 20 | 21 | cd $REPO 22 | 23 | CURRENT=$(git rev-parse --abbrev-ref HEAD) 24 | BRANCHES=( $(git branch | awk -F ' +' '! /\(no branch\)/ {print $2}') ) 25 | 26 | if [ "$CURRENT" = "master" ]; then 27 | git fetch 28 | git pull 29 | else 30 | echo "$CURRENT !== master" 31 | fi 32 | 33 | for BRANCH in "${BRANCHES[@]}"; do 34 | if [ "$BRANCH" != "$CURRENT" ] && [ "$BRANCH" != "master" ]; then 35 | git branch -d -f $BRANCH 36 | fi 37 | done 38 | 39 | git prune 40 | git gc 41 | 42 | cd .. 43 | fi 44 | done 45 | 46 | echo "" 47 | echo "*** Updating inter-package-deps" 48 | 49 | ./dev/scripts/all-deps.js 50 | 51 | echo "" 52 | echo "*** Installing updated packages" 53 | 54 | for REPO in "${DIRECTORIES[@]}"; do 55 | if [ "$REPO" != "" ] && [ -d "./$REPO/.git" ]; then 56 | echo "" 57 | cd $REPO 58 | 59 | DETACHED=$(git status | grep "HEAD detached") 60 | 61 | if [ "$DETACHED" != "" ]; then 62 | echo "*** Resetting $REPO" 63 | 64 | git reset --hard 65 | else 66 | echo "*** Installing $REPO" 67 | 68 | yarn install 69 | yarn dedupe 70 | fi 71 | 72 | cd .. 73 | fi 74 | done 75 | -------------------------------------------------------------------------------- /packages/dev-test/src/util.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import { enhanceObj, stubObj, warnObj } from './util.js'; 7 | 8 | describe('enhanceObj', () => { 9 | it('extends objects with non-existing values', () => { 10 | const test = enhanceObj( 11 | enhanceObj( 12 | { a: () => 1 }, 13 | { b: () => 2 } 14 | ), 15 | { c: () => 3 } 16 | ); 17 | 18 | expect(test.a()).toBe(1); 19 | expect(test.b()).toBe(2); 20 | expect(test.c()).toBe(3); 21 | }); 22 | 23 | it('does not override existing values', () => { 24 | const test = enhanceObj( 25 | enhanceObj( 26 | { a: 0, b: () => 1 }, 27 | { a: () => 0, b: () => 2 } 28 | ), 29 | { c: () => 2 } 30 | ); 31 | 32 | expect(test.a).toBe(0); 33 | expect(test.b()).toBe(1); 34 | expect(test.c()).toBe(2); 35 | }); 36 | }); 37 | 38 | describe('stubObj', () => { 39 | it('has entries throwing for unimplemented values', () => { 40 | const test = stubObj('obj', ['a', 'b'] as const); 41 | 42 | expect( 43 | () => test.b() 44 | ).toThrow('obj.b has not been implemented'); 45 | }); 46 | 47 | it('has entries throwing for unimplemented values (w/ alternatives)', () => { 48 | const test = stubObj('obj', ['a', 'b'] as const, { b: 'obj.a' }); 49 | 50 | expect( 51 | () => test.b() 52 | ).toThrow('obj.b has not been implemented (Use obj.a instead)'); 53 | }); 54 | }); 55 | 56 | describe('warnObj', () => { 57 | let spy: ReturnType; 58 | 59 | beforeEach(() => { 60 | spy = jest.spyOn(console, 'warn'); 61 | }); 62 | 63 | afterEach(() => { 64 | spy.mockRestore(); 65 | }); 66 | 67 | it('has entries warning on unimplemented', () => { 68 | const test = warnObj('obj', ['a', 'b'] as const); 69 | 70 | test.b(); 71 | 72 | expect(spy).toHaveBeenCalledWith('obj.b has been implemented as a noop'); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-contrib.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | 7 | import { execGit, logBin, mkdirpSync } from './util.mjs'; 8 | 9 | const tmpDir = 'packages/build'; 10 | const tmpFile = `${tmpDir}/CONTRIBUTORS`; 11 | 12 | logBin('polkadot-dev-contrib'); 13 | 14 | mkdirpSync(tmpDir); 15 | execGit(`shortlog master -e -n -s > ${tmpFile}`); 16 | 17 | fs.writeFileSync( 18 | 'CONTRIBUTORS', 19 | Object 20 | .entries( 21 | fs 22 | .readFileSync(tmpFile, 'utf-8') 23 | .split('\n') 24 | .map((l) => l.trim()) 25 | .filter((l) => !!l) 26 | .reduce((/** @type {Record} */ all, line) => { 27 | const [c, e] = line.split('\t'); 28 | const count = parseInt(c, 10); 29 | const [name, rest] = e.split(' <'); 30 | const isExcluded = ( 31 | ['GitHub', 'Travis CI'].some((n) => name.startsWith(n)) || 32 | ['>', 'action@github.com>'].some((e) => rest === e) || 33 | [name, rest].some((n) => n.includes('[bot]')) 34 | ); 35 | 36 | if (!isExcluded) { 37 | let [email] = rest.split('>'); 38 | 39 | if (!all[email]) { 40 | email = Object.keys(all).find((k) => 41 | name.includes(' ') && 42 | all[k].name === name 43 | ) || email; 44 | } 45 | 46 | if (all[email]) { 47 | all[email].count += count; 48 | } else { 49 | all[email] = { count, name }; 50 | } 51 | } 52 | 53 | return all; 54 | }, {}) 55 | ) 56 | .sort((a, b) => { 57 | const diff = b[1].count - a[1].count; 58 | 59 | return diff === 0 60 | ? a[1].name.localeCompare(b[1].name) 61 | : diff; 62 | }) 63 | .map(([email, { count, name }], i) => { 64 | execGit(`log master -1 --author=${email} > ${tmpFile}-${i}`); 65 | 66 | const commit = fs 67 | .readFileSync(`${tmpFile}-${i}`, 'utf-8') 68 | .split('\n')[4] 69 | .trim(); 70 | 71 | return `${`${count}`.padStart(8)}\t${name.padEnd(30)}\t${commit}`; 72 | }) 73 | .join('\n') 74 | ); 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## What? 4 | 5 | Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit. 6 | A project is more like an open wiki than a standard guarded open source project. 7 | 8 | ## Rules 9 | 10 | There are a few basic ground-rules for contributors (including the maintainer(s) of the project): 11 | 12 | 1. **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo. 13 | 2. **Non-master branches**, prefixed with a short name moniker (e.g. `-`) must be used for ongoing work. 14 | 3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors. 15 | 4. A pull-request *must not be merged until CI* has finished successfully. 16 | 17 | #### Merging pull requests once CI is successful: 18 | - A pull request with no large change to logic that is an urgent fix may be merged after a non-author contributor has reviewed it well. 19 | - No PR should be merged until all reviews' comments are addressed. 20 | 21 | #### Reviewing pull requests: 22 | When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in: 23 | 24 | - Buggy behaviour. 25 | - Undue maintenance burden. 26 | - Breaking with house coding style. 27 | - Pessimisation (i.e. reduction of speed as measured in the projects benchmarks). 28 | - Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on). 29 | - Uselessness (i.e. it does not strictly add a feature or fix a known issue). 30 | 31 | #### Reviews may not be used as an effective veto for a PR because: 32 | - There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix. 33 | - It does not fit well with some other contributors' longer-term vision for the project. 34 | 35 | ## Releases 36 | 37 | Declaring formal releases remains the prerogative of the project maintainer(s). 38 | 39 | ## Changes to this arrangement 40 | 41 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 42 | 43 | ## Heritage 44 | 45 | These contributing guidelines are modified from the "OPEN Open Source Project" guidelines for the Level project: [https://github.com/Level/community/blob/master/CONTRIBUTING.md](https://github.com/Level/community/blob/master/CONTRIBUTING.md) 46 | -------------------------------------------------------------------------------- /packages/dev-ts/src/loader.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import crypto from 'node:crypto'; 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | import { fileURLToPath } from 'node:url'; 8 | import ts from 'typescript'; 9 | 10 | import { EXT_TS_REGEX, loaderOptions } from './common.js'; 11 | 12 | interface Loaded { 13 | format: 'commonjs' | 'module'; 14 | shortCircuit?: boolean; 15 | source: string; 16 | } 17 | 18 | type NexLoad = (url: string, context: Record) => Promise; 19 | 20 | /** 21 | * Load all TypeScript files, compile via tsc on-the-fly 22 | **/ 23 | export async function load (url: string, context: Record, nextLoad: NexLoad): Promise { 24 | if (EXT_TS_REGEX.test(url)) { 25 | // used the chained loaders to retrieve 26 | const { source } = await nextLoad(url, { 27 | ...context, 28 | format: 'module' 29 | }); 30 | 31 | // we use a hash of the source to determine caching 32 | const sourceHash = `//# sourceHash=${crypto.createHash('sha256').update(source).digest('hex')}`; 33 | const compiledFile = url.includes('/src/') 34 | ? fileURLToPath( 35 | url 36 | .replace(/\.tsx?$/, '.js') 37 | .replace('/src/', '/build-loader/') 38 | ) 39 | : null; 40 | 41 | if (loaderOptions.isCached && compiledFile && fs.existsSync(compiledFile)) { 42 | const compiled = fs.readFileSync(compiledFile, 'utf-8'); 43 | 44 | if (compiled.includes(sourceHash)) { 45 | return { 46 | format: 'module', 47 | source: compiled 48 | }; 49 | } 50 | } 51 | 52 | // compile via typescript 53 | const { outputText } = ts.transpileModule(source.toString(), { 54 | compilerOptions: { 55 | ...( 56 | url.endsWith('.tsx') 57 | ? { jsx: ts.JsxEmit.ReactJSX } 58 | : {} 59 | ), 60 | esModuleInterop: true, 61 | importHelpers: true, 62 | inlineSourceMap: true, 63 | module: ts.ModuleKind.ESNext, 64 | moduleResolution: ts.ModuleResolutionKind.NodeNext, 65 | skipLibCheck: true, 66 | // Aligns with packages/dev/scripts/polkadot-dev-build-ts & packages/dev/config/tsconfig 67 | target: ts.ScriptTarget.ES2022 68 | }, 69 | fileName: fileURLToPath(url) 70 | }); 71 | 72 | if (loaderOptions.isCached && compiledFile) { 73 | const compiledDir = path.dirname(compiledFile); 74 | 75 | if (!fs.existsSync(compiledDir)) { 76 | fs.mkdirSync(compiledDir, { recursive: true }); 77 | } 78 | 79 | fs.writeFileSync(compiledFile, `${outputText}\n${sourceHash}`, 'utf-8'); 80 | } 81 | 82 | return { 83 | format: 'module', 84 | source: outputText 85 | }; 86 | } 87 | 88 | return nextLoad(url, context); 89 | } 90 | -------------------------------------------------------------------------------- /packages/dev-ts/src/testLoader.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import crypto from 'node:crypto'; 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | import { fileURLToPath } from 'node:url'; 8 | import ts from 'typescript'; 9 | 10 | import { EXT_TS_REGEX, loaderOptions } from './common.js'; 11 | 12 | interface Loaded { 13 | format: 'commonjs' | 'module'; 14 | shortCircuit?: boolean; 15 | source: string; 16 | } 17 | 18 | type NexLoad = (url: string, context: Record) => Promise; 19 | 20 | /** 21 | * Load all TypeScript files, compile via tsc on-the-fly 22 | **/ 23 | export async function load (url: string, context: Record, nextLoad: NexLoad): Promise { 24 | if (EXT_TS_REGEX.test(url)) { 25 | // used the chained loaders to retrieve 26 | const { source } = await nextLoad(url, { 27 | ...context, 28 | format: 'module' 29 | }); 30 | 31 | // This ensures there is support for Node v22 while also maintaining backwards compatibility for testing. 32 | const modifiedSrc = Buffer.from(source.toString().replace(/assert\s*\{\s*type:\s*'json'\s*\}/g, 'with { type: \'json\' }'), 'utf-8'); 33 | 34 | // we use a hash of the source to determine caching 35 | const sourceHash = `//# sourceHash=${crypto.createHash('sha256').update(modifiedSrc as unknown as string).digest('hex')}`; 36 | const compiledFile = url.includes('/src/') 37 | ? fileURLToPath( 38 | url 39 | .replace(/\.tsx?$/, '.js') 40 | .replace('/src/', '/build-loader/') 41 | ) 42 | : null; 43 | 44 | if (loaderOptions.isCached && compiledFile && fs.existsSync(compiledFile)) { 45 | const compiled = fs.readFileSync(compiledFile, 'utf-8'); 46 | 47 | if (compiled.includes(sourceHash)) { 48 | return { 49 | format: 'module', 50 | source: compiled 51 | }; 52 | } 53 | } 54 | 55 | // compile via typescript 56 | const { outputText } = ts.transpileModule(modifiedSrc.toString(), { 57 | compilerOptions: { 58 | ...( 59 | url.endsWith('.tsx') 60 | ? { jsx: ts.JsxEmit.ReactJSX } 61 | : {} 62 | ), 63 | esModuleInterop: true, 64 | importHelpers: true, 65 | inlineSourceMap: true, 66 | module: ts.ModuleKind.ESNext, 67 | moduleResolution: ts.ModuleResolutionKind.NodeNext, 68 | skipLibCheck: true, 69 | // Aligns with packages/dev/scripts/polkadot-dev-build-ts & packages/dev/config/tsconfig 70 | target: ts.ScriptTarget.ES2022 71 | }, 72 | fileName: fileURLToPath(url) 73 | }); 74 | 75 | if (loaderOptions.isCached && compiledFile) { 76 | const compiledDir = path.dirname(compiledFile); 77 | 78 | if (!fs.existsSync(compiledDir)) { 79 | fs.mkdirSync(compiledDir, { recursive: true }); 80 | } 81 | 82 | fs.writeFileSync(compiledFile, `${outputText}\n${sourceHash}`, 'utf-8'); 83 | } 84 | 85 | return { 86 | format: 'module', 87 | source: outputText 88 | }; 89 | } 90 | 91 | return nextLoad(url, context); 92 | } 93 | -------------------------------------------------------------------------------- /packages/dev-ts/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/dev-ts 2 | 3 | This is an Node TS loader, specifically written to cater for the polkadot-js needs, aka it is meant to be used inside polkadot-js projects. It doesn't aim to be a catch-all resolver, although it does cover quite a large spectrum of functionality. 4 | 5 | It caters for - 6 | 7 | 1. Pass through resolution and compiling of .ts & .tsx sources 8 | 2. Resolution of TS aliases 9 | 3. Resolution of .json files (alongside aliases) 10 | 4. Resolution of extensionless imports (basic, best-effort) 11 | 12 | 13 | ## Usage 14 | 15 | Just add the loader via the Node.js `--loader` option. The API supported here is only for Node 16.12+, so ensure a new-ish LTS version is used. 16 | 17 | ``` 18 | node --loader @polkadot/dev-ts ... 19 | ``` 20 | 21 | Internally to the polkadot-js libraries, loader caching is used. This means that compiled files are store on-disk alongside the `/src/` folder in `/build-loader/`. To enable caching behavior, the loader endpoint is changed slightly, 22 | 23 | ``` 24 | node --loader @polkadot/dev-ts/cached ... 25 | ``` 26 | 27 | This is generally the suggested default, but it is only exposed via a different loader endpoint to ensure that users explicitly opt-in and not be suprised by "random output folders" being created. 28 | 29 | 30 | ## Caveats 31 | 32 | The Node.js loader API could change in the future (as it has in the Node.js 16.12 version), so it _may_ break or stop working on newer versions, and obviously won't work at all on older versions. As of this writing (Node.js 18.14 being the most-recent LTS), using the `--loader` option will print a warning. 33 | 34 | With all that said, it is used as-is for the polkadot-js test infrastructure and currently operates without issues in _that_ environment. 35 | 36 | TL;DR Different configs could yield some issues. 37 | 38 | 39 | ## Why 40 | 41 | Yes, there are other options available - [@babel/register](https://babeljs.io/docs/babel-register), [@esbuild-kit/esm-loader](https://github.com/esbuild-kit/esm-loader), [@swc/register](https://github.com/swc-project/register), [@swc-node/loader](https://github.com/swc-project/swc-node/tree/master/packages/loader), [ts-node/esm](https://github.com/TypeStrong/ts-node), ... 42 | 43 | We started off with a basic `swc` loader (after swapping the infrastructure from Jest & Babel), just due to the fact that (at that time, and as of writing still) the base swc loader is still a WIP against the newer loader APIs. Since we didn't want to add more dependencies (and compile differently to our internal compiler infrastructure), we [adapted our own](https://nodejs.org/api/esm.html#esm_transpiler_loader). 44 | 45 | Since then we just swapped to using base `tsc` everywhere (for all builds) and may look at changing again (swc, esbuild. etc...) in the future. So effectively having a single loader, while re-inventing the wheel somewhat (since there seems to be a _lot_ of options available) allows us to just keep the loader compiling options fully aligned with what TS -> JS output approach we take. 46 | 47 | It meets our requirements: aligns fully with the overall configs we accross polkadot-js, compiles to ESM (no CJS used when testing/running) and has minimal dependencies that doesn't add bloat. In most cases you would probably be better off with one of the loaders/registration approaches linked in the first paragraph. 48 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/jest.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { AnyFn, WithMock } from '../types.js'; 5 | 6 | import { mock } from 'node:test'; 7 | 8 | import { enhanceObj, stubObj, warnObj } from '../util.js'; 9 | 10 | // logged via Object.keys(jest).sort() 11 | const JEST_KEYS_STUB = ['advanceTimersByTime', 'advanceTimersToNextTimer', 'autoMockOff', 'autoMockOn', 'clearAllMocks', 'clearAllTimers', 'createMockFromModule', 'deepUnmock', 'disableAutomock', 'doMock', 'dontMock', 'enableAutomock', 'fn', 'genMockFromModule', 'getRealSystemTime', 'getSeed', 'getTimerCount', 'isEnvironmentTornDown', 'isMockFunction', 'isolateModules', 'isolateModulesAsync', 'mock', 'mocked', 'now', 'replaceProperty', 'requireActual', 'requireMock', 'resetAllMocks', 'resetModules', 'restoreAllMocks', 'retryTimes', 'runAllImmediates', 'runAllTicks', 'runAllTimers', 'runOnlyPendingTimers', 'setMock', 'setSystemTime', 'setTimeout', 'spyOn', 'unmock', 'unstable_mockModule', 'useFakeTimers', 'useRealTimers'] as const; 12 | 13 | const JEST_KEYS_WARN = ['setTimeout'] as const; 14 | 15 | // logged via Object.keys(jest.fn()).sort() 16 | const MOCK_KEYS_STUB = ['_isMockFunction', 'getMockImplementation', 'getMockName', 'mock', 'mockClear', 'mockImplementation', 'mockImplementationOnce', 'mockName', 'mockRejectedValue', 'mockRejectedValueOnce', 'mockReset', 'mockResolvedValue', 'mockResolvedValueOnce', 'mockRestore', 'mockReturnThis', 'mockReturnValue', 'mockReturnValueOnce', 'withImplementation'] as const; 17 | 18 | const jestStub = stubObj('jest', JEST_KEYS_STUB); 19 | const jestWarn = warnObj('jest', JEST_KEYS_WARN); 20 | const mockStub = stubObj('jest.fn()', MOCK_KEYS_STUB); 21 | 22 | /** 23 | * @internal 24 | * 25 | * This adds the mockReset and mockRestore functionality to any 26 | * spy or mock function 27 | **/ 28 | function extendMock (mocked: WithMock) { 29 | // We use the node:test mock here for casting below - however we 30 | // don't want this in any method signature since this is a private 31 | // types export, which could get us in "some" trouble 32 | // 33 | // Effectively the casts below ensure that our WithMock<*> aligns 34 | // on a high-level to what we use via private type... 35 | const spy = (mocked as unknown as ReturnType); 36 | 37 | return enhanceObj(enhanceObj(mocked, { 38 | mockImplementation: (fn: F): void => { 39 | spy.mock.mockImplementation(fn); 40 | }, 41 | mockImplementationOnce: (fn: F): void => { 42 | spy.mock.mockImplementationOnce(fn); 43 | }, 44 | mockReset: (): void => { 45 | spy.mock.resetCalls(); 46 | }, 47 | mockRestore: (): void => { 48 | spy.mock.restore(); 49 | } 50 | }), mockStub); 51 | } 52 | 53 | /** 54 | * Sets up the jest object. This is certainly not extensive, and probably 55 | * not quite meant to be (never say never). Rather this adds the functionality 56 | * that we use in the polkadot-js projects. 57 | **/ 58 | export function jest () { 59 | return { 60 | jest: enhanceObj(enhanceObj({ 61 | fn: (fn?: F) => extendMock(mock.fn(fn)), 62 | restoreAllMocks: () => { 63 | mock.reset(); 64 | }, 65 | spyOn: (obj: object, key: string) => extendMock(mock.method(obj, key as keyof typeof obj)) 66 | }, jestWarn), jestStub) 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /packages/dev-ts/src/resolver.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import path from 'node:path'; 7 | import { pathToFileURL } from 'node:url'; 8 | 9 | import { CWD_PATH } from './common.js'; 10 | import { resolveAlias, resolveExtBare, resolveExtJs, resolveExtJson, resolveExtTs } from './resolver.js'; 11 | 12 | const ROOT_URL = pathToFileURL(`${CWD_PATH}/`); 13 | const SRC_PATH = 'packages/dev/src'; 14 | const SRC_URL = pathToFileURL(`${CWD_PATH}/${SRC_PATH}/`); 15 | const INDEX_PATH = `${SRC_PATH}/index.ts`; 16 | const INDEX_URL = pathToFileURL(INDEX_PATH); 17 | 18 | describe('resolveExtTs', () => { 19 | it('returns no value for a non .{ts, tsx} extension', () => { 20 | expect( 21 | resolveExtTs(`./${SRC_PATH}/cjs/sample.js`, ROOT_URL) 22 | ).not.toBeDefined(); 23 | }); 24 | 25 | it('returns a correct object for a .ts extension', () => { 26 | expect( 27 | resolveExtTs(INDEX_PATH, ROOT_URL) 28 | ).toEqual({ 29 | format: 'module', 30 | shortCircuit: true, 31 | url: INDEX_URL.href 32 | }); 33 | }); 34 | }); 35 | 36 | describe('resolveExtJs', () => { 37 | const modFound = { 38 | format: 'module', 39 | shortCircuit: true, 40 | url: pathToFileURL(`${CWD_PATH}/${SRC_PATH}/mod.ts`).href 41 | }; 42 | 43 | it('returns the correct value for ./mod.js resolution', () => { 44 | expect( 45 | resolveExtJs('./mod.js', SRC_URL) 46 | ).toEqual(modFound); 47 | }); 48 | 49 | it('returns the correct value for ../mod.js resolution', () => { 50 | expect( 51 | resolveExtJs('../mod.js', pathToFileURL(`${CWD_PATH}/${SRC_PATH}/rootJs/index.ts`)) 52 | ).toEqual(modFound); 53 | }); 54 | 55 | it('returns a correct object for a .jsx extension', () => { 56 | expect( 57 | resolveExtJs(`./${SRC_PATH}/rootJs/Jsx.jsx`, ROOT_URL) 58 | ).toEqual({ 59 | format: 'module', 60 | shortCircuit: true, 61 | url: pathToFileURL(`${SRC_PATH}/rootJs/Jsx.tsx`).href 62 | }); 63 | }); 64 | }); 65 | 66 | describe('resolveExtJson', () => { 67 | it('resolves .json files', () => { 68 | expect( 69 | resolveExtJson('../package.json', SRC_URL) 70 | ).toEqual({ 71 | format: 'json', 72 | shortCircuit: true, 73 | url: pathToFileURL(path.join(SRC_PATH, '../package.json')).href 74 | }); 75 | }); 76 | }); 77 | 78 | describe('resolveExtBare', () => { 79 | const indexFound = { 80 | format: 'module', 81 | shortCircuit: true, 82 | url: INDEX_URL.href 83 | }; 84 | 85 | it('does not resolve non-relative paths', () => { 86 | expect( 87 | resolveExtBare(INDEX_PATH, ROOT_URL) 88 | ).not.toBeDefined(); 89 | }); 90 | 91 | it('resolves to the index via .', () => { 92 | expect( 93 | resolveExtBare('.', SRC_URL) 94 | ).toEqual(indexFound); 95 | }); 96 | 97 | it('resolves to the index via ./index', () => { 98 | expect( 99 | resolveExtBare('./index', SRC_URL) 100 | ).toEqual(indexFound); 101 | }); 102 | 103 | it('resolves to the sub-directory via ./rootJs', () => { 104 | expect( 105 | resolveExtBare('./rootJs', SRC_URL) 106 | ).toEqual({ 107 | format: 'module', 108 | shortCircuit: true, 109 | url: pathToFileURL(`${SRC_PATH}/rootJs/index.ts`).href 110 | }); 111 | }); 112 | 113 | it('resolves to extensionless path', () => { 114 | expect( 115 | resolveExtBare('./packageInfo', SRC_URL) 116 | ).toEqual({ 117 | format: 'module', 118 | shortCircuit: true, 119 | url: pathToFileURL(`${SRC_PATH}/packageInfo.ts`).href 120 | }); 121 | }); 122 | }); 123 | 124 | describe('resolveAliases', () => { 125 | it('resolves packageInfo', () => { 126 | expect( 127 | resolveAlias('@polkadot/dev-ts/packageInfo', ROOT_URL) 128 | ).toEqual({ 129 | format: 'module', 130 | shortCircuit: true, 131 | url: pathToFileURL('packages/dev-ts/src/packageInfo.ts').href 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /packages/dev/config/rollup.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import pluginAlias from '@rollup/plugin-alias'; 5 | import pluginCommonjs from '@rollup/plugin-commonjs'; 6 | import pluginDynamicImportVars from '@rollup/plugin-dynamic-import-vars'; 7 | import pluginInject from '@rollup/plugin-inject'; 8 | import pluginJson from '@rollup/plugin-json'; 9 | import { nodeResolve as pluginResolve } from '@rollup/plugin-node-resolve'; 10 | import fs from 'node:fs'; 11 | import path from 'node:path'; 12 | import pluginCleanup from 'rollup-plugin-cleanup'; 13 | 14 | /** @typedef {{ entries?: Record; external: string[]; globals?: Record; index?: string; inject?: Record; pkg: string; }} BundleDef */ 15 | /** @typedef {{ file: string; format: 'umd'; generatedCode: Record; globals: Record; inlineDynamicImports: true; intro: string; name: string; }} BundleOutput */ 16 | /** @typedef {{ context: 'global'; external: string[]; input: string; output: BundleOutput; plugins: any[]; }} Bundle */ 17 | 18 | /** 19 | * @param {string} pkg 20 | * @returns {string} 21 | */ 22 | function sanitizePkg (pkg) { 23 | return pkg.replace('@polkadot/', ''); 24 | } 25 | 26 | /** 27 | * @param {string} input 28 | * @returns {string} 29 | */ 30 | function createName (input) { 31 | return `polkadot-${sanitizePkg(input)}` 32 | .toLowerCase() 33 | .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase()); 34 | } 35 | 36 | /** 37 | * @param {string} pkg 38 | * @param {string} [index] 39 | * @returns {string} 40 | */ 41 | export function createInput (pkg, index) { 42 | const partialPath = `packages/${sanitizePkg(pkg)}/build`; 43 | 44 | return `${partialPath}/${ 45 | index || 46 | fs.existsSync(path.join(process.cwd(), partialPath, 'bundle.js')) 47 | ? 'bundle.js' 48 | : ( 49 | JSON.parse(fs.readFileSync(path.join(process.cwd(), partialPath, 'package.json'), 'utf8')).browser || 50 | 'index.js' 51 | ) 52 | }`; 53 | } 54 | 55 | /** 56 | * 57 | * @param {string} pkg 58 | * @param {string[]} external 59 | * @param {Record} globals 60 | * @returns {BundleOutput} 61 | */ 62 | export function createOutput (pkg, external, globals) { 63 | const name = sanitizePkg(pkg); 64 | 65 | return { 66 | file: `packages/${name}/build/bundle-polkadot-${name}.js`, 67 | format: 'umd', 68 | generatedCode: { 69 | constBindings: true 70 | }, 71 | globals: external.reduce((all, p) => ({ 72 | [p]: createName(p), 73 | ...all 74 | }), { ...globals }), 75 | // combine multi-chunk builds with dynamic imports 76 | inlineDynamicImports: true, 77 | // this is a mini x-global, determine where our context lies 78 | intro: 'const global = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : window;', 79 | name: createName(pkg) 80 | }; 81 | } 82 | 83 | /** 84 | * 85 | * @param {BundleDef} param0 86 | * @returns {Bundle} 87 | */ 88 | export function createBundle ({ entries = {}, external, globals = {}, index, inject = {}, pkg }) { 89 | return { 90 | // specify this (we define global in the output intro as globalThis || self || window) 91 | context: 'global', 92 | external, 93 | input: createInput(pkg, index), 94 | output: createOutput(pkg, external, globals), 95 | // NOTE The expect-error directives are due to rollup plugins, see 96 | // - https://github.com/rollup/plugins/issues/1488 97 | // - https://github.com/rollup/plugins/issues/1329 98 | plugins: [ 99 | // @ts-expect-error See the linked rollup issues above 100 | pluginAlias({ entries }), 101 | // @ts-expect-error See the linked rollup issues above 102 | pluginJson(), 103 | // @ts-expect-error See the linked rollup issues above 104 | pluginCommonjs(), 105 | // @ts-expect-error See the linked rollup issues above 106 | pluginDynamicImportVars(), 107 | // @ts-expect-error See the linked rollup issues above 108 | pluginInject(inject), 109 | pluginResolve({ browser: true }), 110 | pluginCleanup() 111 | ] 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /packages/dev-ts/src/tsconfig.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { TsAlias } from './types.js'; 5 | 6 | import JSON5 from 'json5'; 7 | import fs from 'node:fs'; 8 | import path from 'node:path'; 9 | import { pathToFileURL } from 'node:url'; 10 | 11 | import { CWD_PATH, CWD_URL, MOD_PATH } from './common.js'; 12 | 13 | interface JsonConfig { 14 | compilerOptions?: { 15 | baseUrl?: string; 16 | paths?: Record; 17 | }; 18 | extends?: string | string[]; 19 | } 20 | 21 | interface PartialConfig { 22 | paths: Record; 23 | url?: URL; 24 | } 25 | 26 | /** 27 | * @internal 28 | * 29 | * Extracts the (relevant) tsconfig info, also using extends 30 | **/ 31 | function readConfigFile (currentPath = CWD_PATH, tsconfig = 'tsconfig.json', fromFile?: string): PartialConfig { 32 | const configFile = path.join(currentPath, tsconfig); 33 | 34 | if (!fs.existsSync(configFile)) { 35 | console.warn(`No ${configFile}${fromFile ? ` (extended from ${fromFile})` : ''} found, assuming defaults`); 36 | 37 | return { paths: {} }; 38 | } 39 | 40 | try { 41 | const { compilerOptions, extends: parentConfig } = JSON5.parse(fs.readFileSync(configFile, 'utf8')); 42 | let url: URL | undefined; 43 | 44 | if (compilerOptions?.baseUrl) { 45 | const configDir = path.dirname(configFile); 46 | 47 | // the baseParentUrl is relative to the actual config file 48 | url = pathToFileURL(path.join(configDir, `${compilerOptions.baseUrl}/`)); 49 | } 50 | 51 | // empty paths if none are found 52 | let paths = compilerOptions?.paths || {}; 53 | 54 | if (parentConfig) { 55 | const allExtends = Array.isArray(parentConfig) 56 | ? parentConfig 57 | : [parentConfig]; 58 | 59 | for (const extendsPath of allExtends) { 60 | const extRoot = extendsPath.startsWith('.') 61 | ? currentPath 62 | : MOD_PATH; 63 | const extSubs = extendsPath.split(/[\\/]/); 64 | const extPath = path.join(extRoot, ...extSubs.slice(0, -1)); 65 | const extConfig = readConfigFile(extPath, extSubs.at(-1), configFile); 66 | 67 | // base configs are overridden by later configs, order here matters 68 | // FIXME The paths would be relative to the baseUrl at that point... for 69 | // now we don't care much since we define these 2 together in all @polkadot 70 | // configs, but it certainly _may_ create and issue at some point (for others) 71 | paths = { ...extConfig.paths, ...paths }; 72 | url = url || extConfig.url; 73 | } 74 | } 75 | 76 | return url 77 | ? { paths, url } 78 | : { paths }; 79 | } catch (error) { 80 | console.error(`FATAL: Error parsing ${configFile}:: ${(error as Error).message}`); 81 | 82 | throw error; 83 | } 84 | } 85 | 86 | /** 87 | * @internal 88 | * 89 | * Retrieves all TS aliases definitions 90 | **/ 91 | function extractAliases (): TsAlias[] { 92 | const { paths, url = CWD_URL } = readConfigFile(); 93 | 94 | return Object 95 | .entries(paths) 96 | .filter((kv): kv is [string, [string, ...string[]]] => !!kv[1].length) 97 | // TODO The path value is an array - we only handle the first entry in there, 98 | // this is a possible fix into the future if it is ever an issue... (may have 99 | // some impacts on the actual loader where only 1 alias is retrieved) 100 | .map(([key, [path]]) => { 101 | const filter = key.split(/[\\/]/); 102 | const isWildcard = filter.at(-1) === '*'; 103 | 104 | // ensure that when we have wilcards specified, they always occur in the last position 105 | if (filter.filter((f) => f.includes('*')).length !== (isWildcard ? 1 : 0)) { 106 | throw new Error(`FATAL: Wildcards in tsconfig.json path entries are only supported in the last position. Invalid ${key}: ${path} mapping`); 107 | } 108 | 109 | return { 110 | filter: isWildcard 111 | ? filter.slice(0, -1) 112 | : filter, 113 | isWildcard, 114 | path, 115 | url 116 | }; 117 | }); 118 | } 119 | 120 | /** We only export the aliases from the config */ 121 | export const tsAliases = extractAliases(); 122 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 0.84.2 4 | 5 | - Write the Buffer Import to necessary Deno build files 6 | - build(dev): ignore removing private fields 7 | 8 | 9 | ## 0.83.1 10 | 11 | - Parallelize the tests in `polkadot-exec-node-test` 12 | 13 | 14 | ## 0.82.1 15 | 16 | - Create seperate testLoader and testCached for Node v22 compatibility with JSON assertions 17 | 18 | 19 | ## 0.81.1 20 | 21 | - Duplicate .d.ts files into cjs 22 | 23 | 24 | ## 0.80.1 25 | 26 | - Typescript 5.5.4 27 | 28 | 29 | ## 0.79.4 30 | 31 | - fix: Add check for `deps` in topo sort 32 | 33 | 34 | ## 0.79.1 35 | 36 | - feat: add topological sorting to the packages when releasing 37 | 38 | 39 | ## 0.78.1 40 | 41 | - Update internal scripts to dedupe git & yarn commands 42 | - Update to latest yarn berry 4.0.2 43 | 44 | 45 | ## 0.77.1 46 | 47 | - Drop support for Node 16 48 | 49 | 50 | ## 0.76.1 51 | 52 | - Update to latest typescript-eslint (incl. new rulesets) 53 | 54 | 55 | ## 0.75.1 56 | 57 | - Swap eslint to flat config 58 | 59 | 60 | ## 0.74.1 61 | 62 | - Ensure correct structures for `tsconfig.*.json` 63 | 64 | 65 | ## 0.73.1 66 | 67 | - Drop support for Node 14 68 | 69 | 70 | ## 0.72.1 71 | 72 | - Split `@polkadot/dev-ts` & `@polkadot/dev-test` packages 73 | 74 | 75 | ## 0.71.1 76 | 77 | - Ensure all `src/*` has `.js` extensions (as per ESM, eslint rules, build updates) 78 | 79 | 80 | ## 0.70.1 81 | 82 | - Remove Babel (all compilation via tsc) 83 | 84 | 85 | ## 0.69.1 86 | 87 | - Remove Jest 88 | 89 | 90 | ## 0.68.1 91 | 92 | - 2023 93 | - Cleanup internally used script dependencies 94 | 95 | 96 | ## 0.67.1 97 | 98 | - Default to `esModuleInterop: false` 99 | 100 | 101 | ## 0.66.1 102 | 103 | - Output commonjs into `cjs/*` 104 | 105 | 106 | ## 0.65.1 107 | 108 | - 2022 109 | - Generate `detectPackage` template (with cjs `__dirname`) 110 | 111 | 112 | ## 0.64.1 113 | 114 | - Use tsconfig references and per-package TS build/lint 115 | 116 | 117 | ## 0.63.1 118 | 119 | - eslint 8 120 | 121 | 122 | ## 0.62.1 123 | 124 | - Swap default package build to esm with type: module 125 | 126 | 127 | ## 0.61.1 128 | 129 | - Build & publish both esm and cjs 130 | 131 | 132 | ## 0.60.1 133 | 134 | - Allow for both esm & cjs Babel config 135 | 136 | 137 | ## 0.59.1 138 | 139 | - Default to new React runtime preset (after React 16.14) 140 | 141 | 142 | ## 0.58.1 143 | 144 | - Drop vuepress dependency completely 145 | 146 | 147 | ## 0.57.1 148 | 149 | - Drop lerna dependency completely 150 | 151 | 152 | ## 0.56.1 153 | 154 | - Optional lerna in publish 155 | 156 | 157 | ## 0.55.3 158 | 159 | - Publish draft release 160 | 161 | 162 | ## 0.54.1 163 | 164 | - typescript-eslint 3 165 | 166 | 167 | ## 0.53.1 168 | 169 | - TypeScript 3.9 170 | 171 | 172 | ## 0.52.1 173 | 174 | - Stricter JSX rules 175 | 176 | 177 | ## 0.51.1 178 | 179 | - Arrow functions with () 180 | - JSX sample tests 181 | 182 | 183 | ## 0.50.1 184 | 185 | - Yarn 2 186 | 187 | 188 | ## 0.41.1 189 | 190 | - TypeScript 3.8.2 191 | - Extend Babel plugins with latest TS features 192 | 193 | 194 | ## 0.40.1 195 | 196 | - Remove `@polkadot/dev-react`, combine into `@polkadot/dev` 197 | - Move all user-facing (non-CI scripts) to JS, which makes cross-platform easier 198 | - Add `polkadot-dev-circular` script to extract circular deps 199 | 200 | 201 | ## 0.34.1 202 | 203 | - Bump deps 204 | 205 | 206 | ## 0.33.1 207 | 208 | - Package scoping checks, build & pre-publish 209 | - Allow `.skip-{npm,build}` files to control build 210 | - Bump deps 211 | 212 | 213 | ## 0.32.1 214 | 215 | - GitHub workflows 216 | - Don't publish this package as beta 217 | - Bump deps 218 | 219 | 220 | ## 0.31.1 221 | 222 | - TypeScript eslint 2 223 | - Bump deps 224 | 225 | 226 | ## 0.30.1 227 | 228 | - Swap to TypeScript eslint 229 | - Bump deps 230 | 231 | 232 | ## 0.29.1 233 | 234 | - Split deploy & build steps 235 | - Rename `yarn run check` to `yarn lint` 236 | - Bump deps 237 | 238 | 239 | ## 0.28.1 240 | 241 | - Remove `useBuiltins` from babel config (corejs) 242 | - Bump deps 243 | 244 | 245 | ## 0.27.1 246 | 247 | - Beta versions now publish with a `beta` tag 248 | 249 | 250 | ## 0.26.1 251 | 252 | - Publish `..-beta.x` versions from CI. This helps a lot with the stream of versions that arrise from merging. 253 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-version.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | import yargs from 'yargs'; 8 | 9 | import { execPm, exitFatal, logBin } from './util.mjs'; 10 | 11 | /** @typedef {{ dependencies?: Record; devDependencies?: Record; peerDependencies?: Record; optionalDependencies?: Record; resolutions?: Record; name?: string; stableVersion?: string; version: string; }} PkgJson */ 12 | 13 | const TYPES = ['major', 'minor', 'patch', 'pre']; 14 | 15 | const [type] = ( 16 | await yargs(process.argv.slice(2)) 17 | .demandCommand(1) 18 | .argv 19 | )._; 20 | 21 | if (typeof type !== 'string' || !TYPES.includes(type)) { 22 | exitFatal(`Invalid version bump "${type}", expected one of ${TYPES.join(', ')}`); 23 | } 24 | 25 | /** 26 | * @param {Record} dependencies 27 | * @param {string[]} others 28 | * @param {string} version 29 | * @returns {Record} 30 | */ 31 | function updateDependencies (dependencies, others, version) { 32 | return Object 33 | .entries(dependencies) 34 | .sort((a, b) => a[0].localeCompare(b[0])) 35 | .reduce((/** @type {Record} */ result, [key, value]) => { 36 | result[key] = others.includes(key) && value !== '*' 37 | ? value.startsWith('^') 38 | ? `^${version}` 39 | : version 40 | : value; 41 | 42 | return result; 43 | }, {}); 44 | } 45 | 46 | /** 47 | * @returns {[string, PkgJson]} 48 | */ 49 | function readCurrentPkgJson () { 50 | const rootPath = path.join(process.cwd(), 'package.json'); 51 | const rootJson = JSON.parse(fs.readFileSync(rootPath, 'utf8')); 52 | 53 | return [rootPath, rootJson]; 54 | } 55 | 56 | /** 57 | * @param {string} path 58 | * @param {unknown} json 59 | */ 60 | function writePkgJson (path, json) { 61 | fs.writeFileSync(path, `${JSON.stringify(json, null, 2)}\n`); 62 | } 63 | 64 | /** 65 | * 66 | * @param {string} version 67 | * @param {string[]} others 68 | * @param {string} pkgPath 69 | * @param {Record} json 70 | */ 71 | function updatePackage (version, others, pkgPath, json) { 72 | const updated = Object 73 | .keys(json) 74 | .reduce((/** @type {Record} */ result, key) => { 75 | if (key === 'version') { 76 | result[key] = version; 77 | } else if (['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'resolutions'].includes(key)) { 78 | result[key] = updateDependencies(json[key], others, version); 79 | } else if (key !== 'stableVersion') { 80 | result[key] = json[key]; 81 | } 82 | 83 | return result; 84 | }, {}); 85 | 86 | writePkgJson(pkgPath, updated); 87 | } 88 | 89 | function removeX () { 90 | const [rootPath, json] = readCurrentPkgJson(); 91 | 92 | if (!json.version?.endsWith('-x')) { 93 | return false; 94 | } 95 | 96 | json.version = json.version.replace('-x', ''); 97 | writePkgJson(rootPath, json); 98 | 99 | return true; 100 | } 101 | 102 | function addX () { 103 | const [rootPath, json] = readCurrentPkgJson(); 104 | 105 | if (json.version.endsWith('-x')) { 106 | return false; 107 | } 108 | 109 | json.version = json.version + '-x'; 110 | writePkgJson(rootPath, json); 111 | 112 | return true; 113 | } 114 | 115 | logBin('polkadot-dev-version'); 116 | 117 | const isX = removeX(); 118 | 119 | execPm(`version ${type === 'pre' ? 'prerelease' : type}`); 120 | 121 | if (isX && type === 'pre') { 122 | addX(); 123 | } 124 | 125 | const [rootPath, rootJson] = readCurrentPkgJson(); 126 | 127 | updatePackage(rootJson.version, [], rootPath, rootJson); 128 | 129 | // yarn workspaces does an OOM, manual looping takes ages 130 | if (fs.existsSync('packages')) { 131 | const packages = fs 132 | .readdirSync('packages') 133 | .map((dir) => path.join(process.cwd(), 'packages', dir, 'package.json')) 134 | .filter((pkgPath) => fs.existsSync(pkgPath)) 135 | .map((pkgPath) => [pkgPath, JSON.parse(fs.readFileSync(pkgPath, 'utf8'))]); 136 | const others = packages.map(([, json]) => json.name); 137 | 138 | packages.forEach(([pkgPath, json]) => { 139 | updatePackage(rootJson.version, others, pkgPath, json); 140 | }); 141 | } 142 | 143 | execPm('install'); 144 | -------------------------------------------------------------------------------- /packages/dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/dev/issues", 4 | "description": "A collection of shared CI scripts and development environment used by @polkadot projects", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/dev/tree/master/packages/dev#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/dev", 11 | "repository": { 12 | "directory": "packages/dev", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/dev.git" 15 | }, 16 | "sideEffects": false, 17 | "type": "module", 18 | "version": "0.84.2", 19 | "bin": { 20 | "polkadot-ci-ghact-build": "./scripts/polkadot-ci-ghact-build.mjs", 21 | "polkadot-ci-ghact-docs": "./scripts/polkadot-ci-ghact-docs.mjs", 22 | "polkadot-ci-ghpages-force": "./scripts/polkadot-ci-ghpages-force.mjs", 23 | "polkadot-dev-build-docs": "./scripts/polkadot-dev-build-docs.mjs", 24 | "polkadot-dev-build-ts": "./scripts/polkadot-dev-build-ts.mjs", 25 | "polkadot-dev-circular": "./scripts/polkadot-dev-circular.mjs", 26 | "polkadot-dev-clean-build": "./scripts/polkadot-dev-clean-build.mjs", 27 | "polkadot-dev-contrib": "./scripts/polkadot-dev-contrib.mjs", 28 | "polkadot-dev-copy-dir": "./scripts/polkadot-dev-copy-dir.mjs", 29 | "polkadot-dev-copy-to": "./scripts/polkadot-dev-copy-to.mjs", 30 | "polkadot-dev-deno-map": "./scripts/polkadot-dev-deno-map.mjs", 31 | "polkadot-dev-run-lint": "./scripts/polkadot-dev-run-lint.mjs", 32 | "polkadot-dev-run-node-ts": "./scripts/polkadot-dev-run-node-ts.mjs", 33 | "polkadot-dev-run-test": "./scripts/polkadot-dev-run-test.mjs", 34 | "polkadot-dev-version": "./scripts/polkadot-dev-version.mjs", 35 | "polkadot-dev-yarn-only": "./scripts/polkadot-dev-yarn-only.mjs", 36 | "polkadot-exec-eslint": "./scripts/polkadot-exec-eslint.mjs", 37 | "polkadot-exec-ghpages": "./scripts/polkadot-exec-ghpages.mjs", 38 | "polkadot-exec-ghrelease": "./scripts/polkadot-exec-ghrelease.mjs", 39 | "polkadot-exec-node-test": "./scripts/polkadot-exec-node-test.mjs", 40 | "polkadot-exec-rollup": "./scripts/polkadot-exec-rollup.mjs", 41 | "polkadot-exec-tsc": "./scripts/polkadot-exec-tsc.mjs", 42 | "polkadot-exec-webpack": "./scripts/polkadot-exec-webpack.mjs" 43 | }, 44 | "exports": { 45 | "./config/eslint": "./config/eslint.js", 46 | "./config/prettier.cjs": "./config/prettier.cjs", 47 | "./config/tsconfig.json": "./config/tsconfig.json", 48 | "./rootJs/dynamic.mjs": "./src/rootJs/dynamic.mjs", 49 | "./rootJs/testJson.json": "./src/rootJs/testJson.json" 50 | }, 51 | "dependencies": { 52 | "@eslint/js": "^8.56.0", 53 | "@polkadot/dev-test": "^0.84.2", 54 | "@polkadot/dev-ts": "^0.84.2", 55 | "@rollup/plugin-alias": "^5.1.1", 56 | "@rollup/plugin-commonjs": "^25.0.8", 57 | "@rollup/plugin-dynamic-import-vars": "^2.1.5", 58 | "@rollup/plugin-inject": "^5.0.5", 59 | "@rollup/plugin-json": "^6.1.0", 60 | "@rollup/plugin-node-resolve": "^15.3.1", 61 | "@tsconfig/strictest": "^2.0.2", 62 | "@typescript-eslint/eslint-plugin": "^6.19.1", 63 | "@typescript-eslint/parser": "^6.19.1", 64 | "eslint": "^8.56.0", 65 | "eslint-config-standard": "^17.1.0", 66 | "eslint-import-resolver-node": "^0.3.9", 67 | "eslint-import-resolver-typescript": "^3.6.1", 68 | "eslint-plugin-deprecation": "^2.0.0", 69 | "eslint-plugin-header": "^3.1.1", 70 | "eslint-plugin-import": "^2.29.1", 71 | "eslint-plugin-import-newlines": "^1.3.4", 72 | "eslint-plugin-jest": "^27.6.3", 73 | "eslint-plugin-n": "^16.6.2", 74 | "eslint-plugin-promise": "^6.1.1", 75 | "eslint-plugin-react": "^7.33.2", 76 | "eslint-plugin-react-hooks": "^4.6.0", 77 | "eslint-plugin-simple-import-sort": "^10.0.0", 78 | "eslint-plugin-sort-destructure-keys": "^1.5.0", 79 | "espree": "^9.6.1", 80 | "gh-pages": "^6.1.1", 81 | "gh-release": "^7.0.2", 82 | "globals": "^13.24.0", 83 | "json5": "^2.2.3", 84 | "madge": "^6.1.0", 85 | "rollup": "^4.9.6", 86 | "rollup-plugin-cleanup": "^3.2.1", 87 | "tslib": "^2.7.0", 88 | "typescript": "^5.5.4", 89 | "webpack": "^5.89.0", 90 | "webpack-cli": "^5.1.4", 91 | "webpack-dev-server": "^4.15.1", 92 | "webpack-merge": "^5.10.0", 93 | "webpack-subresource-integrity": "^5.2.0-rc.1", 94 | "yargs": "^17.7.2" 95 | }, 96 | "devDependencies": { 97 | "@testing-library/react": "^14.1.2", 98 | "@types/node": "^20.11.5", 99 | "@types/react": "^18.2.48", 100 | "@types/react-dom": "^18.2.18", 101 | "@types/yargs": "^17.0.32", 102 | "react": "^18.2.0", 103 | "react-dom": "^18.2.0", 104 | "react-is": "^18.2.0", 105 | "styled-components": "^6.1.8" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { JSDOM } from 'jsdom'; 5 | 6 | /** 7 | * Export a very basic JSDom environment - this is just enough so we have 8 | * @testing-environment/react tests passing in this repo 9 | * 10 | * FIXME: This approach is actually _explicitly_ discouraged by JSDOM - when 11 | * using window you should run the tests inside that context, instead of just 12 | * blindly relying on the globals as we do here 13 | */ 14 | export function browser () { 15 | const { window } = new JSDOM('', { url: 'http://localhost' }); 16 | 17 | return { 18 | // All HTML Elements that are defined on the JSDOM window object. 19 | // (we copied as-is from the types definition). We cannot get this 20 | // via Object.keys(window).filter(...) so we have to specify explicitly 21 | HTMLAnchorElement: window.HTMLAnchorElement, 22 | HTMLAreaElement: window.HTMLAreaElement, 23 | HTMLAudioElement: window.HTMLAudioElement, 24 | HTMLBRElement: window.HTMLBRElement, 25 | HTMLBaseElement: window.HTMLBaseElement, 26 | HTMLBodyElement: window.HTMLBodyElement, 27 | HTMLButtonElement: window.HTMLButtonElement, 28 | HTMLCanvasElement: window.HTMLCanvasElement, 29 | HTMLDListElement: window.HTMLDListElement, 30 | HTMLDataElement: window.HTMLDataElement, 31 | HTMLDataListElement: window.HTMLDataListElement, 32 | HTMLDetailsElement: window.HTMLDetailsElement, 33 | HTMLDialogElement: window.HTMLDialogElement, 34 | HTMLDirectoryElement: window.HTMLDirectoryElement, 35 | HTMLDivElement: window.HTMLDivElement, 36 | HTMLElement: window.HTMLElement, 37 | HTMLEmbedElement: window.HTMLEmbedElement, 38 | HTMLFieldSetElement: window.HTMLFieldSetElement, 39 | HTMLFontElement: window.HTMLFontElement, 40 | HTMLFormElement: window.HTMLFormElement, 41 | HTMLFrameElement: window.HTMLFrameElement, 42 | HTMLFrameSetElement: window.HTMLFrameSetElement, 43 | HTMLHRElement: window.HTMLHRElement, 44 | HTMLHeadElement: window.HTMLHeadElement, 45 | HTMLHeadingElement: window.HTMLHeadingElement, 46 | HTMLHtmlElement: window.HTMLHtmlElement, 47 | HTMLIFrameElement: window.HTMLIFrameElement, 48 | HTMLImageElement: window.HTMLImageElement, 49 | HTMLInputElement: window.HTMLInputElement, 50 | HTMLLIElement: window.HTMLLIElement, 51 | HTMLLabelElement: window.HTMLLabelElement, 52 | HTMLLegendElement: window.HTMLLegendElement, 53 | HTMLLinkElement: window.HTMLLinkElement, 54 | HTMLMapElement: window.HTMLMapElement, 55 | HTMLMarqueeElement: window.HTMLMarqueeElement, 56 | HTMLMediaElement: window.HTMLMediaElement, 57 | HTMLMenuElement: window.HTMLMenuElement, 58 | HTMLMetaElement: window.HTMLMetaElement, 59 | HTMLMeterElement: window.HTMLMeterElement, 60 | HTMLModElement: window.HTMLModElement, 61 | HTMLOListElement: window.HTMLOListElement, 62 | HTMLObjectElement: window.HTMLObjectElement, 63 | HTMLOptGroupElement: window.HTMLOptGroupElement, 64 | HTMLOptionElement: window.HTMLOptionElement, 65 | HTMLOutputElement: window.HTMLOutputElement, 66 | HTMLParagraphElement: window.HTMLParagraphElement, 67 | HTMLParamElement: window.HTMLParamElement, 68 | HTMLPictureElement: window.HTMLPictureElement, 69 | HTMLPreElement: window.HTMLPreElement, 70 | HTMLProgressElement: window.HTMLProgressElement, 71 | HTMLQuoteElement: window.HTMLQuoteElement, 72 | HTMLScriptElement: window.HTMLScriptElement, 73 | HTMLSelectElement: window.HTMLSelectElement, 74 | HTMLSlotElement: window.HTMLSlotElement, 75 | HTMLSourceElement: window.HTMLSourceElement, 76 | HTMLSpanElement: window.HTMLSpanElement, 77 | HTMLStyleElement: window.HTMLStyleElement, 78 | HTMLTableCaptionElement: window.HTMLTableCaptionElement, 79 | HTMLTableCellElement: window.HTMLTableCellElement, 80 | HTMLTableColElement: window.HTMLTableColElement, 81 | HTMLTableElement: window.HTMLTableElement, 82 | HTMLTableRowElement: window.HTMLTableRowElement, 83 | HTMLTableSectionElement: window.HTMLTableSectionElement, 84 | HTMLTemplateElement: window.HTMLTemplateElement, 85 | HTMLTextAreaElement: window.HTMLTextAreaElement, 86 | HTMLTimeElement: window.HTMLTimeElement, 87 | HTMLTitleElement: window.HTMLTitleElement, 88 | HTMLTrackElement: window.HTMLTrackElement, 89 | HTMLUListElement: window.HTMLUListElement, 90 | HTMLUnknownElement: window.HTMLUnknownElement, 91 | HTMLVideoElement: window.HTMLVideoElement, 92 | // normal service resumes, the base top-level names 93 | crypto: window.crypto, 94 | document: window.document, 95 | localStorage: window.localStorage, 96 | navigator: window.navigator, 97 | // window... 98 | window 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /packages/dev/config/eslint.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // @ts-expect-error No definition for this one 5 | import eslintJs from '@eslint/js'; 6 | import tsPlugin from '@typescript-eslint/eslint-plugin'; 7 | import tsParser from '@typescript-eslint/parser'; 8 | // @ts-expect-error No definition for this one 9 | import standardConfig from 'eslint-config-standard'; 10 | import deprecationPlugin from 'eslint-plugin-deprecation'; 11 | // @ts-expect-error No definition for this one 12 | import headerPlugin from 'eslint-plugin-header'; 13 | // @ts-expect-error No definition for this one 14 | import importPlugin from 'eslint-plugin-import'; 15 | // @ts-expect-error No definition for this one 16 | import importNewlinesPlugin from 'eslint-plugin-import-newlines'; 17 | // @ts-expect-error No definition for this one 18 | import jestPlugin from 'eslint-plugin-jest'; 19 | // @ts-expect-error No definition for this one 20 | import nPlugin from 'eslint-plugin-n'; 21 | // @ts-expect-error No definition for this one 22 | import promisePlugin from 'eslint-plugin-promise'; 23 | // @ts-expect-error No definition for this one 24 | import reactPlugin from 'eslint-plugin-react'; 25 | // @ts-expect-error No definition for this one 26 | import reactHooksPlugin from 'eslint-plugin-react-hooks'; 27 | // @ts-expect-error No definition for this one 28 | import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort'; 29 | // @ts-expect-error No definition for this one 30 | import sortDestructureKeysPlugin from 'eslint-plugin-sort-destructure-keys'; 31 | import globals from 'globals'; 32 | 33 | import { overrideAll, overrideJs, overrideJsx, overrideSpec } from './eslint.rules.js'; 34 | 35 | const EXT_JS = ['.cjs', '.js', '.mjs']; 36 | const EXT_TS = ['.ts', '.tsx']; 37 | const EXT_ALL = [...EXT_JS, ...EXT_TS]; 38 | 39 | /** 40 | * @internal 41 | * Converts a list of EXT_* defined above to globs 42 | * @param {string[]} exts 43 | * @returns {string[]} 44 | */ 45 | function extsToGlobs (exts) { 46 | return exts.map((e) => `**/*${e}`); 47 | } 48 | 49 | export default [ 50 | { 51 | ignores: [ 52 | '**/.github/', 53 | '**/.vscode/', 54 | '**/.yarn/', 55 | '**/build/', 56 | '**/build-*/', 57 | '**/coverage/' 58 | ] 59 | }, 60 | { 61 | languageOptions: { 62 | globals: { 63 | ...globals.browser, 64 | ...globals.node 65 | }, 66 | parser: tsParser, 67 | parserOptions: { 68 | ecmaVersion: 'latest', 69 | project: './tsconfig.eslint.json', 70 | sourceType: 'module', 71 | warnOnUnsupportedTypeScriptVersion: false 72 | } 73 | }, 74 | plugins: { 75 | '@typescript-eslint': tsPlugin, 76 | deprecation: deprecationPlugin, 77 | header: headerPlugin, 78 | import: importPlugin, 79 | 'import-newlines': importNewlinesPlugin, 80 | n: nPlugin, 81 | promise: promisePlugin, 82 | 'simple-import-sort': simpleImportSortPlugin, 83 | 'sort-destructure-keys': sortDestructureKeysPlugin 84 | }, 85 | settings: { 86 | 'import/extensions': EXT_ALL, 87 | 'import/parsers': { 88 | '@typescript-eslint/parser': EXT_TS, 89 | espree: EXT_JS 90 | }, 91 | 'import/resolver': { 92 | node: { 93 | extensions: EXT_ALL 94 | }, 95 | typescript: { 96 | project: './tsconfig.eslint.json' 97 | } 98 | } 99 | } 100 | }, 101 | { 102 | files: extsToGlobs(EXT_ALL), 103 | rules: { 104 | ...eslintJs.configs.recommended.rules, 105 | ...standardConfig.rules, 106 | ...tsPlugin.configs['recommended-type-checked'].rules, 107 | ...tsPlugin.configs['stylistic-type-checked'].rules, 108 | ...overrideAll 109 | } 110 | }, 111 | { 112 | files: extsToGlobs(EXT_JS), 113 | rules: { 114 | ...overrideJs 115 | } 116 | }, 117 | { 118 | files: [ 119 | '**/*.tsx', 120 | '**/use*.ts' 121 | ], 122 | plugins: { 123 | react: reactPlugin, 124 | 'react-hooks': reactHooksPlugin 125 | }, 126 | rules: { 127 | ...reactPlugin.configs.recommended.rules, 128 | ...reactHooksPlugin.configs.recommended.rules, 129 | ...overrideJsx 130 | }, 131 | settings: { 132 | react: { 133 | version: 'detect' 134 | } 135 | } 136 | }, 137 | { 138 | files: [ 139 | '**/*.spec.ts', 140 | '**/*.spec.tsx' 141 | ], 142 | languageOptions: { 143 | globals: { 144 | ...globals.jest 145 | } 146 | }, 147 | plugins: { 148 | jest: jestPlugin 149 | }, 150 | rules: { 151 | ...jestPlugin.configs.recommended.rules, 152 | ...overrideSpec 153 | }, 154 | settings: { 155 | jest: { 156 | version: 27 157 | } 158 | } 159 | } 160 | ]; 161 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-dev-run-test.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import process from 'node:process'; 6 | 7 | import { execNodeTs, exitFatal, exitFatalEngine, importPath, logBin, readdirSync } from './util.mjs'; 8 | 9 | // A & B are just helpers here and in the errors below 10 | const EXT_A = ['spec', 'test']; 11 | const EXT_B = ['ts', 'tsx', 'js', 'jsx', 'cjs', 'mjs']; 12 | 13 | // The actual extensions we are looking for 14 | const EXTS = EXT_A.reduce((/** @type {string[]} */ exts, s) => exts.concat(...EXT_B.map((e) => `.${s}.${e}`)), []); 15 | 16 | logBin('polkadot-dev-run-test'); 17 | 18 | exitFatalEngine(); 19 | 20 | const cmd = []; 21 | const nodeFlags = []; 22 | const filters = []; 23 | 24 | /** @type {Record} */ 25 | const filtersExcl = {}; 26 | /** @type {Record} */ 27 | const filtersIncl = {}; 28 | 29 | const args = process.argv.slice(2); 30 | let testEnv = 'node'; 31 | let isDev = false; 32 | 33 | for (let i = 0; i < args.length; i++) { 34 | switch (args[i]) { 35 | // when running inside a dev environment, specifically @polkadot/dev 36 | case '--dev-build': 37 | isDev = true; 38 | break; 39 | 40 | // environment, not passed-through 41 | case '--env': 42 | if (!['browser', 'node'].includes(args[++i])) { 43 | throw new Error(`Invalid --env ${args[i]}, expected 'browser' or 'node'`); 44 | } 45 | 46 | testEnv = args[i]; 47 | break; 48 | 49 | // internal flags with no params 50 | case '--bail': 51 | case '--console': 52 | cmd.push(args[i]); 53 | break; 54 | 55 | // internal flags, with params 56 | case '--logfile': 57 | cmd.push(args[i]); 58 | cmd.push(args[++i]); 59 | break; 60 | 61 | // node flags that could have additional params 62 | case '--import': 63 | case '--loader': 64 | case '--require': 65 | nodeFlags.push(args[i]); 66 | nodeFlags.push(args[++i]); 67 | break; 68 | 69 | // any other non-flag arguments are passed-through 70 | default: 71 | if (args[i].startsWith('-')) { 72 | throw new Error(`Unknown flag ${args[i]} found`); 73 | } 74 | 75 | filters.push(args[i]); 76 | 77 | if (args[i].startsWith('^')) { 78 | const key = args[i].slice(1); 79 | 80 | if (filtersIncl[key]) { 81 | delete filtersIncl[key]; 82 | } else { 83 | filtersExcl[key] = key.split(/[\\/]/); 84 | } 85 | } else { 86 | const key = args[i]; 87 | 88 | if (filtersExcl[key]) { 89 | delete filtersExcl[key]; 90 | } else { 91 | filtersIncl[key] = key.split(/[\\/]/); 92 | } 93 | } 94 | 95 | break; 96 | } 97 | } 98 | 99 | /** 100 | * @param {string[]} parts 101 | * @param {Record} filters 102 | * @returns {boolean} 103 | */ 104 | function applyFilters (parts, filters) { 105 | return Object 106 | .values(filters) 107 | .some((filter) => 108 | parts 109 | .map((_, i) => i) 110 | .filter((i) => 111 | filter[0].startsWith(':') 112 | ? parts[i].includes(filter[0].slice(1)) 113 | : filter.length === 1 114 | ? parts[i].startsWith(filter[0]) 115 | : parts[i] === filter[0] 116 | ) 117 | .some((start) => 118 | filter.every((f, i) => 119 | parts[start + i] && ( 120 | f.startsWith(':') 121 | ? parts[start + i].includes(f.slice(1)) 122 | : i === (filter.length - 1) 123 | ? parts[start + i].startsWith(f) 124 | : parts[start + i] === f 125 | ) 126 | ) 127 | ) 128 | ); 129 | } 130 | 131 | const files = readdirSync('packages', EXTS).filter((file) => { 132 | const parts = file.split(/[\\/]/); 133 | let isIncluded = true; 134 | 135 | if (Object.keys(filtersIncl).length) { 136 | isIncluded = applyFilters(parts, filtersIncl); 137 | } 138 | 139 | if (isIncluded && Object.keys(filtersExcl).length) { 140 | isIncluded = !applyFilters(parts, filtersExcl); 141 | } 142 | 143 | return isIncluded; 144 | }); 145 | 146 | if (files.length === 0) { 147 | exitFatal(`No files matching *.{${EXT_A.join(', ')}}.{${EXT_B.join(', ')}} found${filters.length ? ` (filtering on ${filters.join(', ')})` : ''}`); 148 | } 149 | 150 | try { 151 | const allFlags = `${importPath('@polkadot/dev/scripts/polkadot-exec-node-test.mjs')} ${[...cmd, ...files].join(' ')}`; 152 | 153 | nodeFlags.push('--require'); 154 | nodeFlags.push( 155 | isDev 156 | ? `./packages/dev-test/build/cjs/${testEnv}.js` 157 | : `@polkadot/dev-test/${testEnv}` 158 | ); 159 | 160 | execNodeTs(allFlags, nodeFlags, false, isDev ? './packages/dev-ts/build/testCached.js' : '@polkadot/dev-ts/testCached'); 161 | } catch { 162 | process.exit(1); 163 | } 164 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/jest.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | describe('jest', () => { 5 | it('has been enhanced', () => { 6 | expect(jest.setTimeout).toBeDefined(); 7 | }); 8 | 9 | describe('.fn', () => { 10 | it('works on .toHaveBeenCalled', () => { 11 | const mock = jest.fn(() => 3); 12 | 13 | expect(mock).not.toHaveBeenCalled(); 14 | expect(mock()).toBe(3); 15 | expect(mock).toHaveBeenCalled(); 16 | }); 17 | 18 | it('works on .toHaveBeenCalledTimes', () => { 19 | const mock = jest.fn(() => 3); 20 | 21 | expect(mock()).toBe(3); 22 | expect(mock()).toBe(3); 23 | 24 | expect(mock).toHaveBeenCalledTimes(2); 25 | }); 26 | 27 | it('works with .toHaveBeenCalledWith', () => { 28 | const sum = jest.fn((a: number, b: number) => a + b); 29 | 30 | expect(sum(1, 2)).toBe(3); 31 | 32 | expect(sum).toHaveBeenCalledWith(1, 2); 33 | 34 | expect(sum(2, 3)).toBe(5); 35 | expect(sum(4, 5)).toBe(9); 36 | 37 | expect(sum).toHaveBeenCalledWith(1, 2); 38 | expect(sum).toHaveBeenCalledWith(2, 3); 39 | expect(sum).toHaveBeenCalledWith(4, 5); 40 | 41 | expect(sum).toHaveBeenLastCalledWith(4, 5); 42 | }); 43 | 44 | it('works with .toHaveBeenCalledWith & expect.objectContaining', () => { 45 | const test = jest.fn((a: unknown, b: unknown) => !!a && !!b); 46 | 47 | test({ a: 123, b: 'test' }, null); 48 | 49 | expect(test).toHaveBeenLastCalledWith({ a: 123, b: 'test' }, null); 50 | expect(test).toHaveBeenLastCalledWith(expect.objectContaining({}), null); 51 | expect(test).toHaveBeenLastCalledWith(expect.objectContaining({ a: 123 }), null); 52 | expect(test).toHaveBeenLastCalledWith(expect.objectContaining({ b: 'test' }), null); 53 | }); 54 | 55 | it('allows .mockImplementation', () => { 56 | const mock = jest.fn(() => 3); 57 | 58 | expect(mock()).toBe(3); 59 | 60 | mock.mockImplementation(() => 4); 61 | 62 | expect(mock()).toBe(4); 63 | expect(mock()).toBe(4); 64 | }); 65 | 66 | it('allows .mockImplementationOnce', () => { 67 | const mock = jest.fn(() => 3); 68 | 69 | expect(mock()).toBe(3); 70 | 71 | mock.mockImplementationOnce(() => 4); 72 | 73 | expect(mock()).toBe(4); 74 | expect(mock()).toBe(3); 75 | }); 76 | 77 | it('allows resets', () => { 78 | const mock = jest.fn(() => 3); 79 | 80 | expect(mock).not.toHaveBeenCalled(); 81 | expect(mock()).toBe(3); 82 | expect(mock).toHaveBeenCalled(); 83 | 84 | mock.mockReset(); 85 | 86 | expect(mock).not.toHaveBeenCalled(); 87 | expect(mock()).toBe(3); 88 | expect(mock).toHaveBeenCalled(); 89 | }); 90 | }); 91 | 92 | describe('.spyOn', () => { 93 | it('works on .toHaveBeenCalled', () => { 94 | const obj = { 95 | add: (a: number, b: number) => a + b 96 | }; 97 | const spy = jest.spyOn(obj, 'add'); 98 | 99 | expect(spy).not.toHaveBeenCalled(); 100 | expect(obj.add(1, 2)).toBe(3); 101 | expect(spy).toHaveBeenCalledTimes(1); 102 | }); 103 | 104 | it('allows .mockImplementation', () => { 105 | const obj = { 106 | add: (a: number, b: number) => a + b 107 | }; 108 | const spy = jest.spyOn(obj, 'add'); 109 | 110 | expect(obj.add(1, 2)).toBe(3); 111 | expect(spy).toHaveBeenCalledTimes(1); 112 | 113 | spy.mockImplementation(() => 4); 114 | 115 | expect(obj.add(1, 2)).toBe(4); 116 | expect(spy).toHaveBeenCalledTimes(2); 117 | expect(obj.add(1, 2)).toBe(4); 118 | expect(spy).toHaveBeenCalledTimes(3); 119 | }); 120 | 121 | it('allows .mockImplementationOnce', () => { 122 | const obj = { 123 | add: (a: number, b: number) => a + b 124 | }; 125 | const spy = jest.spyOn(obj, 'add'); 126 | 127 | expect(obj.add(1, 2)).toBe(3); 128 | expect(spy).toHaveBeenCalledTimes(1); 129 | 130 | spy.mockImplementationOnce(() => 4); 131 | 132 | expect(obj.add(1, 2)).toBe(4); 133 | expect(spy).toHaveBeenCalledTimes(2); 134 | expect(obj.add(1, 2)).toBe(3); 135 | expect(spy).toHaveBeenCalledTimes(3); 136 | }); 137 | 138 | it('allows resets', () => { 139 | const obj = { 140 | add: (a: number, b: number) => a + b 141 | }; 142 | const spy = jest.spyOn(obj, 'add'); 143 | 144 | expect(spy).not.toHaveBeenCalled(); 145 | expect(obj.add(1, 2)).toBe(3); 146 | expect(spy).toHaveBeenCalledTimes(1); 147 | 148 | spy.mockReset(); 149 | 150 | expect(spy).not.toHaveBeenCalled(); 151 | expect(obj.add(1, 2)).toBe(3); 152 | expect(spy).toHaveBeenCalledTimes(1); 153 | }); 154 | 155 | it('allows restores', () => { 156 | const obj = { 157 | add: (a: number, b: number) => a + b 158 | }; 159 | const spy = jest.spyOn(obj, 'add'); 160 | 161 | expect(spy).not.toHaveBeenCalled(); 162 | expect(obj.add(1, 2)).toBe(3); 163 | expect(spy).toHaveBeenCalledTimes(1); 164 | 165 | spy.mockRestore(); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /scripts/all-deps.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | 8 | /** @typedef {{ dependencies: Record; devDependencies: Record; peerDependencies: Record; optionalDependencies: Record; resolutions: Record; name: string; version: string; versions: { git: string; npm: string; } }} PkgJson */ 9 | 10 | // The keys to look for in package.json 11 | const PKG_PATHS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'resolutions']; 12 | 13 | const versions = {}; 14 | const paths = []; 15 | let updated = 0; 16 | 17 | /** 18 | * Returns the path of the package.json inside the supplied directory 19 | * 20 | * @param {string} dir 21 | * @returns {string} 22 | */ 23 | function packageJsonPath (dir) { 24 | return path.join(dir, 'package.json'); 25 | } 26 | 27 | /** 28 | * Update the supplied dependency map with the latest (version map) versions 29 | * 30 | * @param {string} dir 31 | * @param {boolean} hasDirLogged 32 | * @param {string} key 33 | * @param {Record} dependencies 34 | * @returns {[number, Record]} 35 | */ 36 | function updateDependencies (dir, hasDirLogged, key, dependencies) { 37 | let count = 0; 38 | const adjusted = Object 39 | .keys(dependencies) 40 | .sort() 41 | .reduce((result, name) => { 42 | const current = dependencies[name]; 43 | const version = !current.startsWith('^') || current.endsWith('-x') 44 | ? current 45 | : versions[name] || current; 46 | 47 | if (version !== current) { 48 | if (count === 0) { 49 | if (!hasDirLogged) { 50 | console.log('\t', dir); 51 | } 52 | 53 | console.log('\t\t', key); 54 | } 55 | 56 | console.log('\t\t\t', name.padStart(30), '\t', current.padStart(8), '->', version); 57 | count++; 58 | updated++; 59 | } 60 | 61 | result[name] = version; 62 | 63 | return result; 64 | }, {}); 65 | 66 | return [count, adjusted]; 67 | } 68 | 69 | /** 70 | * Returns a parsed package.json 71 | * 72 | * @param {string} dir 73 | * @returns {PkgJson} 74 | */ 75 | function parsePackage (dir) { 76 | return JSON.parse( 77 | fs.readFileSync(packageJsonPath(dir), 'utf-8') 78 | ); 79 | } 80 | 81 | /** 82 | * Outputs the supplied package.json 83 | * 84 | * @param {string} dir 85 | * @param {Record} json 86 | */ 87 | function writePackage (dir, json) { 88 | fs.writeFileSync(packageJsonPath(dir), `${JSON.stringify(json, null, 2)}\n`); 89 | } 90 | 91 | /** 92 | * Rerite the package.json with updated dependecies 93 | * 94 | * @param {string} dir 95 | */ 96 | function updatePackage (dir) { 97 | const json = parsePackage(dir); 98 | let hasDirLogged = false; 99 | 100 | writePackage(dir, Object 101 | .keys(json) 102 | .reduce((result, key) => { 103 | if (PKG_PATHS.includes(key)) { 104 | const [count, adjusted] = updateDependencies(dir, hasDirLogged, key, json[key]); 105 | 106 | result[key] = adjusted; 107 | 108 | if (count) { 109 | hasDirLogged = true; 110 | } 111 | } else { 112 | result[key] = json[key]; 113 | } 114 | 115 | return result; 116 | }, {}) 117 | ); 118 | } 119 | 120 | /** 121 | * Loop through package/*, extracting the package names and their versions 122 | * 123 | * @param {string} dir 124 | */ 125 | function findPackages (dir) { 126 | const pkgsDir = path.join(dir, 'packages'); 127 | 128 | paths.push(dir); 129 | 130 | if (!fs.existsSync(pkgsDir)) { 131 | return; 132 | } 133 | 134 | const { versions: { npm: lastVersion } } = parsePackage(dir); 135 | 136 | fs 137 | .readdirSync(pkgsDir) 138 | .filter((entry) => { 139 | const full = path.join(pkgsDir, entry); 140 | 141 | return !['.', '..'].includes(entry) && 142 | fs.lstatSync(full).isDirectory() && 143 | fs.existsSync(path.join(full, 'package.json')); 144 | }) 145 | .forEach((dir) => { 146 | const full = path.join(pkgsDir, dir); 147 | const pkgJson = parsePackage(full); 148 | 149 | paths.push(full); 150 | versions[pkgJson.name] = `^${lastVersion}`; 151 | 152 | // for dev we want to pull through the additionals, i.e. we want to 153 | // align deps found in dev/others with those in dev as master 154 | if (pkgJson.name === '@polkadot/dev') { 155 | PKG_PATHS.forEach((depPath) => { 156 | Object 157 | .entries(pkgJson[depPath] || {}) 158 | .filter(([, version]) => version.startsWith('^')) 159 | .forEach(([pkg, version]) => { 160 | versions[pkg] ??= version; 161 | }); 162 | }); 163 | } 164 | }); 165 | } 166 | 167 | console.log('Extracting ...'); 168 | 169 | fs 170 | .readdirSync('.') 171 | .filter((name) => 172 | !['.', '..'].includes(name) && 173 | fs.existsSync(packageJsonPath(name)) 174 | ) 175 | .sort() 176 | .forEach(findPackages); 177 | 178 | console.log('\t', Object.keys(versions).length, 'packages found'); 179 | 180 | console.log('Updating ...'); 181 | 182 | paths.forEach(updatePackage); 183 | 184 | console.log('\t', updated, 'versions adjusted'); 185 | -------------------------------------------------------------------------------- /packages/dev/src/rootEsm.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import fs from 'node:fs'; 7 | import path from 'node:path'; 8 | 9 | import * as testRoot from './root.js'; 10 | import { runTests } from './rootTests.js'; 11 | 12 | runTests(testRoot); 13 | 14 | describe('as-built output checks', (): void => { 15 | const buildRoot = path.join(process.cwd(), 'packages/dev/build'); 16 | const buildFiles = fs.readdirSync(buildRoot); 17 | 18 | describe('build outputs', (): void => { 19 | it('does not contain the *.spec.ts/js files', (): void => { 20 | expect( 21 | buildFiles.filter((f) => f.includes('.spec.')) 22 | ).toEqual([]); 23 | }); 24 | 25 | it('does not contain the rootRust folder', (): void => { 26 | expect( 27 | buildFiles.filter((f) => f.includes('rootRust')) 28 | ).toEqual([]); 29 | }); 30 | 31 | it('has the static files copied (non-duplicated)', (): void => { 32 | expect( 33 | fs.existsSync(path.join(buildRoot, 'rootStatic/kusama.svg')) 34 | ).toBe(true); 35 | expect( 36 | fs.existsSync(path.join(buildRoot, 'cjs/rootStatic/kusama.svg')) 37 | ).toBe(false); 38 | }); 39 | 40 | it('does not have stand-alone d.ts files copied', (): void => { 41 | expect( 42 | fs.existsSync(path.join(buildRoot, 'rootJs/test.json.d.ts')) 43 | ).toBe(false); 44 | }); 45 | 46 | it('does have cjs + d.ts files copied', (): void => { 47 | expect( 48 | fs.existsSync(path.join(process.cwd(), 'packages/dev-test/build/globals.d.ts')) 49 | ).toBe(true); 50 | }); 51 | }); 52 | 53 | describe('code generation', (): void => { 54 | const jsIdx = { 55 | cjs: fs.readFileSync(path.join(buildRoot, 'cjs/rootJs/index.js'), { encoding: 'utf-8' }), 56 | esm: fs.readFileSync(path.join(buildRoot, 'rootJs/index.js'), { encoding: 'utf-8' }) 57 | } as const; 58 | const idxTypes = Object.keys(jsIdx) as (keyof typeof jsIdx)[]; 59 | 60 | describe('numeric seperators', (): void => { 61 | idxTypes.forEach((type) => 62 | it(`does not conatin them & has the value in ${type}`, (): void => { 63 | expect( 64 | jsIdx[type].includes('123_456_789n') 65 | ).toBe(false); 66 | expect( 67 | jsIdx[type].includes('123456789n') 68 | ).toBe(true); 69 | }) 70 | ); 71 | }); 72 | 73 | describe('dynamic imports', (): void => { 74 | idxTypes.forEach((type) => 75 | it(`contains import(...) in ${type}`, (): void => { 76 | expect( 77 | jsIdx[type].includes("const { sum } = await import('@polkadot/dev/rootJs/dynamic.mjs');") 78 | ).toBe(true); 79 | }) 80 | ); 81 | }); 82 | 83 | describe('type assertions', (): void => { 84 | idxTypes.forEach((type) => 85 | it(`contains import(...) in ${type}`, (): void => { 86 | expect( 87 | jsIdx[type].includes( 88 | type === 'cjs' 89 | ? 'require("@polkadot/dev/rootJs/testJson.json")' 90 | // eslint-disable-next-line no-useless-escape 91 | : "import testJson from '@polkadot/dev/rootJs/testJson.json' assert { type: \'json\' };" 92 | ) 93 | ).toBe(true); 94 | }) 95 | ); 96 | }); 97 | }); 98 | 99 | describe('commonjs', (): void => { 100 | const cjsRoot = path.join(buildRoot, 'cjs'); 101 | 102 | it('contains commonjs package.js inside cjs', (): void => { 103 | expect( 104 | fs 105 | .readFileSync(path.join(cjsRoot, 'package.json'), { encoding: 'utf-8' }) 106 | .includes('"type": "commonjs"') 107 | ).toBe(true); 108 | }); 109 | 110 | it('contains cjs/sample.js', (): void => { 111 | expect( 112 | fs 113 | .readFileSync(path.join(cjsRoot, 'sample.js'), { encoding: 'utf-8' }) 114 | .includes("module.exports = { foo: 'bar' };") 115 | ).toBe(true); 116 | }); 117 | }); 118 | 119 | describe('deno', (): void => { 120 | const denoRoot = path.join(process.cwd(), 'packages/dev/build-deno'); 121 | const denoMod = fs.readFileSync(path.join(denoRoot, 'mod.ts'), 'utf-8'); 122 | 123 | it('has *.ts imports', (): void => { 124 | expect( 125 | denoMod.includes("import './index.ts';") 126 | ).toBe(true); 127 | }); 128 | 129 | it('has node: imports', (): void => { 130 | expect( 131 | denoMod.includes("import nodeCrypto from 'node:crypto';") 132 | ).toBe(true); 133 | }); 134 | 135 | it('has deno.land/x imports', (): void => { 136 | expect( 137 | fs 138 | .readFileSync(path.join(denoRoot, 'rootJs/augmented.ts')) 139 | .includes("declare module 'https://deno.land/x/polkadot/dev/types.ts' {") 140 | ).toBe(true); 141 | }); 142 | 143 | // See https://github.com/denoland/deno/issues/18557 144 | // NOTE: When available, the toBe(false) should be toBe(true) 145 | describe.todo('npm: prefixes', (): void => { 146 | it('has npm: imports', (): void => { 147 | expect( 148 | /import rollupAlias from 'npm:@rollup\/plugin-alias@\^\d\d?\.\d\d?\.\d\d?';/.test(denoMod) 149 | ).toBe(false); // true); 150 | }); 151 | 152 | it('has npm: imports with paths', (): void => { 153 | expect( 154 | /import eslint from 'npm:eslint@\^\d\d?\.\d\d?\.\d\d?\/use-at-your-own-risk';/.test(denoMod) 155 | ).toBe(false); // true); 156 | }); 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /packages/dev-test/src/env/expect.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-test authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | describe('expect', () => { 5 | it('has been decorated', () => { 6 | expect(expect(true).not).toBeDefined(); 7 | }); 8 | 9 | it('throws on unimplemented', () => { 10 | expect( 11 | () => expect(true).not.toHaveReturnedWith() 12 | ).toThrow('expect(...).not.toHaveReturnedWith has not been implemented'); 13 | }); 14 | 15 | it('throws on unimplemented (with alternative)', () => { 16 | expect( 17 | () => expect(true).not.toBeFalsy() 18 | ).toThrow('expect(...).not.toBeFalsy has not been implemented (Use expect(...).toBeTruthy instead)'); 19 | }); 20 | 21 | describe('rejects', () => { 22 | it('matches a rejection via .toThrow', async () => { 23 | await expect( 24 | Promise.reject(new Error('this is a rejection message')) 25 | ).rejects.toThrow(/rejection/); 26 | }); 27 | }); 28 | 29 | describe('.toBeDefined', () => { 30 | it('does not throw on null', () => { 31 | expect(null).toBeDefined(); 32 | }); 33 | 34 | it('throws on undefined', () => { 35 | expect( 36 | () => expect(undefined).toBeDefined() 37 | ).toThrow(); 38 | }); 39 | 40 | it('.not does not throw on undefined', () => { 41 | expect(undefined).not.toBeDefined(); 42 | }); 43 | }); 44 | 45 | describe('.toThrow', () => { 46 | const thrower = () => { 47 | throw new Error('some error'); 48 | }; 49 | 50 | it('matches error with empty throw', () => { 51 | expect(thrower).toThrow(); 52 | }); 53 | 54 | it('matches error with exact message', () => { 55 | expect(thrower).toThrow('some error'); 56 | }); 57 | 58 | it('matches error with regex message', () => { 59 | expect(thrower).toThrow(/me er/); 60 | }); 61 | 62 | it('handles .not correctly (no throw, empty message)', () => { 63 | expect(() => undefined).not.toThrow(); 64 | }); 65 | 66 | it('handles .not correctly (no throw, regex match)', () => { 67 | expect(() => undefined).not.toThrow(/me er/); 68 | }); 69 | 70 | it('handles .not correctly (throw, string match)', () => { 71 | expect(() => undefined).not.toThrow('no match'); 72 | }); 73 | 74 | it('handles .not correctly (throw, regex match)', () => { 75 | expect(() => undefined).not.toThrow(/no match/); 76 | }); 77 | }); 78 | 79 | describe('.toMatch', () => { 80 | it('fails matching when non-object passed in', () => { 81 | expect( 82 | () => expect(undefined).toMatch(/match/) 83 | ).toThrow(/Expected string/); 84 | }); 85 | 86 | it('fails matching when non-matching string passed in', () => { 87 | expect( 88 | () => expect('some').toMatch(/match/) 89 | ).toThrow(/did not match/); 90 | }); 91 | 92 | it('matches string passed', () => { 93 | expect( 94 | () => expect('matching').toMatch(/match/) 95 | ).not.toThrow(); 96 | }); 97 | }); 98 | 99 | describe('.toMatchObject', () => { 100 | it('fails matching when non-object passed in', () => { 101 | expect( 102 | () => expect(undefined).toMatchObject({ foo: 'bar' }) 103 | ).toThrow(/Expected object/); 104 | }); 105 | 106 | it('matches empty object', () => { 107 | expect({ 108 | a: 'foo', 109 | b: 'bar' 110 | }).toMatchObject({}); 111 | }); 112 | 113 | it('matches object with some fields', () => { 114 | expect({ 115 | a: 'foo', 116 | b: 'bar', 117 | c: 123, 118 | d: [456, 789] 119 | }).toMatchObject({ 120 | a: 'foo', 121 | c: 123, 122 | d: [456, 789] 123 | }); 124 | }); 125 | 126 | it('matches an object with some expect.stringMatching supplied', () => { 127 | expect({ 128 | a: 'foo bar', 129 | b: 'baz', 130 | c: 'zaz' 131 | }).toMatchObject({ 132 | a: expect.stringMatching(/o b/), 133 | b: expect.stringMatching('baz'), 134 | c: 'zaz' 135 | }); 136 | }); 137 | 138 | it('matches an object with expect.any supplied', () => { 139 | expect({ 140 | a: 123, 141 | b: Boolean(true), 142 | c: 'foo' 143 | }).toMatchObject({ 144 | a: expect.any(Number), 145 | b: expect.any(Boolean), 146 | c: 'foo' 147 | }); 148 | }); 149 | 150 | it('does not match an object with non instance value for expect.any', () => { 151 | expect( 152 | () => expect({ 153 | a: true, 154 | b: 'foo' 155 | }).toMatchObject({ 156 | a: expect.any(Number), 157 | b: 'foo' 158 | }) 159 | ).toThrow(/not an instance of Number/); 160 | }); 161 | 162 | it('matches an object with expect.anything supplied', () => { 163 | expect({ 164 | a: 123, 165 | b: 'foo' 166 | }).toMatchObject({ 167 | a: expect.anything(), 168 | b: 'foo' 169 | }); 170 | }); 171 | 172 | it('does not match an object with undefined value for expect.anything', () => { 173 | expect( 174 | () => expect({ 175 | b: 'foo' 176 | }).toMatchObject({ 177 | a: expect.anything(), 178 | b: 'foo' 179 | }) 180 | ).toThrow(/non-nullish/); 181 | }); 182 | 183 | it('does not match an object with non-array value', () => { 184 | expect( 185 | () => expect({ 186 | a: 'foo', 187 | b: 'bar' 188 | }).toMatchObject({ 189 | a: 'foo', 190 | b: [123, 456] 191 | }) 192 | ).toThrow(/Expected array value/); 193 | }); 194 | 195 | it('allows for deep matching', () => { 196 | expect({ 197 | a: 123, 198 | b: { 199 | c: 456, 200 | d: { 201 | e: 'foo', 202 | f: 'bar', 203 | g: { 204 | h: [789, { z: 'baz' }] 205 | } 206 | } 207 | } 208 | }).toMatchObject({ 209 | a: 123, 210 | b: { 211 | c: expect.any(Number), 212 | d: { 213 | f: 'bar', 214 | g: { 215 | h: [expect.any(Number), { z: 'baz' }] 216 | } 217 | } 218 | } 219 | }); 220 | }); 221 | }); 222 | }); 223 | -------------------------------------------------------------------------------- /packages/dev-ts/src/resolver.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev-ts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import fs from 'node:fs'; 5 | import path from 'node:path'; 6 | import { fileURLToPath, pathToFileURL, URL } from 'node:url'; 7 | 8 | import { CWD_URL, EXT_JS_REGEX, EXT_JSON_REGEX, EXT_TS_ARRAY, EXT_TS_REGEX } from './common.js'; 9 | import { tsAliases } from './tsconfig.js'; 10 | 11 | interface Resolved { 12 | format: 'commonjs' | 'json' | 'module'; 13 | shortCircuit?: boolean; 14 | url: string; 15 | } 16 | 17 | interface ResolverContext { 18 | parentURL?: string; 19 | } 20 | 21 | type Resolver = (specifier: string, context: ResolverContext) => Resolved | undefined; 22 | 23 | /** 24 | * @internal 25 | * 26 | * From a specified URL, extract the actual full path as well as the 27 | * directory that this path reflects (either equivalent to path or the 28 | * root of the file being referenced) 29 | */ 30 | function getParentPath (parentUrl: URL | string): { parentDir: string; parentPath: string; } { 31 | const parentPath = fileURLToPath(parentUrl); 32 | 33 | return { 34 | parentDir: fs.existsSync(parentPath) && fs.lstatSync(parentPath).isDirectory() 35 | ? parentPath 36 | : path.dirname(parentPath), 37 | parentPath 38 | }; 39 | } 40 | 41 | /** 42 | * @internal 43 | * 44 | * Resolve fully-specified imports with extensions. 45 | **/ 46 | export function resolveExtTs (specifier: string, parentUrl: URL | string): Resolved | void { 47 | // handle .ts extensions directly 48 | if (EXT_TS_REGEX.test(specifier)) { 49 | return { 50 | format: 'module', 51 | shortCircuit: true, 52 | url: new URL(specifier, parentUrl).href 53 | }; 54 | } 55 | } 56 | 57 | /** 58 | * @internal 59 | * 60 | * Resolve fully-specified imports with extensions. Here we cater for the TS 61 | * mapping of import foo from './bar.js' where only './bar.ts' exists 62 | **/ 63 | export function resolveExtJs (specifier: string, parentUrl: URL | string): Resolved | void { 64 | // handle ts imports where import *.js -> *.ts 65 | // (unlike the ts resolution, we only cater for relative paths) 66 | if (specifier.startsWith('.') && EXT_JS_REGEX.test(specifier)) { 67 | const full = fileURLToPath(new URL(specifier, parentUrl)); 68 | 69 | // when it doesn't exist, we try and see if a source replacement helps 70 | if (!fs.existsSync(full)) { 71 | const found = EXT_TS_ARRAY 72 | .map((e) => full.replace(EXT_JS_REGEX, e)) 73 | .find((f) => fs.existsSync(f) && fs.lstatSync(f).isFile()); 74 | 75 | if (found) { 76 | return { 77 | format: 'module', 78 | shortCircuit: true, 79 | url: pathToFileURL(found).href 80 | }; 81 | } 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * @internal 88 | * 89 | * Resolution for Json files. Generally these would be via path aliasing. 90 | */ 91 | export function resolveExtJson (specifier: string, parentUrl: URL | string): Resolved | void { 92 | if (specifier.startsWith('.') && EXT_JSON_REGEX.test(specifier)) { 93 | const { parentDir } = getParentPath(parentUrl); 94 | const jsonPath = path.join(parentDir, specifier); 95 | 96 | if (fs.existsSync(jsonPath)) { 97 | return { 98 | // .json needs to be in 'json' format for the loader, for the 99 | // the rest (it should only be TS) we use the 'module' format 100 | format: 'json', 101 | shortCircuit: true, 102 | url: pathToFileURL(jsonPath).href 103 | }; 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * @internal 110 | * 111 | * Resolve relative (extensionless) paths. 112 | * 113 | * At some point we probably might need to extend this to cater for the 114 | * ts (recommended) approach for using .js extensions inside the sources. 115 | * However, since we don't use this in the polkadot-js code, can kick this 116 | * down the line 117 | **/ 118 | export function resolveExtBare (specifier: string, parentUrl: URL | string): Resolved | void { 119 | if (specifier.startsWith('.')) { 120 | const { parentDir, parentPath } = getParentPath(parentUrl); 121 | const found = specifier === '.' 122 | ? ( 123 | // handle . imports for /index.ts 124 | EXT_TS_ARRAY 125 | .map((e) => path.join(parentDir, `index${e}`)) 126 | .find((f) => fs.existsSync(f)) || 127 | // handle the case where parentUrl needs an extension (generally via alias) 128 | EXT_TS_ARRAY 129 | .map((e) => `${parentPath}${e}`) 130 | .find((f) => fs.existsSync(f)) 131 | ) 132 | : ( 133 | // tests to see if this is a file (without extension) 134 | EXT_TS_ARRAY 135 | .map((e) => path.join(parentDir, `${specifier}${e}`)) 136 | .find((f) => fs.existsSync(f)) || 137 | // test to see if this is a directory 138 | EXT_TS_ARRAY 139 | .map((e) => path.join(parentDir, `${specifier}/index${e}`)) 140 | .find((f) => fs.existsSync(f)) 141 | ); 142 | 143 | if (found) { 144 | return { 145 | format: 'module', 146 | shortCircuit: true, 147 | url: pathToFileURL(found).href 148 | }; 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * @internal 155 | * 156 | * Resolve anything that is not an alias 157 | **/ 158 | export function resolveNonAlias (specifier: string, parentUrl: URL | string): Resolved | void { 159 | return ( 160 | resolveExtTs(specifier, parentUrl) || 161 | resolveExtJs(specifier, parentUrl) || 162 | resolveExtJson(specifier, parentUrl) || 163 | resolveExtBare(specifier, parentUrl) 164 | ); 165 | } 166 | 167 | /** 168 | * @internal 169 | * 170 | * Resolve TS alias mappings as defined in the tsconfig.json file 171 | **/ 172 | export function resolveAlias (specifier: string, _parentUrl: URL | string, aliases = tsAliases): Resolved | void { 173 | const parts = specifier.split(/[\\/]/); 174 | const found = aliases 175 | // return a [filter, [...partIndex]] mapping 176 | .map((alias) => ({ 177 | alias, 178 | indexes: parts 179 | .map((_, i) => i) 180 | .filter((start) => 181 | ( 182 | alias.isWildcard 183 | // parts should have more entries than the wildcard 184 | ? parts.length > alias.filter.length 185 | // or the same amount in case of a non-wildcard match 186 | : parts.length === alias.filter.length 187 | ) && 188 | // match all parts of the alias 189 | alias.filter.every((f, i) => 190 | parts[start + i] && 191 | parts[start + i] === f 192 | ) 193 | ) 194 | })) 195 | // we only return the first 196 | .find(({ indexes }) => indexes.length); 197 | 198 | if (found) { 199 | // do the actual un-aliased resolution 200 | return resolveNonAlias( 201 | `./${found.alias.path.replace('*', path.join(...parts.slice(found.alias.filter.length)))}`, 202 | found.alias.url 203 | ); 204 | } 205 | } 206 | 207 | /** 208 | * Resolves a path using our logic. 209 | * 210 | * 1. First we attempt to directly resolve if .ts/.tsx extension is found 211 | * 2. Then we do relative resolves (this is for extension-less .ts files) 212 | * 3. The we try to do resolution via TS aliases 213 | * 214 | * ... finally, try the next loader in the chain 215 | */ 216 | export function resolve (specifier: string, context: ResolverContext, nextResolve: Resolver) { 217 | const parentUrl = context.parentURL || CWD_URL; 218 | 219 | return ( 220 | resolveNonAlias(specifier, parentUrl) || 221 | resolveAlias(specifier, parentUrl) || 222 | nextResolve(specifier, context) 223 | ); 224 | } 225 | -------------------------------------------------------------------------------- /packages/dev/config/eslint.rules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/dev authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import JSON5 from 'json5'; 5 | import fs from 'node:fs'; 6 | import path from 'node:path'; 7 | import process from 'node:process'; 8 | 9 | const FIXME = { 10 | // This is in the new 6.0.0 and we should switch this on 11 | // at some point. For a first iteration we keep as-is 12 | '@typescript-eslint/prefer-nullish-coalescing': 'off' 13 | }; 14 | 15 | /** 16 | * Returns a copyright header pattern (using tsconfig.base.json) 17 | * 18 | * @returns {string} 19 | */ 20 | function getHeaderPattern () { 21 | const tsPath = path.join(process.cwd(), 'tsconfig.base.json'); 22 | 23 | if (!fs.existsSync(tsPath)) { 24 | throw new Error(`Unable to load ${tsPath}`); 25 | } 26 | 27 | const tsConfig = JSON5.parse(fs.readFileSync(tsPath, 'utf-8')); 28 | 29 | if (!tsConfig?.compilerOptions?.paths) { 30 | throw new Error(`Unable to extract compilerOptions.paths structure from ${tsPath}`); 31 | } 32 | 33 | const paths = Object.keys(tsConfig.compilerOptions.paths); 34 | 35 | if (!paths.length) { 36 | throw new Error(`No keys found in compilerOptions.paths from ${tsPath}`); 37 | } 38 | 39 | const packages = paths.reduce((packages, k) => { 40 | const [pd, pk] = k.split('/'); 41 | 42 | if (pd !== '@polkadot' || !pk) { 43 | throw new Error(`Non @polkadot path in ${tsPath}`); 44 | } 45 | 46 | return packages.length 47 | ? `${packages}|${pk}` 48 | : pk; 49 | }, ''); 50 | const fullyear = new Date().getFullYear(); 51 | const years = []; 52 | 53 | for (let i = 17, last = fullyear - 2000; i < last; i++) { 54 | years.push(`${i}`); 55 | } 56 | 57 | return ` Copyright 20(${years.join('|')})(-${fullyear})? @polkadot/(${packages})`; 58 | } 59 | 60 | export const overrideAll = { 61 | ...FIXME, 62 | // the next 2 enforce isolatedModules & verbatimModuleSyntax 63 | '@typescript-eslint/consistent-type-exports': 'error', 64 | '@typescript-eslint/consistent-type-imports': 'error', 65 | '@typescript-eslint/dot-notation': 'error', 66 | '@typescript-eslint/indent': ['error', 2], 67 | '@typescript-eslint/no-non-null-assertion': 'error', 68 | // ts itself checks and ignores those starting with _, align the linting 69 | '@typescript-eslint/no-unused-vars': ['error', { 70 | args: 'all', 71 | argsIgnorePattern: '^_', 72 | caughtErrors: 'all', 73 | caughtErrorsIgnorePattern: '^_', 74 | destructuredArrayIgnorePattern: '^_', 75 | vars: 'all', 76 | varsIgnorePattern: '^_' 77 | }], 78 | '@typescript-eslint/type-annotation-spacing': 'error', 79 | 'arrow-parens': ['error', 'always'], 80 | 'brace-style': ['error', '1tbs'], 81 | curly: ['error', 'all'], 82 | 'default-param-last': 'off', // conflicts with TS version 83 | 'deprecation/deprecation': 'error', 84 | 'dot-notation': 'off', // conflicts with TS version 85 | 'func-style': ['error', 'declaration', { 86 | allowArrowFunctions: true 87 | }], 88 | // this does help with declarations, but also 89 | // applies to invocations, which is an issue... 90 | // 'function-paren-newline': ['error', 'never'], 91 | 'function-call-argument-newline': ['error', 'consistent'], 92 | 'header/header': ['error', 'line', [ 93 | { pattern: getHeaderPattern() }, 94 | ' SPDX-License-Identifier: Apache-2.0' 95 | ], 2], 96 | 'import-newlines/enforce': ['error', { 97 | forceSingleLine: true, 98 | items: 2048 99 | }], 100 | 'import/export': 'error', 101 | 'import/extensions': ['error', 'ignorePackages', { 102 | cjs: 'always', 103 | js: 'always', 104 | json: 'always', 105 | jsx: 'never', 106 | mjs: 'always', 107 | ts: 'never', 108 | tsx: 'never' 109 | }], 110 | 'import/first': 'error', 111 | 'import/newline-after-import': 'error', 112 | 'import/no-duplicates': 'error', 113 | 'import/order': 'off', // conflicts with simple-import-sort 114 | indent: 'off', // required as 'off' since typescript-eslint has own versions 115 | 'no-extra-semi': 'error', 116 | 'no-unused-vars': 'off', 117 | 'no-use-before-define': 'off', 118 | 'object-curly-newline': ['error', { 119 | ExportDeclaration: { minProperties: 2048 }, 120 | ImportDeclaration: { minProperties: 2048 }, 121 | ObjectPattern: { minProperties: 2048 } 122 | }], 123 | 'padding-line-between-statements': [ 124 | 'error', 125 | { blankLine: 'always', next: '*', prev: ['const', 'let', 'var'] }, 126 | { blankLine: 'any', next: ['const', 'let', 'var'], prev: ['const', 'let', 'var'] }, 127 | { blankLine: 'always', next: 'block-like', prev: '*' }, 128 | { blankLine: 'always', next: '*', prev: 'block-like' }, 129 | { blankLine: 'always', next: 'function', prev: '*' }, 130 | { blankLine: 'always', next: '*', prev: 'function' }, 131 | { blankLine: 'always', next: 'try', prev: '*' }, 132 | { blankLine: 'always', next: '*', prev: 'try' }, 133 | { blankLine: 'always', next: 'return', prev: '*' }, 134 | { blankLine: 'always', next: 'import', prev: '*' }, 135 | { blankLine: 'always', next: '*', prev: 'import' }, 136 | { blankLine: 'any', next: 'import', prev: 'import' } 137 | ], 138 | semi: ['error', 'always'], 139 | 'simple-import-sort/exports': 'error', 140 | 'simple-import-sort/imports': ['error', { 141 | groups: [ 142 | ['^\u0000'], // all side-effects (0 at start) 143 | ['\u0000$', '^@polkadot.*\u0000$', '^\\..*\u0000$'], // types (0 at end) 144 | // ['^node:'], // node 145 | ['^[^/\\.]'], // non-polkadot 146 | ['^@polkadot'], // polkadot 147 | ['^\\.\\.(?!/?$)', '^\\.\\./?$', '^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'] // local (. last) 148 | ] 149 | }], 150 | 'sort-destructure-keys/sort-destructure-keys': ['error', { 151 | caseSensitive: true 152 | }], 153 | 'sort-keys': 'error', 154 | 'spaced-comment': ['error', 'always', { 155 | block: { 156 | // pure export helpers 157 | markers: ['#__PURE__'] 158 | }, 159 | line: { 160 | // TS reference types 161 | markers: ['/ void; 11 | 12 | type Mocked = Partial>; 13 | 14 | // logged via Object.keys(expect).sort() 15 | const EXPECT_KEYS = ['addEqualityTesters', 'addSnapshotSerializer', 'any', 'anything', 'arrayContaining', 'assertions', 'closeTo', 'extend', 'extractExpectedAssertionsErrors', 'getState', 'hasAssertions', 'not', 'objectContaining', 'setState', 'stringContaining', 'stringMatching', 'toMatchInlineSnapshot', 'toMatchSnapshot', 'toThrowErrorMatchingInlineSnapshot', 'toThrowErrorMatchingSnapshot'] as const; 16 | 17 | // logged via Object.keys(expect(0)).sort() 18 | const EXPECT_KEYS_FN = ['lastCalledWith', 'lastReturnedWith', 'not', 'nthCalledWith', 'nthReturnedWith', 'rejects', 'resolves', 'toBe', 'toBeCalled', 'toBeCalledTimes', 'toBeCalledWith', 'toBeCloseTo', 'toBeDefined', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeInstanceOf', 'toBeLessThan', 'toBeLessThanOrEqual', 'toBeNaN', 'toBeNull', 'toBeTruthy', 'toBeUndefined', 'toContain', 'toContainEqual', 'toEqual', 'toHaveBeenCalled', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveBeenLastCalledWith', 'toHaveBeenNthCalledWith', 'toHaveLastReturnedWith', 'toHaveLength', 'toHaveNthReturnedWith', 'toHaveProperty', 'toHaveReturned', 'toHaveReturnedTimes', 'toHaveReturnedWith', 'toMatch', 'toMatchInlineSnapshot', 'toMatchObject', 'toMatchSnapshot', 'toReturn', 'toReturnTimes', 'toReturnWith', 'toStrictEqual', 'toThrow', 'toThrowError', 'toThrowErrorMatchingInlineSnapshot', 'toThrowErrorMatchingSnapshot'] as const; 19 | 20 | const stubExpect = stubObj('expect', EXPECT_KEYS); 21 | const stubExpectFn = stubObj('expect(...)', EXPECT_KEYS_FN, { 22 | toThrowError: 'expect(...).toThrow' 23 | }); 24 | const stubExpectFnRejects = stubObj('expect(...).rejects', EXPECT_KEYS_FN, { 25 | toThrowError: 'expect(...).rejects.toThrow' 26 | }); 27 | const stubExpectFnResolves = stubObj('expect(...).resolves', EXPECT_KEYS_FN); 28 | const stubExpectFnNot = stubObj('expect(...).not', EXPECT_KEYS_FN, { 29 | toBeFalsy: 'expect(...).toBeTruthy', 30 | toBeTruthy: 'expect(...).toBeFalsy', 31 | toThrowError: 'expect(...).not.toThrow' 32 | }); 33 | 34 | /** 35 | * @internal 36 | * 37 | * A helper that wraps a matching function in an ExpectMatcher. This is (currently) 38 | * only used/checked for in the calledWith* helpers 39 | * 40 | * TODO We don't use it in polkadot-js, but a useful enhancement could be for 41 | * any of the to* expectations to detect and use those. An example of useful code 42 | * in that case: 43 | * 44 | * ```js 45 | * expect({ 46 | * a: 'blah', 47 | * b: 3 48 | * }).toEqual( 49 | * expect.objectContaining({ b: 3 }) 50 | * ) 51 | * ``` 52 | * 53 | * An example of matcher use can be seen in the isCalledWith loops 54 | */ 55 | class Matcher { 56 | assertMatch: AssertMatchFn; 57 | 58 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 | constructor (assertFn: (value: any, check: any) => void, check?: unknown) { 60 | this.assertMatch = (value) => assertFn(value, check); 61 | } 62 | } 63 | 64 | /** 65 | * @internal 66 | * 67 | * Asserts that the input value is non-nullish 68 | */ 69 | function assertNonNullish (value: unknown): void { 70 | assert.ok(value !== null && value !== undefined, `Expected non-nullish value, found ${value as string}`); 71 | } 72 | 73 | /** 74 | * @internal 75 | * 76 | * A helper that checks a single call arguments, which may include the 77 | * use of matchers. This is used in finding any call or checking a specific 78 | * call 79 | */ 80 | function assertCallHasArgs (call: { arguments: unknown[] } | undefined, args: unknown[]): void { 81 | assert.ok(call && args.length === call.arguments?.length, 'Number of arguments does not match'); 82 | 83 | args.forEach((arg, i) => assertMatch(call.arguments[i], arg)); 84 | } 85 | 86 | /** 87 | * @internal 88 | * 89 | * A helper that checks for the first instance of a match on the actual call 90 | * arguments (this extracts the toHaveBeenCalledWith logic) 91 | */ 92 | function assertSomeCallHasArgs (value: Mocked | undefined, args: unknown[]) { 93 | assert.ok(value?.mock?.calls.some((call) => { 94 | try { 95 | assertCallHasArgs(call, args); 96 | 97 | return true; 98 | } catch { 99 | return false; 100 | } 101 | }), 'No call found matching arguments'); 102 | } 103 | 104 | /** 105 | * @internal 106 | * 107 | * Asserts that the value is either (equal deep) or matches the matcher (if supplied) 108 | */ 109 | function assertMatch (value: unknown, check: unknown): void { 110 | check instanceof Matcher 111 | ? check.assertMatch(value) 112 | : Array.isArray(check) 113 | ? assertMatchArr(value, check) 114 | : check && typeof check === 'object' 115 | ? assertMatchObj(value, check) 116 | : assert.deepStrictEqual(value, check); 117 | } 118 | 119 | /** 120 | * @internal 121 | * 122 | * A helper to match the supplied array check against the resulting array 123 | * 124 | * @param {unknown} value 125 | * @param {unknown[]} check 126 | */ 127 | function assertMatchArr (value: unknown, check: unknown[]): void { 128 | assert.ok(value && Array.isArray(value), `Expected array value, found ${typeof value}`); 129 | assert.ok(value.length === check.length, `Expected array with ${check.length} entries, found ${value.length}`); 130 | 131 | check.forEach((other, i) => assertMatch(value[i], other)); 132 | } 133 | 134 | /** 135 | * @internal 136 | * 137 | * A helper to match the supplied fields against the resulting object 138 | */ 139 | function assertMatchObj (value: unknown, check: object): void { 140 | assert.ok(value && typeof value === 'object', `Expected object value, found ${typeof value}`); 141 | 142 | Object 143 | .entries(check) 144 | .forEach(([key, other]) => assertMatch((value as Record)[key], other)); 145 | } 146 | 147 | /** 148 | * @internal 149 | * 150 | * A helper to match a string value against another string or regex 151 | */ 152 | function assertMatchStr (value: unknown, check: string | RegExp): void { 153 | assert.ok(typeof value === 'string', `Expected string value, found ${typeof value}`); 154 | 155 | typeof check === 'string' 156 | ? assert.strictEqual(value, check) 157 | : assert.match(value, check); 158 | } 159 | 160 | /** 161 | * @internal 162 | * 163 | * A helper to check the type of a specific value as used in the expect.any(Clazz) matcher 164 | * 165 | * @see https://github.com/facebook/jest/blob/a49c88610e49a3242576160740a32a2fe11161e1/packages/expect/src/asymmetricMatchers.ts#L103-L133 166 | */ 167 | // eslint-disable-next-line @typescript-eslint/ban-types 168 | function assertInstanceOf (value: unknown, Clazz: Function): void { 169 | assert.ok( 170 | (Clazz === Array && Array.isArray(value)) || 171 | (Clazz === BigInt && typeof value === 'bigint') || 172 | (Clazz === Boolean && typeof value === 'boolean') || 173 | (Clazz === Function && typeof value === 'function') || 174 | (Clazz === Number && typeof value === 'number') || 175 | (Clazz === Object && typeof value === 'object') || 176 | (Clazz === String && typeof value === 'string') || 177 | (Clazz === Symbol && typeof value === 'symbol') || 178 | (value instanceof Clazz), 179 | `${value as string} is not an instance of ${Clazz.name}` 180 | ); 181 | } 182 | 183 | /** 184 | * @internal 185 | * 186 | * A helper to ensure that the supplied string/array does include the checker string. 187 | * 188 | * @param {string | unknown[]} value 189 | * @param {string} check 190 | */ 191 | // eslint-disable-next-line @typescript-eslint/ban-types 192 | function assertIncludes (value: string | unknown[], [check, Clazz]: [string, Function]): void { 193 | assertInstanceOf(value, Clazz); 194 | assert.ok(value?.includes(check), `${value as string} does not include ${check}`); 195 | } 196 | 197 | /** 198 | * Sets up the shimmed expect(...) function, including all .to* and .not.to* 199 | * functions. This is not comprehensive, rather is contains what we need to 200 | * make all polkadot-js usages pass 201 | **/ 202 | export function expect () { 203 | const rootMatchers = { 204 | // eslint-disable-next-line @typescript-eslint/ban-types 205 | any: (Clazz: Function) => new Matcher(assertInstanceOf, Clazz), 206 | anything: () => new Matcher(assertNonNullish), 207 | arrayContaining: (check: string) => new Matcher(assertIncludes, [check, Array]), 208 | objectContaining: (check: object) => new Matcher(assertMatchObj, check), 209 | stringContaining: (check: string) => new Matcher(assertIncludes, [check, String]), 210 | stringMatching: (check: string | RegExp) => new Matcher(assertMatchStr, check) 211 | }; 212 | 213 | return { 214 | expect: enhanceObj(enhanceObj((value: unknown) => 215 | enhanceObj({ 216 | not: enhanceObj({ 217 | toBe: (other: unknown) => assert.notStrictEqual(value, other), 218 | toBeDefined: () => assert.ok(value === undefined), 219 | toBeNull: (value: unknown) => assert.ok(value !== null), 220 | toBeUndefined: () => assert.ok(value !== undefined), 221 | toEqual: (other: unknown) => assert.notDeepEqual(value, other), 222 | toHaveBeenCalled: () => assert.ok(!(value as Mocked | undefined)?.mock?.calls.length), 223 | toThrow: (message?: RegExp | Error | string) => assert.doesNotThrow(value as () => unknown, message && { message } as Error) 224 | }, stubExpectFnNot), 225 | rejects: enhanceObj({ 226 | toThrow: (message?: RegExp | Error | string) => assert.rejects(value as Promise, message && { message } as Error) 227 | }, stubExpectFnRejects), 228 | resolves: enhanceObj({}, stubExpectFnResolves), 229 | toBe: (other: unknown) => assert.strictEqual(value, other), 230 | toBeDefined: () => assert.ok(value !== undefined), 231 | toBeFalsy: () => assert.ok(!value), 232 | // eslint-disable-next-line @typescript-eslint/ban-types 233 | toBeInstanceOf: (Clazz: Function) => assertInstanceOf(value, Clazz), 234 | toBeNull: (value: unknown) => assert.ok(value === null), 235 | toBeTruthy: () => assert.ok(value), 236 | toBeUndefined: () => assert.ok(value === undefined), 237 | toEqual: (other: unknown) => assert.deepEqual(value, other), 238 | toHaveBeenCalled: () => assert.ok((value as Mocked | undefined)?.mock?.calls.length), 239 | toHaveBeenCalledTimes: (count: number) => assert.equal((value as Mocked | undefined)?.mock?.calls.length, count), 240 | toHaveBeenCalledWith: (...args: unknown[]) => assertSomeCallHasArgs((value as Mocked | undefined), args), 241 | toHaveBeenLastCalledWith: (...args: unknown[]) => assertCallHasArgs((value as Mocked | undefined)?.mock?.calls.at(-1), args), 242 | toHaveLength: (length: number) => assert.equal((value as unknown[] | undefined)?.length, length), 243 | toMatch: (check: string | RegExp) => assertMatchStr(value, check), 244 | toMatchObject: (check: object) => assertMatchObj(value, check), 245 | toThrow: (message?: RegExp | Error | string) => assert.throws(value as () => unknown, message && { message } as Error) 246 | }, stubExpectFn), rootMatchers), stubExpect) 247 | }; 248 | } 249 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-exec-node-test.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // For Node 18, earliest usable is 18.14: 6 | // 7 | // - node:test added in 18.0, 8 | // - run method exposed in 18.9, 9 | // - mock in 18.13, 10 | // - diagnostics changed in 18.14 11 | // 12 | // Node 16 is not supported: 13 | // 14 | // - node:test added is 16.17, 15 | // - run method exposed in 16.19, 16 | // - mock not available 17 | 18 | import fs from 'node:fs'; 19 | import os from 'node:os'; 20 | import path from 'node:path'; 21 | import process from 'node:process'; 22 | import { run } from 'node:test'; 23 | import { isMainThread, parentPort, Worker, workerData } from 'node:worker_threads'; 24 | 25 | // NOTE error should be defined as "Error", however the @types/node definitions doesn't include all 26 | /** @typedef {{ file?: string; message?: string; }} DiagStat */ 27 | /** @typedef {{ details: { type: string; duration_ms: number; error: { message: string; failureType: unknown; stack: string; cause: { code: number; message: string; stack: string; generatedMessage?: any; }; code: number; } }; file?: string; name: string; testNumber: number; nesting: number; }} FailStat */ 28 | /** @typedef {{ details: { duration_ms: number }; name: string; }} PassStat */ 29 | /** @typedef {{ diag: DiagStat[]; fail: FailStat[]; pass: PassStat[]; skip: unknown[]; todo: unknown[]; total: number; [key: string]: any; }} Stats */ 30 | 31 | console.time('\t elapsed :'); 32 | 33 | const WITH_DEBUG = false; 34 | 35 | const args = process.argv.slice(2); 36 | /** @type {string[]} */ 37 | const files = []; 38 | 39 | /** @type {Stats} */ 40 | const stats = { 41 | diag: [], 42 | fail: [], 43 | pass: [], 44 | skip: [], 45 | todo: [], 46 | total: 0 47 | }; 48 | /** @type {string | null} */ 49 | let logFile = null; 50 | /** @type {number} */ 51 | let startAt = 0; 52 | /** @type {boolean} */ 53 | let bail = false; 54 | /** @type {boolean} */ 55 | let toConsole = false; 56 | /** @type {number} */ 57 | let progressRowCount = 0; 58 | 59 | for (let i = 0; i < args.length; i++) { 60 | if (args[i] === '--bail') { 61 | bail = true; 62 | } else if (args[i] === '--console') { 63 | toConsole = true; 64 | } else if (args[i] === '--logfile') { 65 | logFile = args[++i]; 66 | } else { 67 | files.push(args[i]); 68 | } 69 | } 70 | 71 | /** 72 | * @internal 73 | * 74 | * Performs an indent of the line (and containing lines) with the specific count 75 | * 76 | * @param {number} count 77 | * @param {string} str 78 | * @param {string} start 79 | * @returns {string} 80 | */ 81 | function indent (count, str = '', start = '') { 82 | let pre = '\n'; 83 | 84 | switch (count) { 85 | case 0: 86 | break; 87 | 88 | case 1: 89 | pre += '\t'; 90 | break; 91 | 92 | case 2: 93 | pre += '\t\t'; 94 | break; 95 | 96 | default: 97 | pre += '\t\t\t'; 98 | break; 99 | } 100 | 101 | pre += ' '; 102 | 103 | return `${pre}${start}${ 104 | str 105 | .split('\n') 106 | .map((l) => l.trim()) 107 | .join(`${pre}${start ? ' '.padStart(start.length, ' ') : ''}`) 108 | }\n`; 109 | } 110 | 111 | /** 112 | * @param {FailStat} r 113 | * @return {string | undefined} 114 | */ 115 | function getFilename (r) { 116 | if (r.file?.includes('.spec.') || r.file?.includes('.test.')) { 117 | return r.file; 118 | } 119 | 120 | if (r.details.error.cause.stack) { 121 | const stack = r.details.error.cause.stack 122 | .split('\n') 123 | .map((l) => l.trim()) 124 | .filter((l) => l.startsWith('at ') && (l.includes('.spec.') || l.includes('.test.'))) 125 | .map((l) => l.match(/\(.*:\d\d?:\d\d?\)$/)?.[0]) 126 | .map((l) => l?.replace('(', '')?.replace(')', '')); 127 | 128 | if (stack.length) { 129 | return stack[0]; 130 | } 131 | } 132 | 133 | return r.file; 134 | } 135 | 136 | function complete () { 137 | process.stdout.write('\n'); 138 | 139 | let logError = ''; 140 | 141 | stats.fail.forEach((r) => { 142 | WITH_DEBUG && console.error(JSON.stringify(r, null, 2)); 143 | 144 | let item = ''; 145 | 146 | item += indent(1, [getFilename(r), r.name].filter((s) => !!s).join('\n'), 'x '); 147 | item += indent(2, `${r.details.error.failureType} / ${r.details.error.code}${r.details.error.cause.code && r.details.error.cause.code !== r.details.error.code ? ` / ${r.details.error.cause.code}` : ''}`); 148 | 149 | if (r.details.error.cause.message) { 150 | item += indent(2, r.details.error.cause.message); 151 | } 152 | 153 | logError += item; 154 | 155 | if (r.details.error.cause.stack) { 156 | item += indent(2, r.details.error.cause.stack); 157 | } 158 | 159 | process.stdout.write(item); 160 | }); 161 | 162 | if (logFile && logError) { 163 | try { 164 | fs.appendFileSync(path.join(process.cwd(), logFile), logError); 165 | } catch (e) { 166 | console.error(e); 167 | } 168 | } 169 | 170 | console.log(); 171 | console.log('\t passed ::', stats.pass.length); 172 | console.log('\t failed ::', stats.fail.length); 173 | console.log('\t skipped ::', stats.skip.length); 174 | console.log('\t todo ::', stats.todo.length); 175 | console.log('\t total ::', stats.total); 176 | console.timeEnd('\t elapsed :'); 177 | console.log(); 178 | 179 | // The full error information can be quite useful in the case of overall failures 180 | if ((stats.fail.length || toConsole) && stats.diag.length) { 181 | /** @type {string | undefined} */ 182 | let lastFilename = ''; 183 | 184 | stats.diag.forEach((r) => { 185 | WITH_DEBUG && console.error(JSON.stringify(r, null, 2)); 186 | 187 | if (typeof r === 'string') { 188 | console.log(r); // Node.js <= 18.14 189 | } else if (r.file && r.file.includes('@polkadot/dev/scripts')) { 190 | // Ignore internal diagnostics 191 | } else { 192 | if (lastFilename !== r.file) { 193 | lastFilename = r.file; 194 | 195 | console.log(lastFilename ? `\n${lastFilename}::\n` : '\n'); 196 | } 197 | 198 | // Edge case: We don't need additional noise that is not useful. 199 | if (!r.message?.split(' ').includes('tests')) { 200 | console.log(`\t${r.message?.split('\n').join('\n\t')}`); 201 | } 202 | } 203 | }); 204 | } 205 | 206 | if (toConsole) { 207 | stats.pass.forEach((r) => { 208 | console.log(`pass ${r.name} ${r.details.duration_ms} ms`); 209 | }); 210 | 211 | console.log(); 212 | 213 | stats.fail.forEach((r) => { 214 | console.log(`fail ${r.name}`); 215 | }); 216 | 217 | console.log(); 218 | } 219 | 220 | if (stats.total === 0) { 221 | console.error('FATAL: No tests executed'); 222 | console.error(); 223 | process.exit(1); 224 | } 225 | 226 | process.exit(stats.fail.length); 227 | } 228 | 229 | /** 230 | * Prints the progress in real-time as data is passed from the worker. 231 | * 232 | * @param {string} symbol 233 | */ 234 | function printProgress (symbol) { 235 | if (!progressRowCount) { 236 | progressRowCount = 0; 237 | } 238 | 239 | if (!startAt) { 240 | startAt = performance.now(); 241 | } 242 | 243 | // If starting a new row, calculate and print the elapsed time 244 | if (progressRowCount === 0) { 245 | const now = performance.now(); 246 | const elapsed = (now - startAt) / 1000; 247 | const minutes = Math.floor(elapsed / 60); 248 | const seconds = elapsed - minutes * 60; 249 | 250 | process.stdout.write( 251 | `${`${minutes}:${seconds.toFixed(3).padStart(6, '0')}`.padStart(11)} ` 252 | ); 253 | } 254 | 255 | // Print the symbol with formatting 256 | process.stdout.write(symbol); 257 | 258 | progressRowCount++; 259 | 260 | // Add spaces for readability 261 | if (progressRowCount % 10 === 0) { 262 | process.stdout.write(' '); // Double space every 10 symbols 263 | } else if (progressRowCount % 5 === 0) { 264 | process.stdout.write(' '); // Single space every 5 symbols 265 | } 266 | 267 | // If the row reaches 100 symbols, start a new row 268 | if (progressRowCount >= 100) { 269 | process.stdout.write('\n'); 270 | progressRowCount = 0; 271 | } 272 | } 273 | 274 | async function runParallel () { 275 | const MAX_WORKERS = Math.min(os.cpus().length, files.length); 276 | const chunks = Math.ceil(files.length / MAX_WORKERS); 277 | 278 | try { 279 | // Create and manage worker threads 280 | const results = await Promise.all( 281 | Array.from({ length: MAX_WORKERS }, (_, i) => { 282 | const fileSubset = files.slice(i * chunks, (i + 1) * chunks); 283 | 284 | return new Promise((resolve, reject) => { 285 | const worker = new Worker(new URL(import.meta.url), { 286 | workerData: { files: fileSubset } 287 | }); 288 | 289 | worker.on('message', (message) => { 290 | if (message.type === 'progress') { 291 | printProgress(message.data); 292 | } else if (message.type === 'result') { 293 | resolve(message.data); 294 | } 295 | }); 296 | 297 | worker.on('error', reject); 298 | worker.on('exit', (code) => { 299 | if (code !== 0) { 300 | reject(new Error(`Worker stopped with exit code ${code}`)); 301 | } 302 | }); 303 | }); 304 | }) 305 | ); 306 | 307 | // Aggregate results from workers 308 | results.forEach((result) => { 309 | Object.keys(stats).forEach((key) => { 310 | if (Array.isArray(stats[key])) { 311 | stats[key] = stats[key].concat(result[key]); 312 | } else if (typeof stats[key] === 'number') { 313 | stats[key] += result[key]; 314 | } 315 | }); 316 | }); 317 | 318 | complete(); 319 | } catch (err) { 320 | console.error('Error during parallel execution:', err); 321 | process.exit(1); 322 | } 323 | } 324 | 325 | if (isMainThread) { 326 | console.time('\tElapsed:'); 327 | runParallel().catch((err) => console.error(err)); 328 | } else { 329 | run({ files: workerData.files, timeout: 3_600_000 }) 330 | .on('data', () => undefined) 331 | .on('end', () => parentPort && parentPort.postMessage(stats)) 332 | .on('test:coverage', () => undefined) 333 | .on('test:diagnostic', (/** @type {DiagStat} */data) => { 334 | stats.diag.push(data); 335 | parentPort && parentPort.postMessage({ data: stats, type: 'result' }); 336 | }) 337 | .on('test:fail', (/** @type {FailStat} */ data) => { 338 | const statFail = structuredClone(data); 339 | 340 | if (data.details.error.cause?.stack) { 341 | statFail.details.error.cause.stack = data.details.error.cause.stack; 342 | } 343 | 344 | stats.fail.push(statFail); 345 | stats.total++; 346 | parentPort && parentPort.postMessage({ data: 'x', type: 'progress' }); 347 | 348 | if (bail) { 349 | complete(); 350 | } 351 | }) 352 | .on('test:pass', (data) => { 353 | const symbol = typeof data.skip !== 'undefined' ? '>' : typeof data.todo !== 'undefined' ? '!' : '·'; 354 | 355 | if (symbol === '>') { 356 | stats.skip.push(data); 357 | } else if (symbol === '!') { 358 | stats.todo.push(data); 359 | } else { 360 | stats.pass.push(data); 361 | } 362 | 363 | stats.total++; 364 | parentPort && parentPort.postMessage({ data: symbol, type: 'progress' }); 365 | }) 366 | .on('test:plan', () => undefined) 367 | .on('test:start', () => undefined); 368 | } 369 | -------------------------------------------------------------------------------- /packages/dev/scripts/polkadot-ci-ghact-build.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2017-2025 @polkadot/dev authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import fs from 'node:fs'; 6 | import os from 'node:os'; 7 | import path from 'node:path'; 8 | import process from 'node:process'; 9 | import yargs from 'yargs'; 10 | 11 | import { copyDirSync, copyFileSync, denoCreateDir, execGit, execPm, execSync, exitFatal, GITHUB_REPO, GITHUB_TOKEN_URL, gitSetup, logBin, mkdirpSync, rimrafSync, topoSort } from './util.mjs'; 12 | 13 | /** @typedef {Record} ChangelogMap */ 14 | 15 | logBin('polkadot-ci-ghact-build'); 16 | 17 | const DENO_REPO = 'polkadot-js/build-deno.land'; 18 | const BUND_REPO = 'polkadot-js/build-bundle'; 19 | 20 | const repo = `${GITHUB_TOKEN_URL}/${GITHUB_REPO}.git`; 21 | const denoRepo = `${GITHUB_TOKEN_URL}/${DENO_REPO}.git`; 22 | const bundRepo = `${GITHUB_TOKEN_URL}/${BUND_REPO}.git`; 23 | const bundClone = 'build-bundle-clone'; 24 | const denoClone = 'build-deno-clone'; 25 | 26 | let withDeno = false; 27 | let withBund = false; 28 | let withNpm = false; 29 | 30 | /** @type {string[]} */ 31 | const shouldDeno = []; 32 | /** @type {string[]} */ 33 | const shouldBund = []; 34 | 35 | const argv = await yargs(process.argv.slice(2)) 36 | .options({ 37 | 'skip-beta': { 38 | description: 'Do not increment as beta', 39 | type: 'boolean' 40 | } 41 | }) 42 | .strict() 43 | .argv; 44 | 45 | /** 46 | * Removes a specific file, returning true if found, false otherwise 47 | * 48 | * @param {string} file 49 | * @returns {boolean} 50 | */ 51 | function rmFile (file) { 52 | if (fs.existsSync(file)) { 53 | rimrafSync(file); 54 | 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | /** 62 | * Retrieves the path of the root package.json 63 | * 64 | * @returns {string} 65 | */ 66 | function npmGetJsonPath () { 67 | return path.resolve(process.cwd(), 'package.json'); 68 | } 69 | 70 | /** 71 | * Retrieves the contents of the root package.json 72 | * 73 | * @returns {{ name: string; version: string; versions?: { npm?: string; git?: string } }} 74 | */ 75 | function npmGetJson () { 76 | return JSON.parse( 77 | fs.readFileSync(npmGetJsonPath(), 'utf8') 78 | ); 79 | } 80 | 81 | /** 82 | * Writes the contents of the root package.json 83 | * 84 | * @param {any} json 85 | */ 86 | function npmSetJson (json) { 87 | fs.writeFileSync(npmGetJsonPath(), `${JSON.stringify(json, null, 2)}\n`); 88 | } 89 | 90 | /** 91 | * Retrieved the current version included in package.json 92 | * 93 | * @returns {string} 94 | */ 95 | function npmGetVersion () { 96 | return npmGetJson().version; 97 | } 98 | 99 | /** 100 | * Sets the current to have an -x version specifier (aka beta) 101 | */ 102 | function npmAddVersionX () { 103 | const json = npmGetJson(); 104 | 105 | if (!json.version.endsWith('-x')) { 106 | json.version = json.version + '-x'; 107 | npmSetJson(json); 108 | } 109 | } 110 | 111 | /** 112 | * Removes the current -x version specifier (aka beta) 113 | */ 114 | function npmDelVersionX () { 115 | const json = npmGetJson(); 116 | 117 | if (json.version.endsWith('-x')) { 118 | json.version = json.version.replace('-x', ''); 119 | npmSetJson(json); 120 | } 121 | } 122 | 123 | /** 124 | * Sets the {versions: { npm, git } } fields in package.json 125 | */ 126 | function npmSetVersionFields () { 127 | const json = npmGetJson(); 128 | 129 | if (!json.versions) { 130 | json.versions = {}; 131 | } 132 | 133 | json.versions.git = json.version; 134 | 135 | if (!json.version.endsWith('-x')) { 136 | json.versions.npm = json.version; 137 | } 138 | 139 | npmSetJson(json); 140 | rmFile('.123current'); 141 | } 142 | 143 | /** 144 | * Sets the npm token in the home directory 145 | */ 146 | function npmSetup () { 147 | const registry = 'registry.npmjs.org'; 148 | 149 | fs.writeFileSync(path.join(os.homedir(), '.npmrc'), `//${registry}/:_authToken=${process.env['NPM_TOKEN']}`); 150 | } 151 | 152 | /** 153 | * Publishes the current package 154 | * 155 | * @returns {void} 156 | */ 157 | function npmPublish () { 158 | if (fs.existsSync('.skip-npm') || !withNpm) { 159 | return; 160 | } 161 | 162 | ['LICENSE', 'package.json'] 163 | .filter((file) => !fs.existsSync(path.join(process.cwd(), 'build', file))) 164 | .forEach((file) => copyFileSync(file, 'build')); 165 | 166 | process.chdir('build'); 167 | 168 | const tag = npmGetVersion().includes('-') ? '--tag beta' : ''; 169 | let count = 1; 170 | 171 | while (true) { 172 | try { 173 | execSync(`npm publish --quiet --access public ${tag}`); 174 | 175 | break; 176 | } catch { 177 | if (count < 5) { 178 | const end = Date.now() + 15000; 179 | 180 | console.error(`Publish failed on attempt ${count}/5. Retrying in 15s`); 181 | count++; 182 | 183 | while (Date.now() < end) { 184 | // just spin our wheels 185 | } 186 | } 187 | } 188 | } 189 | 190 | process.chdir('..'); 191 | } 192 | 193 | /** 194 | * Creates a map of changelog entries 195 | * 196 | * @param {string[][]} parts 197 | * @param {ChangelogMap} result 198 | * @returns {ChangelogMap} 199 | */ 200 | function createChangelogMap (parts, result = {}) { 201 | for (let i = 0, count = parts.length; i < count; i++) { 202 | const [n, ...e] = parts[i]; 203 | 204 | if (!result[n]) { 205 | if (e.length) { 206 | result[n] = createChangelogMap([e]); 207 | } else { 208 | result[n] = { '': {} }; 209 | } 210 | } else { 211 | if (e.length) { 212 | createChangelogMap([e], result[n]); 213 | } else { 214 | result[n][''] = {}; 215 | } 216 | } 217 | } 218 | 219 | return result; 220 | } 221 | 222 | /** 223 | * Creates an array of changelog entries 224 | * 225 | * @param {ChangelogMap} map 226 | * @returns {string[]} 227 | */ 228 | function createChangelogArr (map) { 229 | const result = []; 230 | const entries = Object.entries(map); 231 | 232 | for (let i = 0, count = entries.length; i < count; i++) { 233 | const [name, imap] = entries[i]; 234 | 235 | if (name) { 236 | if (imap['']) { 237 | result.push(name); 238 | } 239 | 240 | const inner = createChangelogArr(imap); 241 | 242 | if (inner.length === 1) { 243 | result.push(`${name}-${inner[0]}`); 244 | } else if (inner.length) { 245 | result.push(`${name}-{${inner.join(', ')}}`); 246 | } 247 | } 248 | } 249 | 250 | return result; 251 | } 252 | 253 | /** 254 | * Adds changelog entries 255 | * 256 | * @param {string[]} changelog 257 | * @returns {string} 258 | */ 259 | function addChangelog (changelog) { 260 | const [version, ...names] = changelog; 261 | const entry = `${ 262 | createChangelogArr( 263 | createChangelogMap( 264 | names 265 | .sort() 266 | .map((n) => n.split('-')) 267 | ) 268 | ).join(', ') 269 | } ${version}`; 270 | const newInfo = `## master\n\n- ${entry}\n`; 271 | 272 | if (!fs.existsSync('CHANGELOG.md')) { 273 | fs.writeFileSync('CHANGELOG.md', `# CHANGELOG\n\n${newInfo}`); 274 | } else { 275 | const md = fs.readFileSync('CHANGELOG.md', 'utf-8'); 276 | 277 | fs.writeFileSync('CHANGELOG.md', md.includes('## master\n\n') 278 | ? md.replace('## master\n\n', newInfo) 279 | : md.replace('# CHANGELOG\n\n', `# CHANGELOG\n\n${newInfo}\n`) 280 | ); 281 | } 282 | 283 | return entry; 284 | } 285 | 286 | /** 287 | * 288 | * @param {string} repo 289 | * @param {string} clone 290 | * @param {string[]} names 291 | */ 292 | function commitClone (repo, clone, names) { 293 | if (names.length) { 294 | process.chdir(clone); 295 | 296 | const entry = addChangelog(names); 297 | 298 | gitSetup(); 299 | execGit('add --all .'); 300 | execGit(`commit --no-status --quiet -m "${entry}"`); 301 | execGit(`push ${repo}`, true); 302 | 303 | process.chdir('..'); 304 | } 305 | } 306 | 307 | /** 308 | * Publishes a specific package to polkadot-js bundles 309 | * 310 | * @returns {void} 311 | */ 312 | function bundlePublishPkg () { 313 | const { name, version } = npmGetJson(); 314 | const dirName = name.split('/')[1]; 315 | const bundName = `bundle-polkadot-${dirName}.js`; 316 | const srcPath = path.join('build', bundName); 317 | const dstDir = path.join('../..', bundClone); 318 | 319 | if (!fs.existsSync(srcPath)) { 320 | return; 321 | } 322 | 323 | console.log(`\n *** bundle ${name}`); 324 | 325 | if (shouldBund.length === 0) { 326 | shouldBund.push(version); 327 | } 328 | 329 | shouldBund.push(dirName); 330 | 331 | rimrafSync(path.join(dstDir, bundName)); 332 | copyFileSync(srcPath, dstDir); 333 | } 334 | 335 | /** 336 | * Publishes all packages to polkadot-js bundles 337 | * 338 | * @returns {void} 339 | */ 340 | function bundlePublish () { 341 | const { version } = npmGetJson(); 342 | 343 | if (!withBund && version.includes('-')) { 344 | return; 345 | } 346 | 347 | execGit(`clone ${bundRepo} ${bundClone}`, true); 348 | 349 | loopFunc(bundlePublishPkg); 350 | 351 | commitClone(bundRepo, bundClone, shouldBund); 352 | } 353 | 354 | /** 355 | * Publishes a specific package to Deno 356 | * 357 | * @returns {void} 358 | */ 359 | function denoPublishPkg () { 360 | const { name, version } = npmGetJson(); 361 | 362 | if (fs.existsSync('.skip-deno') || !fs.existsSync('build-deno')) { 363 | return; 364 | } 365 | 366 | console.log(`\n *** deno ${name}`); 367 | 368 | const dirName = denoCreateDir(name); 369 | const denoPath = `../../${denoClone}/${dirName}`; 370 | 371 | if (shouldDeno.length === 0) { 372 | shouldDeno.push(version); 373 | } 374 | 375 | shouldDeno.push(dirName); 376 | 377 | rimrafSync(denoPath); 378 | mkdirpSync(denoPath); 379 | 380 | copyDirSync('build-deno', denoPath); 381 | } 382 | 383 | /** 384 | * Publishes all packages to Deno 385 | * 386 | * @returns {void} 387 | */ 388 | function denoPublish () { 389 | const { version } = npmGetJson(); 390 | 391 | if (!withDeno && version.includes('-')) { 392 | return; 393 | } 394 | 395 | execGit(`clone ${denoRepo} ${denoClone}`, true); 396 | 397 | loopFunc(denoPublishPkg); 398 | 399 | commitClone(denoRepo, denoClone, shouldDeno); 400 | } 401 | 402 | /** 403 | * Retrieves flags based on current specifications 404 | */ 405 | function getFlags () { 406 | withDeno = rmFile('.123deno'); 407 | withBund = rmFile('.123bundle'); 408 | withNpm = rmFile('.123npm'); 409 | } 410 | 411 | /** 412 | * Bumps the current version, also applying to all sub-packages 413 | */ 414 | function verBump () { 415 | const { version: currentVersion, versions } = npmGetJson(); 416 | const [version, tag] = currentVersion.split('-'); 417 | const [,, patch] = version.split('.'); 418 | const lastVersion = versions?.npm || currentVersion; 419 | 420 | if (argv['skip-beta'] || patch === '0') { 421 | // don't allow beta versions 422 | execPm('polkadot-dev-version patch'); 423 | withNpm = true; 424 | } else if (tag || currentVersion === lastVersion) { 425 | // if we don't want to publish, add an X before passing 426 | if (!withNpm) { 427 | npmAddVersionX(); 428 | } else { 429 | npmDelVersionX(); 430 | } 431 | 432 | // beta version, just continue the stream of betas 433 | execPm('polkadot-dev-version pre'); 434 | } else { 435 | // manually set, got for publish 436 | withNpm = true; 437 | } 438 | 439 | // always ensure we have made some changes, so we can commit 440 | npmSetVersionFields(); 441 | rmFile('.123trigger'); 442 | 443 | execPm('polkadot-dev-contrib'); 444 | execGit('add --all .'); 445 | } 446 | 447 | /** 448 | * Commits and pushes the current version on git 449 | */ 450 | function gitPush () { 451 | const version = npmGetVersion(); 452 | let doGHRelease = false; 453 | 454 | if (process.env['GH_RELEASE_GITHUB_API_TOKEN']) { 455 | const changes = fs.readFileSync('CHANGELOG.md', 'utf8'); 456 | 457 | if (changes.includes(`## ${version}`)) { 458 | doGHRelease = true; 459 | } else if (version.endsWith('.1')) { 460 | exitFatal(`Unable to release, no CHANGELOG entry for ${version}`); 461 | } 462 | } 463 | 464 | execGit('add --all .'); 465 | 466 | if (fs.existsSync('docs/README.md')) { 467 | execGit('add --all -f docs'); 468 | } 469 | 470 | // add the skip checks for GitHub ... 471 | execGit(`commit --no-status --quiet -m "[CI Skip] ${version.includes('-x') ? 'bump' : 'release'}/${version.includes('-') ? 'beta' : 'stable'} ${version} 472 | 473 | 474 | skip-checks: true"`); 475 | 476 | // Make sure the release commit is on top of the latest master 477 | execGit(`pull --rebase ${repo} master`); 478 | 479 | // Now push normally 480 | execGit(`push ${repo} HEAD:${process.env['GITHUB_REF']}`, true); 481 | 482 | if (doGHRelease) { 483 | const files = process.env['GH_RELEASE_FILES'] 484 | ? `--assets ${process.env['GH_RELEASE_FILES']}` 485 | : ''; 486 | 487 | execPm(`polkadot-exec-ghrelease --draft ${files} --yes`); 488 | } 489 | } 490 | 491 | /** 492 | * Loops through the packages/* (or root), executing the supplied 493 | * function for each package found 494 | * 495 | * @param {() => unknown} fn 496 | */ 497 | function loopFunc (fn) { 498 | if (fs.existsSync('packages')) { 499 | const dirs = fs 500 | .readdirSync('packages') 501 | .filter((dir) => { 502 | const pkgDir = path.join(process.cwd(), 'packages', dir); 503 | 504 | return fs.statSync(pkgDir).isDirectory() && 505 | fs.existsSync(path.join(pkgDir, 'package.json')) && 506 | fs.existsSync(path.join(pkgDir, 'build')); 507 | }); 508 | 509 | topoSort(dirs) 510 | .forEach((dir) => { 511 | process.chdir(path.join('packages', dir)); 512 | fn(); 513 | process.chdir('../..'); 514 | }); 515 | } else { 516 | fn(); 517 | } 518 | } 519 | 520 | // first do infrastructure setup 521 | gitSetup(); 522 | npmSetup(); 523 | 524 | // get flags immediate, then adjust 525 | getFlags(); 526 | verBump(); 527 | 528 | // perform the actual CI build 529 | execPm('polkadot-dev-clean-build'); 530 | execPm('lint'); 531 | execPm('test'); 532 | execPm('build'); 533 | 534 | // publish to all GH repos 535 | gitPush(); 536 | denoPublish(); 537 | bundlePublish(); 538 | 539 | // publish to npm 540 | loopFunc(npmPublish); 541 | --------------------------------------------------------------------------------