@solid-stack/mason 1.0.13 → 1.0.14
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 +249 -844
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +246 -841
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/index.cjs +250 -846
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +248 -843
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/registry.d.ts +0 -64
- package/dist/commands/registry.d.ts.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cac } from 'cac';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import * as p3 from '@clack/prompts';
|
|
4
|
+
import pc14 from 'picocolors';
|
|
5
|
+
import fs9 from 'fs';
|
|
6
6
|
import path2 from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import Handlebars from 'handlebars';
|
|
@@ -12,7 +12,7 @@ import { createHash } from 'crypto';
|
|
|
12
12
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
13
13
|
const projectRoot = options.projectRoot || process.cwd();
|
|
14
14
|
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
15
|
-
const existedBefore =
|
|
15
|
+
const existedBefore = fs9.existsSync(absolutePath);
|
|
16
16
|
if (options.dryRun) {
|
|
17
17
|
return {
|
|
18
18
|
relativePath: relPath,
|
|
@@ -32,10 +32,10 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
const dir = path2.dirname(absolutePath);
|
|
35
|
-
if (!
|
|
36
|
-
|
|
35
|
+
if (!fs9.existsSync(dir)) {
|
|
36
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
fs9.writeFileSync(absolutePath, content, "utf-8");
|
|
39
39
|
return {
|
|
40
40
|
relativePath: relPath,
|
|
41
41
|
absolutePath,
|
|
@@ -47,17 +47,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
47
47
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
48
48
|
console.log("");
|
|
49
49
|
if (isDryRun) {
|
|
50
|
-
|
|
50
|
+
p3.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
51
51
|
}
|
|
52
52
|
for (const f of files) {
|
|
53
53
|
if (f.action === "created") {
|
|
54
|
-
|
|
54
|
+
p3.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
|
|
55
55
|
} else if (f.action === "overwritten") {
|
|
56
|
-
|
|
56
|
+
p3.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
|
|
57
57
|
} else if (f.action === "skipped") {
|
|
58
|
-
|
|
58
|
+
p3.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
59
59
|
} else {
|
|
60
|
-
|
|
60
|
+
p3.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -129,9 +129,9 @@ function loadMasonConfig(startDir = process.cwd()) {
|
|
|
129
129
|
let current = path2.resolve(startDir);
|
|
130
130
|
while (true) {
|
|
131
131
|
const configPath = path2.join(current, "mason.config.json");
|
|
132
|
-
if (
|
|
132
|
+
if (fs9.existsSync(configPath)) {
|
|
133
133
|
try {
|
|
134
|
-
return JSON.parse(
|
|
134
|
+
return JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
135
135
|
} catch {
|
|
136
136
|
return null;
|
|
137
137
|
}
|
|
@@ -146,7 +146,7 @@ function getEffectiveMode(projectRoot, flags) {
|
|
|
146
146
|
if (flags?.registry) return "registry";
|
|
147
147
|
const config = loadMasonConfig(projectRoot);
|
|
148
148
|
if (config?.mode) return config.mode;
|
|
149
|
-
if (
|
|
149
|
+
if (fs9.existsSync(path2.join(projectRoot, "index.json")) && (fs9.existsSync(path2.join(projectRoot, "features")) || fs9.existsSync(path2.join(projectRoot, "shared"))) && !fs9.existsSync(path2.join(projectRoot, "src"))) {
|
|
150
150
|
return "registry";
|
|
151
151
|
}
|
|
152
152
|
return "app";
|
|
@@ -162,7 +162,7 @@ function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
|
162
162
|
if (mode === "registry") {
|
|
163
163
|
return path2.join(projectRoot, "features");
|
|
164
164
|
}
|
|
165
|
-
if (
|
|
165
|
+
if (fs9.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
166
166
|
return path2.join(projectRoot, "src/features");
|
|
167
167
|
}
|
|
168
168
|
return path2.join(projectRoot, "features");
|
|
@@ -178,7 +178,7 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
178
178
|
if (mode === "registry") {
|
|
179
179
|
return path2.join(projectRoot, "shared");
|
|
180
180
|
}
|
|
181
|
-
if (
|
|
181
|
+
if (fs9.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
182
182
|
return path2.join(projectRoot, "src/shared");
|
|
183
183
|
}
|
|
184
184
|
return path2.join(projectRoot, "shared");
|
|
@@ -188,7 +188,7 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
188
188
|
function findProjectRoot(startDir = process.cwd()) {
|
|
189
189
|
let current = path2.resolve(startDir);
|
|
190
190
|
while (true) {
|
|
191
|
-
if (
|
|
191
|
+
if (fs9.existsSync(path2.join(current, "package.json"))) {
|
|
192
192
|
return current;
|
|
193
193
|
}
|
|
194
194
|
const parent = path2.dirname(current);
|
|
@@ -206,9 +206,9 @@ function resolveSharedDir(projectRoot, flags) {
|
|
|
206
206
|
}
|
|
207
207
|
function listExistingFeatures(projectRoot) {
|
|
208
208
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
209
|
-
if (!
|
|
209
|
+
if (!fs9.existsSync(featuresDir)) return [];
|
|
210
210
|
try {
|
|
211
|
-
const entries =
|
|
211
|
+
const entries = fs9.readdirSync(featuresDir, { withFileTypes: true });
|
|
212
212
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
213
213
|
} catch {
|
|
214
214
|
return [];
|
|
@@ -219,9 +219,9 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
219
219
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
220
220
|
for (const feat of features) {
|
|
221
221
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
222
|
-
if (!
|
|
222
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
223
223
|
try {
|
|
224
|
-
const files =
|
|
224
|
+
const files = fs9.readdirSync(domainDir);
|
|
225
225
|
for (const file of files) {
|
|
226
226
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
227
227
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -234,7 +234,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
234
234
|
});
|
|
235
235
|
} else {
|
|
236
236
|
try {
|
|
237
|
-
const content =
|
|
237
|
+
const content = fs9.readFileSync(fullPath, "utf-8");
|
|
238
238
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
239
239
|
results.push({
|
|
240
240
|
name,
|
|
@@ -257,9 +257,9 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
257
257
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
258
258
|
for (const feat of features) {
|
|
259
259
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
260
|
-
if (!
|
|
260
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
261
261
|
try {
|
|
262
|
-
const files =
|
|
262
|
+
const files = fs9.readdirSync(domainDir);
|
|
263
263
|
for (const file of files) {
|
|
264
264
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
265
265
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -282,9 +282,9 @@ function listUseCases(projectRoot, featureName) {
|
|
|
282
282
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
283
283
|
for (const feat of features) {
|
|
284
284
|
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
285
|
-
if (!
|
|
285
|
+
if (!fs9.existsSync(useCasesDir)) continue;
|
|
286
286
|
try {
|
|
287
|
-
const files =
|
|
287
|
+
const files = fs9.readdirSync(useCasesDir);
|
|
288
288
|
for (const file of files) {
|
|
289
289
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
290
290
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -316,29 +316,29 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
316
316
|
});
|
|
317
317
|
var cachedTemplatesDir = null;
|
|
318
318
|
function getTemplatesDir() {
|
|
319
|
-
if (cachedTemplatesDir &&
|
|
319
|
+
if (cachedTemplatesDir && fs9.existsSync(cachedTemplatesDir)) {
|
|
320
320
|
return cachedTemplatesDir;
|
|
321
321
|
}
|
|
322
322
|
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
323
323
|
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
324
|
-
if (
|
|
324
|
+
if (fs9.existsSync(cand1)) {
|
|
325
325
|
cachedTemplatesDir = cand1;
|
|
326
326
|
return cand1;
|
|
327
327
|
}
|
|
328
328
|
const cand2 = path2.resolve(currentDir, "../templates");
|
|
329
|
-
if (
|
|
329
|
+
if (fs9.existsSync(cand2)) {
|
|
330
330
|
cachedTemplatesDir = cand2;
|
|
331
331
|
return cand2;
|
|
332
332
|
}
|
|
333
333
|
const cand3 = path2.resolve(currentDir, "templates");
|
|
334
|
-
if (
|
|
334
|
+
if (fs9.existsSync(cand3)) {
|
|
335
335
|
cachedTemplatesDir = cand3;
|
|
336
336
|
return cand3;
|
|
337
337
|
}
|
|
338
338
|
let curr = currentDir;
|
|
339
339
|
while (true) {
|
|
340
340
|
const candTemplates = path2.join(curr, "templates");
|
|
341
|
-
if (
|
|
341
|
+
if (fs9.existsSync(candTemplates)) {
|
|
342
342
|
cachedTemplatesDir = candTemplates;
|
|
343
343
|
return candTemplates;
|
|
344
344
|
}
|
|
@@ -351,7 +351,7 @@ function getTemplatesDir() {
|
|
|
351
351
|
var templateCache = /* @__PURE__ */ new Map();
|
|
352
352
|
function loadTemplate(relativePath) {
|
|
353
353
|
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
354
|
-
return
|
|
354
|
+
return fs9.readFileSync(fullPath, "utf-8");
|
|
355
355
|
}
|
|
356
356
|
function renderTemplateFile(relativePath, context) {
|
|
357
357
|
let compiled = templateCache.get(relativePath);
|
|
@@ -463,7 +463,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
463
463
|
const idKey = `${entityKey}Id`;
|
|
464
464
|
const listKey = `${entityKey}s`;
|
|
465
465
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
466
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
466
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join("; ") : "";
|
|
467
467
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
468
468
|
return {
|
|
469
469
|
name: m.name,
|
|
@@ -724,7 +724,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
724
724
|
};
|
|
725
725
|
}
|
|
726
726
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
727
|
-
if (customPath &&
|
|
727
|
+
if (customPath && fs9.existsSync(customPath)) {
|
|
728
728
|
return { filePath: customPath, ifaceName };
|
|
729
729
|
}
|
|
730
730
|
const candidateNames = [
|
|
@@ -735,21 +735,21 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
735
735
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
736
736
|
for (const feat of features) {
|
|
737
737
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
738
|
-
if (!
|
|
738
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
739
739
|
for (const cand of candidateNames) {
|
|
740
740
|
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
741
|
-
if (
|
|
741
|
+
if (fs9.existsSync(p1)) {
|
|
742
742
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
746
|
for (const cand of candidateNames) {
|
|
747
747
|
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
748
|
-
if (
|
|
748
|
+
if (fs9.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
749
749
|
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
750
|
-
if (
|
|
750
|
+
if (fs9.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
751
751
|
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
752
|
-
if (
|
|
752
|
+
if (fs9.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
753
753
|
}
|
|
754
754
|
return null;
|
|
755
755
|
}
|
|
@@ -772,7 +772,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
772
772
|
while (currentFile && !visited.has(currentFile)) {
|
|
773
773
|
visited.add(currentFile);
|
|
774
774
|
try {
|
|
775
|
-
const content =
|
|
775
|
+
const content = fs9.readFileSync(currentFile, "utf-8");
|
|
776
776
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
777
777
|
for (const m of parsed.methods) {
|
|
778
778
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1044,22 +1044,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1044
1044
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1045
1045
|
}));
|
|
1046
1046
|
const domainTypesToImport = new Set(domainImports);
|
|
1047
|
-
for (const
|
|
1048
|
-
const rawType =
|
|
1047
|
+
for (const p16 of [...inputProps, ...outputProps]) {
|
|
1048
|
+
const rawType = p16.type.trim();
|
|
1049
1049
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1050
1050
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1051
1051
|
domainTypesToImport.add(baseType);
|
|
1052
1052
|
}
|
|
1053
1053
|
}
|
|
1054
|
-
const formattedInputProps = inputProps.map((
|
|
1055
|
-
name:
|
|
1056
|
-
optional:
|
|
1057
|
-
type:
|
|
1054
|
+
const formattedInputProps = inputProps.map((p16) => ({
|
|
1055
|
+
name: p16.name,
|
|
1056
|
+
optional: p16.optional,
|
|
1057
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1058
1058
|
}));
|
|
1059
|
-
const formattedOutputProps = outputProps.map((
|
|
1060
|
-
name:
|
|
1061
|
-
optional:
|
|
1062
|
-
type:
|
|
1059
|
+
const formattedOutputProps = outputProps.map((p16) => ({
|
|
1060
|
+
name: p16.name,
|
|
1061
|
+
optional: p16.optional,
|
|
1062
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1063
1063
|
}));
|
|
1064
1064
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1065
1065
|
key: toCamelCase(dep),
|
|
@@ -1246,7 +1246,7 @@ function generateHttpHandler(options) {
|
|
|
1246
1246
|
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1247
1247
|
const handlersDir = path2.join(pxDir, "handlers");
|
|
1248
1248
|
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1249
|
-
if (!
|
|
1249
|
+
if (!fs9.existsSync(pxIndexPath)) {
|
|
1250
1250
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1251
1251
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1252
1252
|
routePath: featurePath
|
|
@@ -1283,8 +1283,8 @@ function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], opti
|
|
|
1283
1283
|
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1284
1284
|
const pascalFeature = toPascalCase(featureName);
|
|
1285
1285
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1286
|
-
if (
|
|
1287
|
-
let content =
|
|
1286
|
+
if (fs9.existsSync(providerPath) && !options.overwrite) {
|
|
1287
|
+
let content = fs9.readFileSync(providerPath, "utf-8");
|
|
1288
1288
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1289
1289
|
for (const b of bindings) {
|
|
1290
1290
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1730,23 +1730,23 @@ var PromptCancelledError = class extends Error {
|
|
|
1730
1730
|
}
|
|
1731
1731
|
};
|
|
1732
1732
|
function handleCancel(result) {
|
|
1733
|
-
if (
|
|
1734
|
-
|
|
1733
|
+
if (p3.isCancel(result)) {
|
|
1734
|
+
p3.cancel("Operation cancelled.");
|
|
1735
1735
|
throw new PromptCancelledError();
|
|
1736
1736
|
}
|
|
1737
1737
|
return result;
|
|
1738
1738
|
}
|
|
1739
1739
|
function printBanner() {
|
|
1740
1740
|
console.log("");
|
|
1741
|
-
|
|
1741
|
+
p3.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
|
|
1742
1742
|
}
|
|
1743
1743
|
async function promptProperties(options = {}) {
|
|
1744
1744
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
1745
1745
|
if (properties.length > 0) {
|
|
1746
|
-
const summary = properties.map((
|
|
1747
|
-
|
|
1746
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
1747
|
+
p3.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
|
|
1748
1748
|
const addMore = handleCancel(
|
|
1749
|
-
await
|
|
1749
|
+
await p3.confirm({
|
|
1750
1750
|
message: "Would you like to add custom fields/properties?",
|
|
1751
1751
|
initialValue: false
|
|
1752
1752
|
})
|
|
@@ -1755,10 +1755,10 @@ async function promptProperties(options = {}) {
|
|
|
1755
1755
|
return properties;
|
|
1756
1756
|
}
|
|
1757
1757
|
}
|
|
1758
|
-
|
|
1758
|
+
p3.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
|
|
1759
1759
|
while (true) {
|
|
1760
1760
|
const name = handleCancel(
|
|
1761
|
-
await
|
|
1761
|
+
await p3.text({
|
|
1762
1762
|
message: "Property name (or press Enter to finish):",
|
|
1763
1763
|
placeholder: "e.g. title, email, status"
|
|
1764
1764
|
})
|
|
@@ -1767,7 +1767,7 @@ async function promptProperties(options = {}) {
|
|
|
1767
1767
|
break;
|
|
1768
1768
|
}
|
|
1769
1769
|
const type = handleCancel(
|
|
1770
|
-
await
|
|
1770
|
+
await p3.select({
|
|
1771
1771
|
message: `Type for property "${name.trim()}":`,
|
|
1772
1772
|
options: [
|
|
1773
1773
|
{ value: "string", label: "string" },
|
|
@@ -1784,7 +1784,7 @@ async function promptProperties(options = {}) {
|
|
|
1784
1784
|
let finalType = type;
|
|
1785
1785
|
if (type === "custom") {
|
|
1786
1786
|
finalType = handleCancel(
|
|
1787
|
-
await
|
|
1787
|
+
await p3.text({
|
|
1788
1788
|
message: `Enter custom type for "${name.trim()}":`,
|
|
1789
1789
|
placeholder: "e.g. UserRole, string | null",
|
|
1790
1790
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -1792,7 +1792,7 @@ async function promptProperties(options = {}) {
|
|
|
1792
1792
|
);
|
|
1793
1793
|
}
|
|
1794
1794
|
const optional = handleCancel(
|
|
1795
|
-
await
|
|
1795
|
+
await p3.confirm({
|
|
1796
1796
|
message: `Is "${name.trim()}" optional?`,
|
|
1797
1797
|
initialValue: false
|
|
1798
1798
|
})
|
|
@@ -1802,8 +1802,8 @@ async function promptProperties(options = {}) {
|
|
|
1802
1802
|
type: finalType.trim(),
|
|
1803
1803
|
optional
|
|
1804
1804
|
});
|
|
1805
|
-
|
|
1806
|
-
|
|
1805
|
+
p3.log.success(
|
|
1806
|
+
pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1807
1807
|
);
|
|
1808
1808
|
}
|
|
1809
1809
|
return properties;
|
|
@@ -1819,7 +1819,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1819
1819
|
}
|
|
1820
1820
|
if (!name) {
|
|
1821
1821
|
name = handleCancel(
|
|
1822
|
-
await
|
|
1822
|
+
await p3.text({
|
|
1823
1823
|
message: "What is the feature name?",
|
|
1824
1824
|
placeholder: "e.g. users, auth, orders, billing",
|
|
1825
1825
|
validate: (val) => {
|
|
@@ -1835,7 +1835,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1835
1835
|
let targetDirRel = defaultDir;
|
|
1836
1836
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1837
1837
|
targetDirRel = handleCancel(
|
|
1838
|
-
await
|
|
1838
|
+
await p3.text({
|
|
1839
1839
|
message: "Target directory for the feature:",
|
|
1840
1840
|
initialValue: defaultDir,
|
|
1841
1841
|
defaultValue: defaultDir
|
|
@@ -1857,14 +1857,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1857
1857
|
const customUseCases = [];
|
|
1858
1858
|
if (!flags.yes) {
|
|
1859
1859
|
includeExpress = handleCancel(
|
|
1860
|
-
await
|
|
1860
|
+
await p3.confirm({
|
|
1861
1861
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
1862
1862
|
initialValue: true
|
|
1863
1863
|
})
|
|
1864
1864
|
);
|
|
1865
1865
|
if (includeExpress) {
|
|
1866
1866
|
routePath = handleCancel(
|
|
1867
|
-
await
|
|
1867
|
+
await p3.text({
|
|
1868
1868
|
message: "Express route prefix:",
|
|
1869
1869
|
initialValue: `/${normalizedName}`,
|
|
1870
1870
|
defaultValue: `/${normalizedName}`
|
|
@@ -1872,14 +1872,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1872
1872
|
).trim();
|
|
1873
1873
|
}
|
|
1874
1874
|
includeDomain = handleCancel(
|
|
1875
|
-
await
|
|
1875
|
+
await p3.confirm({
|
|
1876
1876
|
message: "Include Domain Entity/Entities?",
|
|
1877
1877
|
initialValue: true
|
|
1878
1878
|
})
|
|
1879
1879
|
);
|
|
1880
1880
|
if (includeDomain) {
|
|
1881
1881
|
const domainInput = handleCancel(
|
|
1882
|
-
await
|
|
1882
|
+
await p3.text({
|
|
1883
1883
|
message: "Domain entity name(s) (comma-separated):",
|
|
1884
1884
|
initialValue: defaultEntity,
|
|
1885
1885
|
defaultValue: defaultEntity,
|
|
@@ -1898,21 +1898,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1898
1898
|
entityNames = [defaultEntity];
|
|
1899
1899
|
}
|
|
1900
1900
|
includeInterface = handleCancel(
|
|
1901
|
-
await
|
|
1901
|
+
await p3.confirm({
|
|
1902
1902
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
1903
1903
|
initialValue: true
|
|
1904
1904
|
})
|
|
1905
1905
|
);
|
|
1906
1906
|
if (includeInterface) {
|
|
1907
1907
|
includeInfra = handleCancel(
|
|
1908
|
-
await
|
|
1908
|
+
await p3.confirm({
|
|
1909
1909
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
1910
1910
|
initialValue: true
|
|
1911
1911
|
})
|
|
1912
1912
|
);
|
|
1913
1913
|
}
|
|
1914
1914
|
includeUseCase = handleCancel(
|
|
1915
|
-
await
|
|
1915
|
+
await p3.confirm({
|
|
1916
1916
|
message: "Include Domain Use Cases?",
|
|
1917
1917
|
initialValue: true
|
|
1918
1918
|
})
|
|
@@ -1926,7 +1926,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1926
1926
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
1927
1927
|
];
|
|
1928
1928
|
crudOperations = handleCancel(
|
|
1929
|
-
await
|
|
1929
|
+
await p3.multiselect({
|
|
1930
1930
|
message: "Select standard CRUD use cases to generate:",
|
|
1931
1931
|
options: crudChoices,
|
|
1932
1932
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -1934,13 +1934,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1934
1934
|
})
|
|
1935
1935
|
);
|
|
1936
1936
|
includeTests = handleCancel(
|
|
1937
|
-
await
|
|
1937
|
+
await p3.confirm({
|
|
1938
1938
|
message: "Generate Vitest unit tests for use cases?",
|
|
1939
1939
|
initialValue: true
|
|
1940
1940
|
})
|
|
1941
1941
|
);
|
|
1942
1942
|
const wantCustom = handleCancel(
|
|
1943
|
-
await
|
|
1943
|
+
await p3.confirm({
|
|
1944
1944
|
message: "Do you want to add any custom use cases to this feature?",
|
|
1945
1945
|
initialValue: false
|
|
1946
1946
|
})
|
|
@@ -1950,7 +1950,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1950
1950
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
1951
1951
|
while (addMore) {
|
|
1952
1952
|
const customName = handleCancel(
|
|
1953
|
-
await
|
|
1953
|
+
await p3.text({
|
|
1954
1954
|
message: "Custom Use Case name:",
|
|
1955
1955
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
1956
1956
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -1960,7 +1960,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1960
1960
|
let selectedDeps = [];
|
|
1961
1961
|
if (availableIfaces.length > 0) {
|
|
1962
1962
|
selectedDeps = handleCancel(
|
|
1963
|
-
await
|
|
1963
|
+
await p3.multiselect({
|
|
1964
1964
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
1965
1965
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
1966
1966
|
required: false
|
|
@@ -1969,7 +1969,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1969
1969
|
}
|
|
1970
1970
|
let customInputProps = [];
|
|
1971
1971
|
const hasInput = handleCancel(
|
|
1972
|
-
await
|
|
1972
|
+
await p3.confirm({
|
|
1973
1973
|
message: `Does ${customUcName} accept input arguments?`,
|
|
1974
1974
|
initialValue: true
|
|
1975
1975
|
})
|
|
@@ -1980,7 +1980,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1980
1980
|
}
|
|
1981
1981
|
let customOutputProps = [];
|
|
1982
1982
|
const hasCustomOutput = handleCancel(
|
|
1983
|
-
await
|
|
1983
|
+
await p3.confirm({
|
|
1984
1984
|
message: `Does ${customUcName} return custom output data?`,
|
|
1985
1985
|
initialValue: false
|
|
1986
1986
|
})
|
|
@@ -1990,7 +1990,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1990
1990
|
});
|
|
1991
1991
|
}
|
|
1992
1992
|
const customIncludeTest = handleCancel(
|
|
1993
|
-
await
|
|
1993
|
+
await p3.confirm({
|
|
1994
1994
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
1995
1995
|
initialValue: true
|
|
1996
1996
|
})
|
|
@@ -2000,14 +2000,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2000
2000
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2001
2001
|
if (includeExpress) {
|
|
2002
2002
|
customCreateHttp = handleCancel(
|
|
2003
|
-
await
|
|
2003
|
+
await p3.confirm({
|
|
2004
2004
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2005
2005
|
initialValue: true
|
|
2006
2006
|
})
|
|
2007
2007
|
);
|
|
2008
2008
|
if (customCreateHttp) {
|
|
2009
2009
|
customHttpMethod = handleCancel(
|
|
2010
|
-
await
|
|
2010
|
+
await p3.select({
|
|
2011
2011
|
message: "HTTP method for route:",
|
|
2012
2012
|
options: [
|
|
2013
2013
|
{ value: "post", label: "POST" },
|
|
@@ -2020,7 +2020,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2020
2020
|
})
|
|
2021
2021
|
);
|
|
2022
2022
|
customHttpPath = handleCancel(
|
|
2023
|
-
await
|
|
2023
|
+
await p3.text({
|
|
2024
2024
|
message: "Route subpath (relative to feature prefix):",
|
|
2025
2025
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2026
2026
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2041,7 +2041,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2041
2041
|
validationFields: customInputProps
|
|
2042
2042
|
});
|
|
2043
2043
|
addMore = handleCancel(
|
|
2044
|
-
await
|
|
2044
|
+
await p3.confirm({
|
|
2045
2045
|
message: "Add another custom use case?",
|
|
2046
2046
|
initialValue: false
|
|
2047
2047
|
})
|
|
@@ -2050,7 +2050,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2050
2050
|
}
|
|
2051
2051
|
}
|
|
2052
2052
|
includeDiProvider = handleCancel(
|
|
2053
|
-
await
|
|
2053
|
+
await p3.confirm({
|
|
2054
2054
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2055
2055
|
initialValue: true
|
|
2056
2056
|
})
|
|
@@ -2062,8 +2062,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2062
2062
|
entityNames = [defaultEntity];
|
|
2063
2063
|
}
|
|
2064
2064
|
}
|
|
2065
|
-
const s =
|
|
2066
|
-
s.start(`Generating feature '${
|
|
2065
|
+
const s = p3.spinner();
|
|
2066
|
+
s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
|
|
2067
2067
|
const options = {
|
|
2068
2068
|
name: normalizedName,
|
|
2069
2069
|
projectRoot,
|
|
@@ -2085,7 +2085,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2085
2085
|
const files = generateFeature(options);
|
|
2086
2086
|
if (isRegistry) {
|
|
2087
2087
|
const registryPath = path2.join(targetDir, "registry.json");
|
|
2088
|
-
if (!
|
|
2088
|
+
if (!fs9.existsSync(registryPath)) {
|
|
2089
2089
|
const manifest = {
|
|
2090
2090
|
name: toKebabCase(normalizedName),
|
|
2091
2091
|
type: "feature",
|
|
@@ -2109,8 +2109,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2109
2109
|
}
|
|
2110
2110
|
s.stop("Feature scaffolded successfully!");
|
|
2111
2111
|
printGeneratedFiles(files, options.dryRun);
|
|
2112
|
-
|
|
2113
|
-
|
|
2112
|
+
p3.outro(
|
|
2113
|
+
pc14.green(
|
|
2114
2114
|
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2115
2115
|
)
|
|
2116
2116
|
);
|
|
@@ -2224,7 +2224,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2224
2224
|
}
|
|
2225
2225
|
if (!name) {
|
|
2226
2226
|
name = handleCancel(
|
|
2227
|
-
await
|
|
2227
|
+
await p3.text({
|
|
2228
2228
|
message: "What is the shared module name?",
|
|
2229
2229
|
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2230
2230
|
validate: (val) => {
|
|
@@ -2240,7 +2240,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2240
2240
|
let targetDirRel = defaultDir;
|
|
2241
2241
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2242
2242
|
targetDirRel = handleCancel(
|
|
2243
|
-
await
|
|
2243
|
+
await p3.text({
|
|
2244
2244
|
message: "Target directory for the shared module:",
|
|
2245
2245
|
initialValue: defaultDir,
|
|
2246
2246
|
defaultValue: defaultDir
|
|
@@ -2252,21 +2252,21 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2252
2252
|
let includeStub = true;
|
|
2253
2253
|
if (!flags.yes) {
|
|
2254
2254
|
description = handleCancel(
|
|
2255
|
-
await
|
|
2255
|
+
await p3.text({
|
|
2256
2256
|
message: "Module description (optional):",
|
|
2257
2257
|
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2258
2258
|
initialValue: ""
|
|
2259
2259
|
})
|
|
2260
2260
|
).trim();
|
|
2261
2261
|
includeStub = handleCancel(
|
|
2262
|
-
await
|
|
2262
|
+
await p3.confirm({
|
|
2263
2263
|
message: "Include stub implementation in infrastructure/?",
|
|
2264
2264
|
initialValue: true
|
|
2265
2265
|
})
|
|
2266
2266
|
);
|
|
2267
2267
|
}
|
|
2268
|
-
const s =
|
|
2269
|
-
s.start(`Generating shared module '${
|
|
2268
|
+
const s = p3.spinner();
|
|
2269
|
+
s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
|
|
2270
2270
|
const files = generateShared({
|
|
2271
2271
|
name: normalizedName,
|
|
2272
2272
|
description,
|
|
@@ -2279,8 +2279,8 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2279
2279
|
});
|
|
2280
2280
|
s.stop("Shared module generated successfully!");
|
|
2281
2281
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2282
|
-
|
|
2283
|
-
|
|
2282
|
+
p3.outro(
|
|
2283
|
+
pc14.green(
|
|
2284
2284
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2285
2285
|
)
|
|
2286
2286
|
);
|
|
@@ -2294,14 +2294,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2294
2294
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2295
2295
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2296
2296
|
const selected = handleCancel(
|
|
2297
|
-
await
|
|
2297
|
+
await p3.select({
|
|
2298
2298
|
message: "Select target feature:",
|
|
2299
2299
|
options: featureOptions
|
|
2300
2300
|
})
|
|
2301
2301
|
);
|
|
2302
2302
|
if (selected === "__custom__") {
|
|
2303
2303
|
feature = handleCancel(
|
|
2304
|
-
await
|
|
2304
|
+
await p3.text({
|
|
2305
2305
|
message: "Enter feature name:",
|
|
2306
2306
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2307
2307
|
})
|
|
@@ -2311,7 +2311,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2311
2311
|
}
|
|
2312
2312
|
} else {
|
|
2313
2313
|
feature = handleCancel(
|
|
2314
|
-
await
|
|
2314
|
+
await p3.text({
|
|
2315
2315
|
message: "Enter target feature name:",
|
|
2316
2316
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2317
2317
|
})
|
|
@@ -2322,7 +2322,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2322
2322
|
let name = useCaseNameArg?.trim();
|
|
2323
2323
|
if (!name) {
|
|
2324
2324
|
name = handleCancel(
|
|
2325
|
-
await
|
|
2325
|
+
await p3.text({
|
|
2326
2326
|
message: "Use Case name:",
|
|
2327
2327
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2328
2328
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2338,7 +2338,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2338
2338
|
label: i.name
|
|
2339
2339
|
}));
|
|
2340
2340
|
selectedDeps = handleCancel(
|
|
2341
|
-
await
|
|
2341
|
+
await p3.multiselect({
|
|
2342
2342
|
message: "Select domain interfaces to inject into use case:",
|
|
2343
2343
|
options: multiOptions,
|
|
2344
2344
|
required: false
|
|
@@ -2353,7 +2353,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2353
2353
|
let httpPath = "/";
|
|
2354
2354
|
if (!flags.yes) {
|
|
2355
2355
|
const hasInput = handleCancel(
|
|
2356
|
-
await
|
|
2356
|
+
await p3.confirm({
|
|
2357
2357
|
message: "Does this use case accept input arguments?",
|
|
2358
2358
|
initialValue: true
|
|
2359
2359
|
})
|
|
@@ -2363,7 +2363,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2363
2363
|
});
|
|
2364
2364
|
}
|
|
2365
2365
|
const hasCustomOutput = handleCancel(
|
|
2366
|
-
await
|
|
2366
|
+
await p3.confirm({
|
|
2367
2367
|
message: "Does this use case return custom output data?",
|
|
2368
2368
|
initialValue: false
|
|
2369
2369
|
})
|
|
@@ -2373,20 +2373,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2373
2373
|
});
|
|
2374
2374
|
}
|
|
2375
2375
|
includeTest = handleCancel(
|
|
2376
|
-
await
|
|
2376
|
+
await p3.confirm({
|
|
2377
2377
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2378
2378
|
initialValue: true
|
|
2379
2379
|
})
|
|
2380
2380
|
);
|
|
2381
2381
|
createHttp = handleCancel(
|
|
2382
|
-
await
|
|
2382
|
+
await p3.confirm({
|
|
2383
2383
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2384
2384
|
initialValue: false
|
|
2385
2385
|
})
|
|
2386
2386
|
);
|
|
2387
2387
|
if (createHttp) {
|
|
2388
2388
|
httpMethod = handleCancel(
|
|
2389
|
-
await
|
|
2389
|
+
await p3.select({
|
|
2390
2390
|
message: "HTTP method for route:",
|
|
2391
2391
|
options: [
|
|
2392
2392
|
{ value: "post", label: "POST" },
|
|
@@ -2399,7 +2399,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2399
2399
|
})
|
|
2400
2400
|
);
|
|
2401
2401
|
httpPath = handleCancel(
|
|
2402
|
-
await
|
|
2402
|
+
await p3.text({
|
|
2403
2403
|
message: "Route subpath (relative to feature prefix):",
|
|
2404
2404
|
initialValue: "/",
|
|
2405
2405
|
defaultValue: "/"
|
|
@@ -2407,8 +2407,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2407
2407
|
).trim();
|
|
2408
2408
|
}
|
|
2409
2409
|
}
|
|
2410
|
-
const s =
|
|
2411
|
-
s.start(`Generating use case '${
|
|
2410
|
+
const s = p3.spinner();
|
|
2411
|
+
s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
|
|
2412
2412
|
const options = {
|
|
2413
2413
|
name: ucName,
|
|
2414
2414
|
feature,
|
|
@@ -2440,7 +2440,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2440
2440
|
}
|
|
2441
2441
|
s.stop("Use case generated successfully!");
|
|
2442
2442
|
printGeneratedFiles(files, options.dryRun);
|
|
2443
|
-
|
|
2443
|
+
p3.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2444
2444
|
}
|
|
2445
2445
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2446
2446
|
const projectRoot = findProjectRoot();
|
|
@@ -2451,14 +2451,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2451
2451
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2452
2452
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2453
2453
|
const selected = handleCancel(
|
|
2454
|
-
await
|
|
2454
|
+
await p3.select({
|
|
2455
2455
|
message: "Select target feature:",
|
|
2456
2456
|
options: featureOptions
|
|
2457
2457
|
})
|
|
2458
2458
|
);
|
|
2459
2459
|
if (selected === "__custom__") {
|
|
2460
2460
|
feature = handleCancel(
|
|
2461
|
-
await
|
|
2461
|
+
await p3.text({
|
|
2462
2462
|
message: "Enter feature name:",
|
|
2463
2463
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2464
2464
|
})
|
|
@@ -2468,7 +2468,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2468
2468
|
}
|
|
2469
2469
|
} else {
|
|
2470
2470
|
feature = handleCancel(
|
|
2471
|
-
await
|
|
2471
|
+
await p3.text({
|
|
2472
2472
|
message: "Enter target feature name:",
|
|
2473
2473
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2474
2474
|
})
|
|
@@ -2479,7 +2479,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2479
2479
|
let name = domainNameArg?.trim();
|
|
2480
2480
|
if (!name) {
|
|
2481
2481
|
name = handleCancel(
|
|
2482
|
-
await
|
|
2482
|
+
await p3.text({
|
|
2483
2483
|
message: "Domain model / entity name:",
|
|
2484
2484
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2485
2485
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2491,7 +2491,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2491
2491
|
let properties = [];
|
|
2492
2492
|
if (!flags.yes) {
|
|
2493
2493
|
kind = handleCancel(
|
|
2494
|
-
await
|
|
2494
|
+
await p3.select({
|
|
2495
2495
|
message: "Select domain model type:",
|
|
2496
2496
|
options: [
|
|
2497
2497
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2510,8 +2510,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2510
2510
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2511
2511
|
});
|
|
2512
2512
|
}
|
|
2513
|
-
const s =
|
|
2514
|
-
s.start(`Generating domain model '${
|
|
2513
|
+
const s = p3.spinner();
|
|
2514
|
+
s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
|
|
2515
2515
|
const options = {
|
|
2516
2516
|
name: entityName,
|
|
2517
2517
|
feature,
|
|
@@ -2524,7 +2524,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2524
2524
|
const file = generateDomain(options);
|
|
2525
2525
|
s.stop("Domain model generated successfully!");
|
|
2526
2526
|
printGeneratedFiles([file], options.dryRun);
|
|
2527
|
-
|
|
2527
|
+
p3.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2528
2528
|
}
|
|
2529
2529
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2530
2530
|
const projectRoot = findProjectRoot();
|
|
@@ -2533,7 +2533,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2533
2533
|
let feature = flags.feature?.trim();
|
|
2534
2534
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2535
2535
|
scope = handleCancel(
|
|
2536
|
-
await
|
|
2536
|
+
await p3.select({
|
|
2537
2537
|
message: "Where should this infrastructure implementation be placed?",
|
|
2538
2538
|
options: [
|
|
2539
2539
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2548,14 +2548,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2548
2548
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2549
2549
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2550
2550
|
const selected = handleCancel(
|
|
2551
|
-
await
|
|
2551
|
+
await p3.select({
|
|
2552
2552
|
message: "Select target feature:",
|
|
2553
2553
|
options: featureOptions
|
|
2554
2554
|
})
|
|
2555
2555
|
);
|
|
2556
2556
|
if (selected === "__custom__") {
|
|
2557
2557
|
feature = handleCancel(
|
|
2558
|
-
await
|
|
2558
|
+
await p3.text({
|
|
2559
2559
|
message: "Enter feature name:",
|
|
2560
2560
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2561
2561
|
})
|
|
@@ -2565,7 +2565,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2565
2565
|
}
|
|
2566
2566
|
} else {
|
|
2567
2567
|
feature = handleCancel(
|
|
2568
|
-
await
|
|
2568
|
+
await p3.text({
|
|
2569
2569
|
message: "Enter target feature name:",
|
|
2570
2570
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2571
2571
|
})
|
|
@@ -2578,7 +2578,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2578
2578
|
let name = classNameArg?.trim();
|
|
2579
2579
|
if (!name) {
|
|
2580
2580
|
name = handleCancel(
|
|
2581
|
-
await
|
|
2581
|
+
await p3.text({
|
|
2582
2582
|
message: "Infrastructure class name:",
|
|
2583
2583
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2584
2584
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2597,7 +2597,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2597
2597
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2598
2598
|
);
|
|
2599
2599
|
const ifaceChoice = handleCancel(
|
|
2600
|
-
await
|
|
2600
|
+
await p3.select({
|
|
2601
2601
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2602
2602
|
options: [
|
|
2603
2603
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2609,7 +2609,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2609
2609
|
);
|
|
2610
2610
|
if (ifaceChoice === "__custom__") {
|
|
2611
2611
|
implementsInterface = handleCancel(
|
|
2612
|
-
await
|
|
2612
|
+
await p3.text({
|
|
2613
2613
|
message: "Enter interface or abstract class name:",
|
|
2614
2614
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2615
2615
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2620,14 +2620,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2620
2620
|
}
|
|
2621
2621
|
} else {
|
|
2622
2622
|
const wantImpl = handleCancel(
|
|
2623
|
-
await
|
|
2623
|
+
await p3.confirm({
|
|
2624
2624
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2625
2625
|
initialValue: false
|
|
2626
2626
|
})
|
|
2627
2627
|
);
|
|
2628
2628
|
if (wantImpl) {
|
|
2629
2629
|
implementsInterface = handleCancel(
|
|
2630
|
-
await
|
|
2630
|
+
await p3.text({
|
|
2631
2631
|
message: "Enter interface or abstract class name:",
|
|
2632
2632
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2633
2633
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2647,7 +2647,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2647
2647
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2648
2648
|
if (allInterfaces.length > 0) {
|
|
2649
2649
|
const ifaceChoice = handleCancel(
|
|
2650
|
-
await
|
|
2650
|
+
await p3.select({
|
|
2651
2651
|
message: "Does this class implement an interface or abstract class?",
|
|
2652
2652
|
options: [
|
|
2653
2653
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2659,7 +2659,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2659
2659
|
);
|
|
2660
2660
|
if (ifaceChoice === "__custom__") {
|
|
2661
2661
|
implementsInterface = handleCancel(
|
|
2662
|
-
await
|
|
2662
|
+
await p3.text({
|
|
2663
2663
|
message: "Enter interface or abstract class name:",
|
|
2664
2664
|
placeholder: "e.g. IAuthRepository",
|
|
2665
2665
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2672,7 +2672,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2672
2672
|
}
|
|
2673
2673
|
if (!flags.yes && !flags.kind) {
|
|
2674
2674
|
kind = handleCancel(
|
|
2675
|
-
await
|
|
2675
|
+
await p3.select({
|
|
2676
2676
|
message: "Select implementation kind:",
|
|
2677
2677
|
options: [
|
|
2678
2678
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2683,7 +2683,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2683
2683
|
);
|
|
2684
2684
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2685
2685
|
registerInProvider = handleCancel(
|
|
2686
|
-
await
|
|
2686
|
+
await p3.confirm({
|
|
2687
2687
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2688
2688
|
initialValue: true
|
|
2689
2689
|
})
|
|
@@ -2692,8 +2692,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2692
2692
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2693
2693
|
registerInProvider = !flags.noProvider;
|
|
2694
2694
|
}
|
|
2695
|
-
const s =
|
|
2696
|
-
s.start(`Generating infrastructure class '${
|
|
2695
|
+
const s = p3.spinner();
|
|
2696
|
+
s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
|
|
2697
2697
|
const files = [];
|
|
2698
2698
|
const options = {
|
|
2699
2699
|
name: className,
|
|
@@ -2728,7 +2728,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2728
2728
|
}
|
|
2729
2729
|
s.stop("Infrastructure class generated successfully!");
|
|
2730
2730
|
printGeneratedFiles(files, options.dryRun);
|
|
2731
|
-
|
|
2731
|
+
p3.outro(pc14.green(`Infrastructure class '${className}' created!`));
|
|
2732
2732
|
}
|
|
2733
2733
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2734
2734
|
const projectRoot = findProjectRoot();
|
|
@@ -2739,14 +2739,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2739
2739
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2740
2740
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2741
2741
|
const selected = handleCancel(
|
|
2742
|
-
await
|
|
2742
|
+
await p3.select({
|
|
2743
2743
|
message: "Select target feature:",
|
|
2744
2744
|
options: featureOptions
|
|
2745
2745
|
})
|
|
2746
2746
|
);
|
|
2747
2747
|
if (selected === "__custom__") {
|
|
2748
2748
|
feature = handleCancel(
|
|
2749
|
-
await
|
|
2749
|
+
await p3.text({
|
|
2750
2750
|
message: "Enter feature name:",
|
|
2751
2751
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2752
2752
|
})
|
|
@@ -2756,7 +2756,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2756
2756
|
}
|
|
2757
2757
|
} else {
|
|
2758
2758
|
feature = handleCancel(
|
|
2759
|
-
await
|
|
2759
|
+
await p3.text({
|
|
2760
2760
|
message: "Enter target feature name:",
|
|
2761
2761
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2762
2762
|
})
|
|
@@ -2767,7 +2767,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2767
2767
|
let name = interfaceNameArg?.trim();
|
|
2768
2768
|
if (!name) {
|
|
2769
2769
|
name = handleCancel(
|
|
2770
|
-
await
|
|
2770
|
+
await p3.text({
|
|
2771
2771
|
message: "Domain interface name:",
|
|
2772
2772
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2773
2773
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2785,7 +2785,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2785
2785
|
if (!flags.yes) {
|
|
2786
2786
|
if (existingEntities.length > 0) {
|
|
2787
2787
|
const selectedEntity = handleCancel(
|
|
2788
|
-
await
|
|
2788
|
+
await p3.select({
|
|
2789
2789
|
message: "Select domain entity associated with this interface:",
|
|
2790
2790
|
options: [
|
|
2791
2791
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2796,7 +2796,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2796
2796
|
);
|
|
2797
2797
|
if (selectedEntity === "__custom__") {
|
|
2798
2798
|
entityName = handleCancel(
|
|
2799
|
-
await
|
|
2799
|
+
await p3.text({
|
|
2800
2800
|
message: "Enter entity name:",
|
|
2801
2801
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2802
2802
|
})
|
|
@@ -2806,7 +2806,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2806
2806
|
}
|
|
2807
2807
|
}
|
|
2808
2808
|
kind = handleCancel(
|
|
2809
|
-
await
|
|
2809
|
+
await p3.select({
|
|
2810
2810
|
message: "Interface definition style:",
|
|
2811
2811
|
options: [
|
|
2812
2812
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2816,7 +2816,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2816
2816
|
})
|
|
2817
2817
|
);
|
|
2818
2818
|
methodsPreset = handleCancel(
|
|
2819
|
-
await
|
|
2819
|
+
await p3.select({
|
|
2820
2820
|
message: "Method signature preset:",
|
|
2821
2821
|
options: [
|
|
2822
2822
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2826,22 +2826,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2826
2826
|
})
|
|
2827
2827
|
);
|
|
2828
2828
|
generateStub = handleCancel(
|
|
2829
|
-
await
|
|
2829
|
+
await p3.confirm({
|
|
2830
2830
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2831
2831
|
initialValue: true
|
|
2832
2832
|
})
|
|
2833
2833
|
);
|
|
2834
2834
|
if (generateStub) {
|
|
2835
2835
|
registerInProvider = handleCancel(
|
|
2836
|
-
await
|
|
2836
|
+
await p3.confirm({
|
|
2837
2837
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2838
2838
|
initialValue: true
|
|
2839
2839
|
})
|
|
2840
2840
|
);
|
|
2841
2841
|
}
|
|
2842
2842
|
}
|
|
2843
|
-
const s =
|
|
2844
|
-
s.start(`Generating interface '${
|
|
2843
|
+
const s = p3.spinner();
|
|
2844
|
+
s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
|
|
2845
2845
|
const files = [];
|
|
2846
2846
|
const options = {
|
|
2847
2847
|
name: ifaceName,
|
|
@@ -2890,7 +2890,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2890
2890
|
}
|
|
2891
2891
|
s.stop("Interface generated successfully!");
|
|
2892
2892
|
printGeneratedFiles(files, options.dryRun);
|
|
2893
|
-
|
|
2893
|
+
p3.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2894
2894
|
}
|
|
2895
2895
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2896
2896
|
const projectRoot = findProjectRoot();
|
|
@@ -2901,14 +2901,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2901
2901
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2902
2902
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2903
2903
|
const selected = handleCancel(
|
|
2904
|
-
await
|
|
2904
|
+
await p3.select({
|
|
2905
2905
|
message: "Select target feature:",
|
|
2906
2906
|
options: featureOptions
|
|
2907
2907
|
})
|
|
2908
2908
|
);
|
|
2909
2909
|
if (selected === "__custom__") {
|
|
2910
2910
|
feature = handleCancel(
|
|
2911
|
-
await
|
|
2911
|
+
await p3.text({
|
|
2912
2912
|
message: "Enter feature name:",
|
|
2913
2913
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2914
2914
|
})
|
|
@@ -2918,7 +2918,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2918
2918
|
}
|
|
2919
2919
|
} else {
|
|
2920
2920
|
feature = handleCancel(
|
|
2921
|
-
await
|
|
2921
|
+
await p3.text({
|
|
2922
2922
|
message: "Enter target feature name:",
|
|
2923
2923
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2924
2924
|
})
|
|
@@ -2929,7 +2929,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2929
2929
|
let name = handlerNameArg?.trim();
|
|
2930
2930
|
if (!name) {
|
|
2931
2931
|
name = handleCancel(
|
|
2932
|
-
await
|
|
2932
|
+
await p3.text({
|
|
2933
2933
|
message: "HTTP Route Handler class name:",
|
|
2934
2934
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2935
2935
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2948,7 +2948,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2948
2948
|
let validationFields = [];
|
|
2949
2949
|
if (!flags.yes) {
|
|
2950
2950
|
method = handleCancel(
|
|
2951
|
-
await
|
|
2951
|
+
await p3.select({
|
|
2952
2952
|
message: "HTTP Method:",
|
|
2953
2953
|
options: [
|
|
2954
2954
|
{ value: "get", label: "GET" },
|
|
@@ -2962,7 +2962,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2962
2962
|
);
|
|
2963
2963
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2964
2964
|
subPath = handleCancel(
|
|
2965
|
-
await
|
|
2965
|
+
await p3.text({
|
|
2966
2966
|
message: "Route subpath (relative to feature prefix):",
|
|
2967
2967
|
initialValue: defaultPath,
|
|
2968
2968
|
defaultValue: defaultPath
|
|
@@ -2973,7 +2973,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2973
2973
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2974
2974
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2975
2975
|
const chosenUc = handleCancel(
|
|
2976
|
-
await
|
|
2976
|
+
await p3.select({
|
|
2977
2977
|
message: "Connect this handler to a domain Use Case?",
|
|
2978
2978
|
options: ucOptions,
|
|
2979
2979
|
initialValue: ucOptions[0]?.value
|
|
@@ -2984,7 +2984,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2984
2984
|
}
|
|
2985
2985
|
}
|
|
2986
2986
|
includeValidation = handleCancel(
|
|
2987
|
-
await
|
|
2987
|
+
await p3.confirm({
|
|
2988
2988
|
message: "Include Zod inbound request validation?",
|
|
2989
2989
|
initialValue: method !== "delete"
|
|
2990
2990
|
})
|
|
@@ -2992,7 +2992,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2992
2992
|
if (includeValidation) {
|
|
2993
2993
|
const defaultSource = method === "get" ? "query" : "body";
|
|
2994
2994
|
validationSource = handleCancel(
|
|
2995
|
-
await
|
|
2995
|
+
await p3.select({
|
|
2996
2996
|
message: "Validation data source:",
|
|
2997
2997
|
options: [
|
|
2998
2998
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -3006,8 +3006,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3006
3006
|
});
|
|
3007
3007
|
}
|
|
3008
3008
|
}
|
|
3009
|
-
const s =
|
|
3010
|
-
s.start(`Generating HTTP handler '${
|
|
3009
|
+
const s = p3.spinner();
|
|
3010
|
+
s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
|
|
3011
3011
|
const options = {
|
|
3012
3012
|
name: className,
|
|
3013
3013
|
feature,
|
|
@@ -3025,7 +3025,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3025
3025
|
const files = generateHttpHandler(options);
|
|
3026
3026
|
s.stop("HTTP handler generated successfully!");
|
|
3027
3027
|
printGeneratedFiles(files, options.dryRun);
|
|
3028
|
-
|
|
3028
|
+
p3.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3029
3029
|
}
|
|
3030
3030
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3031
3031
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3035,7 +3035,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3035
3035
|
const formattedMethods = methods.map((m) => {
|
|
3036
3036
|
const isAsync = m.async !== false;
|
|
3037
3037
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3038
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3038
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join(", ") : "";
|
|
3039
3039
|
return {
|
|
3040
3040
|
name: m.name,
|
|
3041
3041
|
isAsync,
|
|
@@ -3105,7 +3105,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3105
3105
|
let name = classNameArg?.trim();
|
|
3106
3106
|
if (!name) {
|
|
3107
3107
|
name = handleCancel(
|
|
3108
|
-
await
|
|
3108
|
+
await p3.text({
|
|
3109
3109
|
message: "Injectable class name:",
|
|
3110
3110
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3111
3111
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3118,7 +3118,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3118
3118
|
let customPath;
|
|
3119
3119
|
if (!flags.yes) {
|
|
3120
3120
|
location = handleCancel(
|
|
3121
|
-
await
|
|
3121
|
+
await p3.select({
|
|
3122
3122
|
message: "Select target architectural location:",
|
|
3123
3123
|
options: [
|
|
3124
3124
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3136,14 +3136,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3136
3136
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3137
3137
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3138
3138
|
const selected = handleCancel(
|
|
3139
|
-
await
|
|
3139
|
+
await p3.select({
|
|
3140
3140
|
message: "Select target feature:",
|
|
3141
3141
|
options: featureOptions
|
|
3142
3142
|
})
|
|
3143
3143
|
);
|
|
3144
3144
|
if (selected === "__custom__") {
|
|
3145
3145
|
feature = handleCancel(
|
|
3146
|
-
await
|
|
3146
|
+
await p3.text({
|
|
3147
3147
|
message: "Enter feature name:",
|
|
3148
3148
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3149
3149
|
})
|
|
@@ -3153,7 +3153,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3153
3153
|
}
|
|
3154
3154
|
} else {
|
|
3155
3155
|
feature = handleCancel(
|
|
3156
|
-
await
|
|
3156
|
+
await p3.text({
|
|
3157
3157
|
message: "Enter target feature name:",
|
|
3158
3158
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3159
3159
|
})
|
|
@@ -3163,7 +3163,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3163
3163
|
feature = toCamelCase(feature);
|
|
3164
3164
|
} else if (location === "custom") {
|
|
3165
3165
|
customPath = handleCancel(
|
|
3166
|
-
await
|
|
3166
|
+
await p3.text({
|
|
3167
3167
|
message: "Enter destination directory path:",
|
|
3168
3168
|
placeholder: "e.g. src/services or lib/utils",
|
|
3169
3169
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3174,14 +3174,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3174
3174
|
let generateInterface2 = false;
|
|
3175
3175
|
if (!flags.yes) {
|
|
3176
3176
|
generateInterface2 = handleCancel(
|
|
3177
|
-
await
|
|
3177
|
+
await p3.confirm({
|
|
3178
3178
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3179
3179
|
initialValue: false
|
|
3180
3180
|
})
|
|
3181
3181
|
);
|
|
3182
3182
|
}
|
|
3183
|
-
const s =
|
|
3184
|
-
s.start(`Generating injectable '${
|
|
3183
|
+
const s = p3.spinner();
|
|
3184
|
+
s.start(`Generating injectable '${pc14.cyan(className)}'...`);
|
|
3185
3185
|
const options = {
|
|
3186
3186
|
name: className,
|
|
3187
3187
|
feature,
|
|
@@ -3195,12 +3195,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3195
3195
|
const files = generateInjectable(options);
|
|
3196
3196
|
s.stop("Injectable class generated successfully!");
|
|
3197
3197
|
printGeneratedFiles(files, options.dryRun);
|
|
3198
|
-
|
|
3198
|
+
p3.outro(pc14.green(`Injectable '${className}' created successfully!`));
|
|
3199
3199
|
}
|
|
3200
3200
|
async function runInteractiveMenu(flags = {}) {
|
|
3201
3201
|
printBanner();
|
|
3202
3202
|
const choice = handleCancel(
|
|
3203
|
-
await
|
|
3203
|
+
await p3.select({
|
|
3204
3204
|
message: "What would you like to create in your architecture?",
|
|
3205
3205
|
options: [
|
|
3206
3206
|
{
|
|
@@ -3308,8 +3308,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3308
3308
|
await runInjectableCommand(name, flags);
|
|
3309
3309
|
break;
|
|
3310
3310
|
default:
|
|
3311
|
-
|
|
3312
|
-
|
|
3311
|
+
p3.log.error(
|
|
3312
|
+
pc14.red(
|
|
3313
3313
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3314
3314
|
)
|
|
3315
3315
|
);
|
|
@@ -3341,7 +3341,7 @@ function findLocalRegistry(startDir = process.cwd()) {
|
|
|
3341
3341
|
let current = path2.resolve(startDir);
|
|
3342
3342
|
while (true) {
|
|
3343
3343
|
const candidate = path2.join(current, "mason-registry");
|
|
3344
|
-
if (
|
|
3344
|
+
if (fs9.existsSync(candidate) && fs9.existsSync(path2.join(candidate, "index.json"))) {
|
|
3345
3345
|
return candidate;
|
|
3346
3346
|
}
|
|
3347
3347
|
const parent = path2.dirname(current);
|
|
@@ -3363,9 +3363,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3363
3363
|
path2.join(process.cwd(), "mason.config.json")
|
|
3364
3364
|
];
|
|
3365
3365
|
for (const configPath of configLocations) {
|
|
3366
|
-
if (
|
|
3366
|
+
if (fs9.existsSync(configPath)) {
|
|
3367
3367
|
try {
|
|
3368
|
-
const config = JSON.parse(
|
|
3368
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
3369
3369
|
if (config.registry && typeof config.registry === "string") {
|
|
3370
3370
|
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
3371
3371
|
}
|
|
@@ -3378,9 +3378,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3378
3378
|
path2.join(process.cwd(), "package.json")
|
|
3379
3379
|
];
|
|
3380
3380
|
for (const pkgPath of pkgLocations) {
|
|
3381
|
-
if (
|
|
3381
|
+
if (fs9.existsSync(pkgPath)) {
|
|
3382
3382
|
try {
|
|
3383
|
-
const pkg = JSON.parse(
|
|
3383
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
3384
3384
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
3385
3385
|
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
3386
3386
|
}
|
|
@@ -3424,10 +3424,10 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
3424
3424
|
baseDir = fileURLToPath(baseDir);
|
|
3425
3425
|
}
|
|
3426
3426
|
const filePath = path2.resolve(baseDir, cleanRelPath);
|
|
3427
|
-
if (!
|
|
3427
|
+
if (!fs9.existsSync(filePath)) {
|
|
3428
3428
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
3429
3429
|
}
|
|
3430
|
-
return
|
|
3430
|
+
return fs9.promises.readFile(filePath, "utf-8");
|
|
3431
3431
|
}
|
|
3432
3432
|
async function fetchRegistryIndex(registryBase) {
|
|
3433
3433
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -3489,26 +3489,26 @@ function findClosestMatch(target, candidates) {
|
|
|
3489
3489
|
return null;
|
|
3490
3490
|
}
|
|
3491
3491
|
function detectPackageManager(projectRoot) {
|
|
3492
|
-
if (
|
|
3493
|
-
if (
|
|
3494
|
-
if (
|
|
3492
|
+
if (fs9.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3493
|
+
if (fs9.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
3494
|
+
if (fs9.existsSync(path2.join(projectRoot, "bun.lockb")) || fs9.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
|
|
3495
3495
|
return "npm";
|
|
3496
3496
|
}
|
|
3497
3497
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
3498
3498
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
3499
3499
|
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
3500
|
-
if (!
|
|
3500
|
+
if (!fs9.existsSync(pkgJsonPath)) {
|
|
3501
3501
|
return requiredPackages;
|
|
3502
3502
|
}
|
|
3503
3503
|
try {
|
|
3504
|
-
const pkg = JSON.parse(
|
|
3504
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
|
|
3505
3505
|
const installed = /* @__PURE__ */ new Set([
|
|
3506
3506
|
...Object.keys(pkg.dependencies || {}),
|
|
3507
3507
|
...Object.keys(pkg.devDependencies || {}),
|
|
3508
3508
|
...Object.keys(pkg.peerDependencies || {})
|
|
3509
3509
|
]);
|
|
3510
|
-
return requiredPackages.filter((
|
|
3511
|
-
const nameOnly =
|
|
3510
|
+
return requiredPackages.filter((p16) => {
|
|
3511
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
3512
3512
|
return !installed.has(nameOnly);
|
|
3513
3513
|
});
|
|
3514
3514
|
} catch {
|
|
@@ -3533,15 +3533,15 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3533
3533
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3534
3534
|
break;
|
|
3535
3535
|
}
|
|
3536
|
-
|
|
3536
|
+
p3.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
|
|
3537
3537
|
execSync(installCmd, {
|
|
3538
3538
|
cwd: projectRoot,
|
|
3539
3539
|
stdio: "inherit"
|
|
3540
3540
|
});
|
|
3541
3541
|
}
|
|
3542
3542
|
function getAllFiles(dir, baseDir = dir) {
|
|
3543
|
-
if (!
|
|
3544
|
-
const entries =
|
|
3543
|
+
if (!fs9.existsSync(dir)) return [];
|
|
3544
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
3545
3545
|
const files = [];
|
|
3546
3546
|
for (const entry of entries) {
|
|
3547
3547
|
const fullPath = path2.join(dir, entry.name);
|
|
@@ -3561,13 +3561,13 @@ function getAllFiles(dir, baseDir = dir) {
|
|
|
3561
3561
|
return files.sort();
|
|
3562
3562
|
}
|
|
3563
3563
|
function computeDirectoryHash(dirPath, files) {
|
|
3564
|
-
if (!
|
|
3564
|
+
if (!fs9.existsSync(dirPath)) return "";
|
|
3565
3565
|
const hash = createHash("sha256");
|
|
3566
|
-
const fileList = (
|
|
3566
|
+
const fileList = (getAllFiles(dirPath)).sort();
|
|
3567
3567
|
for (const relativePath of fileList) {
|
|
3568
3568
|
const absolutePath = path2.join(dirPath, relativePath);
|
|
3569
|
-
if (!
|
|
3570
|
-
const content =
|
|
3569
|
+
if (!fs9.existsSync(absolutePath)) continue;
|
|
3570
|
+
const content = fs9.readFileSync(absolutePath, "utf8");
|
|
3571
3571
|
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
3572
3572
|
hash.update(relativePath);
|
|
3573
3573
|
hash.update(normalizedContent);
|
|
@@ -3577,8 +3577,8 @@ function computeDirectoryHash(dirPath, files) {
|
|
|
3577
3577
|
|
|
3578
3578
|
// src/commands/import.ts
|
|
3579
3579
|
var importPrompts = {
|
|
3580
|
-
select:
|
|
3581
|
-
text:
|
|
3580
|
+
select: p3.select,
|
|
3581
|
+
text: p3.text
|
|
3582
3582
|
};
|
|
3583
3583
|
function rewriteImports(content, aliasMap) {
|
|
3584
3584
|
if (aliasMap.size === 0) return content;
|
|
@@ -3599,9 +3599,9 @@ function getModuleBaseDir(projectRoot, modType) {
|
|
|
3599
3599
|
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3600
3600
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3601
3601
|
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3602
|
-
if (
|
|
3602
|
+
if (fs9.existsSync(inSrc)) return inSrc;
|
|
3603
3603
|
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3604
|
-
if (
|
|
3604
|
+
if (fs9.existsSync(inRoot)) return inRoot;
|
|
3605
3605
|
return null;
|
|
3606
3606
|
}
|
|
3607
3607
|
function checkModuleExists(projectRoot, modType, modName) {
|
|
@@ -3619,13 +3619,13 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3619
3619
|
projectRoot,
|
|
3620
3620
|
path2.join(baseDir, programmaticAlias)
|
|
3621
3621
|
);
|
|
3622
|
-
|
|
3622
|
+
p3.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3623
3623
|
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3624
3624
|
return;
|
|
3625
3625
|
}
|
|
3626
3626
|
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3627
3627
|
if (!existingDir) {
|
|
3628
|
-
|
|
3628
|
+
p3.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14.cyan(depName)}`);
|
|
3629
3629
|
await importModuleRecursive(depType, depName, depName);
|
|
3630
3630
|
return;
|
|
3631
3631
|
}
|
|
@@ -3642,25 +3642,25 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3642
3642
|
}
|
|
3643
3643
|
const localHash = computeDirectoryHash(existingDir);
|
|
3644
3644
|
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3645
|
-
|
|
3645
|
+
p3.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3646
3646
|
importedModules.add(`${depType}:${depName}`);
|
|
3647
3647
|
return;
|
|
3648
3648
|
}
|
|
3649
3649
|
if (flags.overwrite) {
|
|
3650
|
-
|
|
3650
|
+
p3.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3651
3651
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3652
3652
|
const targetDir = path2.join(baseDir, depName);
|
|
3653
3653
|
if (!flags.dryRun) {
|
|
3654
3654
|
if (existingDir !== targetDir) {
|
|
3655
|
-
|
|
3655
|
+
fs9.rmSync(existingDir, { recursive: true, force: true });
|
|
3656
3656
|
}
|
|
3657
|
-
|
|
3657
|
+
fs9.rmSync(targetDir, { recursive: true, force: true });
|
|
3658
3658
|
}
|
|
3659
3659
|
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3660
3660
|
return;
|
|
3661
3661
|
}
|
|
3662
3662
|
if (flags.yes) {
|
|
3663
|
-
|
|
3663
|
+
p3.log.info(`Using existing '${depType}/${depName}'`);
|
|
3664
3664
|
importedModules.add(`${depType}:${depName}`);
|
|
3665
3665
|
return;
|
|
3666
3666
|
}
|
|
@@ -3684,7 +3684,7 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3684
3684
|
})
|
|
3685
3685
|
);
|
|
3686
3686
|
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3687
|
-
|
|
3687
|
+
p3.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3688
3688
|
importedModules.add(`${depType}:${depName}`);
|
|
3689
3689
|
return;
|
|
3690
3690
|
}
|
|
@@ -3709,19 +3709,19 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3709
3709
|
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3710
3710
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3711
3711
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
3712
|
-
|
|
3712
|
+
p3.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3713
3713
|
await importModuleRecursive(depType, depName, newName);
|
|
3714
3714
|
return;
|
|
3715
3715
|
}
|
|
3716
3716
|
if (choice === "overwrite") {
|
|
3717
|
-
|
|
3717
|
+
p3.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3718
3718
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3719
3719
|
const targetDir = path2.join(baseDir, depName);
|
|
3720
3720
|
if (!flags.dryRun) {
|
|
3721
3721
|
if (existingDir !== targetDir) {
|
|
3722
|
-
|
|
3722
|
+
fs9.rmSync(existingDir, { recursive: true, force: true });
|
|
3723
3723
|
}
|
|
3724
|
-
|
|
3724
|
+
fs9.rmSync(targetDir, { recursive: true, force: true });
|
|
3725
3725
|
}
|
|
3726
3726
|
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3727
3727
|
return;
|
|
@@ -3751,19 +3751,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3751
3751
|
);
|
|
3752
3752
|
}
|
|
3753
3753
|
if (type !== "shared" && type !== "feature") {
|
|
3754
|
-
|
|
3754
|
+
p3.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3755
3755
|
process.exit(1);
|
|
3756
3756
|
}
|
|
3757
3757
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3758
|
-
const s =
|
|
3759
|
-
s.start(`Connecting to registry at ${
|
|
3758
|
+
const s = p3.spinner();
|
|
3759
|
+
s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
|
|
3760
3760
|
let index;
|
|
3761
3761
|
try {
|
|
3762
3762
|
index = await fetchRegistryIndex(registryBase);
|
|
3763
3763
|
s.stop("Registry index loaded.");
|
|
3764
3764
|
} catch (err) {
|
|
3765
|
-
s.stop(
|
|
3766
|
-
|
|
3765
|
+
s.stop(pc14.red("Failed to load registry index."));
|
|
3766
|
+
p3.log.error(pc14.red(err.message || String(err)));
|
|
3767
3767
|
process.exit(1);
|
|
3768
3768
|
}
|
|
3769
3769
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3771,7 +3771,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3771
3771
|
let name = nameArg?.trim();
|
|
3772
3772
|
if (!name) {
|
|
3773
3773
|
if (availableList.length === 0) {
|
|
3774
|
-
|
|
3774
|
+
p3.log.warn(`No ${type} modules available in the registry.`);
|
|
3775
3775
|
return;
|
|
3776
3776
|
}
|
|
3777
3777
|
name = handleCancel(
|
|
@@ -3787,9 +3787,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3787
3787
|
}
|
|
3788
3788
|
if (!candidateNames.includes(name)) {
|
|
3789
3789
|
const closest = findClosestMatch(name, candidateNames);
|
|
3790
|
-
|
|
3790
|
+
p3.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3791
3791
|
if (closest) {
|
|
3792
|
-
|
|
3792
|
+
p3.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
|
|
3793
3793
|
}
|
|
3794
3794
|
process.exit(1);
|
|
3795
3795
|
}
|
|
@@ -3799,14 +3799,14 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3799
3799
|
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3800
3800
|
const modKey = `${modType}:${targetName}`;
|
|
3801
3801
|
if (importedModules.has(modKey)) return;
|
|
3802
|
-
|
|
3803
|
-
`Importing ${modType}: ${
|
|
3802
|
+
p3.log.step(
|
|
3803
|
+
`Importing ${modType}: ${pc14.cyan(targetName)}${targetName !== registryName ? pc14.dim(` (from ${registryName})`) : ""}...`
|
|
3804
3804
|
);
|
|
3805
3805
|
let manifest;
|
|
3806
3806
|
try {
|
|
3807
3807
|
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3808
3808
|
} catch (err) {
|
|
3809
|
-
|
|
3809
|
+
p3.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3810
3810
|
process.exit(1);
|
|
3811
3811
|
}
|
|
3812
3812
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
@@ -3852,21 +3852,21 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3852
3852
|
if (file === "registry.json") continue;
|
|
3853
3853
|
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3854
3854
|
const localFilePath = path2.join(targetDir, file);
|
|
3855
|
-
if (
|
|
3856
|
-
|
|
3857
|
-
` \u21B3 Skipping existing file: ${
|
|
3855
|
+
if (fs9.existsSync(localFilePath) && !isOverwrite) {
|
|
3856
|
+
p3.log.warn(
|
|
3857
|
+
` \u21B3 Skipping existing file: ${pc14.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3858
3858
|
);
|
|
3859
3859
|
continue;
|
|
3860
3860
|
}
|
|
3861
3861
|
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3862
3862
|
content = rewriteImports(content, aliasMap);
|
|
3863
3863
|
if (!flags.dryRun) {
|
|
3864
|
-
|
|
3865
|
-
|
|
3864
|
+
fs9.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
3865
|
+
fs9.writeFileSync(localFilePath, content, "utf-8");
|
|
3866
3866
|
}
|
|
3867
3867
|
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
3868
3868
|
totalWrittenFiles.push(displayPath);
|
|
3869
|
-
|
|
3869
|
+
p3.log.message(` ${pc14.green("+")} ${displayPath}`);
|
|
3870
3870
|
}
|
|
3871
3871
|
importedModules.add(modKey);
|
|
3872
3872
|
}
|
|
@@ -3874,564 +3874,35 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3874
3874
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3875
3875
|
if (missingNpm.length > 0) {
|
|
3876
3876
|
if (flags.skipInstall || flags.dryRun) {
|
|
3877
|
-
|
|
3877
|
+
p3.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
|
|
3878
3878
|
} else {
|
|
3879
3879
|
try {
|
|
3880
3880
|
installNpmPackages(projectRoot, missingNpm);
|
|
3881
3881
|
} catch (err) {
|
|
3882
|
-
|
|
3882
|
+
p3.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3883
3883
|
}
|
|
3884
3884
|
}
|
|
3885
3885
|
}
|
|
3886
3886
|
if (flags.dryRun) {
|
|
3887
|
-
|
|
3887
|
+
p3.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
|
|
3888
3888
|
} else {
|
|
3889
3889
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3890
3890
|
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
|
|
3891
|
-
|
|
3892
|
-
|
|
3891
|
+
p3.log.success(
|
|
3892
|
+
pc14.green(
|
|
3893
3893
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3894
3894
|
)
|
|
3895
3895
|
);
|
|
3896
3896
|
}
|
|
3897
3897
|
}
|
|
3898
|
-
function loadMasterIndex(projectRoot) {
|
|
3899
|
-
const indexPath = path2.join(projectRoot, "index.json");
|
|
3900
|
-
if (!fs12.existsSync(indexPath)) {
|
|
3901
|
-
const initialIndex = { shared: [], features: [] };
|
|
3902
|
-
fs12.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
|
|
3903
|
-
return initialIndex;
|
|
3904
|
-
}
|
|
3905
|
-
return JSON.parse(fs12.readFileSync(indexPath, "utf-8"));
|
|
3906
|
-
}
|
|
3907
|
-
function saveMasterIndex(projectRoot, index) {
|
|
3908
|
-
const indexPath = path2.join(projectRoot, "index.json");
|
|
3909
|
-
index.shared.sort((a, b) => a.name.localeCompare(b.name));
|
|
3910
|
-
index.features.sort((a, b) => a.name.localeCompare(b.name));
|
|
3911
|
-
fs12.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
3912
|
-
}
|
|
3913
|
-
function discoverDiskModules(projectRoot) {
|
|
3914
|
-
const results = [];
|
|
3915
|
-
const sharedDir = resolveSharedDir(projectRoot);
|
|
3916
|
-
if (fs12.existsSync(sharedDir)) {
|
|
3917
|
-
const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
|
|
3918
|
-
for (const entry of entries) {
|
|
3919
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3920
|
-
results.push({ name: entry.name, type: "shared", dir: path2.join(sharedDir, entry.name) });
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
3925
|
-
if (fs12.existsSync(featuresDir)) {
|
|
3926
|
-
const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
|
|
3927
|
-
for (const entry of entries) {
|
|
3928
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3929
|
-
results.push({ name: entry.name, type: "feature", dir: path2.join(featuresDir, entry.name) });
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
|
-
return results;
|
|
3934
|
-
}
|
|
3935
|
-
function checkGitStatusForDir(projectRoot, dirPath) {
|
|
3936
|
-
try {
|
|
3937
|
-
const relPath = path2.relative(projectRoot, dirPath);
|
|
3938
|
-
const output = execSync(`git status --porcelain -- "${relPath}"`, {
|
|
3939
|
-
cwd: projectRoot,
|
|
3940
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
3941
|
-
encoding: "utf-8"
|
|
3942
|
-
}).trim();
|
|
3943
|
-
if (!output) {
|
|
3944
|
-
return { hasChanges: false, filesCount: 0 };
|
|
3945
|
-
}
|
|
3946
|
-
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
3947
|
-
return { hasChanges: true, filesCount: lines.length };
|
|
3948
|
-
} catch {
|
|
3949
|
-
return { hasChanges: false, filesCount: 0 };
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
function getRegistryDiff(projectRoot) {
|
|
3953
|
-
const index = loadMasterIndex(projectRoot);
|
|
3954
|
-
const diskModules = discoverDiskModules(projectRoot);
|
|
3955
|
-
const diskMap = /* @__PURE__ */ new Map();
|
|
3956
|
-
for (const mod of diskModules) {
|
|
3957
|
-
diskMap.set(`${mod.type}:${mod.name}`, mod);
|
|
3958
|
-
}
|
|
3959
|
-
const allKeys = /* @__PURE__ */ new Set();
|
|
3960
|
-
for (const item of index.shared) {
|
|
3961
|
-
allKeys.add(`shared:${item.name}`);
|
|
3962
|
-
}
|
|
3963
|
-
for (const item of index.features) {
|
|
3964
|
-
allKeys.add(`feature:${item.name}`);
|
|
3965
|
-
}
|
|
3966
|
-
for (const mod of diskModules) {
|
|
3967
|
-
allKeys.add(`${mod.type}:${mod.name}`);
|
|
3968
|
-
}
|
|
3969
|
-
const statuses = [];
|
|
3970
|
-
for (const key of allKeys) {
|
|
3971
|
-
const [typeStr, name] = key.split(":");
|
|
3972
|
-
const type = typeStr;
|
|
3973
|
-
const registeredEntry = (type === "shared" ? index.shared : index.features).find(
|
|
3974
|
-
(e) => e.name === name
|
|
3975
|
-
);
|
|
3976
|
-
const isRegistered = Boolean(registeredEntry);
|
|
3977
|
-
const diskInfo = diskMap.get(key);
|
|
3978
|
-
const existsOnDisk = Boolean(diskInfo && fs12.existsSync(diskInfo.dir));
|
|
3979
|
-
let hasUncommittedGitChanges = false;
|
|
3980
|
-
let uncommittedFilesCount = 0;
|
|
3981
|
-
let hasIntegrityDrift = false;
|
|
3982
|
-
let localIntegrity;
|
|
3983
|
-
if (existsOnDisk && diskInfo) {
|
|
3984
|
-
const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
|
|
3985
|
-
hasUncommittedGitChanges = git.hasChanges;
|
|
3986
|
-
uncommittedFilesCount = git.filesCount;
|
|
3987
|
-
localIntegrity = computeDirectoryHash(diskInfo.dir);
|
|
3988
|
-
if (isRegistered && registeredEntry?.integrity) {
|
|
3989
|
-
hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
|
|
3990
|
-
}
|
|
3991
|
-
}
|
|
3992
|
-
statuses.push({
|
|
3993
|
-
name,
|
|
3994
|
-
type,
|
|
3995
|
-
isRegistered,
|
|
3996
|
-
existsOnDisk,
|
|
3997
|
-
hasUncommittedGitChanges,
|
|
3998
|
-
uncommittedFilesCount,
|
|
3999
|
-
hasIntegrityDrift,
|
|
4000
|
-
localIntegrity,
|
|
4001
|
-
registeredIntegrity: registeredEntry?.integrity
|
|
4002
|
-
});
|
|
4003
|
-
}
|
|
4004
|
-
return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
4005
|
-
}
|
|
4006
|
-
async function registerModule(typeArg, nameArg, flags = {}) {
|
|
4007
|
-
const projectRoot = findProjectRoot();
|
|
4008
|
-
const index = loadMasterIndex(projectRoot);
|
|
4009
|
-
let type = typeArg?.trim();
|
|
4010
|
-
if (!type) {
|
|
4011
|
-
type = handleCancel(
|
|
4012
|
-
await p15.select({
|
|
4013
|
-
message: "Module type to register:",
|
|
4014
|
-
options: [
|
|
4015
|
-
{ value: "feature", label: "Feature" },
|
|
4016
|
-
{ value: "shared", label: "Shared Module" }
|
|
4017
|
-
]
|
|
4018
|
-
})
|
|
4019
|
-
);
|
|
4020
|
-
}
|
|
4021
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4022
|
-
const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
|
|
4023
|
-
const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
|
|
4024
|
-
let name = nameArg?.trim();
|
|
4025
|
-
if (!name) {
|
|
4026
|
-
if (unregistered.length > 0) {
|
|
4027
|
-
const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
|
|
4028
|
-
opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
|
|
4029
|
-
const choice = handleCancel(
|
|
4030
|
-
await p15.select({
|
|
4031
|
-
message: `Select a ${type} to register:`,
|
|
4032
|
-
options: opts
|
|
4033
|
-
})
|
|
4034
|
-
);
|
|
4035
|
-
if (choice === "__custom__") {
|
|
4036
|
-
name = handleCancel(
|
|
4037
|
-
await p15.text({
|
|
4038
|
-
message: `Enter ${type} name:`,
|
|
4039
|
-
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4040
|
-
})
|
|
4041
|
-
).trim();
|
|
4042
|
-
} else {
|
|
4043
|
-
name = choice;
|
|
4044
|
-
}
|
|
4045
|
-
} else {
|
|
4046
|
-
name = handleCancel(
|
|
4047
|
-
await p15.text({
|
|
4048
|
-
message: `Enter ${type} name to register:`,
|
|
4049
|
-
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4050
|
-
})
|
|
4051
|
-
).trim();
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
4054
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4055
|
-
const modDir = path2.join(folder, name);
|
|
4056
|
-
if (!fs12.existsSync(modDir)) {
|
|
4057
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4058
|
-
process.exit(1);
|
|
4059
|
-
}
|
|
4060
|
-
const files = getAllFiles(modDir);
|
|
4061
|
-
const integrity = computeDirectoryHash(modDir, files);
|
|
4062
|
-
const manifestPath = path2.join(modDir, "registry.json");
|
|
4063
|
-
let manifest;
|
|
4064
|
-
if (fs12.existsSync(manifestPath)) {
|
|
4065
|
-
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4066
|
-
} else {
|
|
4067
|
-
manifest = {
|
|
4068
|
-
name,
|
|
4069
|
-
type,
|
|
4070
|
-
description: `${name} ${type} module`,
|
|
4071
|
-
version: "1.0.0",
|
|
4072
|
-
dependencies: {
|
|
4073
|
-
npm: [],
|
|
4074
|
-
shared: [],
|
|
4075
|
-
features: []
|
|
4076
|
-
},
|
|
4077
|
-
files: []
|
|
4078
|
-
};
|
|
4079
|
-
}
|
|
4080
|
-
manifest.name = name;
|
|
4081
|
-
manifest.type = type;
|
|
4082
|
-
manifest.files = files;
|
|
4083
|
-
manifest.integrity = integrity;
|
|
4084
|
-
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4085
|
-
const targetList = type === "shared" ? index.shared : index.features;
|
|
4086
|
-
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4087
|
-
if (existingIdx >= 0) {
|
|
4088
|
-
targetList[existingIdx] = {
|
|
4089
|
-
name,
|
|
4090
|
-
description: manifest.description,
|
|
4091
|
-
version: manifest.version,
|
|
4092
|
-
integrity
|
|
4093
|
-
};
|
|
4094
|
-
} else {
|
|
4095
|
-
targetList.push({
|
|
4096
|
-
name,
|
|
4097
|
-
description: manifest.description,
|
|
4098
|
-
version: manifest.version,
|
|
4099
|
-
integrity
|
|
4100
|
-
});
|
|
4101
|
-
}
|
|
4102
|
-
saveMasterIndex(projectRoot, index);
|
|
4103
|
-
p15.log.success(
|
|
4104
|
-
pc15.green(
|
|
4105
|
-
`\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
|
|
4106
|
-
)
|
|
4107
|
-
);
|
|
4108
|
-
}
|
|
4109
|
-
async function commitModule(typeArg, nameArg, flags = {}) {
|
|
4110
|
-
const projectRoot = findProjectRoot();
|
|
4111
|
-
let type = typeArg?.trim();
|
|
4112
|
-
if (!type) {
|
|
4113
|
-
type = handleCancel(
|
|
4114
|
-
await p15.select({
|
|
4115
|
-
message: "Module type to commit:",
|
|
4116
|
-
options: [
|
|
4117
|
-
{ value: "feature", label: "Feature" },
|
|
4118
|
-
{ value: "shared", label: "Shared Module" }
|
|
4119
|
-
]
|
|
4120
|
-
})
|
|
4121
|
-
);
|
|
4122
|
-
}
|
|
4123
|
-
let name = nameArg?.trim();
|
|
4124
|
-
if (!name) {
|
|
4125
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4126
|
-
const opts = diskModules.map((m) => {
|
|
4127
|
-
const git = checkGitStatusForDir(projectRoot, m.dir);
|
|
4128
|
-
return {
|
|
4129
|
-
value: m.name,
|
|
4130
|
-
label: m.name,
|
|
4131
|
-
hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
|
|
4132
|
-
};
|
|
4133
|
-
});
|
|
4134
|
-
name = handleCancel(
|
|
4135
|
-
await p15.select({
|
|
4136
|
-
message: `Select ${type} to commit:`,
|
|
4137
|
-
options: opts
|
|
4138
|
-
})
|
|
4139
|
-
);
|
|
4140
|
-
}
|
|
4141
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4142
|
-
const modDir = path2.join(folder, name);
|
|
4143
|
-
if (!fs12.existsSync(modDir)) {
|
|
4144
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4145
|
-
process.exit(1);
|
|
4146
|
-
}
|
|
4147
|
-
const relModDir = path2.relative(projectRoot, modDir);
|
|
4148
|
-
const gitStatus = checkGitStatusForDir(projectRoot, modDir);
|
|
4149
|
-
if (!gitStatus.hasChanges) {
|
|
4150
|
-
p15.log.warn(pc15.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
|
|
4151
|
-
return;
|
|
4152
|
-
}
|
|
4153
|
-
let message = flags.message?.trim();
|
|
4154
|
-
if (!message) {
|
|
4155
|
-
message = handleCancel(
|
|
4156
|
-
await p15.text({
|
|
4157
|
-
message: `Commit message for ${type}/${name}:`,
|
|
4158
|
-
placeholder: `e.g. feat(${name}): update use cases and dependencies`,
|
|
4159
|
-
validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
|
|
4160
|
-
})
|
|
4161
|
-
).trim();
|
|
4162
|
-
}
|
|
4163
|
-
const s = p15.spinner();
|
|
4164
|
-
s.start(`Committing '${type}/${name}' to git repository...`);
|
|
4165
|
-
try {
|
|
4166
|
-
execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
4167
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
|
|
4168
|
-
cwd: projectRoot,
|
|
4169
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4170
|
-
});
|
|
4171
|
-
s.stop(`Changes for '${type}/${name}' committed to git.`);
|
|
4172
|
-
p15.log.success(pc15.green(`\u2713 Committed '${type}/${name}': "${message}"`));
|
|
4173
|
-
} catch (err) {
|
|
4174
|
-
s.stop(pc15.red("Git commit failed."));
|
|
4175
|
-
p15.log.error(pc15.red(err.message || String(err)));
|
|
4176
|
-
process.exit(1);
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
async function registerModuleChanges(typeArg, nameArg, flags = {}) {
|
|
4180
|
-
const projectRoot = findProjectRoot();
|
|
4181
|
-
const index = loadMasterIndex(projectRoot);
|
|
4182
|
-
let type = typeArg?.trim();
|
|
4183
|
-
if (!type) {
|
|
4184
|
-
type = handleCancel(
|
|
4185
|
-
await p15.select({
|
|
4186
|
-
message: "Module type to update:",
|
|
4187
|
-
options: [
|
|
4188
|
-
{ value: "feature", label: "Feature" },
|
|
4189
|
-
{ value: "shared", label: "Shared Module" }
|
|
4190
|
-
]
|
|
4191
|
-
})
|
|
4192
|
-
);
|
|
4193
|
-
}
|
|
4194
|
-
let name = nameArg?.trim();
|
|
4195
|
-
if (!name) {
|
|
4196
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4197
|
-
const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
|
|
4198
|
-
const opts = diskModules.map((m) => {
|
|
4199
|
-
const d = diffs.find((item) => item.name === m.name);
|
|
4200
|
-
return {
|
|
4201
|
-
value: m.name,
|
|
4202
|
-
label: m.name,
|
|
4203
|
-
hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
|
|
4204
|
-
};
|
|
4205
|
-
});
|
|
4206
|
-
name = handleCancel(
|
|
4207
|
-
await p15.select({
|
|
4208
|
-
message: `Select ${type} to register changes:`,
|
|
4209
|
-
options: opts
|
|
4210
|
-
})
|
|
4211
|
-
);
|
|
4212
|
-
}
|
|
4213
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4214
|
-
const modDir = path2.join(folder, name);
|
|
4215
|
-
if (!fs12.existsSync(modDir)) {
|
|
4216
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4217
|
-
process.exit(1);
|
|
4218
|
-
}
|
|
4219
|
-
const files = getAllFiles(modDir);
|
|
4220
|
-
const integrity = computeDirectoryHash(modDir, files);
|
|
4221
|
-
const manifestPath = path2.join(modDir, "registry.json");
|
|
4222
|
-
let manifest;
|
|
4223
|
-
if (fs12.existsSync(manifestPath)) {
|
|
4224
|
-
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4225
|
-
} else {
|
|
4226
|
-
manifest = {
|
|
4227
|
-
name,
|
|
4228
|
-
type,
|
|
4229
|
-
description: `${name} ${type} module`,
|
|
4230
|
-
version: "1.0.0",
|
|
4231
|
-
dependencies: { npm: [], shared: [], features: [] },
|
|
4232
|
-
files: []
|
|
4233
|
-
};
|
|
4234
|
-
}
|
|
4235
|
-
manifest.files = files;
|
|
4236
|
-
manifest.integrity = integrity;
|
|
4237
|
-
if (flags.version) {
|
|
4238
|
-
manifest.version = flags.version;
|
|
4239
|
-
}
|
|
4240
|
-
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4241
|
-
const targetList = type === "shared" ? index.shared : index.features;
|
|
4242
|
-
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4243
|
-
if (existingIdx >= 0) {
|
|
4244
|
-
targetList[existingIdx] = {
|
|
4245
|
-
name,
|
|
4246
|
-
description: manifest.description,
|
|
4247
|
-
version: manifest.version,
|
|
4248
|
-
integrity
|
|
4249
|
-
};
|
|
4250
|
-
} else {
|
|
4251
|
-
targetList.push({
|
|
4252
|
-
name,
|
|
4253
|
-
description: manifest.description,
|
|
4254
|
-
version: manifest.version,
|
|
4255
|
-
integrity
|
|
4256
|
-
});
|
|
4257
|
-
}
|
|
4258
|
-
saveMasterIndex(projectRoot, index);
|
|
4259
|
-
p15.log.success(
|
|
4260
|
-
pc15.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
|
|
4261
|
-
);
|
|
4262
|
-
}
|
|
4263
|
-
async function unregisterModule(typeArg, nameArg, flags = {}) {
|
|
4264
|
-
const projectRoot = findProjectRoot();
|
|
4265
|
-
const index = loadMasterIndex(projectRoot);
|
|
4266
|
-
let type = typeArg?.trim();
|
|
4267
|
-
if (!type) {
|
|
4268
|
-
type = handleCancel(
|
|
4269
|
-
await p15.select({
|
|
4270
|
-
message: "Module type to unregister:",
|
|
4271
|
-
options: [
|
|
4272
|
-
{ value: "feature", label: "Feature" },
|
|
4273
|
-
{ value: "shared", label: "Shared Module" }
|
|
4274
|
-
]
|
|
4275
|
-
})
|
|
4276
|
-
);
|
|
4277
|
-
}
|
|
4278
|
-
const registeredList = type === "shared" ? index.shared : index.features;
|
|
4279
|
-
if (registeredList.length === 0) {
|
|
4280
|
-
p15.log.warn(`No registered ${type} modules found in index.json.`);
|
|
4281
|
-
return;
|
|
4282
|
-
}
|
|
4283
|
-
let name = nameArg?.trim();
|
|
4284
|
-
if (!name) {
|
|
4285
|
-
name = handleCancel(
|
|
4286
|
-
await p15.select({
|
|
4287
|
-
message: `Select ${type} to unregister from index.json:`,
|
|
4288
|
-
options: registeredList.map((item) => ({
|
|
4289
|
-
value: item.name,
|
|
4290
|
-
label: item.name,
|
|
4291
|
-
hint: item.description
|
|
4292
|
-
}))
|
|
4293
|
-
})
|
|
4294
|
-
);
|
|
4295
|
-
}
|
|
4296
|
-
if (type === "shared") {
|
|
4297
|
-
index.shared = index.shared.filter((item) => item.name !== name);
|
|
4298
|
-
} else {
|
|
4299
|
-
index.features = index.features.filter((item) => item.name !== name);
|
|
4300
|
-
}
|
|
4301
|
-
saveMasterIndex(projectRoot, index);
|
|
4302
|
-
p15.log.success(
|
|
4303
|
-
pc15.green(
|
|
4304
|
-
`\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
|
|
4305
|
-
)
|
|
4306
|
-
);
|
|
4307
|
-
}
|
|
4308
|
-
function diffRegistry(flags = {}) {
|
|
4309
|
-
const projectRoot = findProjectRoot();
|
|
4310
|
-
const diffs = getRegistryDiff(projectRoot);
|
|
4311
|
-
p15.log.step(pc15.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
|
|
4312
|
-
const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
|
|
4313
|
-
const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
|
|
4314
|
-
const committedDrift = diffs.filter(
|
|
4315
|
-
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
|
|
4316
|
-
);
|
|
4317
|
-
const upToDate = diffs.filter(
|
|
4318
|
-
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
|
|
4319
|
-
);
|
|
4320
|
-
const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
|
|
4321
|
-
if (unregistered.length > 0) {
|
|
4322
|
-
p15.log.message(pc15.yellow(`
|
|
4323
|
-
\u{1F4E6} Unregistered Modules (${unregistered.length}):`));
|
|
4324
|
-
for (const mod of unregistered) {
|
|
4325
|
-
p15.log.message(` ${pc15.red("\u25CF")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(not registered in index.json)")}`);
|
|
4326
|
-
}
|
|
4327
|
-
}
|
|
4328
|
-
if (uncommitted.length > 0) {
|
|
4329
|
-
p15.log.message(pc15.yellow(`
|
|
4330
|
-
\u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
|
|
4331
|
-
for (const mod of uncommitted) {
|
|
4332
|
-
p15.log.message(
|
|
4333
|
-
` ${pc15.yellow("\u25B2")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
|
|
4334
|
-
);
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
if (committedDrift.length > 0) {
|
|
4338
|
-
p15.log.message(pc15.cyan(`
|
|
4339
|
-
\u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
|
|
4340
|
-
for (const mod of committedDrift) {
|
|
4341
|
-
p15.log.message(
|
|
4342
|
-
` ${pc15.cyan("\u25C6")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(
|
|
4343
|
-
`local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
|
|
4344
|
-
)}`
|
|
4345
|
-
);
|
|
4346
|
-
}
|
|
4347
|
-
}
|
|
4348
|
-
if (missing.length > 0) {
|
|
4349
|
-
p15.log.message(pc15.red(`
|
|
4350
|
-
\u26A0\uFE0F Missing on Disk (${missing.length}):`));
|
|
4351
|
-
for (const mod of missing) {
|
|
4352
|
-
p15.log.message(` ${pc15.red("\u2715")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(in index.json, but folder missing)")}`);
|
|
4353
|
-
}
|
|
4354
|
-
}
|
|
4355
|
-
if (upToDate.length > 0) {
|
|
4356
|
-
p15.log.message(pc15.green(`
|
|
4357
|
-
\u2713 Up to Date & Registered (${upToDate.length}):`));
|
|
4358
|
-
for (const mod of upToDate) {
|
|
4359
|
-
p15.log.message(` ${pc15.green("\u2714")} ${mod.type}/${mod.name} ${pc15.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
p15.log.message("");
|
|
4363
|
-
}
|
|
4364
|
-
async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
|
|
4365
|
-
let action = actionArg?.trim().toLowerCase();
|
|
4366
|
-
if (!action) {
|
|
4367
|
-
action = handleCancel(
|
|
4368
|
-
await p15.select({
|
|
4369
|
-
message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
|
|
4370
|
-
options: [
|
|
4371
|
-
{
|
|
4372
|
-
value: "diff",
|
|
4373
|
-
label: "Inspect Diff & Status",
|
|
4374
|
-
hint: "Summary of unregistered, uncommitted, and drifted modules"
|
|
4375
|
-
},
|
|
4376
|
-
{
|
|
4377
|
-
value: "register",
|
|
4378
|
-
label: "Register Module",
|
|
4379
|
-
hint: "Add an existing module to index.json with integrity checksum"
|
|
4380
|
-
},
|
|
4381
|
-
{
|
|
4382
|
-
value: "commit",
|
|
4383
|
-
label: "Commit Changes",
|
|
4384
|
-
hint: "Commit a module's working changes to git in the background"
|
|
4385
|
-
},
|
|
4386
|
-
{
|
|
4387
|
-
value: "register-changes",
|
|
4388
|
-
label: "Register Changes",
|
|
4389
|
-
hint: "Update index.json & registry.json with latest checksums"
|
|
4390
|
-
},
|
|
4391
|
-
{
|
|
4392
|
-
value: "unregister",
|
|
4393
|
-
label: "Unregister Module",
|
|
4394
|
-
hint: "Remove module from index.json (source code stays on disk)"
|
|
4395
|
-
}
|
|
4396
|
-
]
|
|
4397
|
-
})
|
|
4398
|
-
);
|
|
4399
|
-
}
|
|
4400
|
-
switch (action) {
|
|
4401
|
-
case "diff":
|
|
4402
|
-
case "status":
|
|
4403
|
-
diffRegistry(flags);
|
|
4404
|
-
break;
|
|
4405
|
-
case "register":
|
|
4406
|
-
await registerModule(typeArg, nameArg, flags);
|
|
4407
|
-
break;
|
|
4408
|
-
case "commit":
|
|
4409
|
-
await commitModule(typeArg, nameArg, flags);
|
|
4410
|
-
break;
|
|
4411
|
-
case "register-changes":
|
|
4412
|
-
case "update":
|
|
4413
|
-
await registerModuleChanges(typeArg, nameArg, flags);
|
|
4414
|
-
break;
|
|
4415
|
-
case "unregister":
|
|
4416
|
-
await unregisterModule(typeArg, nameArg, flags);
|
|
4417
|
-
break;
|
|
4418
|
-
default:
|
|
4419
|
-
p15.log.error(
|
|
4420
|
-
pc15.red(
|
|
4421
|
-
`Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
|
|
4422
|
-
)
|
|
4423
|
-
);
|
|
4424
|
-
process.exit(1);
|
|
4425
|
-
}
|
|
4426
|
-
}
|
|
4427
3898
|
|
|
4428
3899
|
// src/cli.ts
|
|
4429
3900
|
function createCli() {
|
|
4430
3901
|
const cli = cac("mason");
|
|
4431
3902
|
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").action(async (args, flags) => {
|
|
4432
3903
|
if (args && args.length > 0) {
|
|
4433
|
-
|
|
4434
|
-
|
|
3904
|
+
p3.log.error(
|
|
3905
|
+
pc14.red(
|
|
4435
3906
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
4436
3907
|
)
|
|
4437
3908
|
);
|
|
@@ -4442,14 +3913,9 @@ function createCli() {
|
|
|
4442
3913
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
4443
3914
|
if (mode === "registry") {
|
|
4444
3915
|
const choice2 = handleCancel(
|
|
4445
|
-
await
|
|
4446
|
-
message: "\u{1F3DB}\uFE0F Mason Registry
|
|
3916
|
+
await p3.select({
|
|
3917
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
4447
3918
|
options: [
|
|
4448
|
-
{
|
|
4449
|
-
value: "diff",
|
|
4450
|
-
label: "Inspect Diff & Status",
|
|
4451
|
-
hint: "Overview of unregistered, uncommitted, or drifted modules"
|
|
4452
|
-
},
|
|
4453
3919
|
{
|
|
4454
3920
|
value: "create_feature",
|
|
4455
3921
|
label: "Initialize New Feature",
|
|
@@ -4465,26 +3931,6 @@ function createCli() {
|
|
|
4465
3931
|
label: "Create New Use Case",
|
|
4466
3932
|
hint: "Add a new use case to an existing feature"
|
|
4467
3933
|
},
|
|
4468
|
-
{
|
|
4469
|
-
value: "register",
|
|
4470
|
-
label: "Register Module",
|
|
4471
|
-
hint: "Add a module to index.json with integrity checksum"
|
|
4472
|
-
},
|
|
4473
|
-
{
|
|
4474
|
-
value: "commit",
|
|
4475
|
-
label: "Commit Module Changes",
|
|
4476
|
-
hint: "Commit working changes in a module to git"
|
|
4477
|
-
},
|
|
4478
|
-
{
|
|
4479
|
-
value: "register_changes",
|
|
4480
|
-
label: "Register Changes",
|
|
4481
|
-
hint: "Recompute checksums and update index.json"
|
|
4482
|
-
},
|
|
4483
|
-
{
|
|
4484
|
-
value: "unregister",
|
|
4485
|
-
label: "Unregister Module",
|
|
4486
|
-
hint: "Remove from index.json but keep source on disk"
|
|
4487
|
-
},
|
|
4488
3934
|
{
|
|
4489
3935
|
value: "import",
|
|
4490
3936
|
label: "Import from Remote Registry",
|
|
@@ -4494,9 +3940,6 @@ function createCli() {
|
|
|
4494
3940
|
})
|
|
4495
3941
|
);
|
|
4496
3942
|
switch (choice2) {
|
|
4497
|
-
case "diff":
|
|
4498
|
-
await handleRegistry("diff", void 0, void 0, flags);
|
|
4499
|
-
break;
|
|
4500
3943
|
case "create_feature":
|
|
4501
3944
|
await handleCreate("feature", void 0, flags);
|
|
4502
3945
|
break;
|
|
@@ -4506,18 +3949,6 @@ function createCli() {
|
|
|
4506
3949
|
case "create_usecase":
|
|
4507
3950
|
await handleCreate("usecase", void 0, flags);
|
|
4508
3951
|
break;
|
|
4509
|
-
case "register":
|
|
4510
|
-
await handleRegistry("register", void 0, void 0, flags);
|
|
4511
|
-
break;
|
|
4512
|
-
case "commit":
|
|
4513
|
-
await handleRegistry("commit", void 0, void 0, flags);
|
|
4514
|
-
break;
|
|
4515
|
-
case "register_changes":
|
|
4516
|
-
await handleRegistry("register-changes", void 0, void 0, flags);
|
|
4517
|
-
break;
|
|
4518
|
-
case "unregister":
|
|
4519
|
-
await handleRegistry("unregister", void 0, void 0, flags);
|
|
4520
|
-
break;
|
|
4521
3952
|
case "import":
|
|
4522
3953
|
await handleImport(void 0, void 0, flags);
|
|
4523
3954
|
break;
|
|
@@ -4525,31 +3956,24 @@ function createCli() {
|
|
|
4525
3956
|
return;
|
|
4526
3957
|
}
|
|
4527
3958
|
const choice = handleCancel(
|
|
4528
|
-
await
|
|
3959
|
+
await p3.select({
|
|
4529
3960
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
4530
3961
|
options: [
|
|
4531
3962
|
{
|
|
4532
3963
|
value: "create",
|
|
4533
|
-
label: "Create
|
|
3964
|
+
label: "Create component",
|
|
4534
3965
|
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
4535
3966
|
},
|
|
4536
3967
|
{
|
|
4537
3968
|
value: "import",
|
|
4538
3969
|
label: "Import from registry",
|
|
4539
3970
|
hint: "Download and link pre-built shared modules or features from the registry"
|
|
4540
|
-
},
|
|
4541
|
-
{
|
|
4542
|
-
value: "registry",
|
|
4543
|
-
label: "Registry management",
|
|
4544
|
-
hint: "Inspect diff, register modules, or commit module changes"
|
|
4545
3971
|
}
|
|
4546
3972
|
]
|
|
4547
3973
|
})
|
|
4548
3974
|
);
|
|
4549
3975
|
if (choice === "create") {
|
|
4550
3976
|
await handleCreate(void 0, void 0, flags);
|
|
4551
|
-
} else if (choice === "registry") {
|
|
4552
|
-
await handleRegistry(void 0, void 0, void 0, flags);
|
|
4553
3977
|
} else {
|
|
4554
3978
|
await handleImport(void 0, void 0, flags);
|
|
4555
3979
|
}
|
|
@@ -4577,25 +4001,6 @@ function createCli() {
|
|
|
4577
4001
|
handleCliError(err);
|
|
4578
4002
|
}
|
|
4579
4003
|
});
|
|
4580
|
-
cli.command(
|
|
4581
|
-
"registry [action] [type] [name]",
|
|
4582
|
-
"Registry & version management (diff, register, commit, register-changes, unregister)"
|
|
4583
|
-
).option("-m, --message <message>", "Commit message for 'commit' command").option("-y, --yes", "Accept defaults and skip interactive prompts").example("mason registry diff").example("mason registry register feature authn").example("mason registry commit shared hasher -m 'Add bcrypt engine'").example("mason registry register-changes shared hasher").example("mason registry unregister feature oldFeature").action(
|
|
4584
|
-
async (action, type, name, flags = {}) => {
|
|
4585
|
-
try {
|
|
4586
|
-
await handleRegistry(action, type, name, flags);
|
|
4587
|
-
} catch (err) {
|
|
4588
|
-
handleCliError(err);
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
);
|
|
4592
|
-
cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
|
|
4593
|
-
try {
|
|
4594
|
-
await handleRegistry("diff", void 0, void 0, flags);
|
|
4595
|
-
} catch (err) {
|
|
4596
|
-
handleCliError(err);
|
|
4597
|
-
}
|
|
4598
|
-
});
|
|
4599
4004
|
const createSubtypes = [
|
|
4600
4005
|
"feature",
|
|
4601
4006
|
"shared",
|
|
@@ -4619,7 +4024,7 @@ function createCli() {
|
|
|
4619
4024
|
});
|
|
4620
4025
|
}
|
|
4621
4026
|
cli.help();
|
|
4622
|
-
cli.version("1.0.
|
|
4027
|
+
cli.version("1.0.14");
|
|
4623
4028
|
return cli;
|
|
4624
4029
|
}
|
|
4625
4030
|
function handleCliError(err) {
|
|
@@ -4627,7 +4032,7 @@ function handleCliError(err) {
|
|
|
4627
4032
|
process.exit(0);
|
|
4628
4033
|
}
|
|
4629
4034
|
const message = err instanceof Error ? err.message : String(err);
|
|
4630
|
-
|
|
4035
|
+
p3.log.error(pc14.red(`Error: ${message}`));
|
|
4631
4036
|
process.exit(1);
|
|
4632
4037
|
}
|
|
4633
4038
|
async function runCli(argv = process.argv) {
|