@solid-stack/mason 1.0.11 → 1.0.13
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 +1508 -315
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1504 -311
- 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/registry.d.ts +64 -0
- package/dist/commands/registry.d.ts.map +1 -0
- 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 +1531 -315
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1515 -312
- 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 +20 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var cac = require('cac');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
5
|
+
var p15 = require('@clack/prompts');
|
|
6
|
+
var pc15 = require('picocolors');
|
|
7
|
+
var fs12 = require('fs');
|
|
8
|
+
var path2 = require('path');
|
|
9
9
|
var url = require('url');
|
|
10
10
|
var Handlebars = require('handlebars');
|
|
11
11
|
var child_process = require('child_process');
|
|
12
|
+
var crypto = require('crypto');
|
|
12
13
|
|
|
13
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -31,16 +32,16 @@ function _interopNamespace(e) {
|
|
|
31
32
|
return Object.freeze(n);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
var
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
var
|
|
35
|
+
var p15__namespace = /*#__PURE__*/_interopNamespace(p15);
|
|
36
|
+
var pc15__default = /*#__PURE__*/_interopDefault(pc15);
|
|
37
|
+
var fs12__default = /*#__PURE__*/_interopDefault(fs12);
|
|
38
|
+
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
38
39
|
var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
|
|
39
40
|
|
|
40
41
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
41
42
|
const projectRoot = options.projectRoot || process.cwd();
|
|
42
|
-
const relPath =
|
|
43
|
-
const existedBefore =
|
|
43
|
+
const relPath = path2__default.default.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
44
|
+
const existedBefore = fs12__default.default.existsSync(absolutePath);
|
|
44
45
|
if (options.dryRun) {
|
|
45
46
|
return {
|
|
46
47
|
relativePath: relPath,
|
|
@@ -59,11 +60,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
59
60
|
existedBefore: true
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
const dir =
|
|
63
|
-
if (!
|
|
64
|
-
|
|
63
|
+
const dir = path2__default.default.dirname(absolutePath);
|
|
64
|
+
if (!fs12__default.default.existsSync(dir)) {
|
|
65
|
+
fs12__default.default.mkdirSync(dir, { recursive: true });
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
fs12__default.default.writeFileSync(absolutePath, content, "utf-8");
|
|
67
68
|
return {
|
|
68
69
|
relativePath: relPath,
|
|
69
70
|
absolutePath,
|
|
@@ -75,17 +76,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
75
76
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
76
77
|
console.log("");
|
|
77
78
|
if (isDryRun) {
|
|
78
|
-
|
|
79
|
+
p15__namespace.log.warn(pc15__default.default.yellow(pc15__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
79
80
|
}
|
|
80
81
|
for (const f of files) {
|
|
81
82
|
if (f.action === "created") {
|
|
82
|
-
|
|
83
|
+
p15__namespace.log.success(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(created)")}`);
|
|
83
84
|
} else if (f.action === "overwritten") {
|
|
84
|
-
|
|
85
|
+
p15__namespace.log.warn(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(overwritten)")}`);
|
|
85
86
|
} else if (f.action === "skipped") {
|
|
86
|
-
|
|
87
|
+
p15__namespace.log.info(`${pc15__default.default.dim(f.relativePath)} ${pc15__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
87
88
|
} else {
|
|
88
|
-
|
|
89
|
+
p15__namespace.log.info(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(preview)")}`);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -153,27 +154,90 @@ function toStubName(ifaceName) {
|
|
|
153
154
|
}
|
|
154
155
|
return `Stub${base}`;
|
|
155
156
|
}
|
|
157
|
+
function loadMasonConfig(startDir = process.cwd()) {
|
|
158
|
+
let current = path2__default.default.resolve(startDir);
|
|
159
|
+
while (true) {
|
|
160
|
+
const configPath = path2__default.default.join(current, "mason.config.json");
|
|
161
|
+
if (fs12__default.default.existsSync(configPath)) {
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const parent = path2__default.default.dirname(current);
|
|
169
|
+
if (parent === current) break;
|
|
170
|
+
current = parent;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function getEffectiveMode(projectRoot, flags) {
|
|
175
|
+
if (flags?.registry) return "registry";
|
|
176
|
+
const config = loadMasonConfig(projectRoot);
|
|
177
|
+
if (config?.mode) return config.mode;
|
|
178
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "index.json")) && (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "features")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "shared"))) && !fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src"))) {
|
|
179
|
+
return "registry";
|
|
180
|
+
}
|
|
181
|
+
return "app";
|
|
182
|
+
}
|
|
183
|
+
function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
184
|
+
if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
|
|
185
|
+
if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
|
|
186
|
+
const config = loadMasonConfig(projectRoot);
|
|
187
|
+
if (config?.paths?.features) {
|
|
188
|
+
return path2__default.default.resolve(projectRoot, config.paths.features);
|
|
189
|
+
}
|
|
190
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
191
|
+
if (mode === "registry") {
|
|
192
|
+
return path2__default.default.join(projectRoot, "features");
|
|
193
|
+
}
|
|
194
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src/features"))) {
|
|
195
|
+
return path2__default.default.join(projectRoot, "src/features");
|
|
196
|
+
}
|
|
197
|
+
return path2__default.default.join(projectRoot, "features");
|
|
198
|
+
}
|
|
199
|
+
function getConfiguredSharedDir(projectRoot, flags) {
|
|
200
|
+
if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
|
|
201
|
+
if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
|
|
202
|
+
const config = loadMasonConfig(projectRoot);
|
|
203
|
+
if (config?.paths?.shared) {
|
|
204
|
+
return path2__default.default.resolve(projectRoot, config.paths.shared);
|
|
205
|
+
}
|
|
206
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
207
|
+
if (mode === "registry") {
|
|
208
|
+
return path2__default.default.join(projectRoot, "shared");
|
|
209
|
+
}
|
|
210
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src/shared"))) {
|
|
211
|
+
return path2__default.default.join(projectRoot, "src/shared");
|
|
212
|
+
}
|
|
213
|
+
return path2__default.default.join(projectRoot, "shared");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/utils/project.ts
|
|
156
217
|
function findProjectRoot(startDir = process.cwd()) {
|
|
157
|
-
let current =
|
|
218
|
+
let current = path2__default.default.resolve(startDir);
|
|
158
219
|
while (true) {
|
|
159
|
-
if (
|
|
220
|
+
if (fs12__default.default.existsSync(path2__default.default.join(current, "package.json"))) {
|
|
160
221
|
return current;
|
|
161
222
|
}
|
|
162
|
-
const parent =
|
|
223
|
+
const parent = path2__default.default.dirname(current);
|
|
163
224
|
if (parent === current) {
|
|
164
|
-
return
|
|
225
|
+
return path2__default.default.resolve(startDir);
|
|
165
226
|
}
|
|
166
227
|
current = parent;
|
|
167
228
|
}
|
|
168
229
|
}
|
|
169
|
-
function resolveFeaturesDir(projectRoot) {
|
|
170
|
-
return
|
|
230
|
+
function resolveFeaturesDir(projectRoot, flags) {
|
|
231
|
+
return getConfiguredFeaturesDir(projectRoot, flags);
|
|
232
|
+
}
|
|
233
|
+
function resolveSharedDir(projectRoot, flags) {
|
|
234
|
+
return getConfiguredSharedDir(projectRoot, flags);
|
|
171
235
|
}
|
|
172
236
|
function listExistingFeatures(projectRoot) {
|
|
173
237
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
174
|
-
if (!
|
|
238
|
+
if (!fs12__default.default.existsSync(featuresDir)) return [];
|
|
175
239
|
try {
|
|
176
|
-
const entries =
|
|
240
|
+
const entries = fs12__default.default.readdirSync(featuresDir, { withFileTypes: true });
|
|
177
241
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
178
242
|
} catch {
|
|
179
243
|
return [];
|
|
@@ -183,14 +247,14 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
183
247
|
const results = [];
|
|
184
248
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
185
249
|
for (const feat of features) {
|
|
186
|
-
const domainDir =
|
|
187
|
-
if (!
|
|
250
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
251
|
+
if (!fs12__default.default.existsSync(domainDir)) continue;
|
|
188
252
|
try {
|
|
189
|
-
const files =
|
|
253
|
+
const files = fs12__default.default.readdirSync(domainDir);
|
|
190
254
|
for (const file of files) {
|
|
191
255
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
192
256
|
const name = file.replace(/\.ts$/, "");
|
|
193
|
-
const fullPath =
|
|
257
|
+
const fullPath = path2__default.default.join(domainDir, file);
|
|
194
258
|
if (/^I[A-Z]/.test(name)) {
|
|
195
259
|
results.push({
|
|
196
260
|
name,
|
|
@@ -199,7 +263,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
199
263
|
});
|
|
200
264
|
} else {
|
|
201
265
|
try {
|
|
202
|
-
const content =
|
|
266
|
+
const content = fs12__default.default.readFileSync(fullPath, "utf-8");
|
|
203
267
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
204
268
|
results.push({
|
|
205
269
|
name,
|
|
@@ -221,17 +285,17 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
221
285
|
const results = [];
|
|
222
286
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
223
287
|
for (const feat of features) {
|
|
224
|
-
const domainDir =
|
|
225
|
-
if (!
|
|
288
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
289
|
+
if (!fs12__default.default.existsSync(domainDir)) continue;
|
|
226
290
|
try {
|
|
227
|
-
const files =
|
|
291
|
+
const files = fs12__default.default.readdirSync(domainDir);
|
|
228
292
|
for (const file of files) {
|
|
229
293
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
230
294
|
const name = file.replace(/\.ts$/, "");
|
|
231
295
|
if (!/^I[A-Z]/.test(name)) {
|
|
232
296
|
results.push({
|
|
233
297
|
name,
|
|
234
|
-
fullPath:
|
|
298
|
+
fullPath: path2__default.default.join(domainDir, file),
|
|
235
299
|
feature: feat
|
|
236
300
|
});
|
|
237
301
|
}
|
|
@@ -246,16 +310,16 @@ function listUseCases(projectRoot, featureName) {
|
|
|
246
310
|
const results = [];
|
|
247
311
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
248
312
|
for (const feat of features) {
|
|
249
|
-
const useCasesDir =
|
|
250
|
-
if (!
|
|
313
|
+
const useCasesDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
314
|
+
if (!fs12__default.default.existsSync(useCasesDir)) continue;
|
|
251
315
|
try {
|
|
252
|
-
const files =
|
|
316
|
+
const files = fs12__default.default.readdirSync(useCasesDir);
|
|
253
317
|
for (const file of files) {
|
|
254
318
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
255
319
|
const name = file.replace(/\.ts$/, "");
|
|
256
320
|
results.push({
|
|
257
321
|
name,
|
|
258
|
-
fullPath:
|
|
322
|
+
fullPath: path2__default.default.join(useCasesDir, file),
|
|
259
323
|
feature: feat
|
|
260
324
|
});
|
|
261
325
|
}
|
|
@@ -281,33 +345,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
281
345
|
});
|
|
282
346
|
var cachedTemplatesDir = null;
|
|
283
347
|
function getTemplatesDir() {
|
|
284
|
-
if (cachedTemplatesDir &&
|
|
348
|
+
if (cachedTemplatesDir && fs12__default.default.existsSync(cachedTemplatesDir)) {
|
|
285
349
|
return cachedTemplatesDir;
|
|
286
350
|
}
|
|
287
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
288
|
-
const cand1 =
|
|
289
|
-
if (
|
|
351
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
|
|
352
|
+
const cand1 = path2__default.default.resolve(currentDir, "../../templates");
|
|
353
|
+
if (fs12__default.default.existsSync(cand1)) {
|
|
290
354
|
cachedTemplatesDir = cand1;
|
|
291
355
|
return cand1;
|
|
292
356
|
}
|
|
293
|
-
const cand2 =
|
|
294
|
-
if (
|
|
357
|
+
const cand2 = path2__default.default.resolve(currentDir, "../templates");
|
|
358
|
+
if (fs12__default.default.existsSync(cand2)) {
|
|
295
359
|
cachedTemplatesDir = cand2;
|
|
296
360
|
return cand2;
|
|
297
361
|
}
|
|
298
|
-
const cand3 =
|
|
299
|
-
if (
|
|
362
|
+
const cand3 = path2__default.default.resolve(currentDir, "templates");
|
|
363
|
+
if (fs12__default.default.existsSync(cand3)) {
|
|
300
364
|
cachedTemplatesDir = cand3;
|
|
301
365
|
return cand3;
|
|
302
366
|
}
|
|
303
367
|
let curr = currentDir;
|
|
304
368
|
while (true) {
|
|
305
|
-
const candTemplates =
|
|
306
|
-
if (
|
|
369
|
+
const candTemplates = path2__default.default.join(curr, "templates");
|
|
370
|
+
if (fs12__default.default.existsSync(candTemplates)) {
|
|
307
371
|
cachedTemplatesDir = candTemplates;
|
|
308
372
|
return candTemplates;
|
|
309
373
|
}
|
|
310
|
-
const parent =
|
|
374
|
+
const parent = path2__default.default.dirname(curr);
|
|
311
375
|
if (parent === curr) break;
|
|
312
376
|
curr = parent;
|
|
313
377
|
}
|
|
@@ -315,8 +379,8 @@ function getTemplatesDir() {
|
|
|
315
379
|
}
|
|
316
380
|
var templateCache = /* @__PURE__ */ new Map();
|
|
317
381
|
function loadTemplate(relativePath) {
|
|
318
|
-
const fullPath =
|
|
319
|
-
return
|
|
382
|
+
const fullPath = path2__default.default.isAbsolute(relativePath) ? relativePath : path2__default.default.join(getTemplatesDir(), relativePath);
|
|
383
|
+
return fs12__default.default.readFileSync(fullPath, "utf-8");
|
|
320
384
|
}
|
|
321
385
|
function renderTemplateFile(relativePath, context) {
|
|
322
386
|
let compiled = templateCache.get(relativePath);
|
|
@@ -403,8 +467,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
403
467
|
function generateDomain(options) {
|
|
404
468
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
405
469
|
const entityName = toPascalCase(options.name);
|
|
406
|
-
const targetDir = options.targetDir ||
|
|
407
|
-
const filePath =
|
|
470
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
471
|
+
const filePath = path2__default.default.join(targetDir, `${entityName}.ts`);
|
|
408
472
|
const code = generateDomainEntityCode(
|
|
409
473
|
entityName,
|
|
410
474
|
options.kind || "interface",
|
|
@@ -428,7 +492,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
428
492
|
const idKey = `${entityKey}Id`;
|
|
429
493
|
const listKey = `${entityKey}s`;
|
|
430
494
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
431
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
495
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
|
|
432
496
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
433
497
|
return {
|
|
434
498
|
name: m.name,
|
|
@@ -453,8 +517,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
453
517
|
function generateInterface(options) {
|
|
454
518
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
455
519
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
456
|
-
const targetDir = options.targetDir ||
|
|
457
|
-
const filePath =
|
|
520
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
521
|
+
const filePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
458
522
|
const code = generateInterfaceCode(
|
|
459
523
|
ifaceName,
|
|
460
524
|
options.kind || "abstractClass",
|
|
@@ -689,7 +753,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
689
753
|
};
|
|
690
754
|
}
|
|
691
755
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
692
|
-
if (customPath &&
|
|
756
|
+
if (customPath && fs12__default.default.existsSync(customPath)) {
|
|
693
757
|
return { filePath: customPath, ifaceName };
|
|
694
758
|
}
|
|
695
759
|
const candidateNames = [
|
|
@@ -699,22 +763,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
699
763
|
];
|
|
700
764
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
701
765
|
for (const feat of features) {
|
|
702
|
-
const domainDir =
|
|
703
|
-
if (!
|
|
766
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
767
|
+
if (!fs12__default.default.existsSync(domainDir)) continue;
|
|
704
768
|
for (const cand of candidateNames) {
|
|
705
|
-
const p1 =
|
|
706
|
-
if (
|
|
769
|
+
const p1 = path2__default.default.join(domainDir, `${cand}.ts`);
|
|
770
|
+
if (fs12__default.default.existsSync(p1)) {
|
|
707
771
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
708
772
|
}
|
|
709
773
|
}
|
|
710
774
|
}
|
|
711
775
|
for (const cand of candidateNames) {
|
|
712
|
-
const p1 =
|
|
713
|
-
if (
|
|
714
|
-
const p22 =
|
|
715
|
-
if (
|
|
716
|
-
const p32 =
|
|
717
|
-
if (
|
|
776
|
+
const p1 = path2__default.default.join(projectRoot, "domain", `${cand}.ts`);
|
|
777
|
+
if (fs12__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
778
|
+
const p22 = path2__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
779
|
+
if (fs12__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
780
|
+
const p32 = path2__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
781
|
+
if (fs12__default.default.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
718
782
|
}
|
|
719
783
|
return null;
|
|
720
784
|
}
|
|
@@ -737,7 +801,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
737
801
|
while (currentFile && !visited.has(currentFile)) {
|
|
738
802
|
visited.add(currentFile);
|
|
739
803
|
try {
|
|
740
|
-
const content =
|
|
804
|
+
const content = fs12__default.default.readFileSync(currentFile, "utf-8");
|
|
741
805
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
742
806
|
for (const m of parsed.methods) {
|
|
743
807
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -813,7 +877,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
813
877
|
const entitiesVar = `${entityVar}s`;
|
|
814
878
|
const idKey = `${entityVar}Id`;
|
|
815
879
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
816
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
880
|
+
const entityImportPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
817
881
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
818
882
|
for (const imp of domainImports) {
|
|
819
883
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -862,7 +926,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
862
926
|
if (baseEntity) {
|
|
863
927
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
864
928
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
865
|
-
const importPath = ifaceImportPath ? `${
|
|
929
|
+
const importPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
866
930
|
finalDomainImports.push({
|
|
867
931
|
name: baseEntity,
|
|
868
932
|
from: importPath,
|
|
@@ -907,8 +971,8 @@ function generateInfrastructure(options) {
|
|
|
907
971
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
908
972
|
const className = toPascalCase(options.name);
|
|
909
973
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
910
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
911
|
-
const filePath =
|
|
974
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2__default.default.join(projectRoot, "infrastructure"));
|
|
975
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
912
976
|
let code;
|
|
913
977
|
if (options.implementsInterface) {
|
|
914
978
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -922,7 +986,7 @@ function generateInfrastructure(options) {
|
|
|
922
986
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
923
987
|
let ifaceImportPath;
|
|
924
988
|
if (ifaceFilePath) {
|
|
925
|
-
let rel =
|
|
989
|
+
let rel = path2__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
926
990
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
927
991
|
ifaceImportPath = rel;
|
|
928
992
|
} else if (isFeatureScope) {
|
|
@@ -935,8 +999,8 @@ function generateInfrastructure(options) {
|
|
|
935
999
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
936
1000
|
let fromPath;
|
|
937
1001
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
938
|
-
const absTarget =
|
|
939
|
-
let rel =
|
|
1002
|
+
const absTarget = path2__default.default.resolve(path2__default.default.dirname(ifaceFilePath), imp.specifier);
|
|
1003
|
+
let rel = path2__default.default.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
940
1004
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
941
1005
|
fromPath = rel;
|
|
942
1006
|
} else {
|
|
@@ -1009,22 +1073,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1009
1073
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1010
1074
|
}));
|
|
1011
1075
|
const domainTypesToImport = new Set(domainImports);
|
|
1012
|
-
for (const
|
|
1013
|
-
const rawType =
|
|
1076
|
+
for (const p17 of [...inputProps, ...outputProps]) {
|
|
1077
|
+
const rawType = p17.type.trim();
|
|
1014
1078
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1015
1079
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1016
1080
|
domainTypesToImport.add(baseType);
|
|
1017
1081
|
}
|
|
1018
1082
|
}
|
|
1019
|
-
const formattedInputProps = inputProps.map((
|
|
1020
|
-
name:
|
|
1021
|
-
optional:
|
|
1022
|
-
type:
|
|
1083
|
+
const formattedInputProps = inputProps.map((p17) => ({
|
|
1084
|
+
name: p17.name,
|
|
1085
|
+
optional: p17.optional,
|
|
1086
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1023
1087
|
}));
|
|
1024
|
-
const formattedOutputProps = outputProps.map((
|
|
1025
|
-
name:
|
|
1026
|
-
optional:
|
|
1027
|
-
type:
|
|
1088
|
+
const formattedOutputProps = outputProps.map((p17) => ({
|
|
1089
|
+
name: p17.name,
|
|
1090
|
+
optional: p17.optional,
|
|
1091
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1028
1092
|
}));
|
|
1029
1093
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1030
1094
|
key: toCamelCase(dep),
|
|
@@ -1123,8 +1187,8 @@ function generateUseCase(options) {
|
|
|
1123
1187
|
const files = [];
|
|
1124
1188
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1125
1189
|
const ucName = toPascalCase(options.name);
|
|
1126
|
-
const targetDir = options.targetDir ||
|
|
1127
|
-
const filePath =
|
|
1190
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1191
|
+
const filePath = path2__default.default.join(targetDir, `${ucName}.ts`);
|
|
1128
1192
|
const code = generateUseCaseCode(
|
|
1129
1193
|
ucName,
|
|
1130
1194
|
options.dependencies || [],
|
|
@@ -1140,7 +1204,7 @@ function generateUseCase(options) {
|
|
|
1140
1204
|
})
|
|
1141
1205
|
);
|
|
1142
1206
|
if (options.includeTest) {
|
|
1143
|
-
const testPath =
|
|
1207
|
+
const testPath = path2__default.default.join(targetDir, `${ucName}.test.ts`);
|
|
1144
1208
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1145
1209
|
files.push(
|
|
1146
1210
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1208,10 +1272,10 @@ function generateHttpHandler(options) {
|
|
|
1208
1272
|
const files = [];
|
|
1209
1273
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1210
1274
|
const className = toPascalCase(options.name);
|
|
1211
|
-
const pxDir =
|
|
1212
|
-
const handlersDir =
|
|
1213
|
-
const pxIndexPath =
|
|
1214
|
-
if (!
|
|
1275
|
+
const pxDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1276
|
+
const handlersDir = path2__default.default.join(pxDir, "handlers");
|
|
1277
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1278
|
+
if (!fs12__default.default.existsSync(pxIndexPath)) {
|
|
1215
1279
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1216
1280
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1217
1281
|
routePath: featurePath
|
|
@@ -1224,7 +1288,7 @@ function generateHttpHandler(options) {
|
|
|
1224
1288
|
})
|
|
1225
1289
|
);
|
|
1226
1290
|
}
|
|
1227
|
-
const handlerFilePath =
|
|
1291
|
+
const handlerFilePath = path2__default.default.join(handlersDir, `${className}.ts`);
|
|
1228
1292
|
const code = generateHttpHandlerCode(
|
|
1229
1293
|
className,
|
|
1230
1294
|
options.method || "get",
|
|
@@ -1245,11 +1309,11 @@ function generateHttpHandler(options) {
|
|
|
1245
1309
|
return files;
|
|
1246
1310
|
}
|
|
1247
1311
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1248
|
-
const providerPath =
|
|
1312
|
+
const providerPath = path2__default.default.join(featureDir, "diProvider.ts");
|
|
1249
1313
|
const pascalFeature = toPascalCase(featureName);
|
|
1250
1314
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1251
|
-
if (
|
|
1252
|
-
let content =
|
|
1315
|
+
if (fs12__default.default.existsSync(providerPath) && !options.overwrite) {
|
|
1316
|
+
let content = fs12__default.default.readFileSync(providerPath, "utf-8");
|
|
1253
1317
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1254
1318
|
for (const b of bindings) {
|
|
1255
1319
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1308,7 +1372,7 @@ function generateFeature(options) {
|
|
|
1308
1372
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1309
1373
|
const rawName = options.name.trim();
|
|
1310
1374
|
const featureName = toCamelCase(rawName);
|
|
1311
|
-
const targetDir = options.targetDir ||
|
|
1375
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1312
1376
|
const routePath = options.routePath || `/${featureName}`;
|
|
1313
1377
|
const rawEntities = [];
|
|
1314
1378
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1329,7 +1393,7 @@ function generateFeature(options) {
|
|
|
1329
1393
|
const domainFile = generateDomain({
|
|
1330
1394
|
name: ent,
|
|
1331
1395
|
feature: featureName,
|
|
1332
|
-
targetDir:
|
|
1396
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1333
1397
|
projectRoot,
|
|
1334
1398
|
dryRun: options.dryRun,
|
|
1335
1399
|
overwrite: options.overwrite,
|
|
@@ -1349,7 +1413,7 @@ function generateFeature(options) {
|
|
|
1349
1413
|
const ifaceFile = generateInterface({
|
|
1350
1414
|
name: ifaceName,
|
|
1351
1415
|
feature: featureName,
|
|
1352
|
-
targetDir:
|
|
1416
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1353
1417
|
entityName: ent,
|
|
1354
1418
|
projectRoot,
|
|
1355
1419
|
dryRun: options.dryRun,
|
|
@@ -1366,7 +1430,7 @@ function generateFeature(options) {
|
|
|
1366
1430
|
const stubFile = generateInfrastructure({
|
|
1367
1431
|
name: stubName,
|
|
1368
1432
|
feature: featureName,
|
|
1369
|
-
targetDir:
|
|
1433
|
+
targetDir: path2__default.default.join(targetDir, "infrastructure"),
|
|
1370
1434
|
kind: "stub",
|
|
1371
1435
|
implementsInterface: ifaceName,
|
|
1372
1436
|
entityName: ent,
|
|
@@ -1408,7 +1472,7 @@ function generateFeature(options) {
|
|
|
1408
1472
|
const ucFiles = generateUseCase({
|
|
1409
1473
|
name: ucName,
|
|
1410
1474
|
feature: featureName,
|
|
1411
|
-
targetDir:
|
|
1475
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1412
1476
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1413
1477
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1414
1478
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1453,7 +1517,7 @@ function generateFeature(options) {
|
|
|
1453
1517
|
const ucFiles = generateUseCase({
|
|
1454
1518
|
name: ucName,
|
|
1455
1519
|
feature: featureName,
|
|
1456
|
-
targetDir:
|
|
1520
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1457
1521
|
dependencies: deps,
|
|
1458
1522
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1459
1523
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1486,7 +1550,7 @@ function generateFeature(options) {
|
|
|
1486
1550
|
const ucFiles = generateUseCase({
|
|
1487
1551
|
name: ucName,
|
|
1488
1552
|
feature: featureName,
|
|
1489
|
-
targetDir:
|
|
1553
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1490
1554
|
dependencies: deps,
|
|
1491
1555
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1492
1556
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1519,7 +1583,7 @@ function generateFeature(options) {
|
|
|
1519
1583
|
const ucFiles = generateUseCase({
|
|
1520
1584
|
name: ucName,
|
|
1521
1585
|
feature: featureName,
|
|
1522
|
-
targetDir:
|
|
1586
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1523
1587
|
dependencies: deps,
|
|
1524
1588
|
inputProperties: [
|
|
1525
1589
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1563,7 +1627,7 @@ function generateFeature(options) {
|
|
|
1563
1627
|
const ucFiles = generateUseCase({
|
|
1564
1628
|
name: ucName,
|
|
1565
1629
|
feature: featureName,
|
|
1566
|
-
targetDir:
|
|
1630
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1567
1631
|
dependencies: deps,
|
|
1568
1632
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1569
1633
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1596,7 +1660,7 @@ function generateFeature(options) {
|
|
|
1596
1660
|
const ucFiles = generateUseCase({
|
|
1597
1661
|
name: ucName,
|
|
1598
1662
|
feature: featureName,
|
|
1599
|
-
targetDir:
|
|
1663
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1600
1664
|
dependencies: deps,
|
|
1601
1665
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1602
1666
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1633,7 +1697,7 @@ function generateFeature(options) {
|
|
|
1633
1697
|
const ucFiles = generateUseCase({
|
|
1634
1698
|
name: ucName,
|
|
1635
1699
|
feature: featureName,
|
|
1636
|
-
targetDir:
|
|
1700
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1637
1701
|
dependencies: customUc.dependencies || [],
|
|
1638
1702
|
inputProperties: customUc.inputProperties || [],
|
|
1639
1703
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1665,8 +1729,8 @@ function generateFeature(options) {
|
|
|
1665
1729
|
}
|
|
1666
1730
|
}
|
|
1667
1731
|
if (options.includeExpress !== false) {
|
|
1668
|
-
const pxDir =
|
|
1669
|
-
const pxIndexPath =
|
|
1732
|
+
const pxDir = path2__default.default.join(targetDir, "pxExpress");
|
|
1733
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1670
1734
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1671
1735
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1672
1736
|
files.push(
|
|
@@ -1695,23 +1759,23 @@ var PromptCancelledError = class extends Error {
|
|
|
1695
1759
|
}
|
|
1696
1760
|
};
|
|
1697
1761
|
function handleCancel(result) {
|
|
1698
|
-
if (
|
|
1699
|
-
|
|
1762
|
+
if (p15__namespace.isCancel(result)) {
|
|
1763
|
+
p15__namespace.cancel("Operation cancelled.");
|
|
1700
1764
|
throw new PromptCancelledError();
|
|
1701
1765
|
}
|
|
1702
1766
|
return result;
|
|
1703
1767
|
}
|
|
1704
1768
|
function printBanner() {
|
|
1705
1769
|
console.log("");
|
|
1706
|
-
|
|
1770
|
+
p15__namespace.intro(pc15__default.default.bgCyan(pc15__default.default.black(" @solid-stack/mason ")));
|
|
1707
1771
|
}
|
|
1708
1772
|
async function promptProperties(options = {}) {
|
|
1709
1773
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
1710
1774
|
if (properties.length > 0) {
|
|
1711
|
-
const summary = properties.map((
|
|
1712
|
-
|
|
1775
|
+
const summary = properties.map((p17) => `${p17.name}: ${p17.type}${p17.optional ? "?" : ""}`).join(", ");
|
|
1776
|
+
p15__namespace.log.info(pc15__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
1713
1777
|
const addMore = handleCancel(
|
|
1714
|
-
await
|
|
1778
|
+
await p15__namespace.confirm({
|
|
1715
1779
|
message: "Would you like to add custom fields/properties?",
|
|
1716
1780
|
initialValue: false
|
|
1717
1781
|
})
|
|
@@ -1720,10 +1784,10 @@ async function promptProperties(options = {}) {
|
|
|
1720
1784
|
return properties;
|
|
1721
1785
|
}
|
|
1722
1786
|
}
|
|
1723
|
-
|
|
1787
|
+
p15__namespace.log.info(pc15__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
1724
1788
|
while (true) {
|
|
1725
1789
|
const name = handleCancel(
|
|
1726
|
-
await
|
|
1790
|
+
await p15__namespace.text({
|
|
1727
1791
|
message: "Property name (or press Enter to finish):",
|
|
1728
1792
|
placeholder: "e.g. title, email, status"
|
|
1729
1793
|
})
|
|
@@ -1732,7 +1796,7 @@ async function promptProperties(options = {}) {
|
|
|
1732
1796
|
break;
|
|
1733
1797
|
}
|
|
1734
1798
|
const type = handleCancel(
|
|
1735
|
-
await
|
|
1799
|
+
await p15__namespace.select({
|
|
1736
1800
|
message: `Type for property "${name.trim()}":`,
|
|
1737
1801
|
options: [
|
|
1738
1802
|
{ value: "string", label: "string" },
|
|
@@ -1749,7 +1813,7 @@ async function promptProperties(options = {}) {
|
|
|
1749
1813
|
let finalType = type;
|
|
1750
1814
|
if (type === "custom") {
|
|
1751
1815
|
finalType = handleCancel(
|
|
1752
|
-
await
|
|
1816
|
+
await p15__namespace.text({
|
|
1753
1817
|
message: `Enter custom type for "${name.trim()}":`,
|
|
1754
1818
|
placeholder: "e.g. UserRole, string | null",
|
|
1755
1819
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -1757,7 +1821,7 @@ async function promptProperties(options = {}) {
|
|
|
1757
1821
|
);
|
|
1758
1822
|
}
|
|
1759
1823
|
const optional = handleCancel(
|
|
1760
|
-
await
|
|
1824
|
+
await p15__namespace.confirm({
|
|
1761
1825
|
message: `Is "${name.trim()}" optional?`,
|
|
1762
1826
|
initialValue: false
|
|
1763
1827
|
})
|
|
@@ -1767,8 +1831,8 @@ async function promptProperties(options = {}) {
|
|
|
1767
1831
|
type: finalType.trim(),
|
|
1768
1832
|
optional
|
|
1769
1833
|
});
|
|
1770
|
-
|
|
1771
|
-
|
|
1834
|
+
p15__namespace.log.success(
|
|
1835
|
+
pc15__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1772
1836
|
);
|
|
1773
1837
|
}
|
|
1774
1838
|
return properties;
|
|
@@ -1777,13 +1841,14 @@ async function promptProperties(options = {}) {
|
|
|
1777
1841
|
// src/commands/feature.ts
|
|
1778
1842
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
1779
1843
|
const projectRoot = findProjectRoot();
|
|
1844
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
1780
1845
|
let name = featureNameArg?.trim();
|
|
1781
1846
|
if (!name && flags.yes) {
|
|
1782
1847
|
name = "feature";
|
|
1783
1848
|
}
|
|
1784
1849
|
if (!name) {
|
|
1785
1850
|
name = handleCancel(
|
|
1786
|
-
await
|
|
1851
|
+
await p15__namespace.text({
|
|
1787
1852
|
message: "What is the feature name?",
|
|
1788
1853
|
placeholder: "e.g. users, auth, orders, billing",
|
|
1789
1854
|
validate: (val) => {
|
|
@@ -1794,18 +1859,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1794
1859
|
).trim();
|
|
1795
1860
|
}
|
|
1796
1861
|
const normalizedName = toCamelCase(name);
|
|
1797
|
-
const
|
|
1862
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
1863
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseFeaturesDir, normalizedName));
|
|
1798
1864
|
let targetDirRel = defaultDir;
|
|
1799
|
-
if (!flags.yes && !flags.target) {
|
|
1865
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1800
1866
|
targetDirRel = handleCancel(
|
|
1801
|
-
await
|
|
1867
|
+
await p15__namespace.text({
|
|
1802
1868
|
message: "Target directory for the feature:",
|
|
1803
1869
|
initialValue: defaultDir,
|
|
1804
1870
|
defaultValue: defaultDir
|
|
1805
1871
|
})
|
|
1806
1872
|
).trim();
|
|
1807
1873
|
}
|
|
1808
|
-
const targetDir = flags.target ?
|
|
1874
|
+
const targetDir = flags.dest ? path2__default.default.resolve(projectRoot, flags.dest) : flags.target ? path2__default.default.resolve(projectRoot, flags.target) : path2__default.default.resolve(projectRoot, targetDirRel);
|
|
1809
1875
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
1810
1876
|
let routePath = `/${normalizedName}`;
|
|
1811
1877
|
let includeDomain = true;
|
|
@@ -1820,14 +1886,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1820
1886
|
const customUseCases = [];
|
|
1821
1887
|
if (!flags.yes) {
|
|
1822
1888
|
includeExpress = handleCancel(
|
|
1823
|
-
await
|
|
1889
|
+
await p15__namespace.confirm({
|
|
1824
1890
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
1825
1891
|
initialValue: true
|
|
1826
1892
|
})
|
|
1827
1893
|
);
|
|
1828
1894
|
if (includeExpress) {
|
|
1829
1895
|
routePath = handleCancel(
|
|
1830
|
-
await
|
|
1896
|
+
await p15__namespace.text({
|
|
1831
1897
|
message: "Express route prefix:",
|
|
1832
1898
|
initialValue: `/${normalizedName}`,
|
|
1833
1899
|
defaultValue: `/${normalizedName}`
|
|
@@ -1835,14 +1901,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1835
1901
|
).trim();
|
|
1836
1902
|
}
|
|
1837
1903
|
includeDomain = handleCancel(
|
|
1838
|
-
await
|
|
1904
|
+
await p15__namespace.confirm({
|
|
1839
1905
|
message: "Include Domain Entity/Entities?",
|
|
1840
1906
|
initialValue: true
|
|
1841
1907
|
})
|
|
1842
1908
|
);
|
|
1843
1909
|
if (includeDomain) {
|
|
1844
1910
|
const domainInput = handleCancel(
|
|
1845
|
-
await
|
|
1911
|
+
await p15__namespace.text({
|
|
1846
1912
|
message: "Domain entity name(s) (comma-separated):",
|
|
1847
1913
|
initialValue: defaultEntity,
|
|
1848
1914
|
defaultValue: defaultEntity,
|
|
@@ -1861,21 +1927,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1861
1927
|
entityNames = [defaultEntity];
|
|
1862
1928
|
}
|
|
1863
1929
|
includeInterface = handleCancel(
|
|
1864
|
-
await
|
|
1930
|
+
await p15__namespace.confirm({
|
|
1865
1931
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
1866
1932
|
initialValue: true
|
|
1867
1933
|
})
|
|
1868
1934
|
);
|
|
1869
1935
|
if (includeInterface) {
|
|
1870
1936
|
includeInfra = handleCancel(
|
|
1871
|
-
await
|
|
1937
|
+
await p15__namespace.confirm({
|
|
1872
1938
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
1873
1939
|
initialValue: true
|
|
1874
1940
|
})
|
|
1875
1941
|
);
|
|
1876
1942
|
}
|
|
1877
1943
|
includeUseCase = handleCancel(
|
|
1878
|
-
await
|
|
1944
|
+
await p15__namespace.confirm({
|
|
1879
1945
|
message: "Include Domain Use Cases?",
|
|
1880
1946
|
initialValue: true
|
|
1881
1947
|
})
|
|
@@ -1889,7 +1955,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1889
1955
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
1890
1956
|
];
|
|
1891
1957
|
crudOperations = handleCancel(
|
|
1892
|
-
await
|
|
1958
|
+
await p15__namespace.multiselect({
|
|
1893
1959
|
message: "Select standard CRUD use cases to generate:",
|
|
1894
1960
|
options: crudChoices,
|
|
1895
1961
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -1897,13 +1963,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1897
1963
|
})
|
|
1898
1964
|
);
|
|
1899
1965
|
includeTests = handleCancel(
|
|
1900
|
-
await
|
|
1966
|
+
await p15__namespace.confirm({
|
|
1901
1967
|
message: "Generate Vitest unit tests for use cases?",
|
|
1902
1968
|
initialValue: true
|
|
1903
1969
|
})
|
|
1904
1970
|
);
|
|
1905
1971
|
const wantCustom = handleCancel(
|
|
1906
|
-
await
|
|
1972
|
+
await p15__namespace.confirm({
|
|
1907
1973
|
message: "Do you want to add any custom use cases to this feature?",
|
|
1908
1974
|
initialValue: false
|
|
1909
1975
|
})
|
|
@@ -1913,7 +1979,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1913
1979
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
1914
1980
|
while (addMore) {
|
|
1915
1981
|
const customName = handleCancel(
|
|
1916
|
-
await
|
|
1982
|
+
await p15__namespace.text({
|
|
1917
1983
|
message: "Custom Use Case name:",
|
|
1918
1984
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
1919
1985
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -1923,7 +1989,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1923
1989
|
let selectedDeps = [];
|
|
1924
1990
|
if (availableIfaces.length > 0) {
|
|
1925
1991
|
selectedDeps = handleCancel(
|
|
1926
|
-
await
|
|
1992
|
+
await p15__namespace.multiselect({
|
|
1927
1993
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
1928
1994
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
1929
1995
|
required: false
|
|
@@ -1932,7 +1998,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1932
1998
|
}
|
|
1933
1999
|
let customInputProps = [];
|
|
1934
2000
|
const hasInput = handleCancel(
|
|
1935
|
-
await
|
|
2001
|
+
await p15__namespace.confirm({
|
|
1936
2002
|
message: `Does ${customUcName} accept input arguments?`,
|
|
1937
2003
|
initialValue: true
|
|
1938
2004
|
})
|
|
@@ -1943,7 +2009,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1943
2009
|
}
|
|
1944
2010
|
let customOutputProps = [];
|
|
1945
2011
|
const hasCustomOutput = handleCancel(
|
|
1946
|
-
await
|
|
2012
|
+
await p15__namespace.confirm({
|
|
1947
2013
|
message: `Does ${customUcName} return custom output data?`,
|
|
1948
2014
|
initialValue: false
|
|
1949
2015
|
})
|
|
@@ -1953,7 +2019,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1953
2019
|
});
|
|
1954
2020
|
}
|
|
1955
2021
|
const customIncludeTest = handleCancel(
|
|
1956
|
-
await
|
|
2022
|
+
await p15__namespace.confirm({
|
|
1957
2023
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
1958
2024
|
initialValue: true
|
|
1959
2025
|
})
|
|
@@ -1963,14 +2029,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1963
2029
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
1964
2030
|
if (includeExpress) {
|
|
1965
2031
|
customCreateHttp = handleCancel(
|
|
1966
|
-
await
|
|
2032
|
+
await p15__namespace.confirm({
|
|
1967
2033
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
1968
2034
|
initialValue: true
|
|
1969
2035
|
})
|
|
1970
2036
|
);
|
|
1971
2037
|
if (customCreateHttp) {
|
|
1972
2038
|
customHttpMethod = handleCancel(
|
|
1973
|
-
await
|
|
2039
|
+
await p15__namespace.select({
|
|
1974
2040
|
message: "HTTP method for route:",
|
|
1975
2041
|
options: [
|
|
1976
2042
|
{ value: "post", label: "POST" },
|
|
@@ -1983,7 +2049,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1983
2049
|
})
|
|
1984
2050
|
);
|
|
1985
2051
|
customHttpPath = handleCancel(
|
|
1986
|
-
await
|
|
2052
|
+
await p15__namespace.text({
|
|
1987
2053
|
message: "Route subpath (relative to feature prefix):",
|
|
1988
2054
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
1989
2055
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2004,7 +2070,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2004
2070
|
validationFields: customInputProps
|
|
2005
2071
|
});
|
|
2006
2072
|
addMore = handleCancel(
|
|
2007
|
-
await
|
|
2073
|
+
await p15__namespace.confirm({
|
|
2008
2074
|
message: "Add another custom use case?",
|
|
2009
2075
|
initialValue: false
|
|
2010
2076
|
})
|
|
@@ -2013,7 +2079,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2013
2079
|
}
|
|
2014
2080
|
}
|
|
2015
2081
|
includeDiProvider = handleCancel(
|
|
2016
|
-
await
|
|
2082
|
+
await p15__namespace.confirm({
|
|
2017
2083
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2018
2084
|
initialValue: true
|
|
2019
2085
|
})
|
|
@@ -2025,8 +2091,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2025
2091
|
entityNames = [defaultEntity];
|
|
2026
2092
|
}
|
|
2027
2093
|
}
|
|
2028
|
-
const s =
|
|
2029
|
-
s.start(`Generating feature '${
|
|
2094
|
+
const s = p15__namespace.spinner();
|
|
2095
|
+
s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
|
|
2030
2096
|
const options = {
|
|
2031
2097
|
name: normalizedName,
|
|
2032
2098
|
projectRoot,
|
|
@@ -2046,9 +2112,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2046
2112
|
includeDiProvider
|
|
2047
2113
|
};
|
|
2048
2114
|
const files = generateFeature(options);
|
|
2115
|
+
if (isRegistry) {
|
|
2116
|
+
const registryPath = path2__default.default.join(targetDir, "registry.json");
|
|
2117
|
+
if (!fs12__default.default.existsSync(registryPath)) {
|
|
2118
|
+
const manifest = {
|
|
2119
|
+
name: toKebabCase(normalizedName),
|
|
2120
|
+
type: "feature",
|
|
2121
|
+
description: flags.description || `${normalizedName} feature module`,
|
|
2122
|
+
version: "1.0.0",
|
|
2123
|
+
dependencies: {
|
|
2124
|
+
npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
|
|
2125
|
+
shared: [],
|
|
2126
|
+
features: []
|
|
2127
|
+
},
|
|
2128
|
+
files: []
|
|
2129
|
+
};
|
|
2130
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2131
|
+
const genFile = writeFileSafely(registryPath, content, {
|
|
2132
|
+
projectRoot,
|
|
2133
|
+
overwrite: options.overwrite,
|
|
2134
|
+
dryRun: options.dryRun
|
|
2135
|
+
});
|
|
2136
|
+
files.push(genFile);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2049
2139
|
s.stop("Feature scaffolded successfully!");
|
|
2050
2140
|
printGeneratedFiles(files, options.dryRun);
|
|
2051
|
-
|
|
2141
|
+
p15__namespace.outro(
|
|
2142
|
+
pc15__default.default.green(
|
|
2143
|
+
`\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2144
|
+
)
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
function generateShared(options) {
|
|
2148
|
+
const files = [];
|
|
2149
|
+
const projectRoot = options.projectRoot || findProjectRoot();
|
|
2150
|
+
const rawName = options.name.trim();
|
|
2151
|
+
const pascalName = toPascalCase(rawName);
|
|
2152
|
+
const camelName = toCamelCase(rawName);
|
|
2153
|
+
const kebabName = toKebabCase(rawName);
|
|
2154
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveSharedDir(projectRoot), kebabName);
|
|
2155
|
+
const fileDefs = [];
|
|
2156
|
+
const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
|
|
2157
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2158
|
+
|
|
2159
|
+
@MakeInjectable
|
|
2160
|
+
export class ${pascalName} {
|
|
2161
|
+
public static deps = { ${camelName}Engine: I${pascalName}Engine };
|
|
2162
|
+
constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
|
|
2163
|
+
}
|
|
2164
|
+
`;
|
|
2165
|
+
fileDefs.push({
|
|
2166
|
+
absolutePath: path2__default.default.join(targetDir, `${pascalName}.ts`),
|
|
2167
|
+
content: facadeContent
|
|
2168
|
+
});
|
|
2169
|
+
const portContent = `export abstract class I${pascalName}Engine {
|
|
2170
|
+
// Define abstract port contracts for ${pascalName} here
|
|
2171
|
+
}
|
|
2172
|
+
`;
|
|
2173
|
+
fileDefs.push({
|
|
2174
|
+
absolutePath: path2__default.default.join(targetDir, "ports", `I${pascalName}Engine.ts`),
|
|
2175
|
+
content: portContent
|
|
2176
|
+
});
|
|
2177
|
+
const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2178
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2179
|
+
|
|
2180
|
+
@MakeInjectable
|
|
2181
|
+
export class Default${pascalName}Engine implements I${pascalName}Engine {
|
|
2182
|
+
// Default concrete implementation for production
|
|
2183
|
+
}
|
|
2184
|
+
`;
|
|
2185
|
+
fileDefs.push({
|
|
2186
|
+
absolutePath: path2__default.default.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
|
|
2187
|
+
content: concreteContent
|
|
2188
|
+
});
|
|
2189
|
+
if (options.includeStub !== false) {
|
|
2190
|
+
const stubContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2191
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2192
|
+
|
|
2193
|
+
@MakeInjectable
|
|
2194
|
+
export class Stub${pascalName}Engine implements I${pascalName}Engine {
|
|
2195
|
+
// In-memory or stub implementation for testing
|
|
2196
|
+
}
|
|
2197
|
+
`;
|
|
2198
|
+
fileDefs.push({
|
|
2199
|
+
absolutePath: path2__default.default.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
|
|
2200
|
+
content: stubContent
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
const providerContent = `import type { Container } from "@solid-stack/di";
|
|
2204
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2205
|
+
import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
|
|
2206
|
+
|
|
2207
|
+
export const diProvider = (c: Container): void => {
|
|
2208
|
+
c.provide(I${pascalName}Engine, Default${pascalName}Engine);
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
export default diProvider;
|
|
2212
|
+
`;
|
|
2213
|
+
fileDefs.push({
|
|
2214
|
+
absolutePath: path2__default.default.join(targetDir, "diProvider.ts"),
|
|
2215
|
+
content: providerContent
|
|
2216
|
+
});
|
|
2217
|
+
if (options.isRegistry) {
|
|
2218
|
+
const registryManifest = {
|
|
2219
|
+
name: kebabName,
|
|
2220
|
+
type: "shared",
|
|
2221
|
+
description: options.description || `${kebabName} shared module`,
|
|
2222
|
+
version: "1.0.0",
|
|
2223
|
+
dependencies: {
|
|
2224
|
+
npm: [],
|
|
2225
|
+
shared: [],
|
|
2226
|
+
features: []
|
|
2227
|
+
},
|
|
2228
|
+
files: []
|
|
2229
|
+
};
|
|
2230
|
+
fileDefs.push({
|
|
2231
|
+
absolutePath: path2__default.default.join(targetDir, "registry.json"),
|
|
2232
|
+
content: JSON.stringify(registryManifest, null, 2) + "\n"
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
for (const def of fileDefs) {
|
|
2236
|
+
const genFile = writeFileSafely(def.absolutePath, def.content, {
|
|
2237
|
+
projectRoot,
|
|
2238
|
+
overwrite: options.overwrite,
|
|
2239
|
+
dryRun: options.dryRun
|
|
2240
|
+
});
|
|
2241
|
+
files.push(genFile);
|
|
2242
|
+
}
|
|
2243
|
+
return files;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// src/commands/shared.ts
|
|
2247
|
+
async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
2248
|
+
const projectRoot = findProjectRoot();
|
|
2249
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2250
|
+
let name = sharedNameArg?.trim();
|
|
2251
|
+
if (!name && flags.yes) {
|
|
2252
|
+
name = "shared";
|
|
2253
|
+
}
|
|
2254
|
+
if (!name) {
|
|
2255
|
+
name = handleCancel(
|
|
2256
|
+
await p15__namespace.text({
|
|
2257
|
+
message: "What is the shared module name?",
|
|
2258
|
+
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2259
|
+
validate: (val) => {
|
|
2260
|
+
if (!val || !val.trim()) return "Shared module name cannot be empty.";
|
|
2261
|
+
return void 0;
|
|
2262
|
+
}
|
|
2263
|
+
})
|
|
2264
|
+
).trim();
|
|
2265
|
+
}
|
|
2266
|
+
const normalizedName = toKebabCase(name);
|
|
2267
|
+
const baseSharedDir = resolveSharedDir(projectRoot, flags);
|
|
2268
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseSharedDir, normalizedName));
|
|
2269
|
+
let targetDirRel = defaultDir;
|
|
2270
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2271
|
+
targetDirRel = handleCancel(
|
|
2272
|
+
await p15__namespace.text({
|
|
2273
|
+
message: "Target directory for the shared module:",
|
|
2274
|
+
initialValue: defaultDir,
|
|
2275
|
+
defaultValue: defaultDir
|
|
2276
|
+
})
|
|
2277
|
+
).trim();
|
|
2278
|
+
}
|
|
2279
|
+
const targetDir = flags.dest ? path2__default.default.resolve(projectRoot, flags.dest) : flags.target ? path2__default.default.resolve(projectRoot, flags.target) : path2__default.default.resolve(projectRoot, targetDirRel);
|
|
2280
|
+
let description = flags.description || "";
|
|
2281
|
+
let includeStub = true;
|
|
2282
|
+
if (!flags.yes) {
|
|
2283
|
+
description = handleCancel(
|
|
2284
|
+
await p15__namespace.text({
|
|
2285
|
+
message: "Module description (optional):",
|
|
2286
|
+
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2287
|
+
initialValue: ""
|
|
2288
|
+
})
|
|
2289
|
+
).trim();
|
|
2290
|
+
includeStub = handleCancel(
|
|
2291
|
+
await p15__namespace.confirm({
|
|
2292
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2293
|
+
initialValue: true
|
|
2294
|
+
})
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
const s = p15__namespace.spinner();
|
|
2298
|
+
s.start(`Generating shared module '${pc15__default.default.cyan(normalizedName)}'...`);
|
|
2299
|
+
const files = generateShared({
|
|
2300
|
+
name: normalizedName,
|
|
2301
|
+
description,
|
|
2302
|
+
projectRoot,
|
|
2303
|
+
targetDir,
|
|
2304
|
+
dryRun: Boolean(flags.dryRun),
|
|
2305
|
+
overwrite: Boolean(flags.overwrite),
|
|
2306
|
+
includeStub,
|
|
2307
|
+
isRegistry
|
|
2308
|
+
});
|
|
2309
|
+
s.stop("Shared module generated successfully!");
|
|
2310
|
+
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2311
|
+
p15__namespace.outro(
|
|
2312
|
+
pc15__default.default.green(
|
|
2313
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2314
|
+
)
|
|
2315
|
+
);
|
|
2052
2316
|
}
|
|
2053
2317
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2054
2318
|
const projectRoot = findProjectRoot();
|
|
@@ -2059,14 +2323,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2059
2323
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2060
2324
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2061
2325
|
const selected = handleCancel(
|
|
2062
|
-
await
|
|
2326
|
+
await p15__namespace.select({
|
|
2063
2327
|
message: "Select target feature:",
|
|
2064
2328
|
options: featureOptions
|
|
2065
2329
|
})
|
|
2066
2330
|
);
|
|
2067
2331
|
if (selected === "__custom__") {
|
|
2068
2332
|
feature = handleCancel(
|
|
2069
|
-
await
|
|
2333
|
+
await p15__namespace.text({
|
|
2070
2334
|
message: "Enter feature name:",
|
|
2071
2335
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2072
2336
|
})
|
|
@@ -2076,7 +2340,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2076
2340
|
}
|
|
2077
2341
|
} else {
|
|
2078
2342
|
feature = handleCancel(
|
|
2079
|
-
await
|
|
2343
|
+
await p15__namespace.text({
|
|
2080
2344
|
message: "Enter target feature name:",
|
|
2081
2345
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2082
2346
|
})
|
|
@@ -2087,7 +2351,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2087
2351
|
let name = useCaseNameArg?.trim();
|
|
2088
2352
|
if (!name) {
|
|
2089
2353
|
name = handleCancel(
|
|
2090
|
-
await
|
|
2354
|
+
await p15__namespace.text({
|
|
2091
2355
|
message: "Use Case name:",
|
|
2092
2356
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2093
2357
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2103,7 +2367,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2103
2367
|
label: i.name
|
|
2104
2368
|
}));
|
|
2105
2369
|
selectedDeps = handleCancel(
|
|
2106
|
-
await
|
|
2370
|
+
await p15__namespace.multiselect({
|
|
2107
2371
|
message: "Select domain interfaces to inject into use case:",
|
|
2108
2372
|
options: multiOptions,
|
|
2109
2373
|
required: false
|
|
@@ -2118,7 +2382,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2118
2382
|
let httpPath = "/";
|
|
2119
2383
|
if (!flags.yes) {
|
|
2120
2384
|
const hasInput = handleCancel(
|
|
2121
|
-
await
|
|
2385
|
+
await p15__namespace.confirm({
|
|
2122
2386
|
message: "Does this use case accept input arguments?",
|
|
2123
2387
|
initialValue: true
|
|
2124
2388
|
})
|
|
@@ -2128,7 +2392,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2128
2392
|
});
|
|
2129
2393
|
}
|
|
2130
2394
|
const hasCustomOutput = handleCancel(
|
|
2131
|
-
await
|
|
2395
|
+
await p15__namespace.confirm({
|
|
2132
2396
|
message: "Does this use case return custom output data?",
|
|
2133
2397
|
initialValue: false
|
|
2134
2398
|
})
|
|
@@ -2138,20 +2402,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2138
2402
|
});
|
|
2139
2403
|
}
|
|
2140
2404
|
includeTest = handleCancel(
|
|
2141
|
-
await
|
|
2405
|
+
await p15__namespace.confirm({
|
|
2142
2406
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2143
2407
|
initialValue: true
|
|
2144
2408
|
})
|
|
2145
2409
|
);
|
|
2146
2410
|
createHttp = handleCancel(
|
|
2147
|
-
await
|
|
2411
|
+
await p15__namespace.confirm({
|
|
2148
2412
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2149
2413
|
initialValue: false
|
|
2150
2414
|
})
|
|
2151
2415
|
);
|
|
2152
2416
|
if (createHttp) {
|
|
2153
2417
|
httpMethod = handleCancel(
|
|
2154
|
-
await
|
|
2418
|
+
await p15__namespace.select({
|
|
2155
2419
|
message: "HTTP method for route:",
|
|
2156
2420
|
options: [
|
|
2157
2421
|
{ value: "post", label: "POST" },
|
|
@@ -2164,7 +2428,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2164
2428
|
})
|
|
2165
2429
|
);
|
|
2166
2430
|
httpPath = handleCancel(
|
|
2167
|
-
await
|
|
2431
|
+
await p15__namespace.text({
|
|
2168
2432
|
message: "Route subpath (relative to feature prefix):",
|
|
2169
2433
|
initialValue: "/",
|
|
2170
2434
|
defaultValue: "/"
|
|
@@ -2172,8 +2436,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2172
2436
|
).trim();
|
|
2173
2437
|
}
|
|
2174
2438
|
}
|
|
2175
|
-
const s =
|
|
2176
|
-
s.start(`Generating use case '${
|
|
2439
|
+
const s = p15__namespace.spinner();
|
|
2440
|
+
s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
|
|
2177
2441
|
const options = {
|
|
2178
2442
|
name: ucName,
|
|
2179
2443
|
feature,
|
|
@@ -2205,7 +2469,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2205
2469
|
}
|
|
2206
2470
|
s.stop("Use case generated successfully!");
|
|
2207
2471
|
printGeneratedFiles(files, options.dryRun);
|
|
2208
|
-
|
|
2472
|
+
p15__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2209
2473
|
}
|
|
2210
2474
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2211
2475
|
const projectRoot = findProjectRoot();
|
|
@@ -2216,14 +2480,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2216
2480
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2217
2481
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2218
2482
|
const selected = handleCancel(
|
|
2219
|
-
await
|
|
2483
|
+
await p15__namespace.select({
|
|
2220
2484
|
message: "Select target feature:",
|
|
2221
2485
|
options: featureOptions
|
|
2222
2486
|
})
|
|
2223
2487
|
);
|
|
2224
2488
|
if (selected === "__custom__") {
|
|
2225
2489
|
feature = handleCancel(
|
|
2226
|
-
await
|
|
2490
|
+
await p15__namespace.text({
|
|
2227
2491
|
message: "Enter feature name:",
|
|
2228
2492
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2229
2493
|
})
|
|
@@ -2233,7 +2497,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2233
2497
|
}
|
|
2234
2498
|
} else {
|
|
2235
2499
|
feature = handleCancel(
|
|
2236
|
-
await
|
|
2500
|
+
await p15__namespace.text({
|
|
2237
2501
|
message: "Enter target feature name:",
|
|
2238
2502
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2239
2503
|
})
|
|
@@ -2244,7 +2508,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2244
2508
|
let name = domainNameArg?.trim();
|
|
2245
2509
|
if (!name) {
|
|
2246
2510
|
name = handleCancel(
|
|
2247
|
-
await
|
|
2511
|
+
await p15__namespace.text({
|
|
2248
2512
|
message: "Domain model / entity name:",
|
|
2249
2513
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2250
2514
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2256,7 +2520,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2256
2520
|
let properties = [];
|
|
2257
2521
|
if (!flags.yes) {
|
|
2258
2522
|
kind = handleCancel(
|
|
2259
|
-
await
|
|
2523
|
+
await p15__namespace.select({
|
|
2260
2524
|
message: "Select domain model type:",
|
|
2261
2525
|
options: [
|
|
2262
2526
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2275,8 +2539,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2275
2539
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2276
2540
|
});
|
|
2277
2541
|
}
|
|
2278
|
-
const s =
|
|
2279
|
-
s.start(`Generating domain model '${
|
|
2542
|
+
const s = p15__namespace.spinner();
|
|
2543
|
+
s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
|
|
2280
2544
|
const options = {
|
|
2281
2545
|
name: entityName,
|
|
2282
2546
|
feature,
|
|
@@ -2289,7 +2553,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2289
2553
|
const file = generateDomain(options);
|
|
2290
2554
|
s.stop("Domain model generated successfully!");
|
|
2291
2555
|
printGeneratedFiles([file], options.dryRun);
|
|
2292
|
-
|
|
2556
|
+
p15__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2293
2557
|
}
|
|
2294
2558
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2295
2559
|
const projectRoot = findProjectRoot();
|
|
@@ -2298,7 +2562,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2298
2562
|
let feature = flags.feature?.trim();
|
|
2299
2563
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2300
2564
|
scope = handleCancel(
|
|
2301
|
-
await
|
|
2565
|
+
await p15__namespace.select({
|
|
2302
2566
|
message: "Where should this infrastructure implementation be placed?",
|
|
2303
2567
|
options: [
|
|
2304
2568
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2313,14 +2577,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2313
2577
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2314
2578
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2315
2579
|
const selected = handleCancel(
|
|
2316
|
-
await
|
|
2580
|
+
await p15__namespace.select({
|
|
2317
2581
|
message: "Select target feature:",
|
|
2318
2582
|
options: featureOptions
|
|
2319
2583
|
})
|
|
2320
2584
|
);
|
|
2321
2585
|
if (selected === "__custom__") {
|
|
2322
2586
|
feature = handleCancel(
|
|
2323
|
-
await
|
|
2587
|
+
await p15__namespace.text({
|
|
2324
2588
|
message: "Enter feature name:",
|
|
2325
2589
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2326
2590
|
})
|
|
@@ -2330,7 +2594,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2330
2594
|
}
|
|
2331
2595
|
} else {
|
|
2332
2596
|
feature = handleCancel(
|
|
2333
|
-
await
|
|
2597
|
+
await p15__namespace.text({
|
|
2334
2598
|
message: "Enter target feature name:",
|
|
2335
2599
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2336
2600
|
})
|
|
@@ -2343,7 +2607,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2343
2607
|
let name = classNameArg?.trim();
|
|
2344
2608
|
if (!name) {
|
|
2345
2609
|
name = handleCancel(
|
|
2346
|
-
await
|
|
2610
|
+
await p15__namespace.text({
|
|
2347
2611
|
message: "Infrastructure class name:",
|
|
2348
2612
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2349
2613
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2362,7 +2626,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2362
2626
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2363
2627
|
);
|
|
2364
2628
|
const ifaceChoice = handleCancel(
|
|
2365
|
-
await
|
|
2629
|
+
await p15__namespace.select({
|
|
2366
2630
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2367
2631
|
options: [
|
|
2368
2632
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2374,7 +2638,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2374
2638
|
);
|
|
2375
2639
|
if (ifaceChoice === "__custom__") {
|
|
2376
2640
|
implementsInterface = handleCancel(
|
|
2377
|
-
await
|
|
2641
|
+
await p15__namespace.text({
|
|
2378
2642
|
message: "Enter interface or abstract class name:",
|
|
2379
2643
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2380
2644
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2385,14 +2649,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2385
2649
|
}
|
|
2386
2650
|
} else {
|
|
2387
2651
|
const wantImpl = handleCancel(
|
|
2388
|
-
await
|
|
2652
|
+
await p15__namespace.confirm({
|
|
2389
2653
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2390
2654
|
initialValue: false
|
|
2391
2655
|
})
|
|
2392
2656
|
);
|
|
2393
2657
|
if (wantImpl) {
|
|
2394
2658
|
implementsInterface = handleCancel(
|
|
2395
|
-
await
|
|
2659
|
+
await p15__namespace.text({
|
|
2396
2660
|
message: "Enter interface or abstract class name:",
|
|
2397
2661
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2398
2662
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2412,7 +2676,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2412
2676
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2413
2677
|
if (allInterfaces.length > 0) {
|
|
2414
2678
|
const ifaceChoice = handleCancel(
|
|
2415
|
-
await
|
|
2679
|
+
await p15__namespace.select({
|
|
2416
2680
|
message: "Does this class implement an interface or abstract class?",
|
|
2417
2681
|
options: [
|
|
2418
2682
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2424,7 +2688,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2424
2688
|
);
|
|
2425
2689
|
if (ifaceChoice === "__custom__") {
|
|
2426
2690
|
implementsInterface = handleCancel(
|
|
2427
|
-
await
|
|
2691
|
+
await p15__namespace.text({
|
|
2428
2692
|
message: "Enter interface or abstract class name:",
|
|
2429
2693
|
placeholder: "e.g. IAuthRepository",
|
|
2430
2694
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2437,7 +2701,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2437
2701
|
}
|
|
2438
2702
|
if (!flags.yes && !flags.kind) {
|
|
2439
2703
|
kind = handleCancel(
|
|
2440
|
-
await
|
|
2704
|
+
await p15__namespace.select({
|
|
2441
2705
|
message: "Select implementation kind:",
|
|
2442
2706
|
options: [
|
|
2443
2707
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2448,7 +2712,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2448
2712
|
);
|
|
2449
2713
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2450
2714
|
registerInProvider = handleCancel(
|
|
2451
|
-
await
|
|
2715
|
+
await p15__namespace.confirm({
|
|
2452
2716
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2453
2717
|
initialValue: true
|
|
2454
2718
|
})
|
|
@@ -2457,8 +2721,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2457
2721
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2458
2722
|
registerInProvider = !flags.noProvider;
|
|
2459
2723
|
}
|
|
2460
|
-
const s =
|
|
2461
|
-
s.start(`Generating infrastructure class '${
|
|
2724
|
+
const s = p15__namespace.spinner();
|
|
2725
|
+
s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
|
|
2462
2726
|
const files = [];
|
|
2463
2727
|
const options = {
|
|
2464
2728
|
name: className,
|
|
@@ -2473,7 +2737,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2473
2737
|
const infraFile = generateInfrastructure(options);
|
|
2474
2738
|
files.push(infraFile);
|
|
2475
2739
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2476
|
-
const featureDir =
|
|
2740
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2477
2741
|
const providerFile = updateOrGenerateDiProvider(
|
|
2478
2742
|
featureDir,
|
|
2479
2743
|
feature,
|
|
@@ -2493,7 +2757,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2493
2757
|
}
|
|
2494
2758
|
s.stop("Infrastructure class generated successfully!");
|
|
2495
2759
|
printGeneratedFiles(files, options.dryRun);
|
|
2496
|
-
|
|
2760
|
+
p15__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2497
2761
|
}
|
|
2498
2762
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2499
2763
|
const projectRoot = findProjectRoot();
|
|
@@ -2504,14 +2768,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2504
2768
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2505
2769
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2506
2770
|
const selected = handleCancel(
|
|
2507
|
-
await
|
|
2771
|
+
await p15__namespace.select({
|
|
2508
2772
|
message: "Select target feature:",
|
|
2509
2773
|
options: featureOptions
|
|
2510
2774
|
})
|
|
2511
2775
|
);
|
|
2512
2776
|
if (selected === "__custom__") {
|
|
2513
2777
|
feature = handleCancel(
|
|
2514
|
-
await
|
|
2778
|
+
await p15__namespace.text({
|
|
2515
2779
|
message: "Enter feature name:",
|
|
2516
2780
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2517
2781
|
})
|
|
@@ -2521,7 +2785,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2521
2785
|
}
|
|
2522
2786
|
} else {
|
|
2523
2787
|
feature = handleCancel(
|
|
2524
|
-
await
|
|
2788
|
+
await p15__namespace.text({
|
|
2525
2789
|
message: "Enter target feature name:",
|
|
2526
2790
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2527
2791
|
})
|
|
@@ -2532,7 +2796,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2532
2796
|
let name = interfaceNameArg?.trim();
|
|
2533
2797
|
if (!name) {
|
|
2534
2798
|
name = handleCancel(
|
|
2535
|
-
await
|
|
2799
|
+
await p15__namespace.text({
|
|
2536
2800
|
message: "Domain interface name:",
|
|
2537
2801
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2538
2802
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2550,7 +2814,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2550
2814
|
if (!flags.yes) {
|
|
2551
2815
|
if (existingEntities.length > 0) {
|
|
2552
2816
|
const selectedEntity = handleCancel(
|
|
2553
|
-
await
|
|
2817
|
+
await p15__namespace.select({
|
|
2554
2818
|
message: "Select domain entity associated with this interface:",
|
|
2555
2819
|
options: [
|
|
2556
2820
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2561,7 +2825,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2561
2825
|
);
|
|
2562
2826
|
if (selectedEntity === "__custom__") {
|
|
2563
2827
|
entityName = handleCancel(
|
|
2564
|
-
await
|
|
2828
|
+
await p15__namespace.text({
|
|
2565
2829
|
message: "Enter entity name:",
|
|
2566
2830
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2567
2831
|
})
|
|
@@ -2571,7 +2835,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2571
2835
|
}
|
|
2572
2836
|
}
|
|
2573
2837
|
kind = handleCancel(
|
|
2574
|
-
await
|
|
2838
|
+
await p15__namespace.select({
|
|
2575
2839
|
message: "Interface definition style:",
|
|
2576
2840
|
options: [
|
|
2577
2841
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2581,7 +2845,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2581
2845
|
})
|
|
2582
2846
|
);
|
|
2583
2847
|
methodsPreset = handleCancel(
|
|
2584
|
-
await
|
|
2848
|
+
await p15__namespace.select({
|
|
2585
2849
|
message: "Method signature preset:",
|
|
2586
2850
|
options: [
|
|
2587
2851
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2591,22 +2855,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2591
2855
|
})
|
|
2592
2856
|
);
|
|
2593
2857
|
generateStub = handleCancel(
|
|
2594
|
-
await
|
|
2858
|
+
await p15__namespace.confirm({
|
|
2595
2859
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2596
2860
|
initialValue: true
|
|
2597
2861
|
})
|
|
2598
2862
|
);
|
|
2599
2863
|
if (generateStub) {
|
|
2600
2864
|
registerInProvider = handleCancel(
|
|
2601
|
-
await
|
|
2865
|
+
await p15__namespace.confirm({
|
|
2602
2866
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2603
2867
|
initialValue: true
|
|
2604
2868
|
})
|
|
2605
2869
|
);
|
|
2606
2870
|
}
|
|
2607
2871
|
}
|
|
2608
|
-
const s =
|
|
2609
|
-
s.start(`Generating interface '${
|
|
2872
|
+
const s = p15__namespace.spinner();
|
|
2873
|
+
s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
|
|
2610
2874
|
const files = [];
|
|
2611
2875
|
const options = {
|
|
2612
2876
|
name: ifaceName,
|
|
@@ -2635,7 +2899,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2635
2899
|
files.push(stubFile);
|
|
2636
2900
|
}
|
|
2637
2901
|
if (registerInProvider && generateStub) {
|
|
2638
|
-
const featureDir =
|
|
2902
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2639
2903
|
const providerFile = updateOrGenerateDiProvider(
|
|
2640
2904
|
featureDir,
|
|
2641
2905
|
feature,
|
|
@@ -2655,7 +2919,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2655
2919
|
}
|
|
2656
2920
|
s.stop("Interface generated successfully!");
|
|
2657
2921
|
printGeneratedFiles(files, options.dryRun);
|
|
2658
|
-
|
|
2922
|
+
p15__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2659
2923
|
}
|
|
2660
2924
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2661
2925
|
const projectRoot = findProjectRoot();
|
|
@@ -2666,14 +2930,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2666
2930
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2667
2931
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2668
2932
|
const selected = handleCancel(
|
|
2669
|
-
await
|
|
2933
|
+
await p15__namespace.select({
|
|
2670
2934
|
message: "Select target feature:",
|
|
2671
2935
|
options: featureOptions
|
|
2672
2936
|
})
|
|
2673
2937
|
);
|
|
2674
2938
|
if (selected === "__custom__") {
|
|
2675
2939
|
feature = handleCancel(
|
|
2676
|
-
await
|
|
2940
|
+
await p15__namespace.text({
|
|
2677
2941
|
message: "Enter feature name:",
|
|
2678
2942
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2679
2943
|
})
|
|
@@ -2683,7 +2947,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2683
2947
|
}
|
|
2684
2948
|
} else {
|
|
2685
2949
|
feature = handleCancel(
|
|
2686
|
-
await
|
|
2950
|
+
await p15__namespace.text({
|
|
2687
2951
|
message: "Enter target feature name:",
|
|
2688
2952
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2689
2953
|
})
|
|
@@ -2694,7 +2958,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2694
2958
|
let name = handlerNameArg?.trim();
|
|
2695
2959
|
if (!name) {
|
|
2696
2960
|
name = handleCancel(
|
|
2697
|
-
await
|
|
2961
|
+
await p15__namespace.text({
|
|
2698
2962
|
message: "HTTP Route Handler class name:",
|
|
2699
2963
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2700
2964
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2713,7 +2977,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2713
2977
|
let validationFields = [];
|
|
2714
2978
|
if (!flags.yes) {
|
|
2715
2979
|
method = handleCancel(
|
|
2716
|
-
await
|
|
2980
|
+
await p15__namespace.select({
|
|
2717
2981
|
message: "HTTP Method:",
|
|
2718
2982
|
options: [
|
|
2719
2983
|
{ value: "get", label: "GET" },
|
|
@@ -2727,7 +2991,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2727
2991
|
);
|
|
2728
2992
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2729
2993
|
subPath = handleCancel(
|
|
2730
|
-
await
|
|
2994
|
+
await p15__namespace.text({
|
|
2731
2995
|
message: "Route subpath (relative to feature prefix):",
|
|
2732
2996
|
initialValue: defaultPath,
|
|
2733
2997
|
defaultValue: defaultPath
|
|
@@ -2738,7 +3002,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2738
3002
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2739
3003
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2740
3004
|
const chosenUc = handleCancel(
|
|
2741
|
-
await
|
|
3005
|
+
await p15__namespace.select({
|
|
2742
3006
|
message: "Connect this handler to a domain Use Case?",
|
|
2743
3007
|
options: ucOptions,
|
|
2744
3008
|
initialValue: ucOptions[0]?.value
|
|
@@ -2749,7 +3013,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2749
3013
|
}
|
|
2750
3014
|
}
|
|
2751
3015
|
includeValidation = handleCancel(
|
|
2752
|
-
await
|
|
3016
|
+
await p15__namespace.confirm({
|
|
2753
3017
|
message: "Include Zod inbound request validation?",
|
|
2754
3018
|
initialValue: method !== "delete"
|
|
2755
3019
|
})
|
|
@@ -2757,7 +3021,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2757
3021
|
if (includeValidation) {
|
|
2758
3022
|
const defaultSource = method === "get" ? "query" : "body";
|
|
2759
3023
|
validationSource = handleCancel(
|
|
2760
|
-
await
|
|
3024
|
+
await p15__namespace.select({
|
|
2761
3025
|
message: "Validation data source:",
|
|
2762
3026
|
options: [
|
|
2763
3027
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -2771,8 +3035,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2771
3035
|
});
|
|
2772
3036
|
}
|
|
2773
3037
|
}
|
|
2774
|
-
const s =
|
|
2775
|
-
s.start(`Generating HTTP handler '${
|
|
3038
|
+
const s = p15__namespace.spinner();
|
|
3039
|
+
s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
|
|
2776
3040
|
const options = {
|
|
2777
3041
|
name: className,
|
|
2778
3042
|
feature,
|
|
@@ -2790,7 +3054,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2790
3054
|
const files = generateHttpHandler(options);
|
|
2791
3055
|
s.stop("HTTP handler generated successfully!");
|
|
2792
3056
|
printGeneratedFiles(files, options.dryRun);
|
|
2793
|
-
|
|
3057
|
+
p15__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
2794
3058
|
}
|
|
2795
3059
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
2796
3060
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -2800,7 +3064,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
2800
3064
|
const formattedMethods = methods.map((m) => {
|
|
2801
3065
|
const isAsync = m.async !== false;
|
|
2802
3066
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
2803
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3067
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
|
|
2804
3068
|
return {
|
|
2805
3069
|
name: m.name,
|
|
2806
3070
|
isAsync,
|
|
@@ -2823,21 +3087,21 @@ function generateInjectable(options) {
|
|
|
2823
3087
|
const className = toPascalCase(options.name);
|
|
2824
3088
|
let targetDir = "";
|
|
2825
3089
|
if (options.customPath) {
|
|
2826
|
-
targetDir =
|
|
3090
|
+
targetDir = path2__default.default.isAbsolute(options.customPath) ? options.customPath : path2__default.default.join(projectRoot, options.customPath);
|
|
2827
3091
|
} else if (options.location === "feature-service" && options.feature) {
|
|
2828
|
-
targetDir =
|
|
3092
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
2829
3093
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
2830
|
-
targetDir =
|
|
3094
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
2831
3095
|
} else if (options.location === "module") {
|
|
2832
|
-
targetDir =
|
|
3096
|
+
targetDir = path2__default.default.join(projectRoot, "modules", className);
|
|
2833
3097
|
} else {
|
|
2834
|
-
targetDir = options.feature ?
|
|
3098
|
+
targetDir = options.feature ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2__default.default.join(projectRoot, "shared", className.toLowerCase());
|
|
2835
3099
|
}
|
|
2836
|
-
const filePath =
|
|
3100
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
2837
3101
|
let ifaceName;
|
|
2838
3102
|
if (options.generateInterface) {
|
|
2839
3103
|
ifaceName = `I${className}`;
|
|
2840
|
-
const ifacePath =
|
|
3104
|
+
const ifacePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
2841
3105
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
2842
3106
|
files.push(
|
|
2843
3107
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -2870,7 +3134,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2870
3134
|
let name = classNameArg?.trim();
|
|
2871
3135
|
if (!name) {
|
|
2872
3136
|
name = handleCancel(
|
|
2873
|
-
await
|
|
3137
|
+
await p15__namespace.text({
|
|
2874
3138
|
message: "Injectable class name:",
|
|
2875
3139
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
2876
3140
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2883,7 +3147,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2883
3147
|
let customPath;
|
|
2884
3148
|
if (!flags.yes) {
|
|
2885
3149
|
location = handleCancel(
|
|
2886
|
-
await
|
|
3150
|
+
await p15__namespace.select({
|
|
2887
3151
|
message: "Select target architectural location:",
|
|
2888
3152
|
options: [
|
|
2889
3153
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -2901,14 +3165,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2901
3165
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2902
3166
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2903
3167
|
const selected = handleCancel(
|
|
2904
|
-
await
|
|
3168
|
+
await p15__namespace.select({
|
|
2905
3169
|
message: "Select target feature:",
|
|
2906
3170
|
options: featureOptions
|
|
2907
3171
|
})
|
|
2908
3172
|
);
|
|
2909
3173
|
if (selected === "__custom__") {
|
|
2910
3174
|
feature = handleCancel(
|
|
2911
|
-
await
|
|
3175
|
+
await p15__namespace.text({
|
|
2912
3176
|
message: "Enter feature name:",
|
|
2913
3177
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2914
3178
|
})
|
|
@@ -2918,7 +3182,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2918
3182
|
}
|
|
2919
3183
|
} else {
|
|
2920
3184
|
feature = handleCancel(
|
|
2921
|
-
await
|
|
3185
|
+
await p15__namespace.text({
|
|
2922
3186
|
message: "Enter target feature name:",
|
|
2923
3187
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2924
3188
|
})
|
|
@@ -2928,7 +3192,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2928
3192
|
feature = toCamelCase(feature);
|
|
2929
3193
|
} else if (location === "custom") {
|
|
2930
3194
|
customPath = handleCancel(
|
|
2931
|
-
await
|
|
3195
|
+
await p15__namespace.text({
|
|
2932
3196
|
message: "Enter destination directory path:",
|
|
2933
3197
|
placeholder: "e.g. src/services or lib/utils",
|
|
2934
3198
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -2939,14 +3203,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2939
3203
|
let generateInterface2 = false;
|
|
2940
3204
|
if (!flags.yes) {
|
|
2941
3205
|
generateInterface2 = handleCancel(
|
|
2942
|
-
await
|
|
3206
|
+
await p15__namespace.confirm({
|
|
2943
3207
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
2944
3208
|
initialValue: false
|
|
2945
3209
|
})
|
|
2946
3210
|
);
|
|
2947
3211
|
}
|
|
2948
|
-
const s =
|
|
2949
|
-
s.start(`Generating injectable '${
|
|
3212
|
+
const s = p15__namespace.spinner();
|
|
3213
|
+
s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
|
|
2950
3214
|
const options = {
|
|
2951
3215
|
name: className,
|
|
2952
3216
|
feature,
|
|
@@ -2960,12 +3224,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2960
3224
|
const files = generateInjectable(options);
|
|
2961
3225
|
s.stop("Injectable class generated successfully!");
|
|
2962
3226
|
printGeneratedFiles(files, options.dryRun);
|
|
2963
|
-
|
|
3227
|
+
p15__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
|
|
2964
3228
|
}
|
|
2965
3229
|
async function runInteractiveMenu(flags = {}) {
|
|
2966
3230
|
printBanner();
|
|
2967
3231
|
const choice = handleCancel(
|
|
2968
|
-
await
|
|
3232
|
+
await p15__namespace.select({
|
|
2969
3233
|
message: "What would you like to create in your architecture?",
|
|
2970
3234
|
options: [
|
|
2971
3235
|
{
|
|
@@ -2973,6 +3237,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
2973
3237
|
label: "Feature",
|
|
2974
3238
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
2975
3239
|
},
|
|
3240
|
+
{
|
|
3241
|
+
value: "shared",
|
|
3242
|
+
label: "Shared Module",
|
|
3243
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3244
|
+
},
|
|
2976
3245
|
{
|
|
2977
3246
|
value: "usecase",
|
|
2978
3247
|
label: "Use Case",
|
|
@@ -3010,6 +3279,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3010
3279
|
case "feature":
|
|
3011
3280
|
await runFeatureCommand(void 0, flags);
|
|
3012
3281
|
break;
|
|
3282
|
+
case "shared":
|
|
3283
|
+
await runSharedCommand(void 0, flags);
|
|
3284
|
+
break;
|
|
3013
3285
|
case "usecase":
|
|
3014
3286
|
await runUseCaseCommand(void 0, flags);
|
|
3015
3287
|
break;
|
|
@@ -3042,6 +3314,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3042
3314
|
case "feature":
|
|
3043
3315
|
await runFeatureCommand(name, flags);
|
|
3044
3316
|
break;
|
|
3317
|
+
case "shared":
|
|
3318
|
+
await runSharedCommand(name, flags);
|
|
3319
|
+
break;
|
|
3045
3320
|
case "usecase":
|
|
3046
3321
|
await runUseCaseCommand(name, flags);
|
|
3047
3322
|
break;
|
|
@@ -3062,49 +3337,114 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3062
3337
|
await runInjectableCommand(name, flags);
|
|
3063
3338
|
break;
|
|
3064
3339
|
default:
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3340
|
+
p15__namespace.log.error(
|
|
3341
|
+
pc15__default.default.red(
|
|
3342
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3068
3343
|
)
|
|
3069
3344
|
);
|
|
3070
3345
|
process.exit(1);
|
|
3071
3346
|
}
|
|
3072
3347
|
}
|
|
3073
3348
|
var DEFAULT_REGISTRY_BASE = "https://raw.githubusercontent.com/solid-stack-digital/mason-registry/main";
|
|
3349
|
+
function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
3350
|
+
let trimmed = urlOrPath.trim();
|
|
3351
|
+
const gitSshRegex = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
|
|
3352
|
+
const sshMatch = trimmed.match(gitSshRegex);
|
|
3353
|
+
if (sshMatch && sshMatch[1] && sshMatch[2]) {
|
|
3354
|
+
return `https://raw.githubusercontent.com/${sshMatch[1]}/${sshMatch[2]}/main`;
|
|
3355
|
+
}
|
|
3356
|
+
const githubWebRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?\/?$/;
|
|
3357
|
+
const webMatch = trimmed.match(githubWebRegex);
|
|
3358
|
+
if (webMatch && webMatch[1] && webMatch[2]) {
|
|
3359
|
+
const owner = webMatch[1];
|
|
3360
|
+
const repo = webMatch[2];
|
|
3361
|
+
const branch = webMatch[3] || "main";
|
|
3362
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
3363
|
+
}
|
|
3364
|
+
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
3365
|
+
return path2__default.default.resolve(baseDir, trimmed);
|
|
3366
|
+
}
|
|
3367
|
+
return trimmed;
|
|
3368
|
+
}
|
|
3369
|
+
function findLocalRegistry(startDir = process.cwd()) {
|
|
3370
|
+
let current = path2__default.default.resolve(startDir);
|
|
3371
|
+
while (true) {
|
|
3372
|
+
const candidate = path2__default.default.join(current, "mason-registry");
|
|
3373
|
+
if (fs12__default.default.existsSync(candidate) && fs12__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
|
|
3374
|
+
return candidate;
|
|
3375
|
+
}
|
|
3376
|
+
const parent = path2__default.default.dirname(current);
|
|
3377
|
+
if (parent === current) break;
|
|
3378
|
+
current = parent;
|
|
3379
|
+
}
|
|
3380
|
+
return null;
|
|
3381
|
+
}
|
|
3074
3382
|
function getRegistryBase(projectRoot, override) {
|
|
3383
|
+
const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
|
|
3075
3384
|
if (override && override.trim()) {
|
|
3076
|
-
return override.trim();
|
|
3385
|
+
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
3077
3386
|
}
|
|
3078
3387
|
if (process.env.MASON_REGISTRY_URL && process.env.MASON_REGISTRY_URL.trim()) {
|
|
3079
|
-
return process.env.MASON_REGISTRY_URL.trim();
|
|
3388
|
+
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
3080
3389
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3390
|
+
const configLocations = [
|
|
3391
|
+
path2__default.default.join(rootDir, "mason.config.json"),
|
|
3392
|
+
path2__default.default.join(process.cwd(), "mason.config.json")
|
|
3393
|
+
];
|
|
3394
|
+
for (const configPath of configLocations) {
|
|
3395
|
+
if (fs12__default.default.existsSync(configPath)) {
|
|
3084
3396
|
try {
|
|
3085
|
-
const config = JSON.parse(
|
|
3397
|
+
const config = JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
|
|
3086
3398
|
if (config.registry && typeof config.registry === "string") {
|
|
3087
|
-
return config.registry.trim();
|
|
3399
|
+
return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
|
|
3088
3400
|
}
|
|
3089
3401
|
} catch {
|
|
3090
3402
|
}
|
|
3091
3403
|
}
|
|
3092
3404
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3405
|
+
const pkgLocations = [
|
|
3406
|
+
path2__default.default.join(rootDir, "package.json"),
|
|
3407
|
+
path2__default.default.join(process.cwd(), "package.json")
|
|
3408
|
+
];
|
|
3409
|
+
for (const pkgPath of pkgLocations) {
|
|
3410
|
+
if (fs12__default.default.existsSync(pkgPath)) {
|
|
3411
|
+
try {
|
|
3412
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgPath, "utf-8"));
|
|
3413
|
+
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
3414
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
|
|
3415
|
+
}
|
|
3416
|
+
} catch {
|
|
3417
|
+
}
|
|
3097
3418
|
}
|
|
3098
3419
|
}
|
|
3420
|
+
const localRegistry = findLocalRegistry(rootDir);
|
|
3421
|
+
if (localRegistry) {
|
|
3422
|
+
return localRegistry;
|
|
3423
|
+
}
|
|
3099
3424
|
return DEFAULT_REGISTRY_BASE;
|
|
3100
3425
|
}
|
|
3101
3426
|
async function fetchRegistryFile(registryBase, relPath) {
|
|
3102
3427
|
const cleanRelPath = relPath.replace(/^\/+/, "");
|
|
3103
3428
|
if (registryBase.startsWith("http://") || registryBase.startsWith("https://")) {
|
|
3104
3429
|
const url = `${registryBase.replace(/\/+$/, "")}/${cleanRelPath}`;
|
|
3105
|
-
const
|
|
3430
|
+
const headers = {};
|
|
3431
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
3432
|
+
if (token) {
|
|
3433
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
3434
|
+
}
|
|
3435
|
+
const res = await fetch(url, { headers });
|
|
3106
3436
|
if (!res.ok) {
|
|
3107
|
-
|
|
3437
|
+
let extraHint = "";
|
|
3438
|
+
if (res.status === 404) {
|
|
3439
|
+
extraHint = `
|
|
3440
|
+
Possible reasons:
|
|
3441
|
+
\u2022 If using a private GitHub repository, ensure GITHUB_TOKEN is set in your environment.
|
|
3442
|
+
\u2022 If your registry has not yet been pushed to GitHub, point to your local directory:
|
|
3443
|
+
- CLI flag: mason import ... --registry /path/to/mason-registry
|
|
3444
|
+
- Environment: export MASON_REGISTRY_URL=/path/to/mason-registry
|
|
3445
|
+
- Config file: Create mason.config.json with { "registry": "/path/to/mason-registry" }`;
|
|
3446
|
+
}
|
|
3447
|
+
throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})${extraHint}`);
|
|
3108
3448
|
}
|
|
3109
3449
|
return await res.text();
|
|
3110
3450
|
}
|
|
@@ -3112,11 +3452,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
3112
3452
|
if (baseDir.startsWith("file://")) {
|
|
3113
3453
|
baseDir = url.fileURLToPath(baseDir);
|
|
3114
3454
|
}
|
|
3115
|
-
const filePath =
|
|
3116
|
-
if (!
|
|
3455
|
+
const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
|
|
3456
|
+
if (!fs12__default.default.existsSync(filePath)) {
|
|
3117
3457
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
3118
3458
|
}
|
|
3119
|
-
return
|
|
3459
|
+
return fs12__default.default.promises.readFile(filePath, "utf-8");
|
|
3120
3460
|
}
|
|
3121
3461
|
async function fetchRegistryIndex(registryBase) {
|
|
3122
3462
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -3178,26 +3518,26 @@ function findClosestMatch(target, candidates) {
|
|
|
3178
3518
|
return null;
|
|
3179
3519
|
}
|
|
3180
3520
|
function detectPackageManager(projectRoot) {
|
|
3181
|
-
if (
|
|
3182
|
-
if (
|
|
3183
|
-
if (
|
|
3521
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3522
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
3523
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lockb")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lock"))) return "bun";
|
|
3184
3524
|
return "npm";
|
|
3185
3525
|
}
|
|
3186
3526
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
3187
3527
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
3188
|
-
const pkgJsonPath =
|
|
3189
|
-
if (!
|
|
3528
|
+
const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
|
|
3529
|
+
if (!fs12__default.default.existsSync(pkgJsonPath)) {
|
|
3190
3530
|
return requiredPackages;
|
|
3191
3531
|
}
|
|
3192
3532
|
try {
|
|
3193
|
-
const pkg = JSON.parse(
|
|
3533
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
3194
3534
|
const installed = /* @__PURE__ */ new Set([
|
|
3195
3535
|
...Object.keys(pkg.dependencies || {}),
|
|
3196
3536
|
...Object.keys(pkg.devDependencies || {}),
|
|
3197
3537
|
...Object.keys(pkg.peerDependencies || {})
|
|
3198
3538
|
]);
|
|
3199
|
-
return requiredPackages.filter((
|
|
3200
|
-
const nameOnly =
|
|
3539
|
+
return requiredPackages.filter((p17) => {
|
|
3540
|
+
const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
|
|
3201
3541
|
return !installed.has(nameOnly);
|
|
3202
3542
|
});
|
|
3203
3543
|
} catch {
|
|
@@ -3222,21 +3562,207 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3222
3562
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3223
3563
|
break;
|
|
3224
3564
|
}
|
|
3225
|
-
|
|
3565
|
+
p15__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
|
|
3226
3566
|
child_process.execSync(installCmd, {
|
|
3227
3567
|
cwd: projectRoot,
|
|
3228
3568
|
stdio: "inherit"
|
|
3229
3569
|
});
|
|
3230
3570
|
}
|
|
3571
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
3572
|
+
if (!fs12__default.default.existsSync(dir)) return [];
|
|
3573
|
+
const entries = fs12__default.default.readdirSync(dir, { withFileTypes: true });
|
|
3574
|
+
const files = [];
|
|
3575
|
+
for (const entry of entries) {
|
|
3576
|
+
const fullPath = path2__default.default.join(dir, entry.name);
|
|
3577
|
+
const relPath = path2__default.default.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
3578
|
+
if (entry.isDirectory()) {
|
|
3579
|
+
if (entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
3583
|
+
} else if (entry.isFile()) {
|
|
3584
|
+
if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
files.push(relPath);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
return files.sort();
|
|
3591
|
+
}
|
|
3592
|
+
function computeDirectoryHash(dirPath, files) {
|
|
3593
|
+
if (!fs12__default.default.existsSync(dirPath)) return "";
|
|
3594
|
+
const hash = crypto.createHash("sha256");
|
|
3595
|
+
const fileList = (files ?? getAllFiles(dirPath)).sort();
|
|
3596
|
+
for (const relativePath of fileList) {
|
|
3597
|
+
const absolutePath = path2__default.default.join(dirPath, relativePath);
|
|
3598
|
+
if (!fs12__default.default.existsSync(absolutePath)) continue;
|
|
3599
|
+
const content = fs12__default.default.readFileSync(absolutePath, "utf8");
|
|
3600
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
3601
|
+
hash.update(relativePath);
|
|
3602
|
+
hash.update(normalizedContent);
|
|
3603
|
+
}
|
|
3604
|
+
return `sha256-${hash.digest("hex")}`;
|
|
3605
|
+
}
|
|
3231
3606
|
|
|
3232
3607
|
// src/commands/import.ts
|
|
3608
|
+
var importPrompts = {
|
|
3609
|
+
select: p15__namespace.select,
|
|
3610
|
+
text: p15__namespace.text
|
|
3611
|
+
};
|
|
3612
|
+
function rewriteImports(content, aliasMap) {
|
|
3613
|
+
if (aliasMap.size === 0) return content;
|
|
3614
|
+
let rewritten = content;
|
|
3615
|
+
for (const [oldImport, newImport] of aliasMap.entries()) {
|
|
3616
|
+
const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
|
|
3617
|
+
const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
|
|
3618
|
+
const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3619
|
+
const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
|
|
3620
|
+
rewritten = rewritten.replace(regex, rawNew);
|
|
3621
|
+
}
|
|
3622
|
+
return rewritten;
|
|
3623
|
+
}
|
|
3624
|
+
function getModuleBaseDir(projectRoot, modType) {
|
|
3625
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3626
|
+
return path2__default.default.join(projectRoot, "src", folder);
|
|
3627
|
+
}
|
|
3628
|
+
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3629
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3630
|
+
const inSrc = path2__default.default.join(projectRoot, "src", folder, modName);
|
|
3631
|
+
if (fs12__default.default.existsSync(inSrc)) return inSrc;
|
|
3632
|
+
const inRoot = path2__default.default.join(projectRoot, folder, modName);
|
|
3633
|
+
if (fs12__default.default.existsSync(inRoot)) return inRoot;
|
|
3634
|
+
return null;
|
|
3635
|
+
}
|
|
3636
|
+
function checkModuleExists(projectRoot, modType, modName) {
|
|
3637
|
+
return getExistingModuleDir(projectRoot, modType, modName) !== null;
|
|
3638
|
+
}
|
|
3639
|
+
async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
|
|
3640
|
+
const folder = depType === "shared" ? "shared" : "features";
|
|
3641
|
+
const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
|
|
3642
|
+
if (isAlreadyImportedThisRun) return;
|
|
3643
|
+
const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
|
|
3644
|
+
if (programmaticAlias) {
|
|
3645
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
|
|
3646
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3647
|
+
const displayDest = path2__default.default.relative(
|
|
3648
|
+
projectRoot,
|
|
3649
|
+
path2__default.default.join(baseDir, programmaticAlias)
|
|
3650
|
+
);
|
|
3651
|
+
p15__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3652
|
+
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3656
|
+
if (!existingDir) {
|
|
3657
|
+
p15__namespace.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc15__default.default.cyan(depName)}`);
|
|
3658
|
+
await importModuleRecursive(depType, depName, depName);
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
|
|
3662
|
+
(item) => item.name === depName
|
|
3663
|
+
);
|
|
3664
|
+
let remoteIntegrity = entry?.integrity;
|
|
3665
|
+
if (!remoteIntegrity) {
|
|
3666
|
+
try {
|
|
3667
|
+
const depManifest = await fetchModuleManifest(registryBase, depType, depName);
|
|
3668
|
+
remoteIntegrity = depManifest.integrity;
|
|
3669
|
+
} catch {
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
const localHash = computeDirectoryHash(existingDir);
|
|
3673
|
+
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3674
|
+
p15__namespace.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3675
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
if (flags.overwrite) {
|
|
3679
|
+
p15__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3680
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3681
|
+
const targetDir = path2__default.default.join(baseDir, depName);
|
|
3682
|
+
if (!flags.dryRun) {
|
|
3683
|
+
if (existingDir !== targetDir) {
|
|
3684
|
+
fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3685
|
+
}
|
|
3686
|
+
fs12__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3687
|
+
}
|
|
3688
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
if (flags.yes) {
|
|
3692
|
+
p15__namespace.log.info(`Using existing '${depType}/${depName}'`);
|
|
3693
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
const choice = handleCancel(
|
|
3697
|
+
await importPrompts.select({
|
|
3698
|
+
message: `Local '${depType}/${depName}' differs from the registry version.`,
|
|
3699
|
+
options: [
|
|
3700
|
+
{
|
|
3701
|
+
value: "keep_existing",
|
|
3702
|
+
label: `Keep existing local version (Recommended)`
|
|
3703
|
+
},
|
|
3704
|
+
{
|
|
3705
|
+
value: "rename",
|
|
3706
|
+
label: "Import fresh copy under a new name (alias)"
|
|
3707
|
+
},
|
|
3708
|
+
{
|
|
3709
|
+
value: "overwrite",
|
|
3710
|
+
label: "Overwrite local version with registry version"
|
|
3711
|
+
}
|
|
3712
|
+
]
|
|
3713
|
+
})
|
|
3714
|
+
);
|
|
3715
|
+
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3716
|
+
p15__namespace.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3717
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
if (choice === "rename") {
|
|
3721
|
+
const newName = handleCancel(
|
|
3722
|
+
await importPrompts.text({
|
|
3723
|
+
message: "What should we name the new copy?",
|
|
3724
|
+
placeholder: `${depName}-copy`,
|
|
3725
|
+
validate(val) {
|
|
3726
|
+
const v = val?.trim();
|
|
3727
|
+
if (!v) return "Name cannot be empty";
|
|
3728
|
+
if (v === depName) return `Name must be different from '${depName}'`;
|
|
3729
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
|
|
3730
|
+
return "Name can only contain alphanumeric characters, dashes, and underscores";
|
|
3731
|
+
}
|
|
3732
|
+
if (checkModuleExists(projectRoot, depType, v)) {
|
|
3733
|
+
return `'${v}' already exists in ${folder}, please choose another name`;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
})
|
|
3737
|
+
).trim();
|
|
3738
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3739
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3740
|
+
const displayDest = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, newName));
|
|
3741
|
+
p15__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3742
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
if (choice === "overwrite") {
|
|
3746
|
+
p15__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3747
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3748
|
+
const targetDir = path2__default.default.join(baseDir, depName);
|
|
3749
|
+
if (!flags.dryRun) {
|
|
3750
|
+
if (existingDir !== targetDir) {
|
|
3751
|
+
fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3752
|
+
}
|
|
3753
|
+
fs12__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3754
|
+
}
|
|
3755
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3233
3759
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3234
3760
|
const projectRoot = findProjectRoot();
|
|
3235
3761
|
let type = typeArg?.trim().toLowerCase();
|
|
3236
3762
|
if (type === "features") type = "feature";
|
|
3237
3763
|
if (!type) {
|
|
3238
3764
|
type = handleCancel(
|
|
3239
|
-
await
|
|
3765
|
+
await importPrompts.select({
|
|
3240
3766
|
message: "What kind of module would you like to import?",
|
|
3241
3767
|
options: [
|
|
3242
3768
|
{
|
|
@@ -3254,19 +3780,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3254
3780
|
);
|
|
3255
3781
|
}
|
|
3256
3782
|
if (type !== "shared" && type !== "feature") {
|
|
3257
|
-
|
|
3783
|
+
p15__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3258
3784
|
process.exit(1);
|
|
3259
3785
|
}
|
|
3260
3786
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3261
|
-
const s =
|
|
3262
|
-
s.start(`Connecting to registry at ${
|
|
3787
|
+
const s = p15__namespace.spinner();
|
|
3788
|
+
s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
|
|
3263
3789
|
let index;
|
|
3264
3790
|
try {
|
|
3265
3791
|
index = await fetchRegistryIndex(registryBase);
|
|
3266
3792
|
s.stop("Registry index loaded.");
|
|
3267
3793
|
} catch (err) {
|
|
3268
|
-
s.stop(
|
|
3269
|
-
|
|
3794
|
+
s.stop(pc15__default.default.red("Failed to load registry index."));
|
|
3795
|
+
p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
|
|
3270
3796
|
process.exit(1);
|
|
3271
3797
|
}
|
|
3272
3798
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3274,11 +3800,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3274
3800
|
let name = nameArg?.trim();
|
|
3275
3801
|
if (!name) {
|
|
3276
3802
|
if (availableList.length === 0) {
|
|
3277
|
-
|
|
3803
|
+
p15__namespace.log.warn(`No ${type} modules available in the registry.`);
|
|
3278
3804
|
return;
|
|
3279
3805
|
}
|
|
3280
3806
|
name = handleCancel(
|
|
3281
|
-
await
|
|
3807
|
+
await importPrompts.select({
|
|
3282
3808
|
message: `Select a ${type} to import:`,
|
|
3283
3809
|
options: availableList.map((item) => ({
|
|
3284
3810
|
value: item.name,
|
|
@@ -3290,122 +3816,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3290
3816
|
}
|
|
3291
3817
|
if (!candidateNames.includes(name)) {
|
|
3292
3818
|
const closest = findClosestMatch(name, candidateNames);
|
|
3293
|
-
|
|
3819
|
+
p15__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3294
3820
|
if (closest) {
|
|
3295
|
-
|
|
3821
|
+
p15__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
|
|
3296
3822
|
}
|
|
3297
3823
|
process.exit(1);
|
|
3298
3824
|
}
|
|
3299
3825
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3300
3826
|
const totalWrittenFiles = [];
|
|
3301
3827
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3302
|
-
async function importModuleRecursive(modType,
|
|
3303
|
-
const modKey = `${modType}:${
|
|
3828
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3829
|
+
const modKey = `${modType}:${targetName}`;
|
|
3304
3830
|
if (importedModules.has(modKey)) return;
|
|
3305
|
-
|
|
3831
|
+
p15__namespace.log.step(
|
|
3832
|
+
`Importing ${modType}: ${pc15__default.default.cyan(targetName)}${targetName !== registryName ? pc15__default.default.dim(` (from ${registryName})`) : ""}...`
|
|
3833
|
+
);
|
|
3306
3834
|
let manifest;
|
|
3307
3835
|
try {
|
|
3308
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3836
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3309
3837
|
} catch (err) {
|
|
3310
|
-
|
|
3838
|
+
p15__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3311
3839
|
process.exit(1);
|
|
3312
3840
|
}
|
|
3841
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3842
|
+
if (targetName !== registryName) {
|
|
3843
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3844
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3845
|
+
}
|
|
3313
3846
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3314
3847
|
for (const dep of sharedDeps) {
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3848
|
+
await resolveDependency(
|
|
3849
|
+
"shared",
|
|
3850
|
+
dep,
|
|
3851
|
+
aliasMap,
|
|
3852
|
+
projectRoot,
|
|
3853
|
+
flags,
|
|
3854
|
+
importModuleRecursive,
|
|
3855
|
+
importedModules,
|
|
3856
|
+
registryBase,
|
|
3857
|
+
index
|
|
3858
|
+
);
|
|
3320
3859
|
}
|
|
3321
3860
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3322
3861
|
for (const dep of featureDeps) {
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3862
|
+
await resolveDependency(
|
|
3863
|
+
"feature",
|
|
3864
|
+
dep,
|
|
3865
|
+
aliasMap,
|
|
3866
|
+
projectRoot,
|
|
3867
|
+
flags,
|
|
3868
|
+
importModuleRecursive,
|
|
3869
|
+
importedModules,
|
|
3870
|
+
registryBase,
|
|
3871
|
+
index
|
|
3872
|
+
);
|
|
3328
3873
|
}
|
|
3329
3874
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3330
3875
|
requiredNpmPackages.add(pkg);
|
|
3331
3876
|
}
|
|
3332
3877
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3333
|
-
const targetDir =
|
|
3878
|
+
const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3879
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3334
3880
|
for (const file of manifest.files || []) {
|
|
3335
3881
|
if (file === "registry.json") continue;
|
|
3336
|
-
const remoteFilePath = `${folder}/${
|
|
3337
|
-
const localFilePath =
|
|
3338
|
-
if (
|
|
3339
|
-
|
|
3882
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3883
|
+
const localFilePath = path2__default.default.join(targetDir, file);
|
|
3884
|
+
if (fs12__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
3885
|
+
p15__namespace.log.warn(
|
|
3886
|
+
` \u21B3 Skipping existing file: ${pc15__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3887
|
+
);
|
|
3340
3888
|
continue;
|
|
3341
3889
|
}
|
|
3342
|
-
|
|
3890
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3891
|
+
content = rewriteImports(content, aliasMap);
|
|
3343
3892
|
if (!flags.dryRun) {
|
|
3344
|
-
|
|
3345
|
-
|
|
3893
|
+
fs12__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
|
|
3894
|
+
fs12__default.default.writeFileSync(localFilePath, content, "utf-8");
|
|
3346
3895
|
}
|
|
3347
|
-
const displayPath =
|
|
3896
|
+
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
3348
3897
|
totalWrittenFiles.push(displayPath);
|
|
3349
|
-
|
|
3898
|
+
p15__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
|
|
3350
3899
|
}
|
|
3351
3900
|
importedModules.add(modKey);
|
|
3352
3901
|
}
|
|
3353
|
-
await importModuleRecursive(type, name);
|
|
3902
|
+
await importModuleRecursive(type, name, name);
|
|
3354
3903
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3355
3904
|
if (missingNpm.length > 0) {
|
|
3356
3905
|
if (flags.skipInstall || flags.dryRun) {
|
|
3357
|
-
|
|
3906
|
+
p15__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
|
|
3358
3907
|
} else {
|
|
3359
3908
|
try {
|
|
3360
3909
|
installNpmPackages(projectRoot, missingNpm);
|
|
3361
3910
|
} catch (err) {
|
|
3362
|
-
|
|
3911
|
+
p15__namespace.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3363
3912
|
}
|
|
3364
3913
|
}
|
|
3365
3914
|
}
|
|
3366
3915
|
if (flags.dryRun) {
|
|
3367
|
-
|
|
3916
|
+
p15__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
3368
3917
|
} else {
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3918
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3919
|
+
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, name));
|
|
3920
|
+
p15__namespace.log.success(
|
|
3921
|
+
pc15__default.default.green(
|
|
3922
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3372
3923
|
)
|
|
3373
3924
|
);
|
|
3374
3925
|
}
|
|
3375
3926
|
}
|
|
3927
|
+
function loadMasterIndex(projectRoot) {
|
|
3928
|
+
const indexPath = path2__default.default.join(projectRoot, "index.json");
|
|
3929
|
+
if (!fs12__default.default.existsSync(indexPath)) {
|
|
3930
|
+
const initialIndex = { shared: [], features: [] };
|
|
3931
|
+
fs12__default.default.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
|
|
3932
|
+
return initialIndex;
|
|
3933
|
+
}
|
|
3934
|
+
return JSON.parse(fs12__default.default.readFileSync(indexPath, "utf-8"));
|
|
3935
|
+
}
|
|
3936
|
+
function saveMasterIndex(projectRoot, index) {
|
|
3937
|
+
const indexPath = path2__default.default.join(projectRoot, "index.json");
|
|
3938
|
+
index.shared.sort((a, b) => a.name.localeCompare(b.name));
|
|
3939
|
+
index.features.sort((a, b) => a.name.localeCompare(b.name));
|
|
3940
|
+
fs12__default.default.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
3941
|
+
}
|
|
3942
|
+
function discoverDiskModules(projectRoot) {
|
|
3943
|
+
const results = [];
|
|
3944
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
3945
|
+
if (fs12__default.default.existsSync(sharedDir)) {
|
|
3946
|
+
const entries = fs12__default.default.readdirSync(sharedDir, { withFileTypes: true });
|
|
3947
|
+
for (const entry of entries) {
|
|
3948
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3949
|
+
results.push({ name: entry.name, type: "shared", dir: path2__default.default.join(sharedDir, entry.name) });
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
3954
|
+
if (fs12__default.default.existsSync(featuresDir)) {
|
|
3955
|
+
const entries = fs12__default.default.readdirSync(featuresDir, { withFileTypes: true });
|
|
3956
|
+
for (const entry of entries) {
|
|
3957
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3958
|
+
results.push({ name: entry.name, type: "feature", dir: path2__default.default.join(featuresDir, entry.name) });
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
return results;
|
|
3963
|
+
}
|
|
3964
|
+
function checkGitStatusForDir(projectRoot, dirPath) {
|
|
3965
|
+
try {
|
|
3966
|
+
const relPath = path2__default.default.relative(projectRoot, dirPath);
|
|
3967
|
+
const output = child_process.execSync(`git status --porcelain -- "${relPath}"`, {
|
|
3968
|
+
cwd: projectRoot,
|
|
3969
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3970
|
+
encoding: "utf-8"
|
|
3971
|
+
}).trim();
|
|
3972
|
+
if (!output) {
|
|
3973
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3974
|
+
}
|
|
3975
|
+
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
3976
|
+
return { hasChanges: true, filesCount: lines.length };
|
|
3977
|
+
} catch {
|
|
3978
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
function getRegistryDiff(projectRoot) {
|
|
3982
|
+
const index = loadMasterIndex(projectRoot);
|
|
3983
|
+
const diskModules = discoverDiskModules(projectRoot);
|
|
3984
|
+
const diskMap = /* @__PURE__ */ new Map();
|
|
3985
|
+
for (const mod of diskModules) {
|
|
3986
|
+
diskMap.set(`${mod.type}:${mod.name}`, mod);
|
|
3987
|
+
}
|
|
3988
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3989
|
+
for (const item of index.shared) {
|
|
3990
|
+
allKeys.add(`shared:${item.name}`);
|
|
3991
|
+
}
|
|
3992
|
+
for (const item of index.features) {
|
|
3993
|
+
allKeys.add(`feature:${item.name}`);
|
|
3994
|
+
}
|
|
3995
|
+
for (const mod of diskModules) {
|
|
3996
|
+
allKeys.add(`${mod.type}:${mod.name}`);
|
|
3997
|
+
}
|
|
3998
|
+
const statuses = [];
|
|
3999
|
+
for (const key of allKeys) {
|
|
4000
|
+
const [typeStr, name] = key.split(":");
|
|
4001
|
+
const type = typeStr;
|
|
4002
|
+
const registeredEntry = (type === "shared" ? index.shared : index.features).find(
|
|
4003
|
+
(e) => e.name === name
|
|
4004
|
+
);
|
|
4005
|
+
const isRegistered = Boolean(registeredEntry);
|
|
4006
|
+
const diskInfo = diskMap.get(key);
|
|
4007
|
+
const existsOnDisk = Boolean(diskInfo && fs12__default.default.existsSync(diskInfo.dir));
|
|
4008
|
+
let hasUncommittedGitChanges = false;
|
|
4009
|
+
let uncommittedFilesCount = 0;
|
|
4010
|
+
let hasIntegrityDrift = false;
|
|
4011
|
+
let localIntegrity;
|
|
4012
|
+
if (existsOnDisk && diskInfo) {
|
|
4013
|
+
const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
|
|
4014
|
+
hasUncommittedGitChanges = git.hasChanges;
|
|
4015
|
+
uncommittedFilesCount = git.filesCount;
|
|
4016
|
+
localIntegrity = computeDirectoryHash(diskInfo.dir);
|
|
4017
|
+
if (isRegistered && registeredEntry?.integrity) {
|
|
4018
|
+
hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
statuses.push({
|
|
4022
|
+
name,
|
|
4023
|
+
type,
|
|
4024
|
+
isRegistered,
|
|
4025
|
+
existsOnDisk,
|
|
4026
|
+
hasUncommittedGitChanges,
|
|
4027
|
+
uncommittedFilesCount,
|
|
4028
|
+
hasIntegrityDrift,
|
|
4029
|
+
localIntegrity,
|
|
4030
|
+
registeredIntegrity: registeredEntry?.integrity
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
4034
|
+
}
|
|
4035
|
+
async function registerModule(typeArg, nameArg, flags = {}) {
|
|
4036
|
+
const projectRoot = findProjectRoot();
|
|
4037
|
+
const index = loadMasterIndex(projectRoot);
|
|
4038
|
+
let type = typeArg?.trim();
|
|
4039
|
+
if (!type) {
|
|
4040
|
+
type = handleCancel(
|
|
4041
|
+
await p15__namespace.select({
|
|
4042
|
+
message: "Module type to register:",
|
|
4043
|
+
options: [
|
|
4044
|
+
{ value: "feature", label: "Feature" },
|
|
4045
|
+
{ value: "shared", label: "Shared Module" }
|
|
4046
|
+
]
|
|
4047
|
+
})
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4051
|
+
const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
|
|
4052
|
+
const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
|
|
4053
|
+
let name = nameArg?.trim();
|
|
4054
|
+
if (!name) {
|
|
4055
|
+
if (unregistered.length > 0) {
|
|
4056
|
+
const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
|
|
4057
|
+
opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
|
|
4058
|
+
const choice = handleCancel(
|
|
4059
|
+
await p15__namespace.select({
|
|
4060
|
+
message: `Select a ${type} to register:`,
|
|
4061
|
+
options: opts
|
|
4062
|
+
})
|
|
4063
|
+
);
|
|
4064
|
+
if (choice === "__custom__") {
|
|
4065
|
+
name = handleCancel(
|
|
4066
|
+
await p15__namespace.text({
|
|
4067
|
+
message: `Enter ${type} name:`,
|
|
4068
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4069
|
+
})
|
|
4070
|
+
).trim();
|
|
4071
|
+
} else {
|
|
4072
|
+
name = choice;
|
|
4073
|
+
}
|
|
4074
|
+
} else {
|
|
4075
|
+
name = handleCancel(
|
|
4076
|
+
await p15__namespace.text({
|
|
4077
|
+
message: `Enter ${type} name to register:`,
|
|
4078
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4079
|
+
})
|
|
4080
|
+
).trim();
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4084
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4085
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4086
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4087
|
+
process.exit(1);
|
|
4088
|
+
}
|
|
4089
|
+
const files = getAllFiles(modDir);
|
|
4090
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4091
|
+
const manifestPath = path2__default.default.join(modDir, "registry.json");
|
|
4092
|
+
let manifest;
|
|
4093
|
+
if (fs12__default.default.existsSync(manifestPath)) {
|
|
4094
|
+
manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
|
|
4095
|
+
} else {
|
|
4096
|
+
manifest = {
|
|
4097
|
+
name,
|
|
4098
|
+
type,
|
|
4099
|
+
description: `${name} ${type} module`,
|
|
4100
|
+
version: "1.0.0",
|
|
4101
|
+
dependencies: {
|
|
4102
|
+
npm: [],
|
|
4103
|
+
shared: [],
|
|
4104
|
+
features: []
|
|
4105
|
+
},
|
|
4106
|
+
files: []
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
manifest.name = name;
|
|
4110
|
+
manifest.type = type;
|
|
4111
|
+
manifest.files = files;
|
|
4112
|
+
manifest.integrity = integrity;
|
|
4113
|
+
fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4114
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4115
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4116
|
+
if (existingIdx >= 0) {
|
|
4117
|
+
targetList[existingIdx] = {
|
|
4118
|
+
name,
|
|
4119
|
+
description: manifest.description,
|
|
4120
|
+
version: manifest.version,
|
|
4121
|
+
integrity
|
|
4122
|
+
};
|
|
4123
|
+
} else {
|
|
4124
|
+
targetList.push({
|
|
4125
|
+
name,
|
|
4126
|
+
description: manifest.description,
|
|
4127
|
+
version: manifest.version,
|
|
4128
|
+
integrity
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
saveMasterIndex(projectRoot, index);
|
|
4132
|
+
p15__namespace.log.success(
|
|
4133
|
+
pc15__default.default.green(
|
|
4134
|
+
`\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
|
|
4135
|
+
)
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
async function commitModule(typeArg, nameArg, flags = {}) {
|
|
4139
|
+
const projectRoot = findProjectRoot();
|
|
4140
|
+
let type = typeArg?.trim();
|
|
4141
|
+
if (!type) {
|
|
4142
|
+
type = handleCancel(
|
|
4143
|
+
await p15__namespace.select({
|
|
4144
|
+
message: "Module type to commit:",
|
|
4145
|
+
options: [
|
|
4146
|
+
{ value: "feature", label: "Feature" },
|
|
4147
|
+
{ value: "shared", label: "Shared Module" }
|
|
4148
|
+
]
|
|
4149
|
+
})
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
let name = nameArg?.trim();
|
|
4153
|
+
if (!name) {
|
|
4154
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4155
|
+
const opts = diskModules.map((m) => {
|
|
4156
|
+
const git = checkGitStatusForDir(projectRoot, m.dir);
|
|
4157
|
+
return {
|
|
4158
|
+
value: m.name,
|
|
4159
|
+
label: m.name,
|
|
4160
|
+
hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
|
|
4161
|
+
};
|
|
4162
|
+
});
|
|
4163
|
+
name = handleCancel(
|
|
4164
|
+
await p15__namespace.select({
|
|
4165
|
+
message: `Select ${type} to commit:`,
|
|
4166
|
+
options: opts
|
|
4167
|
+
})
|
|
4168
|
+
);
|
|
4169
|
+
}
|
|
4170
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4171
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4172
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4173
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4174
|
+
process.exit(1);
|
|
4175
|
+
}
|
|
4176
|
+
const relModDir = path2__default.default.relative(projectRoot, modDir);
|
|
4177
|
+
const gitStatus = checkGitStatusForDir(projectRoot, modDir);
|
|
4178
|
+
if (!gitStatus.hasChanges) {
|
|
4179
|
+
p15__namespace.log.warn(pc15__default.default.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
let message = flags.message?.trim();
|
|
4183
|
+
if (!message) {
|
|
4184
|
+
message = handleCancel(
|
|
4185
|
+
await p15__namespace.text({
|
|
4186
|
+
message: `Commit message for ${type}/${name}:`,
|
|
4187
|
+
placeholder: `e.g. feat(${name}): update use cases and dependencies`,
|
|
4188
|
+
validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
|
|
4189
|
+
})
|
|
4190
|
+
).trim();
|
|
4191
|
+
}
|
|
4192
|
+
const s = p15__namespace.spinner();
|
|
4193
|
+
s.start(`Committing '${type}/${name}' to git repository...`);
|
|
4194
|
+
try {
|
|
4195
|
+
child_process.execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
4196
|
+
child_process.execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
|
|
4197
|
+
cwd: projectRoot,
|
|
4198
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4199
|
+
});
|
|
4200
|
+
s.stop(`Changes for '${type}/${name}' committed to git.`);
|
|
4201
|
+
p15__namespace.log.success(pc15__default.default.green(`\u2713 Committed '${type}/${name}': "${message}"`));
|
|
4202
|
+
} catch (err) {
|
|
4203
|
+
s.stop(pc15__default.default.red("Git commit failed."));
|
|
4204
|
+
p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
|
|
4205
|
+
process.exit(1);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
async function registerModuleChanges(typeArg, nameArg, flags = {}) {
|
|
4209
|
+
const projectRoot = findProjectRoot();
|
|
4210
|
+
const index = loadMasterIndex(projectRoot);
|
|
4211
|
+
let type = typeArg?.trim();
|
|
4212
|
+
if (!type) {
|
|
4213
|
+
type = handleCancel(
|
|
4214
|
+
await p15__namespace.select({
|
|
4215
|
+
message: "Module type to update:",
|
|
4216
|
+
options: [
|
|
4217
|
+
{ value: "feature", label: "Feature" },
|
|
4218
|
+
{ value: "shared", label: "Shared Module" }
|
|
4219
|
+
]
|
|
4220
|
+
})
|
|
4221
|
+
);
|
|
4222
|
+
}
|
|
4223
|
+
let name = nameArg?.trim();
|
|
4224
|
+
if (!name) {
|
|
4225
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4226
|
+
const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
|
|
4227
|
+
const opts = diskModules.map((m) => {
|
|
4228
|
+
const d = diffs.find((item) => item.name === m.name);
|
|
4229
|
+
return {
|
|
4230
|
+
value: m.name,
|
|
4231
|
+
label: m.name,
|
|
4232
|
+
hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
|
|
4233
|
+
};
|
|
4234
|
+
});
|
|
4235
|
+
name = handleCancel(
|
|
4236
|
+
await p15__namespace.select({
|
|
4237
|
+
message: `Select ${type} to register changes:`,
|
|
4238
|
+
options: opts
|
|
4239
|
+
})
|
|
4240
|
+
);
|
|
4241
|
+
}
|
|
4242
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4243
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4244
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4245
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4246
|
+
process.exit(1);
|
|
4247
|
+
}
|
|
4248
|
+
const files = getAllFiles(modDir);
|
|
4249
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4250
|
+
const manifestPath = path2__default.default.join(modDir, "registry.json");
|
|
4251
|
+
let manifest;
|
|
4252
|
+
if (fs12__default.default.existsSync(manifestPath)) {
|
|
4253
|
+
manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
|
|
4254
|
+
} else {
|
|
4255
|
+
manifest = {
|
|
4256
|
+
name,
|
|
4257
|
+
type,
|
|
4258
|
+
description: `${name} ${type} module`,
|
|
4259
|
+
version: "1.0.0",
|
|
4260
|
+
dependencies: { npm: [], shared: [], features: [] },
|
|
4261
|
+
files: []
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
manifest.files = files;
|
|
4265
|
+
manifest.integrity = integrity;
|
|
4266
|
+
if (flags.version) {
|
|
4267
|
+
manifest.version = flags.version;
|
|
4268
|
+
}
|
|
4269
|
+
fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4270
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4271
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4272
|
+
if (existingIdx >= 0) {
|
|
4273
|
+
targetList[existingIdx] = {
|
|
4274
|
+
name,
|
|
4275
|
+
description: manifest.description,
|
|
4276
|
+
version: manifest.version,
|
|
4277
|
+
integrity
|
|
4278
|
+
};
|
|
4279
|
+
} else {
|
|
4280
|
+
targetList.push({
|
|
4281
|
+
name,
|
|
4282
|
+
description: manifest.description,
|
|
4283
|
+
version: manifest.version,
|
|
4284
|
+
integrity
|
|
4285
|
+
});
|
|
4286
|
+
}
|
|
4287
|
+
saveMasterIndex(projectRoot, index);
|
|
4288
|
+
p15__namespace.log.success(
|
|
4289
|
+
pc15__default.default.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
async function unregisterModule(typeArg, nameArg, flags = {}) {
|
|
4293
|
+
const projectRoot = findProjectRoot();
|
|
4294
|
+
const index = loadMasterIndex(projectRoot);
|
|
4295
|
+
let type = typeArg?.trim();
|
|
4296
|
+
if (!type) {
|
|
4297
|
+
type = handleCancel(
|
|
4298
|
+
await p15__namespace.select({
|
|
4299
|
+
message: "Module type to unregister:",
|
|
4300
|
+
options: [
|
|
4301
|
+
{ value: "feature", label: "Feature" },
|
|
4302
|
+
{ value: "shared", label: "Shared Module" }
|
|
4303
|
+
]
|
|
4304
|
+
})
|
|
4305
|
+
);
|
|
4306
|
+
}
|
|
4307
|
+
const registeredList = type === "shared" ? index.shared : index.features;
|
|
4308
|
+
if (registeredList.length === 0) {
|
|
4309
|
+
p15__namespace.log.warn(`No registered ${type} modules found in index.json.`);
|
|
4310
|
+
return;
|
|
4311
|
+
}
|
|
4312
|
+
let name = nameArg?.trim();
|
|
4313
|
+
if (!name) {
|
|
4314
|
+
name = handleCancel(
|
|
4315
|
+
await p15__namespace.select({
|
|
4316
|
+
message: `Select ${type} to unregister from index.json:`,
|
|
4317
|
+
options: registeredList.map((item) => ({
|
|
4318
|
+
value: item.name,
|
|
4319
|
+
label: item.name,
|
|
4320
|
+
hint: item.description
|
|
4321
|
+
}))
|
|
4322
|
+
})
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
if (type === "shared") {
|
|
4326
|
+
index.shared = index.shared.filter((item) => item.name !== name);
|
|
4327
|
+
} else {
|
|
4328
|
+
index.features = index.features.filter((item) => item.name !== name);
|
|
4329
|
+
}
|
|
4330
|
+
saveMasterIndex(projectRoot, index);
|
|
4331
|
+
p15__namespace.log.success(
|
|
4332
|
+
pc15__default.default.green(
|
|
4333
|
+
`\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
|
|
4334
|
+
)
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
function diffRegistry(flags = {}) {
|
|
4338
|
+
const projectRoot = findProjectRoot();
|
|
4339
|
+
const diffs = getRegistryDiff(projectRoot);
|
|
4340
|
+
p15__namespace.log.step(pc15__default.default.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
|
|
4341
|
+
const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
|
|
4342
|
+
const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
|
|
4343
|
+
const committedDrift = diffs.filter(
|
|
4344
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
|
|
4345
|
+
);
|
|
4346
|
+
const upToDate = diffs.filter(
|
|
4347
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
|
|
4348
|
+
);
|
|
4349
|
+
const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
|
|
4350
|
+
if (unregistered.length > 0) {
|
|
4351
|
+
p15__namespace.log.message(pc15__default.default.yellow(`
|
|
4352
|
+
\u{1F4E6} Unregistered Modules (${unregistered.length}):`));
|
|
4353
|
+
for (const mod of unregistered) {
|
|
4354
|
+
p15__namespace.log.message(` ${pc15__default.default.red("\u25CF")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(not registered in index.json)")}`);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
if (uncommitted.length > 0) {
|
|
4358
|
+
p15__namespace.log.message(pc15__default.default.yellow(`
|
|
4359
|
+
\u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
|
|
4360
|
+
for (const mod of uncommitted) {
|
|
4361
|
+
p15__namespace.log.message(
|
|
4362
|
+
` ${pc15__default.default.yellow("\u25B2")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
|
|
4363
|
+
);
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
if (committedDrift.length > 0) {
|
|
4367
|
+
p15__namespace.log.message(pc15__default.default.cyan(`
|
|
4368
|
+
\u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
|
|
4369
|
+
for (const mod of committedDrift) {
|
|
4370
|
+
p15__namespace.log.message(
|
|
4371
|
+
` ${pc15__default.default.cyan("\u25C6")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(
|
|
4372
|
+
`local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
|
|
4373
|
+
)}`
|
|
4374
|
+
);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (missing.length > 0) {
|
|
4378
|
+
p15__namespace.log.message(pc15__default.default.red(`
|
|
4379
|
+
\u26A0\uFE0F Missing on Disk (${missing.length}):`));
|
|
4380
|
+
for (const mod of missing) {
|
|
4381
|
+
p15__namespace.log.message(` ${pc15__default.default.red("\u2715")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(in index.json, but folder missing)")}`);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
if (upToDate.length > 0) {
|
|
4385
|
+
p15__namespace.log.message(pc15__default.default.green(`
|
|
4386
|
+
\u2713 Up to Date & Registered (${upToDate.length}):`));
|
|
4387
|
+
for (const mod of upToDate) {
|
|
4388
|
+
p15__namespace.log.message(` ${pc15__default.default.green("\u2714")} ${mod.type}/${mod.name} ${pc15__default.default.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
p15__namespace.log.message("");
|
|
4392
|
+
}
|
|
4393
|
+
async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
|
|
4394
|
+
let action = actionArg?.trim().toLowerCase();
|
|
4395
|
+
if (!action) {
|
|
4396
|
+
action = handleCancel(
|
|
4397
|
+
await p15__namespace.select({
|
|
4398
|
+
message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
|
|
4399
|
+
options: [
|
|
4400
|
+
{
|
|
4401
|
+
value: "diff",
|
|
4402
|
+
label: "Inspect Diff & Status",
|
|
4403
|
+
hint: "Summary of unregistered, uncommitted, and drifted modules"
|
|
4404
|
+
},
|
|
4405
|
+
{
|
|
4406
|
+
value: "register",
|
|
4407
|
+
label: "Register Module",
|
|
4408
|
+
hint: "Add an existing module to index.json with integrity checksum"
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
value: "commit",
|
|
4412
|
+
label: "Commit Changes",
|
|
4413
|
+
hint: "Commit a module's working changes to git in the background"
|
|
4414
|
+
},
|
|
4415
|
+
{
|
|
4416
|
+
value: "register-changes",
|
|
4417
|
+
label: "Register Changes",
|
|
4418
|
+
hint: "Update index.json & registry.json with latest checksums"
|
|
4419
|
+
},
|
|
4420
|
+
{
|
|
4421
|
+
value: "unregister",
|
|
4422
|
+
label: "Unregister Module",
|
|
4423
|
+
hint: "Remove module from index.json (source code stays on disk)"
|
|
4424
|
+
}
|
|
4425
|
+
]
|
|
4426
|
+
})
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
switch (action) {
|
|
4430
|
+
case "diff":
|
|
4431
|
+
case "status":
|
|
4432
|
+
diffRegistry(flags);
|
|
4433
|
+
break;
|
|
4434
|
+
case "register":
|
|
4435
|
+
await registerModule(typeArg, nameArg, flags);
|
|
4436
|
+
break;
|
|
4437
|
+
case "commit":
|
|
4438
|
+
await commitModule(typeArg, nameArg, flags);
|
|
4439
|
+
break;
|
|
4440
|
+
case "register-changes":
|
|
4441
|
+
case "update":
|
|
4442
|
+
await registerModuleChanges(typeArg, nameArg, flags);
|
|
4443
|
+
break;
|
|
4444
|
+
case "unregister":
|
|
4445
|
+
await unregisterModule(typeArg, nameArg, flags);
|
|
4446
|
+
break;
|
|
4447
|
+
default:
|
|
4448
|
+
p15__namespace.log.error(
|
|
4449
|
+
pc15__default.default.red(
|
|
4450
|
+
`Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
|
|
4451
|
+
)
|
|
4452
|
+
);
|
|
4453
|
+
process.exit(1);
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
3376
4456
|
|
|
3377
4457
|
// src/cli.ts
|
|
3378
4458
|
function createCli() {
|
|
3379
4459
|
const cli = cac.cac("mason");
|
|
3380
|
-
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) => {
|
|
4460
|
+
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) => {
|
|
3381
4461
|
if (args && args.length > 0) {
|
|
3382
|
-
|
|
3383
|
-
|
|
4462
|
+
p15__namespace.log.error(
|
|
4463
|
+
pc15__default.default.red(
|
|
3384
4464
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3385
4465
|
)
|
|
3386
4466
|
);
|
|
3387
4467
|
process.exit(1);
|
|
3388
4468
|
}
|
|
3389
4469
|
try {
|
|
4470
|
+
const projectRoot = findProjectRoot();
|
|
4471
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
4472
|
+
if (mode === "registry") {
|
|
4473
|
+
const choice2 = handleCancel(
|
|
4474
|
+
await p15__namespace.select({
|
|
4475
|
+
message: "\u{1F3DB}\uFE0F Mason Registry Maintainer: What would you like to do?",
|
|
4476
|
+
options: [
|
|
4477
|
+
{
|
|
4478
|
+
value: "diff",
|
|
4479
|
+
label: "Inspect Diff & Status",
|
|
4480
|
+
hint: "Overview of unregistered, uncommitted, or drifted modules"
|
|
4481
|
+
},
|
|
4482
|
+
{
|
|
4483
|
+
value: "create_feature",
|
|
4484
|
+
label: "Initialize New Feature",
|
|
4485
|
+
hint: "Scaffold a new feature slice under features/"
|
|
4486
|
+
},
|
|
4487
|
+
{
|
|
4488
|
+
value: "create_shared",
|
|
4489
|
+
label: "Initialize New Shared Module",
|
|
4490
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
4491
|
+
},
|
|
4492
|
+
{
|
|
4493
|
+
value: "create_usecase",
|
|
4494
|
+
label: "Create New Use Case",
|
|
4495
|
+
hint: "Add a new use case to an existing feature"
|
|
4496
|
+
},
|
|
4497
|
+
{
|
|
4498
|
+
value: "register",
|
|
4499
|
+
label: "Register Module",
|
|
4500
|
+
hint: "Add a module to index.json with integrity checksum"
|
|
4501
|
+
},
|
|
4502
|
+
{
|
|
4503
|
+
value: "commit",
|
|
4504
|
+
label: "Commit Module Changes",
|
|
4505
|
+
hint: "Commit working changes in a module to git"
|
|
4506
|
+
},
|
|
4507
|
+
{
|
|
4508
|
+
value: "register_changes",
|
|
4509
|
+
label: "Register Changes",
|
|
4510
|
+
hint: "Recompute checksums and update index.json"
|
|
4511
|
+
},
|
|
4512
|
+
{
|
|
4513
|
+
value: "unregister",
|
|
4514
|
+
label: "Unregister Module",
|
|
4515
|
+
hint: "Remove from index.json but keep source on disk"
|
|
4516
|
+
},
|
|
4517
|
+
{
|
|
4518
|
+
value: "import",
|
|
4519
|
+
label: "Import from Remote Registry",
|
|
4520
|
+
hint: "Download a module from upstream"
|
|
4521
|
+
}
|
|
4522
|
+
]
|
|
4523
|
+
})
|
|
4524
|
+
);
|
|
4525
|
+
switch (choice2) {
|
|
4526
|
+
case "diff":
|
|
4527
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4528
|
+
break;
|
|
4529
|
+
case "create_feature":
|
|
4530
|
+
await handleCreate("feature", void 0, flags);
|
|
4531
|
+
break;
|
|
4532
|
+
case "create_shared":
|
|
4533
|
+
await handleCreate("shared", void 0, flags);
|
|
4534
|
+
break;
|
|
4535
|
+
case "create_usecase":
|
|
4536
|
+
await handleCreate("usecase", void 0, flags);
|
|
4537
|
+
break;
|
|
4538
|
+
case "register":
|
|
4539
|
+
await handleRegistry("register", void 0, void 0, flags);
|
|
4540
|
+
break;
|
|
4541
|
+
case "commit":
|
|
4542
|
+
await handleRegistry("commit", void 0, void 0, flags);
|
|
4543
|
+
break;
|
|
4544
|
+
case "register_changes":
|
|
4545
|
+
await handleRegistry("register-changes", void 0, void 0, flags);
|
|
4546
|
+
break;
|
|
4547
|
+
case "unregister":
|
|
4548
|
+
await handleRegistry("unregister", void 0, void 0, flags);
|
|
4549
|
+
break;
|
|
4550
|
+
case "import":
|
|
4551
|
+
await handleImport(void 0, void 0, flags);
|
|
4552
|
+
break;
|
|
4553
|
+
}
|
|
4554
|
+
return;
|
|
4555
|
+
}
|
|
3390
4556
|
const choice = handleCancel(
|
|
3391
|
-
await
|
|
4557
|
+
await p15__namespace.select({
|
|
3392
4558
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3393
4559
|
options: [
|
|
3394
4560
|
{
|
|
3395
4561
|
value: "create",
|
|
3396
4562
|
label: "Create local component",
|
|
3397
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
4563
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3398
4564
|
},
|
|
3399
4565
|
{
|
|
3400
4566
|
value: "import",
|
|
3401
4567
|
label: "Import from registry",
|
|
3402
4568
|
hint: "Download and link pre-built shared modules or features from the registry"
|
|
4569
|
+
},
|
|
4570
|
+
{
|
|
4571
|
+
value: "registry",
|
|
4572
|
+
label: "Registry management",
|
|
4573
|
+
hint: "Inspect diff, register modules, or commit module changes"
|
|
3403
4574
|
}
|
|
3404
4575
|
]
|
|
3405
4576
|
})
|
|
3406
4577
|
);
|
|
3407
4578
|
if (choice === "create") {
|
|
3408
4579
|
await handleCreate(void 0, void 0, flags);
|
|
4580
|
+
} else if (choice === "registry") {
|
|
4581
|
+
await handleRegistry(void 0, void 0, void 0, flags);
|
|
3409
4582
|
} else {
|
|
3410
4583
|
await handleImport(void 0, void 0, flags);
|
|
3411
4584
|
}
|
|
@@ -3415,8 +4588,8 @@ function createCli() {
|
|
|
3415
4588
|
});
|
|
3416
4589
|
cli.command(
|
|
3417
4590
|
"create [type] [name]",
|
|
3418
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3419
|
-
).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 = {}) => {
|
|
4591
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
4592
|
+
).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 = {}) => {
|
|
3420
4593
|
try {
|
|
3421
4594
|
await handleCreate(type, name, flags);
|
|
3422
4595
|
} catch (err) {
|
|
@@ -3433,8 +4606,28 @@ function createCli() {
|
|
|
3433
4606
|
handleCliError(err);
|
|
3434
4607
|
}
|
|
3435
4608
|
});
|
|
4609
|
+
cli.command(
|
|
4610
|
+
"registry [action] [type] [name]",
|
|
4611
|
+
"Registry & version management (diff, register, commit, register-changes, unregister)"
|
|
4612
|
+
).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(
|
|
4613
|
+
async (action, type, name, flags = {}) => {
|
|
4614
|
+
try {
|
|
4615
|
+
await handleRegistry(action, type, name, flags);
|
|
4616
|
+
} catch (err) {
|
|
4617
|
+
handleCliError(err);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
);
|
|
4621
|
+
cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
|
|
4622
|
+
try {
|
|
4623
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4624
|
+
} catch (err) {
|
|
4625
|
+
handleCliError(err);
|
|
4626
|
+
}
|
|
4627
|
+
});
|
|
3436
4628
|
const createSubtypes = [
|
|
3437
4629
|
"feature",
|
|
4630
|
+
"shared",
|
|
3438
4631
|
"usecase",
|
|
3439
4632
|
"domain",
|
|
3440
4633
|
"infrastructure",
|
|
@@ -3455,7 +4648,7 @@ function createCli() {
|
|
|
3455
4648
|
});
|
|
3456
4649
|
}
|
|
3457
4650
|
cli.help();
|
|
3458
|
-
cli.version("1.0.
|
|
4651
|
+
cli.version("1.0.13");
|
|
3459
4652
|
return cli;
|
|
3460
4653
|
}
|
|
3461
4654
|
function handleCliError(err) {
|
|
@@ -3463,7 +4656,7 @@ function handleCliError(err) {
|
|
|
3463
4656
|
process.exit(0);
|
|
3464
4657
|
}
|
|
3465
4658
|
const message = err instanceof Error ? err.message : String(err);
|
|
3466
|
-
|
|
4659
|
+
p15__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
|
|
3467
4660
|
process.exit(1);
|
|
3468
4661
|
}
|
|
3469
4662
|
async function runCli(argv = process.argv) {
|