@orkestrel/scaffold 0.0.26 → 0.0.28

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.
@@ -201,6 +201,12 @@ var GLOBAL_SETUP_PATH = "tests/setupGlobal.ts";
201
201
  var GUIDES_TEST_PATH = "tests/guides.test.ts";
202
202
  /** The installed-package proof whose presence makes a workspace `integration`. */
203
203
  var INTEGRATION_TEST_PATH = "tests/integration.test.ts";
204
+ /** The official-tooling drift proof whose presence makes a workspace `conformance`. */
205
+ var CONFORMANCE_TEST_PATH = "tests/conformance.test.ts";
206
+ /** The live-service readiness module whose presence makes a workspace `service`. */
207
+ var SERVICE_SETUP_PATH = "tests/setupService.ts";
208
+ /** The include the live-service project covers, which is a directory rather than one proof. */
209
+ var SERVICE_TEST_INCLUDE = "tests/service/**/*.test.ts";
204
210
  /** The Vite wrapper whose presence makes a workspace `showcase`. */
205
211
  var SHOWCASE_CONFIG_PATH = "configs/app/vite.showcase.config.ts";
206
212
  /** The bare workspace name syntax: lowercase alphanumeric with hyphens, letter first. */
@@ -283,8 +289,8 @@ var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
283
289
  /** The tooling versions scaffold and every generated workspace share. */
284
290
  var BASE_DEV_DEPENDENCIES = Object.freeze({
285
291
  "@microsoft/api-extractor": "^7.58.12",
286
- "@orkestrel/guide": "^0.0.9",
287
- "@orkestrel/scaffold": "^0.0.26",
292
+ "@orkestrel/guide": "^0.0.10",
293
+ "@orkestrel/scaffold": "^0.0.28",
288
294
  "@types/node": "^26.2.0",
289
295
  oxfmt: "^0.62.0",
290
296
  oxlint: "^1.77.0",
@@ -299,11 +305,11 @@ var SOURCE_BROWSER_DEV_DEPENDENCIES = Object.freeze({
299
305
  playwright: "^1.62.1"
300
306
  });
301
307
  /** The development dependency every private `app` environment adds. */
302
- var APP_DEV_DEPENDENCIES = Object.freeze({ "@orkestrel/contract": "^0.0.10" });
308
+ var APP_DEV_DEPENDENCIES = Object.freeze({ "@orkestrel/contract": "^0.0.11" });
303
309
  /** The development dependencies a private Vue browser application adds. */
304
310
  var APP_BROWSER_DEV_DEPENDENCIES = Object.freeze({
305
311
  ...SOURCE_BROWSER_DEV_DEPENDENCIES,
306
- "@orkestrel/html": "^0.0.2",
312
+ "@orkestrel/html": "^0.0.3",
307
313
  "@vitejs/plugin-vue": "^6.0.8",
308
314
  vue: "^3.5.40",
309
315
  "vue-tsc": "^3.3.7"
@@ -321,10 +327,10 @@ var APP_BROWSER_DEV_DEPENDENCIES = Object.freeze({
321
327
  var SHOWCASE_DEV_DEPENDENCIES = Object.freeze({ "vite-plugin-singlefile": "^2.3.3" });
322
328
  /** The development dependencies a private server application adds. */
323
329
  var APP_SERVER_DEV_DEPENDENCIES = Object.freeze({
324
- "@orkestrel/emitter": "^0.0.5",
325
- "@orkestrel/middleware": "^0.0.9",
326
- "@orkestrel/router": "^0.0.8",
327
- "@orkestrel/server": "^0.0.10"
330
+ "@orkestrel/emitter": "^0.0.6",
331
+ "@orkestrel/middleware": "^0.0.10",
332
+ "@orkestrel/router": "^0.0.9",
333
+ "@orkestrel/server": "^0.0.11"
328
334
  });
329
335
  //#endregion
330
336
  //#region src/core/templates.ts
@@ -676,6 +682,44 @@ export function appBrowser(...options: never[]): UserConfig {
676
682
  },
677
683
  options ?? {},
678
684
  )
685
+ `,
686
+ conformance: `// Where this package drifts from the official tooling it stays compatible with.
687
+ // The subject is this package, so the proof is hermetic and stays in \`npm test\`.
688
+ export const conformance = (options?: UserConfig): UserConfig =>
689
+ mergeConfig(
690
+ {
691
+ resolve,
692
+ test: {
693
+ name: { label: 'conformance', color: 'magenta' },
694
+ include: ['${CONFORMANCE_TEST_PATH}'],
695
+ setupFiles: ['./tests/setup.ts'],
696
+ environment: 'node',
697
+ browser: { enabled: false },
698
+ },
699
+ },
700
+ options ?? {},
701
+ )
702
+ `,
703
+ service: `// The live external services this package drives. It starts nothing itself:
704
+ // \`scripts/service.sh\` provisions, \`tests/setupService.ts\` proves readiness, and
705
+ // the project stays out of \`npm test\` because a real service answers it.
706
+ export const service = (options?: UserConfig): UserConfig =>
707
+ mergeConfig(
708
+ {
709
+ resolve,
710
+ test: {
711
+ name: { label: 'service', color: 'red' },
712
+ include: ['${SERVICE_TEST_INCLUDE}'],
713
+ setupFiles: ['./tests/setup.ts', './tests/setupService.ts'],
714
+ environment: 'node',
715
+ browser: { enabled: false },
716
+ testTimeout: 120_000,
717
+ hookTimeout: 120_000,
718
+ fileParallelism: false,
719
+ },
720
+ },
721
+ options ?? {},
722
+ )
679
723
  `,
680
724
  probe: `// A workbench, not a proof. No gate selects this project.
681
725
  export const probe = (options?: UserConfig): UserConfig =>
@@ -1150,7 +1194,7 @@ npm test
1150
1194
  set -eu
1151
1195
 
1152
1196
  printf '%s\\n' \\
1153
- {{services}}
1197
+ {{vendors}}
1154
1198
  ` })
1155
1199
  });
1156
1200
  //#endregion
@@ -1437,7 +1481,9 @@ var isBlueprint = (0, _orkestrel_contract.recordOf)({
1437
1481
  overrides: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isOverride)),
1438
1482
  bin: _orkestrel_contract.isBoolean,
1439
1483
  integration: _orkestrel_contract.isBoolean,
1440
- services: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)),
1484
+ conformance: _orkestrel_contract.isBoolean,
1485
+ service: _orkestrel_contract.isBoolean,
1486
+ vendors: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)),
1441
1487
  global: _orkestrel_contract.isBoolean,
1442
1488
  showcase: _orkestrel_contract.isBoolean
1443
1489
  }, ["description"]);
@@ -1591,7 +1637,8 @@ var isMirror = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.recordOf
1591
1637
  var isCatalogEntry = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.recordOf)({
1592
1638
  name: isDependencyName,
1593
1639
  lookup: (0, _orkestrel_contract.literalOf)("found"),
1594
- version: _orkestrel_contract.isString
1640
+ version: _orkestrel_contract.isString,
1641
+ dependencies: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isDependency))
1595
1642
  }), (0, _orkestrel_contract.recordOf)({
1596
1643
  name: isDependencyName,
1597
1644
  lookup: (0, _orkestrel_contract.literalOf)("missing", "failed"),
@@ -2164,6 +2211,54 @@ function matchesDriftReachability(ownership, finding) {
2164
2211
  return finding.drift === "stale" && ownership === "content";
2165
2212
  }
2166
2213
  /**
2214
+ * Project a catalog into the layers it publishes in.
2215
+ *
2216
+ * @param entries - The catalog rows to order.
2217
+ * @returns One layer per round, each holding the names publishable together,
2218
+ * sorted within the layer; a name whose edges never resolve is omitted.
2219
+ *
2220
+ * @remarks
2221
+ * A layer is a deterministic function of the catalog's own edges, so it is
2222
+ * computed here rather than stored on a row that could disagree with them.
2223
+ * Only RUNTIME edges between catalogued packages count: a development
2224
+ * dependency reaches no consumer, so it constrains nothing about publish order,
2225
+ * and an edge leaving the fleet is a package this catalog does not publish.
2226
+ *
2227
+ * The order matters because these packages are `0.0.x`, where a caret pins one
2228
+ * exact release. Publishing a dependent before its dependency leaves the
2229
+ * dependent pinned to the older release, and two ranges that disagree install
2230
+ * two copies of one package that the compiler reads as two distinct types.
2231
+ *
2232
+ * A cycle cannot be published in rounds, so its members are omitted rather than
2233
+ * placed in an order that would be wrong. An absent name is the report: compare
2234
+ * the returned names against the catalog to find one.
2235
+ *
2236
+ * @example
2237
+ * ```ts
2238
+ * import { catalogToLayers } from '@orkestrel/scaffold'
2239
+ *
2240
+ * catalogToLayers(entries)[0] // the names that depend on nothing in the fleet
2241
+ * ```
2242
+ */
2243
+ function catalogToLayers(entries) {
2244
+ const published = new Set(entries.filter((entry) => entry.lookup === "found").map((entry) => entry.name));
2245
+ const pending = /* @__PURE__ */ new Map();
2246
+ for (const entry of entries) {
2247
+ if (entry.lookup !== "found") continue;
2248
+ const edges = entry.dependencies.map((dependency) => dependency.name);
2249
+ pending.set(entry.name, new Set(edges.filter((name) => published.has(name))));
2250
+ }
2251
+ const layers = [];
2252
+ while (pending.size > 0) {
2253
+ const ready = [...pending].filter(([, edges]) => edges.size === 0).map(([name]) => name);
2254
+ if (ready.length === 0) break;
2255
+ for (const name of ready) pending.delete(name);
2256
+ for (const edges of pending.values()) for (const name of ready) edges.delete(name);
2257
+ layers.push(ready.sort());
2258
+ }
2259
+ return layers;
2260
+ }
2261
+ /**
2167
2262
  * Project a plan into its tally by artifact origin.
2168
2263
  *
2169
2264
  * @param plan - The plan to summarize.
@@ -2624,8 +2719,14 @@ function blueprintToDevDependencies(blueprint) {
2624
2719
  * to the axes the blueprint declares: a check and a test script per declared
2625
2720
  * environment, an aggregate over each axis, the policy and configuration
2626
2721
  * proofs every workspace can pass before it has a public API, and one build per
2627
- * target that actually builds. The isolated installed-package integration
2628
- * proof stays out of `test` and runs from `prepublishOnly` instead.
2722
+ * target that actually builds.
2723
+ *
2724
+ * A proof leaves `test` when a real service or a real install answers it. The
2725
+ * installed-package integration proof and the live-service proof therefore run
2726
+ * from `prepublishOnly` instead. The conformance proof stays in `test`, because
2727
+ * it measures this package against official tooling and drives nothing external:
2728
+ * a conformance run may start a server, but it starts its own and reaches it
2729
+ * over loopback, so the run stays hermetic.
2629
2730
  *
2630
2731
  * The configuration paths interpolated here are the same ones `SRC_MATRIX` and
2631
2732
  * `APP_MATRIX` list as each environment's configuration files, so a rename in
@@ -2672,7 +2773,8 @@ function blueprintToScripts(blueprint) {
2672
2773
  ...compiles ? ["npm run test:src"] : [],
2673
2774
  ...blueprint.app.length > 0 ? ["npm run test:app"] : [],
2674
2775
  "npm run test:policy",
2675
- "npm run test:config"
2776
+ "npm run test:config",
2777
+ ...blueprint.conformance ? ["npm run test:conformance"] : []
2676
2778
  ].join(" && ");
2677
2779
  if (compiles) {
2678
2780
  scripts["test:src"] = [
@@ -2689,8 +2791,10 @@ function blueprintToScripts(blueprint) {
2689
2791
  }
2690
2792
  scripts["test:policy"] = `${vitest} --project policy`;
2691
2793
  scripts["test:config"] = `${vitest} --project config`;
2794
+ if (blueprint.conformance) scripts["test:conformance"] = `${vitest} --project conformance`;
2692
2795
  scripts["test:probe"] = "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe";
2693
2796
  if (integrates) scripts["test:integration"] = `${vitest} --project integration`;
2797
+ if (blueprint.service) scripts["test:service"] = `${vitest} --project service`;
2694
2798
  scripts.build = [
2695
2799
  "npm run clean",
2696
2800
  ...compiles ? ["npm run build:src"] : [],
@@ -2720,7 +2824,11 @@ function blueprintToScripts(blueprint) {
2720
2824
  scripts.serve = "node dist/app/server/main.cjs";
2721
2825
  scripts["serve:build"] = "npm run build:app:server && npm run serve";
2722
2826
  }
2723
- scripts.prepublishOnly = ["npm run format:check && npm run lint:check && npm run check && npm run build && npm test", ...integrates ? ["npm run test:integration"] : []].join(" && ");
2827
+ scripts.prepublishOnly = [
2828
+ "npm run format:check && npm run lint:check && npm run check && npm run build && npm test",
2829
+ ...integrates ? ["npm run test:integration"] : [],
2830
+ ...blueprint.service ? ["npm run test:service"] : []
2831
+ ].join(" && ");
2724
2832
  return scripts;
2725
2833
  }
2726
2834
  /**
@@ -3002,6 +3110,14 @@ export function appShowcase(...options: never[]): UserConfig {
3002
3110
  projects.push("config");
3003
3111
  factories.push(CONFIG_TEMPLATES.factories.guides);
3004
3112
  projects.push(`...(isExactCaseFile(resolveWorkspacePath('${GUIDES_TEST_PATH}')) ? [guides] : [])`);
3113
+ if (blueprint.conformance) {
3114
+ factories.push(CONFIG_TEMPLATES.factories.conformance);
3115
+ projects.push("conformance");
3116
+ }
3117
+ if (blueprint.service) {
3118
+ factories.push(CONFIG_TEMPLATES.factories.service);
3119
+ projects.push("service");
3120
+ }
3005
3121
  if (blueprint.src.length > 0 && blueprint.integration) {
3006
3122
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.integration, { global: blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : "" }));
3007
3123
  projects.push("integration");
@@ -3222,6 +3338,12 @@ function blueprintToSourceArtifacts(blueprint) {
3222
3338
  * workspace and therefore follows the `src` axis as well as its structural
3223
3339
  * flag.
3224
3340
  *
3341
+ * The conformance proof is not emitted either, for the reason the guide proof
3342
+ * is not: it names an official artifact only the package knows, so a generated
3343
+ * placeholder would read as a proof while measuring nothing. `service` emits its
3344
+ * readiness setup alone, because the root configuration names that module by
3345
+ * path.
3346
+ *
3225
3347
  * @example
3226
3348
  * ```ts
3227
3349
  * import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
@@ -3255,6 +3377,13 @@ function blueprintToTestArtifacts(blueprint) {
3255
3377
  environment: "server",
3256
3378
  content: ARTIFACT_TEMPLATES.tests.setup
3257
3379
  });
3380
+ if (blueprint.service) artifacts.push({
3381
+ path: SERVICE_SETUP_PATH,
3382
+ group: "tests",
3383
+ ownership: "birth",
3384
+ origin: "template",
3385
+ content: ARTIFACT_TEMPLATES.tests.setup
3386
+ });
3258
3387
  if (blueprint.global) artifacts.push({
3259
3388
  path: GLOBAL_SETUP_PATH,
3260
3389
  group: "tests",
@@ -3368,22 +3497,22 @@ function blueprintToDocumentArtifacts(blueprint) {
3368
3497
  * Compile the blueprint-dependent orchestration artifacts.
3369
3498
  *
3370
3499
  * @param blueprint - The workspace specification.
3371
- * @returns A service inventory script when services are declared, otherwise none.
3500
+ * @returns A vendor inventory script when vendors are declared, otherwise none.
3372
3501
  *
3373
3502
  * @remarks
3374
- * A service name does not describe startup, readiness, or cleanup. The script
3503
+ * A vendor name does not describe startup, readiness, or cleanup. The script
3375
3504
  * therefore records only the declared inventory and does not invent a service
3376
3505
  * runner or test project.
3377
3506
  */
3378
3507
  function blueprintToOrchestrationArtifacts(blueprint) {
3379
- if (blueprint.services.length === 0) return [];
3380
- const services = blueprint.services.map((service, index) => `\t'${service}'${index === blueprint.services.length - 1 ? "" : " \\"}`).join("\n");
3508
+ if (blueprint.vendors.length === 0) return [];
3509
+ const vendors = blueprint.vendors.map((vendor, index) => `\t'${vendor}'${index === blueprint.vendors.length - 1 ? "" : " \\"}`).join("\n");
3381
3510
  return [{
3382
3511
  path: SERVICE_SCRIPT_PATH,
3383
3512
  group: "orchestration",
3384
3513
  ownership: "birth",
3385
3514
  origin: "template",
3386
- content: (0, _orkestrel_template.fillTemplate)(ARTIFACT_TEMPLATES.orchestration.service, { services })
3515
+ content: (0, _orkestrel_template.fillTemplate)(ARTIFACT_TEMPLATES.orchestration.service, { vendors })
3387
3516
  }];
3388
3517
  }
3389
3518
  /**
@@ -3739,19 +3868,19 @@ function blueprintToQuestions(blueprint) {
3739
3868
  message: "integration projects a published src, and this workspace declares none, so it emits nothing.",
3740
3869
  blocking: false
3741
3870
  });
3742
- const services = /* @__PURE__ */ new Set();
3743
- for (const service of blueprint.services) {
3744
- if (!NAME_PATTERN.test(service)) questions.push({
3745
- field: "services",
3746
- message: `${service} is not a lowercase alphanumeric service name starting with a letter.`,
3871
+ const vendors = /* @__PURE__ */ new Set();
3872
+ for (const vendor of blueprint.vendors) {
3873
+ if (!NAME_PATTERN.test(vendor)) questions.push({
3874
+ field: "vendors",
3875
+ message: `${vendor} is not a lowercase alphanumeric vendor name starting with a letter.`,
3747
3876
  blocking: true
3748
3877
  });
3749
- else if (services.has(service)) questions.push({
3750
- field: "services",
3751
- message: `${service} is declared more than once on services.`,
3878
+ else if (vendors.has(vendor)) questions.push({
3879
+ field: "vendors",
3880
+ message: `${vendor} is declared more than once on vendors.`,
3752
3881
  blocking: true
3753
3882
  });
3754
- services.add(service);
3883
+ vendors.add(vendor);
3755
3884
  }
3756
3885
  if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
3757
3886
  field: "showcase",
@@ -4219,8 +4348,8 @@ var Compiler = class {
4219
4348
  * blueprint.
4220
4349
  *
4221
4350
  * @remarks
4222
- * A blueprint is a closed record of sixteen fields, and most of them have one
4223
- * sensible starting value: an empty list, a cleared flag, `DEFAULT_VERSION`, and
4351
+ * A blueprint is a closed record, and most of its fields have one sensible
4352
+ * starting value: an empty list, a cleared flag, `DEFAULT_VERSION`, and
4224
4353
  * `DEFAULT_ENGINES`. Filling them here is what lets a caller state only what its
4225
4354
  * workspace actually declares.
4226
4355
  *
@@ -4261,7 +4390,9 @@ function createBlueprint(name, input) {
4261
4390
  overrides: input?.overrides ?? [],
4262
4391
  bin: input?.bin ?? false,
4263
4392
  integration: input?.integration ?? false,
4264
- services: input?.services ?? [],
4393
+ conformance: input?.conformance ?? false,
4394
+ service: input?.service ?? false,
4395
+ vendors: input?.vendors ?? [],
4265
4396
  global: input?.global ?? false,
4266
4397
  showcase: input?.showcase ?? false
4267
4398
  }));
@@ -4298,6 +4429,7 @@ exports.BIN_CONFIGS = BIN_CONFIGS;
4298
4429
  exports.BIN_ENTRY_PATH = BIN_ENTRY_PATH;
4299
4430
  exports.CATALOG_AGENT_PATH = CATALOG_AGENT_PATH;
4300
4431
  exports.CONFIG_TEMPLATES = CONFIG_TEMPLATES;
4432
+ exports.CONFORMANCE_TEST_PATH = CONFORMANCE_TEST_PATH;
4301
4433
  exports.CONTROL_CHARACTER_PATTERN = CONTROL_CHARACTER_PATTERN;
4302
4434
  exports.Compiler = Compiler;
4303
4435
  exports.DEFAULT_ENGINES = DEFAULT_ENGINES;
@@ -4331,6 +4463,8 @@ exports.ORCHESTRATION_PATH_NAMES = ORCHESTRATION_PATH_NAMES;
4331
4463
  exports.ORCHESTRATION_PATH_PREFIXES = ORCHESTRATION_PATH_PREFIXES;
4332
4464
  exports.ORKESTREL_RANGE_PATTERN = ORKESTREL_RANGE_PATTERN;
4333
4465
  exports.SERVICE_SCRIPT_PATH = SERVICE_SCRIPT_PATH;
4466
+ exports.SERVICE_SETUP_PATH = SERVICE_SETUP_PATH;
4467
+ exports.SERVICE_TEST_INCLUDE = SERVICE_TEST_INCLUDE;
4334
4468
  exports.SHOWCASE_CONFIG_PATH = SHOWCASE_CONFIG_PATH;
4335
4469
  exports.SHOWCASE_DEV_DEPENDENCIES = SHOWCASE_DEV_DEPENDENCIES;
4336
4470
  exports.SOURCE_BROWSER_DEV_DEPENDENCIES = SOURCE_BROWSER_DEV_DEPENDENCIES;
@@ -4355,6 +4489,7 @@ exports.blueprintToScripts = blueprintToScripts;
4355
4489
  exports.blueprintToSourceArtifacts = blueprintToSourceArtifacts;
4356
4490
  exports.blueprintToTestArtifacts = blueprintToTestArtifacts;
4357
4491
  exports.bytesToHex = bytesToHex;
4492
+ exports.catalogToLayers = catalogToLayers;
4358
4493
  exports.cloneValue = cloneValue;
4359
4494
  exports.compareVersions = compareVersions;
4360
4495
  exports.computeBytes = computeBytes;