@stackonward/cli 1.1.0 → 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 +194 -32
- package/dist/index.js.map +1 -1
- package/dist/resources/registry.json +9 -3
- package/dist/resources/templates/go-backend/scripts/test-migrations.sh +0 -0
- package/dist/resources/templates/nuxt/.husky/commit-msg +0 -0
- package/dist/resources/templates/nuxt/.husky/pre-commit +0 -0
- package/dist/resources/templates/nuxt/README.md +13 -1
- package/dist/resources/templates/nuxt/package.json +1 -1
- package/package.json +14 -12
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,
|
|
312
|
+
function resolveComponentDependencies(componentName, registry2, resolved = /* @__PURE__ */ new Set()) {
|
|
306
313
|
if (resolved.has(componentName)) return [];
|
|
307
|
-
const component =
|
|
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,
|
|
321
|
+
deps.push(...resolveComponentDependencies(depName, registry2, resolved));
|
|
315
322
|
}
|
|
316
323
|
deps.push(component);
|
|
317
324
|
return deps;
|
|
318
325
|
}
|
|
319
|
-
function getComponentsByFramework(
|
|
320
|
-
return Object.values(
|
|
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(
|
|
325
|
-
return Object.values(
|
|
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(
|
|
351
|
-
const match = Object.entries(
|
|
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
|
|
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
|
|
643
|
-
const packageItems = items.filter((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) => !
|
|
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,
|
|
659
|
+
await addComponents(nonPackageItems, options, project, registry2);
|
|
653
660
|
} else if (isSnippet) {
|
|
654
|
-
await addSnippets(nonPackageItems, options, project,
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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 =
|
|
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) =>
|
|
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
|
|
938
|
-
const components = Object.values(
|
|
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
|
|
968
|
-
const snippets = Object.values(
|
|
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.
|
|
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
|