@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,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld deploy <stackId> --target <vps|aws|gcp> — Generate infrastructure files.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { generateInfra } from "@stackweld/core";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { getRulesEngine, getStackEngine } from "../ui/context.js";
|
|
10
|
+
import { box, formatJson, gradientHeader, nextSteps } from "../ui/format.js";
|
|
11
|
+
const VALID_TARGETS = new Set(["vps", "aws", "gcp"]);
|
|
12
|
+
export const deployCommand = new Command("deploy")
|
|
13
|
+
.description("Generate infrastructure-as-code files for a saved stack")
|
|
14
|
+
.argument("<stack-id>", "Stack ID to generate infra for")
|
|
15
|
+
.requiredOption("-t, --target <target>", "Deploy target: vps, aws, or gcp")
|
|
16
|
+
.option("-o, --output <dir>", "Output directory", ".")
|
|
17
|
+
.option("--json", "Output as JSON")
|
|
18
|
+
.option("--dry-run", "Show what would be generated without writing files")
|
|
19
|
+
.action((stackId, opts) => {
|
|
20
|
+
const target = opts.target;
|
|
21
|
+
if (!VALID_TARGETS.has(target)) {
|
|
22
|
+
console.error(chalk.red(`\u2716 Invalid target "${opts.target}". Use: vps, aws, or gcp`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const engine = getStackEngine();
|
|
26
|
+
const rules = getRulesEngine();
|
|
27
|
+
const stack = engine.get(stackId);
|
|
28
|
+
if (!stack) {
|
|
29
|
+
console.error(chalk.red(`\u2716 Stack "${stackId}" not found.`));
|
|
30
|
+
console.error(chalk.dim(" Run: stackweld list"));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Resolve full technology data for each stack technology
|
|
34
|
+
const technologies = stack.technologies.map((st) => {
|
|
35
|
+
const tech = rules.getTechnology(st.technologyId);
|
|
36
|
+
return {
|
|
37
|
+
id: st.technologyId,
|
|
38
|
+
name: tech?.name || st.technologyId,
|
|
39
|
+
category: tech?.category || "runtime",
|
|
40
|
+
dockerImage: tech?.dockerImage,
|
|
41
|
+
defaultPort: st.port || tech?.defaultPort,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const result = generateInfra(technologies, stack.name, target);
|
|
45
|
+
if (opts.json) {
|
|
46
|
+
console.log(formatJson(result));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log(`\n ${gradientHeader("Stackweld")} ${chalk.dim("/ Deploy")}\n`);
|
|
50
|
+
// Build content
|
|
51
|
+
const lines = [];
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(` ${chalk.dim("Stack:")} ${chalk.cyan.bold(stack.name)}`);
|
|
54
|
+
lines.push(` ${chalk.dim("Target:")} ${chalk.yellow(target.toUpperCase())}`);
|
|
55
|
+
lines.push(` ${chalk.dim("Files:")} ${result.files.length}`);
|
|
56
|
+
lines.push("");
|
|
57
|
+
for (const file of result.files) {
|
|
58
|
+
const sizeKB = (Buffer.byteLength(file.content, "utf-8") / 1024).toFixed(1);
|
|
59
|
+
lines.push(` ${chalk.green("\u2713")} ${chalk.white(file.path)} ${chalk.dim(`(${sizeKB}KB)`)}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
console.log(box(lines.join("\n"), `Infrastructure: ${target.toUpperCase()}`));
|
|
63
|
+
// Write files unless dry-run
|
|
64
|
+
if (!opts.dryRun) {
|
|
65
|
+
const outputDir = path.resolve(opts.output);
|
|
66
|
+
let written = 0;
|
|
67
|
+
for (const file of result.files) {
|
|
68
|
+
const filePath = path.join(outputDir, file.path);
|
|
69
|
+
const dirName = path.dirname(filePath);
|
|
70
|
+
if (!fs.existsSync(dirName)) {
|
|
71
|
+
fs.mkdirSync(dirName, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
fs.writeFileSync(filePath, file.content, "utf-8");
|
|
74
|
+
// Make shell scripts executable
|
|
75
|
+
if (file.path.endsWith(".sh")) {
|
|
76
|
+
fs.chmodSync(filePath, 0o755);
|
|
77
|
+
}
|
|
78
|
+
written++;
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.green(` \u2713 ${written} file(s) written to ${outputDir}`));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(chalk.yellow(" Dry run — no files written."));
|
|
84
|
+
}
|
|
85
|
+
// Show instructions
|
|
86
|
+
if (result.instructions.length > 0) {
|
|
87
|
+
console.log(nextSteps(result.instructions));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld doctor — Detect system capabilities and issues.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { box, formatJson, formatToolCheck, gradientHeader, ICONS, sectionHeader, } from "../ui/format.js";
|
|
9
|
+
const INSTALL_SUGGESTIONS = {
|
|
10
|
+
"Node.js": "https://nodejs.org or nvm install --lts",
|
|
11
|
+
npm: "Included with Node.js",
|
|
12
|
+
pnpm: "npm install -g pnpm",
|
|
13
|
+
Docker: "https://docs.docker.com/get-docker/",
|
|
14
|
+
"Docker Compose": "Included with Docker Desktop, or: apt install docker-compose-plugin",
|
|
15
|
+
Git: "apt install git or https://git-scm.com",
|
|
16
|
+
Python: "https://python.org or apt install python3",
|
|
17
|
+
Go: "https://go.dev/dl/ or snap install go",
|
|
18
|
+
Rust: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh",
|
|
19
|
+
Cargo: "Included with Rust (rustup)",
|
|
20
|
+
Bun: "curl -fsSL https://bun.sh/install | bash",
|
|
21
|
+
};
|
|
22
|
+
function check(name, command) {
|
|
23
|
+
try {
|
|
24
|
+
const output = execSync(command, { stdio: "pipe", timeout: 5000 }).toString().trim();
|
|
25
|
+
const versionMatch = output.match(/(\d+\.\d+[.\d]*)/);
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
status: "ok",
|
|
29
|
+
version: versionMatch?.[1] || output.slice(0, 50),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { name, status: "not_found", message: `${name} not found` };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export const doctorCommand = new Command("doctor")
|
|
37
|
+
.description("Check system requirements and environment")
|
|
38
|
+
.option("--json", "Output as JSON")
|
|
39
|
+
.action((opts) => {
|
|
40
|
+
const results = [];
|
|
41
|
+
// System info
|
|
42
|
+
const sysInfo = {
|
|
43
|
+
platform: os.platform(),
|
|
44
|
+
arch: os.arch(),
|
|
45
|
+
release: os.release(),
|
|
46
|
+
cpus: os.cpus().length,
|
|
47
|
+
memory: `${Math.round(os.totalmem() / 1024 / 1024 / 1024)}GB`,
|
|
48
|
+
freeMemory: `${Math.round(os.freemem() / 1024 / 1024 / 1024)}GB`,
|
|
49
|
+
hostname: os.hostname(),
|
|
50
|
+
};
|
|
51
|
+
// Check runtimes & tools (including all required ones)
|
|
52
|
+
results.push(check("Node.js", "node --version"));
|
|
53
|
+
results.push(check("npm", "npm --version"));
|
|
54
|
+
results.push(check("pnpm", "pnpm --version"));
|
|
55
|
+
results.push(check("Docker", "docker --version"));
|
|
56
|
+
results.push(check("Docker Compose", "docker compose version"));
|
|
57
|
+
results.push(check("Git", "git --version"));
|
|
58
|
+
results.push(check("Python", "python3 --version"));
|
|
59
|
+
results.push(check("Go", "go version"));
|
|
60
|
+
results.push(check("Rust", "rustc --version"));
|
|
61
|
+
results.push(check("Cargo", "cargo --version"));
|
|
62
|
+
results.push(check("Bun", "bun --version"));
|
|
63
|
+
// Check ports
|
|
64
|
+
const portChecks = [];
|
|
65
|
+
const commonPorts = [3000, 5432, 6379, 8000, 8080, 27017];
|
|
66
|
+
for (const port of commonPorts) {
|
|
67
|
+
try {
|
|
68
|
+
execSync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null || ss -tlnp 2>/dev/null | grep :${port}`, { stdio: "pipe", timeout: 2000 });
|
|
69
|
+
portChecks.push({
|
|
70
|
+
name: `Port ${port}`,
|
|
71
|
+
status: "warning",
|
|
72
|
+
message: `in use`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
portChecks.push({ name: `Port ${port}`, status: "ok" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Disk space
|
|
80
|
+
let diskInfo;
|
|
81
|
+
try {
|
|
82
|
+
const df = execSync("df -h . | tail -1", { stdio: "pipe" }).toString().trim();
|
|
83
|
+
const parts = df.split(/\s+/);
|
|
84
|
+
diskInfo = {
|
|
85
|
+
name: "Disk Space",
|
|
86
|
+
status: "ok",
|
|
87
|
+
version: `${parts[3]} available of ${parts[1]}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
diskInfo = {
|
|
92
|
+
name: "Disk Space",
|
|
93
|
+
status: "warning",
|
|
94
|
+
message: "Could not check",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (opts.json) {
|
|
98
|
+
console.log(formatJson({
|
|
99
|
+
system: sysInfo,
|
|
100
|
+
tools: results,
|
|
101
|
+
ports: portChecks,
|
|
102
|
+
disk: diskInfo,
|
|
103
|
+
}));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// ── Output ──
|
|
107
|
+
console.log(`\n ${gradientHeader("Stackweld")} ${chalk.dim("/ System Doctor")}\n`);
|
|
108
|
+
// System info box
|
|
109
|
+
const sysContent = [
|
|
110
|
+
`${chalk.dim("OS:")} ${sysInfo.platform} ${sysInfo.arch} (${sysInfo.release})`,
|
|
111
|
+
`${chalk.dim("CPUs:")} ${sysInfo.cpus}`,
|
|
112
|
+
`${chalk.dim("Memory:")} ${sysInfo.freeMemory} free / ${sysInfo.memory} total`,
|
|
113
|
+
`${chalk.dim("Disk:")} ${diskInfo.version || diskInfo.message}`,
|
|
114
|
+
`${chalk.dim("Host:")} ${sysInfo.hostname}`,
|
|
115
|
+
].join("\n");
|
|
116
|
+
console.log(box(sysContent, "System"));
|
|
117
|
+
// Tools
|
|
118
|
+
const found = results.filter((r) => r.status === "ok").length;
|
|
119
|
+
const missing = results.filter((r) => r.status === "not_found").length;
|
|
120
|
+
console.log(sectionHeader(` Tools (${chalk.green(`${String(found)} found`)}, ${missing > 0 ? chalk.red(`${String(missing)} missing`) : chalk.dim("0 missing")})`));
|
|
121
|
+
for (const r of results) {
|
|
122
|
+
console.log(formatToolCheck(r.name, r.status === "ok", r.version, INSTALL_SUGGESTIONS[r.name]));
|
|
123
|
+
}
|
|
124
|
+
// Ports
|
|
125
|
+
const portsInUse = portChecks.filter((p) => p.status === "warning");
|
|
126
|
+
console.log(sectionHeader(` Ports (${portsInUse.length > 0 ? chalk.yellow(`${String(portsInUse.length)} in use`) : chalk.green("all available")})`));
|
|
127
|
+
for (const p of portChecks) {
|
|
128
|
+
const icon = p.status === "ok" ? chalk.green("\u2714") : chalk.yellow("\u26A0");
|
|
129
|
+
const statusText = p.status === "warning" ? chalk.yellow(p.message) : chalk.dim("available");
|
|
130
|
+
console.log(` ${icon} ${p.name} ${statusText}`);
|
|
131
|
+
}
|
|
132
|
+
// WSL detection
|
|
133
|
+
if (sysInfo.release.includes("microsoft") || sysInfo.release.includes("WSL")) {
|
|
134
|
+
console.log(`\n ${ICONS.warning} ${chalk.yellow("WSL detected. Docker Desktop for Windows integration recommended.")}`);
|
|
135
|
+
}
|
|
136
|
+
// Summary
|
|
137
|
+
if (missing === 0) {
|
|
138
|
+
console.log(`\n ${chalk.green("\u2714")} ${chalk.green("All tools available. System is ready.")}\n`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(`\n ${chalk.yellow("\u26A0")} ${chalk.yellow(`${missing} tool(s) missing. Install them for full functionality.`)}\n`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld down — Stop Docker services.
|
|
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 } from "../ui/format.js";
|
|
10
|
+
export const downCommand = new Command("down")
|
|
11
|
+
.description("Stop Docker services")
|
|
12
|
+
.option("-p, --path <dir>", "Project directory", ".")
|
|
13
|
+
.option("--volumes", "Remove volumes on down")
|
|
14
|
+
.action((opts) => {
|
|
15
|
+
const runtime = getRuntimeManager();
|
|
16
|
+
const projectDir = path.resolve(opts.path);
|
|
17
|
+
if (!runtime.isDockerAvailable()) {
|
|
18
|
+
console.error(error("Docker is not available."));
|
|
19
|
+
console.error(chalk.dim(" Install Docker: https://docs.docker.com/get-docker/"));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const composePath = runtime.composeExists(projectDir);
|
|
23
|
+
if (!composePath) {
|
|
24
|
+
console.error(error(`No docker-compose.yml found in ${projectDir}`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const spinner = ora("Stopping services...").start();
|
|
28
|
+
const result = runtime.down({ composePath, projectDir }, opts.volumes);
|
|
29
|
+
if (result.success) {
|
|
30
|
+
spinner.succeed(opts.volumes ? "Services stopped and volumes removed" : "Services stopped");
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
spinner.fail("Failed to stop services");
|
|
34
|
+
console.error(chalk.dim(result.output.slice(0, 300)));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=down.js.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld env [sync|check] — Environment variable sync and safety checks.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { checkDangerous, parseEnvFile, syncEnv } from "@stackweld/core";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { box, error, formatJson } from "../ui/format.js";
|
|
10
|
+
export const envCommand = new Command("env")
|
|
11
|
+
.description("Sync .env files and check for dangerous values")
|
|
12
|
+
.argument("[action]", "Action: sync (default) or check", "sync")
|
|
13
|
+
.option("--path <dir>", "Project directory", process.cwd())
|
|
14
|
+
.option("--json", "Output as JSON")
|
|
15
|
+
.action((action, opts) => {
|
|
16
|
+
const projectDir = path.resolve(opts.path);
|
|
17
|
+
if (action === "check") {
|
|
18
|
+
runCheck(projectDir, opts.json);
|
|
19
|
+
}
|
|
20
|
+
else if (action === "sync") {
|
|
21
|
+
runSync(projectDir, opts.json);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.error(error(`Unknown action: "${action}". Use "sync" or "check".`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
function runSync(projectDir, json) {
|
|
29
|
+
const examplePath = path.join(projectDir, ".env.example");
|
|
30
|
+
const envPath = path.join(projectDir, ".env");
|
|
31
|
+
if (!fs.existsSync(examplePath)) {
|
|
32
|
+
console.error(error(`No .env.example found in ${projectDir}`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (!fs.existsSync(envPath)) {
|
|
36
|
+
console.error(error(`No .env found in ${projectDir}`));
|
|
37
|
+
console.error(chalk.dim(" Create one from the example: cp .env.example .env"));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
let exampleContent;
|
|
41
|
+
let envContent;
|
|
42
|
+
try {
|
|
43
|
+
exampleContent = fs.readFileSync(examplePath, "utf-8");
|
|
44
|
+
envContent = fs.readFileSync(envPath, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.error(error(`Failed to read env files: ${err instanceof Error ? err.message : String(err)}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const result = syncEnv(exampleContent, envContent);
|
|
51
|
+
if (json) {
|
|
52
|
+
console.log(formatJson(result));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push(`${chalk.dim(".env.example:")} ${result.total.example} variables`);
|
|
57
|
+
lines.push(`${chalk.dim(".env:")} ${result.total.actual} variables`);
|
|
58
|
+
lines.push("");
|
|
59
|
+
// Missing
|
|
60
|
+
if (result.missing.length > 0) {
|
|
61
|
+
lines.push(chalk.red(`\u2716 Missing in .env (${result.missing.length}):`));
|
|
62
|
+
for (const key of result.missing) {
|
|
63
|
+
lines.push(` ${chalk.red(key)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
lines.push(chalk.green("\u2714 No missing variables"));
|
|
68
|
+
}
|
|
69
|
+
lines.push("");
|
|
70
|
+
// Extra
|
|
71
|
+
if (result.extra.length > 0) {
|
|
72
|
+
lines.push(chalk.yellow(`\u26A0 Extra in .env (${result.extra.length}):`));
|
|
73
|
+
for (const key of result.extra) {
|
|
74
|
+
lines.push(` ${chalk.yellow(key)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
lines.push(chalk.dim("\u26A0 Extra in .env (0)"));
|
|
79
|
+
}
|
|
80
|
+
lines.push("");
|
|
81
|
+
// Dangerous
|
|
82
|
+
if (result.dangerous.length > 0) {
|
|
83
|
+
lines.push(chalk.yellow(`\u26A0 Dangerous values (${result.dangerous.length}):`));
|
|
84
|
+
for (const d of result.dangerous) {
|
|
85
|
+
lines.push(` ${chalk.yellow(d.key)} = ${chalk.dim(`"${d.value}"`)}`);
|
|
86
|
+
lines.push(` ${chalk.dim("\u2192")} ${d.reason}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
lines.push(chalk.green("\u2714 No dangerous values detected"));
|
|
91
|
+
}
|
|
92
|
+
console.log(box(lines.join("\n"), "Environment Sync Report"));
|
|
93
|
+
}
|
|
94
|
+
function runCheck(projectDir, json) {
|
|
95
|
+
const envPath = path.join(projectDir, ".env");
|
|
96
|
+
if (!fs.existsSync(envPath)) {
|
|
97
|
+
console.error(error(`No .env found in ${projectDir}`));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
let envContent;
|
|
101
|
+
try {
|
|
102
|
+
envContent = fs.readFileSync(envPath, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error(error(`Failed to read .env: ${err instanceof Error ? err.message : String(err)}`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const vars = parseEnvFile(envContent);
|
|
109
|
+
const dangerous = checkDangerous(vars);
|
|
110
|
+
if (json) {
|
|
111
|
+
console.log(formatJson({ total: vars.length, dangerous }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const lines = [];
|
|
115
|
+
lines.push(`${chalk.dim("Variables:")} ${vars.length}`);
|
|
116
|
+
lines.push("");
|
|
117
|
+
if (dangerous.length > 0) {
|
|
118
|
+
lines.push(chalk.yellow(`\u26A0 Dangerous values (${dangerous.length}):`));
|
|
119
|
+
for (const d of dangerous) {
|
|
120
|
+
lines.push(` ${chalk.yellow(d.key)} = ${chalk.dim(`"${d.value}"`)}`);
|
|
121
|
+
lines.push(` ${chalk.dim("\u2192")} ${d.reason}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
lines.push(chalk.green("\u2714 No dangerous values detected"));
|
|
126
|
+
}
|
|
127
|
+
console.log(box(lines.join("\n"), "Environment Check"));
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld export <stack-id> — Export a stack definition to YAML or JSON.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { stringify as yamlStringify } from "yaml";
|
|
8
|
+
import { getStackEngine } from "../ui/context.js";
|
|
9
|
+
export const exportCommand = new Command("export")
|
|
10
|
+
.description("Export a stack definition to YAML or JSON")
|
|
11
|
+
.argument("<id>", "Stack ID")
|
|
12
|
+
.option("-f, --format <fmt>", "Output format: json or yaml", "yaml")
|
|
13
|
+
.option("-o, --output <file>", "Output file (default: stdout)")
|
|
14
|
+
.action((id, opts) => {
|
|
15
|
+
const engine = getStackEngine();
|
|
16
|
+
const stack = engine.get(id);
|
|
17
|
+
if (!stack) {
|
|
18
|
+
console.error(chalk.red(`Stack "${id}" not found.`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// Build portable export object (without internal IDs)
|
|
22
|
+
const exportData = {
|
|
23
|
+
name: stack.name,
|
|
24
|
+
description: stack.description,
|
|
25
|
+
profile: stack.profile,
|
|
26
|
+
version: stack.version,
|
|
27
|
+
tags: stack.tags,
|
|
28
|
+
technologies: stack.technologies.map((t) => ({
|
|
29
|
+
id: t.technologyId,
|
|
30
|
+
version: t.version,
|
|
31
|
+
port: t.port,
|
|
32
|
+
})),
|
|
33
|
+
exportedAt: new Date().toISOString(),
|
|
34
|
+
exportedBy: "stackweld",
|
|
35
|
+
};
|
|
36
|
+
let output;
|
|
37
|
+
if (opts.format === "json") {
|
|
38
|
+
output = JSON.stringify(exportData, null, 2);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
output = yamlStringify(exportData);
|
|
42
|
+
}
|
|
43
|
+
if (opts.output) {
|
|
44
|
+
fs.writeFileSync(opts.output, output, "utf-8");
|
|
45
|
+
console.log(chalk.green(`✓ Exported to ${opts.output}`));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log(output);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=export-stack.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stackweld generate — Smart project generator.
|
|
3
|
+
* Detects project type (full-stack, frontend-only, backend-only),
|
|
4
|
+
* creates proper directory structure, executes official scaffolds,
|
|
5
|
+
* generates per-directory .env files, and leaves everything ready to code.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
export declare const generateCommand: Command;
|
|
9
|
+
//# sourceMappingURL=generate.d.ts.map
|