@solid-stack/mason 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +750 -217
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +747 -214
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/feature.d.ts.map +1 -1
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/core/sharedGenerator.d.ts +13 -0
- package/dist/core/sharedGenerator.d.ts.map +1 -0
- package/dist/index.cjs +770 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +758 -215
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +2 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs4 from 'fs';
|
|
2
|
+
import path2 from 'path';
|
|
3
3
|
import * as p4 from '@clack/prompts';
|
|
4
|
-
import
|
|
4
|
+
import pc14 from 'picocolors';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
7
8
|
import Handlebars from 'handlebars';
|
|
8
9
|
import { cac } from 'cac';
|
|
9
10
|
|
|
@@ -75,8 +76,8 @@ function isValidIdentifier(str) {
|
|
|
75
76
|
}
|
|
76
77
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
77
78
|
const projectRoot = options.projectRoot || process.cwd();
|
|
78
|
-
const relPath =
|
|
79
|
-
const existedBefore =
|
|
79
|
+
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
80
|
+
const existedBefore = fs4.existsSync(absolutePath);
|
|
80
81
|
if (options.dryRun) {
|
|
81
82
|
return {
|
|
82
83
|
relativePath: relPath,
|
|
@@ -95,11 +96,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
95
96
|
existedBefore: true
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
|
-
const dir =
|
|
99
|
-
if (!
|
|
100
|
-
|
|
99
|
+
const dir = path2.dirname(absolutePath);
|
|
100
|
+
if (!fs4.existsSync(dir)) {
|
|
101
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
101
102
|
}
|
|
102
|
-
|
|
103
|
+
fs4.writeFileSync(absolutePath, content, "utf-8");
|
|
103
104
|
return {
|
|
104
105
|
relativePath: relPath,
|
|
105
106
|
absolutePath,
|
|
@@ -111,41 +112,114 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
111
112
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
112
113
|
console.log("");
|
|
113
114
|
if (isDryRun) {
|
|
114
|
-
p4.log.warn(
|
|
115
|
+
p4.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
115
116
|
}
|
|
116
117
|
for (const f of files) {
|
|
117
118
|
if (f.action === "created") {
|
|
118
|
-
p4.log.success(`${
|
|
119
|
+
p4.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
|
|
119
120
|
} else if (f.action === "overwritten") {
|
|
120
|
-
p4.log.warn(`${
|
|
121
|
+
p4.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
|
|
121
122
|
} else if (f.action === "skipped") {
|
|
122
|
-
p4.log.info(`${
|
|
123
|
+
p4.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
123
124
|
} else {
|
|
124
|
-
p4.log.info(`${
|
|
125
|
+
p4.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
function loadMasonConfig(startDir = process.cwd()) {
|
|
130
|
+
let current = path2.resolve(startDir);
|
|
131
|
+
while (true) {
|
|
132
|
+
const configPath = path2.join(current, "mason.config.json");
|
|
133
|
+
if (fs4.existsSync(configPath)) {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const parent = path2.dirname(current);
|
|
141
|
+
if (parent === current) break;
|
|
142
|
+
current = parent;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function getEffectiveMode(projectRoot, flags) {
|
|
147
|
+
if (flags?.registry) return "registry";
|
|
148
|
+
const config = loadMasonConfig(projectRoot);
|
|
149
|
+
if (config?.mode) return config.mode;
|
|
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
|
+
return "registry";
|
|
152
|
+
}
|
|
153
|
+
return "app";
|
|
154
|
+
}
|
|
155
|
+
function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
156
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
157
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
158
|
+
const config = loadMasonConfig(projectRoot);
|
|
159
|
+
if (config?.paths?.features) {
|
|
160
|
+
return path2.resolve(projectRoot, config.paths.features);
|
|
161
|
+
}
|
|
162
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
163
|
+
if (mode === "registry") {
|
|
164
|
+
return path2.join(projectRoot, "features");
|
|
165
|
+
}
|
|
166
|
+
if (fs4.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
167
|
+
return path2.join(projectRoot, "src/features");
|
|
168
|
+
}
|
|
169
|
+
return path2.join(projectRoot, "features");
|
|
170
|
+
}
|
|
171
|
+
function getConfiguredSharedDir(projectRoot, flags) {
|
|
172
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
173
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
174
|
+
const config = loadMasonConfig(projectRoot);
|
|
175
|
+
if (config?.paths?.shared) {
|
|
176
|
+
return path2.resolve(projectRoot, config.paths.shared);
|
|
177
|
+
}
|
|
178
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
179
|
+
if (mode === "registry") {
|
|
180
|
+
return path2.join(projectRoot, "shared");
|
|
181
|
+
}
|
|
182
|
+
if (fs4.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
183
|
+
return path2.join(projectRoot, "src/shared");
|
|
184
|
+
}
|
|
185
|
+
return path2.join(projectRoot, "shared");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/utils/project.ts
|
|
128
189
|
function findProjectRoot(startDir = process.cwd()) {
|
|
129
|
-
let current =
|
|
190
|
+
let current = path2.resolve(startDir);
|
|
130
191
|
while (true) {
|
|
131
|
-
if (
|
|
192
|
+
if (fs4.existsSync(path2.join(current, "package.json"))) {
|
|
132
193
|
return current;
|
|
133
194
|
}
|
|
134
|
-
const parent =
|
|
195
|
+
const parent = path2.dirname(current);
|
|
135
196
|
if (parent === current) {
|
|
136
|
-
return
|
|
197
|
+
return path2.resolve(startDir);
|
|
137
198
|
}
|
|
138
199
|
current = parent;
|
|
139
200
|
}
|
|
140
201
|
}
|
|
141
|
-
function resolveFeaturesDir(projectRoot) {
|
|
142
|
-
return
|
|
202
|
+
function resolveFeaturesDir(projectRoot, flags) {
|
|
203
|
+
return getConfiguredFeaturesDir(projectRoot, flags);
|
|
204
|
+
}
|
|
205
|
+
function resolveSharedDir(projectRoot, flags) {
|
|
206
|
+
return getConfiguredSharedDir(projectRoot, flags);
|
|
207
|
+
}
|
|
208
|
+
function listExistingShared(projectRoot) {
|
|
209
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
210
|
+
if (!fs4.existsSync(sharedDir)) return [];
|
|
211
|
+
try {
|
|
212
|
+
const entries = fs4.readdirSync(sharedDir, { withFileTypes: true });
|
|
213
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
214
|
+
} catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
143
217
|
}
|
|
144
218
|
function listExistingFeatures(projectRoot) {
|
|
145
219
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
146
|
-
if (!
|
|
220
|
+
if (!fs4.existsSync(featuresDir)) return [];
|
|
147
221
|
try {
|
|
148
|
-
const entries =
|
|
222
|
+
const entries = fs4.readdirSync(featuresDir, { withFileTypes: true });
|
|
149
223
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
150
224
|
} catch {
|
|
151
225
|
return [];
|
|
@@ -155,14 +229,14 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
155
229
|
const results = [];
|
|
156
230
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
157
231
|
for (const feat of features) {
|
|
158
|
-
const domainDir =
|
|
159
|
-
if (!
|
|
232
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
233
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
160
234
|
try {
|
|
161
|
-
const files =
|
|
235
|
+
const files = fs4.readdirSync(domainDir);
|
|
162
236
|
for (const file of files) {
|
|
163
237
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
164
238
|
const name = file.replace(/\.ts$/, "");
|
|
165
|
-
const fullPath =
|
|
239
|
+
const fullPath = path2.join(domainDir, file);
|
|
166
240
|
if (/^I[A-Z]/.test(name)) {
|
|
167
241
|
results.push({
|
|
168
242
|
name,
|
|
@@ -171,7 +245,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
171
245
|
});
|
|
172
246
|
} else {
|
|
173
247
|
try {
|
|
174
|
-
const content =
|
|
248
|
+
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
175
249
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
176
250
|
results.push({
|
|
177
251
|
name,
|
|
@@ -193,17 +267,17 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
193
267
|
const results = [];
|
|
194
268
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
195
269
|
for (const feat of features) {
|
|
196
|
-
const domainDir =
|
|
197
|
-
if (!
|
|
270
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
271
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
198
272
|
try {
|
|
199
|
-
const files =
|
|
273
|
+
const files = fs4.readdirSync(domainDir);
|
|
200
274
|
for (const file of files) {
|
|
201
275
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
202
276
|
const name = file.replace(/\.ts$/, "");
|
|
203
277
|
if (!/^I[A-Z]/.test(name)) {
|
|
204
278
|
results.push({
|
|
205
279
|
name,
|
|
206
|
-
fullPath:
|
|
280
|
+
fullPath: path2.join(domainDir, file),
|
|
207
281
|
feature: feat
|
|
208
282
|
});
|
|
209
283
|
}
|
|
@@ -218,16 +292,16 @@ function listUseCases(projectRoot, featureName) {
|
|
|
218
292
|
const results = [];
|
|
219
293
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
220
294
|
for (const feat of features) {
|
|
221
|
-
const useCasesDir =
|
|
222
|
-
if (!
|
|
295
|
+
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
296
|
+
if (!fs4.existsSync(useCasesDir)) continue;
|
|
223
297
|
try {
|
|
224
|
-
const files =
|
|
298
|
+
const files = fs4.readdirSync(useCasesDir);
|
|
225
299
|
for (const file of files) {
|
|
226
300
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
227
301
|
const name = file.replace(/\.ts$/, "");
|
|
228
302
|
results.push({
|
|
229
303
|
name,
|
|
230
|
-
fullPath:
|
|
304
|
+
fullPath: path2.join(useCasesDir, file),
|
|
231
305
|
feature: feat
|
|
232
306
|
});
|
|
233
307
|
}
|
|
@@ -252,13 +326,13 @@ function handleCancel(result) {
|
|
|
252
326
|
}
|
|
253
327
|
function printBanner() {
|
|
254
328
|
console.log("");
|
|
255
|
-
p4.intro(
|
|
329
|
+
p4.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
|
|
256
330
|
}
|
|
257
331
|
async function promptProperties(options = {}) {
|
|
258
332
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
259
333
|
if (properties.length > 0) {
|
|
260
|
-
const summary = properties.map((
|
|
261
|
-
p4.log.info(
|
|
334
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
335
|
+
p4.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
|
|
262
336
|
const addMore = handleCancel(
|
|
263
337
|
await p4.confirm({
|
|
264
338
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -269,7 +343,7 @@ async function promptProperties(options = {}) {
|
|
|
269
343
|
return properties;
|
|
270
344
|
}
|
|
271
345
|
}
|
|
272
|
-
p4.log.info(
|
|
346
|
+
p4.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
|
|
273
347
|
while (true) {
|
|
274
348
|
const name = handleCancel(
|
|
275
349
|
await p4.text({
|
|
@@ -317,7 +391,7 @@ async function promptProperties(options = {}) {
|
|
|
317
391
|
optional
|
|
318
392
|
});
|
|
319
393
|
p4.log.success(
|
|
320
|
-
|
|
394
|
+
pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
321
395
|
);
|
|
322
396
|
}
|
|
323
397
|
return properties;
|
|
@@ -339,25 +413,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
|
339
413
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
340
414
|
}
|
|
341
415
|
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
342
|
-
return
|
|
416
|
+
return path2.resolve(baseDir, trimmed);
|
|
343
417
|
}
|
|
344
418
|
return trimmed;
|
|
345
419
|
}
|
|
346
420
|
function findLocalRegistry(startDir = process.cwd()) {
|
|
347
|
-
let current =
|
|
421
|
+
let current = path2.resolve(startDir);
|
|
348
422
|
while (true) {
|
|
349
|
-
const candidate =
|
|
350
|
-
if (
|
|
423
|
+
const candidate = path2.join(current, "mason-registry");
|
|
424
|
+
if (fs4.existsSync(candidate) && fs4.existsSync(path2.join(candidate, "index.json"))) {
|
|
351
425
|
return candidate;
|
|
352
426
|
}
|
|
353
|
-
const parent =
|
|
427
|
+
const parent = path2.dirname(current);
|
|
354
428
|
if (parent === current) break;
|
|
355
429
|
current = parent;
|
|
356
430
|
}
|
|
357
431
|
return null;
|
|
358
432
|
}
|
|
359
433
|
function getRegistryBase(projectRoot, override) {
|
|
360
|
-
const rootDir = projectRoot ?
|
|
434
|
+
const rootDir = projectRoot ? path2.resolve(projectRoot) : process.cwd();
|
|
361
435
|
if (override && override.trim()) {
|
|
362
436
|
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
363
437
|
}
|
|
@@ -365,30 +439,30 @@ function getRegistryBase(projectRoot, override) {
|
|
|
365
439
|
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
366
440
|
}
|
|
367
441
|
const configLocations = [
|
|
368
|
-
|
|
369
|
-
|
|
442
|
+
path2.join(rootDir, "mason.config.json"),
|
|
443
|
+
path2.join(process.cwd(), "mason.config.json")
|
|
370
444
|
];
|
|
371
445
|
for (const configPath of configLocations) {
|
|
372
|
-
if (
|
|
446
|
+
if (fs4.existsSync(configPath)) {
|
|
373
447
|
try {
|
|
374
|
-
const config = JSON.parse(
|
|
448
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
375
449
|
if (config.registry && typeof config.registry === "string") {
|
|
376
|
-
return normalizeRegistryUrl(config.registry.trim(),
|
|
450
|
+
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
377
451
|
}
|
|
378
452
|
} catch {
|
|
379
453
|
}
|
|
380
454
|
}
|
|
381
455
|
}
|
|
382
456
|
const pkgLocations = [
|
|
383
|
-
|
|
384
|
-
|
|
457
|
+
path2.join(rootDir, "package.json"),
|
|
458
|
+
path2.join(process.cwd(), "package.json")
|
|
385
459
|
];
|
|
386
460
|
for (const pkgPath of pkgLocations) {
|
|
387
|
-
if (
|
|
461
|
+
if (fs4.existsSync(pkgPath)) {
|
|
388
462
|
try {
|
|
389
|
-
const pkg = JSON.parse(
|
|
463
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
390
464
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
391
|
-
return normalizeRegistryUrl(pkg.mason.registry.trim(),
|
|
465
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
392
466
|
}
|
|
393
467
|
} catch {
|
|
394
468
|
}
|
|
@@ -429,11 +503,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
429
503
|
if (baseDir.startsWith("file://")) {
|
|
430
504
|
baseDir = fileURLToPath(baseDir);
|
|
431
505
|
}
|
|
432
|
-
const filePath =
|
|
433
|
-
if (!
|
|
506
|
+
const filePath = path2.resolve(baseDir, cleanRelPath);
|
|
507
|
+
if (!fs4.existsSync(filePath)) {
|
|
434
508
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
435
509
|
}
|
|
436
|
-
return
|
|
510
|
+
return fs4.promises.readFile(filePath, "utf-8");
|
|
437
511
|
}
|
|
438
512
|
async function fetchRegistryIndex(registryBase) {
|
|
439
513
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -495,26 +569,26 @@ function findClosestMatch(target, candidates) {
|
|
|
495
569
|
return null;
|
|
496
570
|
}
|
|
497
571
|
function detectPackageManager(projectRoot) {
|
|
498
|
-
if (
|
|
499
|
-
if (
|
|
500
|
-
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";
|
|
501
575
|
return "npm";
|
|
502
576
|
}
|
|
503
577
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
504
578
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
505
|
-
const pkgJsonPath =
|
|
506
|
-
if (!
|
|
579
|
+
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
580
|
+
if (!fs4.existsSync(pkgJsonPath)) {
|
|
507
581
|
return requiredPackages;
|
|
508
582
|
}
|
|
509
583
|
try {
|
|
510
|
-
const pkg = JSON.parse(
|
|
584
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
|
|
511
585
|
const installed = /* @__PURE__ */ new Set([
|
|
512
586
|
...Object.keys(pkg.dependencies || {}),
|
|
513
587
|
...Object.keys(pkg.devDependencies || {}),
|
|
514
588
|
...Object.keys(pkg.peerDependencies || {})
|
|
515
589
|
]);
|
|
516
|
-
return requiredPackages.filter((
|
|
517
|
-
const nameOnly =
|
|
590
|
+
return requiredPackages.filter((p16) => {
|
|
591
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
518
592
|
return !installed.has(nameOnly);
|
|
519
593
|
});
|
|
520
594
|
} catch {
|
|
@@ -539,12 +613,47 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
539
613
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
540
614
|
break;
|
|
541
615
|
}
|
|
542
|
-
p4.log.info(`Installing missing dependencies with ${manager}: ${
|
|
616
|
+
p4.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
|
|
543
617
|
execSync(installCmd, {
|
|
544
618
|
cwd: projectRoot,
|
|
545
619
|
stdio: "inherit"
|
|
546
620
|
});
|
|
547
621
|
}
|
|
622
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
623
|
+
if (!fs4.existsSync(dir)) return [];
|
|
624
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
625
|
+
const files = [];
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
const fullPath = path2.join(dir, entry.name);
|
|
628
|
+
const relPath = path2.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
629
|
+
if (entry.isDirectory()) {
|
|
630
|
+
if (entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
634
|
+
} else if (entry.isFile()) {
|
|
635
|
+
if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
files.push(relPath);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return files.sort();
|
|
642
|
+
}
|
|
643
|
+
function computeDirectoryHash(dirPath, files) {
|
|
644
|
+
if (!fs4.existsSync(dirPath)) return "";
|
|
645
|
+
const hash = createHash("sha256");
|
|
646
|
+
const fileList = (files ?? getAllFiles(dirPath)).sort();
|
|
647
|
+
for (const relativePath of fileList) {
|
|
648
|
+
const absolutePath = path2.join(dirPath, relativePath);
|
|
649
|
+
if (!fs4.existsSync(absolutePath)) continue;
|
|
650
|
+
const content = fs4.readFileSync(absolutePath, "utf8");
|
|
651
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
652
|
+
hash.update(relativePath);
|
|
653
|
+
hash.update(normalizedContent);
|
|
654
|
+
}
|
|
655
|
+
return `sha256-${hash.digest("hex")}`;
|
|
656
|
+
}
|
|
548
657
|
var hbs = Handlebars.create();
|
|
549
658
|
hbs.registerHelper("eq", (a, b) => a === b);
|
|
550
659
|
hbs.registerHelper("ne", (a, b) => a !== b);
|
|
@@ -561,33 +670,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
561
670
|
});
|
|
562
671
|
var cachedTemplatesDir = null;
|
|
563
672
|
function getTemplatesDir() {
|
|
564
|
-
if (cachedTemplatesDir &&
|
|
673
|
+
if (cachedTemplatesDir && fs4.existsSync(cachedTemplatesDir)) {
|
|
565
674
|
return cachedTemplatesDir;
|
|
566
675
|
}
|
|
567
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
568
|
-
const cand1 =
|
|
569
|
-
if (
|
|
676
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
677
|
+
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
678
|
+
if (fs4.existsSync(cand1)) {
|
|
570
679
|
cachedTemplatesDir = cand1;
|
|
571
680
|
return cand1;
|
|
572
681
|
}
|
|
573
|
-
const cand2 =
|
|
574
|
-
if (
|
|
682
|
+
const cand2 = path2.resolve(currentDir, "../templates");
|
|
683
|
+
if (fs4.existsSync(cand2)) {
|
|
575
684
|
cachedTemplatesDir = cand2;
|
|
576
685
|
return cand2;
|
|
577
686
|
}
|
|
578
|
-
const cand3 =
|
|
579
|
-
if (
|
|
687
|
+
const cand3 = path2.resolve(currentDir, "templates");
|
|
688
|
+
if (fs4.existsSync(cand3)) {
|
|
580
689
|
cachedTemplatesDir = cand3;
|
|
581
690
|
return cand3;
|
|
582
691
|
}
|
|
583
692
|
let curr = currentDir;
|
|
584
693
|
while (true) {
|
|
585
|
-
const candTemplates =
|
|
586
|
-
if (
|
|
694
|
+
const candTemplates = path2.join(curr, "templates");
|
|
695
|
+
if (fs4.existsSync(candTemplates)) {
|
|
587
696
|
cachedTemplatesDir = candTemplates;
|
|
588
697
|
return candTemplates;
|
|
589
698
|
}
|
|
590
|
-
const parent =
|
|
699
|
+
const parent = path2.dirname(curr);
|
|
591
700
|
if (parent === curr) break;
|
|
592
701
|
curr = parent;
|
|
593
702
|
}
|
|
@@ -595,8 +704,8 @@ function getTemplatesDir() {
|
|
|
595
704
|
}
|
|
596
705
|
var templateCache = /* @__PURE__ */ new Map();
|
|
597
706
|
function loadTemplate(relativePath) {
|
|
598
|
-
const fullPath =
|
|
599
|
-
return
|
|
707
|
+
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
708
|
+
return fs4.readFileSync(fullPath, "utf-8");
|
|
600
709
|
}
|
|
601
710
|
function renderTemplateFile(relativePath, context) {
|
|
602
711
|
let compiled = templateCache.get(relativePath);
|
|
@@ -681,8 +790,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
681
790
|
function generateDomain(options) {
|
|
682
791
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
683
792
|
const entityName = toPascalCase(options.name);
|
|
684
|
-
const targetDir = options.targetDir ||
|
|
685
|
-
const filePath =
|
|
793
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
794
|
+
const filePath = path2.join(targetDir, `${entityName}.ts`);
|
|
686
795
|
const code = generateDomainEntityCode(
|
|
687
796
|
entityName,
|
|
688
797
|
options.kind || "interface",
|
|
@@ -706,7 +815,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
706
815
|
const idKey = `${entityKey}Id`;
|
|
707
816
|
const listKey = `${entityKey}s`;
|
|
708
817
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
709
|
-
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("; ") : "";
|
|
710
819
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
711
820
|
return {
|
|
712
821
|
name: m.name,
|
|
@@ -731,8 +840,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
731
840
|
function generateInterface(options) {
|
|
732
841
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
733
842
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
734
|
-
const targetDir = options.targetDir ||
|
|
735
|
-
const filePath =
|
|
843
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
844
|
+
const filePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
736
845
|
const code = generateInterfaceCode(
|
|
737
846
|
ifaceName,
|
|
738
847
|
options.kind || "abstractClass",
|
|
@@ -967,7 +1076,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
967
1076
|
};
|
|
968
1077
|
}
|
|
969
1078
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
970
|
-
if (customPath &&
|
|
1079
|
+
if (customPath && fs4.existsSync(customPath)) {
|
|
971
1080
|
return { filePath: customPath, ifaceName };
|
|
972
1081
|
}
|
|
973
1082
|
const candidateNames = [
|
|
@@ -977,22 +1086,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
977
1086
|
];
|
|
978
1087
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
979
1088
|
for (const feat of features) {
|
|
980
|
-
const domainDir =
|
|
981
|
-
if (!
|
|
1089
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1090
|
+
if (!fs4.existsSync(domainDir)) continue;
|
|
982
1091
|
for (const cand of candidateNames) {
|
|
983
|
-
const p1 =
|
|
984
|
-
if (
|
|
1092
|
+
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
1093
|
+
if (fs4.existsSync(p1)) {
|
|
985
1094
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
986
1095
|
}
|
|
987
1096
|
}
|
|
988
1097
|
}
|
|
989
1098
|
for (const cand of candidateNames) {
|
|
990
|
-
const p1 =
|
|
991
|
-
if (
|
|
992
|
-
const p22 =
|
|
993
|
-
if (
|
|
994
|
-
const p32 =
|
|
995
|
-
if (
|
|
1099
|
+
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
1100
|
+
if (fs4.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
1101
|
+
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
1102
|
+
if (fs4.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
1103
|
+
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
1104
|
+
if (fs4.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
996
1105
|
}
|
|
997
1106
|
return null;
|
|
998
1107
|
}
|
|
@@ -1015,7 +1124,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
1015
1124
|
while (currentFile && !visited.has(currentFile)) {
|
|
1016
1125
|
visited.add(currentFile);
|
|
1017
1126
|
try {
|
|
1018
|
-
const content =
|
|
1127
|
+
const content = fs4.readFileSync(currentFile, "utf-8");
|
|
1019
1128
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
1020
1129
|
for (const m of parsed.methods) {
|
|
1021
1130
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1091,7 +1200,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
1091
1200
|
const entitiesVar = `${entityVar}s`;
|
|
1092
1201
|
const idKey = `${entityVar}Id`;
|
|
1093
1202
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
1094
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
1203
|
+
const entityImportPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1095
1204
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
1096
1205
|
for (const imp of domainImports) {
|
|
1097
1206
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -1140,7 +1249,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
1140
1249
|
if (baseEntity) {
|
|
1141
1250
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
1142
1251
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
1143
|
-
const importPath = ifaceImportPath ? `${
|
|
1252
|
+
const importPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1144
1253
|
finalDomainImports.push({
|
|
1145
1254
|
name: baseEntity,
|
|
1146
1255
|
from: importPath,
|
|
@@ -1185,8 +1294,8 @@ function generateInfrastructure(options) {
|
|
|
1185
1294
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1186
1295
|
const className = toPascalCase(options.name);
|
|
1187
1296
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
1188
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
1189
|
-
const filePath =
|
|
1297
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2.join(projectRoot, "infrastructure"));
|
|
1298
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
1190
1299
|
let code;
|
|
1191
1300
|
if (options.implementsInterface) {
|
|
1192
1301
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -1200,7 +1309,7 @@ function generateInfrastructure(options) {
|
|
|
1200
1309
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
1201
1310
|
let ifaceImportPath;
|
|
1202
1311
|
if (ifaceFilePath) {
|
|
1203
|
-
let rel =
|
|
1312
|
+
let rel = path2.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
1204
1313
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1205
1314
|
ifaceImportPath = rel;
|
|
1206
1315
|
} else if (isFeatureScope) {
|
|
@@ -1213,8 +1322,8 @@ function generateInfrastructure(options) {
|
|
|
1213
1322
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
1214
1323
|
let fromPath;
|
|
1215
1324
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
1216
|
-
const absTarget =
|
|
1217
|
-
let rel =
|
|
1325
|
+
const absTarget = path2.resolve(path2.dirname(ifaceFilePath), imp.specifier);
|
|
1326
|
+
let rel = path2.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
1218
1327
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1219
1328
|
fromPath = rel;
|
|
1220
1329
|
} else {
|
|
@@ -1287,22 +1396,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1287
1396
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1288
1397
|
}));
|
|
1289
1398
|
const domainTypesToImport = new Set(domainImports);
|
|
1290
|
-
for (const
|
|
1291
|
-
const rawType =
|
|
1399
|
+
for (const p16 of [...inputProps, ...outputProps]) {
|
|
1400
|
+
const rawType = p16.type.trim();
|
|
1292
1401
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1293
1402
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1294
1403
|
domainTypesToImport.add(baseType);
|
|
1295
1404
|
}
|
|
1296
1405
|
}
|
|
1297
|
-
const formattedInputProps = inputProps.map((
|
|
1298
|
-
name:
|
|
1299
|
-
optional:
|
|
1300
|
-
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
|
|
1301
1410
|
}));
|
|
1302
|
-
const formattedOutputProps = outputProps.map((
|
|
1303
|
-
name:
|
|
1304
|
-
optional:
|
|
1305
|
-
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
|
|
1306
1415
|
}));
|
|
1307
1416
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1308
1417
|
key: toCamelCase(dep),
|
|
@@ -1401,8 +1510,8 @@ function generateUseCase(options) {
|
|
|
1401
1510
|
const files = [];
|
|
1402
1511
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1403
1512
|
const ucName = toPascalCase(options.name);
|
|
1404
|
-
const targetDir = options.targetDir ||
|
|
1405
|
-
const filePath =
|
|
1513
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1514
|
+
const filePath = path2.join(targetDir, `${ucName}.ts`);
|
|
1406
1515
|
const code = generateUseCaseCode(
|
|
1407
1516
|
ucName,
|
|
1408
1517
|
options.dependencies || [],
|
|
@@ -1418,7 +1527,7 @@ function generateUseCase(options) {
|
|
|
1418
1527
|
})
|
|
1419
1528
|
);
|
|
1420
1529
|
if (options.includeTest) {
|
|
1421
|
-
const testPath =
|
|
1530
|
+
const testPath = path2.join(targetDir, `${ucName}.test.ts`);
|
|
1422
1531
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1423
1532
|
files.push(
|
|
1424
1533
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1486,10 +1595,10 @@ function generateHttpHandler(options) {
|
|
|
1486
1595
|
const files = [];
|
|
1487
1596
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1488
1597
|
const className = toPascalCase(options.name);
|
|
1489
|
-
const pxDir =
|
|
1490
|
-
const handlersDir =
|
|
1491
|
-
const pxIndexPath =
|
|
1492
|
-
if (!
|
|
1598
|
+
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1599
|
+
const handlersDir = path2.join(pxDir, "handlers");
|
|
1600
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1601
|
+
if (!fs4.existsSync(pxIndexPath)) {
|
|
1493
1602
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1494
1603
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1495
1604
|
routePath: featurePath
|
|
@@ -1502,7 +1611,7 @@ function generateHttpHandler(options) {
|
|
|
1502
1611
|
})
|
|
1503
1612
|
);
|
|
1504
1613
|
}
|
|
1505
|
-
const handlerFilePath =
|
|
1614
|
+
const handlerFilePath = path2.join(handlersDir, `${className}.ts`);
|
|
1506
1615
|
const code = generateHttpHandlerCode(
|
|
1507
1616
|
className,
|
|
1508
1617
|
options.method || "get",
|
|
@@ -1523,11 +1632,11 @@ function generateHttpHandler(options) {
|
|
|
1523
1632
|
return files;
|
|
1524
1633
|
}
|
|
1525
1634
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1526
|
-
const providerPath =
|
|
1635
|
+
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1527
1636
|
const pascalFeature = toPascalCase(featureName);
|
|
1528
1637
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1529
|
-
if (
|
|
1530
|
-
let content =
|
|
1638
|
+
if (fs4.existsSync(providerPath) && !options.overwrite) {
|
|
1639
|
+
let content = fs4.readFileSync(providerPath, "utf-8");
|
|
1531
1640
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1532
1641
|
for (const b of bindings) {
|
|
1533
1642
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1586,7 +1695,7 @@ function generateFeature(options) {
|
|
|
1586
1695
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1587
1696
|
const rawName = options.name.trim();
|
|
1588
1697
|
const featureName = toCamelCase(rawName);
|
|
1589
|
-
const targetDir = options.targetDir ||
|
|
1698
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1590
1699
|
const routePath = options.routePath || `/${featureName}`;
|
|
1591
1700
|
const rawEntities = [];
|
|
1592
1701
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1607,7 +1716,7 @@ function generateFeature(options) {
|
|
|
1607
1716
|
const domainFile = generateDomain({
|
|
1608
1717
|
name: ent,
|
|
1609
1718
|
feature: featureName,
|
|
1610
|
-
targetDir:
|
|
1719
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1611
1720
|
projectRoot,
|
|
1612
1721
|
dryRun: options.dryRun,
|
|
1613
1722
|
overwrite: options.overwrite,
|
|
@@ -1627,7 +1736,7 @@ function generateFeature(options) {
|
|
|
1627
1736
|
const ifaceFile = generateInterface({
|
|
1628
1737
|
name: ifaceName,
|
|
1629
1738
|
feature: featureName,
|
|
1630
|
-
targetDir:
|
|
1739
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1631
1740
|
entityName: ent,
|
|
1632
1741
|
projectRoot,
|
|
1633
1742
|
dryRun: options.dryRun,
|
|
@@ -1644,7 +1753,7 @@ function generateFeature(options) {
|
|
|
1644
1753
|
const stubFile = generateInfrastructure({
|
|
1645
1754
|
name: stubName,
|
|
1646
1755
|
feature: featureName,
|
|
1647
|
-
targetDir:
|
|
1756
|
+
targetDir: path2.join(targetDir, "infrastructure"),
|
|
1648
1757
|
kind: "stub",
|
|
1649
1758
|
implementsInterface: ifaceName,
|
|
1650
1759
|
entityName: ent,
|
|
@@ -1686,7 +1795,7 @@ function generateFeature(options) {
|
|
|
1686
1795
|
const ucFiles = generateUseCase({
|
|
1687
1796
|
name: ucName,
|
|
1688
1797
|
feature: featureName,
|
|
1689
|
-
targetDir:
|
|
1798
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1690
1799
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1691
1800
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1692
1801
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1731,7 +1840,7 @@ function generateFeature(options) {
|
|
|
1731
1840
|
const ucFiles = generateUseCase({
|
|
1732
1841
|
name: ucName,
|
|
1733
1842
|
feature: featureName,
|
|
1734
|
-
targetDir:
|
|
1843
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1735
1844
|
dependencies: deps,
|
|
1736
1845
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1737
1846
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1764,7 +1873,7 @@ function generateFeature(options) {
|
|
|
1764
1873
|
const ucFiles = generateUseCase({
|
|
1765
1874
|
name: ucName,
|
|
1766
1875
|
feature: featureName,
|
|
1767
|
-
targetDir:
|
|
1876
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1768
1877
|
dependencies: deps,
|
|
1769
1878
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1770
1879
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1797,7 +1906,7 @@ function generateFeature(options) {
|
|
|
1797
1906
|
const ucFiles = generateUseCase({
|
|
1798
1907
|
name: ucName,
|
|
1799
1908
|
feature: featureName,
|
|
1800
|
-
targetDir:
|
|
1909
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1801
1910
|
dependencies: deps,
|
|
1802
1911
|
inputProperties: [
|
|
1803
1912
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1841,7 +1950,7 @@ function generateFeature(options) {
|
|
|
1841
1950
|
const ucFiles = generateUseCase({
|
|
1842
1951
|
name: ucName,
|
|
1843
1952
|
feature: featureName,
|
|
1844
|
-
targetDir:
|
|
1953
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1845
1954
|
dependencies: deps,
|
|
1846
1955
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1847
1956
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1874,7 +1983,7 @@ function generateFeature(options) {
|
|
|
1874
1983
|
const ucFiles = generateUseCase({
|
|
1875
1984
|
name: ucName,
|
|
1876
1985
|
feature: featureName,
|
|
1877
|
-
targetDir:
|
|
1986
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1878
1987
|
dependencies: deps,
|
|
1879
1988
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1880
1989
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1911,7 +2020,7 @@ function generateFeature(options) {
|
|
|
1911
2020
|
const ucFiles = generateUseCase({
|
|
1912
2021
|
name: ucName,
|
|
1913
2022
|
feature: featureName,
|
|
1914
|
-
targetDir:
|
|
2023
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1915
2024
|
dependencies: customUc.dependencies || [],
|
|
1916
2025
|
inputProperties: customUc.inputProperties || [],
|
|
1917
2026
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1943,8 +2052,8 @@ function generateFeature(options) {
|
|
|
1943
2052
|
}
|
|
1944
2053
|
}
|
|
1945
2054
|
if (options.includeExpress !== false) {
|
|
1946
|
-
const pxDir =
|
|
1947
|
-
const pxIndexPath =
|
|
2055
|
+
const pxDir = path2.join(targetDir, "pxExpress");
|
|
2056
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1948
2057
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1949
2058
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1950
2059
|
files.push(
|
|
@@ -1970,6 +2079,7 @@ function generateFeature(options) {
|
|
|
1970
2079
|
// src/commands/feature.ts
|
|
1971
2080
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
1972
2081
|
const projectRoot = findProjectRoot();
|
|
2082
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
1973
2083
|
let name = featureNameArg?.trim();
|
|
1974
2084
|
if (!name && flags.yes) {
|
|
1975
2085
|
name = "feature";
|
|
@@ -1987,9 +2097,10 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1987
2097
|
).trim();
|
|
1988
2098
|
}
|
|
1989
2099
|
const normalizedName = toCamelCase(name);
|
|
1990
|
-
const
|
|
2100
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
2101
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseFeaturesDir, normalizedName));
|
|
1991
2102
|
let targetDirRel = defaultDir;
|
|
1992
|
-
if (!flags.yes && !flags.target) {
|
|
2103
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1993
2104
|
targetDirRel = handleCancel(
|
|
1994
2105
|
await p4.text({
|
|
1995
2106
|
message: "Target directory for the feature:",
|
|
@@ -1998,7 +2109,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1998
2109
|
})
|
|
1999
2110
|
).trim();
|
|
2000
2111
|
}
|
|
2001
|
-
const targetDir = flags.target ?
|
|
2112
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
2002
2113
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
2003
2114
|
let routePath = `/${normalizedName}`;
|
|
2004
2115
|
let includeDomain = true;
|
|
@@ -2219,7 +2330,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2219
2330
|
}
|
|
2220
2331
|
}
|
|
2221
2332
|
const s = p4.spinner();
|
|
2222
|
-
s.start(`Generating feature '${
|
|
2333
|
+
s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
|
|
2223
2334
|
const options = {
|
|
2224
2335
|
name: normalizedName,
|
|
2225
2336
|
projectRoot,
|
|
@@ -2239,9 +2350,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2239
2350
|
includeDiProvider
|
|
2240
2351
|
};
|
|
2241
2352
|
const files = generateFeature(options);
|
|
2353
|
+
if (isRegistry) {
|
|
2354
|
+
const registryPath = path2.join(targetDir, "registry.json");
|
|
2355
|
+
if (!fs4.existsSync(registryPath)) {
|
|
2356
|
+
const manifest = {
|
|
2357
|
+
name: toKebabCase(normalizedName),
|
|
2358
|
+
type: "feature",
|
|
2359
|
+
description: flags.description || `${normalizedName} feature module`,
|
|
2360
|
+
version: "1.0.0",
|
|
2361
|
+
dependencies: {
|
|
2362
|
+
npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
|
|
2363
|
+
shared: [],
|
|
2364
|
+
features: []
|
|
2365
|
+
},
|
|
2366
|
+
files: []
|
|
2367
|
+
};
|
|
2368
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2369
|
+
const genFile = writeFileSafely(registryPath, content, {
|
|
2370
|
+
projectRoot,
|
|
2371
|
+
overwrite: options.overwrite,
|
|
2372
|
+
dryRun: options.dryRun
|
|
2373
|
+
});
|
|
2374
|
+
files.push(genFile);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2242
2377
|
s.stop("Feature scaffolded successfully!");
|
|
2243
2378
|
printGeneratedFiles(files, options.dryRun);
|
|
2244
|
-
p4.outro(
|
|
2379
|
+
p4.outro(
|
|
2380
|
+
pc14.green(
|
|
2381
|
+
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2382
|
+
)
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
function generateShared(options) {
|
|
2386
|
+
const files = [];
|
|
2387
|
+
const projectRoot = options.projectRoot || findProjectRoot();
|
|
2388
|
+
const rawName = options.name.trim();
|
|
2389
|
+
const pascalName = toPascalCase(rawName);
|
|
2390
|
+
const camelName = toCamelCase(rawName);
|
|
2391
|
+
const kebabName = toKebabCase(rawName);
|
|
2392
|
+
const targetDir = options.targetDir || path2.join(resolveSharedDir(projectRoot), kebabName);
|
|
2393
|
+
const fileDefs = [];
|
|
2394
|
+
const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
|
|
2395
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2396
|
+
|
|
2397
|
+
@MakeInjectable
|
|
2398
|
+
export class ${pascalName} {
|
|
2399
|
+
public static deps = { ${camelName}Engine: I${pascalName}Engine };
|
|
2400
|
+
constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
|
|
2401
|
+
}
|
|
2402
|
+
`;
|
|
2403
|
+
fileDefs.push({
|
|
2404
|
+
absolutePath: path2.join(targetDir, `${pascalName}.ts`),
|
|
2405
|
+
content: facadeContent
|
|
2406
|
+
});
|
|
2407
|
+
const portContent = `export abstract class I${pascalName}Engine {
|
|
2408
|
+
// Define abstract port contracts for ${pascalName} here
|
|
2409
|
+
}
|
|
2410
|
+
`;
|
|
2411
|
+
fileDefs.push({
|
|
2412
|
+
absolutePath: path2.join(targetDir, "ports", `I${pascalName}Engine.ts`),
|
|
2413
|
+
content: portContent
|
|
2414
|
+
});
|
|
2415
|
+
const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2416
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2417
|
+
|
|
2418
|
+
@MakeInjectable
|
|
2419
|
+
export class Default${pascalName}Engine implements I${pascalName}Engine {
|
|
2420
|
+
// Default concrete implementation for production
|
|
2421
|
+
}
|
|
2422
|
+
`;
|
|
2423
|
+
fileDefs.push({
|
|
2424
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
|
|
2425
|
+
content: concreteContent
|
|
2426
|
+
});
|
|
2427
|
+
if (options.includeStub !== false) {
|
|
2428
|
+
const stubContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2429
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2430
|
+
|
|
2431
|
+
@MakeInjectable
|
|
2432
|
+
export class Stub${pascalName}Engine implements I${pascalName}Engine {
|
|
2433
|
+
// In-memory or stub implementation for testing
|
|
2434
|
+
}
|
|
2435
|
+
`;
|
|
2436
|
+
fileDefs.push({
|
|
2437
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
|
|
2438
|
+
content: stubContent
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
const providerContent = `import type { Container } from "@solid-stack/di";
|
|
2442
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2443
|
+
import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
|
|
2444
|
+
|
|
2445
|
+
export const diProvider = (c: Container): void => {
|
|
2446
|
+
c.provide(I${pascalName}Engine, Default${pascalName}Engine);
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
export default diProvider;
|
|
2450
|
+
`;
|
|
2451
|
+
fileDefs.push({
|
|
2452
|
+
absolutePath: path2.join(targetDir, "diProvider.ts"),
|
|
2453
|
+
content: providerContent
|
|
2454
|
+
});
|
|
2455
|
+
if (options.isRegistry) {
|
|
2456
|
+
const registryManifest = {
|
|
2457
|
+
name: kebabName,
|
|
2458
|
+
type: "shared",
|
|
2459
|
+
description: options.description || `${kebabName} shared module`,
|
|
2460
|
+
version: "1.0.0",
|
|
2461
|
+
dependencies: {
|
|
2462
|
+
npm: [],
|
|
2463
|
+
shared: [],
|
|
2464
|
+
features: []
|
|
2465
|
+
},
|
|
2466
|
+
files: []
|
|
2467
|
+
};
|
|
2468
|
+
fileDefs.push({
|
|
2469
|
+
absolutePath: path2.join(targetDir, "registry.json"),
|
|
2470
|
+
content: JSON.stringify(registryManifest, null, 2) + "\n"
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
for (const def of fileDefs) {
|
|
2474
|
+
const genFile = writeFileSafely(def.absolutePath, def.content, {
|
|
2475
|
+
projectRoot,
|
|
2476
|
+
overwrite: options.overwrite,
|
|
2477
|
+
dryRun: options.dryRun
|
|
2478
|
+
});
|
|
2479
|
+
files.push(genFile);
|
|
2480
|
+
}
|
|
2481
|
+
return files;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/commands/shared.ts
|
|
2485
|
+
async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
2486
|
+
const projectRoot = findProjectRoot();
|
|
2487
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2488
|
+
let name = sharedNameArg?.trim();
|
|
2489
|
+
if (!name && flags.yes) {
|
|
2490
|
+
name = "shared";
|
|
2491
|
+
}
|
|
2492
|
+
if (!name) {
|
|
2493
|
+
name = handleCancel(
|
|
2494
|
+
await p4.text({
|
|
2495
|
+
message: "What is the shared module name?",
|
|
2496
|
+
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2497
|
+
validate: (val) => {
|
|
2498
|
+
if (!val || !val.trim()) return "Shared module name cannot be empty.";
|
|
2499
|
+
return void 0;
|
|
2500
|
+
}
|
|
2501
|
+
})
|
|
2502
|
+
).trim();
|
|
2503
|
+
}
|
|
2504
|
+
const normalizedName = toKebabCase(name);
|
|
2505
|
+
const baseSharedDir = resolveSharedDir(projectRoot, flags);
|
|
2506
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseSharedDir, normalizedName));
|
|
2507
|
+
let targetDirRel = defaultDir;
|
|
2508
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2509
|
+
targetDirRel = handleCancel(
|
|
2510
|
+
await p4.text({
|
|
2511
|
+
message: "Target directory for the shared module:",
|
|
2512
|
+
initialValue: defaultDir,
|
|
2513
|
+
defaultValue: defaultDir
|
|
2514
|
+
})
|
|
2515
|
+
).trim();
|
|
2516
|
+
}
|
|
2517
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
2518
|
+
let description = flags.description || "";
|
|
2519
|
+
let includeStub = true;
|
|
2520
|
+
if (!flags.yes) {
|
|
2521
|
+
description = handleCancel(
|
|
2522
|
+
await p4.text({
|
|
2523
|
+
message: "Module description (optional):",
|
|
2524
|
+
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2525
|
+
initialValue: ""
|
|
2526
|
+
})
|
|
2527
|
+
).trim();
|
|
2528
|
+
includeStub = handleCancel(
|
|
2529
|
+
await p4.confirm({
|
|
2530
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2531
|
+
initialValue: true
|
|
2532
|
+
})
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
const s = p4.spinner();
|
|
2536
|
+
s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
|
|
2537
|
+
const files = generateShared({
|
|
2538
|
+
name: normalizedName,
|
|
2539
|
+
description,
|
|
2540
|
+
projectRoot,
|
|
2541
|
+
targetDir,
|
|
2542
|
+
dryRun: Boolean(flags.dryRun),
|
|
2543
|
+
overwrite: Boolean(flags.overwrite),
|
|
2544
|
+
includeStub,
|
|
2545
|
+
isRegistry
|
|
2546
|
+
});
|
|
2547
|
+
s.stop("Shared module generated successfully!");
|
|
2548
|
+
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2549
|
+
p4.outro(
|
|
2550
|
+
pc14.green(
|
|
2551
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2552
|
+
)
|
|
2553
|
+
);
|
|
2245
2554
|
}
|
|
2246
2555
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2247
2556
|
const projectRoot = findProjectRoot();
|
|
@@ -2366,7 +2675,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2366
2675
|
}
|
|
2367
2676
|
}
|
|
2368
2677
|
const s = p4.spinner();
|
|
2369
|
-
s.start(`Generating use case '${
|
|
2678
|
+
s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
|
|
2370
2679
|
const options = {
|
|
2371
2680
|
name: ucName,
|
|
2372
2681
|
feature,
|
|
@@ -2398,7 +2707,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2398
2707
|
}
|
|
2399
2708
|
s.stop("Use case generated successfully!");
|
|
2400
2709
|
printGeneratedFiles(files, options.dryRun);
|
|
2401
|
-
p4.outro(
|
|
2710
|
+
p4.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2402
2711
|
}
|
|
2403
2712
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2404
2713
|
const projectRoot = findProjectRoot();
|
|
@@ -2469,7 +2778,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2469
2778
|
});
|
|
2470
2779
|
}
|
|
2471
2780
|
const s = p4.spinner();
|
|
2472
|
-
s.start(`Generating domain model '${
|
|
2781
|
+
s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
|
|
2473
2782
|
const options = {
|
|
2474
2783
|
name: entityName,
|
|
2475
2784
|
feature,
|
|
@@ -2482,7 +2791,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2482
2791
|
const file = generateDomain(options);
|
|
2483
2792
|
s.stop("Domain model generated successfully!");
|
|
2484
2793
|
printGeneratedFiles([file], options.dryRun);
|
|
2485
|
-
p4.outro(
|
|
2794
|
+
p4.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2486
2795
|
}
|
|
2487
2796
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2488
2797
|
const projectRoot = findProjectRoot();
|
|
@@ -2651,7 +2960,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2651
2960
|
registerInProvider = !flags.noProvider;
|
|
2652
2961
|
}
|
|
2653
2962
|
const s = p4.spinner();
|
|
2654
|
-
s.start(`Generating infrastructure class '${
|
|
2963
|
+
s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
|
|
2655
2964
|
const files = [];
|
|
2656
2965
|
const options = {
|
|
2657
2966
|
name: className,
|
|
@@ -2666,7 +2975,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2666
2975
|
const infraFile = generateInfrastructure(options);
|
|
2667
2976
|
files.push(infraFile);
|
|
2668
2977
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2669
|
-
const featureDir =
|
|
2978
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2670
2979
|
const providerFile = updateOrGenerateDiProvider(
|
|
2671
2980
|
featureDir,
|
|
2672
2981
|
feature,
|
|
@@ -2686,7 +2995,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2686
2995
|
}
|
|
2687
2996
|
s.stop("Infrastructure class generated successfully!");
|
|
2688
2997
|
printGeneratedFiles(files, options.dryRun);
|
|
2689
|
-
p4.outro(
|
|
2998
|
+
p4.outro(pc14.green(`Infrastructure class '${className}' created!`));
|
|
2690
2999
|
}
|
|
2691
3000
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2692
3001
|
const projectRoot = findProjectRoot();
|
|
@@ -2799,7 +3108,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2799
3108
|
}
|
|
2800
3109
|
}
|
|
2801
3110
|
const s = p4.spinner();
|
|
2802
|
-
s.start(`Generating interface '${
|
|
3111
|
+
s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
|
|
2803
3112
|
const files = [];
|
|
2804
3113
|
const options = {
|
|
2805
3114
|
name: ifaceName,
|
|
@@ -2828,7 +3137,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2828
3137
|
files.push(stubFile);
|
|
2829
3138
|
}
|
|
2830
3139
|
if (registerInProvider && generateStub) {
|
|
2831
|
-
const featureDir =
|
|
3140
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2832
3141
|
const providerFile = updateOrGenerateDiProvider(
|
|
2833
3142
|
featureDir,
|
|
2834
3143
|
feature,
|
|
@@ -2848,7 +3157,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2848
3157
|
}
|
|
2849
3158
|
s.stop("Interface generated successfully!");
|
|
2850
3159
|
printGeneratedFiles(files, options.dryRun);
|
|
2851
|
-
p4.outro(
|
|
3160
|
+
p4.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2852
3161
|
}
|
|
2853
3162
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2854
3163
|
const projectRoot = findProjectRoot();
|
|
@@ -2965,7 +3274,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2965
3274
|
}
|
|
2966
3275
|
}
|
|
2967
3276
|
const s = p4.spinner();
|
|
2968
|
-
s.start(`Generating HTTP handler '${
|
|
3277
|
+
s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
|
|
2969
3278
|
const options = {
|
|
2970
3279
|
name: className,
|
|
2971
3280
|
feature,
|
|
@@ -2983,7 +3292,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2983
3292
|
const files = generateHttpHandler(options);
|
|
2984
3293
|
s.stop("HTTP handler generated successfully!");
|
|
2985
3294
|
printGeneratedFiles(files, options.dryRun);
|
|
2986
|
-
p4.outro(
|
|
3295
|
+
p4.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
2987
3296
|
}
|
|
2988
3297
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
2989
3298
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -2993,7 +3302,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
2993
3302
|
const formattedMethods = methods.map((m) => {
|
|
2994
3303
|
const isAsync = m.async !== false;
|
|
2995
3304
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
2996
|
-
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(", ") : "";
|
|
2997
3306
|
return {
|
|
2998
3307
|
name: m.name,
|
|
2999
3308
|
isAsync,
|
|
@@ -3016,21 +3325,21 @@ function generateInjectable(options) {
|
|
|
3016
3325
|
const className = toPascalCase(options.name);
|
|
3017
3326
|
let targetDir = "";
|
|
3018
3327
|
if (options.customPath) {
|
|
3019
|
-
targetDir =
|
|
3328
|
+
targetDir = path2.isAbsolute(options.customPath) ? options.customPath : path2.join(projectRoot, options.customPath);
|
|
3020
3329
|
} else if (options.location === "feature-service" && options.feature) {
|
|
3021
|
-
targetDir =
|
|
3330
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
3022
3331
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
3023
|
-
targetDir =
|
|
3332
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
3024
3333
|
} else if (options.location === "module") {
|
|
3025
|
-
targetDir =
|
|
3334
|
+
targetDir = path2.join(projectRoot, "modules", className);
|
|
3026
3335
|
} else {
|
|
3027
|
-
targetDir = options.feature ?
|
|
3336
|
+
targetDir = options.feature ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2.join(projectRoot, "shared", className.toLowerCase());
|
|
3028
3337
|
}
|
|
3029
|
-
const filePath =
|
|
3338
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
3030
3339
|
let ifaceName;
|
|
3031
3340
|
if (options.generateInterface) {
|
|
3032
3341
|
ifaceName = `I${className}`;
|
|
3033
|
-
const ifacePath =
|
|
3342
|
+
const ifacePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
3034
3343
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
3035
3344
|
files.push(
|
|
3036
3345
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -3139,7 +3448,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3139
3448
|
);
|
|
3140
3449
|
}
|
|
3141
3450
|
const s = p4.spinner();
|
|
3142
|
-
s.start(`Generating injectable '${
|
|
3451
|
+
s.start(`Generating injectable '${pc14.cyan(className)}'...`);
|
|
3143
3452
|
const options = {
|
|
3144
3453
|
name: className,
|
|
3145
3454
|
feature,
|
|
@@ -3153,7 +3462,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3153
3462
|
const files = generateInjectable(options);
|
|
3154
3463
|
s.stop("Injectable class generated successfully!");
|
|
3155
3464
|
printGeneratedFiles(files, options.dryRun);
|
|
3156
|
-
p4.outro(
|
|
3465
|
+
p4.outro(pc14.green(`Injectable '${className}' created successfully!`));
|
|
3157
3466
|
}
|
|
3158
3467
|
async function runInteractiveMenu(flags = {}) {
|
|
3159
3468
|
printBanner();
|
|
@@ -3166,6 +3475,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3166
3475
|
label: "Feature",
|
|
3167
3476
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
3168
3477
|
},
|
|
3478
|
+
{
|
|
3479
|
+
value: "shared",
|
|
3480
|
+
label: "Shared Module",
|
|
3481
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3482
|
+
},
|
|
3169
3483
|
{
|
|
3170
3484
|
value: "usecase",
|
|
3171
3485
|
label: "Use Case",
|
|
@@ -3203,6 +3517,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3203
3517
|
case "feature":
|
|
3204
3518
|
await runFeatureCommand(void 0, flags);
|
|
3205
3519
|
break;
|
|
3520
|
+
case "shared":
|
|
3521
|
+
await runSharedCommand(void 0, flags);
|
|
3522
|
+
break;
|
|
3206
3523
|
case "usecase":
|
|
3207
3524
|
await runUseCaseCommand(void 0, flags);
|
|
3208
3525
|
break;
|
|
@@ -3235,6 +3552,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3235
3552
|
case "feature":
|
|
3236
3553
|
await runFeatureCommand(name, flags);
|
|
3237
3554
|
break;
|
|
3555
|
+
case "shared":
|
|
3556
|
+
await runSharedCommand(name, flags);
|
|
3557
|
+
break;
|
|
3238
3558
|
case "usecase":
|
|
3239
3559
|
await runUseCaseCommand(name, flags);
|
|
3240
3560
|
break;
|
|
@@ -3256,20 +3576,171 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3256
3576
|
break;
|
|
3257
3577
|
default:
|
|
3258
3578
|
p4.log.error(
|
|
3259
|
-
|
|
3260
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3579
|
+
pc14.red(
|
|
3580
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3261
3581
|
)
|
|
3262
3582
|
);
|
|
3263
3583
|
process.exit(1);
|
|
3264
3584
|
}
|
|
3265
3585
|
}
|
|
3586
|
+
var importPrompts = {
|
|
3587
|
+
select: p4.select,
|
|
3588
|
+
text: p4.text
|
|
3589
|
+
};
|
|
3590
|
+
function rewriteImports(content, aliasMap) {
|
|
3591
|
+
if (aliasMap.size === 0) return content;
|
|
3592
|
+
let rewritten = content;
|
|
3593
|
+
for (const [oldImport, newImport] of aliasMap.entries()) {
|
|
3594
|
+
const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
|
|
3595
|
+
const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
|
|
3596
|
+
const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3597
|
+
const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
|
|
3598
|
+
rewritten = rewritten.replace(regex, rawNew);
|
|
3599
|
+
}
|
|
3600
|
+
return rewritten;
|
|
3601
|
+
}
|
|
3602
|
+
function getModuleBaseDir(projectRoot, modType) {
|
|
3603
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3604
|
+
return path2.join(projectRoot, "src", folder);
|
|
3605
|
+
}
|
|
3606
|
+
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3607
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3608
|
+
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3609
|
+
if (fs4.existsSync(inSrc)) return inSrc;
|
|
3610
|
+
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3611
|
+
if (fs4.existsSync(inRoot)) return inRoot;
|
|
3612
|
+
return null;
|
|
3613
|
+
}
|
|
3614
|
+
function checkModuleExists(projectRoot, modType, modName) {
|
|
3615
|
+
return getExistingModuleDir(projectRoot, modType, modName) !== null;
|
|
3616
|
+
}
|
|
3617
|
+
async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
|
|
3618
|
+
const folder = depType === "shared" ? "shared" : "features";
|
|
3619
|
+
const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
|
|
3620
|
+
if (isAlreadyImportedThisRun) return;
|
|
3621
|
+
const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
|
|
3622
|
+
if (programmaticAlias) {
|
|
3623
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
|
|
3624
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3625
|
+
const displayDest = path2.relative(
|
|
3626
|
+
projectRoot,
|
|
3627
|
+
path2.join(baseDir, programmaticAlias)
|
|
3628
|
+
);
|
|
3629
|
+
p4.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3630
|
+
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
3633
|
+
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3634
|
+
if (!existingDir) {
|
|
3635
|
+
p4.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14.cyan(depName)}`);
|
|
3636
|
+
await importModuleRecursive(depType, depName, depName);
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
|
|
3640
|
+
(item) => item.name === depName
|
|
3641
|
+
);
|
|
3642
|
+
let remoteIntegrity = entry?.integrity;
|
|
3643
|
+
if (!remoteIntegrity) {
|
|
3644
|
+
try {
|
|
3645
|
+
const depManifest = await fetchModuleManifest(registryBase, depType, depName);
|
|
3646
|
+
remoteIntegrity = depManifest.integrity;
|
|
3647
|
+
} catch {
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
const localHash = computeDirectoryHash(existingDir);
|
|
3651
|
+
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3652
|
+
p4.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3653
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
if (flags.overwrite) {
|
|
3657
|
+
p4.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3658
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3659
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3660
|
+
if (!flags.dryRun) {
|
|
3661
|
+
if (existingDir !== targetDir) {
|
|
3662
|
+
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
3663
|
+
}
|
|
3664
|
+
fs4.rmSync(targetDir, { recursive: true, force: true });
|
|
3665
|
+
}
|
|
3666
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
if (flags.yes) {
|
|
3670
|
+
p4.log.info(`Using existing '${depType}/${depName}'`);
|
|
3671
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
const choice = handleCancel(
|
|
3675
|
+
await importPrompts.select({
|
|
3676
|
+
message: `Local '${depType}/${depName}' differs from the registry version.`,
|
|
3677
|
+
options: [
|
|
3678
|
+
{
|
|
3679
|
+
value: "keep_existing",
|
|
3680
|
+
label: `Keep existing local version (Recommended)`
|
|
3681
|
+
},
|
|
3682
|
+
{
|
|
3683
|
+
value: "rename",
|
|
3684
|
+
label: "Import fresh copy under a new name (alias)"
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
value: "overwrite",
|
|
3688
|
+
label: "Overwrite local version with registry version"
|
|
3689
|
+
}
|
|
3690
|
+
]
|
|
3691
|
+
})
|
|
3692
|
+
);
|
|
3693
|
+
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3694
|
+
p4.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3695
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
if (choice === "rename") {
|
|
3699
|
+
const newName = handleCancel(
|
|
3700
|
+
await importPrompts.text({
|
|
3701
|
+
message: "What should we name the new copy?",
|
|
3702
|
+
placeholder: `${depName}-copy`,
|
|
3703
|
+
validate(val) {
|
|
3704
|
+
const v = val?.trim();
|
|
3705
|
+
if (!v) return "Name cannot be empty";
|
|
3706
|
+
if (v === depName) return `Name must be different from '${depName}'`;
|
|
3707
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
|
|
3708
|
+
return "Name can only contain alphanumeric characters, dashes, and underscores";
|
|
3709
|
+
}
|
|
3710
|
+
if (checkModuleExists(projectRoot, depType, v)) {
|
|
3711
|
+
return `'${v}' already exists in ${folder}, please choose another name`;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
})
|
|
3715
|
+
).trim();
|
|
3716
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3717
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3718
|
+
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
3719
|
+
p4.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3720
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
if (choice === "overwrite") {
|
|
3724
|
+
p4.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3725
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3726
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3727
|
+
if (!flags.dryRun) {
|
|
3728
|
+
if (existingDir !== targetDir) {
|
|
3729
|
+
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
3730
|
+
}
|
|
3731
|
+
fs4.rmSync(targetDir, { recursive: true, force: true });
|
|
3732
|
+
}
|
|
3733
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3266
3737
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3267
3738
|
const projectRoot = findProjectRoot();
|
|
3268
3739
|
let type = typeArg?.trim().toLowerCase();
|
|
3269
3740
|
if (type === "features") type = "feature";
|
|
3270
3741
|
if (!type) {
|
|
3271
3742
|
type = handleCancel(
|
|
3272
|
-
await
|
|
3743
|
+
await importPrompts.select({
|
|
3273
3744
|
message: "What kind of module would you like to import?",
|
|
3274
3745
|
options: [
|
|
3275
3746
|
{
|
|
@@ -3287,19 +3758,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3287
3758
|
);
|
|
3288
3759
|
}
|
|
3289
3760
|
if (type !== "shared" && type !== "feature") {
|
|
3290
|
-
p4.log.error(
|
|
3761
|
+
p4.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3291
3762
|
process.exit(1);
|
|
3292
3763
|
}
|
|
3293
3764
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3294
3765
|
const s = p4.spinner();
|
|
3295
|
-
s.start(`Connecting to registry at ${
|
|
3766
|
+
s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
|
|
3296
3767
|
let index;
|
|
3297
3768
|
try {
|
|
3298
3769
|
index = await fetchRegistryIndex(registryBase);
|
|
3299
3770
|
s.stop("Registry index loaded.");
|
|
3300
3771
|
} catch (err) {
|
|
3301
|
-
s.stop(
|
|
3302
|
-
p4.log.error(
|
|
3772
|
+
s.stop(pc14.red("Failed to load registry index."));
|
|
3773
|
+
p4.log.error(pc14.red(err.message || String(err)));
|
|
3303
3774
|
process.exit(1);
|
|
3304
3775
|
}
|
|
3305
3776
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3311,7 +3782,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3311
3782
|
return;
|
|
3312
3783
|
}
|
|
3313
3784
|
name = handleCancel(
|
|
3314
|
-
await
|
|
3785
|
+
await importPrompts.select({
|
|
3315
3786
|
message: `Select a ${type} to import:`,
|
|
3316
3787
|
options: availableList.map((item) => ({
|
|
3317
3788
|
value: item.name,
|
|
@@ -3323,71 +3794,94 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3323
3794
|
}
|
|
3324
3795
|
if (!candidateNames.includes(name)) {
|
|
3325
3796
|
const closest = findClosestMatch(name, candidateNames);
|
|
3326
|
-
p4.log.error(
|
|
3797
|
+
p4.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3327
3798
|
if (closest) {
|
|
3328
|
-
p4.log.info(
|
|
3799
|
+
p4.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
|
|
3329
3800
|
}
|
|
3330
3801
|
process.exit(1);
|
|
3331
3802
|
}
|
|
3332
3803
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3333
3804
|
const totalWrittenFiles = [];
|
|
3334
3805
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3335
|
-
async function importModuleRecursive(modType,
|
|
3336
|
-
const modKey = `${modType}:${
|
|
3806
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3807
|
+
const modKey = `${modType}:${targetName}`;
|
|
3337
3808
|
if (importedModules.has(modKey)) return;
|
|
3338
|
-
p4.log.step(
|
|
3809
|
+
p4.log.step(
|
|
3810
|
+
`Importing ${modType}: ${pc14.cyan(targetName)}${targetName !== registryName ? pc14.dim(` (from ${registryName})`) : ""}...`
|
|
3811
|
+
);
|
|
3339
3812
|
let manifest;
|
|
3340
3813
|
try {
|
|
3341
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3814
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3342
3815
|
} catch (err) {
|
|
3343
|
-
p4.log.error(
|
|
3816
|
+
p4.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3344
3817
|
process.exit(1);
|
|
3345
3818
|
}
|
|
3819
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3820
|
+
if (targetName !== registryName) {
|
|
3821
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3822
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3823
|
+
}
|
|
3346
3824
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3347
3825
|
for (const dep of sharedDeps) {
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3826
|
+
await resolveDependency(
|
|
3827
|
+
"shared",
|
|
3828
|
+
dep,
|
|
3829
|
+
aliasMap,
|
|
3830
|
+
projectRoot,
|
|
3831
|
+
flags,
|
|
3832
|
+
importModuleRecursive,
|
|
3833
|
+
importedModules,
|
|
3834
|
+
registryBase,
|
|
3835
|
+
index
|
|
3836
|
+
);
|
|
3353
3837
|
}
|
|
3354
3838
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3355
3839
|
for (const dep of featureDeps) {
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3840
|
+
await resolveDependency(
|
|
3841
|
+
"feature",
|
|
3842
|
+
dep,
|
|
3843
|
+
aliasMap,
|
|
3844
|
+
projectRoot,
|
|
3845
|
+
flags,
|
|
3846
|
+
importModuleRecursive,
|
|
3847
|
+
importedModules,
|
|
3848
|
+
registryBase,
|
|
3849
|
+
index
|
|
3850
|
+
);
|
|
3361
3851
|
}
|
|
3362
3852
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3363
3853
|
requiredNpmPackages.add(pkg);
|
|
3364
3854
|
}
|
|
3365
3855
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3366
|
-
const targetDir =
|
|
3856
|
+
const targetDir = path2.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3857
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3367
3858
|
for (const file of manifest.files || []) {
|
|
3368
3859
|
if (file === "registry.json") continue;
|
|
3369
|
-
const remoteFilePath = `${folder}/${
|
|
3370
|
-
const localFilePath =
|
|
3371
|
-
if (
|
|
3372
|
-
p4.log.warn(
|
|
3860
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3861
|
+
const localFilePath = path2.join(targetDir, 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
|
+
);
|
|
3373
3866
|
continue;
|
|
3374
3867
|
}
|
|
3375
|
-
|
|
3868
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3869
|
+
content = rewriteImports(content, aliasMap);
|
|
3376
3870
|
if (!flags.dryRun) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3871
|
+
fs4.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
3872
|
+
fs4.writeFileSync(localFilePath, content, "utf-8");
|
|
3379
3873
|
}
|
|
3380
|
-
const displayPath =
|
|
3874
|
+
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
3381
3875
|
totalWrittenFiles.push(displayPath);
|
|
3382
|
-
p4.log.message(` ${
|
|
3876
|
+
p4.log.message(` ${pc14.green("+")} ${displayPath}`);
|
|
3383
3877
|
}
|
|
3384
3878
|
importedModules.add(modKey);
|
|
3385
3879
|
}
|
|
3386
|
-
await importModuleRecursive(type, name);
|
|
3880
|
+
await importModuleRecursive(type, name, name);
|
|
3387
3881
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3388
3882
|
if (missingNpm.length > 0) {
|
|
3389
3883
|
if (flags.skipInstall || flags.dryRun) {
|
|
3390
|
-
p4.log.info(`Required npm dependencies to install: ${
|
|
3884
|
+
p4.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
|
|
3391
3885
|
} else {
|
|
3392
3886
|
try {
|
|
3393
3887
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -3397,11 +3891,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3397
3891
|
}
|
|
3398
3892
|
}
|
|
3399
3893
|
if (flags.dryRun) {
|
|
3400
|
-
p4.log.info(
|
|
3894
|
+
p4.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
|
|
3401
3895
|
} else {
|
|
3896
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3897
|
+
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
|
|
3402
3898
|
p4.log.success(
|
|
3403
|
-
|
|
3404
|
-
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in
|
|
3899
|
+
pc14.green(
|
|
3900
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3405
3901
|
)
|
|
3406
3902
|
);
|
|
3407
3903
|
}
|
|
@@ -3410,24 +3906,70 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3410
3906
|
// src/cli.ts
|
|
3411
3907
|
function createCli() {
|
|
3412
3908
|
const cli = cac("mason");
|
|
3413
|
-
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").action(async (args, flags) => {
|
|
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) => {
|
|
3414
3910
|
if (args && args.length > 0) {
|
|
3415
3911
|
p4.log.error(
|
|
3416
|
-
|
|
3912
|
+
pc14.red(
|
|
3417
3913
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3418
3914
|
)
|
|
3419
3915
|
);
|
|
3420
3916
|
process.exit(1);
|
|
3421
3917
|
}
|
|
3422
3918
|
try {
|
|
3919
|
+
const projectRoot = findProjectRoot();
|
|
3920
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
3921
|
+
if (mode === "registry") {
|
|
3922
|
+
const choice2 = handleCancel(
|
|
3923
|
+
await p4.select({
|
|
3924
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
3925
|
+
options: [
|
|
3926
|
+
{
|
|
3927
|
+
value: "create_feature",
|
|
3928
|
+
label: "Initialize New Feature",
|
|
3929
|
+
hint: "Scaffold a new feature slice under features/"
|
|
3930
|
+
},
|
|
3931
|
+
{
|
|
3932
|
+
value: "create_shared",
|
|
3933
|
+
label: "Initialize New Shared Module",
|
|
3934
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
3935
|
+
},
|
|
3936
|
+
{
|
|
3937
|
+
value: "create_usecase",
|
|
3938
|
+
label: "Create New Use Case",
|
|
3939
|
+
hint: "Add a new use case to an existing feature"
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
value: "import",
|
|
3943
|
+
label: "Import from Remote Registry",
|
|
3944
|
+
hint: "Download a module from upstream"
|
|
3945
|
+
}
|
|
3946
|
+
]
|
|
3947
|
+
})
|
|
3948
|
+
);
|
|
3949
|
+
switch (choice2) {
|
|
3950
|
+
case "create_feature":
|
|
3951
|
+
await handleCreate("feature", void 0, flags);
|
|
3952
|
+
break;
|
|
3953
|
+
case "create_shared":
|
|
3954
|
+
await handleCreate("shared", void 0, flags);
|
|
3955
|
+
break;
|
|
3956
|
+
case "create_usecase":
|
|
3957
|
+
await handleCreate("usecase", void 0, flags);
|
|
3958
|
+
break;
|
|
3959
|
+
case "import":
|
|
3960
|
+
await handleImport(void 0, void 0, flags);
|
|
3961
|
+
break;
|
|
3962
|
+
}
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3423
3965
|
const choice = handleCancel(
|
|
3424
3966
|
await p4.select({
|
|
3425
3967
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3426
3968
|
options: [
|
|
3427
3969
|
{
|
|
3428
3970
|
value: "create",
|
|
3429
|
-
label: "Create
|
|
3430
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
3971
|
+
label: "Create component",
|
|
3972
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3431
3973
|
},
|
|
3432
3974
|
{
|
|
3433
3975
|
value: "import",
|
|
@@ -3448,8 +3990,8 @@ function createCli() {
|
|
|
3448
3990
|
});
|
|
3449
3991
|
cli.command(
|
|
3450
3992
|
"create [type] [name]",
|
|
3451
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3452
|
-
).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("-f, --feature <feature>", "Target feature name").option("-t, --target <dir>", "Override target directory").option("--express", "Include Express presentation layer (pxExpress)").option("--no-express", "Skip Express presentation layer").option("-i, --interface <interface>", "Interface or abstract class to implement").option("-k, --kind <kind>", "Implementation kind ('stub' or 'concrete')").option("-g, --global", "Place in global infrastructure/ instead of feature").option("-m, --method <method>", "HTTP method (get, post, put, patch, delete)").option("-p, --path <path>", "Route subpath (e.g. /:id)").option("-u, --usecase <usecase>", "Connected use case name").option("--message <message>", "Custom success message").option("--domainNames <names>", "Comma-separated domain entity names").example("mason create").example("mason create feature users").example("mason create usecase PlaceOrder -f orders").example("mason create domain User -f users").example("mason create infrastructure PostgresUserRepository -f users -i IUserRepository").example("mason create interface IUserRepository -f users").example("mason create httpHandler GetUserHttp -f users -m get -p /:id").example("mason create injectable PasswordHasher").action(async (type, name, flags = {}) => {
|
|
3993
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
3994
|
+
).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("-f, --feature <feature>", "Target feature name").option("-t, --target <dir>", "Override target directory").option("--dest <dir>", "Override target directory (alias for --target)").option("--registry", "Scaffold in registry mode (stamps registry.json)").option("--express", "Include Express presentation layer (pxExpress)").option("--no-express", "Skip Express presentation layer").option("-i, --interface <interface>", "Interface or abstract class to implement").option("-k, --kind <kind>", "Implementation kind ('stub' or 'concrete')").option("-g, --global", "Place in global infrastructure/ instead of feature").option("-m, --method <method>", "HTTP method (get, post, put, patch, delete)").option("-p, --path <path>", "Route subpath (e.g. /:id)").option("-u, --usecase <usecase>", "Connected use case name").option("--message <message>", "Custom success message").option("--domainNames <names>", "Comma-separated domain entity names").example("mason create").example("mason create feature users").example("mason create usecase PlaceOrder -f orders").example("mason create domain User -f users").example("mason create infrastructure PostgresUserRepository -f users -i IUserRepository").example("mason create interface IUserRepository -f users").example("mason create httpHandler GetUserHttp -f users -m get -p /:id").example("mason create injectable PasswordHasher").action(async (type, name, flags = {}) => {
|
|
3453
3995
|
try {
|
|
3454
3996
|
await handleCreate(type, name, flags);
|
|
3455
3997
|
} catch (err) {
|
|
@@ -3468,6 +4010,7 @@ function createCli() {
|
|
|
3468
4010
|
});
|
|
3469
4011
|
const createSubtypes = [
|
|
3470
4012
|
"feature",
|
|
4013
|
+
"shared",
|
|
3471
4014
|
"usecase",
|
|
3472
4015
|
"domain",
|
|
3473
4016
|
"infrastructure",
|
|
@@ -3488,7 +4031,7 @@ function createCli() {
|
|
|
3488
4031
|
});
|
|
3489
4032
|
}
|
|
3490
4033
|
cli.help();
|
|
3491
|
-
cli.version("1.0.
|
|
4034
|
+
cli.version("1.0.14");
|
|
3492
4035
|
return cli;
|
|
3493
4036
|
}
|
|
3494
4037
|
function handleCliError(err) {
|
|
@@ -3496,7 +4039,7 @@ function handleCliError(err) {
|
|
|
3496
4039
|
process.exit(0);
|
|
3497
4040
|
}
|
|
3498
4041
|
const message = err instanceof Error ? err.message : String(err);
|
|
3499
|
-
p4.log.error(
|
|
4042
|
+
p4.log.error(pc14.red(`Error: ${message}`));
|
|
3500
4043
|
process.exit(1);
|
|
3501
4044
|
}
|
|
3502
4045
|
async function runCli(argv = process.argv) {
|
|
@@ -3504,6 +4047,6 @@ async function runCli(argv = process.argv) {
|
|
|
3504
4047
|
cli.parse(argv, { run: true });
|
|
3505
4048
|
}
|
|
3506
4049
|
|
|
3507
|
-
export { DEFAULT_REGISTRY_BASE, PromptCancelledError, 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, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listUseCases, loadInterfaceHierarchy, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, runCli, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
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 };
|
|
3508
4051
|
//# sourceMappingURL=index.js.map
|
|
3509
4052
|
//# sourceMappingURL=index.js.map
|