@loomweaver/cli 0.7.5 → 0.7.6

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 +131 -58
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ function isUnsafePath(path) {
6
6
  }
7
7
  function generate(recipe, input) {
8
8
  const files = recipe.build(input);
9
- const unsafe = Object.keys(files).find(isUnsafePath);
9
+ const unsafe = Object.keys(files).find((path) => isUnsafePath(path));
10
10
  if (unsafe !== void 0) {
11
11
  throw new Error(`Recipe "${recipe.id}" produced an unsafe path: "${unsafe}".`);
12
12
  }
@@ -17,8 +17,16 @@ function amendments(recipe, input) {
17
17
  }
18
18
 
19
19
  // ../devkit/src/lib/amend/merge.ts
20
+ function normalizeProjectRoot(value) {
21
+ const rooted = value.replace(/^\.?\/*/, "");
22
+ let end = rooted.length;
23
+ while (end > 0 && rooted[end - 1] === "/") {
24
+ end -= 1;
25
+ }
26
+ return rooted.slice(0, end);
27
+ }
20
28
  function joinProjectPath(projectRoot, path) {
21
- const root = projectRoot.replace(/^\.?\/*/, "").replace(/\/+$/, "");
29
+ const root = normalizeProjectRoot(projectRoot);
22
30
  return root ? `${root}/${path}` : path;
23
31
  }
24
32
  function resolveAssetInput(glob, projectRoot) {
@@ -34,8 +42,8 @@ function ensurePostcssPlugin(existing, amendment) {
34
42
  declined: [`${amendment.file}: "plugins" is not an object`]
35
43
  };
36
44
  }
37
- const next = { ...plugins ?? {} };
38
- if (amendment.plugin in next) {
45
+ const next = { ...plugins };
46
+ if (Object.hasOwn(next, amendment.plugin)) {
39
47
  return { value: root, added: [], declined: [] };
40
48
  }
41
49
  next[amendment.plugin] = {};
@@ -46,10 +54,10 @@ function ensurePostcssPlugin(existing, amendment) {
46
54
  };
47
55
  }
48
56
  function ensureBuildTarget(target, amendment, projectRoot) {
49
- const next = { ...asObject(target) ?? {} };
57
+ const next = { ...asObject(target) };
50
58
  const added = [];
51
59
  const declined = [];
52
- const options = { ...asObject(next["options"]) ?? {} };
60
+ const options = { ...asObject(next["options"]) };
53
61
  const styles = ensureStrings(
54
62
  options["styles"],
55
63
  amendment.styles.map((style) => joinProjectPath(projectRoot, style))
@@ -65,8 +73,8 @@ function ensureBuildTarget(target, amendment, projectRoot) {
65
73
  }
66
74
  next["options"] = options;
67
75
  if (amendment.inlineCritical !== void 0 || amendment.serviceWorker) {
68
- const configurations = { ...asObject(next["configurations"]) ?? {} };
69
- const production = { ...asObject(configurations["production"]) ?? {} };
76
+ const configurations = { ...asObject(next["configurations"]) };
77
+ const production = { ...asObject(configurations["production"]) };
70
78
  if (amendment.serviceWorker && production["serviceWorker"] === void 0) {
71
79
  production["serviceWorker"] = joinProjectPath(
72
80
  projectRoot,
@@ -96,8 +104,8 @@ function ensureBuildTarget(target, amendment, projectRoot) {
96
104
  return { value: next, added, declined };
97
105
  }
98
106
  function ensureStylesheetSource(css, source) {
99
- const quoted = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
100
- if (new RegExp(`@source\\s+['"]${quoted}/?['"]`).test(css)) {
107
+ const quoted = source.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
108
+ if (new RegExp(String.raw`@source\s+['"]${quoted}/?['"]`).test(css)) {
101
109
  return css;
102
110
  }
103
111
  return `${css.trimEnd()}
@@ -109,7 +117,7 @@ function ensureInlineCritical(optimization, inlineCritical) {
109
117
  if (typeof optimization === "boolean") {
110
118
  return { value: optimization, changed: false, declined: true };
111
119
  }
112
- const root = { ...asObject(optimization) ?? {} };
120
+ const root = { ...asObject(optimization) };
113
121
  const styles = asObject(root["styles"]);
114
122
  if (styles === void 0 && root["styles"] !== void 0) {
115
123
  return { value: optimization, changed: false, declined: true };
@@ -118,7 +126,7 @@ function ensureInlineCritical(optimization, inlineCritical) {
118
126
  return { value: optimization, changed: false, declined: false };
119
127
  }
120
128
  return {
121
- value: { ...root, styles: { ...styles ?? {}, inlineCritical } },
129
+ value: { ...root, styles: { ...styles, inlineCritical } },
122
130
  changed: true,
123
131
  declined: false
124
132
  };
@@ -127,10 +135,11 @@ function ensureStrings(existing, wanted) {
127
135
  const list2 = Array.isArray(existing) ? [...existing] : [];
128
136
  const added = [];
129
137
  for (const entry of wanted) {
130
- if (!list2.includes(entry)) {
131
- list2.push(entry);
132
- added.push(entry);
138
+ if (list2.includes(entry)) {
139
+ continue;
133
140
  }
141
+ list2.push(entry);
142
+ added.push(entry);
134
143
  }
135
144
  return { value: list2, added };
136
145
  }
@@ -145,7 +154,7 @@ function ensureAssets(existing, wanted, projectRoot) {
145
154
  list2.push({
146
155
  glob: glob.glob,
147
156
  input,
148
- ...glob.output === void 0 ? {} : { output: glob.output }
157
+ ...glob.output !== void 0 && { output: glob.output }
149
158
  });
150
159
  added.push(input);
151
160
  }
@@ -163,34 +172,60 @@ function asObject(value) {
163
172
  }
164
173
 
165
174
  // ../devkit/src/lib/amend/compose.ts
166
- var PROVIDERS = /(export\s+const\s+appConfig\s*:[^=]*=\s*\{[\s\S]*?providers\s*:\s*\[)([\s\S]*?)(\n(\s*)\],)/;
175
+ var APP_CONFIG = /export\s+const\s+appConfig\s*:[^=]*=\s*\{/;
176
+ var PROVIDERS_OPEN = /providers\s*:\s*\[/g;
167
177
  var SHELL_IMPORT = /import\s*\{([^}]*)\}\s*from\s*'@loomweaver\/shell';/;
178
+ function closingLine(source, from) {
179
+ let close = source.indexOf("],", from);
180
+ while (close !== -1) {
181
+ let start = close;
182
+ while (start > from && source.charAt(start - 1).trim() === "") {
183
+ start -= 1;
184
+ }
185
+ const gap = source.slice(start, close);
186
+ const newline = gap.indexOf("\n");
187
+ if (newline !== -1) {
188
+ return { insertAt: start + newline, indent: gap.slice(newline + 1) };
189
+ }
190
+ close = source.indexOf("],", close + 2);
191
+ }
192
+ return null;
193
+ }
194
+ function providersBlock(source) {
195
+ const declaration = APP_CONFIG.exec(source);
196
+ if (!declaration) {
197
+ return null;
198
+ }
199
+ PROVIDERS_OPEN.lastIndex = declaration.index + declaration[0].length;
200
+ const open = PROVIDERS_OPEN.exec(source);
201
+ return open ? closingLine(source, open.index + open[0].length) : null;
202
+ }
168
203
  function composePlugin(source, amendment, importPath) {
169
204
  if (source.includes(amendment.symbol)) {
170
205
  return { source, composed: true };
171
206
  }
172
- const providers = PROVIDERS.exec(source);
173
207
  const shellImport = SHELL_IMPORT.exec(source);
174
- if (!providers || !shellImport) {
208
+ if (!shellImport || !providersBlock(source)) {
175
209
  return { source, composed: false };
176
210
  }
177
211
  const withImports = source.replace(
178
212
  SHELL_IMPORT,
179
- `import {${withShellSymbols(shellImport[1])}} from '@loomweaver/shell';
213
+ () => `import {${withShellSymbols(shellImport[1])}} from '@loomweaver/shell';
180
214
  import { ${amendment.symbol} } from '${importPath}';`
181
215
  );
182
- const indent = `${providers[4]} `;
216
+ const block = providersBlock(withImports);
217
+ if (!block) {
218
+ return { source, composed: false };
219
+ }
220
+ const indent = `${block.indent} `;
183
221
  const lines = [
184
222
  `${indent}provideTranslationNamespaces('${amendment.id}'),`,
185
223
  `${indent}provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(", ")}] }),`,
186
224
  `${indent}...providePlugins(${amendment.symbol}),`
187
225
  ].join("\n");
188
226
  return {
189
- source: withImports.replace(
190
- PROVIDERS,
191
- (_all, head, body, tail) => `${head}${body}
192
- ${lines}${tail}`
193
- ),
227
+ source: `${withImports.slice(0, block.insertAt)}
228
+ ${lines}${withImports.slice(block.insertAt)}`,
194
229
  composed: true
195
230
  };
196
231
  }
@@ -211,13 +246,13 @@ function withShellSymbols(existing) {
211
246
  ];
212
247
  const present = existing.split(",").map((symbol) => symbol.trim()).filter(Boolean);
213
248
  const missing = wanted.filter(
214
- (symbol) => !present.some((entry) => entry.replace(/^type\s+/, "") === symbol)
249
+ (symbol) => present.every((entry) => entry.replace(/^type\s+/, "") !== symbol)
215
250
  );
216
251
  if (missing.length === 0) {
217
252
  return existing;
218
253
  }
219
254
  const multiline = existing.includes("\n");
220
- const all = [...present, ...missing].sort((a, b) => a.localeCompare(b));
255
+ const all = [...present, ...missing].toSorted((a, b) => a.localeCompare(b));
221
256
  return multiline ? `
222
257
  ${all.join(",\n ")},
223
258
  ` : ` ${all.join(", ")} `;
@@ -384,17 +419,17 @@ function resolveMenuSlot(menu) {
384
419
  }
385
420
  return typeof menu === "string" && menu.length ? menu : void 0;
386
421
  }
387
- var PLATFORM_BOUND_CHORD_TOKENS = [
422
+ var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
388
423
  "cmd",
389
424
  "command",
390
425
  "ctrl",
391
426
  "control",
392
427
  "meta"
393
- ];
428
+ ]);
394
429
  function assertPlatformNeutralChord(shortcut) {
395
430
  const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
396
431
  const bound = tokens.find(
397
- (token) => PLATFORM_BOUND_CHORD_TOKENS.includes(token)
432
+ (token) => PLATFORM_BOUND_CHORD_TOKENS.has(token)
398
433
  );
399
434
  if (bound) {
400
435
  throw new Error(
@@ -1384,7 +1419,7 @@ import { provideProductIdentity } from '@loomweaver/plugin-sdk';
1384
1419
  'status-bar' (bar) are what the scaffolded weaver targets. */
1385
1420
  export const layout: ShellLayout = {
1386
1421
  regions: [
1387
- ${renderRegions(" ")}
1422
+ ${renderRegions(" ".repeat(4))}
1388
1423
  ],
1389
1424
  };
1390
1425
 
@@ -1569,7 +1604,7 @@ var angularDistribution = {
1569
1604
  return {
1570
1605
  "src/main.ts": mainTs(),
1571
1606
  "src/app/app.config.ts": appConfigTs(d),
1572
- ...d.withTests ? { "src/app/app.config.spec.ts": appConfigSpec() } : {},
1607
+ ...d.withTests && { "src/app/app.config.spec.ts": appConfigSpec() },
1573
1608
  "src/app/app.ts": appTs(),
1574
1609
  "src/app/app.html": appHtml(),
1575
1610
  "src/index.html": indexHtml(d),
@@ -1841,7 +1876,7 @@ import { ShellLayout } from '@loomweaver/shell';
1841
1876
 
1842
1877
  export const ${l.propertyName}Layout: ShellLayout = {
1843
1878
  regions: [
1844
- ${renderRegions(" ")}
1879
+ ${renderRegions(" ".repeat(4))}
1845
1880
  ],
1846
1881
  };
1847
1882
  `;
@@ -1857,7 +1892,7 @@ var layout = {
1857
1892
  // ../devkit/src/recipes/angular-weaver/amendments.ts
1858
1893
  function weaverAmendments(input, where) {
1859
1894
  const w = resolveWeaverInput(input);
1860
- const directory = (where ?? "").replace(/^\.?\/*/, "").replace(/\/+$/, "");
1895
+ const directory = normalizeProjectRoot(where ?? "");
1861
1896
  if (!directory) {
1862
1897
  return [];
1863
1898
  }
@@ -1925,7 +1960,7 @@ function bool(values, name) {
1925
1960
  return typeof value === "boolean" ? value : void 0;
1926
1961
  }
1927
1962
  function kebabCase(name) {
1928
- return name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
1963
+ return name.replaceAll(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
1929
1964
  }
1930
1965
  var ID_PATTERN = "^[a-z][a-z0-9]*(-[a-z0-9]+)*$";
1931
1966
  var PLACEMENT_OPTIONS = [
@@ -2338,6 +2373,14 @@ function validateEntry(raw, index, known) {
2338
2373
  }
2339
2374
  ];
2340
2375
  }
2376
+ return [
2377
+ ...validateEntryIdentity(raw, index),
2378
+ ...validateCapabilities2(raw["capabilities"], index, known),
2379
+ ...validateEntryMetadata(raw, index),
2380
+ ...validateEntryKeys(raw, index)
2381
+ ];
2382
+ }
2383
+ function validateEntryIdentity(raw, index) {
2341
2384
  const findings = [];
2342
2385
  if (typeof raw["id"] !== "string" || raw["id"].length === 0) {
2343
2386
  findings.push({
@@ -2379,7 +2422,10 @@ function validateEntry(raw, index, known) {
2379
2422
  path: at(index, "version")
2380
2423
  });
2381
2424
  }
2382
- findings.push(...validateCapabilities2(raw["capabilities"], index, known));
2425
+ return findings;
2426
+ }
2427
+ function validateEntryMetadata(raw, index) {
2428
+ const findings = [];
2383
2429
  if (raw["downloads"] !== void 0 && (typeof raw["downloads"] !== "number" || raw["downloads"] < 0)) {
2384
2430
  findings.push({
2385
2431
  level: "warning",
@@ -2404,6 +2450,10 @@ function validateEntry(raw, index, known) {
2404
2450
  path: at(index, "repository")
2405
2451
  });
2406
2452
  }
2453
+ return findings;
2454
+ }
2455
+ function validateEntryKeys(raw, index) {
2456
+ const findings = [];
2407
2457
  for (const key of Object.keys(raw)) {
2408
2458
  if (!CATALOG_ENTRY_KEYS.includes(key)) {
2409
2459
  findings.push({
@@ -2443,7 +2493,7 @@ function validateCatalog(catalog, known = KNOWN_CAPABILITIES) {
2443
2493
  }
2444
2494
  const findings = [];
2445
2495
  const seen = /* @__PURE__ */ new Set();
2446
- catalog.forEach((entry, index) => {
2496
+ for (const [index, entry] of catalog.entries()) {
2447
2497
  findings.push(...validateEntry(entry, index, known));
2448
2498
  const id = isPlainObject(entry) ? entry["id"] : void 0;
2449
2499
  if (typeof id === "string" && id.length > 0) {
@@ -2457,7 +2507,7 @@ function validateCatalog(catalog, known = KNOWN_CAPABILITIES) {
2457
2507
  }
2458
2508
  seen.add(id);
2459
2509
  }
2460
- });
2510
+ }
2461
2511
  return findings;
2462
2512
  }
2463
2513
 
@@ -2489,8 +2539,8 @@ function assign(flags, token, next) {
2489
2539
  function parseArgs(argv) {
2490
2540
  const flags = {};
2491
2541
  let command = "";
2492
- for (let i = 0; i < argv.length; i++) {
2493
- const token = argv[i];
2542
+ for (let index = 0; index < argv.length; index++) {
2543
+ const token = argv[index];
2494
2544
  if (token === "-h" || token === "--help") {
2495
2545
  flags["help"] = true;
2496
2546
  continue;
@@ -2500,8 +2550,8 @@ function parseArgs(argv) {
2500
2550
  continue;
2501
2551
  }
2502
2552
  if (token.startsWith("--")) {
2503
- if (assign(flags, token, argv[i + 1])) {
2504
- i++;
2553
+ if (assign(flags, token, argv[index + 1])) {
2554
+ index++;
2505
2555
  }
2506
2556
  continue;
2507
2557
  }
@@ -2581,7 +2631,7 @@ function resolveBuildProject(workspace, target) {
2581
2631
  `No project with a build target found in ${workspace.configFile ?? workspace.root}.`
2582
2632
  );
2583
2633
  }
2584
- const inside = projects.filter((project) => contains(project.root, relativeTo(workspace.root, target))).sort((a, b) => b.root.length - a.root.length);
2634
+ const inside = projects.filter((project) => contains(project.root, relativeTo(workspace.root, target))).toSorted((a, b) => b.root.length - a.root.length);
2585
2635
  if (inside.length > 0) {
2586
2636
  return inside[0];
2587
2637
  }
@@ -2633,8 +2683,15 @@ function contains(projectRoot, target) {
2633
2683
  function relativeTo(root, target) {
2634
2684
  return relative(root, resolve(target)).split(sep).join("/");
2635
2685
  }
2686
+ function withoutTrailingSlashes(path) {
2687
+ let end = path.length;
2688
+ while (end > 0 && path[end - 1] === "/") {
2689
+ end -= 1;
2690
+ }
2691
+ return path.slice(0, end);
2692
+ }
2636
2693
  function normalise(value) {
2637
- return typeof value === "string" ? value.replace(/^\.?\/*/, "").replace(/\/+$/, "") : "";
2694
+ return typeof value === "string" ? withoutTrailingSlashes(value.replace(/^\.?\/*/, "")) : "";
2638
2695
  }
2639
2696
  function asObject2(value) {
2640
2697
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -2656,7 +2713,7 @@ function planAmend(amendments2, target) {
2656
2713
  return {
2657
2714
  amendments: [],
2658
2715
  remaining: [
2659
- `No workspace was found above the target directory, so nothing could be wired here. Add it by hand, or generate inside the workspace: ${amendments2.map(describeAmendment).join(" \xB7 ")}`
2716
+ `No workspace was found above the target directory, so nothing could be wired here. Add it by hand, or generate inside the workspace: ${amendments2.map((amendment) => describeAmendment(amendment)).join(" \xB7 ")}`
2660
2717
  ]
2661
2718
  };
2662
2719
  }
@@ -2671,10 +2728,14 @@ var Amender = class {
2671
2728
  constructor(workspace, target) {
2672
2729
  this.workspace = workspace;
2673
2730
  this.target = target;
2674
- this.planned = [];
2675
- this.remaining = [];
2676
- this.configAdded = [];
2677
2731
  }
2732
+ workspace;
2733
+ target;
2734
+ planned = [];
2735
+ remaining = [];
2736
+ configAdded = [];
2737
+ config;
2738
+ project;
2678
2739
  plan(amendments2) {
2679
2740
  for (const amendment of amendments2) {
2680
2741
  this.planOne(amendment);
@@ -2972,7 +3033,7 @@ function planWrite(files, root) {
2972
3033
  const absoluteRoot = resolve4(root);
2973
3034
  const planned = [];
2974
3035
  const conflicts = [];
2975
- for (const path of Object.keys(files).sort()) {
3036
+ for (const path of Object.keys(files).toSorted((a, b) => a.localeCompare(b))) {
2976
3037
  const absolute = resolve4(absoluteRoot, path);
2977
3038
  const inside = relative4(absoluteRoot, absolute);
2978
3039
  if (inside.startsWith("..") || isAbsolute(inside)) {
@@ -3016,7 +3077,7 @@ function replaceSymlinkEntry(absolute) {
3016
3077
  }
3017
3078
 
3018
3079
  // src/lib/run.ts
3019
- var VERSION = "0.7.5";
3080
+ var VERSION = "0.7.6";
3020
3081
  function help() {
3021
3082
  const commands = SCAFFOLDS.map((s) => ` ${s.name.padEnd(16)}${s.summary}`);
3022
3083
  return [
@@ -3061,7 +3122,7 @@ function reportFindings(io, findings, strict) {
3061
3122
  io.out("No findings.");
3062
3123
  return 0;
3063
3124
  }
3064
- findings.forEach((f) => io.err(`${f.level}: ${f.message}`));
3125
+ for (const f of findings) io.err(`${f.level}: ${f.message}`);
3065
3126
  if (findings.some((f) => f.level === "error")) {
3066
3127
  return 1;
3067
3128
  }
@@ -3134,12 +3195,16 @@ function reportAmendments(io, amend, done) {
3134
3195
  );
3135
3196
  for (const amendment of amend.amendments) {
3136
3197
  io.out(` ${amendment.display}`);
3137
- amendment.added.forEach((entry) => io.out(` + ${entry}`));
3198
+ for (const entry of amendment.added) {
3199
+ io.out(` + ${entry}`);
3200
+ }
3138
3201
  }
3139
3202
  }
3140
3203
  if (amend.remaining.length > 0) {
3141
3204
  io.out("Still to do by hand:");
3142
- amend.remaining.forEach((entry) => io.out(` - ${entry}`));
3205
+ for (const entry of amend.remaining) {
3206
+ io.out(` - ${entry}`);
3207
+ }
3143
3208
  }
3144
3209
  }
3145
3210
  function scaffold(args, io) {
@@ -3157,12 +3222,16 @@ function scaffold(args, io) {
3157
3222
  const amend = planAmend(amendmentsFor(descriptor, args), out);
3158
3223
  if (boolFlag(args, "dry-run")) {
3159
3224
  io.out(`Would write ${paths.length} file(s) into ${plan.root}:`);
3160
- paths.forEach((path) => io.out(` ${path}`));
3225
+ for (const path of paths) {
3226
+ io.out(` ${path}`);
3227
+ }
3161
3228
  if (plan.conflicts.length > 0) {
3162
3229
  io.out(
3163
3230
  `${plan.conflicts.length} of them already exist and would need --force:`
3164
3231
  );
3165
- plan.conflicts.forEach((path) => io.out(` ${path}`));
3232
+ for (const path of plan.conflicts) {
3233
+ io.out(` ${path}`);
3234
+ }
3166
3235
  }
3167
3236
  reportAmendments(io, amend, false);
3168
3237
  return 0;
@@ -3171,13 +3240,17 @@ function scaffold(args, io) {
3171
3240
  io.err(
3172
3241
  `${plan.conflicts.length} file(s) already exist; pass --force to overwrite:`
3173
3242
  );
3174
- plan.conflicts.forEach((path) => io.err(` ${path}`));
3243
+ for (const path of plan.conflicts) {
3244
+ io.err(` ${path}`);
3245
+ }
3175
3246
  return 1;
3176
3247
  }
3177
3248
  applyWrite(files, plan);
3178
3249
  applyAmend(amend);
3179
3250
  io.out(`Wrote ${paths.length} file(s) into ${plan.root}:`);
3180
- paths.forEach((path) => io.out(` ${path}`));
3251
+ for (const path of paths) {
3252
+ io.out(` ${path}`);
3253
+ }
3181
3254
  reportAmendments(io, amend, true);
3182
3255
  return 0;
3183
3256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/cli",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "LoomWeaver scaffolding CLI: generates weavers, distributions and integrations into any project — no Nx workspace, no LoomWeaver checkout and no AI assistant required.",
5
5
  "keywords": [
6
6
  "loomweaver",