@stackweld/cli 0.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.
Files changed (81) hide show
  1. package/dist/__tests__/commands.test.d.ts +2 -0
  2. package/dist/__tests__/commands.test.js +275 -0
  3. package/dist/commands/ai.d.ts +8 -0
  4. package/dist/commands/ai.js +167 -0
  5. package/dist/commands/analyze.d.ts +6 -0
  6. package/dist/commands/analyze.js +90 -0
  7. package/dist/commands/benchmark.d.ts +6 -0
  8. package/dist/commands/benchmark.js +86 -0
  9. package/dist/commands/browse.d.ts +6 -0
  10. package/dist/commands/browse.js +101 -0
  11. package/dist/commands/clone.d.ts +3 -0
  12. package/dist/commands/clone.js +37 -0
  13. package/dist/commands/compare.d.ts +6 -0
  14. package/dist/commands/compare.js +93 -0
  15. package/dist/commands/completion.d.ts +6 -0
  16. package/dist/commands/completion.js +86 -0
  17. package/dist/commands/config.d.ts +6 -0
  18. package/dist/commands/config.js +56 -0
  19. package/dist/commands/cost.d.ts +6 -0
  20. package/dist/commands/cost.js +101 -0
  21. package/dist/commands/create.d.ts +7 -0
  22. package/dist/commands/create.js +111 -0
  23. package/dist/commands/delete.d.ts +6 -0
  24. package/dist/commands/delete.js +33 -0
  25. package/dist/commands/deploy.d.ts +6 -0
  26. package/dist/commands/deploy.js +90 -0
  27. package/dist/commands/doctor.d.ts +6 -0
  28. package/dist/commands/doctor.js +144 -0
  29. package/dist/commands/down.d.ts +6 -0
  30. package/dist/commands/down.js +37 -0
  31. package/dist/commands/env.d.ts +6 -0
  32. package/dist/commands/env.js +129 -0
  33. package/dist/commands/export-stack.d.ts +6 -0
  34. package/dist/commands/export-stack.js +51 -0
  35. package/dist/commands/generate.d.ts +9 -0
  36. package/dist/commands/generate.js +542 -0
  37. package/dist/commands/health.d.ts +6 -0
  38. package/dist/commands/health.js +68 -0
  39. package/dist/commands/import-stack.d.ts +6 -0
  40. package/dist/commands/import-stack.js +68 -0
  41. package/dist/commands/info.d.ts +6 -0
  42. package/dist/commands/info.js +56 -0
  43. package/dist/commands/init.d.ts +6 -0
  44. package/dist/commands/init.js +186 -0
  45. package/dist/commands/learn.d.ts +6 -0
  46. package/dist/commands/learn.js +91 -0
  47. package/dist/commands/lint.d.ts +6 -0
  48. package/dist/commands/lint.js +193 -0
  49. package/dist/commands/list.d.ts +6 -0
  50. package/dist/commands/list.js +27 -0
  51. package/dist/commands/logs.d.ts +6 -0
  52. package/dist/commands/logs.js +37 -0
  53. package/dist/commands/migrate.d.ts +6 -0
  54. package/dist/commands/migrate.js +57 -0
  55. package/dist/commands/plugin.d.ts +8 -0
  56. package/dist/commands/plugin.js +131 -0
  57. package/dist/commands/preview.d.ts +7 -0
  58. package/dist/commands/preview.js +100 -0
  59. package/dist/commands/save.d.ts +6 -0
  60. package/dist/commands/save.js +32 -0
  61. package/dist/commands/scaffold.d.ts +7 -0
  62. package/dist/commands/scaffold.js +100 -0
  63. package/dist/commands/score.d.ts +9 -0
  64. package/dist/commands/score.js +111 -0
  65. package/dist/commands/share.d.ts +10 -0
  66. package/dist/commands/share.js +93 -0
  67. package/dist/commands/status.d.ts +6 -0
  68. package/dist/commands/status.js +39 -0
  69. package/dist/commands/template.d.ts +3 -0
  70. package/dist/commands/template.js +162 -0
  71. package/dist/commands/up.d.ts +6 -0
  72. package/dist/commands/up.js +54 -0
  73. package/dist/commands/version-cmd.d.ts +6 -0
  74. package/dist/commands/version-cmd.js +100 -0
  75. package/dist/index.d.ts +6 -0
  76. package/dist/index.js +160 -0
  77. package/dist/ui/context.d.ts +10 -0
  78. package/dist/ui/context.js +90 -0
  79. package/dist/ui/format.d.ts +59 -0
  80. package/dist/ui/format.js +295 -0
  81. package/package.json +52 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * stackweld import <file> — Import a stack definition from YAML or JSON.
3
+ */
4
+ import * as fs from "node:fs";
5
+ import chalk from "chalk";
6
+ import { Command } from "commander";
7
+ import { parse as yamlParse } from "yaml";
8
+ import { getStackEngine } from "../ui/context.js";
9
+ import { formatStackRow, formatValidation } from "../ui/format.js";
10
+ export const importCommand = new Command("import")
11
+ .description("Import a stack definition from a YAML or JSON file")
12
+ .argument("<file>", "Path to the YAML or JSON file")
13
+ .option("--json", "Output result as JSON")
14
+ .action((file, opts) => {
15
+ if (!fs.existsSync(file)) {
16
+ console.error(chalk.red(`File not found: ${file}`));
17
+ process.exit(1);
18
+ }
19
+ const raw = fs.readFileSync(file, "utf-8");
20
+ let data;
21
+ // Detect format
22
+ if (file.endsWith(".json")) {
23
+ try {
24
+ data = JSON.parse(raw);
25
+ }
26
+ catch {
27
+ console.error(chalk.red("Invalid JSON file."));
28
+ process.exit(1);
29
+ }
30
+ }
31
+ else {
32
+ // Try YAML (also handles JSON since JSON is valid YAML)
33
+ try {
34
+ data = yamlParse(raw);
35
+ }
36
+ catch {
37
+ console.error(chalk.red("Invalid YAML/JSON file."));
38
+ process.exit(1);
39
+ }
40
+ }
41
+ if (!data.name || !data.technologies || !Array.isArray(data.technologies)) {
42
+ console.error(chalk.red("Invalid stack file. Required: name, technologies array."));
43
+ process.exit(1);
44
+ }
45
+ const engine = getStackEngine();
46
+ const technologies = data.technologies.map((t) => ({
47
+ technologyId: t.id,
48
+ version: t.version,
49
+ port: t.port,
50
+ }));
51
+ const { stack, validation } = engine.create({
52
+ name: data.name,
53
+ description: data.description,
54
+ profile: data.profile || "standard",
55
+ technologies,
56
+ tags: data.tags,
57
+ });
58
+ console.log(formatValidation(validation));
59
+ console.log("");
60
+ if (opts.json) {
61
+ console.log(JSON.stringify({ stack, validation }, null, 2));
62
+ }
63
+ else {
64
+ console.log(formatStackRow(stack));
65
+ console.log(chalk.green(`\n✓ Imported "${stack.name}" successfully`));
66
+ }
67
+ });
68
+ //# sourceMappingURL=import-stack.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld info <id> — Show detailed information about a stack or technology.
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const infoCommand: Command;
6
+ //# sourceMappingURL=info.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * stackweld info <id> — Show detailed information about a stack or technology.
3
+ */
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import { getRulesEngine, getStackEngine } from "../ui/context.js";
7
+ import { formatJson, formatStackRow, formatTechnology } from "../ui/format.js";
8
+ export const infoCommand = new Command("info")
9
+ .description("Show details about a stack or technology")
10
+ .argument("<id>", "Stack ID or technology ID")
11
+ .option("--json", "Output as JSON")
12
+ .action((id, opts) => {
13
+ const engine = getStackEngine();
14
+ const rules = getRulesEngine();
15
+ // Try stack first
16
+ const stack = engine.get(id);
17
+ if (stack) {
18
+ if (opts.json) {
19
+ console.log(formatJson(stack));
20
+ }
21
+ else {
22
+ console.log(formatStackRow(stack));
23
+ // Show individual tech details
24
+ console.log(chalk.bold("\nTechnologies:"));
25
+ for (const t of stack.technologies) {
26
+ const tech = rules.getTechnology(t.technologyId);
27
+ if (tech) {
28
+ console.log(` ${chalk.cyan(tech.name)} ${chalk.dim(`v${t.version}`)} ${t.port ? chalk.dim(`:${t.port}`) : ""}`);
29
+ }
30
+ }
31
+ // Show version history
32
+ const history = engine.getVersionHistory(stack.id);
33
+ if (history.length > 1) {
34
+ console.log(chalk.bold("\nVersion history:"));
35
+ for (const v of history.slice(0, 5)) {
36
+ console.log(` ${chalk.dim(`v${v.version}`)} ${chalk.dim(v.timestamp)} ${v.changelog}`);
37
+ }
38
+ }
39
+ }
40
+ return;
41
+ }
42
+ // Try technology
43
+ const tech = rules.getTechnology(id);
44
+ if (tech) {
45
+ if (opts.json) {
46
+ console.log(formatJson(tech));
47
+ }
48
+ else {
49
+ console.log(formatTechnology(tech));
50
+ }
51
+ return;
52
+ }
53
+ console.error(chalk.red(`"${id}" not found as stack or technology.`));
54
+ process.exit(1);
55
+ });
56
+ //# sourceMappingURL=info.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld init — Initialize Stackweld in the current directory or interactively create a new stack.
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const initCommand: Command;
6
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,186 @@
1
+ /**
2
+ * stackweld init — Initialize Stackweld in the current directory or interactively create a new stack.
3
+ */
4
+ import { checkbox, confirm, input, select } from "@inquirer/prompts";
5
+ import { getAllTemplates } from "@stackweld/templates";
6
+ import chalk from "chalk";
7
+ import { Command } from "commander";
8
+ import { getRulesEngine, getStackEngine } from "../ui/context.js";
9
+ import { formatStackSummary, formatValidation, gradientHeader, nextSteps, stepIndicator, warning, } from "../ui/format.js";
10
+ export const initCommand = new Command("init")
11
+ .description("Create a new stack interactively")
12
+ .option("--template <id>", "Start from a template")
13
+ .option("--json", "Output result as JSON")
14
+ .action(async (opts) => {
15
+ console.log(`\n ${gradientHeader("Stackweld")} ${chalk.dim("/ New Stack Wizard")}\n`);
16
+ const rules = getRulesEngine();
17
+ const engine = getStackEngine();
18
+ // ── Step 1: Choose mode ──
19
+ console.log(stepIndicator(1, 5, "Choose how to start"));
20
+ const mode = opts.template
21
+ ? "template"
22
+ : await select({
23
+ message: "How do you want to start?",
24
+ choices: [
25
+ { name: "From scratch — pick technologies one by one", value: "scratch" },
26
+ { name: "From a template — use a pre-built stack", value: "template" },
27
+ ],
28
+ });
29
+ let technologies = [];
30
+ let profile = "standard";
31
+ let stackName = "";
32
+ if (mode === "template") {
33
+ // ── Step 2: Select template ──
34
+ console.log(`\n${stepIndicator(2, 5, "Select template")}`);
35
+ const templates = getAllTemplates();
36
+ if (templates.length === 0) {
37
+ console.log(warning("No templates available. Run from scratch instead."));
38
+ return;
39
+ }
40
+ const templateId = opts.template ||
41
+ (await select({
42
+ message: "Choose a template:",
43
+ choices: templates.map((t) => ({
44
+ name: `${chalk.cyan(t.name)} ${chalk.dim("—")} ${t.description}`,
45
+ value: t.id,
46
+ })),
47
+ }));
48
+ const template = templates.find((t) => t.id === templateId);
49
+ if (!template) {
50
+ console.error(chalk.red(`\u2716 Template "${templateId}" not found.`));
51
+ console.error(chalk.dim(` Available templates: ${templates.map((t) => t.id).join(", ")}`));
52
+ process.exit(1);
53
+ }
54
+ // ── Step 3: Name ──
55
+ console.log(`\n${stepIndicator(3, 5, "Name your project")}`);
56
+ stackName = await input({
57
+ message: "Project name:",
58
+ default: template.variables.projectName || "my-project",
59
+ validate: (v) => (v.trim().length > 0 ? true : "Name cannot be empty"),
60
+ });
61
+ technologies = template.technologyIds.map((tid) => {
62
+ const tech = rules.getTechnology(tid);
63
+ return {
64
+ technologyId: tid,
65
+ version: tech?.defaultVersion || "latest",
66
+ };
67
+ });
68
+ profile = template.profile;
69
+ // Steps 4-5 skipped for template mode
70
+ console.log(`\n${stepIndicator(4, 5, chalk.dim("Profile: ") + profile)}`);
71
+ console.log(`${stepIndicator(5, 5, `${chalk.dim("Technologies: ") + technologies.length} from template`)}`);
72
+ }
73
+ else {
74
+ // ── Step 2: Name ──
75
+ console.log(`\n${stepIndicator(2, 5, "Name your stack")}`);
76
+ stackName = await input({
77
+ message: "Stack name:",
78
+ default: "my-stack",
79
+ validate: (v) => (v.trim().length > 0 ? true : "Name cannot be empty"),
80
+ });
81
+ // ── Step 3: Profile ──
82
+ console.log(`\n${stepIndicator(3, 5, "Choose a project profile")}`);
83
+ profile = (await select({
84
+ message: "Project profile:",
85
+ choices: [
86
+ {
87
+ name: `${chalk.cyan("Rapid")} ${chalk.dim("— Quick prototyping, minimal config")}`,
88
+ value: "rapid",
89
+ },
90
+ {
91
+ name: `${chalk.cyan("Standard")} ${chalk.dim("— Balanced defaults for most projects")}`,
92
+ value: "standard",
93
+ },
94
+ {
95
+ name: `${chalk.cyan("Production")} ${chalk.dim("— Battle-tested, monitoring included")}`,
96
+ value: "production",
97
+ },
98
+ {
99
+ name: `${chalk.cyan("Enterprise")} ${chalk.dim("— Full compliance, audit, security")}`,
100
+ value: "enterprise",
101
+ },
102
+ {
103
+ name: `${chalk.cyan("Lightweight")}${chalk.dim("— Minimal footprint")}`,
104
+ value: "lightweight",
105
+ },
106
+ ],
107
+ }));
108
+ // ── Step 4: Technologies ──
109
+ console.log(`\n${stepIndicator(4, 5, "Select technologies by category")}`);
110
+ const categories = [
111
+ "runtime",
112
+ "frontend",
113
+ "backend",
114
+ "database",
115
+ "orm",
116
+ "auth",
117
+ "styling",
118
+ "service",
119
+ "devops",
120
+ ];
121
+ for (const category of categories) {
122
+ const available = rules.getByCategory(category);
123
+ if (available.length === 0)
124
+ continue;
125
+ const selected = await checkbox({
126
+ message: `${chalk.cyan(category)} technologies:`,
127
+ choices: available.map((t) => ({
128
+ name: `${t.name} ${chalk.dim(`(${t.defaultVersion})`)}`,
129
+ value: t.id,
130
+ })),
131
+ });
132
+ for (const id of selected) {
133
+ const tech = rules.getTechnology(id);
134
+ if (tech) {
135
+ technologies.push({
136
+ technologyId: id,
137
+ version: tech.defaultVersion,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ // ── Step 5: Confirm ──
143
+ console.log(`\n${stepIndicator(5, 5, "Review and confirm")}`);
144
+ }
145
+ if (technologies.length === 0) {
146
+ console.log(warning("No technologies selected. Aborting."));
147
+ return;
148
+ }
149
+ // Show summary before creating
150
+ console.log("");
151
+ console.log(chalk.bold(" Summary:"));
152
+ console.log(` ${chalk.dim("Name:")} ${chalk.cyan(stackName)}`);
153
+ console.log(` ${chalk.dim("Profile:")} ${profile}`);
154
+ console.log(` ${chalk.dim("Techs:")} ${technologies.map((t) => t.technologyId).join(", ")}`);
155
+ console.log("");
156
+ if (mode !== "template") {
157
+ const proceed = await confirm({
158
+ message: "Create this stack?",
159
+ default: true,
160
+ });
161
+ if (!proceed) {
162
+ console.log(chalk.dim(" Cancelled."));
163
+ return;
164
+ }
165
+ }
166
+ const { stack, validation } = engine.create({
167
+ name: stackName,
168
+ profile,
169
+ technologies,
170
+ });
171
+ console.log("");
172
+ console.log(formatValidation(validation));
173
+ console.log("");
174
+ if (opts.json) {
175
+ console.log(JSON.stringify({ stack, validation }, null, 2));
176
+ }
177
+ else {
178
+ console.log(formatStackSummary(stack));
179
+ console.log(nextSteps([
180
+ `stackweld scaffold ${stack.id} --path .`,
181
+ `stackweld generate --name ${stackName} --path . --techs ${technologies.map((t) => t.technologyId).join(",")}`,
182
+ `stackweld info ${stack.id}`,
183
+ ]));
184
+ }
185
+ });
186
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld learn <technology> — Show learning resources for a technology.
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const learnCommand: Command;
6
+ //# sourceMappingURL=learn.d.ts.map
@@ -0,0 +1,91 @@
1
+ /**
2
+ * stackweld learn <technology> — Show learning resources for a technology.
3
+ */
4
+ import { getLearningResources } from "@stackweld/registry";
5
+ import chalk from "chalk";
6
+ import { Command } from "commander";
7
+ import { getRulesEngine } from "../ui/context.js";
8
+ import { box } from "../ui/format.js";
9
+ const DIFFICULTY_ICONS = {
10
+ beginner: "\u{1F4D6}", // open book
11
+ intermediate: "\u{1F4DA}", // books
12
+ advanced: "\u{1F393}", // graduation cap
13
+ };
14
+ const DIFFICULTY_ORDER = {
15
+ beginner: 0,
16
+ intermediate: 1,
17
+ advanced: 2,
18
+ };
19
+ const DIFFICULTY_LABELS = {
20
+ beginner: "Beginner",
21
+ intermediate: "Intermediate",
22
+ advanced: "Advanced",
23
+ };
24
+ export const learnCommand = new Command("learn")
25
+ .description("Show learning resources for a technology")
26
+ .argument("<technology>", "Technology ID (e.g. nextjs, react, docker)")
27
+ .option("--json", "Output as JSON")
28
+ .action((technologyId, opts) => {
29
+ const rules = getRulesEngine();
30
+ const tech = rules.getTechnology(technologyId);
31
+ const resources = getLearningResources(technologyId);
32
+ const displayName = tech?.name ?? technologyId;
33
+ if (!resources || resources.length === 0) {
34
+ if (opts.json) {
35
+ console.log(JSON.stringify({ technology: technologyId, resources: [] }, null, 2));
36
+ return;
37
+ }
38
+ console.log("");
39
+ console.log(chalk.yellow(` No learning resources found for "${technologyId}".`));
40
+ console.log("");
41
+ console.log(chalk.dim(" Try searching the official website or documentation."));
42
+ if (tech?.website) {
43
+ console.log(chalk.dim(` Website: ${chalk.cyan(tech.website)}`));
44
+ }
45
+ console.log("");
46
+ return;
47
+ }
48
+ if (opts.json) {
49
+ console.log(JSON.stringify({ technology: technologyId, name: displayName, resources }, null, 2));
50
+ return;
51
+ }
52
+ // Group resources by difficulty
53
+ const grouped = {};
54
+ for (const resource of resources) {
55
+ if (!grouped[resource.difficulty]) {
56
+ grouped[resource.difficulty] = [];
57
+ }
58
+ grouped[resource.difficulty].push(resource);
59
+ }
60
+ const lines = [];
61
+ lines.push("");
62
+ let counter = 1;
63
+ const difficulties = Object.keys(grouped).sort((a, b) => (DIFFICULTY_ORDER[a] ?? 99) - (DIFFICULTY_ORDER[b] ?? 99));
64
+ for (const difficulty of difficulties) {
65
+ const icon = DIFFICULTY_ICONS[difficulty] ?? "";
66
+ const label = DIFFICULTY_LABELS[difficulty] ?? difficulty;
67
+ lines.push(` ${icon} ${chalk.bold(label)}`);
68
+ for (const resource of grouped[difficulty]) {
69
+ lines.push(` ${chalk.white(`${counter}.`)} ${resource.title}`);
70
+ lines.push(` ${chalk.cyan(resource.url)}`);
71
+ counter++;
72
+ }
73
+ lines.push("");
74
+ }
75
+ // Check for missing difficulty levels and show placeholder
76
+ for (const diff of ["beginner", "intermediate", "advanced"]) {
77
+ if (!grouped[diff]) {
78
+ const icon = DIFFICULTY_ICONS[diff] ?? "";
79
+ const label = DIFFICULTY_LABELS[diff] ?? diff;
80
+ lines.push(` ${icon} ${chalk.bold(label)}`);
81
+ lines.push(chalk.dim(` (No ${diff} resources yet)`));
82
+ lines.push("");
83
+ }
84
+ }
85
+ lines.push(` ${chalk.dim("Tip: Start with #1, then build a")}`);
86
+ lines.push(` ${chalk.dim("project before moving to intermediate.")}`);
87
+ lines.push("");
88
+ console.log(box(lines.join("\n"), `Learning Path: ${displayName}`));
89
+ console.log("");
90
+ });
91
+ //# sourceMappingURL=learn.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld lint — Validate a stack against team standards (.stackweldrc).
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const lintCommand: Command;
6
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1,193 @@
1
+ /**
2
+ * stackweld lint — Validate a stack against team standards (.stackweldrc).
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { detectStack, lintStack, loadStandards } from "@stackweld/core";
7
+ import chalk from "chalk";
8
+ import { Command } from "commander";
9
+ import { getStackEngine } from "../ui/context.js";
10
+ import { box, formatJson, gradientHeader } from "../ui/format.js";
11
+ function buildStackFromDetected(detected) {
12
+ return {
13
+ name: "detected",
14
+ profile: "standard",
15
+ technologies: detected.technologies.map((t) => ({ technologyId: t.id })),
16
+ };
17
+ }
18
+ export const lintCommand = new Command("lint")
19
+ .description("Validate a stack against team standards (.stackweldrc)")
20
+ .option("-c, --config <path>", "Path to standards config file")
21
+ .option("-s, --stack <id>", "Lint a saved stack by ID")
22
+ .option("--json", "Output as JSON")
23
+ .action((opts) => {
24
+ // ── Load standards ──
25
+ const configPath = opts.config
26
+ ? path.resolve(opts.config)
27
+ : path.resolve(process.cwd(), ".stackweldrc");
28
+ let standards = null;
29
+ if (opts.config) {
30
+ // Explicit config path
31
+ if (!fs.existsSync(configPath)) {
32
+ console.error(chalk.red(`\u2716 Config file not found: ${configPath}`));
33
+ process.exit(1);
34
+ }
35
+ standards = loadStandards(path.dirname(configPath));
36
+ if (!standards) {
37
+ // Try reading directly if the dirname resolution didn't work
38
+ try {
39
+ const content = fs.readFileSync(configPath, "utf-8");
40
+ standards = JSON.parse(content);
41
+ }
42
+ catch {
43
+ console.error(chalk.red(`\u2716 Could not parse config: ${configPath}`));
44
+ process.exit(1);
45
+ }
46
+ }
47
+ }
48
+ else {
49
+ standards = loadStandards(process.cwd());
50
+ }
51
+ if (!standards) {
52
+ console.error(chalk.red("\u2716 No .stackweldrc found in current directory."));
53
+ console.error(chalk.dim(" Create one or use --config <path>"));
54
+ process.exit(1);
55
+ }
56
+ // ── Resolve stack ──
57
+ let stackLike;
58
+ if (opts.stack) {
59
+ const engine = getStackEngine();
60
+ const saved = engine.get(opts.stack);
61
+ if (!saved) {
62
+ console.error(chalk.red(`\u2716 Stack "${opts.stack}" not found.`));
63
+ console.error(chalk.dim(" Run: stackweld list"));
64
+ process.exit(1);
65
+ }
66
+ stackLike = saved;
67
+ }
68
+ else {
69
+ // Detect from current directory
70
+ const detected = detectStack(process.cwd());
71
+ if (detected.technologies.length === 0) {
72
+ console.error(chalk.red("\u2716 Could not detect any technologies in the current directory."));
73
+ console.error(chalk.dim(" Use --stack <id> to lint a saved stack instead."));
74
+ process.exit(1);
75
+ }
76
+ stackLike = buildStackFromDetected(detected);
77
+ }
78
+ // ── Run lint ──
79
+ // Cast to StackDefinition shape for the linter
80
+ const result = lintStack(stackLike, standards);
81
+ if (opts.json) {
82
+ console.log(formatJson({ standards, result }));
83
+ return;
84
+ }
85
+ // ── Format output ──
86
+ const teamName = standards.team || "Team Standards";
87
+ const lines = [];
88
+ lines.push("");
89
+ let passCount = 0;
90
+ let failCount = 0;
91
+ // Required technologies
92
+ if (standards.requiredTechnologies) {
93
+ const techIds = new Set(stackLike.technologies.map((t) => t.technologyId));
94
+ for (const req of standards.requiredTechnologies) {
95
+ if (techIds.has(req)) {
96
+ lines.push(` ${chalk.green("\u2713")} Required: ${req}`);
97
+ passCount++;
98
+ }
99
+ else {
100
+ lines.push(` ${chalk.red("\u2717")} Required: ${req} ${chalk.red("missing")}`);
101
+ failCount++;
102
+ }
103
+ }
104
+ }
105
+ // Blocked technologies
106
+ if (standards.blockedTechnologies) {
107
+ const techIds = new Set(stackLike.technologies.map((t) => t.technologyId));
108
+ for (const blocked of standards.blockedTechnologies) {
109
+ if (techIds.has(blocked)) {
110
+ lines.push(` ${chalk.red("\u2717")} Blocked: ${blocked} found ${chalk.red("\u2014 remove it")}`);
111
+ failCount++;
112
+ }
113
+ else {
114
+ lines.push(` ${chalk.green("\u2713")} Blocked: ${blocked} ${chalk.dim("(not present)")}`);
115
+ passCount++;
116
+ }
117
+ }
118
+ }
119
+ // Min profile
120
+ if (standards.minProfile) {
121
+ const profileViolation = result.warnings.find((w) => w.rule === "minProfile");
122
+ if (profileViolation) {
123
+ lines.push(` ${chalk.yellow("\u26A0")} Profile: ${stackLike.profile} < ${standards.minProfile}`);
124
+ failCount++;
125
+ }
126
+ else {
127
+ lines.push(` ${chalk.green("\u2713")} Profile: ${stackLike.profile} \u2265 ${standards.minProfile}`);
128
+ passCount++;
129
+ }
130
+ }
131
+ // Require Docker
132
+ if (standards.requireDocker) {
133
+ const dockerViolation = result.violations.find((v) => v.rule === "requireDocker");
134
+ if (dockerViolation) {
135
+ lines.push(` ${chalk.red("\u2717")} Docker required but missing`);
136
+ failCount++;
137
+ }
138
+ else {
139
+ lines.push(` ${chalk.green("\u2713")} Docker present`);
140
+ passCount++;
141
+ }
142
+ }
143
+ // Require tests
144
+ if (standards.requireTests) {
145
+ const testWarning = result.warnings.find((w) => w.rule === "requireTests");
146
+ if (testWarning) {
147
+ lines.push(` ${chalk.yellow("\u26A0")} No testing framework found`);
148
+ failCount++;
149
+ }
150
+ else {
151
+ lines.push(` ${chalk.green("\u2713")} Tests present`);
152
+ passCount++;
153
+ }
154
+ }
155
+ // Require TypeScript
156
+ if (standards.requireTypeScript) {
157
+ const tsViolation = result.violations.find((v) => v.rule === "requireTypeScript");
158
+ if (tsViolation) {
159
+ lines.push(` ${chalk.red("\u2717")} TypeScript required but missing`);
160
+ failCount++;
161
+ }
162
+ else {
163
+ lines.push(` ${chalk.green("\u2713")} TypeScript present`);
164
+ passCount++;
165
+ }
166
+ }
167
+ // Custom rules
168
+ if (standards.customRules) {
169
+ for (const rule of standards.customRules) {
170
+ const violation = result.violations.find((v) => v.rule === `custom:${rule.name}`);
171
+ if (violation) {
172
+ lines.push(` ${chalk.red("\u2717")} ${rule.message}`);
173
+ failCount++;
174
+ }
175
+ else {
176
+ lines.push(` ${chalk.green("\u2713")} Custom: ${rule.name}`);
177
+ passCount++;
178
+ }
179
+ }
180
+ }
181
+ lines.push("");
182
+ lines.push(` ${chalk.green(`${String(passCount)} passed`)} ${chalk.dim("\u00B7")} ` +
183
+ `${failCount > 0 ? chalk.red(`${String(failCount)} violation(s)`) : chalk.dim("0 violations")}`);
184
+ lines.push(` Status: ${result.passed ? chalk.green.bold("PASSED") : chalk.red.bold("FAILED")}`);
185
+ lines.push("");
186
+ console.log(`\n ${gradientHeader("Stackweld")} ${chalk.dim("/ Lint")}\n`);
187
+ console.log(box(lines.join("\n"), `Stack Lint: ${teamName}`));
188
+ console.log("");
189
+ if (!result.passed) {
190
+ process.exit(1);
191
+ }
192
+ });
193
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld list — List saved stacks.
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const listCommand: Command;
6
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * stackweld list — List saved stacks.
3
+ */
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import { getStackEngine } from "../ui/context.js";
7
+ import { emptyState, formatJson, formatStackTable } from "../ui/format.js";
8
+ export const listCommand = new Command("list")
9
+ .aliases(["ls"])
10
+ .description("List all saved stacks")
11
+ .option("--json", "Output as JSON")
12
+ .action((opts) => {
13
+ const engine = getStackEngine();
14
+ const stacks = engine.list();
15
+ if (stacks.length === 0) {
16
+ console.log(emptyState("No stacks saved yet.", "Run `stackweld init` to create one."));
17
+ return;
18
+ }
19
+ if (opts.json) {
20
+ console.log(formatJson(stacks));
21
+ return;
22
+ }
23
+ console.log(chalk.bold(`\n ${stacks.length} stack(s)\n`));
24
+ console.log(formatStackTable(stacks));
25
+ console.log("");
26
+ });
27
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stackweld logs [service] — Show logs from Docker services.
3
+ */
4
+ import { Command } from "commander";
5
+ export declare const logsCommand: Command;
6
+ //# sourceMappingURL=logs.d.ts.map