@solid-stack/mason 1.0.13 → 1.0.15
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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs4 from 'fs';
|
|
2
2
|
import path2 from 'path';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
3
|
+
import * as p4 from '@clack/prompts';
|
|
4
|
+
import pc14 from 'picocolors';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
@@ -77,7 +77,7 @@ function isValidIdentifier(str) {
|
|
|
77
77
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
78
78
|
const projectRoot = options.projectRoot || process.cwd();
|
|
79
79
|
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
80
|
-
const existedBefore =
|
|
80
|
+
const existedBefore = fs4.existsSync(absolutePath);
|
|
81
81
|
if (options.dryRun) {
|
|
82
82
|
return {
|
|
83
83
|
relativePath: relPath,
|
|
@@ -97,10 +97,10 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
const dir = path2.dirname(absolutePath);
|
|
100
|
-
if (!
|
|
101
|
-
|
|
100
|
+
if (!fs4.existsSync(dir)) {
|
|
101
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
fs4.writeFileSync(absolutePath, content, "utf-8");
|
|
104
104
|
return {
|
|
105
105
|
relativePath: relPath,
|
|
106
106
|
absolutePath,
|
|
@@ -112,17 +112,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
112
112
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
113
113
|
console.log("");
|
|
114
114
|
if (isDryRun) {
|
|
115
|
-
|
|
115
|
+
p4.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
116
116
|
}
|
|
117
117
|
for (const f of files) {
|
|
118
118
|
if (f.action === "created") {
|
|
119
|
-
|
|
119
|
+
p4.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
|
|
120
120
|
} else if (f.action === "overwritten") {
|
|
121
|
-
|
|
121
|
+
p4.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
|
|
122
122
|
} else if (f.action === "skipped") {
|
|
123
|
-
|
|
123
|
+
p4.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
124
124
|
} else {
|
|
125
|
-
|
|
125
|
+
p4.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -130,9 +130,9 @@ function loadMasonConfig(startDir = process.cwd()) {
|
|
|
130
130
|
let current = path2.resolve(startDir);
|
|
131
131
|
while (true) {
|
|
132
132
|
const configPath = path2.join(current, "mason.config.json");
|
|
133
|
-
if (
|
|
133
|
+
if (fs4.existsSync(configPath)) {
|
|
134
134
|
try {
|
|
135
|
-
return JSON.parse(
|
|
135
|
+
return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
136
136
|
} catch {
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
@@ -147,7 +147,7 @@ function getEffectiveMode(projectRoot, flags) {
|
|
|
147
147
|
if (flags?.registry) return "registry";
|
|
148
148
|
const config = loadMasonConfig(projectRoot);
|
|
149
149
|
if (config?.mode) return config.mode;
|
|
150
|
-
if (
|
|
150
|
+
if (fs4.existsSync(path2.join(projectRoot, "index.json")) && (fs4.existsSync(path2.join(projectRoot, "features")) || fs4.existsSync(path2.join(projectRoot, "shared"))) && !fs4.existsSync(path2.join(projectRoot, "src"))) {
|
|
151
151
|
return "registry";
|
|
152
152
|
}
|
|
153
153
|
return "app";
|
|
@@ -163,7 +163,7 @@ function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
|
163
163
|
if (mode === "registry") {
|
|
164
164
|
return path2.join(projectRoot, "features");
|
|
165
165
|
}
|
|
166
|
-
if (
|
|
166
|
+
if (fs4.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
167
167
|
return path2.join(projectRoot, "src/features");
|
|
168
168
|
}
|
|
169
169
|
return path2.join(projectRoot, "features");
|
|
@@ -179,7 +179,7 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
179
179
|
if (mode === "registry") {
|
|
180
180
|
return path2.join(projectRoot, "shared");
|
|
181
181
|
}
|
|
182
|
-
if (
|
|
182
|
+
if (fs4.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
183
183
|
return path2.join(projectRoot, "src/shared");
|
|
184
184
|
}
|
|
185
185
|
return path2.join(projectRoot, "shared");
|
|
@@ -189,7 +189,7 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
189
189
|
function findProjectRoot(startDir = process.cwd()) {
|
|
190
190
|
let current = path2.resolve(startDir);
|
|
191
191
|
while (true) {
|
|
192
|
-
if (
|
|
192
|
+
if (fs4.existsSync(path2.join(current, "package.json"))) {
|
|
193
193
|
return current;
|
|
194
194
|
}
|
|
195
195
|
const parent = path2.dirname(current);
|
|
@@ -207,9 +207,9 @@ function resolveSharedDir(projectRoot, flags) {
|
|
|
207
207
|
}
|
|
208
208
|
function listExistingShared(projectRoot) {
|
|
209
209
|
const sharedDir = resolveSharedDir(projectRoot);
|
|
210
|
-
if (!
|
|
210
|
+
if (!fs4.existsSync(sharedDir)) return [];
|
|
211
211
|
try {
|
|
212
|
-
const entries =
|
|
212
|
+
const entries = fs4.readdirSync(sharedDir, { withFileTypes: true });
|
|
213
213
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
214
214
|
} catch {
|
|
215
215
|
return [];
|
|
@@ -217,9 +217,9 @@ function listExistingShared(projectRoot) {
|
|
|
217
217
|
}
|
|
218
218
|
function listExistingFeatures(projectRoot) {
|
|
219
219
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
220
|
-
if (!
|
|
220
|
+
if (!fs4.existsSync(featuresDir)) return [];
|
|
221
221
|
try {
|
|
222
|
-
const entries =
|
|
222
|
+
const entries = fs4.readdirSync(featuresDir, { withFileTypes: true });
|
|
223
223
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
224
224
|
} catch {
|
|
225
225
|
return [];
|
|
@@ -230,9 +230,9 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
230
230
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
231
231
|
for (const feat of features) {
|
|
232
232
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
233
|
-
if (!
|
|
233
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
234
234
|
try {
|
|
235
|
-
const files =
|
|
235
|
+
const files = fs4.readdirSync(domainDir);
|
|
236
236
|
for (const file of files) {
|
|
237
237
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
238
238
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -245,7 +245,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
245
245
|
});
|
|
246
246
|
} else {
|
|
247
247
|
try {
|
|
248
|
-
const content =
|
|
248
|
+
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
249
249
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
250
250
|
results.push({
|
|
251
251
|
name,
|
|
@@ -268,9 +268,9 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
268
268
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
269
269
|
for (const feat of features) {
|
|
270
270
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
271
|
-
if (!
|
|
271
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
272
272
|
try {
|
|
273
|
-
const files =
|
|
273
|
+
const files = fs4.readdirSync(domainDir);
|
|
274
274
|
for (const file of files) {
|
|
275
275
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
276
276
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -293,9 +293,9 @@ function listUseCases(projectRoot, featureName) {
|
|
|
293
293
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
294
294
|
for (const feat of features) {
|
|
295
295
|
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
296
|
-
if (!
|
|
296
|
+
if (!fs4.existsSync(useCasesDir)) continue;
|
|
297
297
|
try {
|
|
298
|
-
const files =
|
|
298
|
+
const files = fs4.readdirSync(useCasesDir);
|
|
299
299
|
for (const file of files) {
|
|
300
300
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
301
301
|
const name = file.replace(/\.ts$/, "");
|
|
@@ -318,23 +318,23 @@ var PromptCancelledError = class extends Error {
|
|
|
318
318
|
}
|
|
319
319
|
};
|
|
320
320
|
function handleCancel(result) {
|
|
321
|
-
if (
|
|
322
|
-
|
|
321
|
+
if (p4.isCancel(result)) {
|
|
322
|
+
p4.cancel("Operation cancelled.");
|
|
323
323
|
throw new PromptCancelledError();
|
|
324
324
|
}
|
|
325
325
|
return result;
|
|
326
326
|
}
|
|
327
327
|
function printBanner() {
|
|
328
328
|
console.log("");
|
|
329
|
-
|
|
329
|
+
p4.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
|
|
330
330
|
}
|
|
331
331
|
async function promptProperties(options = {}) {
|
|
332
332
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
333
333
|
if (properties.length > 0) {
|
|
334
|
-
const summary = properties.map((
|
|
335
|
-
|
|
334
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
335
|
+
p4.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
|
|
336
336
|
const addMore = handleCancel(
|
|
337
|
-
await
|
|
337
|
+
await p4.confirm({
|
|
338
338
|
message: "Would you like to add custom fields/properties?",
|
|
339
339
|
initialValue: false
|
|
340
340
|
})
|
|
@@ -343,10 +343,10 @@ async function promptProperties(options = {}) {
|
|
|
343
343
|
return properties;
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
|
-
|
|
346
|
+
p4.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
|
|
347
347
|
while (true) {
|
|
348
348
|
const name = handleCancel(
|
|
349
|
-
await
|
|
349
|
+
await p4.text({
|
|
350
350
|
message: "Property name (or press Enter to finish):",
|
|
351
351
|
placeholder: "e.g. title, email, status"
|
|
352
352
|
})
|
|
@@ -355,7 +355,7 @@ async function promptProperties(options = {}) {
|
|
|
355
355
|
break;
|
|
356
356
|
}
|
|
357
357
|
const type = handleCancel(
|
|
358
|
-
await
|
|
358
|
+
await p4.select({
|
|
359
359
|
message: `Type for property "${name.trim()}":`,
|
|
360
360
|
options: [
|
|
361
361
|
{ value: "string", label: "string" },
|
|
@@ -372,7 +372,7 @@ async function promptProperties(options = {}) {
|
|
|
372
372
|
let finalType = type;
|
|
373
373
|
if (type === "custom") {
|
|
374
374
|
finalType = handleCancel(
|
|
375
|
-
await
|
|
375
|
+
await p4.text({
|
|
376
376
|
message: `Enter custom type for "${name.trim()}":`,
|
|
377
377
|
placeholder: "e.g. UserRole, string | null",
|
|
378
378
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -380,7 +380,7 @@ async function promptProperties(options = {}) {
|
|
|
380
380
|
);
|
|
381
381
|
}
|
|
382
382
|
const optional = handleCancel(
|
|
383
|
-
await
|
|
383
|
+
await p4.confirm({
|
|
384
384
|
message: `Is "${name.trim()}" optional?`,
|
|
385
385
|
initialValue: false
|
|
386
386
|
})
|
|
@@ -390,8 +390,8 @@ async function promptProperties(options = {}) {
|
|
|
390
390
|
type: finalType.trim(),
|
|
391
391
|
optional
|
|
392
392
|
});
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
p4.log.success(
|
|
394
|
+
pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
395
395
|
);
|
|
396
396
|
}
|
|
397
397
|
return properties;
|
|
@@ -421,7 +421,7 @@ function findLocalRegistry(startDir = process.cwd()) {
|
|
|
421
421
|
let current = path2.resolve(startDir);
|
|
422
422
|
while (true) {
|
|
423
423
|
const candidate = path2.join(current, "mason-registry");
|
|
424
|
-
if (
|
|
424
|
+
if (fs4.existsSync(candidate) && fs4.existsSync(path2.join(candidate, "index.json"))) {
|
|
425
425
|
return candidate;
|
|
426
426
|
}
|
|
427
427
|
const parent = path2.dirname(current);
|
|
@@ -443,9 +443,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
443
443
|
path2.join(process.cwd(), "mason.config.json")
|
|
444
444
|
];
|
|
445
445
|
for (const configPath of configLocations) {
|
|
446
|
-
if (
|
|
446
|
+
if (fs4.existsSync(configPath)) {
|
|
447
447
|
try {
|
|
448
|
-
const config = JSON.parse(
|
|
448
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
449
449
|
if (config.registry && typeof config.registry === "string") {
|
|
450
450
|
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
451
451
|
}
|
|
@@ -458,9 +458,9 @@ function getRegistryBase(projectRoot, override) {
|
|
|
458
458
|
path2.join(process.cwd(), "package.json")
|
|
459
459
|
];
|
|
460
460
|
for (const pkgPath of pkgLocations) {
|
|
461
|
-
if (
|
|
461
|
+
if (fs4.existsSync(pkgPath)) {
|
|
462
462
|
try {
|
|
463
|
-
const pkg = JSON.parse(
|
|
463
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
464
464
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
465
465
|
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
466
466
|
}
|
|
@@ -504,10 +504,10 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
504
504
|
baseDir = fileURLToPath(baseDir);
|
|
505
505
|
}
|
|
506
506
|
const filePath = path2.resolve(baseDir, cleanRelPath);
|
|
507
|
-
if (!
|
|
507
|
+
if (!fs4.existsSync(filePath)) {
|
|
508
508
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
509
509
|
}
|
|
510
|
-
return
|
|
510
|
+
return fs4.promises.readFile(filePath, "utf-8");
|
|
511
511
|
}
|
|
512
512
|
async function fetchRegistryIndex(registryBase) {
|
|
513
513
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -569,26 +569,26 @@ function findClosestMatch(target, candidates) {
|
|
|
569
569
|
return null;
|
|
570
570
|
}
|
|
571
571
|
function detectPackageManager(projectRoot) {
|
|
572
|
-
if (
|
|
573
|
-
if (
|
|
574
|
-
if (
|
|
572
|
+
if (fs4.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
573
|
+
if (fs4.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
574
|
+
if (fs4.existsSync(path2.join(projectRoot, "bun.lockb")) || fs4.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
|
|
575
575
|
return "npm";
|
|
576
576
|
}
|
|
577
577
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
578
578
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
579
579
|
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
580
|
-
if (!
|
|
580
|
+
if (!fs4.existsSync(pkgJsonPath)) {
|
|
581
581
|
return requiredPackages;
|
|
582
582
|
}
|
|
583
583
|
try {
|
|
584
|
-
const pkg = JSON.parse(
|
|
584
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
|
|
585
585
|
const installed = /* @__PURE__ */ new Set([
|
|
586
586
|
...Object.keys(pkg.dependencies || {}),
|
|
587
587
|
...Object.keys(pkg.devDependencies || {}),
|
|
588
588
|
...Object.keys(pkg.peerDependencies || {})
|
|
589
589
|
]);
|
|
590
|
-
return requiredPackages.filter((
|
|
591
|
-
const nameOnly =
|
|
590
|
+
return requiredPackages.filter((p16) => {
|
|
591
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
592
592
|
return !installed.has(nameOnly);
|
|
593
593
|
});
|
|
594
594
|
} catch {
|
|
@@ -613,15 +613,15 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
613
613
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
614
614
|
break;
|
|
615
615
|
}
|
|
616
|
-
|
|
616
|
+
p4.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
|
|
617
617
|
execSync(installCmd, {
|
|
618
618
|
cwd: projectRoot,
|
|
619
619
|
stdio: "inherit"
|
|
620
620
|
});
|
|
621
621
|
}
|
|
622
622
|
function getAllFiles(dir, baseDir = dir) {
|
|
623
|
-
if (!
|
|
624
|
-
const entries =
|
|
623
|
+
if (!fs4.existsSync(dir)) return [];
|
|
624
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
625
625
|
const files = [];
|
|
626
626
|
for (const entry of entries) {
|
|
627
627
|
const fullPath = path2.join(dir, entry.name);
|
|
@@ -641,13 +641,13 @@ function getAllFiles(dir, baseDir = dir) {
|
|
|
641
641
|
return files.sort();
|
|
642
642
|
}
|
|
643
643
|
function computeDirectoryHash(dirPath, files) {
|
|
644
|
-
if (!
|
|
644
|
+
if (!fs4.existsSync(dirPath)) return "";
|
|
645
645
|
const hash = createHash("sha256");
|
|
646
646
|
const fileList = (files ?? getAllFiles(dirPath)).sort();
|
|
647
647
|
for (const relativePath of fileList) {
|
|
648
648
|
const absolutePath = path2.join(dirPath, relativePath);
|
|
649
|
-
if (!
|
|
650
|
-
const content =
|
|
649
|
+
if (!fs4.existsSync(absolutePath)) continue;
|
|
650
|
+
const content = fs4.readFileSync(absolutePath, "utf8");
|
|
651
651
|
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
652
652
|
hash.update(relativePath);
|
|
653
653
|
hash.update(normalizedContent);
|
|
@@ -670,29 +670,29 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
670
670
|
});
|
|
671
671
|
var cachedTemplatesDir = null;
|
|
672
672
|
function getTemplatesDir() {
|
|
673
|
-
if (cachedTemplatesDir &&
|
|
673
|
+
if (cachedTemplatesDir && fs4.existsSync(cachedTemplatesDir)) {
|
|
674
674
|
return cachedTemplatesDir;
|
|
675
675
|
}
|
|
676
676
|
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
677
677
|
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
678
|
-
if (
|
|
678
|
+
if (fs4.existsSync(cand1)) {
|
|
679
679
|
cachedTemplatesDir = cand1;
|
|
680
680
|
return cand1;
|
|
681
681
|
}
|
|
682
682
|
const cand2 = path2.resolve(currentDir, "../templates");
|
|
683
|
-
if (
|
|
683
|
+
if (fs4.existsSync(cand2)) {
|
|
684
684
|
cachedTemplatesDir = cand2;
|
|
685
685
|
return cand2;
|
|
686
686
|
}
|
|
687
687
|
const cand3 = path2.resolve(currentDir, "templates");
|
|
688
|
-
if (
|
|
688
|
+
if (fs4.existsSync(cand3)) {
|
|
689
689
|
cachedTemplatesDir = cand3;
|
|
690
690
|
return cand3;
|
|
691
691
|
}
|
|
692
692
|
let curr = currentDir;
|
|
693
693
|
while (true) {
|
|
694
694
|
const candTemplates = path2.join(curr, "templates");
|
|
695
|
-
if (
|
|
695
|
+
if (fs4.existsSync(candTemplates)) {
|
|
696
696
|
cachedTemplatesDir = candTemplates;
|
|
697
697
|
return candTemplates;
|
|
698
698
|
}
|
|
@@ -705,7 +705,7 @@ function getTemplatesDir() {
|
|
|
705
705
|
var templateCache = /* @__PURE__ */ new Map();
|
|
706
706
|
function loadTemplate(relativePath) {
|
|
707
707
|
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
708
|
-
return
|
|
708
|
+
return fs4.readFileSync(fullPath, "utf-8");
|
|
709
709
|
}
|
|
710
710
|
function renderTemplateFile(relativePath, context) {
|
|
711
711
|
let compiled = templateCache.get(relativePath);
|
|
@@ -815,7 +815,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
815
815
|
const idKey = `${entityKey}Id`;
|
|
816
816
|
const listKey = `${entityKey}s`;
|
|
817
817
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
818
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
818
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join("; ") : "";
|
|
819
819
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
820
820
|
return {
|
|
821
821
|
name: m.name,
|
|
@@ -1076,7 +1076,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
1076
1076
|
};
|
|
1077
1077
|
}
|
|
1078
1078
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
1079
|
-
if (customPath &&
|
|
1079
|
+
if (customPath && fs4.existsSync(customPath)) {
|
|
1080
1080
|
return { filePath: customPath, ifaceName };
|
|
1081
1081
|
}
|
|
1082
1082
|
const candidateNames = [
|
|
@@ -1087,21 +1087,21 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
1087
1087
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
1088
1088
|
for (const feat of features) {
|
|
1089
1089
|
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1090
|
-
if (!
|
|
1090
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
1091
1091
|
for (const cand of candidateNames) {
|
|
1092
1092
|
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
1093
|
-
if (
|
|
1093
|
+
if (fs4.existsSync(p1)) {
|
|
1094
1094
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
1095
1095
|
}
|
|
1096
1096
|
}
|
|
1097
1097
|
}
|
|
1098
1098
|
for (const cand of candidateNames) {
|
|
1099
1099
|
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
1100
|
-
if (
|
|
1100
|
+
if (fs4.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
1101
1101
|
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
1102
|
-
if (
|
|
1102
|
+
if (fs4.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
1103
1103
|
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
1104
|
-
if (
|
|
1104
|
+
if (fs4.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
1105
1105
|
}
|
|
1106
1106
|
return null;
|
|
1107
1107
|
}
|
|
@@ -1124,7 +1124,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
1124
1124
|
while (currentFile && !visited.has(currentFile)) {
|
|
1125
1125
|
visited.add(currentFile);
|
|
1126
1126
|
try {
|
|
1127
|
-
const content =
|
|
1127
|
+
const content = fs4.readFileSync(currentFile, "utf-8");
|
|
1128
1128
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
1129
1129
|
for (const m of parsed.methods) {
|
|
1130
1130
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1396,22 +1396,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1396
1396
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1397
1397
|
}));
|
|
1398
1398
|
const domainTypesToImport = new Set(domainImports);
|
|
1399
|
-
for (const
|
|
1400
|
-
const rawType =
|
|
1399
|
+
for (const p16 of [...inputProps, ...outputProps]) {
|
|
1400
|
+
const rawType = p16.type.trim();
|
|
1401
1401
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1402
1402
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1403
1403
|
domainTypesToImport.add(baseType);
|
|
1404
1404
|
}
|
|
1405
1405
|
}
|
|
1406
|
-
const formattedInputProps = inputProps.map((
|
|
1407
|
-
name:
|
|
1408
|
-
optional:
|
|
1409
|
-
type:
|
|
1406
|
+
const formattedInputProps = inputProps.map((p16) => ({
|
|
1407
|
+
name: p16.name,
|
|
1408
|
+
optional: p16.optional,
|
|
1409
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1410
1410
|
}));
|
|
1411
|
-
const formattedOutputProps = outputProps.map((
|
|
1412
|
-
name:
|
|
1413
|
-
optional:
|
|
1414
|
-
type:
|
|
1411
|
+
const formattedOutputProps = outputProps.map((p16) => ({
|
|
1412
|
+
name: p16.name,
|
|
1413
|
+
optional: p16.optional,
|
|
1414
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1415
1415
|
}));
|
|
1416
1416
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1417
1417
|
key: toCamelCase(dep),
|
|
@@ -1598,7 +1598,7 @@ function generateHttpHandler(options) {
|
|
|
1598
1598
|
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1599
1599
|
const handlersDir = path2.join(pxDir, "handlers");
|
|
1600
1600
|
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1601
|
-
if (!
|
|
1601
|
+
if (!fs4.existsSync(pxIndexPath)) {
|
|
1602
1602
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1603
1603
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1604
1604
|
routePath: featurePath
|
|
@@ -1635,8 +1635,8 @@ function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], opti
|
|
|
1635
1635
|
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1636
1636
|
const pascalFeature = toPascalCase(featureName);
|
|
1637
1637
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1638
|
-
if (
|
|
1639
|
-
let content =
|
|
1638
|
+
if (fs4.existsSync(providerPath) && !options.overwrite) {
|
|
1639
|
+
let content = fs4.readFileSync(providerPath, "utf-8");
|
|
1640
1640
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1641
1641
|
for (const b of bindings) {
|
|
1642
1642
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -2086,7 +2086,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2086
2086
|
}
|
|
2087
2087
|
if (!name) {
|
|
2088
2088
|
name = handleCancel(
|
|
2089
|
-
await
|
|
2089
|
+
await p4.text({
|
|
2090
2090
|
message: "What is the feature name?",
|
|
2091
2091
|
placeholder: "e.g. users, auth, orders, billing",
|
|
2092
2092
|
validate: (val) => {
|
|
@@ -2102,7 +2102,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2102
2102
|
let targetDirRel = defaultDir;
|
|
2103
2103
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2104
2104
|
targetDirRel = handleCancel(
|
|
2105
|
-
await
|
|
2105
|
+
await p4.text({
|
|
2106
2106
|
message: "Target directory for the feature:",
|
|
2107
2107
|
initialValue: defaultDir,
|
|
2108
2108
|
defaultValue: defaultDir
|
|
@@ -2124,14 +2124,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2124
2124
|
const customUseCases = [];
|
|
2125
2125
|
if (!flags.yes) {
|
|
2126
2126
|
includeExpress = handleCancel(
|
|
2127
|
-
await
|
|
2127
|
+
await p4.confirm({
|
|
2128
2128
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
2129
2129
|
initialValue: true
|
|
2130
2130
|
})
|
|
2131
2131
|
);
|
|
2132
2132
|
if (includeExpress) {
|
|
2133
2133
|
routePath = handleCancel(
|
|
2134
|
-
await
|
|
2134
|
+
await p4.text({
|
|
2135
2135
|
message: "Express route prefix:",
|
|
2136
2136
|
initialValue: `/${normalizedName}`,
|
|
2137
2137
|
defaultValue: `/${normalizedName}`
|
|
@@ -2139,14 +2139,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2139
2139
|
).trim();
|
|
2140
2140
|
}
|
|
2141
2141
|
includeDomain = handleCancel(
|
|
2142
|
-
await
|
|
2142
|
+
await p4.confirm({
|
|
2143
2143
|
message: "Include Domain Entity/Entities?",
|
|
2144
2144
|
initialValue: true
|
|
2145
2145
|
})
|
|
2146
2146
|
);
|
|
2147
2147
|
if (includeDomain) {
|
|
2148
2148
|
const domainInput = handleCancel(
|
|
2149
|
-
await
|
|
2149
|
+
await p4.text({
|
|
2150
2150
|
message: "Domain entity name(s) (comma-separated):",
|
|
2151
2151
|
initialValue: defaultEntity,
|
|
2152
2152
|
defaultValue: defaultEntity,
|
|
@@ -2165,21 +2165,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2165
2165
|
entityNames = [defaultEntity];
|
|
2166
2166
|
}
|
|
2167
2167
|
includeInterface = handleCancel(
|
|
2168
|
-
await
|
|
2168
|
+
await p4.confirm({
|
|
2169
2169
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
2170
2170
|
initialValue: true
|
|
2171
2171
|
})
|
|
2172
2172
|
);
|
|
2173
2173
|
if (includeInterface) {
|
|
2174
2174
|
includeInfra = handleCancel(
|
|
2175
|
-
await
|
|
2175
|
+
await p4.confirm({
|
|
2176
2176
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
2177
2177
|
initialValue: true
|
|
2178
2178
|
})
|
|
2179
2179
|
);
|
|
2180
2180
|
}
|
|
2181
2181
|
includeUseCase = handleCancel(
|
|
2182
|
-
await
|
|
2182
|
+
await p4.confirm({
|
|
2183
2183
|
message: "Include Domain Use Cases?",
|
|
2184
2184
|
initialValue: true
|
|
2185
2185
|
})
|
|
@@ -2193,7 +2193,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2193
2193
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
2194
2194
|
];
|
|
2195
2195
|
crudOperations = handleCancel(
|
|
2196
|
-
await
|
|
2196
|
+
await p4.multiselect({
|
|
2197
2197
|
message: "Select standard CRUD use cases to generate:",
|
|
2198
2198
|
options: crudChoices,
|
|
2199
2199
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -2201,13 +2201,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2201
2201
|
})
|
|
2202
2202
|
);
|
|
2203
2203
|
includeTests = handleCancel(
|
|
2204
|
-
await
|
|
2204
|
+
await p4.confirm({
|
|
2205
2205
|
message: "Generate Vitest unit tests for use cases?",
|
|
2206
2206
|
initialValue: true
|
|
2207
2207
|
})
|
|
2208
2208
|
);
|
|
2209
2209
|
const wantCustom = handleCancel(
|
|
2210
|
-
await
|
|
2210
|
+
await p4.confirm({
|
|
2211
2211
|
message: "Do you want to add any custom use cases to this feature?",
|
|
2212
2212
|
initialValue: false
|
|
2213
2213
|
})
|
|
@@ -2217,7 +2217,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2217
2217
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
2218
2218
|
while (addMore) {
|
|
2219
2219
|
const customName = handleCancel(
|
|
2220
|
-
await
|
|
2220
|
+
await p4.text({
|
|
2221
2221
|
message: "Custom Use Case name:",
|
|
2222
2222
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
2223
2223
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2227,7 +2227,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2227
2227
|
let selectedDeps = [];
|
|
2228
2228
|
if (availableIfaces.length > 0) {
|
|
2229
2229
|
selectedDeps = handleCancel(
|
|
2230
|
-
await
|
|
2230
|
+
await p4.multiselect({
|
|
2231
2231
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
2232
2232
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
2233
2233
|
required: false
|
|
@@ -2236,7 +2236,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2236
2236
|
}
|
|
2237
2237
|
let customInputProps = [];
|
|
2238
2238
|
const hasInput = handleCancel(
|
|
2239
|
-
await
|
|
2239
|
+
await p4.confirm({
|
|
2240
2240
|
message: `Does ${customUcName} accept input arguments?`,
|
|
2241
2241
|
initialValue: true
|
|
2242
2242
|
})
|
|
@@ -2247,7 +2247,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2247
2247
|
}
|
|
2248
2248
|
let customOutputProps = [];
|
|
2249
2249
|
const hasCustomOutput = handleCancel(
|
|
2250
|
-
await
|
|
2250
|
+
await p4.confirm({
|
|
2251
2251
|
message: `Does ${customUcName} return custom output data?`,
|
|
2252
2252
|
initialValue: false
|
|
2253
2253
|
})
|
|
@@ -2257,7 +2257,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2257
2257
|
});
|
|
2258
2258
|
}
|
|
2259
2259
|
const customIncludeTest = handleCancel(
|
|
2260
|
-
await
|
|
2260
|
+
await p4.confirm({
|
|
2261
2261
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2262
2262
|
initialValue: true
|
|
2263
2263
|
})
|
|
@@ -2267,14 +2267,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2267
2267
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2268
2268
|
if (includeExpress) {
|
|
2269
2269
|
customCreateHttp = handleCancel(
|
|
2270
|
-
await
|
|
2270
|
+
await p4.confirm({
|
|
2271
2271
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2272
2272
|
initialValue: true
|
|
2273
2273
|
})
|
|
2274
2274
|
);
|
|
2275
2275
|
if (customCreateHttp) {
|
|
2276
2276
|
customHttpMethod = handleCancel(
|
|
2277
|
-
await
|
|
2277
|
+
await p4.select({
|
|
2278
2278
|
message: "HTTP method for route:",
|
|
2279
2279
|
options: [
|
|
2280
2280
|
{ value: "post", label: "POST" },
|
|
@@ -2287,7 +2287,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2287
2287
|
})
|
|
2288
2288
|
);
|
|
2289
2289
|
customHttpPath = handleCancel(
|
|
2290
|
-
await
|
|
2290
|
+
await p4.text({
|
|
2291
2291
|
message: "Route subpath (relative to feature prefix):",
|
|
2292
2292
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2293
2293
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2308,7 +2308,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2308
2308
|
validationFields: customInputProps
|
|
2309
2309
|
});
|
|
2310
2310
|
addMore = handleCancel(
|
|
2311
|
-
await
|
|
2311
|
+
await p4.confirm({
|
|
2312
2312
|
message: "Add another custom use case?",
|
|
2313
2313
|
initialValue: false
|
|
2314
2314
|
})
|
|
@@ -2317,7 +2317,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2317
2317
|
}
|
|
2318
2318
|
}
|
|
2319
2319
|
includeDiProvider = handleCancel(
|
|
2320
|
-
await
|
|
2320
|
+
await p4.confirm({
|
|
2321
2321
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2322
2322
|
initialValue: true
|
|
2323
2323
|
})
|
|
@@ -2329,8 +2329,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2329
2329
|
entityNames = [defaultEntity];
|
|
2330
2330
|
}
|
|
2331
2331
|
}
|
|
2332
|
-
const s =
|
|
2333
|
-
s.start(`Generating feature '${
|
|
2332
|
+
const s = p4.spinner();
|
|
2333
|
+
s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
|
|
2334
2334
|
const options = {
|
|
2335
2335
|
name: normalizedName,
|
|
2336
2336
|
projectRoot,
|
|
@@ -2352,7 +2352,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2352
2352
|
const files = generateFeature(options);
|
|
2353
2353
|
if (isRegistry) {
|
|
2354
2354
|
const registryPath = path2.join(targetDir, "registry.json");
|
|
2355
|
-
if (!
|
|
2355
|
+
if (!fs4.existsSync(registryPath)) {
|
|
2356
2356
|
const manifest = {
|
|
2357
2357
|
name: toKebabCase(normalizedName),
|
|
2358
2358
|
type: "feature",
|
|
@@ -2376,8 +2376,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2376
2376
|
}
|
|
2377
2377
|
s.stop("Feature scaffolded successfully!");
|
|
2378
2378
|
printGeneratedFiles(files, options.dryRun);
|
|
2379
|
-
|
|
2380
|
-
|
|
2379
|
+
p4.outro(
|
|
2380
|
+
pc14.green(
|
|
2381
2381
|
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2382
2382
|
)
|
|
2383
2383
|
);
|
|
@@ -2491,7 +2491,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2491
2491
|
}
|
|
2492
2492
|
if (!name) {
|
|
2493
2493
|
name = handleCancel(
|
|
2494
|
-
await
|
|
2494
|
+
await p4.text({
|
|
2495
2495
|
message: "What is the shared module name?",
|
|
2496
2496
|
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2497
2497
|
validate: (val) => {
|
|
@@ -2507,7 +2507,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2507
2507
|
let targetDirRel = defaultDir;
|
|
2508
2508
|
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2509
2509
|
targetDirRel = handleCancel(
|
|
2510
|
-
await
|
|
2510
|
+
await p4.text({
|
|
2511
2511
|
message: "Target directory for the shared module:",
|
|
2512
2512
|
initialValue: defaultDir,
|
|
2513
2513
|
defaultValue: defaultDir
|
|
@@ -2519,21 +2519,21 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2519
2519
|
let includeStub = true;
|
|
2520
2520
|
if (!flags.yes) {
|
|
2521
2521
|
description = handleCancel(
|
|
2522
|
-
await
|
|
2522
|
+
await p4.text({
|
|
2523
2523
|
message: "Module description (optional):",
|
|
2524
2524
|
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2525
2525
|
initialValue: ""
|
|
2526
2526
|
})
|
|
2527
2527
|
).trim();
|
|
2528
2528
|
includeStub = handleCancel(
|
|
2529
|
-
await
|
|
2529
|
+
await p4.confirm({
|
|
2530
2530
|
message: "Include stub implementation in infrastructure/?",
|
|
2531
2531
|
initialValue: true
|
|
2532
2532
|
})
|
|
2533
2533
|
);
|
|
2534
2534
|
}
|
|
2535
|
-
const s =
|
|
2536
|
-
s.start(`Generating shared module '${
|
|
2535
|
+
const s = p4.spinner();
|
|
2536
|
+
s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
|
|
2537
2537
|
const files = generateShared({
|
|
2538
2538
|
name: normalizedName,
|
|
2539
2539
|
description,
|
|
@@ -2546,8 +2546,8 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2546
2546
|
});
|
|
2547
2547
|
s.stop("Shared module generated successfully!");
|
|
2548
2548
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2549
|
-
|
|
2550
|
-
|
|
2549
|
+
p4.outro(
|
|
2550
|
+
pc14.green(
|
|
2551
2551
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2552
2552
|
)
|
|
2553
2553
|
);
|
|
@@ -2561,14 +2561,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2561
2561
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2562
2562
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2563
2563
|
const selected = handleCancel(
|
|
2564
|
-
await
|
|
2564
|
+
await p4.select({
|
|
2565
2565
|
message: "Select target feature:",
|
|
2566
2566
|
options: featureOptions
|
|
2567
2567
|
})
|
|
2568
2568
|
);
|
|
2569
2569
|
if (selected === "__custom__") {
|
|
2570
2570
|
feature = handleCancel(
|
|
2571
|
-
await
|
|
2571
|
+
await p4.text({
|
|
2572
2572
|
message: "Enter feature name:",
|
|
2573
2573
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2574
2574
|
})
|
|
@@ -2578,7 +2578,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2578
2578
|
}
|
|
2579
2579
|
} else {
|
|
2580
2580
|
feature = handleCancel(
|
|
2581
|
-
await
|
|
2581
|
+
await p4.text({
|
|
2582
2582
|
message: "Enter target feature name:",
|
|
2583
2583
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2584
2584
|
})
|
|
@@ -2589,7 +2589,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2589
2589
|
let name = useCaseNameArg?.trim();
|
|
2590
2590
|
if (!name) {
|
|
2591
2591
|
name = handleCancel(
|
|
2592
|
-
await
|
|
2592
|
+
await p4.text({
|
|
2593
2593
|
message: "Use Case name:",
|
|
2594
2594
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2595
2595
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2605,7 +2605,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2605
2605
|
label: i.name
|
|
2606
2606
|
}));
|
|
2607
2607
|
selectedDeps = handleCancel(
|
|
2608
|
-
await
|
|
2608
|
+
await p4.multiselect({
|
|
2609
2609
|
message: "Select domain interfaces to inject into use case:",
|
|
2610
2610
|
options: multiOptions,
|
|
2611
2611
|
required: false
|
|
@@ -2620,7 +2620,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2620
2620
|
let httpPath = "/";
|
|
2621
2621
|
if (!flags.yes) {
|
|
2622
2622
|
const hasInput = handleCancel(
|
|
2623
|
-
await
|
|
2623
|
+
await p4.confirm({
|
|
2624
2624
|
message: "Does this use case accept input arguments?",
|
|
2625
2625
|
initialValue: true
|
|
2626
2626
|
})
|
|
@@ -2630,7 +2630,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2630
2630
|
});
|
|
2631
2631
|
}
|
|
2632
2632
|
const hasCustomOutput = handleCancel(
|
|
2633
|
-
await
|
|
2633
|
+
await p4.confirm({
|
|
2634
2634
|
message: "Does this use case return custom output data?",
|
|
2635
2635
|
initialValue: false
|
|
2636
2636
|
})
|
|
@@ -2640,20 +2640,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2640
2640
|
});
|
|
2641
2641
|
}
|
|
2642
2642
|
includeTest = handleCancel(
|
|
2643
|
-
await
|
|
2643
|
+
await p4.confirm({
|
|
2644
2644
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2645
2645
|
initialValue: true
|
|
2646
2646
|
})
|
|
2647
2647
|
);
|
|
2648
2648
|
createHttp = handleCancel(
|
|
2649
|
-
await
|
|
2649
|
+
await p4.confirm({
|
|
2650
2650
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2651
2651
|
initialValue: false
|
|
2652
2652
|
})
|
|
2653
2653
|
);
|
|
2654
2654
|
if (createHttp) {
|
|
2655
2655
|
httpMethod = handleCancel(
|
|
2656
|
-
await
|
|
2656
|
+
await p4.select({
|
|
2657
2657
|
message: "HTTP method for route:",
|
|
2658
2658
|
options: [
|
|
2659
2659
|
{ value: "post", label: "POST" },
|
|
@@ -2666,7 +2666,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2666
2666
|
})
|
|
2667
2667
|
);
|
|
2668
2668
|
httpPath = handleCancel(
|
|
2669
|
-
await
|
|
2669
|
+
await p4.text({
|
|
2670
2670
|
message: "Route subpath (relative to feature prefix):",
|
|
2671
2671
|
initialValue: "/",
|
|
2672
2672
|
defaultValue: "/"
|
|
@@ -2674,8 +2674,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2674
2674
|
).trim();
|
|
2675
2675
|
}
|
|
2676
2676
|
}
|
|
2677
|
-
const s =
|
|
2678
|
-
s.start(`Generating use case '${
|
|
2677
|
+
const s = p4.spinner();
|
|
2678
|
+
s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
|
|
2679
2679
|
const options = {
|
|
2680
2680
|
name: ucName,
|
|
2681
2681
|
feature,
|
|
@@ -2707,7 +2707,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2707
2707
|
}
|
|
2708
2708
|
s.stop("Use case generated successfully!");
|
|
2709
2709
|
printGeneratedFiles(files, options.dryRun);
|
|
2710
|
-
|
|
2710
|
+
p4.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2711
2711
|
}
|
|
2712
2712
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2713
2713
|
const projectRoot = findProjectRoot();
|
|
@@ -2718,14 +2718,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2718
2718
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2719
2719
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2720
2720
|
const selected = handleCancel(
|
|
2721
|
-
await
|
|
2721
|
+
await p4.select({
|
|
2722
2722
|
message: "Select target feature:",
|
|
2723
2723
|
options: featureOptions
|
|
2724
2724
|
})
|
|
2725
2725
|
);
|
|
2726
2726
|
if (selected === "__custom__") {
|
|
2727
2727
|
feature = handleCancel(
|
|
2728
|
-
await
|
|
2728
|
+
await p4.text({
|
|
2729
2729
|
message: "Enter feature name:",
|
|
2730
2730
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2731
2731
|
})
|
|
@@ -2735,7 +2735,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2735
2735
|
}
|
|
2736
2736
|
} else {
|
|
2737
2737
|
feature = handleCancel(
|
|
2738
|
-
await
|
|
2738
|
+
await p4.text({
|
|
2739
2739
|
message: "Enter target feature name:",
|
|
2740
2740
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2741
2741
|
})
|
|
@@ -2746,7 +2746,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2746
2746
|
let name = domainNameArg?.trim();
|
|
2747
2747
|
if (!name) {
|
|
2748
2748
|
name = handleCancel(
|
|
2749
|
-
await
|
|
2749
|
+
await p4.text({
|
|
2750
2750
|
message: "Domain model / entity name:",
|
|
2751
2751
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2752
2752
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2758,7 +2758,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2758
2758
|
let properties = [];
|
|
2759
2759
|
if (!flags.yes) {
|
|
2760
2760
|
kind = handleCancel(
|
|
2761
|
-
await
|
|
2761
|
+
await p4.select({
|
|
2762
2762
|
message: "Select domain model type:",
|
|
2763
2763
|
options: [
|
|
2764
2764
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2777,8 +2777,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2777
2777
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2778
2778
|
});
|
|
2779
2779
|
}
|
|
2780
|
-
const s =
|
|
2781
|
-
s.start(`Generating domain model '${
|
|
2780
|
+
const s = p4.spinner();
|
|
2781
|
+
s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
|
|
2782
2782
|
const options = {
|
|
2783
2783
|
name: entityName,
|
|
2784
2784
|
feature,
|
|
@@ -2791,7 +2791,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2791
2791
|
const file = generateDomain(options);
|
|
2792
2792
|
s.stop("Domain model generated successfully!");
|
|
2793
2793
|
printGeneratedFiles([file], options.dryRun);
|
|
2794
|
-
|
|
2794
|
+
p4.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2795
2795
|
}
|
|
2796
2796
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2797
2797
|
const projectRoot = findProjectRoot();
|
|
@@ -2800,7 +2800,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2800
2800
|
let feature = flags.feature?.trim();
|
|
2801
2801
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2802
2802
|
scope = handleCancel(
|
|
2803
|
-
await
|
|
2803
|
+
await p4.select({
|
|
2804
2804
|
message: "Where should this infrastructure implementation be placed?",
|
|
2805
2805
|
options: [
|
|
2806
2806
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2815,14 +2815,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2815
2815
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2816
2816
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2817
2817
|
const selected = handleCancel(
|
|
2818
|
-
await
|
|
2818
|
+
await p4.select({
|
|
2819
2819
|
message: "Select target feature:",
|
|
2820
2820
|
options: featureOptions
|
|
2821
2821
|
})
|
|
2822
2822
|
);
|
|
2823
2823
|
if (selected === "__custom__") {
|
|
2824
2824
|
feature = handleCancel(
|
|
2825
|
-
await
|
|
2825
|
+
await p4.text({
|
|
2826
2826
|
message: "Enter feature name:",
|
|
2827
2827
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2828
2828
|
})
|
|
@@ -2832,7 +2832,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2832
2832
|
}
|
|
2833
2833
|
} else {
|
|
2834
2834
|
feature = handleCancel(
|
|
2835
|
-
await
|
|
2835
|
+
await p4.text({
|
|
2836
2836
|
message: "Enter target feature name:",
|
|
2837
2837
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2838
2838
|
})
|
|
@@ -2845,7 +2845,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2845
2845
|
let name = classNameArg?.trim();
|
|
2846
2846
|
if (!name) {
|
|
2847
2847
|
name = handleCancel(
|
|
2848
|
-
await
|
|
2848
|
+
await p4.text({
|
|
2849
2849
|
message: "Infrastructure class name:",
|
|
2850
2850
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2851
2851
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2864,7 +2864,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2864
2864
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2865
2865
|
);
|
|
2866
2866
|
const ifaceChoice = handleCancel(
|
|
2867
|
-
await
|
|
2867
|
+
await p4.select({
|
|
2868
2868
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2869
2869
|
options: [
|
|
2870
2870
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2876,7 +2876,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2876
2876
|
);
|
|
2877
2877
|
if (ifaceChoice === "__custom__") {
|
|
2878
2878
|
implementsInterface = handleCancel(
|
|
2879
|
-
await
|
|
2879
|
+
await p4.text({
|
|
2880
2880
|
message: "Enter interface or abstract class name:",
|
|
2881
2881
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2882
2882
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2887,14 +2887,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2887
2887
|
}
|
|
2888
2888
|
} else {
|
|
2889
2889
|
const wantImpl = handleCancel(
|
|
2890
|
-
await
|
|
2890
|
+
await p4.confirm({
|
|
2891
2891
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2892
2892
|
initialValue: false
|
|
2893
2893
|
})
|
|
2894
2894
|
);
|
|
2895
2895
|
if (wantImpl) {
|
|
2896
2896
|
implementsInterface = handleCancel(
|
|
2897
|
-
await
|
|
2897
|
+
await p4.text({
|
|
2898
2898
|
message: "Enter interface or abstract class name:",
|
|
2899
2899
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2900
2900
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2914,7 +2914,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2914
2914
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2915
2915
|
if (allInterfaces.length > 0) {
|
|
2916
2916
|
const ifaceChoice = handleCancel(
|
|
2917
|
-
await
|
|
2917
|
+
await p4.select({
|
|
2918
2918
|
message: "Does this class implement an interface or abstract class?",
|
|
2919
2919
|
options: [
|
|
2920
2920
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2926,7 +2926,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2926
2926
|
);
|
|
2927
2927
|
if (ifaceChoice === "__custom__") {
|
|
2928
2928
|
implementsInterface = handleCancel(
|
|
2929
|
-
await
|
|
2929
|
+
await p4.text({
|
|
2930
2930
|
message: "Enter interface or abstract class name:",
|
|
2931
2931
|
placeholder: "e.g. IAuthRepository",
|
|
2932
2932
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2939,7 +2939,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2939
2939
|
}
|
|
2940
2940
|
if (!flags.yes && !flags.kind) {
|
|
2941
2941
|
kind = handleCancel(
|
|
2942
|
-
await
|
|
2942
|
+
await p4.select({
|
|
2943
2943
|
message: "Select implementation kind:",
|
|
2944
2944
|
options: [
|
|
2945
2945
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2950,7 +2950,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2950
2950
|
);
|
|
2951
2951
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2952
2952
|
registerInProvider = handleCancel(
|
|
2953
|
-
await
|
|
2953
|
+
await p4.confirm({
|
|
2954
2954
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2955
2955
|
initialValue: true
|
|
2956
2956
|
})
|
|
@@ -2959,8 +2959,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2959
2959
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2960
2960
|
registerInProvider = !flags.noProvider;
|
|
2961
2961
|
}
|
|
2962
|
-
const s =
|
|
2963
|
-
s.start(`Generating infrastructure class '${
|
|
2962
|
+
const s = p4.spinner();
|
|
2963
|
+
s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
|
|
2964
2964
|
const files = [];
|
|
2965
2965
|
const options = {
|
|
2966
2966
|
name: className,
|
|
@@ -2995,7 +2995,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2995
2995
|
}
|
|
2996
2996
|
s.stop("Infrastructure class generated successfully!");
|
|
2997
2997
|
printGeneratedFiles(files, options.dryRun);
|
|
2998
|
-
|
|
2998
|
+
p4.outro(pc14.green(`Infrastructure class '${className}' created!`));
|
|
2999
2999
|
}
|
|
3000
3000
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
3001
3001
|
const projectRoot = findProjectRoot();
|
|
@@ -3006,14 +3006,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3006
3006
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3007
3007
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3008
3008
|
const selected = handleCancel(
|
|
3009
|
-
await
|
|
3009
|
+
await p4.select({
|
|
3010
3010
|
message: "Select target feature:",
|
|
3011
3011
|
options: featureOptions
|
|
3012
3012
|
})
|
|
3013
3013
|
);
|
|
3014
3014
|
if (selected === "__custom__") {
|
|
3015
3015
|
feature = handleCancel(
|
|
3016
|
-
await
|
|
3016
|
+
await p4.text({
|
|
3017
3017
|
message: "Enter feature name:",
|
|
3018
3018
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3019
3019
|
})
|
|
@@ -3023,7 +3023,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3023
3023
|
}
|
|
3024
3024
|
} else {
|
|
3025
3025
|
feature = handleCancel(
|
|
3026
|
-
await
|
|
3026
|
+
await p4.text({
|
|
3027
3027
|
message: "Enter target feature name:",
|
|
3028
3028
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3029
3029
|
})
|
|
@@ -3034,7 +3034,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3034
3034
|
let name = interfaceNameArg?.trim();
|
|
3035
3035
|
if (!name) {
|
|
3036
3036
|
name = handleCancel(
|
|
3037
|
-
await
|
|
3037
|
+
await p4.text({
|
|
3038
3038
|
message: "Domain interface name:",
|
|
3039
3039
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
3040
3040
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -3052,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3052
3052
|
if (!flags.yes) {
|
|
3053
3053
|
if (existingEntities.length > 0) {
|
|
3054
3054
|
const selectedEntity = handleCancel(
|
|
3055
|
-
await
|
|
3055
|
+
await p4.select({
|
|
3056
3056
|
message: "Select domain entity associated with this interface:",
|
|
3057
3057
|
options: [
|
|
3058
3058
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -3063,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3063
3063
|
);
|
|
3064
3064
|
if (selectedEntity === "__custom__") {
|
|
3065
3065
|
entityName = handleCancel(
|
|
3066
|
-
await
|
|
3066
|
+
await p4.text({
|
|
3067
3067
|
message: "Enter entity name:",
|
|
3068
3068
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
3069
3069
|
})
|
|
@@ -3073,7 +3073,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3073
3073
|
}
|
|
3074
3074
|
}
|
|
3075
3075
|
kind = handleCancel(
|
|
3076
|
-
await
|
|
3076
|
+
await p4.select({
|
|
3077
3077
|
message: "Interface definition style:",
|
|
3078
3078
|
options: [
|
|
3079
3079
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -3083,7 +3083,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3083
3083
|
})
|
|
3084
3084
|
);
|
|
3085
3085
|
methodsPreset = handleCancel(
|
|
3086
|
-
await
|
|
3086
|
+
await p4.select({
|
|
3087
3087
|
message: "Method signature preset:",
|
|
3088
3088
|
options: [
|
|
3089
3089
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -3093,22 +3093,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3093
3093
|
})
|
|
3094
3094
|
);
|
|
3095
3095
|
generateStub = handleCancel(
|
|
3096
|
-
await
|
|
3096
|
+
await p4.confirm({
|
|
3097
3097
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
3098
3098
|
initialValue: true
|
|
3099
3099
|
})
|
|
3100
3100
|
);
|
|
3101
3101
|
if (generateStub) {
|
|
3102
3102
|
registerInProvider = handleCancel(
|
|
3103
|
-
await
|
|
3103
|
+
await p4.confirm({
|
|
3104
3104
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
3105
3105
|
initialValue: true
|
|
3106
3106
|
})
|
|
3107
3107
|
);
|
|
3108
3108
|
}
|
|
3109
3109
|
}
|
|
3110
|
-
const s =
|
|
3111
|
-
s.start(`Generating interface '${
|
|
3110
|
+
const s = p4.spinner();
|
|
3111
|
+
s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
|
|
3112
3112
|
const files = [];
|
|
3113
3113
|
const options = {
|
|
3114
3114
|
name: ifaceName,
|
|
@@ -3157,7 +3157,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3157
3157
|
}
|
|
3158
3158
|
s.stop("Interface generated successfully!");
|
|
3159
3159
|
printGeneratedFiles(files, options.dryRun);
|
|
3160
|
-
|
|
3160
|
+
p4.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
3161
3161
|
}
|
|
3162
3162
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
3163
3163
|
const projectRoot = findProjectRoot();
|
|
@@ -3168,14 +3168,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3168
3168
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3169
3169
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3170
3170
|
const selected = handleCancel(
|
|
3171
|
-
await
|
|
3171
|
+
await p4.select({
|
|
3172
3172
|
message: "Select target feature:",
|
|
3173
3173
|
options: featureOptions
|
|
3174
3174
|
})
|
|
3175
3175
|
);
|
|
3176
3176
|
if (selected === "__custom__") {
|
|
3177
3177
|
feature = handleCancel(
|
|
3178
|
-
await
|
|
3178
|
+
await p4.text({
|
|
3179
3179
|
message: "Enter feature name:",
|
|
3180
3180
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3181
3181
|
})
|
|
@@ -3185,7 +3185,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3185
3185
|
}
|
|
3186
3186
|
} else {
|
|
3187
3187
|
feature = handleCancel(
|
|
3188
|
-
await
|
|
3188
|
+
await p4.text({
|
|
3189
3189
|
message: "Enter target feature name:",
|
|
3190
3190
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3191
3191
|
})
|
|
@@ -3196,7 +3196,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3196
3196
|
let name = handlerNameArg?.trim();
|
|
3197
3197
|
if (!name) {
|
|
3198
3198
|
name = handleCancel(
|
|
3199
|
-
await
|
|
3199
|
+
await p4.text({
|
|
3200
3200
|
message: "HTTP Route Handler class name:",
|
|
3201
3201
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
3202
3202
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -3215,7 +3215,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3215
3215
|
let validationFields = [];
|
|
3216
3216
|
if (!flags.yes) {
|
|
3217
3217
|
method = handleCancel(
|
|
3218
|
-
await
|
|
3218
|
+
await p4.select({
|
|
3219
3219
|
message: "HTTP Method:",
|
|
3220
3220
|
options: [
|
|
3221
3221
|
{ value: "get", label: "GET" },
|
|
@@ -3229,7 +3229,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3229
3229
|
);
|
|
3230
3230
|
const defaultPath = method === "get" ? "/" : "/";
|
|
3231
3231
|
subPath = handleCancel(
|
|
3232
|
-
await
|
|
3232
|
+
await p4.text({
|
|
3233
3233
|
message: "Route subpath (relative to feature prefix):",
|
|
3234
3234
|
initialValue: defaultPath,
|
|
3235
3235
|
defaultValue: defaultPath
|
|
@@ -3240,7 +3240,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3240
3240
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
3241
3241
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
3242
3242
|
const chosenUc = handleCancel(
|
|
3243
|
-
await
|
|
3243
|
+
await p4.select({
|
|
3244
3244
|
message: "Connect this handler to a domain Use Case?",
|
|
3245
3245
|
options: ucOptions,
|
|
3246
3246
|
initialValue: ucOptions[0]?.value
|
|
@@ -3251,7 +3251,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3251
3251
|
}
|
|
3252
3252
|
}
|
|
3253
3253
|
includeValidation = handleCancel(
|
|
3254
|
-
await
|
|
3254
|
+
await p4.confirm({
|
|
3255
3255
|
message: "Include Zod inbound request validation?",
|
|
3256
3256
|
initialValue: method !== "delete"
|
|
3257
3257
|
})
|
|
@@ -3259,7 +3259,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3259
3259
|
if (includeValidation) {
|
|
3260
3260
|
const defaultSource = method === "get" ? "query" : "body";
|
|
3261
3261
|
validationSource = handleCancel(
|
|
3262
|
-
await
|
|
3262
|
+
await p4.select({
|
|
3263
3263
|
message: "Validation data source:",
|
|
3264
3264
|
options: [
|
|
3265
3265
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -3273,8 +3273,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3273
3273
|
});
|
|
3274
3274
|
}
|
|
3275
3275
|
}
|
|
3276
|
-
const s =
|
|
3277
|
-
s.start(`Generating HTTP handler '${
|
|
3276
|
+
const s = p4.spinner();
|
|
3277
|
+
s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
|
|
3278
3278
|
const options = {
|
|
3279
3279
|
name: className,
|
|
3280
3280
|
feature,
|
|
@@ -3292,7 +3292,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3292
3292
|
const files = generateHttpHandler(options);
|
|
3293
3293
|
s.stop("HTTP handler generated successfully!");
|
|
3294
3294
|
printGeneratedFiles(files, options.dryRun);
|
|
3295
|
-
|
|
3295
|
+
p4.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3296
3296
|
}
|
|
3297
3297
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3298
3298
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3302,7 +3302,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3302
3302
|
const formattedMethods = methods.map((m) => {
|
|
3303
3303
|
const isAsync = m.async !== false;
|
|
3304
3304
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3305
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3305
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join(", ") : "";
|
|
3306
3306
|
return {
|
|
3307
3307
|
name: m.name,
|
|
3308
3308
|
isAsync,
|
|
@@ -3372,7 +3372,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3372
3372
|
let name = classNameArg?.trim();
|
|
3373
3373
|
if (!name) {
|
|
3374
3374
|
name = handleCancel(
|
|
3375
|
-
await
|
|
3375
|
+
await p4.text({
|
|
3376
3376
|
message: "Injectable class name:",
|
|
3377
3377
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3378
3378
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3385,7 +3385,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3385
3385
|
let customPath;
|
|
3386
3386
|
if (!flags.yes) {
|
|
3387
3387
|
location = handleCancel(
|
|
3388
|
-
await
|
|
3388
|
+
await p4.select({
|
|
3389
3389
|
message: "Select target architectural location:",
|
|
3390
3390
|
options: [
|
|
3391
3391
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3403,14 +3403,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3403
3403
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3404
3404
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3405
3405
|
const selected = handleCancel(
|
|
3406
|
-
await
|
|
3406
|
+
await p4.select({
|
|
3407
3407
|
message: "Select target feature:",
|
|
3408
3408
|
options: featureOptions
|
|
3409
3409
|
})
|
|
3410
3410
|
);
|
|
3411
3411
|
if (selected === "__custom__") {
|
|
3412
3412
|
feature = handleCancel(
|
|
3413
|
-
await
|
|
3413
|
+
await p4.text({
|
|
3414
3414
|
message: "Enter feature name:",
|
|
3415
3415
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3416
3416
|
})
|
|
@@ -3420,7 +3420,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3420
3420
|
}
|
|
3421
3421
|
} else {
|
|
3422
3422
|
feature = handleCancel(
|
|
3423
|
-
await
|
|
3423
|
+
await p4.text({
|
|
3424
3424
|
message: "Enter target feature name:",
|
|
3425
3425
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3426
3426
|
})
|
|
@@ -3430,7 +3430,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3430
3430
|
feature = toCamelCase(feature);
|
|
3431
3431
|
} else if (location === "custom") {
|
|
3432
3432
|
customPath = handleCancel(
|
|
3433
|
-
await
|
|
3433
|
+
await p4.text({
|
|
3434
3434
|
message: "Enter destination directory path:",
|
|
3435
3435
|
placeholder: "e.g. src/services or lib/utils",
|
|
3436
3436
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3441,14 +3441,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3441
3441
|
let generateInterface2 = false;
|
|
3442
3442
|
if (!flags.yes) {
|
|
3443
3443
|
generateInterface2 = handleCancel(
|
|
3444
|
-
await
|
|
3444
|
+
await p4.confirm({
|
|
3445
3445
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3446
3446
|
initialValue: false
|
|
3447
3447
|
})
|
|
3448
3448
|
);
|
|
3449
3449
|
}
|
|
3450
|
-
const s =
|
|
3451
|
-
s.start(`Generating injectable '${
|
|
3450
|
+
const s = p4.spinner();
|
|
3451
|
+
s.start(`Generating injectable '${pc14.cyan(className)}'...`);
|
|
3452
3452
|
const options = {
|
|
3453
3453
|
name: className,
|
|
3454
3454
|
feature,
|
|
@@ -3462,12 +3462,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3462
3462
|
const files = generateInjectable(options);
|
|
3463
3463
|
s.stop("Injectable class generated successfully!");
|
|
3464
3464
|
printGeneratedFiles(files, options.dryRun);
|
|
3465
|
-
|
|
3465
|
+
p4.outro(pc14.green(`Injectable '${className}' created successfully!`));
|
|
3466
3466
|
}
|
|
3467
3467
|
async function runInteractiveMenu(flags = {}) {
|
|
3468
3468
|
printBanner();
|
|
3469
3469
|
const choice = handleCancel(
|
|
3470
|
-
await
|
|
3470
|
+
await p4.select({
|
|
3471
3471
|
message: "What would you like to create in your architecture?",
|
|
3472
3472
|
options: [
|
|
3473
3473
|
{
|
|
@@ -3575,8 +3575,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3575
3575
|
await runInjectableCommand(name, flags);
|
|
3576
3576
|
break;
|
|
3577
3577
|
default:
|
|
3578
|
-
|
|
3579
|
-
|
|
3578
|
+
p4.log.error(
|
|
3579
|
+
pc14.red(
|
|
3580
3580
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3581
3581
|
)
|
|
3582
3582
|
);
|
|
@@ -3584,8 +3584,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3584
3584
|
}
|
|
3585
3585
|
}
|
|
3586
3586
|
var importPrompts = {
|
|
3587
|
-
select:
|
|
3588
|
-
text:
|
|
3587
|
+
select: p4.select,
|
|
3588
|
+
text: p4.text
|
|
3589
3589
|
};
|
|
3590
3590
|
function rewriteImports(content, aliasMap) {
|
|
3591
3591
|
if (aliasMap.size === 0) return content;
|
|
@@ -3606,9 +3606,9 @@ function getModuleBaseDir(projectRoot, modType) {
|
|
|
3606
3606
|
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3607
3607
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3608
3608
|
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3609
|
-
if (
|
|
3609
|
+
if (fs4.existsSync(inSrc)) return inSrc;
|
|
3610
3610
|
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3611
|
-
if (
|
|
3611
|
+
if (fs4.existsSync(inRoot)) return inRoot;
|
|
3612
3612
|
return null;
|
|
3613
3613
|
}
|
|
3614
3614
|
function checkModuleExists(projectRoot, modType, modName) {
|
|
@@ -3626,13 +3626,13 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3626
3626
|
projectRoot,
|
|
3627
3627
|
path2.join(baseDir, programmaticAlias)
|
|
3628
3628
|
);
|
|
3629
|
-
|
|
3629
|
+
p4.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3630
3630
|
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3631
3631
|
return;
|
|
3632
3632
|
}
|
|
3633
3633
|
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3634
3634
|
if (!existingDir) {
|
|
3635
|
-
|
|
3635
|
+
p4.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14.cyan(depName)}`);
|
|
3636
3636
|
await importModuleRecursive(depType, depName, depName);
|
|
3637
3637
|
return;
|
|
3638
3638
|
}
|
|
@@ -3649,25 +3649,25 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3649
3649
|
}
|
|
3650
3650
|
const localHash = computeDirectoryHash(existingDir);
|
|
3651
3651
|
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3652
|
-
|
|
3652
|
+
p4.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3653
3653
|
importedModules.add(`${depType}:${depName}`);
|
|
3654
3654
|
return;
|
|
3655
3655
|
}
|
|
3656
3656
|
if (flags.overwrite) {
|
|
3657
|
-
|
|
3657
|
+
p4.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3658
3658
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3659
3659
|
const targetDir = path2.join(baseDir, depName);
|
|
3660
3660
|
if (!flags.dryRun) {
|
|
3661
3661
|
if (existingDir !== targetDir) {
|
|
3662
|
-
|
|
3662
|
+
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
3663
3663
|
}
|
|
3664
|
-
|
|
3664
|
+
fs4.rmSync(targetDir, { recursive: true, force: true });
|
|
3665
3665
|
}
|
|
3666
3666
|
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3667
3667
|
return;
|
|
3668
3668
|
}
|
|
3669
3669
|
if (flags.yes) {
|
|
3670
|
-
|
|
3670
|
+
p4.log.info(`Using existing '${depType}/${depName}'`);
|
|
3671
3671
|
importedModules.add(`${depType}:${depName}`);
|
|
3672
3672
|
return;
|
|
3673
3673
|
}
|
|
@@ -3691,7 +3691,7 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3691
3691
|
})
|
|
3692
3692
|
);
|
|
3693
3693
|
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3694
|
-
|
|
3694
|
+
p4.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3695
3695
|
importedModules.add(`${depType}:${depName}`);
|
|
3696
3696
|
return;
|
|
3697
3697
|
}
|
|
@@ -3716,19 +3716,19 @@ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags,
|
|
|
3716
3716
|
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3717
3717
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3718
3718
|
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
3719
|
-
|
|
3719
|
+
p4.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3720
3720
|
await importModuleRecursive(depType, depName, newName);
|
|
3721
3721
|
return;
|
|
3722
3722
|
}
|
|
3723
3723
|
if (choice === "overwrite") {
|
|
3724
|
-
|
|
3724
|
+
p4.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3725
3725
|
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3726
3726
|
const targetDir = path2.join(baseDir, depName);
|
|
3727
3727
|
if (!flags.dryRun) {
|
|
3728
3728
|
if (existingDir !== targetDir) {
|
|
3729
|
-
|
|
3729
|
+
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
3730
3730
|
}
|
|
3731
|
-
|
|
3731
|
+
fs4.rmSync(targetDir, { recursive: true, force: true });
|
|
3732
3732
|
}
|
|
3733
3733
|
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3734
3734
|
return;
|
|
@@ -3758,19 +3758,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3758
3758
|
);
|
|
3759
3759
|
}
|
|
3760
3760
|
if (type !== "shared" && type !== "feature") {
|
|
3761
|
-
|
|
3761
|
+
p4.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3762
3762
|
process.exit(1);
|
|
3763
3763
|
}
|
|
3764
3764
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3765
|
-
const s =
|
|
3766
|
-
s.start(`Connecting to registry at ${
|
|
3765
|
+
const s = p4.spinner();
|
|
3766
|
+
s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
|
|
3767
3767
|
let index;
|
|
3768
3768
|
try {
|
|
3769
3769
|
index = await fetchRegistryIndex(registryBase);
|
|
3770
3770
|
s.stop("Registry index loaded.");
|
|
3771
3771
|
} catch (err) {
|
|
3772
|
-
s.stop(
|
|
3773
|
-
|
|
3772
|
+
s.stop(pc14.red("Failed to load registry index."));
|
|
3773
|
+
p4.log.error(pc14.red(err.message || String(err)));
|
|
3774
3774
|
process.exit(1);
|
|
3775
3775
|
}
|
|
3776
3776
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3778,7 +3778,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3778
3778
|
let name = nameArg?.trim();
|
|
3779
3779
|
if (!name) {
|
|
3780
3780
|
if (availableList.length === 0) {
|
|
3781
|
-
|
|
3781
|
+
p4.log.warn(`No ${type} modules available in the registry.`);
|
|
3782
3782
|
return;
|
|
3783
3783
|
}
|
|
3784
3784
|
name = handleCancel(
|
|
@@ -3794,9 +3794,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3794
3794
|
}
|
|
3795
3795
|
if (!candidateNames.includes(name)) {
|
|
3796
3796
|
const closest = findClosestMatch(name, candidateNames);
|
|
3797
|
-
|
|
3797
|
+
p4.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3798
3798
|
if (closest) {
|
|
3799
|
-
|
|
3799
|
+
p4.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
|
|
3800
3800
|
}
|
|
3801
3801
|
process.exit(1);
|
|
3802
3802
|
}
|
|
@@ -3806,14 +3806,14 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3806
3806
|
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3807
3807
|
const modKey = `${modType}:${targetName}`;
|
|
3808
3808
|
if (importedModules.has(modKey)) return;
|
|
3809
|
-
|
|
3810
|
-
`Importing ${modType}: ${
|
|
3809
|
+
p4.log.step(
|
|
3810
|
+
`Importing ${modType}: ${pc14.cyan(targetName)}${targetName !== registryName ? pc14.dim(` (from ${registryName})`) : ""}...`
|
|
3811
3811
|
);
|
|
3812
3812
|
let manifest;
|
|
3813
3813
|
try {
|
|
3814
3814
|
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3815
3815
|
} catch (err) {
|
|
3816
|
-
|
|
3816
|
+
p4.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3817
3817
|
process.exit(1);
|
|
3818
3818
|
}
|
|
3819
3819
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
@@ -3859,21 +3859,21 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3859
3859
|
if (file === "registry.json") continue;
|
|
3860
3860
|
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3861
3861
|
const localFilePath = path2.join(targetDir, file);
|
|
3862
|
-
if (
|
|
3863
|
-
|
|
3864
|
-
` \u21B3 Skipping existing file: ${
|
|
3862
|
+
if (fs4.existsSync(localFilePath) && !isOverwrite) {
|
|
3863
|
+
p4.log.warn(
|
|
3864
|
+
` \u21B3 Skipping existing file: ${pc14.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3865
3865
|
);
|
|
3866
3866
|
continue;
|
|
3867
3867
|
}
|
|
3868
3868
|
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3869
3869
|
content = rewriteImports(content, aliasMap);
|
|
3870
3870
|
if (!flags.dryRun) {
|
|
3871
|
-
|
|
3872
|
-
|
|
3871
|
+
fs4.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
3872
|
+
fs4.writeFileSync(localFilePath, content, "utf-8");
|
|
3873
3873
|
}
|
|
3874
3874
|
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
3875
3875
|
totalWrittenFiles.push(displayPath);
|
|
3876
|
-
|
|
3876
|
+
p4.log.message(` ${pc14.green("+")} ${displayPath}`);
|
|
3877
3877
|
}
|
|
3878
3878
|
importedModules.add(modKey);
|
|
3879
3879
|
}
|
|
@@ -3881,564 +3881,35 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3881
3881
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3882
3882
|
if (missingNpm.length > 0) {
|
|
3883
3883
|
if (flags.skipInstall || flags.dryRun) {
|
|
3884
|
-
|
|
3884
|
+
p4.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
|
|
3885
3885
|
} else {
|
|
3886
3886
|
try {
|
|
3887
3887
|
installNpmPackages(projectRoot, missingNpm);
|
|
3888
3888
|
} catch (err) {
|
|
3889
|
-
|
|
3889
|
+
p4.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3890
3890
|
}
|
|
3891
3891
|
}
|
|
3892
3892
|
}
|
|
3893
3893
|
if (flags.dryRun) {
|
|
3894
|
-
|
|
3894
|
+
p4.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
|
|
3895
3895
|
} else {
|
|
3896
3896
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3897
3897
|
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
|
|
3898
|
-
|
|
3899
|
-
|
|
3898
|
+
p4.log.success(
|
|
3899
|
+
pc14.green(
|
|
3900
3900
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3901
3901
|
)
|
|
3902
3902
|
);
|
|
3903
3903
|
}
|
|
3904
3904
|
}
|
|
3905
|
-
function loadMasterIndex(projectRoot) {
|
|
3906
|
-
const indexPath = path2.join(projectRoot, "index.json");
|
|
3907
|
-
if (!fs12.existsSync(indexPath)) {
|
|
3908
|
-
const initialIndex = { shared: [], features: [] };
|
|
3909
|
-
fs12.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
|
|
3910
|
-
return initialIndex;
|
|
3911
|
-
}
|
|
3912
|
-
return JSON.parse(fs12.readFileSync(indexPath, "utf-8"));
|
|
3913
|
-
}
|
|
3914
|
-
function saveMasterIndex(projectRoot, index) {
|
|
3915
|
-
const indexPath = path2.join(projectRoot, "index.json");
|
|
3916
|
-
index.shared.sort((a, b) => a.name.localeCompare(b.name));
|
|
3917
|
-
index.features.sort((a, b) => a.name.localeCompare(b.name));
|
|
3918
|
-
fs12.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
3919
|
-
}
|
|
3920
|
-
function discoverDiskModules(projectRoot) {
|
|
3921
|
-
const results = [];
|
|
3922
|
-
const sharedDir = resolveSharedDir(projectRoot);
|
|
3923
|
-
if (fs12.existsSync(sharedDir)) {
|
|
3924
|
-
const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
|
|
3925
|
-
for (const entry of entries) {
|
|
3926
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3927
|
-
results.push({ name: entry.name, type: "shared", dir: path2.join(sharedDir, entry.name) });
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
}
|
|
3931
|
-
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
3932
|
-
if (fs12.existsSync(featuresDir)) {
|
|
3933
|
-
const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
|
|
3934
|
-
for (const entry of entries) {
|
|
3935
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3936
|
-
results.push({ name: entry.name, type: "feature", dir: path2.join(featuresDir, entry.name) });
|
|
3937
|
-
}
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
return results;
|
|
3941
|
-
}
|
|
3942
|
-
function checkGitStatusForDir(projectRoot, dirPath) {
|
|
3943
|
-
try {
|
|
3944
|
-
const relPath = path2.relative(projectRoot, dirPath);
|
|
3945
|
-
const output = execSync(`git status --porcelain -- "${relPath}"`, {
|
|
3946
|
-
cwd: projectRoot,
|
|
3947
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
3948
|
-
encoding: "utf-8"
|
|
3949
|
-
}).trim();
|
|
3950
|
-
if (!output) {
|
|
3951
|
-
return { hasChanges: false, filesCount: 0 };
|
|
3952
|
-
}
|
|
3953
|
-
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
3954
|
-
return { hasChanges: true, filesCount: lines.length };
|
|
3955
|
-
} catch {
|
|
3956
|
-
return { hasChanges: false, filesCount: 0 };
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3959
|
-
function getRegistryDiff(projectRoot) {
|
|
3960
|
-
const index = loadMasterIndex(projectRoot);
|
|
3961
|
-
const diskModules = discoverDiskModules(projectRoot);
|
|
3962
|
-
const diskMap = /* @__PURE__ */ new Map();
|
|
3963
|
-
for (const mod of diskModules) {
|
|
3964
|
-
diskMap.set(`${mod.type}:${mod.name}`, mod);
|
|
3965
|
-
}
|
|
3966
|
-
const allKeys = /* @__PURE__ */ new Set();
|
|
3967
|
-
for (const item of index.shared) {
|
|
3968
|
-
allKeys.add(`shared:${item.name}`);
|
|
3969
|
-
}
|
|
3970
|
-
for (const item of index.features) {
|
|
3971
|
-
allKeys.add(`feature:${item.name}`);
|
|
3972
|
-
}
|
|
3973
|
-
for (const mod of diskModules) {
|
|
3974
|
-
allKeys.add(`${mod.type}:${mod.name}`);
|
|
3975
|
-
}
|
|
3976
|
-
const statuses = [];
|
|
3977
|
-
for (const key of allKeys) {
|
|
3978
|
-
const [typeStr, name] = key.split(":");
|
|
3979
|
-
const type = typeStr;
|
|
3980
|
-
const registeredEntry = (type === "shared" ? index.shared : index.features).find(
|
|
3981
|
-
(e) => e.name === name
|
|
3982
|
-
);
|
|
3983
|
-
const isRegistered = Boolean(registeredEntry);
|
|
3984
|
-
const diskInfo = diskMap.get(key);
|
|
3985
|
-
const existsOnDisk = Boolean(diskInfo && fs12.existsSync(diskInfo.dir));
|
|
3986
|
-
let hasUncommittedGitChanges = false;
|
|
3987
|
-
let uncommittedFilesCount = 0;
|
|
3988
|
-
let hasIntegrityDrift = false;
|
|
3989
|
-
let localIntegrity;
|
|
3990
|
-
if (existsOnDisk && diskInfo) {
|
|
3991
|
-
const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
|
|
3992
|
-
hasUncommittedGitChanges = git.hasChanges;
|
|
3993
|
-
uncommittedFilesCount = git.filesCount;
|
|
3994
|
-
localIntegrity = computeDirectoryHash(diskInfo.dir);
|
|
3995
|
-
if (isRegistered && registeredEntry?.integrity) {
|
|
3996
|
-
hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
statuses.push({
|
|
4000
|
-
name,
|
|
4001
|
-
type,
|
|
4002
|
-
isRegistered,
|
|
4003
|
-
existsOnDisk,
|
|
4004
|
-
hasUncommittedGitChanges,
|
|
4005
|
-
uncommittedFilesCount,
|
|
4006
|
-
hasIntegrityDrift,
|
|
4007
|
-
localIntegrity,
|
|
4008
|
-
registeredIntegrity: registeredEntry?.integrity
|
|
4009
|
-
});
|
|
4010
|
-
}
|
|
4011
|
-
return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
4012
|
-
}
|
|
4013
|
-
async function registerModule(typeArg, nameArg, flags = {}) {
|
|
4014
|
-
const projectRoot = findProjectRoot();
|
|
4015
|
-
const index = loadMasterIndex(projectRoot);
|
|
4016
|
-
let type = typeArg?.trim();
|
|
4017
|
-
if (!type) {
|
|
4018
|
-
type = handleCancel(
|
|
4019
|
-
await p15.select({
|
|
4020
|
-
message: "Module type to register:",
|
|
4021
|
-
options: [
|
|
4022
|
-
{ value: "feature", label: "Feature" },
|
|
4023
|
-
{ value: "shared", label: "Shared Module" }
|
|
4024
|
-
]
|
|
4025
|
-
})
|
|
4026
|
-
);
|
|
4027
|
-
}
|
|
4028
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4029
|
-
const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
|
|
4030
|
-
const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
|
|
4031
|
-
let name = nameArg?.trim();
|
|
4032
|
-
if (!name) {
|
|
4033
|
-
if (unregistered.length > 0) {
|
|
4034
|
-
const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
|
|
4035
|
-
opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
|
|
4036
|
-
const choice = handleCancel(
|
|
4037
|
-
await p15.select({
|
|
4038
|
-
message: `Select a ${type} to register:`,
|
|
4039
|
-
options: opts
|
|
4040
|
-
})
|
|
4041
|
-
);
|
|
4042
|
-
if (choice === "__custom__") {
|
|
4043
|
-
name = handleCancel(
|
|
4044
|
-
await p15.text({
|
|
4045
|
-
message: `Enter ${type} name:`,
|
|
4046
|
-
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4047
|
-
})
|
|
4048
|
-
).trim();
|
|
4049
|
-
} else {
|
|
4050
|
-
name = choice;
|
|
4051
|
-
}
|
|
4052
|
-
} else {
|
|
4053
|
-
name = handleCancel(
|
|
4054
|
-
await p15.text({
|
|
4055
|
-
message: `Enter ${type} name to register:`,
|
|
4056
|
-
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4057
|
-
})
|
|
4058
|
-
).trim();
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4062
|
-
const modDir = path2.join(folder, name);
|
|
4063
|
-
if (!fs12.existsSync(modDir)) {
|
|
4064
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4065
|
-
process.exit(1);
|
|
4066
|
-
}
|
|
4067
|
-
const files = getAllFiles(modDir);
|
|
4068
|
-
const integrity = computeDirectoryHash(modDir, files);
|
|
4069
|
-
const manifestPath = path2.join(modDir, "registry.json");
|
|
4070
|
-
let manifest;
|
|
4071
|
-
if (fs12.existsSync(manifestPath)) {
|
|
4072
|
-
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4073
|
-
} else {
|
|
4074
|
-
manifest = {
|
|
4075
|
-
name,
|
|
4076
|
-
type,
|
|
4077
|
-
description: `${name} ${type} module`,
|
|
4078
|
-
version: "1.0.0",
|
|
4079
|
-
dependencies: {
|
|
4080
|
-
npm: [],
|
|
4081
|
-
shared: [],
|
|
4082
|
-
features: []
|
|
4083
|
-
},
|
|
4084
|
-
files: []
|
|
4085
|
-
};
|
|
4086
|
-
}
|
|
4087
|
-
manifest.name = name;
|
|
4088
|
-
manifest.type = type;
|
|
4089
|
-
manifest.files = files;
|
|
4090
|
-
manifest.integrity = integrity;
|
|
4091
|
-
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4092
|
-
const targetList = type === "shared" ? index.shared : index.features;
|
|
4093
|
-
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4094
|
-
if (existingIdx >= 0) {
|
|
4095
|
-
targetList[existingIdx] = {
|
|
4096
|
-
name,
|
|
4097
|
-
description: manifest.description,
|
|
4098
|
-
version: manifest.version,
|
|
4099
|
-
integrity
|
|
4100
|
-
};
|
|
4101
|
-
} else {
|
|
4102
|
-
targetList.push({
|
|
4103
|
-
name,
|
|
4104
|
-
description: manifest.description,
|
|
4105
|
-
version: manifest.version,
|
|
4106
|
-
integrity
|
|
4107
|
-
});
|
|
4108
|
-
}
|
|
4109
|
-
saveMasterIndex(projectRoot, index);
|
|
4110
|
-
p15.log.success(
|
|
4111
|
-
pc15.green(
|
|
4112
|
-
`\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
|
|
4113
|
-
)
|
|
4114
|
-
);
|
|
4115
|
-
}
|
|
4116
|
-
async function commitModule(typeArg, nameArg, flags = {}) {
|
|
4117
|
-
const projectRoot = findProjectRoot();
|
|
4118
|
-
let type = typeArg?.trim();
|
|
4119
|
-
if (!type) {
|
|
4120
|
-
type = handleCancel(
|
|
4121
|
-
await p15.select({
|
|
4122
|
-
message: "Module type to commit:",
|
|
4123
|
-
options: [
|
|
4124
|
-
{ value: "feature", label: "Feature" },
|
|
4125
|
-
{ value: "shared", label: "Shared Module" }
|
|
4126
|
-
]
|
|
4127
|
-
})
|
|
4128
|
-
);
|
|
4129
|
-
}
|
|
4130
|
-
let name = nameArg?.trim();
|
|
4131
|
-
if (!name) {
|
|
4132
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4133
|
-
const opts = diskModules.map((m) => {
|
|
4134
|
-
const git = checkGitStatusForDir(projectRoot, m.dir);
|
|
4135
|
-
return {
|
|
4136
|
-
value: m.name,
|
|
4137
|
-
label: m.name,
|
|
4138
|
-
hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
|
|
4139
|
-
};
|
|
4140
|
-
});
|
|
4141
|
-
name = handleCancel(
|
|
4142
|
-
await p15.select({
|
|
4143
|
-
message: `Select ${type} to commit:`,
|
|
4144
|
-
options: opts
|
|
4145
|
-
})
|
|
4146
|
-
);
|
|
4147
|
-
}
|
|
4148
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4149
|
-
const modDir = path2.join(folder, name);
|
|
4150
|
-
if (!fs12.existsSync(modDir)) {
|
|
4151
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4152
|
-
process.exit(1);
|
|
4153
|
-
}
|
|
4154
|
-
const relModDir = path2.relative(projectRoot, modDir);
|
|
4155
|
-
const gitStatus = checkGitStatusForDir(projectRoot, modDir);
|
|
4156
|
-
if (!gitStatus.hasChanges) {
|
|
4157
|
-
p15.log.warn(pc15.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
|
|
4158
|
-
return;
|
|
4159
|
-
}
|
|
4160
|
-
let message = flags.message?.trim();
|
|
4161
|
-
if (!message) {
|
|
4162
|
-
message = handleCancel(
|
|
4163
|
-
await p15.text({
|
|
4164
|
-
message: `Commit message for ${type}/${name}:`,
|
|
4165
|
-
placeholder: `e.g. feat(${name}): update use cases and dependencies`,
|
|
4166
|
-
validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
|
|
4167
|
-
})
|
|
4168
|
-
).trim();
|
|
4169
|
-
}
|
|
4170
|
-
const s = p15.spinner();
|
|
4171
|
-
s.start(`Committing '${type}/${name}' to git repository...`);
|
|
4172
|
-
try {
|
|
4173
|
-
execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
4174
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
|
|
4175
|
-
cwd: projectRoot,
|
|
4176
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4177
|
-
});
|
|
4178
|
-
s.stop(`Changes for '${type}/${name}' committed to git.`);
|
|
4179
|
-
p15.log.success(pc15.green(`\u2713 Committed '${type}/${name}': "${message}"`));
|
|
4180
|
-
} catch (err) {
|
|
4181
|
-
s.stop(pc15.red("Git commit failed."));
|
|
4182
|
-
p15.log.error(pc15.red(err.message || String(err)));
|
|
4183
|
-
process.exit(1);
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
async function registerModuleChanges(typeArg, nameArg, flags = {}) {
|
|
4187
|
-
const projectRoot = findProjectRoot();
|
|
4188
|
-
const index = loadMasterIndex(projectRoot);
|
|
4189
|
-
let type = typeArg?.trim();
|
|
4190
|
-
if (!type) {
|
|
4191
|
-
type = handleCancel(
|
|
4192
|
-
await p15.select({
|
|
4193
|
-
message: "Module type to update:",
|
|
4194
|
-
options: [
|
|
4195
|
-
{ value: "feature", label: "Feature" },
|
|
4196
|
-
{ value: "shared", label: "Shared Module" }
|
|
4197
|
-
]
|
|
4198
|
-
})
|
|
4199
|
-
);
|
|
4200
|
-
}
|
|
4201
|
-
let name = nameArg?.trim();
|
|
4202
|
-
if (!name) {
|
|
4203
|
-
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4204
|
-
const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
|
|
4205
|
-
const opts = diskModules.map((m) => {
|
|
4206
|
-
const d = diffs.find((item) => item.name === m.name);
|
|
4207
|
-
return {
|
|
4208
|
-
value: m.name,
|
|
4209
|
-
label: m.name,
|
|
4210
|
-
hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
|
|
4211
|
-
};
|
|
4212
|
-
});
|
|
4213
|
-
name = handleCancel(
|
|
4214
|
-
await p15.select({
|
|
4215
|
-
message: `Select ${type} to register changes:`,
|
|
4216
|
-
options: opts
|
|
4217
|
-
})
|
|
4218
|
-
);
|
|
4219
|
-
}
|
|
4220
|
-
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4221
|
-
const modDir = path2.join(folder, name);
|
|
4222
|
-
if (!fs12.existsSync(modDir)) {
|
|
4223
|
-
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4224
|
-
process.exit(1);
|
|
4225
|
-
}
|
|
4226
|
-
const files = getAllFiles(modDir);
|
|
4227
|
-
const integrity = computeDirectoryHash(modDir, files);
|
|
4228
|
-
const manifestPath = path2.join(modDir, "registry.json");
|
|
4229
|
-
let manifest;
|
|
4230
|
-
if (fs12.existsSync(manifestPath)) {
|
|
4231
|
-
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4232
|
-
} else {
|
|
4233
|
-
manifest = {
|
|
4234
|
-
name,
|
|
4235
|
-
type,
|
|
4236
|
-
description: `${name} ${type} module`,
|
|
4237
|
-
version: "1.0.0",
|
|
4238
|
-
dependencies: { npm: [], shared: [], features: [] },
|
|
4239
|
-
files: []
|
|
4240
|
-
};
|
|
4241
|
-
}
|
|
4242
|
-
manifest.files = files;
|
|
4243
|
-
manifest.integrity = integrity;
|
|
4244
|
-
if (flags.version) {
|
|
4245
|
-
manifest.version = flags.version;
|
|
4246
|
-
}
|
|
4247
|
-
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4248
|
-
const targetList = type === "shared" ? index.shared : index.features;
|
|
4249
|
-
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4250
|
-
if (existingIdx >= 0) {
|
|
4251
|
-
targetList[existingIdx] = {
|
|
4252
|
-
name,
|
|
4253
|
-
description: manifest.description,
|
|
4254
|
-
version: manifest.version,
|
|
4255
|
-
integrity
|
|
4256
|
-
};
|
|
4257
|
-
} else {
|
|
4258
|
-
targetList.push({
|
|
4259
|
-
name,
|
|
4260
|
-
description: manifest.description,
|
|
4261
|
-
version: manifest.version,
|
|
4262
|
-
integrity
|
|
4263
|
-
});
|
|
4264
|
-
}
|
|
4265
|
-
saveMasterIndex(projectRoot, index);
|
|
4266
|
-
p15.log.success(
|
|
4267
|
-
pc15.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
|
|
4268
|
-
);
|
|
4269
|
-
}
|
|
4270
|
-
async function unregisterModule(typeArg, nameArg, flags = {}) {
|
|
4271
|
-
const projectRoot = findProjectRoot();
|
|
4272
|
-
const index = loadMasterIndex(projectRoot);
|
|
4273
|
-
let type = typeArg?.trim();
|
|
4274
|
-
if (!type) {
|
|
4275
|
-
type = handleCancel(
|
|
4276
|
-
await p15.select({
|
|
4277
|
-
message: "Module type to unregister:",
|
|
4278
|
-
options: [
|
|
4279
|
-
{ value: "feature", label: "Feature" },
|
|
4280
|
-
{ value: "shared", label: "Shared Module" }
|
|
4281
|
-
]
|
|
4282
|
-
})
|
|
4283
|
-
);
|
|
4284
|
-
}
|
|
4285
|
-
const registeredList = type === "shared" ? index.shared : index.features;
|
|
4286
|
-
if (registeredList.length === 0) {
|
|
4287
|
-
p15.log.warn(`No registered ${type} modules found in index.json.`);
|
|
4288
|
-
return;
|
|
4289
|
-
}
|
|
4290
|
-
let name = nameArg?.trim();
|
|
4291
|
-
if (!name) {
|
|
4292
|
-
name = handleCancel(
|
|
4293
|
-
await p15.select({
|
|
4294
|
-
message: `Select ${type} to unregister from index.json:`,
|
|
4295
|
-
options: registeredList.map((item) => ({
|
|
4296
|
-
value: item.name,
|
|
4297
|
-
label: item.name,
|
|
4298
|
-
hint: item.description
|
|
4299
|
-
}))
|
|
4300
|
-
})
|
|
4301
|
-
);
|
|
4302
|
-
}
|
|
4303
|
-
if (type === "shared") {
|
|
4304
|
-
index.shared = index.shared.filter((item) => item.name !== name);
|
|
4305
|
-
} else {
|
|
4306
|
-
index.features = index.features.filter((item) => item.name !== name);
|
|
4307
|
-
}
|
|
4308
|
-
saveMasterIndex(projectRoot, index);
|
|
4309
|
-
p15.log.success(
|
|
4310
|
-
pc15.green(
|
|
4311
|
-
`\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
|
|
4312
|
-
)
|
|
4313
|
-
);
|
|
4314
|
-
}
|
|
4315
|
-
function diffRegistry(flags = {}) {
|
|
4316
|
-
const projectRoot = findProjectRoot();
|
|
4317
|
-
const diffs = getRegistryDiff(projectRoot);
|
|
4318
|
-
p15.log.step(pc15.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
|
|
4319
|
-
const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
|
|
4320
|
-
const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
|
|
4321
|
-
const committedDrift = diffs.filter(
|
|
4322
|
-
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
|
|
4323
|
-
);
|
|
4324
|
-
const upToDate = diffs.filter(
|
|
4325
|
-
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
|
|
4326
|
-
);
|
|
4327
|
-
const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
|
|
4328
|
-
if (unregistered.length > 0) {
|
|
4329
|
-
p15.log.message(pc15.yellow(`
|
|
4330
|
-
\u{1F4E6} Unregistered Modules (${unregistered.length}):`));
|
|
4331
|
-
for (const mod of unregistered) {
|
|
4332
|
-
p15.log.message(` ${pc15.red("\u25CF")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(not registered in index.json)")}`);
|
|
4333
|
-
}
|
|
4334
|
-
}
|
|
4335
|
-
if (uncommitted.length > 0) {
|
|
4336
|
-
p15.log.message(pc15.yellow(`
|
|
4337
|
-
\u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
|
|
4338
|
-
for (const mod of uncommitted) {
|
|
4339
|
-
p15.log.message(
|
|
4340
|
-
` ${pc15.yellow("\u25B2")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
|
|
4341
|
-
);
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
if (committedDrift.length > 0) {
|
|
4345
|
-
p15.log.message(pc15.cyan(`
|
|
4346
|
-
\u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
|
|
4347
|
-
for (const mod of committedDrift) {
|
|
4348
|
-
p15.log.message(
|
|
4349
|
-
` ${pc15.cyan("\u25C6")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(
|
|
4350
|
-
`local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
|
|
4351
|
-
)}`
|
|
4352
|
-
);
|
|
4353
|
-
}
|
|
4354
|
-
}
|
|
4355
|
-
if (missing.length > 0) {
|
|
4356
|
-
p15.log.message(pc15.red(`
|
|
4357
|
-
\u26A0\uFE0F Missing on Disk (${missing.length}):`));
|
|
4358
|
-
for (const mod of missing) {
|
|
4359
|
-
p15.log.message(` ${pc15.red("\u2715")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(in index.json, but folder missing)")}`);
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
if (upToDate.length > 0) {
|
|
4363
|
-
p15.log.message(pc15.green(`
|
|
4364
|
-
\u2713 Up to Date & Registered (${upToDate.length}):`));
|
|
4365
|
-
for (const mod of upToDate) {
|
|
4366
|
-
p15.log.message(` ${pc15.green("\u2714")} ${mod.type}/${mod.name} ${pc15.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
p15.log.message("");
|
|
4370
|
-
}
|
|
4371
|
-
async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
|
|
4372
|
-
let action = actionArg?.trim().toLowerCase();
|
|
4373
|
-
if (!action) {
|
|
4374
|
-
action = handleCancel(
|
|
4375
|
-
await p15.select({
|
|
4376
|
-
message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
|
|
4377
|
-
options: [
|
|
4378
|
-
{
|
|
4379
|
-
value: "diff",
|
|
4380
|
-
label: "Inspect Diff & Status",
|
|
4381
|
-
hint: "Summary of unregistered, uncommitted, and drifted modules"
|
|
4382
|
-
},
|
|
4383
|
-
{
|
|
4384
|
-
value: "register",
|
|
4385
|
-
label: "Register Module",
|
|
4386
|
-
hint: "Add an existing module to index.json with integrity checksum"
|
|
4387
|
-
},
|
|
4388
|
-
{
|
|
4389
|
-
value: "commit",
|
|
4390
|
-
label: "Commit Changes",
|
|
4391
|
-
hint: "Commit a module's working changes to git in the background"
|
|
4392
|
-
},
|
|
4393
|
-
{
|
|
4394
|
-
value: "register-changes",
|
|
4395
|
-
label: "Register Changes",
|
|
4396
|
-
hint: "Update index.json & registry.json with latest checksums"
|
|
4397
|
-
},
|
|
4398
|
-
{
|
|
4399
|
-
value: "unregister",
|
|
4400
|
-
label: "Unregister Module",
|
|
4401
|
-
hint: "Remove module from index.json (source code stays on disk)"
|
|
4402
|
-
}
|
|
4403
|
-
]
|
|
4404
|
-
})
|
|
4405
|
-
);
|
|
4406
|
-
}
|
|
4407
|
-
switch (action) {
|
|
4408
|
-
case "diff":
|
|
4409
|
-
case "status":
|
|
4410
|
-
diffRegistry(flags);
|
|
4411
|
-
break;
|
|
4412
|
-
case "register":
|
|
4413
|
-
await registerModule(typeArg, nameArg, flags);
|
|
4414
|
-
break;
|
|
4415
|
-
case "commit":
|
|
4416
|
-
await commitModule(typeArg, nameArg, flags);
|
|
4417
|
-
break;
|
|
4418
|
-
case "register-changes":
|
|
4419
|
-
case "update":
|
|
4420
|
-
await registerModuleChanges(typeArg, nameArg, flags);
|
|
4421
|
-
break;
|
|
4422
|
-
case "unregister":
|
|
4423
|
-
await unregisterModule(typeArg, nameArg, flags);
|
|
4424
|
-
break;
|
|
4425
|
-
default:
|
|
4426
|
-
p15.log.error(
|
|
4427
|
-
pc15.red(
|
|
4428
|
-
`Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
|
|
4429
|
-
)
|
|
4430
|
-
);
|
|
4431
|
-
process.exit(1);
|
|
4432
|
-
}
|
|
4433
|
-
}
|
|
4434
3905
|
|
|
4435
3906
|
// src/cli.ts
|
|
4436
3907
|
function createCli() {
|
|
4437
3908
|
const cli = cac("mason");
|
|
4438
3909
|
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) => {
|
|
4439
3910
|
if (args && args.length > 0) {
|
|
4440
|
-
|
|
4441
|
-
|
|
3911
|
+
p4.log.error(
|
|
3912
|
+
pc14.red(
|
|
4442
3913
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
4443
3914
|
)
|
|
4444
3915
|
);
|
|
@@ -4449,14 +3920,9 @@ function createCli() {
|
|
|
4449
3920
|
const mode = getEffectiveMode(projectRoot, flags);
|
|
4450
3921
|
if (mode === "registry") {
|
|
4451
3922
|
const choice2 = handleCancel(
|
|
4452
|
-
await
|
|
4453
|
-
message: "\u{1F3DB}\uFE0F Mason Registry
|
|
3923
|
+
await p4.select({
|
|
3924
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
4454
3925
|
options: [
|
|
4455
|
-
{
|
|
4456
|
-
value: "diff",
|
|
4457
|
-
label: "Inspect Diff & Status",
|
|
4458
|
-
hint: "Overview of unregistered, uncommitted, or drifted modules"
|
|
4459
|
-
},
|
|
4460
3926
|
{
|
|
4461
3927
|
value: "create_feature",
|
|
4462
3928
|
label: "Initialize New Feature",
|
|
@@ -4472,26 +3938,6 @@ function createCli() {
|
|
|
4472
3938
|
label: "Create New Use Case",
|
|
4473
3939
|
hint: "Add a new use case to an existing feature"
|
|
4474
3940
|
},
|
|
4475
|
-
{
|
|
4476
|
-
value: "register",
|
|
4477
|
-
label: "Register Module",
|
|
4478
|
-
hint: "Add a module to index.json with integrity checksum"
|
|
4479
|
-
},
|
|
4480
|
-
{
|
|
4481
|
-
value: "commit",
|
|
4482
|
-
label: "Commit Module Changes",
|
|
4483
|
-
hint: "Commit working changes in a module to git"
|
|
4484
|
-
},
|
|
4485
|
-
{
|
|
4486
|
-
value: "register_changes",
|
|
4487
|
-
label: "Register Changes",
|
|
4488
|
-
hint: "Recompute checksums and update index.json"
|
|
4489
|
-
},
|
|
4490
|
-
{
|
|
4491
|
-
value: "unregister",
|
|
4492
|
-
label: "Unregister Module",
|
|
4493
|
-
hint: "Remove from index.json but keep source on disk"
|
|
4494
|
-
},
|
|
4495
3941
|
{
|
|
4496
3942
|
value: "import",
|
|
4497
3943
|
label: "Import from Remote Registry",
|
|
@@ -4501,9 +3947,6 @@ function createCli() {
|
|
|
4501
3947
|
})
|
|
4502
3948
|
);
|
|
4503
3949
|
switch (choice2) {
|
|
4504
|
-
case "diff":
|
|
4505
|
-
await handleRegistry("diff", void 0, void 0, flags);
|
|
4506
|
-
break;
|
|
4507
3950
|
case "create_feature":
|
|
4508
3951
|
await handleCreate("feature", void 0, flags);
|
|
4509
3952
|
break;
|
|
@@ -4513,18 +3956,6 @@ function createCli() {
|
|
|
4513
3956
|
case "create_usecase":
|
|
4514
3957
|
await handleCreate("usecase", void 0, flags);
|
|
4515
3958
|
break;
|
|
4516
|
-
case "register":
|
|
4517
|
-
await handleRegistry("register", void 0, void 0, flags);
|
|
4518
|
-
break;
|
|
4519
|
-
case "commit":
|
|
4520
|
-
await handleRegistry("commit", void 0, void 0, flags);
|
|
4521
|
-
break;
|
|
4522
|
-
case "register_changes":
|
|
4523
|
-
await handleRegistry("register-changes", void 0, void 0, flags);
|
|
4524
|
-
break;
|
|
4525
|
-
case "unregister":
|
|
4526
|
-
await handleRegistry("unregister", void 0, void 0, flags);
|
|
4527
|
-
break;
|
|
4528
3959
|
case "import":
|
|
4529
3960
|
await handleImport(void 0, void 0, flags);
|
|
4530
3961
|
break;
|
|
@@ -4532,31 +3963,24 @@ function createCli() {
|
|
|
4532
3963
|
return;
|
|
4533
3964
|
}
|
|
4534
3965
|
const choice = handleCancel(
|
|
4535
|
-
await
|
|
3966
|
+
await p4.select({
|
|
4536
3967
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
4537
3968
|
options: [
|
|
4538
3969
|
{
|
|
4539
3970
|
value: "create",
|
|
4540
|
-
label: "Create
|
|
3971
|
+
label: "Create component",
|
|
4541
3972
|
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
4542
3973
|
},
|
|
4543
3974
|
{
|
|
4544
3975
|
value: "import",
|
|
4545
3976
|
label: "Import from registry",
|
|
4546
3977
|
hint: "Download and link pre-built shared modules or features from the registry"
|
|
4547
|
-
},
|
|
4548
|
-
{
|
|
4549
|
-
value: "registry",
|
|
4550
|
-
label: "Registry management",
|
|
4551
|
-
hint: "Inspect diff, register modules, or commit module changes"
|
|
4552
3978
|
}
|
|
4553
3979
|
]
|
|
4554
3980
|
})
|
|
4555
3981
|
);
|
|
4556
3982
|
if (choice === "create") {
|
|
4557
3983
|
await handleCreate(void 0, void 0, flags);
|
|
4558
|
-
} else if (choice === "registry") {
|
|
4559
|
-
await handleRegistry(void 0, void 0, void 0, flags);
|
|
4560
3984
|
} else {
|
|
4561
3985
|
await handleImport(void 0, void 0, flags);
|
|
4562
3986
|
}
|
|
@@ -4584,25 +4008,6 @@ function createCli() {
|
|
|
4584
4008
|
handleCliError(err);
|
|
4585
4009
|
}
|
|
4586
4010
|
});
|
|
4587
|
-
cli.command(
|
|
4588
|
-
"registry [action] [type] [name]",
|
|
4589
|
-
"Registry & version management (diff, register, commit, register-changes, unregister)"
|
|
4590
|
-
).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(
|
|
4591
|
-
async (action, type, name, flags = {}) => {
|
|
4592
|
-
try {
|
|
4593
|
-
await handleRegistry(action, type, name, flags);
|
|
4594
|
-
} catch (err) {
|
|
4595
|
-
handleCliError(err);
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4598
|
-
);
|
|
4599
|
-
cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
|
|
4600
|
-
try {
|
|
4601
|
-
await handleRegistry("diff", void 0, void 0, flags);
|
|
4602
|
-
} catch (err) {
|
|
4603
|
-
handleCliError(err);
|
|
4604
|
-
}
|
|
4605
|
-
});
|
|
4606
4011
|
const createSubtypes = [
|
|
4607
4012
|
"feature",
|
|
4608
4013
|
"shared",
|
|
@@ -4626,7 +4031,7 @@ function createCli() {
|
|
|
4626
4031
|
});
|
|
4627
4032
|
}
|
|
4628
4033
|
cli.help();
|
|
4629
|
-
cli.version("1.0.
|
|
4034
|
+
cli.version("1.0.14");
|
|
4630
4035
|
return cli;
|
|
4631
4036
|
}
|
|
4632
4037
|
function handleCliError(err) {
|
|
@@ -4634,7 +4039,7 @@ function handleCliError(err) {
|
|
|
4634
4039
|
process.exit(0);
|
|
4635
4040
|
}
|
|
4636
4041
|
const message = err instanceof Error ? err.message : String(err);
|
|
4637
|
-
|
|
4042
|
+
p4.log.error(pc14.red(`Error: ${message}`));
|
|
4638
4043
|
process.exit(1);
|
|
4639
4044
|
}
|
|
4640
4045
|
async function runCli(argv = process.argv) {
|
|
@@ -4642,6 +4047,6 @@ async function runCli(argv = process.argv) {
|
|
|
4642
4047
|
cli.parse(argv, { run: true });
|
|
4643
4048
|
}
|
|
4644
4049
|
|
|
4645
|
-
export { DEFAULT_REGISTRY_BASE, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport,
|
|
4050
|
+
export { DEFAULT_REGISTRY_BASE, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, resolveSharedDir, runCli, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
4646
4051
|
//# sourceMappingURL=index.js.map
|
|
4647
4052
|
//# sourceMappingURL=index.js.map
|