@stackweld/core 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 (172) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +498 -0
  3. package/.turbo/turbo-test.log +21 -0
  4. package/.turbo/turbo-typecheck.log +4 -0
  5. package/dist/__tests__/compatibility-scorer.test.d.ts +2 -0
  6. package/dist/__tests__/compatibility-scorer.test.d.ts.map +1 -0
  7. package/dist/__tests__/compatibility-scorer.test.js +226 -0
  8. package/dist/__tests__/compatibility-scorer.test.js.map +1 -0
  9. package/dist/__tests__/rules-engine.test.d.ts +2 -0
  10. package/dist/__tests__/rules-engine.test.d.ts.map +1 -0
  11. package/dist/__tests__/rules-engine.test.js +161 -0
  12. package/dist/__tests__/rules-engine.test.js.map +1 -0
  13. package/dist/__tests__/scaffold-orchestrator.test.d.ts +2 -0
  14. package/dist/__tests__/scaffold-orchestrator.test.d.ts.map +1 -0
  15. package/dist/__tests__/scaffold-orchestrator.test.js +149 -0
  16. package/dist/__tests__/scaffold-orchestrator.test.js.map +1 -0
  17. package/dist/__tests__/stack-engine.test.d.ts +2 -0
  18. package/dist/__tests__/stack-engine.test.d.ts.map +1 -0
  19. package/dist/__tests__/stack-engine.test.js +278 -0
  20. package/dist/__tests__/stack-engine.test.js.map +1 -0
  21. package/dist/db/database.d.ts +9 -0
  22. package/dist/db/database.d.ts.map +1 -0
  23. package/dist/db/database.js +106 -0
  24. package/dist/db/database.js.map +1 -0
  25. package/dist/db/index.d.ts +2 -0
  26. package/dist/db/index.d.ts.map +1 -0
  27. package/dist/db/index.js +2 -0
  28. package/dist/db/index.js.map +1 -0
  29. package/dist/engine/compatibility-scorer.d.ts +37 -0
  30. package/dist/engine/compatibility-scorer.d.ts.map +1 -0
  31. package/dist/engine/compatibility-scorer.js +178 -0
  32. package/dist/engine/compatibility-scorer.js.map +1 -0
  33. package/dist/engine/compose-generator.d.ts +35 -0
  34. package/dist/engine/compose-generator.d.ts.map +1 -0
  35. package/dist/engine/compose-generator.js +95 -0
  36. package/dist/engine/compose-generator.js.map +1 -0
  37. package/dist/engine/cost-estimator.d.ts +22 -0
  38. package/dist/engine/cost-estimator.d.ts.map +1 -0
  39. package/dist/engine/cost-estimator.js +451 -0
  40. package/dist/engine/cost-estimator.js.map +1 -0
  41. package/dist/engine/env-analyzer.d.ts +36 -0
  42. package/dist/engine/env-analyzer.d.ts.map +1 -0
  43. package/dist/engine/env-analyzer.js +111 -0
  44. package/dist/engine/env-analyzer.js.map +1 -0
  45. package/dist/engine/health-checker.d.ts +20 -0
  46. package/dist/engine/health-checker.d.ts.map +1 -0
  47. package/dist/engine/health-checker.js +377 -0
  48. package/dist/engine/health-checker.js.map +1 -0
  49. package/dist/engine/index.d.ts +11 -0
  50. package/dist/engine/index.d.ts.map +1 -0
  51. package/dist/engine/index.js +7 -0
  52. package/dist/engine/index.js.map +1 -0
  53. package/dist/engine/infra-generator.d.ts +26 -0
  54. package/dist/engine/infra-generator.d.ts.map +1 -0
  55. package/dist/engine/infra-generator.js +751 -0
  56. package/dist/engine/infra-generator.js.map +1 -0
  57. package/dist/engine/migration-planner.d.ts +34 -0
  58. package/dist/engine/migration-planner.d.ts.map +1 -0
  59. package/dist/engine/migration-planner.js +427 -0
  60. package/dist/engine/migration-planner.js.map +1 -0
  61. package/dist/engine/performance-profiler.d.ts +22 -0
  62. package/dist/engine/performance-profiler.d.ts.map +1 -0
  63. package/dist/engine/performance-profiler.js +292 -0
  64. package/dist/engine/performance-profiler.js.map +1 -0
  65. package/dist/engine/plugin-loader.d.ts +36 -0
  66. package/dist/engine/plugin-loader.d.ts.map +1 -0
  67. package/dist/engine/plugin-loader.js +157 -0
  68. package/dist/engine/plugin-loader.js.map +1 -0
  69. package/dist/engine/preferences.d.ts +24 -0
  70. package/dist/engine/preferences.d.ts.map +1 -0
  71. package/dist/engine/preferences.js +62 -0
  72. package/dist/engine/preferences.js.map +1 -0
  73. package/dist/engine/rules-engine.d.ts +31 -0
  74. package/dist/engine/rules-engine.d.ts.map +1 -0
  75. package/dist/engine/rules-engine.js +179 -0
  76. package/dist/engine/rules-engine.js.map +1 -0
  77. package/dist/engine/runtime-manager.d.ts +65 -0
  78. package/dist/engine/runtime-manager.d.ts.map +1 -0
  79. package/dist/engine/runtime-manager.js +181 -0
  80. package/dist/engine/runtime-manager.js.map +1 -0
  81. package/dist/engine/scaffold-orchestrator.d.ts +103 -0
  82. package/dist/engine/scaffold-orchestrator.d.ts.map +1 -0
  83. package/dist/engine/scaffold-orchestrator.js +934 -0
  84. package/dist/engine/scaffold-orchestrator.js.map +1 -0
  85. package/dist/engine/stack-detector.d.ts +21 -0
  86. package/dist/engine/stack-detector.d.ts.map +1 -0
  87. package/dist/engine/stack-detector.js +313 -0
  88. package/dist/engine/stack-detector.js.map +1 -0
  89. package/dist/engine/stack-differ.d.ts +26 -0
  90. package/dist/engine/stack-differ.d.ts.map +1 -0
  91. package/dist/engine/stack-differ.js +80 -0
  92. package/dist/engine/stack-differ.js.map +1 -0
  93. package/dist/engine/stack-engine.d.ts +54 -0
  94. package/dist/engine/stack-engine.d.ts.map +1 -0
  95. package/dist/engine/stack-engine.js +186 -0
  96. package/dist/engine/stack-engine.js.map +1 -0
  97. package/dist/engine/stack-serializer.d.ts +32 -0
  98. package/dist/engine/stack-serializer.d.ts.map +1 -0
  99. package/dist/engine/stack-serializer.js +75 -0
  100. package/dist/engine/stack-serializer.js.map +1 -0
  101. package/dist/engine/standards-linter.d.ts +34 -0
  102. package/dist/engine/standards-linter.d.ts.map +1 -0
  103. package/dist/engine/standards-linter.js +162 -0
  104. package/dist/engine/standards-linter.js.map +1 -0
  105. package/dist/engine/tech-installer.d.ts +37 -0
  106. package/dist/engine/tech-installer.d.ts.map +1 -0
  107. package/dist/engine/tech-installer.js +508 -0
  108. package/dist/engine/tech-installer.js.map +1 -0
  109. package/dist/index.d.ts +39 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +25 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/types/index.d.ts +6 -0
  114. package/dist/types/index.d.ts.map +1 -0
  115. package/dist/types/index.js +2 -0
  116. package/dist/types/index.js.map +1 -0
  117. package/dist/types/project.d.ts +33 -0
  118. package/dist/types/project.d.ts.map +1 -0
  119. package/dist/types/project.js +6 -0
  120. package/dist/types/project.js.map +1 -0
  121. package/dist/types/stack.d.ts +29 -0
  122. package/dist/types/stack.d.ts.map +1 -0
  123. package/dist/types/stack.js +6 -0
  124. package/dist/types/stack.js.map +1 -0
  125. package/dist/types/technology.d.ts +47 -0
  126. package/dist/types/technology.d.ts.map +1 -0
  127. package/dist/types/technology.js +6 -0
  128. package/dist/types/technology.js.map +1 -0
  129. package/dist/types/template.d.ts +34 -0
  130. package/dist/types/template.d.ts.map +1 -0
  131. package/dist/types/template.js +6 -0
  132. package/dist/types/template.js.map +1 -0
  133. package/dist/types/validation.d.ts +20 -0
  134. package/dist/types/validation.d.ts.map +1 -0
  135. package/dist/types/validation.js +5 -0
  136. package/dist/types/validation.js.map +1 -0
  137. package/package.json +39 -0
  138. package/src/__tests__/compatibility-scorer.test.ts +264 -0
  139. package/src/__tests__/rules-engine.test.ts +170 -0
  140. package/src/__tests__/scaffold-orchestrator.test.ts +161 -0
  141. package/src/__tests__/stack-engine.test.ts +328 -0
  142. package/src/db/database.ts +112 -0
  143. package/src/db/index.ts +1 -0
  144. package/src/engine/compatibility-scorer.ts +222 -0
  145. package/src/engine/compose-generator.ts +134 -0
  146. package/src/engine/cost-estimator.ts +498 -0
  147. package/src/engine/env-analyzer.ts +156 -0
  148. package/src/engine/health-checker.ts +421 -0
  149. package/src/engine/index.ts +17 -0
  150. package/src/engine/infra-generator.ts +837 -0
  151. package/src/engine/migration-planner.ts +496 -0
  152. package/src/engine/performance-profiler.ts +354 -0
  153. package/src/engine/plugin-loader.ts +216 -0
  154. package/src/engine/preferences.ts +85 -0
  155. package/src/engine/rules-engine.ts +204 -0
  156. package/src/engine/runtime-manager.ts +207 -0
  157. package/src/engine/scaffold-orchestrator.ts +1052 -0
  158. package/src/engine/stack-detector.ts +345 -0
  159. package/src/engine/stack-differ.ts +118 -0
  160. package/src/engine/stack-engine.ts +258 -0
  161. package/src/engine/stack-serializer.ts +95 -0
  162. package/src/engine/standards-linter.ts +210 -0
  163. package/src/engine/tech-installer.ts +650 -0
  164. package/src/index.ts +78 -0
  165. package/src/types/index.ts +10 -0
  166. package/src/types/project.ts +36 -0
  167. package/src/types/stack.ts +32 -0
  168. package/src/types/technology.ts +58 -0
  169. package/src/types/template.ts +37 -0
  170. package/src/types/validation.ts +22 -0
  171. package/tsconfig.json +10 -0
  172. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Performance Profiler — Estimates performance characteristics of a tech stack.
3
+ */
4
+
5
+ import type { Technology, TechnologyCategory } from "../types/technology.js";
6
+
7
+ // ─── Types ────────────────────────────────────────────
8
+
9
+ export interface TechPerformance {
10
+ id: string;
11
+ name: string;
12
+ category: TechnologyCategory;
13
+ perf: "fast" | "moderate" | "heavy";
14
+ note: string;
15
+ }
16
+
17
+ export interface PerformanceProfile {
18
+ stackName: string;
19
+ rating: "blazing" | "fast" | "moderate" | "heavy";
20
+ estimatedReqPerSec: string;
21
+ estimatedColdStart: string;
22
+ estimatedMemory: string;
23
+ notes: string[];
24
+ techProfiles: TechPerformance[];
25
+ }
26
+
27
+ // ─── Knowledge Maps ───────────────────────────────────
28
+
29
+ const RUNTIME_PERF: Record<string, { perf: TechPerformance["perf"]; note: string }> = {
30
+ go: { perf: "fast", note: "Compiled, goroutines, minimal GC pause" },
31
+ rust: { perf: "fast", note: "Zero-cost abstractions, no GC" },
32
+ bun: { perf: "fast", note: "Native-speed JS runtime with built-in bundler" },
33
+ nodejs: { perf: "moderate", note: "V8 JIT, single-threaded event loop" },
34
+ deno: { perf: "moderate", note: "V8 JIT, secure by default" },
35
+ python: { perf: "heavy", note: "Interpreted, GIL limits concurrency" },
36
+ php: { perf: "heavy", note: "Process-per-request model, opcache helps" },
37
+ };
38
+
39
+ const FRAMEWORK_PERF: Record<string, { perf: TechPerformance["perf"]; note: string }> = {
40
+ fastify: { perf: "fast", note: "Schema-based serialization, low overhead" },
41
+ hono: { perf: "fast", note: "Ultra-lightweight, works on edge runtimes" },
42
+ gin: { perf: "fast", note: "High-performance Go HTTP framework" },
43
+ echo: { perf: "fast", note: "Minimal Go framework, fast router" },
44
+ fastapi: { perf: "fast", note: "Async Python with Starlette + Pydantic" },
45
+ actix: { perf: "fast", note: "Rust actor framework, top benchmark scores" },
46
+ express: { perf: "moderate", note: "Mature middleware stack, moderate throughput" },
47
+ nestjs: { perf: "moderate", note: "Decorator-heavy, adds abstraction overhead" },
48
+ django: { perf: "moderate", note: "Batteries-included, ORM adds overhead" },
49
+ flask: { perf: "moderate", note: "Minimal but synchronous by default" },
50
+ nextjs: { perf: "moderate", note: "SSR/SSG adds build complexity" },
51
+ nuxt: { perf: "moderate", note: "Vue SSR framework, similar profile to Next" },
52
+ remix: { perf: "moderate", note: "Nested routes, server-first rendering" },
53
+ laravel: { perf: "moderate", note: "Full PHP framework, Eloquent ORM overhead" },
54
+ sveltekit: { perf: "moderate", note: "Compiled output is fast, SSR moderate" },
55
+ };
56
+
57
+ const DATABASE_PERF: Record<string, { perf: TechPerformance["perf"]; note: string }> = {
58
+ redis: { perf: "fast", note: "In-memory, sub-millisecond latency" },
59
+ sqlite: { perf: "fast", note: "Embedded, zero network overhead" },
60
+ postgresql: { perf: "moderate", note: "ACID-compliant, excellent query planner" },
61
+ mysql: { perf: "moderate", note: "Widely optimized, good read performance" },
62
+ mongodb: { perf: "moderate", note: "Document store, flexible but heavier writes" },
63
+ mariadb: { perf: "moderate", note: "MySQL fork, comparable performance" },
64
+ };
65
+
66
+ const SERVICE_PERF: Record<string, { perf: TechPerformance["perf"]; note: string }> = {
67
+ grafana: { perf: "heavy", note: "Dashboard rendering, query aggregation" },
68
+ prometheus: { perf: "heavy", note: "Time-series storage, scraping overhead" },
69
+ elasticsearch: { perf: "heavy", note: "JVM-based, memory-intensive indexing" },
70
+ rabbitmq: { perf: "moderate", note: "Erlang-based message broker" },
71
+ kafka: { perf: "moderate", note: "High-throughput streaming, JVM overhead" },
72
+ nginx: { perf: "fast", note: "Event-driven, very low resource usage" },
73
+ traefik: { perf: "fast", note: "Go-based reverse proxy, auto-discovery" },
74
+ };
75
+
76
+ const ALL_PERF_MAPS: Record<
77
+ string,
78
+ Record<string, { perf: TechPerformance["perf"]; note: string }>
79
+ > = {
80
+ runtime: RUNTIME_PERF,
81
+ backend: FRAMEWORK_PERF,
82
+ frontend: FRAMEWORK_PERF,
83
+ database: DATABASE_PERF,
84
+ service: SERVICE_PERF,
85
+ };
86
+
87
+ // ─── Benchmark Estimates ──────────────────────────────
88
+
89
+ interface BenchmarkKey {
90
+ runtime?: string;
91
+ framework?: string;
92
+ database?: string;
93
+ }
94
+
95
+ const BENCHMARKS: Array<{
96
+ match: BenchmarkKey;
97
+ reqPerSec: string;
98
+ coldStart: string;
99
+ memory: string;
100
+ }> = [
101
+ {
102
+ match: { runtime: "rust", framework: "actix", database: "postgresql" },
103
+ reqPerSec: "~30k req/s",
104
+ coldStart: "~50ms",
105
+ memory: "~32MB",
106
+ },
107
+ {
108
+ match: { runtime: "go", framework: "gin", database: "postgresql" },
109
+ reqPerSec: "~25k req/s",
110
+ coldStart: "~80ms",
111
+ memory: "~48MB",
112
+ },
113
+ {
114
+ match: { runtime: "go", framework: "echo", database: "postgresql" },
115
+ reqPerSec: "~25k req/s",
116
+ coldStart: "~80ms",
117
+ memory: "~48MB",
118
+ },
119
+ {
120
+ match: { runtime: "bun", framework: "hono", database: "postgresql" },
121
+ reqPerSec: "~20k req/s",
122
+ coldStart: "~100ms",
123
+ memory: "~64MB",
124
+ },
125
+ {
126
+ match: { runtime: "nodejs", framework: "fastify", database: "postgresql" },
127
+ reqPerSec: "~15k req/s",
128
+ coldStart: "~200ms",
129
+ memory: "~128MB",
130
+ },
131
+ {
132
+ match: { runtime: "nodejs", framework: "express", database: "postgresql" },
133
+ reqPerSec: "~8k req/s",
134
+ coldStart: "~300ms",
135
+ memory: "~128MB",
136
+ },
137
+ {
138
+ match: { runtime: "nodejs", framework: "nestjs", database: "postgresql" },
139
+ reqPerSec: "~6k req/s",
140
+ coldStart: "~500ms",
141
+ memory: "~192MB",
142
+ },
143
+ {
144
+ match: { runtime: "python", framework: "fastapi", database: "postgresql" },
145
+ reqPerSec: "~5k req/s",
146
+ coldStart: "~400ms",
147
+ memory: "~128MB",
148
+ },
149
+ {
150
+ match: { runtime: "python", framework: "django", database: "postgresql" },
151
+ reqPerSec: "~2k req/s",
152
+ coldStart: "~600ms",
153
+ memory: "~192MB",
154
+ },
155
+ {
156
+ match: { runtime: "python", framework: "flask", database: "postgresql" },
157
+ reqPerSec: "~3k req/s",
158
+ coldStart: "~400ms",
159
+ memory: "~128MB",
160
+ },
161
+ {
162
+ match: { runtime: "php", framework: "laravel", database: "mysql" },
163
+ reqPerSec: "~1.5k req/s",
164
+ coldStart: "~500ms",
165
+ memory: "~192MB",
166
+ },
167
+ // Partial matches (no database)
168
+ {
169
+ match: { runtime: "go", framework: "gin" },
170
+ reqPerSec: "~40k req/s",
171
+ coldStart: "~50ms",
172
+ memory: "~32MB",
173
+ },
174
+ {
175
+ match: { runtime: "rust", framework: "actix" },
176
+ reqPerSec: "~50k req/s",
177
+ coldStart: "~30ms",
178
+ memory: "~16MB",
179
+ },
180
+ {
181
+ match: { runtime: "nodejs", framework: "fastify" },
182
+ reqPerSec: "~25k req/s",
183
+ coldStart: "~150ms",
184
+ memory: "~96MB",
185
+ },
186
+ {
187
+ match: { runtime: "nodejs", framework: "express" },
188
+ reqPerSec: "~12k req/s",
189
+ coldStart: "~200ms",
190
+ memory: "~96MB",
191
+ },
192
+ {
193
+ match: { runtime: "python", framework: "fastapi" },
194
+ reqPerSec: "~8k req/s",
195
+ coldStart: "~300ms",
196
+ memory: "~96MB",
197
+ },
198
+ ];
199
+
200
+ // ─── Rating Logic ─────────────────────────────────────
201
+
202
+ const PERF_RANK: Record<TechPerformance["perf"], number> = {
203
+ fast: 3,
204
+ moderate: 2,
205
+ heavy: 1,
206
+ };
207
+
208
+ function perfToRating(perf: TechPerformance["perf"]): PerformanceProfile["rating"] {
209
+ switch (perf) {
210
+ case "fast":
211
+ return "blazing";
212
+ case "moderate":
213
+ return "fast";
214
+ case "heavy":
215
+ return "moderate";
216
+ }
217
+ }
218
+
219
+ function worstPerf(perfs: TechPerformance["perf"][]): TechPerformance["perf"] {
220
+ if (perfs.length === 0) return "moderate";
221
+ let worst: TechPerformance["perf"] = "fast";
222
+ for (const p of perfs) {
223
+ if (PERF_RANK[p] < PERF_RANK[worst]) worst = p;
224
+ }
225
+ return worst;
226
+ }
227
+
228
+ // ─── Main Function ────────────────────────────────────
229
+
230
+ export function profilePerformance(technologies: Technology[]): PerformanceProfile {
231
+ const techProfiles: TechPerformance[] = [];
232
+ const criticalPerfs: TechPerformance["perf"][] = [];
233
+ const notes: string[] = [];
234
+
235
+ // Identify key components
236
+ let runtimeId: string | undefined;
237
+ let frameworkId: string | undefined;
238
+ let databaseId: string | undefined;
239
+
240
+ for (const tech of technologies) {
241
+ const perfMap = ALL_PERF_MAPS[tech.category];
242
+ const entry = perfMap?.[tech.id];
243
+
244
+ if (entry) {
245
+ techProfiles.push({
246
+ id: tech.id,
247
+ name: tech.name,
248
+ category: tech.category,
249
+ perf: entry.perf,
250
+ note: entry.note,
251
+ });
252
+
253
+ // Track critical path components
254
+ if (
255
+ tech.category === "runtime" ||
256
+ tech.category === "backend" ||
257
+ tech.category === "database"
258
+ ) {
259
+ criticalPerfs.push(entry.perf);
260
+ }
261
+ } else {
262
+ // Unknown tech — assume moderate
263
+ techProfiles.push({
264
+ id: tech.id,
265
+ name: tech.name,
266
+ category: tech.category,
267
+ perf: "moderate",
268
+ note: "No benchmark data available",
269
+ });
270
+ }
271
+
272
+ if (tech.category === "runtime") runtimeId = tech.id;
273
+ if (tech.category === "backend") frameworkId = tech.id;
274
+ if (tech.category === "database") databaseId = tech.id;
275
+ }
276
+
277
+ // Overall rating based on slowest critical-path component
278
+ const worst = worstPerf(criticalPerfs);
279
+ const rating = criticalPerfs.length > 0 ? perfToRating(worst) : "moderate";
280
+
281
+ // Build stack name
282
+ const stackName = technologies.map((t) => t.name).join(" + ");
283
+
284
+ // Find matching benchmark
285
+ let estimatedReqPerSec = "Varies by implementation";
286
+ let estimatedColdStart = "N/A";
287
+ let estimatedMemory = "N/A";
288
+
289
+ // Try exact match first, then partial
290
+ const matchKey: BenchmarkKey = {
291
+ runtime: runtimeId,
292
+ framework: frameworkId,
293
+ database: databaseId,
294
+ };
295
+
296
+ let bestMatch: (typeof BENCHMARKS)[number] | undefined;
297
+ let bestMatchScore = 0;
298
+
299
+ for (const bench of BENCHMARKS) {
300
+ let score = 0;
301
+ let valid = true;
302
+
303
+ for (const [key, value] of Object.entries(bench.match)) {
304
+ const actual = matchKey[key as keyof BenchmarkKey];
305
+ if (value && actual === value) {
306
+ score++;
307
+ } else if (value) {
308
+ valid = false;
309
+ break;
310
+ }
311
+ }
312
+
313
+ if (valid && score > bestMatchScore) {
314
+ bestMatch = bench;
315
+ bestMatchScore = score;
316
+ }
317
+ }
318
+
319
+ if (bestMatch) {
320
+ estimatedReqPerSec = bestMatch.reqPerSec;
321
+ estimatedColdStart = bestMatch.coldStart;
322
+ estimatedMemory = bestMatch.memory;
323
+ }
324
+
325
+ // Generate notes
326
+ const fastTechs = techProfiles.filter((t) => t.perf === "fast");
327
+ const heavyTechs = techProfiles.filter((t) => t.perf === "heavy");
328
+
329
+ if (fastTechs.length > 0 && heavyTechs.length === 0) {
330
+ notes.push("All components are high-performance — excellent stack choice.");
331
+ }
332
+ if (heavyTechs.length > 0) {
333
+ notes.push(`Bottleneck: ${heavyTechs.map((t) => t.name).join(", ")} may limit throughput.`);
334
+ }
335
+ if (runtimeId && !frameworkId) {
336
+ notes.push("No framework detected — raw runtime performance may differ with framework choice.");
337
+ }
338
+ if (!databaseId) {
339
+ notes.push("No database detected — estimates assume compute-only workload.");
340
+ }
341
+ if (rating === "blazing") {
342
+ notes.push("This stack is suitable for high-traffic production workloads.");
343
+ }
344
+
345
+ return {
346
+ stackName,
347
+ rating,
348
+ estimatedReqPerSec,
349
+ estimatedColdStart,
350
+ estimatedMemory,
351
+ notes,
352
+ techProfiles,
353
+ };
354
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Plugin Loader — Discovers, installs, and loads Stackweld plugins.
3
+ *
4
+ * Plugins live in ~/.stackweld/plugins/<name>/ and expose a
5
+ * stackweld.plugin.json manifest describing their capabilities.
6
+ */
7
+
8
+ import * as fs from "node:fs";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+ import type { Technology } from "../types/technology.js";
12
+ import type { Template } from "../types/template.js";
13
+
14
+ // ─── Types ────────────────────────────────────────────
15
+
16
+ export interface PluginCommand {
17
+ name: string;
18
+ description: string;
19
+ handler: string;
20
+ }
21
+
22
+ export interface StackweldPlugin {
23
+ name: string;
24
+ version: string;
25
+ description: string;
26
+ technologies?: Technology[];
27
+ templates?: Template[];
28
+ commands?: PluginCommand[];
29
+ }
30
+
31
+ export interface PluginManifest {
32
+ name: string;
33
+ version: string;
34
+ description: string;
35
+ main: string;
36
+ type: "technology" | "template" | "command" | "mixed";
37
+ }
38
+
39
+ // ─── Plugin Directory ─────────────────────────────────
40
+
41
+ export function getPluginDir(): string {
42
+ return path.join(os.homedir(), ".stackweld", "plugins");
43
+ }
44
+
45
+ function ensurePluginDir(): void {
46
+ const dir = getPluginDir();
47
+ if (!fs.existsSync(dir)) {
48
+ fs.mkdirSync(dir, { recursive: true });
49
+ }
50
+ }
51
+
52
+ // ─── List Plugins ─────────────────────────────────────
53
+
54
+ export function listPlugins(): PluginManifest[] {
55
+ const dir = getPluginDir();
56
+ if (!fs.existsSync(dir)) return [];
57
+
58
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
59
+ const manifests: PluginManifest[] = [];
60
+
61
+ for (const entry of entries) {
62
+ if (!entry.isDirectory()) continue;
63
+
64
+ const manifestPath = path.join(dir, entry.name, "stackweld.plugin.json");
65
+ if (!fs.existsSync(manifestPath)) continue;
66
+
67
+ try {
68
+ const raw = fs.readFileSync(manifestPath, "utf-8");
69
+ const manifest = JSON.parse(raw) as PluginManifest;
70
+ manifests.push(manifest);
71
+ } catch {
72
+ // Skip malformed manifests
73
+ }
74
+ }
75
+
76
+ return manifests;
77
+ }
78
+
79
+ // ─── Install Plugin ───────────────────────────────────
80
+
81
+ export function installPlugin(nameOrPath: string): PluginManifest {
82
+ ensurePluginDir();
83
+
84
+ const sourcePath = path.resolve(nameOrPath);
85
+
86
+ if (!fs.existsSync(sourcePath)) {
87
+ throw new Error(`Plugin source not found: ${sourcePath}`);
88
+ }
89
+
90
+ if (!fs.statSync(sourcePath).isDirectory()) {
91
+ throw new Error(`Plugin source must be a directory: ${sourcePath}`);
92
+ }
93
+
94
+ const manifestPath = path.join(sourcePath, "stackweld.plugin.json");
95
+ if (!fs.existsSync(manifestPath)) {
96
+ throw new Error(`No stackweld.plugin.json found in: ${sourcePath}`);
97
+ }
98
+
99
+ let manifest: PluginManifest;
100
+ try {
101
+ const raw = fs.readFileSync(manifestPath, "utf-8");
102
+ manifest = JSON.parse(raw) as PluginManifest;
103
+ } catch {
104
+ throw new Error(`Invalid stackweld.plugin.json in: ${sourcePath}`);
105
+ }
106
+
107
+ if (!manifest.name || !manifest.version || !manifest.main) {
108
+ throw new Error("Plugin manifest must have name, version, and main fields.");
109
+ }
110
+
111
+ const destDir = path.join(getPluginDir(), manifest.name);
112
+
113
+ // Remove existing installation
114
+ if (fs.existsSync(destDir)) {
115
+ fs.rmSync(destDir, { recursive: true, force: true });
116
+ }
117
+
118
+ // Copy plugin directory
119
+ copyDirRecursive(sourcePath, destDir);
120
+
121
+ return manifest;
122
+ }
123
+
124
+ function copyDirRecursive(src: string, dest: string): void {
125
+ fs.mkdirSync(dest, { recursive: true });
126
+
127
+ const entries = fs.readdirSync(src, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ const srcPath = path.join(src, entry.name);
130
+ const destPath = path.join(dest, entry.name);
131
+
132
+ if (entry.isDirectory()) {
133
+ // Skip node_modules and .git
134
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
135
+ copyDirRecursive(srcPath, destPath);
136
+ } else {
137
+ fs.copyFileSync(srcPath, destPath);
138
+ }
139
+ }
140
+ }
141
+
142
+ // ─── Remove Plugin ────────────────────────────────────
143
+
144
+ export function removePlugin(name: string): void {
145
+ const pluginDir = path.join(getPluginDir(), name);
146
+
147
+ if (!fs.existsSync(pluginDir)) {
148
+ throw new Error(`Plugin "${name}" is not installed.`);
149
+ }
150
+
151
+ fs.rmSync(pluginDir, { recursive: true, force: true });
152
+ }
153
+
154
+ // ─── Load Plugin ──────────────────────────────────────
155
+
156
+ export function loadPlugin(name: string): StackweldPlugin | null {
157
+ const pluginDir = path.join(getPluginDir(), name);
158
+ const manifestPath = path.join(pluginDir, "stackweld.plugin.json");
159
+
160
+ if (!fs.existsSync(manifestPath)) return null;
161
+
162
+ try {
163
+ const raw = fs.readFileSync(manifestPath, "utf-8");
164
+ const manifest = JSON.parse(raw) as PluginManifest;
165
+ const mainPath = path.join(pluginDir, manifest.main);
166
+
167
+ if (!fs.existsSync(mainPath)) return null;
168
+
169
+ // Read the main file and parse it as JSON data (for static plugins)
170
+ // or return manifest-only info
171
+ const mainRaw = fs.readFileSync(mainPath, "utf-8");
172
+ const pluginData = JSON.parse(mainRaw) as StackweldPlugin;
173
+
174
+ return {
175
+ name: manifest.name,
176
+ version: manifest.version,
177
+ description: manifest.description,
178
+ technologies: pluginData.technologies || [],
179
+ templates: pluginData.templates || [],
180
+ commands: pluginData.commands || [],
181
+ };
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ // ─── Get Plugin Info ──────────────────────────────────
188
+
189
+ export function getPluginInfo(name: string): PluginManifest | null {
190
+ const manifestPath = path.join(getPluginDir(), name, "stackweld.plugin.json");
191
+
192
+ if (!fs.existsSync(manifestPath)) return null;
193
+
194
+ try {
195
+ const raw = fs.readFileSync(manifestPath, "utf-8");
196
+ return JSON.parse(raw) as PluginManifest;
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+
202
+ // ─── Load All Plugin Technologies ─────────────────────
203
+
204
+ export function loadPluginTechnologies(): Technology[] {
205
+ const plugins = listPlugins();
206
+ const allTechs: Technology[] = [];
207
+
208
+ for (const manifest of plugins) {
209
+ const plugin = loadPlugin(manifest.name);
210
+ if (plugin?.technologies) {
211
+ allTechs.push(...plugin.technologies);
212
+ }
213
+ }
214
+
215
+ return allTechs;
216
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Preferences — Persistent user preferences stored in SQLite.
3
+ */
4
+
5
+ import { getDatabase } from "../db/database.js";
6
+
7
+ export interface UserPreferences {
8
+ editor: string;
9
+ packageManager: string;
10
+ shell: string;
11
+ dockerMode: string;
12
+ defaultProfile: string;
13
+ theme: string;
14
+ }
15
+
16
+ const DEFAULTS: UserPreferences = {
17
+ editor: "code",
18
+ packageManager: "pnpm",
19
+ shell: "zsh",
20
+ dockerMode: "compose",
21
+ defaultProfile: "standard",
22
+ theme: "dark",
23
+ };
24
+
25
+ function ensureTable(): void {
26
+ const db = getDatabase();
27
+ db.exec(`
28
+ CREATE TABLE IF NOT EXISTS preferences (
29
+ key TEXT PRIMARY KEY,
30
+ value TEXT NOT NULL
31
+ )
32
+ `);
33
+ }
34
+
35
+ /** Get all preferences, merged with defaults. */
36
+ export function getPreferences(): UserPreferences {
37
+ ensureTable();
38
+ const db = getDatabase();
39
+ const rows = db.prepare("SELECT key, value FROM preferences").all() as {
40
+ key: string;
41
+ value: string;
42
+ }[];
43
+
44
+ const prefs = { ...DEFAULTS };
45
+ for (const row of rows) {
46
+ if (row.key in prefs) {
47
+ (prefs as Record<string, string>)[row.key] = row.value;
48
+ }
49
+ }
50
+ return prefs;
51
+ }
52
+
53
+ /** Get a single preference. */
54
+ export function getPreference(key: keyof UserPreferences): string {
55
+ ensureTable();
56
+ const db = getDatabase();
57
+ const row = db.prepare("SELECT value FROM preferences WHERE key = ?").get(key) as
58
+ | { value: string }
59
+ | undefined;
60
+ return row?.value ?? DEFAULTS[key];
61
+ }
62
+
63
+ /** Set a single preference. */
64
+ export function setPreference(key: keyof UserPreferences, value: string): void {
65
+ ensureTable();
66
+ const db = getDatabase();
67
+ db.prepare("INSERT OR REPLACE INTO preferences (key, value) VALUES (?, ?)").run(key, value);
68
+ }
69
+
70
+ /** Reset all preferences to defaults. */
71
+ export function resetPreferences(): void {
72
+ ensureTable();
73
+ const db = getDatabase();
74
+ db.prepare("DELETE FROM preferences").run();
75
+ }
76
+
77
+ /** Get the list of valid preference keys. */
78
+ export function getPreferenceKeys(): (keyof UserPreferences)[] {
79
+ return Object.keys(DEFAULTS) as (keyof UserPreferences)[];
80
+ }
81
+
82
+ /** Get default values. */
83
+ export function getDefaultPreferences(): UserPreferences {
84
+ return { ...DEFAULTS };
85
+ }