@loomweaver/cli 0.9.0 → 0.9.1

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 +323 -23
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -199,6 +199,7 @@ function asObject(value) {
199
199
 
200
200
  // ../devkit/src/lib/amend/compose.ts
201
201
  var APP_CONFIG = /export\s+const\s+appConfig\s*:[^=]*=\s*\{/;
202
+ var NAMESPACES = /provideTranslationNamespaces\(([^)]*)\)/;
202
203
  var PROVIDERS_OPEN = /providers\s*:\s*\[/g;
203
204
  var SHELL_IMPORT = /import\s*\{([^}]*)\}\s*from\s*'@loomweaver\/shell';/;
204
205
  function closingLine(source, from) {
@@ -228,47 +229,96 @@ function providersBlock(source) {
228
229
  }
229
230
  function composePlugin(source, amendment, importPath) {
230
231
  if (source.includes(amendment.symbol)) {
231
- return { source, composed: true };
232
+ return { source, composed: true, kept: [] };
232
233
  }
233
234
  const shellImport = SHELL_IMPORT.exec(source);
234
235
  if (!shellImport || !providersBlock(source)) {
235
- return { source, composed: false };
236
+ return { source, composed: false, kept: [] };
236
237
  }
238
+ const wanted = providersToAdd(source, amendment);
239
+ const ownSymbols = [amendment.symbol, ...wanted.flatMap((provider) => provider.own ?? [])];
240
+ const foreign = wanted.flatMap((provider) => provider.from ?? []);
237
241
  const withImports = source.replace(
238
242
  SHELL_IMPORT,
239
- () => `import {${withShellSymbols(shellImport[1])}} from '@loomweaver/shell';
240
- import { ${amendment.symbol} } from '${importPath}';`
243
+ () => [
244
+ `import {${withShellSymbols(shellImport[1], wanted)}} from '@loomweaver/shell';`,
245
+ ...foreign.map(
246
+ (entry) => `import { ${[...entry.symbols].toSorted((a, b) => a.localeCompare(b)).join(", ")} } from '${entry.path}';`
247
+ ),
248
+ `import { ${ownSymbols.toSorted((a, b) => a.localeCompare(b)).join(", ")} } from '${importPath}';`
249
+ ].join("\n")
241
250
  );
242
- const block = providersBlock(withImports);
251
+ const joined = joinNamespaces(withImports, amendment.id);
252
+ const block = providersBlock(joined.source);
243
253
  if (!block) {
244
- return { source, composed: false };
254
+ return { source, composed: false, kept: [] };
245
255
  }
246
256
  const indent = `${block.indent} `;
247
257
  const lines = [
248
- `${indent}provideTranslationNamespaces('${amendment.id}'),`,
258
+ ...wanted.map((provider) => `${indent}${provider.line}`),
259
+ ...joined.joined ? [] : [`${indent}provideTranslationNamespaces('${amendment.id}'),`],
249
260
  `${indent}provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(", ")}] }),`,
250
261
  `${indent}...providePlugins(${amendment.symbol}),`
251
262
  ].join("\n");
252
263
  return {
253
- source: `${withImports.slice(0, block.insertAt)}
254
- ${lines}${withImports.slice(block.insertAt)}`,
255
- composed: true
264
+ source: `${joined.source.slice(0, block.insertAt)}
265
+ ${lines}${joined.source.slice(block.insertAt)}`,
266
+ composed: true,
267
+ kept: keptProviders(source, amendment).map((provider) => provider.line)
256
268
  };
257
269
  }
270
+ function joinNamespaces(source, id) {
271
+ const existing = NAMESPACES.exec(source);
272
+ if (!existing) {
273
+ return { source, joined: false };
274
+ }
275
+ const names = existing[1].split(",").map((name) => name.trim()).filter(Boolean);
276
+ if (names.includes(`'${id}'`)) {
277
+ return { source, joined: true };
278
+ }
279
+ return {
280
+ source: source.replace(
281
+ NAMESPACES,
282
+ () => `provideTranslationNamespaces(${[...names, `'${id}'`].join(", ")})`
283
+ ),
284
+ joined: true
285
+ };
286
+ }
287
+ function providersToAdd(source, amendment) {
288
+ return (amendment.providers ?? []).filter(
289
+ (provider) => !(provider.unless && source.includes(provider.unless))
290
+ );
291
+ }
292
+ function keptProviders(source, amendment) {
293
+ return (amendment.providers ?? []).filter(
294
+ (provider) => provider.unless !== void 0 && source.includes(provider.unless)
295
+ );
296
+ }
258
297
  function composeLines(amendment, importPath) {
298
+ const providers = amendment.providers ?? [];
299
+ const own = [amendment.symbol, ...providers.flatMap((provider) => provider.own ?? [])];
300
+ const shell = [
301
+ "providePlugins",
302
+ "provideCapabilityGrants",
303
+ "provideTranslationNamespaces",
304
+ ...providers.flatMap((provider) => provider.shell ?? [])
305
+ ];
259
306
  return [
260
- `import { ${amendment.symbol} } from '${importPath}';`,
261
- "import { providePlugins, provideCapabilityGrants, provideTranslationNamespaces } from '@loomweaver/shell';",
307
+ `import { ${own.join(", ")} } from '${importPath}';`,
308
+ `import { ${shell.join(", ")} } from '@loomweaver/shell';`,
309
+ ...providers.flatMap((provider) => provider.from ?? []).map((entry) => `import { ${entry.symbols.join(", ")} } from '${entry.path}';`),
310
+ ...providers.map((provider) => provider.line),
262
311
  `provideTranslationNamespaces('${amendment.id}'),`,
263
312
  `provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(", ")}] }),`,
264
313
  `...providePlugins(${amendment.symbol}),`
265
314
  ];
266
315
  }
267
- function withShellSymbols(existing) {
316
+ function withShellSymbols(existing, providers) {
268
317
  const wanted = [
269
318
  "provideCapabilityGrants",
270
319
  "providePlugins",
271
- "provideTranslationNamespaces"
320
+ "provideTranslationNamespaces",
321
+ ...providers.flatMap((provider) => provider.shell ?? [])
272
322
  ];
273
323
  const present = existing.split(",").map((symbol) => symbol.trim()).filter(Boolean);
274
324
  const missing = wanted.filter(
@@ -296,7 +346,8 @@ function describeAmendment(amendment) {
296
346
  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.`;
297
347
  }
298
348
  if (amendment.kind === "compose-plugin") {
299
- return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, provideTranslationNamespaces('${amendment.id}'), provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(
349
+ const providers = (amendment.providers ?? []).map((provider) => provider.line.replace(/,$/, ""));
350
+ 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(
300
351
  ", "
301
352
  )}] }) and ...providePlugins(${amendment.symbol}). Without it none of its contributions appear.`;
302
353
  }
@@ -704,7 +755,7 @@ function pause(): Promise<void> {
704
755
  }
705
756
 
706
757
  // ../devkit/src/recipes/angular-weaver/agent-files.ts
707
- var AG_UI_ADAPTER_VERSION = "0.9.0";
758
+ var AG_UI_ADAPTER_VERSION = "0.9.1";
708
759
  var AG_UI_PROTOCOL_VERSION = "0.0.x";
709
760
  function connectionFile(w) {
710
761
  return `import { signal } from '@angular/core';
@@ -1686,9 +1737,17 @@ var framePlugin = {
1686
1737
  }
1687
1738
  };
1688
1739
 
1740
+ // ../devkit/src/recipes/platform-version.ts
1741
+ var PLATFORM_VERSION = "0.9.1";
1742
+
1689
1743
  // ../devkit/src/recipes/angular-distribution/amendments.ts
1690
1744
  function distributionAmendments(d) {
1691
1745
  return [
1746
+ {
1747
+ kind: "package",
1748
+ name: "@loomweaver/frame-kit",
1749
+ version: `^${PLATFORM_VERSION}`
1750
+ },
1692
1751
  ...d.styles === "tailwind" ? [
1693
1752
  {
1694
1753
  kind: "postcss",
@@ -2226,6 +2285,7 @@ var angularDistribution = {
2226
2285
  };
2227
2286
 
2228
2287
  // ../devkit/src/recipes/auth-source/recipe.ts
2288
+ var SESSION_PLUGIN_ID = "session";
2229
2289
  function resolveAuthSourceInput(input) {
2230
2290
  if (!isKebabId(input.name)) {
2231
2291
  throw new Error(`Auth source name must be kebab-case (e.g. "dev"); got "${input.name}".`);
@@ -2233,9 +2293,13 @@ function resolveAuthSourceInput(input) {
2233
2293
  return {
2234
2294
  name: input.name,
2235
2295
  className: toPascalCase(input.name),
2236
- propertyName: toCamelCase(input.name)
2296
+ propertyName: toCamelCase(input.name),
2297
+ bare: input.bare ?? false
2237
2298
  };
2238
2299
  }
2300
+ function sessionPluginSymbol(a) {
2301
+ return `${a.propertyName}SessionPlugin`;
2302
+ }
2239
2303
  function moduleFile(a) {
2240
2304
  return `// Provider-neutral AuthSource. LoomWeaver owns no authentication \u2014 it only reacts to
2241
2305
  // a session snapshot. Wire it with: provideAuthSource(() => ${a.propertyName}AuthSource()).
@@ -2274,11 +2338,177 @@ export function cycle${a.className}User(): void {
2274
2338
  }
2275
2339
  `;
2276
2340
  }
2341
+ function pluginFile2(a) {
2342
+ return `// The verbs a user needs to operate the stand-in session: sign in, switch the account, sign
2343
+ // out, from a rail item whose menu carries them. This is presentation for a product that has no
2344
+ // backend yet; nothing here protects anything. Delete it once your own session arrives.
2345
+ import type { Disposable, Plugin, PluginContext } from '@loomweaver/plugin-sdk';
2346
+ import { cycle${a.className}User, ${a.propertyName}AuthSource } from './${a.name}-auth-source';
2347
+
2348
+ const MENU = 'session.account/menu';
2349
+ const snapshot = ${a.propertyName}AuthSource();
2350
+
2351
+ let drawn: Disposable[] = [];
2352
+
2353
+ function signIn(): void {
2354
+ if (!snapshot().authenticated) {
2355
+ cycle${a.className}User();
2356
+ }
2357
+ }
2358
+
2359
+ function switchAccount(): void {
2360
+ cycle${a.className}User();
2361
+ if (!snapshot().authenticated) {
2362
+ cycle${a.className}User();
2363
+ }
2364
+ }
2365
+
2366
+ function signOut(): void {
2367
+ while (snapshot().authenticated) {
2368
+ cycle${a.className}User();
2369
+ }
2370
+ }
2371
+
2372
+ function initialsOf(name: string): string {
2373
+ return name
2374
+ .split(' ')
2375
+ .map((word) => word[0] ?? '')
2376
+ .join('')
2377
+ .slice(0, 2)
2378
+ .toUpperCase();
2379
+ }
2380
+
2381
+ function draw(ctx: PluginContext): void {
2382
+ for (const part of drawn) {
2383
+ part.dispose();
2384
+ }
2385
+ const current = snapshot();
2386
+ const name = current.displayName ?? '';
2387
+ drawn = [
2388
+ ctx.registerRailItem({
2389
+ id: 'session.account',
2390
+ rail: 'primary',
2391
+ icon: 'account',
2392
+ title: current.authenticated ? name : 'session.signIn',
2393
+ anchor: 'bottom',
2394
+ order: 20,
2395
+ menu: MENU,
2396
+ menuTrigger: 'primary',
2397
+ ...(current.authenticated ? { initials: initialsOf(name) } : {}),
2398
+ menuHeader: current.authenticated
2399
+ ? {
2400
+ title: name,
2401
+ detail: \`session.role.\${current.roles.includes('admin') ? 'admin' : 'user'}\`,
2402
+ initials: initialsOf(name),
2403
+ }
2404
+ : { title: 'session.signedOut', icon: 'account' },
2405
+ }),
2406
+ ...(current.authenticated
2407
+ ? [
2408
+ ctx.registerMenuItem({
2409
+ id: 'session.menu.switch',
2410
+ menu: MENU,
2411
+ command: 'session.switchAccount',
2412
+ group: 'account',
2413
+ order: 10,
2414
+ }),
2415
+ ctx.registerMenuItem({
2416
+ id: 'session.menu.signOut',
2417
+ menu: MENU,
2418
+ command: 'session.signOut',
2419
+ group: 'account',
2420
+ order: 20,
2421
+ }),
2422
+ ]
2423
+ : [
2424
+ ctx.registerMenuItem({
2425
+ id: 'session.menu.signIn',
2426
+ menu: MENU,
2427
+ command: 'session.signIn',
2428
+ group: 'account',
2429
+ order: 10,
2430
+ }),
2431
+ ]),
2432
+ ];
2433
+ }
2434
+
2435
+ export const ${sessionPluginSymbol(a)}: Plugin = {
2436
+ manifest: { id: 'session', name: 'Account', capabilities: ['contributions'] },
2437
+ activate(ctx) {
2438
+ const then = (step: () => void) => () => {
2439
+ step();
2440
+ draw(ctx);
2441
+ };
2442
+ ctx.registerCommand({
2443
+ id: 'session.signIn',
2444
+ title: 'session.signIn',
2445
+ icon: 'account',
2446
+ access: { authenticated: false },
2447
+ run: then(signIn),
2448
+ });
2449
+ ctx.registerCommand({
2450
+ id: 'session.switchAccount',
2451
+ title: 'session.switchAccount',
2452
+ icon: 'account',
2453
+ access: { authenticated: true },
2454
+ run: then(switchAccount),
2455
+ });
2456
+ ctx.registerCommand({
2457
+ id: 'session.signOut',
2458
+ title: 'session.signOut',
2459
+ icon: 'signOut',
2460
+ access: { authenticated: true },
2461
+ run: then(signOut),
2462
+ });
2463
+ draw(ctx);
2464
+ },
2465
+ deactivate() {
2466
+ for (const part of drawn) {
2467
+ part.dispose();
2468
+ }
2469
+ drawn = [];
2470
+ },
2471
+ };
2472
+ `;
2473
+ }
2474
+ function indexFile2(a) {
2475
+ return `export { cycle${a.className}User, ${a.propertyName}AuthSource } from './${a.name}-auth-source';
2476
+ export { ${sessionPluginSymbol(a)} } from './${a.name}-session.plugin';
2477
+ `;
2478
+ }
2479
+ var I18N = {
2480
+ en: {
2481
+ signIn: "Sign in",
2482
+ switchAccount: "Switch account",
2483
+ signOut: "Sign out",
2484
+ signedOut: "Not signed in",
2485
+ role: { user: "User", admin: "Administrator" }
2486
+ },
2487
+ de: {
2488
+ signIn: "Anmelden",
2489
+ switchAccount: "Konto wechseln",
2490
+ signOut: "Abmelden",
2491
+ signedOut: "Nicht angemeldet",
2492
+ role: { user: "Benutzer", admin: "Administrator" }
2493
+ }
2494
+ };
2277
2495
  var authSource = {
2278
2496
  id: "auth-source",
2279
2497
  build(input) {
2280
2498
  const a = resolveAuthSourceInput(input);
2281
- return { [`${a.name}-auth-source.ts`]: moduleFile(a) };
2499
+ const source = { [`${a.name}-auth-source.ts`]: moduleFile(a) };
2500
+ if (a.bare) {
2501
+ return source;
2502
+ }
2503
+ return {
2504
+ ...source,
2505
+ [`${a.name}-session.plugin.ts`]: pluginFile2(a),
2506
+ "index.ts": indexFile2(a),
2507
+ "i18n/en.json": `${JSON.stringify(I18N.en, null, 2)}
2508
+ `,
2509
+ "i18n/de.json": `${JSON.stringify(I18N.de, null, 2)}
2510
+ `
2511
+ };
2282
2512
  }
2283
2513
  };
2284
2514
 
@@ -2543,6 +2773,54 @@ function weaverAmendments(input, where) {
2543
2773
  ];
2544
2774
  }
2545
2775
 
2776
+ // ../devkit/src/recipes/auth-source/recipe-amendments.ts
2777
+ function authSourceAmendments(input, where) {
2778
+ const a = resolveAuthSourceInput(input);
2779
+ const directory = normalizeProjectRoot(where ?? "");
2780
+ if (a.bare || !directory) {
2781
+ return [];
2782
+ }
2783
+ return [
2784
+ {
2785
+ kind: "build-target",
2786
+ styles: [],
2787
+ assets: [
2788
+ {
2789
+ glob: "**/*.json",
2790
+ input: `${directory}/i18n`,
2791
+ from: "workspace",
2792
+ output: `i18n/${SESSION_PLUGIN_ID}`
2793
+ }
2794
+ ]
2795
+ },
2796
+ {
2797
+ kind: "compose-plugin",
2798
+ id: SESSION_PLUGIN_ID,
2799
+ symbol: sessionPluginSymbol(a),
2800
+ capabilities: ["contributions"],
2801
+ sourceRoot: directory,
2802
+ providers: [
2803
+ {
2804
+ line: `provideAuthSource(() => ${a.propertyName}AuthSource()),`,
2805
+ shell: ["provideAuthSource"],
2806
+ own: [`${a.propertyName}AuthSource`],
2807
+ unless: "provideAuthSource("
2808
+ },
2809
+ {
2810
+ line: "provideIcons({ account: heroUserCircle, signOut: heroArrowRightStartOnRectangle }),",
2811
+ shell: ["provideIcons"],
2812
+ from: [
2813
+ {
2814
+ path: "@ng-icons/heroicons/outline",
2815
+ symbols: ["heroArrowRightStartOnRectangle", "heroUserCircle"]
2816
+ }
2817
+ ]
2818
+ }
2819
+ ]
2820
+ }
2821
+ ];
2822
+ }
2823
+
2546
2824
  // ../devkit/src/lib/scaffolds/inputs.ts
2547
2825
  function weaverInput(values) {
2548
2826
  return {
@@ -2623,6 +2901,9 @@ var APP_OPTION = {
2623
2901
  description: "Application to drop into. Inferred when the workspace has exactly one.",
2624
2902
  workspaceOnly: true
2625
2903
  };
2904
+ function authSourceInput(values) {
2905
+ return { name: str(values, "name") ?? "", bare: bool(values, "bare") };
2906
+ }
2626
2907
  var SCAFFOLDS = [
2627
2908
  {
2628
2909
  name: "weaver",
@@ -2780,7 +3061,7 @@ var SCAFFOLDS = [
2780
3061
  },
2781
3062
  {
2782
3063
  name: "auth-source",
2783
- summary: "a provider-neutral AuthSource that feeds the session",
3064
+ summary: "a stand-in session a user can operate: the AuthSource, the sign-in, switch and sign-out verbs in the rail, composed in",
2784
3065
  options: [
2785
3066
  {
2786
3067
  name: "name",
@@ -2789,9 +3070,22 @@ var SCAFFOLDS = [
2789
3070
  required: true,
2790
3071
  pattern: ID_PATTERN
2791
3072
  },
2792
- APP_OPTION
3073
+ {
3074
+ name: "bare",
3075
+ type: "boolean",
3076
+ description: "Write the AuthSource alone, without the verbs, for a product that maps a session of its own onto it.",
3077
+ default: false
3078
+ },
3079
+ APP_OPTION,
3080
+ {
3081
+ name: "directory",
3082
+ type: "string",
3083
+ description: "Where the files land, relative to the workspace root.",
3084
+ workspaceOnly: true
3085
+ }
2793
3086
  ],
2794
- build: (values) => generate(authSource, { name: str(values, "name") ?? "" })
3087
+ build: (values) => generate(authSource, authSourceInput(values)),
3088
+ amend: (values) => authSourceAmendments(authSourceInput(values), str(values, "directory"))
2795
3089
  },
2796
3090
  {
2797
3091
  name: "settings-store",
@@ -2850,6 +3144,8 @@ var SCAFFOLDS = [
2850
3144
  function findScaffold(name) {
2851
3145
  return SCAFFOLDS.find((scaffold2) => scaffold2.name === name);
2852
3146
  }
3147
+
3148
+ // ../devkit/src/lib/scaffolds/surface.ts
2853
3149
  function portableOptions(scaffold2) {
2854
3150
  return scaffold2.options.filter((option) => !option.workspaceOnly);
2855
3151
  }
@@ -3709,7 +4005,11 @@ var Amender = class {
3709
4005
  file: root,
3710
4006
  display: this.displayName(root),
3711
4007
  added: [
3712
- `${amendment.symbol}, its translations and its capability grants`
4008
+ `${amendment.symbol}, its translations and its capability grants`,
4009
+ ...(amendment.providers ?? []).filter((provider) => !result.kept.includes(provider.line)).map((provider) => provider.line.replace(/,$/, "")),
4010
+ ...result.kept.map(
4011
+ (line) => `kept the ${line.split("(", 1)[0]} already there instead of ${line.replace(/,$/, "")}`
4012
+ )
3713
4013
  ],
3714
4014
  content: result.source
3715
4015
  });
@@ -3929,7 +4229,7 @@ function replaceSymlinkEntry(absolute) {
3929
4229
  }
3930
4230
 
3931
4231
  // src/lib/run.ts
3932
- var VERSION = "0.9.0";
4232
+ var VERSION = "0.9.1";
3933
4233
  function help() {
3934
4234
  const commands = SCAFFOLDS.map((s) => ` ${s.name.padEnd(16)}${s.summary}`);
3935
4235
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
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",