@stackweld/core 0.3.0 → 0.4.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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/compatibility-scorer.test.d.ts +1 -1
  3. package/dist/__tests__/rules-engine.test.d.ts +1 -1
  4. package/dist/__tests__/scaffold-orchestrator.test.d.ts +1 -1
  5. package/dist/__tests__/stack-engine.test.d.ts +1 -1
  6. package/dist/db/database.d.ts +1 -1
  7. package/dist/engine/compatibility-scorer.d.ts +16 -19
  8. package/dist/engine/compose-generator.d.ts +21 -31
  9. package/dist/engine/compose-generator.js +1 -15
  10. package/dist/engine/cost-estimator.d.ts +13 -13
  11. package/dist/engine/env-analyzer.d.ts +14 -14
  12. package/dist/engine/health-checker.d.ts +12 -12
  13. package/dist/engine/health-checker.js +14 -6
  14. package/dist/engine/infra-generator.d.ts +14 -17
  15. package/dist/engine/license-manager.d.ts +29 -0
  16. package/dist/engine/license-manager.js +130 -0
  17. package/dist/engine/migration-planner.d.ts +18 -22
  18. package/dist/engine/performance-profiler.d.ts +13 -13
  19. package/dist/engine/preferences.d.ts +7 -7
  20. package/dist/engine/runtime-manager.d.ts +46 -56
  21. package/dist/engine/runtime-manager.js +24 -10
  22. package/dist/engine/scaffold-orchestrator.js +42 -6
  23. package/dist/engine/stack-detector.d.ts +10 -10
  24. package/dist/engine/stack-differ.d.ts +15 -15
  25. package/dist/engine/stack-differ.js +76 -72
  26. package/dist/engine/stack-engine.d.ts +0 -20
  27. package/dist/engine/stack-engine.js +0 -24
  28. package/dist/engine/stack-serializer.d.ts +8 -8
  29. package/dist/engine/tech-installer.d.ts +12 -12
  30. package/dist/engine/tech-installer.js +3 -2
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +1 -0
  33. package/dist/types/project.d.ts +19 -19
  34. package/dist/types/project.js +1 -1
  35. package/dist/types/stack.d.ts +18 -18
  36. package/dist/types/stack.js +1 -1
  37. package/dist/types/technology.d.ts +28 -37
  38. package/dist/types/technology.js +1 -1
  39. package/dist/types/template.d.ts +22 -22
  40. package/dist/types/template.js +1 -1
  41. package/dist/types/validation.d.ts +12 -12
  42. package/dist/types/validation.js +1 -1
  43. package/package.json +1 -1
  44. package/LICENSE +0 -21
@@ -2,12 +2,12 @@
2
2
  * Preferences — Persistent user preferences stored in SQLite.
3
3
  */
4
4
  export interface UserPreferences {
5
- editor: string;
6
- packageManager: string;
7
- shell: string;
8
- dockerMode: string;
9
- defaultProfile: string;
10
- theme: string;
5
+ editor: string;
6
+ packageManager: string;
7
+ shell: string;
8
+ dockerMode: string;
9
+ defaultProfile: string;
10
+ theme: string;
11
11
  }
12
12
  /** Get all preferences, merged with defaults. */
13
13
  export declare function getPreferences(): UserPreferences;
@@ -21,4 +21,4 @@ export declare function resetPreferences(): void;
21
21
  export declare function getPreferenceKeys(): (keyof UserPreferences)[];
22
22
  /** Get default values. */
23
23
  export declare function getDefaultPreferences(): UserPreferences;
24
- //# sourceMappingURL=preferences.d.ts.map
24
+ //# sourceMappingURL=preferences.d.ts.map
@@ -4,62 +4,52 @@
4
4
  */
5
5
  import type { RuntimeState, ServiceStatus, Technology } from "../types/index.js";
6
6
  export interface RuntimeOptions {
7
- composePath: string;
8
- projectDir: string;
7
+ composePath: string;
8
+ projectDir: string;
9
9
  }
10
10
  export declare class RuntimeManager {
11
- private technologies;
12
- constructor(technologies: Technology[]);
13
- /**
14
- * Start all services with docker compose up.
15
- */
16
- up(
17
- opts: RuntimeOptions,
18
- detach?: boolean,
19
- ): {
20
- success: boolean;
21
- output: string;
22
- };
23
- /**
24
- * Stop all services.
25
- */
26
- down(
27
- opts: RuntimeOptions,
28
- volumes?: boolean,
29
- ): {
30
- success: boolean;
31
- output: string;
32
- };
33
- /**
34
- * Get status of all services in the compose file.
35
- */
36
- status(opts: RuntimeOptions): ServiceStatus[];
37
- /**
38
- * Get logs for a specific service or all services.
39
- */
40
- logs(opts: RuntimeOptions, service?: string, tail?: number, follow?: boolean): string;
41
- /**
42
- * Wait for all services to be healthy.
43
- */
44
- waitForHealthy(
45
- opts: RuntimeOptions,
46
- timeoutMs?: number,
47
- intervalMs?: number,
48
- ): Promise<{
49
- healthy: boolean;
50
- services: ServiceStatus[];
51
- }>;
52
- /**
53
- * Build RuntimeState from current container status.
54
- */
55
- getRuntimeState(projectId: string, opts: RuntimeOptions): RuntimeState;
56
- /**
57
- * Check if docker compose file exists at path.
58
- */
59
- composeExists(projectDir: string): string | null;
60
- /**
61
- * Check if Docker is available.
62
- */
63
- isDockerAvailable(): boolean;
11
+ private technologies;
12
+ constructor(technologies: Technology[]);
13
+ /**
14
+ * Start all services with docker compose up.
15
+ */
16
+ up(opts: RuntimeOptions, detach?: boolean): {
17
+ success: boolean;
18
+ output: string;
19
+ };
20
+ /**
21
+ * Stop all services.
22
+ */
23
+ down(opts: RuntimeOptions, volumes?: boolean): {
24
+ success: boolean;
25
+ output: string;
26
+ };
27
+ /**
28
+ * Get status of all services in the compose file.
29
+ */
30
+ status(opts: RuntimeOptions): ServiceStatus[];
31
+ /**
32
+ * Get logs for a specific service or all services.
33
+ */
34
+ logs(opts: RuntimeOptions, service?: string, tail?: number, follow?: boolean): string;
35
+ /**
36
+ * Wait for all services to be healthy.
37
+ */
38
+ waitForHealthy(opts: RuntimeOptions, timeoutMs?: number, intervalMs?: number): Promise<{
39
+ healthy: boolean;
40
+ services: ServiceStatus[];
41
+ }>;
42
+ /**
43
+ * Build RuntimeState from current container status.
44
+ */
45
+ getRuntimeState(projectId: string, opts: RuntimeOptions): RuntimeState;
46
+ /**
47
+ * Check if docker compose file exists at path.
48
+ */
49
+ composeExists(projectDir: string): string | null;
50
+ /**
51
+ * Check if Docker is available.
52
+ */
53
+ isDockerAvailable(): boolean;
64
54
  }
65
- //# sourceMappingURL=runtime-manager.d.ts.map
55
+ //# sourceMappingURL=runtime-manager.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * Runtime Manager — Manages Docker Compose lifecycle for stacks.
3
3
  * Handles up, down, status, logs, and health check waiting.
4
4
  */
5
- import { execSync } from "node:child_process";
5
+ import { execFileSync } from "node:child_process";
6
6
  import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
8
  export class RuntimeManager {
@@ -14,9 +14,11 @@ export class RuntimeManager {
14
14
  * Start all services with docker compose up.
15
15
  */
16
16
  up(opts, detach = true) {
17
- const flags = detach ? "-d" : "";
17
+ const args = ["compose", "-f", opts.composePath, "up"];
18
+ if (detach)
19
+ args.push("-d");
18
20
  try {
19
- const output = execSync(`docker compose -f "${opts.composePath}" up ${flags}`, {
21
+ const output = execFileSync("docker", args, {
20
22
  cwd: opts.projectDir,
21
23
  stdio: "pipe",
22
24
  timeout: 120_000,
@@ -32,9 +34,11 @@ export class RuntimeManager {
32
34
  * Stop all services.
33
35
  */
34
36
  down(opts, volumes = false) {
37
+ const args = ["compose", "-f", opts.composePath, "down"];
38
+ if (volumes)
39
+ args.push("--volumes");
35
40
  try {
36
- const volumesFlag = volumes ? " --volumes" : "";
37
- const output = execSync(`docker compose -f "${opts.composePath}" down${volumesFlag}`, {
41
+ const output = execFileSync("docker", args, {
38
42
  cwd: opts.projectDir,
39
43
  stdio: "pipe",
40
44
  timeout: 60_000,
@@ -51,7 +55,7 @@ export class RuntimeManager {
51
55
  */
52
56
  status(opts) {
53
57
  try {
54
- const raw = execSync(`docker compose -f "${opts.composePath}" ps --format json`, {
58
+ const raw = execFileSync("docker", ["compose", "-f", opts.composePath, "ps", "--format", "json"], {
55
59
  cwd: opts.projectDir,
56
60
  stdio: "pipe",
57
61
  timeout: 10_000,
@@ -112,10 +116,20 @@ export class RuntimeManager {
112
116
  * Get logs for a specific service or all services.
113
117
  */
114
118
  logs(opts, service, tail = 50, follow = false) {
119
+ if (service && !/^[a-zA-Z0-9_-]+$/.test(service)) {
120
+ throw new Error(`Invalid service name: ${service}`);
121
+ }
122
+ const args = ["compose", "-f", opts.composePath, "logs", "--tail", String(tail)];
123
+ if (follow)
124
+ args.push("-f");
125
+ if (service)
126
+ args.push(service);
115
127
  try {
116
- const serviceArg = service || "";
117
- const followFlag = follow ? " -f" : "";
118
- return execSync(`docker compose -f "${opts.composePath}" logs --tail ${tail}${followFlag} ${serviceArg}`, { cwd: opts.projectDir, stdio: follow ? "inherit" : "pipe", timeout: follow ? 0 : 10_000 }).toString();
128
+ return execFileSync("docker", args, {
129
+ cwd: opts.projectDir,
130
+ stdio: follow ? "inherit" : "pipe",
131
+ timeout: follow ? 0 : 10_000,
132
+ }).toString();
119
133
  }
120
134
  catch (err) {
121
135
  return err instanceof Error ? err.message : String(err);
@@ -170,7 +184,7 @@ export class RuntimeManager {
170
184
  */
171
185
  isDockerAvailable() {
172
186
  try {
173
- execSync("docker info", { stdio: "pipe", timeout: 5_000 });
187
+ execFileSync("docker", ["info"], { stdio: "pipe", timeout: 5_000 });
174
188
  return true;
175
189
  }
176
190
  catch {
@@ -7,7 +7,41 @@
7
7
  * - .gitignore (combined)
8
8
  * - devcontainer.json
9
9
  */
10
- import { execSync } from "node:child_process";
10
+ import { execFileSync } from "node:child_process";
11
+ import { randomBytes } from "node:crypto";
12
+ const WEAK_SECRETS = new Set([
13
+ "postgres",
14
+ "root",
15
+ "admin",
16
+ "password",
17
+ "secret",
18
+ "change-me",
19
+ "change-me-in-production",
20
+ "minioadmin",
21
+ "mariadb",
22
+ ]);
23
+ function securifyEnvValue(key, value, projectName) {
24
+ const lower = key.toLowerCase();
25
+ if (WEAK_SECRETS.has(value)) {
26
+ if (lower.includes("user") || lower.includes("username") || lower.includes("email")) {
27
+ return value; // usernames are fine as defaults
28
+ }
29
+ return randomBytes(20).toString("base64url");
30
+ }
31
+ // Replace weak passwords inside DATABASE_URL strings
32
+ if (lower.includes("url") &&
33
+ (value.includes("postgres:postgres") ||
34
+ value.includes("root:root") ||
35
+ value.includes("admin:admin"))) {
36
+ const securePass = randomBytes(20).toString("base64url");
37
+ return value
38
+ .replace(/postgres:postgres/g, `postgres:${securePass}`)
39
+ .replace(/root:root/g, `root:${securePass}`)
40
+ .replace(/admin:admin/g, `admin:${securePass}`)
41
+ .replace(/\/app/g, `/${projectName.replace(/[^a-zA-Z0-9_]/g, "_")}`);
42
+ }
43
+ return value;
44
+ }
11
45
  export class ScaffoldOrchestrator {
12
46
  technologies;
13
47
  constructor(technologies) {
@@ -109,9 +143,10 @@ export class ScaffoldOrchestrator {
109
143
  * Generate .env.example from all technologies' envVars.
110
144
  */
111
145
  generateEnvExample(stack, techs) {
146
+ const projectName = stack.name.replace(/\s+/g, "-").toLowerCase();
112
147
  const lines = [
113
148
  `# ${stack.name} — Environment Variables`,
114
- `# Generated by Stackweld`,
149
+ `# Generated by Stackweld — rotate secrets before production`,
115
150
  "",
116
151
  ];
117
152
  const seen = new Set();
@@ -122,7 +157,8 @@ export class ScaffoldOrchestrator {
122
157
  lines.push(`# ${tech.name}`);
123
158
  for (const [key, value] of entries) {
124
159
  if (!seen.has(key)) {
125
- lines.push(`${key}=${value}`);
160
+ const secureValue = securifyEnvValue(key, value, projectName);
161
+ lines.push(`${key}=${secureValue}`);
126
162
  seen.add(key);
127
163
  }
128
164
  }
@@ -354,11 +390,11 @@ export class ScaffoldOrchestrator {
354
390
  */
355
391
  initGit(projectDir, stack, initialCommit = true) {
356
392
  try {
357
- execSync("git init", { cwd: projectDir, stdio: "pipe" });
393
+ execFileSync("git", ["init"], { cwd: projectDir, stdio: "pipe" });
358
394
  if (initialCommit) {
359
- execSync("git add -A", { cwd: projectDir, stdio: "pipe" });
395
+ execFileSync("git", ["add", "-A"], { cwd: projectDir, stdio: "pipe" });
360
396
  const msg = `Initial commit: ${stack.name} (${stack.profile})\n\nGenerated by Stackweld`;
361
- execSync(`git commit -m "${msg}"`, {
397
+ execFileSync("git", ["commit", "-m", msg], {
362
398
  cwd: projectDir,
363
399
  stdio: "pipe",
364
400
  });
@@ -2,20 +2,20 @@
2
2
  * Stack Detector — Analyze a project directory to detect its technology stack.
3
3
  */
4
4
  export interface DetectedStack {
5
- technologies: DetectedTech[];
6
- confidence: number;
7
- projectType: "frontend" | "backend" | "fullstack" | "monorepo" | "library" | "unknown";
8
- packageManagers: string[];
5
+ technologies: DetectedTech[];
6
+ confidence: number;
7
+ projectType: "frontend" | "backend" | "fullstack" | "monorepo" | "library" | "unknown";
8
+ packageManagers: string[];
9
9
  }
10
10
  export interface DetectedTech {
11
- id: string;
12
- name: string;
13
- confidence: number;
14
- detectedVia: string;
15
- version?: string;
11
+ id: string;
12
+ name: string;
13
+ confidence: number;
14
+ detectedVia: string;
15
+ version?: string;
16
16
  }
17
17
  /**
18
18
  * Detect the technology stack of a project at the given path.
19
19
  */
20
20
  export declare function detectStack(projectPath: string): DetectedStack;
21
- //# sourceMappingURL=stack-detector.d.ts.map
21
+ //# sourceMappingURL=stack-detector.d.ts.map
@@ -3,24 +3,24 @@
3
3
  */
4
4
  import type { StackDefinition } from "../types/stack.js";
5
5
  export interface StackDiff {
6
- added: DiffItem[];
7
- removed: DiffItem[];
8
- changed: DiffChange[];
9
- unchanged: string[];
10
- summary: string;
6
+ added: DiffItem[];
7
+ removed: DiffItem[];
8
+ changed: DiffChange[];
9
+ unchanged: string[];
10
+ summary: string;
11
11
  }
12
12
  export interface DiffItem {
13
- technologyId: string;
14
- name: string;
15
- version?: string;
16
- port?: number;
13
+ technologyId: string;
14
+ name: string;
15
+ version?: string;
16
+ port?: number;
17
17
  }
18
18
  export interface DiffChange {
19
- technologyId: string;
20
- name: string;
21
- field: string;
22
- from: string;
23
- to: string;
19
+ technologyId: string;
20
+ name: string;
21
+ field: string;
22
+ from: string;
23
+ to: string;
24
24
  }
25
25
  export declare function diffStacks(stackA: StackDefinition, stackB: StackDefinition): StackDiff;
26
- //# sourceMappingURL=stack-differ.d.ts.map
26
+ //# sourceMappingURL=stack-differ.d.ts.map
@@ -3,78 +3,82 @@
3
3
  */
4
4
  // ─── Main Function ────────────────────────────────────
5
5
  export function diffStacks(stackA, stackB) {
6
- const techMapA = new Map(stackA.technologies.map((t) => [t.technologyId, t]));
7
- const techMapB = new Map(stackB.technologies.map((t) => [t.technologyId, t]));
8
- const added = [];
9
- const removed = [];
10
- const changed = [];
11
- const unchanged = [];
12
- // Find removed (in A but not B) and changed/unchanged
13
- for (const [id, techA] of techMapA) {
14
- const techB = techMapB.get(id);
15
- if (!techB) {
16
- removed.push({
17
- technologyId: id,
18
- name: id,
19
- version: techA.version,
20
- port: techA.port,
21
- });
22
- continue;
6
+ const techMapA = new Map(stackA.technologies.map((t) => [t.technologyId, t]));
7
+ const techMapB = new Map(stackB.technologies.map((t) => [t.technologyId, t]));
8
+ const added = [];
9
+ const removed = [];
10
+ const changed = [];
11
+ const unchanged = [];
12
+ // Find removed (in A but not B) and changed/unchanged
13
+ for (const [id, techA] of techMapA) {
14
+ const techB = techMapB.get(id);
15
+ if (!techB) {
16
+ removed.push({
17
+ technologyId: id,
18
+ name: id,
19
+ version: techA.version,
20
+ port: techA.port,
21
+ });
22
+ continue;
23
+ }
24
+ let hasChanges = false;
25
+ if (techA.version !== techB.version) {
26
+ changed.push({
27
+ technologyId: id,
28
+ name: id,
29
+ field: "version",
30
+ from: techA.version,
31
+ to: techB.version,
32
+ });
33
+ hasChanges = true;
34
+ }
35
+ if (techA.port !== techB.port) {
36
+ changed.push({
37
+ technologyId: id,
38
+ name: id,
39
+ field: "port",
40
+ from: techA.port !== undefined ? String(techA.port) : "none",
41
+ to: techB.port !== undefined ? String(techB.port) : "none",
42
+ });
43
+ hasChanges = true;
44
+ }
45
+ const configA = JSON.stringify(techA.config ?? {});
46
+ const configB = JSON.stringify(techB.config ?? {});
47
+ if (configA !== configB) {
48
+ changed.push({
49
+ technologyId: id,
50
+ name: id,
51
+ field: "config",
52
+ from: configA,
53
+ to: configB,
54
+ });
55
+ hasChanges = true;
56
+ }
57
+ if (!hasChanges) {
58
+ unchanged.push(id);
59
+ }
23
60
  }
24
- let hasChanges = false;
25
- if (techA.version !== techB.version) {
26
- changed.push({
27
- technologyId: id,
28
- name: id,
29
- field: "version",
30
- from: techA.version,
31
- to: techB.version,
32
- });
33
- hasChanges = true;
61
+ // Find added (in B but not A)
62
+ for (const [id, techB] of techMapB) {
63
+ if (!techMapA.has(id)) {
64
+ added.push({
65
+ technologyId: id,
66
+ name: id,
67
+ version: techB.version,
68
+ port: techB.port,
69
+ });
70
+ }
34
71
  }
35
- if (techA.port !== techB.port) {
36
- changed.push({
37
- technologyId: id,
38
- name: id,
39
- field: "port",
40
- from: techA.port !== undefined ? String(techA.port) : "none",
41
- to: techB.port !== undefined ? String(techB.port) : "none",
42
- });
43
- hasChanges = true;
44
- }
45
- const configA = JSON.stringify(techA.config ?? {});
46
- const configB = JSON.stringify(techB.config ?? {});
47
- if (configA !== configB) {
48
- changed.push({
49
- technologyId: id,
50
- name: id,
51
- field: "config",
52
- from: configA,
53
- to: configB,
54
- });
55
- hasChanges = true;
56
- }
57
- if (!hasChanges) {
58
- unchanged.push(id);
59
- }
60
- }
61
- // Find added (in B but not A)
62
- for (const [id, techB] of techMapB) {
63
- if (!techMapA.has(id)) {
64
- added.push({
65
- technologyId: id,
66
- name: id,
67
- version: techB.version,
68
- port: techB.port,
69
- });
70
- }
71
- }
72
- const parts = [];
73
- if (added.length > 0) parts.push(`+${added.length} added`);
74
- if (removed.length > 0) parts.push(`-${removed.length} removed`);
75
- if (changed.length > 0) parts.push(`~${changed.length} changed`);
76
- if (unchanged.length > 0) parts.push(`${unchanged.length} unchanged`);
77
- const summary = parts.join(", ");
78
- return { added, removed, changed, unchanged, summary };
72
+ const parts = [];
73
+ if (added.length > 0)
74
+ parts.push(`+${added.length} added`);
75
+ if (removed.length > 0)
76
+ parts.push(`-${removed.length} removed`);
77
+ if (changed.length > 0)
78
+ parts.push(`~${changed.length} changed`);
79
+ if (unchanged.length > 0)
80
+ parts.push(`${unchanged.length} unchanged`);
81
+ const summary = parts.join(", ");
82
+ return { added, removed, changed, unchanged, summary };
79
83
  }
80
- //# sourceMappingURL=stack-differ.js.map
84
+ //# sourceMappingURL=stack-differ.js.map
@@ -1,16 +1,8 @@
1
- /**
2
- * Stack Engine — CRUD operations for stack definitions.
3
- * Handles creation, validation, versioning, and persistence.
4
- */
5
1
  import type { StackDefinition, StackProfile, StackTechnology, StackVersion, ValidationResult } from "../types/index.js";
6
2
  import type { RulesEngine } from "./rules-engine.js";
7
3
  export declare class StackEngine {
8
4
  private rules;
9
5
  constructor(rules: RulesEngine);
10
- /**
11
- * Create a new stack definition.
12
- * Validates technologies and auto-resolves dependencies.
13
- */
14
6
  create(opts: {
15
7
  name: string;
16
8
  description?: string;
@@ -21,21 +13,9 @@ export declare class StackEngine {
21
13
  stack: StackDefinition;
22
14
  validation: ValidationResult;
23
15
  };
24
- /**
25
- * Get a stack by ID.
26
- */
27
16
  get(id: string): StackDefinition | null;
28
- /**
29
- * List all stacks.
30
- */
31
17
  list(): StackDefinition[];
32
- /**
33
- * Delete a stack.
34
- */
35
18
  delete(id: string): boolean;
36
- /**
37
- * Update a stack. Auto-increments version and saves snapshot.
38
- */
39
19
  update(id: string, changes: Partial<Pick<StackDefinition, "name" | "description" | "profile" | "technologies" | "tags">>): {
40
20
  stack: StackDefinition;
41
21
  validation: ValidationResult;
@@ -1,7 +1,3 @@
1
- /**
2
- * Stack Engine — CRUD operations for stack definitions.
3
- * Handles creation, validation, versioning, and persistence.
4
- */
5
1
  import { randomUUID } from "node:crypto";
6
2
  import { getDatabase } from "../db/database.js";
7
3
  export class StackEngine {
@@ -9,13 +5,8 @@ export class StackEngine {
9
5
  constructor(rules) {
10
6
  this.rules = rules;
11
7
  }
12
- /**
13
- * Create a new stack definition.
14
- * Validates technologies and auto-resolves dependencies.
15
- */
16
8
  create(opts) {
17
9
  const validation = this.rules.validate(opts.technologies);
18
- // Add auto-resolved dependencies
19
10
  const allTechs = [...opts.technologies];
20
11
  for (const depId of validation.resolvedDependencies) {
21
12
  const tech = this.rules.getTechnology(depId);
@@ -27,7 +18,6 @@ export class StackEngine {
27
18
  });
28
19
  }
29
20
  }
30
- // Apply port assignments to existing techs
31
21
  for (const t of allTechs) {
32
22
  if (validation.portAssignments[t.technologyId]) {
33
23
  t.port = validation.portAssignments[t.technologyId];
@@ -50,9 +40,6 @@ export class StackEngine {
50
40
  }
51
41
  return { stack, validation };
52
42
  }
53
- /**
54
- * Get a stack by ID.
55
- */
56
43
  get(id) {
57
44
  const db = getDatabase();
58
45
  const row = db.prepare("SELECT * FROM stacks WHERE id = ?").get(id);
@@ -78,25 +65,16 @@ export class StackEngine {
78
65
  })),
79
66
  };
80
67
  }
81
- /**
82
- * List all stacks.
83
- */
84
68
  list() {
85
69
  const db = getDatabase();
86
70
  const rows = db.prepare("SELECT id FROM stacks ORDER BY updated_at DESC").all();
87
71
  return rows.map((r) => this.get(r.id)).filter(Boolean);
88
72
  }
89
- /**
90
- * Delete a stack.
91
- */
92
73
  delete(id) {
93
74
  const db = getDatabase();
94
75
  const result = db.prepare("DELETE FROM stacks WHERE id = ?").run(id);
95
76
  return result.changes > 0;
96
77
  }
97
- /**
98
- * Update a stack. Auto-increments version and saves snapshot.
99
- */
100
78
  update(id, changes) {
101
79
  const existing = this.get(id);
102
80
  if (!existing)
@@ -112,7 +90,6 @@ export class StackEngine {
112
90
  updated_at = ?, tags = ?
113
91
  WHERE id = ?
114
92
  `).run(changes.name || existing.name, changes.description ?? existing.description, changes.profile || existing.profile, newVersion, now, JSON.stringify(changes.tags || existing.tags), id);
115
- // Replace technologies
116
93
  db.prepare("DELETE FROM stack_technologies WHERE stack_id = ?").run(id);
117
94
  for (const t of techs) {
118
95
  db.prepare(`
@@ -120,7 +97,6 @@ export class StackEngine {
120
97
  VALUES (?, ?, ?, ?, ?)
121
98
  `).run(id, t.technologyId, t.version, t.port || null, JSON.stringify(t.config || {}));
122
99
  }
123
- // Save version snapshot
124
100
  const updatedStack = this.get(id);
125
101
  this.saveVersionSnapshot(updatedStack, `Updated to v${newVersion}`);
126
102
  return { stack: updatedStack, validation };
@@ -3,13 +3,13 @@
3
3
  * Uses zlib deflate + base64url encoding. No server needed.
4
4
  */
5
5
  export interface ShareableStack {
6
- name: string;
7
- profile: string;
8
- technologies: Array<{
9
- id: string;
10
- version?: string;
11
- port?: number;
12
- }>;
6
+ name: string;
7
+ profile: string;
8
+ technologies: Array<{
9
+ id: string;
10
+ version?: string;
11
+ port?: number;
12
+ }>;
13
13
  }
14
14
  /**
15
15
  * Compress a stack into a URL-safe base64 string.
@@ -29,4 +29,4 @@ export declare function generateShareUrl(stack: ShareableStack, baseUrl?: string
29
29
  * Extract the encoded payload from a share URL.
30
30
  */
31
31
  export declare function extractFromShareUrl(url: string): string;
32
- //# sourceMappingURL=stack-serializer.d.ts.map
32
+ //# sourceMappingURL=stack-serializer.d.ts.map