@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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs4 from 'fs';
|
|
2
2
|
import path2 from 'path';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
3
|
+
import * as p19 from '@clack/prompts';
|
|
4
|
+
import pc14 from 'picocolors';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
@@ -112,17 +112,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
112
112
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
113
113
|
console.log("");
|
|
114
114
|
if (isDryRun) {
|
|
115
|
-
|
|
115
|
+
p19.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
116
116
|
}
|
|
117
117
|
for (const f of files) {
|
|
118
118
|
if (f.action === "created") {
|
|
119
|
-
|
|
119
|
+
p19.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
|
|
120
120
|
} else if (f.action === "overwritten") {
|
|
121
|
-
|
|
121
|
+
p19.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
|
|
122
122
|
} else if (f.action === "skipped") {
|
|
123
|
-
|
|
123
|
+
p19.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
124
124
|
} else {
|
|
125
|
-
|
|
125
|
+
p19.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -324,23 +324,23 @@ var PromptCancelledError = class extends Error {
|
|
|
324
324
|
}
|
|
325
325
|
};
|
|
326
326
|
function handleCancel(result) {
|
|
327
|
-
if (
|
|
328
|
-
|
|
327
|
+
if (p19.isCancel(result)) {
|
|
328
|
+
p19.cancel("Operation cancelled.");
|
|
329
329
|
throw new PromptCancelledError();
|
|
330
330
|
}
|
|
331
331
|
return result;
|
|
332
332
|
}
|
|
333
333
|
function printBanner() {
|
|
334
334
|
console.log("");
|
|
335
|
-
|
|
335
|
+
p19.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
|
|
336
336
|
}
|
|
337
337
|
async function promptProperties(options = {}) {
|
|
338
338
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
339
339
|
if (properties.length > 0) {
|
|
340
|
-
const summary = properties.map((
|
|
341
|
-
|
|
340
|
+
const summary = properties.map((p22) => `${p22.name}: ${p22.type}${p22.optional ? "?" : ""}`).join(", ");
|
|
341
|
+
p19.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
|
|
342
342
|
const addMore = handleCancel(
|
|
343
|
-
await
|
|
343
|
+
await p19.confirm({
|
|
344
344
|
message: "Would you like to add custom fields/properties?",
|
|
345
345
|
initialValue: false
|
|
346
346
|
})
|
|
@@ -349,10 +349,10 @@ async function promptProperties(options = {}) {
|
|
|
349
349
|
return properties;
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
p19.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
|
|
353
353
|
while (true) {
|
|
354
354
|
const name = handleCancel(
|
|
355
|
-
await
|
|
355
|
+
await p19.text({
|
|
356
356
|
message: "Property name (or press Enter to finish):",
|
|
357
357
|
placeholder: "e.g. title, email, status"
|
|
358
358
|
})
|
|
@@ -361,7 +361,7 @@ async function promptProperties(options = {}) {
|
|
|
361
361
|
break;
|
|
362
362
|
}
|
|
363
363
|
const type = handleCancel(
|
|
364
|
-
await
|
|
364
|
+
await p19.select({
|
|
365
365
|
message: `Type for property "${name.trim()}":`,
|
|
366
366
|
options: [
|
|
367
367
|
{ value: "string", label: "string" },
|
|
@@ -378,7 +378,7 @@ async function promptProperties(options = {}) {
|
|
|
378
378
|
let finalType = type;
|
|
379
379
|
if (type === "custom") {
|
|
380
380
|
finalType = handleCancel(
|
|
381
|
-
await
|
|
381
|
+
await p19.text({
|
|
382
382
|
message: `Enter custom type for "${name.trim()}":`,
|
|
383
383
|
placeholder: "e.g. UserRole, string | null",
|
|
384
384
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -386,7 +386,7 @@ async function promptProperties(options = {}) {
|
|
|
386
386
|
);
|
|
387
387
|
}
|
|
388
388
|
const optional = handleCancel(
|
|
389
|
-
await
|
|
389
|
+
await p19.confirm({
|
|
390
390
|
message: `Is "${name.trim()}" optional?`,
|
|
391
391
|
initialValue: false
|
|
392
392
|
})
|
|
@@ -396,8 +396,8 @@ async function promptProperties(options = {}) {
|
|
|
396
396
|
type: finalType.trim(),
|
|
397
397
|
optional
|
|
398
398
|
});
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
p19.log.success(
|
|
400
|
+
pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
401
401
|
);
|
|
402
402
|
}
|
|
403
403
|
return properties;
|
|
@@ -605,8 +605,8 @@ function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
|
605
605
|
...Object.keys(pkg.devDependencies || {}),
|
|
606
606
|
...Object.keys(pkg.peerDependencies || {})
|
|
607
607
|
]);
|
|
608
|
-
return requiredPackages.filter((
|
|
609
|
-
const nameOnly =
|
|
608
|
+
return requiredPackages.filter((p22) => {
|
|
609
|
+
const nameOnly = p22.startsWith("@") ? `@${p22.slice(1).split("@")[0] || ""}` : p22.split("@")[0] || p22;
|
|
610
610
|
return !installed.has(nameOnly);
|
|
611
611
|
});
|
|
612
612
|
} catch {
|
|
@@ -631,7 +631,7 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
631
631
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
632
632
|
break;
|
|
633
633
|
}
|
|
634
|
-
|
|
634
|
+
p19.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
|
|
635
635
|
execSync(installCmd, {
|
|
636
636
|
cwd: projectRoot,
|
|
637
637
|
stdio: "inherit"
|
|
@@ -833,7 +833,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
833
833
|
const idKey = `${entityKey}Id`;
|
|
834
834
|
const listKey = `${entityKey}s`;
|
|
835
835
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
836
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
836
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p22) => `${p22.name}: ${p22.type}`).join("; ") : "";
|
|
837
837
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
838
838
|
return {
|
|
839
839
|
name: m.name,
|
|
@@ -1414,22 +1414,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1414
1414
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1415
1415
|
}));
|
|
1416
1416
|
const domainTypesToImport = new Set(domainImports);
|
|
1417
|
-
for (const
|
|
1418
|
-
const rawType =
|
|
1417
|
+
for (const p22 of [...inputProps, ...outputProps]) {
|
|
1418
|
+
const rawType = p22.type.trim();
|
|
1419
1419
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1420
1420
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1421
1421
|
domainTypesToImport.add(baseType);
|
|
1422
1422
|
}
|
|
1423
1423
|
}
|
|
1424
|
-
const formattedInputProps = inputProps.map((
|
|
1425
|
-
name:
|
|
1426
|
-
optional:
|
|
1427
|
-
type:
|
|
1424
|
+
const formattedInputProps = inputProps.map((p22) => ({
|
|
1425
|
+
name: p22.name,
|
|
1426
|
+
optional: p22.optional,
|
|
1427
|
+
type: p22.optional && !p22.type.includes("undefined") ? `${p22.type} | undefined` : p22.type
|
|
1428
1428
|
}));
|
|
1429
|
-
const formattedOutputProps = outputProps.map((
|
|
1430
|
-
name:
|
|
1431
|
-
optional:
|
|
1432
|
-
type:
|
|
1429
|
+
const formattedOutputProps = outputProps.map((p22) => ({
|
|
1430
|
+
name: p22.name,
|
|
1431
|
+
optional: p22.optional,
|
|
1432
|
+
type: p22.optional && !p22.type.includes("undefined") ? `${p22.type} | undefined` : p22.type
|
|
1433
1433
|
}));
|
|
1434
1434
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1435
1435
|
key: toCamelCase(dep),
|
|
@@ -2104,7 +2104,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2104
2104
|
}
|
|
2105
2105
|
if (!name) {
|
|
2106
2106
|
name = handleCancel(
|
|
2107
|
-
await
|
|
2107
|
+
await p19.text({
|
|
2108
2108
|
message: "What is the feature name?",
|
|
2109
2109
|
placeholder: "e.g. users, auth, orders, billing",
|
|
2110
2110
|
validate: (val) => {
|
|
@@ -2120,7 +2120,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2120
2120
|
let targetDirRel = defaultDir;
|
|
2121
2121
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2122
2122
|
targetDirRel = handleCancel(
|
|
2123
|
-
await
|
|
2123
|
+
await p19.text({
|
|
2124
2124
|
message: "Target directory for the feature:",
|
|
2125
2125
|
initialValue: defaultDir,
|
|
2126
2126
|
defaultValue: defaultDir
|
|
@@ -2142,14 +2142,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2142
2142
|
const customUseCases = [];
|
|
2143
2143
|
if (!flags.yes) {
|
|
2144
2144
|
includeExpress = handleCancel(
|
|
2145
|
-
await
|
|
2145
|
+
await p19.confirm({
|
|
2146
2146
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
2147
2147
|
initialValue: true
|
|
2148
2148
|
})
|
|
2149
2149
|
);
|
|
2150
2150
|
if (includeExpress) {
|
|
2151
2151
|
routePath = handleCancel(
|
|
2152
|
-
await
|
|
2152
|
+
await p19.text({
|
|
2153
2153
|
message: "Express route prefix:",
|
|
2154
2154
|
initialValue: `/${normalizedName}`,
|
|
2155
2155
|
defaultValue: `/${normalizedName}`
|
|
@@ -2157,14 +2157,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2157
2157
|
).trim();
|
|
2158
2158
|
}
|
|
2159
2159
|
includeDomain = handleCancel(
|
|
2160
|
-
await
|
|
2160
|
+
await p19.confirm({
|
|
2161
2161
|
message: "Include Domain Entity/Entities?",
|
|
2162
2162
|
initialValue: true
|
|
2163
2163
|
})
|
|
2164
2164
|
);
|
|
2165
2165
|
if (includeDomain) {
|
|
2166
2166
|
const domainInput = handleCancel(
|
|
2167
|
-
await
|
|
2167
|
+
await p19.text({
|
|
2168
2168
|
message: "Domain entity name(s) (comma-separated):",
|
|
2169
2169
|
initialValue: defaultEntity,
|
|
2170
2170
|
defaultValue: defaultEntity,
|
|
@@ -2183,21 +2183,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2183
2183
|
entityNames = [defaultEntity];
|
|
2184
2184
|
}
|
|
2185
2185
|
includeInterface = handleCancel(
|
|
2186
|
-
await
|
|
2186
|
+
await p19.confirm({
|
|
2187
2187
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
2188
2188
|
initialValue: true
|
|
2189
2189
|
})
|
|
2190
2190
|
);
|
|
2191
2191
|
if (includeInterface) {
|
|
2192
2192
|
includeInfra = handleCancel(
|
|
2193
|
-
await
|
|
2193
|
+
await p19.confirm({
|
|
2194
2194
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
2195
2195
|
initialValue: true
|
|
2196
2196
|
})
|
|
2197
2197
|
);
|
|
2198
2198
|
}
|
|
2199
2199
|
includeUseCase = handleCancel(
|
|
2200
|
-
await
|
|
2200
|
+
await p19.confirm({
|
|
2201
2201
|
message: "Include Domain Use Cases?",
|
|
2202
2202
|
initialValue: true
|
|
2203
2203
|
})
|
|
@@ -2211,7 +2211,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2211
2211
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
2212
2212
|
];
|
|
2213
2213
|
crudOperations = handleCancel(
|
|
2214
|
-
await
|
|
2214
|
+
await p19.multiselect({
|
|
2215
2215
|
message: "Select standard CRUD use cases to generate:",
|
|
2216
2216
|
options: crudChoices,
|
|
2217
2217
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -2219,13 +2219,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2219
2219
|
})
|
|
2220
2220
|
);
|
|
2221
2221
|
includeTests = handleCancel(
|
|
2222
|
-
await
|
|
2222
|
+
await p19.confirm({
|
|
2223
2223
|
message: "Generate Vitest unit tests for use cases?",
|
|
2224
2224
|
initialValue: true
|
|
2225
2225
|
})
|
|
2226
2226
|
);
|
|
2227
2227
|
const wantCustom = handleCancel(
|
|
2228
|
-
await
|
|
2228
|
+
await p19.confirm({
|
|
2229
2229
|
message: "Do you want to add any custom use cases to this feature?",
|
|
2230
2230
|
initialValue: false
|
|
2231
2231
|
})
|
|
@@ -2235,7 +2235,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2235
2235
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
2236
2236
|
while (addMore) {
|
|
2237
2237
|
const customName = handleCancel(
|
|
2238
|
-
await
|
|
2238
|
+
await p19.text({
|
|
2239
2239
|
message: "Custom Use Case name:",
|
|
2240
2240
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
2241
2241
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2245,7 +2245,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2245
2245
|
let selectedDeps = [];
|
|
2246
2246
|
if (availableIfaces.length > 0) {
|
|
2247
2247
|
selectedDeps = handleCancel(
|
|
2248
|
-
await
|
|
2248
|
+
await p19.multiselect({
|
|
2249
2249
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
2250
2250
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
2251
2251
|
required: false
|
|
@@ -2254,7 +2254,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2254
2254
|
}
|
|
2255
2255
|
let customInputProps = [];
|
|
2256
2256
|
const hasInput = handleCancel(
|
|
2257
|
-
await
|
|
2257
|
+
await p19.confirm({
|
|
2258
2258
|
message: `Does ${customUcName} accept input arguments?`,
|
|
2259
2259
|
initialValue: true
|
|
2260
2260
|
})
|
|
@@ -2265,7 +2265,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2265
2265
|
}
|
|
2266
2266
|
let customOutputProps = [];
|
|
2267
2267
|
const hasCustomOutput = handleCancel(
|
|
2268
|
-
await
|
|
2268
|
+
await p19.confirm({
|
|
2269
2269
|
message: `Does ${customUcName} return custom output data?`,
|
|
2270
2270
|
initialValue: false
|
|
2271
2271
|
})
|
|
@@ -2275,7 +2275,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2275
2275
|
});
|
|
2276
2276
|
}
|
|
2277
2277
|
const customIncludeTest = handleCancel(
|
|
2278
|
-
await
|
|
2278
|
+
await p19.confirm({
|
|
2279
2279
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2280
2280
|
initialValue: true
|
|
2281
2281
|
})
|
|
@@ -2285,14 +2285,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2285
2285
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2286
2286
|
if (includeExpress) {
|
|
2287
2287
|
customCreateHttp = handleCancel(
|
|
2288
|
-
await
|
|
2288
|
+
await p19.confirm({
|
|
2289
2289
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2290
2290
|
initialValue: true
|
|
2291
2291
|
})
|
|
2292
2292
|
);
|
|
2293
2293
|
if (customCreateHttp) {
|
|
2294
2294
|
customHttpMethod = handleCancel(
|
|
2295
|
-
await
|
|
2295
|
+
await p19.select({
|
|
2296
2296
|
message: "HTTP method for route:",
|
|
2297
2297
|
options: [
|
|
2298
2298
|
{ value: "post", label: "POST" },
|
|
@@ -2305,7 +2305,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2305
2305
|
})
|
|
2306
2306
|
);
|
|
2307
2307
|
customHttpPath = handleCancel(
|
|
2308
|
-
await
|
|
2308
|
+
await p19.text({
|
|
2309
2309
|
message: "Route subpath (relative to feature prefix):",
|
|
2310
2310
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2311
2311
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2326,7 +2326,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2326
2326
|
validationFields: customInputProps
|
|
2327
2327
|
});
|
|
2328
2328
|
addMore = handleCancel(
|
|
2329
|
-
await
|
|
2329
|
+
await p19.confirm({
|
|
2330
2330
|
message: "Add another custom use case?",
|
|
2331
2331
|
initialValue: false
|
|
2332
2332
|
})
|
|
@@ -2335,7 +2335,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2335
2335
|
}
|
|
2336
2336
|
}
|
|
2337
2337
|
includeDiProvider = handleCancel(
|
|
2338
|
-
await
|
|
2338
|
+
await p19.confirm({
|
|
2339
2339
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2340
2340
|
initialValue: true
|
|
2341
2341
|
})
|
|
@@ -2347,8 +2347,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2347
2347
|
entityNames = [defaultEntity];
|
|
2348
2348
|
}
|
|
2349
2349
|
}
|
|
2350
|
-
const s =
|
|
2351
|
-
s.start(`Generating feature '${
|
|
2350
|
+
const s = p19.spinner();
|
|
2351
|
+
s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
|
|
2352
2352
|
const options = {
|
|
2353
2353
|
name: normalizedName,
|
|
2354
2354
|
projectRoot,
|
|
@@ -2394,8 +2394,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2394
2394
|
}
|
|
2395
2395
|
s.stop("Feature scaffolded successfully!");
|
|
2396
2396
|
printGeneratedFiles(files, options.dryRun);
|
|
2397
|
-
|
|
2398
|
-
|
|
2397
|
+
p19.outro(
|
|
2398
|
+
pc14.green(
|
|
2399
2399
|
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2400
2400
|
)
|
|
2401
2401
|
);
|
|
@@ -2509,7 +2509,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2509
2509
|
}
|
|
2510
2510
|
if (!name) {
|
|
2511
2511
|
name = handleCancel(
|
|
2512
|
-
await
|
|
2512
|
+
await p19.text({
|
|
2513
2513
|
message: "What is the shared module name?",
|
|
2514
2514
|
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2515
2515
|
validate: (val) => {
|
|
@@ -2525,7 +2525,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2525
2525
|
let targetDirRel = defaultDir;
|
|
2526
2526
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2527
2527
|
targetDirRel = handleCancel(
|
|
2528
|
-
await
|
|
2528
|
+
await p19.text({
|
|
2529
2529
|
message: "Target directory for the shared module:",
|
|
2530
2530
|
initialValue: defaultDir,
|
|
2531
2531
|
defaultValue: defaultDir
|
|
@@ -2537,21 +2537,21 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2537
2537
|
let includeStub = true;
|
|
2538
2538
|
if (!flags.yes) {
|
|
2539
2539
|
description = handleCancel(
|
|
2540
|
-
await
|
|
2540
|
+
await p19.text({
|
|
2541
2541
|
message: "Module description (optional):",
|
|
2542
2542
|
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2543
2543
|
initialValue: ""
|
|
2544
2544
|
})
|
|
2545
2545
|
).trim();
|
|
2546
2546
|
includeStub = handleCancel(
|
|
2547
|
-
await
|
|
2547
|
+
await p19.confirm({
|
|
2548
2548
|
message: "Include stub implementation in infrastructure/?",
|
|
2549
2549
|
initialValue: true
|
|
2550
2550
|
})
|
|
2551
2551
|
);
|
|
2552
2552
|
}
|
|
2553
|
-
const s =
|
|
2554
|
-
s.start(`Generating shared module '${
|
|
2553
|
+
const s = p19.spinner();
|
|
2554
|
+
s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
|
|
2555
2555
|
const files = generateShared({
|
|
2556
2556
|
name: normalizedName,
|
|
2557
2557
|
description,
|
|
@@ -2564,8 +2564,8 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2564
2564
|
});
|
|
2565
2565
|
s.stop("Shared module generated successfully!");
|
|
2566
2566
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2567
|
-
|
|
2568
|
-
|
|
2567
|
+
p19.outro(
|
|
2568
|
+
pc14.green(
|
|
2569
2569
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2570
2570
|
)
|
|
2571
2571
|
);
|
|
@@ -2579,14 +2579,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2579
2579
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2580
2580
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2581
2581
|
const selected = handleCancel(
|
|
2582
|
-
await
|
|
2582
|
+
await p19.select({
|
|
2583
2583
|
message: "Select target feature:",
|
|
2584
2584
|
options: featureOptions
|
|
2585
2585
|
})
|
|
2586
2586
|
);
|
|
2587
2587
|
if (selected === "__custom__") {
|
|
2588
2588
|
feature = handleCancel(
|
|
2589
|
-
await
|
|
2589
|
+
await p19.text({
|
|
2590
2590
|
message: "Enter feature name:",
|
|
2591
2591
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2592
2592
|
})
|
|
@@ -2596,7 +2596,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2596
2596
|
}
|
|
2597
2597
|
} else {
|
|
2598
2598
|
feature = handleCancel(
|
|
2599
|
-
await
|
|
2599
|
+
await p19.text({
|
|
2600
2600
|
message: "Enter target feature name:",
|
|
2601
2601
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2602
2602
|
})
|
|
@@ -2607,7 +2607,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2607
2607
|
let name = useCaseNameArg?.trim();
|
|
2608
2608
|
if (!name) {
|
|
2609
2609
|
name = handleCancel(
|
|
2610
|
-
await
|
|
2610
|
+
await p19.text({
|
|
2611
2611
|
message: "Use Case name:",
|
|
2612
2612
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2613
2613
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2623,7 +2623,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2623
2623
|
label: i.name
|
|
2624
2624
|
}));
|
|
2625
2625
|
selectedDeps = handleCancel(
|
|
2626
|
-
await
|
|
2626
|
+
await p19.multiselect({
|
|
2627
2627
|
message: "Select domain interfaces to inject into use case:",
|
|
2628
2628
|
options: multiOptions,
|
|
2629
2629
|
required: false
|
|
@@ -2638,7 +2638,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2638
2638
|
let httpPath = "/";
|
|
2639
2639
|
if (!flags.yes) {
|
|
2640
2640
|
const hasInput = handleCancel(
|
|
2641
|
-
await
|
|
2641
|
+
await p19.confirm({
|
|
2642
2642
|
message: "Does this use case accept input arguments?",
|
|
2643
2643
|
initialValue: true
|
|
2644
2644
|
})
|
|
@@ -2648,7 +2648,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2648
2648
|
});
|
|
2649
2649
|
}
|
|
2650
2650
|
const hasCustomOutput = handleCancel(
|
|
2651
|
-
await
|
|
2651
|
+
await p19.confirm({
|
|
2652
2652
|
message: "Does this use case return custom output data?",
|
|
2653
2653
|
initialValue: false
|
|
2654
2654
|
})
|
|
@@ -2658,20 +2658,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2658
2658
|
});
|
|
2659
2659
|
}
|
|
2660
2660
|
includeTest = handleCancel(
|
|
2661
|
-
await
|
|
2661
|
+
await p19.confirm({
|
|
2662
2662
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2663
2663
|
initialValue: true
|
|
2664
2664
|
})
|
|
2665
2665
|
);
|
|
2666
2666
|
createHttp = handleCancel(
|
|
2667
|
-
await
|
|
2667
|
+
await p19.confirm({
|
|
2668
2668
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2669
2669
|
initialValue: false
|
|
2670
2670
|
})
|
|
2671
2671
|
);
|
|
2672
2672
|
if (createHttp) {
|
|
2673
2673
|
httpMethod = handleCancel(
|
|
2674
|
-
await
|
|
2674
|
+
await p19.select({
|
|
2675
2675
|
message: "HTTP method for route:",
|
|
2676
2676
|
options: [
|
|
2677
2677
|
{ value: "post", label: "POST" },
|
|
@@ -2684,7 +2684,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2684
2684
|
})
|
|
2685
2685
|
);
|
|
2686
2686
|
httpPath = handleCancel(
|
|
2687
|
-
await
|
|
2687
|
+
await p19.text({
|
|
2688
2688
|
message: "Route subpath (relative to feature prefix):",
|
|
2689
2689
|
initialValue: "/",
|
|
2690
2690
|
defaultValue: "/"
|
|
@@ -2692,8 +2692,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2692
2692
|
).trim();
|
|
2693
2693
|
}
|
|
2694
2694
|
}
|
|
2695
|
-
const s =
|
|
2696
|
-
s.start(`Generating use case '${
|
|
2695
|
+
const s = p19.spinner();
|
|
2696
|
+
s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
|
|
2697
2697
|
const options = {
|
|
2698
2698
|
name: ucName,
|
|
2699
2699
|
feature,
|
|
@@ -2725,7 +2725,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2725
2725
|
}
|
|
2726
2726
|
s.stop("Use case generated successfully!");
|
|
2727
2727
|
printGeneratedFiles(files, options.dryRun);
|
|
2728
|
-
|
|
2728
|
+
p19.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2729
2729
|
}
|
|
2730
2730
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2731
2731
|
const projectRoot = findProjectRoot();
|
|
@@ -2736,14 +2736,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2736
2736
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2737
2737
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2738
2738
|
const selected = handleCancel(
|
|
2739
|
-
await
|
|
2739
|
+
await p19.select({
|
|
2740
2740
|
message: "Select target feature:",
|
|
2741
2741
|
options: featureOptions
|
|
2742
2742
|
})
|
|
2743
2743
|
);
|
|
2744
2744
|
if (selected === "__custom__") {
|
|
2745
2745
|
feature = handleCancel(
|
|
2746
|
-
await
|
|
2746
|
+
await p19.text({
|
|
2747
2747
|
message: "Enter feature name:",
|
|
2748
2748
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2749
2749
|
})
|
|
@@ -2753,7 +2753,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2753
2753
|
}
|
|
2754
2754
|
} else {
|
|
2755
2755
|
feature = handleCancel(
|
|
2756
|
-
await
|
|
2756
|
+
await p19.text({
|
|
2757
2757
|
message: "Enter target feature name:",
|
|
2758
2758
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2759
2759
|
})
|
|
@@ -2764,7 +2764,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2764
2764
|
let name = domainNameArg?.trim();
|
|
2765
2765
|
if (!name) {
|
|
2766
2766
|
name = handleCancel(
|
|
2767
|
-
await
|
|
2767
|
+
await p19.text({
|
|
2768
2768
|
message: "Domain model / entity name:",
|
|
2769
2769
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2770
2770
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2776,7 +2776,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2776
2776
|
let properties = [];
|
|
2777
2777
|
if (!flags.yes) {
|
|
2778
2778
|
kind = handleCancel(
|
|
2779
|
-
await
|
|
2779
|
+
await p19.select({
|
|
2780
2780
|
message: "Select domain model type:",
|
|
2781
2781
|
options: [
|
|
2782
2782
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2795,8 +2795,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2795
2795
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2796
2796
|
});
|
|
2797
2797
|
}
|
|
2798
|
-
const s =
|
|
2799
|
-
s.start(`Generating domain model '${
|
|
2798
|
+
const s = p19.spinner();
|
|
2799
|
+
s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
|
|
2800
2800
|
const options = {
|
|
2801
2801
|
name: entityName,
|
|
2802
2802
|
feature,
|
|
@@ -2809,7 +2809,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2809
2809
|
const file = generateDomain(options);
|
|
2810
2810
|
s.stop("Domain model generated successfully!");
|
|
2811
2811
|
printGeneratedFiles([file], options.dryRun);
|
|
2812
|
-
|
|
2812
|
+
p19.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2813
2813
|
}
|
|
2814
2814
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2815
2815
|
const projectRoot = findProjectRoot();
|
|
@@ -2818,7 +2818,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2818
2818
|
let feature = flags.feature?.trim();
|
|
2819
2819
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2820
2820
|
scope = handleCancel(
|
|
2821
|
-
await
|
|
2821
|
+
await p19.select({
|
|
2822
2822
|
message: "Where should this infrastructure implementation be placed?",
|
|
2823
2823
|
options: [
|
|
2824
2824
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2833,14 +2833,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2833
2833
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2834
2834
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2835
2835
|
const selected = handleCancel(
|
|
2836
|
-
await
|
|
2836
|
+
await p19.select({
|
|
2837
2837
|
message: "Select target feature:",
|
|
2838
2838
|
options: featureOptions
|
|
2839
2839
|
})
|
|
2840
2840
|
);
|
|
2841
2841
|
if (selected === "__custom__") {
|
|
2842
2842
|
feature = handleCancel(
|
|
2843
|
-
await
|
|
2843
|
+
await p19.text({
|
|
2844
2844
|
message: "Enter feature name:",
|
|
2845
2845
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2846
2846
|
})
|
|
@@ -2850,7 +2850,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2850
2850
|
}
|
|
2851
2851
|
} else {
|
|
2852
2852
|
feature = handleCancel(
|
|
2853
|
-
await
|
|
2853
|
+
await p19.text({
|
|
2854
2854
|
message: "Enter target feature name:",
|
|
2855
2855
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2856
2856
|
})
|
|
@@ -2863,7 +2863,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2863
2863
|
let name = classNameArg?.trim();
|
|
2864
2864
|
if (!name) {
|
|
2865
2865
|
name = handleCancel(
|
|
2866
|
-
await
|
|
2866
|
+
await p19.text({
|
|
2867
2867
|
message: "Infrastructure class name:",
|
|
2868
2868
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2869
2869
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2882,7 +2882,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2882
2882
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2883
2883
|
);
|
|
2884
2884
|
const ifaceChoice = handleCancel(
|
|
2885
|
-
await
|
|
2885
|
+
await p19.select({
|
|
2886
2886
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2887
2887
|
options: [
|
|
2888
2888
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2894,7 +2894,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2894
2894
|
);
|
|
2895
2895
|
if (ifaceChoice === "__custom__") {
|
|
2896
2896
|
implementsInterface = handleCancel(
|
|
2897
|
-
await
|
|
2897
|
+
await p19.text({
|
|
2898
2898
|
message: "Enter interface or abstract class name:",
|
|
2899
2899
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2900
2900
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2905,14 +2905,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2905
2905
|
}
|
|
2906
2906
|
} else {
|
|
2907
2907
|
const wantImpl = handleCancel(
|
|
2908
|
-
await
|
|
2908
|
+
await p19.confirm({
|
|
2909
2909
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2910
2910
|
initialValue: false
|
|
2911
2911
|
})
|
|
2912
2912
|
);
|
|
2913
2913
|
if (wantImpl) {
|
|
2914
2914
|
implementsInterface = handleCancel(
|
|
2915
|
-
await
|
|
2915
|
+
await p19.text({
|
|
2916
2916
|
message: "Enter interface or abstract class name:",
|
|
2917
2917
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2918
2918
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2932,7 +2932,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2932
2932
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2933
2933
|
if (allInterfaces.length > 0) {
|
|
2934
2934
|
const ifaceChoice = handleCancel(
|
|
2935
|
-
await
|
|
2935
|
+
await p19.select({
|
|
2936
2936
|
message: "Does this class implement an interface or abstract class?",
|
|
2937
2937
|
options: [
|
|
2938
2938
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2944,7 +2944,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2944
2944
|
);
|
|
2945
2945
|
if (ifaceChoice === "__custom__") {
|
|
2946
2946
|
implementsInterface = handleCancel(
|
|
2947
|
-
await
|
|
2947
|
+
await p19.text({
|
|
2948
2948
|
message: "Enter interface or abstract class name:",
|
|
2949
2949
|
placeholder: "e.g. IAuthRepository",
|
|
2950
2950
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2957,7 +2957,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2957
2957
|
}
|
|
2958
2958
|
if (!flags.yes && !flags.kind) {
|
|
2959
2959
|
kind = handleCancel(
|
|
2960
|
-
await
|
|
2960
|
+
await p19.select({
|
|
2961
2961
|
message: "Select implementation kind:",
|
|
2962
2962
|
options: [
|
|
2963
2963
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2968,7 +2968,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2968
2968
|
);
|
|
2969
2969
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2970
2970
|
registerInProvider = handleCancel(
|
|
2971
|
-
await
|
|
2971
|
+
await p19.confirm({
|
|
2972
2972
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2973
2973
|
initialValue: true
|
|
2974
2974
|
})
|
|
@@ -2977,8 +2977,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2977
2977
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2978
2978
|
registerInProvider = !flags.noProvider;
|
|
2979
2979
|
}
|
|
2980
|
-
const s =
|
|
2981
|
-
s.start(`Generating infrastructure class '${
|
|
2980
|
+
const s = p19.spinner();
|
|
2981
|
+
s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
|
|
2982
2982
|
const files = [];
|
|
2983
2983
|
const options = {
|
|
2984
2984
|
name: className,
|
|
@@ -3013,7 +3013,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
3013
3013
|
}
|
|
3014
3014
|
s.stop("Infrastructure class generated successfully!");
|
|
3015
3015
|
printGeneratedFiles(files, options.dryRun);
|
|
3016
|
-
|
|
3016
|
+
p19.outro(pc14.green(`Infrastructure class '${className}' created!`));
|
|
3017
3017
|
}
|
|
3018
3018
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
3019
3019
|
const projectRoot = findProjectRoot();
|
|
@@ -3024,14 +3024,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3024
3024
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3025
3025
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3026
3026
|
const selected = handleCancel(
|
|
3027
|
-
await
|
|
3027
|
+
await p19.select({
|
|
3028
3028
|
message: "Select target feature:",
|
|
3029
3029
|
options: featureOptions
|
|
3030
3030
|
})
|
|
3031
3031
|
);
|
|
3032
3032
|
if (selected === "__custom__") {
|
|
3033
3033
|
feature = handleCancel(
|
|
3034
|
-
await
|
|
3034
|
+
await p19.text({
|
|
3035
3035
|
message: "Enter feature name:",
|
|
3036
3036
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3037
3037
|
})
|
|
@@ -3041,7 +3041,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3041
3041
|
}
|
|
3042
3042
|
} else {
|
|
3043
3043
|
feature = handleCancel(
|
|
3044
|
-
await
|
|
3044
|
+
await p19.text({
|
|
3045
3045
|
message: "Enter target feature name:",
|
|
3046
3046
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3047
3047
|
})
|
|
@@ -3052,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3052
3052
|
let name = interfaceNameArg?.trim();
|
|
3053
3053
|
if (!name) {
|
|
3054
3054
|
name = handleCancel(
|
|
3055
|
-
await
|
|
3055
|
+
await p19.text({
|
|
3056
3056
|
message: "Domain interface name:",
|
|
3057
3057
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
3058
3058
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -3070,7 +3070,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3070
3070
|
if (!flags.yes) {
|
|
3071
3071
|
if (existingEntities.length > 0) {
|
|
3072
3072
|
const selectedEntity = handleCancel(
|
|
3073
|
-
await
|
|
3073
|
+
await p19.select({
|
|
3074
3074
|
message: "Select domain entity associated with this interface:",
|
|
3075
3075
|
options: [
|
|
3076
3076
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -3081,7 +3081,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3081
3081
|
);
|
|
3082
3082
|
if (selectedEntity === "__custom__") {
|
|
3083
3083
|
entityName = handleCancel(
|
|
3084
|
-
await
|
|
3084
|
+
await p19.text({
|
|
3085
3085
|
message: "Enter entity name:",
|
|
3086
3086
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
3087
3087
|
})
|
|
@@ -3091,7 +3091,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3091
3091
|
}
|
|
3092
3092
|
}
|
|
3093
3093
|
kind = handleCancel(
|
|
3094
|
-
await
|
|
3094
|
+
await p19.select({
|
|
3095
3095
|
message: "Interface definition style:",
|
|
3096
3096
|
options: [
|
|
3097
3097
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -3101,7 +3101,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3101
3101
|
})
|
|
3102
3102
|
);
|
|
3103
3103
|
methodsPreset = handleCancel(
|
|
3104
|
-
await
|
|
3104
|
+
await p19.select({
|
|
3105
3105
|
message: "Method signature preset:",
|
|
3106
3106
|
options: [
|
|
3107
3107
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -3111,22 +3111,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3111
3111
|
})
|
|
3112
3112
|
);
|
|
3113
3113
|
generateStub = handleCancel(
|
|
3114
|
-
await
|
|
3114
|
+
await p19.confirm({
|
|
3115
3115
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
3116
3116
|
initialValue: true
|
|
3117
3117
|
})
|
|
3118
3118
|
);
|
|
3119
3119
|
if (generateStub) {
|
|
3120
3120
|
registerInProvider = handleCancel(
|
|
3121
|
-
await
|
|
3121
|
+
await p19.confirm({
|
|
3122
3122
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
3123
3123
|
initialValue: true
|
|
3124
3124
|
})
|
|
3125
3125
|
);
|
|
3126
3126
|
}
|
|
3127
3127
|
}
|
|
3128
|
-
const s =
|
|
3129
|
-
s.start(`Generating interface '${
|
|
3128
|
+
const s = p19.spinner();
|
|
3129
|
+
s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
|
|
3130
3130
|
const files = [];
|
|
3131
3131
|
const options = {
|
|
3132
3132
|
name: ifaceName,
|
|
@@ -3175,7 +3175,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3175
3175
|
}
|
|
3176
3176
|
s.stop("Interface generated successfully!");
|
|
3177
3177
|
printGeneratedFiles(files, options.dryRun);
|
|
3178
|
-
|
|
3178
|
+
p19.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
3179
3179
|
}
|
|
3180
3180
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
3181
3181
|
const projectRoot = findProjectRoot();
|
|
@@ -3186,14 +3186,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3186
3186
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3187
3187
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3188
3188
|
const selected = handleCancel(
|
|
3189
|
-
await
|
|
3189
|
+
await p19.select({
|
|
3190
3190
|
message: "Select target feature:",
|
|
3191
3191
|
options: featureOptions
|
|
3192
3192
|
})
|
|
3193
3193
|
);
|
|
3194
3194
|
if (selected === "__custom__") {
|
|
3195
3195
|
feature = handleCancel(
|
|
3196
|
-
await
|
|
3196
|
+
await p19.text({
|
|
3197
3197
|
message: "Enter feature name:",
|
|
3198
3198
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3199
3199
|
})
|
|
@@ -3203,7 +3203,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3203
3203
|
}
|
|
3204
3204
|
} else {
|
|
3205
3205
|
feature = handleCancel(
|
|
3206
|
-
await
|
|
3206
|
+
await p19.text({
|
|
3207
3207
|
message: "Enter target feature name:",
|
|
3208
3208
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3209
3209
|
})
|
|
@@ -3214,7 +3214,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3214
3214
|
let name = handlerNameArg?.trim();
|
|
3215
3215
|
if (!name) {
|
|
3216
3216
|
name = handleCancel(
|
|
3217
|
-
await
|
|
3217
|
+
await p19.text({
|
|
3218
3218
|
message: "HTTP Route Handler class name:",
|
|
3219
3219
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
3220
3220
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -3233,7 +3233,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3233
3233
|
let validationFields = [];
|
|
3234
3234
|
if (!flags.yes) {
|
|
3235
3235
|
method = handleCancel(
|
|
3236
|
-
await
|
|
3236
|
+
await p19.select({
|
|
3237
3237
|
message: "HTTP Method:",
|
|
3238
3238
|
options: [
|
|
3239
3239
|
{ value: "get", label: "GET" },
|
|
@@ -3247,7 +3247,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3247
3247
|
);
|
|
3248
3248
|
const defaultPath = method === "get" ? "/" : "/";
|
|
3249
3249
|
subPath = handleCancel(
|
|
3250
|
-
await
|
|
3250
|
+
await p19.text({
|
|
3251
3251
|
message: "Route subpath (relative to feature prefix):",
|
|
3252
3252
|
initialValue: defaultPath,
|
|
3253
3253
|
defaultValue: defaultPath
|
|
@@ -3258,7 +3258,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3258
3258
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
3259
3259
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
3260
3260
|
const chosenUc = handleCancel(
|
|
3261
|
-
await
|
|
3261
|
+
await p19.select({
|
|
3262
3262
|
message: "Connect this handler to a domain Use Case?",
|
|
3263
3263
|
options: ucOptions,
|
|
3264
3264
|
initialValue: ucOptions[0]?.value
|
|
@@ -3269,7 +3269,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3269
3269
|
}
|
|
3270
3270
|
}
|
|
3271
3271
|
includeValidation = handleCancel(
|
|
3272
|
-
await
|
|
3272
|
+
await p19.confirm({
|
|
3273
3273
|
message: "Include Zod inbound request validation?",
|
|
3274
3274
|
initialValue: method !== "delete"
|
|
3275
3275
|
})
|
|
@@ -3277,7 +3277,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3277
3277
|
if (includeValidation) {
|
|
3278
3278
|
const defaultSource = method === "get" ? "query" : "body";
|
|
3279
3279
|
validationSource = handleCancel(
|
|
3280
|
-
await
|
|
3280
|
+
await p19.select({
|
|
3281
3281
|
message: "Validation data source:",
|
|
3282
3282
|
options: [
|
|
3283
3283
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -3291,8 +3291,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3291
3291
|
});
|
|
3292
3292
|
}
|
|
3293
3293
|
}
|
|
3294
|
-
const s =
|
|
3295
|
-
s.start(`Generating HTTP handler '${
|
|
3294
|
+
const s = p19.spinner();
|
|
3295
|
+
s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
|
|
3296
3296
|
const options = {
|
|
3297
3297
|
name: className,
|
|
3298
3298
|
feature,
|
|
@@ -3310,7 +3310,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3310
3310
|
const files = generateHttpHandler(options);
|
|
3311
3311
|
s.stop("HTTP handler generated successfully!");
|
|
3312
3312
|
printGeneratedFiles(files, options.dryRun);
|
|
3313
|
-
|
|
3313
|
+
p19.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3314
3314
|
}
|
|
3315
3315
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3316
3316
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3320,7 +3320,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3320
3320
|
const formattedMethods = methods.map((m) => {
|
|
3321
3321
|
const isAsync = m.async !== false;
|
|
3322
3322
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3323
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3323
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p22) => `${p22.name}: ${p22.type}`).join(", ") : "";
|
|
3324
3324
|
return {
|
|
3325
3325
|
name: m.name,
|
|
3326
3326
|
isAsync,
|
|
@@ -3390,7 +3390,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3390
3390
|
let name = classNameArg?.trim();
|
|
3391
3391
|
if (!name) {
|
|
3392
3392
|
name = handleCancel(
|
|
3393
|
-
await
|
|
3393
|
+
await p19.text({
|
|
3394
3394
|
message: "Injectable class name:",
|
|
3395
3395
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3396
3396
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3403,7 +3403,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3403
3403
|
let customPath;
|
|
3404
3404
|
if (!flags.yes) {
|
|
3405
3405
|
location = handleCancel(
|
|
3406
|
-
await
|
|
3406
|
+
await p19.select({
|
|
3407
3407
|
message: "Select target architectural location:",
|
|
3408
3408
|
options: [
|
|
3409
3409
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3421,14 +3421,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3421
3421
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3422
3422
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3423
3423
|
const selected = handleCancel(
|
|
3424
|
-
await
|
|
3424
|
+
await p19.select({
|
|
3425
3425
|
message: "Select target feature:",
|
|
3426
3426
|
options: featureOptions
|
|
3427
3427
|
})
|
|
3428
3428
|
);
|
|
3429
3429
|
if (selected === "__custom__") {
|
|
3430
3430
|
feature = handleCancel(
|
|
3431
|
-
await
|
|
3431
|
+
await p19.text({
|
|
3432
3432
|
message: "Enter feature name:",
|
|
3433
3433
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3434
3434
|
})
|
|
@@ -3438,7 +3438,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3438
3438
|
}
|
|
3439
3439
|
} else {
|
|
3440
3440
|
feature = handleCancel(
|
|
3441
|
-
await
|
|
3441
|
+
await p19.text({
|
|
3442
3442
|
message: "Enter target feature name:",
|
|
3443
3443
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3444
3444
|
})
|
|
@@ -3448,7 +3448,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3448
3448
|
feature = toCamelCase(feature);
|
|
3449
3449
|
} else if (location === "custom") {
|
|
3450
3450
|
customPath = handleCancel(
|
|
3451
|
-
await
|
|
3451
|
+
await p19.text({
|
|
3452
3452
|
message: "Enter destination directory path:",
|
|
3453
3453
|
placeholder: "e.g. src/services or lib/utils",
|
|
3454
3454
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3459,14 +3459,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3459
3459
|
let generateInterface2 = false;
|
|
3460
3460
|
if (!flags.yes) {
|
|
3461
3461
|
generateInterface2 = handleCancel(
|
|
3462
|
-
await
|
|
3462
|
+
await p19.confirm({
|
|
3463
3463
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3464
3464
|
initialValue: false
|
|
3465
3465
|
})
|
|
3466
3466
|
);
|
|
3467
3467
|
}
|
|
3468
|
-
const s =
|
|
3469
|
-
s.start(`Generating injectable '${
|
|
3468
|
+
const s = p19.spinner();
|
|
3469
|
+
s.start(`Generating injectable '${pc14.cyan(className)}'...`);
|
|
3470
3470
|
const options = {
|
|
3471
3471
|
name: className,
|
|
3472
3472
|
feature,
|
|
@@ -3480,17 +3480,17 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3480
3480
|
const files = generateInjectable(options);
|
|
3481
3481
|
s.stop("Injectable class generated successfully!");
|
|
3482
3482
|
printGeneratedFiles(files, options.dryRun);
|
|
3483
|
-
|
|
3483
|
+
p19.outro(pc14.green(`Injectable '${className}' created successfully!`));
|
|
3484
3484
|
}
|
|
3485
|
-
async function
|
|
3485
|
+
async function runEnvInit(flags = {}) {
|
|
3486
3486
|
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3487
|
-
const s =
|
|
3487
|
+
const s = p19.spinner();
|
|
3488
3488
|
s.start("Scanning for .example environment files...");
|
|
3489
3489
|
const files = fs4.readdirSync(projectRoot);
|
|
3490
3490
|
const exampleFiles = files.filter((file) => file.startsWith(".env") && file.endsWith(".example")).sort();
|
|
3491
3491
|
if (exampleFiles.length === 0) {
|
|
3492
3492
|
s.stop("No .example files found.");
|
|
3493
|
-
|
|
3493
|
+
p19.log.info(pc14.yellow(`No files matching '.env*.example' were found in ${projectRoot}`));
|
|
3494
3494
|
return [];
|
|
3495
3495
|
}
|
|
3496
3496
|
s.message(`Found ${exampleFiles.length} template(s). Generating...`);
|
|
@@ -3499,28 +3499,141 @@ async function runEnvCommand(flags = {}) {
|
|
|
3499
3499
|
const sourcePath = path2.join(projectRoot, exampleFile);
|
|
3500
3500
|
const targetFile = exampleFile.replace(/\.example$/, "");
|
|
3501
3501
|
const targetPath = path2.join(projectRoot, targetFile);
|
|
3502
|
+
let overwrite = flags.overwrite;
|
|
3503
|
+
if (overwrite === void 0 && !flags.dryRun && !flags.yes && !process.env.VITEST && process.stdin.isTTY && fs4.existsSync(targetPath)) {
|
|
3504
|
+
overwrite = await p19.confirm({
|
|
3505
|
+
message: `${targetFile} already exists. Overwrite?`,
|
|
3506
|
+
initialValue: false
|
|
3507
|
+
});
|
|
3508
|
+
if (p19.isCancel(overwrite)) {
|
|
3509
|
+
p19.cancel("Operation cancelled.");
|
|
3510
|
+
process.exit(0);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3502
3513
|
const content = fs4.readFileSync(sourcePath, "utf-8");
|
|
3503
3514
|
const genFile = writeFileSafely(targetPath, content, {
|
|
3504
3515
|
projectRoot,
|
|
3505
3516
|
dryRun: Boolean(flags.dryRun),
|
|
3506
|
-
overwrite: Boolean(
|
|
3517
|
+
overwrite: Boolean(overwrite)
|
|
3507
3518
|
});
|
|
3508
3519
|
generatedFiles.push(genFile);
|
|
3509
3520
|
}
|
|
3510
3521
|
s.stop("Environment files processed!");
|
|
3511
3522
|
printGeneratedFiles(generatedFiles, Boolean(flags.dryRun));
|
|
3512
|
-
|
|
3523
|
+
p19.outro(pc14.green("\u2713 Local environment files are ready!"));
|
|
3513
3524
|
return generatedFiles;
|
|
3514
3525
|
}
|
|
3515
|
-
|
|
3526
|
+
var runEnvCommand = runEnvInit;
|
|
3527
|
+
var EnvEditor = class {
|
|
3528
|
+
lines;
|
|
3529
|
+
filePath;
|
|
3530
|
+
constructor(filePath) {
|
|
3531
|
+
this.filePath = filePath;
|
|
3532
|
+
this.lines = fs4.existsSync(filePath) ? fs4.readFileSync(filePath, "utf-8").split(/\r?\n/) : [];
|
|
3533
|
+
}
|
|
3534
|
+
getLines() {
|
|
3535
|
+
return [...this.lines];
|
|
3536
|
+
}
|
|
3537
|
+
getKeys() {
|
|
3538
|
+
return this.lines.filter((line) => line.trim() && !line.trim().startsWith("#")).map((line) => {
|
|
3539
|
+
const eqIdx = line.indexOf("=");
|
|
3540
|
+
return eqIdx !== -1 ? line.substring(0, eqIdx).trim() : line.trim();
|
|
3541
|
+
}).filter(Boolean);
|
|
3542
|
+
}
|
|
3543
|
+
getValue(key) {
|
|
3544
|
+
const line = this.lines.find((l) => {
|
|
3545
|
+
const trimmed = l.trim();
|
|
3546
|
+
if (trimmed.startsWith("#") || !trimmed) return false;
|
|
3547
|
+
const eqIdx = l.indexOf("=");
|
|
3548
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3549
|
+
return k === key;
|
|
3550
|
+
});
|
|
3551
|
+
if (!line) return null;
|
|
3552
|
+
const eqIndex = line.indexOf("=");
|
|
3553
|
+
return eqIndex !== -1 ? line.substring(eqIndex + 1).trim() : "";
|
|
3554
|
+
}
|
|
3555
|
+
has(key) {
|
|
3556
|
+
return this.getKeys().includes(key);
|
|
3557
|
+
}
|
|
3558
|
+
set(key, value) {
|
|
3559
|
+
const newLine = `${key}=${value}`;
|
|
3560
|
+
const index = this.lines.findIndex((l) => {
|
|
3561
|
+
const trimmed = l.trim();
|
|
3562
|
+
if (trimmed.startsWith("#") || !trimmed) return false;
|
|
3563
|
+
const eqIdx = l.indexOf("=");
|
|
3564
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3565
|
+
return k === key;
|
|
3566
|
+
});
|
|
3567
|
+
if (index !== -1) {
|
|
3568
|
+
this.lines[index] = newLine;
|
|
3569
|
+
} else {
|
|
3570
|
+
if (this.lines.length > 0 && this.lines[this.lines.length - 1] !== "") {
|
|
3571
|
+
this.lines.push("");
|
|
3572
|
+
}
|
|
3573
|
+
this.lines.push(newLine);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
delete(key) {
|
|
3577
|
+
const initialLen = this.lines.length;
|
|
3578
|
+
this.lines = this.lines.filter((l) => {
|
|
3579
|
+
const trimmed = l.trim();
|
|
3580
|
+
if (trimmed.startsWith("#") || !trimmed) return true;
|
|
3581
|
+
const eqIdx = l.indexOf("=");
|
|
3582
|
+
const k = eqIdx !== -1 ? l.substring(0, eqIdx).trim() : l.trim();
|
|
3583
|
+
return k !== key;
|
|
3584
|
+
});
|
|
3585
|
+
return this.lines.length !== initialLen;
|
|
3586
|
+
}
|
|
3587
|
+
redactAll() {
|
|
3588
|
+
this.lines = this.lines.map((line) => {
|
|
3589
|
+
const trimmed = line.trim();
|
|
3590
|
+
if (trimmed.startsWith("#") || !trimmed) return line;
|
|
3591
|
+
const eqIndex = line.indexOf("=");
|
|
3592
|
+
return eqIndex !== -1 ? line.substring(0, eqIndex + 1) : line;
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
save() {
|
|
3596
|
+
const dir = path2.dirname(this.filePath);
|
|
3597
|
+
if (!fs4.existsSync(dir)) {
|
|
3598
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
3599
|
+
}
|
|
3600
|
+
fs4.writeFileSync(this.filePath, this.lines.join("\n"), "utf-8");
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
function discoverEnvironments(projectRoot) {
|
|
3604
|
+
if (!fs4.existsSync(projectRoot)) {
|
|
3605
|
+
return ["local", "dev", "prod"];
|
|
3606
|
+
}
|
|
3607
|
+
const files = fs4.readdirSync(projectRoot);
|
|
3608
|
+
const envs = /* @__PURE__ */ new Set();
|
|
3609
|
+
for (const file of files) {
|
|
3610
|
+
if (file.startsWith(".env")) {
|
|
3611
|
+
const cleanName = file.replace(/\.example$/, "");
|
|
3612
|
+
const match = cleanName.match(/^\.env\.([a-zA-Z0-9_-]+)$/);
|
|
3613
|
+
if (match && match[1]) {
|
|
3614
|
+
envs.add(match[1]);
|
|
3615
|
+
} else if (cleanName === ".env") {
|
|
3616
|
+
envs.add("local");
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
return envs.size > 0 ? Array.from(envs) : ["local", "dev", "prod"];
|
|
3621
|
+
}
|
|
3622
|
+
function resolveEnvFilename(envName, isExample = false) {
|
|
3623
|
+
const base = envName === "local" ? ".env" : `.env.${envName}`;
|
|
3624
|
+
return isExample ? `${base}.example` : base;
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
// src/commands/env/initExamples.ts
|
|
3628
|
+
async function runEnvInitExamples(flags = {}) {
|
|
3516
3629
|
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3517
|
-
const s =
|
|
3630
|
+
const s = p19.spinner();
|
|
3518
3631
|
s.start("Scanning for local environment files...");
|
|
3519
3632
|
const files = fs4.readdirSync(projectRoot, { withFileTypes: true });
|
|
3520
3633
|
const envFiles = files.filter((dirent) => dirent.isFile() && dirent.name.startsWith(".env") && !dirent.name.endsWith(".example")).map((dirent) => dirent.name).sort();
|
|
3521
3634
|
if (envFiles.length === 0) {
|
|
3522
3635
|
s.stop("No local .env files found.");
|
|
3523
|
-
|
|
3636
|
+
p19.log.info(pc14.yellow(`No files matching '.env*' were found in ${projectRoot}`));
|
|
3524
3637
|
return [];
|
|
3525
3638
|
}
|
|
3526
3639
|
s.message(`Found ${envFiles.length} local .env file(s). Generating templates...`);
|
|
@@ -3529,34 +3642,526 @@ async function runEnvExamplesCommand(flags = {}) {
|
|
|
3529
3642
|
const sourcePath = path2.join(projectRoot, envFile);
|
|
3530
3643
|
const targetFile = `${envFile}.example`;
|
|
3531
3644
|
const targetPath = path2.join(projectRoot, targetFile);
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3645
|
+
let overwrite = flags.overwrite;
|
|
3646
|
+
if (overwrite === void 0 && !flags.dryRun && !flags.yes && !process.env.VITEST && process.stdin.isTTY && fs4.existsSync(targetPath)) {
|
|
3647
|
+
overwrite = await p19.confirm({
|
|
3648
|
+
message: `${targetFile} already exists. Overwrite with redacted values?`,
|
|
3649
|
+
initialValue: true
|
|
3650
|
+
});
|
|
3651
|
+
if (p19.isCancel(overwrite)) {
|
|
3652
|
+
p19.cancel("Operation cancelled.");
|
|
3653
|
+
process.exit(0);
|
|
3539
3654
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3655
|
+
}
|
|
3656
|
+
const editor = new EnvEditor(sourcePath);
|
|
3657
|
+
editor.redactAll();
|
|
3658
|
+
const redactedContent = editor.getLines().join("\n");
|
|
3542
3659
|
const genFile = writeFileSafely(targetPath, redactedContent, {
|
|
3543
3660
|
projectRoot,
|
|
3544
3661
|
dryRun: Boolean(flags.dryRun),
|
|
3545
|
-
overwrite: Boolean(
|
|
3662
|
+
overwrite: Boolean(overwrite)
|
|
3546
3663
|
});
|
|
3547
3664
|
generatedFiles.push(genFile);
|
|
3548
3665
|
}
|
|
3549
3666
|
s.stop("Environment example templates generated!");
|
|
3550
3667
|
printGeneratedFiles(generatedFiles, Boolean(flags.dryRun));
|
|
3551
|
-
|
|
3668
|
+
p19.outro(pc14.green("\u2713 .env*.example files are up to date and safely redacted!"));
|
|
3552
3669
|
return generatedFiles;
|
|
3553
3670
|
}
|
|
3671
|
+
var runEnvExamplesCommand = runEnvInitExamples;
|
|
3672
|
+
function isInteractive(flags) {
|
|
3673
|
+
if (flags.yes || Boolean(flags.nonInteractive)) return false;
|
|
3674
|
+
if (process.env.VITEST) return false;
|
|
3675
|
+
return Boolean(process.stdin.isTTY);
|
|
3676
|
+
}
|
|
3677
|
+
function getAllExistingKeys(projectRoot, envs) {
|
|
3678
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3679
|
+
for (const env of envs) {
|
|
3680
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3681
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3682
|
+
if (fs4.existsSync(localPath)) {
|
|
3683
|
+
new EnvEditor(localPath).getKeys().forEach((k) => keys.add(k));
|
|
3684
|
+
}
|
|
3685
|
+
if (fs4.existsSync(examplePath)) {
|
|
3686
|
+
new EnvEditor(examplePath).getKeys().forEach((k) => keys.add(k));
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
return Array.from(keys).sort();
|
|
3690
|
+
}
|
|
3691
|
+
async function runEnvAdd(key, value, flags = {}) {
|
|
3692
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3693
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3694
|
+
let targetKey = key?.trim();
|
|
3695
|
+
if (!targetKey) {
|
|
3696
|
+
if (!isInteractive(flags)) {
|
|
3697
|
+
throw new Error("Variable key is required for 'mason env add <key> [value]'");
|
|
3698
|
+
}
|
|
3699
|
+
targetKey = handleCancel(
|
|
3700
|
+
await p19.text({
|
|
3701
|
+
message: "Enter the variable key name:",
|
|
3702
|
+
placeholder: "e.g. DATABASE_URL, STRIPE_KEY, PORT",
|
|
3703
|
+
validate: (val) => {
|
|
3704
|
+
if (!val || !val.trim()) return "Variable key name cannot be empty";
|
|
3705
|
+
if (/\s/.test(val.trim())) return "Variable key cannot contain spaces";
|
|
3706
|
+
if (val.includes("=")) return "Variable key cannot contain '='";
|
|
3707
|
+
return void 0;
|
|
3708
|
+
}
|
|
3709
|
+
})
|
|
3710
|
+
).trim();
|
|
3711
|
+
}
|
|
3712
|
+
let targetEnvs = [];
|
|
3713
|
+
if (flags.env) {
|
|
3714
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3715
|
+
} else if (!isInteractive(flags)) {
|
|
3716
|
+
targetEnvs = allEnvs;
|
|
3717
|
+
} else {
|
|
3718
|
+
targetEnvs = handleCancel(
|
|
3719
|
+
await p19.multiselect({
|
|
3720
|
+
message: `Which environments should receive ${pc14.bold(targetKey)}?`,
|
|
3721
|
+
options: allEnvs.map((env) => ({
|
|
3722
|
+
value: env,
|
|
3723
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`
|
|
3724
|
+
})),
|
|
3725
|
+
initialValues: allEnvs,
|
|
3726
|
+
required: true
|
|
3727
|
+
})
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
if (targetEnvs.length === 0) {
|
|
3731
|
+
p19.log.warn("No environments selected. Nothing was changed.");
|
|
3732
|
+
return { key: targetKey, environments: [] };
|
|
3733
|
+
}
|
|
3734
|
+
for (const env of targetEnvs) {
|
|
3735
|
+
let envVal = value;
|
|
3736
|
+
if (envVal === void 0) {
|
|
3737
|
+
if (!isInteractive(flags)) {
|
|
3738
|
+
envVal = "";
|
|
3739
|
+
} else {
|
|
3740
|
+
envVal = handleCancel(
|
|
3741
|
+
await p19.text({
|
|
3742
|
+
message: `Value for ${pc14.cyan(targetKey)} in ${pc14.bold(env)} (${resolveEnvFilename(env)})?`,
|
|
3743
|
+
initialValue: "",
|
|
3744
|
+
placeholder: "Leave empty for blank value"
|
|
3745
|
+
})
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
} else if (isInteractive(flags) && !flags.yes) {
|
|
3749
|
+
envVal = handleCancel(
|
|
3750
|
+
await p19.text({
|
|
3751
|
+
message: `Value for ${pc14.cyan(targetKey)} in ${pc14.bold(env)} (${resolveEnvFilename(env)})?`,
|
|
3752
|
+
initialValue: value ?? "",
|
|
3753
|
+
placeholder: "Leave empty for blank value"
|
|
3754
|
+
})
|
|
3755
|
+
);
|
|
3756
|
+
}
|
|
3757
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3758
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3759
|
+
if (flags.dryRun) {
|
|
3760
|
+
p19.log.info(
|
|
3761
|
+
pc14.cyan(`[DRY RUN] Would set ${targetKey}=${envVal} in ${resolveEnvFilename(env, false)}`)
|
|
3762
|
+
);
|
|
3763
|
+
p19.log.info(
|
|
3764
|
+
pc14.cyan(`[DRY RUN] Would add ${targetKey}= to ${resolveEnvFilename(env, true)}`)
|
|
3765
|
+
);
|
|
3766
|
+
} else {
|
|
3767
|
+
const localEditor = new EnvEditor(localPath);
|
|
3768
|
+
localEditor.set(targetKey, envVal);
|
|
3769
|
+
localEditor.save();
|
|
3770
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3771
|
+
exampleEditor.set(targetKey, "");
|
|
3772
|
+
exampleEditor.save();
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
if (!flags.dryRun) {
|
|
3776
|
+
p19.log.success(
|
|
3777
|
+
pc14.green(
|
|
3778
|
+
`\u2713 Added ${pc14.bold(targetKey)} to [${targetEnvs.join(", ")}] and updated .example templates.`
|
|
3779
|
+
)
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3783
|
+
}
|
|
3784
|
+
async function runEnvUpdate(key, value, flags = {}) {
|
|
3785
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3786
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3787
|
+
const existingKeys = getAllExistingKeys(projectRoot, allEnvs);
|
|
3788
|
+
let targetKey = key?.trim();
|
|
3789
|
+
if (!targetKey) {
|
|
3790
|
+
if (!isInteractive(flags)) {
|
|
3791
|
+
throw new Error("Variable key is required for 'mason env update <key> [value]'");
|
|
3792
|
+
}
|
|
3793
|
+
if (existingKeys.length === 0) {
|
|
3794
|
+
p19.log.error(pc14.red("No environment variables found to update."));
|
|
3795
|
+
return { key: "", environments: [] };
|
|
3796
|
+
}
|
|
3797
|
+
targetKey = handleCancel(
|
|
3798
|
+
await p19.select({
|
|
3799
|
+
message: "Select a variable to update:",
|
|
3800
|
+
options: existingKeys.map((k) => ({ value: k, label: k }))
|
|
3801
|
+
})
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
const envsWithKey = allEnvs.filter((env) => {
|
|
3805
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3806
|
+
return fs4.existsSync(localPath) && new EnvEditor(localPath).has(targetKey);
|
|
3807
|
+
});
|
|
3808
|
+
let targetEnvs = [];
|
|
3809
|
+
if (flags.env) {
|
|
3810
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3811
|
+
} else if (!isInteractive(flags)) {
|
|
3812
|
+
targetEnvs = envsWithKey.length > 0 ? envsWithKey : allEnvs;
|
|
3813
|
+
} else {
|
|
3814
|
+
targetEnvs = handleCancel(
|
|
3815
|
+
await p19.multiselect({
|
|
3816
|
+
message: `Which environments should be updated for ${pc14.bold(targetKey)}?`,
|
|
3817
|
+
options: allEnvs.map((env) => ({
|
|
3818
|
+
value: env,
|
|
3819
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`,
|
|
3820
|
+
hint: envsWithKey.includes(env) ? "currently exists" : "not set"
|
|
3821
|
+
})),
|
|
3822
|
+
initialValues: envsWithKey.length > 0 ? envsWithKey : allEnvs,
|
|
3823
|
+
required: true
|
|
3824
|
+
})
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
if (targetEnvs.length === 0) {
|
|
3828
|
+
p19.log.warn("No environments selected. Nothing was changed.");
|
|
3829
|
+
return { key: targetKey, environments: [] };
|
|
3830
|
+
}
|
|
3831
|
+
for (const env of targetEnvs) {
|
|
3832
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3833
|
+
const localEditor = new EnvEditor(localPath);
|
|
3834
|
+
const currentVal = localEditor.getValue(targetKey);
|
|
3835
|
+
let newVal = value;
|
|
3836
|
+
if (newVal === void 0) {
|
|
3837
|
+
if (!isInteractive(flags)) {
|
|
3838
|
+
newVal = currentVal ?? "";
|
|
3839
|
+
} else {
|
|
3840
|
+
newVal = handleCancel(
|
|
3841
|
+
await p19.text({
|
|
3842
|
+
message: `New value for ${pc14.cyan(targetKey)} in ${pc14.bold(env)}? (Current: "${currentVal ?? ""}")`,
|
|
3843
|
+
initialValue: currentVal ?? ""
|
|
3844
|
+
})
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
} else if (isInteractive(flags) && !flags.yes) {
|
|
3848
|
+
newVal = handleCancel(
|
|
3849
|
+
await p19.text({
|
|
3850
|
+
message: `New value for ${pc14.cyan(targetKey)} in ${pc14.bold(env)}? (Current: "${currentVal ?? ""}")`,
|
|
3851
|
+
initialValue: value ?? currentVal ?? ""
|
|
3852
|
+
})
|
|
3853
|
+
);
|
|
3854
|
+
}
|
|
3855
|
+
if (flags.dryRun) {
|
|
3856
|
+
p19.log.info(
|
|
3857
|
+
pc14.cyan(`[DRY RUN] Would update ${targetKey}=${newVal} in ${resolveEnvFilename(env, false)}`)
|
|
3858
|
+
);
|
|
3859
|
+
} else {
|
|
3860
|
+
localEditor.set(targetKey, newVal);
|
|
3861
|
+
localEditor.save();
|
|
3862
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3863
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3864
|
+
if (!exampleEditor.has(targetKey)) {
|
|
3865
|
+
exampleEditor.set(targetKey, "");
|
|
3866
|
+
exampleEditor.save();
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
if (!flags.dryRun) {
|
|
3871
|
+
p19.log.success(
|
|
3872
|
+
pc14.green(`\u2713 Updated ${pc14.bold(targetKey)} in [${targetEnvs.join(", ")}].`)
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3876
|
+
}
|
|
3877
|
+
async function runEnvDelete(key, flags = {}) {
|
|
3878
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3879
|
+
const allEnvs = discoverEnvironments(projectRoot);
|
|
3880
|
+
const existingKeys = getAllExistingKeys(projectRoot, allEnvs);
|
|
3881
|
+
let targetKey = key?.trim();
|
|
3882
|
+
if (!targetKey) {
|
|
3883
|
+
if (!isInteractive(flags)) {
|
|
3884
|
+
throw new Error("Variable key is required for 'mason env delete <key>'");
|
|
3885
|
+
}
|
|
3886
|
+
if (existingKeys.length === 0) {
|
|
3887
|
+
p19.log.error(pc14.red("No environment variables found to delete."));
|
|
3888
|
+
return { key: "", environments: [] };
|
|
3889
|
+
}
|
|
3890
|
+
targetKey = handleCancel(
|
|
3891
|
+
await p19.select({
|
|
3892
|
+
message: "Select a variable to delete:",
|
|
3893
|
+
options: existingKeys.map((k) => ({ value: k, label: k }))
|
|
3894
|
+
})
|
|
3895
|
+
);
|
|
3896
|
+
}
|
|
3897
|
+
const envsWithKey = allEnvs.filter((env) => {
|
|
3898
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3899
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3900
|
+
return fs4.existsSync(localPath) && new EnvEditor(localPath).has(targetKey) || fs4.existsSync(examplePath) && new EnvEditor(examplePath).has(targetKey);
|
|
3901
|
+
});
|
|
3902
|
+
let targetEnvs = [];
|
|
3903
|
+
if (flags.env) {
|
|
3904
|
+
targetEnvs = String(flags.env).split(",").map((s) => s.trim()).filter(Boolean);
|
|
3905
|
+
} else if (!isInteractive(flags)) {
|
|
3906
|
+
targetEnvs = envsWithKey.length > 0 ? envsWithKey : allEnvs;
|
|
3907
|
+
} else {
|
|
3908
|
+
targetEnvs = handleCancel(
|
|
3909
|
+
await p19.multiselect({
|
|
3910
|
+
message: `Which environments should remove ${pc14.bold(targetKey)}?`,
|
|
3911
|
+
options: allEnvs.map((env) => ({
|
|
3912
|
+
value: env,
|
|
3913
|
+
label: env === "local" ? "local (.env)" : `${env} (.env.${env})`,
|
|
3914
|
+
...envsWithKey.includes(env) ? { hint: "currently present" } : {}
|
|
3915
|
+
})),
|
|
3916
|
+
initialValues: envsWithKey.length > 0 ? envsWithKey : allEnvs,
|
|
3917
|
+
required: true
|
|
3918
|
+
})
|
|
3919
|
+
);
|
|
3920
|
+
}
|
|
3921
|
+
if (targetEnvs.length === 0) {
|
|
3922
|
+
p19.log.warn("No environments selected. Nothing was deleted.");
|
|
3923
|
+
return { key: targetKey, environments: [] };
|
|
3924
|
+
}
|
|
3925
|
+
if (isInteractive(flags) && !flags.yes) {
|
|
3926
|
+
const confirm13 = handleCancel(
|
|
3927
|
+
await p19.confirm({
|
|
3928
|
+
message: `Are you sure you want to delete ${pc14.bold(targetKey)} from ${targetEnvs.join(", ")} and their .example templates?`,
|
|
3929
|
+
initialValue: false
|
|
3930
|
+
})
|
|
3931
|
+
);
|
|
3932
|
+
if (!confirm13) {
|
|
3933
|
+
p19.log.warn("Operation cancelled.");
|
|
3934
|
+
return { key: targetKey, environments: [] };
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
for (const env of targetEnvs) {
|
|
3938
|
+
const localPath = path2.join(projectRoot, resolveEnvFilename(env, false));
|
|
3939
|
+
const examplePath = path2.join(projectRoot, resolveEnvFilename(env, true));
|
|
3940
|
+
if (flags.dryRun) {
|
|
3941
|
+
p19.log.info(
|
|
3942
|
+
pc14.cyan(`[DRY RUN] Would delete ${targetKey} from ${resolveEnvFilename(env, false)} and ${resolveEnvFilename(env, true)}`)
|
|
3943
|
+
);
|
|
3944
|
+
} else {
|
|
3945
|
+
if (fs4.existsSync(localPath)) {
|
|
3946
|
+
const localEditor = new EnvEditor(localPath);
|
|
3947
|
+
localEditor.delete(targetKey);
|
|
3948
|
+
localEditor.save();
|
|
3949
|
+
}
|
|
3950
|
+
if (fs4.existsSync(examplePath)) {
|
|
3951
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3952
|
+
exampleEditor.delete(targetKey);
|
|
3953
|
+
exampleEditor.save();
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
if (!flags.dryRun) {
|
|
3958
|
+
p19.log.success(
|
|
3959
|
+
pc14.green(`\u2713 Deleted ${pc14.bold(targetKey)} from [${targetEnvs.join(", ")}] and .example templates.`)
|
|
3960
|
+
);
|
|
3961
|
+
}
|
|
3962
|
+
return { key: targetKey, environments: targetEnvs };
|
|
3963
|
+
}
|
|
3964
|
+
async function runEnvAudit(flags = {}) {
|
|
3965
|
+
const projectRoot = flags.cwd || flags.targetDir || findProjectRoot();
|
|
3966
|
+
const envs = discoverEnvironments(projectRoot);
|
|
3967
|
+
p19.intro(pc14.bgCyan(pc14.black(" mason env audit ")));
|
|
3968
|
+
const items = [];
|
|
3969
|
+
let totalIssues = 0;
|
|
3970
|
+
let hasDrift = false;
|
|
3971
|
+
let hasErrors = false;
|
|
3972
|
+
for (const env of envs) {
|
|
3973
|
+
const localFile = resolveEnvFilename(env, false);
|
|
3974
|
+
const exampleFile = resolveEnvFilename(env, true);
|
|
3975
|
+
const localPath = path2.join(projectRoot, localFile);
|
|
3976
|
+
const examplePath = path2.join(projectRoot, exampleFile);
|
|
3977
|
+
const localExists = fs4.existsSync(localPath);
|
|
3978
|
+
const exampleExists = fs4.existsSync(examplePath);
|
|
3979
|
+
const localEditor = new EnvEditor(localPath);
|
|
3980
|
+
const exampleEditor = new EnvEditor(examplePath);
|
|
3981
|
+
const localKeys = localExists ? localEditor.getKeys() : [];
|
|
3982
|
+
const exampleKeys = exampleExists ? exampleEditor.getKeys() : [];
|
|
3983
|
+
const missingInLocal = exampleKeys.filter((k) => !localKeys.includes(k));
|
|
3984
|
+
const missingInExample = localKeys.filter((k) => !exampleKeys.includes(k));
|
|
3985
|
+
const emptyLocals = localKeys.filter((k) => {
|
|
3986
|
+
const val = localEditor.getValue(k);
|
|
3987
|
+
return val === "" || val === null;
|
|
3988
|
+
});
|
|
3989
|
+
const envIssues = (!localExists ? 1 : 0) + (!exampleExists ? 1 : 0) + missingInLocal.length + missingInExample.length + emptyLocals.length;
|
|
3990
|
+
totalIssues += envIssues;
|
|
3991
|
+
if (missingInExample.length > 0 || !exampleExists) hasDrift = true;
|
|
3992
|
+
if (missingInLocal.length > 0 || !localExists) hasErrors = true;
|
|
3993
|
+
items.push({
|
|
3994
|
+
env,
|
|
3995
|
+
localFile,
|
|
3996
|
+
exampleFile,
|
|
3997
|
+
localExists,
|
|
3998
|
+
exampleExists,
|
|
3999
|
+
missingInLocal,
|
|
4000
|
+
missingInExample,
|
|
4001
|
+
emptyLocals
|
|
4002
|
+
});
|
|
4003
|
+
if (!localExists && !exampleExists) {
|
|
4004
|
+
p19.log.warn(
|
|
4005
|
+
pc14.yellow(`\u25B2 ${pc14.bold(env)}: Neither '${localFile}' nor '${exampleFile}' exist.`)
|
|
4006
|
+
);
|
|
4007
|
+
continue;
|
|
4008
|
+
}
|
|
4009
|
+
if (!localExists) {
|
|
4010
|
+
p19.log.error(
|
|
4011
|
+
pc14.red(`\u2717 ${pc14.bold(env)}: Local file '${localFile}' is missing! Run 'mason env init' to create it.`)
|
|
4012
|
+
);
|
|
4013
|
+
continue;
|
|
4014
|
+
}
|
|
4015
|
+
if (!exampleExists) {
|
|
4016
|
+
p19.log.warn(
|
|
4017
|
+
pc14.yellow(`\u25B2 ${pc14.bold(env)}: Template '${exampleFile}' is missing! Run 'mason env init-examples' to generate it.`)
|
|
4018
|
+
);
|
|
4019
|
+
continue;
|
|
4020
|
+
}
|
|
4021
|
+
if (envIssues === 0) {
|
|
4022
|
+
p19.log.success(
|
|
4023
|
+
pc14.green(`\u2713 ${pc14.bold(env)} (${localFile} \u2194 ${exampleFile}): All ${localKeys.length} keys in sync!`)
|
|
4024
|
+
);
|
|
4025
|
+
} else {
|
|
4026
|
+
p19.log.step(
|
|
4027
|
+
pc14.bold(pc14.cyan(`Environment: ${env} (${localFile} \u2194 ${exampleFile})`))
|
|
4028
|
+
);
|
|
4029
|
+
if (missingInLocal.length > 0) {
|
|
4030
|
+
p19.log.error(
|
|
4031
|
+
pc14.red(` \u25CF Missing locally (${missingInLocal.length} in template but missing in ${localFile}):`)
|
|
4032
|
+
);
|
|
4033
|
+
for (const k of missingInLocal) {
|
|
4034
|
+
p19.log.message(pc14.red(` - ${k}`));
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
if (missingInExample.length > 0) {
|
|
4038
|
+
p19.log.warn(
|
|
4039
|
+
pc14.yellow(` \u25CF Template drift (${missingInExample.length} local keys missing in ${exampleFile}):`)
|
|
4040
|
+
);
|
|
4041
|
+
for (const k of missingInExample) {
|
|
4042
|
+
p19.log.message(pc14.yellow(` - ${k}`));
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
if (emptyLocals.length > 0) {
|
|
4046
|
+
p19.log.info(
|
|
4047
|
+
pc14.dim(` \u25B2 Empty values (${emptyLocals.length} defined with blank value in ${localFile}):`)
|
|
4048
|
+
);
|
|
4049
|
+
for (const k of emptyLocals) {
|
|
4050
|
+
p19.log.message(pc14.dim(` - ${k}`));
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
if (totalIssues === 0) {
|
|
4056
|
+
p19.outro(pc14.green("\u2713 All environment files are in sync! No drift detected."));
|
|
4057
|
+
} else {
|
|
4058
|
+
p19.outro(
|
|
4059
|
+
pc14.yellow(`Found ${totalIssues} environment issue(s) across inspected environments.`)
|
|
4060
|
+
);
|
|
4061
|
+
}
|
|
4062
|
+
return {
|
|
4063
|
+
hasDrift,
|
|
4064
|
+
hasErrors,
|
|
4065
|
+
totalIssues,
|
|
4066
|
+
items
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
// src/commands/env/index.ts
|
|
4071
|
+
async function runEnvMenu(flags = {}) {
|
|
4072
|
+
const task = handleCancel(
|
|
4073
|
+
await p19.select({
|
|
4074
|
+
message: "What environment task do you want to run?",
|
|
4075
|
+
options: [
|
|
4076
|
+
{
|
|
4077
|
+
value: "add",
|
|
4078
|
+
label: "Add Variable",
|
|
4079
|
+
hint: "Add a new key across selected environments and sync .example templates"
|
|
4080
|
+
},
|
|
4081
|
+
{
|
|
4082
|
+
value: "update",
|
|
4083
|
+
label: "Update Variable",
|
|
4084
|
+
hint: "Update an existing variable value across environments"
|
|
4085
|
+
},
|
|
4086
|
+
{
|
|
4087
|
+
value: "delete",
|
|
4088
|
+
label: "Delete Variable",
|
|
4089
|
+
hint: "Delete a variable from .env and .example files"
|
|
4090
|
+
},
|
|
4091
|
+
{
|
|
4092
|
+
value: "audit",
|
|
4093
|
+
label: "Audit Drift",
|
|
4094
|
+
hint: "Scan for missing keys, uncommitted secrets, or template drift"
|
|
4095
|
+
},
|
|
4096
|
+
{
|
|
4097
|
+
value: "init",
|
|
4098
|
+
label: "Init Files (.env)",
|
|
4099
|
+
hint: "Clone .env*.example templates to local .env files"
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
value: "init-examples",
|
|
4103
|
+
label: "Init Templates (.env.example)",
|
|
4104
|
+
hint: "Safely clone and redact local .env files to .example templates"
|
|
4105
|
+
}
|
|
4106
|
+
]
|
|
4107
|
+
})
|
|
4108
|
+
);
|
|
4109
|
+
switch (task) {
|
|
4110
|
+
case "add":
|
|
4111
|
+
return await runEnvAdd(void 0, void 0, flags);
|
|
4112
|
+
case "update":
|
|
4113
|
+
return await runEnvUpdate(void 0, void 0, flags);
|
|
4114
|
+
case "delete":
|
|
4115
|
+
return await runEnvDelete(void 0, flags);
|
|
4116
|
+
case "audit":
|
|
4117
|
+
return await runEnvAudit(flags);
|
|
4118
|
+
case "init":
|
|
4119
|
+
return await runEnvInit(flags);
|
|
4120
|
+
case "init-examples":
|
|
4121
|
+
return await runEnvInitExamples(flags);
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
async function handleEnv(subcommand, args = [], flags = {}) {
|
|
4125
|
+
if (!subcommand) {
|
|
4126
|
+
return await runEnvMenu(flags);
|
|
4127
|
+
}
|
|
4128
|
+
const normalized = subcommand.trim().toLowerCase();
|
|
4129
|
+
switch (normalized) {
|
|
4130
|
+
case "init":
|
|
4131
|
+
return await runEnvInit(flags);
|
|
4132
|
+
case "init-examples":
|
|
4133
|
+
case "initexamples":
|
|
4134
|
+
return await runEnvInitExamples(flags);
|
|
4135
|
+
case "add":
|
|
4136
|
+
return await runEnvAdd(args[0], args[1], flags);
|
|
4137
|
+
case "update":
|
|
4138
|
+
return await runEnvUpdate(args[0], args[1], flags);
|
|
4139
|
+
case "delete":
|
|
4140
|
+
case "remove":
|
|
4141
|
+
case "rm":
|
|
4142
|
+
return await runEnvDelete(args[0], flags);
|
|
4143
|
+
case "audit":
|
|
4144
|
+
return await runEnvAudit(flags);
|
|
4145
|
+
default:
|
|
4146
|
+
p19.log.error(
|
|
4147
|
+
pc14.red(
|
|
4148
|
+
`Unknown env subcommand: "${subcommand}". Allowed subcommands: init, init-examples, add, update, delete, audit.`
|
|
4149
|
+
)
|
|
4150
|
+
);
|
|
4151
|
+
if (process.env.VITEST) {
|
|
4152
|
+
throw new Error(
|
|
4153
|
+
`Unknown env subcommand: "${subcommand}". Allowed subcommands: init, init-examples, add, update, delete, audit.`
|
|
4154
|
+
);
|
|
4155
|
+
}
|
|
4156
|
+
process.exit(1);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
3554
4159
|
|
|
3555
4160
|
// src/commands/menu.ts
|
|
3556
4161
|
async function runInteractiveMenu(flags = {}) {
|
|
3557
4162
|
printBanner();
|
|
3558
4163
|
const choice = handleCancel(
|
|
3559
|
-
await
|
|
4164
|
+
await p19.select({
|
|
3560
4165
|
message: "What would you like to create in your architecture?",
|
|
3561
4166
|
options: [
|
|
3562
4167
|
{
|
|
@@ -3601,13 +4206,8 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3601
4206
|
},
|
|
3602
4207
|
{
|
|
3603
4208
|
value: "env",
|
|
3604
|
-
label: "
|
|
3605
|
-
hint: "
|
|
3606
|
-
},
|
|
3607
|
-
{
|
|
3608
|
-
value: "envExamples",
|
|
3609
|
-
label: "Sync Env Examples",
|
|
3610
|
-
hint: "Safely clone and redact local .env files to .example templates (Sync)"
|
|
4209
|
+
label: "Environment Variables",
|
|
4210
|
+
hint: "Manage .env files, add/delete keys, and sync templates"
|
|
3611
4211
|
}
|
|
3612
4212
|
]
|
|
3613
4213
|
})
|
|
@@ -3638,10 +4238,7 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3638
4238
|
await runInjectableCommand(void 0, flags);
|
|
3639
4239
|
break;
|
|
3640
4240
|
case "env":
|
|
3641
|
-
await
|
|
3642
|
-
break;
|
|
3643
|
-
case "envExamples":
|
|
3644
|
-
await runEnvExamplesCommand({ ...flags, overwrite: flags.overwrite ?? true });
|
|
4241
|
+
await runEnvMenu(flags);
|
|
3645
4242
|
break;
|
|
3646
4243
|
}
|
|
3647
4244
|
}
|
|
@@ -3680,8 +4277,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3680
4277
|
await runInjectableCommand(name, flags);
|
|
3681
4278
|
break;
|
|
3682
4279
|
default:
|
|
3683
|
-
|
|
3684
|
-
|
|
4280
|
+
p19.log.error(
|
|
4281
|
+
pc14.red(
|
|
3685
4282
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3686
4283
|
)
|
|
3687
4284
|
);
|
|
@@ -3689,9 +4286,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3689
4286
|
}
|
|
3690
4287
|
}
|
|
3691
4288
|
var importPrompts = {
|
|
3692
|
-
select:
|
|
3693
|
-
text:
|
|
3694
|
-
confirm:
|
|
4289
|
+
select: p19.select,
|
|
4290
|
+
text: p19.text,
|
|
4291
|
+
confirm: p19.confirm
|
|
3695
4292
|
};
|
|
3696
4293
|
function isTestFile(filePath) {
|
|
3697
4294
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -3801,19 +4398,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3801
4398
|
);
|
|
3802
4399
|
}
|
|
3803
4400
|
if (type !== "shared" && type !== "feature") {
|
|
3804
|
-
|
|
4401
|
+
p19.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3805
4402
|
process.exit(1);
|
|
3806
4403
|
}
|
|
3807
4404
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3808
|
-
const s =
|
|
3809
|
-
s.start(`Connecting to registry at ${
|
|
4405
|
+
const s = p19.spinner();
|
|
4406
|
+
s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
|
|
3810
4407
|
let index;
|
|
3811
4408
|
try {
|
|
3812
4409
|
index = await fetchRegistryIndex(registryBase);
|
|
3813
4410
|
s.stop("Registry index loaded.");
|
|
3814
4411
|
} catch (err) {
|
|
3815
|
-
s.stop(
|
|
3816
|
-
|
|
4412
|
+
s.stop(pc14.red("Failed to load registry index."));
|
|
4413
|
+
p19.log.error(pc14.red(err.message || String(err)));
|
|
3817
4414
|
process.exit(1);
|
|
3818
4415
|
}
|
|
3819
4416
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3821,7 +4418,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3821
4418
|
let name = nameArg?.trim();
|
|
3822
4419
|
if (!name) {
|
|
3823
4420
|
if (availableList.length === 0) {
|
|
3824
|
-
|
|
4421
|
+
p19.log.warn(`No ${type} modules available in the registry.`);
|
|
3825
4422
|
return;
|
|
3826
4423
|
}
|
|
3827
4424
|
name = handleCancel(
|
|
@@ -3837,9 +4434,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3837
4434
|
}
|
|
3838
4435
|
if (!candidateNames.includes(name)) {
|
|
3839
4436
|
const closest = findClosestMatch(name, candidateNames);
|
|
3840
|
-
|
|
4437
|
+
p19.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3841
4438
|
if (closest) {
|
|
3842
|
-
|
|
4439
|
+
p19.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
|
|
3843
4440
|
}
|
|
3844
4441
|
process.exit(1);
|
|
3845
4442
|
}
|
|
@@ -3851,7 +4448,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3851
4448
|
try {
|
|
3852
4449
|
manifest = await fetchModuleManifest(registryBase, modType, modName);
|
|
3853
4450
|
} catch (err) {
|
|
3854
|
-
|
|
4451
|
+
p19.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
|
|
3855
4452
|
process.exit(1);
|
|
3856
4453
|
}
|
|
3857
4454
|
nodes.set(key, {
|
|
@@ -3891,7 +4488,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3891
4488
|
buildTopologicalOrder(rootKey);
|
|
3892
4489
|
const resolutions = /* @__PURE__ */ new Map();
|
|
3893
4490
|
const globalAliasMap = /* @__PURE__ */ new Map();
|
|
3894
|
-
|
|
4491
|
+
p19.log.step(`Importing ${type}: ${pc14.cyan(name)}...`);
|
|
3895
4492
|
for (const key of orderedKeys) {
|
|
3896
4493
|
const node = nodes.get(key);
|
|
3897
4494
|
const { type: modType, name: registryName, manifest } = node;
|
|
@@ -3923,8 +4520,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3923
4520
|
const programmaticPointTo = pointToMap[registryName] || pointToMap[`${modType}/${registryName}`];
|
|
3924
4521
|
if (programmaticPointTo) {
|
|
3925
4522
|
if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
|
|
3926
|
-
|
|
3927
|
-
|
|
4523
|
+
p19.log.error(
|
|
4524
|
+
pc14.red(
|
|
3928
4525
|
`Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
|
|
3929
4526
|
)
|
|
3930
4527
|
);
|
|
@@ -3932,7 +4529,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3932
4529
|
}
|
|
3933
4530
|
globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticPointTo}/`);
|
|
3934
4531
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticPointTo}`);
|
|
3935
|
-
|
|
4532
|
+
p19.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${programmaticPointTo}'`);
|
|
3936
4533
|
resolutions.set(key, {
|
|
3937
4534
|
type: modType,
|
|
3938
4535
|
registryName,
|
|
@@ -3948,7 +4545,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3948
4545
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticAlias}`);
|
|
3949
4546
|
const baseDir = getModuleBaseDir(projectRoot, modType);
|
|
3950
4547
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, programmaticAlias));
|
|
3951
|
-
|
|
4548
|
+
p19.log.step(`Importing '${registryName}' into '${displayDest}'...`);
|
|
3952
4549
|
resolutions.set(key, {
|
|
3953
4550
|
type: modType,
|
|
3954
4551
|
registryName,
|
|
@@ -3960,7 +4557,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3960
4557
|
}
|
|
3961
4558
|
const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
|
|
3962
4559
|
if (!existingDir) {
|
|
3963
|
-
|
|
4560
|
+
p19.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14.cyan(registryName)}`);
|
|
3964
4561
|
resolutions.set(key, {
|
|
3965
4562
|
type: modType,
|
|
3966
4563
|
registryName,
|
|
@@ -3976,7 +4573,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3976
4573
|
const remoteIntegrity = manifest.integrity || entry?.integrity;
|
|
3977
4574
|
const localHash = computeDirectoryHash(existingDir);
|
|
3978
4575
|
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3979
|
-
|
|
4576
|
+
p19.log.step(`\u2713 ${modType}/${registryName} is up to date (reusing existing)`);
|
|
3980
4577
|
resolutions.set(key, {
|
|
3981
4578
|
type: modType,
|
|
3982
4579
|
registryName,
|
|
@@ -3987,7 +4584,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3987
4584
|
continue;
|
|
3988
4585
|
}
|
|
3989
4586
|
if (flags.overwrite) {
|
|
3990
|
-
|
|
4587
|
+
p19.log.step(`Overwriting '${modType}/${registryName}' with registry version (--overwrite)...`);
|
|
3991
4588
|
resolutions.set(key, {
|
|
3992
4589
|
type: modType,
|
|
3993
4590
|
registryName,
|
|
@@ -3998,7 +4595,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3998
4595
|
continue;
|
|
3999
4596
|
}
|
|
4000
4597
|
if (flags.yes) {
|
|
4001
|
-
|
|
4598
|
+
p19.log.info(`Using existing '${modType}/${registryName}'`);
|
|
4002
4599
|
resolutions.set(key, {
|
|
4003
4600
|
type: modType,
|
|
4004
4601
|
registryName,
|
|
@@ -4032,7 +4629,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4032
4629
|
})
|
|
4033
4630
|
);
|
|
4034
4631
|
if (choice === "keep_existing" || choice === "use_existing") {
|
|
4035
|
-
|
|
4632
|
+
p19.log.step(`Skipping '${modType}/${registryName}' (keeping existing)`);
|
|
4036
4633
|
resolutions.set(key, {
|
|
4037
4634
|
type: modType,
|
|
4038
4635
|
registryName,
|
|
@@ -4104,7 +4701,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4104
4701
|
chosenName = chosenName.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
|
|
4105
4702
|
globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${chosenName}/`);
|
|
4106
4703
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${chosenName}`);
|
|
4107
|
-
|
|
4704
|
+
p19.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${chosenName}'`);
|
|
4108
4705
|
resolutions.set(key, {
|
|
4109
4706
|
type: modType,
|
|
4110
4707
|
registryName,
|
|
@@ -4136,7 +4733,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4136
4733
|
globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${newName}`);
|
|
4137
4734
|
const baseDir = getModuleBaseDir(projectRoot, modType);
|
|
4138
4735
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
4139
|
-
|
|
4736
|
+
p19.log.step(`Importing '${registryName}' into '${displayDest}'...`);
|
|
4140
4737
|
resolutions.set(key, {
|
|
4141
4738
|
type: modType,
|
|
4142
4739
|
registryName,
|
|
@@ -4147,7 +4744,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4147
4744
|
continue;
|
|
4148
4745
|
}
|
|
4149
4746
|
if (choice === "overwrite") {
|
|
4150
|
-
|
|
4747
|
+
p19.log.step(`Overwriting '${modType}/${registryName}' with registry version...`);
|
|
4151
4748
|
resolutions.set(key, {
|
|
4152
4749
|
type: modType,
|
|
4153
4750
|
registryName,
|
|
@@ -4190,8 +4787,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4190
4787
|
const targetDir = path2.join(getModuleBaseDir(projectRoot, res.type), res.targetName);
|
|
4191
4788
|
const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
|
|
4192
4789
|
if (key !== rootKey) {
|
|
4193
|
-
|
|
4194
|
-
`Importing ${res.type}: ${
|
|
4790
|
+
p19.log.step(
|
|
4791
|
+
`Importing ${res.type}: ${pc14.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14.dim(` (from ${res.registryName})`) : ""}...`
|
|
4195
4792
|
);
|
|
4196
4793
|
}
|
|
4197
4794
|
if (res.action === "overwrite" && !flags.dryRun) {
|
|
@@ -4209,8 +4806,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4209
4806
|
const remoteFilePath = `${folder}/${res.registryName}/${file}`;
|
|
4210
4807
|
const localFilePath = path2.join(targetDir, file);
|
|
4211
4808
|
if (fs4.existsSync(localFilePath) && !isOverwrite) {
|
|
4212
|
-
|
|
4213
|
-
` \u21B3 Skipping existing file: ${
|
|
4809
|
+
p19.log.warn(
|
|
4810
|
+
` \u21B3 Skipping existing file: ${pc14.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
4214
4811
|
);
|
|
4215
4812
|
continue;
|
|
4216
4813
|
}
|
|
@@ -4222,18 +4819,18 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4222
4819
|
}
|
|
4223
4820
|
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
4224
4821
|
totalWrittenFiles.push(displayPath);
|
|
4225
|
-
|
|
4822
|
+
p19.log.message(` ${pc14.green("+")} ${displayPath}`);
|
|
4226
4823
|
}
|
|
4227
4824
|
}
|
|
4228
4825
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
4229
4826
|
if (missingNpm.length > 0) {
|
|
4230
4827
|
if (flags.skipInstall || flags.dryRun) {
|
|
4231
|
-
|
|
4828
|
+
p19.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
|
|
4232
4829
|
} else {
|
|
4233
4830
|
try {
|
|
4234
4831
|
installNpmPackages(projectRoot, missingNpm);
|
|
4235
4832
|
} catch (err) {
|
|
4236
|
-
|
|
4833
|
+
p19.log.warn(
|
|
4237
4834
|
`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(
|
|
4238
4835
|
" "
|
|
4239
4836
|
)}`
|
|
@@ -4242,13 +4839,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4242
4839
|
}
|
|
4243
4840
|
}
|
|
4244
4841
|
if (flags.dryRun) {
|
|
4245
|
-
|
|
4842
|
+
p19.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
|
|
4246
4843
|
} else {
|
|
4247
4844
|
const rootRes = resolutions.get(rootKey);
|
|
4248
4845
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
4249
4846
|
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, rootRes.targetName));
|
|
4250
|
-
|
|
4251
|
-
|
|
4847
|
+
p19.log.success(
|
|
4848
|
+
pc14.green(
|
|
4252
4849
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
4253
4850
|
)
|
|
4254
4851
|
);
|
|
@@ -4471,10 +5068,10 @@ var FeatureDependencyGraph = class {
|
|
|
4471
5068
|
if (cycle) return cycle;
|
|
4472
5069
|
} else if (inStack.has(next)) {
|
|
4473
5070
|
const cycle = [next];
|
|
4474
|
-
let
|
|
4475
|
-
while (
|
|
4476
|
-
cycle.push(
|
|
4477
|
-
|
|
5071
|
+
let p22 = curr;
|
|
5072
|
+
while (p22 !== next) {
|
|
5073
|
+
cycle.push(p22);
|
|
5074
|
+
p22 = parent.get(p22) || next;
|
|
4478
5075
|
}
|
|
4479
5076
|
cycle.push(next);
|
|
4480
5077
|
cycle.reverse();
|
|
@@ -5376,15 +5973,15 @@ ${compiledDetails}
|
|
|
5376
5973
|
// src/commands/lint.ts
|
|
5377
5974
|
function formatViolation(v) {
|
|
5378
5975
|
const lines = [];
|
|
5379
|
-
lines.push(
|
|
5976
|
+
lines.push(pc14.underline(pc14.cyan(v.relativePath)) + pc14.dim(":") + pc14.yellow(String(v.line)));
|
|
5380
5977
|
const gutter = `${String(v.line).padStart(4, " ")} | `;
|
|
5381
|
-
lines.push(
|
|
5978
|
+
lines.push(pc14.dim(gutter) + v.code);
|
|
5382
5979
|
const categoryShort = v.category.replace(" Violation", "");
|
|
5383
5980
|
lines.push(
|
|
5384
|
-
" " +
|
|
5981
|
+
" " + pc14.red(pc14.bold("\u2716")) + " " + pc14.red(`[${categoryShort}]`) + " " + v.advice
|
|
5385
5982
|
);
|
|
5386
5983
|
if (v.fixExample?.good) {
|
|
5387
|
-
lines.push(" " +
|
|
5984
|
+
lines.push(" " + pc14.dim("\u2192") + " " + pc14.dim(v.fixExample.good));
|
|
5388
5985
|
}
|
|
5389
5986
|
return lines.join("\n");
|
|
5390
5987
|
}
|
|
@@ -5409,7 +6006,7 @@ async function handleLint(flags = {}) {
|
|
|
5409
6006
|
console.log("");
|
|
5410
6007
|
}
|
|
5411
6008
|
console.log(
|
|
5412
|
-
|
|
6009
|
+
pc14.red(
|
|
5413
6010
|
`\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).`
|
|
5414
6011
|
)
|
|
5415
6012
|
);
|
|
@@ -5428,7 +6025,7 @@ ${compiledDetails}
|
|
|
5428
6025
|
}
|
|
5429
6026
|
if (!quiet) {
|
|
5430
6027
|
console.log(
|
|
5431
|
-
|
|
6028
|
+
pc14.green(
|
|
5432
6029
|
`\u2714 Dependency rules verified across ${summary.totalFeatures} feature${summary.totalFeatures === 1 ? "" : "s"}.`
|
|
5433
6030
|
)
|
|
5434
6031
|
);
|
|
@@ -5443,7 +6040,7 @@ ${compiledDetails}
|
|
|
5443
6040
|
console.log(formatViolation(v));
|
|
5444
6041
|
console.log("");
|
|
5445
6042
|
}
|
|
5446
|
-
console.log(
|
|
6043
|
+
console.log(pc14.red(`\u2716 Dependency rules check failed (1 error).`));
|
|
5447
6044
|
console.log("");
|
|
5448
6045
|
}
|
|
5449
6046
|
}
|
|
@@ -5453,7 +6050,7 @@ ${compiledDetails}
|
|
|
5453
6050
|
async function handleInit(type, flags = {}) {
|
|
5454
6051
|
if (!type) {
|
|
5455
6052
|
const choice = handleCancel(
|
|
5456
|
-
await
|
|
6053
|
+
await p19.select({
|
|
5457
6054
|
message: "What would you like to initialize?",
|
|
5458
6055
|
options: [
|
|
5459
6056
|
{
|
|
@@ -5470,20 +6067,20 @@ async function handleInit(type, flags = {}) {
|
|
|
5470
6067
|
})
|
|
5471
6068
|
);
|
|
5472
6069
|
if (choice === "env") {
|
|
5473
|
-
await
|
|
6070
|
+
await runEnvInit(flags);
|
|
5474
6071
|
} else if (choice === "env-examples") {
|
|
5475
|
-
await
|
|
6072
|
+
await runEnvInitExamples(flags);
|
|
5476
6073
|
}
|
|
5477
6074
|
return;
|
|
5478
6075
|
}
|
|
5479
6076
|
const normalized = type.trim().toLowerCase();
|
|
5480
6077
|
if (normalized === "env") {
|
|
5481
|
-
await
|
|
6078
|
+
await runEnvInit(flags);
|
|
5482
6079
|
} else if (normalized === "env-examples" || normalized === "envexamples") {
|
|
5483
|
-
await
|
|
6080
|
+
await runEnvInitExamples(flags);
|
|
5484
6081
|
} else {
|
|
5485
|
-
|
|
5486
|
-
|
|
6082
|
+
p19.log.error(
|
|
6083
|
+
pc14.red(
|
|
5487
6084
|
`Unknown init target: "${type}". Allowed targets: env, env-examples.`
|
|
5488
6085
|
)
|
|
5489
6086
|
);
|
|
@@ -5499,8 +6096,8 @@ function createCli() {
|
|
|
5499
6096
|
const cli = cac("mason");
|
|
5500
6097
|
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) => {
|
|
5501
6098
|
if (args && args.length > 0) {
|
|
5502
|
-
|
|
5503
|
-
|
|
6099
|
+
p19.log.error(
|
|
6100
|
+
pc14.red(
|
|
5504
6101
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
5505
6102
|
)
|
|
5506
6103
|
);
|
|
@@ -5511,7 +6108,7 @@ function createCli() {
|
|
|
5511
6108
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
5512
6109
|
if (mode === "registry") {
|
|
5513
6110
|
const choice2 = handleCancel(
|
|
5514
|
-
await
|
|
6111
|
+
await p19.select({
|
|
5515
6112
|
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
5516
6113
|
options: [
|
|
5517
6114
|
{
|
|
@@ -5554,7 +6151,7 @@ function createCli() {
|
|
|
5554
6151
|
return;
|
|
5555
6152
|
}
|
|
5556
6153
|
const choice = handleCancel(
|
|
5557
|
-
await
|
|
6154
|
+
await p19.select({
|
|
5558
6155
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
5559
6156
|
options: [
|
|
5560
6157
|
{
|
|
@@ -5573,9 +6170,9 @@ function createCli() {
|
|
|
5573
6170
|
hint: "Scan codebase and verify Clean Architecture dependency rules"
|
|
5574
6171
|
},
|
|
5575
6172
|
{
|
|
5576
|
-
value: "
|
|
5577
|
-
label: "
|
|
5578
|
-
hint: "
|
|
6173
|
+
value: "env",
|
|
6174
|
+
label: "Environment variables",
|
|
6175
|
+
hint: "Manage .env files, add/delete keys, and sync templates"
|
|
5579
6176
|
}
|
|
5580
6177
|
]
|
|
5581
6178
|
})
|
|
@@ -5586,8 +6183,8 @@ function createCli() {
|
|
|
5586
6183
|
await handleImport(void 0, void 0, flags);
|
|
5587
6184
|
} else if (choice === "lint") {
|
|
5588
6185
|
await handleLint(flags);
|
|
5589
|
-
} else if (choice === "
|
|
5590
|
-
await
|
|
6186
|
+
} else if (choice === "env") {
|
|
6187
|
+
await handleEnv(void 0, [], flags);
|
|
5591
6188
|
}
|
|
5592
6189
|
} catch (err) {
|
|
5593
6190
|
handleCliError(err);
|
|
@@ -5635,10 +6232,20 @@ function createCli() {
|
|
|
5635
6232
|
handleCliError(err);
|
|
5636
6233
|
}
|
|
5637
6234
|
});
|
|
6235
|
+
cli.command(
|
|
6236
|
+
"env [subcommand] [...args]",
|
|
6237
|
+
"Environment variable management and template synchronization (init, init-examples, add, update, delete, audit)"
|
|
6238
|
+
).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 = {}) => {
|
|
6239
|
+
try {
|
|
6240
|
+
await handleEnv(subcommand, args, flags);
|
|
6241
|
+
} catch (err) {
|
|
6242
|
+
handleCliError(err);
|
|
6243
|
+
}
|
|
6244
|
+
});
|
|
5638
6245
|
cli.command(
|
|
5639
6246
|
"init [type]",
|
|
5640
|
-
"Initialize project configurations
|
|
5641
|
-
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").example("mason init env").example("mason init env
|
|
6247
|
+
"Initialize project configurations (legacy shorthand: use 'mason env init')"
|
|
6248
|
+
).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 = {}) => {
|
|
5642
6249
|
try {
|
|
5643
6250
|
await handleInit(type, flags);
|
|
5644
6251
|
} catch (err) {
|
|
@@ -5647,10 +6254,10 @@ function createCli() {
|
|
|
5647
6254
|
});
|
|
5648
6255
|
cli.command(
|
|
5649
6256
|
"env-examples",
|
|
5650
|
-
"Shorthand for 'mason init
|
|
6257
|
+
"Shorthand for 'mason env init-examples'"
|
|
5651
6258
|
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing .env*.example files").action(async (flags = {}) => {
|
|
5652
6259
|
try {
|
|
5653
|
-
await
|
|
6260
|
+
await handleEnv("init-examples", [], flags);
|
|
5654
6261
|
} catch (err) {
|
|
5655
6262
|
handleCliError(err);
|
|
5656
6263
|
}
|
|
@@ -5686,7 +6293,7 @@ function handleCliError(err) {
|
|
|
5686
6293
|
process.exit(0);
|
|
5687
6294
|
}
|
|
5688
6295
|
const message = err instanceof Error ? err.message : String(err);
|
|
5689
|
-
|
|
6296
|
+
p19.log.error(pc14.red(`Error: ${message}`));
|
|
5690
6297
|
if (process.env.VITEST) {
|
|
5691
6298
|
throw err;
|
|
5692
6299
|
}
|
|
@@ -5697,6 +6304,6 @@ async function runCli(argv = process.argv) {
|
|
|
5697
6304
|
cli.parse(argv, { run: true });
|
|
5698
6305
|
}
|
|
5699
6306
|
|
|
5700
|
-
export { DEFAULT_REGISTRY_BASE, DependencyRuleError, FeatureDependencyGraph, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, handleInit, handleLint, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, lintCodebase, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, resolveSharedDir, runCli, runEnvCommand, runEnvExamplesCommand, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
6307
|
+
export { DEFAULT_REGISTRY_BASE, DependencyRuleError, EnvEditor, FeatureDependencyGraph, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, discoverEnvironments, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleEnv, handleImport, handleInit, handleLint, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, lintCodebase, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveEnvFilename, resolveFeaturesDir, resolveSharedDir, runCli, runEnvAdd, runEnvAudit, runEnvCommand, runEnvDelete, runEnvExamplesCommand, runEnvInit, runEnvInitExamples, runEnvMenu, runEnvUpdate, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
5701
6308
|
//# sourceMappingURL=index.js.map
|
|
5702
6309
|
//# sourceMappingURL=index.js.map
|