@ultimat3/cli 21.0.0 → 22.1.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 +84 -1650
- package/README.md +40 -2
- package/package.json +30 -30
- package/src/api-registration.ts +124 -0
- package/src/app-artifacts.ts +39 -0
- package/src/app-boundaries.ts +5 -10
- package/src/app-load.ts +37 -5
- package/src/app-openapi.ts +27 -0
- package/src/app-permissions.ts +0 -0
- package/src/app-root.ts +1 -1
- package/src/async-pages.ts +32 -0
- package/src/bin.ts +7 -1
- package/src/boundary-findings.ts +36 -0
- package/src/browser-launcher-fake-html.ts +80 -0
- package/src/browser-launcher-fake.ts +165 -0
- package/src/browser-launcher-port.ts +159 -0
- package/src/browser-launcher.ts +26 -133
- package/src/budgets.ts +11 -1
- package/src/cdp-shot-a11y.ts +77 -0
- package/src/cdp-shot-clock.ts +14 -0
- package/src/cdp-shot-driver.ts +150 -0
- package/src/cdp-shot-element.ts +147 -0
- package/src/cdp-shot-errors.ts +62 -0
- package/src/cdp-shot-keys.ts +152 -0
- package/src/cdp-shot-page.ts +230 -0
- package/src/cdp-shot-watch.ts +241 -0
- package/src/cmd-affected-spec.ts +24 -0
- package/src/cmd-affected.ts +3 -19
- package/src/cmd-build-spec.ts +16 -0
- package/src/cmd-build.ts +46 -20
- package/src/cmd-ci-spec.ts +24 -0
- package/src/cmd-ci.ts +17 -21
- package/src/cmd-db-branch.ts +2 -2
- package/src/cmd-db-spec.ts +99 -0
- package/src/cmd-db.ts +15 -94
- package/src/cmd-deploy-helm.ts +136 -0
- package/src/cmd-deploy-spec.ts +40 -0
- package/src/cmd-deploy.ts +111 -43
- package/src/cmd-dev-spec.ts +25 -0
- package/src/cmd-dev.ts +55 -36
- package/src/cmd-docs-spec.ts +16 -0
- package/src/cmd-docs.ts +2 -11
- package/src/cmd-doctor-spec.ts +21 -0
- package/src/cmd-doctor.ts +4 -18
- package/src/cmd-env-spec.ts +18 -0
- package/src/cmd-env.ts +4 -13
- package/src/cmd-errors-spec.ts +23 -0
- package/src/cmd-errors.ts +3 -17
- package/src/cmd-fix-spec.ts +18 -0
- package/src/cmd-fix.ts +4 -13
- package/src/cmd-generate-spec.ts +31 -0
- package/src/cmd-generate.ts +46 -42
- package/src/cmd-i18n-spec.ts +16 -0
- package/src/cmd-i18n.ts +7 -14
- package/src/cmd-jobs-spec.ts +56 -0
- package/src/cmd-jobs.ts +3 -50
- package/src/cmd-manifest-spec.ts +15 -0
- package/src/cmd-manifest.ts +19 -20
- package/src/cmd-mcp-spec.ts +21 -0
- package/src/cmd-mcp.ts +2 -15
- package/src/cmd-new-spec.ts +33 -0
- package/src/cmd-new.ts +38 -33
- package/src/cmd-policy-spec.ts +13 -0
- package/src/cmd-policy.ts +2 -9
- package/src/cmd-pr-spec.ts +39 -0
- package/src/cmd-pr.ts +5 -65
- package/src/cmd-registries-spec.ts +32 -0
- package/src/cmd-registries.ts +4 -25
- package/src/cmd-routes-spec.ts +12 -0
- package/src/cmd-routes.ts +2 -7
- package/src/cmd-secrets-spec.ts +19 -0
- package/src/cmd-secrets.ts +22 -34
- package/src/cmd-shot-island.ts +12 -13
- package/src/cmd-shot-spec.ts +53 -0
- package/src/cmd-shot.ts +11 -60
- package/src/cmd-tasks-spec.ts +21 -0
- package/src/cmd-tasks.ts +2 -16
- package/src/cmd-test-spec.ts +54 -0
- package/src/cmd-test.ts +4 -49
- package/src/cmd-verify-spec.ts +28 -0
- package/src/cmd-verify.ts +3 -23
- package/src/db-seed.ts +4 -2
- package/src/dev-dashboard.ts +4 -4
- package/src/dev-lock.ts +18 -2
- package/src/dev-port.ts +21 -0
- package/src/dev-route-table.ts +3 -3
- package/src/dispatch.ts +12 -5
- package/src/document-styles.ts +1 -1
- package/src/drift.ts +4 -18
- package/src/error-catalog.ts +16 -9
- package/src/error-codes.ts +28 -30
- package/src/error-pages.ts +19 -7
- package/src/errors.ts +14 -0
- package/src/favicon.ts +2 -2
- package/src/fix-imports.ts +1 -1
- package/src/fix-scan.ts +2 -9
- package/src/flag-reads.ts +1 -2
- package/src/foreign-text.ts +36 -0
- package/src/framework-schema.ts +5 -6
- package/src/generate-feature.ts +42 -0
- package/src/generate-files.ts +7 -3
- package/src/generate-grants.ts +83 -0
- package/src/generate-kinds.ts +56 -4
- package/src/i18n-index.ts +59 -10
- package/src/icon-assets.ts +1 -1
- package/src/image-prepare.ts +14 -0
- package/src/import-scan.ts +63 -0
- package/src/index.ts +67 -348
- package/src/invocation-flags.ts +26 -0
- package/src/island-bundle.ts +6 -1
- package/src/island-capture.ts +6 -6
- package/src/island-harness-script.ts +6 -2
- package/src/island-realtime.ts +15 -8
- package/src/island-shot.ts +5 -0
- package/src/island-store.ts +131 -0
- package/src/island-verdict.ts +1 -1
- package/src/job-registration.ts +42 -0
- package/src/jobs-driver.ts +2 -2
- package/src/load-findings.ts +51 -0
- package/src/mcp-db-target.ts +1 -1
- package/src/mcp-errors.ts +28 -27
- package/src/mcp-host.ts +27 -12
- package/src/mcp-ui-diff.ts +27 -0
- package/src/mcp-ui-inspect.ts +4 -4
- package/src/mcp-ui-interact.ts +21 -15
- package/src/mcp-ui.ts +23 -17
- package/src/measure-database.ts +73 -0
- package/src/measure-paths.ts +74 -0
- package/src/measure-scope.ts +74 -0
- package/src/messages.ts +1 -3
- package/src/metrics-endpoint.ts +1 -1
- package/src/otlp-export.ts +1 -1
- package/src/output.ts +6 -0
- package/src/permission-grants.ts +86 -0
- package/src/prerender-out.ts +25 -0
- package/src/prerender.ts +121 -102
- package/src/pwa-artifacts.ts +3 -3
- package/src/reexport-manifest.ts +2 -1
- package/src/registry.ts +80 -56
- package/src/role-realtime.ts +36 -0
- package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
- package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
- package/src/role-start-types.ts +112 -0
- package/src/{dev-roles.ts → role-start.ts} +42 -115
- package/src/{dev-sync.ts → role-sync.ts} +22 -8
- package/src/root-env.ts +67 -0
- package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
- package/src/{dev-services.ts → runtime-bindings.ts} +39 -16
- package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
- package/src/runtime-jobs.ts +87 -0
- package/src/{dev-live-feed.ts → runtime-live-feed.ts} +18 -5
- package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
- package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
- package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
- package/src/runtime-realtime.ts +55 -0
- package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
- package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
- package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
- package/src/scaffold-fixture.ts +28 -6
- package/src/scaffold-typecheck.ts +6 -3
- package/src/schema-drift.ts +7 -1
- package/src/script-csp.ts +5 -2
- package/src/secrets-rotation.ts +59 -0
- package/src/serve-boot.ts +192 -0
- package/src/serve-drain.ts +24 -0
- package/src/serve-entry.ts +6 -0
- package/src/serve-env.ts +116 -0
- package/src/serve-types.ts +55 -0
- package/src/serve.ts +44 -347
- package/src/shot-server.ts +2 -2
- package/src/shot-settle.ts +10 -1
- package/src/shot-theme.ts +3 -3
- package/src/shot-verdict.ts +16 -7
- package/src/signal-shred.ts +27 -0
- package/src/solid-loader.ts +26 -2
- package/src/static-report.ts +8 -1
- package/src/sync-url.ts +21 -1
- package/src/templates/action.ts +30 -16
- package/src/templates/entity.ts +12 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/job.ts +10 -7
- package/src/templates/policy.ts +20 -2
- package/src/templates/resource-create.ts +127 -0
- package/src/templates/resource-form-island.ts +49 -12
- package/src/templates/resource.ts +10 -4
- package/src/templates/scaffold-app.ts +4 -1
- package/src/templates/scaffold-auth.ts +3 -1
- package/src/templates/scaffold-container-compose.ts +184 -0
- package/src/templates/scaffold-container.ts +24 -159
- package/src/templates/scaffold-dashboard-example.ts +2 -2
- package/src/templates/scaffold-db-package.ts +16 -3
- package/src/templates/scaffold-demo-org.ts +41 -0
- package/src/templates/scaffold-entries.ts +1 -1
- package/src/templates/scaffold-env.ts +6 -0
- package/src/templates/scaffold-helm-templates.ts +66 -7
- package/src/templates/scaffold-helm.ts +27 -5
- package/src/templates/scaffold-i18n.ts +15 -10
- package/src/templates/scaffold-repo.ts +13 -10
- package/src/templates/scaffold-roles.ts +38 -10
- package/src/templates/slice-foundation.ts +1 -1
- package/src/templates/wrap.ts +4 -1
- package/src/test-passes.ts +2 -1
- package/src/test-workers.ts +26 -0
- package/src/ts-scan.ts +3 -6
- package/src/tsconfig-references.ts +1 -2
- package/src/verify-checks.ts +41 -39
- package/src/verify-e2e.ts +10 -7
- package/src/verify-run.ts +11 -1
- package/src/verify-step.ts +3 -3
- package/src/verify-tests.ts +1 -26
- package/src/verify-typecheck.ts +28 -0
- package/src/web-binding.ts +2 -2
- package/src/workspace-graph.ts +10 -33
- package/src/cdp-browser.ts +0 -94
- package/src/cdp-connection.ts +0 -247
- package/src/cdp-e2e-page.ts +0 -180
- package/src/cdp-e2e-session.ts +0 -199
- package/src/cdp-errors.ts +0 -56
- package/src/cdp-launch.ts +0 -193
- package/src/cdp-offline-script.ts +0 -73
- package/src/cdp-pipe.ts +0 -77
- package/src/e2e-app.ts +0 -103
- package/src/e2e-browser-handle.ts +0 -55
- package/src/e2e-dom-fixture.ts +0 -117
- package/src/e2e-driver.ts +0 -117
- package/src/e2e-errors.ts +0 -117
- package/src/e2e-evaluate.ts +0 -156
- package/src/e2e-locator.ts +0 -86
- package/src/e2e-page.ts +0 -153
- package/src/e2e-preload.ts +0 -64
- package/src/e2e-probe.ts +0 -23
- package/src/e2e-selection.ts +0 -182
- package/src/e2e-spawn.ts +0 -169
- package/src/measurement-actor.ts +0 -26
- /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
- /package/src/{dev-render.ts → runtime-render.ts} +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// `x shot`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-shot.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const shotSpec: CommandSpec = {
|
|
7
|
+
name: 'shot',
|
|
8
|
+
summary: 'photograph one route, one island in a state it declares, or every island in the app',
|
|
9
|
+
usage:
|
|
10
|
+
'x shot <route> | --island <name> [--state <id>] | --all-islands [--port 0] [--out <dir>] [--settle 2000] [--json]',
|
|
11
|
+
requiresApp: true,
|
|
12
|
+
flags: [
|
|
13
|
+
{ name: 'port', type: 'string', summary: 'dev port (0 lets the kernel pick a free one)' },
|
|
14
|
+
{ name: 'out', type: 'string', summary: 'where shot.png and verdict.json are written' },
|
|
15
|
+
{ name: 'full', type: 'boolean', summary: 'whole page, not the fold', default: true },
|
|
16
|
+
{ name: 'settle', type: 'string', summary: 'ms to wait after load before capturing' },
|
|
17
|
+
{ name: 'timeout', type: 'string', summary: 'ms one navigation may take' },
|
|
18
|
+
{ name: 'browser', type: 'string', summary: 'Chrome or Chromium binary to launch' },
|
|
19
|
+
{
|
|
20
|
+
name: 'cdp-url',
|
|
21
|
+
type: 'string',
|
|
22
|
+
summary: 'attach to a browser somebody else is running (a provider session, a sidecar)',
|
|
23
|
+
},
|
|
24
|
+
{ name: 'allow-hosts', type: 'string', summary: 'extra hosts the page may request' },
|
|
25
|
+
{
|
|
26
|
+
name: 'theme',
|
|
27
|
+
type: 'string',
|
|
28
|
+
summary: "light or dark, stored as the visitor's choice; absent is the app's own default",
|
|
29
|
+
},
|
|
30
|
+
// A FLAG on `x shot` and never a second command: photographing a route and photographing a
|
|
31
|
+
// component are one job with two subjects, and a parallel command would be the second path
|
|
32
|
+
// axiom 1 refuses.
|
|
33
|
+
{
|
|
34
|
+
name: 'island',
|
|
35
|
+
type: 'string',
|
|
36
|
+
summary: 'photograph one island in every state it declares',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'state',
|
|
40
|
+
type: 'string',
|
|
41
|
+
summary: 'one declared state of that island, not all of them',
|
|
42
|
+
},
|
|
43
|
+
// Its own SPELLING and never `--island` with no value: the parser refuses a bare `--island`
|
|
44
|
+
// ("expects a value") and `--island=` is an empty name, so "every island" had no form a
|
|
45
|
+
// reader could type that could not be read as a mistyped one. A boolean cannot be confused
|
|
46
|
+
// with a name, and `x shot --all-islands` says what it does beside `x shot --island <name>`.
|
|
47
|
+
{
|
|
48
|
+
name: 'all-islands',
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
summary: 'every island in the app, in every state it declares, plus an index.md',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
package/src/cmd-shot.ts
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
import { mkdirSync } from 'node:fs';
|
|
9
9
|
import { join, resolve } from 'node:path';
|
|
10
10
|
import { IDLE_HYDRATE_TIMEOUT_MS } from '@ultimat3/render';
|
|
11
|
-
import type { ColorScheme, ScrapeDriver, ScrapePage, ScrapeSession } from '@ultimat3/scraping';
|
|
12
|
-
import { DEFAULT_PAGE_TIMEOUT_MS, systemScrapeClock } from '@ultimat3/scraping';
|
|
13
11
|
import { requireAppRoot } from './app-root';
|
|
14
12
|
import { appBrowser } from './browser-launcher';
|
|
13
|
+
import type { ShotColorScheme, ShotDriver, ShotPage, ShotSession } from './browser-launcher-port';
|
|
14
|
+
import { DEFAULT_PAGE_TIMEOUT_MS, systemShotClock } from './cdp-shot-clock';
|
|
15
15
|
import {
|
|
16
16
|
islandShot,
|
|
17
17
|
islandShotResult,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
refuseSweepWithRoute,
|
|
23
23
|
refuseSweepWithState,
|
|
24
24
|
} from './cmd-shot-island';
|
|
25
|
+
import { shotSpec } from './cmd-shot-spec';
|
|
25
26
|
import type { CliCommand, CommandContext } from './command';
|
|
26
27
|
import { BadFlagError, MissingPositionalError } from './errors';
|
|
27
28
|
import { intFlagOr, PORT_RANGE } from './flag-number';
|
|
@@ -181,7 +182,7 @@ const intFlag = (
|
|
|
181
182
|
export interface ShotRun {
|
|
182
183
|
readonly route: string;
|
|
183
184
|
readonly outDir: string;
|
|
184
|
-
readonly driver:
|
|
185
|
+
readonly driver: ShotDriver;
|
|
185
186
|
readonly boot: () => Promise<ShotServer>;
|
|
186
187
|
readonly settleMs: number;
|
|
187
188
|
readonly timeoutMs: number;
|
|
@@ -200,7 +201,7 @@ export interface ShotRun {
|
|
|
200
201
|
* neither: the box's own preference and the app's own default — what `x shot` has always done,
|
|
201
202
|
* and the point of `defaultMode` — and `ui.shot` names one explicitly for exactly that reason.
|
|
202
203
|
*/
|
|
203
|
-
readonly colorScheme?:
|
|
204
|
+
readonly colorScheme?: ShotColorScheme | undefined;
|
|
204
205
|
readonly now?: (() => Date) | undefined;
|
|
205
206
|
/**
|
|
206
207
|
* Something to do with the page AFTER the islands settled and BEFORE the picture — `ui.inspect`
|
|
@@ -209,7 +210,7 @@ export interface ShotRun {
|
|
|
209
210
|
* caller who never calls it gets the count from the first settle.
|
|
210
211
|
*/
|
|
211
212
|
readonly act?:
|
|
212
|
-
| ((page:
|
|
213
|
+
| ((page: ShotPage, settle: () => Promise<IslandCount | null>) => Promise<void>)
|
|
213
214
|
| undefined;
|
|
214
215
|
}
|
|
215
216
|
|
|
@@ -226,7 +227,7 @@ const quietly = async (stop: () => Promise<void>): Promise<void> => {
|
|
|
226
227
|
*/
|
|
227
228
|
export async function runShot(options: ShotRun): Promise<ShotArtifacts> {
|
|
228
229
|
const server = await options.boot();
|
|
229
|
-
let session:
|
|
230
|
+
let session: ShotSession | undefined;
|
|
230
231
|
try {
|
|
231
232
|
const requestedUrl = new URL(options.route, server.url).toString();
|
|
232
233
|
session = await options.driver.open({
|
|
@@ -235,7 +236,7 @@ export async function runShot(options: ShotRun): Promise<ShotArtifacts> {
|
|
|
235
236
|
// inside your network is the widest SSRF surface an app can own, and a screenshot command is
|
|
236
237
|
// not the place to open it by default. Every refusal lands in the verdict's `refused` count.
|
|
237
238
|
rules: { allowHosts: allowHostsFrom(server.url, options.extraHosts) },
|
|
238
|
-
clock:
|
|
239
|
+
clock: systemShotClock,
|
|
239
240
|
timeoutMs: options.timeoutMs,
|
|
240
241
|
});
|
|
241
242
|
const page = session.page;
|
|
@@ -312,54 +313,7 @@ export const shotResult = (artifacts: ShotArtifacts): CommandResult => ({
|
|
|
312
313
|
});
|
|
313
314
|
|
|
314
315
|
export const shotCommand: CliCommand = {
|
|
315
|
-
spec:
|
|
316
|
-
name: 'shot',
|
|
317
|
-
summary: 'photograph one route, one island in a state it declares, or every island in the app',
|
|
318
|
-
usage:
|
|
319
|
-
'x shot <route> | --island <name> [--state <id>] | --all-islands [--port 0] [--out <dir>] [--settle 2000] [--json]',
|
|
320
|
-
requiresApp: true,
|
|
321
|
-
flags: [
|
|
322
|
-
{ name: 'port', type: 'string', summary: 'dev port (0 lets the kernel pick a free one)' },
|
|
323
|
-
{ name: 'out', type: 'string', summary: 'where shot.png and verdict.json are written' },
|
|
324
|
-
{ name: 'full', type: 'boolean', summary: 'whole page, not the fold', default: true },
|
|
325
|
-
{ name: 'settle', type: 'string', summary: 'ms to wait after load before capturing' },
|
|
326
|
-
{ name: 'timeout', type: 'string', summary: 'ms one navigation may take' },
|
|
327
|
-
{ name: 'browser', type: 'string', summary: 'browser executable puppeteer-core launches' },
|
|
328
|
-
{
|
|
329
|
-
name: 'cdp-url',
|
|
330
|
-
type: 'string',
|
|
331
|
-
summary: 'attach to a browser somebody else is running (a provider session, a sidecar)',
|
|
332
|
-
},
|
|
333
|
-
{ name: 'allow-hosts', type: 'string', summary: 'extra hosts the page may request' },
|
|
334
|
-
{
|
|
335
|
-
name: 'theme',
|
|
336
|
-
type: 'string',
|
|
337
|
-
summary: "light or dark, stored as the visitor's choice; absent is the app's own default",
|
|
338
|
-
},
|
|
339
|
-
// A FLAG on `x shot` and never a second command: photographing a route and photographing a
|
|
340
|
-
// component are one job with two subjects, and a parallel command would be the second path
|
|
341
|
-
// axiom 1 refuses.
|
|
342
|
-
{
|
|
343
|
-
name: 'island',
|
|
344
|
-
type: 'string',
|
|
345
|
-
summary: 'photograph one island in every state it declares',
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: 'state',
|
|
349
|
-
type: 'string',
|
|
350
|
-
summary: 'one declared state of that island, not all of them',
|
|
351
|
-
},
|
|
352
|
-
// Its own SPELLING and never `--island` with no value: the parser refuses a bare `--island`
|
|
353
|
-
// ("expects a value") and `--island=` is an empty name, so "every island" had no form a
|
|
354
|
-
// reader could type that could not be read as a mistyped one. A boolean cannot be confused
|
|
355
|
-
// with a name, and `x shot --all-islands` says what it does beside `x shot --island <name>`.
|
|
356
|
-
{
|
|
357
|
-
name: 'all-islands',
|
|
358
|
-
type: 'boolean',
|
|
359
|
-
summary: 'every island in the app, in every state it declares, plus an index.md',
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
},
|
|
316
|
+
spec: shotSpec,
|
|
363
317
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
364
318
|
const root = requireAppRoot('shot', ctx.cwd).dir;
|
|
365
319
|
// Every value read before anything boots: a typo must not cost a browser and a dev server to
|
|
@@ -396,8 +350,7 @@ export const shotCommand: CliCommand = {
|
|
|
396
350
|
// Which browser this run gets — start one here, or attach to one somebody else is running.
|
|
397
351
|
// Decided by `shot-browser.ts` over plain inputs, and decided HERE, before a dev server or a
|
|
398
352
|
// provider session exists to pay for a typo. It also PROBES for an installed Chrome and refuses
|
|
399
|
-
// when there is none
|
|
400
|
-
// a library throw one embedded Postgres later.
|
|
353
|
+
// when there is none, so a missing browser costs no embedded Postgres boot.
|
|
401
354
|
const { cdpUrl, executablePath } = shotBrowserChoice({
|
|
402
355
|
cdpFlag: flagString(ctx.args, 'cdp-url'),
|
|
403
356
|
browserFlag: flagString(ctx.args, 'browser'),
|
|
@@ -423,10 +376,8 @@ export const shotCommand: CliCommand = {
|
|
|
423
376
|
await islandShot({ ...shared, island, ...(state === undefined ? {} : { state }) }),
|
|
424
377
|
);
|
|
425
378
|
}
|
|
426
|
-
//
|
|
427
|
-
// an embedded Postgres to be told to run `bun add -d puppeteer-core`.
|
|
379
|
+
// Built before the boot, for the same reason.
|
|
428
380
|
const driver = await appBrowser({
|
|
429
|
-
root,
|
|
430
381
|
...(executablePath === undefined ? {} : { executablePath }),
|
|
431
382
|
...(cdpUrl === undefined ? {} : { cdpUrl }),
|
|
432
383
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// `x tasks`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-tasks.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const tasksSpec: CommandSpec = {
|
|
7
|
+
name: 'tasks',
|
|
8
|
+
summary: 'cron tasks, their timezone and their next run',
|
|
9
|
+
usage: 'x tasks [list|show <name>] [--count n] [--json]',
|
|
10
|
+
requiresApp: true,
|
|
11
|
+
subcommands: ['list', 'show'],
|
|
12
|
+
defaultSubcommand: 'list',
|
|
13
|
+
flags: [
|
|
14
|
+
{
|
|
15
|
+
name: 'count',
|
|
16
|
+
type: 'string',
|
|
17
|
+
summary: 'show: how many upcoming occurrences to list',
|
|
18
|
+
subcommands: ['show'],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
package/src/cmd-tasks.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { TaskHandle } from '@ultimat3/jobs';
|
|
|
8
8
|
import type { CronPhrases } from '@ultimat3/time';
|
|
9
9
|
import { loadApp } from './app-load';
|
|
10
10
|
import { requireAppRoot } from './app-root';
|
|
11
|
+
import { tasksSpec } from './cmd-tasks-spec';
|
|
11
12
|
import type { CliCommand, CommandContext } from './command';
|
|
12
13
|
import { BadFlagError, DeclarationUnknownError } from './errors';
|
|
13
14
|
import { msg } from './messages';
|
|
@@ -130,22 +131,7 @@ function runShow(ctx: CommandContext, nowMs: number, findings: readonly Finding[
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
export const tasksCommand: CliCommand = {
|
|
133
|
-
spec:
|
|
134
|
-
name: 'tasks',
|
|
135
|
-
summary: 'cron tasks, their timezone and their next run',
|
|
136
|
-
usage: 'x tasks [list|show <name>] [--count n] [--json]',
|
|
137
|
-
requiresApp: true,
|
|
138
|
-
subcommands: ['list', 'show'],
|
|
139
|
-
defaultSubcommand: 'list',
|
|
140
|
-
flags: [
|
|
141
|
-
{
|
|
142
|
-
name: 'count',
|
|
143
|
-
type: 'string',
|
|
144
|
-
summary: 'show: how many upcoming occurrences to list',
|
|
145
|
-
subcommands: ['show'],
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
},
|
|
134
|
+
spec: tasksSpec,
|
|
149
135
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
150
136
|
const root = requireAppRoot('tasks', ctx.cwd).dir;
|
|
151
137
|
const { findings } = await loadApp(root);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// `x test`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-test.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import { TEST_TYPES } from '@ultimat3/testing/test-types';
|
|
5
|
+
import { DEFAULT_BASE } from './affected';
|
|
6
|
+
import type { CommandSpec } from './parse';
|
|
7
|
+
import { SERIAL_TYPES, WORKER_CEILING, WORKER_FLOOR, WORKER_OVERSUBSCRIBE } from './test-workers';
|
|
8
|
+
|
|
9
|
+
export const testSpec: CommandSpec = {
|
|
10
|
+
name: 'test',
|
|
11
|
+
summary:
|
|
12
|
+
'run one test type — or the whole suite — across N workers, one isolated database per worker',
|
|
13
|
+
usage: `x test [${TEST_TYPES.join('|')}] [--filter text] [--sample N] [--affected [--base ref] [--dirty]] [--workers N] [--worker I] [--json] [-- <bun test flags>]`,
|
|
14
|
+
positionalChoices: TEST_TYPES,
|
|
15
|
+
// The one command that hands a tail to another tool — `bun test` — and the reason
|
|
16
|
+
// `CommandSpec.passthrough` exists: `x test unit -- --coverage --bail` parsed both flags and
|
|
17
|
+
// dropped both, so a run that measured no coverage reported exactly what a coverage run does.
|
|
18
|
+
passthrough: true,
|
|
19
|
+
flags: [
|
|
20
|
+
{
|
|
21
|
+
name: 'workers',
|
|
22
|
+
type: 'string',
|
|
23
|
+
summary: `bun worker count (default: ${WORKER_OVERSUBSCRIBE}x CPUs, min ${WORKER_FLOOR}, max ${WORKER_CEILING}); clamped to the file count, and to 1 for ${SERIAL_TYPES.join(' and ')}`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'worker',
|
|
27
|
+
type: 'string',
|
|
28
|
+
summary:
|
|
29
|
+
'run only shard I of an N-way split of the selection, serially — one CI job\u2019s share',
|
|
30
|
+
},
|
|
31
|
+
{ name: 'filter', type: 'string', summary: 'only files whose path contains this substring' },
|
|
32
|
+
{
|
|
33
|
+
name: 'sample',
|
|
34
|
+
type: 'string',
|
|
35
|
+
summary:
|
|
36
|
+
'run at most N files of the selected type — a fast signal for the eval loop, never a gate',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'affected',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
summary: 'only the workspaces a diff touches, and everything that depends on one of them',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'base',
|
|
45
|
+
type: 'string',
|
|
46
|
+
summary: `--affected: git ref to diff against, merge-base style (default: ${DEFAULT_BASE})`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'dirty',
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
summary: '--affected: also count uncommitted work, whichever agent in this checkout made it',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
package/src/cmd-test.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AffectedScope } from './affected';
|
|
9
9
|
import { affectedScope, affectedScopeJson, DEFAULT_BASE, inScope } from './affected';
|
|
10
|
+
import { testSpec } from './cmd-test-spec';
|
|
10
11
|
import type { CliCommand, CommandContext } from './command';
|
|
11
12
|
import { ok } from './command';
|
|
12
13
|
import { BadFlagError, NoTestFilesError } from './errors';
|
|
@@ -18,9 +19,9 @@ import { flagBool, flagString } from './parse';
|
|
|
18
19
|
import { quoteArg } from './shell-quote';
|
|
19
20
|
import { discoverTests, missingSelection, readSample, readType, sampleFiles } from './test-select';
|
|
20
21
|
import { runShards } from './test-shards';
|
|
21
|
-
import { defaultWorkers,
|
|
22
|
+
import { defaultWorkers, SERIAL_TYPES, WORKER_CEILING } from './test-workers';
|
|
22
23
|
import type { TestType } from './verify-tests';
|
|
23
|
-
import {
|
|
24
|
+
import { TEST_TYPES } from './verify-tests';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* `--workers` and `--shard`. `Number.parseInt` alone accepted `4abc` and `4.9` as four, while
|
|
@@ -100,53 +101,7 @@ const withScope = (result: CommandResult, scope: AffectedScope): CommandResult =
|
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
export const testCommand: CliCommand = {
|
|
103
|
-
spec:
|
|
104
|
-
name: 'test',
|
|
105
|
-
summary:
|
|
106
|
-
'run one test type — or the whole suite — across N workers, one isolated database per worker',
|
|
107
|
-
usage: `x test [${TEST_TYPES.join('|')}] [--filter text] [--sample N] [--affected [--base ref] [--dirty]] [--workers N] [--worker I] [--json] [-- <bun test flags>]`,
|
|
108
|
-
positionalChoices: TEST_TYPES,
|
|
109
|
-
// The one command that hands a tail to another tool — `bun test` — and the reason
|
|
110
|
-
// `CommandSpec.passthrough` exists: `x test unit -- --coverage --bail` parsed both flags and
|
|
111
|
-
// dropped both, so a run that measured no coverage reported exactly what a coverage run does.
|
|
112
|
-
passthrough: true,
|
|
113
|
-
flags: [
|
|
114
|
-
{
|
|
115
|
-
name: 'workers',
|
|
116
|
-
type: 'string',
|
|
117
|
-
summary: `bun worker count (default: ${WORKER_OVERSUBSCRIBE}x CPUs, min ${WORKER_FLOOR}, max ${WORKER_CEILING}); clamped to the file count, and to 1 for ${SERIAL_TYPES.join(' and ')}`,
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: 'worker',
|
|
121
|
-
type: 'string',
|
|
122
|
-
summary:
|
|
123
|
-
'run only shard I of an N-way split of the selection, serially — one CI job\u2019s share',
|
|
124
|
-
},
|
|
125
|
-
{ name: 'filter', type: 'string', summary: 'only files whose path contains this substring' },
|
|
126
|
-
{
|
|
127
|
-
name: 'sample',
|
|
128
|
-
type: 'string',
|
|
129
|
-
summary:
|
|
130
|
-
'run at most N files of the selected type — a fast signal for the eval loop, never a gate',
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: 'affected',
|
|
134
|
-
type: 'boolean',
|
|
135
|
-
summary: 'only the workspaces a diff touches, and everything that depends on one of them',
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: 'base',
|
|
139
|
-
type: 'string',
|
|
140
|
-
summary: `--affected: git ref to diff against, merge-base style (default: ${DEFAULT_BASE})`,
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'dirty',
|
|
144
|
-
type: 'boolean',
|
|
145
|
-
summary:
|
|
146
|
-
'--affected: also count uncommitted work, whichever agent in this checkout made it',
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
},
|
|
104
|
+
spec: testSpec,
|
|
150
105
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
151
106
|
const type = readOnlyType(ctx.args.positionals);
|
|
152
107
|
const filter = flagString(ctx.args, 'filter');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// `x verify`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-verify.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
import { WORKER_CEILING, WORKER_FLOOR, WORKER_OVERSUBSCRIBE } from './test-workers';
|
|
6
|
+
|
|
7
|
+
export const verifySpec: CommandSpec = {
|
|
8
|
+
name: 'verify',
|
|
9
|
+
summary: 'the gate: typecheck, lint, boundaries, all tests, drift, contract, budgets',
|
|
10
|
+
usage: 'x verify [--only <step>] [--workers N] [--json]',
|
|
11
|
+
requiresApp: true,
|
|
12
|
+
// Two flags, and only one of them narrows. `--workers` changes how wide the test steps
|
|
13
|
+
// spread, never which steps run. `--only` runs one step and says so in both renderers —
|
|
14
|
+
// never silently, which is the whole of what makes it safe to have.
|
|
15
|
+
flags: [
|
|
16
|
+
{
|
|
17
|
+
name: 'workers',
|
|
18
|
+
type: 'string',
|
|
19
|
+
summary: `test processes per parallel step (default: ${WORKER_OVERSUBSCRIBE}x CPUs, min ${WORKER_FLOOR}, max ${WORKER_CEILING})`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'only',
|
|
23
|
+
type: 'string',
|
|
24
|
+
summary:
|
|
25
|
+
'run ONE step by name — an iteration loop, NOT A GATE RUN; the gate is this command with no flag',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
package/src/cmd-verify.ts
CHANGED
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import { nearestName } from '@ultimat3/core';
|
|
11
11
|
import { requireAppRoot } from './app-root';
|
|
12
|
+
import { verifySpec } from './cmd-verify-spec';
|
|
12
13
|
import type { CliCommand, CommandContext } from './command';
|
|
13
14
|
import { BadFlagError } from './errors';
|
|
14
15
|
import { readIntFlag } from './flag-number';
|
|
15
16
|
import type { CommandResult } from './output';
|
|
16
17
|
import type { ParsedArgs } from './parse';
|
|
17
18
|
import { flagString } from './parse';
|
|
18
|
-
import { WORKER_CEILING, WORKER_FLOOR
|
|
19
|
+
import { WORKER_CEILING, WORKER_FLOOR } from './test-workers';
|
|
19
20
|
import { VERIFY_STEPS } from './verify-checks';
|
|
20
21
|
import { runVerify } from './verify-run';
|
|
21
22
|
import type { VerifyStepName } from './verify-step';
|
|
@@ -28,28 +29,7 @@ export { VERIFY_STEPS } from './verify-checks';
|
|
|
28
29
|
export { runVerify } from './verify-run';
|
|
29
30
|
|
|
30
31
|
export const verifyCommand: CliCommand = {
|
|
31
|
-
spec:
|
|
32
|
-
name: 'verify',
|
|
33
|
-
summary: 'the gate: typecheck, lint, boundaries, all tests, drift, contract, budgets',
|
|
34
|
-
usage: 'x verify [--only <step>] [--workers N] [--json]',
|
|
35
|
-
requiresApp: true,
|
|
36
|
-
// Two flags, and only one of them narrows. `--workers` changes how wide the test steps
|
|
37
|
-
// spread, never which steps run. `--only` runs one step and says so in both renderers —
|
|
38
|
-
// never silently, which is the whole of what makes it safe to have.
|
|
39
|
-
flags: [
|
|
40
|
-
{
|
|
41
|
-
name: 'workers',
|
|
42
|
-
type: 'string',
|
|
43
|
-
summary: `test processes per parallel step (default: ${WORKER_OVERSUBSCRIBE}x CPUs, min ${WORKER_FLOOR}, max ${WORKER_CEILING})`,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'only',
|
|
47
|
-
type: 'string',
|
|
48
|
-
summary:
|
|
49
|
-
'run ONE step by name — an iteration loop, NOT A GATE RUN; the gate is this command with no flag',
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
32
|
+
spec: verifySpec,
|
|
53
33
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
54
34
|
const root = requireAppRoot('verify', ctx.cwd).dir;
|
|
55
35
|
// Both readers before the run: an unrunnable flag must be refused in milliseconds, not after
|
package/src/db-seed.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { BadFlagError } from './errors';
|
|
|
16
16
|
import type { Finding, JsonValue } from './output';
|
|
17
17
|
import { findingFrom } from './output';
|
|
18
18
|
import { hasPathSegment } from './path-segments';
|
|
19
|
+
import { isTest } from './source-files';
|
|
19
20
|
import { renderTable } from './table';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -102,10 +103,11 @@ export async function discoverSeeds(root: string): Promise<SeedDiscovery> {
|
|
|
102
103
|
const seen = new Set<string>();
|
|
103
104
|
for (const pattern of SEED_GLOBS) {
|
|
104
105
|
for await (const absolute of new Bun.Glob(pattern).scan({ cwd: root, absolute: true })) {
|
|
105
|
-
|
|
106
|
+
const file = relative(root, absolute).split(sep).join('/');
|
|
107
|
+
// Root-relative, as `loadApp` tests it: an absolute path under `my.test.app/` is no test.
|
|
108
|
+
if (hasPathSegment(absolute, 'node_modules') || isTest(file)) continue;
|
|
106
109
|
if (seen.has(absolute)) continue;
|
|
107
110
|
seen.add(absolute);
|
|
108
|
-
const file = relative(root, absolute).split(sep).join('/');
|
|
109
111
|
let module: Record<string, unknown>;
|
|
110
112
|
try {
|
|
111
113
|
module = (await import(absolute)) as Record<string, unknown>;
|
package/src/dev-dashboard.ts
CHANGED
|
@@ -22,14 +22,14 @@ import { json as jsonResponse } from '@ultimat3/http';
|
|
|
22
22
|
import type { MemoryMailDriver } from '@ultimat3/mail';
|
|
23
23
|
import { isMemoryDriver } from '@ultimat3/mail';
|
|
24
24
|
import type { Manifest } from '@ultimat3/manifest';
|
|
25
|
-
import { checkAppBoundaries } from './app-boundaries';
|
|
26
25
|
import { appManifest, readAppManifest } from './app-manifest';
|
|
26
|
+
import { appBoundaryFindings } from './boundary-findings';
|
|
27
27
|
import type { StatementLedger } from './dev-n-plus-one';
|
|
28
28
|
import { devPolicyMatrix } from './dev-policy';
|
|
29
|
-
import type { RunningServices } from './dev-runtime';
|
|
30
|
-
import type { DevServices } from './dev-services';
|
|
31
29
|
import type { TraceRecorder } from './dev-traces';
|
|
32
30
|
import type { Finding } from './output';
|
|
31
|
+
import type { DevServices } from './runtime-bindings';
|
|
32
|
+
import type { RunningServices } from './runtime-services';
|
|
33
33
|
import { loopFacts } from './statement-loop';
|
|
34
34
|
|
|
35
35
|
export interface DevStatus {
|
|
@@ -195,7 +195,7 @@ const boundariesPanel = (input: DevDashboardInput): DevPanel<BoundariesPanelData
|
|
|
195
195
|
titleKey: 'dev.panel.boundaries.title',
|
|
196
196
|
questionKey: 'dev.panel.boundaries.question',
|
|
197
197
|
async data(): Promise<BoundariesPanelData> {
|
|
198
|
-
return { findings: await
|
|
198
|
+
return { findings: await appBoundaryFindings(input.root) };
|
|
199
199
|
},
|
|
200
200
|
});
|
|
201
201
|
|
package/src/dev-lock.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// The lock file is what makes the second one nameable at all: nothing else in the process can tell
|
|
15
15
|
// "another dev server owns this directory" from "the database is broken".
|
|
16
16
|
|
|
17
|
-
import { closeSync, mkdirSync, openSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { stringField, UltimateError } from '@ultimat3/core';
|
|
20
20
|
import { exec, type Runner } from './exec';
|
|
@@ -75,11 +75,27 @@ export const isProcessAlive = (pid: number): boolean => {
|
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The lock a LIVE `x dev` holds on this state directory, or `undefined` — for a command that must
|
|
80
|
+
* not run beside one (`x db reset` deletes the data directory that process has open). A missing,
|
|
81
|
+
* unreadable or stale lock is no holder: only `preflight` may clear one.
|
|
82
|
+
*/
|
|
83
|
+
export const liveDevLock = (stateDir: string): DevLock | undefined => {
|
|
84
|
+
let raw: string;
|
|
85
|
+
try {
|
|
86
|
+
raw = readFileSync(lockPath(stateDir), 'utf-8');
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const lock = parseLock(raw);
|
|
91
|
+
return lock !== null && isProcessAlive(lock.pid) ? lock : undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
78
94
|
/**
|
|
79
95
|
* Refused before boot, so the failure names the process holding the directory.
|
|
80
96
|
*
|
|
81
97
|
* The lock is on the CHECKOUT, not on the database, and the cause says so. `x dev` is one process
|
|
82
|
-
* running every role (`
|
|
98
|
+
* running every role (`role-start.ts`), so a second one is unsupported whatever the services are.
|
|
83
99
|
* The embedded-Postgres sentence is appended only when the database actually IS embedded — with an
|
|
84
100
|
* external `DATABASE_URL` it would name a mechanism that is not in play, which is the same defect
|
|
85
101
|
* as the message this whole module replaced.
|
package/src/dev-port.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Which port `x dev` binds: `--port`, else `PORT` from the environment, else 3000. The flag used to
|
|
2
|
+
// declare `default: '3000'`, and a default is indistinguishable from a caller's value — so the
|
|
3
|
+
// scaffold's own `.env.development` `PORT=` was read by nothing and every app booted on 3000.
|
|
4
|
+
|
|
5
|
+
import { PORT_RANGE, readIntFlag } from './flag-number';
|
|
6
|
+
import type { ParsedArgs } from './parse';
|
|
7
|
+
import { portFromEnv } from './serve';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_DEV_PORT = 3000;
|
|
10
|
+
|
|
11
|
+
/** The `metricsPortFor` shape: an explicit value, then the env, then the constant. */
|
|
12
|
+
export const devPortFor = (
|
|
13
|
+
args: ParsedArgs,
|
|
14
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
15
|
+
): number =>
|
|
16
|
+
readIntFlag(args, {
|
|
17
|
+
name: 'port',
|
|
18
|
+
command: 'dev',
|
|
19
|
+
...PORT_RANGE,
|
|
20
|
+
example: `x dev --port ${DEFAULT_DEV_PORT}`,
|
|
21
|
+
}) ?? portFromEnv(env);
|
package/src/dev-route-table.ts
CHANGED
|
@@ -7,11 +7,8 @@ import { describeRoutes } from '@ultimat3/render';
|
|
|
7
7
|
import type { Storage } from '@ultimat3/storage';
|
|
8
8
|
import { apiRoutes } from './api-routes';
|
|
9
9
|
import { mountAppMcp } from './app-mcp';
|
|
10
|
-
import { assetRoutes } from './dev-assets';
|
|
11
10
|
import type { DevDashboardInput } from './dev-dashboard';
|
|
12
11
|
import { devDashboardRoutes } from './dev-dashboard';
|
|
13
|
-
import { appRoutes } from './dev-render';
|
|
14
|
-
import { storageRoutes } from './dev-storage';
|
|
15
12
|
import { errorPageStyleSources } from './error-page-csp';
|
|
16
13
|
import type { IslandBundle } from './island-bundle';
|
|
17
14
|
import { islandHarnessRoutes } from './island-harness-route';
|
|
@@ -19,6 +16,9 @@ import { islandRoutes } from './island-routes';
|
|
|
19
16
|
import { loadIslandStates } from './island-states-load';
|
|
20
17
|
import { pageSync } from './page-sync';
|
|
21
18
|
import { loadPwaArtifacts } from './pwa-artifacts';
|
|
19
|
+
import { assetRoutes } from './runtime-assets';
|
|
20
|
+
import { appRoutes } from './runtime-render';
|
|
21
|
+
import { storageRoutes } from './runtime-storage';
|
|
22
22
|
import { styleBundle } from './style-bundle';
|
|
23
23
|
import { styleRoutes } from './style-routes';
|
|
24
24
|
import { serviceWorkerArtifacts } from './sw-artifacts';
|
package/src/dispatch.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { exitCodeFor, findingFrom, render } from './output';
|
|
|
16
16
|
import type { ParsedArgs } from './parse';
|
|
17
17
|
import { parseArgs, wantsJson } from './parse';
|
|
18
18
|
import { commandFor, SPECS } from './registry';
|
|
19
|
+
import { rootEnvAdditions } from './root-env';
|
|
19
20
|
|
|
20
21
|
export interface DispatchOptions {
|
|
21
22
|
readonly argv: readonly string[];
|
|
@@ -120,14 +121,20 @@ export async function dispatch(options: DispatchOptions): Promise<number> {
|
|
|
120
121
|
? { ...args, command: 'help', positionals: [args.command] }
|
|
121
122
|
: args;
|
|
122
123
|
|
|
124
|
+
const cwd = resolveCwd(
|
|
125
|
+
options.cwd,
|
|
126
|
+
typeof args.flags.get('cwd') === 'string' ? String(args.flags.get('cwd')) : undefined,
|
|
127
|
+
);
|
|
128
|
+
// The ROOT's `.env*`, not the cwd's: Bun loaded the cwd's at startup, and a command run from
|
|
129
|
+
// `apps/web` finds its root by walking up. Written into the process env too when that IS the env
|
|
130
|
+
// passed in, because a package reading `Bun.env` directly must see what the command sees.
|
|
131
|
+
const added = await rootEnvAdditions(cwd, options.env);
|
|
132
|
+
if (options.env === Bun.env) Object.assign(Bun.env, added);
|
|
123
133
|
const ctx: CommandContext = {
|
|
124
134
|
args: helpArgs,
|
|
125
|
-
cwd
|
|
126
|
-
options.cwd,
|
|
127
|
-
typeof args.flags.get('cwd') === 'string' ? String(args.flags.get('cwd')) : undefined,
|
|
128
|
-
),
|
|
135
|
+
cwd,
|
|
129
136
|
runner: options.runner ?? exec,
|
|
130
|
-
env: options.env,
|
|
137
|
+
env: options.env === Bun.env ? options.env : { ...added, ...options.env },
|
|
131
138
|
bunVersion: options.bunVersion,
|
|
132
139
|
...(options.invocation === undefined ? {} : { invocation: options.invocation }),
|
|
133
140
|
};
|
package/src/document-styles.ts
CHANGED
|
@@ -25,7 +25,7 @@ export const definesRootCustomProperties = (css: string): boolean => ROOT_CUSTOM
|
|
|
25
25
|
|
|
26
26
|
export interface SurfaceDocument {
|
|
27
27
|
readonly surface: Surface;
|
|
28
|
-
/** Exactly the CSS `
|
|
28
|
+
/** Exactly the CSS `runtime-render.ts` would inline into a document on this surface. */
|
|
29
29
|
readonly css: string;
|
|
30
30
|
}
|
|
31
31
|
|