@ultimat3/cli 5.0.1 → 7.0.0
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/CLAUDE.md +75 -6
- package/README.md +2 -2
- package/package.json +28 -24
- package/src/affected.ts +320 -0
- package/src/browser-launcher.ts +109 -0
- package/src/ci-log.ts +0 -0
- package/src/ci-runs.ts +179 -0
- package/src/cmd-affected.ts +109 -0
- package/src/cmd-build.ts +36 -3
- package/src/cmd-ci.ts +273 -0
- package/src/cmd-dev.ts +35 -2
- package/src/cmd-generate.ts +16 -348
- package/src/cmd-i18n.ts +32 -16
- package/src/cmd-pr.ts +308 -0
- package/src/cmd-shot.ts +320 -0
- package/src/cmd-test.ts +96 -7
- package/src/cmd-verify.ts +10 -427
- package/src/compile-externals.ts +34 -0
- package/src/dev-lock.ts +275 -0
- package/src/dev-render.ts +7 -17
- package/src/error-codes.ts +18 -0
- package/src/generate-files.ts +127 -0
- package/src/generate-write.ts +229 -0
- package/src/gh-target.ts +118 -0
- package/src/gh.ts +204 -0
- package/src/i18n-audit.ts +39 -1
- package/src/i18n-registration.ts +130 -0
- package/src/index.ts +37 -0
- package/src/island-bundle.ts +68 -2
- package/src/island-solid-production.ts +129 -0
- package/src/island-styles.ts +41 -0
- package/src/mcp-errors.ts +11 -0
- package/src/messages.ts +67 -0
- package/src/pr-threads.ts +291 -0
- package/src/prerender.ts +52 -10
- package/src/registry.ts +8 -0
- package/src/shot-verdict.ts +337 -0
- package/src/solid-loader.ts +127 -0
- package/src/static-report.ts +219 -0
- package/src/templates/admin-page.ts +46 -5
- package/src/templates/index.ts +1 -0
- package/src/templates/island-fixture.ts +76 -0
- package/src/templates/island.ts +129 -18
- package/src/templates/resource-form-island.ts +279 -0
- package/src/templates/resource.ts +52 -43
- package/src/templates/route.ts +45 -6
- package/src/templates/scaffold-app.ts +70 -19
- package/src/templates/scaffold-container.ts +2 -2
- package/src/templates/scaffold-db-package.ts +88 -39
- package/src/templates/scaffold-docs.ts +18 -1
- package/src/templates/scaffold-i18n.ts +9 -2
- package/src/templates/scaffold-mcp-package.ts +35 -2
- package/src/templates/scaffold-package-shape.ts +7 -2
- package/src/templates/scaffold-repo.ts +2 -2
- package/src/test-shards.ts +19 -3
- package/src/verify-checks.ts +349 -0
- package/src/verify-run.ts +122 -0
- package/src/verify-step.ts +7 -0
- package/src/workspace-graph.ts +241 -0
- package/types/babel-modules.d.ts +31 -0
|
@@ -13,6 +13,37 @@ import { packageShapeFiles, workspacePackageJson } from './scaffold-package-shap
|
|
|
13
13
|
|
|
14
14
|
const DESCRIPTION = 'Entity re-exports and SQL migrations, no business logic';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The example slice's entity lives in `apps/web/app/post/`, so `src/schema.ts` re-exports it from
|
|
18
|
+
* there — an edge this manifest has to declare or it exists only inside the root tsconfig's
|
|
19
|
+
* `paths`, where `bun --filter` and every change-detection tool are blind to it
|
|
20
|
+
* (`X_WORKSPACE_DEP_UNDECLARED`). Written here rather than through `workspacePackageJson` for the
|
|
21
|
+
* reason `scaffold-i18n.ts` states: that helper is the dependency-free shape.
|
|
22
|
+
*
|
|
23
|
+
* Under `--no-example` there is no entity and no import, so there is no dependency either: a pin
|
|
24
|
+
* for an edge the package does not have is the same lie in the other direction.
|
|
25
|
+
*/
|
|
26
|
+
const dbPackage = (app: NameSet, example: boolean): string =>
|
|
27
|
+
example
|
|
28
|
+
? `{
|
|
29
|
+
"name": "@${app.kebab}/db",
|
|
30
|
+
"version": "0.0.0",
|
|
31
|
+
"private": true,
|
|
32
|
+
"type": "module",
|
|
33
|
+
"description": "${DESCRIPTION}",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./src/index.ts"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"typecheck": "tsc --noEmit -p ../../tsconfig.json"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@${app.kebab}/web": "0.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
: workspacePackageJson(app, 'db', DESCRIPTION);
|
|
46
|
+
|
|
16
47
|
const dbIndex =
|
|
17
48
|
(): string => `// Schema and migrations only — no business logic lives in this package. The client itself is
|
|
18
49
|
// @ultimat3/db's: one connection pool, sized by ROLE, shared by every package in the app.
|
|
@@ -28,20 +59,6 @@ export * as schema from './schema';
|
|
|
28
59
|
const SCHEMA_HEADER = `// Every entity the app declares, re-exported here. This list is what the migration generator
|
|
29
60
|
// reads, so an entity that is not exported here does not exist as far as the database is concerned.`;
|
|
30
61
|
|
|
31
|
-
/**
|
|
32
|
-
* `bun run db:seed`'s entry point. Identical either way — only the rows differ. Interpolated, not
|
|
33
|
-
* nested, so it carries exactly the escaping a single template literal needs.
|
|
34
|
-
*/
|
|
35
|
-
const SEED_MAIN = `
|
|
36
|
-
|
|
37
|
-
if (import.meta.main) {
|
|
38
|
-
const count = await seed();
|
|
39
|
-
// Bun's stdout, not process.stdout: one runtime, one API. Awaited because the write resolves
|
|
40
|
-
// asynchronously, and this JSON line is the whole output of \`bun run db:seed\`.
|
|
41
|
-
await Bun.stdout.write(\`\${JSON.stringify({ ok: true, seeded: count })}\\n\`);
|
|
42
|
-
}
|
|
43
|
-
`;
|
|
44
|
-
|
|
45
62
|
const dbSchema = (app: NameSet, example: boolean): string =>
|
|
46
63
|
example
|
|
47
64
|
? `${SCHEMA_HEADER}
|
|
@@ -52,38 +69,70 @@ export { post } from '@${app.kebab}/web/app/post/entity';
|
|
|
52
69
|
export {};
|
|
53
70
|
`;
|
|
54
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The seed, as a `defineSeed()` — which is what `x db seed` discovers and what the framework has
|
|
74
|
+
* meant by "a seed" since 2.0.0.
|
|
75
|
+
*
|
|
76
|
+
* It used to be a plain `export async function seed()` with an `import.meta.main` block, run by a
|
|
77
|
+
* `bun run db:seed` npm script, and that shipped two defects at once. `x db seed` discovers
|
|
78
|
+
* every `seed*.ts` under a package's `src` and looks for an exported `Seed`, so the scaffold's
|
|
79
|
+
* own seed was invisible to its own command — `x db seed` on a fresh app answered "no seed
|
|
80
|
+
* matched".
|
|
81
|
+
* And `bun run db:seed` reaches the database through `@ultimat3/db`'s `db()`, which reads
|
|
82
|
+
* `DATABASE_URL` and speaks `postgres:` only, so on a clone with no Postgres it cannot see the
|
|
83
|
+
* embedded PGlite that `x db migrate` had just migrated in process. `bin/setup` therefore printed
|
|
84
|
+
* `✓ migrations applied` and then died on `X_DB_UNAVAILABLE`, whose `fix:` says "run `x dev` to use
|
|
85
|
+
* the embedded PGlite" — naming the mechanism that had just worked one line above.
|
|
86
|
+
*
|
|
87
|
+
* `x db seed` owns the connection, the tier and the per-seed transaction. One runner, one answer.
|
|
88
|
+
*/
|
|
55
89
|
const dbSeed = (app: NameSet, example: boolean): string =>
|
|
56
90
|
example
|
|
57
|
-
? `// Deterministic
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
? `// Deterministic fixtures: the same rows every time, so a test, a demo and a branch database
|
|
92
|
+
// all see the same content.
|
|
93
|
+
//
|
|
94
|
+
// \`x db seed\` is the runner — it discovers every exported \`defineSeed()\` in a package's
|
|
95
|
+
// \`src/seed*.ts\`, opens the database exactly as \`x db migrate\` does (embedded PGlite
|
|
96
|
+
// included), and wraps each seed in its own transaction. Never a plain \`bun run\` script: that
|
|
97
|
+
// reaches the database through \`db()\`, which needs a \`postgres:\` \`DATABASE_URL\` and so cannot
|
|
98
|
+
// see the embedded database at all.
|
|
99
|
+
import { defineSeed } from '@ultimat3/entity';
|
|
100
|
+
import { post } from './schema';
|
|
61
101
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
/** Stable across runs: \`id('post:hello')\` is a UUID v5 of the label, not a random one. */
|
|
103
|
+
export const ${app.camel}Seed = defineSeed('${app.kebab}', async ({ insert, id }) => {
|
|
104
|
+
await insert(post, [
|
|
105
|
+
{
|
|
106
|
+
id: id('post:hello'),
|
|
107
|
+
orgId: id('org:demo'),
|
|
108
|
+
title: 'Hello ${app.pascal}',
|
|
109
|
+
price: { minor: 0, currency: 'USD' },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: id('post:second'),
|
|
113
|
+
orgId: id('org:demo'),
|
|
114
|
+
title: 'Second post',
|
|
115
|
+
price: { minor: 1900, currency: 'USD' },
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
`
|
|
120
|
+
: `// Deterministic fixtures, run by \`x db seed\`. No entity is declared yet, so there is nothing
|
|
121
|
+
// to insert — the shape stays so the first \`x g entity\` has one obvious place to seed from.
|
|
122
|
+
//
|
|
123
|
+
// \`x db seed\` discovers every exported \`defineSeed()\` in a package's \`src/seed*.ts\` and opens
|
|
124
|
+
// the database the way \`x db migrate\` does, embedded PGlite included. Never a plain \`bun run\`
|
|
125
|
+
// script: that needs a \`postgres:\` \`DATABASE_URL\` and cannot see the embedded database.
|
|
126
|
+
import { defineSeed } from '@ultimat3/entity';
|
|
79
127
|
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
}
|
|
128
|
+
export const ${app.camel}Seed = defineSeed('${app.kebab}', async () => {
|
|
129
|
+
// \`await insert(<entity>, [...])\` once an entity exists.
|
|
130
|
+
});
|
|
131
|
+
`;
|
|
83
132
|
|
|
84
133
|
/** Every file the `packages/db` workspace ships, in the order `x new` writes them. */
|
|
85
134
|
export const dbPackageFiles = (app: NameSet, example: boolean): readonly GeneratedFile[] => [
|
|
86
|
-
{ path: 'packages/db/package.json', contents:
|
|
135
|
+
{ path: 'packages/db/package.json', contents: dbPackage(app, example) },
|
|
87
136
|
...packageShapeFiles(app, 'db', DESCRIPTION),
|
|
88
137
|
{ path: 'packages/db/src/index.ts', contents: dbIndex() },
|
|
89
138
|
{ path: 'packages/db/src/schema.ts', contents: dbSchema(app, example) },
|
|
@@ -88,7 +88,11 @@ bun install
|
|
|
88
88
|
# this script is documented idempotent, and the guard is what makes that true here.
|
|
89
89
|
ls packages/db/migrations/*.sql >/dev/null 2>&1 || bunx x db gen "initial"
|
|
90
90
|
bunx x db migrate "$@"
|
|
91
|
-
bun run
|
|
91
|
+
# \`x db seed\`, never \`bun run\`: the CLI owns the connection, so this reaches the same embedded
|
|
92
|
+
# PGlite the migration above just wrote to. A plain script goes through \`db()\`, which needs a
|
|
93
|
+
# \`postgres:\` DATABASE_URL and so dies on a clone with no Postgres — one line after reporting a
|
|
94
|
+
# successful migration.
|
|
95
|
+
bunx x db seed
|
|
92
96
|
echo "setup complete — next: x dev"
|
|
93
97
|
`;
|
|
94
98
|
|
|
@@ -103,6 +107,19 @@ const binCheck = (): string => `#!/usr/bin/env bash
|
|
|
103
107
|
# The gate. Same steps as CI, because a check that lives only in CI cannot be run locally.
|
|
104
108
|
set -euo pipefail
|
|
105
109
|
cd "$(dirname "$0")/.."
|
|
110
|
+
# The build FIRST, and not as a convenience: \`x verify\`'s budgets step compares declared limits
|
|
111
|
+
# against measured bytes in .x/build-stats.json, so with no build it reports X_BUDGET_UNMEASURED and
|
|
112
|
+
# the very first gate anyone runs on a brand-new app is red for a reason that has nothing to do with
|
|
113
|
+
# their code. Cheap on a warm tree, and it makes "green" reachable from a fresh clone.
|
|
114
|
+
#
|
|
115
|
+
# \`--json\` is forwarded to BOTH, or the contract breaks: \`bin/check --json\` would otherwise print
|
|
116
|
+
# the build's human renderer to stdout and then the gate's JSON, and a machine consumer reading one
|
|
117
|
+
# document off stdout gets neither. Both commands emit one object; a reader takes the last line.
|
|
118
|
+
build_flags=""
|
|
119
|
+
for arg in "$@"; do
|
|
120
|
+
case "$arg" in --json|-j) build_flags="--json" ;; esac
|
|
121
|
+
done
|
|
122
|
+
bunx x build --target static $build_flags
|
|
106
123
|
exec bunx x verify "$@"
|
|
107
124
|
`;
|
|
108
125
|
|
|
@@ -87,8 +87,15 @@ export type AppCatalog = typeof en;
|
|
|
87
87
|
export type TranslationKey = KeyOf<AppCatalog>;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
* The app's ONE way to read a string. Never \`useI18n()\` directly and never \`t\` from
|
|
91
|
+
* \`@ultimat3/i18n\`, for two independent reasons:
|
|
92
|
+
*
|
|
93
|
+
* 1. the type parameter makes an unknown key a compile error instead of a \`⟦key⟧\` someone
|
|
94
|
+
* notices in production;
|
|
95
|
+
* 2. importing THIS module is what registers the catalogs — \`defineCatalogs()\` above runs on
|
|
96
|
+
* import and nowhere else. A page that reached past it rendered every string as \`⟦key⟧\`
|
|
97
|
+
* with \`x verify\` green, because nothing in the app depended on the module that registers
|
|
98
|
+
* (issue #249). \`x i18n check\` now refuses that app; this import is why it never happens.
|
|
92
99
|
*/
|
|
93
100
|
export const useT = (): Translator<AppCatalog> => useI18n<AppCatalog>();
|
|
94
101
|
`;
|
|
@@ -3,10 +3,43 @@
|
|
|
3
3
|
// itself.
|
|
4
4
|
|
|
5
5
|
import type { GeneratedFile, NameSet } from './naming';
|
|
6
|
-
import { packageShapeFiles
|
|
6
|
+
import { packageShapeFiles } from './scaffold-package-shape';
|
|
7
7
|
|
|
8
8
|
const DESCRIPTION = "The app's own MCP tools";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Writes its own manifest, for the reason `scaffold-i18n.ts` does: `workspacePackageJson` is the
|
|
12
|
+
* dependency-free shape, and this package has a real dependency — `src/index.ts` below imports the
|
|
13
|
+
* app's actions out of `apps/web/api`, because `registerActions` has to see them.
|
|
14
|
+
*
|
|
15
|
+
* The edge points AT the app, which is unusual and correct. `apps/web` is a workspace like any
|
|
16
|
+
* other; reversing it would put the tool catalog upstream of the actions it projects. Declared
|
|
17
|
+
* rather than left to the root tsconfig's `paths`: an undeclared edge resolves for `tsc` and for
|
|
18
|
+
* nothing else — not for `bun --filter` ordering, not for any tool asking what a change affects
|
|
19
|
+
* (`X_WORKSPACE_DEP_UNDECLARED`).
|
|
20
|
+
*
|
|
21
|
+
* `"0.0.0"` and not `workspace:*`: it is the version `apps/web` really carries, which is what
|
|
22
|
+
* `checkLockstep` compares a sibling pin against, and it is the one spelling every other manifest
|
|
23
|
+
* `x new` writes already uses.
|
|
24
|
+
*/
|
|
25
|
+
const mcpPackage = (app: NameSet): string => `{
|
|
26
|
+
"name": "@${app.kebab}/mcp",
|
|
27
|
+
"version": "0.0.0",
|
|
28
|
+
"private": true,
|
|
29
|
+
"type": "module",
|
|
30
|
+
"description": "${DESCRIPTION}",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/index.ts"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"typecheck": "tsc --noEmit -p ../../tsconfig.json"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@${app.kebab}/web": "0.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
|
|
10
43
|
const mcpIndex = (
|
|
11
44
|
app: NameSet,
|
|
12
45
|
): string => `// The app's own MCP tools. Every action with mcp.expose is already a tool; add app-specific
|
|
@@ -42,7 +75,7 @@ unitTest('the app exposes its actions as MCP tools', () => {
|
|
|
42
75
|
|
|
43
76
|
/** Every file the `packages/mcp` workspace ships, in the order `x new` writes them. */
|
|
44
77
|
export const mcpPackageFiles = (app: NameSet): readonly GeneratedFile[] => [
|
|
45
|
-
{ path: 'packages/mcp/package.json', contents:
|
|
78
|
+
{ path: 'packages/mcp/package.json', contents: mcpPackage(app) },
|
|
46
79
|
...packageShapeFiles(app, 'mcp', DESCRIPTION),
|
|
47
80
|
{ path: 'packages/mcp/src/index.ts', contents: mcpIndex(app) },
|
|
48
81
|
{ path: 'packages/mcp/src/index.test.ts', contents: mcpTest() },
|
|
@@ -50,8 +50,13 @@ export const packageShapeFiles = (
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The manifest every scaffolded `packages/*` carries. Private, versionless-by-convention and
|
|
53
|
-
* dependency-free: these packages only re-export a framework package's types
|
|
54
|
-
*
|
|
53
|
+
* dependency-free: these packages only re-export a framework package's types. Three now DO name a
|
|
54
|
+
* dependency and each writes its own manifest — `scaffold-i18n.ts`, `scaffold-mcp-package.ts` and
|
|
55
|
+
* `scaffold-db-package.ts` (the last only under `--example`, which is the branch that emits the
|
|
56
|
+
* import). Each says why at its own site. They write their own rather than taking a `dependencies`
|
|
57
|
+
* parameter here because the edge is a fact about the SOURCE that template emits, and a parameter
|
|
58
|
+
* would let a caller declare an edge its generated code does not have — which is the drift
|
|
59
|
+
* `X_WORKSPACE_DEP_UNDECLARED` exists to catch, pointed the other way. Lives beside
|
|
55
60
|
* `packageShapeFiles` because every caller of one calls the other.
|
|
56
61
|
*/
|
|
57
62
|
export const workspacePackageJson = (app: NameSet, name: string, description: string): string => `{
|
|
@@ -45,12 +45,12 @@ const rootPackage = (app: NameSet, version: string): string => `{
|
|
|
45
45
|
"lint": "biome check .",
|
|
46
46
|
"test": "bun test",
|
|
47
47
|
"db:migrate": "x db migrate",
|
|
48
|
-
"db:seed": "
|
|
48
|
+
"db:seed": "x db seed"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@biomejs/biome": "${BIOME_VERSION}",
|
|
52
52
|
"@electric-sql/pglite": "^0.5.4",
|
|
53
|
-
"@types/bun": "^1.
|
|
53
|
+
"@types/bun": "^1.4.0",
|
|
54
54
|
"@ultimat3/testing": "^${version}",
|
|
55
55
|
"typescript": "^6.0.3"
|
|
56
56
|
},
|
package/src/test-shards.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// cmd-test.ts because a printed reproduction is only true if it carries every input to the split —
|
|
4
4
|
// that rule is this file's, and argv parsing is that one's.
|
|
5
5
|
|
|
6
|
+
import type { AffectedSelection } from './affected';
|
|
6
7
|
import { docsFor } from './error-codes';
|
|
7
8
|
import type { Runner } from './exec';
|
|
8
9
|
import { execOutput } from './exec';
|
|
@@ -72,12 +73,18 @@ export interface ReproduceOptions {
|
|
|
72
73
|
readonly type?: TestType;
|
|
73
74
|
/** Files `--sample` kept, so the rerun samples the same corpus instead of the whole type. */
|
|
74
75
|
readonly sample?: number;
|
|
76
|
+
/**
|
|
77
|
+
* The `--affected` narrowing, when there was one. The fourth input to the split and the one most
|
|
78
|
+
* easily forgotten: `--affected` decides which files exist to shard at all, so a rerun without it
|
|
79
|
+
* re-splits the WHOLE corpus and its shard 2 is a different shard 2.
|
|
80
|
+
*/
|
|
81
|
+
readonly affected?: AffectedSelection;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
/**
|
|
78
|
-
* Every input to the split, printed back. The type and `--
|
|
79
|
-
* shard, `--sample` decides how many of them survive, `--workers` decides the bins — drop
|
|
80
|
-
* and the command still runs, over a different file set, which reproduces nothing.
|
|
85
|
+
* Every input to the split, printed back. The type, `--filter` and `--affected` decide which files
|
|
86
|
+
* exist to shard, `--sample` decides how many of them survive, `--workers` decides the bins — drop
|
|
87
|
+
* any one and the command still runs, over a different file set, which reproduces nothing.
|
|
81
88
|
*/
|
|
82
89
|
export function reproduceFor(shard: Shard, options: ReproduceOptions): string {
|
|
83
90
|
return [
|
|
@@ -85,6 +92,12 @@ export function reproduceFor(shard: Shard, options: ReproduceOptions): string {
|
|
|
85
92
|
...(options.type === undefined ? [] : [quoteArg(options.type)]),
|
|
86
93
|
...(options.filter === undefined ? [] : ['--filter', quoteArg(options.filter)]),
|
|
87
94
|
...(options.sample === undefined ? [] : ['--sample', String(options.sample)]),
|
|
95
|
+
// `--base` is emitted always rather than only when non-default: the default is `main`, and a
|
|
96
|
+
// rerun days later against a moved `main` is a different diff wearing the same flag.
|
|
97
|
+
...(options.affected === undefined
|
|
98
|
+
? []
|
|
99
|
+
: ['--affected', '--base', quoteArg(options.affected.base)]),
|
|
100
|
+
...(options.affected?.dirty === true ? ['--dirty'] : []),
|
|
88
101
|
'--workers',
|
|
89
102
|
String(options.workers),
|
|
90
103
|
'--worker',
|
|
@@ -107,6 +120,8 @@ export interface RunShardsOptions {
|
|
|
107
120
|
* shard of the sample and would otherwise report that shard's size as the corpus.
|
|
108
121
|
*/
|
|
109
122
|
readonly sample?: { readonly kept: number; readonly total: number };
|
|
123
|
+
/** Passed straight to `reproduceFor`: see `ReproduceOptions.affected`. */
|
|
124
|
+
readonly affected?: AffectedSelection;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
/** The reproduction's inputs, resolved once: `workers` is the split's real width, not the ask. */
|
|
@@ -115,6 +130,7 @@ const planOf = (options: RunShardsOptions, workers: number): ReproduceOptions =>
|
|
|
115
130
|
...(options.filter === undefined ? {} : { filter: options.filter }),
|
|
116
131
|
...(options.type === undefined ? {} : { type: options.type }),
|
|
117
132
|
...(options.sample === undefined ? {} : { sample: options.sample.kept }),
|
|
133
|
+
...(options.affected === undefined ? {} : { affected: options.affected }),
|
|
118
134
|
});
|
|
119
135
|
|
|
120
136
|
const failureOf = (shard: Shard, code: number, plan: ReproduceOptions): Finding => ({
|