@ultimat3/cli 20.2.1 → 22.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 +84 -1582
- package/README.md +40 -2
- package/package.json +31 -31
- package/src/api-registration.ts +124 -0
- package/src/app-artifacts.ts +39 -0
- package/src/app-boundaries.ts +5 -10
- package/src/app-env.ts +2 -2
- 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 +56 -13
- package/src/build-errors.ts +54 -0
- 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 +117 -42
- package/src/cmd-dev-spec.ts +25 -0
- package/src/cmd-dev.ts +78 -122
- 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 +119 -0
- 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 +33 -28
- package/src/error-pages.ts +19 -7
- package/src/error-unthrown.ts +130 -0
- package/src/errors.ts +22 -29
- 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 -334
- package/src/invocation-flags.ts +26 -0
- package/src/island-bundle.ts +38 -11
- package/src/island-capture.ts +6 -6
- package/src/island-harness-script.ts +6 -2
- package/src/island-realtime.ts +98 -0
- package/src/island-shot.ts +5 -0
- package/src/island-store.ts +131 -0
- package/src/island-verdict.ts +2 -2
- package/src/job-registration.ts +42 -0
- package/src/jobs-driver.ts +2 -2
- package/src/live-routes.ts +82 -42
- package/src/load-findings.ts +51 -0
- package/src/mcp-db-target.ts +1 -1
- package/src/mcp-errors.ts +30 -26
- 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 +19 -15
- 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/page-sync.ts +54 -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/realtime-browser-probe-fixture.ts +2 -2
- 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} +23 -11
- 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} +43 -17
- 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} +20 -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-render.ts → runtime-render.ts} +119 -20
- 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 -338
- 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/sw-artifacts.ts +13 -3
- package/src/sync-url.ts +31 -0
- 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 +76 -30
- package/src/templates/resource.ts +10 -4
- package/src/templates/route.ts +3 -0
- 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 +25 -145
- 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 +41 -0
- package/src/verify-run.ts +115 -50
- package/src/verify-step.ts +3 -3
- package/src/verify-tests.ts +22 -30
- package/src/verify-typecheck.ts +28 -0
- package/src/web-binding.ts +2 -2
- package/src/worker-bundle.ts +192 -0
- package/src/workspace-graph.ts +10 -33
- package/src/cdp-browser.ts +0 -100
- package/src/cdp-connection.ts +0 -211
- package/src/cdp-e2e-page.ts +0 -209
- package/src/cdp-errors.ts +0 -56
- package/src/cdp-launch.ts +0 -139
- package/src/e2e-dom-fixture.ts +0 -117
- package/src/e2e-driver.ts +0 -97
- package/src/e2e-errors.ts +0 -103
- package/src/e2e-evaluate.ts +0 -156
- package/src/e2e-locator.ts +0 -86
- package/src/e2e-page.ts +0 -150
- package/src/e2e-selection.ts +0 -182
- package/src/measurement-actor.ts +0 -26
- /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
package/src/prerender.ts
CHANGED
|
@@ -12,13 +12,16 @@ import { loadApp } from './app-load';
|
|
|
12
12
|
import { appManifest } from './app-manifest';
|
|
13
13
|
import type { RouteStats } from './budgets';
|
|
14
14
|
import { measureDocumentJs, writeBuildStats } from './budgets';
|
|
15
|
-
import { routeDocument } from './dev-render';
|
|
16
15
|
import { errorPageDocument, STATIC_ERROR_PAGE } from './error-pages';
|
|
17
16
|
import { FAVICON_PATH, faviconBytes } from './favicon';
|
|
18
17
|
import type { IslandBundle } from './island-bundle';
|
|
19
18
|
import { buildIslands, writeIslands } from './island-bundle';
|
|
20
|
-
import {
|
|
19
|
+
import { measureDatabase } from './measure-database';
|
|
20
|
+
import { measurePaths } from './measure-paths';
|
|
21
|
+
import { measureScope, withAppUrl } from './measure-scope';
|
|
22
|
+
import { clearPrerenderOut } from './prerender-out';
|
|
21
23
|
import { loadPwaArtifacts, WEB_MANIFEST_PATH, writePwaIcons } from './pwa-artifacts';
|
|
24
|
+
import { routeDocument } from './runtime-render';
|
|
22
25
|
import type { SkippedRoute, UnmeasuredRoute } from './static-report';
|
|
23
26
|
import { skippedRoute, skipReasonFor, writeStaticReport } from './static-report';
|
|
24
27
|
import { styleBundle, writeStyles } from './style-bundle';
|
|
@@ -142,6 +145,8 @@ const declaresBudget = (entry: RouteEntry): boolean => {
|
|
|
142
145
|
export async function prerenderSite(options: PrerenderOptions): Promise<PrerenderReport> {
|
|
143
146
|
// The same load `x dev` and `x manifest` perform: importing the app's modules IS what fills the
|
|
144
147
|
// route registry, so there is no route table to prerender before this runs.
|
|
148
|
+
// Emptied FIRST: the export only ever gained files, so a deleted route's HTML kept shipping.
|
|
149
|
+
await clearPrerenderOut(options.out, options.root);
|
|
145
150
|
await loadApp(options.root);
|
|
146
151
|
const buildId = (await appManifest(options.root)).manifest.buildId;
|
|
147
152
|
const origin = options.origin ?? DEFAULT_ORIGIN;
|
|
@@ -218,7 +223,7 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
|
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
// Every render below goes through `routeDocument`, which is the function a REQUEST reaches — and
|
|
221
|
-
// a request arrives inside `runWithContext`, installed by the HTTP pipeline (`
|
|
226
|
+
// a request arrives inside `runWithContext`, installed by the HTTP pipeline (`runtime-render.ts`).
|
|
222
227
|
// Called bare, any route whose component, `load` or `meta` reads `useContext()` threw
|
|
223
228
|
// `X_NO_CONTEXT`: measured against `examples/dummy`, `/posts/new` and `/settings` were filed
|
|
224
229
|
// unmeasured for that reason alone, and a `render: 'static'` route reading it failed the whole
|
|
@@ -226,115 +231,129 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
|
|
|
226
231
|
// documents, and this build's own id so a component reading `ctx.buildId` stamps the artifact
|
|
227
232
|
// with the id the report and the stats carry.
|
|
228
233
|
const ctx = createContext({ role: 'web', buildId });
|
|
229
|
-
// A SECOND
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// `renderStatic` keeps `ctx`: its output is a
|
|
233
|
-
// refuses must fail the build, never render another
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
...(pwa === undefined ? {} : { pwaHead: pwa.head + (swHead ?? '') }),
|
|
245
|
-
}),
|
|
246
|
-
);
|
|
234
|
+
// A SECOND scope, for the branch below that renders only to weigh (`measure-scope.ts`): a
|
|
235
|
+
// request context holding the app's measurement actor, with the app's own API answered in
|
|
236
|
+
// process, because an `app/` page's `load` calls policy-guarded queries over its typed client
|
|
237
|
+
// and no server is listening during a build. `renderStatic` keeps `ctx`: its output is a
|
|
238
|
+
// published file, and a `site/` load a policy refuses must fail the build, never render another
|
|
239
|
+
// actor's rows into it.
|
|
240
|
+
const measure = await measureScope({ origin, buildId });
|
|
241
|
+
// Started on the first route that needs weighing, and released however the loop ends.
|
|
242
|
+
const database = measureDatabase(options.root);
|
|
243
|
+
const render = (entry: RouteEntry, data: { url: string; params: Record<string, string> }) =>
|
|
244
|
+
routeDocument(entry, data, {
|
|
245
|
+
resolveIsland: (file: string) => islands.resolverFor(file),
|
|
246
|
+
themeHead: theme.head,
|
|
247
|
+
...(pwa === undefined ? {} : { pwaHead: pwa.head + (swHead ?? '') }),
|
|
248
|
+
});
|
|
247
249
|
const document = (entry: RouteEntry, data: { url: string; params: Record<string, string> }) =>
|
|
248
|
-
|
|
250
|
+
runWithContext(ctx, () => render(entry, data));
|
|
249
251
|
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
252
|
+
try {
|
|
253
|
+
for (const entry of routeEntries()) {
|
|
254
|
+
const facts = { surface: entry.surface, render: entry.config.render, route: entry.path };
|
|
255
|
+
const reason = skipReasonFor(facts);
|
|
256
|
+
if (reason !== null) {
|
|
257
|
+
skipped.push(skippedRoute(facts, reason));
|
|
258
|
+
if (!declaresBudget(entry)) continue;
|
|
259
|
+
// Non-fatal, and that is deliberate: an ssr page's `load` may want a request, a session or a
|
|
260
|
+
// database this build does not have, and a `x build --target static` that started failing on
|
|
261
|
+
// routes it never used to touch would be a worse regression than the gap it closes. A route
|
|
262
|
+
// that will not render here is reported, gets no stats entry, and stays `X_BUDGET_UNMEASURED`.
|
|
263
|
+
try {
|
|
264
|
+
// A dynamic route is rendered at the paths its own `prerender()` lists, and the row holds
|
|
265
|
+
// the heaviest — a static route's rule. One that lists none is its own finding.
|
|
266
|
+
// Inside the measuring scope: a route's `prerender()` lists its paths by reading the app's
|
|
267
|
+
// own data, the same way its `load` does.
|
|
268
|
+
await database.ready();
|
|
269
|
+
const plan = await withAppUrl(origin, () => measure.run(() => measurePaths(entry)));
|
|
270
|
+
if ('unmeasured' in plan) {
|
|
271
|
+
unmeasured.push(plan.unmeasured);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
let row: RouteStats | undefined;
|
|
275
|
+
for (const { path, params } of plan.paths) {
|
|
276
|
+
const data = { url: new URL(path, origin).href, params };
|
|
277
|
+
const html = await withAppUrl(origin, () => measure.run(() => render(entry, data)));
|
|
278
|
+
const measured = await measureDocumentJs(html, options.out);
|
|
279
|
+
if (row !== undefined && measured.jsBytes <= row.jsBytes) continue;
|
|
280
|
+
const chain = heaviestSource(islands, measured.entries);
|
|
281
|
+
row = {
|
|
282
|
+
path: entry.path,
|
|
283
|
+
jsBytes: measured.jsBytes,
|
|
284
|
+
frameworkJsBytes: measured.frameworkBytes,
|
|
285
|
+
...(chain === undefined ? {} : { heaviestChain: chain }),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (row !== undefined) routes.push(row);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// `renderThrowable`, never `String(error)`: this is a caught unknown, and a hostile
|
|
291
|
+
// `toString` here would take the whole build down instead of one route's measurement.
|
|
292
|
+
// A framework error rides along with its code, cause and fix: `checkBudgets` reports
|
|
293
|
+
// `X_ISLAND_PROPS_INVALID` under its own name, because that sentence — the island, the
|
|
294
|
+
// prop, its bytes — is the finding, and `X_BUDGET_UNMEASURED` pointing at this list was
|
|
295
|
+
// a second command between the author and it.
|
|
296
|
+
unmeasured.push({
|
|
297
|
+
path: entry.path,
|
|
298
|
+
reason: renderThrowable(error),
|
|
299
|
+
...(isUltimateError(error)
|
|
300
|
+
? { code: error.code, cause: error.cause, fix: error.fix }
|
|
301
|
+
: {}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const artifacts = await renderStatic(
|
|
307
|
+
entry,
|
|
308
|
+
({ path, params }) => document(entry, { url: new URL(path, origin).href, params }),
|
|
309
|
+
{ buildId },
|
|
310
|
+
);
|
|
311
|
+
// `enumeratePrerender` answers `[]` for a dynamic route with no `prerender()`, so a
|
|
312
|
+
// `render: 'static'` route with a param writes nothing and used to be reported NOWHERE — past
|
|
313
|
+
// the skip branch by its mode, absent from `pages` by its zero artifacts. A route in neither
|
|
314
|
+
// list is the defect this report exists to close, wearing its other shape.
|
|
315
|
+
if (artifacts.length === 0) {
|
|
316
|
+
skipped.push(skippedRoute(facts, 'no-prerender-paths'));
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// One stats row per ROUTE, holding its heaviest page. `checkBudgets` looks a route up by
|
|
320
|
+
// `route.url`, which is the manifest's DECLARED pattern (`/blog/:slug`), and this pushed the
|
|
321
|
+
// FILLED path (`/blog/hello`) — so no dynamic static route has ever been weighed: every one
|
|
322
|
+
// was `X_BUDGET_UNMEASURED` and `X_BUDGET_EXCEEDED` could not fire for the whole class. The
|
|
323
|
+
// heaviest page and not the first, because a budget is a ceiling: the page that breaks it is
|
|
324
|
+
// the one the route has to answer for. `pages` below still names every filled path.
|
|
325
|
+
let heaviest: RouteStats | undefined;
|
|
326
|
+
for (const artifact of artifacts) {
|
|
327
|
+
const file = join(options.out, artifact.outputPath);
|
|
328
|
+
const bytes = await Bun.write(file, artifact.html);
|
|
329
|
+
pages.push({
|
|
330
|
+
route: entry.path,
|
|
331
|
+
path: artifact.path,
|
|
332
|
+
file: artifact.outputPath,
|
|
333
|
+
hash: artifact.hash,
|
|
334
|
+
bytes,
|
|
264
335
|
});
|
|
265
|
-
|
|
336
|
+
// `artifact.hash` is `contentHash(html)` — the same identity that becomes this page's ETag,
|
|
337
|
+
// so the precache revision and the HTTP validator can never disagree about one document.
|
|
338
|
+
// Keyed by the FILLED path, which for a non-dynamic route is the declared one; a dynamic
|
|
339
|
+
// route is not precached as a single URL anyway (`buildPrecacheManifest` skips it).
|
|
340
|
+
documents.set(artifact.path, { revision: artifact.hash, bytes });
|
|
341
|
+
// Measured from the document that was just written, so the `budgets` step compares a
|
|
342
|
+
// declared budget against bytes that exist on disk rather than against a graph's estimate.
|
|
343
|
+
const measured = await measureDocumentJs(artifact.html, options.out);
|
|
266
344
|
const chain = heaviestSource(islands, measured.entries);
|
|
267
|
-
|
|
345
|
+
if (heaviest !== undefined && heaviest.jsBytes >= measured.jsBytes) continue;
|
|
346
|
+
heaviest = {
|
|
268
347
|
path: entry.path,
|
|
269
348
|
jsBytes: measured.jsBytes,
|
|
270
349
|
frameworkJsBytes: measured.frameworkBytes,
|
|
271
350
|
...(chain === undefined ? {} : { heaviestChain: chain }),
|
|
272
|
-
}
|
|
273
|
-
} catch (error) {
|
|
274
|
-
// `renderThrowable`, never `String(error)`: this is a caught unknown, and a hostile
|
|
275
|
-
// `toString` here would take the whole build down instead of one route's measurement.
|
|
276
|
-
// A framework error rides along with its code, cause and fix: `checkBudgets` reports
|
|
277
|
-
// `X_ISLAND_PROPS_INVALID` under its own name, because that sentence — the island, the
|
|
278
|
-
// prop, its bytes — is the finding, and `X_BUDGET_UNMEASURED` pointing at this list was
|
|
279
|
-
// a second command between the author and it.
|
|
280
|
-
unmeasured.push({
|
|
281
|
-
path: entry.path,
|
|
282
|
-
reason: renderThrowable(error),
|
|
283
|
-
...(isUltimateError(error)
|
|
284
|
-
? { code: error.code, cause: error.cause, fix: error.fix }
|
|
285
|
-
: {}),
|
|
286
|
-
});
|
|
351
|
+
};
|
|
287
352
|
}
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
const artifacts = await renderStatic(
|
|
291
|
-
entry,
|
|
292
|
-
({ path, params }) => document(entry, { url: new URL(path, origin).href, params }),
|
|
293
|
-
{ buildId },
|
|
294
|
-
);
|
|
295
|
-
// `enumeratePrerender` answers `[]` for a dynamic route with no `prerender()`, so a
|
|
296
|
-
// `render: 'static'` route with a param writes nothing and used to be reported NOWHERE — past
|
|
297
|
-
// the skip branch by its mode, absent from `pages` by its zero artifacts. A route in neither
|
|
298
|
-
// list is the defect this report exists to close, wearing its other shape.
|
|
299
|
-
if (artifacts.length === 0) {
|
|
300
|
-
skipped.push(skippedRoute(facts, 'no-prerender-paths'));
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
// One stats row per ROUTE, holding its heaviest page. `checkBudgets` looks a route up by
|
|
304
|
-
// `route.url`, which is the manifest's DECLARED pattern (`/blog/:slug`), and this pushed the
|
|
305
|
-
// FILLED path (`/blog/hello`) — so no dynamic static route has ever been weighed: every one
|
|
306
|
-
// was `X_BUDGET_UNMEASURED` and `X_BUDGET_EXCEEDED` could not fire for the whole class. The
|
|
307
|
-
// heaviest page and not the first, because a budget is a ceiling: the page that breaks it is
|
|
308
|
-
// the one the route has to answer for. `pages` below still names every filled path.
|
|
309
|
-
let heaviest: RouteStats | undefined;
|
|
310
|
-
for (const artifact of artifacts) {
|
|
311
|
-
const file = join(options.out, artifact.outputPath);
|
|
312
|
-
const bytes = await Bun.write(file, artifact.html);
|
|
313
|
-
pages.push({
|
|
314
|
-
route: entry.path,
|
|
315
|
-
path: artifact.path,
|
|
316
|
-
file: artifact.outputPath,
|
|
317
|
-
hash: artifact.hash,
|
|
318
|
-
bytes,
|
|
319
|
-
});
|
|
320
|
-
// `artifact.hash` is `contentHash(html)` — the same identity that becomes this page's ETag,
|
|
321
|
-
// so the precache revision and the HTTP validator can never disagree about one document.
|
|
322
|
-
// Keyed by the FILLED path, which for a non-dynamic route is the declared one; a dynamic
|
|
323
|
-
// route is not precached as a single URL anyway (`buildPrecacheManifest` skips it).
|
|
324
|
-
documents.set(artifact.path, { revision: artifact.hash, bytes });
|
|
325
|
-
// Measured from the document that was just written, so the `budgets` step compares a
|
|
326
|
-
// declared budget against bytes that exist on disk rather than against a graph's estimate.
|
|
327
|
-
const measured = await measureDocumentJs(artifact.html, options.out);
|
|
328
|
-
const chain = heaviestSource(islands, measured.entries);
|
|
329
|
-
if (heaviest !== undefined && heaviest.jsBytes >= measured.jsBytes) continue;
|
|
330
|
-
heaviest = {
|
|
331
|
-
path: entry.path,
|
|
332
|
-
jsBytes: measured.jsBytes,
|
|
333
|
-
frameworkJsBytes: measured.frameworkBytes,
|
|
334
|
-
...(chain === undefined ? {} : { heaviestChain: chain }),
|
|
335
|
-
};
|
|
353
|
+
if (heaviest !== undefined) routes.push(heaviest);
|
|
336
354
|
}
|
|
337
|
-
|
|
355
|
+
} finally {
|
|
356
|
+
await database.close();
|
|
338
357
|
}
|
|
339
358
|
// The worker, LAST: every document it precaches has now been rendered, hashed and weighed. A
|
|
340
359
|
// static host runs no route table, so both files go into the artifact — a
|
package/src/pwa-artifacts.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// `<link rel="manifest">`, no `theme-color` and no apple-touch icon — and no browser has ever
|
|
8
8
|
// offered to install one, however the config was written.
|
|
9
9
|
//
|
|
10
|
-
// WHY HERE. `
|
|
10
|
+
// WHY HERE. `runtime-assets.ts`'s reason exactly: three packages declare what an installable app is and
|
|
11
11
|
// none of them can read a config file off disk. This one composes tier 0's `pwa` block with tier
|
|
12
12
|
// 4's generator and hands both served surfaces and the static export the same two strings.
|
|
13
13
|
//
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
// manifest is not, so the worker lands behind a real browser check rather than beside this.
|
|
17
17
|
|
|
18
18
|
// why: Bun exposes no synchronous file-existence primitive, and this read is the same one
|
|
19
|
-
// `app-auth.ts` and `
|
|
19
|
+
// `app-auth.ts` and `runtime-cache.ts` each make before importing an app's config — a root with no
|
|
20
20
|
// `app.config.ts` is an ordinary answer here (a scratch root, `x build` outside an app).
|
|
21
21
|
import { existsSync } from 'node:fs';
|
|
22
22
|
// why: Bun exposes no path-join primitive, and `APP_CONFIG_FILE` is app-root-relative — the same
|
|
23
|
-
// necessity `favicon.ts` and `
|
|
23
|
+
// necessity `favicon.ts` and `runtime-assets.ts` each record for their own root-relative constant.
|
|
24
24
|
import { join } from 'node:path';
|
|
25
25
|
import type { PwaColors, PwaOfflineConfig } from '@ultimat3/core';
|
|
26
26
|
import type { CacheHint, Route, UltimateRequest } from '@ultimat3/http';
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
// Bundled AND imported by `realtime-browser-barrel.test.ts` — the import is what gives it an lcov
|
|
5
5
|
// record, since `Bun.build()` reads this file without evaluating it.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { useQuery } from '@ultimat3/realtime';
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const probeUseQuery = useQuery;
|
package/src/reexport-manifest.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// none: its length is a function of the package's API size, so the ceiling measures the wrong
|
|
4
4
|
// thing there. One added statement of logic disqualifies it and re-arms the ceiling on the spot.
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { maskLiterals } from '@ultimat3/core';
|
|
7
|
+
import { CLOSERS, OPENERS } from './ts-scan';
|
|
7
8
|
|
|
8
9
|
/** Every statement a manifest may hold begins with one of these two words. */
|
|
9
10
|
const IMPORT_OR_EXPORT = /^(?:import|export)\b/;
|
package/src/registry.ts
CHANGED
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
// The command registry: the one list the parser, the help catalogue and the dispatcher all read.
|
|
2
2
|
// A command that is not here does not exist — there is no second place to register one.
|
|
3
|
+
//
|
|
4
|
+
// LAZY, `As of 2026-09-23`: every declaration is static (`cmd-<name>-spec.ts`) and every body is
|
|
5
|
+
// `await import()`ed when that command runs. Importing all 28 bodies to answer one of them was
|
|
6
|
+
// the CLI's whole startup: `x --help` paid for `sass`, Babel, every framework package and the
|
|
7
|
+
// dev server it never started (plan 101 slice 12 d).
|
|
3
8
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
9
|
+
import { affectedSpec } from './cmd-affected-spec';
|
|
10
|
+
import { buildSpec } from './cmd-build-spec';
|
|
11
|
+
import { ciSpec } from './cmd-ci-spec';
|
|
12
|
+
import { dbSpec } from './cmd-db-spec';
|
|
13
|
+
import { deploySpec } from './cmd-deploy-spec';
|
|
14
|
+
import { devSpec } from './cmd-dev-spec';
|
|
15
|
+
import { docsSpec } from './cmd-docs-spec';
|
|
16
|
+
import { doctorSpec } from './cmd-doctor-spec';
|
|
17
|
+
import { envSpec } from './cmd-env-spec';
|
|
18
|
+
import { errorsSpec } from './cmd-errors-spec';
|
|
19
|
+
import { fixSpec } from './cmd-fix-spec';
|
|
20
|
+
import { generateSpec } from './cmd-generate-spec';
|
|
21
|
+
// Registered here, not by whichever command body happens to load: `dispatch` renders a CLI code's
|
|
22
|
+
// title for a refusal raised before any body is imported.
|
|
23
|
+
import './error-codes';
|
|
16
24
|
import { createHelpCommand, createVersionCommand } from './cmd-help';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
25
|
+
import { i18nSpec } from './cmd-i18n-spec';
|
|
26
|
+
import { jobsSpec } from './cmd-jobs-spec';
|
|
27
|
+
import { manifestSpec } from './cmd-manifest-spec';
|
|
28
|
+
import { mcpSpec } from './cmd-mcp-spec';
|
|
29
|
+
import { newSpec } from './cmd-new-spec';
|
|
22
30
|
import { plannedCommands } from './cmd-planned';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
31
|
+
import { policySpec } from './cmd-policy-spec';
|
|
32
|
+
import { prSpec } from './cmd-pr-spec';
|
|
33
|
+
import { actionsSpec, entitiesSpec, queriesSpec } from './cmd-registries-spec';
|
|
34
|
+
import { routesSpec } from './cmd-routes-spec';
|
|
35
|
+
import { secretsSpec } from './cmd-secrets-spec';
|
|
36
|
+
import { shotSpec } from './cmd-shot-spec';
|
|
37
|
+
import { tasksSpec } from './cmd-tasks-spec';
|
|
38
|
+
import { testSpec } from './cmd-test-spec';
|
|
39
|
+
import { verifySpec } from './cmd-verify-spec';
|
|
32
40
|
import type { CliCommand } from './command';
|
|
33
41
|
import type { CommandSpec } from './parse';
|
|
34
42
|
import { loadVersion } from './version-loader';
|
|
@@ -48,35 +56,51 @@ export function cliVersion(): string {
|
|
|
48
56
|
return cliVersionCache;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
/**
|
|
60
|
+
* A command whose body loads on first run. The spec is the module's own (`cmd-<name>.ts` reads
|
|
61
|
+
* the same `cmd-<name>-spec.ts`), so the parser and the body can never describe two commands.
|
|
62
|
+
*/
|
|
63
|
+
export interface LazyCommand extends CliCommand {
|
|
64
|
+
/** The body module's own command — what `run` delegates to, exposed so a test can hold them equal. */
|
|
65
|
+
load(): Promise<CliCommand>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const lazy = (spec: CommandSpec, load: () => Promise<CliCommand>): LazyCommand => ({
|
|
69
|
+
spec,
|
|
70
|
+
load,
|
|
71
|
+
run: async (ctx) => (await load()).run(ctx),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** The shipped commands, each a static declaration plus the body it loads on first run. */
|
|
75
|
+
export const LAZY_COMMANDS: readonly LazyCommand[] = [
|
|
76
|
+
lazy(newSpec, async () => (await import('./cmd-new')).newCommand),
|
|
77
|
+
lazy(devSpec, async () => (await import('./cmd-dev')).devCommand),
|
|
78
|
+
lazy(buildSpec, async () => (await import('./cmd-build')).buildCommand),
|
|
79
|
+
lazy(testSpec, async () => (await import('./cmd-test')).testCommand),
|
|
80
|
+
lazy(verifySpec, async () => (await import('./cmd-verify')).verifyCommand),
|
|
81
|
+
lazy(generateSpec, async () => (await import('./cmd-generate')).generateCommand),
|
|
82
|
+
lazy(dbSpec, async () => (await import('./cmd-db')).dbCommand),
|
|
83
|
+
lazy(mcpSpec, async () => (await import('./cmd-mcp')).mcpCommand),
|
|
84
|
+
lazy(doctorSpec, async () => (await import('./cmd-doctor')).doctorCommand),
|
|
85
|
+
lazy(deploySpec, async () => (await import('./cmd-deploy')).deployCommand),
|
|
86
|
+
lazy(envSpec, async () => (await import('./cmd-env')).envCommand),
|
|
87
|
+
lazy(secretsSpec, async () => (await import('./cmd-secrets')).secretsCommand),
|
|
88
|
+
lazy(manifestSpec, async () => (await import('./cmd-manifest')).manifestCommand),
|
|
89
|
+
lazy(routesSpec, async () => (await import('./cmd-routes')).routesCommand),
|
|
90
|
+
lazy(actionsSpec, async () => (await import('./cmd-registries')).actionsCommand),
|
|
91
|
+
lazy(queriesSpec, async () => (await import('./cmd-registries')).queriesCommand),
|
|
92
|
+
lazy(entitiesSpec, async () => (await import('./cmd-registries')).entitiesCommand),
|
|
93
|
+
lazy(jobsSpec, async () => (await import('./cmd-jobs')).jobsCommand),
|
|
94
|
+
lazy(tasksSpec, async () => (await import('./cmd-tasks')).tasksCommand),
|
|
95
|
+
lazy(policySpec, async () => (await import('./cmd-policy')).policyCommand),
|
|
96
|
+
lazy(i18nSpec, async () => (await import('./cmd-i18n')).i18nCommand),
|
|
97
|
+
lazy(errorsSpec, async () => (await import('./cmd-errors')).errorsCommand),
|
|
98
|
+
lazy(docsSpec, async () => (await import('./cmd-docs')).docsCommand),
|
|
99
|
+
lazy(fixSpec, async () => (await import('./cmd-fix')).fixCommand),
|
|
100
|
+
lazy(affectedSpec, async () => (await import('./cmd-affected')).affectedCommand),
|
|
101
|
+
lazy(shotSpec, async () => (await import('./cmd-shot')).shotCommand),
|
|
102
|
+
lazy(prSpec, async () => (await import('./cmd-pr')).prCommand),
|
|
103
|
+
lazy(ciSpec, async () => (await import('./cmd-ci')).ciCommand),
|
|
80
104
|
];
|
|
81
105
|
|
|
82
106
|
/**
|
|
@@ -85,7 +109,7 @@ const CORE: readonly CliCommand[] = [
|
|
|
85
109
|
* facts, and only one of them is true — see `cmd-planned.ts`.
|
|
86
110
|
*/
|
|
87
111
|
export const COMMANDS: readonly CliCommand[] = [
|
|
88
|
-
...
|
|
112
|
+
...LAZY_COMMANDS,
|
|
89
113
|
...plannedCommands(),
|
|
90
114
|
createHelpCommand(() => SPECS),
|
|
91
115
|
createVersionCommand(cliVersion),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// `realtime.enabled`, obeyed where the roles are chosen. Until 22.0.0 no boot read it: `x dev`
|
|
2
|
+
// started a `sync` node and `ROLE=sync` served one whatever the key said, so `enabled: false` was
|
|
3
|
+
// documentation. Off now means no `sync` node, no replicator and — with no node to feed — no live
|
|
4
|
+
// feed wiring (`runtime-live-feed.ts` starts nothing for a null node).
|
|
5
|
+
|
|
6
|
+
import type { RealtimeConfig, Role } from '@ultimat3/core';
|
|
7
|
+
import { ConfigInvalidError, logger } from '@ultimat3/core';
|
|
8
|
+
|
|
9
|
+
/** The roles that exist only to serve realtime. `web` publishes too, but serves pages first. */
|
|
10
|
+
const REALTIME_ROLES: readonly Role[] = ['sync', 'replicator'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The roles this process may start. Disabled drops the realtime roles and keeps the rest, which is
|
|
14
|
+
* what `x dev`'s default set needs on an app that opted out (`enabled: false`; the default is on).
|
|
15
|
+
* A selection that was NOTHING but realtime roles is refused instead: that is `ROLE=sync` on an app
|
|
16
|
+
* that turned realtime off, a container that would bind nothing and restart behind its probe.
|
|
17
|
+
*/
|
|
18
|
+
export function rolesUnderRealtime(
|
|
19
|
+
selected: readonly Role[],
|
|
20
|
+
realtime: Pick<RealtimeConfig, 'enabled'>,
|
|
21
|
+
): readonly Role[] {
|
|
22
|
+
// The key is read only when a realtime role was asked for, so a selection without one never
|
|
23
|
+
// depends on it — a hand-built runtime in a test that starts `web` alone included.
|
|
24
|
+
const dropped = selected.filter((role) => REALTIME_ROLES.includes(role));
|
|
25
|
+
if (dropped.length === 0 || realtime.enabled) return selected;
|
|
26
|
+
const kept = selected.filter((role) => !REALTIME_ROLES.includes(role));
|
|
27
|
+
if (kept.length === 0) {
|
|
28
|
+
throw new ConfigInvalidError({
|
|
29
|
+
cause: `role ${dropped.join(',')} was asked for and realtime.enabled is false in app.config.ts, so this process would serve nothing`,
|
|
30
|
+
fix: `set realtime: { enabled: true } in app.config.ts, or run a role that is not realtime: x dev --role web,worker,scheduler`,
|
|
31
|
+
meta: { key: 'realtime.enabled', roles: dropped },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
logger.info(`realtime.enabled is false in app.config.ts: not starting ${dropped.join(', ')}`);
|
|
35
|
+
return kept;
|
|
36
|
+
}
|
|
@@ -7,8 +7,8 @@ import { describeEntities } from '@ultimat3/entity';
|
|
|
7
7
|
import { ReplicatorSlotHeldError } from '@ultimat3/realtime';
|
|
8
8
|
import type { Replicator, Transport } from '@ultimat3/realtime/server';
|
|
9
9
|
import { createReplicator, replicatorLockKey, selectChangeFeed } from '@ultimat3/realtime/server';
|
|
10
|
-
import type { DevServices, Env } from './dev-services';
|
|
11
10
|
import { BadFlagError } from './errors';
|
|
11
|
+
import type { DevServices, Env } from './runtime-bindings';
|
|
12
12
|
|
|
13
13
|
export interface StartReplicatorOptions {
|
|
14
14
|
readonly services: DevServices;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The one `RunningServices` every `
|
|
1
|
+
// The one `RunningServices` every `role-start` test file boots roles against, and the one reset
|
|
2
2
|
// between them. Shared rather than copied, for the reason `policy-fixture.ts` gives: three files
|
|
3
3
|
// start the same roles, and a second copy of the runtime drifts while each file keeps passing.
|
|
4
4
|
//
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `*.test.ts` — a fixture written there is one `tsc` never reads.
|
|
7
7
|
|
|
8
8
|
import { noopPurgeDriver } from '@ultimat3/cache';
|
|
9
|
-
import { resetLifecycle } from '@ultimat3/core';
|
|
9
|
+
import { type RealtimeConfig, resetLifecycle } from '@ultimat3/core';
|
|
10
10
|
import { resetHttpConfig } from '@ultimat3/http';
|
|
11
11
|
import {
|
|
12
12
|
createMemoryDriver,
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
import { createMemoryDriver as createMemoryMailDriver } from '@ultimat3/mail';
|
|
20
20
|
import { DEFAULT_PRESENCE_TTL_MS, InProcessTransport } from '@ultimat3/realtime/server';
|
|
21
21
|
import { defineStorage, localDriver } from '@ultimat3/storage';
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
22
|
+
import { resolveServices } from './runtime-bindings';
|
|
23
|
+
import type { RunningServices } from './runtime-services';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Every service a role touches, embedded but real — no PGlite boot for a role-wiring test.
|
|
@@ -28,7 +28,10 @@ import { resolveServices } from './dev-services';
|
|
|
28
28
|
* `root` is a parameter and not a constant: each test file owns its own directory and deletes it,
|
|
29
29
|
* so two files sharing one on-disk storage root cannot leave the other's fixture half-removed.
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
/** Enabled, in-process: the section every role test before 22.0.0 was implicitly booting. */
|
|
32
|
+
const REALTIME_ON: RealtimeConfig = { enabled: true, transport: 'memory', urlEnv: undefined };
|
|
33
|
+
|
|
34
|
+
export function fixtureRuntime(root: string, realtime = REALTIME_ON): RunningServices {
|
|
32
35
|
const services = resolveServices(root, {});
|
|
33
36
|
const transport = new InProcessTransport();
|
|
34
37
|
return {
|
|
@@ -41,6 +44,7 @@ export function fixtureRuntime(root: string): RunningServices {
|
|
|
41
44
|
outbox: createMemoryOutboxStore(),
|
|
42
45
|
events: createMemoryEventBus(),
|
|
43
46
|
transport,
|
|
47
|
+
realtime,
|
|
44
48
|
transportDetail: 'in-process fanout',
|
|
45
49
|
// The sync role reads this to build its `PresenceRegistry`; the default is what a boot with no
|
|
46
50
|
// `NATS_URL` resolves to, so the fixture is the real number rather than a rounder one.
|