@fabricorg/fabric-gen 0.7.6 → 0.7.8
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 +60 -0
- package/dist/cli.cjs +8 -631
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +8 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +587 -611
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -65
- package/dist/index.d.ts +64 -65
- package/dist/index.js +599 -23
- package/dist/index.js.map +1 -1
- package/package.json +19 -18
- package/dist/chunk-LHZN67DB.js +0 -621
- package/dist/chunk-LHZN67DB.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,658 +1,634 @@
|
|
|
1
|
-
"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
9
|
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
19
18
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
var import_gen_events = require("@fabricorg/gen-events");
|
|
54
|
-
var import_gen_types = require("@fabricorg/gen-types");
|
|
55
|
-
var import_gen_prisma = require("@fabricorg/gen-prisma");
|
|
56
|
-
var import_gen_graphql = require("@fabricorg/gen-graphql");
|
|
57
|
-
var import_gen_sdui = require("@fabricorg/gen-sdui");
|
|
58
|
-
var import_sdui_composer = require("@fabricorg/sdui-composer");
|
|
59
|
-
var import_gen_conformance = require("@fabricorg/gen-conformance");
|
|
60
|
-
var import_assembly = require("@fabricorg/assembly");
|
|
61
|
-
var import_sdui_release = require("@fabricorg/sdui-release");
|
|
62
|
-
var GENERATOR_TARGETS = {
|
|
63
|
-
types: import_gen_types.typesGenerator,
|
|
64
|
-
events: import_gen_events.eventsGenerator,
|
|
65
|
-
capability: import_gen_capability.capabilityGenerator,
|
|
66
|
-
docs: import_gen_docs.docsGenerator,
|
|
67
|
-
prisma: import_gen_prisma.prismaGenerator,
|
|
68
|
-
graphql: import_gen_graphql.graphqlGenerator,
|
|
69
|
-
sdui: import_gen_sdui.sduiGenerator,
|
|
70
|
-
conformance: import_gen_conformance.conformanceGenerator
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let fs_promises = require("fs/promises");
|
|
25
|
+
let crypto = require("crypto");
|
|
26
|
+
let path = require("path");
|
|
27
|
+
path = __toESM(path, 1);
|
|
28
|
+
let url = require("url");
|
|
29
|
+
let _fabricorg_compiler = require("@fabricorg/compiler");
|
|
30
|
+
let _fabricorg_platform_manifest = require("@fabricorg/platform/manifest");
|
|
31
|
+
let _fabricorg_gen_capability = require("@fabricorg/gen-capability");
|
|
32
|
+
let _fabricorg_gen_docs = require("@fabricorg/gen-docs");
|
|
33
|
+
let _fabricorg_gen_events = require("@fabricorg/gen-events");
|
|
34
|
+
let _fabricorg_gen_types = require("@fabricorg/gen-types");
|
|
35
|
+
let _fabricorg_gen_prisma = require("@fabricorg/gen-prisma");
|
|
36
|
+
let _fabricorg_gen_graphql = require("@fabricorg/gen-graphql");
|
|
37
|
+
let _fabricorg_gen_sdui = require("@fabricorg/gen-sdui");
|
|
38
|
+
let _fabricorg_sdui_composer = require("@fabricorg/sdui-composer");
|
|
39
|
+
let _fabricorg_gen_conformance = require("@fabricorg/gen-conformance");
|
|
40
|
+
let _fabricorg_assembly = require("@fabricorg/assembly");
|
|
41
|
+
let _fabricorg_sdui_release = require("@fabricorg/sdui-release");
|
|
42
|
+
//#region index.ts
|
|
43
|
+
const GENERATOR_TARGETS = {
|
|
44
|
+
types: _fabricorg_gen_types.typesGenerator,
|
|
45
|
+
events: _fabricorg_gen_events.eventsGenerator,
|
|
46
|
+
capability: _fabricorg_gen_capability.capabilityGenerator,
|
|
47
|
+
docs: _fabricorg_gen_docs.docsGenerator,
|
|
48
|
+
prisma: _fabricorg_gen_prisma.prismaGenerator,
|
|
49
|
+
graphql: _fabricorg_gen_graphql.graphqlGenerator,
|
|
50
|
+
sdui: _fabricorg_gen_sdui.sduiGenerator,
|
|
51
|
+
conformance: _fabricorg_gen_conformance.conformanceGenerator
|
|
71
52
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
53
|
+
const compareCanonicalStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
54
|
+
const usage = [
|
|
55
|
+
"Usage:",
|
|
56
|
+
" fabric-gen init capability <namespace> [--directory <path>]",
|
|
57
|
+
" fabric-gen export --module <file> [--export <name>] --out <manifest.json>",
|
|
58
|
+
" fabric-gen init experience <application> [--directory <path>]",
|
|
59
|
+
" fabric-gen check-application [--config <path>]",
|
|
60
|
+
" fabric-gen promote-experience --config <path> --key <pem> [--out <path>]",
|
|
61
|
+
" fabric-gen --input <manifest.json> --out <directory> [--targets <list>] [--check]"
|
|
81
62
|
].join("\n");
|
|
82
63
|
var FabricGenUsageError = class extends Error {
|
|
83
|
-
|
|
64
|
+
name = "FabricGenUsageError";
|
|
84
65
|
};
|
|
85
66
|
function fabricGenCliExitCode(error) {
|
|
86
|
-
|
|
67
|
+
return error instanceof FabricGenUsageError ? 2 : 1;
|
|
87
68
|
}
|
|
88
69
|
function flagValue(args, flag) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
const index = args.indexOf(flag);
|
|
71
|
+
if (index < 0) return void 0;
|
|
72
|
+
const value = args[index + 1];
|
|
73
|
+
if (!value || value.startsWith("--")) throw new FabricGenUsageError(usage);
|
|
74
|
+
return value;
|
|
94
75
|
}
|
|
76
|
+
/** Execute one CLI command and return its single human-readable result line. */
|
|
95
77
|
async function runFabricGenCommand(args) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
78
|
+
if (args[0] === "export") {
|
|
79
|
+
const moduleFile = flagValue(args, "--module");
|
|
80
|
+
const out = flagValue(args, "--out");
|
|
81
|
+
if (!moduleFile || !out) throw new FabricGenUsageError(usage);
|
|
82
|
+
const result = await exportModuleManifestFile({
|
|
83
|
+
moduleFile,
|
|
84
|
+
out,
|
|
85
|
+
exportName: flagValue(args, "--export")
|
|
86
|
+
});
|
|
87
|
+
return `Exported "${result.namespace}" from ${result.exportName} to ${out}: ${result.objectTypes} object types, ${result.actions} actions, ${result.eventTypes} event types.`;
|
|
88
|
+
}
|
|
89
|
+
if (args[0] === "init") {
|
|
90
|
+
if (!args[2] || args[2].startsWith("--")) throw new FabricGenUsageError(usage);
|
|
91
|
+
const name = args[2];
|
|
92
|
+
const directory = flagValue(args, "--directory") ?? name;
|
|
93
|
+
if (args[1] === "capability") return `Initialized capability "${name}" with ${(await initializeCapability({
|
|
94
|
+
directory,
|
|
95
|
+
namespace: name
|
|
96
|
+
})).created.length} files.`;
|
|
97
|
+
if (args[1] === "experience") return `Initialized experience integration "${name}" with ${(await initializeExperience({
|
|
98
|
+
directory,
|
|
99
|
+
application: name
|
|
100
|
+
})).created.length} files.`;
|
|
101
|
+
throw new FabricGenUsageError(usage);
|
|
102
|
+
}
|
|
103
|
+
if (args[0] === "promote-experience") {
|
|
104
|
+
const configPath = flagValue(args, "--config");
|
|
105
|
+
const privateKeyPath = flagValue(args, "--key");
|
|
106
|
+
if (!configPath) throw new FabricGenUsageError("promote-experience requires --config.");
|
|
107
|
+
if (!privateKeyPath) throw new FabricGenUsageError("promote-experience requires --key.");
|
|
108
|
+
const outputPath = flagValue(args, "--out");
|
|
109
|
+
const result = await promoteExperience({
|
|
110
|
+
configPath,
|
|
111
|
+
privateKeyPath,
|
|
112
|
+
...outputPath ? { outputPath } : {}
|
|
113
|
+
});
|
|
114
|
+
return [
|
|
115
|
+
`Promoted ${result.application} release ${result.releaseDigest.slice(0, 12)} against assembly ${result.assemblyDigest.slice(0, 12)}.`,
|
|
116
|
+
`Routes: ${result.routes.views} views, ${result.routes.intents} intents.`,
|
|
117
|
+
`Wrote ${result.outputPath}`
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
120
|
+
if (args[0] === "check-application") {
|
|
121
|
+
const result = await checkApplication({ configPath: flagValue(args, "--config") ?? "fabric.application.json" });
|
|
122
|
+
return `Checked "${result.application}": ${result.capabilities} capabilities, ${result.releases} releases.`;
|
|
123
|
+
}
|
|
124
|
+
const inputPath = flagValue(args, "--input");
|
|
125
|
+
const outputDirectory = flagValue(args, "--out");
|
|
126
|
+
if (!inputPath || !outputDirectory) throw new FabricGenUsageError(usage);
|
|
127
|
+
const result = await generateFromManifest({
|
|
128
|
+
inputPath,
|
|
129
|
+
outputDirectory,
|
|
130
|
+
targets: (flagValue(args, "--targets") ?? "types,events,capability,docs").split(",").filter(Boolean),
|
|
131
|
+
check: args.includes("--check")
|
|
132
|
+
});
|
|
133
|
+
return `${args.includes("--check") ? "Checked" : "Generated"} ${result.files.length} files.`;
|
|
151
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Add experience integration files to an existing application. It deliberately
|
|
137
|
+
* does not create package.json, routes, authentication, billing, or navigation:
|
|
138
|
+
* those remain owned by the Supastarter-derived application shell.
|
|
139
|
+
*/
|
|
140
|
+
/** What the scaffolded files are, and what has to be installed for them to compile. */
|
|
152
141
|
function experienceReadme(application) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
142
|
+
return [
|
|
143
|
+
`# ${application} experience integration`,
|
|
144
|
+
"",
|
|
145
|
+
"| File | What it is |",
|
|
146
|
+
"| --- | --- |",
|
|
147
|
+
"| `fabric.experience.ts` | The server runtime and gateway, wired to your host. |",
|
|
148
|
+
"| `fabric.component-pack.ts` | The components this application is allowed to render. |",
|
|
149
|
+
"| `fabric.application.json` | What the deployment gate reads. |",
|
|
150
|
+
"| `fabric.assembly.lock.json` | The resolved assembly. Empty until you add capabilities. |",
|
|
151
|
+
"| `fabric.runtime.json` | Capabilities the running host actually registers. |",
|
|
152
|
+
"",
|
|
153
|
+
"## Install",
|
|
154
|
+
"",
|
|
155
|
+
"The scaffolded files import three packages this generator does not install:",
|
|
156
|
+
"",
|
|
157
|
+
"```bash",
|
|
158
|
+
"pnpm add @fabricorg/experience-runtime @fabricorg/experience-gateway @fabricorg/component-pack",
|
|
159
|
+
"```",
|
|
160
|
+
"",
|
|
161
|
+
"## Check it",
|
|
162
|
+
"",
|
|
163
|
+
"```bash",
|
|
164
|
+
"fabric-gen check-application --config fabric.application.json",
|
|
165
|
+
"```",
|
|
166
|
+
"",
|
|
167
|
+
"It passes on the scaffold and keeps passing as you add capabilities, as long",
|
|
168
|
+
"as the lockfile, the runtime capabilities, and every promoted release agree.",
|
|
169
|
+
"Regenerate the lockfile from your composition root with `resolveAssembly`;",
|
|
170
|
+
"`fabric.runtime.json` must list what that same root registers with the host,",
|
|
171
|
+
"which is what stops a deployment from serving a plan its host cannot answer.",
|
|
172
|
+
""
|
|
173
|
+
].join("\n");
|
|
185
174
|
}
|
|
186
175
|
async function initializeExperience(options) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
`
|
|
258
|
-
],
|
|
259
|
-
[
|
|
260
|
-
import_node_path.default.join(options.directory, "fabric.runtime.json"),
|
|
261
|
-
"[]\n"
|
|
262
|
-
],
|
|
263
|
-
[
|
|
264
|
-
import_node_path.default.join(options.directory, "README.md"),
|
|
265
|
-
experienceReadme(options.application)
|
|
266
|
-
]
|
|
267
|
-
];
|
|
268
|
-
await (0, import_promises.mkdir)(options.directory, { recursive: true });
|
|
269
|
-
const created = [];
|
|
270
|
-
try {
|
|
271
|
-
for (const [file, content] of files) {
|
|
272
|
-
await (0, import_promises.writeFile)(file, content, { encoding: "utf8", flag: "wx" });
|
|
273
|
-
created.push(file);
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
await Promise.all(created.map((file) => (0, import_promises.rm)(file, { force: true })));
|
|
277
|
-
if (error.code === "EEXIST") {
|
|
278
|
-
throw new Error("Refusing to overwrite an existing experience integration file.");
|
|
279
|
-
}
|
|
280
|
-
throw error;
|
|
281
|
-
}
|
|
282
|
-
return { created };
|
|
176
|
+
if (!/^[a-z][a-z0-9-]*$/.test(options.application)) throw new Error("Application name must use lowercase letters, numbers, and hyphens.");
|
|
177
|
+
const functionName = options.application.split("-").map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
178
|
+
const files = [
|
|
179
|
+
[path.default.join(options.directory, "fabric.experience.ts"), [
|
|
180
|
+
"import { createExperienceServerRuntime } from \"@fabricorg/experience-runtime/server\";",
|
|
181
|
+
"import { createExperienceGateway } from \"@fabricorg/experience-gateway\";",
|
|
182
|
+
"",
|
|
183
|
+
`export const create${functionName}ExperienceRuntime = (`,
|
|
184
|
+
" options: Parameters<typeof createExperienceServerRuntime>[0],",
|
|
185
|
+
") => createExperienceServerRuntime(options);",
|
|
186
|
+
"",
|
|
187
|
+
`export const create${functionName}ExperienceGateway = (`,
|
|
188
|
+
" options: Parameters<typeof createExperienceGateway>[0],",
|
|
189
|
+
") => createExperienceGateway(options);",
|
|
190
|
+
""
|
|
191
|
+
].join("\n")],
|
|
192
|
+
[path.default.join(options.directory, "fabric.component-pack.ts"), [
|
|
193
|
+
"import { defineComponentPack } from \"@fabricorg/component-pack\";",
|
|
194
|
+
"",
|
|
195
|
+
`export const define${functionName}ComponentPack = (artifactDigest: string) =>`,
|
|
196
|
+
" defineComponentPack({",
|
|
197
|
+
`\t\tname: "${options.application}",`,
|
|
198
|
+
`\t\tnamespace: "${options.application}",`,
|
|
199
|
+
" version: \"0.1.0\",",
|
|
200
|
+
" artifactDigest,",
|
|
201
|
+
" tokenContract: { name: \"fabric-family\", version: \"1.0.0\" },",
|
|
202
|
+
" components: {",
|
|
203
|
+
" Text: { module: \"./components/text.js\", roles: [\"content\"], slots: [] },",
|
|
204
|
+
" },",
|
|
205
|
+
" });",
|
|
206
|
+
""
|
|
207
|
+
].join("\n")],
|
|
208
|
+
[path.default.join(options.directory, "fabric.application.json"), `${JSON.stringify({
|
|
209
|
+
formatVersion: 1,
|
|
210
|
+
application: options.application,
|
|
211
|
+
assembly: "./fabric.assembly.lock.json",
|
|
212
|
+
runtimeCapabilities: "./fabric.runtime.json",
|
|
213
|
+
releases: []
|
|
214
|
+
}, null, 2)}\n`],
|
|
215
|
+
[path.default.join(options.directory, "fabric.assembly.lock.json"), `${JSON.stringify((0, _fabricorg_assembly.resolveAssembly)({
|
|
216
|
+
manifest: {
|
|
217
|
+
formatVersion: 2,
|
|
218
|
+
application: options.application,
|
|
219
|
+
capabilities: [],
|
|
220
|
+
componentPacks: [],
|
|
221
|
+
bindings: [],
|
|
222
|
+
contracts: []
|
|
223
|
+
},
|
|
224
|
+
capabilities: [],
|
|
225
|
+
componentPacks: []
|
|
226
|
+
}).lockfile, null, 2)}\n`],
|
|
227
|
+
[path.default.join(options.directory, "fabric.runtime.json"), "[]\n"],
|
|
228
|
+
[path.default.join(options.directory, "README.md"), experienceReadme(options.application)]
|
|
229
|
+
];
|
|
230
|
+
await (0, fs_promises.mkdir)(options.directory, { recursive: true });
|
|
231
|
+
const created = [];
|
|
232
|
+
try {
|
|
233
|
+
for (const [file, content] of files) {
|
|
234
|
+
await (0, fs_promises.writeFile)(file, content, {
|
|
235
|
+
encoding: "utf8",
|
|
236
|
+
flag: "wx"
|
|
237
|
+
});
|
|
238
|
+
created.push(file);
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
await Promise.all(created.map((file) => (0, fs_promises.rm)(file, { force: true })));
|
|
242
|
+
if (error.code === "EEXIST") throw new Error("Refusing to overwrite an existing experience integration file.");
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
return { created };
|
|
283
246
|
}
|
|
284
247
|
async function readJson(file) {
|
|
285
|
-
|
|
248
|
+
return JSON.parse(await (0, fs_promises.readFile)(file, "utf8"));
|
|
286
249
|
}
|
|
287
250
|
function assertApplicationCheckConfig(value) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
throw new Error("Application check configuration assembly must be a file path.");
|
|
295
|
-
}
|
|
296
|
-
if (typeof config.runtimeCapabilities !== "string" || !config.runtimeCapabilities) {
|
|
297
|
-
throw new Error("Application check configuration runtimeCapabilities must be a file path.");
|
|
298
|
-
}
|
|
299
|
-
if (!Array.isArray(config.releases) || !config.releases.every((entry) => typeof entry === "string" && entry)) {
|
|
300
|
-
throw new Error("Application check configuration releases must be an array of file paths.");
|
|
301
|
-
}
|
|
251
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Application check configuration must be a JSON object.");
|
|
252
|
+
const config = value;
|
|
253
|
+
if (config.formatVersion !== 1) throw new Error("Application check configuration formatVersion must be 1.");
|
|
254
|
+
if (typeof config.assembly !== "string" || !config.assembly) throw new Error("Application check configuration assembly must be a file path.");
|
|
255
|
+
if (typeof config.runtimeCapabilities !== "string" || !config.runtimeCapabilities) throw new Error("Application check configuration runtimeCapabilities must be a file path.");
|
|
256
|
+
if (!Array.isArray(config.releases) || !config.releases.every((entry) => typeof entry === "string" && entry)) throw new Error("Application check configuration releases must be an array of file paths.");
|
|
302
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Verify an application's immutable composition inputs before deployment.
|
|
260
|
+
* Every runtime capability must exactly match the approved lockfile, and every
|
|
261
|
+
* promoted experience release must be intact and bound to that assembly.
|
|
262
|
+
*/
|
|
303
263
|
async function checkApplication(options) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
`Experience release "${release.document.name}@${release.document.version}" belongs to assembly "${release.assemblyDigest}", not "${assembly.assemblyDigest}".`
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
application: assembly.application,
|
|
334
|
-
capabilities: assembly.capabilities.length,
|
|
335
|
-
releases: configValue.releases.length,
|
|
336
|
-
assemblyDigest: assembly.assemblyDigest
|
|
337
|
-
};
|
|
264
|
+
const configValue = await readJson(options.configPath);
|
|
265
|
+
assertApplicationCheckConfig(configValue);
|
|
266
|
+
const base = path.default.dirname(options.configPath);
|
|
267
|
+
const resolve = (file) => path.default.resolve(base, file);
|
|
268
|
+
const assembly = await readJson(resolve(configValue.assembly));
|
|
269
|
+
(0, _fabricorg_assembly.assertAssemblyLockfile)(assembly);
|
|
270
|
+
const runtimeCapabilities = await readJson(resolve(configValue.runtimeCapabilities));
|
|
271
|
+
if (!Array.isArray(runtimeCapabilities)) throw new Error("Runtime capabilities must be a JSON array.");
|
|
272
|
+
(0, _fabricorg_assembly.assertAssemblyRuntimeCompatible)(assembly, runtimeCapabilities);
|
|
273
|
+
for (const releasePath of configValue.releases) {
|
|
274
|
+
const release = await readJson(resolve(releasePath));
|
|
275
|
+
try {
|
|
276
|
+
(0, _fabricorg_sdui_release.assertSduiReleaseIntegrity)(release);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw new Error(`Experience release "${releasePath}" is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
279
|
+
}
|
|
280
|
+
if (release.assemblyDigest !== assembly.assemblyDigest) throw new Error(`Experience release "${release.document.name}@${release.document.version}" belongs to assembly "${release.assemblyDigest}", not "${assembly.assemblyDigest}".`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
application: assembly.application,
|
|
284
|
+
capabilities: assembly.capabilities.length,
|
|
285
|
+
releases: configValue.releases.length,
|
|
286
|
+
assemblyDigest: assembly.assemblyDigest
|
|
287
|
+
};
|
|
338
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Turn an authored FabricModule into the normalized manifest every generator reads.
|
|
291
|
+
*
|
|
292
|
+
* This is the seam between the two halves of the authoring loop. The module is
|
|
293
|
+
* TypeScript, so the compiler checks it as you write; the manifest is the
|
|
294
|
+
* normalized JSON the code generators and the assembly compiler consume. Until
|
|
295
|
+
* this command existed, nothing shipped connected them: `init capability`
|
|
296
|
+
* scaffolded an empty manifest, and an author who filled it in by hand was
|
|
297
|
+
* guessing at the shape of `exportModuleManifest`'s output.
|
|
298
|
+
*/
|
|
339
299
|
async function exportModuleManifestFile(options) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
actions: manifest.actions.length,
|
|
366
|
-
eventTypes: manifest.eventTypes.length
|
|
367
|
-
};
|
|
300
|
+
const loaded = await importAuthoredModule(path.default.resolve(options.moduleFile), options.moduleFile);
|
|
301
|
+
const candidates = Object.entries(loaded).filter(([, value]) => isFabricModuleLike(value));
|
|
302
|
+
let entry;
|
|
303
|
+
if (options.exportName) {
|
|
304
|
+
const named = loaded[options.exportName];
|
|
305
|
+
if (named === void 0) throw new Error(`"${options.moduleFile}" has no export named "${options.exportName}". It exports: ${Object.keys(loaded).join(", ") || "nothing"}.`);
|
|
306
|
+
if (!isFabricModuleLike(named)) throw new Error(`Export "${options.exportName}" of "${options.moduleFile}" is not a FabricModule: it has no string "namespace".`);
|
|
307
|
+
entry = [options.exportName, named];
|
|
308
|
+
} else if (candidates.length === 1) entry = candidates[0];
|
|
309
|
+
else if (candidates.length === 0) throw new Error(`"${options.moduleFile}" exports no FabricModule. Export one, with a string "namespace", or name it with --export.`);
|
|
310
|
+
else throw new Error(`"${options.moduleFile}" exports ${candidates.length} modules (${candidates.map(([name]) => name).join(", ")}). Choose one with --export.`);
|
|
311
|
+
const manifest = (0, _fabricorg_platform_manifest.exportModuleManifest)(entry[1]);
|
|
312
|
+
(0, _fabricorg_compiler.compileManifestBundle)({
|
|
313
|
+
manifestVersion: 2,
|
|
314
|
+
modules: [manifest]
|
|
315
|
+
});
|
|
316
|
+
await (0, fs_promises.mkdir)(path.default.dirname(path.default.resolve(options.out)), { recursive: true });
|
|
317
|
+
await (0, fs_promises.writeFile)(path.default.resolve(options.out), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
318
|
+
return {
|
|
319
|
+
namespace: manifest.namespace,
|
|
320
|
+
exportName: entry[0],
|
|
321
|
+
objectTypes: manifest.objectTypes.length,
|
|
322
|
+
actions: manifest.actions.length,
|
|
323
|
+
eventTypes: manifest.eventTypes.length
|
|
324
|
+
};
|
|
368
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Import an authored module, whatever the surrounding project calls itself.
|
|
328
|
+
*
|
|
329
|
+
* Node decides how to read a `.ts` file from the nearest package.json: without
|
|
330
|
+
* `"type": "module"` it reads it as CommonJS and rejects `export const` with
|
|
331
|
+
* "Unexpected token 'export'". Most projects are CommonJS, so a scaffolded
|
|
332
|
+
* module that imports fine in a bare directory fails inside a real one. When
|
|
333
|
+
* that happens the file is copied to a sibling `.mts`, which is unambiguously
|
|
334
|
+
* ESM, and imported from there. The copy sits in the same directory so
|
|
335
|
+
* relative imports and package resolution behave exactly as they would.
|
|
336
|
+
*/
|
|
369
337
|
async function importAuthoredModule(absolute, display) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
throw new Error(`Could not load module file "${display}": ${reason}
|
|
389
|
-
A TypeScript file needs a runtime that strips types (Node 22.18 or newer); otherwise point --module at the built JavaScript.`);
|
|
390
|
-
}
|
|
338
|
+
try {
|
|
339
|
+
return await import((0, url.pathToFileURL)(absolute).href);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
342
|
+
if (/Unexpected token '(export|import)'|Cannot use import statement outside a module/.test(reason) && /\.[cm]?ts$/.test(absolute)) {
|
|
343
|
+
const sibling = path.default.join(path.default.dirname(absolute), `.fabric-gen-${process.pid}-${path.default.basename(absolute, path.default.extname(absolute))}.mts`);
|
|
344
|
+
try {
|
|
345
|
+
await (0, fs_promises.writeFile)(sibling, await (0, fs_promises.readFile)(absolute, "utf8"), "utf8");
|
|
346
|
+
return await import((0, url.pathToFileURL)(sibling).href);
|
|
347
|
+
} catch (retried) {
|
|
348
|
+
const retriedReason = retried instanceof Error ? retried.message : String(retried);
|
|
349
|
+
throw new Error(`Could not load module file "${display}": ${retriedReason}\nA TypeScript file needs a runtime that strips types (Node 22.18 or newer); otherwise point --module at the built JavaScript.`);
|
|
350
|
+
} finally {
|
|
351
|
+
await (0, fs_promises.rm)(sibling, { force: true });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
throw new Error(`Could not load module file "${display}": ${reason}\nA TypeScript file needs a runtime that strips types (Node 22.18 or newer); otherwise point --module at the built JavaScript.`);
|
|
355
|
+
}
|
|
391
356
|
}
|
|
392
357
|
function isFabricModuleLike(value) {
|
|
393
|
-
|
|
358
|
+
return typeof value === "object" && value !== null && typeof value.namespace === "string";
|
|
394
359
|
}
|
|
395
360
|
async function initializeCapability(options) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (error.code === "EEXIST") {
|
|
528
|
-
throw new Error("Refusing to overwrite a file created during capability initialization.");
|
|
529
|
-
}
|
|
530
|
-
throw error;
|
|
531
|
-
}
|
|
532
|
-
return { created };
|
|
361
|
+
if (!/^[a-z][a-z0-9-]*$/.test(options.namespace)) throw new Error("Capability namespace must use lowercase letters, numbers, and hyphens.");
|
|
362
|
+
const displayName = options.namespace.split("-").map((part, index) => index === 0 ? `${part.charAt(0).toUpperCase()}${part.slice(1)}` : part).join(" ");
|
|
363
|
+
const modulePath = path.default.join(options.directory, "module.ts");
|
|
364
|
+
const readmePath = path.default.join(options.directory, "README.md");
|
|
365
|
+
for (const file of [readmePath, modulePath]) try {
|
|
366
|
+
await (0, fs_promises.access)(file);
|
|
367
|
+
throw new Error(`Refusing to overwrite existing file "${file}".`);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (error instanceof Error && error.message.startsWith("Refusing to overwrite")) throw error;
|
|
370
|
+
if (error.code !== "ENOENT") throw error;
|
|
371
|
+
}
|
|
372
|
+
const module = [
|
|
373
|
+
`import type { ActionDefinition } from "@fabricorg/platform/actions";`,
|
|
374
|
+
`import type { FabricModule } from "@fabricorg/platform/modules";`,
|
|
375
|
+
"",
|
|
376
|
+
`/** One governed action. Every field below is checked when the manifest is exported. */`,
|
|
377
|
+
`const recordExample: ActionDefinition = {`,
|
|
378
|
+
`\tactionId: "${options.namespace}.record_example",`,
|
|
379
|
+
`\tnamespace: "${options.namespace}",`,
|
|
380
|
+
" version: 1,",
|
|
381
|
+
" kind: \"atomic\",",
|
|
382
|
+
" idempotent: true,",
|
|
383
|
+
" mutatesDomain: true,",
|
|
384
|
+
" emitsEvents: [\"ExampleRecorded\"],",
|
|
385
|
+
" // The six execution dimensions. A strict certification profile requires all of them.",
|
|
386
|
+
" execution: {",
|
|
387
|
+
" connectivity: \"online-required\",",
|
|
388
|
+
" authorityMoment: \"execution\",",
|
|
389
|
+
" idempotency: \"required\",",
|
|
390
|
+
" completion: \"immediate\",",
|
|
391
|
+
" consistency: \"authoritative-now\",",
|
|
392
|
+
" sensitivity: \"internal\",",
|
|
393
|
+
" },",
|
|
394
|
+
" schema: {",
|
|
395
|
+
" parse(value: unknown): { exampleId: string } {",
|
|
396
|
+
" const input = value as { exampleId?: unknown };",
|
|
397
|
+
" if (typeof input?.exampleId !== \"string\") throw new Error(\"exampleId is required\");",
|
|
398
|
+
" return { exampleId: input.exampleId };",
|
|
399
|
+
" },",
|
|
400
|
+
" safeParse(value: unknown) {",
|
|
401
|
+
" try {",
|
|
402
|
+
" return { success: true as const, data: this.parse(value) };",
|
|
403
|
+
" } catch (error) {",
|
|
404
|
+
" return { success: false as const, error: { issues: [{ path: [\"exampleId\"], message: error instanceof Error ? error.message : String(error) }] } };",
|
|
405
|
+
" }",
|
|
406
|
+
" },",
|
|
407
|
+
" },",
|
|
408
|
+
" handler: async (_context, parameters) => ({",
|
|
409
|
+
" success: true,",
|
|
410
|
+
" data: {",
|
|
411
|
+
" ...(parameters as { exampleId: string }),",
|
|
412
|
+
" _events: [{",
|
|
413
|
+
" eventType: \"ExampleRecorded\",",
|
|
414
|
+
" entityType: \"Example\",",
|
|
415
|
+
" entityId: (parameters as { exampleId: string }).exampleId,",
|
|
416
|
+
" payload: parameters,",
|
|
417
|
+
" }],",
|
|
418
|
+
" },",
|
|
419
|
+
" }),",
|
|
420
|
+
"};",
|
|
421
|
+
"",
|
|
422
|
+
`export const ${camelCase(options.namespace)}Module: FabricModule = {`,
|
|
423
|
+
`\tnamespace: "${options.namespace}",`,
|
|
424
|
+
" version: \"0.1.0\",",
|
|
425
|
+
" capability: {",
|
|
426
|
+
`\t\tdisplayName: "${displayName}",`,
|
|
427
|
+
`\t\tdescription: "Governed ${displayName.toLowerCase()} capability.",`,
|
|
428
|
+
" stability: \"experimental\",",
|
|
429
|
+
" },",
|
|
430
|
+
" objectTypes: [{",
|
|
431
|
+
" type: \"Example\",",
|
|
432
|
+
" schema: {",
|
|
433
|
+
" type: \"object\",",
|
|
434
|
+
" properties: { exampleId: { type: \"string\" } },",
|
|
435
|
+
" required: [\"exampleId\"],",
|
|
436
|
+
" },",
|
|
437
|
+
" }],",
|
|
438
|
+
`\tidPrefixes: [{ prefix: "exm", name: "Example", module: "${options.namespace}" }],`,
|
|
439
|
+
" eventTypes: [{ eventType: \"ExampleRecorded\", schemaVersion: 1 }],",
|
|
440
|
+
" actions: [recordExample],",
|
|
441
|
+
"};",
|
|
442
|
+
""
|
|
443
|
+
].join("\n");
|
|
444
|
+
const readme = [
|
|
445
|
+
`# ${displayName}`,
|
|
446
|
+
"",
|
|
447
|
+
"`module.ts` is the source of truth. It is TypeScript, so the shape of every",
|
|
448
|
+
"action, object type, and event is checked as you write it.",
|
|
449
|
+
"",
|
|
450
|
+
"## The loop",
|
|
451
|
+
"",
|
|
452
|
+
"```bash",
|
|
453
|
+
"# 1. Export the authored module to the normalized manifest the generators read.",
|
|
454
|
+
"fabric-gen export --module module.ts --out fabric.manifest.json",
|
|
455
|
+
"",
|
|
456
|
+
"# 2. Generate types, event contracts, the usage contract, and docs.",
|
|
457
|
+
"fabric-gen --input fabric.manifest.json --out generated",
|
|
458
|
+
"",
|
|
459
|
+
"# 3. In CI, prove the generated files match the manifest.",
|
|
460
|
+
"fabric-gen --input fabric.manifest.json --out generated --check",
|
|
461
|
+
"```",
|
|
462
|
+
"",
|
|
463
|
+
"Commit both `fabric.manifest.json` and `generated/`. The manifest is the",
|
|
464
|
+
"contract other capabilities and applications compile against; regenerating",
|
|
465
|
+
"is how a consumer learns what changed.",
|
|
466
|
+
"",
|
|
467
|
+
"## What to edit",
|
|
468
|
+
"",
|
|
469
|
+
"- **Object types** carry a portable JSON Schema. They become generated TypeScript types.",
|
|
470
|
+
"- **Actions** declare six execution dimensions. A host refuses an action whose",
|
|
471
|
+
" declaration does not match how it is invoked, so declare them honestly.",
|
|
472
|
+
"- **Events** are the capability's public vocabulary. Adding a field is compatible;",
|
|
473
|
+
" removing or retyping one is not.",
|
|
474
|
+
""
|
|
475
|
+
].join("\n");
|
|
476
|
+
await (0, fs_promises.mkdir)(options.directory, { recursive: true });
|
|
477
|
+
const created = [];
|
|
478
|
+
try {
|
|
479
|
+
for (const [file, content] of [[readmePath, readme], [modulePath, module]]) {
|
|
480
|
+
await (0, fs_promises.writeFile)(file, content, {
|
|
481
|
+
encoding: "utf8",
|
|
482
|
+
flag: "wx"
|
|
483
|
+
});
|
|
484
|
+
created.push(file);
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
await Promise.all(created.map((file) => (0, fs_promises.rm)(file, { force: true })));
|
|
488
|
+
if (error.code === "EEXIST") throw new Error("Refusing to overwrite a file created during capability initialization.");
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
return { created };
|
|
533
492
|
}
|
|
534
493
|
function camelCase(namespace) {
|
|
535
|
-
|
|
494
|
+
return namespace.split("-").map((part, index) => index === 0 ? part : `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
536
495
|
}
|
|
537
496
|
async function generateFromManifest(options) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
497
|
+
const input = JSON.parse(await (0, fs_promises.readFile)(options.inputPath, "utf8"));
|
|
498
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Manifest input must be a JSON object.");
|
|
499
|
+
const bundle = "modules" in input ? input : {
|
|
500
|
+
manifestVersion: input.manifestVersion,
|
|
501
|
+
modules: [input]
|
|
502
|
+
};
|
|
503
|
+
const ir = (0, _fabricorg_compiler.compileManifestBundle)(bundle);
|
|
504
|
+
const files = (await Promise.all(options.targets.map(async (name) => {
|
|
505
|
+
const target = GENERATOR_TARGETS[name];
|
|
506
|
+
if (!target) throw new Error(`Unknown generator target "${name}".`);
|
|
507
|
+
return (await (0, _fabricorg_compiler.runGenerator)(target, ir)).map((file) => ({
|
|
508
|
+
...file,
|
|
509
|
+
path: `${name}/${file.path}`
|
|
510
|
+
}));
|
|
511
|
+
}))).flat().sort((left, right) => compareCanonicalStrings(left.path, right.path));
|
|
512
|
+
const expected = new Set(files.map((file) => file.path));
|
|
513
|
+
const existing = await listFiles(options.outputDirectory);
|
|
514
|
+
const selectedPrefixes = options.targets.map((target) => `${target}/`);
|
|
515
|
+
const stale = existing.filter((file) => selectedPrefixes.some((prefix) => file.startsWith(prefix)) && !expected.has(file));
|
|
516
|
+
if (!options.check) await Promise.all(stale.map((file) => (0, fs_promises.rm)(path.default.join(options.outputDirectory, file), { force: true })));
|
|
517
|
+
const changed = [];
|
|
518
|
+
for (const file of files) {
|
|
519
|
+
const target = path.default.join(options.outputDirectory, file.path);
|
|
520
|
+
let current;
|
|
521
|
+
try {
|
|
522
|
+
current = await (0, fs_promises.readFile)(target, "utf8");
|
|
523
|
+
} catch {
|
|
524
|
+
current = void 0;
|
|
525
|
+
}
|
|
526
|
+
if (current !== file.content) {
|
|
527
|
+
changed.push(file.path);
|
|
528
|
+
if (!options.check) {
|
|
529
|
+
await (0, fs_promises.mkdir)(path.default.dirname(target), { recursive: true });
|
|
530
|
+
await (0, fs_promises.writeFile)(target, file.content, "utf8");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (options.check && (changed.length || stale.length)) throw new Error(`Generated artifacts drifted: ${[...changed, ...stale.map((file) => `stale:${file}`)].join(", ")}`);
|
|
535
|
+
return {
|
|
536
|
+
changed,
|
|
537
|
+
stale,
|
|
538
|
+
files
|
|
539
|
+
};
|
|
571
540
|
}
|
|
572
541
|
async function listFiles(directory, prefix = "") {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
return nested.flat().sort();
|
|
542
|
+
let entries;
|
|
543
|
+
try {
|
|
544
|
+
entries = await (0, fs_promises.readdir)(directory, { withFileTypes: true });
|
|
545
|
+
} catch {
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
return (await Promise.all(entries.map((entry) => entry.isDirectory() ? listFiles(path.default.join(directory, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]))).flat().sort();
|
|
581
549
|
}
|
|
582
550
|
function assertExperiencePromotionConfig(value) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
551
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Experience promotion configuration must be a JSON object.");
|
|
552
|
+
const config = value;
|
|
553
|
+
if (config.formatVersion !== 1) throw new Error("Experience promotion configuration formatVersion must be 1.");
|
|
554
|
+
for (const field of [
|
|
555
|
+
"application",
|
|
556
|
+
"issuer",
|
|
557
|
+
"keyId",
|
|
558
|
+
"assembly",
|
|
559
|
+
"release"
|
|
560
|
+
]) if (typeof config[field] !== "string" || !config[field]) throw new Error(`Experience promotion configuration ${field} must be a non-empty string.`);
|
|
561
|
+
if (!/^[a-z][a-z0-9-]*$/.test(config.application)) throw new Error("Experience promotion configuration application must be lowercase letters, numbers, and hyphens.");
|
|
562
|
+
if (!Array.isArray(config.contracts) || !config.contracts.every((entry) => typeof entry === "string" && entry)) throw new Error("Experience promotion configuration contracts must be an array of file paths.");
|
|
563
|
+
const known = /* @__PURE__ */ new Set([
|
|
564
|
+
"formatVersion",
|
|
565
|
+
"application",
|
|
566
|
+
"issuer",
|
|
567
|
+
"keyId",
|
|
568
|
+
"assembly",
|
|
569
|
+
"release",
|
|
570
|
+
"contracts"
|
|
571
|
+
]);
|
|
572
|
+
for (const field of Object.keys(config)) {
|
|
573
|
+
if (/key|secret|credential|token|pem|password/i.test(field) && field !== "keyId") throw new Error(`Experience promotion configuration must not carry "${field}"; pass the signing key with --key.`);
|
|
574
|
+
if (!known.has(field)) throw new Error(`Experience promotion configuration has an unknown field "${field}".`);
|
|
575
|
+
}
|
|
608
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* Turn a validated release into a signed one.
|
|
579
|
+
*
|
|
580
|
+
* Promotion is where a granted `capability://` reference becomes a concrete
|
|
581
|
+
* action id and version, so it is also where that mapping has to be bound to
|
|
582
|
+
* the assembly that approved it. The signing key is a command-line argument
|
|
583
|
+
* rather than a configuration field, because a configuration file is the kind
|
|
584
|
+
* of thing that ends up committed.
|
|
585
|
+
*/
|
|
609
586
|
async function promoteExperience(options) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
`, "utf8");
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
587
|
+
const configValue = await readJson(options.configPath);
|
|
588
|
+
assertExperiencePromotionConfig(configValue);
|
|
589
|
+
const base = path.default.dirname(options.configPath);
|
|
590
|
+
const resolve = (file) => path.default.resolve(base, file);
|
|
591
|
+
const assembly = await readJson(resolve(configValue.assembly));
|
|
592
|
+
(0, _fabricorg_assembly.assertAssemblyLockfile)(assembly);
|
|
593
|
+
const release = await readJson(resolve(configValue.release));
|
|
594
|
+
(0, _fabricorg_sdui_release.assertSduiReleaseIntegrity)(release);
|
|
595
|
+
const contracts = await Promise.all(configValue.contracts.map(async (file) => await readJson(resolve(file))));
|
|
596
|
+
const privateKey = (0, crypto.createPrivateKey)(await (0, fs_promises.readFile)(options.privateKeyPath, "utf8"));
|
|
597
|
+
const promoted = await (0, _fabricorg_sdui_composer.promoteExperienceRelease)({
|
|
598
|
+
application: configValue.application,
|
|
599
|
+
release,
|
|
600
|
+
contracts,
|
|
601
|
+
assembly,
|
|
602
|
+
issuer: configValue.issuer,
|
|
603
|
+
signer: (0, _fabricorg_sdui_composer.createEs256ReleaseSigner)({
|
|
604
|
+
keyId: configValue.keyId,
|
|
605
|
+
privateKey
|
|
606
|
+
})
|
|
607
|
+
});
|
|
608
|
+
const outputPath = options.outputPath ? path.default.resolve(options.outputPath) : resolve(`${configValue.application}.experience-release.jws`);
|
|
609
|
+
await (0, fs_promises.mkdir)(path.default.dirname(outputPath), { recursive: true });
|
|
610
|
+
await (0, fs_promises.writeFile)(outputPath, `${promoted.compactJws}\n`, "utf8");
|
|
611
|
+
return {
|
|
612
|
+
application: configValue.application,
|
|
613
|
+
releaseDigest: release.releaseDigest,
|
|
614
|
+
assemblyDigest: assembly.assemblyDigest,
|
|
615
|
+
routes: {
|
|
616
|
+
views: promoted.release.routes.views.length,
|
|
617
|
+
intents: promoted.release.routes.intents.length
|
|
618
|
+
},
|
|
619
|
+
outputPath
|
|
620
|
+
};
|
|
644
621
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
622
|
+
//#endregion
|
|
623
|
+
exports.FabricGenUsageError = FabricGenUsageError;
|
|
624
|
+
exports.GENERATOR_TARGETS = GENERATOR_TARGETS;
|
|
625
|
+
exports.checkApplication = checkApplication;
|
|
626
|
+
exports.exportModuleManifestFile = exportModuleManifestFile;
|
|
627
|
+
exports.fabricGenCliExitCode = fabricGenCliExitCode;
|
|
628
|
+
exports.generateFromManifest = generateFromManifest;
|
|
629
|
+
exports.initializeCapability = initializeCapability;
|
|
630
|
+
exports.initializeExperience = initializeExperience;
|
|
631
|
+
exports.promoteExperience = promoteExperience;
|
|
632
|
+
exports.runFabricGenCommand = runFabricGenCommand;
|
|
633
|
+
|
|
658
634
|
//# sourceMappingURL=index.cjs.map
|