@solid-stack/mason 1.0.26 → 1.0.27
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/bin.cjs +926 -321
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +923 -318
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/env/audit.d.ts +18 -0
- package/dist/commands/env/audit.d.ts.map +1 -0
- package/dist/commands/env/index.d.ts +8 -0
- package/dist/commands/env/index.d.ts.map +1 -0
- package/dist/commands/env/init.d.ts +4 -0
- package/dist/commands/env/init.d.ts.map +1 -0
- package/dist/commands/env/initExamples.d.ts +4 -0
- package/dist/commands/env/initExamples.d.ts.map +1 -0
- package/dist/commands/env/mutate.d.ts +13 -0
- package/dist/commands/env/mutate.d.ts.map +1 -0
- package/dist/commands/env.d.ts +1 -2
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/envExamples.d.ts +1 -2
- package/dist/commands/envExamples.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/index.cjs +862 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +850 -243
- package/dist/index.js.map +1 -1
- package/dist/utils/envManager.d.ts +23 -0
- package/dist/utils/envManager.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cac } from 'cac';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import * as p19 from '@clack/prompts';
|
|
4
|
+
import pc13 from 'picocolors';
|
|
5
|
+
import fs14 from 'fs';
|
|
6
6
|
import path2 from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import Handlebars from 'handlebars';
|
|
@@ -12,7 +12,7 @@ import { createHash } from 'crypto';
|
|
|
12
12
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
13
13
|
const projectRoot = options.projectRoot || process.cwd();
|
|
14
14
|
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
15
|
-
const existedBefore =
|
|
15
|
+
const existedBefore = fs14.existsSync(absolutePath);
|
|
16
16
|
if (options.dryRun) {
|
|
17
17
|
return {
|
|
18
18
|
relativePath: relPath,
|
|
@@ -32,10 +32,10 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
const dir = path2.dirname(absolutePath);
|
|
35
|
-
if (!
|
|
36
|
-
|
|
35
|
+
if (!fs14.existsSync(dir)) {
|
|
36
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
fs14.writeFileSync(absolutePath, content, "utf-8");
|
|
39
39
|
return {
|
|
40
40
|
relativePath: relPath,
|
|
41
41
|
absolutePath,
|
|
@@ -47,17 +47,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
47
47
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
48
48
|
console.log("");
|
|
49
49
|
if (isDryRun) {
|
|
50
|
-
|
|
50
|
+
p19.log.warn(pc13.yellow(pc13.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
51
51
|
}
|
|
52
52
|
for (const f of files) {
|
|
53
53
|
if (f.action === "created") {
|
|
54
|
-
|
|
54
|
+
p19.log.success(`${pc13.bold(f.relativePath)} ${pc13.dim("(created)")}`);
|
|
55
55
|
} else if (f.action === "overwritten") {
|
|
56
|
-
|
|
56
|
+
p19.log.warn(`${pc13.bold(f.relativePath)} ${pc13.dim("(overwritten)")}`);
|
|
57
57
|
} else if (f.action === "skipped") {
|
|
58
|
-
|
|
58
|
+
p19.log.info(`${pc13.dim(f.relativePath)} ${pc13.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
59
59
|
} else {
|
|
60
|
-
|
|
60
|
+
p19.log.info(`${pc13.bold(f.relativePath)} ${pc13.dim("(preview)")}`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -129,9 +129,9 @@ function loadMasonConfig(startDir = process.cwd()) {
|
|
|
129
129
|
let current = path2.resolve(startDir);
|
|
130
130
|
while (true) {
|
|
131
131
|
const configPath = path2.join(current, "mason.config.json");
|
|
132
|
-
if (
|
|
132
|
+
if (fs14.existsSync(configPath)) {
|
|
133
133
|
try {
|
|
134
|
-
return JSON.parse(
|
|
134
|
+
return JSON.parse(fs14.readFileSync(configPath, "utf-8"));
|
|
135
135
|
} catch {
|
|
136
136
|
return null;
|
|
137
137
|
}
|
|
@@ -146,7 +146,7 @@ function getEffectiveMode(projectRoot, flags) {
|
|
|
146
146
|
if (flags?.registry) return "registry";
|
|
147
147
|
const config = loadMasonConfig(projectRoot);
|
|
148
148
|
if (config?.mode) return config.mode;
|
|
149
|
-
if (
|
|
149
|
+
if (fs14.existsSync(path2.join(projectRoot, "index.json")) && (fs14.existsSync(path2.join(projectRoot, "features")) || fs14.existsSync(path2.join(projectRoot, "shared")) || fs14.existsSync(path2.join(projectRoot, "src/features")) || fs14.existsSync(path2.join(projectRoot, "src/shared")))) {
|
|
150
150
|
return "registry";
|
|
151
151
|
}
|
|
152
152
|
return "app";
|
|
@@ -160,12 +160,12 @@ function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
|
160
160
|
}
|
|
161
161
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
162
162
|
if (mode === "registry") {
|
|
163
|
-
if (
|
|
163
|
+
if (fs14.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
164
164
|
return path2.join(projectRoot, "src/features");
|
|
165
165
|
}
|
|
166
166
|
return path2.join(projectRoot, "features");
|
|
167
167
|
}
|
|
168
|
-
if (
|
|
168
|
+
if (fs14.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
169
169
|
return path2.join(projectRoot, "src/features");
|
|
170
170
|
}
|
|
171
171
|
return path2.join(projectRoot, "features");
|
|
@@ -179,12 +179,12 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
179
179
|
}
|
|
180
180
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
181
181
|
if (mode === "registry") {
|
|
182
|
-
if (
|
|
182
|
+
if (fs14.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
183
183
|
return path2.join(projectRoot, "src/shared");
|
|
184
184
|
}
|
|
185
185
|
return path2.join(projectRoot, "shared");
|
|
186
186
|
}
|
|
187
|
-
if (
|
|
187
|
+
if (fs14.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
188
188
|
return path2.join(projectRoot, "src/shared");
|
|
189
189
|
}
|
|
190
190
|
return path2.join(projectRoot, "shared");
|
|
@@ -194,7 +194,7 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
194
194
|
function findProjectRoot(startDir = process.cwd()) {
|
|
195
195
|
let current = path2.resolve(startDir);
|
|
196
196
|
while (true) {
|
|
197
|
-
if (
|
|
197
|
+
if (fs14.existsSync(path2.join(current, "package.json"))) {
|
|
198
198
|
return current;
|
|
199
199
|
}
|
|
200
200
|
const parent = path2.dirname(current);
|
|
@@ -212,9 +212,9 @@ function resolveSharedDir(projectRoot, flags) {
|
|
|
212
212
|
}
|
|
213
213
|
function listExistingFeatures(projectRoot) {
|
|
214
214
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
215
|
-
if (!
|
|
215
|
+
if (!fs14.existsSync(featuresDir)) return [];
|
|
216
216
|
try {
|
|
217
|
-
const entries =
|
|
217
|
+
const entries = fs14.readdirSync(featuresDir, { withFileTypes: true });
|
|
218
218
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
219
219
|
} catch {
|
|
220
220
|
return [];
|
|
@@ -225,9 +225,9 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
225
225
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
226
226
|
for (const feat of features) {
|
|
227
227
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
228
|
-
if (!
|
|
228
|
+
if (!fs14.existsSync(domainDir)) continue;
|
|
229
229
|
try {
|
|
230
|
-
const files =
|
|
230
|
+
const files = fs14.readdirSync(domainDir);
|
|
231
231
|
for (const file of files) {
|
|
232
232
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
233
233
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -240,7 +240,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
240
240
|
});
|
|
241
241
|
} else {
|
|
242
242
|
try {
|
|
243
|
-
const content =
|
|
243
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
244
244
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
245
245
|
results.push({
|
|
246
246
|
name,
|
|
@@ -263,9 +263,9 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
263
263
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
264
264
|
for (const feat of features) {
|
|
265
265
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
266
|
-
if (!
|
|
266
|
+
if (!fs14.existsSync(domainDir)) continue;
|
|
267
267
|
try {
|
|
268
|
-
const files =
|
|
268
|
+
const files = fs14.readdirSync(domainDir);
|
|
269
269
|
for (const file of files) {
|
|
270
270
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
271
271
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -288,9 +288,9 @@ function listUseCases(projectRoot, featureName) {
|
|
|
288
288
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
289
289
|
for (const feat of features) {
|
|
290
290
|
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
291
|
-
if (!
|
|
291
|
+
if (!fs14.existsSync(useCasesDir)) continue;
|
|
292
292
|
try {
|
|
293
|
-
const files =
|
|
293
|
+
const files = fs14.readdirSync(useCasesDir);
|
|
294
294
|
for (const file of files) {
|
|
295
295
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
296
296
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -322,29 +322,29 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
322
322
|
});
|
|
323
323
|
var cachedTemplatesDir = null;
|
|
324
324
|
function getTemplatesDir() {
|
|
325
|
-
if (cachedTemplatesDir &&
|
|
325
|
+
if (cachedTemplatesDir && fs14.existsSync(cachedTemplatesDir)) {
|
|
326
326
|
return cachedTemplatesDir;
|
|
327
327
|
}
|
|
328
328
|
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
329
329
|
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
330
|
-
if (
|
|
330
|
+
if (fs14.existsSync(cand1)) {
|
|
331
331
|
cachedTemplatesDir = cand1;
|
|
332
332
|
return cand1;
|
|
333
333
|
}
|
|
334
334
|
const cand2 = path2.resolve(currentDir, "../templates");
|
|
335
|
-
if (
|
|
335
|
+
if (fs14.existsSync(cand2)) {
|
|
336
336
|
cachedTemplatesDir = cand2;
|
|
337
337
|
return cand2;
|
|
338
338
|
}
|
|
339
339
|
const cand3 = path2.resolve(currentDir, "templates");
|
|
340
|
-
if (
|
|
340
|
+
if (fs14.existsSync(cand3)) {
|
|
341
341
|
cachedTemplatesDir = cand3;
|
|
342
342
|
return cand3;
|
|
343
343
|
}
|
|
344
344
|
let curr = currentDir;
|
|
345
345
|
while (true) {
|
|
346
346
|
const candTemplates = path2.join(curr, "templates");
|
|
347
|
-
if (
|
|
347
|
+
if (fs14.existsSync(candTemplates)) {
|
|
348
348
|
cachedTemplatesDir = candTemplates;
|
|
349
349
|
return candTemplates;
|
|
350
350
|
}
|
|
@@ -357,7 +357,7 @@ function getTemplatesDir() {
|
|
|
357
357
|
var templateCache = /* @__PURE__ */ new Map();
|
|
358
358
|
function loadTemplate(relativePath) {
|
|
359
359
|
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
360
|
-
return
|
|
360
|
+
return fs14.readFileSync(fullPath, "utf-8");
|
|
361
361
|
}
|
|
362
362
|
function renderTemplateFile(relativePath, context) {
|
|
363
363
|
let compiled = templateCache.get(relativePath);
|
|
@@ -469,7 +469,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
469
469
|
const idKey = `${entityKey}Id`;
|
|
470
470
|
const listKey = `${entityKey}s`;
|
|
471
471
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
472
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
472
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p22) => `${p22.name}: ${p22.type}`).join("; ") : "";
|
|
473
473
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
474
474
|
return {
|
|
475
475
|
name: m.name,
|
|
@@ -730,7 +730,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
730
730
|
};
|
|
731
731
|
}
|
|
732
732
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
733
|
-
if (customPath &&
|
|
733
|
+
if (customPath && fs14.existsSync(customPath)) {
|
|
734
734
|
return { filePath: customPath, ifaceName };
|
|
735
735
|
}
|
|
736
736
|
const candidateNames = [
|
|
@@ -741,21 +741,21 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
741
741
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
742
742
|
for (const feat of features) {
|
|
743
743
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
744
|
-
if (!
|
|
744
|
+
if (!fs14.existsSync(domainDir)) continue;
|
|
745
745
|
for (const cand of candidateNames) {
|
|
746
746
|
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
747
|
-
if (
|
|
747
|
+
if (fs14.existsSync(p1)) {
|
|
748
748
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
749
749
|
}
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
752
|
for (const cand of candidateNames) {
|
|
753
753
|
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
754
|
-
if (
|
|
754
|
+
if (fs14.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
755
755
|
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
756
|
-
if (
|
|
756
|
+
if (fs14.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
757
757
|
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
758
|
-
if (
|
|
758
|
+
if (fs14.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
759
759
|
}
|
|
760
760
|
return null;
|
|
761
761
|
}
|
|
@@ -778,7 +778,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
778
778
|
while (currentFile && !visited.has(currentFile)) {
|
|
779
779
|
visited.add(currentFile);
|
|
780
780
|
try {
|
|
781
|
-
const content =
|
|
781
|
+
const content = fs14.readFileSync(currentFile, "utf-8");
|
|
782
782
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
783
783
|
for (const m of parsed.methods) {
|
|
784
784
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1050,22 +1050,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1050
1050
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1051
1051
|
}));
|
|
1052
1052
|
const domainTypesToImport = new Set(domainImports);
|
|
1053
|
-
for (const
|
|
1054
|
-
const rawType =
|
|
1053
|
+
for (const p22 of [...inputProps, ...outputProps]) {
|
|
1054
|
+
const rawType = p22.type.trim();
|
|
1055
1055
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1056
1056
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1057
1057
|
domainTypesToImport.add(baseType);
|
|
1058
1058
|
}
|
|
1059
1059
|
}
|
|
1060
|
-
const formattedInputProps = inputProps.map((
|
|
1061
|
-
name:
|
|
1062
|
-
optional:
|
|
1063
|
-
type:
|
|
1060
|
+
const formattedInputProps = inputProps.map((p22) => ({
|
|
1061
|
+
name: p22.name,
|
|
1062
|
+
optional: p22.optional,
|
|
1063
|
+
type: p22.optional && !p22.type.includes("undefined") ? `${p22.type} | undefined` : p22.type
|
|
1064
1064
|
}));
|
|
1065
|
-
const formattedOutputProps = outputProps.map((
|
|
1066
|
-
name:
|
|
1067
|
-
optional:
|
|
1068
|
-
type:
|
|
1065
|
+
const formattedOutputProps = outputProps.map((p22) => ({
|
|
1066
|
+
name: p22.name,
|
|
1067
|
+
optional: p22.optional,
|
|
1068
|
+
type: p22.optional && !p22.type.includes("undefined") ? `${p22.type} | undefined` : p22.type
|
|
1069
1069
|
}));
|
|
1070
1070
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1071
1071
|
key: toCamelCase(dep),
|
|
@@ -1252,7 +1252,7 @@ function generateHttpHandler(options) {
|
|
|
1252
1252
|
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1253
1253
|
const handlersDir = path2.join(pxDir, "handlers");
|
|
1254
1254
|
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1255
|
-
if (!
|
|
1255
|
+
if (!fs14.existsSync(pxIndexPath)) {
|
|
1256
1256
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1257
1257
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1258
1258
|
routePath: featurePath
|
|
@@ -1289,8 +1289,8 @@ function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], opti
|
|
|
1289
1289
|
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1290
1290
|
const pascalFeature = toPascalCase(featureName);
|
|
1291
1291
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1292
|
-
if (
|
|
1293
|
-
let content =
|
|
1292
|
+
if (fs14.existsSync(providerPath) && !options.overwrite) {
|
|
1293
|
+
let content = fs14.readFileSync(providerPath, "utf-8");
|
|
1294
1294
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1295
1295
|
for (const b of bindings) {
|
|
1296
1296
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1736,23 +1736,23 @@ var PromptCancelledError = class extends Error {
|
|
|
1736
1736
|
}
|
|
1737
1737
|
};
|
|
1738
1738
|
function handleCancel(result) {
|
|
1739
|
-
if (
|
|
1740
|
-
|
|
1739
|
+
if (p19.isCancel(result)) {
|
|
1740
|
+
p19.cancel("Operation cancelled.");
|
|
1741
1741
|
throw new PromptCancelledError();
|
|
1742
1742
|
}
|
|
1743
1743
|
return result;
|
|
1744
1744
|
}
|
|
1745
1745
|
function printBanner() {
|
|
1746
1746
|
console.log("");
|
|
1747
|
-
|
|
1747
|
+
p19.intro(pc13.bgCyan(pc13.black(" @solid-stack/mason ")));
|
|
1748
1748
|
}
|
|
1749
1749
|
async function promptProperties(options = {}) {
|
|
1750
1750
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
1751
1751
|
if (properties.length > 0) {
|
|
1752
|
-
const summary = properties.map((
|
|
1753
|
-
|
|
1752
|
+
const summary = properties.map((p22) => `${p22.name}: ${p22.type}${p22.optional ? "?" : ""}`).join(", ");
|
|
1753
|
+
p19.log.info(pc13.dim(`Pre-configured fields: ${summary}`));
|
|
1754
1754
|
const addMore = handleCancel(
|
|
1755
|
-
await
|
|
1755
|
+
await p19.confirm({
|
|
1756
1756
|
message: "Would you like to add custom fields/properties?",
|
|
1757
1757
|
initialValue: false
|
|
1758
1758
|
})
|
|
@@ -1761,10 +1761,10 @@ async function promptProperties(options = {}) {
|
|
|
1761
1761
|
return properties;
|
|
1762
1762
|
}
|
|
1763
1763
|
}
|
|
1764
|
-
|
|
1764
|
+
p19.log.info(pc13.cyan("Enter properties (leave name blank when finished):"));
|
|
1765
1765
|
while (true) {
|
|
1766
1766
|
const name = handleCancel(
|
|
1767
|
-
await
|
|
1767
|
+
await p19.text({
|
|
1768
1768
|
message: "Property name (or press Enter to finish):",
|
|
1769
1769
|
placeholder: "e.g. title, email, status"
|
|
1770
1770
|
})
|
|
@@ -1773,7 +1773,7 @@ async function promptProperties(options = {}) {
|
|
|
1773
1773
|
break;
|
|
1774
1774
|
}
|
|
1775
1775
|
const type = handleCancel(
|
|
1776
|
-
await
|
|
1776
|
+
await p19.select({
|
|
1777
1777
|
message: `Type for property "${name.trim()}":`,
|
|
1778
1778
|
options: [
|
|
1779
1779
|
{ value: "string", label: "string" },
|
|
@@ -1790,7 +1790,7 @@ async function promptProperties(options = {}) {
|
|
|
1790
1790
|
let finalType = type;
|
|
1791
1791
|
if (type === "custom") {
|
|
1792
1792
|
finalType = handleCancel(
|
|
1793
|
-
await
|
|
1793
|
+
await p19.text({
|
|
1794
1794
|
message: `Enter custom type for "${name.trim()}":`,
|
|
1795
1795
|
placeholder: "e.g. UserRole, string | null",
|
|
1796
1796
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -1798,7 +1798,7 @@ async function promptProperties(options = {}) {
|
|
|
1798
1798
|
);
|
|
1799
1799
|
}
|
|
1800
1800
|
const optional = handleCancel(
|
|
1801
|
-
await
|
|
1801
|
+
await p19.confirm({
|
|
1802
1802
|
message: `Is "${name.trim()}" optional?`,
|
|
1803
1803
|
initialValue: false
|
|
1804
1804
|
})
|
|
@@ -1808,8 +1808,8 @@ async function promptProperties(options = {}) {
|
|
|
1808
1808
|
type: finalType.trim(),
|
|
1809
1809
|
optional
|
|
1810
1810
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1811
|
+
p19.log.success(
|
|
1812
|
+
pc13.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1813
1813
|
);
|
|
1814
1814
|
}
|
|
1815
1815
|
return properties;
|
|
@@ -1825,7 +1825,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1825
1825
|
}
|
|
1826
1826
|
if (!name) {
|
|
1827
1827
|
name = handleCancel(
|
|
1828
|
-
await
|
|
1828
|
+
await p19.text({
|
|
1829
1829
|
message: "What is the feature name?",
|
|
1830
1830
|
placeholder: "e.g. users, auth, orders, billing",
|
|
1831
1831
|
validate: (val) => {
|
|
@@ -1841,7 +1841,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1841
1841
|
let targetDirRel = defaultDir;
|
|
1842
1842
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1843
1843
|
targetDirRel = handleCancel(
|
|
1844
|
-
await
|
|
1844
|
+
await p19.text({
|
|
1845
1845
|
message: "Target directory for the feature:",
|
|
1846
1846
|
initialValue: defaultDir,
|
|
1847
1847
|
defaultValue: defaultDir
|
|
@@ -1863,14 +1863,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1863
1863
|
const customUseCases = [];
|
|
1864
1864
|
if (!flags.yes) {
|
|
1865
1865
|
includeExpress = handleCancel(
|
|
1866
|
-
await
|
|
1866
|
+
await p19.confirm({
|
|
1867
1867
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
1868
1868
|
initialValue: true
|
|
1869
1869
|
})
|
|
1870
1870
|
);
|
|
1871
1871
|
if (includeExpress) {
|
|
1872
1872
|
routePath = handleCancel(
|
|
1873
|
-
await
|
|
1873
|
+
await p19.text({
|
|
1874
1874
|
message: "Express route prefix:",
|
|
1875
1875
|
initialValue: `/${normalizedName}`,
|
|
1876
1876
|
defaultValue: `/${normalizedName}`
|
|
@@ -1878,14 +1878,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1878
1878
|
).trim();
|
|
1879
1879
|
}
|
|
1880
1880
|
includeDomain = handleCancel(
|
|
1881
|
-
await
|
|
1881
|
+
await p19.confirm({
|
|
1882
1882
|
message: "Include Domain Entity/Entities?",
|
|
1883
1883
|
initialValue: true
|
|
1884
1884
|
})
|
|
1885
1885
|
);
|
|
1886
1886
|
if (includeDomain) {
|
|
1887
1887
|
const domainInput = handleCancel(
|
|
1888
|
-
await
|
|
1888
|
+
await p19.text({
|
|
1889
1889
|
message: "Domain entity name(s) (comma-separated):",
|
|
1890
1890
|
initialValue: defaultEntity,
|
|
1891
1891
|
defaultValue: defaultEntity,
|
|
@@ -1904,21 +1904,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1904
1904
|
entityNames = [defaultEntity];
|
|
1905
1905
|
}
|
|
1906
1906
|
includeInterface = handleCancel(
|
|
1907
|
-
await
|
|
1907
|
+
await p19.confirm({
|
|
1908
1908
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
1909
1909
|
initialValue: true
|
|
1910
1910
|
})
|
|
1911
1911
|
);
|
|
1912
1912
|
if (includeInterface) {
|
|
1913
1913
|
includeInfra = handleCancel(
|
|
1914
|
-
await
|
|
1914
|
+
await p19.confirm({
|
|
1915
1915
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
1916
1916
|
initialValue: true
|
|
1917
1917
|
})
|
|
1918
1918
|
);
|
|
1919
1919
|
}
|
|
1920
1920
|
includeUseCase = handleCancel(
|
|
1921
|
-
await
|
|
1921
|
+
await p19.confirm({
|
|
1922
1922
|
message: "Include Domain Use Cases?",
|
|
1923
1923
|
initialValue: true
|
|
1924
1924
|
})
|
|
@@ -1932,7 +1932,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1932
1932
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
1933
1933
|
];
|
|
1934
1934
|
crudOperations = handleCancel(
|
|
1935
|
-
await
|
|
1935
|
+
await p19.multiselect({
|
|
1936
1936
|
message: "Select standard CRUD use cases to generate:",
|
|
1937
1937
|
options: crudChoices,
|
|
1938
1938
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -1940,13 +1940,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1940
1940
|
})
|
|
1941
1941
|
);
|
|
1942
1942
|
includeTests = handleCancel(
|
|
1943
|
-
await
|
|
1943
|
+
await p19.confirm({
|
|
1944
1944
|
message: "Generate Vitest unit tests for use cases?",
|
|
1945
1945
|
initialValue: true
|
|
1946
1946
|
})
|
|
1947
1947
|
);
|
|
1948
1948
|
const wantCustom = handleCancel(
|
|
1949
|
-
await
|
|
1949
|
+
await p19.confirm({
|
|
1950
1950
|
message: "Do you want to add any custom use cases to this feature?",
|
|
1951
1951
|
initialValue: false
|
|
1952
1952
|
})
|
|
@@ -1956,7 +1956,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1956
1956
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
1957
1957
|
while (addMore) {
|
|
1958
1958
|
const customName = handleCancel(
|
|
1959
|
-
await
|
|
1959
|
+
await p19.text({
|
|
1960
1960
|
message: "Custom Use Case name:",
|
|
1961
1961
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
1962
1962
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -1966,7 +1966,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1966
1966
|
let selectedDeps = [];
|
|
1967
1967
|
if (availableIfaces.length > 0) {
|
|
1968
1968
|
selectedDeps = handleCancel(
|
|
1969
|
-
await
|
|
1969
|
+
await p19.multiselect({
|
|
1970
1970
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
1971
1971
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
1972
1972
|
required: false
|
|
@@ -1975,7 +1975,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1975
1975
|
}
|
|
1976
1976
|
let customInputProps = [];
|
|
1977
1977
|
const hasInput = handleCancel(
|
|
1978
|
-
await
|
|
1978
|
+
await p19.confirm({
|
|
1979
1979
|
message: `Does ${customUcName} accept input arguments?`,
|
|
1980
1980
|
initialValue: true
|
|
1981
1981
|
})
|
|
@@ -1986,7 +1986,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1986
1986
|
}
|
|
1987
1987
|
let customOutputProps = [];
|
|
1988
1988
|
const hasCustomOutput = handleCancel(
|
|
1989
|
-
await
|
|
1989
|
+
await p19.confirm({
|
|
1990
1990
|
message: `Does ${customUcName} return custom output data?`,
|
|
1991
1991
|
initialValue: false
|
|
1992
1992
|
})
|
|
@@ -1996,7 +1996,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1996
1996
|
});
|
|
1997
1997
|
}
|
|
1998
1998
|
const customIncludeTest = handleCancel(
|
|
1999
|
-
await
|
|
1999
|
+
await p19.confirm({
|
|
2000
2000
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2001
2001
|
initialValue: true
|
|
2002
2002
|
})
|
|
@@ -2006,14 +2006,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2006
2006
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2007
2007
|
if (includeExpress) {
|
|
2008
2008
|
customCreateHttp = handleCancel(
|
|
2009
|
-
await
|
|
2009
|
+
await p19.confirm({
|
|
2010
2010
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2011
2011
|
initialValue: true
|
|
2012
2012
|
})
|
|
2013
2013
|
);
|
|
2014
2014
|
if (customCreateHttp) {
|
|
2015
2015
|
customHttpMethod = handleCancel(
|
|
2016
|
-
await
|
|
2016
|
+
await p19.select({
|
|
2017
2017
|
message: "HTTP method for route:",
|
|
2018
2018
|
options: [
|
|
2019
2019
|
{ value: "post", label: "POST" },
|
|
@@ -2026,7 +2026,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2026
2026
|
})
|
|
2027
2027
|
);
|
|
2028
2028
|
customHttpPath = handleCancel(
|
|
2029
|
-
await
|
|
2029
|
+
await p19.text({
|
|
2030
2030
|
message: "Route subpath (relative to feature prefix):",
|
|
2031
2031
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2032
2032
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2047,7 +2047,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2047
2047
|
validationFields: customInputProps
|
|
2048
2048
|
});
|
|
2049
2049
|
addMore = handleCancel(
|
|
2050
|
-
await
|
|
2050
|
+
await p19.confirm({
|
|
2051
2051
|
message: "Add another custom use case?",
|
|
2052
2052
|
initialValue: false
|
|
2053
2053
|
})
|
|
@@ -2056,7 +2056,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2056
2056
|
}
|
|
2057
2057
|
}
|
|
2058
2058
|
includeDiProvider = handleCancel(
|
|
2059
|
-
await
|
|
2059
|
+
await p19.confirm({
|
|
2060
2060
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2061
2061
|
initialValue: true
|
|
2062
2062
|
})
|
|
@@ -2068,8 +2068,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2068
2068
|
entityNames = [defaultEntity];
|
|
2069
2069
|
}
|
|
2070
2070
|
}
|
|
2071
|
-
const s =
|
|
2072
|
-
s.start(`Generating feature '${
|
|
2071
|
+
const s = p19.spinner();
|
|
2072
|
+
s.start(`Generating feature '${pc13.cyan(normalizedName)}'...`);
|
|
2073
2073
|
const options = {
|
|
2074
2074
|
name: normalizedName,
|
|
2075
2075
|
projectRoot,
|
|
@@ -2091,7 +2091,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2091
2091
|
const files = generateFeature(options);
|
|
2092
2092
|
if (isRegistry) {
|
|
2093
2093
|
const registryPath = path2.join(targetDir, "registry.json");
|
|
2094
|
-
if (!
|
|
2094
|
+
if (!fs14.existsSync(registryPath)) {
|
|
2095
2095
|
const manifest = {
|
|
2096
2096
|
name: toKebabCase(normalizedName),
|
|
2097
2097
|
type: "feature",
|
|
@@ -2115,8 +2115,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2115
2115
|
}
|
|
2116
2116
|
s.stop("Feature scaffolded successfully!");
|
|
2117
2117
|
printGeneratedFiles(files, options.dryRun);
|
|
2118
|
-
|
|
2119
|
-
|
|
2118
|
+
p19.outro(
|
|
2119
|
+
pc13.green(
|
|
2120
2120
|
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2121
2121
|
)
|
|
2122
2122
|
);
|
|
@@ -2230,7 +2230,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2230
2230
|
}
|
|
2231
2231
|
if (!name) {
|
|
2232
2232
|
name = handleCancel(
|
|
2233
|
-
await
|
|
2233
|
+
await p19.text({
|
|
2234
2234
|
message: "What is the shared module name?",
|
|
2235
2235
|
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2236
2236
|
validate: (val) => {
|
|
@@ -2246,7 +2246,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2246
2246
|
let targetDirRel = defaultDir;
|
|
2247
2247
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2248
2248
|
targetDirRel = handleCancel(
|
|
2249
|
-
await
|
|
2249
|
+
await p19.text({
|
|
2250
2250
|
message: "Target directory for the shared module:",
|
|
2251
2251
|
initialValue: defaultDir,
|
|
2252
2252
|
defaultValue: defaultDir
|
|
@@ -2258,21 +2258,21 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2258
2258
|
let includeStub = true;
|
|
2259
2259
|
if (!flags.yes) {
|
|
2260
2260
|
description = handleCancel(
|
|
2261
|
-
await
|
|
2261
|
+
await p19.text({
|
|
2262
2262
|
message: "Module description (optional):",
|
|
2263
2263
|
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2264
2264
|
initialValue: ""
|
|
2265
2265
|
})
|
|
2266
2266
|
).trim();
|
|
2267
2267
|
includeStub = handleCancel(
|
|
2268
|
-
await
|
|
2268
|
+
await p19.confirm({
|
|
2269
2269
|
message: "Include stub implementation in infrastructure/?",
|
|
2270
2270
|
initialValue: true
|
|
2271
2271
|
})
|
|
2272
2272
|
);
|
|
2273
2273
|
}
|
|
2274
|
-
const s =
|
|
2275
|
-
s.start(`Generating shared module '${
|
|
2274
|
+
const s = p19.spinner();
|
|
2275
|
+
s.start(`Generating shared module '${pc13.cyan(normalizedName)}'...`);
|
|
2276
2276
|
const files = generateShared({
|
|
2277
2277
|
name: normalizedName,
|
|
2278
2278
|
description,
|
|
@@ -2285,8 +2285,8 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2285
2285
|
});
|
|
2286
2286
|
s.stop("Shared module generated successfully!");
|
|
2287
2287
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2288
|
-
|
|
2289
|
-
|
|
2288
|
+
p19.outro(
|
|
2289
|
+
pc13.green(
|
|
2290
2290
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2291
2291
|
)
|
|
2292
2292
|
);
|
|
@@ -2300,14 +2300,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2300
2300
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2301
2301
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2302
2302
|
const selected = handleCancel(
|
|
2303
|
-
await
|
|
2303
|
+
await p19.select({
|
|
2304
2304
|
message: "Select target feature:",
|
|
2305
2305
|
options: featureOptions
|
|
2306
2306
|
})
|
|
2307
2307
|
);
|
|
2308
2308
|
if (selected === "__custom__") {
|
|
2309
2309
|
feature = handleCancel(
|
|
2310
|
-
await
|
|
2310
|
+
await p19.text({
|
|
2311
2311
|
message: "Enter feature name:",
|
|
2312
2312
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2313
2313
|
})
|
|
@@ -2317,7 +2317,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2317
2317
|
}
|
|
2318
2318
|
} else {
|
|
2319
2319
|
feature = handleCancel(
|
|
2320
|
-
await
|
|
2320
|
+
await p19.text({
|
|
2321
2321
|
message: "Enter target feature name:",
|
|
2322
2322
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2323
2323
|
})
|
|
@@ -2328,7 +2328,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2328
2328
|
let name = useCaseNameArg?.trim();
|
|
2329
2329
|
if (!name) {
|
|
2330
2330
|
name = handleCancel(
|
|
2331
|
-
await
|
|
2331
|
+
await p19.text({
|
|
2332
2332
|
message: "Use Case name:",
|
|
2333
2333
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2334
2334
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2344,7 +2344,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2344
2344
|
label: i.name
|
|
2345
2345
|
}));
|
|
2346
2346
|
selectedDeps = handleCancel(
|
|
2347
|
-
await
|
|
2347
|
+
await p19.multiselect({
|
|
2348
2348
|
message: "Select domain interfaces to inject into use case:",
|
|
2349
2349
|
options: multiOptions,
|
|
2350
2350
|
required: false
|
|
@@ -2359,7 +2359,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2359
2359
|
let httpPath = "/";
|
|
2360
2360
|
if (!flags.yes) {
|
|
2361
2361
|
const hasInput = handleCancel(
|
|
2362
|
-
await
|
|
2362
|
+
await p19.confirm({
|
|
2363
2363
|
message: "Does this use case accept input arguments?",
|
|
2364
2364
|
initialValue: true
|
|
2365
2365
|
})
|
|
@@ -2369,7 +2369,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2369
2369
|
});
|
|
2370
2370
|
}
|
|
2371
2371
|
const hasCustomOutput = handleCancel(
|
|
2372
|
-
await
|
|
2372
|
+
await p19.confirm({
|
|
2373
2373
|
message: "Does this use case return custom output data?",
|
|
2374
2374
|
initialValue: false
|
|
2375
2375
|
})
|
|
@@ -2379,20 +2379,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2379
2379
|
});
|
|
2380
2380
|
}
|
|
2381
2381
|
includeTest = handleCancel(
|
|
2382
|
-
await
|
|
2382
|
+
await p19.confirm({
|
|
2383
2383
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2384
2384
|
initialValue: true
|
|
2385
2385
|
})
|
|
2386
2386
|
);
|
|
2387
2387
|
createHttp = handleCancel(
|
|
2388
|
-
await
|
|
2388
|
+
await p19.confirm({
|
|
2389
2389
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2390
2390
|
initialValue: false
|
|
2391
2391
|
})
|
|
2392
2392
|
);
|
|
2393
2393
|
if (createHttp) {
|
|
2394
2394
|
httpMethod = handleCancel(
|
|
2395
|
-
await
|
|
2395
|
+
await p19.select({
|
|
2396
2396
|
message: "HTTP method for route:",
|
|
2397
2397
|
options: [
|
|
2398
2398
|
{ value: "post", label: "POST" },
|
|
@@ -2405,7 +2405,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2405
2405
|
})
|
|
2406
2406
|
);
|
|
2407
2407
|
httpPath = handleCancel(
|
|
2408
|
-
await
|
|
2408
|
+
await p19.text({
|
|
2409
2409
|
message: "Route subpath (relative to feature prefix):",
|
|
2410
2410
|
initialValue: "/",
|
|
2411
2411
|
defaultValue: "/"
|
|
@@ -2413,8 +2413,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2413
2413
|
).trim();
|
|
2414
2414
|
}
|
|
2415
2415
|
}
|
|
2416
|
-
const s =
|
|
2417
|
-
s.start(`Generating use case '${
|
|
2416
|
+
const s = p19.spinner();
|
|
2417
|
+
s.start(`Generating use case '${pc13.cyan(ucName)}'...`);
|
|
2418
2418
|
const options = {
|
|
2419
2419
|
name: ucName,
|
|
2420
2420
|
feature,
|
|
@@ -2446,7 +2446,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2446
2446
|
}
|
|
2447
2447
|
s.stop("Use case generated successfully!");
|
|
2448
2448
|
printGeneratedFiles(files, options.dryRun);
|
|
2449
|
-
|
|
2449
|
+
p19.outro(pc13.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2450
2450
|
}
|
|
2451
2451
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2452
2452
|
const projectRoot = findProjectRoot();
|
|
@@ -2457,14 +2457,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2457
2457
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2458
2458
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2459
2459
|
const selected = handleCancel(
|
|
2460
|
-
await
|
|
2460
|
+
await p19.select({
|
|
2461
2461
|
message: "Select target feature:",
|
|
2462
2462
|
options: featureOptions
|
|
2463
2463
|
})
|
|
2464
2464
|
);
|
|
2465
2465
|
if (selected === "__custom__") {
|
|
2466
2466
|
feature = handleCancel(
|
|
2467
|
-
await
|
|
2467
|
+
await p19.text({
|
|
2468
2468
|
message: "Enter feature name:",
|
|
2469
2469
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2470
2470
|
})
|
|
@@ -2474,7 +2474,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2474
2474
|
}
|
|
2475
2475
|
} else {
|
|
2476
2476
|
feature = handleCancel(
|
|
2477
|
-
await
|
|
2477
|
+
await p19.text({
|
|
2478
2478
|
message: "Enter target feature name:",
|
|
2479
2479
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2480
2480
|
})
|
|
@@ -2485,7 +2485,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2485
2485
|
let name = domainNameArg?.trim();
|
|
2486
2486
|
if (!name) {
|
|
2487
2487
|
name = handleCancel(
|
|
2488
|
-
await
|
|
2488
|
+
await p19.text({
|
|
2489
2489
|
message: "Domain model / entity name:",
|
|
2490
2490
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2491
2491
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2497,7 +2497,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2497
2497
|
let properties = [];
|
|
2498
2498
|
if (!flags.yes) {
|
|
2499
2499
|
kind = handleCancel(
|
|
2500
|
-
await
|
|
2500
|
+
await p19.select({
|
|
2501
2501
|
message: "Select domain model type:",
|
|
2502
2502
|
options: [
|
|
2503
2503
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2516,8 +2516,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2516
2516
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2517
2517
|
});
|
|
2518
2518
|
}
|
|
2519
|
-
const s =
|
|
2520
|
-
s.start(`Generating domain model '${
|
|
2519
|
+
const s = p19.spinner();
|
|
2520
|
+
s.start(`Generating domain model '${pc13.cyan(entityName)}'...`);
|
|
2521
2521
|
const options = {
|
|
2522
2522
|
name: entityName,
|
|
2523
2523
|
feature,
|
|
@@ -2530,7 +2530,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2530
2530
|
const file = generateDomain(options);
|
|
2531
2531
|
s.stop("Domain model generated successfully!");
|
|
2532
2532
|
printGeneratedFiles([file], options.dryRun);
|
|
2533
|
-
|
|
2533
|
+
p19.outro(pc13.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2534
2534
|
}
|
|
2535
2535
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2536
2536
|
const projectRoot = findProjectRoot();
|
|
@@ -2539,7 +2539,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2539
2539
|
let feature = flags.feature?.trim();
|
|
2540
2540
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2541
2541
|
scope = handleCancel(
|
|
2542
|
-
await
|
|
2542
|
+
await p19.select({
|
|
2543
2543
|
message: "Where should this infrastructure implementation be placed?",
|
|
2544
2544
|
options: [
|
|
2545
2545
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2554,14 +2554,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2554
2554
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2555
2555
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2556
2556
|
const selected = handleCancel(
|
|
2557
|
-
await
|
|
2557
|
+
await p19.select({
|
|
2558
2558
|
message: "Select target feature:",
|
|
2559
2559
|
options: featureOptions
|
|
2560
2560
|
})
|
|
2561
2561
|
);
|
|
2562
2562
|
if (selected === "__custom__") {
|
|
2563
2563
|
feature = handleCancel(
|
|
2564
|
-
await
|
|
2564
|
+
await p19.text({
|
|
2565
2565
|
message: "Enter feature name:",
|
|
2566
2566
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2567
2567
|
})
|
|
@@ -2571,7 +2571,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2571
2571
|
}
|
|
2572
2572
|
} else {
|
|
2573
2573
|
feature = handleCancel(
|
|
2574
|
-
await
|
|
2574
|
+
await p19.text({
|
|
2575
2575
|
message: "Enter target feature name:",
|
|
2576
2576
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2577
2577
|
})
|
|
@@ -2584,7 +2584,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2584
2584
|
let name = classNameArg?.trim();
|
|
2585
2585
|
if (!name) {
|
|
2586
2586
|
name = handleCancel(
|
|
2587
|
-
await
|
|
2587
|
+
await p19.text({
|
|
2588
2588
|
message: "Infrastructure class name:",
|
|
2589
2589
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2590
2590
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2603,7 +2603,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2603
2603
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2604
2604
|
);
|
|
2605
2605
|
const ifaceChoice = handleCancel(
|
|
2606
|
-
await
|
|
2606
|
+
await p19.select({
|
|
2607
2607
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2608
2608
|
options: [
|
|
2609
2609
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2615,7 +2615,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2615
2615
|
);
|
|
2616
2616
|
if (ifaceChoice === "__custom__") {
|
|
2617
2617
|
implementsInterface = handleCancel(
|
|
2618
|
-
await
|
|
2618
|
+
await p19.text({
|
|
2619
2619
|
message: "Enter interface or abstract class name:",
|
|
2620
2620
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2621
2621
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2626,14 +2626,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2626
2626
|
}
|
|
2627
2627
|
} else {
|
|
2628
2628
|
const wantImpl = handleCancel(
|
|
2629
|
-
await
|
|
2629
|
+
await p19.confirm({
|
|
2630
2630
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2631
2631
|
initialValue: false
|
|
2632
2632
|
})
|
|
2633
2633
|
);
|
|
2634
2634
|
if (wantImpl) {
|
|
2635
2635
|
implementsInterface = handleCancel(
|
|
2636
|
-
await
|
|
2636
|
+
await p19.text({
|
|
2637
2637
|
message: "Enter interface or abstract class name:",
|
|
2638
2638
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2639
2639
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2653,7 +2653,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2653
2653
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2654
2654
|
if (allInterfaces.length > 0) {
|
|
2655
2655
|
const ifaceChoice = handleCancel(
|
|
2656
|
-
await
|
|
2656
|
+
await p19.select({
|
|
2657
2657
|
message: "Does this class implement an interface or abstract class?",
|
|
2658
2658
|
options: [
|
|
2659
2659
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2665,7 +2665,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2665
2665
|
);
|
|
2666
2666
|
if (ifaceChoice === "__custom__") {
|
|
2667
2667
|
implementsInterface = handleCancel(
|
|
2668
|
-
await
|
|
2668
|
+
await p19.text({
|
|
2669
2669
|
message: "Enter interface or abstract class name:",
|
|
2670
2670
|
placeholder: "e.g. IAuthRepository",
|
|
2671
2671
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2678,7 +2678,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2678
2678
|
}
|
|
2679
2679
|
if (!flags.yes && !flags.kind) {
|
|
2680
2680
|
kind = handleCancel(
|
|
2681
|
-
await
|
|
2681
|
+
await p19.select({
|
|
2682
2682
|
message: "Select implementation kind:",
|
|
2683
2683
|
options: [
|
|
2684
2684
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2689,7 +2689,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2689
2689
|
);
|
|
2690
2690
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2691
2691
|
registerInProvider = handleCancel(
|
|
2692
|
-
await
|
|
2692
|
+
await p19.confirm({
|
|
2693
2693
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2694
2694
|
initialValue: true
|
|
2695
2695
|
})
|
|
@@ -2698,8 +2698,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2698
2698
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2699
2699
|
registerInProvider = !flags.noProvider;
|
|
2700
2700
|
}
|
|
2701
|
-
const s =
|
|
2702
|
-
s.start(`Generating infrastructure class '${
|
|
2701
|
+
const s = p19.spinner();
|
|
2702
|
+
s.start(`Generating infrastructure class '${pc13.cyan(className)}'...`);
|
|
2703
2703
|
const files = [];
|
|
2704
2704
|
const options = {
|
|
2705
2705
|
name: className,
|
|
@@ -2734,7 +2734,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2734
2734
|
}
|
|
2735
2735
|
s.stop("Infrastructure class generated successfully!");
|
|
2736
2736
|
printGeneratedFiles(files, options.dryRun);
|
|
2737
|
-
|
|
2737
|
+
p19.outro(pc13.green(`Infrastructure class '${className}' created!`));
|
|
2738
2738
|
}
|
|
2739
2739
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2740
2740
|
const projectRoot = findProjectRoot();
|
|
@@ -2745,14 +2745,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2745
2745
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2746
2746
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2747
2747
|
const selected = handleCancel(
|
|
2748
|
-
await
|
|
2748
|
+
await p19.select({
|
|
2749
2749
|
message: "Select target feature:",
|
|
2750
2750
|
options: featureOptions
|
|
2751
2751
|
})
|
|
2752
2752
|
);
|
|
2753
2753
|
if (selected === "__custom__") {
|
|
2754
2754
|
feature = handleCancel(
|
|
2755
|
-
await
|
|
2755
|
+
await p19.text({
|
|
2756
2756
|
message: "Enter feature name:",
|
|
2757
2757
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2758
2758
|
})
|
|
@@ -2762,7 +2762,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2762
2762
|
}
|
|
2763
2763
|
} else {
|
|
2764
2764
|
feature = handleCancel(
|
|
2765
|
-
await
|
|
2765
|
+
await p19.text({
|
|
2766
2766
|
message: "Enter target feature name:",
|
|
2767
2767
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2768
2768
|
})
|
|
@@ -2773,7 +2773,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2773
2773
|
let name = interfaceNameArg?.trim();
|
|
2774
2774
|
if (!name) {
|
|
2775
2775
|
name = handleCancel(
|
|
2776
|
-
await
|
|
2776
|
+
await p19.text({
|
|
2777
2777
|
message: "Domain interface name:",
|
|
2778
2778
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2779
2779
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2791,7 +2791,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2791
2791
|
if (!flags.yes) {
|
|
2792
2792
|
if (existingEntities.length > 0) {
|
|
2793
2793
|
const selectedEntity = handleCancel(
|
|
2794
|
-
await
|
|
2794
|
+
await p19.select({
|
|
2795
2795
|
message: "Select domain entity associated with this interface:",
|
|
2796
2796
|
options: [
|
|
2797
2797
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2802,7 +2802,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2802
2802
|
);
|
|
2803
2803
|
if (selectedEntity === "__custom__") {
|
|
2804
2804
|
entityName = handleCancel(
|
|
2805
|
-
await
|
|
2805
|
+
await p19.text({
|
|
2806
2806
|
message: "Enter entity name:",
|
|
2807
2807
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2808
2808
|
})
|
|
@@ -2812,7 +2812,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2812
2812
|
}
|
|
2813
2813
|
}
|
|
2814
2814
|
kind = handleCancel(
|
|
2815
|
-
await
|
|
2815
|
+
await p19.select({
|
|
2816
2816
|
message: "Interface definition style:",
|
|
2817
2817
|
options: [
|
|
2818
2818
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2822,7 +2822,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2822
2822
|
})
|
|
2823
2823
|
);
|
|
2824
2824
|
methodsPreset = handleCancel(
|
|
2825
|
-
await
|
|
2825
|
+
await p19.select({
|
|
2826
2826
|
message: "Method signature preset:",
|
|
2827
2827
|
options: [
|
|
2828
2828
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2832,22 +2832,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2832
2832
|
})
|
|
2833
2833
|
);
|
|
2834
2834
|
generateStub = handleCancel(
|
|
2835
|
-
await
|
|
2835
|
+
await p19.confirm({
|
|
2836
2836
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2837
2837
|
initialValue: true
|
|
2838
2838
|
})
|
|
2839
2839
|
);
|
|
2840
2840
|
if (generateStub) {
|
|
2841
2841
|
registerInProvider = handleCancel(
|
|
2842
|
-
await
|
|
2842
|
+
await p19.confirm({
|
|
2843
2843
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2844
2844
|
initialValue: true
|
|
2845
2845
|
})
|
|
2846
2846
|
);
|
|
2847
2847
|
}
|
|
2848
2848
|
}
|
|
2849
|
-
const s =
|
|
2850
|
-
s.start(`Generating interface '${
|
|
2849
|
+
const s = p19.spinner();
|
|
2850
|
+
s.start(`Generating interface '${pc13.cyan(ifaceName)}'...`);
|
|
2851
2851
|
const files = [];
|
|
2852
2852
|
const options = {
|
|
2853
2853
|
name: ifaceName,
|
|
@@ -2896,7 +2896,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2896
2896
|
}
|
|
2897
2897
|
s.stop("Interface generated successfully!");
|
|
2898
2898
|
printGeneratedFiles(files, options.dryRun);
|
|
2899
|
-
|
|
2899
|
+
p19.outro(pc13.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2900
2900
|
}
|
|
2901
2901
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2902
2902
|
const projectRoot = findProjectRoot();
|
|
@@ -2907,14 +2907,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2907
2907
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2908
2908
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2909
2909
|
const selected = handleCancel(
|
|
2910
|
-
await
|
|
2910
|
+
await p19.select({
|
|
2911
2911
|
message: "Select target feature:",
|
|
2912
2912
|
options: featureOptions
|
|
2913
2913
|
})
|
|
2914
2914
|
);
|
|
2915
2915
|
if (selected === "__custom__") {
|
|
2916
2916
|
feature = handleCancel(
|
|
2917
|
-
await
|
|
2917
|
+
await p19.text({
|
|
2918
2918
|
message: "Enter feature name:",
|
|
2919
2919
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2920
2920
|
})
|
|
@@ -2924,7 +2924,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2924
2924
|
}
|
|
2925
2925
|
} else {
|
|
2926
2926
|
feature = handleCancel(
|
|
2927
|
-
await
|
|
2927
|
+
await p19.text({
|
|
2928
2928
|
message: "Enter target feature name:",
|
|
2929
2929
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2930
2930
|
})
|
|
@@ -2935,7 +2935,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2935
2935
|
let name = handlerNameArg?.trim();
|
|
2936
2936
|
if (!name) {
|
|
2937
2937
|
name = handleCancel(
|
|
2938
|
-
await
|
|
2938
|
+
await p19.text({
|
|
2939
2939
|
message: "HTTP Route Handler class name:",
|
|
2940
2940
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2941
2941
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2954,7 +2954,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2954
2954
|
let validationFields = [];
|
|
2955
2955
|
if (!flags.yes) {
|
|
2956
2956
|
method = handleCancel(
|
|
2957
|
-
await
|
|
2957
|
+
await p19.select({
|
|
2958
2958
|
message: "HTTP Method:",
|
|
2959
2959
|
options: [
|
|
2960
2960
|
{ value: "get", label: "GET" },
|
|
@@ -2968,7 +2968,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2968
2968
|
);
|
|
2969
2969
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2970
2970
|
subPath = handleCancel(
|
|
2971
|
-
await
|
|
2971
|
+
await p19.text({
|
|
2972
2972
|
message: "Route subpath (relative to feature prefix):",
|
|
2973
2973
|
initialValue: defaultPath,
|
|
2974
2974
|
defaultValue: defaultPath
|
|
@@ -2979,7 +2979,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2979
2979
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2980
2980
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2981
2981
|
const chosenUc = handleCancel(
|
|
2982
|
-
await
|
|
2982
|
+
await p19.select({
|
|
2983
2983
|
message: "Connect this handler to a domain Use Case?",
|
|
2984
2984
|
options: ucOptions,
|
|
2985
2985
|
initialValue: ucOptions[0]?.value
|
|
@@ -2990,7 +2990,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2990
2990
|
}
|
|
2991
2991
|
}
|
|
2992
2992
|
includeValidation = handleCancel(
|
|
2993
|
-
await
|
|
2993
|
+
await p19.confirm({
|
|
2994
2994
|
message: "Include Zod inbound request validation?",
|
|
2995
2995
|
initialValue: method !== "delete"
|
|
2996
2996
|
})
|
|
@@ -2998,7 +2998,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2998
2998
|
if (includeValidation) {
|
|
2999
2999
|
const defaultSource = method === "get" ? "query" : "body";
|
|
3000
3000
|
validationSource = handleCancel(
|
|
3001
|
-
await
|
|
3001
|
+
await p19.select({
|
|
3002
3002
|
message: "Validation data source:",
|
|
3003
3003
|
options: [
|
|
3004
3004
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -3012,8 +3012,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3012
3012
|
});
|
|
3013
3013
|
}
|
|
3014
3014
|
}
|
|
3015
|
-
const s =
|
|
3016
|
-
s.start(`Generating HTTP handler '${
|
|
3015
|
+
const s = p19.spinner();
|
|
3016
|
+
s.start(`Generating HTTP handler '${pc13.cyan(className)}'...`);
|
|
3017
3017
|
const options = {
|
|
3018
3018
|
name: className,
|
|
3019
3019
|
feature,
|
|
@@ -3031,7 +3031,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3031
3031
|
const files = generateHttpHandler(options);
|
|
3032
3032
|
s.stop("HTTP handler generated successfully!");
|
|
3033
3033
|
printGeneratedFiles(files, options.dryRun);
|
|
3034
|
-
|
|
3034
|
+
p19.outro(pc13.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3035
3035
|
}
|
|
3036
3036
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3037
3037
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3041,7 +3041,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3041
3041
|
const formattedMethods = methods.map((m) => {
|
|
3042
3042
|
const isAsync = m.async !== false;
|
|
3043
3043
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3044
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3044
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p22) => `${p22.name}: ${p22.type}`).join(", ") : "";
|
|
3045
3045
|
return {
|
|
3046
3046
|
name: m.name,
|
|
3047
3047
|
isAsync,
|
|
@@ -3111,7 +3111,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3111
3111
|
let name = classNameArg?.trim();
|
|
3112
3112
|
if (!name) {
|
|
3113
3113
|
name = handleCancel(
|
|
3114
|
-
await
|
|
3114
|
+
await p19.text({
|
|
3115
3115
|
message: "Injectable class name:",
|
|
3116
3116
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3117
3117
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3124,7 +3124,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3124
3124
|
let customPath;
|
|
3125
3125
|
if (!flags.yes) {
|
|
3126
3126
|
location = handleCancel(
|
|
3127
|
-
await
|
|
3127
|
+
await p19.select({
|
|
3128
3128
|
message: "Select target architectural location:",
|
|
3129
3129
|
options: [
|
|
3130
3130
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3142,14 +3142,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3142
3142
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3143
3143
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3144
3144
|
const selected = handleCancel(
|
|
3145
|
-
await
|
|
3145
|
+
await p19.select({
|
|
3146
3146
|
message: "Select target feature:",
|
|
3147
3147
|
options: featureOptions
|
|
3148
3148
|
})
|
|
3149
3149
|
);
|
|
3150
3150
|
if (selected === "__custom__") {
|
|
3151
3151
|
feature = handleCancel(
|
|
3152
|
-
await
|
|
3152
|
+
await p19.text({
|
|
3153
3153
|
message: "Enter feature name:",
|
|
3154
3154
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3155
3155
|
})
|
|
@@ -3159,7 +3159,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3159
3159
|
}
|
|
3160
3160
|
} else {
|
|
3161
3161
|
feature = handleCancel(
|
|
3162
|
-
await
|
|
3162
|
+
await p19.text({
|
|
3163
3163
|
message: "Enter target feature name:",
|
|
3164
3164
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3165
3165
|
})
|
|
@@ -3169,7 +3169,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3169
3169
|
feature = toCamelCase(feature);
|
|
3170
3170
|
} else if (location === "custom") {
|
|
3171
3171
|
customPath = handleCancel(
|
|
3172
|
-
await
|
|
3172
|
+
await p19.text({
|
|
3173
3173
|
message: "Enter destination directory path:",
|
|
3174
3174
|
placeholder: "e.g. src/services or lib/utils",
|
|
3175
3175
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3180,14 +3180,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3180
3180
|
let generateInterface2 = false;
|
|
3181
3181
|
if (!flags.yes) {
|
|
3182
3182
|
generateInterface2 = handleCancel(
|
|
3183
|
-
await
|
|
3183
|
+
await p19.confirm({
|
|
3184
3184
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3185
3185
|
initialValue: false
|
|
3186
3186
|
})
|
|
3187
3187
|
);
|
|
3188
3188
|
}
|
|
3189
|
-
const s =
|
|
3190
|
-
s.start(`Generating injectable '${
|
|
3189
|
+
const s = p19.spinner();
|
|
3190
|
+
s.start(`Generating injectable '${pc13.cyan(className)}'...`);
|
|
3191
3191
|
const options = {
|
|
3192
3192
|
name: className,
|
|
3193
3193
|
feature,
|
|
@@ -3201,17 +3201,17 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3201
3201
|
const files = generateInjectable(options);
|
|
3202
3202
|
s.stop("Injectable class generated successfully!");
|
|
3203
3203
|
printGeneratedFiles(files, options.dryRun);
|
|
3204
|
-
|
|
3204
|
+
p19.outro(pc13.green(`Injectable '${className}' created successfully!`));
|
|
3205
3205
|
}
|
|
3206
|
-
async function
|
|
3206
|
+
async function runEnvInit(flags = {}) {
|
|
3207
3207
|
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3208
|
-
const s =
|
|
3208
|
+
const s = p19.spinner();
|
|
3209
3209
|
s.start("Scanning for .example environment files...");
|
|
3210
|
-
const files =
|
|
3210
|
+
const files = fs14.readdirSync(projectRoot);
|
|
3211
3211
|
const exampleFiles = files.filter((file) => file.startsWith(".env") && file.endsWith(".example")).sort();
|
|
3212
3212
|
if (exampleFiles.length === 0) {
|
|
3213
3213
|
s.stop("No .example files found.");
|
|
3214
|
-
|
|
3214
|
+
p19.log.info(pc13.yellow(`No files matching '.env*.example' were found in ${projectRoot}`));
|
|
3215
3215
|
return [];
|
|
3216
3216
|
}
|
|
3217
3217
|
s.message(`Found ${exampleFiles.length} template(s). Generating...`);
|
|
@@ -3220,28 +3220,140 @@ async function runEnvCommand(flags = {}) {
|
|
|
3220
3220
|
const sourcePath = path2.join(projectRoot, exampleFile);
|
|
3221
3221
|
const targetFile = exampleFile.replace(/\.example$/, "");
|
|
3222
3222
|
const targetPath = path2.join(projectRoot, targetFile);
|
|
3223
|
-
|
|
3223
|
+
let overwrite = flags.overwrite;
|
|
3224
|
+
if (overwrite === void 0 && !flags.dryRun && !flags.yes && !process.env.VITEST && process.stdin.isTTY && fs14.existsSync(targetPath)) {
|
|
3225
|
+
overwrite = await p19.confirm({
|
|
3226
|
+
message: `${targetFile} already exists. Overwrite?`,
|
|
3227
|
+
initialValue: false
|
|
3228
|
+
});
|
|
3229
|
+
if (p19.isCancel(overwrite)) {
|
|
3230
|
+
p19.cancel("Operation cancelled.");
|
|
3231
|
+
process.exit(0);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
const content = fs14.readFileSync(sourcePath, "utf-8");
|
|
3224
3235
|
const genFile = writeFileSafely(targetPath, content, {
|
|
3225
3236
|
projectRoot,
|
|
3226
3237
|
dryRun: Boolean(flags.dryRun),
|
|
3227
|
-
overwrite: Boolean(
|
|
3238
|
+
overwrite: Boolean(overwrite)
|
|
3228
3239
|
});
|
|
3229
3240
|
generatedFiles.push(genFile);
|
|
3230
3241
|
}
|
|
3231
3242
|
s.stop("Environment files processed!");
|
|
3232
3243
|
printGeneratedFiles(generatedFiles, Boolean(flags.dryRun));
|
|
3233
|
-
|
|
3244
|
+
p19.outro(pc13.green("\u2713 Local environment files are ready!"));
|
|
3234
3245
|
return generatedFiles;
|
|
3235
3246
|
}
|
|
3236
|
-
|
|
3247
|
+
var EnvEditor = class {
|
|
3248
|
+
lines;
|
|
3249
|
+
filePath;
|
|
3250
|
+
constructor(filePath) {
|
|
3251
|
+
this.filePath = filePath;
|
|
3252
|
+
this.lines = fs14.existsSync(filePath) ? fs14.readFileSync(filePath, "utf-8").split(/\r?\n/) : [];
|
|
3253
|
+
}
|
|
3254
|
+
getLines() {
|
|
3255
|
+
return [...this.lines];
|
|
3256
|
+
}
|
|
3257
|
+
getKeys() {
|
|
3258
|
+
return this.lines.filter((line) => line.trim() && !line.trim().startsWith("#")).map((line) => {
|
|
3259
|
+
const eqIdx = line.indexOf("=");
|
|
3260
|
+
return eqIdx !== -1 ? line.substring(0, eqIdx).trim() : line.trim();
|
|
3261
|
+
}).filter(Boolean);
|
|
3262
|
+
}
|
|
3263
|
+
getValue(key) {
|
|
3264
|
+
const line = this.lines.find((l) => {
|
|
3265
|
+
const trimmed = l.trim();
|
|
3266
|
+
if (trimmed.startsWith("#") || !trimmed) return false;
|
|
3267
|
+
const eqIdx = l.indexOf("=");
|
|
3268
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3269
|
+
return k === key;
|
|
3270
|
+
});
|
|
3271
|
+
if (!line) return null;
|
|
3272
|
+
const eqIndex = line.indexOf("=");
|
|
3273
|
+
return eqIndex !== -1 ? line.substring(eqIndex + 1).trim() : "";
|
|
3274
|
+
}
|
|
3275
|
+
has(key) {
|
|
3276
|
+
return this.getKeys().includes(key);
|
|
3277
|
+
}
|
|
3278
|
+
set(key, value) {
|
|
3279
|
+
const newLine = `${key}=${value}`;
|
|
3280
|
+
const index = this.lines.findIndex((l) => {
|
|
3281
|
+
const trimmed = l.trim();
|
|
3282
|
+
if (trimmed.startsWith("#") || !trimmed) return false;
|
|
3283
|
+
const eqIdx = l.indexOf("=");
|
|
3284
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3285
|
+
return k === key;
|
|
3286
|
+
});
|
|
3287
|
+
if (index !== -1) {
|
|
3288
|
+
this.lines[index] = newLine;
|
|
3289
|
+
} else {
|
|
3290
|
+
if (this.lines.length > 0 && this.lines[this.lines.length - 1] !== "") {
|
|
3291
|
+
this.lines.push("");
|
|
3292
|
+
}
|
|
3293
|
+
this.lines.push(newLine);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
delete(key) {
|
|
3297
|
+
const initialLen = this.lines.length;
|
|
3298
|
+
this.lines = this.lines.filter((l) => {
|
|
3299
|
+
const trimmed = l.trim();
|
|
3300
|
+
if (trimmed.startsWith("#") || !trimmed) return true;
|
|
3301
|
+
const eqIdx = l.indexOf("=");
|
|
3302
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3303
|
+
return k !== key;
|
|
3304
|
+
});
|
|
3305
|
+
return this.lines.length !== initialLen;
|
|
3306
|
+
}
|
|
3307
|
+
redactAll() {
|
|
3308
|
+
this.lines = this.lines.map((line) => {
|
|
3309
|
+
const trimmed = line.trim();
|
|
3310
|
+
if (trimmed.startsWith("#") || !trimmed) return line;
|
|
3311
|
+
const eqIndex = line.indexOf("=");
|
|
3312
|
+
return eqIndex !== -1 ? line.substring(0, eqIndex + 1) : line;
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
save() {
|
|
3316
|
+
const dir = path2.dirname(this.filePath);
|
|
3317
|
+
if (!fs14.existsSync(dir)) {
|
|
3318
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3319
|
+
}
|
|
3320
|
+
fs14.writeFileSync(this.filePath, this.lines.join("\n"), "utf-8");
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
function discoverEnvironments(projectRoot) {
|
|
3324
|
+
if (!fs14.existsSync(projectRoot)) {
|
|
3325
|
+
return ["local", "dev", "prod"];
|
|
3326
|
+
}
|
|
3327
|
+
const files = fs14.readdirSync(projectRoot);
|
|
3328
|
+
const envs = /* @__PURE__ */ new Set();
|
|
3329
|
+
for (const file of files) {
|
|
3330
|
+
if (file.startsWith(".env")) {
|
|
3331
|
+
const cleanName = file.replace(/\.example$/, "");
|
|
3332
|
+
const match = cleanName.match(/^\.env\.([a-zA-Z0-9_-]+)$/);
|
|
3333
|
+
if (match && match[1]) {
|
|
3334
|
+
envs.add(match[1]);
|
|
3335
|
+
} else if (cleanName === ".env") {
|
|
3336
|
+
envs.add("local");
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return envs.size > 0 ? Array.from(envs) : ["local", "dev", "prod"];
|
|
3341
|
+
}
|
|
3342
|
+
function resolveEnvFilename(envName, isExample = false) {
|
|
3343
|
+
const base = envName === "local" ? ".env" : `.env.${envName}`;
|
|
3344
|
+
return isExample ? `${base}.example` : base;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// src/commands/env/initExamples.ts
|
|
3348
|
+
async function runEnvInitExamples(flags = {}) {
|
|
3237
3349
|
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3238
|
-
const s =
|
|
3350
|
+
const s = p19.spinner();
|
|
3239
3351
|
s.start("Scanning for local environment files...");
|
|
3240
|
-
const files =
|
|
3352
|
+
const files = fs14.readdirSync(projectRoot, { withFileTypes: true });
|
|
3241
3353
|
const envFiles = files.filter((dirent) => dirent.isFile() && dirent.name.startsWith(".env") && !dirent.name.endsWith(".example")).map((dirent) => dirent.name).sort();
|
|
3242
3354
|
if (envFiles.length === 0) {
|
|
3243
3355
|
s.stop("No local .env files found.");
|
|
3244
|
-
|
|
3356
|
+
p19.log.info(pc13.yellow(`No files matching '.env*' were found in ${projectRoot}`));
|
|
3245
3357
|
return [];
|
|
3246
3358
|
}
|
|
3247
3359
|
s.message(`Found ${envFiles.length} local .env file(s). Generating templates...`);
|
|
@@ -3250,34 +3362,525 @@ async function runEnvExamplesCommand(flags = {}) {
|
|
|
3250
3362
|
const sourcePath = path2.join(projectRoot, envFile);
|
|
3251
3363
|
const targetFile = `${envFile}.example`;
|
|
3252
3364
|
const targetPath = path2.join(projectRoot, targetFile);
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3365
|
+
let overwrite = flags.overwrite;
|
|
3366
|
+
if (overwrite === void 0 && !flags.dryRun && !flags.yes && !process.env.VITEST && process.stdin.isTTY && fs14.existsSync(targetPath)) {
|
|
3367
|
+
overwrite = await p19.confirm({
|
|
3368
|
+
message: `${targetFile} already exists. Overwrite with redacted values?`,
|
|
3369
|
+
initialValue: true
|
|
3370
|
+
});
|
|
3371
|
+
if (p19.isCancel(overwrite)) {
|
|
3372
|
+
p19.cancel("Operation cancelled.");
|
|
3373
|
+
process.exit(0);
|
|
3260
3374
|
}
|
|
3261
|
-
|
|
3262
|
-
|
|
3375
|
+
}
|
|
3376
|
+
const editor = new EnvEditor(sourcePath);
|
|
3377
|
+
editor.redactAll();
|
|
3378
|
+
const redactedContent = editor.getLines().join("\n");
|
|
3263
3379
|
const genFile = writeFileSafely(targetPath, redactedContent, {
|
|
3264
3380
|
projectRoot,
|
|
3265
3381
|
dryRun: Boolean(flags.dryRun),
|
|
3266
|
-
overwrite: Boolean(
|
|
3382
|
+
overwrite: Boolean(overwrite)
|
|
3267
3383
|
});
|
|
3268
3384
|
generatedFiles.push(genFile);
|
|
3269
3385
|
}
|
|
3270
3386
|
s.stop("Environment example templates generated!");
|
|
3271
3387
|
printGeneratedFiles(generatedFiles, Boolean(flags.dryRun));
|
|
3272
|
-
|
|
3388
|
+
p19.outro(pc13.green("\u2713 .env*.example files are up to date and safely redacted!"));
|
|
3273
3389
|
return generatedFiles;
|
|
3274
3390
|
}
|
|
3391
|
+
function isInteractive(flags) {
|
|
3392
|
+
if (flags.yes || Boolean(flags.nonInteractive)) return false;
|
|
3393
|
+
if (process.env.VITEST) return false;
|
|
3394
|
+
return Boolean(process.stdin.isTTY);
|
|
3395
|
+
}
|
|
3396
|
+
function getAllExistingKeys(projectRoot, envs) {
|
|
3397
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3398
|
+
for (const env of envs) {
|
|
3399
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3400
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3401
|
+
if (fs14.existsSync(localPath)) {
|
|
3402
|
+
new EnvEditor(localPath).getKeys().forEach((k) => keys.add(k));
|
|
3403
|
+
}
|
|
3404
|
+
if (fs14.existsSync(examplePath)) {
|
|
3405
|
+
new EnvEditor(examplePath).getKeys().forEach((k) => keys.add(k));
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
return Array.from(keys).sort();
|
|
3409
|
+
}
|
|
3410
|
+
async function runEnvAdd(key, value, flags = {}) {
|
|
3411
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3412
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3413
|
+
let targetKey = key?.trim();
|
|
3414
|
+
if (!targetKey) {
|
|
3415
|
+
if (!isInteractive(flags)) {
|
|
3416
|
+
throw new Error("Variable key is required for 'mason env add <key> [value]'");
|
|
3417
|
+
}
|
|
3418
|
+
targetKey = handleCancel(
|
|
3419
|
+
await p19.text({
|
|
3420
|
+
message: "Enter the variable key name:",
|
|
3421
|
+
placeholder: "e.g. DATABASE_URL, STRIPE_KEY, PORT",
|
|
3422
|
+
validate: (val) => {
|
|
3423
|
+
if (!val || !val.trim()) return "Variable key name cannot be empty";
|
|
3424
|
+
if (/\s/.test(val.trim())) return "Variable key cannot contain spaces";
|
|
3425
|
+
if (val.includes("=")) return "Variable key cannot contain '='";
|
|
3426
|
+
return void 0;
|
|
3427
|
+
}
|
|
3428
|
+
})
|
|
3429
|
+
).trim();
|
|
3430
|
+
}
|
|
3431
|
+
let targetEnvs = [];
|
|
3432
|
+
if (flags.env) {
|
|
3433
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3434
|
+
} else if (!isInteractive(flags)) {
|
|
3435
|
+
targetEnvs = allEnvs;
|
|
3436
|
+
} else {
|
|
3437
|
+
targetEnvs = handleCancel(
|
|
3438
|
+
await p19.multiselect({
|
|
3439
|
+
message: `Which environments should receive ${pc13.bold(targetKey)}?`,
|
|
3440
|
+
options: allEnvs.map((env) => ({
|
|
3441
|
+
value: env,
|
|
3442
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`
|
|
3443
|
+
})),
|
|
3444
|
+
initialValues: allEnvs,
|
|
3445
|
+
required: true
|
|
3446
|
+
})
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
if (targetEnvs.length === 0) {
|
|
3450
|
+
p19.log.warn("No environments selected. Nothing was changed.");
|
|
3451
|
+
return { key: targetKey, environments: [] };
|
|
3452
|
+
}
|
|
3453
|
+
for (const env of targetEnvs) {
|
|
3454
|
+
let envVal = value;
|
|
3455
|
+
if (envVal === void 0) {
|
|
3456
|
+
if (!isInteractive(flags)) {
|
|
3457
|
+
envVal = "";
|
|
3458
|
+
} else {
|
|
3459
|
+
envVal = handleCancel(
|
|
3460
|
+
await p19.text({
|
|
3461
|
+
message: `Value for ${pc13.cyan(targetKey)} in ${pc13.bold(env)} (${resolveEnvFilename(env)})?`,
|
|
3462
|
+
initialValue: "",
|
|
3463
|
+
placeholder: "Leave empty for blank value"
|
|
3464
|
+
})
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
} else if (isInteractive(flags) && !flags.yes) {
|
|
3468
|
+
envVal = handleCancel(
|
|
3469
|
+
await p19.text({
|
|
3470
|
+
message: `Value for ${pc13.cyan(targetKey)} in ${pc13.bold(env)} (${resolveEnvFilename(env)})?`,
|
|
3471
|
+
initialValue: value ?? "",
|
|
3472
|
+
placeholder: "Leave empty for blank value"
|
|
3473
|
+
})
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3477
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3478
|
+
if (flags.dryRun) {
|
|
3479
|
+
p19.log.info(
|
|
3480
|
+
pc13.cyan(`[DRY RUN] Would set ${targetKey}=${envVal} in ${resolveEnvFilename(env, false)}`)
|
|
3481
|
+
);
|
|
3482
|
+
p19.log.info(
|
|
3483
|
+
pc13.cyan(`[DRY RUN] Would add ${targetKey}= to ${resolveEnvFilename(env, true)}`)
|
|
3484
|
+
);
|
|
3485
|
+
} else {
|
|
3486
|
+
const localEditor = new EnvEditor(localPath);
|
|
3487
|
+
localEditor.set(targetKey, envVal);
|
|
3488
|
+
localEditor.save();
|
|
3489
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3490
|
+
exampleEditor.set(targetKey, "");
|
|
3491
|
+
exampleEditor.save();
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
if (!flags.dryRun) {
|
|
3495
|
+
p19.log.success(
|
|
3496
|
+
pc13.green(
|
|
3497
|
+
`\u2713 Added ${pc13.bold(targetKey)} to [${targetEnvs.join(", ")}] and updated .example templates.`
|
|
3498
|
+
)
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3502
|
+
}
|
|
3503
|
+
async function runEnvUpdate(key, value, flags = {}) {
|
|
3504
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3505
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3506
|
+
const existingKeys = getAllExistingKeys(projectRoot, allEnvs);
|
|
3507
|
+
let targetKey = key?.trim();
|
|
3508
|
+
if (!targetKey) {
|
|
3509
|
+
if (!isInteractive(flags)) {
|
|
3510
|
+
throw new Error("Variable key is required for 'mason env update <key> [value]'");
|
|
3511
|
+
}
|
|
3512
|
+
if (existingKeys.length === 0) {
|
|
3513
|
+
p19.log.error(pc13.red("No environment variables found to update."));
|
|
3514
|
+
return { key: "", environments: [] };
|
|
3515
|
+
}
|
|
3516
|
+
targetKey = handleCancel(
|
|
3517
|
+
await p19.select({
|
|
3518
|
+
message: "Select a variable to update:",
|
|
3519
|
+
options: existingKeys.map((k) => ({ value: k, label: k }))
|
|
3520
|
+
})
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
const envsWithKey = allEnvs.filter((env) => {
|
|
3524
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3525
|
+
return fs14.existsSync(localPath) && new EnvEditor(localPath).has(targetKey);
|
|
3526
|
+
});
|
|
3527
|
+
let targetEnvs = [];
|
|
3528
|
+
if (flags.env) {
|
|
3529
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3530
|
+
} else if (!isInteractive(flags)) {
|
|
3531
|
+
targetEnvs = envsWithKey.length > 0 ? envsWithKey : allEnvs;
|
|
3532
|
+
} else {
|
|
3533
|
+
targetEnvs = handleCancel(
|
|
3534
|
+
await p19.multiselect({
|
|
3535
|
+
message: `Which environments should be updated for ${pc13.bold(targetKey)}?`,
|
|
3536
|
+
options: allEnvs.map((env) => ({
|
|
3537
|
+
value: env,
|
|
3538
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`,
|
|
3539
|
+
hint: envsWithKey.includes(env) ? "currently exists" : "not set"
|
|
3540
|
+
})),
|
|
3541
|
+
initialValues: envsWithKey.length > 0 ? envsWithKey : allEnvs,
|
|
3542
|
+
required: true
|
|
3543
|
+
})
|
|
3544
|
+
);
|
|
3545
|
+
}
|
|
3546
|
+
if (targetEnvs.length === 0) {
|
|
3547
|
+
p19.log.warn("No environments selected. Nothing was changed.");
|
|
3548
|
+
return { key: targetKey, environments: [] };
|
|
3549
|
+
}
|
|
3550
|
+
for (const env of targetEnvs) {
|
|
3551
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3552
|
+
const localEditor = new EnvEditor(localPath);
|
|
3553
|
+
const currentVal = localEditor.getValue(targetKey);
|
|
3554
|
+
let newVal = value;
|
|
3555
|
+
if (newVal === void 0) {
|
|
3556
|
+
if (!isInteractive(flags)) {
|
|
3557
|
+
newVal = currentVal ?? "";
|
|
3558
|
+
} else {
|
|
3559
|
+
newVal = handleCancel(
|
|
3560
|
+
await p19.text({
|
|
3561
|
+
message: `New value for ${pc13.cyan(targetKey)} in ${pc13.bold(env)}? (Current: "${currentVal ?? ""}")`,
|
|
3562
|
+
initialValue: currentVal ?? ""
|
|
3563
|
+
})
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
} else if (isInteractive(flags) && !flags.yes) {
|
|
3567
|
+
newVal = handleCancel(
|
|
3568
|
+
await p19.text({
|
|
3569
|
+
message: `New value for ${pc13.cyan(targetKey)} in ${pc13.bold(env)}? (Current: "${currentVal ?? ""}")`,
|
|
3570
|
+
initialValue: value ?? currentVal ?? ""
|
|
3571
|
+
})
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
if (flags.dryRun) {
|
|
3575
|
+
p19.log.info(
|
|
3576
|
+
pc13.cyan(`[DRY RUN] Would update ${targetKey}=${newVal} in ${resolveEnvFilename(env, false)}`)
|
|
3577
|
+
);
|
|
3578
|
+
} else {
|
|
3579
|
+
localEditor.set(targetKey, newVal);
|
|
3580
|
+
localEditor.save();
|
|
3581
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3582
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3583
|
+
if (!exampleEditor.has(targetKey)) {
|
|
3584
|
+
exampleEditor.set(targetKey, "");
|
|
3585
|
+
exampleEditor.save();
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
if (!flags.dryRun) {
|
|
3590
|
+
p19.log.success(
|
|
3591
|
+
pc13.green(`\u2713 Updated ${pc13.bold(targetKey)} in [${targetEnvs.join(", ")}].`)
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3595
|
+
}
|
|
3596
|
+
async function runEnvDelete(key, flags = {}) {
|
|
3597
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3598
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3599
|
+
const existingKeys = getAllExistingKeys(projectRoot, allEnvs);
|
|
3600
|
+
let targetKey = key?.trim();
|
|
3601
|
+
if (!targetKey) {
|
|
3602
|
+
if (!isInteractive(flags)) {
|
|
3603
|
+
throw new Error("Variable key is required for 'mason env delete <key>'");
|
|
3604
|
+
}
|
|
3605
|
+
if (existingKeys.length === 0) {
|
|
3606
|
+
p19.log.error(pc13.red("No environment variables found to delete."));
|
|
3607
|
+
return { key: "", environments: [] };
|
|
3608
|
+
}
|
|
3609
|
+
targetKey = handleCancel(
|
|
3610
|
+
await p19.select({
|
|
3611
|
+
message: "Select a variable to delete:",
|
|
3612
|
+
options: existingKeys.map((k) => ({ value: k, label: k }))
|
|
3613
|
+
})
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
const envsWithKey = allEnvs.filter((env) => {
|
|
3617
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3618
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3619
|
+
return fs14.existsSync(localPath) && new EnvEditor(localPath).has(targetKey) || fs14.existsSync(examplePath) && new EnvEditor(examplePath).has(targetKey);
|
|
3620
|
+
});
|
|
3621
|
+
let targetEnvs = [];
|
|
3622
|
+
if (flags.env) {
|
|
3623
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3624
|
+
} else if (!isInteractive(flags)) {
|
|
3625
|
+
targetEnvs = envsWithKey.length > 0 ? envsWithKey : allEnvs;
|
|
3626
|
+
} else {
|
|
3627
|
+
targetEnvs = handleCancel(
|
|
3628
|
+
await p19.multiselect({
|
|
3629
|
+
message: `Which environments should remove ${pc13.bold(targetKey)}?`,
|
|
3630
|
+
options: allEnvs.map((env) => ({
|
|
3631
|
+
value: env,
|
|
3632
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`,
|
|
3633
|
+
...envsWithKey.includes(env) ? { hint: "currently present" } : {}
|
|
3634
|
+
})),
|
|
3635
|
+
initialValues: envsWithKey.length > 0 ? envsWithKey : allEnvs,
|
|
3636
|
+
required: true
|
|
3637
|
+
})
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
if (targetEnvs.length === 0) {
|
|
3641
|
+
p19.log.warn("No environments selected. Nothing was deleted.");
|
|
3642
|
+
return { key: targetKey, environments: [] };
|
|
3643
|
+
}
|
|
3644
|
+
if (isInteractive(flags) && !flags.yes) {
|
|
3645
|
+
const confirm13 = handleCancel(
|
|
3646
|
+
await p19.confirm({
|
|
3647
|
+
message: `Are you sure you want to delete ${pc13.bold(targetKey)} from ${targetEnvs.join(", ")} and their .example templates?`,
|
|
3648
|
+
initialValue: false
|
|
3649
|
+
})
|
|
3650
|
+
);
|
|
3651
|
+
if (!confirm13) {
|
|
3652
|
+
p19.log.warn("Operation cancelled.");
|
|
3653
|
+
return { key: targetKey, environments: [] };
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
for (const env of targetEnvs) {
|
|
3657
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3658
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3659
|
+
if (flags.dryRun) {
|
|
3660
|
+
p19.log.info(
|
|
3661
|
+
pc13.cyan(`[DRY RUN] Would delete ${targetKey} from ${resolveEnvFilename(env, false)} and ${resolveEnvFilename(env, true)}`)
|
|
3662
|
+
);
|
|
3663
|
+
} else {
|
|
3664
|
+
if (fs14.existsSync(localPath)) {
|
|
3665
|
+
const localEditor = new EnvEditor(localPath);
|
|
3666
|
+
localEditor.delete(targetKey);
|
|
3667
|
+
localEditor.save();
|
|
3668
|
+
}
|
|
3669
|
+
if (fs14.existsSync(examplePath)) {
|
|
3670
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3671
|
+
exampleEditor.delete(targetKey);
|
|
3672
|
+
exampleEditor.save();
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
if (!flags.dryRun) {
|
|
3677
|
+
p19.log.success(
|
|
3678
|
+
pc13.green(`\u2713 Deleted ${pc13.bold(targetKey)} from [${targetEnvs.join(", ")}] and .example templates.`)
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3681
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3682
|
+
}
|
|
3683
|
+
async function runEnvAudit(flags = {}) {
|
|
3684
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3685
|
+
const envs = discoverEnvironments(projectRoot);
|
|
3686
|
+
p19.intro(pc13.bgCyan(pc13.black(" mason env audit ")));
|
|
3687
|
+
const items = [];
|
|
3688
|
+
let totalIssues = 0;
|
|
3689
|
+
let hasDrift = false;
|
|
3690
|
+
let hasErrors = false;
|
|
3691
|
+
for (const env of envs) {
|
|
3692
|
+
const localFile = resolveEnvFilename(env, false);
|
|
3693
|
+
const exampleFile = resolveEnvFilename(env, true);
|
|
3694
|
+
const localPath = path2.join(projectRoot, localFile);
|
|
3695
|
+
const examplePath = path2.join(projectRoot, exampleFile);
|
|
3696
|
+
const localExists = fs14.existsSync(localPath);
|
|
3697
|
+
const exampleExists = fs14.existsSync(examplePath);
|
|
3698
|
+
const localEditor = new EnvEditor(localPath);
|
|
3699
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3700
|
+
const localKeys = localExists ? localEditor.getKeys() : [];
|
|
3701
|
+
const exampleKeys = exampleExists ? exampleEditor.getKeys() : [];
|
|
3702
|
+
const missingInLocal = exampleKeys.filter((k) => !localKeys.includes(k));
|
|
3703
|
+
const missingInExample = localKeys.filter((k) => !exampleKeys.includes(k));
|
|
3704
|
+
const emptyLocals = localKeys.filter((k) => {
|
|
3705
|
+
const val = localEditor.getValue(k);
|
|
3706
|
+
return val === "" || val === null;
|
|
3707
|
+
});
|
|
3708
|
+
const envIssues = (!localExists ? 1 : 0) + (!exampleExists ? 1 : 0) + missingInLocal.length + missingInExample.length + emptyLocals.length;
|
|
3709
|
+
totalIssues += envIssues;
|
|
3710
|
+
if (missingInExample.length > 0 || !exampleExists) hasDrift = true;
|
|
3711
|
+
if (missingInLocal.length > 0 || !localExists) hasErrors = true;
|
|
3712
|
+
items.push({
|
|
3713
|
+
env,
|
|
3714
|
+
localFile,
|
|
3715
|
+
exampleFile,
|
|
3716
|
+
localExists,
|
|
3717
|
+
exampleExists,
|
|
3718
|
+
missingInLocal,
|
|
3719
|
+
missingInExample,
|
|
3720
|
+
emptyLocals
|
|
3721
|
+
});
|
|
3722
|
+
if (!localExists && !exampleExists) {
|
|
3723
|
+
p19.log.warn(
|
|
3724
|
+
pc13.yellow(`\u25B2 ${pc13.bold(env)}: Neither '${localFile}' nor '${exampleFile}' exist.`)
|
|
3725
|
+
);
|
|
3726
|
+
continue;
|
|
3727
|
+
}
|
|
3728
|
+
if (!localExists) {
|
|
3729
|
+
p19.log.error(
|
|
3730
|
+
pc13.red(`\u2717 ${pc13.bold(env)}: Local file '${localFile}' is missing! Run 'mason env init' to create it.`)
|
|
3731
|
+
);
|
|
3732
|
+
continue;
|
|
3733
|
+
}
|
|
3734
|
+
if (!exampleExists) {
|
|
3735
|
+
p19.log.warn(
|
|
3736
|
+
pc13.yellow(`\u25B2 ${pc13.bold(env)}: Template '${exampleFile}' is missing! Run 'mason env init-examples' to generate it.`)
|
|
3737
|
+
);
|
|
3738
|
+
continue;
|
|
3739
|
+
}
|
|
3740
|
+
if (envIssues === 0) {
|
|
3741
|
+
p19.log.success(
|
|
3742
|
+
pc13.green(`\u2713 ${pc13.bold(env)} (${localFile} \u2194 ${exampleFile}): All ${localKeys.length} keys in sync!`)
|
|
3743
|
+
);
|
|
3744
|
+
} else {
|
|
3745
|
+
p19.log.step(
|
|
3746
|
+
pc13.bold(pc13.cyan(`Environment: ${env} (${localFile} \u2194 ${exampleFile})`))
|
|
3747
|
+
);
|
|
3748
|
+
if (missingInLocal.length > 0) {
|
|
3749
|
+
p19.log.error(
|
|
3750
|
+
pc13.red(` \u25CF Missing locally (${missingInLocal.length} in template but missing in ${localFile}):`)
|
|
3751
|
+
);
|
|
3752
|
+
for (const k of missingInLocal) {
|
|
3753
|
+
p19.log.message(pc13.red(` - ${k}`));
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
if (missingInExample.length > 0) {
|
|
3757
|
+
p19.log.warn(
|
|
3758
|
+
pc13.yellow(` \u25CF Template drift (${missingInExample.length} local keys missing in ${exampleFile}):`)
|
|
3759
|
+
);
|
|
3760
|
+
for (const k of missingInExample) {
|
|
3761
|
+
p19.log.message(pc13.yellow(` - ${k}`));
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
if (emptyLocals.length > 0) {
|
|
3765
|
+
p19.log.info(
|
|
3766
|
+
pc13.dim(` \u25B2 Empty values (${emptyLocals.length} defined with blank value in ${localFile}):`)
|
|
3767
|
+
);
|
|
3768
|
+
for (const k of emptyLocals) {
|
|
3769
|
+
p19.log.message(pc13.dim(` - ${k}`));
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
if (totalIssues === 0) {
|
|
3775
|
+
p19.outro(pc13.green("\u2713 All environment files are in sync! No drift detected."));
|
|
3776
|
+
} else {
|
|
3777
|
+
p19.outro(
|
|
3778
|
+
pc13.yellow(`Found ${totalIssues} environment issue(s) across inspected environments.`)
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
return {
|
|
3782
|
+
hasDrift,
|
|
3783
|
+
hasErrors,
|
|
3784
|
+
totalIssues,
|
|
3785
|
+
items
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
// src/commands/env/index.ts
|
|
3790
|
+
async function runEnvMenu(flags = {}) {
|
|
3791
|
+
const task = handleCancel(
|
|
3792
|
+
await p19.select({
|
|
3793
|
+
message: "What environment task do you want to run?",
|
|
3794
|
+
options: [
|
|
3795
|
+
{
|
|
3796
|
+
value: "add",
|
|
3797
|
+
label: "Add Variable",
|
|
3798
|
+
hint: "Add a new key across selected environments and sync .example templates"
|
|
3799
|
+
},
|
|
3800
|
+
{
|
|
3801
|
+
value: "update",
|
|
3802
|
+
label: "Update Variable",
|
|
3803
|
+
hint: "Update an existing variable value across environments"
|
|
3804
|
+
},
|
|
3805
|
+
{
|
|
3806
|
+
value: "delete",
|
|
3807
|
+
label: "Delete Variable",
|
|
3808
|
+
hint: "Delete a variable from .env and .example files"
|
|
3809
|
+
},
|
|
3810
|
+
{
|
|
3811
|
+
value: "audit",
|
|
3812
|
+
label: "Audit Drift",
|
|
3813
|
+
hint: "Scan for missing keys, uncommitted secrets, or template drift"
|
|
3814
|
+
},
|
|
3815
|
+
{
|
|
3816
|
+
value: "init",
|
|
3817
|
+
label: "Init Files (.env)",
|
|
3818
|
+
hint: "Clone .env*.example templates to local .env files"
|
|
3819
|
+
},
|
|
3820
|
+
{
|
|
3821
|
+
value: "init-examples",
|
|
3822
|
+
label: "Init Templates (.env.example)",
|
|
3823
|
+
hint: "Safely clone and redact local .env files to .example templates"
|
|
3824
|
+
}
|
|
3825
|
+
]
|
|
3826
|
+
})
|
|
3827
|
+
);
|
|
3828
|
+
switch (task) {
|
|
3829
|
+
case "add":
|
|
3830
|
+
return await runEnvAdd(void 0, void 0, flags);
|
|
3831
|
+
case "update":
|
|
3832
|
+
return await runEnvUpdate(void 0, void 0, flags);
|
|
3833
|
+
case "delete":
|
|
3834
|
+
return await runEnvDelete(void 0, flags);
|
|
3835
|
+
case "audit":
|
|
3836
|
+
return await runEnvAudit(flags);
|
|
3837
|
+
case "init":
|
|
3838
|
+
return await runEnvInit(flags);
|
|
3839
|
+
case "init-examples":
|
|
3840
|
+
return await runEnvInitExamples(flags);
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
async function handleEnv(subcommand, args = [], flags = {}) {
|
|
3844
|
+
if (!subcommand) {
|
|
3845
|
+
return await runEnvMenu(flags);
|
|
3846
|
+
}
|
|
3847
|
+
const normalized = subcommand.trim().toLowerCase();
|
|
3848
|
+
switch (normalized) {
|
|
3849
|
+
case "init":
|
|
3850
|
+
return await runEnvInit(flags);
|
|
3851
|
+
case "init-examples":
|
|
3852
|
+
case "initexamples":
|
|
3853
|
+
return await runEnvInitExamples(flags);
|
|
3854
|
+
case "add":
|
|
3855
|
+
return await runEnvAdd(args[0], args[1], flags);
|
|
3856
|
+
case "update":
|
|
3857
|
+
return await runEnvUpdate(args[0], args[1], flags);
|
|
3858
|
+
case "delete":
|
|
3859
|
+
case "remove":
|
|
3860
|
+
case "rm":
|
|
3861
|
+
return await runEnvDelete(args[0], flags);
|
|
3862
|
+
case "audit":
|
|
3863
|
+
return await runEnvAudit(flags);
|
|
3864
|
+
default:
|
|
3865
|
+
p19.log.error(
|
|
3866
|
+
pc13.red(
|
|
3867
|
+
`Unknown env subcommand: "${subcommand}". Allowed subcommands: init, init-examples, add, update, delete, audit.`
|
|
3868
|
+
)
|
|
3869
|
+
);
|
|
3870
|
+
if (process.env.VITEST) {
|
|
3871
|
+
throw new Error(
|
|
3872
|
+
`Unknown env subcommand: "${subcommand}". Allowed subcommands: init, init-examples, add, update, delete, audit.`
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
process.exit(1);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3275
3878
|
|
|
3276
3879
|
// src/commands/menu.ts
|
|
3277
3880
|
async function runInteractiveMenu(flags = {}) {
|
|
3278
3881
|
printBanner();
|
|
3279
3882
|
const choice = handleCancel(
|
|
3280
|
-
await
|
|
3883
|
+
await p19.select({
|
|
3281
3884
|
message: "What would you like to create in your architecture?",
|
|
3282
3885
|
options: [
|
|
3283
3886
|
{
|
|
@@ -3322,13 +3925,8 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3322
3925
|
},
|
|
3323
3926
|
{
|
|
3324
3927
|
value: "env",
|
|
3325
|
-
label: "
|
|
3326
|
-
hint: "
|
|
3327
|
-
},
|
|
3328
|
-
{
|
|
3329
|
-
value: "envExamples",
|
|
3330
|
-
label: "Sync Env Examples",
|
|
3331
|
-
hint: "Safely clone and redact local .env files to .example templates (Sync)"
|
|
3928
|
+
label: "Environment Variables",
|
|
3929
|
+
hint: "Manage .env files, add/delete keys, and sync templates"
|
|
3332
3930
|
}
|
|
3333
3931
|
]
|
|
3334
3932
|
})
|
|
@@ -3359,10 +3957,7 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3359
3957
|
await runInjectableCommand(void 0, flags);
|
|
3360
3958
|
break;
|
|
3361
3959
|
case "env":
|
|
3362
|
-
await
|
|
3363
|
-
break;
|
|
3364
|
-
case "envExamples":
|
|
3365
|
-
await runEnvExamplesCommand({ ...flags, overwrite: flags.overwrite ?? true });
|
|
3960
|
+
await runEnvMenu(flags);
|
|
3366
3961
|
break;
|
|
3367
3962
|
}
|
|
3368
3963
|
}
|
|
@@ -3401,8 +3996,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3401
3996
|
await runInjectableCommand(name, flags);
|
|
3402
3997
|
break;
|
|
3403
3998
|
default:
|
|
3404
|
-
|
|
3405
|
-
|
|
3999
|
+
p19.log.error(
|
|
4000
|
+
pc13.red(
|
|
3406
4001
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3407
4002
|
)
|
|
3408
4003
|
);
|
|
@@ -3434,7 +4029,7 @@ function findLocalRegistry(startDir = process.cwd()) {
|
|
|
3434
4029
|
let current = path2.resolve(startDir);
|
|
3435
4030
|
while (true) {
|
|
3436
4031
|
const candidate = path2.join(current, "mason-registry");
|
|
3437
|
-
if (
|
|
4032
|
+
if (fs14.existsSync(candidate) && fs14.existsSync(path2.join(candidate, "index.json"))) {
|
|
3438
4033
|
return candidate;
|
|
3439
4034
|
}
|
|
3440
4035
|
const parent = path2.dirname(current);
|
|
@@ -3456,9 +4051,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3456
4051
|
path2.join(process.cwd(), "mason.config.json")
|
|
3457
4052
|
];
|
|
3458
4053
|
for (const configPath of configLocations) {
|
|
3459
|
-
if (
|
|
4054
|
+
if (fs14.existsSync(configPath)) {
|
|
3460
4055
|
try {
|
|
3461
|
-
const config = JSON.parse(
|
|
4056
|
+
const config = JSON.parse(fs14.readFileSync(configPath, "utf-8"));
|
|
3462
4057
|
if (config.registry && typeof config.registry === "string") {
|
|
3463
4058
|
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
3464
4059
|
}
|
|
@@ -3471,9 +4066,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3471
4066
|
path2.join(process.cwd(), "package.json")
|
|
3472
4067
|
];
|
|
3473
4068
|
for (const pkgPath of pkgLocations) {
|
|
3474
|
-
if (
|
|
4069
|
+
if (fs14.existsSync(pkgPath)) {
|
|
3475
4070
|
try {
|
|
3476
|
-
const pkg = JSON.parse(
|
|
4071
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
3477
4072
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
3478
4073
|
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
3479
4074
|
}
|
|
@@ -3524,15 +4119,15 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
3524
4119
|
baseDir = fileURLToPath(baseDir);
|
|
3525
4120
|
}
|
|
3526
4121
|
let filePath = path2.resolve(baseDir, cleanRelPath);
|
|
3527
|
-
if (!
|
|
4122
|
+
if (!fs14.existsSync(filePath)) {
|
|
3528
4123
|
const inSrcPath = path2.resolve(baseDir, "src", cleanRelPath);
|
|
3529
|
-
if (
|
|
4124
|
+
if (fs14.existsSync(inSrcPath)) {
|
|
3530
4125
|
filePath = inSrcPath;
|
|
3531
4126
|
} else {
|
|
3532
4127
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
3533
4128
|
}
|
|
3534
4129
|
}
|
|
3535
|
-
return
|
|
4130
|
+
return fs14.promises.readFile(filePath, "utf-8");
|
|
3536
4131
|
}
|
|
3537
4132
|
async function fetchRegistryIndex(registryBase) {
|
|
3538
4133
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -3594,26 +4189,26 @@ function findClosestMatch(target, candidates) {
|
|
|
3594
4189
|
return null;
|
|
3595
4190
|
}
|
|
3596
4191
|
function detectPackageManager(projectRoot) {
|
|
3597
|
-
if (
|
|
3598
|
-
if (
|
|
3599
|
-
if (
|
|
4192
|
+
if (fs14.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
4193
|
+
if (fs14.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
4194
|
+
if (fs14.existsSync(path2.join(projectRoot, "bun.lockb")) || fs14.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
|
|
3600
4195
|
return "npm";
|
|
3601
4196
|
}
|
|
3602
4197
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
3603
4198
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
3604
4199
|
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
3605
|
-
if (!
|
|
4200
|
+
if (!fs14.existsSync(pkgJsonPath)) {
|
|
3606
4201
|
return requiredPackages;
|
|
3607
4202
|
}
|
|
3608
4203
|
try {
|
|
3609
|
-
const pkg = JSON.parse(
|
|
4204
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
3610
4205
|
const installed = /* @__PURE__ */ new Set([
|
|
3611
4206
|
...Object.keys(pkg.dependencies || {}),
|
|
3612
4207
|
...Object.keys(pkg.devDependencies || {}),
|
|
3613
4208
|
...Object.keys(pkg.peerDependencies || {})
|
|
3614
4209
|
]);
|
|
3615
|
-
return requiredPackages.filter((
|
|
3616
|
-
const nameOnly =
|
|
4210
|
+
return requiredPackages.filter((p22) => {
|
|
4211
|
+
const nameOnly = p22.startsWith("@") ? `@${p22.slice(1).split("@")[0] || ""}` : p22.split("@")[0] || p22;
|
|
3617
4212
|
return !installed.has(nameOnly);
|
|
3618
4213
|
});
|
|
3619
4214
|
} catch {
|
|
@@ -3638,15 +4233,15 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3638
4233
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3639
4234
|
break;
|
|
3640
4235
|
}
|
|
3641
|
-
|
|
4236
|
+
p19.log.info(`Installing missing dependencies with ${manager}: ${pc13.cyan(packages.join(", "))}`);
|
|
3642
4237
|
execSync(installCmd, {
|
|
3643
4238
|
cwd: projectRoot,
|
|
3644
4239
|
stdio: "inherit"
|
|
3645
4240
|
});
|
|
3646
4241
|
}
|
|
3647
4242
|
function getAllFiles(dir, baseDir = dir) {
|
|
3648
|
-
if (!
|
|
3649
|
-
const entries =
|
|
4243
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4244
|
+
const entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
3650
4245
|
const files = [];
|
|
3651
4246
|
for (const entry of entries) {
|
|
3652
4247
|
const fullPath = path2.join(dir, entry.name);
|
|
@@ -3666,13 +4261,13 @@ function getAllFiles(dir, baseDir = dir) {
|
|
|
3666
4261
|
return files.sort();
|
|
3667
4262
|
}
|
|
3668
4263
|
function computeDirectoryHash(dirPath, files) {
|
|
3669
|
-
if (!
|
|
4264
|
+
if (!fs14.existsSync(dirPath)) return "";
|
|
3670
4265
|
const hash = createHash("sha256");
|
|
3671
4266
|
const fileList = (getAllFiles(dirPath)).sort();
|
|
3672
4267
|
for (const relativePath of fileList) {
|
|
3673
4268
|
const absolutePath = path2.join(dirPath, relativePath);
|
|
3674
|
-
if (!
|
|
3675
|
-
const content =
|
|
4269
|
+
if (!fs14.existsSync(absolutePath)) continue;
|
|
4270
|
+
const content = fs14.readFileSync(absolutePath, "utf8");
|
|
3676
4271
|
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
3677
4272
|
hash.update(relativePath);
|
|
3678
4273
|
hash.update(normalizedContent);
|
|
@@ -3682,9 +4277,9 @@ function computeDirectoryHash(dirPath, files) {
|
|
|
3682
4277
|
|
|
3683
4278
|
// src/commands/import.ts
|
|
3684
4279
|
var importPrompts = {
|
|
3685
|
-
select:
|
|
3686
|
-
text:
|
|
3687
|
-
confirm:
|
|
4280
|
+
select: p19.select,
|
|
4281
|
+
text: p19.text,
|
|
4282
|
+
confirm: p19.confirm
|
|
3688
4283
|
};
|
|
3689
4284
|
function isTestFile(filePath) {
|
|
3690
4285
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -3716,11 +4311,11 @@ function getExistingModuleDir(projectRoot, modType, modName) {
|
|
|
3716
4311
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3717
4312
|
const configuredDir = modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
|
|
3718
4313
|
const inConfigured = path2.join(configuredDir, modName);
|
|
3719
|
-
if (
|
|
4314
|
+
if (fs14.existsSync(inConfigured)) return inConfigured;
|
|
3720
4315
|
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3721
|
-
if (
|
|
4316
|
+
if (fs14.existsSync(inSrc)) return inSrc;
|
|
3722
4317
|
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3723
|
-
if (
|
|
4318
|
+
if (fs14.existsSync(inRoot)) return inRoot;
|
|
3724
4319
|
return null;
|
|
3725
4320
|
}
|
|
3726
4321
|
function checkModuleExists(projectRoot, modType, modName) {
|
|
@@ -3736,9 +4331,9 @@ function getAvailableLocalModules(projectRoot, modType) {
|
|
|
3736
4331
|
];
|
|
3737
4332
|
const set = /* @__PURE__ */ new Set();
|
|
3738
4333
|
for (const dir of dirs) {
|
|
3739
|
-
if (
|
|
4334
|
+
if (fs14.existsSync(dir)) {
|
|
3740
4335
|
try {
|
|
3741
|
-
const entries =
|
|
4336
|
+
const entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
3742
4337
|
for (const e of entries) {
|
|
3743
4338
|
if (e.isDirectory() && !e.name.startsWith(".")) {
|
|
3744
4339
|
set.add(e.name);
|
|
@@ -3794,19 +4389,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3794
4389
|
);
|
|
3795
4390
|
}
|
|
3796
4391
|
if (type !== "shared" && type !== "feature") {
|
|
3797
|
-
|
|
4392
|
+
p19.log.error(pc13.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3798
4393
|
process.exit(1);
|
|
3799
4394
|
}
|
|
3800
4395
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3801
|
-
const s =
|
|
3802
|
-
s.start(`Connecting to registry at ${
|
|
4396
|
+
const s = p19.spinner();
|
|
4397
|
+
s.start(`Connecting to registry at ${pc13.dim(registryBase)}...`);
|
|
3803
4398
|
let index;
|
|
3804
4399
|
try {
|
|
3805
4400
|
index = await fetchRegistryIndex(registryBase);
|
|
3806
4401
|
s.stop("Registry index loaded.");
|
|
3807
4402
|
} catch (err) {
|
|
3808
|
-
s.stop(
|
|
3809
|
-
|
|
4403
|
+
s.stop(pc13.red("Failed to load registry index."));
|
|
4404
|
+
p19.log.error(pc13.red(err.message || String(err)));
|
|
3810
4405
|
process.exit(1);
|
|
3811
4406
|
}
|
|
3812
4407
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3814,7 +4409,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3814
4409
|
let name = nameArg?.trim();
|
|
3815
4410
|
if (!name) {
|
|
3816
4411
|
if (availableList.length === 0) {
|
|
3817
|
-
|
|
4412
|
+
p19.log.warn(`No ${type} modules available in the registry.`);
|
|
3818
4413
|
return;
|
|
3819
4414
|
}
|
|
3820
4415
|
name = handleCancel(
|
|
@@ -3830,9 +4425,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3830
4425
|
}
|
|
3831
4426
|
if (!candidateNames.includes(name)) {
|
|
3832
4427
|
const closest = findClosestMatch(name, candidateNames);
|
|
3833
|
-
|
|
4428
|
+
p19.log.error(pc13.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3834
4429
|
if (closest) {
|
|
3835
|
-
|
|
4430
|
+
p19.log.info(pc13.yellow(`Did you mean '${pc13.cyan(closest)}'?`));
|
|
3836
4431
|
}
|
|
3837
4432
|
process.exit(1);
|
|
3838
4433
|
}
|
|
@@ -3844,7 +4439,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3844
4439
|
try {
|
|
3845
4440
|
manifest = await fetchModuleManifest(registryBase, modType, modName);
|
|
3846
4441
|
} catch (err) {
|
|
3847
|
-
|
|
4442
|
+
p19.log.error(pc13.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
|
|
3848
4443
|
process.exit(1);
|
|
3849
4444
|
}
|
|
3850
4445
|
nodes.set(key, {
|
|
@@ -3884,7 +4479,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3884
4479
|
buildTopologicalOrder(rootKey);
|
|
3885
4480
|
const resolutions = /* @__PURE__ */ new Map();
|
|
3886
4481
|
const globalAliasMap = /* @__PURE__ */ new Map();
|
|
3887
|
-
|
|
4482
|
+
p19.log.step(`Importing ${type}: ${pc13.cyan(name)}...`);
|
|
3888
4483
|
for (const key of orderedKeys) {
|
|
3889
4484
|
const node = nodes.get(key);
|
|
3890
4485
|
const { type: modType, name: registryName, manifest } = node;
|
|
@@ -3916,8 +4511,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3916
4511
|
const programmaticPointTo = pointToMap[registryName] || pointToMap[`${modType}/${registryName}`];
|
|
3917
4512
|
if (programmaticPointTo) {
|
|
3918
4513
|
if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
|
|
3919
|
-
|
|
3920
|
-
|
|
4514
|
+
p19.log.error(
|
|
4515
|
+
pc13.red(
|
|
3921
4516
|
`Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
|
|
3922
4517
|
)
|
|
3923
4518
|
);
|
|
@@ -3925,7 +4520,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3925
4520
|
}
|
|
3926
4521
|
globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticPointTo}/`);
|
|
3927
4522
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticPointTo}`);
|
|
3928
|
-
|
|
4523
|
+
p19.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${programmaticPointTo}'`);
|
|
3929
4524
|
resolutions.set(key, {
|
|
3930
4525
|
type: modType,
|
|
3931
4526
|
registryName,
|
|
@@ -3941,7 +4536,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3941
4536
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticAlias}`);
|
|
3942
4537
|
const baseDir = getModuleBaseDir(projectRoot, modType);
|
|
3943
4538
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, programmaticAlias));
|
|
3944
|
-
|
|
4539
|
+
p19.log.step(`Importing '${registryName}' into '${displayDest}'...`);
|
|
3945
4540
|
resolutions.set(key, {
|
|
3946
4541
|
type: modType,
|
|
3947
4542
|
registryName,
|
|
@@ -3953,7 +4548,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3953
4548
|
}
|
|
3954
4549
|
const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
|
|
3955
4550
|
if (!existingDir) {
|
|
3956
|
-
|
|
4551
|
+
p19.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc13.cyan(registryName)}`);
|
|
3957
4552
|
resolutions.set(key, {
|
|
3958
4553
|
type: modType,
|
|
3959
4554
|
registryName,
|
|
@@ -3969,7 +4564,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3969
4564
|
const remoteIntegrity = manifest.integrity || entry?.integrity;
|
|
3970
4565
|
const localHash = computeDirectoryHash(existingDir);
|
|
3971
4566
|
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3972
|
-
|
|
4567
|
+
p19.log.step(`\u2713 ${modType}/${registryName} is up to date (reusing existing)`);
|
|
3973
4568
|
resolutions.set(key, {
|
|
3974
4569
|
type: modType,
|
|
3975
4570
|
registryName,
|
|
@@ -3980,7 +4575,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3980
4575
|
continue;
|
|
3981
4576
|
}
|
|
3982
4577
|
if (flags.overwrite) {
|
|
3983
|
-
|
|
4578
|
+
p19.log.step(`Overwriting '${modType}/${registryName}' with registry version (--overwrite)...`);
|
|
3984
4579
|
resolutions.set(key, {
|
|
3985
4580
|
type: modType,
|
|
3986
4581
|
registryName,
|
|
@@ -3991,7 +4586,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3991
4586
|
continue;
|
|
3992
4587
|
}
|
|
3993
4588
|
if (flags.yes) {
|
|
3994
|
-
|
|
4589
|
+
p19.log.info(`Using existing '${modType}/${registryName}'`);
|
|
3995
4590
|
resolutions.set(key, {
|
|
3996
4591
|
type: modType,
|
|
3997
4592
|
registryName,
|
|
@@ -4025,7 +4620,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4025
4620
|
})
|
|
4026
4621
|
);
|
|
4027
4622
|
if (choice === "keep_existing" || choice === "use_existing") {
|
|
4028
|
-
|
|
4623
|
+
p19.log.step(`Skipping '${modType}/${registryName}' (keeping existing)`);
|
|
4029
4624
|
resolutions.set(key, {
|
|
4030
4625
|
type: modType,
|
|
4031
4626
|
registryName,
|
|
@@ -4097,7 +4692,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4097
4692
|
chosenName = chosenName.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
|
|
4098
4693
|
globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${chosenName}/`);
|
|
4099
4694
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${chosenName}`);
|
|
4100
|
-
|
|
4695
|
+
p19.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${chosenName}'`);
|
|
4101
4696
|
resolutions.set(key, {
|
|
4102
4697
|
type: modType,
|
|
4103
4698
|
registryName,
|
|
@@ -4129,7 +4724,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4129
4724
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${newName}`);
|
|
4130
4725
|
const baseDir = getModuleBaseDir(projectRoot, modType);
|
|
4131
4726
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
4132
|
-
|
|
4727
|
+
p19.log.step(`Importing '${registryName}' into '${displayDest}'...`);
|
|
4133
4728
|
resolutions.set(key, {
|
|
4134
4729
|
type: modType,
|
|
4135
4730
|
registryName,
|
|
@@ -4140,7 +4735,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4140
4735
|
continue;
|
|
4141
4736
|
}
|
|
4142
4737
|
if (choice === "overwrite") {
|
|
4143
|
-
|
|
4738
|
+
p19.log.step(`Overwriting '${modType}/${registryName}' with registry version...`);
|
|
4144
4739
|
resolutions.set(key, {
|
|
4145
4740
|
type: modType,
|
|
4146
4741
|
registryName,
|
|
@@ -4183,17 +4778,17 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4183
4778
|
const targetDir = path2.join(getModuleBaseDir(projectRoot, res.type), res.targetName);
|
|
4184
4779
|
const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
|
|
4185
4780
|
if (key !== rootKey) {
|
|
4186
|
-
|
|
4187
|
-
`Importing ${res.type}: ${
|
|
4781
|
+
p19.log.step(
|
|
4782
|
+
`Importing ${res.type}: ${pc13.cyan(res.targetName)}${res.targetName !== res.registryName ? pc13.dim(` (from ${res.registryName})`) : ""}...`
|
|
4188
4783
|
);
|
|
4189
4784
|
}
|
|
4190
4785
|
if (res.action === "overwrite" && !flags.dryRun) {
|
|
4191
4786
|
const existingDir = getExistingModuleDir(projectRoot, res.type, res.targetName);
|
|
4192
4787
|
if (existingDir && existingDir !== targetDir) {
|
|
4193
|
-
|
|
4788
|
+
fs14.rmSync(existingDir, { recursive: true, force: true });
|
|
4194
4789
|
}
|
|
4195
|
-
if (
|
|
4196
|
-
|
|
4790
|
+
if (fs14.existsSync(targetDir)) {
|
|
4791
|
+
fs14.rmSync(targetDir, { recursive: true, force: true });
|
|
4197
4792
|
}
|
|
4198
4793
|
}
|
|
4199
4794
|
for (const file of res.manifest.files || []) {
|
|
@@ -4201,32 +4796,32 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4201
4796
|
if (!includeTests && isTestFile(file)) continue;
|
|
4202
4797
|
const remoteFilePath = `${folder}/${res.registryName}/${file}`;
|
|
4203
4798
|
const localFilePath = path2.join(targetDir, file);
|
|
4204
|
-
if (
|
|
4205
|
-
|
|
4206
|
-
` \u21B3 Skipping existing file: ${
|
|
4799
|
+
if (fs14.existsSync(localFilePath) && !isOverwrite) {
|
|
4800
|
+
p19.log.warn(
|
|
4801
|
+
` \u21B3 Skipping existing file: ${pc13.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
4207
4802
|
);
|
|
4208
4803
|
continue;
|
|
4209
4804
|
}
|
|
4210
4805
|
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
4211
4806
|
content = rewriteImports(content, globalAliasMap);
|
|
4212
4807
|
if (!flags.dryRun) {
|
|
4213
|
-
|
|
4214
|
-
|
|
4808
|
+
fs14.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
4809
|
+
fs14.writeFileSync(localFilePath, content, "utf-8");
|
|
4215
4810
|
}
|
|
4216
4811
|
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
4217
4812
|
totalWrittenFiles.push(displayPath);
|
|
4218
|
-
|
|
4813
|
+
p19.log.message(` ${pc13.green("+")} ${displayPath}`);
|
|
4219
4814
|
}
|
|
4220
4815
|
}
|
|
4221
4816
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
4222
4817
|
if (missingNpm.length > 0) {
|
|
4223
4818
|
if (flags.skipInstall || flags.dryRun) {
|
|
4224
|
-
|
|
4819
|
+
p19.log.info(`Required npm dependencies to install: ${pc13.cyan(missingNpm.join(", "))}`);
|
|
4225
4820
|
} else {
|
|
4226
4821
|
try {
|
|
4227
4822
|
installNpmPackages(projectRoot, missingNpm);
|
|
4228
4823
|
} catch (err) {
|
|
4229
|
-
|
|
4824
|
+
p19.log.warn(
|
|
4230
4825
|
`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(
|
|
4231
4826
|
" "
|
|
4232
4827
|
)}`
|
|
@@ -4235,13 +4830,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4235
4830
|
}
|
|
4236
4831
|
}
|
|
4237
4832
|
if (flags.dryRun) {
|
|
4238
|
-
|
|
4833
|
+
p19.log.info(pc13.yellow("Dry run complete: No files were written to disk."));
|
|
4239
4834
|
} else {
|
|
4240
4835
|
const rootRes = resolutions.get(rootKey);
|
|
4241
4836
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
4242
4837
|
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, rootRes.targetName));
|
|
4243
|
-
|
|
4244
|
-
|
|
4838
|
+
p19.log.success(
|
|
4839
|
+
pc13.green(
|
|
4245
4840
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
4246
4841
|
)
|
|
4247
4842
|
);
|
|
@@ -4263,8 +4858,8 @@ function isTestFile2(filePath, projectRoot) {
|
|
|
4263
4858
|
return base.endsWith(".test.ts") || base.endsWith(".test.js") || base.endsWith(".spec.ts") || base.endsWith(".spec.js") || base.endsWith(".d.ts") || norm.includes("/__tests__/") || norm.startsWith("__tests__/") || /(?:^|\/)tests?\//.test(norm);
|
|
4264
4859
|
}
|
|
4265
4860
|
function scanDirFiles(dir, projectRoot) {
|
|
4266
|
-
if (!
|
|
4267
|
-
const entries =
|
|
4861
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4862
|
+
const entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
4268
4863
|
const files = [];
|
|
4269
4864
|
for (const entry of entries) {
|
|
4270
4865
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
@@ -4464,10 +5059,10 @@ var FeatureDependencyGraph = class {
|
|
|
4464
5059
|
if (cycle) return cycle;
|
|
4465
5060
|
} else if (inStack.has(next)) {
|
|
4466
5061
|
const cycle = [next];
|
|
4467
|
-
let
|
|
4468
|
-
while (
|
|
4469
|
-
cycle.push(
|
|
4470
|
-
|
|
5062
|
+
let p22 = curr;
|
|
5063
|
+
while (p22 !== next) {
|
|
5064
|
+
cycle.push(p22);
|
|
5065
|
+
p22 = parent.get(p22) || next;
|
|
4471
5066
|
}
|
|
4472
5067
|
cycle.push(next);
|
|
4473
5068
|
cycle.reverse();
|
|
@@ -4483,8 +5078,8 @@ var FeatureDependencyGraph = class {
|
|
|
4483
5078
|
};
|
|
4484
5079
|
function discoverAllFeatures(projectRoot) {
|
|
4485
5080
|
const featuresBase = resolveFeaturesDir(projectRoot);
|
|
4486
|
-
if (!
|
|
4487
|
-
const entries =
|
|
5081
|
+
if (!fs14.existsSync(featuresBase)) return [];
|
|
5082
|
+
const entries = fs14.readdirSync(featuresBase, { withFileTypes: true });
|
|
4488
5083
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "__tests__" && e.name !== "tests").map((e) => ({
|
|
4489
5084
|
name: e.name,
|
|
4490
5085
|
dir: path2.join(featuresBase, e.name)
|
|
@@ -4875,7 +5470,7 @@ Code: ${v.code}`;
|
|
|
4875
5470
|
const relPath = path2.relative(projectRoot, file).replace(/\\/g, "/");
|
|
4876
5471
|
const { layer } = classifyFile(file, projectRoot);
|
|
4877
5472
|
if (!layer) continue;
|
|
4878
|
-
const content =
|
|
5473
|
+
const content = fs14.readFileSync(file, "utf-8");
|
|
4879
5474
|
const statements = extractImportStatements(content);
|
|
4880
5475
|
const baseName = path2.basename(file);
|
|
4881
5476
|
const isBarrel = baseName === "index.ts" || baseName === "index.js";
|
|
@@ -5369,15 +5964,15 @@ ${compiledDetails}
|
|
|
5369
5964
|
// src/commands/lint.ts
|
|
5370
5965
|
function formatViolation(v) {
|
|
5371
5966
|
const lines = [];
|
|
5372
|
-
lines.push(
|
|
5967
|
+
lines.push(pc13.underline(pc13.cyan(v.relativePath)) + pc13.dim(":") + pc13.yellow(String(v.line)));
|
|
5373
5968
|
const gutter = `${String(v.line).padStart(4, " ")} | `;
|
|
5374
|
-
lines.push(
|
|
5969
|
+
lines.push(pc13.dim(gutter) + v.code);
|
|
5375
5970
|
const categoryShort = v.category.replace(" Violation", "");
|
|
5376
5971
|
lines.push(
|
|
5377
|
-
" " +
|
|
5972
|
+
" " + pc13.red(pc13.bold("\u2716")) + " " + pc13.red(`[${categoryShort}]`) + " " + v.advice
|
|
5378
5973
|
);
|
|
5379
5974
|
if (v.fixExample?.good) {
|
|
5380
|
-
lines.push(" " +
|
|
5975
|
+
lines.push(" " + pc13.dim("\u2192") + " " + pc13.dim(v.fixExample.good));
|
|
5381
5976
|
}
|
|
5382
5977
|
return lines.join("\n");
|
|
5383
5978
|
}
|
|
@@ -5402,7 +5997,7 @@ async function handleLint(flags = {}) {
|
|
|
5402
5997
|
console.log("");
|
|
5403
5998
|
}
|
|
5404
5999
|
console.log(
|
|
5405
|
-
|
|
6000
|
+
pc13.red(
|
|
5406
6001
|
`\u2716 Dependency rules check failed with ${summary.totalViolations} error${summary.totalViolations > 1 ? "s" : ""} across ${summary.violatedFeatures} feature${summary.violatedFeatures > 1 ? "s" : ""} (${summary.totalFeatures} features scanned).`
|
|
5407
6002
|
)
|
|
5408
6003
|
);
|
|
@@ -5421,7 +6016,7 @@ ${compiledDetails}
|
|
|
5421
6016
|
}
|
|
5422
6017
|
if (!quiet) {
|
|
5423
6018
|
console.log(
|
|
5424
|
-
|
|
6019
|
+
pc13.green(
|
|
5425
6020
|
`\u2714 Dependency rules verified across ${summary.totalFeatures} feature${summary.totalFeatures === 1 ? "" : "s"}.`
|
|
5426
6021
|
)
|
|
5427
6022
|
);
|
|
@@ -5436,7 +6031,7 @@ ${compiledDetails}
|
|
|
5436
6031
|
console.log(formatViolation(v));
|
|
5437
6032
|
console.log("");
|
|
5438
6033
|
}
|
|
5439
|
-
console.log(
|
|
6034
|
+
console.log(pc13.red(`\u2716 Dependency rules check failed (1 error).`));
|
|
5440
6035
|
console.log("");
|
|
5441
6036
|
}
|
|
5442
6037
|
}
|
|
@@ -5446,7 +6041,7 @@ ${compiledDetails}
|
|
|
5446
6041
|
async function handleInit(type, flags = {}) {
|
|
5447
6042
|
if (!type) {
|
|
5448
6043
|
const choice = handleCancel(
|
|
5449
|
-
await
|
|
6044
|
+
await p19.select({
|
|
5450
6045
|
message: "What would you like to initialize?",
|
|
5451
6046
|
options: [
|
|
5452
6047
|
{
|
|
@@ -5463,20 +6058,20 @@ async function handleInit(type, flags = {}) {
|
|
|
5463
6058
|
})
|
|
5464
6059
|
);
|
|
5465
6060
|
if (choice === "env") {
|
|
5466
|
-
await
|
|
6061
|
+
await runEnvInit(flags);
|
|
5467
6062
|
} else if (choice === "env-examples") {
|
|
5468
|
-
await
|
|
6063
|
+
await runEnvInitExamples(flags);
|
|
5469
6064
|
}
|
|
5470
6065
|
return;
|
|
5471
6066
|
}
|
|
5472
6067
|
const normalized = type.trim().toLowerCase();
|
|
5473
6068
|
if (normalized === "env") {
|
|
5474
|
-
await
|
|
6069
|
+
await runEnvInit(flags);
|
|
5475
6070
|
} else if (normalized === "env-examples" || normalized === "envexamples") {
|
|
5476
|
-
await
|
|
6071
|
+
await runEnvInitExamples(flags);
|
|
5477
6072
|
} else {
|
|
5478
|
-
|
|
5479
|
-
|
|
6073
|
+
p19.log.error(
|
|
6074
|
+
pc13.red(
|
|
5480
6075
|
`Unknown init target: "${type}". Allowed targets: env, env-examples.`
|
|
5481
6076
|
)
|
|
5482
6077
|
);
|
|
@@ -5492,8 +6087,8 @@ function createCli() {
|
|
|
5492
6087
|
const cli = cac("mason");
|
|
5493
6088
|
cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").option("--point-to <mapping>", "Point a dependency to an existing local module (e.g. --point-to time:mytime)").option("--alias <mapping>", "Import a dependency fresh under an alias name (e.g. --alias time:time-mason)").option("--tests", "Include test files (use --no-tests to exclude)").action(async (args, flags) => {
|
|
5494
6089
|
if (args && args.length > 0) {
|
|
5495
|
-
|
|
5496
|
-
|
|
6090
|
+
p19.log.error(
|
|
6091
|
+
pc13.red(
|
|
5497
6092
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
5498
6093
|
)
|
|
5499
6094
|
);
|
|
@@ -5504,7 +6099,7 @@ function createCli() {
|
|
|
5504
6099
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
5505
6100
|
if (mode === "registry") {
|
|
5506
6101
|
const choice2 = handleCancel(
|
|
5507
|
-
await
|
|
6102
|
+
await p19.select({
|
|
5508
6103
|
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
5509
6104
|
options: [
|
|
5510
6105
|
{
|
|
@@ -5547,7 +6142,7 @@ function createCli() {
|
|
|
5547
6142
|
return;
|
|
5548
6143
|
}
|
|
5549
6144
|
const choice = handleCancel(
|
|
5550
|
-
await
|
|
6145
|
+
await p19.select({
|
|
5551
6146
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
5552
6147
|
options: [
|
|
5553
6148
|
{
|
|
@@ -5566,9 +6161,9 @@ function createCli() {
|
|
|
5566
6161
|
hint: "Scan codebase and verify Clean Architecture dependency rules"
|
|
5567
6162
|
},
|
|
5568
6163
|
{
|
|
5569
|
-
value: "
|
|
5570
|
-
label: "
|
|
5571
|
-
hint: "
|
|
6164
|
+
value: "env",
|
|
6165
|
+
label: "Environment variables",
|
|
6166
|
+
hint: "Manage .env files, add/delete keys, and sync templates"
|
|
5572
6167
|
}
|
|
5573
6168
|
]
|
|
5574
6169
|
})
|
|
@@ -5579,8 +6174,8 @@ function createCli() {
|
|
|
5579
6174
|
await handleImport(void 0, void 0, flags);
|
|
5580
6175
|
} else if (choice === "lint") {
|
|
5581
6176
|
await handleLint(flags);
|
|
5582
|
-
} else if (choice === "
|
|
5583
|
-
await
|
|
6177
|
+
} else if (choice === "env") {
|
|
6178
|
+
await handleEnv(void 0, [], flags);
|
|
5584
6179
|
}
|
|
5585
6180
|
} catch (err) {
|
|
5586
6181
|
handleCliError(err);
|
|
@@ -5628,10 +6223,20 @@ function createCli() {
|
|
|
5628
6223
|
handleCliError(err);
|
|
5629
6224
|
}
|
|
5630
6225
|
});
|
|
6226
|
+
cli.command(
|
|
6227
|
+
"env [subcommand] [...args]",
|
|
6228
|
+
"Environment variable management and template synchronization (init, init-examples, add, update, delete, audit)"
|
|
6229
|
+
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-e, --env <environments>", "Comma-separated target environments (e.g. dev,prod)").example("mason env").example("mason env init").example("mason env init-examples").example("mason env add API_KEY secret123").example("mason env update API_KEY newsecret").example("mason env delete API_KEY").example("mason env audit").action(async (subcommand, args = [], flags = {}) => {
|
|
6230
|
+
try {
|
|
6231
|
+
await handleEnv(subcommand, args, flags);
|
|
6232
|
+
} catch (err) {
|
|
6233
|
+
handleCliError(err);
|
|
6234
|
+
}
|
|
6235
|
+
});
|
|
5631
6236
|
cli.command(
|
|
5632
6237
|
"init [type]",
|
|
5633
|
-
"Initialize project configurations
|
|
5634
|
-
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").example("mason init env").example("mason init env
|
|
6238
|
+
"Initialize project configurations (legacy shorthand: use 'mason env init')"
|
|
6239
|
+
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").example("mason init env").example("mason init env-examples").action(async (type, flags = {}) => {
|
|
5635
6240
|
try {
|
|
5636
6241
|
await handleInit(type, flags);
|
|
5637
6242
|
} catch (err) {
|
|
@@ -5640,10 +6245,10 @@ function createCli() {
|
|
|
5640
6245
|
});
|
|
5641
6246
|
cli.command(
|
|
5642
6247
|
"env-examples",
|
|
5643
|
-
"Shorthand for 'mason init
|
|
6248
|
+
"Shorthand for 'mason env init-examples'"
|
|
5644
6249
|
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing .env*.example files").action(async (flags = {}) => {
|
|
5645
6250
|
try {
|
|
5646
|
-
await
|
|
6251
|
+
await handleEnv("init-examples", [], flags);
|
|
5647
6252
|
} catch (err) {
|
|
5648
6253
|
handleCliError(err);
|
|
5649
6254
|
}
|
|
@@ -5679,7 +6284,7 @@ function handleCliError(err) {
|
|
|
5679
6284
|
process.exit(0);
|
|
5680
6285
|
}
|
|
5681
6286
|
const message = err instanceof Error ? err.message : String(err);
|
|
5682
|
-
|
|
6287
|
+
p19.log.error(pc13.red(`Error: ${message}`));
|
|
5683
6288
|
if (process.env.VITEST) {
|
|
5684
6289
|
throw err;
|
|
5685
6290
|
}
|