@loomweaver/cli 0.9.2 → 0.9.3
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/dist/main.mjs +665 -195
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -99,45 +99,14 @@ function ensureBuildTarget(target, amendment, projectRoot) {
|
|
|
99
99
|
}
|
|
100
100
|
next["options"] = options;
|
|
101
101
|
if (amendment.inlineCritical !== void 0 || amendment.serviceWorker || amendment.initialBudget) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
if (amendment.inlineCritical !== void 0) {
|
|
112
|
-
const critical = ensureInlineCritical(
|
|
113
|
-
production["optimization"],
|
|
114
|
-
amendment.inlineCritical
|
|
115
|
-
);
|
|
116
|
-
if (critical.declined) {
|
|
117
|
-
declined.push(
|
|
118
|
-
"production optimization is a boolean, so inlineCritical cannot be set beside it \u2014 a release build then loads the stylesheet with an inline handler the generated content-security policy blocks, and renders unstyled"
|
|
119
|
-
);
|
|
120
|
-
} else if (critical.changed) {
|
|
121
|
-
production["optimization"] = critical.value;
|
|
122
|
-
added.push(
|
|
123
|
-
`production optimization.styles.inlineCritical: ${amendment.inlineCritical}`
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (amendment.initialBudget) {
|
|
128
|
-
const budgets = ensureInitialBudget(
|
|
129
|
-
production["budgets"],
|
|
130
|
-
amendment.initialBudget
|
|
131
|
-
);
|
|
132
|
-
if (budgets.changed) {
|
|
133
|
-
production["budgets"] = budgets.value;
|
|
134
|
-
added.push(
|
|
135
|
-
`production budget initial: ${amendment.initialBudget.warning} warning, ${amendment.initialBudget.error} error`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
configurations["production"] = production;
|
|
140
|
-
next["configurations"] = configurations;
|
|
102
|
+
const production = ensureProductionConfiguration(
|
|
103
|
+
next["configurations"],
|
|
104
|
+
amendment,
|
|
105
|
+
projectRoot
|
|
106
|
+
);
|
|
107
|
+
next["configurations"] = production.value;
|
|
108
|
+
added.push(...production.added);
|
|
109
|
+
declined.push(...production.declined);
|
|
141
110
|
}
|
|
142
111
|
return { value: next, added, declined };
|
|
143
112
|
}
|
|
@@ -151,6 +120,49 @@ function ensureStylesheetSource(css, source) {
|
|
|
151
120
|
@source '${source}';
|
|
152
121
|
`;
|
|
153
122
|
}
|
|
123
|
+
function ensureProductionConfiguration(value, amendment, projectRoot) {
|
|
124
|
+
const configurations = { ...asObject(value) };
|
|
125
|
+
const production = { ...asObject(configurations["production"]) };
|
|
126
|
+
const added = [];
|
|
127
|
+
const declined = [];
|
|
128
|
+
if (amendment.serviceWorker && production["serviceWorker"] === void 0) {
|
|
129
|
+
production["serviceWorker"] = joinProjectPath(
|
|
130
|
+
projectRoot,
|
|
131
|
+
amendment.serviceWorker
|
|
132
|
+
);
|
|
133
|
+
added.push(`production serviceWorker: ${production["serviceWorker"]}`);
|
|
134
|
+
}
|
|
135
|
+
if (amendment.inlineCritical !== void 0) {
|
|
136
|
+
const critical = ensureInlineCritical(
|
|
137
|
+
production["optimization"],
|
|
138
|
+
amendment.inlineCritical
|
|
139
|
+
);
|
|
140
|
+
if (critical.declined) {
|
|
141
|
+
declined.push(
|
|
142
|
+
"production optimization is a boolean, so inlineCritical cannot be set beside it \u2014 a release build then loads the stylesheet with an inline handler the generated content-security policy blocks, and renders unstyled"
|
|
143
|
+
);
|
|
144
|
+
} else if (critical.changed) {
|
|
145
|
+
production["optimization"] = critical.value;
|
|
146
|
+
added.push(
|
|
147
|
+
`production optimization.styles.inlineCritical: ${amendment.inlineCritical}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (amendment.initialBudget) {
|
|
152
|
+
const budgets = ensureInitialBudget(
|
|
153
|
+
production["budgets"],
|
|
154
|
+
amendment.initialBudget
|
|
155
|
+
);
|
|
156
|
+
if (budgets.changed) {
|
|
157
|
+
production["budgets"] = budgets.value;
|
|
158
|
+
added.push(
|
|
159
|
+
`production budget initial: ${amendment.initialBudget.warning} warning, ${amendment.initialBudget.error} error`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
configurations["production"] = production;
|
|
164
|
+
return { value: configurations, added, declined };
|
|
165
|
+
}
|
|
154
166
|
function ensureInitialBudget(budgets, wanted) {
|
|
155
167
|
const entries = Array.isArray(budgets) ? [...budgets] : [];
|
|
156
168
|
const index = entries.findIndex(
|
|
@@ -338,10 +350,11 @@ function joinNamespaces(source, id) {
|
|
|
338
350
|
if (names.includes(`'${id}'`)) {
|
|
339
351
|
return { source, joined: true };
|
|
340
352
|
}
|
|
353
|
+
const namespaces = [...names, `'${id}'`].join(", ");
|
|
341
354
|
return {
|
|
342
355
|
source: source.replace(
|
|
343
356
|
NAMESPACES,
|
|
344
|
-
() => `provideTranslationNamespaces(${
|
|
357
|
+
() => `provideTranslationNamespaces(${namespaces})`
|
|
345
358
|
),
|
|
346
359
|
joined: true
|
|
347
360
|
};
|
|
@@ -408,15 +421,19 @@ function describeAmendment(amendment) {
|
|
|
408
421
|
return `Add an @source entry for '${amendment.sourceRoot}' to the application's entry stylesheet, resolved from that stylesheet. Without it none of that code's utilities are emitted.`;
|
|
409
422
|
}
|
|
410
423
|
if (amendment.kind === "compose-plugin") {
|
|
411
|
-
const providers = (amendment.providers ?? []).map((provider) => provider.line.replace(/,$/, ""));
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
424
|
+
const providers = (amendment.providers ?? []).map((provider) => `${provider.line.replace(/,$/, "")}, `).join("");
|
|
425
|
+
const capabilities = amendment.capabilities.map((capability) => `'${capability}'`).join(", ");
|
|
426
|
+
const imports = (amendment.providers ?? []).flatMap((provider) => provider.from ?? []).map(
|
|
427
|
+
(imported) => ` import { ${imported.symbols.join(", ")} } from '${imported.path}'.`
|
|
428
|
+
).join("");
|
|
429
|
+
return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, ${providers}provideTranslationNamespaces('${amendment.id}'), provideCapabilityGrants({ ${amendment.id}: [${capabilities}] }) and ...providePlugins(${amendment.symbol}).${imports} Without it none of its contributions appear.`;
|
|
415
430
|
}
|
|
416
431
|
return [
|
|
417
432
|
...amendment.styles.length > 0 ? [`name ${amendment.styles.join(", ")} in styles`] : [],
|
|
418
433
|
...amendment.assets.length > 0 ? [
|
|
419
|
-
`add assets for ${amendment.assets.map(
|
|
434
|
+
`add assets for ${amendment.assets.map(
|
|
435
|
+
(asset) => asset.output ? `${asset.input} served under ${asset.output}` : asset.input
|
|
436
|
+
).join(
|
|
420
437
|
", "
|
|
421
438
|
)} (the shell fetches its own strings at runtime, so without that glob every label in the chrome renders as its raw translation key)`
|
|
422
439
|
] : [],
|
|
@@ -820,7 +837,7 @@ function pause(): Promise<void> {
|
|
|
820
837
|
}
|
|
821
838
|
|
|
822
839
|
// ../devkit/src/recipes/angular-weaver/agent-files.ts
|
|
823
|
-
var AG_UI_ADAPTER_VERSION = "0.9.
|
|
840
|
+
var AG_UI_ADAPTER_VERSION = "0.9.3";
|
|
824
841
|
var AG_UI_PROTOCOL_VERSION = "0.0.x";
|
|
825
842
|
function connectionFile(w) {
|
|
826
843
|
return `import { signal } from '@angular/core';
|
|
@@ -1187,109 +1204,8 @@ function toneHelper() {
|
|
|
1187
1204
|
].join("\n");
|
|
1188
1205
|
}
|
|
1189
1206
|
|
|
1190
|
-
// ../devkit/src/recipes/angular-weaver/
|
|
1191
|
-
var DEFAULT_MENU_SLOT = "content/tab/context";
|
|
1207
|
+
// ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
|
|
1192
1208
|
var SURFACE_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 9h8M8 13h8M8 17h5"/></svg>';
|
|
1193
|
-
function accessLiteral(spec) {
|
|
1194
|
-
if (spec === "authenticated") return "{ authenticated: true }";
|
|
1195
|
-
if (spec === "anonymous") return "{ authenticated: false }";
|
|
1196
|
-
if (spec.startsWith("role:")) {
|
|
1197
|
-
const role = spec.slice("role:".length);
|
|
1198
|
-
if (!role)
|
|
1199
|
-
throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
|
|
1200
|
-
if (/['\\]/.test(role))
|
|
1201
|
-
throw new Error(
|
|
1202
|
-
`Access role "${role}" must not contain quotes or backslashes.`
|
|
1203
|
-
);
|
|
1204
|
-
return `{ anyRole: ['${role}'] }`;
|
|
1205
|
-
}
|
|
1206
|
-
throw new Error(
|
|
1207
|
-
`Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
function resolveMenuSlot(menu) {
|
|
1211
|
-
if (menu === true) {
|
|
1212
|
-
return DEFAULT_MENU_SLOT;
|
|
1213
|
-
}
|
|
1214
|
-
return typeof menu === "string" && menu.length ? menu : void 0;
|
|
1215
|
-
}
|
|
1216
|
-
var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
|
|
1217
|
-
"cmd",
|
|
1218
|
-
"command",
|
|
1219
|
-
"ctrl",
|
|
1220
|
-
"control",
|
|
1221
|
-
"meta"
|
|
1222
|
-
]);
|
|
1223
|
-
function assertPlatformNeutralChord(shortcut) {
|
|
1224
|
-
const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
|
|
1225
|
-
const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
|
|
1226
|
-
if (bound) {
|
|
1227
|
-
throw new Error(
|
|
1228
|
-
`Shortcut "${shortcut}" binds the platform-specific "${bound}" key. Use the neutral 'mod' token (e.g. 'mod+shift+k') \u2014 the host renders it as \u2318 on macOS and Ctrl elsewhere.`
|
|
1229
|
-
);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
function resolveFeatures(id, input) {
|
|
1233
|
-
const menuSlot = resolveMenuSlot(input?.menu);
|
|
1234
|
-
const barItem = Boolean(input?.barItem);
|
|
1235
|
-
const hasShortcut = Boolean(input?.shortcut?.trim());
|
|
1236
|
-
if (hasShortcut) {
|
|
1237
|
-
assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
|
|
1238
|
-
}
|
|
1239
|
-
const instanceable = Boolean(input?.instanceable);
|
|
1240
|
-
const container = Boolean(input?.container);
|
|
1241
|
-
if (instanceable && container) {
|
|
1242
|
-
throw new Error(
|
|
1243
|
-
'A surface cannot be both a container and instanceable: a container tab holds its own ":id" and is therefore routable, while named instances exist only for a docked, non-routable surface. Pick one.'
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
const agent = Boolean(input?.agent);
|
|
1247
|
-
return {
|
|
1248
|
-
command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
|
|
1249
|
-
menuSlot,
|
|
1250
|
-
settings: Boolean(input?.settings),
|
|
1251
|
-
access: input?.access ? accessLiteral(input.access) : void 0,
|
|
1252
|
-
shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
|
|
1253
|
-
barItem,
|
|
1254
|
-
about: Boolean(input?.about),
|
|
1255
|
-
instanceable,
|
|
1256
|
-
container,
|
|
1257
|
-
agent,
|
|
1258
|
-
spec: input?.spec !== false
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
function deriveCapabilities(features) {
|
|
1262
|
-
const set = /* @__PURE__ */ new Set(["contributions", "navigation"]);
|
|
1263
|
-
if (features.command) set.add("ui");
|
|
1264
|
-
if (features.about) {
|
|
1265
|
-
set.add("ui");
|
|
1266
|
-
set.add("host");
|
|
1267
|
-
}
|
|
1268
|
-
if (features.agent) {
|
|
1269
|
-
set.add("ui");
|
|
1270
|
-
set.add("automation");
|
|
1271
|
-
}
|
|
1272
|
-
return KNOWN_CAPABILITIES.filter((capability) => set.has(capability));
|
|
1273
|
-
}
|
|
1274
|
-
function resolveWeaverInput(input) {
|
|
1275
|
-
if (!isKebabId(input.id)) {
|
|
1276
|
-
throw new Error(
|
|
1277
|
-
`Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
const features = resolveFeatures(input.id, input.features);
|
|
1281
|
-
const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
|
|
1282
|
-
return {
|
|
1283
|
-
id: input.id,
|
|
1284
|
-
name: input.name?.trim() || toTitleCase(input.id),
|
|
1285
|
-
className: toPascalCase(input.id),
|
|
1286
|
-
propertyName: toCamelCase(input.id),
|
|
1287
|
-
capabilities,
|
|
1288
|
-
features,
|
|
1289
|
-
prefix: input.prefix?.trim() || "lw",
|
|
1290
|
-
importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
1209
|
function containerChildIds(w) {
|
|
1294
1210
|
return [`${w.id}.canvas`, `${w.id}.details`];
|
|
1295
1211
|
}
|
|
@@ -1426,7 +1342,7 @@ function settingsBlock(w) {
|
|
|
1426
1342
|
" });"
|
|
1427
1343
|
].join("\n");
|
|
1428
1344
|
}
|
|
1429
|
-
function
|
|
1345
|
+
function pluginImports(w) {
|
|
1430
1346
|
const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
|
|
1431
1347
|
if (w.features.container) {
|
|
1432
1348
|
imports.push(
|
|
@@ -1450,6 +1366,9 @@ function pluginFile(w) {
|
|
|
1450
1366
|
if (w.features.settings) {
|
|
1451
1367
|
imports.unshift("import { signal } from '@angular/core';");
|
|
1452
1368
|
}
|
|
1369
|
+
return imports;
|
|
1370
|
+
}
|
|
1371
|
+
function pluginConsts(w) {
|
|
1453
1372
|
const consts = [`const icon =
|
|
1454
1373
|
'${SURFACE_ICON}';`];
|
|
1455
1374
|
if (w.features.settings) {
|
|
@@ -1458,6 +1377,10 @@ function pluginFile(w) {
|
|
|
1458
1377
|
`const ${w.propertyName}Note = signal('');`
|
|
1459
1378
|
);
|
|
1460
1379
|
}
|
|
1380
|
+
if (w.features.command) consts.push(toneHelper());
|
|
1381
|
+
return consts;
|
|
1382
|
+
}
|
|
1383
|
+
function pluginBody(w) {
|
|
1461
1384
|
const body = [` ctx.contributeIcons({ '${w.id}': icon });`];
|
|
1462
1385
|
if (w.features.agent) {
|
|
1463
1386
|
body.push(
|
|
@@ -1472,14 +1395,17 @@ function pluginFile(w) {
|
|
|
1472
1395
|
if (w.features.menuSlot) body.push(menuBlock(w));
|
|
1473
1396
|
if (w.features.settings) body.push(settingsBlock(w));
|
|
1474
1397
|
if (w.features.agent) body.push(agentSurfaceBlock(w));
|
|
1398
|
+
return body;
|
|
1399
|
+
}
|
|
1400
|
+
function pluginFile(w) {
|
|
1401
|
+
const body = pluginBody(w);
|
|
1475
1402
|
const deactivate = w.features.agent ? `
|
|
1476
1403
|
deactivate() {
|
|
1477
1404
|
${w.propertyName}Agent.set(null);
|
|
1478
1405
|
},` : "";
|
|
1479
|
-
|
|
1480
|
-
return `${imports.join("\n")}
|
|
1406
|
+
return `${pluginImports(w).join("\n")}
|
|
1481
1407
|
|
|
1482
|
-
${
|
|
1408
|
+
${pluginConsts(w).join("\n\n")}
|
|
1483
1409
|
|
|
1484
1410
|
export const ${w.propertyName}Plugin: Plugin = {
|
|
1485
1411
|
manifest: {
|
|
@@ -1493,6 +1419,109 @@ ${body.join("\n")}
|
|
|
1493
1419
|
};
|
|
1494
1420
|
`;
|
|
1495
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
// ../devkit/src/recipes/angular-weaver/recipe.ts
|
|
1424
|
+
var DEFAULT_MENU_SLOT = "content/tab/context";
|
|
1425
|
+
function accessLiteral(spec) {
|
|
1426
|
+
if (spec === "authenticated") return "{ authenticated: true }";
|
|
1427
|
+
if (spec === "anonymous") return "{ authenticated: false }";
|
|
1428
|
+
if (spec.startsWith("role:")) {
|
|
1429
|
+
const role = spec.slice("role:".length);
|
|
1430
|
+
if (!role)
|
|
1431
|
+
throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
|
|
1432
|
+
if (/['\\]/.test(role))
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
`Access role "${role}" must not contain quotes or backslashes.`
|
|
1435
|
+
);
|
|
1436
|
+
return `{ anyRole: ['${role}'] }`;
|
|
1437
|
+
}
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
`Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
function resolveMenuSlot(menu) {
|
|
1443
|
+
if (menu === true) {
|
|
1444
|
+
return DEFAULT_MENU_SLOT;
|
|
1445
|
+
}
|
|
1446
|
+
return typeof menu === "string" && menu.length ? menu : void 0;
|
|
1447
|
+
}
|
|
1448
|
+
var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
|
|
1449
|
+
"cmd",
|
|
1450
|
+
"command",
|
|
1451
|
+
"ctrl",
|
|
1452
|
+
"control",
|
|
1453
|
+
"meta"
|
|
1454
|
+
]);
|
|
1455
|
+
function assertPlatformNeutralChord(shortcut) {
|
|
1456
|
+
const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
|
|
1457
|
+
const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
|
|
1458
|
+
if (bound) {
|
|
1459
|
+
throw new Error(
|
|
1460
|
+
`Shortcut "${shortcut}" binds the platform-specific "${bound}" key. Use the neutral 'mod' token (e.g. 'mod+shift+k') \u2014 the host renders it as \u2318 on macOS and Ctrl elsewhere.`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
function resolveFeatures(id, input) {
|
|
1465
|
+
const menuSlot = resolveMenuSlot(input?.menu);
|
|
1466
|
+
const barItem = Boolean(input?.barItem);
|
|
1467
|
+
const hasShortcut = Boolean(input?.shortcut?.trim());
|
|
1468
|
+
if (hasShortcut) {
|
|
1469
|
+
assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
|
|
1470
|
+
}
|
|
1471
|
+
const instanceable = Boolean(input?.instanceable);
|
|
1472
|
+
const container = Boolean(input?.container);
|
|
1473
|
+
if (instanceable && container) {
|
|
1474
|
+
throw new Error(
|
|
1475
|
+
'A surface cannot be both a container and instanceable: a container tab holds its own ":id" and is therefore routable, while named instances exist only for a docked, non-routable surface. Pick one.'
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
const agent = Boolean(input?.agent);
|
|
1479
|
+
return {
|
|
1480
|
+
command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
|
|
1481
|
+
menuSlot,
|
|
1482
|
+
settings: Boolean(input?.settings),
|
|
1483
|
+
access: input?.access ? accessLiteral(input.access) : void 0,
|
|
1484
|
+
shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
|
|
1485
|
+
barItem,
|
|
1486
|
+
about: Boolean(input?.about),
|
|
1487
|
+
instanceable,
|
|
1488
|
+
container,
|
|
1489
|
+
agent,
|
|
1490
|
+
spec: input?.spec !== false
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function deriveCapabilities(features) {
|
|
1494
|
+
const set = /* @__PURE__ */ new Set(["contributions", "navigation"]);
|
|
1495
|
+
if (features.command) set.add("ui");
|
|
1496
|
+
if (features.about) {
|
|
1497
|
+
set.add("ui");
|
|
1498
|
+
set.add("host");
|
|
1499
|
+
}
|
|
1500
|
+
if (features.agent) {
|
|
1501
|
+
set.add("ui");
|
|
1502
|
+
set.add("automation");
|
|
1503
|
+
}
|
|
1504
|
+
return KNOWN_CAPABILITIES.filter((capability) => set.has(capability));
|
|
1505
|
+
}
|
|
1506
|
+
function resolveWeaverInput(input) {
|
|
1507
|
+
if (!isKebabId(input.id)) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
`Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const features = resolveFeatures(input.id, input.features);
|
|
1513
|
+
const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
|
|
1514
|
+
return {
|
|
1515
|
+
id: input.id,
|
|
1516
|
+
name: input.name?.trim() || toTitleCase(input.id),
|
|
1517
|
+
className: toPascalCase(input.id),
|
|
1518
|
+
propertyName: toCamelCase(input.id),
|
|
1519
|
+
capabilities,
|
|
1520
|
+
features,
|
|
1521
|
+
prefix: input.prefix?.trim() || "lw",
|
|
1522
|
+
importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1496
1525
|
function aboutDialogFile(w) {
|
|
1497
1526
|
return `import { Component, inject } from '@angular/core';
|
|
1498
1527
|
import { DialogRef, PluginHost } from '@loomweaver/plugin-sdk';
|
|
@@ -1803,7 +1832,7 @@ var framePlugin = {
|
|
|
1803
1832
|
};
|
|
1804
1833
|
|
|
1805
1834
|
// ../devkit/src/recipes/platform-version.ts
|
|
1806
|
-
var PLATFORM_VERSION = "0.9.
|
|
1835
|
+
var PLATFORM_VERSION = "0.9.3";
|
|
1807
1836
|
|
|
1808
1837
|
// ../devkit/src/recipes/angular-distribution/amendments.ts
|
|
1809
1838
|
var DISTRIBUTION_INITIAL_BUDGET = {
|
|
@@ -2095,7 +2124,7 @@ function resolveDistributionInput(input) {
|
|
|
2095
2124
|
);
|
|
2096
2125
|
}
|
|
2097
2126
|
const directory = input.directory === void 0 ? `apps/${input.name}` : input.directory.trim();
|
|
2098
|
-
const depth = directory.split("/").filter(Boolean).length;
|
|
2127
|
+
const depth = normalizeProjectRoot(directory).split("/").filter(Boolean).length;
|
|
2099
2128
|
return {
|
|
2100
2129
|
name: input.name,
|
|
2101
2130
|
title: input.title?.trim() || toTitleCase(input.name),
|
|
@@ -2815,10 +2844,10 @@ function weaverAmendments(input, where) {
|
|
|
2815
2844
|
version: AG_UI_PROTOCOL_VERSION
|
|
2816
2845
|
}
|
|
2817
2846
|
] : [];
|
|
2818
|
-
|
|
2819
|
-
if (!directory) {
|
|
2847
|
+
if (where === void 0 || where === "") {
|
|
2820
2848
|
return packages;
|
|
2821
2849
|
}
|
|
2850
|
+
const directory = normalizeProjectRoot(where);
|
|
2822
2851
|
return [
|
|
2823
2852
|
...packages,
|
|
2824
2853
|
{
|
|
@@ -2827,19 +2856,19 @@ function weaverAmendments(input, where) {
|
|
|
2827
2856
|
assets: [
|
|
2828
2857
|
{
|
|
2829
2858
|
glob: "**/*.json",
|
|
2830
|
-
input:
|
|
2859
|
+
input: joinProjectPath(directory, "src/lib/i18n"),
|
|
2831
2860
|
from: "workspace",
|
|
2832
2861
|
output: `i18n/${w.id}`
|
|
2833
2862
|
}
|
|
2834
2863
|
]
|
|
2835
2864
|
},
|
|
2836
|
-
{ kind: "stylesheet-source", sourceRoot:
|
|
2865
|
+
{ kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
|
|
2837
2866
|
{
|
|
2838
2867
|
kind: "compose-plugin",
|
|
2839
2868
|
id: w.id,
|
|
2840
2869
|
symbol: `${w.propertyName}Plugin`,
|
|
2841
2870
|
capabilities: w.capabilities,
|
|
2842
|
-
sourceRoot:
|
|
2871
|
+
sourceRoot: joinProjectPath(directory, "src")
|
|
2843
2872
|
}
|
|
2844
2873
|
];
|
|
2845
2874
|
}
|
|
@@ -2847,10 +2876,10 @@ function weaverAmendments(input, where) {
|
|
|
2847
2876
|
// ../devkit/src/recipes/auth-source/recipe-amendments.ts
|
|
2848
2877
|
function authSourceAmendments(input, where) {
|
|
2849
2878
|
const a = resolveAuthSourceInput(input);
|
|
2850
|
-
|
|
2851
|
-
if (a.bare || !directory) {
|
|
2879
|
+
if (a.bare || where === void 0 || where === "") {
|
|
2852
2880
|
return [];
|
|
2853
2881
|
}
|
|
2882
|
+
const directory = normalizeProjectRoot(where);
|
|
2854
2883
|
return [
|
|
2855
2884
|
{
|
|
2856
2885
|
kind: "build-target",
|
|
@@ -2858,7 +2887,7 @@ function authSourceAmendments(input, where) {
|
|
|
2858
2887
|
assets: [
|
|
2859
2888
|
{
|
|
2860
2889
|
glob: "**/*.json",
|
|
2861
|
-
input:
|
|
2890
|
+
input: joinProjectPath(directory, "i18n"),
|
|
2862
2891
|
from: "workspace",
|
|
2863
2892
|
output: `i18n/${SESSION_PLUGIN_ID}`
|
|
2864
2893
|
}
|
|
@@ -3559,7 +3588,7 @@ function read(ts, file, call) {
|
|
|
3559
3588
|
return {
|
|
3560
3589
|
at: at2,
|
|
3561
3590
|
id: id.text,
|
|
3562
|
-
callable: callable
|
|
3591
|
+
callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
|
|
3563
3592
|
described: properties.has("description"),
|
|
3564
3593
|
answers: properties.has("answers"),
|
|
3565
3594
|
returnsValue: returnsValue(ts, properties.get("run")),
|
|
@@ -3687,8 +3716,9 @@ function loadTypeScript(fromDir) {
|
|
|
3687
3716
|
}
|
|
3688
3717
|
|
|
3689
3718
|
// src/lib/run.ts
|
|
3690
|
-
import {
|
|
3691
|
-
import {
|
|
3719
|
+
import { spawnSync } from "node:child_process";
|
|
3720
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
3721
|
+
import { join as join4 } from "node:path";
|
|
3692
3722
|
|
|
3693
3723
|
// src/lib/args.ts
|
|
3694
3724
|
var ArgError = class extends Error {
|
|
@@ -4034,10 +4064,7 @@ var Amender = class {
|
|
|
4034
4064
|
if (!/@import\s+['"]tailwindcss['"]/.test(css)) {
|
|
4035
4065
|
return;
|
|
4036
4066
|
}
|
|
4037
|
-
const source = posix.relative(
|
|
4038
|
-
this.displayName(dirname2(entry)),
|
|
4039
|
-
amendment.sourceRoot
|
|
4040
|
-
);
|
|
4067
|
+
const source = posix.relative(this.displayName(dirname2(entry)), amendment.sourceRoot) || ".";
|
|
4041
4068
|
const next = ensureStylesheetSource(css, source);
|
|
4042
4069
|
if (next === css) {
|
|
4043
4070
|
return;
|
|
@@ -4164,8 +4191,409 @@ function relativeImport(fromDir, sourceRoot) {
|
|
|
4164
4191
|
return path.startsWith(".") ? path : `./${path}`;
|
|
4165
4192
|
}
|
|
4166
4193
|
|
|
4194
|
+
// src/lib/init.ts
|
|
4195
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
4196
|
+
import { join as join3 } from "node:path";
|
|
4197
|
+
|
|
4198
|
+
// src/lib/init-plan.ts
|
|
4199
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
4200
|
+
import { basename as basename2, join as join2, resolve as resolve5 } from "node:path";
|
|
4201
|
+
|
|
4202
|
+
// src/lib/nx-applications.ts
|
|
4203
|
+
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
4204
|
+
import { basename, join, relative as relative3, sep as sep3 } from "node:path";
|
|
4205
|
+
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "tmp", "coverage"]);
|
|
4206
|
+
function nxApplications(workspaceRoot) {
|
|
4207
|
+
const found = [];
|
|
4208
|
+
const walk = (folder) => {
|
|
4209
|
+
const project = join(folder, "project.json");
|
|
4210
|
+
if (existsSync3(project)) {
|
|
4211
|
+
const config = readJsonFile(project);
|
|
4212
|
+
const name = typeof config["name"] === "string" ? config["name"] : basename(folder);
|
|
4213
|
+
if (config["projectType"] === "application" && !name.endsWith("-e2e")) {
|
|
4214
|
+
found.push({
|
|
4215
|
+
name,
|
|
4216
|
+
root: relative3(workspaceRoot, folder).split(sep3).join("/")
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
for (const entry of readdirSync(folder, { withFileTypes: true })) {
|
|
4221
|
+
if (!entry.isDirectory() || SKIPPED.has(entry.name) || entry.name.startsWith(".")) {
|
|
4222
|
+
continue;
|
|
4223
|
+
}
|
|
4224
|
+
walk(join(folder, entry.name));
|
|
4225
|
+
}
|
|
4226
|
+
};
|
|
4227
|
+
walk(workspaceRoot);
|
|
4228
|
+
return found.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
// src/lib/package-manager.ts
|
|
4232
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
4233
|
+
import { resolve as resolve4 } from "node:path";
|
|
4234
|
+
var PACKAGE_MANAGERS = [
|
|
4235
|
+
"npm",
|
|
4236
|
+
"pnpm",
|
|
4237
|
+
"yarn",
|
|
4238
|
+
"bun"
|
|
4239
|
+
];
|
|
4240
|
+
var LOCKFILES = [
|
|
4241
|
+
["package-lock.json", "npm"],
|
|
4242
|
+
["npm-shrinkwrap.json", "npm"],
|
|
4243
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
4244
|
+
["yarn.lock", "yarn"],
|
|
4245
|
+
["bun.lock", "bun"],
|
|
4246
|
+
["bun.lockb", "bun"]
|
|
4247
|
+
];
|
|
4248
|
+
function detectPackageManager(root) {
|
|
4249
|
+
for (const [lockfile, manager] of LOCKFILES) {
|
|
4250
|
+
if (existsSync4(resolve4(root, lockfile))) {
|
|
4251
|
+
return { manager, lockfile };
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
return { manager: "npm" };
|
|
4255
|
+
}
|
|
4256
|
+
function packageManagerFrom(value) {
|
|
4257
|
+
if (value === void 0) {
|
|
4258
|
+
return void 0;
|
|
4259
|
+
}
|
|
4260
|
+
if (!PACKAGE_MANAGERS.includes(value)) {
|
|
4261
|
+
throw new ArgError(
|
|
4262
|
+
`Option --package-manager must be one of: ${PACKAGE_MANAGERS.join(", ")}.`
|
|
4263
|
+
);
|
|
4264
|
+
}
|
|
4265
|
+
return value;
|
|
4266
|
+
}
|
|
4267
|
+
function installCommand(manager, packages, dev) {
|
|
4268
|
+
const devFlag = manager === "bun" ? "-d" : "-D";
|
|
4269
|
+
const verb = manager === "npm" ? "install" : "add";
|
|
4270
|
+
return [manager, verb, ...dev ? [devFlag] : [], ...packages];
|
|
4271
|
+
}
|
|
4272
|
+
function execCommand(manager, argv) {
|
|
4273
|
+
if (manager === "npm") {
|
|
4274
|
+
return ["npx", ...argv];
|
|
4275
|
+
}
|
|
4276
|
+
if (manager === "pnpm") {
|
|
4277
|
+
return ["pnpm", "exec", ...argv];
|
|
4278
|
+
}
|
|
4279
|
+
if (manager === "bun") {
|
|
4280
|
+
return ["bunx", ...argv];
|
|
4281
|
+
}
|
|
4282
|
+
return ["yarn", ...argv];
|
|
4283
|
+
}
|
|
4284
|
+
function runScriptCommand(manager, script) {
|
|
4285
|
+
return `${manager} ${script}`;
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// src/lib/init-plan.ts
|
|
4289
|
+
var RUNTIME_PACKAGES = [
|
|
4290
|
+
"@loomweaver/shell",
|
|
4291
|
+
"@loomweaver/plugin-sdk",
|
|
4292
|
+
"@loomweaver/frame-kit",
|
|
4293
|
+
"@angular/cdk",
|
|
4294
|
+
"@jsverse/transloco",
|
|
4295
|
+
"@ng-icons/heroicons"
|
|
4296
|
+
];
|
|
4297
|
+
var STYLE_PACKAGES = [
|
|
4298
|
+
"tailwindcss",
|
|
4299
|
+
"@tailwindcss/postcss",
|
|
4300
|
+
"@tailwindcss/typography"
|
|
4301
|
+
];
|
|
4302
|
+
var SERVICE_WORKER = "@angular/service-worker";
|
|
4303
|
+
var NX_COLLECTION = "@loomweaver/devkit";
|
|
4304
|
+
function planInit(args, deps) {
|
|
4305
|
+
const found = findWorkspace(deps.cwd);
|
|
4306
|
+
if (!found?.kind) {
|
|
4307
|
+
throw new WorkspaceError(
|
|
4308
|
+
`No Angular application or Nx workspace here: expected an angular.json or an nx.json at or above ${resolve5(deps.cwd)}. Create one first ("ng new my-studio --style=css --ssr=false", or "npx create-nx-workspace") and run init inside it.`
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
const workspace = found;
|
|
4312
|
+
const manifest2 = readManifest(workspace.root);
|
|
4313
|
+
const override = packageManagerFrom(stringFlag(args, "package-manager"));
|
|
4314
|
+
const detected = detectPackageManager(workspace.root);
|
|
4315
|
+
const app = workspace.kind === "nx" ? chooseApplication(workspace.root, stringFlag(args, "app")) : void 0;
|
|
4316
|
+
const name = app?.name ?? kebab(manifest2.name ?? basename2(workspace.root));
|
|
4317
|
+
const styles = stringFlag(args, "styles") ?? "tailwind";
|
|
4318
|
+
const declared = { ...manifest2.dependencies, ...manifest2.devDependencies };
|
|
4319
|
+
const missing = (names) => names.filter((candidate) => !Object.hasOwn(declared, candidate));
|
|
4320
|
+
return {
|
|
4321
|
+
workspace,
|
|
4322
|
+
manager: override ?? detected.manager,
|
|
4323
|
+
lockfile: override ? void 0 : detected.lockfile,
|
|
4324
|
+
name,
|
|
4325
|
+
title: stringFlag(args, "title") ?? titleCase(name),
|
|
4326
|
+
styles,
|
|
4327
|
+
weaver: weaverId(args),
|
|
4328
|
+
runtime: [
|
|
4329
|
+
...missing(RUNTIME_PACKAGES),
|
|
4330
|
+
...Object.hasOwn(declared, SERVICE_WORKER) ? [] : [serviceWorkerSpec(workspace.root, manifest2)]
|
|
4331
|
+
],
|
|
4332
|
+
dev: [
|
|
4333
|
+
...styles === "tailwind" ? missing(STYLE_PACKAGES) : [],
|
|
4334
|
+
...workspace.kind === "nx" && !Object.hasOwn(declared, NX_COLLECTION) ? [collectionSpec(deps.version)] : []
|
|
4335
|
+
],
|
|
4336
|
+
app,
|
|
4337
|
+
dryRun: boolFlag(args, "dry-run") === true
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
function chooseApplication(root, wanted) {
|
|
4341
|
+
const apps = nxApplications(root);
|
|
4342
|
+
const names = apps.map((candidate) => candidate.name).join(", ");
|
|
4343
|
+
if (wanted) {
|
|
4344
|
+
const app = apps.find((candidate) => candidate.name === wanted);
|
|
4345
|
+
if (!app) {
|
|
4346
|
+
throw new WorkspaceError(
|
|
4347
|
+
`No application named "${wanted}" in this workspace${apps.length > 0 ? `; there are: ${names}` : ""}.`
|
|
4348
|
+
);
|
|
4349
|
+
}
|
|
4350
|
+
return app;
|
|
4351
|
+
}
|
|
4352
|
+
if (apps.length === 1) {
|
|
4353
|
+
return apps[0];
|
|
4354
|
+
}
|
|
4355
|
+
if (apps.length === 0) {
|
|
4356
|
+
throw new WorkspaceError(
|
|
4357
|
+
'This Nx workspace has no application (no project.json with "projectType": "application"). Generate one first, then run init with --app <name>.'
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
throw new WorkspaceError(
|
|
4361
|
+
`This Nx workspace has ${apps.length} applications, so none was chosen: ${names}. Name one with --app.`
|
|
4362
|
+
);
|
|
4363
|
+
}
|
|
4364
|
+
function weaverId(args) {
|
|
4365
|
+
const value = args.flags["weaver"];
|
|
4366
|
+
if (value === false) {
|
|
4367
|
+
return void 0;
|
|
4368
|
+
}
|
|
4369
|
+
if (value === true) {
|
|
4370
|
+
throw new ArgError(
|
|
4371
|
+
"Option --weaver needs a plugin id, or pass --no-weaver."
|
|
4372
|
+
);
|
|
4373
|
+
}
|
|
4374
|
+
return value ?? "notes";
|
|
4375
|
+
}
|
|
4376
|
+
function readManifest(root) {
|
|
4377
|
+
const file = join2(root, "package.json");
|
|
4378
|
+
return existsSync5(file) ? readJsonFile(file) : {};
|
|
4379
|
+
}
|
|
4380
|
+
function serviceWorkerSpec(root, manifest2) {
|
|
4381
|
+
const installed = join2(root, "node_modules/@angular/core/package.json");
|
|
4382
|
+
if (existsSync5(installed)) {
|
|
4383
|
+
const { version } = JSON.parse(readFileSync3(installed, "utf8"));
|
|
4384
|
+
return `${SERVICE_WORKER}@${version}`;
|
|
4385
|
+
}
|
|
4386
|
+
const declared = manifest2.dependencies?.["@angular/core"];
|
|
4387
|
+
return declared ? `${SERVICE_WORKER}@${declared}` : SERVICE_WORKER;
|
|
4388
|
+
}
|
|
4389
|
+
function collectionSpec(version) {
|
|
4390
|
+
return version === "0.0.0" ? `${NX_COLLECTION}@latest` : `${NX_COLLECTION}@${version}`;
|
|
4391
|
+
}
|
|
4392
|
+
function kebab(value) {
|
|
4393
|
+
const unscoped = value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : value;
|
|
4394
|
+
return unscoped.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("-");
|
|
4395
|
+
}
|
|
4396
|
+
function titleCase(name) {
|
|
4397
|
+
return name.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
// src/lib/init.ts
|
|
4401
|
+
var INIT_FLAGS = [
|
|
4402
|
+
"title",
|
|
4403
|
+
"styles",
|
|
4404
|
+
"weaver",
|
|
4405
|
+
"app",
|
|
4406
|
+
"package-manager",
|
|
4407
|
+
"dry-run"
|
|
4408
|
+
];
|
|
4409
|
+
function init(args, io, deps) {
|
|
4410
|
+
rejectUnknownFlags(args, INIT_FLAGS);
|
|
4411
|
+
const plan = planInit(args, deps);
|
|
4412
|
+
report(io, plan);
|
|
4413
|
+
let didSomething = false;
|
|
4414
|
+
for (const step of stepsFor(plan)) {
|
|
4415
|
+
if (step.done) {
|
|
4416
|
+
io.out(`${step.label}: already there.`);
|
|
4417
|
+
continue;
|
|
4418
|
+
}
|
|
4419
|
+
didSomething = true;
|
|
4420
|
+
const code = perform(step, plan, io, deps);
|
|
4421
|
+
if (code !== 0) {
|
|
4422
|
+
io.err(`${step.label} failed; nothing after it was run.`);
|
|
4423
|
+
return code;
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
if (!plan.weaver) {
|
|
4427
|
+
io.out(
|
|
4428
|
+
"First weaver: skipped as asked; the rail stays empty until a plugin is composed in."
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
if (!didSomething) {
|
|
4432
|
+
io.out(
|
|
4433
|
+
"Nothing left to do: the packages, the distribution and the weaver are all in place."
|
|
4434
|
+
);
|
|
4435
|
+
}
|
|
4436
|
+
io.out(
|
|
4437
|
+
`${plan.dryRun ? "Then serve" : "Serve"} it with: ${serveCommand(plan)}`
|
|
4438
|
+
);
|
|
4439
|
+
return 0;
|
|
4440
|
+
}
|
|
4441
|
+
function perform(step, plan, io, deps) {
|
|
4442
|
+
if (step.command) {
|
|
4443
|
+
if (plan.dryRun) {
|
|
4444
|
+
io.out(`Would run: ${step.command.join(" ")}`);
|
|
4445
|
+
return 0;
|
|
4446
|
+
}
|
|
4447
|
+
io.out(`${step.label}: ${step.describe}`);
|
|
4448
|
+
deps.exec(step.command, plan.workspace.root);
|
|
4449
|
+
return 0;
|
|
4450
|
+
}
|
|
4451
|
+
if (step.argv) {
|
|
4452
|
+
io.out(
|
|
4453
|
+
plan.dryRun ? `Would ${step.label.toLowerCase()}: ${step.describe}` : `${step.label}: ${step.describe}`
|
|
4454
|
+
);
|
|
4455
|
+
if (plan.dryRun && step.after) {
|
|
4456
|
+
io.out(` (${step.after})`);
|
|
4457
|
+
}
|
|
4458
|
+
return deps.run(plan.dryRun ? [...step.argv, "--dry-run"] : step.argv);
|
|
4459
|
+
}
|
|
4460
|
+
return 0;
|
|
4461
|
+
}
|
|
4462
|
+
function report(io, plan) {
|
|
4463
|
+
const where = plan.workspace.kind === "nx" ? `Nx workspace, application ${plan.app?.name}` : "Angular CLI application";
|
|
4464
|
+
io.out(
|
|
4465
|
+
`${plan.dryRun ? "Would take" : "Taking"} ${plan.workspace.root} to a product: ${where}, packages with ${managerLabel(plan)}.`
|
|
4466
|
+
);
|
|
4467
|
+
}
|
|
4468
|
+
function managerLabel(plan) {
|
|
4469
|
+
if (plan.lockfile) {
|
|
4470
|
+
return `${plan.manager} (from ${plan.lockfile})`;
|
|
4471
|
+
}
|
|
4472
|
+
if (plan.manager === "npm") {
|
|
4473
|
+
return "npm (no lockfile found, so npm)";
|
|
4474
|
+
}
|
|
4475
|
+
return plan.manager;
|
|
4476
|
+
}
|
|
4477
|
+
function stepsFor(plan) {
|
|
4478
|
+
return plan.workspace.kind === "nx" ? nxSteps(plan) : angularSteps(plan);
|
|
4479
|
+
}
|
|
4480
|
+
function angularSteps(plan) {
|
|
4481
|
+
const root = plan.workspace.root;
|
|
4482
|
+
const steps = [
|
|
4483
|
+
...installSteps(plan),
|
|
4484
|
+
{
|
|
4485
|
+
label: "Distribution",
|
|
4486
|
+
describe: `scaffold the composition root for "${plan.title}" (${plan.styles}) and wire the build`,
|
|
4487
|
+
done: composesShell(join3(root, "src/app/app.config.ts")),
|
|
4488
|
+
argv: [
|
|
4489
|
+
"distribution",
|
|
4490
|
+
"--name",
|
|
4491
|
+
plan.name,
|
|
4492
|
+
"--title",
|
|
4493
|
+
plan.title,
|
|
4494
|
+
"--styles",
|
|
4495
|
+
plan.styles,
|
|
4496
|
+
"--out",
|
|
4497
|
+
root,
|
|
4498
|
+
"--force"
|
|
4499
|
+
]
|
|
4500
|
+
}
|
|
4501
|
+
];
|
|
4502
|
+
if (plan.weaver) {
|
|
4503
|
+
steps.push({
|
|
4504
|
+
label: "First weaver",
|
|
4505
|
+
describe: weaverDescription(plan.weaver),
|
|
4506
|
+
done: existsSync6(join3(root, "src", plan.weaver, "src/index.ts")),
|
|
4507
|
+
after: "planned against the composition root as it is now; once the distribution step has run, the weaver composes into it",
|
|
4508
|
+
argv: [
|
|
4509
|
+
"weaver",
|
|
4510
|
+
"--id",
|
|
4511
|
+
plan.weaver,
|
|
4512
|
+
"--command",
|
|
4513
|
+
"--out",
|
|
4514
|
+
join3(root, "src", plan.weaver)
|
|
4515
|
+
]
|
|
4516
|
+
});
|
|
4517
|
+
}
|
|
4518
|
+
return steps;
|
|
4519
|
+
}
|
|
4520
|
+
function nxSteps(plan) {
|
|
4521
|
+
const root = plan.workspace.root;
|
|
4522
|
+
const app = plan.app;
|
|
4523
|
+
if (!app) {
|
|
4524
|
+
return [];
|
|
4525
|
+
}
|
|
4526
|
+
const generator = (name, argv) => execCommand(plan.manager, ["nx", "g", `${NX_COLLECTION}:${name}`, ...argv]);
|
|
4527
|
+
const steps = [
|
|
4528
|
+
...installSteps(plan),
|
|
4529
|
+
{
|
|
4530
|
+
label: "Distribution",
|
|
4531
|
+
describe: `nx g ${NX_COLLECTION}:distribution over ${app.name} for "${plan.title}" (${plan.styles})`,
|
|
4532
|
+
done: composesShell(join3(root, app.root, "src/app/app.config.ts")),
|
|
4533
|
+
command: generator("distribution", [
|
|
4534
|
+
"--name",
|
|
4535
|
+
app.name,
|
|
4536
|
+
"--directory",
|
|
4537
|
+
app.root,
|
|
4538
|
+
"--title",
|
|
4539
|
+
plan.title,
|
|
4540
|
+
"--styles",
|
|
4541
|
+
plan.styles,
|
|
4542
|
+
"--force"
|
|
4543
|
+
])
|
|
4544
|
+
}
|
|
4545
|
+
];
|
|
4546
|
+
if (plan.weaver) {
|
|
4547
|
+
steps.push({
|
|
4548
|
+
label: "First weaver",
|
|
4549
|
+
describe: weaverDescription(plan.weaver),
|
|
4550
|
+
done: existsSync6(join3(root, "libs", `${plan.weaver}-weaver`)),
|
|
4551
|
+
command: generator("weaver", [
|
|
4552
|
+
"--id",
|
|
4553
|
+
plan.weaver,
|
|
4554
|
+
"--command",
|
|
4555
|
+
"--app",
|
|
4556
|
+
app.name
|
|
4557
|
+
])
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
return steps;
|
|
4561
|
+
}
|
|
4562
|
+
function installSteps(plan) {
|
|
4563
|
+
return [
|
|
4564
|
+
{
|
|
4565
|
+
label: "Install",
|
|
4566
|
+
describe: plan.runtime.join(" "),
|
|
4567
|
+
done: plan.runtime.length === 0,
|
|
4568
|
+
command: installCommand(plan.manager, plan.runtime, false)
|
|
4569
|
+
},
|
|
4570
|
+
{
|
|
4571
|
+
label: "Install for development",
|
|
4572
|
+
describe: plan.dev.join(" "),
|
|
4573
|
+
done: plan.dev.length === 0,
|
|
4574
|
+
command: installCommand(plan.manager, plan.dev, true)
|
|
4575
|
+
}
|
|
4576
|
+
];
|
|
4577
|
+
}
|
|
4578
|
+
function weaverDescription(id) {
|
|
4579
|
+
return `"${id}" with a command on mod+shift+${id.charAt(0)}`;
|
|
4580
|
+
}
|
|
4581
|
+
function serveCommand(plan) {
|
|
4582
|
+
if (plan.workspace.kind === "nx") {
|
|
4583
|
+
return execCommand(plan.manager, [
|
|
4584
|
+
"nx",
|
|
4585
|
+
"serve",
|
|
4586
|
+
plan.app?.name ?? ""
|
|
4587
|
+
]).join(" ");
|
|
4588
|
+
}
|
|
4589
|
+
return runScriptCommand(plan.manager, "start");
|
|
4590
|
+
}
|
|
4591
|
+
function composesShell(file) {
|
|
4592
|
+
return existsSync6(file) && readFileSync4(file, "utf8").includes("provideShell(");
|
|
4593
|
+
}
|
|
4594
|
+
|
|
4167
4595
|
// src/lib/scaffold.ts
|
|
4168
|
-
import { relative as
|
|
4596
|
+
import { relative as relative4, resolve as resolve6 } from "node:path";
|
|
4169
4597
|
function findScaffold2(name) {
|
|
4170
4598
|
const scaffold2 = findScaffold(name);
|
|
4171
4599
|
if (!scaffold2) {
|
|
@@ -4182,16 +4610,16 @@ function allowedFlagsFor(scaffold2) {
|
|
|
4182
4610
|
]);
|
|
4183
4611
|
}
|
|
4184
4612
|
function readFlag(args, option) {
|
|
4185
|
-
const
|
|
4186
|
-
const value = args.flags[
|
|
4613
|
+
const kebab2 = kebabCase(option.name);
|
|
4614
|
+
const value = args.flags[kebab2] ?? args.flags[option.name];
|
|
4187
4615
|
if (value === void 0) {
|
|
4188
4616
|
return void 0;
|
|
4189
4617
|
}
|
|
4190
4618
|
if (option.type === "string" && typeof value !== "string") {
|
|
4191
|
-
throw new ArgError(`Option --${
|
|
4619
|
+
throw new ArgError(`Option --${kebab2} needs a value.`);
|
|
4192
4620
|
}
|
|
4193
4621
|
if (option.type === "boolean" && typeof value !== "boolean") {
|
|
4194
|
-
throw new ArgError(`Option --${
|
|
4622
|
+
throw new ArgError(`Option --${kebab2} does not take a value.`);
|
|
4195
4623
|
}
|
|
4196
4624
|
return value;
|
|
4197
4625
|
}
|
|
@@ -4201,9 +4629,7 @@ function valuesFor(scaffold2, args) {
|
|
|
4201
4629
|
const value = readFlag(args, option);
|
|
4202
4630
|
if (value === void 0) {
|
|
4203
4631
|
if (option.required) {
|
|
4204
|
-
throw new ArgError(
|
|
4205
|
-
`Option --${kebabCase(option.name)} is required.`
|
|
4206
|
-
);
|
|
4632
|
+
throw new ArgError(`Option --${kebabCase(option.name)} is required.`);
|
|
4207
4633
|
}
|
|
4208
4634
|
continue;
|
|
4209
4635
|
}
|
|
@@ -4217,8 +4643,13 @@ function valuesFor(scaffold2, args) {
|
|
|
4217
4643
|
return values;
|
|
4218
4644
|
}
|
|
4219
4645
|
function directoryFromOut(out) {
|
|
4220
|
-
const
|
|
4221
|
-
|
|
4646
|
+
const target = resolve6(out ?? ".");
|
|
4647
|
+
const base = findWorkspace(target)?.root ?? process.cwd();
|
|
4648
|
+
const below = relative4(base, target);
|
|
4649
|
+
if (below.startsWith("..")) {
|
|
4650
|
+
return "";
|
|
4651
|
+
}
|
|
4652
|
+
return below || ".";
|
|
4222
4653
|
}
|
|
4223
4654
|
function scaffoldValues(scaffold2, args) {
|
|
4224
4655
|
const values = valuesFor(scaffold2, args);
|
|
@@ -4249,16 +4680,16 @@ import {
|
|
|
4249
4680
|
rmSync,
|
|
4250
4681
|
writeFileSync as writeFileSync2
|
|
4251
4682
|
} from "node:fs";
|
|
4252
|
-
import { dirname as dirname3, isAbsolute, relative as
|
|
4683
|
+
import { dirname as dirname3, isAbsolute, relative as relative5, resolve as resolve7 } from "node:path";
|
|
4253
4684
|
var WriteError = class extends Error {
|
|
4254
4685
|
};
|
|
4255
4686
|
function planWrite(files, root) {
|
|
4256
|
-
const absoluteRoot =
|
|
4687
|
+
const absoluteRoot = resolve7(root);
|
|
4257
4688
|
const planned = [];
|
|
4258
4689
|
const conflicts = [];
|
|
4259
4690
|
for (const path of Object.keys(files).toSorted((a, b) => a.localeCompare(b))) {
|
|
4260
|
-
const absolute =
|
|
4261
|
-
const inside =
|
|
4691
|
+
const absolute = resolve7(absoluteRoot, path);
|
|
4692
|
+
const inside = relative5(absoluteRoot, absolute);
|
|
4262
4693
|
if (inside.startsWith("..") || isAbsolute(inside)) {
|
|
4263
4694
|
throw new WriteError(`Refusing to write outside the target directory: ${path}`);
|
|
4264
4695
|
}
|
|
@@ -4286,7 +4717,7 @@ function entryExists(absolute) {
|
|
|
4286
4717
|
}
|
|
4287
4718
|
}
|
|
4288
4719
|
function assertResolvesInsideRoot(root, path, absolute) {
|
|
4289
|
-
const inside =
|
|
4720
|
+
const inside = relative5(realpathSync(root), realpathSync(dirname3(absolute)));
|
|
4290
4721
|
if (inside.startsWith("..") || isAbsolute(inside)) {
|
|
4291
4722
|
throw new WriteError(
|
|
4292
4723
|
`Refusing to write through a link that leaves the target directory: ${path}`
|
|
@@ -4300,7 +4731,7 @@ function replaceSymlinkEntry(absolute) {
|
|
|
4300
4731
|
}
|
|
4301
4732
|
|
|
4302
4733
|
// src/lib/run.ts
|
|
4303
|
-
var VERSION = "0.9.
|
|
4734
|
+
var VERSION = "0.9.3";
|
|
4304
4735
|
function help() {
|
|
4305
4736
|
const commands = SCAFFOLDS.map((s) => ` ${s.name.padEnd(16)}${s.summary}`);
|
|
4306
4737
|
return [
|
|
@@ -4312,6 +4743,10 @@ function help() {
|
|
|
4312
4743
|
...commands,
|
|
4313
4744
|
"",
|
|
4314
4745
|
"Other commands:",
|
|
4746
|
+
" init take the Angular application or Nx workspace you are in to a running product:",
|
|
4747
|
+
" install the platform, scaffold the distribution and a first weaver, say what to serve",
|
|
4748
|
+
" [--title <t>] [--styles tailwind|precompiled] [--weaver <id>|--no-weaver] [--app <nx app>]",
|
|
4749
|
+
" [--package-manager npm|pnpm|yarn|bun] [--dry-run]",
|
|
4315
4750
|
" list print every scaffold with its options",
|
|
4316
4751
|
" validate-manifest --id <id> [--name <name>] [--capabilities <a,b>]",
|
|
4317
4752
|
" validate-i18n --dir <dir> check <lang>.json bundles for key parity",
|
|
@@ -4370,15 +4805,17 @@ function validateManifestCommand(args, io) {
|
|
|
4370
4805
|
}
|
|
4371
4806
|
function readBundles(dir) {
|
|
4372
4807
|
const bundles = {};
|
|
4373
|
-
for (const entry of
|
|
4808
|
+
for (const entry of readdirSync2(dir)) {
|
|
4374
4809
|
if (!entry.endsWith(".json")) {
|
|
4375
4810
|
continue;
|
|
4376
4811
|
}
|
|
4377
4812
|
const language = entry.slice(0, -".json".length);
|
|
4378
4813
|
try {
|
|
4379
|
-
bundles[language] = JSON.parse(
|
|
4814
|
+
bundles[language] = JSON.parse(readFileSync5(join4(dir, entry), "utf8"));
|
|
4380
4815
|
} catch (error) {
|
|
4381
|
-
throw new ArgError(
|
|
4816
|
+
throw new ArgError(
|
|
4817
|
+
`${entry} is not valid JSON: ${error.message}`
|
|
4818
|
+
);
|
|
4382
4819
|
}
|
|
4383
4820
|
}
|
|
4384
4821
|
if (Object.keys(bundles).length === 0) {
|
|
@@ -4397,15 +4834,15 @@ function validateI18nCommand(args, io) {
|
|
|
4397
4834
|
function readSources(dir) {
|
|
4398
4835
|
const sources = [];
|
|
4399
4836
|
const walk = (folder) => {
|
|
4400
|
-
for (const entry of
|
|
4837
|
+
for (const entry of readdirSync2(folder, { withFileTypes: true })) {
|
|
4401
4838
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) {
|
|
4402
4839
|
continue;
|
|
4403
4840
|
}
|
|
4404
|
-
const path =
|
|
4841
|
+
const path = join4(folder, entry.name);
|
|
4405
4842
|
if (entry.isDirectory()) {
|
|
4406
4843
|
walk(path);
|
|
4407
4844
|
} else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".spec.ts")) {
|
|
4408
|
-
sources.push({ path, text:
|
|
4845
|
+
sources.push({ path, text: readFileSync5(path, "utf8") });
|
|
4409
4846
|
}
|
|
4410
4847
|
}
|
|
4411
4848
|
};
|
|
@@ -4428,19 +4865,25 @@ function validateCommandsCommand(args, io) {
|
|
|
4428
4865
|
`typescript is not installed where ${dir} can reach it; the check reads sources with the TypeScript compiler API, so run it inside the project.`
|
|
4429
4866
|
);
|
|
4430
4867
|
}
|
|
4431
|
-
return reportFindings(
|
|
4868
|
+
return reportFindings(
|
|
4869
|
+
io,
|
|
4870
|
+
validateCommands(readSources(dir), ts),
|
|
4871
|
+
boolFlag(args, "strict") === true
|
|
4872
|
+
);
|
|
4432
4873
|
}
|
|
4433
4874
|
function readCatalog(file) {
|
|
4434
4875
|
let raw;
|
|
4435
4876
|
try {
|
|
4436
|
-
raw =
|
|
4877
|
+
raw = readFileSync5(file, "utf8");
|
|
4437
4878
|
} catch (error) {
|
|
4438
4879
|
throw new ArgError(`Cannot read ${file}: ${error.message}`);
|
|
4439
4880
|
}
|
|
4440
4881
|
try {
|
|
4441
4882
|
return JSON.parse(raw);
|
|
4442
4883
|
} catch (error) {
|
|
4443
|
-
throw new ArgError(
|
|
4884
|
+
throw new ArgError(
|
|
4885
|
+
`${file} is not valid JSON: ${error.message}`
|
|
4886
|
+
);
|
|
4444
4887
|
}
|
|
4445
4888
|
}
|
|
4446
4889
|
function validateCatalogCommand(args, io) {
|
|
@@ -4517,7 +4960,26 @@ function scaffold(args, io) {
|
|
|
4517
4960
|
reportAmendments(io, amend, true);
|
|
4518
4961
|
return 0;
|
|
4519
4962
|
}
|
|
4520
|
-
|
|
4963
|
+
var ExecError = class extends Error {
|
|
4964
|
+
};
|
|
4965
|
+
function execInherit(command, cwd) {
|
|
4966
|
+
const result = spawnSync(command[0], command.slice(1), {
|
|
4967
|
+
cwd,
|
|
4968
|
+
stdio: "inherit",
|
|
4969
|
+
shell: process.platform === "win32"
|
|
4970
|
+
});
|
|
4971
|
+
if (result.error) {
|
|
4972
|
+
throw new ExecError(
|
|
4973
|
+
`Could not run "${command.join(" ")}": ${result.error.message}`
|
|
4974
|
+
);
|
|
4975
|
+
}
|
|
4976
|
+
if (result.status !== 0) {
|
|
4977
|
+
throw new ExecError(
|
|
4978
|
+
`"${command.join(" ")}" exited with ${result.status ?? "a signal"}.`
|
|
4979
|
+
);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
function run(argv, io, deps) {
|
|
4521
4983
|
let args;
|
|
4522
4984
|
try {
|
|
4523
4985
|
args = parseArgs(argv);
|
|
@@ -4538,6 +5000,14 @@ function run(argv, io) {
|
|
|
4538
5000
|
return 1;
|
|
4539
5001
|
}
|
|
4540
5002
|
try {
|
|
5003
|
+
if (args.command === "init") {
|
|
5004
|
+
return init(args, io, {
|
|
5005
|
+
cwd: deps?.cwd ?? process.cwd(),
|
|
5006
|
+
exec: deps?.exec ?? execInherit,
|
|
5007
|
+
run: deps?.run ?? ((inner) => run(inner, io, deps)),
|
|
5008
|
+
version: deps?.version ?? VERSION
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
4541
5011
|
if (args.command === "list") {
|
|
4542
5012
|
return list(args, io);
|
|
4543
5013
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomweaver/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "LoomWeaver: open-source plugin platform for Angular workbenches. The scaffolding CLI: generates weavers, distributions and integrations into any project, with no Nx workspace and no checkout required.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loomweaver",
|