@loomweaver/cli 0.9.2 → 0.9.4

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.
Files changed (2) hide show
  1. package/dist/main.mjs +667 -195
  2. 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 configurations = { ...asObject(next["configurations"]) };
103
- const production = { ...asObject(configurations["production"]) };
104
- if (amendment.serviceWorker && production["serviceWorker"] === void 0) {
105
- production["serviceWorker"] = joinProjectPath(
106
- projectRoot,
107
- amendment.serviceWorker
108
- );
109
- added.push(`production serviceWorker: ${production["serviceWorker"]}`);
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(${[...names, `'${id}'`].join(", ")})`
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
- return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, ${providers.map((line) => `${line}, `).join("")}provideTranslationNamespaces('${amendment.id}'), provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(
413
- ", "
414
- )}] }) and ...providePlugins(${amendment.symbol}). Without it none of its contributions appear.`;
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((asset) => asset.input).join(
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.2";
840
+ var AG_UI_ADAPTER_VERSION = "0.9.4";
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/recipe.ts
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 pluginFile(w) {
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
- if (w.features.command) consts.push(toneHelper());
1480
- return `${imports.join("\n")}
1406
+ return `${pluginImports(w).join("\n")}
1481
1407
 
1482
- ${consts.join("\n\n")}
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.2";
1835
+ var PLATFORM_VERSION = "0.9.4";
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),
@@ -2738,6 +2767,7 @@ function bootstrapFile(t) {
2738
2767
  --lw-content: var(--bs-body-color);
2739
2768
  --lw-content-muted: var(--bs-secondary-color);
2740
2769
  --lw-content-faint: var(--bs-secondary-color);
2770
+ --lw-unsaved: var(--bs-warning-text-emphasis);
2741
2771
 
2742
2772
  --lw-tooltip: var(--bs-emphasis-color);
2743
2773
  --lw-tooltip-content: var(--bs-body-bg);
@@ -2815,10 +2845,10 @@ function weaverAmendments(input, where) {
2815
2845
  version: AG_UI_PROTOCOL_VERSION
2816
2846
  }
2817
2847
  ] : [];
2818
- const directory = normalizeProjectRoot(where ?? "");
2819
- if (!directory) {
2848
+ if (where === void 0 || where === "") {
2820
2849
  return packages;
2821
2850
  }
2851
+ const directory = normalizeProjectRoot(where);
2822
2852
  return [
2823
2853
  ...packages,
2824
2854
  {
@@ -2827,19 +2857,19 @@ function weaverAmendments(input, where) {
2827
2857
  assets: [
2828
2858
  {
2829
2859
  glob: "**/*.json",
2830
- input: `${directory}/src/lib/i18n`,
2860
+ input: joinProjectPath(directory, "src/lib/i18n"),
2831
2861
  from: "workspace",
2832
2862
  output: `i18n/${w.id}`
2833
2863
  }
2834
2864
  ]
2835
2865
  },
2836
- { kind: "stylesheet-source", sourceRoot: `${directory}/src` },
2866
+ { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
2837
2867
  {
2838
2868
  kind: "compose-plugin",
2839
2869
  id: w.id,
2840
2870
  symbol: `${w.propertyName}Plugin`,
2841
2871
  capabilities: w.capabilities,
2842
- sourceRoot: `${directory}/src`
2872
+ sourceRoot: joinProjectPath(directory, "src")
2843
2873
  }
2844
2874
  ];
2845
2875
  }
@@ -2847,10 +2877,10 @@ function weaverAmendments(input, where) {
2847
2877
  // ../devkit/src/recipes/auth-source/recipe-amendments.ts
2848
2878
  function authSourceAmendments(input, where) {
2849
2879
  const a = resolveAuthSourceInput(input);
2850
- const directory = normalizeProjectRoot(where ?? "");
2851
- if (a.bare || !directory) {
2880
+ if (a.bare || where === void 0 || where === "") {
2852
2881
  return [];
2853
2882
  }
2883
+ const directory = normalizeProjectRoot(where);
2854
2884
  return [
2855
2885
  {
2856
2886
  kind: "build-target",
@@ -2858,7 +2888,7 @@ function authSourceAmendments(input, where) {
2858
2888
  assets: [
2859
2889
  {
2860
2890
  glob: "**/*.json",
2861
- input: `${directory}/i18n`,
2891
+ input: joinProjectPath(directory, "i18n"),
2862
2892
  from: "workspace",
2863
2893
  output: `i18n/${SESSION_PLUGIN_ID}`
2864
2894
  }
@@ -3559,7 +3589,7 @@ function read(ts, file, call) {
3559
3589
  return {
3560
3590
  at: at2,
3561
3591
  id: id.text,
3562
- callable: callable !== void 0 && callable.kind === ts.SyntaxKind.TrueKeyword,
3592
+ callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
3563
3593
  described: properties.has("description"),
3564
3594
  answers: properties.has("answers"),
3565
3595
  returnsValue: returnsValue(ts, properties.get("run")),
@@ -3687,8 +3717,9 @@ function loadTypeScript(fromDir) {
3687
3717
  }
3688
3718
 
3689
3719
  // src/lib/run.ts
3690
- import { readdirSync, readFileSync as readFileSync3 } from "node:fs";
3691
- import { join } from "node:path";
3720
+ import { spawnSync } from "node:child_process";
3721
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
3722
+ import { join as join4 } from "node:path";
3692
3723
 
3693
3724
  // src/lib/args.ts
3694
3725
  var ArgError = class extends Error {
@@ -4034,10 +4065,7 @@ var Amender = class {
4034
4065
  if (!/@import\s+['"]tailwindcss['"]/.test(css)) {
4035
4066
  return;
4036
4067
  }
4037
- const source = posix.relative(
4038
- this.displayName(dirname2(entry)),
4039
- amendment.sourceRoot
4040
- );
4068
+ const source = posix.relative(this.displayName(dirname2(entry)), amendment.sourceRoot) || ".";
4041
4069
  const next = ensureStylesheetSource(css, source);
4042
4070
  if (next === css) {
4043
4071
  return;
@@ -4164,8 +4192,410 @@ function relativeImport(fromDir, sourceRoot) {
4164
4192
  return path.startsWith(".") ? path : `./${path}`;
4165
4193
  }
4166
4194
 
4195
+ // src/lib/init.ts
4196
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
4197
+ import { join as join3 } from "node:path";
4198
+
4199
+ // src/lib/init-plan.ts
4200
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
4201
+ import { basename as basename2, join as join2, resolve as resolve5 } from "node:path";
4202
+
4203
+ // src/lib/nx-applications.ts
4204
+ import { existsSync as existsSync3, readdirSync } from "node:fs";
4205
+ import { basename, join, relative as relative3, sep as sep3 } from "node:path";
4206
+ var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "tmp", "coverage"]);
4207
+ function nxApplications(workspaceRoot) {
4208
+ const found = [];
4209
+ const walk = (folder) => {
4210
+ const project = join(folder, "project.json");
4211
+ if (existsSync3(project)) {
4212
+ const config = readJsonFile(project);
4213
+ const name = typeof config["name"] === "string" ? config["name"] : basename(folder);
4214
+ if (config["projectType"] === "application" && !name.endsWith("-e2e")) {
4215
+ found.push({
4216
+ name,
4217
+ root: relative3(workspaceRoot, folder).split(sep3).join("/")
4218
+ });
4219
+ }
4220
+ }
4221
+ for (const entry of readdirSync(folder, { withFileTypes: true })) {
4222
+ if (!entry.isDirectory() || SKIPPED.has(entry.name) || entry.name.startsWith(".")) {
4223
+ continue;
4224
+ }
4225
+ walk(join(folder, entry.name));
4226
+ }
4227
+ };
4228
+ walk(workspaceRoot);
4229
+ return found.toSorted((a, b) => a.name.localeCompare(b.name));
4230
+ }
4231
+
4232
+ // src/lib/package-manager.ts
4233
+ import { existsSync as existsSync4 } from "node:fs";
4234
+ import { resolve as resolve4 } from "node:path";
4235
+ var PACKAGE_MANAGERS = [
4236
+ "npm",
4237
+ "pnpm",
4238
+ "yarn",
4239
+ "bun"
4240
+ ];
4241
+ var LOCKFILES = [
4242
+ ["package-lock.json", "npm"],
4243
+ ["npm-shrinkwrap.json", "npm"],
4244
+ ["pnpm-lock.yaml", "pnpm"],
4245
+ ["yarn.lock", "yarn"],
4246
+ ["bun.lock", "bun"],
4247
+ ["bun.lockb", "bun"]
4248
+ ];
4249
+ function detectPackageManager(root) {
4250
+ for (const [lockfile, manager] of LOCKFILES) {
4251
+ if (existsSync4(resolve4(root, lockfile))) {
4252
+ return { manager, lockfile };
4253
+ }
4254
+ }
4255
+ return { manager: "npm" };
4256
+ }
4257
+ function packageManagerFrom(value) {
4258
+ if (value === void 0) {
4259
+ return void 0;
4260
+ }
4261
+ if (!PACKAGE_MANAGERS.includes(value)) {
4262
+ throw new ArgError(
4263
+ `Option --package-manager must be one of: ${PACKAGE_MANAGERS.join(", ")}.`
4264
+ );
4265
+ }
4266
+ return value;
4267
+ }
4268
+ function installCommand(manager, packages, dev) {
4269
+ const devFlag = manager === "bun" ? "-d" : "-D";
4270
+ const verb = manager === "npm" ? "install" : "add";
4271
+ return [manager, verb, ...dev ? [devFlag] : [], ...packages];
4272
+ }
4273
+ function execCommand(manager, argv) {
4274
+ if (manager === "npm") {
4275
+ return ["npx", ...argv];
4276
+ }
4277
+ if (manager === "pnpm") {
4278
+ return ["pnpm", "exec", ...argv];
4279
+ }
4280
+ if (manager === "bun") {
4281
+ return ["bunx", ...argv];
4282
+ }
4283
+ return ["yarn", ...argv];
4284
+ }
4285
+ function runScriptCommand(manager, script) {
4286
+ return `${manager} ${script}`;
4287
+ }
4288
+
4289
+ // src/lib/init-plan.ts
4290
+ var RUNTIME_PACKAGES = [
4291
+ "@loomweaver/shell",
4292
+ "@loomweaver/plugin-sdk",
4293
+ "@loomweaver/frame-kit",
4294
+ "@angular/cdk",
4295
+ "@jsverse/transloco",
4296
+ "@ng-icons/heroicons"
4297
+ ];
4298
+ var STYLE_PACKAGES = [
4299
+ "tailwindcss",
4300
+ "@tailwindcss/postcss",
4301
+ "@tailwindcss/typography"
4302
+ ];
4303
+ var SERVICE_WORKER = "@angular/service-worker";
4304
+ var NX_COLLECTION = "@loomweaver/devkit";
4305
+ function planInit(args, deps) {
4306
+ const found = findWorkspace(deps.cwd);
4307
+ if (!found?.kind) {
4308
+ throw new WorkspaceError(
4309
+ `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.`
4310
+ );
4311
+ }
4312
+ const workspace = found;
4313
+ const manifest2 = readManifest(workspace.root);
4314
+ const override = packageManagerFrom(stringFlag(args, "package-manager"));
4315
+ const detected = detectPackageManager(workspace.root);
4316
+ const app = workspace.kind === "nx" ? chooseApplication(workspace.root, stringFlag(args, "app")) : void 0;
4317
+ const name = app?.name ?? kebab(manifest2.name ?? basename2(workspace.root));
4318
+ const styles = stringFlag(args, "styles") ?? "tailwind";
4319
+ const declared = { ...manifest2.dependencies, ...manifest2.devDependencies };
4320
+ const missing = (names) => names.filter((candidate) => !Object.hasOwn(declared, candidate));
4321
+ return {
4322
+ workspace,
4323
+ manager: override ?? detected.manager,
4324
+ lockfile: override ? void 0 : detected.lockfile,
4325
+ name,
4326
+ title: stringFlag(args, "title") ?? titleCase(name),
4327
+ styles,
4328
+ weaver: weaverId(args),
4329
+ runtime: [
4330
+ ...missing(RUNTIME_PACKAGES),
4331
+ ...Object.hasOwn(declared, SERVICE_WORKER) ? [] : [serviceWorkerSpec(workspace.root, manifest2)]
4332
+ ],
4333
+ dev: [
4334
+ ...styles === "tailwind" ? missing(STYLE_PACKAGES) : [],
4335
+ ...workspace.kind === "nx" && !Object.hasOwn(declared, NX_COLLECTION) ? [collectionSpec(deps.version)] : []
4336
+ ],
4337
+ app,
4338
+ dryRun: boolFlag(args, "dry-run") === true
4339
+ };
4340
+ }
4341
+ function chooseApplication(root, wanted) {
4342
+ const apps = nxApplications(root);
4343
+ const names = apps.map((candidate) => candidate.name).join(", ");
4344
+ if (wanted) {
4345
+ const app = apps.find((candidate) => candidate.name === wanted);
4346
+ if (!app) {
4347
+ const alternatives = apps.length > 0 ? `; there are: ${names}` : "";
4348
+ throw new WorkspaceError(
4349
+ `No application named "${wanted}" in this workspace${alternatives}.`
4350
+ );
4351
+ }
4352
+ return app;
4353
+ }
4354
+ if (apps.length === 1) {
4355
+ return apps[0];
4356
+ }
4357
+ if (apps.length === 0) {
4358
+ throw new WorkspaceError(
4359
+ 'This Nx workspace has no application (no project.json with "projectType": "application"). Generate one first, then run init with --app <name>.'
4360
+ );
4361
+ }
4362
+ throw new WorkspaceError(
4363
+ `This Nx workspace has ${apps.length} applications, so none was chosen: ${names}. Name one with --app.`
4364
+ );
4365
+ }
4366
+ function weaverId(args) {
4367
+ const value = args.flags["weaver"];
4368
+ if (value === false) {
4369
+ return void 0;
4370
+ }
4371
+ if (value === true) {
4372
+ throw new ArgError(
4373
+ "Option --weaver needs a plugin id, or pass --no-weaver."
4374
+ );
4375
+ }
4376
+ return value ?? "notes";
4377
+ }
4378
+ function readManifest(root) {
4379
+ const file = join2(root, "package.json");
4380
+ return existsSync5(file) ? readJsonFile(file) : {};
4381
+ }
4382
+ function serviceWorkerSpec(root, manifest2) {
4383
+ const installed = join2(root, "node_modules/@angular/core/package.json");
4384
+ if (existsSync5(installed)) {
4385
+ const { version } = JSON.parse(readFileSync3(installed, "utf8"));
4386
+ return `${SERVICE_WORKER}@${version}`;
4387
+ }
4388
+ const declared = manifest2.dependencies?.["@angular/core"];
4389
+ return declared ? `${SERVICE_WORKER}@${declared}` : SERVICE_WORKER;
4390
+ }
4391
+ function collectionSpec(version) {
4392
+ return version === "0.0.0" ? `${NX_COLLECTION}@latest` : `${NX_COLLECTION}@${version}`;
4393
+ }
4394
+ function kebab(value) {
4395
+ const unscoped = value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : value;
4396
+ return unscoped.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("-");
4397
+ }
4398
+ function titleCase(name) {
4399
+ return name.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4400
+ }
4401
+
4402
+ // src/lib/init.ts
4403
+ var INIT_FLAGS = [
4404
+ "title",
4405
+ "styles",
4406
+ "weaver",
4407
+ "app",
4408
+ "package-manager",
4409
+ "dry-run"
4410
+ ];
4411
+ function init(args, io, deps) {
4412
+ rejectUnknownFlags(args, INIT_FLAGS);
4413
+ const plan = planInit(args, deps);
4414
+ report(io, plan);
4415
+ let didSomething = false;
4416
+ for (const step of stepsFor(plan)) {
4417
+ if (step.done) {
4418
+ io.out(`${step.label}: already there.`);
4419
+ continue;
4420
+ }
4421
+ didSomething = true;
4422
+ const code = perform(step, plan, io, deps);
4423
+ if (code !== 0) {
4424
+ io.err(`${step.label} failed; nothing after it was run.`);
4425
+ return code;
4426
+ }
4427
+ }
4428
+ if (!plan.weaver) {
4429
+ io.out(
4430
+ "First weaver: skipped as asked; the rail stays empty until a plugin is composed in."
4431
+ );
4432
+ }
4433
+ if (!didSomething) {
4434
+ io.out(
4435
+ "Nothing left to do: the packages, the distribution and the weaver are all in place."
4436
+ );
4437
+ }
4438
+ io.out(
4439
+ `${plan.dryRun ? "Then serve" : "Serve"} it with: ${serveCommand(plan)}`
4440
+ );
4441
+ return 0;
4442
+ }
4443
+ function perform(step, plan, io, deps) {
4444
+ if (step.command) {
4445
+ if (plan.dryRun) {
4446
+ io.out(`Would run: ${step.command.join(" ")}`);
4447
+ return 0;
4448
+ }
4449
+ io.out(`${step.label}: ${step.describe}`);
4450
+ deps.exec(step.command, plan.workspace.root);
4451
+ return 0;
4452
+ }
4453
+ if (step.argv) {
4454
+ io.out(
4455
+ plan.dryRun ? `Would ${step.label.toLowerCase()}: ${step.describe}` : `${step.label}: ${step.describe}`
4456
+ );
4457
+ if (plan.dryRun && step.after) {
4458
+ io.out(` (${step.after})`);
4459
+ }
4460
+ return deps.run(plan.dryRun ? [...step.argv, "--dry-run"] : step.argv);
4461
+ }
4462
+ return 0;
4463
+ }
4464
+ function report(io, plan) {
4465
+ const where = plan.workspace.kind === "nx" ? `Nx workspace, application ${plan.app?.name}` : "Angular CLI application";
4466
+ io.out(
4467
+ `${plan.dryRun ? "Would take" : "Taking"} ${plan.workspace.root} to a product: ${where}, packages with ${managerLabel(plan)}.`
4468
+ );
4469
+ }
4470
+ function managerLabel(plan) {
4471
+ if (plan.lockfile) {
4472
+ return `${plan.manager} (from ${plan.lockfile})`;
4473
+ }
4474
+ if (plan.manager === "npm") {
4475
+ return "npm (no lockfile found, so npm)";
4476
+ }
4477
+ return plan.manager;
4478
+ }
4479
+ function stepsFor(plan) {
4480
+ return plan.workspace.kind === "nx" ? nxSteps(plan) : angularSteps(plan);
4481
+ }
4482
+ function angularSteps(plan) {
4483
+ const root = plan.workspace.root;
4484
+ const steps = [
4485
+ ...installSteps(plan),
4486
+ {
4487
+ label: "Distribution",
4488
+ describe: `scaffold the composition root for "${plan.title}" (${plan.styles}) and wire the build`,
4489
+ done: composesShell(join3(root, "src/app/app.config.ts")),
4490
+ argv: [
4491
+ "distribution",
4492
+ "--name",
4493
+ plan.name,
4494
+ "--title",
4495
+ plan.title,
4496
+ "--styles",
4497
+ plan.styles,
4498
+ "--out",
4499
+ root,
4500
+ "--force"
4501
+ ]
4502
+ }
4503
+ ];
4504
+ if (plan.weaver) {
4505
+ steps.push({
4506
+ label: "First weaver",
4507
+ describe: weaverDescription(plan.weaver),
4508
+ done: existsSync6(join3(root, "src", plan.weaver, "src/index.ts")),
4509
+ after: "planned against the composition root as it is now; once the distribution step has run, the weaver composes into it",
4510
+ argv: [
4511
+ "weaver",
4512
+ "--id",
4513
+ plan.weaver,
4514
+ "--command",
4515
+ "--out",
4516
+ join3(root, "src", plan.weaver)
4517
+ ]
4518
+ });
4519
+ }
4520
+ return steps;
4521
+ }
4522
+ function nxSteps(plan) {
4523
+ const root = plan.workspace.root;
4524
+ const app = plan.app;
4525
+ if (!app) {
4526
+ return [];
4527
+ }
4528
+ const generator = (name, argv) => execCommand(plan.manager, ["nx", "g", `${NX_COLLECTION}:${name}`, ...argv]);
4529
+ const steps = [
4530
+ ...installSteps(plan),
4531
+ {
4532
+ label: "Distribution",
4533
+ describe: `nx g ${NX_COLLECTION}:distribution over ${app.name} for "${plan.title}" (${plan.styles})`,
4534
+ done: composesShell(join3(root, app.root, "src/app/app.config.ts")),
4535
+ command: generator("distribution", [
4536
+ "--name",
4537
+ app.name,
4538
+ "--directory",
4539
+ app.root,
4540
+ "--title",
4541
+ plan.title,
4542
+ "--styles",
4543
+ plan.styles,
4544
+ "--force"
4545
+ ])
4546
+ }
4547
+ ];
4548
+ if (plan.weaver) {
4549
+ steps.push({
4550
+ label: "First weaver",
4551
+ describe: weaverDescription(plan.weaver),
4552
+ done: existsSync6(join3(root, "libs", `${plan.weaver}-weaver`)),
4553
+ command: generator("weaver", [
4554
+ "--id",
4555
+ plan.weaver,
4556
+ "--command",
4557
+ "--app",
4558
+ app.name
4559
+ ])
4560
+ });
4561
+ }
4562
+ return steps;
4563
+ }
4564
+ function installSteps(plan) {
4565
+ return [
4566
+ {
4567
+ label: "Install",
4568
+ describe: plan.runtime.join(" "),
4569
+ done: plan.runtime.length === 0,
4570
+ command: installCommand(plan.manager, plan.runtime, false)
4571
+ },
4572
+ {
4573
+ label: "Install for development",
4574
+ describe: plan.dev.join(" "),
4575
+ done: plan.dev.length === 0,
4576
+ command: installCommand(plan.manager, plan.dev, true)
4577
+ }
4578
+ ];
4579
+ }
4580
+ function weaverDescription(id) {
4581
+ return `"${id}" with a command on mod+shift+${id.charAt(0)}`;
4582
+ }
4583
+ function serveCommand(plan) {
4584
+ if (plan.workspace.kind === "nx") {
4585
+ return execCommand(plan.manager, [
4586
+ "nx",
4587
+ "serve",
4588
+ plan.app?.name ?? ""
4589
+ ]).join(" ");
4590
+ }
4591
+ return runScriptCommand(plan.manager, "start");
4592
+ }
4593
+ function composesShell(file) {
4594
+ return existsSync6(file) && readFileSync4(file, "utf8").includes("provideShell(");
4595
+ }
4596
+
4167
4597
  // src/lib/scaffold.ts
4168
- import { relative as relative3, resolve as resolve4 } from "node:path";
4598
+ import { relative as relative4, resolve as resolve6 } from "node:path";
4169
4599
  function findScaffold2(name) {
4170
4600
  const scaffold2 = findScaffold(name);
4171
4601
  if (!scaffold2) {
@@ -4182,16 +4612,16 @@ function allowedFlagsFor(scaffold2) {
4182
4612
  ]);
4183
4613
  }
4184
4614
  function readFlag(args, option) {
4185
- const kebab = kebabCase(option.name);
4186
- const value = args.flags[kebab] ?? args.flags[option.name];
4615
+ const kebab2 = kebabCase(option.name);
4616
+ const value = args.flags[kebab2] ?? args.flags[option.name];
4187
4617
  if (value === void 0) {
4188
4618
  return void 0;
4189
4619
  }
4190
4620
  if (option.type === "string" && typeof value !== "string") {
4191
- throw new ArgError(`Option --${kebab} needs a value.`);
4621
+ throw new ArgError(`Option --${kebab2} needs a value.`);
4192
4622
  }
4193
4623
  if (option.type === "boolean" && typeof value !== "boolean") {
4194
- throw new ArgError(`Option --${kebab} does not take a value.`);
4624
+ throw new ArgError(`Option --${kebab2} does not take a value.`);
4195
4625
  }
4196
4626
  return value;
4197
4627
  }
@@ -4201,9 +4631,7 @@ function valuesFor(scaffold2, args) {
4201
4631
  const value = readFlag(args, option);
4202
4632
  if (value === void 0) {
4203
4633
  if (option.required) {
4204
- throw new ArgError(
4205
- `Option --${kebabCase(option.name)} is required.`
4206
- );
4634
+ throw new ArgError(`Option --${kebabCase(option.name)} is required.`);
4207
4635
  }
4208
4636
  continue;
4209
4637
  }
@@ -4217,8 +4645,13 @@ function valuesFor(scaffold2, args) {
4217
4645
  return values;
4218
4646
  }
4219
4647
  function directoryFromOut(out) {
4220
- const below = relative3(process.cwd(), resolve4(out ?? "."));
4221
- return below.startsWith("..") ? "" : below;
4648
+ const target = resolve6(out ?? ".");
4649
+ const base = findWorkspace(target)?.root ?? process.cwd();
4650
+ const below = relative4(base, target);
4651
+ if (below.startsWith("..")) {
4652
+ return "";
4653
+ }
4654
+ return below || ".";
4222
4655
  }
4223
4656
  function scaffoldValues(scaffold2, args) {
4224
4657
  const values = valuesFor(scaffold2, args);
@@ -4249,16 +4682,16 @@ import {
4249
4682
  rmSync,
4250
4683
  writeFileSync as writeFileSync2
4251
4684
  } from "node:fs";
4252
- import { dirname as dirname3, isAbsolute, relative as relative4, resolve as resolve5 } from "node:path";
4685
+ import { dirname as dirname3, isAbsolute, relative as relative5, resolve as resolve7 } from "node:path";
4253
4686
  var WriteError = class extends Error {
4254
4687
  };
4255
4688
  function planWrite(files, root) {
4256
- const absoluteRoot = resolve5(root);
4689
+ const absoluteRoot = resolve7(root);
4257
4690
  const planned = [];
4258
4691
  const conflicts = [];
4259
4692
  for (const path of Object.keys(files).toSorted((a, b) => a.localeCompare(b))) {
4260
- const absolute = resolve5(absoluteRoot, path);
4261
- const inside = relative4(absoluteRoot, absolute);
4693
+ const absolute = resolve7(absoluteRoot, path);
4694
+ const inside = relative5(absoluteRoot, absolute);
4262
4695
  if (inside.startsWith("..") || isAbsolute(inside)) {
4263
4696
  throw new WriteError(`Refusing to write outside the target directory: ${path}`);
4264
4697
  }
@@ -4286,7 +4719,7 @@ function entryExists(absolute) {
4286
4719
  }
4287
4720
  }
4288
4721
  function assertResolvesInsideRoot(root, path, absolute) {
4289
- const inside = relative4(realpathSync(root), realpathSync(dirname3(absolute)));
4722
+ const inside = relative5(realpathSync(root), realpathSync(dirname3(absolute)));
4290
4723
  if (inside.startsWith("..") || isAbsolute(inside)) {
4291
4724
  throw new WriteError(
4292
4725
  `Refusing to write through a link that leaves the target directory: ${path}`
@@ -4300,7 +4733,7 @@ function replaceSymlinkEntry(absolute) {
4300
4733
  }
4301
4734
 
4302
4735
  // src/lib/run.ts
4303
- var VERSION = "0.9.2";
4736
+ var VERSION = "0.9.4";
4304
4737
  function help() {
4305
4738
  const commands = SCAFFOLDS.map((s) => ` ${s.name.padEnd(16)}${s.summary}`);
4306
4739
  return [
@@ -4312,6 +4745,10 @@ function help() {
4312
4745
  ...commands,
4313
4746
  "",
4314
4747
  "Other commands:",
4748
+ " init take the Angular application or Nx workspace you are in to a running product:",
4749
+ " install the platform, scaffold the distribution and a first weaver, say what to serve",
4750
+ " [--title <t>] [--styles tailwind|precompiled] [--weaver <id>|--no-weaver] [--app <nx app>]",
4751
+ " [--package-manager npm|pnpm|yarn|bun] [--dry-run]",
4315
4752
  " list print every scaffold with its options",
4316
4753
  " validate-manifest --id <id> [--name <name>] [--capabilities <a,b>]",
4317
4754
  " validate-i18n --dir <dir> check <lang>.json bundles for key parity",
@@ -4370,15 +4807,17 @@ function validateManifestCommand(args, io) {
4370
4807
  }
4371
4808
  function readBundles(dir) {
4372
4809
  const bundles = {};
4373
- for (const entry of readdirSync(dir)) {
4810
+ for (const entry of readdirSync2(dir)) {
4374
4811
  if (!entry.endsWith(".json")) {
4375
4812
  continue;
4376
4813
  }
4377
4814
  const language = entry.slice(0, -".json".length);
4378
4815
  try {
4379
- bundles[language] = JSON.parse(readFileSync3(join(dir, entry), "utf8"));
4816
+ bundles[language] = JSON.parse(readFileSync5(join4(dir, entry), "utf8"));
4380
4817
  } catch (error) {
4381
- throw new ArgError(`${entry} is not valid JSON: ${error.message}`);
4818
+ throw new ArgError(
4819
+ `${entry} is not valid JSON: ${error.message}`
4820
+ );
4382
4821
  }
4383
4822
  }
4384
4823
  if (Object.keys(bundles).length === 0) {
@@ -4397,15 +4836,15 @@ function validateI18nCommand(args, io) {
4397
4836
  function readSources(dir) {
4398
4837
  const sources = [];
4399
4838
  const walk = (folder) => {
4400
- for (const entry of readdirSync(folder, { withFileTypes: true })) {
4839
+ for (const entry of readdirSync2(folder, { withFileTypes: true })) {
4401
4840
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) {
4402
4841
  continue;
4403
4842
  }
4404
- const path = join(folder, entry.name);
4843
+ const path = join4(folder, entry.name);
4405
4844
  if (entry.isDirectory()) {
4406
4845
  walk(path);
4407
4846
  } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".spec.ts")) {
4408
- sources.push({ path, text: readFileSync3(path, "utf8") });
4847
+ sources.push({ path, text: readFileSync5(path, "utf8") });
4409
4848
  }
4410
4849
  }
4411
4850
  };
@@ -4428,19 +4867,25 @@ function validateCommandsCommand(args, io) {
4428
4867
  `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
4868
  );
4430
4869
  }
4431
- return reportFindings(io, validateCommands(readSources(dir), ts), boolFlag(args, "strict") === true);
4870
+ return reportFindings(
4871
+ io,
4872
+ validateCommands(readSources(dir), ts),
4873
+ boolFlag(args, "strict") === true
4874
+ );
4432
4875
  }
4433
4876
  function readCatalog(file) {
4434
4877
  let raw;
4435
4878
  try {
4436
- raw = readFileSync3(file, "utf8");
4879
+ raw = readFileSync5(file, "utf8");
4437
4880
  } catch (error) {
4438
4881
  throw new ArgError(`Cannot read ${file}: ${error.message}`);
4439
4882
  }
4440
4883
  try {
4441
4884
  return JSON.parse(raw);
4442
4885
  } catch (error) {
4443
- throw new ArgError(`${file} is not valid JSON: ${error.message}`);
4886
+ throw new ArgError(
4887
+ `${file} is not valid JSON: ${error.message}`
4888
+ );
4444
4889
  }
4445
4890
  }
4446
4891
  function validateCatalogCommand(args, io) {
@@ -4517,7 +4962,26 @@ function scaffold(args, io) {
4517
4962
  reportAmendments(io, amend, true);
4518
4963
  return 0;
4519
4964
  }
4520
- function run(argv, io) {
4965
+ var ExecError = class extends Error {
4966
+ };
4967
+ function execInherit(command, cwd) {
4968
+ const result = spawnSync(command[0], command.slice(1), {
4969
+ cwd,
4970
+ stdio: "inherit",
4971
+ shell: process.platform === "win32"
4972
+ });
4973
+ if (result.error) {
4974
+ throw new ExecError(
4975
+ `Could not run "${command.join(" ")}": ${result.error.message}`
4976
+ );
4977
+ }
4978
+ if (result.status !== 0) {
4979
+ throw new ExecError(
4980
+ `"${command.join(" ")}" exited with ${result.status ?? "a signal"}.`
4981
+ );
4982
+ }
4983
+ }
4984
+ function run(argv, io, deps) {
4521
4985
  let args;
4522
4986
  try {
4523
4987
  args = parseArgs(argv);
@@ -4538,6 +5002,14 @@ function run(argv, io) {
4538
5002
  return 1;
4539
5003
  }
4540
5004
  try {
5005
+ if (args.command === "init") {
5006
+ return init(args, io, {
5007
+ cwd: deps?.cwd ?? process.cwd(),
5008
+ exec: deps?.exec ?? execInherit,
5009
+ run: deps?.run ?? ((inner) => run(inner, io, deps)),
5010
+ version: deps?.version ?? VERSION
5011
+ });
5012
+ }
4541
5013
  if (args.command === "list") {
4542
5014
  return list(args, io);
4543
5015
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/cli",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
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",