carveout 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +44 -0
- package/dist/src/api.d.ts +135 -3
- package/dist/src/api.js +144 -2
- package/dist/src/carve.d.ts +55 -2
- package/dist/src/carve.js +31 -19
- package/dist/src/cut.d.ts +17 -0
- package/dist/src/cut.js +13 -7
- package/dist/src/extract.d.ts +31 -0
- package/dist/src/extract.js +31 -0
- package/dist/src/graph.d.ts +7 -2
- package/dist/src/graph.js +7 -4
- package/dist/src/project/mock-runtime.d.ts +9 -0
- package/dist/src/project/mock-runtime.js +81 -0
- package/dist/src/project/paths.d.ts +10 -0
- package/dist/src/project/paths.js +35 -0
- package/dist/src/project/project.d.ts +3 -6
- package/dist/src/project/project.js +3 -104
- package/dist/src/pseudonyms/carry.d.ts +161 -0
- package/dist/src/pseudonyms/carry.js +301 -0
- package/dist/src/pseudonyms/draft.js +1 -1
- package/dist/src/pseudonyms/folder.js +2 -40
- package/dist/src/pseudonyms/legacy/aliases-v1.d.ts +107 -0
- package/dist/src/pseudonyms/legacy/aliases-v1.js +391 -0
- package/dist/src/pseudonyms/legacy/vocabulary-v1.d.ts +107 -0
- package/dist/src/pseudonyms/legacy/vocabulary-v1.js +460 -0
- package/dist/src/pseudonyms/legacy.d.ts +34 -0
- package/dist/src/pseudonyms/legacy.js +46 -0
- package/dist/src/pseudonyms/vocabulary.d.ts +29 -1
- package/dist/src/pseudonyms/vocabulary.js +57 -0
- package/dist/src/pseudonyms.d.ts +20 -0
- package/dist/src/pseudonyms.js +20 -0
- package/package.json +9 -1
- package/skills/carveout/SKILL.md +15 -13
- package/skills/carveout-gate/SKILL.md +3 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `carveout/extract`: the stub extractor and the fixture compile a cut
|
|
3
|
+
* runs, for a platform that calls them itself (sandbox-factory's worker,
|
|
4
|
+
* whose `slice/*`, `compiler-config.ts` and `package-registry.ts` these
|
|
5
|
+
* were forked from; NOTICE.md lists where they differ now).
|
|
6
|
+
*
|
|
7
|
+
* - `extract/hash.ts`: `sha256`, `gitBlobId`, `gitFileMode`.
|
|
8
|
+
* - `extract/typescript.ts`: the boundary extractor and its parts
|
|
9
|
+
* (`extractTypeScriptBoundary`, `boundedHost`, `extractionOptions`,
|
|
10
|
+
* `filterDeclaration`, `sanitizeDeclaration`, `NODE_BUILTINS`,
|
|
11
|
+
* `isNodeBuiltin`, `isScriptPath`, `isDataPath`, `declarationPathFor`).
|
|
12
|
+
* - `extract/fixture.ts`: `compileFixture`, `packageUses`,
|
|
13
|
+
* `packageModules`, `packageShim`, `SHIM_PATH`, `UNTYPED_PACKAGE_OPTIONS`,
|
|
14
|
+
* `UNTYPED_PACKAGE_CODES`, `DIAGNOSTICS_MAX`.
|
|
15
|
+
* - `extract/package-types.ts`: `npmPackageTypes` (npm, scripts off) and
|
|
16
|
+
* the `PackageTypesFetcher` contract `analyse` takes.
|
|
17
|
+
* - `extract/compiler-config.ts`: `loadConfig`, `nearestCompilerOptions`,
|
|
18
|
+
* `workspacePackages`, `workspaceModuleResolver`, `exportTarget`,
|
|
19
|
+
* `pnpmWorkspacePatterns`.
|
|
20
|
+
* - `extract/package-registry.ts`: `packageRegistries`,
|
|
21
|
+
* `isPublicRegistry`, `lockIntegrities`, `publicByIntegrity`.
|
|
22
|
+
*
|
|
23
|
+
* Only `npmPackageTypes` starts a process; `publicByIntegrity` reads the
|
|
24
|
+
* public registry over HTTPS.
|
|
25
|
+
*/
|
|
26
|
+
export * from "./extract/hash.js";
|
|
27
|
+
export * from "./extract/typescript.js";
|
|
28
|
+
export * from "./extract/fixture.js";
|
|
29
|
+
export * from "./extract/package-types.js";
|
|
30
|
+
export * from "./extract/compiler-config.js";
|
|
31
|
+
export * from "./extract/package-registry.js";
|
package/dist/src/graph.d.ts
CHANGED
|
@@ -25,9 +25,12 @@ export declare const IGNORED_DIRECTORIES: readonly string[];
|
|
|
25
25
|
export declare const GRAPH_FILES_MAX = 50000;
|
|
26
26
|
/**
|
|
27
27
|
* The checkout's files, `/`-separated and sorted: what Git tracks plus
|
|
28
|
-
* untracked files it does not ignore, or every file outside Git.
|
|
28
|
+
* untracked files it does not ignore, or every file outside Git. With
|
|
29
|
+
* `git: false`, every file outside the ignored folders, and Git is not run.
|
|
29
30
|
*/
|
|
30
|
-
export declare function listFiles(root: string
|
|
31
|
+
export declare function listFiles(root: string, options?: {
|
|
32
|
+
readonly git?: boolean;
|
|
33
|
+
}): string[];
|
|
31
34
|
/**
|
|
32
35
|
* The checkout's files a Vite glob's patterns match, as the bundler reads
|
|
33
36
|
* them: relative to the importing file's folder, or to the root for a
|
|
@@ -54,6 +57,8 @@ export interface GraphOptions {
|
|
|
54
57
|
readonly followWorkspaces?: boolean;
|
|
55
58
|
/** Files read as if the checkout had none (the owner's acceptance tests). */
|
|
56
59
|
readonly unseen?: readonly string[];
|
|
60
|
+
/** The checkout's files, as the caller lists them; by default `listFiles`. */
|
|
61
|
+
readonly files?: readonly string[];
|
|
57
62
|
}
|
|
58
63
|
/** What `walkGraph` takes: the walk's options and the graph's. */
|
|
59
64
|
export type WalkOptions = ReachOptions & GraphOptions;
|
package/dist/src/graph.js
CHANGED
|
@@ -58,11 +58,14 @@ function walk(root, directory = "", into = []) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* The checkout's files, `/`-separated and sorted: what Git tracks plus
|
|
61
|
-
* untracked files it does not ignore, or every file outside Git.
|
|
61
|
+
* untracked files it does not ignore, or every file outside Git. With
|
|
62
|
+
* `git: false`, every file outside the ignored folders, and Git is not run.
|
|
62
63
|
*/
|
|
63
|
-
export function listFiles(root) {
|
|
64
|
+
export function listFiles(root, options = {}) {
|
|
64
65
|
let listed;
|
|
65
66
|
try {
|
|
67
|
+
if (options.git === false)
|
|
68
|
+
throw new Error("Git is not to be run.");
|
|
66
69
|
listed = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 256 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] })
|
|
67
70
|
.toString("utf8")
|
|
68
71
|
.split("\0")
|
|
@@ -140,9 +143,9 @@ export function buildGraph(root, listed, options = {}) {
|
|
|
140
143
|
*/
|
|
141
144
|
export function walkGraph(root, entryPoints, budget, options = {}) {
|
|
142
145
|
const started = performance.now();
|
|
143
|
-
const { followWorkspaces, unseen, ...rest } = options;
|
|
146
|
+
const { followWorkspaces, unseen, files: listed, ...rest } = options;
|
|
144
147
|
const hidden = new Set(unseen ?? []);
|
|
145
|
-
const files = listFiles(root).filter((file) => !hidden.has(file));
|
|
148
|
+
const files = (listed ?? listFiles(root)).filter((file) => !hidden.has(file));
|
|
146
149
|
const graphOptions = followWorkspaces === undefined ? {} : { followWorkspaces };
|
|
147
150
|
const builder = graphBuilder(root, files, performance.now() - started, graphOptions);
|
|
148
151
|
const absoluteRoot = resolve(root);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recording mock runtime every generated module and mock package
|
|
3
|
+
* imports (`carveout/mock.js`), and its declarations (`carveout/mock.d.ts`),
|
|
4
|
+
* as text. Pure: no Node module and no compiler.
|
|
5
|
+
*/
|
|
6
|
+
/** The runtime itself, written at the harness's `mock.js`. */
|
|
7
|
+
export declare const MOCK_RUNTIME = "// The recording mock every generated module and package shares.\n//\n// A mock answers any property with another mock, any call with a mock and\n// records the call. It is not thenable, so awaiting one gives the mock\n// back, and it prints as [mock <name>]. Tests can install an implementation\n// for a path with mockImplementation() and read what was called with\n// recordedCalls(). Nothing here reaches a network or a credential.\nconst calls = [];\nconst implementations = new Map();\nconst fixtures = new Map();\nexport function recordedCalls() {\n return calls.slice();\n}\n/** Clears recorded calls and installed implementations; fixtures stay. */\nexport function resetMocks() {\n calls.length = 0;\n implementations.clear();\n}\n/** @param {string} path such as \"module:lib/service.ts.createService\" */\nexport function mockImplementation(path, implementation) {\n implementations.set(path, implementation);\n}\n/** The task's default behaviour for a path; mockImplementation() overrides it. */\nexport function fixture(path, implementation) {\n fixtures.set(path, implementation);\n}\nexport function createMock(origin, name = origin) {\n const target = function mock() {};\n return new Proxy(target, {\n get(_target, property) {\n if (property === \"then\") return undefined;\n if (property === Symbol.toPrimitive) return () => `[mock ${name}]`;\n if (property === Symbol.iterator) return function* iterate() {};\n if (property === \"toJSON\") return () => ({ mock: name });\n if (typeof property === \"symbol\") return undefined;\n return createMock(origin, `${name}.${property}`);\n },\n apply(_target, _self, args) {\n calls.push({ name, args });\n const implementation = implementations.get(name) ?? fixtures.get(name);\n return implementation === undefined\n ? createMock(origin, `${name}()`)\n : implementation(...args);\n },\n construct(_target, args) {\n calls.push({ name: `new ${name}`, args });\n const implementation =\n implementations.get(`new ${name}`) ?? fixtures.get(`new ${name}`);\n return implementation === undefined\n ? createMock(origin, `${name}#`)\n : implementation(...args);\n },\n has() {\n return true;\n },\n });\n}\n";
|
|
8
|
+
/** Its declarations, written at the harness's `mock.d.ts`. */
|
|
9
|
+
export declare const MOCK_RUNTIME_TYPES = "export interface RecordedCall {\n readonly name: string;\n readonly args: readonly unknown[];\n}\nexport function recordedCalls(): RecordedCall[];\nexport function resetMocks(): void;\nexport function mockImplementation(\n path: string,\n implementation: (...args: unknown[]) => unknown,\n): void;\nexport function fixture(\n path: string,\n implementation: (...args: any[]) => unknown,\n): void;\nexport function createMock(origin: string, name?: string): any;\n";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recording mock runtime every generated module and mock package
|
|
3
|
+
* imports (`carveout/mock.js`), and its declarations (`carveout/mock.d.ts`),
|
|
4
|
+
* as text. Pure: no Node module and no compiler.
|
|
5
|
+
*/
|
|
6
|
+
/** The runtime itself, written at the harness's `mock.js`. */
|
|
7
|
+
export const MOCK_RUNTIME = `// The recording mock every generated module and package shares.
|
|
8
|
+
//
|
|
9
|
+
// A mock answers any property with another mock, any call with a mock and
|
|
10
|
+
// records the call. It is not thenable, so awaiting one gives the mock
|
|
11
|
+
// back, and it prints as [mock <name>]. Tests can install an implementation
|
|
12
|
+
// for a path with mockImplementation() and read what was called with
|
|
13
|
+
// recordedCalls(). Nothing here reaches a network or a credential.
|
|
14
|
+
const calls = [];
|
|
15
|
+
const implementations = new Map();
|
|
16
|
+
const fixtures = new Map();
|
|
17
|
+
export function recordedCalls() {
|
|
18
|
+
return calls.slice();
|
|
19
|
+
}
|
|
20
|
+
/** Clears recorded calls and installed implementations; fixtures stay. */
|
|
21
|
+
export function resetMocks() {
|
|
22
|
+
calls.length = 0;
|
|
23
|
+
implementations.clear();
|
|
24
|
+
}
|
|
25
|
+
/** @param {string} path such as "module:lib/service.ts.createService" */
|
|
26
|
+
export function mockImplementation(path, implementation) {
|
|
27
|
+
implementations.set(path, implementation);
|
|
28
|
+
}
|
|
29
|
+
/** The task's default behaviour for a path; mockImplementation() overrides it. */
|
|
30
|
+
export function fixture(path, implementation) {
|
|
31
|
+
fixtures.set(path, implementation);
|
|
32
|
+
}
|
|
33
|
+
export function createMock(origin, name = origin) {
|
|
34
|
+
const target = function mock() {};
|
|
35
|
+
return new Proxy(target, {
|
|
36
|
+
get(_target, property) {
|
|
37
|
+
if (property === "then") return undefined;
|
|
38
|
+
if (property === Symbol.toPrimitive) return () => \`[mock \${name}]\`;
|
|
39
|
+
if (property === Symbol.iterator) return function* iterate() {};
|
|
40
|
+
if (property === "toJSON") return () => ({ mock: name });
|
|
41
|
+
if (typeof property === "symbol") return undefined;
|
|
42
|
+
return createMock(origin, \`\${name}.\${property}\`);
|
|
43
|
+
},
|
|
44
|
+
apply(_target, _self, args) {
|
|
45
|
+
calls.push({ name, args });
|
|
46
|
+
const implementation = implementations.get(name) ?? fixtures.get(name);
|
|
47
|
+
return implementation === undefined
|
|
48
|
+
? createMock(origin, \`\${name}()\`)
|
|
49
|
+
: implementation(...args);
|
|
50
|
+
},
|
|
51
|
+
construct(_target, args) {
|
|
52
|
+
calls.push({ name: \`new \${name}\`, args });
|
|
53
|
+
const implementation =
|
|
54
|
+
implementations.get(\`new \${name}\`) ?? fixtures.get(\`new \${name}\`);
|
|
55
|
+
return implementation === undefined
|
|
56
|
+
? createMock(origin, \`\${name}#\`)
|
|
57
|
+
: implementation(...args);
|
|
58
|
+
},
|
|
59
|
+
has() {
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
/** Its declarations, written at the harness's `mock.d.ts`. */
|
|
66
|
+
export const MOCK_RUNTIME_TYPES = `export interface RecordedCall {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly args: readonly unknown[];
|
|
69
|
+
}
|
|
70
|
+
export function recordedCalls(): RecordedCall[];
|
|
71
|
+
export function resetMocks(): void;
|
|
72
|
+
export function mockImplementation(
|
|
73
|
+
path: string,
|
|
74
|
+
implementation: (...args: unknown[]) => unknown,
|
|
75
|
+
): void;
|
|
76
|
+
export function fixture(
|
|
77
|
+
path: string,
|
|
78
|
+
implementation: (...args: any[]) => unknown,
|
|
79
|
+
): void;
|
|
80
|
+
export function createMock(origin: string, name?: string): any;
|
|
81
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths inside a generated folder, as `/`-separated strings: pure, with no
|
|
3
|
+
* Node module and no compiler, so the pseudonyms entry (`carveout/pseudonyms`)
|
|
4
|
+
* can use them in a browser.
|
|
5
|
+
*/
|
|
6
|
+
export declare function dirnameOf(path: string): string;
|
|
7
|
+
/** `../lib/service.js` from `src/app.ts` to `lib/service.ts`. */
|
|
8
|
+
export declare function relativeSpecifier(fromFile: string, toFile: string): string;
|
|
9
|
+
/** The emitted JavaScript path of a TypeScript source path. */
|
|
10
|
+
export declare function runtimePath(path: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths inside a generated folder, as `/`-separated strings: pure, with no
|
|
3
|
+
* Node module and no compiler, so the pseudonyms entry (`carveout/pseudonyms`)
|
|
4
|
+
* can use them in a browser.
|
|
5
|
+
*/
|
|
6
|
+
export function dirnameOf(path) {
|
|
7
|
+
const index = path.lastIndexOf("/");
|
|
8
|
+
return index === -1 ? "" : path.slice(0, index);
|
|
9
|
+
}
|
|
10
|
+
/** `../lib/service.js` from `src/app.ts` to `lib/service.ts`. */
|
|
11
|
+
export function relativeSpecifier(fromFile, toFile) {
|
|
12
|
+
const from = dirnameOf(fromFile).split("/").filter(Boolean);
|
|
13
|
+
const to = toFile.split("/").filter(Boolean);
|
|
14
|
+
let common = 0;
|
|
15
|
+
while (common < from.length &&
|
|
16
|
+
common < to.length - 1 &&
|
|
17
|
+
from[common] === to[common])
|
|
18
|
+
common += 1;
|
|
19
|
+
const up = from.length - common;
|
|
20
|
+
const rest = to.slice(common).join("/");
|
|
21
|
+
const prefix = up === 0 ? "./" : "../".repeat(up);
|
|
22
|
+
return `${prefix}${runtimePath(rest)}`;
|
|
23
|
+
}
|
|
24
|
+
/** The emitted JavaScript path of a TypeScript source path. */
|
|
25
|
+
export function runtimePath(path) {
|
|
26
|
+
if (path.endsWith(".d.ts"))
|
|
27
|
+
return `${path.slice(0, -5)}.js`;
|
|
28
|
+
if (path.endsWith(".mts"))
|
|
29
|
+
return `${path.slice(0, -4)}.mjs`;
|
|
30
|
+
if (path.endsWith(".cts"))
|
|
31
|
+
return `${path.slice(0, -4)}.cjs`;
|
|
32
|
+
if (path.endsWith(".tsx") || path.endsWith(".ts"))
|
|
33
|
+
return `${path.replace(/\.tsx?$/, "")}.js`;
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
@@ -21,6 +21,9 @@ import { type VitestSettings } from "./vitest-config.js";
|
|
|
21
21
|
import type { BoundaryContract, BoundarySymbol, SliceManifest } from "../slice/manifest.js";
|
|
22
22
|
import { type Toolchain } from "./build.js";
|
|
23
23
|
import { type PathClass, type ResolvedDependency } from "./provenance.js";
|
|
24
|
+
import { MOCK_RUNTIME_TYPES } from "./mock-runtime.js";
|
|
25
|
+
import { dirnameOf, relativeSpecifier, runtimePath } from "./paths.js";
|
|
26
|
+
export { MOCK_RUNTIME_TYPES, dirnameOf, relativeSpecifier, runtimePath };
|
|
24
27
|
export declare const HARNESS_DIR = "carveout";
|
|
25
28
|
export declare const MOCKS_DIR = "carveout/packages";
|
|
26
29
|
/** Mocks of modules that are no package (`virtual:build-info`), with the loader's map of them. */
|
|
@@ -194,11 +197,6 @@ export declare function runtimeKindOf(symbol: BoundarySymbol): "value" | "type"
|
|
|
194
197
|
* it (harmless for a type), a test must not assert it.
|
|
195
198
|
*/
|
|
196
199
|
export declare function guessedFromImport(symbol: BoundarySymbol): boolean;
|
|
197
|
-
export declare function dirnameOf(path: string): string;
|
|
198
|
-
/** `../lib/service.js` from `src/app.ts` to `lib/service.ts`. */
|
|
199
|
-
export declare function relativeSpecifier(fromFile: string, toFile: string): string;
|
|
200
|
-
/** The emitted JavaScript path of a TypeScript source path. */
|
|
201
|
-
export declare function runtimePath(path: string): string;
|
|
202
200
|
/** What `npm run dev` and `npm test` run once the project is built. */
|
|
203
201
|
export declare const SANDBOX_RUN_ARGV: {
|
|
204
202
|
readonly dev: readonly ["node", "--env-file=carveout.env", "--import=./carveout/assets.js", string];
|
|
@@ -219,7 +217,6 @@ export declare function enumMembers(declaration: string): {
|
|
|
219
217
|
name: string;
|
|
220
218
|
value: string | number;
|
|
221
219
|
}[] | null;
|
|
222
|
-
export declare const MOCK_RUNTIME_TYPES = "export interface RecordedCall {\n readonly name: string;\n readonly args: readonly unknown[];\n}\nexport function recordedCalls(): RecordedCall[];\nexport function resetMocks(): void;\nexport function mockImplementation(\n path: string,\n implementation: (...args: unknown[]) => unknown,\n): void;\nexport function fixture(\n path: string,\n implementation: (...args: any[]) => unknown,\n): void;\nexport function createMock(origin: string, name?: string): any;\n";
|
|
223
220
|
/**
|
|
224
221
|
* Module declarations for the specifiers a slice reads names of, each
|
|
225
222
|
* exporting those names as values and types, as the worker writes them for
|
|
@@ -26,6 +26,9 @@ import { TEST_PATH_PATTERN, isTestPath, testRunnerOf, } from "../slice/reach.js"
|
|
|
26
26
|
import { GLOBAL_CALL_SOURCE, usesTestGlobals } from "./runner.js";
|
|
27
27
|
import { scanSecrets, secretBlockers } from "./secrets.js";
|
|
28
28
|
import { moduleSpecifiers, rewriteSpecifiers } from "../specifiers.js";
|
|
29
|
+
import { MOCK_RUNTIME, MOCK_RUNTIME_TYPES } from "./mock-runtime.js";
|
|
30
|
+
import { dirnameOf, relativeSpecifier, runtimePath } from "./paths.js";
|
|
31
|
+
export { MOCK_RUNTIME_TYPES, dirnameOf, relativeSpecifier, runtimePath };
|
|
29
32
|
export const HARNESS_DIR = "carveout";
|
|
30
33
|
export const MOCKS_DIR = `${HARNESS_DIR}/packages`;
|
|
31
34
|
/** Mocks of modules that are no package (`virtual:build-info`), with the loader's map of them. */
|
|
@@ -334,36 +337,6 @@ export function guessedFromImport(symbol) {
|
|
|
334
337
|
// this pattern finds only the named bindings of an import, for the mock
|
|
335
338
|
// packages' exports.
|
|
336
339
|
const NAMED_IMPORT = /\bimport\s+(type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*['"]([^'"\n]+)['"]/g;
|
|
337
|
-
export function dirnameOf(path) {
|
|
338
|
-
const index = path.lastIndexOf("/");
|
|
339
|
-
return index === -1 ? "" : path.slice(0, index);
|
|
340
|
-
}
|
|
341
|
-
/** `../lib/service.js` from `src/app.ts` to `lib/service.ts`. */
|
|
342
|
-
export function relativeSpecifier(fromFile, toFile) {
|
|
343
|
-
const from = dirnameOf(fromFile).split("/").filter(Boolean);
|
|
344
|
-
const to = toFile.split("/").filter(Boolean);
|
|
345
|
-
let common = 0;
|
|
346
|
-
while (common < from.length &&
|
|
347
|
-
common < to.length - 1 &&
|
|
348
|
-
from[common] === to[common])
|
|
349
|
-
common += 1;
|
|
350
|
-
const up = from.length - common;
|
|
351
|
-
const rest = to.slice(common).join("/");
|
|
352
|
-
const prefix = up === 0 ? "./" : "../".repeat(up);
|
|
353
|
-
return `${prefix}${runtimePath(rest)}`;
|
|
354
|
-
}
|
|
355
|
-
/** The emitted JavaScript path of a TypeScript source path. */
|
|
356
|
-
export function runtimePath(path) {
|
|
357
|
-
if (path.endsWith(".d.ts"))
|
|
358
|
-
return `${path.slice(0, -5)}.js`;
|
|
359
|
-
if (path.endsWith(".mts"))
|
|
360
|
-
return `${path.slice(0, -4)}.mjs`;
|
|
361
|
-
if (path.endsWith(".cts"))
|
|
362
|
-
return `${path.slice(0, -4)}.cjs`;
|
|
363
|
-
if (path.endsWith(".tsx") || path.endsWith(".ts"))
|
|
364
|
-
return `${path.replace(/\.tsx?$/, "")}.js`;
|
|
365
|
-
return path;
|
|
366
|
-
}
|
|
367
340
|
/** What `npm run dev` and `npm test` run once the project is built. */
|
|
368
341
|
export const SANDBOX_RUN_ARGV = {
|
|
369
342
|
dev: [
|
|
@@ -504,80 +477,6 @@ function mockRuntimeFor(modulePath, originalModule, symbols, blockers, stubText
|
|
|
504
477
|
lines.push("export {};");
|
|
505
478
|
return `${lines.join("\n")}\n`;
|
|
506
479
|
}
|
|
507
|
-
const MOCK_RUNTIME = `// The recording mock every generated module and package shares.
|
|
508
|
-
//
|
|
509
|
-
// A mock answers any property with another mock, any call with a mock and
|
|
510
|
-
// records the call. It is not thenable, so awaiting one gives the mock
|
|
511
|
-
// back, and it prints as [mock <name>]. Tests can install an implementation
|
|
512
|
-
// for a path with mockImplementation() and read what was called with
|
|
513
|
-
// recordedCalls(). Nothing here reaches a network or a credential.
|
|
514
|
-
const calls = [];
|
|
515
|
-
const implementations = new Map();
|
|
516
|
-
const fixtures = new Map();
|
|
517
|
-
export function recordedCalls() {
|
|
518
|
-
return calls.slice();
|
|
519
|
-
}
|
|
520
|
-
/** Clears recorded calls and installed implementations; fixtures stay. */
|
|
521
|
-
export function resetMocks() {
|
|
522
|
-
calls.length = 0;
|
|
523
|
-
implementations.clear();
|
|
524
|
-
}
|
|
525
|
-
/** @param {string} path such as "module:lib/service.ts.createService" */
|
|
526
|
-
export function mockImplementation(path, implementation) {
|
|
527
|
-
implementations.set(path, implementation);
|
|
528
|
-
}
|
|
529
|
-
/** The task's default behaviour for a path; mockImplementation() overrides it. */
|
|
530
|
-
export function fixture(path, implementation) {
|
|
531
|
-
fixtures.set(path, implementation);
|
|
532
|
-
}
|
|
533
|
-
export function createMock(origin, name = origin) {
|
|
534
|
-
const target = function mock() {};
|
|
535
|
-
return new Proxy(target, {
|
|
536
|
-
get(_target, property) {
|
|
537
|
-
if (property === "then") return undefined;
|
|
538
|
-
if (property === Symbol.toPrimitive) return () => \`[mock \${name}]\`;
|
|
539
|
-
if (property === Symbol.iterator) return function* iterate() {};
|
|
540
|
-
if (property === "toJSON") return () => ({ mock: name });
|
|
541
|
-
if (typeof property === "symbol") return undefined;
|
|
542
|
-
return createMock(origin, \`\${name}.\${property}\`);
|
|
543
|
-
},
|
|
544
|
-
apply(_target, _self, args) {
|
|
545
|
-
calls.push({ name, args });
|
|
546
|
-
const implementation = implementations.get(name) ?? fixtures.get(name);
|
|
547
|
-
return implementation === undefined
|
|
548
|
-
? createMock(origin, \`\${name}()\`)
|
|
549
|
-
: implementation(...args);
|
|
550
|
-
},
|
|
551
|
-
construct(_target, args) {
|
|
552
|
-
calls.push({ name: \`new \${name}\`, args });
|
|
553
|
-
const implementation =
|
|
554
|
-
implementations.get(\`new \${name}\`) ?? fixtures.get(\`new \${name}\`);
|
|
555
|
-
return implementation === undefined
|
|
556
|
-
? createMock(origin, \`\${name}#\`)
|
|
557
|
-
: implementation(...args);
|
|
558
|
-
},
|
|
559
|
-
has() {
|
|
560
|
-
return true;
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
`;
|
|
565
|
-
export const MOCK_RUNTIME_TYPES = `export interface RecordedCall {
|
|
566
|
-
readonly name: string;
|
|
567
|
-
readonly args: readonly unknown[];
|
|
568
|
-
}
|
|
569
|
-
export function recordedCalls(): RecordedCall[];
|
|
570
|
-
export function resetMocks(): void;
|
|
571
|
-
export function mockImplementation(
|
|
572
|
-
path: string,
|
|
573
|
-
implementation: (...args: unknown[]) => unknown,
|
|
574
|
-
): void;
|
|
575
|
-
export function fixture(
|
|
576
|
-
path: string,
|
|
577
|
-
implementation: (...args: any[]) => unknown,
|
|
578
|
-
): void;
|
|
579
|
-
export function createMock(origin: string, name?: string): any;
|
|
580
|
-
`;
|
|
581
480
|
/** Classes Node has and the ES libraries do not, read as both a value and a type. */
|
|
582
481
|
const NODE_CLASSES = [
|
|
583
482
|
"AbortController",
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrying a change between a version's public names and the repository's:
|
|
3
|
+
* the reverse path merge-back needs (a submission, made in public names,
|
|
4
|
+
* as a patch on the repository at the pinned commit), and the forward path
|
|
5
|
+
* the CI judge needs (a pull request's files, in the repository's names,
|
|
6
|
+
* as a submission on the version).
|
|
7
|
+
*
|
|
8
|
+
* The build's transforms are token-for-token and never add or remove a
|
|
9
|
+
* line: the name table, import specifiers rewritten to the slice's layout,
|
|
10
|
+
* mocked packages and environment variables renamed. So a version's file
|
|
11
|
+
* and the repository's line up line by line, and a change carries by line:
|
|
12
|
+
*
|
|
13
|
+
* - A line the change kept is the other side's line at the same place,
|
|
14
|
+
* byte for byte. A change that touches nothing carries to the original.
|
|
15
|
+
* - A line the change added is renamed token by token, first by what the
|
|
16
|
+
* two originals' differing lines show the build renamed in this file
|
|
17
|
+
* (an import specifier, a package, an environment variable), then by the
|
|
18
|
+
* name table. It is refused unless renaming it back gives it again, so
|
|
19
|
+
* new text holding a name the side it was written on never shows (the
|
|
20
|
+
* repository's own names, in a contribution) cannot pass through.
|
|
21
|
+
* - A line the change added that imports the sandbox's harness (a mock, a
|
|
22
|
+
* stub), which the repository does not have, is refused.
|
|
23
|
+
*
|
|
24
|
+
* Pure and platform-neutral: the worker reads the files, the API writes
|
|
25
|
+
* the pull request.
|
|
26
|
+
*
|
|
27
|
+
* Moved here verbatim from sandbox-factory's packages/core/src/sandbox/carry.ts
|
|
28
|
+
* (commit f31d780), on carveout's alias implementation (`aliases.ts`). One
|
|
29
|
+
* addition: `aliases` on `ReverseInput`, `ForwardInput` and `projectPathOf`,
|
|
30
|
+
* so a version built with an older renaming carries back under it
|
|
31
|
+
* (`ALIASES_V1`, `legacy.ts`).
|
|
32
|
+
*/
|
|
33
|
+
import { type AliasRule, type AliasedFile } from "./aliases.js";
|
|
34
|
+
/**
|
|
35
|
+
* The renaming a version was built with: how a file is renamed under a
|
|
36
|
+
* table, and how a table is inverted. carveout's own by default
|
|
37
|
+
* (`CURRENT_ALIASES`); `ALIASES_V1` for a version the platform built before.
|
|
38
|
+
*/
|
|
39
|
+
export interface AliasImplementation {
|
|
40
|
+
readonly renameFile: (file: AliasedFile, rules: readonly AliasRule[]) => AliasedFile;
|
|
41
|
+
readonly invertAliasRules: (rules: readonly AliasRule[]) => AliasRule[];
|
|
42
|
+
}
|
|
43
|
+
/** carveout's renaming (`aliases.ts`). */
|
|
44
|
+
export declare const CURRENT_ALIASES: AliasImplementation;
|
|
45
|
+
/** One file a change touched, on both sides. */
|
|
46
|
+
export interface CarriedFile {
|
|
47
|
+
/** Where the change was made, and where it lands. */
|
|
48
|
+
readonly fromPath: string;
|
|
49
|
+
readonly toPath: string;
|
|
50
|
+
/** The file before the change on each side; null for a new file. */
|
|
51
|
+
readonly fromOriginal: string | null;
|
|
52
|
+
readonly toOriginal: string | null;
|
|
53
|
+
/** The file after the change, on the side it was made. */
|
|
54
|
+
readonly edited: string;
|
|
55
|
+
}
|
|
56
|
+
export interface CarryRefusal {
|
|
57
|
+
/** The path the change was made at. */
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly reason: string;
|
|
60
|
+
}
|
|
61
|
+
export interface CarriedChange {
|
|
62
|
+
/** Each changed file at its path on the other side, whole. */
|
|
63
|
+
readonly files: readonly {
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly text: string;
|
|
66
|
+
}[];
|
|
67
|
+
readonly refused: readonly CarryRefusal[];
|
|
68
|
+
}
|
|
69
|
+
/** How a line of new text is renamed into the other side's names, and back. */
|
|
70
|
+
export interface Renaming {
|
|
71
|
+
readonly to: (text: string, file: CarriedFile) => string;
|
|
72
|
+
readonly back: (text: string, file: CarriedFile) => string;
|
|
73
|
+
/** Whether a path, resolved on the side the change was made, is harness. */
|
|
74
|
+
readonly isHarness?: (path: string) => boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Splits text into lines, each with its line ending. */
|
|
77
|
+
export declare function linesOf(text: string): string[];
|
|
78
|
+
/**
|
|
79
|
+
* A line as tokens: string literals whole (a template literal only without
|
|
80
|
+
* `${`), identifiers, runs of space, single characters. One pass, linear in
|
|
81
|
+
* the line: a quote that does not close on it is a character of its own.
|
|
82
|
+
*/
|
|
83
|
+
export declare function tokensOf(line: string): string[];
|
|
84
|
+
/**
|
|
85
|
+
* What the two sides' originals show was renamed in this file: each token
|
|
86
|
+
* that differs where the lines otherwise line up, from one side to the
|
|
87
|
+
* other. A token renamed two ways is listed as ambiguous.
|
|
88
|
+
*/
|
|
89
|
+
export declare function learnedRenames(from: readonly string[], to: readonly string[]): {
|
|
90
|
+
renames: Map<string, string>;
|
|
91
|
+
ambiguous: Set<string>;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* For each line of `b`, the line of `a` it keeps, or -1 for one the change
|
|
95
|
+
* added: a longest common subsequence, after the shared head and tail.
|
|
96
|
+
*/
|
|
97
|
+
export declare function keptLines(a: readonly string[], b: readonly string[]): number[];
|
|
98
|
+
/** A relative specifier resolved against the importing file's folder. */
|
|
99
|
+
export declare function resolveRelative(importer: string, specifier: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* The change carried to the other side, file by file, with what cannot be
|
|
102
|
+
* carried refused. A refused file is left out of `files`.
|
|
103
|
+
*/
|
|
104
|
+
export declare function carryChange(files: readonly CarriedFile[], renaming: Renaming): CarriedChange;
|
|
105
|
+
export interface ReverseInput {
|
|
106
|
+
/** The submission's files, at their paths in the version's project. */
|
|
107
|
+
readonly submitted: readonly {
|
|
108
|
+
readonly path: string;
|
|
109
|
+
readonly text: string;
|
|
110
|
+
}[];
|
|
111
|
+
/** The version's public file at a project path; null for none. */
|
|
112
|
+
readonly publicText: (path: string) => string | null;
|
|
113
|
+
/** The repository's file at the pinned commit; null for none. */
|
|
114
|
+
readonly repositoryText: (path: string) => string | null;
|
|
115
|
+
/** The version's name table (`pseudonym.lunox`). */
|
|
116
|
+
readonly rules: readonly AliasRule[];
|
|
117
|
+
/** Repository path to project path, for each file the build moved. */
|
|
118
|
+
readonly renames: readonly {
|
|
119
|
+
readonly from: string;
|
|
120
|
+
readonly to: string;
|
|
121
|
+
}[];
|
|
122
|
+
/** The version's harness files (mocks, stubs, tests), at project paths. */
|
|
123
|
+
readonly harness: readonly string[];
|
|
124
|
+
/** The renaming the version was built with; carveout's by default. */
|
|
125
|
+
readonly aliases?: AliasImplementation;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A submission, made in the version's public names, as whole files on the
|
|
129
|
+
* repository at the pinned commit: merge-back's reverse path.
|
|
130
|
+
*/
|
|
131
|
+
export declare function reverseSubmission(input: ReverseInput): CarriedChange;
|
|
132
|
+
/**
|
|
133
|
+
* Where a repository file is in the version's project: where the build
|
|
134
|
+
* moved it, else where the name table's path rules put it.
|
|
135
|
+
*/
|
|
136
|
+
export declare function projectPathOf(path: string, renames: readonly {
|
|
137
|
+
readonly from: string;
|
|
138
|
+
readonly to: string;
|
|
139
|
+
}[], rules: readonly AliasRule[], aliases?: AliasImplementation): string;
|
|
140
|
+
export interface ForwardInput {
|
|
141
|
+
/** A change's files, at their repository paths. */
|
|
142
|
+
readonly changed: readonly {
|
|
143
|
+
readonly path: string;
|
|
144
|
+
readonly text: string;
|
|
145
|
+
}[];
|
|
146
|
+
readonly publicText: (path: string) => string | null;
|
|
147
|
+
readonly repositoryText: (path: string) => string | null;
|
|
148
|
+
readonly rules: readonly AliasRule[];
|
|
149
|
+
readonly renames: readonly {
|
|
150
|
+
readonly from: string;
|
|
151
|
+
readonly to: string;
|
|
152
|
+
}[];
|
|
153
|
+
/** The renaming the version was built with; carveout's by default. */
|
|
154
|
+
readonly aliases?: AliasImplementation;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A change made in the repository's names, as whole files on the version's
|
|
158
|
+
* project: the CI judge's forward path. Only files the version has (or its
|
|
159
|
+
* paths name) are carried; the caller keeps to the editable ones.
|
|
160
|
+
*/
|
|
161
|
+
export declare function forwardChange(input: ForwardInput): CarriedChange;
|