@solid-stack/mason 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +750 -217
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +747 -214
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/feature.d.ts.map +1 -1
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/core/sharedGenerator.d.ts +13 -0
- package/dist/core/sharedGenerator.d.ts.map +1 -0
- package/dist/index.cjs +770 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +758 -215
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +2 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
var cac = require('cac');
|
|
5
5
|
var p3 = require('@clack/prompts');
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
6
|
+
var pc14 = require('picocolors');
|
|
7
|
+
var fs9 = 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 }; }
|
|
@@ -32,15 +33,15 @@ function _interopNamespace(e) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
var p3__namespace = /*#__PURE__*/_interopNamespace(p3);
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
var
|
|
36
|
+
var pc14__default = /*#__PURE__*/_interopDefault(pc14);
|
|
37
|
+
var fs9__default = /*#__PURE__*/_interopDefault(fs9);
|
|
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 = fs9__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 (!fs9__default.default.existsSync(dir)) {
|
|
65
|
+
fs9__default.default.mkdirSync(dir, { recursive: true });
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
fs9__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
|
-
p3__namespace.log.warn(
|
|
79
|
+
p3__namespace.log.warn(pc14__default.default.yellow(pc14__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
|
-
p3__namespace.log.success(`${
|
|
83
|
+
p3__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
|
|
83
84
|
} else if (f.action === "overwritten") {
|
|
84
|
-
p3__namespace.log.warn(`${
|
|
85
|
+
p3__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
|
|
85
86
|
} else if (f.action === "skipped") {
|
|
86
|
-
p3__namespace.log.info(`${
|
|
87
|
+
p3__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
87
88
|
} else {
|
|
88
|
-
p3__namespace.log.info(`${
|
|
89
|
+
p3__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__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 (fs9__default.default.existsSync(configPath)) {
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(fs9__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 (fs9__default.default.existsSync(path2__default.default.join(projectRoot, "index.json")) && (fs9__default.default.existsSync(path2__default.default.join(projectRoot, "features")) || fs9__default.default.existsSync(path2__default.default.join(projectRoot, "shared"))) && !fs9__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 (fs9__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 (fs9__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 (fs9__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 (!fs9__default.default.existsSync(featuresDir)) return [];
|
|
175
239
|
try {
|
|
176
|
-
const entries =
|
|
240
|
+
const entries = fs9__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 (!fs9__default.default.existsSync(domainDir)) continue;
|
|
188
252
|
try {
|
|
189
|
-
const files =
|
|
253
|
+
const files = fs9__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 = fs9__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 (!fs9__default.default.existsSync(domainDir)) continue;
|
|
226
290
|
try {
|
|
227
|
-
const files =
|
|
291
|
+
const files = fs9__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 (!fs9__default.default.existsSync(useCasesDir)) continue;
|
|
251
315
|
try {
|
|
252
|
-
const files =
|
|
316
|
+
const files = fs9__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 && fs9__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 (fs9__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 (fs9__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 (fs9__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 (fs9__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 fs9__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((p16) => `${p16.name}: ${p16.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 && fs9__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 (!fs9__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 (fs9__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 (fs9__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
778
|
+
const p22 = path2__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
779
|
+
if (fs9__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
780
|
+
const p32 = path2__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
781
|
+
if (fs9__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 = fs9__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 p16 of [...inputProps, ...outputProps]) {
|
|
1077
|
+
const rawType = p16.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((p16) => ({
|
|
1084
|
+
name: p16.name,
|
|
1085
|
+
optional: p16.optional,
|
|
1086
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1023
1087
|
}));
|
|
1024
|
-
const formattedOutputProps = outputProps.map((
|
|
1025
|
-
name:
|
|
1026
|
-
optional:
|
|
1027
|
-
type:
|
|
1088
|
+
const formattedOutputProps = outputProps.map((p16) => ({
|
|
1089
|
+
name: p16.name,
|
|
1090
|
+
optional: p16.optional,
|
|
1091
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.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 (!fs9__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 (fs9__default.default.existsSync(providerPath) && !options.overwrite) {
|
|
1316
|
+
let content = fs9__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(
|
|
@@ -1703,13 +1767,13 @@ function handleCancel(result) {
|
|
|
1703
1767
|
}
|
|
1704
1768
|
function printBanner() {
|
|
1705
1769
|
console.log("");
|
|
1706
|
-
p3__namespace.intro(
|
|
1770
|
+
p3__namespace.intro(pc14__default.default.bgCyan(pc14__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
|
-
p3__namespace.log.info(
|
|
1775
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
1776
|
+
p3__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
1713
1777
|
const addMore = handleCancel(
|
|
1714
1778
|
await p3__namespace.confirm({
|
|
1715
1779
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -1720,7 +1784,7 @@ async function promptProperties(options = {}) {
|
|
|
1720
1784
|
return properties;
|
|
1721
1785
|
}
|
|
1722
1786
|
}
|
|
1723
|
-
p3__namespace.log.info(
|
|
1787
|
+
p3__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
1724
1788
|
while (true) {
|
|
1725
1789
|
const name = handleCancel(
|
|
1726
1790
|
await p3__namespace.text({
|
|
@@ -1768,7 +1832,7 @@ async function promptProperties(options = {}) {
|
|
|
1768
1832
|
optional
|
|
1769
1833
|
});
|
|
1770
1834
|
p3__namespace.log.success(
|
|
1771
|
-
|
|
1835
|
+
pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1772
1836
|
);
|
|
1773
1837
|
}
|
|
1774
1838
|
return properties;
|
|
@@ -1777,6 +1841,7 @@ 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";
|
|
@@ -1794,9 +1859,10 @@ 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
1867
|
await p3__namespace.text({
|
|
1802
1868
|
message: "Target directory for the feature:",
|
|
@@ -1805,7 +1871,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
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;
|
|
@@ -2026,7 +2092,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2026
2092
|
}
|
|
2027
2093
|
}
|
|
2028
2094
|
const s = p3__namespace.spinner();
|
|
2029
|
-
s.start(`Generating feature '${
|
|
2095
|
+
s.start(`Generating feature '${pc14__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 (!fs9__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
|
-
p3__namespace.outro(
|
|
2141
|
+
p3__namespace.outro(
|
|
2142
|
+
pc14__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 p3__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 p3__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 p3__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 p3__namespace.confirm({
|
|
2292
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2293
|
+
initialValue: true
|
|
2294
|
+
})
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
const s = p3__namespace.spinner();
|
|
2298
|
+
s.start(`Generating shared module '${pc14__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
|
+
p3__namespace.outro(
|
|
2312
|
+
pc14__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();
|
|
@@ -2173,7 +2437,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2173
2437
|
}
|
|
2174
2438
|
}
|
|
2175
2439
|
const s = p3__namespace.spinner();
|
|
2176
|
-
s.start(`Generating use case '${
|
|
2440
|
+
s.start(`Generating use case '${pc14__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
|
-
p3__namespace.outro(
|
|
2472
|
+
p3__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2209
2473
|
}
|
|
2210
2474
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2211
2475
|
const projectRoot = findProjectRoot();
|
|
@@ -2276,7 +2540,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2276
2540
|
});
|
|
2277
2541
|
}
|
|
2278
2542
|
const s = p3__namespace.spinner();
|
|
2279
|
-
s.start(`Generating domain model '${
|
|
2543
|
+
s.start(`Generating domain model '${pc14__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
|
-
p3__namespace.outro(
|
|
2556
|
+
p3__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2293
2557
|
}
|
|
2294
2558
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2295
2559
|
const projectRoot = findProjectRoot();
|
|
@@ -2458,7 +2722,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2458
2722
|
registerInProvider = !flags.noProvider;
|
|
2459
2723
|
}
|
|
2460
2724
|
const s = p3__namespace.spinner();
|
|
2461
|
-
s.start(`Generating infrastructure class '${
|
|
2725
|
+
s.start(`Generating infrastructure class '${pc14__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
|
-
p3__namespace.outro(
|
|
2760
|
+
p3__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2497
2761
|
}
|
|
2498
2762
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2499
2763
|
const projectRoot = findProjectRoot();
|
|
@@ -2606,7 +2870,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2606
2870
|
}
|
|
2607
2871
|
}
|
|
2608
2872
|
const s = p3__namespace.spinner();
|
|
2609
|
-
s.start(`Generating interface '${
|
|
2873
|
+
s.start(`Generating interface '${pc14__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
|
-
p3__namespace.outro(
|
|
2922
|
+
p3__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2659
2923
|
}
|
|
2660
2924
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2661
2925
|
const projectRoot = findProjectRoot();
|
|
@@ -2772,7 +3036,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2772
3036
|
}
|
|
2773
3037
|
}
|
|
2774
3038
|
const s = p3__namespace.spinner();
|
|
2775
|
-
s.start(`Generating HTTP handler '${
|
|
3039
|
+
s.start(`Generating HTTP handler '${pc14__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
|
-
p3__namespace.outro(
|
|
3057
|
+
p3__namespace.outro(pc14__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((p16) => `${p16.name}: ${p16.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, {
|
|
@@ -2946,7 +3210,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2946
3210
|
);
|
|
2947
3211
|
}
|
|
2948
3212
|
const s = p3__namespace.spinner();
|
|
2949
|
-
s.start(`Generating injectable '${
|
|
3213
|
+
s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
|
|
2950
3214
|
const options = {
|
|
2951
3215
|
name: className,
|
|
2952
3216
|
feature,
|
|
@@ -2960,7 +3224,7 @@ 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
|
-
p3__namespace.outro(
|
|
3227
|
+
p3__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
|
|
2964
3228
|
}
|
|
2965
3229
|
async function runInteractiveMenu(flags = {}) {
|
|
2966
3230
|
printBanner();
|
|
@@ -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;
|
|
@@ -3063,8 +3338,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3063
3338
|
break;
|
|
3064
3339
|
default:
|
|
3065
3340
|
p3__namespace.log.error(
|
|
3066
|
-
|
|
3067
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3341
|
+
pc14__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);
|
|
@@ -3087,25 +3362,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
|
3087
3362
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
3088
3363
|
}
|
|
3089
3364
|
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
3090
|
-
return
|
|
3365
|
+
return path2__default.default.resolve(baseDir, trimmed);
|
|
3091
3366
|
}
|
|
3092
3367
|
return trimmed;
|
|
3093
3368
|
}
|
|
3094
3369
|
function findLocalRegistry(startDir = process.cwd()) {
|
|
3095
|
-
let current =
|
|
3370
|
+
let current = path2__default.default.resolve(startDir);
|
|
3096
3371
|
while (true) {
|
|
3097
|
-
const candidate =
|
|
3098
|
-
if (
|
|
3372
|
+
const candidate = path2__default.default.join(current, "mason-registry");
|
|
3373
|
+
if (fs9__default.default.existsSync(candidate) && fs9__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
|
|
3099
3374
|
return candidate;
|
|
3100
3375
|
}
|
|
3101
|
-
const parent =
|
|
3376
|
+
const parent = path2__default.default.dirname(current);
|
|
3102
3377
|
if (parent === current) break;
|
|
3103
3378
|
current = parent;
|
|
3104
3379
|
}
|
|
3105
3380
|
return null;
|
|
3106
3381
|
}
|
|
3107
3382
|
function getRegistryBase(projectRoot, override) {
|
|
3108
|
-
const rootDir = projectRoot ?
|
|
3383
|
+
const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
|
|
3109
3384
|
if (override && override.trim()) {
|
|
3110
3385
|
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
3111
3386
|
}
|
|
@@ -3113,30 +3388,30 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3113
3388
|
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
3114
3389
|
}
|
|
3115
3390
|
const configLocations = [
|
|
3116
|
-
|
|
3117
|
-
|
|
3391
|
+
path2__default.default.join(rootDir, "mason.config.json"),
|
|
3392
|
+
path2__default.default.join(process.cwd(), "mason.config.json")
|
|
3118
3393
|
];
|
|
3119
3394
|
for (const configPath of configLocations) {
|
|
3120
|
-
if (
|
|
3395
|
+
if (fs9__default.default.existsSync(configPath)) {
|
|
3121
3396
|
try {
|
|
3122
|
-
const config = JSON.parse(
|
|
3397
|
+
const config = JSON.parse(fs9__default.default.readFileSync(configPath, "utf-8"));
|
|
3123
3398
|
if (config.registry && typeof config.registry === "string") {
|
|
3124
|
-
return normalizeRegistryUrl(config.registry.trim(),
|
|
3399
|
+
return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
|
|
3125
3400
|
}
|
|
3126
3401
|
} catch {
|
|
3127
3402
|
}
|
|
3128
3403
|
}
|
|
3129
3404
|
}
|
|
3130
3405
|
const pkgLocations = [
|
|
3131
|
-
|
|
3132
|
-
|
|
3406
|
+
path2__default.default.join(rootDir, "package.json"),
|
|
3407
|
+
path2__default.default.join(process.cwd(), "package.json")
|
|
3133
3408
|
];
|
|
3134
3409
|
for (const pkgPath of pkgLocations) {
|
|
3135
|
-
if (
|
|
3410
|
+
if (fs9__default.default.existsSync(pkgPath)) {
|
|
3136
3411
|
try {
|
|
3137
|
-
const pkg = JSON.parse(
|
|
3412
|
+
const pkg = JSON.parse(fs9__default.default.readFileSync(pkgPath, "utf-8"));
|
|
3138
3413
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
3139
|
-
return normalizeRegistryUrl(pkg.mason.registry.trim(),
|
|
3414
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
|
|
3140
3415
|
}
|
|
3141
3416
|
} catch {
|
|
3142
3417
|
}
|
|
@@ -3177,11 +3452,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
3177
3452
|
if (baseDir.startsWith("file://")) {
|
|
3178
3453
|
baseDir = url.fileURLToPath(baseDir);
|
|
3179
3454
|
}
|
|
3180
|
-
const filePath =
|
|
3181
|
-
if (!
|
|
3455
|
+
const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
|
|
3456
|
+
if (!fs9__default.default.existsSync(filePath)) {
|
|
3182
3457
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
3183
3458
|
}
|
|
3184
|
-
return
|
|
3459
|
+
return fs9__default.default.promises.readFile(filePath, "utf-8");
|
|
3185
3460
|
}
|
|
3186
3461
|
async function fetchRegistryIndex(registryBase) {
|
|
3187
3462
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -3243,26 +3518,26 @@ function findClosestMatch(target, candidates) {
|
|
|
3243
3518
|
return null;
|
|
3244
3519
|
}
|
|
3245
3520
|
function detectPackageManager(projectRoot) {
|
|
3246
|
-
if (
|
|
3247
|
-
if (
|
|
3248
|
-
if (
|
|
3521
|
+
if (fs9__default.default.existsSync(path2__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3522
|
+
if (fs9__default.default.existsSync(path2__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
3523
|
+
if (fs9__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lockb")) || fs9__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lock"))) return "bun";
|
|
3249
3524
|
return "npm";
|
|
3250
3525
|
}
|
|
3251
3526
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
3252
3527
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
3253
|
-
const pkgJsonPath =
|
|
3254
|
-
if (!
|
|
3528
|
+
const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
|
|
3529
|
+
if (!fs9__default.default.existsSync(pkgJsonPath)) {
|
|
3255
3530
|
return requiredPackages;
|
|
3256
3531
|
}
|
|
3257
3532
|
try {
|
|
3258
|
-
const pkg = JSON.parse(
|
|
3533
|
+
const pkg = JSON.parse(fs9__default.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
3259
3534
|
const installed = /* @__PURE__ */ new Set([
|
|
3260
3535
|
...Object.keys(pkg.dependencies || {}),
|
|
3261
3536
|
...Object.keys(pkg.devDependencies || {}),
|
|
3262
3537
|
...Object.keys(pkg.peerDependencies || {})
|
|
3263
3538
|
]);
|
|
3264
|
-
return requiredPackages.filter((
|
|
3265
|
-
const nameOnly =
|
|
3539
|
+
return requiredPackages.filter((p16) => {
|
|
3540
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
3266
3541
|
return !installed.has(nameOnly);
|
|
3267
3542
|
});
|
|
3268
3543
|
} catch {
|
|
@@ -3287,21 +3562,207 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3287
3562
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3288
3563
|
break;
|
|
3289
3564
|
}
|
|
3290
|
-
p3__namespace.log.info(`Installing missing dependencies with ${manager}: ${
|
|
3565
|
+
p3__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
|
|
3291
3566
|
child_process.execSync(installCmd, {
|
|
3292
3567
|
cwd: projectRoot,
|
|
3293
3568
|
stdio: "inherit"
|
|
3294
3569
|
});
|
|
3295
3570
|
}
|
|
3571
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
3572
|
+
if (!fs9__default.default.existsSync(dir)) return [];
|
|
3573
|
+
const entries = fs9__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 (!fs9__default.default.existsSync(dirPath)) return "";
|
|
3594
|
+
const hash = crypto.createHash("sha256");
|
|
3595
|
+
const fileList = (getAllFiles(dirPath)).sort();
|
|
3596
|
+
for (const relativePath of fileList) {
|
|
3597
|
+
const absolutePath = path2__default.default.join(dirPath, relativePath);
|
|
3598
|
+
if (!fs9__default.default.existsSync(absolutePath)) continue;
|
|
3599
|
+
const content = fs9__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
|
+
}
|
|
3296
3606
|
|
|
3297
3607
|
// src/commands/import.ts
|
|
3608
|
+
var importPrompts = {
|
|
3609
|
+
select: p3__namespace.select,
|
|
3610
|
+
text: p3__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 (fs9__default.default.existsSync(inSrc)) return inSrc;
|
|
3632
|
+
const inRoot = path2__default.default.join(projectRoot, folder, modName);
|
|
3633
|
+
if (fs9__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
|
+
p3__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
|
+
p3__namespace.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14__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
|
+
p3__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
|
+
p3__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
|
+
fs9__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3685
|
+
}
|
|
3686
|
+
fs9__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
|
+
p3__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
|
+
p3__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
|
+
p3__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3742
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
if (choice === "overwrite") {
|
|
3746
|
+
p3__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
|
+
fs9__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3752
|
+
}
|
|
3753
|
+
fs9__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3754
|
+
}
|
|
3755
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3298
3759
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3299
3760
|
const projectRoot = findProjectRoot();
|
|
3300
3761
|
let type = typeArg?.trim().toLowerCase();
|
|
3301
3762
|
if (type === "features") type = "feature";
|
|
3302
3763
|
if (!type) {
|
|
3303
3764
|
type = handleCancel(
|
|
3304
|
-
await
|
|
3765
|
+
await importPrompts.select({
|
|
3305
3766
|
message: "What kind of module would you like to import?",
|
|
3306
3767
|
options: [
|
|
3307
3768
|
{
|
|
@@ -3319,19 +3780,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3319
3780
|
);
|
|
3320
3781
|
}
|
|
3321
3782
|
if (type !== "shared" && type !== "feature") {
|
|
3322
|
-
p3__namespace.log.error(
|
|
3783
|
+
p3__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3323
3784
|
process.exit(1);
|
|
3324
3785
|
}
|
|
3325
3786
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3326
3787
|
const s = p3__namespace.spinner();
|
|
3327
|
-
s.start(`Connecting to registry at ${
|
|
3788
|
+
s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
|
|
3328
3789
|
let index;
|
|
3329
3790
|
try {
|
|
3330
3791
|
index = await fetchRegistryIndex(registryBase);
|
|
3331
3792
|
s.stop("Registry index loaded.");
|
|
3332
3793
|
} catch (err) {
|
|
3333
|
-
s.stop(
|
|
3334
|
-
p3__namespace.log.error(
|
|
3794
|
+
s.stop(pc14__default.default.red("Failed to load registry index."));
|
|
3795
|
+
p3__namespace.log.error(pc14__default.default.red(err.message || String(err)));
|
|
3335
3796
|
process.exit(1);
|
|
3336
3797
|
}
|
|
3337
3798
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3343,7 +3804,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3343
3804
|
return;
|
|
3344
3805
|
}
|
|
3345
3806
|
name = handleCancel(
|
|
3346
|
-
await
|
|
3807
|
+
await importPrompts.select({
|
|
3347
3808
|
message: `Select a ${type} to import:`,
|
|
3348
3809
|
options: availableList.map((item) => ({
|
|
3349
3810
|
value: item.name,
|
|
@@ -3355,71 +3816,94 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3355
3816
|
}
|
|
3356
3817
|
if (!candidateNames.includes(name)) {
|
|
3357
3818
|
const closest = findClosestMatch(name, candidateNames);
|
|
3358
|
-
p3__namespace.log.error(
|
|
3819
|
+
p3__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3359
3820
|
if (closest) {
|
|
3360
|
-
p3__namespace.log.info(
|
|
3821
|
+
p3__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
|
|
3361
3822
|
}
|
|
3362
3823
|
process.exit(1);
|
|
3363
3824
|
}
|
|
3364
3825
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3365
3826
|
const totalWrittenFiles = [];
|
|
3366
3827
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3367
|
-
async function importModuleRecursive(modType,
|
|
3368
|
-
const modKey = `${modType}:${
|
|
3828
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3829
|
+
const modKey = `${modType}:${targetName}`;
|
|
3369
3830
|
if (importedModules.has(modKey)) return;
|
|
3370
|
-
p3__namespace.log.step(
|
|
3831
|
+
p3__namespace.log.step(
|
|
3832
|
+
`Importing ${modType}: ${pc14__default.default.cyan(targetName)}${targetName !== registryName ? pc14__default.default.dim(` (from ${registryName})`) : ""}...`
|
|
3833
|
+
);
|
|
3371
3834
|
let manifest;
|
|
3372
3835
|
try {
|
|
3373
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3836
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3374
3837
|
} catch (err) {
|
|
3375
|
-
p3__namespace.log.error(
|
|
3838
|
+
p3__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3376
3839
|
process.exit(1);
|
|
3377
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
|
+
}
|
|
3378
3846
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3379
3847
|
for (const dep of sharedDeps) {
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3848
|
+
await resolveDependency(
|
|
3849
|
+
"shared",
|
|
3850
|
+
dep,
|
|
3851
|
+
aliasMap,
|
|
3852
|
+
projectRoot,
|
|
3853
|
+
flags,
|
|
3854
|
+
importModuleRecursive,
|
|
3855
|
+
importedModules,
|
|
3856
|
+
registryBase,
|
|
3857
|
+
index
|
|
3858
|
+
);
|
|
3385
3859
|
}
|
|
3386
3860
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3387
3861
|
for (const dep of featureDeps) {
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3862
|
+
await resolveDependency(
|
|
3863
|
+
"feature",
|
|
3864
|
+
dep,
|
|
3865
|
+
aliasMap,
|
|
3866
|
+
projectRoot,
|
|
3867
|
+
flags,
|
|
3868
|
+
importModuleRecursive,
|
|
3869
|
+
importedModules,
|
|
3870
|
+
registryBase,
|
|
3871
|
+
index
|
|
3872
|
+
);
|
|
3393
3873
|
}
|
|
3394
3874
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3395
3875
|
requiredNpmPackages.add(pkg);
|
|
3396
3876
|
}
|
|
3397
3877
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3398
|
-
const targetDir =
|
|
3878
|
+
const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3879
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3399
3880
|
for (const file of manifest.files || []) {
|
|
3400
3881
|
if (file === "registry.json") continue;
|
|
3401
|
-
const remoteFilePath = `${folder}/${
|
|
3402
|
-
const localFilePath =
|
|
3403
|
-
if (
|
|
3404
|
-
p3__namespace.log.warn(
|
|
3882
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3883
|
+
const localFilePath = path2__default.default.join(targetDir, file);
|
|
3884
|
+
if (fs9__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
3885
|
+
p3__namespace.log.warn(
|
|
3886
|
+
` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3887
|
+
);
|
|
3405
3888
|
continue;
|
|
3406
3889
|
}
|
|
3407
|
-
|
|
3890
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3891
|
+
content = rewriteImports(content, aliasMap);
|
|
3408
3892
|
if (!flags.dryRun) {
|
|
3409
|
-
|
|
3410
|
-
|
|
3893
|
+
fs9__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
|
|
3894
|
+
fs9__default.default.writeFileSync(localFilePath, content, "utf-8");
|
|
3411
3895
|
}
|
|
3412
|
-
const displayPath =
|
|
3896
|
+
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
3413
3897
|
totalWrittenFiles.push(displayPath);
|
|
3414
|
-
p3__namespace.log.message(` ${
|
|
3898
|
+
p3__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
|
|
3415
3899
|
}
|
|
3416
3900
|
importedModules.add(modKey);
|
|
3417
3901
|
}
|
|
3418
|
-
await importModuleRecursive(type, name);
|
|
3902
|
+
await importModuleRecursive(type, name, name);
|
|
3419
3903
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3420
3904
|
if (missingNpm.length > 0) {
|
|
3421
3905
|
if (flags.skipInstall || flags.dryRun) {
|
|
3422
|
-
p3__namespace.log.info(`Required npm dependencies to install: ${
|
|
3906
|
+
p3__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
|
|
3423
3907
|
} else {
|
|
3424
3908
|
try {
|
|
3425
3909
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -3429,11 +3913,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3429
3913
|
}
|
|
3430
3914
|
}
|
|
3431
3915
|
if (flags.dryRun) {
|
|
3432
|
-
p3__namespace.log.info(
|
|
3916
|
+
p3__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
3433
3917
|
} else {
|
|
3918
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3919
|
+
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, name));
|
|
3434
3920
|
p3__namespace.log.success(
|
|
3435
|
-
|
|
3436
|
-
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in
|
|
3921
|
+
pc14__default.default.green(
|
|
3922
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3437
3923
|
)
|
|
3438
3924
|
);
|
|
3439
3925
|
}
|
|
@@ -3442,24 +3928,70 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3442
3928
|
// src/cli.ts
|
|
3443
3929
|
function createCli() {
|
|
3444
3930
|
const cli = cac.cac("mason");
|
|
3445
|
-
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) => {
|
|
3931
|
+
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) => {
|
|
3446
3932
|
if (args && args.length > 0) {
|
|
3447
3933
|
p3__namespace.log.error(
|
|
3448
|
-
|
|
3934
|
+
pc14__default.default.red(
|
|
3449
3935
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3450
3936
|
)
|
|
3451
3937
|
);
|
|
3452
3938
|
process.exit(1);
|
|
3453
3939
|
}
|
|
3454
3940
|
try {
|
|
3941
|
+
const projectRoot = findProjectRoot();
|
|
3942
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
3943
|
+
if (mode === "registry") {
|
|
3944
|
+
const choice2 = handleCancel(
|
|
3945
|
+
await p3__namespace.select({
|
|
3946
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
3947
|
+
options: [
|
|
3948
|
+
{
|
|
3949
|
+
value: "create_feature",
|
|
3950
|
+
label: "Initialize New Feature",
|
|
3951
|
+
hint: "Scaffold a new feature slice under features/"
|
|
3952
|
+
},
|
|
3953
|
+
{
|
|
3954
|
+
value: "create_shared",
|
|
3955
|
+
label: "Initialize New Shared Module",
|
|
3956
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
3957
|
+
},
|
|
3958
|
+
{
|
|
3959
|
+
value: "create_usecase",
|
|
3960
|
+
label: "Create New Use Case",
|
|
3961
|
+
hint: "Add a new use case to an existing feature"
|
|
3962
|
+
},
|
|
3963
|
+
{
|
|
3964
|
+
value: "import",
|
|
3965
|
+
label: "Import from Remote Registry",
|
|
3966
|
+
hint: "Download a module from upstream"
|
|
3967
|
+
}
|
|
3968
|
+
]
|
|
3969
|
+
})
|
|
3970
|
+
);
|
|
3971
|
+
switch (choice2) {
|
|
3972
|
+
case "create_feature":
|
|
3973
|
+
await handleCreate("feature", void 0, flags);
|
|
3974
|
+
break;
|
|
3975
|
+
case "create_shared":
|
|
3976
|
+
await handleCreate("shared", void 0, flags);
|
|
3977
|
+
break;
|
|
3978
|
+
case "create_usecase":
|
|
3979
|
+
await handleCreate("usecase", void 0, flags);
|
|
3980
|
+
break;
|
|
3981
|
+
case "import":
|
|
3982
|
+
await handleImport(void 0, void 0, flags);
|
|
3983
|
+
break;
|
|
3984
|
+
}
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3455
3987
|
const choice = handleCancel(
|
|
3456
3988
|
await p3__namespace.select({
|
|
3457
3989
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3458
3990
|
options: [
|
|
3459
3991
|
{
|
|
3460
3992
|
value: "create",
|
|
3461
|
-
label: "Create
|
|
3462
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
3993
|
+
label: "Create component",
|
|
3994
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3463
3995
|
},
|
|
3464
3996
|
{
|
|
3465
3997
|
value: "import",
|
|
@@ -3480,8 +4012,8 @@ function createCli() {
|
|
|
3480
4012
|
});
|
|
3481
4013
|
cli.command(
|
|
3482
4014
|
"create [type] [name]",
|
|
3483
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3484
|
-
).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 = {}) => {
|
|
4015
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
4016
|
+
).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 = {}) => {
|
|
3485
4017
|
try {
|
|
3486
4018
|
await handleCreate(type, name, flags);
|
|
3487
4019
|
} catch (err) {
|
|
@@ -3500,6 +4032,7 @@ function createCli() {
|
|
|
3500
4032
|
});
|
|
3501
4033
|
const createSubtypes = [
|
|
3502
4034
|
"feature",
|
|
4035
|
+
"shared",
|
|
3503
4036
|
"usecase",
|
|
3504
4037
|
"domain",
|
|
3505
4038
|
"infrastructure",
|
|
@@ -3520,7 +4053,7 @@ function createCli() {
|
|
|
3520
4053
|
});
|
|
3521
4054
|
}
|
|
3522
4055
|
cli.help();
|
|
3523
|
-
cli.version("1.0.
|
|
4056
|
+
cli.version("1.0.14");
|
|
3524
4057
|
return cli;
|
|
3525
4058
|
}
|
|
3526
4059
|
function handleCliError(err) {
|
|
@@ -3528,7 +4061,7 @@ function handleCliError(err) {
|
|
|
3528
4061
|
process.exit(0);
|
|
3529
4062
|
}
|
|
3530
4063
|
const message = err instanceof Error ? err.message : String(err);
|
|
3531
|
-
p3__namespace.log.error(
|
|
4064
|
+
p3__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
|
|
3532
4065
|
process.exit(1);
|
|
3533
4066
|
}
|
|
3534
4067
|
async function runCli(argv = process.argv) {
|