@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.
- package/dist/__tests__/commands.test.d.ts +2 -0
- package/dist/__tests__/commands.test.js +275 -0
- package/dist/commands/ai.d.ts +8 -0
- package/dist/commands/ai.js +167 -0
- package/dist/commands/analyze.d.ts +6 -0
- package/dist/commands/analyze.js +90 -0
- package/dist/commands/benchmark.d.ts +6 -0
- package/dist/commands/benchmark.js +86 -0
- package/dist/commands/browse.d.ts +6 -0
- package/dist/commands/browse.js +101 -0
- package/dist/commands/clone.d.ts +3 -0
- package/dist/commands/clone.js +37 -0
- package/dist/commands/compare.d.ts +6 -0
- package/dist/commands/compare.js +93 -0
- package/dist/commands/completion.d.ts +6 -0
- package/dist/commands/completion.js +86 -0
- package/dist/commands/config.d.ts +6 -0
- package/dist/commands/config.js +56 -0
- package/dist/commands/cost.d.ts +6 -0
- package/dist/commands/cost.js +101 -0
- package/dist/commands/create.d.ts +7 -0
- package/dist/commands/create.js +111 -0
- package/dist/commands/delete.d.ts +6 -0
- package/dist/commands/delete.js +33 -0
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.js +90 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.js +144 -0
- package/dist/commands/down.d.ts +6 -0
- package/dist/commands/down.js +37 -0
- package/dist/commands/env.d.ts +6 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/export-stack.d.ts +6 -0
- package/dist/commands/export-stack.js +51 -0
- package/dist/commands/generate.d.ts +9 -0
- package/dist/commands/generate.js +542 -0
- package/dist/commands/health.d.ts +6 -0
- package/dist/commands/health.js +68 -0
- package/dist/commands/import-stack.d.ts +6 -0
- package/dist/commands/import-stack.js +68 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.js +56 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +186 -0
- package/dist/commands/learn.d.ts +6 -0
- package/dist/commands/learn.js +91 -0
- package/dist/commands/lint.d.ts +6 -0
- package/dist/commands/lint.js +193 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +27 -0
- package/dist/commands/logs.d.ts +6 -0
- package/dist/commands/logs.js +37 -0
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.js +57 -0
- package/dist/commands/plugin.d.ts +8 -0
- package/dist/commands/plugin.js +131 -0
- package/dist/commands/preview.d.ts +7 -0
- package/dist/commands/preview.js +100 -0
- package/dist/commands/save.d.ts +6 -0
- package/dist/commands/save.js +32 -0
- package/dist/commands/scaffold.d.ts +7 -0
- package/dist/commands/scaffold.js +100 -0
- package/dist/commands/score.d.ts +9 -0
- package/dist/commands/score.js +111 -0
- package/dist/commands/share.d.ts +10 -0
- package/dist/commands/share.js +93 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +39 -0
- package/dist/commands/template.d.ts +3 -0
- package/dist/commands/template.js +162 -0
- package/dist/commands/up.d.ts +6 -0
- package/dist/commands/up.js +54 -0
- package/dist/commands/version-cmd.d.ts +6 -0
- package/dist/commands/version-cmd.js +100 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +160 -0
- package/dist/ui/context.d.ts +10 -0
- package/dist/ui/context.js +90 -0
- package/dist/ui/format.d.ts +59 -0
- package/dist/ui/format.js +295 -0
- package/package.json +52 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld share <stackId> — Generate a shareable URL for a saved stack.
|
|
3
|
+
* stackweld import-url <url> — Import a stack from a share URL.
|
|
4
|
+
*
|
|
5
|
+
* Stack data is encoded in the URL hash — no server needed.
|
|
6
|
+
*/
|
|
7
|
+
import { deserializeStack, extractFromShareUrl, generateShareUrl, } from "@stackweld/core";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { getStackEngine } from "../ui/context.js";
|
|
11
|
+
import { box, formatStackRow, formatValidation, info } from "../ui/format.js";
|
|
12
|
+
export const shareCommand = new Command("share")
|
|
13
|
+
.description("Generate a shareable URL for a saved stack")
|
|
14
|
+
.argument("<id>", "Stack ID")
|
|
15
|
+
.option("--base-url <url>", "Custom base URL", "https://stackweld.dev/s/#")
|
|
16
|
+
.action((id, opts) => {
|
|
17
|
+
const engine = getStackEngine();
|
|
18
|
+
const stack = engine.get(id);
|
|
19
|
+
if (!stack) {
|
|
20
|
+
console.error(chalk.red(`Stack "${id}" not found.`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const shareable = {
|
|
24
|
+
name: stack.name,
|
|
25
|
+
profile: stack.profile,
|
|
26
|
+
technologies: stack.technologies.map((t) => ({
|
|
27
|
+
id: t.technologyId,
|
|
28
|
+
version: t.version || undefined,
|
|
29
|
+
port: t.port,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
const url = generateShareUrl(shareable, opts.baseUrl);
|
|
33
|
+
const content = [
|
|
34
|
+
"",
|
|
35
|
+
` ${chalk.cyan(url)}`,
|
|
36
|
+
"",
|
|
37
|
+
` Share this URL ${chalk.dim("\u2014")} the recipient can`,
|
|
38
|
+
` import it with:`,
|
|
39
|
+
` ${chalk.white("stackweld import-url <url>")}`,
|
|
40
|
+
"",
|
|
41
|
+
` No server needed ${chalk.dim("\u2014")} stack data is`,
|
|
42
|
+
` encoded in the URL itself.`,
|
|
43
|
+
"",
|
|
44
|
+
].join("\n");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(box(content, `Stack Shared: ${stack.name}`));
|
|
47
|
+
console.log("");
|
|
48
|
+
});
|
|
49
|
+
export const importUrlCommand = new Command("import-url")
|
|
50
|
+
.description("Import a stack from a share URL")
|
|
51
|
+
.argument("<url>", "Share URL generated by stackweld share")
|
|
52
|
+
.option("--json", "Output result as JSON")
|
|
53
|
+
.action((url, opts) => {
|
|
54
|
+
let encoded;
|
|
55
|
+
try {
|
|
56
|
+
encoded = extractFromShareUrl(url);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.error(chalk.red("Invalid share URL. Expected format: https://stackweld.dev/s/#<data>"));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
let shareable;
|
|
63
|
+
try {
|
|
64
|
+
shareable = deserializeStack(encoded);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
console.error(chalk.red("Failed to decode stack from URL. The data may be corrupted."));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const engine = getStackEngine();
|
|
71
|
+
const technologies = shareable.technologies.map((t) => ({
|
|
72
|
+
technologyId: t.id,
|
|
73
|
+
version: t.version || "latest",
|
|
74
|
+
port: t.port,
|
|
75
|
+
}));
|
|
76
|
+
const { stack, validation } = engine.create({
|
|
77
|
+
name: shareable.name,
|
|
78
|
+
profile: shareable.profile || "standard",
|
|
79
|
+
technologies,
|
|
80
|
+
});
|
|
81
|
+
console.log(formatValidation(validation));
|
|
82
|
+
console.log("");
|
|
83
|
+
if (opts.json) {
|
|
84
|
+
console.log(JSON.stringify({ stack, validation }, null, 2));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(formatStackRow(stack));
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(info(`Imported "${stack.name}" from shared URL`));
|
|
90
|
+
console.log("");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld status — Show status of Docker services.
|
|
3
|
+
*/
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { getRuntimeManager } from "../ui/context.js";
|
|
8
|
+
import { emptyState, error, formatJson, formatServiceStatus } from "../ui/format.js";
|
|
9
|
+
export const statusCommand = new Command("status")
|
|
10
|
+
.description("Show status of running services")
|
|
11
|
+
.option("-p, --path <dir>", "Project directory", ".")
|
|
12
|
+
.option("--json", "Output as JSON")
|
|
13
|
+
.action((opts) => {
|
|
14
|
+
const runtime = getRuntimeManager();
|
|
15
|
+
const projectDir = path.resolve(opts.path);
|
|
16
|
+
if (!runtime.isDockerAvailable()) {
|
|
17
|
+
console.error(error("Docker is not available."));
|
|
18
|
+
console.error(chalk.dim(" Install Docker: https://docs.docker.com/get-docker/"));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const composePath = runtime.composeExists(projectDir);
|
|
22
|
+
if (!composePath) {
|
|
23
|
+
console.error(error(`No docker-compose.yml found in ${projectDir}`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const services = runtime.status({ composePath, projectDir });
|
|
27
|
+
if (opts.json) {
|
|
28
|
+
console.log(formatJson(services));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (services.length === 0) {
|
|
32
|
+
console.log(emptyState("No services running.", "Run `stackweld up` to start services."));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.bold(`\n Services (${services.length})\n`));
|
|
36
|
+
console.log(formatServiceStatus(services));
|
|
37
|
+
console.log("");
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { input } from "@inquirer/prompts";
|
|
3
|
+
import { getDatabase } from "@stackweld/core";
|
|
4
|
+
import { getAllTemplates, getTemplate } from "@stackweld/templates";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { getRulesEngine, getStackEngine } from "../ui/context.js";
|
|
8
|
+
import { formatJson, formatStackRow, formatTemplate, formatValidation } from "../ui/format.js";
|
|
9
|
+
export const templateCommand = new Command("template")
|
|
10
|
+
.description("Manage templates")
|
|
11
|
+
.addCommand(new Command("list")
|
|
12
|
+
.description("List all available templates")
|
|
13
|
+
.option("--json", "Output as JSON")
|
|
14
|
+
.action((opts) => {
|
|
15
|
+
const templates = getAllTemplates();
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
console.log(formatJson(templates));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(chalk.bold(`${templates.length} template(s):\n`));
|
|
21
|
+
for (const t of templates) {
|
|
22
|
+
console.log(formatTemplate(t));
|
|
23
|
+
console.log("");
|
|
24
|
+
}
|
|
25
|
+
}))
|
|
26
|
+
.addCommand(new Command("use")
|
|
27
|
+
.description("Create a stack from a template")
|
|
28
|
+
.argument("<template-id>", "Template ID")
|
|
29
|
+
.option("--name <name>", "Stack name")
|
|
30
|
+
.option("--json", "Output as JSON")
|
|
31
|
+
.action(async (templateId, opts) => {
|
|
32
|
+
const template = getTemplate(templateId);
|
|
33
|
+
if (!template) {
|
|
34
|
+
console.error(chalk.red(`Template "${templateId}" not found.`));
|
|
35
|
+
console.log(chalk.dim("Available: " +
|
|
36
|
+
getAllTemplates()
|
|
37
|
+
.map((t) => t.id)
|
|
38
|
+
.join(", ")));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const rules = getRulesEngine();
|
|
42
|
+
const engine = getStackEngine();
|
|
43
|
+
const name = opts.name ||
|
|
44
|
+
(await input({
|
|
45
|
+
message: "Stack name:",
|
|
46
|
+
default: template.variables.projectName || template.name,
|
|
47
|
+
}));
|
|
48
|
+
const technologies = template.technologyIds.map((tid) => {
|
|
49
|
+
const tech = rules.getTechnology(tid);
|
|
50
|
+
return { technologyId: tid, version: tech?.defaultVersion || "latest" };
|
|
51
|
+
});
|
|
52
|
+
const { stack, validation } = engine.create({
|
|
53
|
+
name,
|
|
54
|
+
description: template.description,
|
|
55
|
+
profile: template.profile,
|
|
56
|
+
technologies,
|
|
57
|
+
tags: [`template:${template.id}`],
|
|
58
|
+
});
|
|
59
|
+
console.log(formatValidation(validation));
|
|
60
|
+
console.log("");
|
|
61
|
+
if (opts.json) {
|
|
62
|
+
console.log(JSON.stringify({ stack, validation }, null, 2));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(formatStackRow(stack));
|
|
66
|
+
if (validation.valid) {
|
|
67
|
+
console.log(chalk.green(`\n✓ Created from template "${template.name}"`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}))
|
|
71
|
+
.addCommand(new Command("save")
|
|
72
|
+
.description("Save a stack as a reusable custom template")
|
|
73
|
+
.argument("<stack-id>", "Stack ID to save as template")
|
|
74
|
+
.option("--name <name>", "Template name")
|
|
75
|
+
.action(async (stackId, opts) => {
|
|
76
|
+
const engine = getStackEngine();
|
|
77
|
+
const stack = engine.get(stackId);
|
|
78
|
+
if (!stack) {
|
|
79
|
+
console.error(chalk.red(`Stack "${stackId}" not found.`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const name = opts.name ||
|
|
83
|
+
(await input({
|
|
84
|
+
message: "Template name:",
|
|
85
|
+
default: `${stack.name} Template`,
|
|
86
|
+
}));
|
|
87
|
+
const db = getDatabase();
|
|
88
|
+
const id = randomUUID();
|
|
89
|
+
const techIds = stack.technologies.map((t) => t.technologyId);
|
|
90
|
+
db.prepare("INSERT INTO custom_templates (id, name, description, profile, technology_ids) VALUES (?, ?, ?, ?, ?)").run(id, name, stack.description, stack.profile, JSON.stringify(techIds));
|
|
91
|
+
console.log(chalk.green(`\n✓ Template "${name}" saved`));
|
|
92
|
+
console.log(chalk.dim(` ID: ${id}`));
|
|
93
|
+
console.log(chalk.dim(` Technologies: ${techIds.join(", ")}`));
|
|
94
|
+
console.log(chalk.dim(` Profile: ${stack.profile}`));
|
|
95
|
+
console.log(chalk.dim(`\nUse it: stackweld template use-custom ${id}`));
|
|
96
|
+
}))
|
|
97
|
+
.addCommand(new Command("saved")
|
|
98
|
+
.description("List saved custom templates")
|
|
99
|
+
.option("--json", "Output as JSON")
|
|
100
|
+
.action((opts) => {
|
|
101
|
+
const db = getDatabase();
|
|
102
|
+
const rows = db
|
|
103
|
+
.prepare("SELECT * FROM custom_templates ORDER BY created_at DESC")
|
|
104
|
+
.all();
|
|
105
|
+
if (rows.length === 0) {
|
|
106
|
+
console.log(chalk.dim("No custom templates saved yet. Use `stackweld template save <stack-id>`."));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (opts.json) {
|
|
110
|
+
console.log(formatJson(rows.map((r) => ({ ...r, technology_ids: JSON.parse(r.technology_ids) }))));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(chalk.bold(`${rows.length} custom template(s):\n`));
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
const techIds = JSON.parse(row.technology_ids);
|
|
116
|
+
console.log(`${chalk.bold.magenta(row.name)} ${chalk.dim(`(${row.id})`)}`);
|
|
117
|
+
console.log(` ${chalk.dim("Profile:")} ${row.profile}`);
|
|
118
|
+
console.log(` ${chalk.dim("Technologies:")} ${techIds.join(", ")}`);
|
|
119
|
+
console.log(` ${chalk.dim("Created:")} ${row.created_at}`);
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
}))
|
|
123
|
+
.addCommand(new Command("use-custom")
|
|
124
|
+
.description("Create a stack from a saved custom template")
|
|
125
|
+
.argument("<template-id>", "Custom template ID")
|
|
126
|
+
.option("--name <name>", "Stack name")
|
|
127
|
+
.action(async (templateId, opts) => {
|
|
128
|
+
const db = getDatabase();
|
|
129
|
+
const row = db
|
|
130
|
+
.prepare("SELECT * FROM custom_templates WHERE id = ?")
|
|
131
|
+
.get(templateId);
|
|
132
|
+
if (!row) {
|
|
133
|
+
console.error(chalk.red(`Custom template "${templateId}" not found.`));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const rules = getRulesEngine();
|
|
137
|
+
const engine = getStackEngine();
|
|
138
|
+
const techIds = JSON.parse(row.technology_ids);
|
|
139
|
+
const name = opts.name ||
|
|
140
|
+
(await input({
|
|
141
|
+
message: "Stack name:",
|
|
142
|
+
default: row.name.replace(" Template", ""),
|
|
143
|
+
}));
|
|
144
|
+
const technologies = techIds.map((tid) => {
|
|
145
|
+
const tech = rules.getTechnology(tid);
|
|
146
|
+
return { technologyId: tid, version: tech?.defaultVersion || "latest" };
|
|
147
|
+
});
|
|
148
|
+
const { stack, validation } = engine.create({
|
|
149
|
+
name,
|
|
150
|
+
description: row.description,
|
|
151
|
+
profile: row.profile,
|
|
152
|
+
technologies,
|
|
153
|
+
tags: [`custom-template:${row.name}`],
|
|
154
|
+
});
|
|
155
|
+
console.log(formatValidation(validation));
|
|
156
|
+
console.log("");
|
|
157
|
+
console.log(formatStackRow(stack));
|
|
158
|
+
if (validation.valid) {
|
|
159
|
+
console.log(chalk.green(`\n✓ Created from custom template "${row.name}"`));
|
|
160
|
+
}
|
|
161
|
+
}));
|
|
162
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld up — Start Docker services for the current project.
|
|
3
|
+
*/
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { getRuntimeManager } from "../ui/context.js";
|
|
9
|
+
import { error, formatServiceStatus } from "../ui/format.js";
|
|
10
|
+
export const upCommand = new Command("up")
|
|
11
|
+
.description("Start Docker services")
|
|
12
|
+
.option("-p, --path <dir>", "Project directory", ".")
|
|
13
|
+
.option("--no-wait", "Don't wait for health checks")
|
|
14
|
+
.option("--timeout <ms>", "Health check timeout in ms", "60000")
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const runtime = getRuntimeManager();
|
|
17
|
+
const projectDir = path.resolve(opts.path);
|
|
18
|
+
if (!runtime.isDockerAvailable()) {
|
|
19
|
+
console.error(error("Docker is not available."));
|
|
20
|
+
console.error(chalk.dim(" Install Docker: https://docs.docker.com/get-docker/"));
|
|
21
|
+
console.error(chalk.dim(" Run `stackweld doctor` to check your environment."));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const composePath = runtime.composeExists(projectDir);
|
|
25
|
+
if (!composePath) {
|
|
26
|
+
console.error(error(`No docker-compose.yml found in ${projectDir}`));
|
|
27
|
+
console.error(chalk.dim(" Run `stackweld scaffold` to generate one from a stack."));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const runtimeOpts = { composePath, projectDir };
|
|
31
|
+
const spinner = ora("Starting services...").start();
|
|
32
|
+
const result = runtime.up(runtimeOpts);
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
spinner.fail("Failed to start services");
|
|
35
|
+
console.error(chalk.red(result.output.slice(0, 500)));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
spinner.succeed("Services started");
|
|
39
|
+
if (opts.wait !== false) {
|
|
40
|
+
const waitSpinner = ora("Waiting for health checks...").start();
|
|
41
|
+
const timeout = Number.parseInt(opts.timeout, 10) || 60_000;
|
|
42
|
+
const health = await runtime.waitForHealthy(runtimeOpts, timeout);
|
|
43
|
+
if (health.healthy) {
|
|
44
|
+
waitSpinner.succeed("All services healthy");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
waitSpinner.warn("Some services may not be healthy yet");
|
|
48
|
+
}
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(formatServiceStatus(health.services));
|
|
51
|
+
console.log("");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=up.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld version — Stack version management.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { getStackEngine } from "../ui/context.js";
|
|
7
|
+
import { formatJson } from "../ui/format.js";
|
|
8
|
+
export const versionCommand = new Command("version")
|
|
9
|
+
.description("Manage stack versions")
|
|
10
|
+
.addCommand(new Command("list")
|
|
11
|
+
.description("Show version history for a stack")
|
|
12
|
+
.argument("<stack-id>", "Stack ID")
|
|
13
|
+
.option("--json", "Output as JSON")
|
|
14
|
+
.action((stackId, opts) => {
|
|
15
|
+
const engine = getStackEngine();
|
|
16
|
+
const stack = engine.get(stackId);
|
|
17
|
+
if (!stack) {
|
|
18
|
+
console.error(chalk.red(`Stack "${stackId}" not found.`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const history = engine.getVersionHistory(stackId);
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(formatJson(history));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log(chalk.bold(`Version history for "${stack.name}":\n`));
|
|
27
|
+
for (const v of history) {
|
|
28
|
+
console.log(` ${chalk.cyan(`v${v.version}`)} ${chalk.dim(v.timestamp)} — ${v.changelog}`);
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
.addCommand(new Command("rollback")
|
|
32
|
+
.description("Rollback a stack to a previous version")
|
|
33
|
+
.argument("<stack-id>", "Stack ID")
|
|
34
|
+
.requiredOption("--to <version>", "Target version number")
|
|
35
|
+
.action((stackId, opts) => {
|
|
36
|
+
const engine = getStackEngine();
|
|
37
|
+
const targetVersion = Number.parseInt(opts.to, 10);
|
|
38
|
+
if (Number.isNaN(targetVersion)) {
|
|
39
|
+
console.error(chalk.red("Invalid version number."));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const result = engine.rollback(stackId, targetVersion);
|
|
43
|
+
if (!result) {
|
|
44
|
+
console.error(chalk.red(`Could not rollback to version ${targetVersion}.`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(chalk.green(`✓ Rolled back to v${targetVersion}`));
|
|
48
|
+
}))
|
|
49
|
+
.addCommand(new Command("diff")
|
|
50
|
+
.description("Compare two versions of a stack")
|
|
51
|
+
.argument("<stack-id>", "Stack ID")
|
|
52
|
+
.argument("<version-a>", "First version")
|
|
53
|
+
.argument("<version-b>", "Second version")
|
|
54
|
+
.action((stackId, vA, vB) => {
|
|
55
|
+
const engine = getStackEngine();
|
|
56
|
+
const history = engine.getVersionHistory(stackId);
|
|
57
|
+
const a = Number.parseInt(vA, 10);
|
|
58
|
+
const b = Number.parseInt(vB, 10);
|
|
59
|
+
const snapshotA = history.find((h) => h.version === a);
|
|
60
|
+
const snapshotB = history.find((h) => h.version === b);
|
|
61
|
+
if (!snapshotA || !snapshotB) {
|
|
62
|
+
console.error(chalk.red("One or both versions not found."));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const techsA = new Set(snapshotA.snapshot.technologies.map((t) => t.technologyId));
|
|
66
|
+
const techsB = new Set(snapshotB.snapshot.technologies.map((t) => t.technologyId));
|
|
67
|
+
const added = [...techsB].filter((t) => !techsA.has(t));
|
|
68
|
+
const removed = [...techsA].filter((t) => !techsB.has(t));
|
|
69
|
+
const common = [...techsA].filter((t) => techsB.has(t));
|
|
70
|
+
console.log(chalk.bold(`Diff: v${a} → v${b}\n`));
|
|
71
|
+
if (added.length > 0) {
|
|
72
|
+
console.log(chalk.green(" Added:"));
|
|
73
|
+
for (const t of added)
|
|
74
|
+
console.log(chalk.green(` + ${t}`));
|
|
75
|
+
}
|
|
76
|
+
if (removed.length > 0) {
|
|
77
|
+
console.log(chalk.red(" Removed:"));
|
|
78
|
+
for (const t of removed)
|
|
79
|
+
console.log(chalk.red(` - ${t}`));
|
|
80
|
+
}
|
|
81
|
+
// Check version changes
|
|
82
|
+
for (const tid of common) {
|
|
83
|
+
const versionA = snapshotA.snapshot.technologies.find((t) => t.technologyId === tid)?.version;
|
|
84
|
+
const versionB = snapshotB.snapshot.technologies.find((t) => t.technologyId === tid)?.version;
|
|
85
|
+
if (versionA !== versionB) {
|
|
86
|
+
console.log(chalk.yellow(` Changed: ${tid} ${versionA} → ${versionB}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (added.length === 0 && removed.length === 0) {
|
|
90
|
+
console.log(chalk.dim(" No technology changes between versions."));
|
|
91
|
+
}
|
|
92
|
+
// Profile/name changes
|
|
93
|
+
if (snapshotA.snapshot.profile !== snapshotB.snapshot.profile) {
|
|
94
|
+
console.log(chalk.yellow(` Profile: ${snapshotA.snapshot.profile} → ${snapshotB.snapshot.profile}`));
|
|
95
|
+
}
|
|
96
|
+
if (snapshotA.snapshot.name !== snapshotB.snapshot.name) {
|
|
97
|
+
console.log(chalk.yellow(` Name: ${snapshotA.snapshot.name} → ${snapshotB.snapshot.name}`));
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
//# sourceMappingURL=version-cmd.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stackweld CLI — The operating system for your dev stacks.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { aiCommand } from "./commands/ai.js";
|
|
8
|
+
import { analyzeCommand } from "./commands/analyze.js";
|
|
9
|
+
import { benchmarkCommand } from "./commands/benchmark.js";
|
|
10
|
+
import { browseCommand } from "./commands/browse.js";
|
|
11
|
+
import { cloneCommand } from "./commands/clone.js";
|
|
12
|
+
import { compareCommand } from "./commands/compare.js";
|
|
13
|
+
import { completionCommand } from "./commands/completion.js";
|
|
14
|
+
import { configCommand } from "./commands/config.js";
|
|
15
|
+
import { costCommand } from "./commands/cost.js";
|
|
16
|
+
import { createCommand } from "./commands/create.js";
|
|
17
|
+
import { deleteCommand } from "./commands/delete.js";
|
|
18
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
19
|
+
import { doctorCommand } from "./commands/doctor.js";
|
|
20
|
+
import { downCommand } from "./commands/down.js";
|
|
21
|
+
import { envCommand } from "./commands/env.js";
|
|
22
|
+
import { exportCommand } from "./commands/export-stack.js";
|
|
23
|
+
import { generateCommand } from "./commands/generate.js";
|
|
24
|
+
import { healthCommand } from "./commands/health.js";
|
|
25
|
+
import { importCommand } from "./commands/import-stack.js";
|
|
26
|
+
import { infoCommand } from "./commands/info.js";
|
|
27
|
+
import { initCommand } from "./commands/init.js";
|
|
28
|
+
import { learnCommand } from "./commands/learn.js";
|
|
29
|
+
import { lintCommand } from "./commands/lint.js";
|
|
30
|
+
import { listCommand } from "./commands/list.js";
|
|
31
|
+
import { logsCommand } from "./commands/logs.js";
|
|
32
|
+
import { migrateCommand } from "./commands/migrate.js";
|
|
33
|
+
import { pluginCommand } from "./commands/plugin.js";
|
|
34
|
+
import { previewCommand } from "./commands/preview.js";
|
|
35
|
+
import { saveCommand } from "./commands/save.js";
|
|
36
|
+
import { scaffoldCommand } from "./commands/scaffold.js";
|
|
37
|
+
import { scoreCommand } from "./commands/score.js";
|
|
38
|
+
import { importUrlCommand, shareCommand } from "./commands/share.js";
|
|
39
|
+
import { statusCommand } from "./commands/status.js";
|
|
40
|
+
import { templateCommand } from "./commands/template.js";
|
|
41
|
+
import { upCommand } from "./commands/up.js";
|
|
42
|
+
import { versionCommand } from "./commands/version-cmd.js";
|
|
43
|
+
import { banner } from "./ui/format.js";
|
|
44
|
+
// Read version from package.json at build time; fallback for development
|
|
45
|
+
const VERSION = "0.2.0";
|
|
46
|
+
const program = new Command();
|
|
47
|
+
program
|
|
48
|
+
.name("stackweld")
|
|
49
|
+
.description("The operating system for your dev stacks")
|
|
50
|
+
.version(VERSION, "-V, --version", "Show version number")
|
|
51
|
+
.addCommand(initCommand)
|
|
52
|
+
.addCommand(createCommand)
|
|
53
|
+
.addCommand(generateCommand)
|
|
54
|
+
.addCommand(listCommand)
|
|
55
|
+
.addCommand(infoCommand)
|
|
56
|
+
.addCommand(browseCommand)
|
|
57
|
+
.addCommand(doctorCommand)
|
|
58
|
+
.addCommand(upCommand)
|
|
59
|
+
.addCommand(downCommand)
|
|
60
|
+
.addCommand(statusCommand)
|
|
61
|
+
.addCommand(logsCommand)
|
|
62
|
+
.addCommand(scaffoldCommand)
|
|
63
|
+
.addCommand(saveCommand)
|
|
64
|
+
.addCommand(deleteCommand)
|
|
65
|
+
.addCommand(cloneCommand)
|
|
66
|
+
.addCommand(exportCommand)
|
|
67
|
+
.addCommand(importCommand)
|
|
68
|
+
.addCommand(templateCommand)
|
|
69
|
+
.addCommand(configCommand)
|
|
70
|
+
.addCommand(completionCommand)
|
|
71
|
+
.addCommand(aiCommand)
|
|
72
|
+
.addCommand(scoreCommand)
|
|
73
|
+
.addCommand(analyzeCommand)
|
|
74
|
+
.addCommand(envCommand)
|
|
75
|
+
.addCommand(previewCommand)
|
|
76
|
+
.addCommand(shareCommand)
|
|
77
|
+
.addCommand(importUrlCommand)
|
|
78
|
+
.addCommand(healthCommand)
|
|
79
|
+
.addCommand(compareCommand)
|
|
80
|
+
.addCommand(migrateCommand)
|
|
81
|
+
.addCommand(learnCommand)
|
|
82
|
+
.addCommand(deployCommand)
|
|
83
|
+
.addCommand(lintCommand)
|
|
84
|
+
.addCommand(benchmarkCommand)
|
|
85
|
+
.addCommand(costCommand)
|
|
86
|
+
.addCommand(pluginCommand)
|
|
87
|
+
.addCommand(versionCommand);
|
|
88
|
+
// Show banner when run without arguments
|
|
89
|
+
if (process.argv.length <= 2) {
|
|
90
|
+
console.log(banner(VERSION));
|
|
91
|
+
console.log(chalk.bold(" Commands:"));
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(chalk.cyan(" Setup"));
|
|
94
|
+
console.log(`${chalk.dim(" init ")}Create a new stack interactively`);
|
|
95
|
+
console.log(`${chalk.dim(" generate ")}Scaffold a full project (one-shot)`);
|
|
96
|
+
console.log(`${chalk.dim(" create ")}Scaffold from a stack or template`);
|
|
97
|
+
console.log(`${chalk.dim(" doctor ")}Check system requirements`);
|
|
98
|
+
console.log("");
|
|
99
|
+
console.log(chalk.cyan(" Stacks"));
|
|
100
|
+
console.log(`${chalk.dim(" list ")}List all saved stacks`);
|
|
101
|
+
console.log(`${chalk.dim(" info <id> ")}Show stack or technology details`);
|
|
102
|
+
console.log(`${chalk.dim(" browse ")}Browse technology catalog`);
|
|
103
|
+
console.log(`${chalk.dim(" save ")}Save a version snapshot`);
|
|
104
|
+
console.log(`${chalk.dim(" delete ")}Delete a saved stack`);
|
|
105
|
+
console.log(`${chalk.dim(" clone ")}Duplicate a stack`);
|
|
106
|
+
console.log(`${chalk.dim(" export/import ")}Export or import stack definitions`);
|
|
107
|
+
console.log(`${chalk.dim(" share <id> ")}Generate a shareable URL for a stack`);
|
|
108
|
+
console.log(`${chalk.dim(" import-url <url> ")}Import a stack from a share URL`);
|
|
109
|
+
console.log(`${chalk.dim(" preview <id> ")}Preview docker-compose.yml for a stack`);
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log(chalk.cyan(" Runtime"));
|
|
112
|
+
console.log(`${chalk.dim(" up ")}Start Docker services`);
|
|
113
|
+
console.log(`${chalk.dim(" down ")}Stop Docker services`);
|
|
114
|
+
console.log(`${chalk.dim(" status ")}Show service status`);
|
|
115
|
+
console.log(`${chalk.dim(" logs ")}Show service logs`);
|
|
116
|
+
console.log("");
|
|
117
|
+
console.log(chalk.cyan(" Analysis"));
|
|
118
|
+
console.log(`${chalk.dim(" analyze [path] ")}Detect project stack automatically`);
|
|
119
|
+
console.log(`${chalk.dim(" health [path] ")}Check project health and best practices`);
|
|
120
|
+
console.log(`${chalk.dim(" compare <a> <b> ")}Compare two saved stacks`);
|
|
121
|
+
console.log(`${chalk.dim(" env [sync|check]")} Sync .env files and check for issues`);
|
|
122
|
+
console.log(`${chalk.dim(" score <a> [b] ")}Compatibility score between technologies`);
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(chalk.cyan(" Deploy & Standards"));
|
|
125
|
+
console.log(`${chalk.dim(" deploy <id> ")}Generate infrastructure files (VPS/AWS/GCP)`);
|
|
126
|
+
console.log(`${chalk.dim(" lint ")}Validate stack against team standards`);
|
|
127
|
+
console.log(`${chalk.dim(" benchmark <id> ")}Show performance profile for a stack`);
|
|
128
|
+
console.log(`${chalk.dim(" cost <id> ")}Estimate monthly hosting costs`);
|
|
129
|
+
console.log("");
|
|
130
|
+
console.log(chalk.cyan(" Plugins"));
|
|
131
|
+
console.log(`${chalk.dim(" plugin list ")}List installed plugins`);
|
|
132
|
+
console.log(`${chalk.dim(" plugin install ")}Install a plugin from local directory`);
|
|
133
|
+
console.log(`${chalk.dim(" plugin remove ")}Remove an installed plugin`);
|
|
134
|
+
console.log(`${chalk.dim(" plugin info ")}Show plugin details`);
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log(chalk.cyan(" Migration & Learning"));
|
|
137
|
+
console.log(`${chalk.dim(" migrate ")}Generate a migration plan between techs`);
|
|
138
|
+
console.log(`${chalk.dim(" learn <tech> ")}Show learning resources for a technology`);
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(chalk.cyan(" Extras"));
|
|
141
|
+
console.log(`${chalk.dim(" ai suggest ")}AI-powered stack suggestions`);
|
|
142
|
+
console.log(`${chalk.dim(" template ")}Manage templates`);
|
|
143
|
+
console.log(`${chalk.dim(" config ")}Manage preferences`);
|
|
144
|
+
console.log(`${chalk.dim(" completion ")}Generate shell completions`);
|
|
145
|
+
console.log("");
|
|
146
|
+
console.log(chalk.dim(` Run ${chalk.white("stackweld <command> --help")} for detailed usage.`));
|
|
147
|
+
console.log("");
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
151
|
+
if (err && err.code === "commander.unknownCommand") {
|
|
152
|
+
const cmd = process.argv[2];
|
|
153
|
+
console.error(chalk.red(`\u2716 Unknown command: "${cmd}"`));
|
|
154
|
+
console.error(chalk.dim(` Run ${chalk.white("stackweld --help")} to see available commands.`));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
console.error(chalk.red(`\u2716 ${err instanceof Error ? err.message : String(err)}`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CLI context — initializes engine instances once.
|
|
3
|
+
* All initialization is lazy with proper error handling.
|
|
4
|
+
*/
|
|
5
|
+
import { RulesEngine, RuntimeManager, ScaffoldOrchestrator, StackEngine } from "@stackweld/core";
|
|
6
|
+
export declare function getRulesEngine(): RulesEngine;
|
|
7
|
+
export declare function getStackEngine(): StackEngine;
|
|
8
|
+
export declare function getScaffoldOrchestrator(): ScaffoldOrchestrator;
|
|
9
|
+
export declare function getRuntimeManager(): RuntimeManager;
|
|
10
|
+
//# sourceMappingURL=context.d.ts.map
|