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
package/NOTICE.md
CHANGED
|
@@ -6,3 +6,47 @@ pseudonyms (`src/pseudonyms`: aliases, vocabulary, draft) are carved out of
|
|
|
6
6
|
`packages/core/src/slice`, `packages/core/src/sandbox` and
|
|
7
7
|
`apps/worker/src/tools/slice`. The import graph (`src/graph.ts`) replaces the
|
|
8
8
|
graphify driver there with the TypeScript compiler alone.
|
|
9
|
+
|
|
10
|
+
`src/pseudonyms/carry.ts` is the platform's `packages/core/src/sandbox/carry.ts`
|
|
11
|
+
(commit f31d780), moved here verbatim but for a pluggable renaming
|
|
12
|
+
(`aliases`). `src/pseudonyms/legacy/` holds that commit's `aliases.ts` and
|
|
13
|
+
`vocabulary.ts` frozen, exported with a `V1` suffix (`legacy.ts`).
|
|
14
|
+
|
|
15
|
+
## Where the forks differ from the platform
|
|
16
|
+
|
|
17
|
+
carveout's behaviour is kept in each case; `carveout/extract` and
|
|
18
|
+
`carveout/pseudonyms` export it.
|
|
19
|
+
|
|
20
|
+
- `isDataPath`: also `.toml`, `.csv`, `.xml`, `.sql`, `.graphql`, `.gql`,
|
|
21
|
+
`.prisma`, and the images, fonts and media `isAssetPath` names.
|
|
22
|
+
- `boundedHost`: never reads the checkout's own `node_modules`.
|
|
23
|
+
- `sanitizeDeclaration` and `extractTypeScriptBoundary`: `widenLiterals`
|
|
24
|
+
(default true); with it, a constant read as a type (`typeof KIND`) keeps
|
|
25
|
+
its name-like string literals.
|
|
26
|
+
- `filterDeclaration`: `export { a, b } from "./m"` keeps only the names
|
|
27
|
+
asked of it; a barrel lists the names passed through its `export *`.
|
|
28
|
+
- `extractTypeScriptBoundary`: `CutEdge` carries `typeOnly`, not
|
|
29
|
+
`targetSymbol`, and an edge with no import asks for the whole module;
|
|
30
|
+
a copied asset is no module to stub; a workspace package's import asks
|
|
31
|
+
its source module; the result has `surface` (the surface check).
|
|
32
|
+
- `compileFixture`: drops `rootDir`, `rootDirs` and `outDir`; messages name
|
|
33
|
+
files relative to the checkout (`withoutRoots`).
|
|
34
|
+
- `packageUses`: reads `const { a } = await import("pkg")`.
|
|
35
|
+
- `loadConfig`: an optional third argument shares workspaces and loaded
|
|
36
|
+
configurations across calls.
|
|
37
|
+
- `package-registry.ts`: adds `lockIntegrities` and `publicByIntegrity`
|
|
38
|
+
(a pnpm entry with no URL, matched against the public registry).
|
|
39
|
+
- `analyseSlice` (`src/cut.ts`): walks with carveout's own graph and
|
|
40
|
+
`reach`; blocks on a path read unless `pathReads: "omit"`; `CutError`
|
|
41
|
+
codes `no_files`, `too_large`, `unreadable` where the platform throws
|
|
42
|
+
`AnalysisError("tool_failed" | "too_large")`; adds `surface`,
|
|
43
|
+
`keepsImplicitAny`, `implicitAnyFiles`, `walk`; no graphify communities.
|
|
44
|
+
- Alias rules: a path may hold any character but a NUL, a backslash, a
|
|
45
|
+
quote or a newline; a path rule matches after a `/` too; a rule's target
|
|
46
|
+
may occur where an earlier rule put it.
|
|
47
|
+
- Vocabulary: a term's words do not join across `/`; `scanVocabulary`
|
|
48
|
+
drops a found one-word name `tooCommonAlone` and adds handle stems and
|
|
49
|
+
accent-free spellings.
|
|
50
|
+
- Draft: made-up placeholders first, a tail word per extra word of a term
|
|
51
|
+
(`contoso-labs`), restyled to each spelling, and an earlier cut's
|
|
52
|
+
placeholders kept (`preferred`).
|
package/dist/src/api.d.ts
CHANGED
|
@@ -15,7 +15,16 @@
|
|
|
15
15
|
* to throw.
|
|
16
16
|
*
|
|
17
17
|
* `walk` returns the walk `plan` and `cut` make, whole, for a platform
|
|
18
|
-
* that generates its own stubs and project from it.
|
|
18
|
+
* that generates its own stubs and project from it. `analyse` goes on from
|
|
19
|
+
* the walk to what `cut` writes the folder from (manifest, contract, stubs,
|
|
20
|
+
* surface), and runs no process at all, Git included; `importGraph` builds
|
|
21
|
+
* the graph once for many analyses. `folderLayout` says where a cut folder
|
|
22
|
+
* keeps its harness, mocks, configuration and tests, and what a grader runs.
|
|
23
|
+
*
|
|
24
|
+
* The extractor and the fixture compile are `carveout/extract`
|
|
25
|
+
* (`extract.ts`); the alias table, the vocabulary scan, the draft, carry-back
|
|
26
|
+
* and the mock runtime's text, pure, are `carveout/pseudonyms`
|
|
27
|
+
* (`pseudonyms.ts`).
|
|
19
28
|
*
|
|
20
29
|
* Also exported: the policy's text checks (`integrityOf`,
|
|
21
30
|
* `gainedIntegrity`, `weakenedTest`) and the slice and project modules
|
|
@@ -23,13 +32,16 @@
|
|
|
23
32
|
*/
|
|
24
33
|
import type { ReachResult } from "./slice/reach.js";
|
|
25
34
|
import { type CarveoutRecord, type Patch, type PlanSurface } from "./carve.js";
|
|
26
|
-
import {
|
|
35
|
+
import { CutError, type CutErrorCode, type SliceAnalysis } from "./cut.js";
|
|
36
|
+
import type { PackageTypesFetcher } from "./extract/package-types.js";
|
|
37
|
+
import { type CutEdge, type SliceExposure, type SliceExternals, type SliceOrigin, type SliceGraph, type SliceTestLeft, type TestRunner } from "./core.js";
|
|
27
38
|
import type { Readiness } from "./readiness.js";
|
|
28
39
|
import type { Receipt } from "./receipt.js";
|
|
29
40
|
export * from "./core.js";
|
|
30
41
|
export { gainedIntegrity, integrityOf, weakenedTest, type Refusal } from "./policy.js";
|
|
31
42
|
export type { CarveoutRecord, Patch, PlanSurface, Readiness, Receipt };
|
|
32
|
-
export type { ReachResult };
|
|
43
|
+
export type { ReachResult, PackageTypesFetcher };
|
|
44
|
+
export { CutError, type CutErrorCode };
|
|
33
45
|
/** The walk's bounds, as `carveout plan` and `cut` take them. */
|
|
34
46
|
export interface WalkOptions {
|
|
35
47
|
/** The repository's checkout root. */
|
|
@@ -160,3 +172,123 @@ export interface PatchInput {
|
|
|
160
172
|
* own record; runs nothing from the folder.
|
|
161
173
|
*/
|
|
162
174
|
export declare function patch(input: PatchInput): Promise<Patch>;
|
|
175
|
+
/**
|
|
176
|
+
* The import graph of a whole checkout, read once, for many `analyse`
|
|
177
|
+
* calls on it (`AnalyseOptions.graph`). It does not depend on the entries;
|
|
178
|
+
* it does on whether imports of the repository's own packages are followed
|
|
179
|
+
* into their source, which `copyPure` and the repository's
|
|
180
|
+
* `carveout.config.json` decide, as they do for the analyses: build it with
|
|
181
|
+
* the same `copyPure` they take. Runs no process: the files are every file
|
|
182
|
+
* under the root outside the folders a graph ignores, or `files`.
|
|
183
|
+
*/
|
|
184
|
+
export declare function importGraph(root: string, options?: {
|
|
185
|
+
readonly copyPure?: boolean;
|
|
186
|
+
readonly files?: readonly string[];
|
|
187
|
+
}): SliceGraph;
|
|
188
|
+
export interface AnalyseOptions extends WalkOptions {
|
|
189
|
+
/** The commit the root is a checkout of, stamped into the manifest and the contract; Git is not asked. */
|
|
190
|
+
readonly commitSha: string | null;
|
|
191
|
+
/** Whether an included file differs from that commit; by default none does. */
|
|
192
|
+
readonly isDirty?: (paths: readonly string[]) => boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Fetches the types of the public packages the slice reads (`npmPackageTypes`
|
|
195
|
+
* from `carveout/extract`, or the caller's own). Absent: every package is
|
|
196
|
+
* untyped and nothing is fetched or run.
|
|
197
|
+
*/
|
|
198
|
+
readonly packageTypes?: PackageTypesFetcher;
|
|
199
|
+
/** Widen literal values in the stubs to their types; default true (the CLI's default is off but for `--public`). */
|
|
200
|
+
readonly widenLiterals?: boolean;
|
|
201
|
+
/** `omit`: a file read by path blocks nothing (see `analyseSlice`); default `block`, as the CLI. */
|
|
202
|
+
readonly pathReads?: "block" | "omit";
|
|
203
|
+
/** The externals of the included files when the caller has them all; null to read them here. */
|
|
204
|
+
readonly externalsOf?: (included: readonly string[]) => SliceExternals | null;
|
|
205
|
+
/** The checkout's files; by default every file under the root outside the folders a graph ignores. */
|
|
206
|
+
readonly files?: readonly string[];
|
|
207
|
+
/**
|
|
208
|
+
* A graph from `importGraph`, reused across analyses. Without it the graph
|
|
209
|
+
* is read lazily, as `cut` reads it, from the entries outward; the walk is
|
|
210
|
+
* the same, but the manifest's `graphSha256` is of the graph used.
|
|
211
|
+
*/
|
|
212
|
+
readonly graph?: SliceGraph;
|
|
213
|
+
readonly signal?: AbortSignal;
|
|
214
|
+
/** The analysis's progress lines. */
|
|
215
|
+
readonly log?: (line: string) => void;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* What `cut` writes a folder from: the manifest, the boundary contract (and
|
|
219
|
+
* its canonical JSON), the stubs, the public surface's check, whether it is
|
|
220
|
+
* ready, how it stands with noImplicitAny, and the walk.
|
|
221
|
+
*/
|
|
222
|
+
export type Analysis = SliceAnalysis;
|
|
223
|
+
/**
|
|
224
|
+
* The analysis `cut` makes before it writes anything, on the same code
|
|
225
|
+
* path and with the same request after `carveout.config.json`, for a
|
|
226
|
+
* platform that writes its own folder: on the same checkout, its manifest
|
|
227
|
+
* and contract are the cut's, but for the commit and dirty fields, which
|
|
228
|
+
* are the caller's here. It runs no process (no Git, no npm unless the
|
|
229
|
+
* `packageTypes` given does) and writes nothing. Throws a `CutError` when
|
|
230
|
+
* no slice can be made at all.
|
|
231
|
+
*/
|
|
232
|
+
export declare function analyse(options: AnalyseOptions): Promise<Analysis>;
|
|
233
|
+
/** One command a grader runs on the staged folder; none goes through `npm run`. */
|
|
234
|
+
export interface LayoutCommand {
|
|
235
|
+
readonly name: "install" | "build" | "public-tests";
|
|
236
|
+
readonly argv: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
/** How one test file runs on its own, with a TAP report on stdout. */
|
|
239
|
+
export interface LayoutTestRun {
|
|
240
|
+
/** The command, before the file. */
|
|
241
|
+
readonly argv: readonly string[];
|
|
242
|
+
/** Whether the file is given as its emitted JavaScript (after the build) rather than its source. */
|
|
243
|
+
readonly emitted: boolean;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Where a folder handed to a contributor keeps what is not theirs to
|
|
247
|
+
* change, and how it is graded: a carveout folder's (`folderLayout`), or a
|
|
248
|
+
* platform's own written in the same terms.
|
|
249
|
+
*/
|
|
250
|
+
export interface FolderLayout {
|
|
251
|
+
/** The harness: generated code a submission never changes. */
|
|
252
|
+
readonly harnessDir: string;
|
|
253
|
+
/** The mock packages. */
|
|
254
|
+
readonly mocksDir: string;
|
|
255
|
+
/** The recording mock runtime, which task code never imports. */
|
|
256
|
+
readonly mockRuntimePath: string;
|
|
257
|
+
/** The placeholder environment every run reads (`--env-file`). */
|
|
258
|
+
readonly envFile: string;
|
|
259
|
+
/** Top-level folders a submission never writes into. */
|
|
260
|
+
readonly harnessRoots: readonly string[];
|
|
261
|
+
/** Root files that configure the run, each with why a change to it is refused. */
|
|
262
|
+
readonly configFiles: Readonly<Record<string, string>>;
|
|
263
|
+
/** What a grader runs, in order, on the staged folder. */
|
|
264
|
+
readonly commands: readonly LayoutCommand[];
|
|
265
|
+
readonly tests: {
|
|
266
|
+
/** The public suite's folder; null when tests sit beside the code they test. */
|
|
267
|
+
readonly publicDir: string | null;
|
|
268
|
+
/** Where hidden tests are staged; null when each goes at its own repository path. */
|
|
269
|
+
readonly privateDir: string | null;
|
|
270
|
+
/** The test files the folder holds. */
|
|
271
|
+
readonly files: readonly string[];
|
|
272
|
+
/** One hidden test on its own, by the runner it imports; null for a runner the folder does not install. */
|
|
273
|
+
readonly run: {
|
|
274
|
+
readonly "node:test": LayoutTestRun;
|
|
275
|
+
readonly vitest: LayoutTestRun | null;
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Where a carveout folder keeps its harness (`carveout/`), its mock
|
|
281
|
+
* packages (`carveout/packages`), its mock runtime, its configuration and
|
|
282
|
+
* its tests, and the commands that install, build and test it as `carveout
|
|
283
|
+
* check` does (`npm test` is `node carveout/test.js` after the build), for
|
|
284
|
+
* a grader that takes the layout as a parameter. Hidden tests go at their
|
|
285
|
+
* repository paths, as `carveout check --accept` puts them, and run one at
|
|
286
|
+
* a time with `tests.run` (`layoutTestArgv`). Read from the folder's record.
|
|
287
|
+
*/
|
|
288
|
+
export declare function folderLayout(record: CarveoutRecord): FolderLayout;
|
|
289
|
+
/**
|
|
290
|
+
* The command that runs one test file of a folder on its own, with a TAP
|
|
291
|
+
* report on stdout: `path` as the folder holds its source; null when the
|
|
292
|
+
* layout has no way to run that runner's tests.
|
|
293
|
+
*/
|
|
294
|
+
export declare function layoutTestArgv(layout: FolderLayout, path: string, runner?: "node:test" | "vitest"): readonly string[] | null;
|
package/dist/src/api.js
CHANGED
|
@@ -15,7 +15,16 @@
|
|
|
15
15
|
* to throw.
|
|
16
16
|
*
|
|
17
17
|
* `walk` returns the walk `plan` and `cut` make, whole, for a platform
|
|
18
|
-
* that generates its own stubs and project from it.
|
|
18
|
+
* that generates its own stubs and project from it. `analyse` goes on from
|
|
19
|
+
* the walk to what `cut` writes the folder from (manifest, contract, stubs,
|
|
20
|
+
* surface), and runs no process at all, Git included; `importGraph` builds
|
|
21
|
+
* the graph once for many analyses. `folderLayout` says where a cut folder
|
|
22
|
+
* keeps its harness, mocks, configuration and tests, and what a grader runs.
|
|
23
|
+
*
|
|
24
|
+
* The extractor and the fixture compile are `carveout/extract`
|
|
25
|
+
* (`extract.ts`); the alias table, the vocabulary scan, the draft, carry-back
|
|
26
|
+
* and the mock runtime's text, pure, are `carveout/pseudonyms`
|
|
27
|
+
* (`pseudonyms.ts`).
|
|
19
28
|
*
|
|
20
29
|
* Also exported: the policy's text checks (`integrityOf`,
|
|
21
30
|
* `gainedIntegrity`, `weakenedTest`) and the slice and project modules
|
|
@@ -24,10 +33,14 @@
|
|
|
24
33
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
25
34
|
import { tmpdir } from "node:os";
|
|
26
35
|
import { join } from "node:path";
|
|
27
|
-
import { cutSlice, patchProject, planBlockers, planSlice, } from "./carve.js";
|
|
36
|
+
import { RECORD_PATH, cutSlice, patchProject, planBlockers, planSlice, sliceInputOf, } from "./carve.js";
|
|
37
|
+
import { CutError, analyseSlice } from "./cut.js";
|
|
38
|
+
import { buildGraph, listFiles, walkGraph } from "./graph.js";
|
|
39
|
+
import { ASSETS_LOADER_PATH, BUILD_SCRIPT_PATH, ENV_FILE, HARNESS_DIR, MOCKS_DIR, MOCK_RUNTIME_PATH, TESTS_DIR, TEST_RUNNER_PATH, runtimePath, } from "./project/project.js";
|
|
28
40
|
import { SLICE_BUDGET_DEFAULTS, TEST_LEFT_REASON, testsVerdict, } from "./core.js";
|
|
29
41
|
export * from "./core.js";
|
|
30
42
|
export { gainedIntegrity, integrityOf, weakenedTest } from "./policy.js";
|
|
43
|
+
export { CutError };
|
|
31
44
|
const budgetOf = (options) => ({
|
|
32
45
|
maxFiles: options.maxFiles ?? SLICE_BUDGET_DEFAULTS.maxFiles,
|
|
33
46
|
maxDepth: options.depth ?? SLICE_BUDGET_DEFAULTS.maxDepth,
|
|
@@ -132,3 +145,132 @@ export function patch(input) {
|
|
|
132
145
|
...(input.allow === undefined ? {} : { allow: input.allow }),
|
|
133
146
|
});
|
|
134
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* The import graph of a whole checkout, read once, for many `analyse`
|
|
150
|
+
* calls on it (`AnalyseOptions.graph`). It does not depend on the entries;
|
|
151
|
+
* it does on whether imports of the repository's own packages are followed
|
|
152
|
+
* into their source, which `copyPure` and the repository's
|
|
153
|
+
* `carveout.config.json` decide, as they do for the analyses: build it with
|
|
154
|
+
* the same `copyPure` they take. Runs no process: the files are every file
|
|
155
|
+
* under the root outside the folders a graph ignores, or `files`.
|
|
156
|
+
*/
|
|
157
|
+
export function importGraph(root, options = {}) {
|
|
158
|
+
const { repo, settled } = sliceInputOf({
|
|
159
|
+
repo: root,
|
|
160
|
+
entryPoints: [],
|
|
161
|
+
...(options.copyPure === undefined ? {} : { copyPure: options.copyPure }),
|
|
162
|
+
});
|
|
163
|
+
return buildGraph(repo, options.files ?? listFiles(repo, { git: false }), {
|
|
164
|
+
followWorkspaces: settled.followWorkspaces,
|
|
165
|
+
}).graph;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The analysis `cut` makes before it writes anything, on the same code
|
|
169
|
+
* path and with the same request after `carveout.config.json`, for a
|
|
170
|
+
* platform that writes its own folder: on the same checkout, its manifest
|
|
171
|
+
* and contract are the cut's, but for the commit and dirty fields, which
|
|
172
|
+
* are the caller's here. It runs no process (no Git, no npm unless the
|
|
173
|
+
* `packageTypes` given does) and writes nothing. Throws a `CutError` when
|
|
174
|
+
* no slice can be made at all.
|
|
175
|
+
*/
|
|
176
|
+
export function analyse(options) {
|
|
177
|
+
const { repo, entryPoints, budget, walk: walkOptions, params } = sliceInputOf(requestOf(options));
|
|
178
|
+
const graph = options.graph ??
|
|
179
|
+
walkGraph(repo, entryPoints, budget, {
|
|
180
|
+
...walkOptions,
|
|
181
|
+
files: options.files ?? listFiles(repo, { git: false }),
|
|
182
|
+
}).built.graph;
|
|
183
|
+
return analyseSlice({
|
|
184
|
+
sourceDir: repo,
|
|
185
|
+
graph,
|
|
186
|
+
params,
|
|
187
|
+
source: { commitSha: options.commitSha, isDirty: options.isDirty ?? (() => false) },
|
|
188
|
+
signal: options.signal ?? new AbortController().signal,
|
|
189
|
+
log: options.log ?? (() => { }),
|
|
190
|
+
widenLiterals: options.widenLiterals ?? true,
|
|
191
|
+
...(options.packageTypes === undefined ? {} : { packageTypes: options.packageTypes }),
|
|
192
|
+
...(options.pathReads === undefined ? {} : { pathReads: options.pathReads }),
|
|
193
|
+
...(options.externalsOf === undefined ? {} : { externalsOf: options.externalsOf }),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** Why a change to a carveout folder's root file is refused. */
|
|
197
|
+
const CONFIG_REASONS = {
|
|
198
|
+
"package.json": "package.json is the folder's; scripts and dependencies are not the submission's to change.",
|
|
199
|
+
"package-lock.json": "The lockfile is the folder's.",
|
|
200
|
+
".npmrc": ".npmrc is the folder's.",
|
|
201
|
+
".nvmrc": ".nvmrc is the folder's.",
|
|
202
|
+
"tsconfig.json": "tsconfig.json is the folder's; the build's options are not the submission's to change.",
|
|
203
|
+
"vitest.config.mjs": "vitest.config.mjs is the folder's; how tests run is not the submission's to change.",
|
|
204
|
+
[ENV_FILE]: `${ENV_FILE} is the folder's placeholder environment.`,
|
|
205
|
+
[RECORD_PATH]: `${RECORD_PATH} is the folder's record of the cut.`,
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Where a carveout folder keeps its harness (`carveout/`), its mock
|
|
209
|
+
* packages (`carveout/packages`), its mock runtime, its configuration and
|
|
210
|
+
* its tests, and the commands that install, build and test it as `carveout
|
|
211
|
+
* check` does (`npm test` is `node carveout/test.js` after the build), for
|
|
212
|
+
* a grader that takes the layout as a parameter. Hidden tests go at their
|
|
213
|
+
* repository paths, as `carveout check --accept` puts them, and run one at
|
|
214
|
+
* a time with `tests.run` (`layoutTestArgv`). Read from the folder's record.
|
|
215
|
+
*/
|
|
216
|
+
export function folderLayout(record) {
|
|
217
|
+
const generated = new Set(record.generated.map((file) => file.path));
|
|
218
|
+
const configFiles = {};
|
|
219
|
+
for (const path of [...generated, RECORD_PATH, ...(record.lockfile ? ["package-lock.json"] : [])].sort())
|
|
220
|
+
if (!path.includes("/") && !/\.(?:[cm]?[jt]sx?|d\.ts)$/.test(path) && !/\.md$/i.test(path))
|
|
221
|
+
configFiles[path] = CONFIG_REASONS[path] ?? `${path} is the folder's own; carveout wrote it.`;
|
|
222
|
+
const install = ["--ignore-scripts", "--no-audit", "--no-fund"];
|
|
223
|
+
return {
|
|
224
|
+
harnessDir: HARNESS_DIR,
|
|
225
|
+
mocksDir: MOCKS_DIR,
|
|
226
|
+
mockRuntimePath: MOCK_RUNTIME_PATH,
|
|
227
|
+
envFile: ENV_FILE,
|
|
228
|
+
harnessRoots: [HARNESS_DIR, "node_modules"],
|
|
229
|
+
configFiles,
|
|
230
|
+
commands: [
|
|
231
|
+
{ name: "install", argv: record.lockfile ? ["npm", "ci", ...install] : ["npm", "install", ...install] },
|
|
232
|
+
{
|
|
233
|
+
name: "build",
|
|
234
|
+
argv: generated.has(BUILD_SCRIPT_PATH)
|
|
235
|
+
? ["node", BUILD_SCRIPT_PATH]
|
|
236
|
+
: ["node", "node_modules/typescript/bin/tsc", "-p", "tsconfig.json"],
|
|
237
|
+
},
|
|
238
|
+
{ name: "public-tests", argv: ["node", TEST_RUNNER_PATH] },
|
|
239
|
+
],
|
|
240
|
+
tests: {
|
|
241
|
+
publicDir: null,
|
|
242
|
+
privateDir: null,
|
|
243
|
+
files: [
|
|
244
|
+
...new Set([
|
|
245
|
+
...(record.tests?.carried ?? []),
|
|
246
|
+
...[...generated].filter((path) => path.startsWith(`${TESTS_DIR}/`) && /\.test\.[cm]?tsx?$/.test(path)),
|
|
247
|
+
]),
|
|
248
|
+
].sort(),
|
|
249
|
+
run: {
|
|
250
|
+
"node:test": {
|
|
251
|
+
argv: [
|
|
252
|
+
"node",
|
|
253
|
+
`--env-file=${ENV_FILE}`,
|
|
254
|
+
`--import=./${ASSETS_LOADER_PATH}`,
|
|
255
|
+
"--test",
|
|
256
|
+
"--test-reporter=tap",
|
|
257
|
+
"--test-reporter-destination=stdout",
|
|
258
|
+
],
|
|
259
|
+
emitted: true,
|
|
260
|
+
},
|
|
261
|
+
vitest: generated.has("vitest.config.mjs")
|
|
262
|
+
? { argv: ["node_modules/.bin/vitest", "run", "--config", "vitest.config.mjs", "--reporter=tap"], emitted: false }
|
|
263
|
+
: null,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The command that runs one test file of a folder on its own, with a TAP
|
|
270
|
+
* report on stdout: `path` as the folder holds its source; null when the
|
|
271
|
+
* layout has no way to run that runner's tests.
|
|
272
|
+
*/
|
|
273
|
+
export function layoutTestArgv(layout, path, runner = "node:test") {
|
|
274
|
+
const run = layout.tests.run[runner];
|
|
275
|
+
return run === null ? null : [...run.argv, run.emitted ? runtimePath(path) : path];
|
|
276
|
+
}
|
package/dist/src/carve.d.ts
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* scripts off, the public registry only) and resolves the folder's
|
|
9
9
|
* lockfile with npm; `offline` skips both.
|
|
10
10
|
*/
|
|
11
|
-
import { type BoundaryContract, type CoverageGate, type DependencyChoice, type GeneratedFile, type ImportRewrite, type ReachResult, type ResolvedDependency, type SliceBudget, type SliceManifest, type SliceExternals, type SliceExposure, type SliceTestLeft, type TestRunner } from "./core.js";
|
|
11
|
+
import { type BoundaryContract, type CoverageGate, type DependencyChoice, type GeneratedFile, type ImportRewrite, type ReachResult, type ResolvedDependency, type SliceBudget, type SliceManifest, type SliceParams, type SliceExternals, type SliceExposure, type SliceTestLeft, type TestRunner } from "./core.js";
|
|
12
|
+
import { type VitestSettings } from "./project/vitest-config.js";
|
|
12
13
|
import { type Refusal } from "./policy.js";
|
|
13
14
|
import { type SandboxSpec } from "./sandbox.js";
|
|
14
15
|
import { type Readiness } from "./readiness.js";
|
|
15
16
|
import { type AliasRule, type FolderPseudonyms } from "./pseudonyms/index.js";
|
|
16
17
|
import { type Receipt } from "./receipt.js";
|
|
17
|
-
export declare const CARVEOUT_VERSION = "0.
|
|
18
|
+
export declare const CARVEOUT_VERSION = "0.3.1";
|
|
18
19
|
export declare const RECORD_PATH = "carveout.json";
|
|
19
20
|
export declare const RECORD_SCHEMA_VERSION = 1;
|
|
20
21
|
export declare const BOUNDARY_PATH = "BOUNDARY.md";
|
|
@@ -532,6 +533,58 @@ export interface ComputedCut {
|
|
|
532
533
|
readonly record: Omit<CarveoutRecord, "generated" | "lockfile">;
|
|
533
534
|
readonly contract: BoundaryContract;
|
|
534
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* What a request asks of `analyseSlice`, after the owner's configuration
|
|
538
|
+
* (`settle`): the walk's options, the test setup and the slice's params.
|
|
539
|
+
* `computeCut` and the library's `analyse` (`api.ts`) share it, so both
|
|
540
|
+
* cut the same slice of the same request.
|
|
541
|
+
*/
|
|
542
|
+
export declare function sliceInputOf(request: SliceRequest & {
|
|
543
|
+
readonly copy?: readonly string[];
|
|
544
|
+
readonly widenLiterals?: boolean;
|
|
545
|
+
readonly public?: boolean;
|
|
546
|
+
readonly live?: readonly string[];
|
|
547
|
+
}): {
|
|
548
|
+
settled: SliceRequest & {
|
|
549
|
+
readonly copy?: readonly string[];
|
|
550
|
+
readonly widenLiterals?: boolean;
|
|
551
|
+
readonly public?: boolean;
|
|
552
|
+
readonly live?: readonly string[];
|
|
553
|
+
} & {
|
|
554
|
+
readonly wholePackage: boolean;
|
|
555
|
+
readonly followWorkspaces: boolean;
|
|
556
|
+
readonly copyPure: boolean;
|
|
557
|
+
readonly private: readonly string[];
|
|
558
|
+
readonly copy: readonly string[];
|
|
559
|
+
readonly widenLiterals: boolean;
|
|
560
|
+
readonly public: boolean;
|
|
561
|
+
readonly maxExposure: {
|
|
562
|
+
readonly lines?: number;
|
|
563
|
+
readonly percent?: number;
|
|
564
|
+
};
|
|
565
|
+
};
|
|
566
|
+
repo: string;
|
|
567
|
+
budget: SliceBudget;
|
|
568
|
+
entryPoints: string[];
|
|
569
|
+
setup: string[];
|
|
570
|
+
vitest: VitestSettings | null;
|
|
571
|
+
testRunner: "vitest" | "node:test" | null;
|
|
572
|
+
live: string[];
|
|
573
|
+
walk: {
|
|
574
|
+
unseen?: readonly string[] | undefined;
|
|
575
|
+
roots?: string[] | undefined;
|
|
576
|
+
isPrivate?: ((path: string) => boolean) | undefined;
|
|
577
|
+
pure?: {
|
|
578
|
+
readonly package: (specifier: string) => boolean;
|
|
579
|
+
readonly sideEffects: (file: string) => boolean;
|
|
580
|
+
} | undefined;
|
|
581
|
+
followWorkspaces?: boolean | undefined;
|
|
582
|
+
wholePackage?: boolean | undefined;
|
|
583
|
+
tests: boolean;
|
|
584
|
+
runnerOf: (file: string, specifiers: readonly string[]) => TestRunner | null;
|
|
585
|
+
};
|
|
586
|
+
params: SliceParams;
|
|
587
|
+
};
|
|
535
588
|
export declare function computeCut(request: Omit<CutRequest, "out">): Promise<ComputedCut>;
|
|
536
589
|
/**
|
|
537
590
|
* The packages whose classes the code tests with `instanceof`, by package
|
package/dist/src/carve.js
CHANGED
|
@@ -35,7 +35,7 @@ import { sandboxRun } from "./sandbox.js";
|
|
|
35
35
|
import { configuredFolders, liveCandidates } from "./readiness.js";
|
|
36
36
|
import { exactToFolder, fromFolder, specifierPackages, toFolder, keptNames, looseLeaks, maskKept, pseudonymize, vocabularyBlockers, vocabularyLeaks, workspaceNames, ownedNames, workspaceVocabulary, } from "./pseudonyms/index.js";
|
|
37
37
|
import { RECEIPT_SCHEMA_VERSION, folderRecordFor, folderView, newCutId, receiptPathFor, trustedBaseline, trustedCut, trustedCutWith, writeReceipt, } from "./receipt.js";
|
|
38
|
-
export const CARVEOUT_VERSION = "0.
|
|
38
|
+
export const CARVEOUT_VERSION = "0.3.1";
|
|
39
39
|
export const RECORD_PATH = "carveout.json";
|
|
40
40
|
export const RECORD_SCHEMA_VERSION = 1;
|
|
41
41
|
/** Blockers that stop a cut before it writes anything. */
|
|
@@ -918,40 +918,52 @@ function withoutOutsideImporters(contract, outsideName) {
|
|
|
918
918
|
})),
|
|
919
919
|
};
|
|
920
920
|
}
|
|
921
|
-
|
|
921
|
+
/**
|
|
922
|
+
* What a request asks of `analyseSlice`, after the owner's configuration
|
|
923
|
+
* (`settle`): the walk's options, the test setup and the slice's params.
|
|
924
|
+
* `computeCut` and the library's `analyse` (`api.ts`) share it, so both
|
|
925
|
+
* cut the same slice of the same request.
|
|
926
|
+
*/
|
|
927
|
+
export function sliceInputOf(request) {
|
|
922
928
|
const settled = settle(request);
|
|
923
929
|
const repo = resolve(request.repo);
|
|
924
|
-
const log = request.log ?? (() => { });
|
|
925
930
|
const budget = request.budget ?? SLICE_BUDGET_DEFAULTS;
|
|
926
931
|
const entryPoints = entriesOf(request);
|
|
927
932
|
const { setup, settings: vitest } = withTestSetup(repo, entryPoints, request.tests === true);
|
|
928
933
|
const testRunner = request.tests === true ? packageTestRunner(repo, entryPoints) : null;
|
|
929
|
-
const
|
|
934
|
+
const live = [...new Set(request.live ?? [])].sort(compare);
|
|
935
|
+
const walk = {
|
|
930
936
|
...walkOptions(settled, repo),
|
|
931
937
|
...(setup.length === 0 ? {} : { roots: setup }),
|
|
932
938
|
...(request.unseen === undefined || request.unseen.length === 0 ? {} : { unseen: request.unseen }),
|
|
933
|
-
}
|
|
939
|
+
};
|
|
940
|
+
const params = {
|
|
941
|
+
entryPoints,
|
|
942
|
+
budget,
|
|
943
|
+
tests: request.tests === true,
|
|
944
|
+
...(setup.length === 0 ? {} : { setupFiles: setup }),
|
|
945
|
+
...(testRunner === "vitest" ? { testRunner } : {}),
|
|
946
|
+
...(live.length === 0 ? {} : { live }),
|
|
947
|
+
...(settled.wholePackage ? { wholePackage: true } : {}),
|
|
948
|
+
...(settled.followWorkspaces ? { followWorkspaces: true } : {}),
|
|
949
|
+
...(settled.copyPure ? { copyPure: true } : {}),
|
|
950
|
+
...(settled.private.length === 0 ? {} : { private: settled.private }),
|
|
951
|
+
...(settled.copy.length === 0 ? {} : { copy: settled.copy }),
|
|
952
|
+
};
|
|
953
|
+
return { settled, repo, budget, entryPoints, setup, vitest, testRunner, live, walk, params };
|
|
954
|
+
}
|
|
955
|
+
export async function computeCut(request) {
|
|
956
|
+
const { settled, repo, budget, entryPoints, vitest, testRunner, live, walk, params } = sliceInputOf(request);
|
|
957
|
+
const log = request.log ?? (() => { });
|
|
958
|
+
const { built } = walkGraph(repo, entryPoints, budget, walk);
|
|
934
959
|
log(`Read the imports of ${built.files.length} files.`);
|
|
935
960
|
const commitSha = git(repo, ["rev-parse", "HEAD"]);
|
|
936
961
|
const signal = AbortSignal.timeout(15 * 60_000);
|
|
937
|
-
const live = [...new Set(request.live ?? [])].sort(compare);
|
|
938
962
|
const workspaces = workspacePackages(repo);
|
|
939
963
|
const analysis = await analyseSlice({
|
|
940
964
|
sourceDir: repo,
|
|
941
965
|
graph: built.graph,
|
|
942
|
-
params
|
|
943
|
-
entryPoints,
|
|
944
|
-
budget,
|
|
945
|
-
tests: request.tests === true,
|
|
946
|
-
...(setup.length === 0 ? {} : { setupFiles: setup }),
|
|
947
|
-
...(testRunner === "vitest" ? { testRunner } : {}),
|
|
948
|
-
...(live.length === 0 ? {} : { live }),
|
|
949
|
-
...(settled.wholePackage ? { wholePackage: true } : {}),
|
|
950
|
-
...(settled.followWorkspaces ? { followWorkspaces: true } : {}),
|
|
951
|
-
...(settled.copyPure ? { copyPure: true } : {}),
|
|
952
|
-
...(settled.private.length === 0 ? {} : { private: settled.private }),
|
|
953
|
-
...(settled.copy.length === 0 ? {} : { copy: settled.copy }),
|
|
954
|
-
},
|
|
966
|
+
params,
|
|
955
967
|
source: {
|
|
956
968
|
commitSha,
|
|
957
969
|
isDirty: (paths) => commitSha !== null &&
|
package/dist/src/cut.d.ts
CHANGED
|
@@ -11,8 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { BoundaryContract, ReachResult, SliceExternals, SliceGraph, SliceManifest, SliceParams } from "./core.js";
|
|
13
13
|
import { type PackageTypesFetcher } from "./extract/package-types.js";
|
|
14
|
+
/** Why a cut could not be made at all. */
|
|
15
|
+
export type CutErrorCode =
|
|
16
|
+
/** The entry points reach no file. */
|
|
17
|
+
"no_files"
|
|
18
|
+
/** The included source is past `INCLUDED_BYTES_MAX`. */
|
|
19
|
+
| "too_large"
|
|
20
|
+
/** An included file is outside the checkout, a symbolic link or not a regular file. */
|
|
21
|
+
| "unreadable";
|
|
14
22
|
/** Why a cut could not be made at all, as opposed to a blocker it reports. */
|
|
15
23
|
export declare class CutError extends Error {
|
|
24
|
+
readonly code: CutErrorCode;
|
|
25
|
+
constructor(code: CutErrorCode, message: string);
|
|
16
26
|
}
|
|
17
27
|
/** Bytes of included source one slice may copy. */
|
|
18
28
|
export declare const INCLUDED_BYTES_MAX: number;
|
|
@@ -75,4 +85,11 @@ export declare function analyseSlice(input: {
|
|
|
75
85
|
readonly packageTypes?: PackageTypesFetcher;
|
|
76
86
|
/** See `extractTypeScriptBoundary`; true unless told otherwise. */
|
|
77
87
|
readonly widenLiterals?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* What a file read by path does to the slice: `block` (the default, the
|
|
90
|
+
* CLI's) makes a `path_read` blocker of a path the slice cannot copy and
|
|
91
|
+
* of a file read by path outside the slice; `omit` leaves both out, as
|
|
92
|
+
* sandbox-factory's slice tool did, which has no way to carry one.
|
|
93
|
+
*/
|
|
94
|
+
readonly pathReads?: "block" | "omit";
|
|
78
95
|
}): Promise<SliceAnalysis>;
|
package/dist/src/cut.js
CHANGED
|
@@ -25,6 +25,11 @@ import { lockedSpec, typesPackageOf, } from "./extract/package-types.js";
|
|
|
25
25
|
import { declarationPathFor, extractTypeScriptBoundary, isNodeBuiltin, isDataPath, isScriptPath, packageNameOf, } from "./extract/typescript.js";
|
|
26
26
|
/** Why a cut could not be made at all, as opposed to a blocker it reports. */
|
|
27
27
|
export class CutError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
constructor(code, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
}
|
|
28
33
|
}
|
|
29
34
|
/** Bytes of included source one slice may copy. */
|
|
30
35
|
export const INCLUDED_BYTES_MAX = 64 * 1024 * 1024;
|
|
@@ -140,22 +145,22 @@ async function readIncluded(sourceDir, files) {
|
|
|
140
145
|
const absolute = resolve(sourceDir, path);
|
|
141
146
|
const rel = relative(sourceDir, absolute);
|
|
142
147
|
if (rel.startsWith("..") || isAbsolute(rel) || rel === "")
|
|
143
|
-
throw new CutError(`${path} is outside the repository.`);
|
|
148
|
+
throw new CutError("unreadable", `${path} is outside the repository.`);
|
|
144
149
|
// One handle for the check and the read, so the file cannot be swapped
|
|
145
150
|
// between them; a symlink fails to open, and a FIFO opens without
|
|
146
151
|
// blocking and then fails the regular-file check.
|
|
147
152
|
const handle = await open(absolute, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
|
|
148
153
|
if (handle === null)
|
|
149
|
-
throw new CutError(`${path} cannot be read (a symbolic link is not copied).`);
|
|
154
|
+
throw new CutError("unreadable", `${path} cannot be read (a symbolic link is not copied).`);
|
|
150
155
|
let stats;
|
|
151
156
|
let bytes;
|
|
152
157
|
try {
|
|
153
158
|
stats = await handle.stat();
|
|
154
159
|
if (!stats.isFile())
|
|
155
|
-
throw new CutError(`${path} is not a regular file.`);
|
|
160
|
+
throw new CutError("unreadable", `${path} is not a regular file.`);
|
|
156
161
|
total += stats.size;
|
|
157
162
|
if (total > INCLUDED_BYTES_MAX)
|
|
158
|
-
throw new CutError(`The slice holds more than ${INCLUDED_BYTES_MAX / 1024 / 1024} MB of source; narrow the entry points or the budget.`);
|
|
163
|
+
throw new CutError("too_large", `The slice holds more than ${INCLUDED_BYTES_MAX / 1024 / 1024} MB of source; narrow the entry points or the budget.`);
|
|
159
164
|
bytes = await handle.readFile();
|
|
160
165
|
}
|
|
161
166
|
finally {
|
|
@@ -259,7 +264,7 @@ export async function analyseSlice(input) {
|
|
|
259
264
|
});
|
|
260
265
|
if (walk.included.length === 0) {
|
|
261
266
|
// Nothing to slice: say which entry points missed before failing.
|
|
262
|
-
throw new CutError(blockers.length === 0
|
|
267
|
+
throw new CutError("no_files", blockers.length === 0
|
|
263
268
|
? "The entry points reach no file."
|
|
264
269
|
: blockers.map((blocker) => blocker.detail).join(" "));
|
|
265
270
|
}
|
|
@@ -289,8 +294,9 @@ export async function analyseSlice(input) {
|
|
|
289
294
|
// A test brought along may hold a computed import: what it names is the
|
|
290
295
|
// baseline's business, not a blocker (see `reach`).
|
|
291
296
|
const carriedTests = new Set(walk.tests);
|
|
297
|
+
const omitPathReads = input.pathReads === "omit";
|
|
292
298
|
for (const dependency of walk.dependencies) {
|
|
293
|
-
if (dependency.status === "path" && copied(dependency.specifier ?? ""))
|
|
299
|
+
if (dependency.status === "path" && (omitPathReads || copied(dependency.specifier ?? "")))
|
|
294
300
|
continue;
|
|
295
301
|
if (dependency.status === "dynamic" && carriedTests.has(dependency.file))
|
|
296
302
|
continue;
|
|
@@ -340,7 +346,7 @@ export async function analyseSlice(input) {
|
|
|
340
346
|
}
|
|
341
347
|
// A file read by path must be there itself; a stub at its path is not it.
|
|
342
348
|
for (const edge of walk.cuts.outbound)
|
|
343
|
-
if (edge.relation === "reads" && !copied(edge.to))
|
|
349
|
+
if (edge.relation === "reads" && !omitPathReads && !copied(edge.to))
|
|
344
350
|
blockers.push({
|
|
345
351
|
code: "path_read",
|
|
346
352
|
file: edge.from,
|
|
@@ -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";
|