@stackweld/core 0.2.1 → 0.3.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/package.json +16 -10
  4. package/.turbo/turbo-build.log +0 -4
  5. package/.turbo/turbo-lint.log +0 -498
  6. package/.turbo/turbo-test.log +0 -21
  7. package/.turbo/turbo-typecheck.log +0 -4
  8. package/dist/__tests__/compatibility-scorer.test.d.ts.map +0 -1
  9. package/dist/__tests__/compatibility-scorer.test.js.map +0 -1
  10. package/dist/__tests__/rules-engine.test.d.ts.map +0 -1
  11. package/dist/__tests__/rules-engine.test.js.map +0 -1
  12. package/dist/__tests__/scaffold-orchestrator.test.d.ts.map +0 -1
  13. package/dist/__tests__/scaffold-orchestrator.test.js.map +0 -1
  14. package/dist/__tests__/stack-engine.test.d.ts.map +0 -1
  15. package/dist/__tests__/stack-engine.test.js.map +0 -1
  16. package/dist/db/database.d.ts.map +0 -1
  17. package/dist/db/database.js.map +0 -1
  18. package/dist/db/index.d.ts.map +0 -1
  19. package/dist/db/index.js.map +0 -1
  20. package/dist/engine/compatibility-scorer.d.ts.map +0 -1
  21. package/dist/engine/compatibility-scorer.js.map +0 -1
  22. package/dist/engine/compose-generator.d.ts.map +0 -1
  23. package/dist/engine/compose-generator.js.map +0 -1
  24. package/dist/engine/cost-estimator.d.ts.map +0 -1
  25. package/dist/engine/cost-estimator.js.map +0 -1
  26. package/dist/engine/env-analyzer.d.ts.map +0 -1
  27. package/dist/engine/env-analyzer.js.map +0 -1
  28. package/dist/engine/health-checker.d.ts.map +0 -1
  29. package/dist/engine/health-checker.js.map +0 -1
  30. package/dist/engine/index.d.ts.map +0 -1
  31. package/dist/engine/index.js.map +0 -1
  32. package/dist/engine/infra-generator.d.ts.map +0 -1
  33. package/dist/engine/infra-generator.js.map +0 -1
  34. package/dist/engine/migration-planner.d.ts.map +0 -1
  35. package/dist/engine/migration-planner.js.map +0 -1
  36. package/dist/engine/performance-profiler.d.ts.map +0 -1
  37. package/dist/engine/performance-profiler.js.map +0 -1
  38. package/dist/engine/plugin-loader.d.ts.map +0 -1
  39. package/dist/engine/plugin-loader.js.map +0 -1
  40. package/dist/engine/preferences.d.ts.map +0 -1
  41. package/dist/engine/preferences.js.map +0 -1
  42. package/dist/engine/rules-engine.d.ts.map +0 -1
  43. package/dist/engine/rules-engine.js.map +0 -1
  44. package/dist/engine/runtime-manager.d.ts.map +0 -1
  45. package/dist/engine/runtime-manager.js.map +0 -1
  46. package/dist/engine/scaffold-orchestrator.d.ts.map +0 -1
  47. package/dist/engine/scaffold-orchestrator.js.map +0 -1
  48. package/dist/engine/stack-detector.d.ts.map +0 -1
  49. package/dist/engine/stack-detector.js.map +0 -1
  50. package/dist/engine/stack-differ.d.ts.map +0 -1
  51. package/dist/engine/stack-differ.js.map +0 -1
  52. package/dist/engine/stack-engine.d.ts.map +0 -1
  53. package/dist/engine/stack-engine.js.map +0 -1
  54. package/dist/engine/stack-serializer.d.ts.map +0 -1
  55. package/dist/engine/stack-serializer.js.map +0 -1
  56. package/dist/engine/standards-linter.d.ts.map +0 -1
  57. package/dist/engine/standards-linter.js.map +0 -1
  58. package/dist/engine/tech-installer.d.ts.map +0 -1
  59. package/dist/engine/tech-installer.js.map +0 -1
  60. package/dist/index.d.ts.map +0 -1
  61. package/dist/index.js.map +0 -1
  62. package/dist/types/index.d.ts.map +0 -1
  63. package/dist/types/index.js.map +0 -1
  64. package/dist/types/project.d.ts.map +0 -1
  65. package/dist/types/project.js.map +0 -1
  66. package/dist/types/stack.d.ts.map +0 -1
  67. package/dist/types/stack.js.map +0 -1
  68. package/dist/types/technology.d.ts.map +0 -1
  69. package/dist/types/technology.js.map +0 -1
  70. package/dist/types/template.d.ts.map +0 -1
  71. package/dist/types/template.js.map +0 -1
  72. package/dist/types/validation.d.ts.map +0 -1
  73. package/dist/types/validation.js.map +0 -1
  74. package/src/__tests__/compatibility-scorer.test.ts +0 -264
  75. package/src/__tests__/rules-engine.test.ts +0 -170
  76. package/src/__tests__/scaffold-orchestrator.test.ts +0 -161
  77. package/src/__tests__/stack-engine.test.ts +0 -328
  78. package/src/db/database.ts +0 -112
  79. package/src/db/index.ts +0 -1
  80. package/src/engine/compatibility-scorer.ts +0 -222
  81. package/src/engine/compose-generator.ts +0 -134
  82. package/src/engine/cost-estimator.ts +0 -498
  83. package/src/engine/env-analyzer.ts +0 -156
  84. package/src/engine/health-checker.ts +0 -421
  85. package/src/engine/index.ts +0 -17
  86. package/src/engine/infra-generator.ts +0 -837
  87. package/src/engine/migration-planner.ts +0 -496
  88. package/src/engine/performance-profiler.ts +0 -354
  89. package/src/engine/plugin-loader.ts +0 -216
  90. package/src/engine/preferences.ts +0 -85
  91. package/src/engine/rules-engine.ts +0 -204
  92. package/src/engine/runtime-manager.ts +0 -207
  93. package/src/engine/scaffold-orchestrator.ts +0 -1052
  94. package/src/engine/stack-detector.ts +0 -345
  95. package/src/engine/stack-differ.ts +0 -118
  96. package/src/engine/stack-engine.ts +0 -258
  97. package/src/engine/stack-serializer.ts +0 -95
  98. package/src/engine/standards-linter.ts +0 -210
  99. package/src/engine/tech-installer.ts +0 -650
  100. package/src/index.ts +0 -78
  101. package/src/types/index.ts +0 -10
  102. package/src/types/project.ts +0 -36
  103. package/src/types/stack.ts +0 -32
  104. package/src/types/technology.ts +0 -58
  105. package/src/types/template.ts +0 -37
  106. package/src/types/validation.ts +0 -22
  107. package/tsconfig.json +0 -10
  108. package/tsconfig.tsbuildinfo +0 -1
@@ -1,258 +0,0 @@
1
- /**
2
- * Stack Engine — CRUD operations for stack definitions.
3
- * Handles creation, validation, versioning, and persistence.
4
- */
5
-
6
- import { randomUUID } from "node:crypto";
7
- import { getDatabase } from "../db/database.js";
8
- import type {
9
- StackDefinition,
10
- StackProfile,
11
- StackTechnology,
12
- StackVersion,
13
- ValidationResult,
14
- } from "../types/index.js";
15
- import type { RulesEngine } from "./rules-engine.js";
16
-
17
- export class StackEngine {
18
- private rules: RulesEngine;
19
-
20
- constructor(rules: RulesEngine) {
21
- this.rules = rules;
22
- }
23
-
24
- /**
25
- * Create a new stack definition.
26
- * Validates technologies and auto-resolves dependencies.
27
- */
28
- create(opts: {
29
- name: string;
30
- description?: string;
31
- profile?: StackProfile;
32
- technologies: StackTechnology[];
33
- tags?: string[];
34
- }): { stack: StackDefinition; validation: ValidationResult } {
35
- const validation = this.rules.validate(opts.technologies);
36
-
37
- // Add auto-resolved dependencies
38
- const allTechs = [...opts.technologies];
39
- for (const depId of validation.resolvedDependencies) {
40
- const tech = this.rules.getTechnology(depId);
41
- if (tech) {
42
- allTechs.push({
43
- technologyId: depId,
44
- version: tech.defaultVersion,
45
- port: validation.portAssignments[depId],
46
- });
47
- }
48
- }
49
-
50
- // Apply port assignments to existing techs
51
- for (const t of allTechs) {
52
- if (validation.portAssignments[t.technologyId]) {
53
- t.port = validation.portAssignments[t.technologyId];
54
- }
55
- }
56
-
57
- const now = new Date().toISOString();
58
- const stack: StackDefinition = {
59
- id: randomUUID(),
60
- name: opts.name,
61
- description: opts.description || "",
62
- profile: opts.profile || "standard",
63
- technologies: allTechs,
64
- createdAt: now,
65
- updatedAt: now,
66
- version: 1,
67
- tags: opts.tags || [],
68
- };
69
-
70
- if (validation.valid) {
71
- this.persist(stack);
72
- }
73
-
74
- return { stack, validation };
75
- }
76
-
77
- /**
78
- * Get a stack by ID.
79
- */
80
- get(id: string): StackDefinition | null {
81
- const db = getDatabase();
82
- const row = db.prepare("SELECT * FROM stacks WHERE id = ?").get(id) as
83
- | Record<string, unknown>
84
- | undefined;
85
- if (!row) return null;
86
-
87
- const techRows = db
88
- .prepare("SELECT * FROM stack_technologies WHERE stack_id = ?")
89
- .all(id) as Record<string, unknown>[];
90
-
91
- return {
92
- id: row.id as string,
93
- name: row.name as string,
94
- description: row.description as string,
95
- profile: row.profile as StackProfile,
96
- version: row.version as number,
97
- createdAt: row.created_at as string,
98
- updatedAt: row.updated_at as string,
99
- tags: JSON.parse(row.tags as string),
100
- technologies: techRows.map((t) => ({
101
- technologyId: t.technology_id as string,
102
- version: t.version as string,
103
- port: t.port as number | undefined,
104
- config: JSON.parse((t.config as string) || "{}"),
105
- })),
106
- };
107
- }
108
-
109
- /**
110
- * List all stacks.
111
- */
112
- list(): StackDefinition[] {
113
- const db = getDatabase();
114
- const rows = db.prepare("SELECT id FROM stacks ORDER BY updated_at DESC").all() as {
115
- id: string;
116
- }[];
117
- return rows.map((r) => this.get(r.id)!).filter(Boolean);
118
- }
119
-
120
- /**
121
- * Delete a stack.
122
- */
123
- delete(id: string): boolean {
124
- const db = getDatabase();
125
- const result = db.prepare("DELETE FROM stacks WHERE id = ?").run(id);
126
- return result.changes > 0;
127
- }
128
-
129
- /**
130
- * Update a stack. Auto-increments version and saves snapshot.
131
- */
132
- update(
133
- id: string,
134
- changes: Partial<
135
- Pick<StackDefinition, "name" | "description" | "profile" | "technologies" | "tags">
136
- >,
137
- ): {
138
- stack: StackDefinition;
139
- validation: ValidationResult;
140
- } | null {
141
- const existing = this.get(id);
142
- if (!existing) return null;
143
-
144
- const techs = changes.technologies || existing.technologies;
145
- const validation = this.rules.validate(techs);
146
-
147
- const now = new Date().toISOString();
148
- const newVersion = existing.version + 1;
149
-
150
- const db = getDatabase();
151
- db.prepare(`
152
- UPDATE stacks SET
153
- name = ?, description = ?, profile = ?, version = ?,
154
- updated_at = ?, tags = ?
155
- WHERE id = ?
156
- `).run(
157
- changes.name || existing.name,
158
- changes.description ?? existing.description,
159
- changes.profile || existing.profile,
160
- newVersion,
161
- now,
162
- JSON.stringify(changes.tags || existing.tags),
163
- id,
164
- );
165
-
166
- // Replace technologies
167
- db.prepare("DELETE FROM stack_technologies WHERE stack_id = ?").run(id);
168
- for (const t of techs) {
169
- db.prepare(`
170
- INSERT INTO stack_technologies (stack_id, technology_id, version, port, config)
171
- VALUES (?, ?, ?, ?, ?)
172
- `).run(id, t.technologyId, t.version, t.port || null, JSON.stringify(t.config || {}));
173
- }
174
-
175
- // Save version snapshot
176
- const updatedStack = this.get(id)!;
177
- this.saveVersionSnapshot(updatedStack, `Updated to v${newVersion}`);
178
-
179
- return { stack: updatedStack, validation };
180
- }
181
-
182
- /**
183
- * Get version history for a stack.
184
- */
185
- getVersionHistory(stackId: string): StackVersion[] {
186
- const db = getDatabase();
187
- const rows = db
188
- .prepare("SELECT * FROM stack_versions WHERE stack_id = ? ORDER BY version DESC")
189
- .all(stackId) as Record<string, unknown>[];
190
-
191
- return rows.map((r) => ({
192
- version: r.version as number,
193
- timestamp: r.created_at as string,
194
- changelog: r.changelog as string,
195
- snapshot: JSON.parse(r.snapshot as string),
196
- }));
197
- }
198
-
199
- /**
200
- * Rollback to a specific version.
201
- */
202
- rollback(stackId: string, targetVersion: number): StackDefinition | null {
203
- const db = getDatabase();
204
- const versionRow = db
205
- .prepare("SELECT snapshot FROM stack_versions WHERE stack_id = ? AND version = ?")
206
- .get(stackId, targetVersion) as { snapshot: string } | undefined;
207
-
208
- if (!versionRow) return null;
209
-
210
- const snapshot = JSON.parse(versionRow.snapshot) as StackDefinition;
211
- this.update(stackId, {
212
- name: snapshot.name,
213
- description: snapshot.description,
214
- profile: snapshot.profile,
215
- technologies: snapshot.technologies,
216
- tags: snapshot.tags,
217
- });
218
-
219
- return this.get(stackId);
220
- }
221
-
222
- // ─── Private ────────────────────────────────────────────
223
-
224
- private persist(stack: StackDefinition): void {
225
- const db = getDatabase();
226
-
227
- db.prepare(`
228
- INSERT INTO stacks (id, name, description, profile, version, created_at, updated_at, tags)
229
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
230
- `).run(
231
- stack.id,
232
- stack.name,
233
- stack.description,
234
- stack.profile,
235
- stack.version,
236
- stack.createdAt,
237
- stack.updatedAt,
238
- JSON.stringify(stack.tags),
239
- );
240
-
241
- for (const t of stack.technologies) {
242
- db.prepare(`
243
- INSERT INTO stack_technologies (stack_id, technology_id, version, port, config)
244
- VALUES (?, ?, ?, ?, ?)
245
- `).run(stack.id, t.technologyId, t.version, t.port || null, JSON.stringify(t.config || {}));
246
- }
247
-
248
- this.saveVersionSnapshot(stack, "Initial creation");
249
- }
250
-
251
- private saveVersionSnapshot(stack: StackDefinition, changelog: string): void {
252
- const db = getDatabase();
253
- db.prepare(`
254
- INSERT OR REPLACE INTO stack_versions (stack_id, version, changelog, snapshot)
255
- VALUES (?, ?, ?, ?)
256
- `).run(stack.id, stack.version, changelog, JSON.stringify(stack));
257
- }
258
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * Stack Serializer — Compress/decompress stack definitions into URL-safe strings.
3
- * Uses zlib deflate + base64url encoding. No server needed.
4
- */
5
-
6
- import { deflateSync, inflateSync } from "node:zlib";
7
-
8
- export interface ShareableStack {
9
- name: string;
10
- profile: string;
11
- technologies: Array<{ id: string; version?: string; port?: number }>;
12
- }
13
-
14
- const DEFAULT_BASE_URL = "https://stackweld.dev/s/#";
15
-
16
- /**
17
- * Compress a stack into a URL-safe base64 string.
18
- * Flow: JSON → deflate → base64url
19
- */
20
- export function serializeStack(stack: ShareableStack): string {
21
- const json = JSON.stringify({
22
- n: stack.name,
23
- p: stack.profile,
24
- t: stack.technologies.map((t) => {
25
- const entry: Record<string, unknown> = { i: t.id };
26
- if (t.version) entry.v = t.version;
27
- if (t.port) entry.p = t.port;
28
- return entry;
29
- }),
30
- });
31
-
32
- const compressed = deflateSync(Buffer.from(json, "utf-8"));
33
- return toBase64Url(compressed);
34
- }
35
-
36
- /**
37
- * Decompress from a base64url string back to a stack definition.
38
- * Flow: base64url → inflate → JSON
39
- */
40
- export function deserializeStack(encoded: string): ShareableStack {
41
- const buffer = fromBase64Url(encoded);
42
- const json = inflateSync(buffer).toString("utf-8");
43
-
44
- const data = JSON.parse(json) as {
45
- n: string;
46
- p: string;
47
- t: Array<{ i: string; v?: string; p?: number }>;
48
- };
49
-
50
- return {
51
- name: data.n,
52
- profile: data.p,
53
- technologies: data.t.map((t) => ({
54
- id: t.i,
55
- version: t.v,
56
- port: t.p,
57
- })),
58
- };
59
- }
60
-
61
- /**
62
- * Generate a full shareable URL for a stack.
63
- */
64
- export function generateShareUrl(
65
- stack: ShareableStack,
66
- baseUrl: string = DEFAULT_BASE_URL,
67
- ): string {
68
- return `${baseUrl}${serializeStack(stack)}`;
69
- }
70
-
71
- /**
72
- * Extract the encoded payload from a share URL.
73
- */
74
- export function extractFromShareUrl(url: string): string {
75
- const hashIndex = url.indexOf("#");
76
- if (hashIndex === -1) {
77
- throw new Error("Invalid share URL: no hash fragment found.");
78
- }
79
- return url.substring(hashIndex + 1);
80
- }
81
-
82
- // ─── Base64url helpers ────────────────────────────────
83
-
84
- function toBase64Url(buffer: Buffer): string {
85
- return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
86
- }
87
-
88
- function fromBase64Url(str: string): Buffer {
89
- let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
90
- // Add padding
91
- const pad = base64.length % 4;
92
- if (pad === 2) base64 += "==";
93
- else if (pad === 3) base64 += "=";
94
- return Buffer.from(base64, "base64");
95
- }
@@ -1,210 +0,0 @@
1
- /**
2
- * Stack Standards Linter — Validates stacks against team-defined standards.
3
- * Reads .stackweldrc config and checks compliance.
4
- */
5
-
6
- import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import type { StackDefinition } from "../types/index.js";
9
-
10
- // ─── Types ────────────────────────────────────────────
11
-
12
- export interface StackStandards {
13
- team?: string;
14
- allowedTechnologies?: string[];
15
- blockedTechnologies?: string[];
16
- requiredTechnologies?: string[];
17
- minProfile?: string;
18
- requireDocker?: boolean;
19
- requireTests?: boolean;
20
- requireTypeScript?: boolean;
21
- customRules?: Array<{ name: string; check: string; message: string }>;
22
- }
23
-
24
- export interface LintResult {
25
- passed: boolean;
26
- violations: LintViolation[];
27
- warnings: LintWarning[];
28
- }
29
-
30
- export interface LintViolation {
31
- rule: string;
32
- message: string;
33
- severity: "error" | "warning";
34
- }
35
-
36
- export type LintWarning = LintViolation;
37
-
38
- // ─── Profile Ordering ─────────────────────────────────
39
-
40
- const PROFILE_ORDER: Record<string, number> = {
41
- lightweight: 0,
42
- rapid: 1,
43
- standard: 2,
44
- production: 3,
45
- enterprise: 4,
46
- };
47
-
48
- function profileLevel(profile: string): number {
49
- return PROFILE_ORDER[profile] ?? -1;
50
- }
51
-
52
- // ─── Testing Frameworks ───────────────────────────────
53
-
54
- const TESTING_FRAMEWORKS = new Set([
55
- "vitest",
56
- "jest",
57
- "playwright",
58
- "cypress",
59
- "mocha",
60
- "ava",
61
- "tap",
62
- "pytest",
63
- "unittest",
64
- "go-test",
65
- "cargo-test",
66
- ]);
67
-
68
- // ─── Lint Function ────────────────────────────────────
69
-
70
- export function lintStack(stack: StackDefinition, standards: StackStandards): LintResult {
71
- const violations: LintViolation[] = [];
72
- const warnings: LintWarning[] = [];
73
-
74
- const techIds = new Set(stack.technologies.map((t) => t.technologyId));
75
-
76
- // Allowed technologies check
77
- if (standards.allowedTechnologies && standards.allowedTechnologies.length > 0) {
78
- const allowed = new Set(standards.allowedTechnologies);
79
- for (const id of techIds) {
80
- if (!allowed.has(id)) {
81
- violations.push({
82
- rule: "allowedTechnologies",
83
- message: `Technology "${id}" is not in the allowed list`,
84
- severity: "error",
85
- });
86
- }
87
- }
88
- }
89
-
90
- // Blocked technologies check
91
- if (standards.blockedTechnologies && standards.blockedTechnologies.length > 0) {
92
- for (const blocked of standards.blockedTechnologies) {
93
- if (techIds.has(blocked)) {
94
- violations.push({
95
- rule: "blockedTechnologies",
96
- message: `Blocked technology "${blocked}" found — remove it`,
97
- severity: "error",
98
- });
99
- }
100
- }
101
- }
102
-
103
- // Required technologies check
104
- if (standards.requiredTechnologies && standards.requiredTechnologies.length > 0) {
105
- for (const required of standards.requiredTechnologies) {
106
- if (!techIds.has(required)) {
107
- violations.push({
108
- rule: "requiredTechnologies",
109
- message: `Required technology "${required}" is missing`,
110
- severity: "error",
111
- });
112
- }
113
- }
114
- }
115
-
116
- // Minimum profile check
117
- if (standards.minProfile) {
118
- const minLevel = profileLevel(standards.minProfile);
119
- const currentLevel = profileLevel(stack.profile);
120
- if (minLevel >= 0 && currentLevel < minLevel) {
121
- warnings.push({
122
- rule: "minProfile",
123
- message: `Profile "${stack.profile}" is below minimum "${standards.minProfile}"`,
124
- severity: "warning",
125
- });
126
- }
127
- }
128
-
129
- // Require Docker
130
- if (standards.requireDocker) {
131
- if (!techIds.has("docker") && !techIds.has("docker-compose")) {
132
- violations.push({
133
- rule: "requireDocker",
134
- message: "Docker is required but not present in the stack",
135
- severity: "error",
136
- });
137
- }
138
- }
139
-
140
- // Require tests
141
- if (standards.requireTests) {
142
- const hasTestFramework = Array.from(techIds).some((id) => TESTING_FRAMEWORKS.has(id));
143
- if (!hasTestFramework) {
144
- warnings.push({
145
- rule: "requireTests",
146
- message: "No testing framework found (vitest, jest, playwright, cypress, etc.)",
147
- severity: "warning",
148
- });
149
- }
150
- }
151
-
152
- // Require TypeScript
153
- if (standards.requireTypeScript) {
154
- if (!techIds.has("typescript")) {
155
- violations.push({
156
- rule: "requireTypeScript",
157
- message: "TypeScript is required but not present in the stack",
158
- severity: "error",
159
- });
160
- }
161
- }
162
-
163
- // Custom rules (simple presence/absence checks)
164
- if (standards.customRules) {
165
- for (const rule of standards.customRules) {
166
- if (rule.check === "present") {
167
- if (!techIds.has(rule.name)) {
168
- violations.push({
169
- rule: `custom:${rule.name}`,
170
- message: rule.message,
171
- severity: "error",
172
- });
173
- }
174
- } else if (rule.check === "absent") {
175
- if (techIds.has(rule.name)) {
176
- violations.push({
177
- rule: `custom:${rule.name}`,
178
- message: rule.message,
179
- severity: "error",
180
- });
181
- }
182
- }
183
- }
184
- }
185
-
186
- const hasErrors = violations.some((v) => v.severity === "error");
187
-
188
- return {
189
- passed: !hasErrors,
190
- violations,
191
- warnings,
192
- };
193
- }
194
-
195
- // ─── Config Loader ────────────────────────────────────
196
-
197
- export function loadStandards(projectPath: string): StackStandards | null {
198
- const configPath = path.resolve(projectPath, ".stackweldrc");
199
- if (!fs.existsSync(configPath)) {
200
- return null;
201
- }
202
-
203
- try {
204
- const content = fs.readFileSync(configPath, "utf-8");
205
- const parsed = JSON.parse(content) as StackStandards;
206
- return parsed;
207
- } catch {
208
- return null;
209
- }
210
- }