diff --git a/test/fixtures/test426/.github/workflows/ci.yml b/test/fixtures/test426/.github/workflows/ci.yml
new file mode 100644
index 000000000000..72d33dddb54c
--- /dev/null
+++ b/test/fixtures/test426/.github/workflows/ci.yml
@@ -0,0 +1,70 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ name: Validate tests and build npm package
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ # Fetch all to count number of commits and compute pkg version
+ fetch-depth: 0
+ - uses: actions/setup-node@v6
+ with:
+ node-version: lts/*
+ cache: npm
+ - name: Install dependencies
+ run: npm ci
+ - name: Build npm package
+ run: node scripts/build-npm-package.js
+ - name: Check that JSON files match their TypeScript types
+ run: node scripts/check-types.js
+ - name: Pack npm package
+ run: mkdir -p dist && npm pack ./npm-package --pack-destination dist
+ - name: Upload package tarball
+ uses: actions/upload-artifact@v7
+ with:
+ name: npm-package
+ path: dist/*.tgz
+ if-no-files-found: error
+ retention-days: 7
+
+ publish:
+ name: Publish to npm
+ needs: build
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository == 'tc39/source-map-tests'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write # for npm trusted publishing
+ concurrency:
+ group: npm-publish
+ cancel-in-progress: false
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v6
+ with:
+ node-version: lts/*
+ registry-url: https://registry.npmjs.org
+ - name: Build npm package
+ run: node scripts/build-npm-package.js
+ - name: Publish
+ run: |
+ name=$(node -p "require('./npm-package/package.json').name")
+ version=$(node -p "require('./npm-package/package.json').version")
+ if [ -n "$(npm view "$name@$version" version 2>/dev/null)" ]; then
+ echo "$name@$version is already published, skipping."
+ else
+ npm publish ./npm-package --access public
+ fi
diff --git a/test/fixtures/test426/.gitignore b/test/fixtures/test426/.gitignore
new file mode 100644
index 000000000000..a311c506246a
--- /dev/null
+++ b/test/fixtures/test426/.gitignore
@@ -0,0 +1,6 @@
+npm-package/*
+!npm-package/README.md
+!npm-package/package.template.json
+!npm-package/types/
+node_modules/
+.tmp/
diff --git a/test/fixtures/test426/README.md b/test/fixtures/test426/README.md
index 1eb2818fcfa9..98dcb05da245 100644
--- a/test/fixtures/test426/README.md
+++ b/test/fixtures/test426/README.md
@@ -58,3 +58,42 @@ More information about running Chrome Devtools without building Chromium can be
Follow the [running Node.js tests guide](https://github.com/nodejs/node/blob/main/BUILDING.md#running-tests) to build Node.js and run tests locally.
Read more about running the source map tests at [here](https://github.com/nodejs/node/tree/main/test/test426).
+
+## npm package
+
+The test cases are published to npm as [`@tc39/source-map-tests`](https://www.npmjs.com/package/@tc39/source-map-tests), so that source map implementations can run them in their own test suites. A new version is published for every commit to `main`.
+
+```sh
+npm install --save-dev --save-exact @tc39/source-map-tests
+```
+
+### Usage
+
+`@tc39/source-map-tests/suites.json` lists the test suites:
+
+```json
+{
+ "suites": [
+ { "kind": "interpreting", "file": "interpreting/source-map-spec.json" },
+ { "kind": "decoding", "file": "decoding/scopes.json" }
+ ]
+}
+```
+
+There are two types of test suites: `interpreting` and `decoding`:
+- **interpreting** provided a source map to apply to a generated file, together with some actions to perform through it to check that it has been applied correctly.
+- **decoding** suites check that a source map is correctly decoded into a [Decoded Source Map Record](https://tc39.es/ecma426/#decoded-source-map-record).
+
+### TypeScript
+
+The package's root exports the types of the JSON files:
+
+```ts
+import type { InterpretingSuite, DecodingSuite, TestAction } from "@tc39/source-map-tests";
+```
+
+With TypeScript 7.1 or later, you can load type definitions for the various suites by adding `"types": ["@tc39/source-map-tests"]` to your `tsconfig.json`. Imported JSON modules then will be typed appropriately:
+```ts
+import spec from "@tc39/source-map-tests/interpreting/source-map-spec.json" with { type: "json" };
+spec.tests[0].testActions; // TestAction[]
+```
diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map
index 9c6f3fbde1ed..7a6d25afc73f 100644
--- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map
+++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map
@@ -4,7 +4,7 @@
"sections": [{
"offset": {
"line": 0,
- "column": 0,
+ "column": 0
},
"map": {
"version": 3,
@@ -16,7 +16,7 @@
}, {
"offset": {
"line": 1,
- "column": 0,
+ "column": 0
},
"map": {
"version": 3,
diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden
index 7c3a8456e6b0..69f6ebf9dcb9 100644
--- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden
+++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden
@@ -1,5 +1,5 @@
{
"file": "app.js",
"sources": [],
- "mappings": [],
+ "mappings": []
}
\ No newline at end of file
diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index.map b/test/fixtures/test426/decoding/debug-id/debug-id-index.map
index 11de486e1a9a..0b7231929e4b 100644
--- a/test/fixtures/test426/decoding/debug-id/debug-id-index.map
+++ b/test/fixtures/test426/decoding/debug-id/debug-id-index.map
@@ -5,7 +5,7 @@
"sections": [{
"offset": {
"line": 0,
- "column": 0,
+ "column": 0
},
"map": {
"version": 3,
@@ -17,7 +17,7 @@
}, {
"offset": {
"line": 1,
- "column": 0,
+ "column": 0
},
"map": {
"version": 3,
diff --git a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts
index 1f9862f012ec..526b584ca199 100644
--- a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts
+++ b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts
@@ -2,5 +2,5 @@
///
interface SourceMapRecord {
- debugId: string|null;
+ debugId?: string|null;
}
diff --git a/test/fixtures/test426/npm-package/README.md b/test/fixtures/test426/npm-package/README.md
new file mode 100644
index 000000000000..11012332c544
--- /dev/null
+++ b/test/fixtures/test426/npm-package/README.md
@@ -0,0 +1,5 @@
+# @tc39/source-map-tests
+
+Cross-implementation test cases for the [ECMA-426 source map specification](https://tc39.es/ecma426/).
+
+See the [tc39/source-map-tests README](https://github.com/tc39/source-map-tests#npm-package) for the package contents and usage.
diff --git a/test/fixtures/test426/npm-package/package.template.json b/test/fixtures/test426/npm-package/package.template.json
new file mode 100644
index 000000000000..68c0ae4ac298
--- /dev/null
+++ b/test/fixtures/test426/npm-package/package.template.json
@@ -0,0 +1,26 @@
+{
+ "name": "@tc39/source-map-tests",
+ "version": "X.X.X",
+ "description": "Cross-implementation test cases for the ECMA-426 source map specification",
+ "license": "BSD-3-Clause",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/tc39/source-map-tests.git"
+ },
+ "homepage": "https://github.com/tc39/source-map-tests#readme",
+ "bugs": "https://github.com/tc39/source-map-tests/issues",
+ "keywords": [
+ "source-map",
+ "sourcemap",
+ "ecma-426",
+ "test-suite",
+ "tests"
+ ],
+ "files": [
+ "suites.json",
+ "interpreting/",
+ "decoding/",
+ "types/",
+ "LICENSE.md"
+ ]
+}
diff --git a/test/fixtures/test426/npm-package/types/index.d.ts b/test/fixtures/test426/npm-package/types/index.d.ts
new file mode 100644
index 000000000000..658794c6f1d0
--- /dev/null
+++ b/test/fixtures/test426/npm-package/types/index.d.ts
@@ -0,0 +1,2 @@
+///
+export * from "./types.js";
diff --git a/test/fixtures/test426/npm-package/types/modules.d.ts b/test/fixtures/test426/npm-package/types/modules.d.ts
new file mode 100644
index 000000000000..08300745affe
--- /dev/null
+++ b/test/fixtures/test426/npm-package/types/modules.d.ts
@@ -0,0 +1,14 @@
+declare module "@tc39/source-map-tests/suites*.json" with { type: "json" } {
+ const index: import("./types.js").SuitesIndex;
+ export default index;
+}
+
+declare module "@tc39/source-map-tests/interpreting/*.json" with { type: "json" } {
+ const suite: import("./types.js").InterpretingSuite;
+ export default suite;
+}
+
+declare module "@tc39/source-map-tests/decoding/*.json" with { type: "json" } {
+ const suite: import("./types.js").DecodingSuite;
+ export default suite;
+}
diff --git a/test/fixtures/test426/npm-package/types/types.d.ts b/test/fixtures/test426/npm-package/types/types.d.ts
new file mode 100644
index 000000000000..7cbe9d48892c
--- /dev/null
+++ b/test/fixtures/test426/npm-package/types/types.d.ts
@@ -0,0 +1,90 @@
+/**
+ * Types for the JSON files in this package.
+ *
+ * All paths are relative to the package root.
+ */
+
+/** The format of `suites.json`. */
+export interface SuitesIndex {
+ suites: SuiteInfo[];
+}
+
+export type SuiteInfo =
+ | { kind: "interpreting"; file: string }
+ | { kind: "decoding"; file: string };
+
+/** A file from the test suite, with its contents inlined. */
+export interface TestFile {
+ path: string;
+ content: string;
+}
+
+/** The format of interpreting suite files (`interpreting/.json`). */
+export interface InterpretingSuite {
+ tests: InterpretingTest[];
+}
+
+export interface InterpretingTest {
+ name: string;
+ description: string;
+ sourceMapIsValid: boolean;
+ testActions: TestAction[];
+ /**
+ * The transpiled source that the source map refers to.
+ */
+ baseFile: TestFile;
+ sourceMapFile: TestFile;
+}
+
+export type TestAction =
+ | CheckMappingAction
+ | CheckMappingTransitiveAction
+ | CheckIgnoreListAction;
+
+export interface CheckMappingAction {
+ actionType: "checkMapping";
+ /** 0-based. */
+ generatedLine: number;
+ /** 0-based. */
+ generatedColumn: number;
+ originalSource: string | null;
+ /** 0-based. */
+ originalLine: number | null;
+ /** 0-based. */
+ originalColumn: number | null;
+ mappedName: string | null;
+}
+
+export interface CheckMappingTransitiveAction
+ extends Omit {
+ actionType: "checkMappingTransitive";
+ /** Source maps to apply after `sourceMapFile`, in order. */
+ intermediateMaps: TestFile[];
+}
+
+export interface CheckIgnoreListAction {
+ actionType: "checkIgnoreList";
+ /** Sources that must be in the ignore list. */
+ present: string[];
+}
+
+/** The format of decoding suite files (`decoding/.json`). */
+export interface DecodingSuite {
+ tests: DecodingTest[];
+}
+
+export interface DecodingTest {
+ name: string;
+ sourceMapFile: TestFile;
+ /**
+ * The expected Decoded Source Map Record, as JSON in the format described by
+ * `decoding/source-map-record.d.ts` (and extended by the proposal-specific
+ * `decoding//.d.ts` files).
+ */
+ goldenFile: TestFile;
+ /**
+ * Whether decoding this source map should optionally report an error
+ * (https://tc39.es/ecma426/#sec-optional-errors).
+ */
+ optionalErrors: boolean;
+}
diff --git a/test/fixtures/test426/package-lock.json b/test/fixtures/test426/package-lock.json
new file mode 100644
index 000000000000..daf25a482c9f
--- /dev/null
+++ b/test/fixtures/test426/package-lock.json
@@ -0,0 +1,405 @@
+{
+ "name": "source-map-tests",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "typescript": "7.1.0-beta"
+ }
+ },
+ "node_modules/@typescript/typescript-aix-ppc64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.1.0-beta.tgz",
+ "integrity": "sha512-DXDG+jIWxBHGdoO70tcHON33rCnSzsEZj9P13oyf/r/KEGIo6awsdSNVNjAw0uMwEpqRu75IrgCcW58YcVkZHg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-android-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-android-arm64/-/typescript-android-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-UZx6PxoQavuvaVTrSf3aw0Oy/J/uJpRylipA0kzc038jMvZXh0UtE8vnQp20FqtdgD5bhXZ0aJrRJyoTSUO58Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-q7mxblcyX0E7hXsY4k+Q6KWKK5mGmfVq+BDesdTgRVgjodk27aIxER77Zyj9+TgSTlCje8F7+JM6f6hl2BuNdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-D5EYqG5Wm/guw9+0j8+Mn1Jbr/gfS/VtFTuoH8PJtFbcqeDaQGMulc7SW8q51QG6Y3wGOGX3nbsTOw6fIKWWAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-9A0XX0tcnH9qPdolSKKzSjTb93dlB0UEcAJbHYEsrFX2fkFNSum9+dvkQQJ9vLc+7vxHetDPujq9Qe6wx+Xhow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-EDf/dBHfY0TxX3NmUTek0hbffeyZlh3+ls6WQW3ICB8vYxwYLFnzg7wkgEUvl9eRlpiJ/nmc2S8arPMzINJ7rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.1.0-beta.tgz",
+ "integrity": "sha512-AvN9HmzOQh46G4312ooSgMHhbB7DtnLwg8D9mo324X49B7weMwtJrBG1MxT9IyI8kzDd7YRtW+1hWGun7WG7Qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-hNqQBgQ5b5oGqN76QqbAU6CwYhJZ6iOcWc2kPTUs2OentgpxZktETziH6KIyRFJPaiBncognwn2ZLu3DNhHiWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-loong64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.1.0-beta.tgz",
+ "integrity": "sha512-xbUustlBrFflt2pNEyG5ye+d4WGPc/rfr2xK29MXeobPSw297JqwuKRrvbKM5o3fE/eUM/c+urVm6jQ3IaMVTw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-mips64el": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.1.0-beta.tgz",
+ "integrity": "sha512-fS4/G7/0bXZ3NSq3nBMAXyg65I3tO4S7j08VNQ2g1aNC/qq79gE7Qne1BNjVsjMFfHBVBFjQYoTQCjmVU2iFlg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-ppc64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.1.0-beta.tgz",
+ "integrity": "sha512-0upCCxYPX8X6gOwkvfv+Ee7/ufyy7k97mTrGE0e5BxLlnUEa+w+rtgnv2dsKcxFdwdeZYVlZKCqgN/xCBV08+A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-riscv64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.1.0-beta.tgz",
+ "integrity": "sha512-0jKU9YRgOPRgiy5V/UWv37kB53PaTpIbwGCbbOMUPPAkrjVRNOIAJaz7Wa7s3H7mdDebX1LopUEPEJR+7Xa7zg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-s390x": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.1.0-beta.tgz",
+ "integrity": "sha512-j+d04s2kxaPQh6DLPb01sknAQfzsrdSZy15jBRHyLEZoaaQ19dVjbFRlkNVvYwUG7ZdP8+y5b/7RmPxdtHUKdQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-kcD51ihf+pnI5KT4uQQYG04OpwznaZYBlIQhDhl8fflFd27NktxXsUeguxZyvUMyWfum7IF2bBS6Jv7ox0AgFQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-8W85YyoGK+rgGoSU9jJQUpELbrKcZcbdJk2jPNQ6UKDKI7ryPlPFTrV2jCE5RQuv4Tt96hMDTc33s4YfRrLFBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-iGsTE0ve2PG0AcyZinwAR0LnGEZhu/y4dceS0hZsYCaKdYETvQtvtlNWlmYKempsBr6C7Il7dMXHVk+VFm9u0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-Z2FERdHB9fAKI+KGqdWn04O7uNlnB+VYiEm6INE1aZuzzLNd3KBqQ6IpKUJF2ny4GigGU6nVu75XD9tAUODPng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-NVG3qeyCq/rJEOhaRjR3LsWgjrjo5vJW25omVLeHAVnpKIG4pE4cenwwKGR06DiJ1s0QTOymcJeKXMEpsiNBVg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-sunos-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-ovAdoWXEn2B5/fyWG69pLU3FWmQwPWOjQV5TFbcxtpnOv+R3g8ySGHl1NYRq7Lb/GYdFDvaGcZ+SiU7iKuiMsA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-arm64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.1.0-beta.tgz",
+ "integrity": "sha512-lJbo71IEOfL7ZZIZnArRaOLqjn9BAfoSzyUQn0aSjfSpDZL4ru0afUsdBfo6PQEjD6ssdmi0n/RwaPADX6bsvQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-x64": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.1.0-beta.tgz",
+ "integrity": "sha512-UKcHvbybY2tDTvFW2b1vwiAvDlIRooo69lh8CXWVhOQejcvpM5+IgbiFW9EIwR5Wi5X/v+BdN7gdWbigBuJ3rw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "7.1.0-beta",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.1.0-beta.tgz",
+ "integrity": "sha512-cjWjZAJDsmZzsNC5GwHNlW0YPM65jZa5oR0KyQcYX3KSVIfTvtBq//vewISyatKk4OLi5grZjHhIVE9fdrw1UQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc"
+ },
+ "engines": {
+ "node": ">=16.20.0"
+ },
+ "optionalDependencies": {
+ "@typescript/typescript-aix-ppc64": "7.1.0-beta",
+ "@typescript/typescript-android-arm64": "7.1.0-beta",
+ "@typescript/typescript-darwin-arm64": "7.1.0-beta",
+ "@typescript/typescript-darwin-x64": "7.1.0-beta",
+ "@typescript/typescript-freebsd-arm64": "7.1.0-beta",
+ "@typescript/typescript-freebsd-x64": "7.1.0-beta",
+ "@typescript/typescript-linux-arm": "7.1.0-beta",
+ "@typescript/typescript-linux-arm64": "7.1.0-beta",
+ "@typescript/typescript-linux-loong64": "7.1.0-beta",
+ "@typescript/typescript-linux-mips64el": "7.1.0-beta",
+ "@typescript/typescript-linux-ppc64": "7.1.0-beta",
+ "@typescript/typescript-linux-riscv64": "7.1.0-beta",
+ "@typescript/typescript-linux-s390x": "7.1.0-beta",
+ "@typescript/typescript-linux-x64": "7.1.0-beta",
+ "@typescript/typescript-netbsd-arm64": "7.1.0-beta",
+ "@typescript/typescript-netbsd-x64": "7.1.0-beta",
+ "@typescript/typescript-openbsd-arm64": "7.1.0-beta",
+ "@typescript/typescript-openbsd-x64": "7.1.0-beta",
+ "@typescript/typescript-sunos-x64": "7.1.0-beta",
+ "@typescript/typescript-win32-arm64": "7.1.0-beta",
+ "@typescript/typescript-win32-x64": "7.1.0-beta"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/test426/package.json b/test/fixtures/test426/package.json
new file mode 100644
index 000000000000..f760360f8fe1
--- /dev/null
+++ b/test/fixtures/test426/package.json
@@ -0,0 +1,11 @@
+{
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "node scripts/build-npm-package.js",
+ "check-types": "node scripts/build-npm-package.js && node scripts/check-types.js"
+ },
+ "devDependencies": {
+ "typescript": "7.1.0-beta"
+ }
+}
diff --git a/test/fixtures/test426/scripts/build-npm-package.js b/test/fixtures/test426/scripts/build-npm-package.js
new file mode 100644
index 000000000000..7570fefce1d6
--- /dev/null
+++ b/test/fixtures/test426/scripts/build-npm-package.js
@@ -0,0 +1,240 @@
+// Builds the contents for the @tc39/source-map-tests npm package,
+// in the `npm-package` folder.
+
+import { execFileSync } from "node:child_process";
+import fs from "node:fs";
+import path from "node:path";
+
+const root = path.resolve(import.meta.dirname, "..");
+const outDir = path.join(root, "npm-package");
+const committedFiles = new Set([
+ "README.md",
+ "package.template.json",
+ "types",
+]);
+
+const errors = [];
+const error = msg => errors.push(msg);
+
+const rel = abs => path.relative(root, abs).split(path.sep).join("/");
+const abs = relPath => path.join(root, relPath);
+const out = relPath => path.join(outDir, relPath);
+
+function walk(dir) {
+ return fs
+ .readdirSync(dir, { withFileTypes: true })
+ .flatMap(e =>
+ e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]
+ );
+}
+
+/**
+ * Returns `{ path, content }` for a repo-relative path, or null if missing.
+ * `packagePath` is where the file ends up in the npm package.
+ */
+function fileEntry(relPath, context, packagePath = relPath) {
+ if (!fs.existsSync(abs(relPath))) {
+ error(`${context}: referenced file ${relPath} does not exist`);
+ return null;
+ }
+ return { path: packagePath, content: fs.readFileSync(abs(relPath), "utf8") };
+}
+
+function checkUniqueNames(suiteName, tests) {
+ const seen = new Set();
+ for (const { name } of tests) {
+ if (seen.has(name)) error(`${suiteName}: duplicate test name ${name}`);
+ seen.add(name);
+ }
+}
+
+// Interpreting suites: [-proposal]-tests.json manifests in the repo root,
+// whose files live in resources/. In the package, resources/
+// is copied to interpreting/resources/.
+
+const referencedResources = new Set();
+
+function buildInterpretingSuite(manifestFile) {
+ const name = manifestFile.replace(/-tests\.json$/, "").replace(/-proposal$/, "");
+ const manifest = JSON.parse(fs.readFileSync(abs(manifestFile), "utf8"));
+ const base = path.posix.join("resources", manifest.resourceBasePath ?? "");
+ const resource = (file, context) => {
+ const relPath = path.posix.join(base, file);
+ referencedResources.add(relPath);
+ return fileEntry(relPath, context, `interpreting/${relPath}`);
+ };
+
+ const tests = manifest.tests.map(test => {
+ const context = `${manifestFile} (${test.name})`;
+ return {
+ name: test.name,
+ description: test.description,
+ sourceMapIsValid: test.sourceMapIsValid,
+ baseFile: resource(test.baseFile, context),
+ sourceMapFile: resource(test.sourceMapFile, context),
+ testActions: (test.testActions ?? []).map(action =>
+ action.intermediateMaps
+ ? {
+ ...action,
+ intermediateMaps: action.intermediateMaps.map(m =>
+ resource(m, context)
+ ),
+ }
+ : action
+ ),
+ };
+ });
+ checkUniqueNames(name, tests);
+ return { name, kind: "interpreting", tests };
+}
+
+// Decoding suites: each directory under decoding/ containing .map files,
+// each with a .map.golden and an optional .error/.map.error.
+
+function buildDecodingSuite(dir) {
+ const name = path.basename(dir);
+ const files = fs.readdirSync(dir).sort();
+ const used = new Set();
+ const tests = [];
+
+ for (const file of files.filter(f => f.endsWith(".map"))) {
+ const testName = file.slice(0, -".map".length);
+ const context = `${rel(dir)} (${testName})`;
+ const sourceMapFile = fileEntry(rel(path.join(dir, file)), context);
+ const goldenFile = fileEntry(rel(path.join(dir, `${file}.golden`)), context);
+ const errorFile = [`${testName}.error`, `${file}.error`].find(f =>
+ files.includes(f)
+ );
+ used.add(file).add(`${file}.golden`).add(errorFile);
+
+ if (goldenFile) {
+ try {
+ JSON.parse(goldenFile.content);
+ } catch (e) {
+ error(`${goldenFile.path}: invalid JSON (${e.message})`);
+ }
+ }
+ tests.push({
+ name: testName,
+ sourceMapFile,
+ goldenFile,
+ optionalErrors: errorFile !== undefined,
+ });
+ }
+
+ for (const file of files) {
+ if (/\.(golden|error)$/.test(file) && !used.has(file)) {
+ error(`${rel(path.join(dir, file))}: no matching .map file`);
+ }
+ }
+
+ checkUniqueNames(name, tests);
+ return { name, kind: "decoding", tests };
+}
+
+// Stable spec tests come first, followed by proposals.
+const isProposal = f => f.endsWith("-proposal-tests.json");
+const interpretingSuites = fs
+ .readdirSync(root)
+ .filter(f => f.endsWith("-tests.json"))
+ .sort((a, b) => isProposal(a) - isProposal(b) || a.localeCompare(b))
+ .map(buildInterpretingSuite);
+
+const decodingDirs = [
+ path.join(root, "decoding"),
+ ...fs
+ .readdirSync(path.join(root, "decoding"), { withFileTypes: true })
+ .filter(e => e.isDirectory())
+ .map(e => path.join(root, "decoding", e.name))
+ .sort(),
+];
+const decodingSuites = decodingDirs
+ .filter(dir => fs.readdirSync(dir).some(f => f.endsWith(".map")))
+ .map(buildDecodingSuite);
+
+const suites = [...interpretingSuites, ...decodingSuites];
+
+// Suites go in /.json, next to the directory with their files.
+const suiteFile = ({ name, kind }) => `${kind}/${name}.json`;
+
+const suiteFiles = new Set();
+for (const suite of suites) {
+ const file = suiteFile(suite);
+ if (suiteFiles.has(file)) error(`two suites would be written to ${file}`);
+ suiteFiles.add(file);
+}
+
+for (const file of walk(path.join(root, "resources"))) {
+ if (file.endsWith(".map") && !referencedResources.has(rel(file))) {
+ error(`${rel(file)}: source map not referenced by any test`);
+ }
+}
+
+if (errors.length > 0) {
+ console.error(`Found ${errors.length} problem(s) in the test data:`);
+ for (const msg of errors) console.error(` - ${msg}`);
+ process.exit(1);
+}
+
+// Write the package.
+
+const writeJSON = (relPath, data) => {
+ fs.mkdirSync(path.dirname(out(relPath)), { recursive: true });
+ fs.writeFileSync(out(relPath), JSON.stringify(data, null, 2) + "\n");
+};
+
+const git = (...gitArgs) =>
+ execFileSync("git", gitArgs, { cwd: root, encoding: "utf8" }).trim();
+const version = `0.0.${git("rev-list", "--count", "HEAD")}`;
+const gitHead = git("rev-parse", "HEAD");
+
+for (const entry of fs.readdirSync(outDir)) {
+ if (!committedFiles.has(entry)) {
+ fs.rmSync(out(entry), { recursive: true, force: true });
+ }
+}
+
+fs.cpSync(abs("resources"), out("interpreting/resources"), { recursive: true });
+fs.cpSync(abs("decoding"), out("decoding"), { recursive: true });
+fs.copyFileSync(abs("LICENSE.md"), out("LICENSE.md"));
+
+for (const suite of suites) {
+ writeJSON(suiteFile(suite), { tests: suite.tests });
+}
+const packageJson = JSON.parse(
+ fs.readFileSync(out("package.template.json"), "utf8")
+);
+
+const decodingTypes = walk(abs("decoding"))
+ .filter(f => f.endsWith(".d.ts"))
+ .map(rel)
+ .sort();
+const exportedFiles = [
+ "package.json",
+ "suites.json",
+ ...suites.map(suiteFile),
+ ...decodingTypes,
+];
+writeJSON("package.json", {
+ ...packageJson,
+ version,
+ gitHead,
+ exports: {
+ ".": {
+ "types@>=7.1.0-0": "./types/index.d.ts",
+ types: "./types/types.d.ts",
+ },
+ ...Object.fromEntries(exportedFiles.map(f => [`./${f}`, `./${f}`])),
+ },
+});
+writeJSON("suites.json", {
+ suites: suites.map(suite => ({
+ kind: suite.kind,
+ file: suiteFile(suite),
+ })),
+});
+
+console.log(`Built ${packageJson.name}@${version} in ${rel(outDir)}/`);
+for (const { name, kind, tests } of suites) {
+ console.log(` ${name} (${kind}): ${tests.length} tests`);
+}
diff --git a/test/fixtures/test426/scripts/check-types.js b/test/fixtures/test426/scripts/check-types.js
new file mode 100644
index 000000000000..7c5cbf0d67e6
--- /dev/null
+++ b/test/fixtures/test426/scripts/check-types.js
@@ -0,0 +1,150 @@
+// Checks that the JSON files in the generated npm package match
+// the TS definitions. Run build-npm-package.js first.
+
+import { spawnSync } from "node:child_process";
+import fs from "node:fs";
+import path from "node:path";
+
+const root = path.resolve(import.meta.dirname, "..");
+const outDir = path.join(root, ".tmp", "check-types");
+const tsc = path.join(root, "node_modules", ".bin", "tsc");
+
+const compilerOptions = {
+ strict: true,
+ noEmit: true,
+ skipLibCheck: false,
+ module: "nodenext",
+ moduleResolution: "nodenext",
+ target: "esnext",
+ types: [],
+};
+
+const relFrom = (from, to) => {
+ const rel = path.relative(path.dirname(from), to).split(path.sep).join("/");
+ return rel.startsWith(".") ? rel : `./${rel}`;
+};
+
+function writeCheck(tsFile, header, type, jsonFile) {
+ const json = fs.readFileSync(jsonFile, "utf8");
+ fs.mkdirSync(path.dirname(tsFile), { recursive: true });
+ fs.writeFileSync(
+ tsFile,
+ `${header}\nexport default ${json.trim()} satisfies ${type};\n`
+ );
+}
+
+function runTsc(name, projectDir, files) {
+ const tsconfig = path.join(projectDir, "tsconfig.json");
+ fs.writeFileSync(tsconfig, JSON.stringify({ compilerOptions, files }, null, 2));
+ const { status } = spawnSync(tsc, ["-p", tsconfig, "--pretty"], {
+ stdio: "inherit",
+ });
+ console.log(`${status === 0 ? "ok" : "FAIL"} ${name}`);
+ return status === 0;
+}
+
+fs.rmSync(outDir, { recursive: true, force: true });
+let ok = true;
+
+// npm package files.
+
+const pkgDir = path.join(root, "npm-package");
+if (!fs.existsSync(path.join(pkgDir, "suites.json"))) {
+ console.error("npm-package/suites.json not found, run scripts/build-npm-package.js first.");
+ process.exit(1);
+}
+{
+ const projectDir = path.join(outDir, "npm-package");
+ const typesFile = path.join(pkgDir, "types", "types.d.ts");
+ const files = [];
+ const check = (jsonRel, type) => {
+ const tsFile = path.join(projectDir, `${jsonRel}.ts`);
+ const header = `import type { ${type} } from "${relFrom(tsFile, typesFile)}";`;
+ writeCheck(tsFile, header, type, path.join(pkgDir, jsonRel));
+ files.push(path.relative(projectDir, tsFile));
+ };
+
+ const index = JSON.parse(fs.readFileSync(path.join(pkgDir, "suites.json"), "utf8"));
+ const expectedTypes = new Map([["suites.json", "SuitesIndex"]]);
+ for (const { kind, file } of index.suites) {
+ expectedTypes.set(file, kind === "interpreting" ? "InterpretingSuite" : "DecodingSuite");
+ }
+ for (const [file, type] of expectedTypes) check(file, type);
+
+ // Check that importing each exported JSON file `with { type: "json" }` gets
+ // its declared type from modules.d.ts.
+ const { name: pkgName, exports } = JSON.parse(
+ fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")
+ );
+ const exportedJSON = Object.keys(exports)
+ .filter(key => key.endsWith(".json") && key !== "./package.json")
+ .map(key => key.slice(2));
+ const importsFile = path.join(projectDir, "imports.ts");
+ const typeNames = [...new Set(expectedTypes.values())];
+ fs.writeFileSync(
+ importsFile,
+ [
+ `import type { ${typeNames.join(", ")} } from "${relFrom(importsFile, path.join(pkgDir, "types", "index.js"))}";`,
+ "type Equals = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;",
+ ...exportedJSON.flatMap((file, i) => {
+ const type = expectedTypes.get(file);
+ if (!type) throw new Error(`Exported ${file} is not a known suite file`);
+ return [
+ `import json${i} from "${pkgName}/${file}" with { type: "json" };`,
+ `export const check${i}: Equals = true; // ${file}`,
+ ];
+ }),
+ ].join("\n") + "\n"
+ );
+ files.push(path.relative(projectDir, importsFile));
+
+ ok = runTsc("npm-package", projectDir, files) && ok;
+
+ // Check that each exported decoding/**/*.d.ts file can be loaded through the
+ // package's exports. Each one is checked separately, because they extend
+ // the same global interfaces.
+ for (const key of Object.keys(exports).filter(k => k.endsWith(".d.ts"))) {
+ const name = `npm-package ${key.slice(2)}`;
+ const dtsDir = path.join(outDir, "exports", key.slice(2));
+ const linkDir = path.join(dtsDir, "node_modules", ...pkgName.split("/"));
+ fs.mkdirSync(path.dirname(linkDir), { recursive: true });
+ fs.symlinkSync(pkgDir, linkDir, "dir");
+ fs.writeFileSync(
+ path.join(dtsDir, "index.ts"),
+ `/// \nexport type T = SourceMapRecord;\n`
+ );
+ ok = runTsc(name, dtsDir, ["index.ts"]) && ok;
+ }
+}
+
+// Check golden files
+
+const decodingDir = path.join(root, "decoding");
+const decodingSuiteDirs = [
+ decodingDir,
+ ...fs
+ .readdirSync(decodingDir, { withFileTypes: true })
+ .filter(e => e.isDirectory())
+ .map(e => path.join(decodingDir, e.name)),
+];
+for (const dir of decodingSuiteDirs) {
+ const goldens = fs.readdirSync(dir).filter(f => f.endsWith(".golden")).sort();
+ if (goldens.length === 0) continue;
+
+ const name = path.relative(root, dir).split(path.sep).join("/");
+ const projectDir = path.join(outDir, name);
+ const suiteTypes = path.join(dir, `${path.basename(dir)}.d.ts`);
+ const typesFile = fs.existsSync(suiteTypes)
+ ? suiteTypes
+ : path.join(decodingDir, "source-map-record.d.ts");
+
+ const files = goldens.map(golden => {
+ const tsFile = path.join(projectDir, `${golden}.ts`);
+ const header = `/// `;
+ writeCheck(tsFile, header, "SourceMapRecord", path.join(dir, golden));
+ return path.relative(projectDir, tsFile);
+ });
+ ok = runTsc(name, projectDir, files) && ok;
+}
+
+process.exit(ok ? 0 : 1);
diff --git a/test/test426/README.md b/test/test426/README.md
index 6935fc43c3d2..85db408434bc 100644
--- a/test/test426/README.md
+++ b/test/test426/README.md
@@ -7,7 +7,7 @@ suite ensures that the Node.js source map implementation conforms to the
The [`test/fixtures/test426`](../fixtures/test426/) contains a copy of the set of
[Source Map Tests][] suite. The last updated hash is:
-*
+*
See the json files in [the `status` folder](./status) for prerequisites,
expected failures, and support status for specific tests.