@ultimat3/cli 23.0.0 → 25.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 +28 -13
- package/README.md +32 -7
- package/package.json +29 -29
- package/src/admin-catalog-keys.ts +2 -2
- package/src/affected.ts +30 -36
- package/src/api-registration.ts +89 -0
- package/src/app-artifacts.ts +1 -1
- package/src/app-auth.ts +4 -27
- package/src/app-boundaries.ts +17 -4
- package/src/app-config-load.ts +51 -0
- package/src/app-env.ts +26 -25
- package/src/app-load.ts +38 -14
- package/src/app-manifest.ts +4 -10
- package/src/app-mcp.ts +31 -46
- package/src/app-name.ts +52 -0
- package/src/app-permissions-borrowed.ts +123 -0
- package/src/app-permissions-site.ts +48 -0
- package/src/app-permissions.ts +7 -17
- package/src/app-root.ts +9 -5
- package/src/bin.ts +3 -7
- package/src/browser-launcher.ts +4 -2
- package/src/budgets-charged.ts +60 -0
- package/src/budgets-scan.ts +76 -0
- package/src/budgets-unmeasured.ts +91 -0
- package/src/budgets.ts +117 -154
- package/src/cdp-shot-driver.ts +20 -1
- package/src/cdp-shot-watch.ts +35 -31
- package/src/ci-log.ts +0 -0
- package/src/cmd-auth-spec.ts +16 -0
- package/src/cmd-auth.ts +65 -0
- package/src/cmd-build-spec.ts +7 -1
- package/src/cmd-build.ts +151 -22
- package/src/cmd-db-backfill.ts +26 -13
- package/src/cmd-db-branch.ts +2 -2
- package/src/cmd-db.ts +10 -6
- package/src/cmd-deploy-compose.ts +94 -0
- package/src/cmd-deploy-drain.ts +48 -0
- package/src/cmd-deploy-helm.ts +7 -6
- package/src/cmd-deploy-start-first.ts +178 -0
- package/src/cmd-deploy.ts +158 -36
- package/src/cmd-dev.ts +18 -356
- package/src/cmd-doctor-spec.ts +2 -9
- package/src/cmd-doctor.ts +57 -80
- package/src/cmd-env.ts +55 -16
- package/src/cmd-generate.ts +89 -32
- package/src/cmd-help.ts +19 -6
- package/src/cmd-i18n.ts +21 -17
- package/src/cmd-jobs-spec.ts +7 -22
- package/src/cmd-jobs.ts +33 -140
- package/src/cmd-manifest-spec.ts +1 -1
- package/src/cmd-manifest.ts +6 -25
- package/src/cmd-mcp.ts +71 -22
- package/src/cmd-new.ts +36 -8
- package/src/cmd-planned.ts +24 -8
- package/src/cmd-routes.ts +15 -5
- package/src/cmd-secrets.ts +47 -6
- package/src/cmd-shot-matrix.ts +22 -7
- package/src/cmd-shot-spec.ts +10 -2
- package/src/cmd-shot.ts +46 -41
- package/src/cmd-tasks.ts +2 -2
- package/src/cmd-test-spec.ts +1 -7
- package/src/cmd-test.ts +14 -30
- package/src/cmd-verify.ts +4 -4
- package/src/coverage-lcov.ts +4 -2
- package/src/db-backfill.ts +2 -2
- package/src/db-replay-engine.ts +4 -10
- package/src/db-subscribes.ts +27 -16
- package/src/dev-boot.ts +290 -0
- package/src/dev-child-watch.ts +63 -12
- package/src/dev-dashboard-guard.ts +76 -0
- package/src/dev-dashboard.ts +56 -30
- package/src/dev-lock.ts +100 -31
- package/src/dev-rebuild.ts +80 -0
- package/src/dev-restart-retire.ts +46 -0
- package/src/dev-route-table.ts +30 -8
- package/src/dev-supervisor.ts +114 -22
- package/src/dev-traces.ts +53 -20
- package/src/dispatch.ts +17 -13
- package/src/doctor-app-url.ts +39 -0
- package/src/doctor-auth.ts +95 -0
- package/src/doctor-offline.ts +6 -5
- package/src/document-options.ts +7 -0
- package/src/drift-replica-identity.ts +42 -0
- package/src/drift.ts +29 -6
- package/src/error-catalog.ts +6 -9
- package/src/error-codes.ts +29 -3
- package/src/error-page-csp.ts +14 -15
- package/src/error-unthrown.ts +29 -11
- package/src/errors.ts +54 -83
- package/src/exec.ts +8 -0
- package/src/favicon.ts +2 -2
- package/src/fix-command.ts +9 -1
- package/src/framework-env.ts +148 -0
- package/src/framework-schema-apply.ts +78 -0
- package/src/framework-schema-stamp.ts +96 -0
- package/src/framework-schema.ts +25 -3
- package/src/generate-catalog-locales.ts +79 -0
- package/src/generate-files.ts +4 -3
- package/src/generate-kinds.ts +18 -0
- package/src/generate-plural.ts +50 -0
- package/src/generate-shadow.ts +95 -0
- package/src/generate-write.ts +47 -13
- package/src/gh-target.ts +12 -7
- package/src/hold.ts +56 -1
- package/src/i18n-audit.ts +25 -9
- package/src/i18n-index.ts +5 -6
- package/src/i18n-registration.ts +7 -9
- package/src/import-scan.ts +28 -3
- package/src/index.ts +2 -6
- package/src/island-bundle.ts +57 -79
- package/src/island-capture.ts +1 -1
- package/src/island-duplicate-refusal.ts +57 -0
- package/src/island-duplicates.ts +221 -0
- package/src/island-entry-missing.ts +64 -0
- package/src/island-harness-route.ts +20 -4
- package/src/island-harness-script.ts +23 -2
- package/src/island-identity.ts +17 -14
- package/src/island-package-dedupe.ts +150 -0
- package/src/island-realtime.ts +36 -16
- package/src/island-routes.ts +2 -2
- package/src/island-runtime.ts +241 -0
- package/src/island-sources.ts +105 -0
- package/src/island-states-load.ts +6 -0
- package/src/island-store.ts +90 -6
- package/src/jobs-json.ts +0 -26
- package/src/jobs-report.ts +36 -20
- package/src/local-cli-handoff.ts +90 -0
- package/src/loopback-host.ts +13 -0
- package/src/manifest-staleness.ts +62 -0
- package/src/mcp-errors.ts +20 -4
- package/src/mcp-host.ts +22 -14
- package/src/mcp-test-run.ts +38 -0
- package/src/mcp-ui-inspect.ts +3 -2
- package/src/mcp-ui-interact.ts +3 -2
- package/src/mcp-ui.ts +8 -7
- package/src/measure-scope.ts +15 -10
- package/src/messages.ts +29 -16
- package/src/metrics-endpoint.ts +28 -3
- package/src/migration-extensions.ts +1 -1
- package/src/otlp-export.ts +2 -2
- package/src/output.ts +24 -0
- package/src/page-navigation.ts +7 -23
- package/src/page-speculation.ts +19 -31
- package/src/parse.ts +0 -8
- package/src/permission-grants.ts +3 -3
- package/src/posix-path.ts +30 -0
- package/src/prerender-locales.ts +6 -4
- package/src/prerender-out.ts +34 -7
- package/src/prerender-report.ts +58 -0
- package/src/prerender-served.ts +52 -0
- package/src/prerender.ts +71 -103
- package/src/pwa-artifacts.ts +33 -94
- package/src/pwa-manifest-locales.ts +9 -9
- package/src/registry.ts +2 -0
- package/src/retry-memo.ts +1 -1
- package/src/revalidated-response.ts +4 -3
- package/src/role-realtime.ts +7 -1
- package/src/role-replicator.ts +124 -29
- package/src/role-start.ts +69 -85
- package/src/role-sync.ts +119 -25
- package/src/root-env.ts +157 -28
- package/src/route-islands.ts +69 -0
- package/src/runtime-admin.ts +4 -1
- package/src/runtime-assets.ts +26 -9
- package/src/runtime-bindings.ts +27 -7
- package/src/runtime-cache.ts +46 -55
- package/src/runtime-hooks.ts +2 -1
- package/src/runtime-idempotency-scope.ts +23 -0
- package/src/runtime-isr-outcome.ts +100 -0
- package/src/runtime-isr.ts +36 -0
- package/src/runtime-jobs.ts +76 -41
- package/src/runtime-live-feed.ts +1 -1
- package/src/runtime-mail.ts +26 -0
- package/src/runtime-mfa-warning.ts +29 -0
- package/src/runtime-notify-retention.ts +10 -46
- package/src/runtime-overrides.ts +4 -4
- package/src/runtime-purge.ts +38 -16
- package/src/runtime-queue.ts +43 -22
- package/src/runtime-realtime.ts +11 -45
- package/src/runtime-render.ts +136 -114
- package/src/runtime-replica.ts +3 -8
- package/src/runtime-services.ts +49 -26
- package/src/runtime-storage-upload.ts +2 -2
- package/src/runtime-storage.ts +13 -8
- package/src/schema-diff.ts +16 -0
- package/src/schema-drift.ts +45 -15
- package/src/secrets-rotation.ts +8 -9
- package/src/seo-routes.ts +80 -9
- package/src/serve-boot.ts +51 -12
- package/src/serve-drain.ts +11 -33
- package/src/serve-env.ts +9 -6
- package/src/serve-retire.ts +95 -0
- package/src/serve-web.ts +31 -15
- package/src/serve.ts +27 -10
- package/src/server-barrels.ts +6 -3
- package/src/shot-browser.ts +1 -1
- package/src/shot-locale.ts +6 -33
- package/src/shot-settle.ts +3 -3
- package/src/shot-theme.ts +1 -1
- package/src/shot-viewport.ts +152 -0
- package/src/signal-shred.ts +26 -12
- package/src/site-asset-routes.ts +2 -2
- package/src/site-assets.ts +3 -5
- package/src/site-config.ts +9 -48
- package/src/site-seo.ts +7 -5
- package/src/sitemap-lastmod.ts +5 -0
- package/src/solid-loader.ts +12 -3
- package/src/source-files.ts +19 -3
- package/src/static-report.ts +5 -0
- package/src/stored-object-headers.ts +43 -0
- package/src/style-routes.ts +2 -2
- package/src/sw-artifacts.ts +8 -8
- package/src/sw-precache-plan.ts +34 -4
- package/src/sync-authenticator.ts +3 -3
- package/src/sync-url.ts +3 -3
- package/src/tasks-facts.ts +3 -29
- package/src/templates/action.ts +7 -4
- package/src/templates/backfill.ts +4 -4
- package/src/templates/entity.ts +2 -2
- package/src/templates/github/ci.yml.ts +28 -18
- package/src/templates/github/image.yml.ts +92 -0
- package/src/templates/github/runner.ts +18 -0
- package/src/templates/guard-bare-error.ts +16 -2
- package/src/templates/guard-unzoned-date.ts +56 -6
- package/src/templates/index.ts +1 -1
- package/src/templates/job-body-test.ts +2 -2
- package/src/templates/job.ts +6 -6
- package/src/templates/naming.ts +28 -0
- package/src/templates/query.ts +9 -8
- package/src/templates/resource-page.ts +9 -7
- package/src/templates/resource-service.ts +2 -2
- package/src/templates/route.ts +26 -14
- package/src/templates/scaffold-api.ts +2 -3
- package/src/templates/scaffold-app.ts +8 -3
- package/src/templates/scaffold-auth.ts +205 -70
- package/src/templates/scaffold-bin.ts +122 -0
- package/src/templates/scaffold-claude-commands.ts +11 -11
- package/src/templates/scaffold-container-compose.ts +10 -1
- package/src/templates/scaffold-container.ts +16 -8
- package/src/templates/scaffold-dashboard-bare.ts +8 -4
- package/src/templates/scaffold-dashboard-example-tests.ts +208 -0
- package/src/templates/scaffold-dashboard-example.ts +112 -172
- package/src/templates/scaffold-dashboard-shared.ts +62 -1
- package/src/templates/scaffold-dashboard.ts +9 -5
- package/src/templates/scaffold-db-client.ts +4 -2
- package/src/templates/scaffold-db-package.ts +2 -2
- package/src/templates/scaffold-docs.ts +20 -77
- package/src/templates/scaffold-domain-package.ts +0 -4
- package/src/templates/scaffold-entries.ts +4 -3
- package/src/templates/scaffold-env.ts +6 -3
- package/src/templates/scaffold-gitattributes.ts +59 -0
- package/src/templates/scaffold-helm-fences.ts +76 -0
- package/src/templates/scaffold-helm-templates.ts +100 -19
- package/src/templates/scaffold-helm.ts +62 -5
- package/src/templates/scaffold-http.ts +1 -1
- package/src/templates/scaffold-i18n.ts +15 -2
- package/src/templates/scaffold-icon.ts +2 -2
- package/src/templates/scaffold-repo.ts +27 -16
- package/src/templates/scaffold-shell.ts +55 -37
- package/src/test-counts.ts +2 -6
- package/src/test-ignores.ts +25 -0
- package/src/test-only-shipped.ts +193 -0
- package/src/test-passes.ts +0 -15
- package/src/test-select.ts +7 -15
- package/src/test-shards.ts +40 -95
- package/src/test-workers.ts +26 -4
- package/src/theme-boot.ts +8 -33
- package/src/theme-brand.ts +66 -0
- package/src/trusted-hops-error.ts +17 -0
- package/src/trusted-hops.ts +49 -0
- package/src/unscanned-admin.ts +2 -2
- package/src/unthrown-codes.ts +26 -0
- package/src/verify-checks.ts +29 -48
- package/src/verify-coverage-run.ts +3 -2
- package/src/verify-deadline.ts +207 -33
- package/src/verify-merge.ts +108 -43
- package/src/verify-part-step.ts +98 -0
- package/src/verify-role-load-probe.ts +12 -2
- package/src/verify-role-load.ts +9 -4
- package/src/verify-run.ts +32 -8
- package/src/verify-shard.ts +13 -2
- package/src/verify-stalled.ts +152 -0
- package/src/verify-step.ts +2 -0
- package/src/verify-test-run.ts +3 -2
- package/src/verify-tests.ts +5 -3
- package/src/worker-bundle.ts +2 -2
- package/src/workspace-checks.ts +53 -7
- package/src/workspace-graph.ts +3 -2
- package/src/write-line.ts +82 -7
- package/src/browser-launcher-fake-html.ts +0 -80
- package/src/browser-launcher-fake.ts +0 -168
- package/src/build-errors.ts +0 -54
- package/src/jobs-drain.ts +0 -163
- package/src/scaffold-typecheck.ts +0 -227
- package/src/thrown-by.ts +0 -24
- package/src/verify-errors.ts +0 -58
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// A generator name whose type spelling the emitted code ALSO uses as a global or a local alias:
|
|
2
|
+
// `x g resource promise` imports `type { Promise }` beside `Promise<Row>`, and `row` writes
|
|
3
|
+
// `type Row = Row`. Decided on the planned files themselves, so a template that starts using
|
|
4
|
+
// another global is covered the day it does — and the names that clash nowhere stay allowed.
|
|
5
|
+
// The same for a VALUE: `x g job job` imports `{ job }` from `@ultimat3/jobs` and declares
|
|
6
|
+
// `export const job = job(…)` beside it, which is TS2440 in the module the run just wrote.
|
|
7
|
+
|
|
8
|
+
import { BadFlagError } from './errors';
|
|
9
|
+
import type { Generator } from './generate-kinds';
|
|
10
|
+
import type { GeneratedFile } from './templates';
|
|
11
|
+
import { camel, kebab, names } from './templates/naming';
|
|
12
|
+
|
|
13
|
+
/** The emitted file that imports `type` from the slice AND uses the same name as its own. */
|
|
14
|
+
function shadowingFile(files: readonly GeneratedFile[], type: string): string | undefined {
|
|
15
|
+
// A pascal spelling is `[A-Za-z0-9]` only (`naming.ts`), so it splices into a pattern as is.
|
|
16
|
+
const imported = new RegExp(
|
|
17
|
+
`import\\s+(?:type\\s+)?\\{[^}]*\\b${type}\\b[^}]*\\}\\s+from\\s+'\\.`,
|
|
18
|
+
);
|
|
19
|
+
const reused = new RegExp(
|
|
20
|
+
`\\b${type}<|^\\s*(?:export\\s+)?(?:type\\s+${type}\\s*=|interface\\s+${type}\\b)`,
|
|
21
|
+
'm',
|
|
22
|
+
);
|
|
23
|
+
return files.find(
|
|
24
|
+
(file) =>
|
|
25
|
+
typeof file.contents === 'string' &&
|
|
26
|
+
/\.tsx?$/.test(file.path) &&
|
|
27
|
+
imported.test(file.contents) &&
|
|
28
|
+
reused.test(file.contents),
|
|
29
|
+
)?.path;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Refuses `name` when its type spelling would shadow a name the planned files rely on. */
|
|
33
|
+
export function refuseShadowedTypes(
|
|
34
|
+
files: readonly GeneratedFile[],
|
|
35
|
+
kind: Generator,
|
|
36
|
+
name: string,
|
|
37
|
+
): void {
|
|
38
|
+
const type = names(name).pascal;
|
|
39
|
+
const file = shadowingFile(files, type);
|
|
40
|
+
if (file === undefined) return;
|
|
41
|
+
const noun = kind.split(':').at(-1) ?? kind;
|
|
42
|
+
throw new BadFlagError({
|
|
43
|
+
flag: 'name',
|
|
44
|
+
command: `g ${kind}`,
|
|
45
|
+
reason: `"${name}" becomes the type ${type}, and ${file} imports it beside its own use of ${type} — the global or local type the template means is shadowed and the file does not compile`,
|
|
46
|
+
fix: ['x g', kind, `${kebab(name)}-${noun}`].join(' '),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A `{ … }` value import's local names — `type` specifiers and `import type` lines excluded. */
|
|
51
|
+
const VALUE_IMPORT = /^import\s+\{([^}]*)\}\s+from\s+'[^']+';/gm;
|
|
52
|
+
|
|
53
|
+
const importedValues = (source: string): readonly string[] =>
|
|
54
|
+
[...source.matchAll(VALUE_IMPORT)].flatMap((match) =>
|
|
55
|
+
(match[1] ?? '')
|
|
56
|
+
.split(',')
|
|
57
|
+
.map((specifier) => specifier.trim())
|
|
58
|
+
.filter((specifier) => specifier !== '' && !specifier.startsWith('type '))
|
|
59
|
+
.map((specifier) => specifier.split(/\s+as\s+/).at(-1) ?? specifier),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/** The emitted file that imports `value` AND declares a binding of the same name. */
|
|
63
|
+
function redeclaringFile(files: readonly GeneratedFile[], value: string): string | undefined {
|
|
64
|
+
// A camel spelling is `[A-Za-z0-9]` only (`naming.ts`), so it splices into a pattern as is.
|
|
65
|
+
const declared = new RegExp(
|
|
66
|
+
`^\\s*(?:export\\s+)?(?:const|let|var|function|class)\\s+${value}\\b`,
|
|
67
|
+
'm',
|
|
68
|
+
);
|
|
69
|
+
return files.find(
|
|
70
|
+
(file) =>
|
|
71
|
+
typeof file.contents === 'string' &&
|
|
72
|
+
/\.tsx?$/.test(file.path) &&
|
|
73
|
+
declared.test(file.contents) &&
|
|
74
|
+
importedValues(file.contents).includes(value),
|
|
75
|
+
)?.path;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Refuses `name` when its camel spelling is a value a planned file both imports and declares. */
|
|
79
|
+
export function refuseShadowedValues(
|
|
80
|
+
files: readonly GeneratedFile[],
|
|
81
|
+
kind: Generator,
|
|
82
|
+
name: string,
|
|
83
|
+
): void {
|
|
84
|
+
const value = camel(name);
|
|
85
|
+
if (value === '') return;
|
|
86
|
+
const file = redeclaringFile(files, value);
|
|
87
|
+
if (file === undefined) return;
|
|
88
|
+
const noun = kind.split(':').at(-1) ?? kind;
|
|
89
|
+
throw new BadFlagError({
|
|
90
|
+
flag: 'name',
|
|
91
|
+
command: `g ${kind}`,
|
|
92
|
+
reason: `"${name}" becomes the binding ${value}, and ${file} imports ${value} and declares its own — a module that declares a name it imports does not compile (TS2440)`,
|
|
93
|
+
fix: ['x g', kind, `${kebab(name)}-${noun}`].join(' '),
|
|
94
|
+
});
|
|
95
|
+
}
|
package/src/generate-write.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function containedPath(root: string, path: string): string {
|
|
|
102
102
|
dir: base,
|
|
103
103
|
// Command first, the caveat behind a `#`: the line runs verbatim and the shell drops the
|
|
104
104
|
// rest. `x g <kind> <name>` pasted into bash is a redirect, not a command.
|
|
105
|
-
fix: `x g resource
|
|
105
|
+
fix: `x g resource post --dry-run # name every file relative to the app root, no ".." segment`,
|
|
106
106
|
});
|
|
107
107
|
return target;
|
|
108
108
|
}
|
|
@@ -198,14 +198,56 @@ function planFile(
|
|
|
198
198
|
return { kind: 'write', file, absolute, contents: file.contents };
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/** Every file's plan, decided without touching the disk — the first of `writeFiles`' two passes. */
|
|
202
|
+
async function planAll(
|
|
203
|
+
root: string,
|
|
204
|
+
files: readonly GeneratedFile[],
|
|
205
|
+
force: boolean,
|
|
206
|
+
invocation: string,
|
|
207
|
+
): Promise<readonly WritePlan[]> {
|
|
208
|
+
const plans: WritePlan[] = [];
|
|
209
|
+
for (const file of files) {
|
|
210
|
+
const absolute = containedPath(root, file.path);
|
|
211
|
+
plans.push(
|
|
212
|
+
file.merge === 'json'
|
|
213
|
+
? await planJsonMerge(file, absolute)
|
|
214
|
+
: planFile(file, absolute, force, invocation),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return plans;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const conflictsOf = (plans: readonly WritePlan[]): readonly Finding[] =>
|
|
221
|
+
plans.flatMap((plan) => (plan.kind === 'conflict' ? [plan.finding] : []));
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* What `writeFiles` WOULD do, and nothing else: the paths it would write and the conflicts that
|
|
225
|
+
* would stop it. `x g --dry-run` answers with this — the bare file list said "would write 33"
|
|
226
|
+
* where the real run was `X_GENERATE_CONFLICT`, and listed slice modules the run skips as writes.
|
|
227
|
+
*/
|
|
228
|
+
export async function planWrites(
|
|
229
|
+
root: string,
|
|
230
|
+
files: readonly GeneratedFile[],
|
|
231
|
+
force: boolean,
|
|
232
|
+
invocation = 'x g <kind> <name>',
|
|
233
|
+
): Promise<WriteReport> {
|
|
234
|
+
const plans = await planAll(root, files, force, invocation);
|
|
235
|
+
const conflicts = conflictsOf(plans);
|
|
236
|
+
if (conflicts.length > 0) return { written: [], conflicts };
|
|
237
|
+
return {
|
|
238
|
+
written: plans.flatMap((plan) => (plan.kind === 'write' ? [plan.file.path] : [])),
|
|
239
|
+
conflicts: [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
201
243
|
/**
|
|
202
244
|
* Never clobbers, and never half-writes. A generator that overwrites is a generator nobody runs
|
|
203
245
|
* twice; a generator that lands four of seven files and then reports a conflict is worse, because
|
|
204
246
|
* the next run conflicts on the files the failed one wrote.
|
|
205
247
|
*
|
|
206
248
|
* Two passes, and the split is the point: the first decides — containment, existence, whether a
|
|
207
|
-
* catalog can be merged into — and touches nothing
|
|
208
|
-
*
|
|
249
|
+
* catalog can be merged into — and touches nothing (`planWrites` is that pass alone), the second
|
|
250
|
+
* writes only when the first found no conflict at all.
|
|
209
251
|
*/
|
|
210
252
|
export async function writeFiles(
|
|
211
253
|
root: string,
|
|
@@ -218,16 +260,8 @@ export async function writeFiles(
|
|
|
218
260
|
*/
|
|
219
261
|
invocation = 'x g <kind> <name>',
|
|
220
262
|
): Promise<WriteReport> {
|
|
221
|
-
const plans
|
|
222
|
-
|
|
223
|
-
const absolute = containedPath(root, file.path);
|
|
224
|
-
plans.push(
|
|
225
|
-
file.merge === 'json'
|
|
226
|
-
? await planJsonMerge(file, absolute)
|
|
227
|
-
: planFile(file, absolute, force, invocation),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
const conflicts = plans.flatMap((plan) => (plan.kind === 'conflict' ? [plan.finding] : []));
|
|
263
|
+
const plans = await planAll(root, files, force, invocation);
|
|
264
|
+
const conflicts = conflictsOf(plans);
|
|
231
265
|
if (conflicts.length > 0) return { written: [], conflicts };
|
|
232
266
|
|
|
233
267
|
const written: string[] = [];
|
package/src/gh-target.ts
CHANGED
|
@@ -76,18 +76,23 @@ const PR_VIEW = t.object({ number: t.number });
|
|
|
76
76
|
* The pull request for the current branch. gh exits non-zero when there is none, with a message
|
|
77
77
|
* that names the branch — so the refusal keeps gh's own sentence and adds the remedy gh has no
|
|
78
78
|
* opinion about: name the number, or open the PR.
|
|
79
|
+
*
|
|
80
|
+
* No `--repo`: gh refuses `pr view --repo <slug>` without a number, url or branch before any
|
|
81
|
+
* network call, so that argv could never succeed. The checkout-scoped form resolves the branch's
|
|
82
|
+
* tracking remote itself — the same resolution `gh repo view` gave `resolveRepo`. `repo` names the
|
|
83
|
+
* pull request in the refusal only.
|
|
79
84
|
*/
|
|
80
85
|
export async function resolvePrNumber(host: GhHost, repo: GhRepo): Promise<number> {
|
|
81
86
|
try {
|
|
82
|
-
const viewed = await ghJson(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{ label: 'gh pr view', fix: 'x pr review --pr 241 --json' },
|
|
87
|
-
);
|
|
87
|
+
const viewed = await ghJson(host, ['pr', 'view', '--json', 'number'], PR_VIEW, {
|
|
88
|
+
label: 'gh pr view',
|
|
89
|
+
fix: 'x pr review --pr 241 --json',
|
|
90
|
+
});
|
|
88
91
|
return viewed.number;
|
|
89
92
|
} catch (error) {
|
|
90
|
-
if (error instanceof GhFailedError)
|
|
93
|
+
if (error instanceof GhFailedError) {
|
|
94
|
+
throw new PrNotFoundError({ detail: `${repo.slug}: ${error.cause}` });
|
|
95
|
+
}
|
|
91
96
|
throw error;
|
|
92
97
|
}
|
|
93
98
|
}
|
package/src/hold.ts
CHANGED
|
@@ -48,6 +48,18 @@ export function holdUntilShutdown(
|
|
|
48
48
|
release: () => Promise<void>,
|
|
49
49
|
options: HoldOptions = {},
|
|
50
50
|
): () => Promise<void> {
|
|
51
|
+
return armHold(name, release, options).hold;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A hold whose handlers are already listening, and the way to give them back unheld. */
|
|
55
|
+
interface ArmedHold {
|
|
56
|
+
readonly hold: () => Promise<void>;
|
|
57
|
+
/** Uninstall the handlers and the hook without waiting for any shutdown — a boot that failed. */
|
|
58
|
+
readonly disarm: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The signal handlers and the `accept` hook, installed NOW; the release waits for `hold()`. */
|
|
62
|
+
function armHold(name: string, release: () => Promise<void>, options: HoldOptions): ArmedHold {
|
|
51
63
|
const uninstall = installSignalHandlers({ exit: false });
|
|
52
64
|
let unregister = (): void => {};
|
|
53
65
|
// The hook's own `reason`, not a stopwatch of ours: `deadlineAt` is the instant core computed
|
|
@@ -64,7 +76,7 @@ export function holdUntilShutdown(
|
|
|
64
76
|
});
|
|
65
77
|
|
|
66
78
|
let held: Promise<void> | undefined;
|
|
67
|
-
|
|
79
|
+
const hold = (): Promise<void> => {
|
|
68
80
|
// Memoised: awaiting a hold twice must not release twice, and `dispatch` is not the only
|
|
69
81
|
// caller a test can be.
|
|
70
82
|
held ??= (async () => {
|
|
@@ -79,6 +91,49 @@ export function holdUntilShutdown(
|
|
|
79
91
|
})();
|
|
80
92
|
return held;
|
|
81
93
|
};
|
|
94
|
+
return {
|
|
95
|
+
hold,
|
|
96
|
+
disarm: () => {
|
|
97
|
+
unregister();
|
|
98
|
+
uninstall();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What a boot answers once it is up: the app, and the hold that keeps the process on it. */
|
|
104
|
+
export interface BootedHold<T> {
|
|
105
|
+
readonly app: T;
|
|
106
|
+
readonly hold: () => Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Boot under the drain's handlers, never ahead of them. `runRole` used to install them after the
|
|
111
|
+
* boot returned, while `startRoles` starts the worker claiming in the middle of it — so a SIGTERM
|
|
112
|
+
* landing between the two (a rollout replacing a pod still booting) met Bun's default and ended a
|
|
113
|
+
* process holding claimed jobs, each one stranded for a whole visibility timeout. Armed first, the
|
|
114
|
+
* same signal is a drain: whatever the boot has started hears core's phases (the worker stops
|
|
115
|
+
* claiming and hands back at its cut-off), and the release — the app's own `stop()` — runs once
|
|
116
|
+
* the boot has finished, so nothing it acquired after the signal is left running.
|
|
117
|
+
*
|
|
118
|
+
* A boot that throws gives the handlers back and rethrows its own error: the refusal is the
|
|
119
|
+
* finding, and handlers left installed would turn the next signal into a drain of nothing.
|
|
120
|
+
*/
|
|
121
|
+
export async function holdWhileBooting<T extends { stop(): Promise<void> }>(
|
|
122
|
+
name: string,
|
|
123
|
+
boot: () => Promise<T>,
|
|
124
|
+
options: HoldOptions = {},
|
|
125
|
+
): Promise<BootedHold<T>> {
|
|
126
|
+
let booted: T | undefined;
|
|
127
|
+
const armed = armHold(name, async () => await booted?.stop(), options);
|
|
128
|
+
let app: T;
|
|
129
|
+
try {
|
|
130
|
+
app = await boot();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
armed.disarm();
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
booted = app;
|
|
136
|
+
return { app, hold: armed.hold };
|
|
82
137
|
}
|
|
83
138
|
|
|
84
139
|
/**
|
package/src/i18n-audit.ts
CHANGED
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
// `node:` and not Bun: Bun exposes no existence check (`existsSync`, which is how a missing
|
|
6
6
|
// `catalogs/` directory reads as "nothing shipped" instead of a throw) and no path API at all —
|
|
7
|
-
// `join` builds the absolute path `Bun.file` reads
|
|
7
|
+
// `join` builds the absolute path `Bun.file` reads; `posixRelative` turns it back into the
|
|
8
8
|
// root-relative POSIX shape every CLI-reported path is keyed by.
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
|
-
import { join
|
|
11
|
-
import { renderThrowable } from '@ultimat3/core';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { DEFAULT_LOCALE, renderThrowable } from '@ultimat3/core';
|
|
12
12
|
import type { Catalog, Extraction, ExtractReport, Locale } from '@ultimat3/i18n';
|
|
13
13
|
import {
|
|
14
14
|
auditCatalogs,
|
|
15
15
|
catalogInvalid,
|
|
16
16
|
catalogKeys,
|
|
17
|
-
DEFAULT_LOCALE,
|
|
18
17
|
extractFromFiles,
|
|
19
18
|
loadCatalog,
|
|
20
19
|
missingFrom,
|
|
21
20
|
nestCatalog,
|
|
22
21
|
} from '@ultimat3/i18n';
|
|
22
|
+
import { isLoudMiss, loudMiss } from './i18n-registration';
|
|
23
|
+
import { posixRelative } from './posix-path';
|
|
23
24
|
import { eachSourceFile, isTest } from './source-files';
|
|
24
25
|
import { CATALOG_ROOT, catalogPath } from './templates/locales';
|
|
25
26
|
|
|
@@ -28,16 +29,19 @@ import { CATALOG_ROOT, catalogPath } from './templates/locales';
|
|
|
28
29
|
* glob set every text-scanning gate step already shares (`errors`, `filesize`) — a second glob
|
|
29
30
|
* here would mean this command and `x verify` disagree on what "the app's source" is. Test files
|
|
30
31
|
* are excluded: a fixture's `t('fixture.key')` is not a gap the shipped catalogs owe an answer to.
|
|
32
|
+
* So are `guards/`: gate tooling renders nothing, and its fix lines quote `t('…')` as an example.
|
|
31
33
|
* `extractFromFiles` reads by absolute path, so its `file` label is rewritten back to the same
|
|
32
34
|
* root-relative POSIX shape `app-load.ts` uses for every other CLI-reported path.
|
|
33
35
|
*/
|
|
36
|
+
const isGuard = (file: string): boolean => file.startsWith('guards/');
|
|
37
|
+
|
|
34
38
|
export async function scanSource(root: string): Promise<Extraction> {
|
|
35
39
|
const files: string[] = [];
|
|
36
40
|
for await (const file of eachSourceFile(root)) {
|
|
37
|
-
if (!isTest(file)) files.push(file);
|
|
41
|
+
if (!isTest(file) && !isGuard(file)) files.push(file);
|
|
38
42
|
}
|
|
39
43
|
const extraction = await extractFromFiles(files.map((file) => join(root, file)));
|
|
40
|
-
const toRelative = (file: string): string =>
|
|
44
|
+
const toRelative = (file: string): string => posixRelative(root, file);
|
|
41
45
|
return {
|
|
42
46
|
usages: extraction.usages.map((usage) => ({ ...usage, file: toRelative(usage.file) })),
|
|
43
47
|
dynamic: extraction.dynamic.map((entry) => ({ ...entry, file: toRelative(entry.file) })),
|
|
@@ -182,11 +186,23 @@ function sortCatalog(catalog: Catalog): Catalog {
|
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
/**
|
|
185
|
-
* `x i18n add`'s seed: every key the default locale
|
|
186
|
-
*
|
|
189
|
+
* `x i18n add`'s seed, and `x i18n sync`'s source on a real merge: every key the default locale
|
|
190
|
+
* defines, each value copied INSIDE the CLI's
|
|
191
|
+
* one placeholder marker — `⟦Hello {name}⟧`. A bare copy rendered the default locale's string to
|
|
192
|
+
* every `fr` reader under a green gate, an untranslated catalog no audit could tell from a
|
|
193
|
+
* finished one. Marked, the source text still sits where the translation goes, the page shows the
|
|
194
|
+
* brackets, and `withPlaceholdersMissing` counts every key as missing (`X_CATALOG_MISSING_KEYS`)
|
|
195
|
+
* until a human replaces it. `loudMiss` is the marker's one spelling — a second one would be a
|
|
196
|
+
* placeholder this command writes and the gate cannot see.
|
|
187
197
|
*/
|
|
188
198
|
export function seedCatalog(source: Catalog): Catalog {
|
|
189
|
-
|
|
199
|
+
const marked: Record<string, string> = {};
|
|
200
|
+
for (const key of catalogKeys(source)) {
|
|
201
|
+
const value = source[key];
|
|
202
|
+
// A value already a placeholder stays one, as written — never `⟦⟦key⟧⟧`.
|
|
203
|
+
if (value !== undefined) marked[key] = isLoudMiss(value) ? value : loudMiss(value);
|
|
204
|
+
}
|
|
205
|
+
return marked;
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
export interface SyncResult {
|
package/src/i18n-index.ts
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
// why: Bun has no synchronous existence check — `Bun.file(p).exists()` is async, and this decides
|
|
10
10
|
// whether to write at all, before any await the caller could interleave with.
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
|
+
import { APP_CATALOGS_PATH } from '@ultimat3/i18n/app-catalogs';
|
|
12
13
|
import { containedPath } from './generate-write';
|
|
13
14
|
import type { Finding } from './output';
|
|
14
15
|
import { CATALOG_ROOT, i18nIndex, localeEntry, localeImport } from './templates';
|
|
15
16
|
|
|
16
|
-
export const I18N_INDEX_PATH = 'packages/i18n/src/index.ts';
|
|
17
|
-
|
|
18
17
|
/** Every locale with a catalog on disk, sorted — the file names are the tags. */
|
|
19
18
|
export async function catalogLocales(root: string): Promise<readonly string[]> {
|
|
20
19
|
const catalogDir = containedPath(root, CATALOG_ROOT);
|
|
@@ -50,7 +49,7 @@ const importedLocales = (source: string): readonly string[] =>
|
|
|
50
49
|
* overwritten: it was, on every `x g`, with a template hard-coding `default: 'en'`.
|
|
51
50
|
*/
|
|
52
51
|
export async function syncI18nIndex(root: string): Promise<IndexSync> {
|
|
53
|
-
const indexAbsolute = containedPath(root,
|
|
52
|
+
const indexAbsolute = containedPath(root, APP_CATALOGS_PATH);
|
|
54
53
|
if (!existsSync(indexAbsolute)) return { registered: false, findings: [] };
|
|
55
54
|
const current = await Bun.file(indexAbsolute).text();
|
|
56
55
|
const onDisk = await catalogLocales(root);
|
|
@@ -83,7 +82,7 @@ function withLocales(source: string, locales: readonly string[]): string | undef
|
|
|
83
82
|
|
|
84
83
|
const refusal = (locales: readonly string[]): Finding => ({
|
|
85
84
|
code: 'X_CATALOG_UNREGISTERED',
|
|
86
|
-
cause: `${
|
|
87
|
-
fix: `edit ${
|
|
88
|
-
at:
|
|
85
|
+
cause: `${APP_CATALOGS_PATH} is hand-written in a shape this writer cannot add ${locales.join(', ')} to, so those catalogs are on disk and not selectable`,
|
|
86
|
+
fix: `edit ${APP_CATALOGS_PATH} — add ${locales.map(localeImport).join(' ')} and ${locales.map(localeEntry).join(', ')} to the locales passed to defineCatalogs`,
|
|
87
|
+
at: APP_CATALOGS_PATH,
|
|
89
88
|
});
|
package/src/i18n-registration.ts
CHANGED
|
@@ -18,12 +18,13 @@ import {
|
|
|
18
18
|
pluralVariantsOf,
|
|
19
19
|
registeredLocales,
|
|
20
20
|
} from '@ultimat3/i18n';
|
|
21
|
+
import { APP_CATALOGS_PATH } from '@ultimat3/i18n/app-catalogs';
|
|
21
22
|
import { mountedAdminKeys } from './admin-catalog-keys';
|
|
23
|
+
import type { LoadedApp } from './app-load';
|
|
22
24
|
import { loadApp } from './app-load';
|
|
23
25
|
import type { DuplicateProbe } from './duplicate-packages';
|
|
24
26
|
import { duplicateCause, duplicateFinding, findDuplicateInstalls } from './duplicate-packages';
|
|
25
27
|
import { auditApp } from './i18n-audit';
|
|
26
|
-
import { I18N_INDEX_PATH } from './i18n-index';
|
|
27
28
|
import type { Finding } from './output';
|
|
28
29
|
import { findingFrom } from './output';
|
|
29
30
|
import { CATALOG_ROOT, catalogPath } from './templates/locales';
|
|
@@ -37,10 +38,7 @@ const I18N_PKG = '@ultimat3/i18n';
|
|
|
37
38
|
* can resolve `@ultimat3/*`. `loadApp` is the production value, and it is the same call
|
|
38
39
|
* `serveApp` makes: asking a different loader than the server uses would prove nothing.
|
|
39
40
|
*/
|
|
40
|
-
export type AppLoader = (root: string) => Promise<
|
|
41
|
-
readonly findings: readonly Finding[];
|
|
42
|
-
readonly defaultLocale: string;
|
|
43
|
-
}>;
|
|
41
|
+
export type AppLoader = (root: string) => Promise<Pick<LoadedApp, 'findings' | 'locales'>>;
|
|
44
42
|
|
|
45
43
|
export interface RegistrationInput {
|
|
46
44
|
readonly root: string;
|
|
@@ -121,9 +119,9 @@ export async function checkRegistration(input: RegistrationInput): Promise<Regis
|
|
|
121
119
|
let locales = gaps.length;
|
|
122
120
|
|
|
123
121
|
if (Object.keys(input.catalogs).length === 0) {
|
|
124
|
-
const unresolved = unresolvedUsedKeys(input, app.defaultLocale);
|
|
122
|
+
const unresolved = unresolvedUsedKeys(input, app.locales.defaultLocale);
|
|
125
123
|
if (unresolved.length > 0) {
|
|
126
|
-
findings.push(findingFrom(catalogsNeverRegistered(app.defaultLocale, unresolved)));
|
|
124
|
+
findings.push(findingFrom(catalogsNeverRegistered(app.locales.defaultLocale, unresolved)));
|
|
127
125
|
unregistered += unresolved.length;
|
|
128
126
|
locales += 1;
|
|
129
127
|
}
|
|
@@ -182,7 +180,7 @@ function adminKeysFinding(locale: Locale, missing: readonly string[]): Finding {
|
|
|
182
180
|
* the half that knows what an app's directories are.
|
|
183
181
|
*/
|
|
184
182
|
async function indexSource(root: string): Promise<string | undefined> {
|
|
185
|
-
const file = Bun.file(join(root,
|
|
183
|
+
const file = Bun.file(join(root, APP_CATALOGS_PATH));
|
|
186
184
|
return (await file.exists()) ? file.text() : undefined;
|
|
187
185
|
}
|
|
188
186
|
|
|
@@ -208,7 +206,7 @@ export function unregisteredFix(
|
|
|
208
206
|
// report a registered locale as unregistered, which is the direction that costs trust.
|
|
209
207
|
if (index.includes(`${CATALOG_ROOT.split('/').pop() ?? 'catalogs'}/${locale}.json`)) return {};
|
|
210
208
|
return {
|
|
211
|
-
fix: `x i18n sync ${locale} # re-derives ${
|
|
209
|
+
fix: `x i18n sync ${locale} # re-derives ${APP_CATALOGS_PATH} from the catalogs on disk`,
|
|
212
210
|
};
|
|
213
211
|
}
|
|
214
212
|
|
package/src/import-scan.ts
CHANGED
|
@@ -42,12 +42,36 @@ export function scanRuntimeImports(file: ScannedFile): readonly string[] {
|
|
|
42
42
|
.map((entry) => entry.path);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* A type-position `import('…')` — `typeof import('@ultimat3/cli')`, `let x: import('a').B` — is no
|
|
47
|
+
* import statement, so the transpiler erases it whole and neither pass above sees it (K16: the tier
|
|
48
|
+
* rule passed one). Read off `masked`, the source with comments and string CONTENTS blanked and
|
|
49
|
+
* delimiters kept (`@ultimat3/core`'s `maskLiterals`): a match there is code, and the specifier is
|
|
50
|
+
* read back from `source` at the same offsets. Taken as an argument because this file is a leaf.
|
|
51
|
+
* A dynamic `import(x)` is reported too, which the runtime pass already holds — callers dedupe.
|
|
52
|
+
*/
|
|
53
|
+
const IMPORT_CALL = /(?<![\w$.])import\s*\(\s*(['"])/g;
|
|
54
|
+
|
|
55
|
+
export function typePositionImports(source: string, masked: string): readonly string[] {
|
|
56
|
+
const found: string[] = [];
|
|
57
|
+
for (const match of masked.matchAll(IMPORT_CALL)) {
|
|
58
|
+
const quote = match[1] as string;
|
|
59
|
+
const open = match.index + match[0].length;
|
|
60
|
+
const close = masked.indexOf(quote, open);
|
|
61
|
+
if (close === -1) continue;
|
|
62
|
+
const specifier = source.slice(open, close);
|
|
63
|
+
if (specifier !== '' && !specifier.includes('\n')) found.push(specifier);
|
|
64
|
+
}
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
/**
|
|
46
69
|
* Every specifier the file names, type-only ones included. Throws when the file does not parse —
|
|
47
70
|
* a scan that answered `[]` there would read as a file importing nothing. The rewrite pass alone
|
|
48
|
-
* is forgiving: a rewrite the parser refuses falls back to the runtime scan.
|
|
71
|
+
* is forgiving: a rewrite the parser refuses falls back to the runtime scan. Hand it `masked`
|
|
72
|
+
* (core's `maskLiterals(file.source)`) and a type-position `import('…')` is read as well.
|
|
49
73
|
*/
|
|
50
|
-
export function scanAllImports(file: ScannedFile): readonly string[] {
|
|
74
|
+
export function scanAllImports(file: ScannedFile, masked?: string): readonly string[] {
|
|
51
75
|
const runtime = scanRuntimeImports(file);
|
|
52
76
|
let typed: readonly string[] = [];
|
|
53
77
|
try {
|
|
@@ -58,6 +82,7 @@ export function scanAllImports(file: ScannedFile): readonly string[] {
|
|
|
58
82
|
} catch {
|
|
59
83
|
typed = [];
|
|
60
84
|
}
|
|
85
|
+
const inTypes = masked === undefined ? [] : typePositionImports(file.source, masked);
|
|
61
86
|
// The rewritten pass first: it holds every import in SOURCE order, which callers report in.
|
|
62
|
-
return [...new Set([...typed, ...runtime])];
|
|
87
|
+
return [...new Set([...typed, ...inTypes, ...runtime])];
|
|
63
88
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { appManifest, writeAppManifest } from './app-manifest';
|
|
|
21
21
|
export { requireAppRoot } from './app-root';
|
|
22
22
|
export type { RouteStats } from './budgets';
|
|
23
23
|
export { checkBudgets, FRAMEWORK_INLINE_SCRIPTS, FRAMEWORK_SCRIPTS } from './budgets';
|
|
24
|
-
export { BUILD_ENTRY, BUILD_TARGETS, readTarget } from './cmd-build';
|
|
24
|
+
export { BUILD_ENTRY, BUILD_TARGETS, BuildEntryMissingError, readTarget } from './cmd-build';
|
|
25
25
|
export { dbCommand } from './cmd-db';
|
|
26
26
|
export { planDeploy } from './cmd-deploy';
|
|
27
27
|
export { startDev } from './cmd-dev';
|
|
@@ -69,10 +69,8 @@ export {
|
|
|
69
69
|
export { checkErrorCodesThrown } from './error-unthrown';
|
|
70
70
|
export {
|
|
71
71
|
BadFlagError,
|
|
72
|
-
BuildEntryMissingError,
|
|
73
72
|
BunVersionError,
|
|
74
73
|
CatalogExistsError,
|
|
75
|
-
CliNotImplementedError,
|
|
76
74
|
DeclarationUnknownError,
|
|
77
75
|
ErrorCodeUnknownError,
|
|
78
76
|
FixTargetUnknownError,
|
|
@@ -117,7 +115,6 @@ export { ICON_BASE_PATH, ICON_SOURCE } from './icon-assets';
|
|
|
117
115
|
// `x build`'s and `x dev`'s wiring, and every name here is a semver promise forever.
|
|
118
116
|
export type { IslandChunk, SharedChunk } from './island-bundle';
|
|
119
117
|
export { buildIslands } from './island-bundle';
|
|
120
|
-
export { JOB_STATES } from './jobs-report';
|
|
121
118
|
export { renderJobTable } from './jobs-table';
|
|
122
119
|
export { msg } from './messages';
|
|
123
120
|
export { DEFAULT_METRICS_PORT, startMetricsEndpoint } from './metrics-endpoint';
|
|
@@ -125,7 +122,7 @@ export { parseMigrationSql, readMigrations } from './migrations';
|
|
|
125
122
|
export type { CommandResult, Finding, JsonValue } from './output';
|
|
126
123
|
export { exitCodeFor, findingFrom, render, renderFinding, renderHuman } from './output';
|
|
127
124
|
export type { CommandSpec, FlagSpec, ParsedArgs } from './parse';
|
|
128
|
-
export { flagBool, flagList, flagString, GLOBAL_FLAGS,
|
|
125
|
+
export { flagBool, flagList, flagString, GLOBAL_FLAGS, parseArgs } from './parse';
|
|
129
126
|
export type { PrerenderedPage, PrerenderReport } from './prerender';
|
|
130
127
|
export { DEFAULT_ORIGIN, isPrerenderable, prerenderSite } from './prerender';
|
|
131
128
|
export type { PwaArtifacts } from './pwa-artifacts';
|
|
@@ -171,7 +168,6 @@ export { readVerifyFloor, skippedSuiteFinding, VERIFY_FLOOR_FILE } from './verif
|
|
|
171
168
|
export type { HostCheck, StepOutcome, VerifyStep, VerifyStepName } from './verify-step';
|
|
172
169
|
export { VERIFY_STEP_NAMES } from './verify-step';
|
|
173
170
|
export type { TestType } from './verify-tests';
|
|
174
|
-
export { TEST_TYPES } from './verify-tests';
|
|
175
171
|
export {
|
|
176
172
|
checkFileSizes,
|
|
177
173
|
checkLockstep,
|