@stackonward/cli 1.0.1 → 1.2.0

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/index.js CHANGED
@@ -177,6 +177,13 @@ function isTextFile(filename) {
177
177
  const basename = path.basename(filename);
178
178
  return textExtensions.includes(ext) || basename.startsWith(".") || !ext;
179
179
  }
180
+ async function readJson(filePath) {
181
+ try {
182
+ return await fs.readJson(filePath);
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
180
187
  async function isDirEmpty(dir) {
181
188
  if (!await fs.pathExists(dir)) return true;
182
189
  const files = await fs.readdir(dir);
@@ -302,27 +309,27 @@ async function loadSnippetsRegistry() {
302
309
  }
303
310
  return {};
304
311
  }
305
- function resolveComponentDependencies(componentName, registry, resolved = /* @__PURE__ */ new Set()) {
312
+ function resolveComponentDependencies(componentName, registry2, resolved = /* @__PURE__ */ new Set()) {
306
313
  if (resolved.has(componentName)) return [];
307
- const component = registry.components[componentName];
314
+ const component = registry2.components[componentName];
308
315
  if (!component) {
309
316
  throw new Error(`Component "${componentName}" not found in registry`);
310
317
  }
311
318
  resolved.add(componentName);
312
319
  const deps = [];
313
320
  for (const depName of component.registryDependencies) {
314
- deps.push(...resolveComponentDependencies(depName, registry, resolved));
321
+ deps.push(...resolveComponentDependencies(depName, registry2, resolved));
315
322
  }
316
323
  deps.push(component);
317
324
  return deps;
318
325
  }
319
- function getComponentsByFramework(registry, framework) {
320
- return Object.values(registry.components).filter(
326
+ function getComponentsByFramework(registry2, framework) {
327
+ return Object.values(registry2.components).filter(
321
328
  (c) => c.framework === framework || c.framework === "universal" || framework === "nuxt" && c.framework === "vue"
322
329
  );
323
330
  }
324
- function getSnippetsByFramework(registry, framework) {
325
- return Object.values(registry.snippets).filter(
331
+ function getSnippetsByFramework(registry2, framework) {
332
+ return Object.values(registry2.snippets).filter(
326
333
  (s) => s.framework === framework || s.framework === "universal" || framework === "nuxt" && s.framework === "vue"
327
334
  );
328
335
  }
@@ -347,8 +354,8 @@ function resolveComponentFramework(framework) {
347
354
  }
348
355
  return null;
349
356
  }
350
- function findComponentKey(registry, component) {
351
- const match = Object.entries(registry.components).find(
357
+ function findComponentKey(registry2, component) {
358
+ const match = Object.entries(registry2.components).find(
352
359
  ([, candidate]) => candidate === component
353
360
  );
354
361
  return match?.[0] ?? component.name.toLowerCase();
@@ -594,12 +601,12 @@ async function installTemplateRegistryItems(templateInfo, targetDir) {
594
601
  if (!registryItems?.length) {
595
602
  return;
596
603
  }
597
- const registry = await loadRegistry();
604
+ const registry2 = await loadRegistry();
598
605
  const installPlan = buildComponentInstallPlan({
599
606
  componentKeys: [...registryItems],
600
607
  framework: "nuxt",
601
608
  projectRoot: targetDir,
602
- registry
609
+ registry: registry2
603
610
  });
604
611
  await copyFiles(installPlan.filesToCopy, false);
605
612
  }
@@ -639,19 +646,19 @@ async function add(items, options) {
639
646
  logger.error("No package.json found. Please run this command in a project directory.");
640
647
  process.exit(1);
641
648
  }
642
- const registry = await loadRegistry();
643
- const packageItems = items.filter((i) => registry.packages[i]).map((i) => registry.packages[i]);
649
+ const registry2 = await loadRegistry();
650
+ const packageItems = items.filter((i) => registry2.packages[i]).map((i) => registry2.packages[i]);
644
651
  if (packageItems.length > 0) {
645
652
  await addPackages(packageItems, project);
646
653
  }
647
- const nonPackageItems = items.filter((i) => !registry.packages[i]);
654
+ const nonPackageItems = items.filter((i) => !registry2.packages[i]);
648
655
  if (items.length > 0 && nonPackageItems.length === 0) return;
649
656
  const isComponent = options.component || !options.snippet && !options.component;
650
657
  const isSnippet = options.snippet;
651
658
  if (isComponent) {
652
- await addComponents(nonPackageItems, options, project, registry);
659
+ await addComponents(nonPackageItems, options, project, registry2);
653
660
  } else if (isSnippet) {
654
- await addSnippets(nonPackageItems, options, project, registry);
661
+ await addSnippets(nonPackageItems, options, project, registry2);
655
662
  }
656
663
  }
657
664
  async function addPackages(packages, project) {
@@ -691,13 +698,13 @@ async function patchProjectNuxtConfig(root, layer) {
691
698
  }
692
699
  logger.warn(`No nuxt.config found; add "${layer}" to your nuxt.config extends manually`);
693
700
  }
694
- async function addComponents(items, options, project, registry) {
701
+ async function addComponents(items, options, project, registry2) {
695
702
  const framework = resolveComponentFramework(project.framework);
696
703
  if (!framework) {
697
704
  logger.error("Could not detect framework. Supported: Nuxt, Vue, React");
698
705
  process.exit(1);
699
706
  }
700
- const availableComponents = getComponentsByFramework(registry, framework);
707
+ const availableComponents = getComponentsByFramework(registry2, framework);
701
708
  if (availableComponents.length === 0) {
702
709
  logger.warn(`No components available for ${framework}`);
703
710
  return;
@@ -708,7 +715,7 @@ async function addComponents(items, options, project, registry) {
708
715
  } else if (items.length > 0) {
709
716
  for (const item of items) {
710
717
  try {
711
- const deps = resolveComponentDependencies(item, registry);
718
+ const deps = resolveComponentDependencies(item, registry2);
712
719
  selectedComponents.push(...deps);
713
720
  } catch (error) {
714
721
  logger.error(error.message);
@@ -722,7 +729,7 @@ async function addComponents(items, options, project, registry) {
722
729
  ...components.map((c) => ({
723
730
  title: ` ${c.name}`,
724
731
  description: c.description,
725
- value: findComponentKey(registry, c)
732
+ value: findComponentKey(registry2, c)
726
733
  }))
727
734
  ]);
728
735
  const response = await prompts2({
@@ -737,12 +744,12 @@ async function addComponents(items, options, project, registry) {
737
744
  return;
738
745
  }
739
746
  for (const name of response.components) {
740
- const deps = resolveComponentDependencies(name, registry);
747
+ const deps = resolveComponentDependencies(name, registry2);
741
748
  selectedComponents.push(...deps);
742
749
  }
743
750
  }
744
751
  const componentKeys = [
745
- ...new Set(selectedComponents.map((component) => findComponentKey(registry, component)))
752
+ ...new Set(selectedComponents.map((component) => findComponentKey(registry2, component)))
746
753
  ];
747
754
  if (selectedComponents.length === 0) {
748
755
  logger.info("No components to add");
@@ -767,7 +774,7 @@ async function addComponents(items, options, project, registry) {
767
774
  componentKeys,
768
775
  framework,
769
776
  projectRoot: project.root,
770
- registry,
777
+ registry: registry2,
771
778
  targetBase: options.path
772
779
  });
773
780
  const spinner = ora2("Adding components...").start();
@@ -808,9 +815,9 @@ async function addComponents(items, options, project, registry) {
808
815
  logger.br();
809
816
  logger.success("Done!");
810
817
  }
811
- async function addSnippets(items, options, project, registry) {
818
+ async function addSnippets(items, options, project, registry2) {
812
819
  const framework = project.framework === "nuxt" ? "nuxt" : project.framework === "vue" ? "vue" : project.framework === "react" ? "react" : "node";
813
- const availableSnippets = getSnippetsByFramework(registry, framework);
820
+ const availableSnippets = getSnippetsByFramework(registry2, framework);
814
821
  if (availableSnippets.length === 0) {
815
822
  logger.warn(`No snippets available for ${framework}`);
816
823
  return;
@@ -820,7 +827,7 @@ async function addSnippets(items, options, project, registry) {
820
827
  selectedSnippets = availableSnippets;
821
828
  } else if (items.length > 0) {
822
829
  for (const item of items) {
823
- const snippet = registry.snippets[item];
830
+ const snippet = registry2.snippets[item];
824
831
  if (!snippet) {
825
832
  logger.error(`Snippet "${item}" not found`);
826
833
  process.exit(1);
@@ -848,7 +855,7 @@ async function addSnippets(items, options, project, registry) {
848
855
  logger.info("No snippets selected");
849
856
  return;
850
857
  }
851
- selectedSnippets = response.snippets.map((name) => registry.snippets[name]);
858
+ selectedSnippets = response.snippets.map((name) => registry2.snippets[name]);
852
859
  }
853
860
  if (selectedSnippets.length === 0) {
854
861
  logger.info("No snippets to add");
@@ -934,8 +941,8 @@ function listTemplates() {
934
941
  async function listComponents() {
935
942
  logger.title("Components");
936
943
  logger.br();
937
- const registry = await loadRegistry();
938
- const components = Object.values(registry.components);
944
+ const registry2 = await loadRegistry();
945
+ const components = Object.values(registry2.components);
939
946
  if (components.length === 0) {
940
947
  logger.info(" No components available");
941
948
  logger.br();
@@ -964,8 +971,8 @@ async function listComponents() {
964
971
  async function listSnippets() {
965
972
  logger.title("Snippets");
966
973
  logger.br();
967
- const registry = await loadRegistry();
968
- const snippets = Object.values(registry.snippets);
974
+ const registry2 = await loadRegistry();
975
+ const snippets = Object.values(registry2.snippets);
969
976
  if (snippets.length === 0) {
970
977
  logger.info(" No snippets available");
971
978
  logger.br();
@@ -5413,12 +5420,167 @@ async function runApple(mode, options) {
5413
5420
  }
5414
5421
  }
5415
5422
 
5423
+ // src/commands/analytics.ts
5424
+ import { randomUUID as randomUUID3 } from "crypto";
5425
+ import { mkdir as mkdir4, rename as rename2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
5426
+ import path19 from "path";
5427
+
5428
+ // src/analytics/generator.ts
5429
+ var AnalyticsArtifactError = class extends Error {
5430
+ code;
5431
+ constructor(code, message) {
5432
+ super(message);
5433
+ this.name = "AnalyticsArtifactError";
5434
+ this.code = code;
5435
+ }
5436
+ };
5437
+ var AnalyticsArtifactGeneratorRegistry = class {
5438
+ #generators = /* @__PURE__ */ new Map();
5439
+ constructor(generators) {
5440
+ for (const generator of generators) {
5441
+ if (this.#generators.has(generator.id)) {
5442
+ throw new AnalyticsArtifactError(
5443
+ "duplicate_provider",
5444
+ `Analytics provider is registered more than once: ${generator.id}`
5445
+ );
5446
+ }
5447
+ this.#generators.set(generator.id, generator);
5448
+ }
5449
+ }
5450
+ get(provider) {
5451
+ const generator = this.#generators.get(provider);
5452
+ if (!generator) {
5453
+ throw new AnalyticsArtifactError(
5454
+ "unknown_provider",
5455
+ `Unknown analytics provider: ${provider}`
5456
+ );
5457
+ }
5458
+ return generator;
5459
+ }
5460
+ };
5461
+
5462
+ // src/analytics/google-tag-manager.ts
5463
+ import {
5464
+ createGoogleTagManagerContainer,
5465
+ getGoogleTagManagerContainerConfigJsonSchema
5466
+ } from "@stackonward/google-tag-manager/container";
5467
+ var googleTagManagerArtifactGenerator = {
5468
+ id: "google-tag-manager",
5469
+ schema: getGoogleTagManagerContainerConfigJsonSchema,
5470
+ generate(input) {
5471
+ try {
5472
+ const container = createGoogleTagManagerContainer(input);
5473
+ return {
5474
+ contents: `${JSON.stringify(container, null, 2)}
5475
+ `,
5476
+ resourceCounts: Object.freeze({
5477
+ tags: container.containerVersion.tag.length,
5478
+ triggers: container.containerVersion.trigger.length,
5479
+ variables: container.containerVersion.variable.length
5480
+ })
5481
+ };
5482
+ } catch (error) {
5483
+ if (error instanceof Error && error.name === "ZodError") {
5484
+ throw new AnalyticsArtifactError(
5485
+ "invalid_configuration",
5486
+ "Analytics provider configuration is invalid"
5487
+ );
5488
+ }
5489
+ throw error;
5490
+ }
5491
+ }
5492
+ };
5493
+
5494
+ // src/commands/analytics.ts
5495
+ var registry = new AnalyticsArtifactGeneratorRegistry([googleTagManagerArtifactGenerator]);
5496
+ function registerAnalytics(program2) {
5497
+ const analytics = program2.command("analytics").description("Generate reviewed analytics provider configuration artifacts");
5498
+ analytics.command("schema").description("Print the selected analytics provider configuration schema").requiredOption("--provider <provider>", "Analytics provider").action(
5499
+ (options) => guardedAnalytics(() => {
5500
+ process.stdout.write(
5501
+ `${JSON.stringify(registry.get(options.provider).schema(), null, 2)}
5502
+ `
5503
+ );
5504
+ })
5505
+ );
5506
+ analytics.command("generate").description("Generate a provider configuration artifact without publishing it").requiredOption("--provider <provider>", "Analytics provider").requiredOption("--config <file>", "Product analytics configuration JSON").requiredOption("--out <file>", "Destination artifact").option("--overwrite", "Replace an existing destination").action((options) => guardedAnalytics(() => generateArtifact(options)));
5507
+ }
5508
+ async function generateArtifact(options) {
5509
+ const generator = registry.get(options.provider);
5510
+ const input = await readJson(path19.resolve(options.config));
5511
+ if (input === null) {
5512
+ throw new AnalyticsArtifactError(
5513
+ "configuration_unreadable",
5514
+ "Analytics configuration must be a readable JSON file"
5515
+ );
5516
+ }
5517
+ const artifact = generator.generate(input);
5518
+ const destination = path19.resolve(options.out);
5519
+ await writeArtifact(destination, artifact.contents, options.overwrite === true);
5520
+ process.stdout.write(
5521
+ `${JSON.stringify({
5522
+ schemaVersion: 1,
5523
+ outcome: "generated",
5524
+ provider: options.provider,
5525
+ artifact: destination,
5526
+ resources: artifact.resourceCounts,
5527
+ published: false
5528
+ })}
5529
+ `
5530
+ );
5531
+ }
5532
+ async function writeArtifact(destination, contents, overwrite) {
5533
+ await mkdir4(path19.dirname(destination), { recursive: true });
5534
+ if (!overwrite) {
5535
+ try {
5536
+ await writeFile3(destination, contents, { flag: "wx" });
5537
+ return;
5538
+ } catch (error) {
5539
+ if (isFileExistsError(error)) {
5540
+ throw new AnalyticsArtifactError(
5541
+ "artifact_exists",
5542
+ "Analytics artifact already exists; pass --overwrite to replace it"
5543
+ );
5544
+ }
5545
+ throw error;
5546
+ }
5547
+ }
5548
+ const temporary = `${destination}.${process.pid}.${randomUUID3()}.tmp`;
5549
+ let created = false;
5550
+ try {
5551
+ await writeFile3(temporary, contents, { flag: "wx" });
5552
+ created = true;
5553
+ await rename2(temporary, destination);
5554
+ created = false;
5555
+ } finally {
5556
+ if (created) {
5557
+ await rm4(temporary, { force: true });
5558
+ }
5559
+ }
5560
+ }
5561
+ async function guardedAnalytics(operation) {
5562
+ try {
5563
+ await operation();
5564
+ } catch (error) {
5565
+ const failure = error instanceof AnalyticsArtifactError ? error : new AnalyticsArtifactError("analytics_command_failed", "Analytics command failed");
5566
+ process.stderr.write(
5567
+ `${JSON.stringify({ schemaVersion: 1, outcome: "failed", code: failure.code })}
5568
+ `
5569
+ );
5570
+ process.exitCode = 2;
5571
+ }
5572
+ }
5573
+ function isFileExistsError(error) {
5574
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
5575
+ }
5576
+
5416
5577
  // src/index.ts
5417
5578
  var program = new Command();
5418
- program.name("stackonward").description("Create projects and install reusable components and snippets").version("1.0.1");
5579
+ program.name("stackonward").description("Create projects and install reusable components and snippets").version("1.2.0");
5419
5580
  program.command("init [name]").description("Create a new project from template").option("-t, --template <template>", "Template to use").option("-d, --dir <directory>", "Target directory").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--module <module>", "Go module path (required for go-backend)").option("--author <author>", "Author name").option("-y, --yes", "Use defaults without interactive prompts").action(init);
5420
5581
  program.command("add [items...]").description("Add components or snippets to your project").option("-c, --component", "Add component(s)").option("-s, --snippet", "Add snippet(s)").option("-a, --all", "Add all items in category").option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files").option("-p, --path <path>", "Custom install path").action(add);
5421
5582
  program.command("list [type]").description("List available templates, components, or snippets").option("-t, --templates", "List templates").option("-c, --components", "List components").option("-s, --snippets", "List snippets").action(list);
5422
5583
  registerApple(program);
5584
+ registerAnalytics(program);
5423
5585
  await program.parseAsync();
5424
5586
  //# sourceMappingURL=index.js.map