@fabricorg/fabric-gen 0.4.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,88 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9a2f5e: Add `fabric-gen promote-experience`, which turns a validated release into a signed one from a
8
+ declared configuration: the assembly it was validated against, the release, and the usage contracts
9
+ routes are compiled from.
10
+
11
+ The signing key is a `--key` argument rather than a configuration field, and a configuration that
12
+ carries one is refused, because a configuration file is the kind of thing that ends up committed.
13
+
14
+ ### Patch Changes
15
+
16
+ - a598fb9: Close the remaining review findings.
17
+
18
+ **One capability-reference grammar.** `sdui-release` accepted any name after the namespace while
19
+ `sdui-composer` rejected `/`, so a view named `orders/reports/daily` produced a release that validated
20
+ and then died at promotion with nothing wrong upstream to point at. Both now share
21
+ `parseCapabilityRef`, tightened to lowercase path segments matching what the platform permits for a
22
+ view name. Two views in one contract that reduce to the same reference are reported as
23
+ `ambiguous_capability_reference` at validation rather than surfacing as an ambiguous route later.
24
+
25
+ **A bounded replay window.** A client-supplied `capturedAt` had no lower bound. Deferring it to the
26
+ Host looked safe and was not: the Host checks staleness only when the capture-authority adapter
27
+ supplies an `expiresAt`, and skips the execution-time expiry check entirely for actions governed at
28
+ capture. A gateway that accepts replay must now declare `maxOfflineReplayAgeMs`, and the instant must
29
+ be RFC 3339 with an explicit offset.
30
+
31
+ **One token-set grammar.** `document.tokenSet` accepted names and versions that `supportedTokenSets`
32
+ rejected, while validation treats the two fields as interchangeable.
33
+
34
+ **Port requirements narrow as they are checked.** A requirement for a standard _and_ a version could
35
+ be satisfied by two different adapters covering one constraint each. `requirement.standard.version`
36
+ was declared and never read.
37
+
38
+ - 0d1f6f6: Close what a third independent review found, including two defects introduced by the previous fix.
39
+
40
+ **Offline replay counted refusals as successes.** `PlatformHost` reports a refused capture by
41
+ _returning_ `reconciliation_required` with the reason, not by throwing, so the queue took its success
42
+ branch, deleted the capture and reported it applied. The reconciliation path was unreachable. Replay
43
+ now reads the returned outcome. The already-applied branch is removed entirely: a repeated key
44
+ returns the original invocation without complaint, and an idempotency _conflict_ means the key was
45
+ reused for a materially different command, so that capture was never applied and is kept rather than
46
+ discarded.
47
+
48
+ **`deriveAuthorizationGrants` defaulted open and reached the signed artifact.** Requirements are
49
+ optional metadata, so a capability declaring none granted everything to an actor holding nothing.
50
+ Grants are also carried onto a release unexamined and promotion signs a route for each, so a surplus
51
+ grant became a signed route to a capability the screen never renders. Undeclared requirements now
52
+ withhold by default, and `document` scopes the result to references the screen actually binds.
53
+
54
+ Several conformance checks could not fail: a search suite passed an adapter returning nothing, a
55
+ payments suite passed a double-charger that omitted a provider reference, an identity suite certified
56
+ timestamps the scope check then rejected on every request, an MFE probe could be refused for an
57
+ unrelated reason, and a channel check accepted any exception as a refusal. Each now proves what it
58
+ claims. Promotion configuration rejects anything reading like a key rather than two spellings,
59
+ rejects unknown fields, and constrains the application name that decides the default output path.
60
+
61
+ - Updated dependencies [8ce5e06]
62
+ - Updated dependencies [a598fb9]
63
+ - Updated dependencies [8f75ac9]
64
+ - Updated dependencies [9a5b18a]
65
+ - Updated dependencies [9b2e55b]
66
+ - Updated dependencies [0d1f6f6]
67
+ - Updated dependencies [274d948]
68
+ - @fabricorg/sdui-release@0.5.0
69
+ - @fabricorg/sdui-composer@0.2.0
70
+ - @fabricorg/assembly@0.4.0
71
+ - @fabricorg/gen-conformance@1.0.0
72
+ - @fabricorg/gen-graphql@4.0.0
73
+
74
+ ## 0.5.0
75
+
76
+ ### Minor Changes
77
+
78
+ - Add `fabric-gen init experience` for wiring the supported experience tier into an
79
+ existing application without generating a competing shell.
80
+
81
+ ### Patch Changes
82
+
83
+ - Updated dependencies
84
+ - @fabricorg/sdui-release@0.4.0
85
+
3
86
  ## 0.4.1
4
87
 
5
88
  ### Patch Changes
package/README.md CHANGED
@@ -7,9 +7,14 @@ Create a compiler-valid capability without copying a repository template:
7
7
 
8
8
  ```bash
9
9
  fabric-gen init capability case-review
10
+ fabric-gen init experience portfolio --directory apps/saas/modules/portfolio
10
11
  fabric-gen init capability case-review --directory packages/case-review
11
12
  ```
12
13
 
14
+ `init experience` adds runtime, gateway, component-pack, and application-check
15
+ integration files to an existing Supastarter-derived application. It never creates or
16
+ replaces the application shell.
17
+
13
18
  Initialization refuses to overwrite either `README.md` or
14
19
  `fabric.manifest.json`.
15
20
 
@@ -1,5 +1,6 @@
1
1
  // index.ts
2
2
  import { access, mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
3
+ import { createPrivateKey } from "crypto";
3
4
  import path from "path";
4
5
  import { compileManifestBundle, runGenerator } from "@fabricorg/compiler";
5
6
  import { capabilityGenerator } from "@fabricorg/gen-capability";
@@ -9,6 +10,7 @@ import { typesGenerator } from "@fabricorg/gen-types";
9
10
  import { prismaGenerator } from "@fabricorg/gen-prisma";
10
11
  import { graphqlGenerator } from "@fabricorg/gen-graphql";
11
12
  import { sduiGenerator } from "@fabricorg/gen-sdui";
13
+ import { createEs256ReleaseSigner, promoteExperienceRelease } from "@fabricorg/sdui-composer";
12
14
  import { conformanceGenerator } from "@fabricorg/gen-conformance";
13
15
  import {
14
16
  assertAssemblyLockfile,
@@ -31,7 +33,9 @@ var compareCanonicalStrings = (left, right) => left < right ? -1 : left > right
31
33
  var usage = [
32
34
  "Usage:",
33
35
  " fabric-gen init capability <namespace> [--directory <path>]",
36
+ " fabric-gen init experience <application> [--directory <path>]",
34
37
  " fabric-gen check-application [--config <path>]",
38
+ " fabric-gen promote-experience --config <path> --key <pem> [--out <path>]",
35
39
  " fabric-gen --input <manifest.json> --out <directory> [--targets <list>] [--check]"
36
40
  ].join("\n");
37
41
  var FabricGenUsageError = class extends Error {
@@ -49,13 +53,37 @@ function flagValue(args, flag) {
49
53
  }
50
54
  async function runFabricGenCommand(args) {
51
55
  if (args[0] === "init") {
52
- if (args[1] !== "capability" || !args[2] || args[2].startsWith("--")) {
56
+ if (!args[2] || args[2].startsWith("--")) {
53
57
  throw new FabricGenUsageError(usage);
54
58
  }
55
- const namespace = args[2];
56
- const directory = flagValue(args, "--directory") ?? namespace;
57
- const result2 = await initializeCapability({ directory, namespace });
58
- return `Initialized capability "${namespace}" with ${result2.created.length} files.`;
59
+ const name = args[2];
60
+ const directory = flagValue(args, "--directory") ?? name;
61
+ if (args[1] === "capability") {
62
+ const result2 = await initializeCapability({ directory, namespace: name });
63
+ return `Initialized capability "${name}" with ${result2.created.length} files.`;
64
+ }
65
+ if (args[1] === "experience") {
66
+ const result2 = await initializeExperience({ directory, application: name });
67
+ return `Initialized experience integration "${name}" with ${result2.created.length} files.`;
68
+ }
69
+ throw new FabricGenUsageError(usage);
70
+ }
71
+ if (args[0] === "promote-experience") {
72
+ const configPath = flagValue(args, "--config");
73
+ const privateKeyPath = flagValue(args, "--key");
74
+ if (!configPath) throw new FabricGenUsageError("promote-experience requires --config.");
75
+ if (!privateKeyPath) throw new FabricGenUsageError("promote-experience requires --key.");
76
+ const outputPath = flagValue(args, "--out");
77
+ const result2 = await promoteExperience({
78
+ configPath,
79
+ privateKeyPath,
80
+ ...outputPath ? { outputPath } : {}
81
+ });
82
+ return [
83
+ `Promoted ${result2.application} release ${result2.releaseDigest.slice(0, 12)} against assembly ${result2.assemblyDigest.slice(0, 12)}.`,
84
+ `Routes: ${result2.routes.views} views, ${result2.routes.intents} intents.`,
85
+ `Wrote ${result2.outputPath}`
86
+ ].join("\n");
59
87
  }
60
88
  if (args[0] === "check-application") {
61
89
  const configPath = flagValue(args, "--config") ?? "fabric.application.json";
@@ -73,6 +101,75 @@ async function runFabricGenCommand(args) {
73
101
  });
74
102
  return `${args.includes("--check") ? "Checked" : "Generated"} ${result.files.length} files.`;
75
103
  }
104
+ async function initializeExperience(options) {
105
+ if (!/^[a-z][a-z0-9-]*$/.test(options.application)) {
106
+ throw new Error("Application name must use lowercase letters, numbers, and hyphens.");
107
+ }
108
+ const functionName = options.application.split("-").map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
109
+ const files = [
110
+ [
111
+ path.join(options.directory, "fabric.experience.ts"),
112
+ [
113
+ 'import { createExperienceServerRuntime } from "@fabricorg/experience-runtime/server";',
114
+ 'import { createExperienceGateway } from "@fabricorg/experience-gateway";',
115
+ "",
116
+ `export const create${functionName}ExperienceRuntime = (`,
117
+ " options: Parameters<typeof createExperienceServerRuntime>[0],",
118
+ ") => createExperienceServerRuntime(options);",
119
+ "",
120
+ `export const create${functionName}ExperienceGateway = (`,
121
+ " options: Parameters<typeof createExperienceGateway>[0],",
122
+ ") => createExperienceGateway(options);",
123
+ ""
124
+ ].join("\n")
125
+ ],
126
+ [
127
+ path.join(options.directory, "fabric.component-pack.ts"),
128
+ [
129
+ 'import { defineComponentPack } from "@fabricorg/component-pack";',
130
+ "",
131
+ `export const define${functionName}ComponentPack = (artifactDigest: string) =>`,
132
+ " defineComponentPack({",
133
+ ` name: "${options.application}",`,
134
+ ` namespace: "${options.application}",`,
135
+ ' version: "0.1.0",',
136
+ " artifactDigest,",
137
+ ' tokenContract: { name: "fabric-family", version: "1.0.0" },',
138
+ " components: {",
139
+ ' Text: { module: "./components/text.js", roles: ["content"], slots: [] },',
140
+ " },",
141
+ " });",
142
+ ""
143
+ ].join("\n")
144
+ ],
145
+ [
146
+ path.join(options.directory, "fabric.application.json"),
147
+ `${JSON.stringify({
148
+ formatVersion: 1,
149
+ application: options.application,
150
+ assembly: "./fabric.assembly.lock.json",
151
+ runtimeCapabilities: "./fabric.runtime.json",
152
+ releases: []
153
+ }, null, 2)}
154
+ `
155
+ ]
156
+ ];
157
+ await mkdir(options.directory, { recursive: true });
158
+ const created = [];
159
+ try {
160
+ for (const [file, content] of files) {
161
+ await writeFile(file, content, { encoding: "utf8", flag: "wx" });
162
+ created.push(file);
163
+ }
164
+ } catch (error) {
165
+ await Promise.all(created.map((file) => rm(file, { force: true })));
166
+ if (error.code === "EEXIST") {
167
+ throw new Error("Refusing to overwrite an existing experience integration file.");
168
+ }
169
+ throw error;
170
+ }
171
+ return { created };
172
+ }
76
173
  async function readJson(file) {
77
174
  return JSON.parse(await readFile(file, "utf8"));
78
175
  }
@@ -238,14 +335,79 @@ async function listFiles(directory, prefix = "") {
238
335
  const nested = await Promise.all(entries.map((entry) => entry.isDirectory() ? listFiles(path.join(directory, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]));
239
336
  return nested.flat().sort();
240
337
  }
338
+ function assertExperiencePromotionConfig(value) {
339
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
340
+ throw new Error("Experience promotion configuration must be a JSON object.");
341
+ }
342
+ const config = value;
343
+ if (config.formatVersion !== 1) throw new Error("Experience promotion configuration formatVersion must be 1.");
344
+ for (const field of ["application", "issuer", "keyId", "assembly", "release"]) {
345
+ if (typeof config[field] !== "string" || !config[field]) {
346
+ throw new Error(`Experience promotion configuration ${field} must be a non-empty string.`);
347
+ }
348
+ }
349
+ if (!/^[a-z][a-z0-9-]*$/.test(config.application)) {
350
+ throw new Error("Experience promotion configuration application must be lowercase letters, numbers, and hyphens.");
351
+ }
352
+ if (!Array.isArray(config.contracts) || !config.contracts.every((entry) => typeof entry === "string" && entry)) {
353
+ throw new Error("Experience promotion configuration contracts must be an array of file paths.");
354
+ }
355
+ const known = /* @__PURE__ */ new Set(["formatVersion", "application", "issuer", "keyId", "assembly", "release", "contracts"]);
356
+ for (const field of Object.keys(config)) {
357
+ if (/key|secret|credential|token|pem|password/i.test(field) && field !== "keyId") {
358
+ throw new Error(`Experience promotion configuration must not carry "${field}"; pass the signing key with --key.`);
359
+ }
360
+ if (!known.has(field)) {
361
+ throw new Error(`Experience promotion configuration has an unknown field "${field}".`);
362
+ }
363
+ }
364
+ }
365
+ async function promoteExperience(options) {
366
+ const configValue = await readJson(options.configPath);
367
+ assertExperiencePromotionConfig(configValue);
368
+ const base = path.dirname(options.configPath);
369
+ const resolve = (file) => path.resolve(base, file);
370
+ const assembly = await readJson(resolve(configValue.assembly));
371
+ assertAssemblyLockfile(assembly);
372
+ const release = await readJson(resolve(configValue.release));
373
+ assertSduiReleaseIntegrity(release);
374
+ const contracts = await Promise.all(
375
+ configValue.contracts.map(async (file) => await readJson(resolve(file)))
376
+ );
377
+ const privateKey = createPrivateKey(await readFile(options.privateKeyPath, "utf8"));
378
+ const promoted = await promoteExperienceRelease({
379
+ application: configValue.application,
380
+ release,
381
+ contracts,
382
+ assembly,
383
+ issuer: configValue.issuer,
384
+ signer: createEs256ReleaseSigner({ keyId: configValue.keyId, privateKey })
385
+ });
386
+ const outputPath = options.outputPath ? path.resolve(options.outputPath) : resolve(`${configValue.application}.experience-release.jws`);
387
+ await mkdir(path.dirname(outputPath), { recursive: true });
388
+ await writeFile(outputPath, `${promoted.compactJws}
389
+ `, "utf8");
390
+ return {
391
+ application: configValue.application,
392
+ releaseDigest: release.releaseDigest,
393
+ assemblyDigest: assembly.assemblyDigest,
394
+ routes: {
395
+ views: promoted.release.routes.views.length,
396
+ intents: promoted.release.routes.intents.length
397
+ },
398
+ outputPath
399
+ };
400
+ }
241
401
 
242
402
  export {
243
403
  GENERATOR_TARGETS,
244
404
  FabricGenUsageError,
245
405
  fabricGenCliExitCode,
246
406
  runFabricGenCommand,
407
+ initializeExperience,
247
408
  checkApplication,
248
409
  initializeCapability,
249
- generateFromManifest
410
+ generateFromManifest,
411
+ promoteExperience
250
412
  };
251
- //# sourceMappingURL=chunk-CUNWMAA2.js.map
413
+ //# sourceMappingURL=chunk-4QFNFERM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["import { access, mkdir, readFile, readdir, rm, writeFile } from \"node:fs/promises\";\nimport { createPrivateKey } from \"node:crypto\";\nimport path from \"node:path\";\nimport { compileManifestBundle, runGenerator, type GeneratedFile, type GeneratorTarget } from \"@fabricorg/compiler\";\nimport { capabilityGenerator } from \"@fabricorg/gen-capability\";\nimport { docsGenerator } from \"@fabricorg/gen-docs\";\nimport { eventsGenerator } from \"@fabricorg/gen-events\";\nimport { typesGenerator } from \"@fabricorg/gen-types\";\nimport { prismaGenerator } from \"@fabricorg/gen-prisma\";\nimport { graphqlGenerator } from \"@fabricorg/gen-graphql\";\nimport { sduiGenerator } from \"@fabricorg/gen-sdui\";\nimport type { UsageContractDocument } from \"@fabricorg/gen-capability\";\nimport { createEs256ReleaseSigner, promoteExperienceRelease } from \"@fabricorg/sdui-composer\";\nimport { conformanceGenerator } from \"@fabricorg/gen-conformance\";\nimport {\n\tassertAssemblyLockfile,\n\tassertAssemblyRuntimeCompatible,\n\ttype AssemblyLockfile,\n\ttype AvailableCapability,\n} from \"@fabricorg/assembly\";\nimport {\n\tassertSduiReleaseIntegrity,\n\ttype SduiRelease,\n} from \"@fabricorg/sdui-release\";\n\nexport const GENERATOR_TARGETS: Record<string, GeneratorTarget> = {\n\ttypes: typesGenerator,\n\tevents: eventsGenerator,\n\tcapability: capabilityGenerator,\n\tdocs: docsGenerator,\n\tprisma: prismaGenerator,\n\tgraphql: graphqlGenerator,\n\tsdui: sduiGenerator,\n\tconformance: conformanceGenerator,\n};\nconst compareCanonicalStrings = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0;\n\nexport interface GenerateOptions { inputPath: string; outputDirectory: string; targets: string[]; check?: boolean; }\nexport interface GenerateResult { changed: string[]; stale: string[]; files: GeneratedFile[]; }\nexport interface InitializeCapabilityOptions { directory: string; namespace: string; }\nexport interface InitializeCapabilityResult { created: string[]; }\nexport interface InitializeExperienceOptions { directory: string; application: string; }\nexport interface InitializeExperienceResult { created: string[]; }\nexport interface CheckApplicationOptions { configPath: string; }\nexport interface PromoteExperienceOptions {\n\tconfigPath: string;\n\t/** PEM-encoded ES256 private key. Never read from the configuration file. */\n\tprivateKeyPath: string;\n\toutputPath?: string;\n}\nexport interface PromoteExperienceResult {\n\tapplication: string;\n\treleaseDigest: string;\n\tassemblyDigest: string;\n\troutes: { views: number; intents: number };\n\toutputPath: string;\n}\nexport interface CheckApplicationResult {\n\tapplication: string;\n\tcapabilities: number;\n\treleases: number;\n\tassemblyDigest: string;\n}\n\nconst usage = [\n\t\"Usage:\",\n\t\" fabric-gen init capability <namespace> [--directory <path>]\",\n\t\" fabric-gen init experience <application> [--directory <path>]\",\n\t\" fabric-gen check-application [--config <path>]\",\n\t\" fabric-gen promote-experience --config <path> --key <pem> [--out <path>]\",\n\t\" fabric-gen --input <manifest.json> --out <directory> [--targets <list>] [--check]\",\n].join(\"\\n\");\n\nexport class FabricGenUsageError extends Error {\n\toverride readonly name = \"FabricGenUsageError\";\n}\n\nexport function fabricGenCliExitCode(error: unknown): number {\n\treturn error instanceof FabricGenUsageError ? 2 : 1;\n}\n\nfunction flagValue(args: readonly string[], flag: string): string | undefined {\n\tconst index = args.indexOf(flag);\n\tif (index < 0) return undefined;\n\tconst value = args[index + 1];\n\tif (!value || value.startsWith(\"--\")) throw new FabricGenUsageError(usage);\n\treturn value;\n}\n\n/** Execute one CLI command and return its single human-readable result line. */\nexport async function runFabricGenCommand(args: readonly string[]): Promise<string> {\n\tif (args[0] === \"init\") {\n\t\tif (!args[2] || args[2].startsWith(\"--\")) {\n\t\t\tthrow new FabricGenUsageError(usage);\n\t\t}\n\t\tconst name = args[2];\n\t\tconst directory = flagValue(args, \"--directory\") ?? name;\n\t\tif (args[1] === \"capability\") {\n\t\t\tconst result = await initializeCapability({ directory, namespace: name });\n\t\t\treturn `Initialized capability \"${name}\" with ${result.created.length} files.`;\n\t\t}\n\t\tif (args[1] === \"experience\") {\n\t\t\tconst result = await initializeExperience({ directory, application: name });\n\t\t\treturn `Initialized experience integration \"${name}\" with ${result.created.length} files.`;\n\t\t}\n\t\tthrow new FabricGenUsageError(usage);\n\t}\n\tif (args[0] === \"promote-experience\") {\n\t\tconst configPath = flagValue(args, \"--config\");\n\t\tconst privateKeyPath = flagValue(args, \"--key\");\n\t\tif (!configPath) throw new FabricGenUsageError(\"promote-experience requires --config.\");\n\t\tif (!privateKeyPath) throw new FabricGenUsageError(\"promote-experience requires --key.\");\n\t\tconst outputPath = flagValue(args, \"--out\");\n\t\tconst result = await promoteExperience({\n\t\t\tconfigPath,\n\t\t\tprivateKeyPath,\n\t\t\t...(outputPath ? { outputPath } : {}),\n\t\t});\n\t\treturn [\n\t\t\t`Promoted ${result.application} release ${result.releaseDigest.slice(0, 12)} against assembly ${result.assemblyDigest.slice(0, 12)}.`,\n\t\t\t`Routes: ${result.routes.views} views, ${result.routes.intents} intents.`,\n\t\t\t`Wrote ${result.outputPath}`,\n\t\t].join(\"\\n\");\n\t}\n\n\tif (args[0] === \"check-application\") {\n\t\tconst configPath = flagValue(args, \"--config\") ?? \"fabric.application.json\";\n\t\tconst result = await checkApplication({ configPath });\n\t\treturn `Checked \"${result.application}\": ${result.capabilities} capabilities, ${result.releases} releases.`;\n\t}\n\tconst inputPath = flagValue(args, \"--input\");\n\tconst outputDirectory = flagValue(args, \"--out\");\n\tif (!inputPath || !outputDirectory) throw new FabricGenUsageError(usage);\n\tconst result = await generateFromManifest({\n\t\tinputPath,\n\t\toutputDirectory,\n\t\ttargets: (flagValue(args, \"--targets\") ?? \"types,events,capability,docs\")\n\t\t\t.split(\",\")\n\t\t\t.filter(Boolean),\n\t\tcheck: args.includes(\"--check\"),\n\t});\n\treturn `${args.includes(\"--check\") ? \"Checked\" : \"Generated\"} ${result.files.length} files.`;\n}\n\n/**\n * Add experience integration files to an existing application. It deliberately\n * does not create package.json, routes, authentication, billing, or navigation:\n * those remain owned by the Supastarter-derived application shell.\n */\nexport async function initializeExperience(\n\toptions: InitializeExperienceOptions,\n): Promise<InitializeExperienceResult> {\n\tif (!/^[a-z][a-z0-9-]*$/.test(options.application)) {\n\t\tthrow new Error(\"Application name must use lowercase letters, numbers, and hyphens.\");\n\t}\n\tconst functionName = options.application\n\t\t.split(\"-\")\n\t\t.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)\n\t\t.join(\"\");\n\tconst files = [\n\t\t[\n\t\t\tpath.join(options.directory, \"fabric.experience.ts\"),\n\t\t\t[\n\t\t\t\t'import { createExperienceServerRuntime } from \"@fabricorg/experience-runtime/server\";',\n\t\t\t\t'import { createExperienceGateway } from \"@fabricorg/experience-gateway\";',\n\t\t\t\t\"\",\n\t\t\t\t`export const create${functionName}ExperienceRuntime = (`,\n\t\t\t\t\"\\toptions: Parameters<typeof createExperienceServerRuntime>[0],\",\n\t\t\t\t\") => createExperienceServerRuntime(options);\",\n\t\t\t\t\"\",\n\t\t\t\t`export const create${functionName}ExperienceGateway = (`,\n\t\t\t\t\"\\toptions: Parameters<typeof createExperienceGateway>[0],\",\n\t\t\t\t\") => createExperienceGateway(options);\",\n\t\t\t\t\"\",\n\t\t\t].join(\"\\n\"),\n\t\t],\n\t\t[\n\t\t\tpath.join(options.directory, \"fabric.component-pack.ts\"),\n\t\t\t[\n\t\t\t\t'import { defineComponentPack } from \"@fabricorg/component-pack\";',\n\t\t\t\t\"\",\n\t\t\t\t`export const define${functionName}ComponentPack = (artifactDigest: string) =>`,\n\t\t\t\t\"\\tdefineComponentPack({\",\n\t\t\t\t`\\t\\tname: \"${options.application}\",`,\n\t\t\t\t`\\t\\tnamespace: \"${options.application}\",`,\n\t\t\t\t'\\t\\tversion: \"0.1.0\",',\n\t\t\t\t\"\\t\\tartifactDigest,\",\n\t\t\t\t'\\t\\ttokenContract: { name: \"fabric-family\", version: \"1.0.0\" },',\n\t\t\t\t\"\\t\\tcomponents: {\",\n\t\t\t\t'\\t\\t\\tText: { module: \"./components/text.js\", roles: [\"content\"], slots: [] },',\n\t\t\t\t\"\\t\\t},\",\n\t\t\t\t\"\\t});\",\n\t\t\t\t\"\",\n\t\t\t].join(\"\\n\"),\n\t\t],\n\t\t[\n\t\t\tpath.join(options.directory, \"fabric.application.json\"),\n\t\t\t`${JSON.stringify({\n\t\t\t\tformatVersion: 1,\n\t\t\t\tapplication: options.application,\n\t\t\t\tassembly: \"./fabric.assembly.lock.json\",\n\t\t\t\truntimeCapabilities: \"./fabric.runtime.json\",\n\t\t\t\treleases: [],\n\t\t\t}, null, 2)}\\n`,\n\t\t],\n\t] as const;\n\tawait mkdir(options.directory, { recursive: true });\n\tconst created: string[] = [];\n\ttry {\n\t\tfor (const [file, content] of files) {\n\t\t\tawait writeFile(file, content, { encoding: \"utf8\", flag: \"wx\" });\n\t\t\tcreated.push(file);\n\t\t}\n\t} catch (error) {\n\t\tawait Promise.all(created.map((file) => rm(file, { force: true })));\n\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\tthrow new Error(\"Refusing to overwrite an existing experience integration file.\");\n\t\t}\n\t\tthrow error;\n\t}\n\treturn { created };\n}\n\ninterface ApplicationCheckConfig {\n\tformatVersion: 1;\n\tassembly: string;\n\truntimeCapabilities: string;\n\treleases: string[];\n}\n\nasync function readJson(file: string): Promise<unknown> {\n\treturn JSON.parse(await readFile(file, \"utf8\"));\n}\n\nfunction assertApplicationCheckConfig(value: unknown): asserts value is ApplicationCheckConfig {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Application check configuration must be a JSON object.\");\n\t}\n\tconst config = value as Record<string, unknown>;\n\tif (config.formatVersion !== 1) throw new Error(\"Application check configuration formatVersion must be 1.\");\n\tif (typeof config.assembly !== \"string\" || !config.assembly) {\n\t\tthrow new Error(\"Application check configuration assembly must be a file path.\");\n\t}\n\tif (typeof config.runtimeCapabilities !== \"string\" || !config.runtimeCapabilities) {\n\t\tthrow new Error(\"Application check configuration runtimeCapabilities must be a file path.\");\n\t}\n\tif (!Array.isArray(config.releases) || !config.releases.every((entry) => typeof entry === \"string\" && entry)) {\n\t\tthrow new Error(\"Application check configuration releases must be an array of file paths.\");\n\t}\n}\n\n/**\n * Verify an application's immutable composition inputs before deployment.\n * Every runtime capability must exactly match the approved lockfile, and every\n * promoted experience release must be intact and bound to that assembly.\n */\nexport async function checkApplication(\n\toptions: CheckApplicationOptions,\n): Promise<CheckApplicationResult> {\n\tconst configValue = await readJson(options.configPath);\n\tassertApplicationCheckConfig(configValue);\n\tconst base = path.dirname(options.configPath);\n\tconst resolve = (file: string): string => path.resolve(base, file);\n\tconst assembly = await readJson(resolve(configValue.assembly)) as AssemblyLockfile;\n\tassertAssemblyLockfile(assembly);\n\tconst runtimeCapabilities = await readJson(\n\t\tresolve(configValue.runtimeCapabilities),\n\t) as AvailableCapability[];\n\tif (!Array.isArray(runtimeCapabilities)) {\n\t\tthrow new Error(\"Runtime capabilities must be a JSON array.\");\n\t}\n\tassertAssemblyRuntimeCompatible(assembly, runtimeCapabilities);\n\tfor (const releasePath of configValue.releases) {\n\t\tconst release = await readJson(resolve(releasePath)) as SduiRelease;\n\t\ttry {\n\t\t\tassertSduiReleaseIntegrity(release);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Experience release \"${releasePath}\" is invalid: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tif (release.assemblyDigest !== assembly.assemblyDigest) {\n\t\t\tthrow new Error(\n\t\t\t\t`Experience release \"${release.document.name}@${release.document.version}\" belongs to assembly \"${release.assemblyDigest}\", not \"${assembly.assemblyDigest}\".`,\n\t\t\t);\n\t\t}\n\t}\n\treturn {\n\t\tapplication: assembly.application,\n\t\tcapabilities: assembly.capabilities.length,\n\t\treleases: configValue.releases.length,\n\t\tassemblyDigest: assembly.assemblyDigest,\n\t};\n}\n\n/**\n * Create the smallest useful capability source tree. The manifest is accepted\n * by the compiler immediately, while leaving domain vocabulary to the adopter.\n */\nexport async function initializeCapability(\n\toptions: InitializeCapabilityOptions,\n): Promise<InitializeCapabilityResult> {\n\tif (!/^[a-z][a-z0-9-]*$/.test(options.namespace)) {\n\t\tthrow new Error(\"Capability namespace must use lowercase letters, numbers, and hyphens.\");\n\t}\n\tconst displayName = options.namespace\n\t\t.split(\"-\")\n\t\t.map((part, index) => index === 0\n\t\t\t? `${part.charAt(0).toUpperCase()}${part.slice(1)}`\n\t\t\t: part)\n\t\t.join(\" \");\n\tconst manifestPath = path.join(options.directory, \"fabric.manifest.json\");\n\tconst readmePath = path.join(options.directory, \"README.md\");\n\tfor (const file of [readmePath, manifestPath]) {\n\t\ttry {\n\t\t\tawait access(file);\n\t\t\tthrow new Error(`Refusing to overwrite existing file \"${file}\".`);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && error.message.startsWith(\"Refusing to overwrite\")) throw error;\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t}\n\t}\n\tconst manifest = {\n\t\tmanifestVersion: 2,\n\t\tnamespace: options.namespace,\n\t\tobjectTypes: [],\n\t\tsubjectTypes: [],\n\t\tidPrefixes: [],\n\t\tdependencies: [],\n\t\tpolicies: [],\n\t\teventTypes: [],\n\t\tactions: [],\n\t\tviews: [],\n\t\tstateMachines: [],\n\t\tcapability: {\n\t\t\tdisplayName,\n\t\t\tdescription: `Governed ${displayName.toLowerCase()} capability.`,\n\t\t\tstability: \"experimental\",\n\t\t},\n\t};\n\tconst readme = [\n\t\t`# ${displayName}`,\n\t\t\"\",\n\t\t\"Define domain objects, governed actions, events, and projections in `fabric.manifest.json`.\",\n\t\t\"\",\n\t\t\"```bash\",\n\t\t\"fabric-gen --input fabric.manifest.json --out generated\",\n\t\t\"fabric-gen --input fabric.manifest.json --out generated --check\",\n\t\t\"```\",\n\t\t\"\",\n\t].join(\"\\n\");\n\tawait mkdir(options.directory, { recursive: true });\n\tconst created: string[] = [];\n\ttry {\n\t\tfor (const [file, content] of [\n\t\t\t[readmePath, readme],\n\t\t\t[manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`],\n\t\t] as const) {\n\t\t\tawait writeFile(file, content, { encoding: \"utf8\", flag: \"wx\" });\n\t\t\tcreated.push(file);\n\t\t}\n\t} catch (error) {\n\t\tawait Promise.all(created.map((file) => rm(file, { force: true })));\n\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\tthrow new Error(\"Refusing to overwrite a file created during capability initialization.\");\n\t\t}\n\t\tthrow error;\n\t}\n\treturn { created };\n}\n\nexport async function generateFromManifest(options: GenerateOptions): Promise<GenerateResult> {\n\tconst input = JSON.parse(await readFile(options.inputPath, \"utf8\"));\n\tif (!input || typeof input !== \"object\" || Array.isArray(input)) throw new Error(\"Manifest input must be a JSON object.\");\n\tconst bundle = \"modules\" in input ? input : { manifestVersion: input.manifestVersion, modules: [input] };\n\tconst ir = compileManifestBundle(bundle);\n\tconst files = (await Promise.all(options.targets.map(async (name) => {\n\t\tconst target = GENERATOR_TARGETS[name];\n\t\tif (!target) throw new Error(`Unknown generator target \"${name}\".`);\n\t\treturn (await runGenerator(target, ir)).map((file) => ({ ...file, path: `${name}/${file.path}` }));\n\t}))).flat().sort((left, right) => compareCanonicalStrings(left.path, right.path));\n\tconst expected = new Set(files.map((file) => file.path));\n\tconst existing = await listFiles(options.outputDirectory);\n\tconst selectedPrefixes = options.targets.map((target) => `${target}/`);\n\tconst stale = existing.filter((file) => selectedPrefixes.some((prefix) => file.startsWith(prefix)) && !expected.has(file));\n\tif (!options.check) await Promise.all(stale.map((file) => rm(path.join(options.outputDirectory, file), { force: true })));\n\tconst changed: string[] = [];\n\tfor (const file of files) {\n\t\tconst target = path.join(options.outputDirectory, file.path);\n\t\tlet current: string | undefined;\n\t\ttry { current = await readFile(target, \"utf8\"); } catch { current = undefined; }\n\t\tif (current !== file.content) {\n\t\t\tchanged.push(file.path);\n\t\t\tif (!options.check) { await mkdir(path.dirname(target), { recursive: true }); await writeFile(target, file.content, \"utf8\"); }\n\t\t}\n\t}\n\tif (options.check && (changed.length || stale.length)) throw new Error(`Generated artifacts drifted: ${[...changed, ...stale.map((file) => `stale:${file}`)].join(\", \")}`);\n\treturn { changed, stale, files };\n}\n\nasync function listFiles(directory: string, prefix = \"\"): Promise<string[]> {\n\tlet entries;\n\ttry { entries = await readdir(directory, { withFileTypes: true }); } catch { return []; }\n\tconst nested = await Promise.all(entries.map((entry) => entry.isDirectory() ? listFiles(path.join(directory, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]));\n\treturn nested.flat().sort();\n}\n\ninterface ExperiencePromotionConfig {\n\tformatVersion: 1;\n\tapplication: string;\n\tissuer: string;\n\tkeyId: string;\n\tassembly: string;\n\trelease: string;\n\tcontracts: string[];\n}\n\nfunction assertExperiencePromotionConfig(value: unknown): asserts value is ExperiencePromotionConfig {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Experience promotion configuration must be a JSON object.\");\n\t}\n\tconst config = value as Record<string, unknown>;\n\tif (config.formatVersion !== 1) throw new Error(\"Experience promotion configuration formatVersion must be 1.\");\n\tfor (const field of [\"application\", \"issuer\", \"keyId\", \"assembly\", \"release\"] as const) {\n\t\tif (typeof config[field] !== \"string\" || !config[field]) {\n\t\t\tthrow new Error(`Experience promotion configuration ${field} must be a non-empty string.`);\n\t\t}\n\t}\n\t// The application name is interpolated into the default output path and\n\t// signed into the release body, so \"../../x\" would escape the configuration\n\t// directory and \"/etc/x\" would ignore it entirely.\n\tif (!/^[a-z][a-z0-9-]*$/.test(config.application as string)) {\n\t\tthrow new Error(\"Experience promotion configuration application must be lowercase letters, numbers, and hyphens.\");\n\t}\n\tif (!Array.isArray(config.contracts) || !config.contracts.every((entry) => typeof entry === \"string\" && entry)) {\n\t\tthrow new Error(\"Experience promotion configuration contracts must be an array of file paths.\");\n\t}\n\t// A signing key in a checked-in configuration file is a key in version\n\t// control. Enumerating two spellings only catches two spellings, so reject\n\t// anything that reads like one, and reject unknown fields so a future\n\t// spelling cannot arrive unnoticed.\n\tconst known = new Set([\"formatVersion\", \"application\", \"issuer\", \"keyId\", \"assembly\", \"release\", \"contracts\"]);\n\tfor (const field of Object.keys(config)) {\n\t\tif (/key|secret|credential|token|pem|password/i.test(field) && field !== \"keyId\") {\n\t\t\tthrow new Error(`Experience promotion configuration must not carry \"${field}\"; pass the signing key with --key.`);\n\t\t}\n\t\tif (!known.has(field)) {\n\t\t\tthrow new Error(`Experience promotion configuration has an unknown field \"${field}\".`);\n\t\t}\n\t}\n}\n\n/**\n * Turn a validated release into a signed one.\n *\n * Promotion is where a granted `capability://` reference becomes a concrete\n * action id and version, so it is also where that mapping has to be bound to\n * the assembly that approved it. The signing key is a command-line argument\n * rather than a configuration field, because a configuration file is the kind\n * of thing that ends up committed.\n */\nexport async function promoteExperience(\n\toptions: PromoteExperienceOptions,\n): Promise<PromoteExperienceResult> {\n\tconst configValue = await readJson(options.configPath);\n\tassertExperiencePromotionConfig(configValue);\n\tconst base = path.dirname(options.configPath);\n\tconst resolve = (file: string): string => path.resolve(base, file);\n\n\tconst assembly = await readJson(resolve(configValue.assembly)) as AssemblyLockfile;\n\tassertAssemblyLockfile(assembly);\n\tconst release = await readJson(resolve(configValue.release)) as SduiRelease;\n\tassertSduiReleaseIntegrity(release);\n\tconst contracts = await Promise.all(\n\t\tconfigValue.contracts.map(async (file) => await readJson(resolve(file)) as UsageContractDocument),\n\t);\n\n\tconst privateKey = createPrivateKey(await readFile(options.privateKeyPath, \"utf8\"));\n\tconst promoted = await promoteExperienceRelease({\n\t\tapplication: configValue.application,\n\t\trelease,\n\t\tcontracts,\n\t\tassembly,\n\t\tissuer: configValue.issuer,\n\t\tsigner: createEs256ReleaseSigner({ keyId: configValue.keyId, privateKey }),\n\t});\n\n\tconst outputPath = options.outputPath\n\t\t? path.resolve(options.outputPath)\n\t\t: resolve(`${configValue.application}.experience-release.jws`);\n\tawait mkdir(path.dirname(outputPath), { recursive: true });\n\tawait writeFile(outputPath, `${promoted.compactJws}\\n`, \"utf8\");\n\n\treturn {\n\t\tapplication: configValue.application,\n\t\treleaseDigest: release.releaseDigest,\n\t\tassemblyDigest: assembly.assemblyDigest,\n\t\troutes: {\n\t\t\tviews: promoted.release.routes.views.length,\n\t\t\tintents: promoted.release.routes.intents.length,\n\t\t},\n\t\toutputPath,\n\t};\n}\n"],"mappings":"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","names":["result"]}
package/dist/cli.cjs CHANGED
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // index.ts
27
27
  var import_promises = require("fs/promises");
28
+ var import_node_crypto = require("crypto");
28
29
  var import_node_path = __toESM(require("path"), 1);
29
30
  var import_compiler = require("@fabricorg/compiler");
30
31
  var import_gen_capability = require("@fabricorg/gen-capability");
@@ -34,6 +35,7 @@ var import_gen_types = require("@fabricorg/gen-types");
34
35
  var import_gen_prisma = require("@fabricorg/gen-prisma");
35
36
  var import_gen_graphql = require("@fabricorg/gen-graphql");
36
37
  var import_gen_sdui = require("@fabricorg/gen-sdui");
38
+ var import_sdui_composer = require("@fabricorg/sdui-composer");
37
39
  var import_gen_conformance = require("@fabricorg/gen-conformance");
38
40
  var import_assembly = require("@fabricorg/assembly");
39
41
  var import_sdui_release = require("@fabricorg/sdui-release");
@@ -51,7 +53,9 @@ var compareCanonicalStrings = (left, right) => left < right ? -1 : left > right
51
53
  var usage = [
52
54
  "Usage:",
53
55
  " fabric-gen init capability <namespace> [--directory <path>]",
56
+ " fabric-gen init experience <application> [--directory <path>]",
54
57
  " fabric-gen check-application [--config <path>]",
58
+ " fabric-gen promote-experience --config <path> --key <pem> [--out <path>]",
55
59
  " fabric-gen --input <manifest.json> --out <directory> [--targets <list>] [--check]"
56
60
  ].join("\n");
57
61
  var FabricGenUsageError = class extends Error {
@@ -69,13 +73,37 @@ function flagValue(args2, flag) {
69
73
  }
70
74
  async function runFabricGenCommand(args2) {
71
75
  if (args2[0] === "init") {
72
- if (args2[1] !== "capability" || !args2[2] || args2[2].startsWith("--")) {
76
+ if (!args2[2] || args2[2].startsWith("--")) {
73
77
  throw new FabricGenUsageError(usage);
74
78
  }
75
- const namespace = args2[2];
76
- const directory = flagValue(args2, "--directory") ?? namespace;
77
- const result2 = await initializeCapability({ directory, namespace });
78
- return `Initialized capability "${namespace}" with ${result2.created.length} files.`;
79
+ const name = args2[2];
80
+ const directory = flagValue(args2, "--directory") ?? name;
81
+ if (args2[1] === "capability") {
82
+ const result2 = await initializeCapability({ directory, namespace: name });
83
+ return `Initialized capability "${name}" with ${result2.created.length} files.`;
84
+ }
85
+ if (args2[1] === "experience") {
86
+ const result2 = await initializeExperience({ directory, application: name });
87
+ return `Initialized experience integration "${name}" with ${result2.created.length} files.`;
88
+ }
89
+ throw new FabricGenUsageError(usage);
90
+ }
91
+ if (args2[0] === "promote-experience") {
92
+ const configPath = flagValue(args2, "--config");
93
+ const privateKeyPath = flagValue(args2, "--key");
94
+ if (!configPath) throw new FabricGenUsageError("promote-experience requires --config.");
95
+ if (!privateKeyPath) throw new FabricGenUsageError("promote-experience requires --key.");
96
+ const outputPath = flagValue(args2, "--out");
97
+ const result2 = await promoteExperience({
98
+ configPath,
99
+ privateKeyPath,
100
+ ...outputPath ? { outputPath } : {}
101
+ });
102
+ return [
103
+ `Promoted ${result2.application} release ${result2.releaseDigest.slice(0, 12)} against assembly ${result2.assemblyDigest.slice(0, 12)}.`,
104
+ `Routes: ${result2.routes.views} views, ${result2.routes.intents} intents.`,
105
+ `Wrote ${result2.outputPath}`
106
+ ].join("\n");
79
107
  }
80
108
  if (args2[0] === "check-application") {
81
109
  const configPath = flagValue(args2, "--config") ?? "fabric.application.json";
@@ -93,6 +121,75 @@ async function runFabricGenCommand(args2) {
93
121
  });
94
122
  return `${args2.includes("--check") ? "Checked" : "Generated"} ${result.files.length} files.`;
95
123
  }
124
+ async function initializeExperience(options) {
125
+ if (!/^[a-z][a-z0-9-]*$/.test(options.application)) {
126
+ throw new Error("Application name must use lowercase letters, numbers, and hyphens.");
127
+ }
128
+ const functionName = options.application.split("-").map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
129
+ const files = [
130
+ [
131
+ import_node_path.default.join(options.directory, "fabric.experience.ts"),
132
+ [
133
+ 'import { createExperienceServerRuntime } from "@fabricorg/experience-runtime/server";',
134
+ 'import { createExperienceGateway } from "@fabricorg/experience-gateway";',
135
+ "",
136
+ `export const create${functionName}ExperienceRuntime = (`,
137
+ " options: Parameters<typeof createExperienceServerRuntime>[0],",
138
+ ") => createExperienceServerRuntime(options);",
139
+ "",
140
+ `export const create${functionName}ExperienceGateway = (`,
141
+ " options: Parameters<typeof createExperienceGateway>[0],",
142
+ ") => createExperienceGateway(options);",
143
+ ""
144
+ ].join("\n")
145
+ ],
146
+ [
147
+ import_node_path.default.join(options.directory, "fabric.component-pack.ts"),
148
+ [
149
+ 'import { defineComponentPack } from "@fabricorg/component-pack";',
150
+ "",
151
+ `export const define${functionName}ComponentPack = (artifactDigest: string) =>`,
152
+ " defineComponentPack({",
153
+ ` name: "${options.application}",`,
154
+ ` namespace: "${options.application}",`,
155
+ ' version: "0.1.0",',
156
+ " artifactDigest,",
157
+ ' tokenContract: { name: "fabric-family", version: "1.0.0" },',
158
+ " components: {",
159
+ ' Text: { module: "./components/text.js", roles: ["content"], slots: [] },',
160
+ " },",
161
+ " });",
162
+ ""
163
+ ].join("\n")
164
+ ],
165
+ [
166
+ import_node_path.default.join(options.directory, "fabric.application.json"),
167
+ `${JSON.stringify({
168
+ formatVersion: 1,
169
+ application: options.application,
170
+ assembly: "./fabric.assembly.lock.json",
171
+ runtimeCapabilities: "./fabric.runtime.json",
172
+ releases: []
173
+ }, null, 2)}
174
+ `
175
+ ]
176
+ ];
177
+ await (0, import_promises.mkdir)(options.directory, { recursive: true });
178
+ const created = [];
179
+ try {
180
+ for (const [file, content] of files) {
181
+ await (0, import_promises.writeFile)(file, content, { encoding: "utf8", flag: "wx" });
182
+ created.push(file);
183
+ }
184
+ } catch (error) {
185
+ await Promise.all(created.map((file) => (0, import_promises.rm)(file, { force: true })));
186
+ if (error.code === "EEXIST") {
187
+ throw new Error("Refusing to overwrite an existing experience integration file.");
188
+ }
189
+ throw error;
190
+ }
191
+ return { created };
192
+ }
96
193
  async function readJson(file) {
97
194
  return JSON.parse(await (0, import_promises.readFile)(file, "utf8"));
98
195
  }
@@ -258,6 +355,69 @@ async function listFiles(directory, prefix = "") {
258
355
  const nested = await Promise.all(entries.map((entry) => entry.isDirectory() ? listFiles(import_node_path.default.join(directory, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]));
259
356
  return nested.flat().sort();
260
357
  }
358
+ function assertExperiencePromotionConfig(value) {
359
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
360
+ throw new Error("Experience promotion configuration must be a JSON object.");
361
+ }
362
+ const config = value;
363
+ if (config.formatVersion !== 1) throw new Error("Experience promotion configuration formatVersion must be 1.");
364
+ for (const field of ["application", "issuer", "keyId", "assembly", "release"]) {
365
+ if (typeof config[field] !== "string" || !config[field]) {
366
+ throw new Error(`Experience promotion configuration ${field} must be a non-empty string.`);
367
+ }
368
+ }
369
+ if (!/^[a-z][a-z0-9-]*$/.test(config.application)) {
370
+ throw new Error("Experience promotion configuration application must be lowercase letters, numbers, and hyphens.");
371
+ }
372
+ if (!Array.isArray(config.contracts) || !config.contracts.every((entry) => typeof entry === "string" && entry)) {
373
+ throw new Error("Experience promotion configuration contracts must be an array of file paths.");
374
+ }
375
+ const known = /* @__PURE__ */ new Set(["formatVersion", "application", "issuer", "keyId", "assembly", "release", "contracts"]);
376
+ for (const field of Object.keys(config)) {
377
+ if (/key|secret|credential|token|pem|password/i.test(field) && field !== "keyId") {
378
+ throw new Error(`Experience promotion configuration must not carry "${field}"; pass the signing key with --key.`);
379
+ }
380
+ if (!known.has(field)) {
381
+ throw new Error(`Experience promotion configuration has an unknown field "${field}".`);
382
+ }
383
+ }
384
+ }
385
+ async function promoteExperience(options) {
386
+ const configValue = await readJson(options.configPath);
387
+ assertExperiencePromotionConfig(configValue);
388
+ const base = import_node_path.default.dirname(options.configPath);
389
+ const resolve = (file) => import_node_path.default.resolve(base, file);
390
+ const assembly = await readJson(resolve(configValue.assembly));
391
+ (0, import_assembly.assertAssemblyLockfile)(assembly);
392
+ const release = await readJson(resolve(configValue.release));
393
+ (0, import_sdui_release.assertSduiReleaseIntegrity)(release);
394
+ const contracts = await Promise.all(
395
+ configValue.contracts.map(async (file) => await readJson(resolve(file)))
396
+ );
397
+ const privateKey = (0, import_node_crypto.createPrivateKey)(await (0, import_promises.readFile)(options.privateKeyPath, "utf8"));
398
+ const promoted = await (0, import_sdui_composer.promoteExperienceRelease)({
399
+ application: configValue.application,
400
+ release,
401
+ contracts,
402
+ assembly,
403
+ issuer: configValue.issuer,
404
+ signer: (0, import_sdui_composer.createEs256ReleaseSigner)({ keyId: configValue.keyId, privateKey })
405
+ });
406
+ const outputPath = options.outputPath ? import_node_path.default.resolve(options.outputPath) : resolve(`${configValue.application}.experience-release.jws`);
407
+ await (0, import_promises.mkdir)(import_node_path.default.dirname(outputPath), { recursive: true });
408
+ await (0, import_promises.writeFile)(outputPath, `${promoted.compactJws}
409
+ `, "utf8");
410
+ return {
411
+ application: configValue.application,
412
+ releaseDigest: release.releaseDigest,
413
+ assemblyDigest: assembly.assemblyDigest,
414
+ routes: {
415
+ views: promoted.release.routes.views.length,
416
+ intents: promoted.release.routes.intents.length
417
+ },
418
+ outputPath
419
+ };
420
+ }
261
421
 
262
422
  // cli.ts
263
423
  var args = process.argv.slice(2);