@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.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cac } from 'cac';
|
|
3
3
|
import * as p3 from '@clack/prompts';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import pc14 from 'picocolors';
|
|
5
|
+
import fs9 from 'fs';
|
|
6
|
+
import path2 from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import Handlebars from 'handlebars';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
|
+
import { createHash } from 'crypto';
|
|
10
11
|
|
|
11
12
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
12
13
|
const projectRoot = options.projectRoot || process.cwd();
|
|
13
|
-
const relPath =
|
|
14
|
-
const existedBefore =
|
|
14
|
+
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
15
|
+
const existedBefore = fs9.existsSync(absolutePath);
|
|
15
16
|
if (options.dryRun) {
|
|
16
17
|
return {
|
|
17
18
|
relativePath: relPath,
|
|
@@ -30,11 +31,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
30
31
|
existedBefore: true
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
|
-
const dir =
|
|
34
|
-
if (!
|
|
35
|
-
|
|
34
|
+
const dir = path2.dirname(absolutePath);
|
|
35
|
+
if (!fs9.existsSync(dir)) {
|
|
36
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
fs9.writeFileSync(absolutePath, content, "utf-8");
|
|
38
39
|
return {
|
|
39
40
|
relativePath: relPath,
|
|
40
41
|
absolutePath,
|
|
@@ -46,17 +47,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
46
47
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
47
48
|
console.log("");
|
|
48
49
|
if (isDryRun) {
|
|
49
|
-
p3.log.warn(
|
|
50
|
+
p3.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
50
51
|
}
|
|
51
52
|
for (const f of files) {
|
|
52
53
|
if (f.action === "created") {
|
|
53
|
-
p3.log.success(`${
|
|
54
|
+
p3.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
|
|
54
55
|
} else if (f.action === "overwritten") {
|
|
55
|
-
p3.log.warn(`${
|
|
56
|
+
p3.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
|
|
56
57
|
} else if (f.action === "skipped") {
|
|
57
|
-
p3.log.info(`${
|
|
58
|
+
p3.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
58
59
|
} else {
|
|
59
|
-
p3.log.info(`${
|
|
60
|
+
p3.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -124,27 +125,90 @@ function toStubName(ifaceName) {
|
|
|
124
125
|
}
|
|
125
126
|
return `Stub${base}`;
|
|
126
127
|
}
|
|
128
|
+
function loadMasonConfig(startDir = process.cwd()) {
|
|
129
|
+
let current = path2.resolve(startDir);
|
|
130
|
+
while (true) {
|
|
131
|
+
const configPath = path2.join(current, "mason.config.json");
|
|
132
|
+
if (fs9.existsSync(configPath)) {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const parent = path2.dirname(current);
|
|
140
|
+
if (parent === current) break;
|
|
141
|
+
current = parent;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function getEffectiveMode(projectRoot, flags) {
|
|
146
|
+
if (flags?.registry) return "registry";
|
|
147
|
+
const config = loadMasonConfig(projectRoot);
|
|
148
|
+
if (config?.mode) return config.mode;
|
|
149
|
+
if (fs9.existsSync(path2.join(projectRoot, "index.json")) && (fs9.existsSync(path2.join(projectRoot, "features")) || fs9.existsSync(path2.join(projectRoot, "shared"))) && !fs9.existsSync(path2.join(projectRoot, "src"))) {
|
|
150
|
+
return "registry";
|
|
151
|
+
}
|
|
152
|
+
return "app";
|
|
153
|
+
}
|
|
154
|
+
function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
155
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
156
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
157
|
+
const config = loadMasonConfig(projectRoot);
|
|
158
|
+
if (config?.paths?.features) {
|
|
159
|
+
return path2.resolve(projectRoot, config.paths.features);
|
|
160
|
+
}
|
|
161
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
162
|
+
if (mode === "registry") {
|
|
163
|
+
return path2.join(projectRoot, "features");
|
|
164
|
+
}
|
|
165
|
+
if (fs9.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
166
|
+
return path2.join(projectRoot, "src/features");
|
|
167
|
+
}
|
|
168
|
+
return path2.join(projectRoot, "features");
|
|
169
|
+
}
|
|
170
|
+
function getConfiguredSharedDir(projectRoot, flags) {
|
|
171
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
172
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
173
|
+
const config = loadMasonConfig(projectRoot);
|
|
174
|
+
if (config?.paths?.shared) {
|
|
175
|
+
return path2.resolve(projectRoot, config.paths.shared);
|
|
176
|
+
}
|
|
177
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
178
|
+
if (mode === "registry") {
|
|
179
|
+
return path2.join(projectRoot, "shared");
|
|
180
|
+
}
|
|
181
|
+
if (fs9.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
182
|
+
return path2.join(projectRoot, "src/shared");
|
|
183
|
+
}
|
|
184
|
+
return path2.join(projectRoot, "shared");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/utils/project.ts
|
|
127
188
|
function findProjectRoot(startDir = process.cwd()) {
|
|
128
|
-
let current =
|
|
189
|
+
let current = path2.resolve(startDir);
|
|
129
190
|
while (true) {
|
|
130
|
-
if (
|
|
191
|
+
if (fs9.existsSync(path2.join(current, "package.json"))) {
|
|
131
192
|
return current;
|
|
132
193
|
}
|
|
133
|
-
const parent =
|
|
194
|
+
const parent = path2.dirname(current);
|
|
134
195
|
if (parent === current) {
|
|
135
|
-
return
|
|
196
|
+
return path2.resolve(startDir);
|
|
136
197
|
}
|
|
137
198
|
current = parent;
|
|
138
199
|
}
|
|
139
200
|
}
|
|
140
|
-
function resolveFeaturesDir(projectRoot) {
|
|
141
|
-
return
|
|
201
|
+
function resolveFeaturesDir(projectRoot, flags) {
|
|
202
|
+
return getConfiguredFeaturesDir(projectRoot, flags);
|
|
203
|
+
}
|
|
204
|
+
function resolveSharedDir(projectRoot, flags) {
|
|
205
|
+
return getConfiguredSharedDir(projectRoot, flags);
|
|
142
206
|
}
|
|
143
207
|
function listExistingFeatures(projectRoot) {
|
|
144
208
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
145
|
-
if (!
|
|
209
|
+
if (!fs9.existsSync(featuresDir)) return [];
|
|
146
210
|
try {
|
|
147
|
-
const entries =
|
|
211
|
+
const entries = fs9.readdirSync(featuresDir, { withFileTypes: true });
|
|
148
212
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
149
213
|
} catch {
|
|
150
214
|
return [];
|
|
@@ -154,14 +218,14 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
154
218
|
const results = [];
|
|
155
219
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
156
220
|
for (const feat of features) {
|
|
157
|
-
const domainDir =
|
|
158
|
-
if (!
|
|
221
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
222
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
159
223
|
try {
|
|
160
|
-
const files =
|
|
224
|
+
const files = fs9.readdirSync(domainDir);
|
|
161
225
|
for (const file of files) {
|
|
162
226
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
163
227
|
const name = file.replace(/\.ts$/, "");
|
|
164
|
-
const fullPath =
|
|
228
|
+
const fullPath = path2.join(domainDir, file);
|
|
165
229
|
if (/^I[A-Z]/.test(name)) {
|
|
166
230
|
results.push({
|
|
167
231
|
name,
|
|
@@ -170,7 +234,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
170
234
|
});
|
|
171
235
|
} else {
|
|
172
236
|
try {
|
|
173
|
-
const content =
|
|
237
|
+
const content = fs9.readFileSync(fullPath, "utf-8");
|
|
174
238
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
175
239
|
results.push({
|
|
176
240
|
name,
|
|
@@ -192,17 +256,17 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
192
256
|
const results = [];
|
|
193
257
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
194
258
|
for (const feat of features) {
|
|
195
|
-
const domainDir =
|
|
196
|
-
if (!
|
|
259
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
260
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
197
261
|
try {
|
|
198
|
-
const files =
|
|
262
|
+
const files = fs9.readdirSync(domainDir);
|
|
199
263
|
for (const file of files) {
|
|
200
264
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
201
265
|
const name = file.replace(/\.ts$/, "");
|
|
202
266
|
if (!/^I[A-Z]/.test(name)) {
|
|
203
267
|
results.push({
|
|
204
268
|
name,
|
|
205
|
-
fullPath:
|
|
269
|
+
fullPath: path2.join(domainDir, file),
|
|
206
270
|
feature: feat
|
|
207
271
|
});
|
|
208
272
|
}
|
|
@@ -217,16 +281,16 @@ function listUseCases(projectRoot, featureName) {
|
|
|
217
281
|
const results = [];
|
|
218
282
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
219
283
|
for (const feat of features) {
|
|
220
|
-
const useCasesDir =
|
|
221
|
-
if (!
|
|
284
|
+
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
285
|
+
if (!fs9.existsSync(useCasesDir)) continue;
|
|
222
286
|
try {
|
|
223
|
-
const files =
|
|
287
|
+
const files = fs9.readdirSync(useCasesDir);
|
|
224
288
|
for (const file of files) {
|
|
225
289
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
226
290
|
const name = file.replace(/\.ts$/, "");
|
|
227
291
|
results.push({
|
|
228
292
|
name,
|
|
229
|
-
fullPath:
|
|
293
|
+
fullPath: path2.join(useCasesDir, file),
|
|
230
294
|
feature: feat
|
|
231
295
|
});
|
|
232
296
|
}
|
|
@@ -252,33 +316,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
252
316
|
});
|
|
253
317
|
var cachedTemplatesDir = null;
|
|
254
318
|
function getTemplatesDir() {
|
|
255
|
-
if (cachedTemplatesDir &&
|
|
319
|
+
if (cachedTemplatesDir && fs9.existsSync(cachedTemplatesDir)) {
|
|
256
320
|
return cachedTemplatesDir;
|
|
257
321
|
}
|
|
258
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
259
|
-
const cand1 =
|
|
260
|
-
if (
|
|
322
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
323
|
+
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
324
|
+
if (fs9.existsSync(cand1)) {
|
|
261
325
|
cachedTemplatesDir = cand1;
|
|
262
326
|
return cand1;
|
|
263
327
|
}
|
|
264
|
-
const cand2 =
|
|
265
|
-
if (
|
|
328
|
+
const cand2 = path2.resolve(currentDir, "../templates");
|
|
329
|
+
if (fs9.existsSync(cand2)) {
|
|
266
330
|
cachedTemplatesDir = cand2;
|
|
267
331
|
return cand2;
|
|
268
332
|
}
|
|
269
|
-
const cand3 =
|
|
270
|
-
if (
|
|
333
|
+
const cand3 = path2.resolve(currentDir, "templates");
|
|
334
|
+
if (fs9.existsSync(cand3)) {
|
|
271
335
|
cachedTemplatesDir = cand3;
|
|
272
336
|
return cand3;
|
|
273
337
|
}
|
|
274
338
|
let curr = currentDir;
|
|
275
339
|
while (true) {
|
|
276
|
-
const candTemplates =
|
|
277
|
-
if (
|
|
340
|
+
const candTemplates = path2.join(curr, "templates");
|
|
341
|
+
if (fs9.existsSync(candTemplates)) {
|
|
278
342
|
cachedTemplatesDir = candTemplates;
|
|
279
343
|
return candTemplates;
|
|
280
344
|
}
|
|
281
|
-
const parent =
|
|
345
|
+
const parent = path2.dirname(curr);
|
|
282
346
|
if (parent === curr) break;
|
|
283
347
|
curr = parent;
|
|
284
348
|
}
|
|
@@ -286,8 +350,8 @@ function getTemplatesDir() {
|
|
|
286
350
|
}
|
|
287
351
|
var templateCache = /* @__PURE__ */ new Map();
|
|
288
352
|
function loadTemplate(relativePath) {
|
|
289
|
-
const fullPath =
|
|
290
|
-
return
|
|
353
|
+
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
354
|
+
return fs9.readFileSync(fullPath, "utf-8");
|
|
291
355
|
}
|
|
292
356
|
function renderTemplateFile(relativePath, context) {
|
|
293
357
|
let compiled = templateCache.get(relativePath);
|
|
@@ -374,8 +438,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
374
438
|
function generateDomain(options) {
|
|
375
439
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
376
440
|
const entityName = toPascalCase(options.name);
|
|
377
|
-
const targetDir = options.targetDir ||
|
|
378
|
-
const filePath =
|
|
441
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
442
|
+
const filePath = path2.join(targetDir, `${entityName}.ts`);
|
|
379
443
|
const code = generateDomainEntityCode(
|
|
380
444
|
entityName,
|
|
381
445
|
options.kind || "interface",
|
|
@@ -399,7 +463,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
399
463
|
const idKey = `${entityKey}Id`;
|
|
400
464
|
const listKey = `${entityKey}s`;
|
|
401
465
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
402
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
466
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join("; ") : "";
|
|
403
467
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
404
468
|
return {
|
|
405
469
|
name: m.name,
|
|
@@ -424,8 +488,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
424
488
|
function generateInterface(options) {
|
|
425
489
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
426
490
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
427
|
-
const targetDir = options.targetDir ||
|
|
428
|
-
const filePath =
|
|
491
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
492
|
+
const filePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
429
493
|
const code = generateInterfaceCode(
|
|
430
494
|
ifaceName,
|
|
431
495
|
options.kind || "abstractClass",
|
|
@@ -660,7 +724,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
660
724
|
};
|
|
661
725
|
}
|
|
662
726
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
663
|
-
if (customPath &&
|
|
727
|
+
if (customPath && fs9.existsSync(customPath)) {
|
|
664
728
|
return { filePath: customPath, ifaceName };
|
|
665
729
|
}
|
|
666
730
|
const candidateNames = [
|
|
@@ -670,22 +734,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
670
734
|
];
|
|
671
735
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
672
736
|
for (const feat of features) {
|
|
673
|
-
const domainDir =
|
|
674
|
-
if (!
|
|
737
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
738
|
+
if (!fs9.existsSync(domainDir)) continue;
|
|
675
739
|
for (const cand of candidateNames) {
|
|
676
|
-
const p1 =
|
|
677
|
-
if (
|
|
740
|
+
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
741
|
+
if (fs9.existsSync(p1)) {
|
|
678
742
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
679
743
|
}
|
|
680
744
|
}
|
|
681
745
|
}
|
|
682
746
|
for (const cand of candidateNames) {
|
|
683
|
-
const p1 =
|
|
684
|
-
if (
|
|
685
|
-
const p22 =
|
|
686
|
-
if (
|
|
687
|
-
const p32 =
|
|
688
|
-
if (
|
|
747
|
+
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
748
|
+
if (fs9.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
749
|
+
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
750
|
+
if (fs9.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
751
|
+
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
752
|
+
if (fs9.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
689
753
|
}
|
|
690
754
|
return null;
|
|
691
755
|
}
|
|
@@ -708,7 +772,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
708
772
|
while (currentFile && !visited.has(currentFile)) {
|
|
709
773
|
visited.add(currentFile);
|
|
710
774
|
try {
|
|
711
|
-
const content =
|
|
775
|
+
const content = fs9.readFileSync(currentFile, "utf-8");
|
|
712
776
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
713
777
|
for (const m of parsed.methods) {
|
|
714
778
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -784,7 +848,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
784
848
|
const entitiesVar = `${entityVar}s`;
|
|
785
849
|
const idKey = `${entityVar}Id`;
|
|
786
850
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
787
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
851
|
+
const entityImportPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
788
852
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
789
853
|
for (const imp of domainImports) {
|
|
790
854
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -833,7 +897,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
833
897
|
if (baseEntity) {
|
|
834
898
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
835
899
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
836
|
-
const importPath = ifaceImportPath ? `${
|
|
900
|
+
const importPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
837
901
|
finalDomainImports.push({
|
|
838
902
|
name: baseEntity,
|
|
839
903
|
from: importPath,
|
|
@@ -878,8 +942,8 @@ function generateInfrastructure(options) {
|
|
|
878
942
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
879
943
|
const className = toPascalCase(options.name);
|
|
880
944
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
881
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
882
|
-
const filePath =
|
|
945
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2.join(projectRoot, "infrastructure"));
|
|
946
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
883
947
|
let code;
|
|
884
948
|
if (options.implementsInterface) {
|
|
885
949
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -893,7 +957,7 @@ function generateInfrastructure(options) {
|
|
|
893
957
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
894
958
|
let ifaceImportPath;
|
|
895
959
|
if (ifaceFilePath) {
|
|
896
|
-
let rel =
|
|
960
|
+
let rel = path2.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
897
961
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
898
962
|
ifaceImportPath = rel;
|
|
899
963
|
} else if (isFeatureScope) {
|
|
@@ -906,8 +970,8 @@ function generateInfrastructure(options) {
|
|
|
906
970
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
907
971
|
let fromPath;
|
|
908
972
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
909
|
-
const absTarget =
|
|
910
|
-
let rel =
|
|
973
|
+
const absTarget = path2.resolve(path2.dirname(ifaceFilePath), imp.specifier);
|
|
974
|
+
let rel = path2.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
911
975
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
912
976
|
fromPath = rel;
|
|
913
977
|
} else {
|
|
@@ -980,22 +1044,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
980
1044
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
981
1045
|
}));
|
|
982
1046
|
const domainTypesToImport = new Set(domainImports);
|
|
983
|
-
for (const
|
|
984
|
-
const rawType =
|
|
1047
|
+
for (const p16 of [...inputProps, ...outputProps]) {
|
|
1048
|
+
const rawType = p16.type.trim();
|
|
985
1049
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
986
1050
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
987
1051
|
domainTypesToImport.add(baseType);
|
|
988
1052
|
}
|
|
989
1053
|
}
|
|
990
|
-
const formattedInputProps = inputProps.map((
|
|
991
|
-
name:
|
|
992
|
-
optional:
|
|
993
|
-
type:
|
|
1054
|
+
const formattedInputProps = inputProps.map((p16) => ({
|
|
1055
|
+
name: p16.name,
|
|
1056
|
+
optional: p16.optional,
|
|
1057
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
994
1058
|
}));
|
|
995
|
-
const formattedOutputProps = outputProps.map((
|
|
996
|
-
name:
|
|
997
|
-
optional:
|
|
998
|
-
type:
|
|
1059
|
+
const formattedOutputProps = outputProps.map((p16) => ({
|
|
1060
|
+
name: p16.name,
|
|
1061
|
+
optional: p16.optional,
|
|
1062
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
999
1063
|
}));
|
|
1000
1064
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1001
1065
|
key: toCamelCase(dep),
|
|
@@ -1094,8 +1158,8 @@ function generateUseCase(options) {
|
|
|
1094
1158
|
const files = [];
|
|
1095
1159
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1096
1160
|
const ucName = toPascalCase(options.name);
|
|
1097
|
-
const targetDir = options.targetDir ||
|
|
1098
|
-
const filePath =
|
|
1161
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1162
|
+
const filePath = path2.join(targetDir, `${ucName}.ts`);
|
|
1099
1163
|
const code = generateUseCaseCode(
|
|
1100
1164
|
ucName,
|
|
1101
1165
|
options.dependencies || [],
|
|
@@ -1111,7 +1175,7 @@ function generateUseCase(options) {
|
|
|
1111
1175
|
})
|
|
1112
1176
|
);
|
|
1113
1177
|
if (options.includeTest) {
|
|
1114
|
-
const testPath =
|
|
1178
|
+
const testPath = path2.join(targetDir, `${ucName}.test.ts`);
|
|
1115
1179
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1116
1180
|
files.push(
|
|
1117
1181
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1179,10 +1243,10 @@ function generateHttpHandler(options) {
|
|
|
1179
1243
|
const files = [];
|
|
1180
1244
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1181
1245
|
const className = toPascalCase(options.name);
|
|
1182
|
-
const pxDir =
|
|
1183
|
-
const handlersDir =
|
|
1184
|
-
const pxIndexPath =
|
|
1185
|
-
if (!
|
|
1246
|
+
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1247
|
+
const handlersDir = path2.join(pxDir, "handlers");
|
|
1248
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1249
|
+
if (!fs9.existsSync(pxIndexPath)) {
|
|
1186
1250
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1187
1251
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1188
1252
|
routePath: featurePath
|
|
@@ -1195,7 +1259,7 @@ function generateHttpHandler(options) {
|
|
|
1195
1259
|
})
|
|
1196
1260
|
);
|
|
1197
1261
|
}
|
|
1198
|
-
const handlerFilePath =
|
|
1262
|
+
const handlerFilePath = path2.join(handlersDir, `${className}.ts`);
|
|
1199
1263
|
const code = generateHttpHandlerCode(
|
|
1200
1264
|
className,
|
|
1201
1265
|
options.method || "get",
|
|
@@ -1216,11 +1280,11 @@ function generateHttpHandler(options) {
|
|
|
1216
1280
|
return files;
|
|
1217
1281
|
}
|
|
1218
1282
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1219
|
-
const providerPath =
|
|
1283
|
+
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1220
1284
|
const pascalFeature = toPascalCase(featureName);
|
|
1221
1285
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1222
|
-
if (
|
|
1223
|
-
let content =
|
|
1286
|
+
if (fs9.existsSync(providerPath) && !options.overwrite) {
|
|
1287
|
+
let content = fs9.readFileSync(providerPath, "utf-8");
|
|
1224
1288
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1225
1289
|
for (const b of bindings) {
|
|
1226
1290
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1279,7 +1343,7 @@ function generateFeature(options) {
|
|
|
1279
1343
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1280
1344
|
const rawName = options.name.trim();
|
|
1281
1345
|
const featureName = toCamelCase(rawName);
|
|
1282
|
-
const targetDir = options.targetDir ||
|
|
1346
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1283
1347
|
const routePath = options.routePath || `/${featureName}`;
|
|
1284
1348
|
const rawEntities = [];
|
|
1285
1349
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1300,7 +1364,7 @@ function generateFeature(options) {
|
|
|
1300
1364
|
const domainFile = generateDomain({
|
|
1301
1365
|
name: ent,
|
|
1302
1366
|
feature: featureName,
|
|
1303
|
-
targetDir:
|
|
1367
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1304
1368
|
projectRoot,
|
|
1305
1369
|
dryRun: options.dryRun,
|
|
1306
1370
|
overwrite: options.overwrite,
|
|
@@ -1320,7 +1384,7 @@ function generateFeature(options) {
|
|
|
1320
1384
|
const ifaceFile = generateInterface({
|
|
1321
1385
|
name: ifaceName,
|
|
1322
1386
|
feature: featureName,
|
|
1323
|
-
targetDir:
|
|
1387
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1324
1388
|
entityName: ent,
|
|
1325
1389
|
projectRoot,
|
|
1326
1390
|
dryRun: options.dryRun,
|
|
@@ -1337,7 +1401,7 @@ function generateFeature(options) {
|
|
|
1337
1401
|
const stubFile = generateInfrastructure({
|
|
1338
1402
|
name: stubName,
|
|
1339
1403
|
feature: featureName,
|
|
1340
|
-
targetDir:
|
|
1404
|
+
targetDir: path2.join(targetDir, "infrastructure"),
|
|
1341
1405
|
kind: "stub",
|
|
1342
1406
|
implementsInterface: ifaceName,
|
|
1343
1407
|
entityName: ent,
|
|
@@ -1379,7 +1443,7 @@ function generateFeature(options) {
|
|
|
1379
1443
|
const ucFiles = generateUseCase({
|
|
1380
1444
|
name: ucName,
|
|
1381
1445
|
feature: featureName,
|
|
1382
|
-
targetDir:
|
|
1446
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1383
1447
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1384
1448
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1385
1449
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1424,7 +1488,7 @@ function generateFeature(options) {
|
|
|
1424
1488
|
const ucFiles = generateUseCase({
|
|
1425
1489
|
name: ucName,
|
|
1426
1490
|
feature: featureName,
|
|
1427
|
-
targetDir:
|
|
1491
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1428
1492
|
dependencies: deps,
|
|
1429
1493
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1430
1494
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1457,7 +1521,7 @@ function generateFeature(options) {
|
|
|
1457
1521
|
const ucFiles = generateUseCase({
|
|
1458
1522
|
name: ucName,
|
|
1459
1523
|
feature: featureName,
|
|
1460
|
-
targetDir:
|
|
1524
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1461
1525
|
dependencies: deps,
|
|
1462
1526
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1463
1527
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1490,7 +1554,7 @@ function generateFeature(options) {
|
|
|
1490
1554
|
const ucFiles = generateUseCase({
|
|
1491
1555
|
name: ucName,
|
|
1492
1556
|
feature: featureName,
|
|
1493
|
-
targetDir:
|
|
1557
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1494
1558
|
dependencies: deps,
|
|
1495
1559
|
inputProperties: [
|
|
1496
1560
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1534,7 +1598,7 @@ function generateFeature(options) {
|
|
|
1534
1598
|
const ucFiles = generateUseCase({
|
|
1535
1599
|
name: ucName,
|
|
1536
1600
|
feature: featureName,
|
|
1537
|
-
targetDir:
|
|
1601
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1538
1602
|
dependencies: deps,
|
|
1539
1603
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1540
1604
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1567,7 +1631,7 @@ function generateFeature(options) {
|
|
|
1567
1631
|
const ucFiles = generateUseCase({
|
|
1568
1632
|
name: ucName,
|
|
1569
1633
|
feature: featureName,
|
|
1570
|
-
targetDir:
|
|
1634
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1571
1635
|
dependencies: deps,
|
|
1572
1636
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1573
1637
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1604,7 +1668,7 @@ function generateFeature(options) {
|
|
|
1604
1668
|
const ucFiles = generateUseCase({
|
|
1605
1669
|
name: ucName,
|
|
1606
1670
|
feature: featureName,
|
|
1607
|
-
targetDir:
|
|
1671
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1608
1672
|
dependencies: customUc.dependencies || [],
|
|
1609
1673
|
inputProperties: customUc.inputProperties || [],
|
|
1610
1674
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1636,8 +1700,8 @@ function generateFeature(options) {
|
|
|
1636
1700
|
}
|
|
1637
1701
|
}
|
|
1638
1702
|
if (options.includeExpress !== false) {
|
|
1639
|
-
const pxDir =
|
|
1640
|
-
const pxIndexPath =
|
|
1703
|
+
const pxDir = path2.join(targetDir, "pxExpress");
|
|
1704
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1641
1705
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1642
1706
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1643
1707
|
files.push(
|
|
@@ -1674,13 +1738,13 @@ function handleCancel(result) {
|
|
|
1674
1738
|
}
|
|
1675
1739
|
function printBanner() {
|
|
1676
1740
|
console.log("");
|
|
1677
|
-
p3.intro(
|
|
1741
|
+
p3.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
|
|
1678
1742
|
}
|
|
1679
1743
|
async function promptProperties(options = {}) {
|
|
1680
1744
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
1681
1745
|
if (properties.length > 0) {
|
|
1682
|
-
const summary = properties.map((
|
|
1683
|
-
p3.log.info(
|
|
1746
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
1747
|
+
p3.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
|
|
1684
1748
|
const addMore = handleCancel(
|
|
1685
1749
|
await p3.confirm({
|
|
1686
1750
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -1691,7 +1755,7 @@ async function promptProperties(options = {}) {
|
|
|
1691
1755
|
return properties;
|
|
1692
1756
|
}
|
|
1693
1757
|
}
|
|
1694
|
-
p3.log.info(
|
|
1758
|
+
p3.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
|
|
1695
1759
|
while (true) {
|
|
1696
1760
|
const name = handleCancel(
|
|
1697
1761
|
await p3.text({
|
|
@@ -1739,7 +1803,7 @@ async function promptProperties(options = {}) {
|
|
|
1739
1803
|
optional
|
|
1740
1804
|
});
|
|
1741
1805
|
p3.log.success(
|
|
1742
|
-
|
|
1806
|
+
pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1743
1807
|
);
|
|
1744
1808
|
}
|
|
1745
1809
|
return properties;
|
|
@@ -1748,6 +1812,7 @@ async function promptProperties(options = {}) {
|
|
|
1748
1812
|
// src/commands/feature.ts
|
|
1749
1813
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
1750
1814
|
const projectRoot = findProjectRoot();
|
|
1815
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
1751
1816
|
let name = featureNameArg?.trim();
|
|
1752
1817
|
if (!name && flags.yes) {
|
|
1753
1818
|
name = "feature";
|
|
@@ -1765,9 +1830,10 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1765
1830
|
).trim();
|
|
1766
1831
|
}
|
|
1767
1832
|
const normalizedName = toCamelCase(name);
|
|
1768
|
-
const
|
|
1833
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
1834
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseFeaturesDir, normalizedName));
|
|
1769
1835
|
let targetDirRel = defaultDir;
|
|
1770
|
-
if (!flags.yes && !flags.target) {
|
|
1836
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1771
1837
|
targetDirRel = handleCancel(
|
|
1772
1838
|
await p3.text({
|
|
1773
1839
|
message: "Target directory for the feature:",
|
|
@@ -1776,7 +1842,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1776
1842
|
})
|
|
1777
1843
|
).trim();
|
|
1778
1844
|
}
|
|
1779
|
-
const targetDir = flags.target ?
|
|
1845
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
1780
1846
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
1781
1847
|
let routePath = `/${normalizedName}`;
|
|
1782
1848
|
let includeDomain = true;
|
|
@@ -1997,7 +2063,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1997
2063
|
}
|
|
1998
2064
|
}
|
|
1999
2065
|
const s = p3.spinner();
|
|
2000
|
-
s.start(`Generating feature '${
|
|
2066
|
+
s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
|
|
2001
2067
|
const options = {
|
|
2002
2068
|
name: normalizedName,
|
|
2003
2069
|
projectRoot,
|
|
@@ -2017,9 +2083,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2017
2083
|
includeDiProvider
|
|
2018
2084
|
};
|
|
2019
2085
|
const files = generateFeature(options);
|
|
2086
|
+
if (isRegistry) {
|
|
2087
|
+
const registryPath = path2.join(targetDir, "registry.json");
|
|
2088
|
+
if (!fs9.existsSync(registryPath)) {
|
|
2089
|
+
const manifest = {
|
|
2090
|
+
name: toKebabCase(normalizedName),
|
|
2091
|
+
type: "feature",
|
|
2092
|
+
description: flags.description || `${normalizedName} feature module`,
|
|
2093
|
+
version: "1.0.0",
|
|
2094
|
+
dependencies: {
|
|
2095
|
+
npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
|
|
2096
|
+
shared: [],
|
|
2097
|
+
features: []
|
|
2098
|
+
},
|
|
2099
|
+
files: []
|
|
2100
|
+
};
|
|
2101
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2102
|
+
const genFile = writeFileSafely(registryPath, content, {
|
|
2103
|
+
projectRoot,
|
|
2104
|
+
overwrite: options.overwrite,
|
|
2105
|
+
dryRun: options.dryRun
|
|
2106
|
+
});
|
|
2107
|
+
files.push(genFile);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2020
2110
|
s.stop("Feature scaffolded successfully!");
|
|
2021
2111
|
printGeneratedFiles(files, options.dryRun);
|
|
2022
|
-
p3.outro(
|
|
2112
|
+
p3.outro(
|
|
2113
|
+
pc14.green(
|
|
2114
|
+
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2115
|
+
)
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
function generateShared(options) {
|
|
2119
|
+
const files = [];
|
|
2120
|
+
const projectRoot = options.projectRoot || findProjectRoot();
|
|
2121
|
+
const rawName = options.name.trim();
|
|
2122
|
+
const pascalName = toPascalCase(rawName);
|
|
2123
|
+
const camelName = toCamelCase(rawName);
|
|
2124
|
+
const kebabName = toKebabCase(rawName);
|
|
2125
|
+
const targetDir = options.targetDir || path2.join(resolveSharedDir(projectRoot), kebabName);
|
|
2126
|
+
const fileDefs = [];
|
|
2127
|
+
const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
|
|
2128
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2129
|
+
|
|
2130
|
+
@MakeInjectable
|
|
2131
|
+
export class ${pascalName} {
|
|
2132
|
+
public static deps = { ${camelName}Engine: I${pascalName}Engine };
|
|
2133
|
+
constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
|
|
2134
|
+
}
|
|
2135
|
+
`;
|
|
2136
|
+
fileDefs.push({
|
|
2137
|
+
absolutePath: path2.join(targetDir, `${pascalName}.ts`),
|
|
2138
|
+
content: facadeContent
|
|
2139
|
+
});
|
|
2140
|
+
const portContent = `export abstract class I${pascalName}Engine {
|
|
2141
|
+
// Define abstract port contracts for ${pascalName} here
|
|
2142
|
+
}
|
|
2143
|
+
`;
|
|
2144
|
+
fileDefs.push({
|
|
2145
|
+
absolutePath: path2.join(targetDir, "ports", `I${pascalName}Engine.ts`),
|
|
2146
|
+
content: portContent
|
|
2147
|
+
});
|
|
2148
|
+
const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2149
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2150
|
+
|
|
2151
|
+
@MakeInjectable
|
|
2152
|
+
export class Default${pascalName}Engine implements I${pascalName}Engine {
|
|
2153
|
+
// Default concrete implementation for production
|
|
2154
|
+
}
|
|
2155
|
+
`;
|
|
2156
|
+
fileDefs.push({
|
|
2157
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
|
|
2158
|
+
content: concreteContent
|
|
2159
|
+
});
|
|
2160
|
+
if (options.includeStub !== false) {
|
|
2161
|
+
const stubContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2162
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2163
|
+
|
|
2164
|
+
@MakeInjectable
|
|
2165
|
+
export class Stub${pascalName}Engine implements I${pascalName}Engine {
|
|
2166
|
+
// In-memory or stub implementation for testing
|
|
2167
|
+
}
|
|
2168
|
+
`;
|
|
2169
|
+
fileDefs.push({
|
|
2170
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
|
|
2171
|
+
content: stubContent
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
const providerContent = `import type { Container } from "@solid-stack/di";
|
|
2175
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2176
|
+
import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
|
|
2177
|
+
|
|
2178
|
+
export const diProvider = (c: Container): void => {
|
|
2179
|
+
c.provide(I${pascalName}Engine, Default${pascalName}Engine);
|
|
2180
|
+
};
|
|
2181
|
+
|
|
2182
|
+
export default diProvider;
|
|
2183
|
+
`;
|
|
2184
|
+
fileDefs.push({
|
|
2185
|
+
absolutePath: path2.join(targetDir, "diProvider.ts"),
|
|
2186
|
+
content: providerContent
|
|
2187
|
+
});
|
|
2188
|
+
if (options.isRegistry) {
|
|
2189
|
+
const registryManifest = {
|
|
2190
|
+
name: kebabName,
|
|
2191
|
+
type: "shared",
|
|
2192
|
+
description: options.description || `${kebabName} shared module`,
|
|
2193
|
+
version: "1.0.0",
|
|
2194
|
+
dependencies: {
|
|
2195
|
+
npm: [],
|
|
2196
|
+
shared: [],
|
|
2197
|
+
features: []
|
|
2198
|
+
},
|
|
2199
|
+
files: []
|
|
2200
|
+
};
|
|
2201
|
+
fileDefs.push({
|
|
2202
|
+
absolutePath: path2.join(targetDir, "registry.json"),
|
|
2203
|
+
content: JSON.stringify(registryManifest, null, 2) + "\n"
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
for (const def of fileDefs) {
|
|
2207
|
+
const genFile = writeFileSafely(def.absolutePath, def.content, {
|
|
2208
|
+
projectRoot,
|
|
2209
|
+
overwrite: options.overwrite,
|
|
2210
|
+
dryRun: options.dryRun
|
|
2211
|
+
});
|
|
2212
|
+
files.push(genFile);
|
|
2213
|
+
}
|
|
2214
|
+
return files;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// src/commands/shared.ts
|
|
2218
|
+
async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
2219
|
+
const projectRoot = findProjectRoot();
|
|
2220
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2221
|
+
let name = sharedNameArg?.trim();
|
|
2222
|
+
if (!name && flags.yes) {
|
|
2223
|
+
name = "shared";
|
|
2224
|
+
}
|
|
2225
|
+
if (!name) {
|
|
2226
|
+
name = handleCancel(
|
|
2227
|
+
await p3.text({
|
|
2228
|
+
message: "What is the shared module name?",
|
|
2229
|
+
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2230
|
+
validate: (val) => {
|
|
2231
|
+
if (!val || !val.trim()) return "Shared module name cannot be empty.";
|
|
2232
|
+
return void 0;
|
|
2233
|
+
}
|
|
2234
|
+
})
|
|
2235
|
+
).trim();
|
|
2236
|
+
}
|
|
2237
|
+
const normalizedName = toKebabCase(name);
|
|
2238
|
+
const baseSharedDir = resolveSharedDir(projectRoot, flags);
|
|
2239
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseSharedDir, normalizedName));
|
|
2240
|
+
let targetDirRel = defaultDir;
|
|
2241
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2242
|
+
targetDirRel = handleCancel(
|
|
2243
|
+
await p3.text({
|
|
2244
|
+
message: "Target directory for the shared module:",
|
|
2245
|
+
initialValue: defaultDir,
|
|
2246
|
+
defaultValue: defaultDir
|
|
2247
|
+
})
|
|
2248
|
+
).trim();
|
|
2249
|
+
}
|
|
2250
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
2251
|
+
let description = flags.description || "";
|
|
2252
|
+
let includeStub = true;
|
|
2253
|
+
if (!flags.yes) {
|
|
2254
|
+
description = handleCancel(
|
|
2255
|
+
await p3.text({
|
|
2256
|
+
message: "Module description (optional):",
|
|
2257
|
+
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2258
|
+
initialValue: ""
|
|
2259
|
+
})
|
|
2260
|
+
).trim();
|
|
2261
|
+
includeStub = handleCancel(
|
|
2262
|
+
await p3.confirm({
|
|
2263
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2264
|
+
initialValue: true
|
|
2265
|
+
})
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
const s = p3.spinner();
|
|
2269
|
+
s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
|
|
2270
|
+
const files = generateShared({
|
|
2271
|
+
name: normalizedName,
|
|
2272
|
+
description,
|
|
2273
|
+
projectRoot,
|
|
2274
|
+
targetDir,
|
|
2275
|
+
dryRun: Boolean(flags.dryRun),
|
|
2276
|
+
overwrite: Boolean(flags.overwrite),
|
|
2277
|
+
includeStub,
|
|
2278
|
+
isRegistry
|
|
2279
|
+
});
|
|
2280
|
+
s.stop("Shared module generated successfully!");
|
|
2281
|
+
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2282
|
+
p3.outro(
|
|
2283
|
+
pc14.green(
|
|
2284
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2023
2287
|
}
|
|
2024
2288
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2025
2289
|
const projectRoot = findProjectRoot();
|
|
@@ -2144,7 +2408,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2144
2408
|
}
|
|
2145
2409
|
}
|
|
2146
2410
|
const s = p3.spinner();
|
|
2147
|
-
s.start(`Generating use case '${
|
|
2411
|
+
s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
|
|
2148
2412
|
const options = {
|
|
2149
2413
|
name: ucName,
|
|
2150
2414
|
feature,
|
|
@@ -2176,7 +2440,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2176
2440
|
}
|
|
2177
2441
|
s.stop("Use case generated successfully!");
|
|
2178
2442
|
printGeneratedFiles(files, options.dryRun);
|
|
2179
|
-
p3.outro(
|
|
2443
|
+
p3.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2180
2444
|
}
|
|
2181
2445
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2182
2446
|
const projectRoot = findProjectRoot();
|
|
@@ -2247,7 +2511,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2247
2511
|
});
|
|
2248
2512
|
}
|
|
2249
2513
|
const s = p3.spinner();
|
|
2250
|
-
s.start(`Generating domain model '${
|
|
2514
|
+
s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
|
|
2251
2515
|
const options = {
|
|
2252
2516
|
name: entityName,
|
|
2253
2517
|
feature,
|
|
@@ -2260,7 +2524,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2260
2524
|
const file = generateDomain(options);
|
|
2261
2525
|
s.stop("Domain model generated successfully!");
|
|
2262
2526
|
printGeneratedFiles([file], options.dryRun);
|
|
2263
|
-
p3.outro(
|
|
2527
|
+
p3.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2264
2528
|
}
|
|
2265
2529
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2266
2530
|
const projectRoot = findProjectRoot();
|
|
@@ -2429,7 +2693,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2429
2693
|
registerInProvider = !flags.noProvider;
|
|
2430
2694
|
}
|
|
2431
2695
|
const s = p3.spinner();
|
|
2432
|
-
s.start(`Generating infrastructure class '${
|
|
2696
|
+
s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
|
|
2433
2697
|
const files = [];
|
|
2434
2698
|
const options = {
|
|
2435
2699
|
name: className,
|
|
@@ -2444,7 +2708,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2444
2708
|
const infraFile = generateInfrastructure(options);
|
|
2445
2709
|
files.push(infraFile);
|
|
2446
2710
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2447
|
-
const featureDir =
|
|
2711
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2448
2712
|
const providerFile = updateOrGenerateDiProvider(
|
|
2449
2713
|
featureDir,
|
|
2450
2714
|
feature,
|
|
@@ -2464,7 +2728,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2464
2728
|
}
|
|
2465
2729
|
s.stop("Infrastructure class generated successfully!");
|
|
2466
2730
|
printGeneratedFiles(files, options.dryRun);
|
|
2467
|
-
p3.outro(
|
|
2731
|
+
p3.outro(pc14.green(`Infrastructure class '${className}' created!`));
|
|
2468
2732
|
}
|
|
2469
2733
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2470
2734
|
const projectRoot = findProjectRoot();
|
|
@@ -2577,7 +2841,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2577
2841
|
}
|
|
2578
2842
|
}
|
|
2579
2843
|
const s = p3.spinner();
|
|
2580
|
-
s.start(`Generating interface '${
|
|
2844
|
+
s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
|
|
2581
2845
|
const files = [];
|
|
2582
2846
|
const options = {
|
|
2583
2847
|
name: ifaceName,
|
|
@@ -2606,7 +2870,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2606
2870
|
files.push(stubFile);
|
|
2607
2871
|
}
|
|
2608
2872
|
if (registerInProvider && generateStub) {
|
|
2609
|
-
const featureDir =
|
|
2873
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2610
2874
|
const providerFile = updateOrGenerateDiProvider(
|
|
2611
2875
|
featureDir,
|
|
2612
2876
|
feature,
|
|
@@ -2626,7 +2890,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2626
2890
|
}
|
|
2627
2891
|
s.stop("Interface generated successfully!");
|
|
2628
2892
|
printGeneratedFiles(files, options.dryRun);
|
|
2629
|
-
p3.outro(
|
|
2893
|
+
p3.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2630
2894
|
}
|
|
2631
2895
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2632
2896
|
const projectRoot = findProjectRoot();
|
|
@@ -2743,7 +3007,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2743
3007
|
}
|
|
2744
3008
|
}
|
|
2745
3009
|
const s = p3.spinner();
|
|
2746
|
-
s.start(`Generating HTTP handler '${
|
|
3010
|
+
s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
|
|
2747
3011
|
const options = {
|
|
2748
3012
|
name: className,
|
|
2749
3013
|
feature,
|
|
@@ -2761,7 +3025,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2761
3025
|
const files = generateHttpHandler(options);
|
|
2762
3026
|
s.stop("HTTP handler generated successfully!");
|
|
2763
3027
|
printGeneratedFiles(files, options.dryRun);
|
|
2764
|
-
p3.outro(
|
|
3028
|
+
p3.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
2765
3029
|
}
|
|
2766
3030
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
2767
3031
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -2771,7 +3035,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
2771
3035
|
const formattedMethods = methods.map((m) => {
|
|
2772
3036
|
const isAsync = m.async !== false;
|
|
2773
3037
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
2774
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3038
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join(", ") : "";
|
|
2775
3039
|
return {
|
|
2776
3040
|
name: m.name,
|
|
2777
3041
|
isAsync,
|
|
@@ -2794,21 +3058,21 @@ function generateInjectable(options) {
|
|
|
2794
3058
|
const className = toPascalCase(options.name);
|
|
2795
3059
|
let targetDir = "";
|
|
2796
3060
|
if (options.customPath) {
|
|
2797
|
-
targetDir =
|
|
3061
|
+
targetDir = path2.isAbsolute(options.customPath) ? options.customPath : path2.join(projectRoot, options.customPath);
|
|
2798
3062
|
} else if (options.location === "feature-service" && options.feature) {
|
|
2799
|
-
targetDir =
|
|
3063
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
2800
3064
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
2801
|
-
targetDir =
|
|
3065
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
2802
3066
|
} else if (options.location === "module") {
|
|
2803
|
-
targetDir =
|
|
3067
|
+
targetDir = path2.join(projectRoot, "modules", className);
|
|
2804
3068
|
} else {
|
|
2805
|
-
targetDir = options.feature ?
|
|
3069
|
+
targetDir = options.feature ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2.join(projectRoot, "shared", className.toLowerCase());
|
|
2806
3070
|
}
|
|
2807
|
-
const filePath =
|
|
3071
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
2808
3072
|
let ifaceName;
|
|
2809
3073
|
if (options.generateInterface) {
|
|
2810
3074
|
ifaceName = `I${className}`;
|
|
2811
|
-
const ifacePath =
|
|
3075
|
+
const ifacePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
2812
3076
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
2813
3077
|
files.push(
|
|
2814
3078
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -2917,7 +3181,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2917
3181
|
);
|
|
2918
3182
|
}
|
|
2919
3183
|
const s = p3.spinner();
|
|
2920
|
-
s.start(`Generating injectable '${
|
|
3184
|
+
s.start(`Generating injectable '${pc14.cyan(className)}'...`);
|
|
2921
3185
|
const options = {
|
|
2922
3186
|
name: className,
|
|
2923
3187
|
feature,
|
|
@@ -2931,7 +3195,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2931
3195
|
const files = generateInjectable(options);
|
|
2932
3196
|
s.stop("Injectable class generated successfully!");
|
|
2933
3197
|
printGeneratedFiles(files, options.dryRun);
|
|
2934
|
-
p3.outro(
|
|
3198
|
+
p3.outro(pc14.green(`Injectable '${className}' created successfully!`));
|
|
2935
3199
|
}
|
|
2936
3200
|
async function runInteractiveMenu(flags = {}) {
|
|
2937
3201
|
printBanner();
|
|
@@ -2944,6 +3208,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
2944
3208
|
label: "Feature",
|
|
2945
3209
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
2946
3210
|
},
|
|
3211
|
+
{
|
|
3212
|
+
value: "shared",
|
|
3213
|
+
label: "Shared Module",
|
|
3214
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3215
|
+
},
|
|
2947
3216
|
{
|
|
2948
3217
|
value: "usecase",
|
|
2949
3218
|
label: "Use Case",
|
|
@@ -2981,6 +3250,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
2981
3250
|
case "feature":
|
|
2982
3251
|
await runFeatureCommand(void 0, flags);
|
|
2983
3252
|
break;
|
|
3253
|
+
case "shared":
|
|
3254
|
+
await runSharedCommand(void 0, flags);
|
|
3255
|
+
break;
|
|
2984
3256
|
case "usecase":
|
|
2985
3257
|
await runUseCaseCommand(void 0, flags);
|
|
2986
3258
|
break;
|
|
@@ -3013,6 +3285,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3013
3285
|
case "feature":
|
|
3014
3286
|
await runFeatureCommand(name, flags);
|
|
3015
3287
|
break;
|
|
3288
|
+
case "shared":
|
|
3289
|
+
await runSharedCommand(name, flags);
|
|
3290
|
+
break;
|
|
3016
3291
|
case "usecase":
|
|
3017
3292
|
await runUseCaseCommand(name, flags);
|
|
3018
3293
|
break;
|
|
@@ -3034,8 +3309,8 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3034
3309
|
break;
|
|
3035
3310
|
default:
|
|
3036
3311
|
p3.log.error(
|
|
3037
|
-
|
|
3038
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3312
|
+
pc14.red(
|
|
3313
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3039
3314
|
)
|
|
3040
3315
|
);
|
|
3041
3316
|
process.exit(1);
|
|
@@ -3058,25 +3333,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
|
3058
3333
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
3059
3334
|
}
|
|
3060
3335
|
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
3061
|
-
return
|
|
3336
|
+
return path2.resolve(baseDir, trimmed);
|
|
3062
3337
|
}
|
|
3063
3338
|
return trimmed;
|
|
3064
3339
|
}
|
|
3065
3340
|
function findLocalRegistry(startDir = process.cwd()) {
|
|
3066
|
-
let current =
|
|
3341
|
+
let current = path2.resolve(startDir);
|
|
3067
3342
|
while (true) {
|
|
3068
|
-
const candidate =
|
|
3069
|
-
if (
|
|
3343
|
+
const candidate = path2.join(current, "mason-registry");
|
|
3344
|
+
if (fs9.existsSync(candidate) && fs9.existsSync(path2.join(candidate, "index.json"))) {
|
|
3070
3345
|
return candidate;
|
|
3071
3346
|
}
|
|
3072
|
-
const parent =
|
|
3347
|
+
const parent = path2.dirname(current);
|
|
3073
3348
|
if (parent === current) break;
|
|
3074
3349
|
current = parent;
|
|
3075
3350
|
}
|
|
3076
3351
|
return null;
|
|
3077
3352
|
}
|
|
3078
3353
|
function getRegistryBase(projectRoot, override) {
|
|
3079
|
-
const rootDir = projectRoot ?
|
|
3354
|
+
const rootDir = projectRoot ? path2.resolve(projectRoot) : process.cwd();
|
|
3080
3355
|
if (override && override.trim()) {
|
|
3081
3356
|
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
3082
3357
|
}
|
|
@@ -3084,30 +3359,30 @@ function getRegistryBase(projectRoot, override) {
|
|
|
3084
3359
|
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
3085
3360
|
}
|
|
3086
3361
|
const configLocations = [
|
|
3087
|
-
|
|
3088
|
-
|
|
3362
|
+
path2.join(rootDir, "mason.config.json"),
|
|
3363
|
+
path2.join(process.cwd(), "mason.config.json")
|
|
3089
3364
|
];
|
|
3090
3365
|
for (const configPath of configLocations) {
|
|
3091
|
-
if (
|
|
3366
|
+
if (fs9.existsSync(configPath)) {
|
|
3092
3367
|
try {
|
|
3093
|
-
const config = JSON.parse(
|
|
3368
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
3094
3369
|
if (config.registry && typeof config.registry === "string") {
|
|
3095
|
-
return normalizeRegistryUrl(config.registry.trim(),
|
|
3370
|
+
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
3096
3371
|
}
|
|
3097
3372
|
} catch {
|
|
3098
3373
|
}
|
|
3099
3374
|
}
|
|
3100
3375
|
}
|
|
3101
3376
|
const pkgLocations = [
|
|
3102
|
-
|
|
3103
|
-
|
|
3377
|
+
path2.join(rootDir, "package.json"),
|
|
3378
|
+
path2.join(process.cwd(), "package.json")
|
|
3104
3379
|
];
|
|
3105
3380
|
for (const pkgPath of pkgLocations) {
|
|
3106
|
-
if (
|
|
3381
|
+
if (fs9.existsSync(pkgPath)) {
|
|
3107
3382
|
try {
|
|
3108
|
-
const pkg = JSON.parse(
|
|
3383
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
3109
3384
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
3110
|
-
return normalizeRegistryUrl(pkg.mason.registry.trim(),
|
|
3385
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
3111
3386
|
}
|
|
3112
3387
|
} catch {
|
|
3113
3388
|
}
|
|
@@ -3148,11 +3423,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
3148
3423
|
if (baseDir.startsWith("file://")) {
|
|
3149
3424
|
baseDir = fileURLToPath(baseDir);
|
|
3150
3425
|
}
|
|
3151
|
-
const filePath =
|
|
3152
|
-
if (!
|
|
3426
|
+
const filePath = path2.resolve(baseDir, cleanRelPath);
|
|
3427
|
+
if (!fs9.existsSync(filePath)) {
|
|
3153
3428
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
3154
3429
|
}
|
|
3155
|
-
return
|
|
3430
|
+
return fs9.promises.readFile(filePath, "utf-8");
|
|
3156
3431
|
}
|
|
3157
3432
|
async function fetchRegistryIndex(registryBase) {
|
|
3158
3433
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -3214,26 +3489,26 @@ function findClosestMatch(target, candidates) {
|
|
|
3214
3489
|
return null;
|
|
3215
3490
|
}
|
|
3216
3491
|
function detectPackageManager(projectRoot) {
|
|
3217
|
-
if (
|
|
3218
|
-
if (
|
|
3219
|
-
if (
|
|
3492
|
+
if (fs9.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3493
|
+
if (fs9.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
3494
|
+
if (fs9.existsSync(path2.join(projectRoot, "bun.lockb")) || fs9.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
|
|
3220
3495
|
return "npm";
|
|
3221
3496
|
}
|
|
3222
3497
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
3223
3498
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
3224
|
-
const pkgJsonPath =
|
|
3225
|
-
if (!
|
|
3499
|
+
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
3500
|
+
if (!fs9.existsSync(pkgJsonPath)) {
|
|
3226
3501
|
return requiredPackages;
|
|
3227
3502
|
}
|
|
3228
3503
|
try {
|
|
3229
|
-
const pkg = JSON.parse(
|
|
3504
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
|
|
3230
3505
|
const installed = /* @__PURE__ */ new Set([
|
|
3231
3506
|
...Object.keys(pkg.dependencies || {}),
|
|
3232
3507
|
...Object.keys(pkg.devDependencies || {}),
|
|
3233
3508
|
...Object.keys(pkg.peerDependencies || {})
|
|
3234
3509
|
]);
|
|
3235
|
-
return requiredPackages.filter((
|
|
3236
|
-
const nameOnly =
|
|
3510
|
+
return requiredPackages.filter((p16) => {
|
|
3511
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
3237
3512
|
return !installed.has(nameOnly);
|
|
3238
3513
|
});
|
|
3239
3514
|
} catch {
|
|
@@ -3258,21 +3533,207 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3258
3533
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3259
3534
|
break;
|
|
3260
3535
|
}
|
|
3261
|
-
p3.log.info(`Installing missing dependencies with ${manager}: ${
|
|
3536
|
+
p3.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
|
|
3262
3537
|
execSync(installCmd, {
|
|
3263
3538
|
cwd: projectRoot,
|
|
3264
3539
|
stdio: "inherit"
|
|
3265
3540
|
});
|
|
3266
3541
|
}
|
|
3542
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
3543
|
+
if (!fs9.existsSync(dir)) return [];
|
|
3544
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
3545
|
+
const files = [];
|
|
3546
|
+
for (const entry of entries) {
|
|
3547
|
+
const fullPath = path2.join(dir, entry.name);
|
|
3548
|
+
const relPath = path2.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
3549
|
+
if (entry.isDirectory()) {
|
|
3550
|
+
if (entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
3554
|
+
} else if (entry.isFile()) {
|
|
3555
|
+
if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
|
|
3556
|
+
continue;
|
|
3557
|
+
}
|
|
3558
|
+
files.push(relPath);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
return files.sort();
|
|
3562
|
+
}
|
|
3563
|
+
function computeDirectoryHash(dirPath, files) {
|
|
3564
|
+
if (!fs9.existsSync(dirPath)) return "";
|
|
3565
|
+
const hash = createHash("sha256");
|
|
3566
|
+
const fileList = (getAllFiles(dirPath)).sort();
|
|
3567
|
+
for (const relativePath of fileList) {
|
|
3568
|
+
const absolutePath = path2.join(dirPath, relativePath);
|
|
3569
|
+
if (!fs9.existsSync(absolutePath)) continue;
|
|
3570
|
+
const content = fs9.readFileSync(absolutePath, "utf8");
|
|
3571
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
3572
|
+
hash.update(relativePath);
|
|
3573
|
+
hash.update(normalizedContent);
|
|
3574
|
+
}
|
|
3575
|
+
return `sha256-${hash.digest("hex")}`;
|
|
3576
|
+
}
|
|
3267
3577
|
|
|
3268
3578
|
// src/commands/import.ts
|
|
3579
|
+
var importPrompts = {
|
|
3580
|
+
select: p3.select,
|
|
3581
|
+
text: p3.text
|
|
3582
|
+
};
|
|
3583
|
+
function rewriteImports(content, aliasMap) {
|
|
3584
|
+
if (aliasMap.size === 0) return content;
|
|
3585
|
+
let rewritten = content;
|
|
3586
|
+
for (const [oldImport, newImport] of aliasMap.entries()) {
|
|
3587
|
+
const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
|
|
3588
|
+
const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
|
|
3589
|
+
const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3590
|
+
const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
|
|
3591
|
+
rewritten = rewritten.replace(regex, rawNew);
|
|
3592
|
+
}
|
|
3593
|
+
return rewritten;
|
|
3594
|
+
}
|
|
3595
|
+
function getModuleBaseDir(projectRoot, modType) {
|
|
3596
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3597
|
+
return path2.join(projectRoot, "src", folder);
|
|
3598
|
+
}
|
|
3599
|
+
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3600
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3601
|
+
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3602
|
+
if (fs9.existsSync(inSrc)) return inSrc;
|
|
3603
|
+
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3604
|
+
if (fs9.existsSync(inRoot)) return inRoot;
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
function checkModuleExists(projectRoot, modType, modName) {
|
|
3608
|
+
return getExistingModuleDir(projectRoot, modType, modName) !== null;
|
|
3609
|
+
}
|
|
3610
|
+
async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
|
|
3611
|
+
const folder = depType === "shared" ? "shared" : "features";
|
|
3612
|
+
const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
|
|
3613
|
+
if (isAlreadyImportedThisRun) return;
|
|
3614
|
+
const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
|
|
3615
|
+
if (programmaticAlias) {
|
|
3616
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
|
|
3617
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3618
|
+
const displayDest = path2.relative(
|
|
3619
|
+
projectRoot,
|
|
3620
|
+
path2.join(baseDir, programmaticAlias)
|
|
3621
|
+
);
|
|
3622
|
+
p3.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3623
|
+
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3627
|
+
if (!existingDir) {
|
|
3628
|
+
p3.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14.cyan(depName)}`);
|
|
3629
|
+
await importModuleRecursive(depType, depName, depName);
|
|
3630
|
+
return;
|
|
3631
|
+
}
|
|
3632
|
+
const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
|
|
3633
|
+
(item) => item.name === depName
|
|
3634
|
+
);
|
|
3635
|
+
let remoteIntegrity = entry?.integrity;
|
|
3636
|
+
if (!remoteIntegrity) {
|
|
3637
|
+
try {
|
|
3638
|
+
const depManifest = await fetchModuleManifest(registryBase, depType, depName);
|
|
3639
|
+
remoteIntegrity = depManifest.integrity;
|
|
3640
|
+
} catch {
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
const localHash = computeDirectoryHash(existingDir);
|
|
3644
|
+
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3645
|
+
p3.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3646
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3647
|
+
return;
|
|
3648
|
+
}
|
|
3649
|
+
if (flags.overwrite) {
|
|
3650
|
+
p3.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3651
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3652
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3653
|
+
if (!flags.dryRun) {
|
|
3654
|
+
if (existingDir !== targetDir) {
|
|
3655
|
+
fs9.rmSync(existingDir, { recursive: true, force: true });
|
|
3656
|
+
}
|
|
3657
|
+
fs9.rmSync(targetDir, { recursive: true, force: true });
|
|
3658
|
+
}
|
|
3659
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
if (flags.yes) {
|
|
3663
|
+
p3.log.info(`Using existing '${depType}/${depName}'`);
|
|
3664
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
const choice = handleCancel(
|
|
3668
|
+
await importPrompts.select({
|
|
3669
|
+
message: `Local '${depType}/${depName}' differs from the registry version.`,
|
|
3670
|
+
options: [
|
|
3671
|
+
{
|
|
3672
|
+
value: "keep_existing",
|
|
3673
|
+
label: `Keep existing local version (Recommended)`
|
|
3674
|
+
},
|
|
3675
|
+
{
|
|
3676
|
+
value: "rename",
|
|
3677
|
+
label: "Import fresh copy under a new name (alias)"
|
|
3678
|
+
},
|
|
3679
|
+
{
|
|
3680
|
+
value: "overwrite",
|
|
3681
|
+
label: "Overwrite local version with registry version"
|
|
3682
|
+
}
|
|
3683
|
+
]
|
|
3684
|
+
})
|
|
3685
|
+
);
|
|
3686
|
+
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3687
|
+
p3.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3688
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
if (choice === "rename") {
|
|
3692
|
+
const newName = handleCancel(
|
|
3693
|
+
await importPrompts.text({
|
|
3694
|
+
message: "What should we name the new copy?",
|
|
3695
|
+
placeholder: `${depName}-copy`,
|
|
3696
|
+
validate(val) {
|
|
3697
|
+
const v = val?.trim();
|
|
3698
|
+
if (!v) return "Name cannot be empty";
|
|
3699
|
+
if (v === depName) return `Name must be different from '${depName}'`;
|
|
3700
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
|
|
3701
|
+
return "Name can only contain alphanumeric characters, dashes, and underscores";
|
|
3702
|
+
}
|
|
3703
|
+
if (checkModuleExists(projectRoot, depType, v)) {
|
|
3704
|
+
return `'${v}' already exists in ${folder}, please choose another name`;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
})
|
|
3708
|
+
).trim();
|
|
3709
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3710
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3711
|
+
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
3712
|
+
p3.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3713
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
if (choice === "overwrite") {
|
|
3717
|
+
p3.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3718
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3719
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3720
|
+
if (!flags.dryRun) {
|
|
3721
|
+
if (existingDir !== targetDir) {
|
|
3722
|
+
fs9.rmSync(existingDir, { recursive: true, force: true });
|
|
3723
|
+
}
|
|
3724
|
+
fs9.rmSync(targetDir, { recursive: true, force: true });
|
|
3725
|
+
}
|
|
3726
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3269
3730
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3270
3731
|
const projectRoot = findProjectRoot();
|
|
3271
3732
|
let type = typeArg?.trim().toLowerCase();
|
|
3272
3733
|
if (type === "features") type = "feature";
|
|
3273
3734
|
if (!type) {
|
|
3274
3735
|
type = handleCancel(
|
|
3275
|
-
await
|
|
3736
|
+
await importPrompts.select({
|
|
3276
3737
|
message: "What kind of module would you like to import?",
|
|
3277
3738
|
options: [
|
|
3278
3739
|
{
|
|
@@ -3290,19 +3751,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3290
3751
|
);
|
|
3291
3752
|
}
|
|
3292
3753
|
if (type !== "shared" && type !== "feature") {
|
|
3293
|
-
p3.log.error(
|
|
3754
|
+
p3.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3294
3755
|
process.exit(1);
|
|
3295
3756
|
}
|
|
3296
3757
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3297
3758
|
const s = p3.spinner();
|
|
3298
|
-
s.start(`Connecting to registry at ${
|
|
3759
|
+
s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
|
|
3299
3760
|
let index;
|
|
3300
3761
|
try {
|
|
3301
3762
|
index = await fetchRegistryIndex(registryBase);
|
|
3302
3763
|
s.stop("Registry index loaded.");
|
|
3303
3764
|
} catch (err) {
|
|
3304
|
-
s.stop(
|
|
3305
|
-
p3.log.error(
|
|
3765
|
+
s.stop(pc14.red("Failed to load registry index."));
|
|
3766
|
+
p3.log.error(pc14.red(err.message || String(err)));
|
|
3306
3767
|
process.exit(1);
|
|
3307
3768
|
}
|
|
3308
3769
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3314,7 +3775,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3314
3775
|
return;
|
|
3315
3776
|
}
|
|
3316
3777
|
name = handleCancel(
|
|
3317
|
-
await
|
|
3778
|
+
await importPrompts.select({
|
|
3318
3779
|
message: `Select a ${type} to import:`,
|
|
3319
3780
|
options: availableList.map((item) => ({
|
|
3320
3781
|
value: item.name,
|
|
@@ -3326,71 +3787,94 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3326
3787
|
}
|
|
3327
3788
|
if (!candidateNames.includes(name)) {
|
|
3328
3789
|
const closest = findClosestMatch(name, candidateNames);
|
|
3329
|
-
p3.log.error(
|
|
3790
|
+
p3.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3330
3791
|
if (closest) {
|
|
3331
|
-
p3.log.info(
|
|
3792
|
+
p3.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
|
|
3332
3793
|
}
|
|
3333
3794
|
process.exit(1);
|
|
3334
3795
|
}
|
|
3335
3796
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3336
3797
|
const totalWrittenFiles = [];
|
|
3337
3798
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3338
|
-
async function importModuleRecursive(modType,
|
|
3339
|
-
const modKey = `${modType}:${
|
|
3799
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3800
|
+
const modKey = `${modType}:${targetName}`;
|
|
3340
3801
|
if (importedModules.has(modKey)) return;
|
|
3341
|
-
p3.log.step(
|
|
3802
|
+
p3.log.step(
|
|
3803
|
+
`Importing ${modType}: ${pc14.cyan(targetName)}${targetName !== registryName ? pc14.dim(` (from ${registryName})`) : ""}...`
|
|
3804
|
+
);
|
|
3342
3805
|
let manifest;
|
|
3343
3806
|
try {
|
|
3344
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3807
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3345
3808
|
} catch (err) {
|
|
3346
|
-
p3.log.error(
|
|
3809
|
+
p3.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3347
3810
|
process.exit(1);
|
|
3348
3811
|
}
|
|
3812
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3813
|
+
if (targetName !== registryName) {
|
|
3814
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3815
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3816
|
+
}
|
|
3349
3817
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3350
3818
|
for (const dep of sharedDeps) {
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3819
|
+
await resolveDependency(
|
|
3820
|
+
"shared",
|
|
3821
|
+
dep,
|
|
3822
|
+
aliasMap,
|
|
3823
|
+
projectRoot,
|
|
3824
|
+
flags,
|
|
3825
|
+
importModuleRecursive,
|
|
3826
|
+
importedModules,
|
|
3827
|
+
registryBase,
|
|
3828
|
+
index
|
|
3829
|
+
);
|
|
3356
3830
|
}
|
|
3357
3831
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3358
3832
|
for (const dep of featureDeps) {
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3833
|
+
await resolveDependency(
|
|
3834
|
+
"feature",
|
|
3835
|
+
dep,
|
|
3836
|
+
aliasMap,
|
|
3837
|
+
projectRoot,
|
|
3838
|
+
flags,
|
|
3839
|
+
importModuleRecursive,
|
|
3840
|
+
importedModules,
|
|
3841
|
+
registryBase,
|
|
3842
|
+
index
|
|
3843
|
+
);
|
|
3364
3844
|
}
|
|
3365
3845
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3366
3846
|
requiredNpmPackages.add(pkg);
|
|
3367
3847
|
}
|
|
3368
3848
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3369
|
-
const targetDir =
|
|
3849
|
+
const targetDir = path2.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3850
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3370
3851
|
for (const file of manifest.files || []) {
|
|
3371
3852
|
if (file === "registry.json") continue;
|
|
3372
|
-
const remoteFilePath = `${folder}/${
|
|
3373
|
-
const localFilePath =
|
|
3374
|
-
if (
|
|
3375
|
-
p3.log.warn(
|
|
3853
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3854
|
+
const localFilePath = path2.join(targetDir, file);
|
|
3855
|
+
if (fs9.existsSync(localFilePath) && !isOverwrite) {
|
|
3856
|
+
p3.log.warn(
|
|
3857
|
+
` \u21B3 Skipping existing file: ${pc14.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3858
|
+
);
|
|
3376
3859
|
continue;
|
|
3377
3860
|
}
|
|
3378
|
-
|
|
3861
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3862
|
+
content = rewriteImports(content, aliasMap);
|
|
3379
3863
|
if (!flags.dryRun) {
|
|
3380
|
-
|
|
3381
|
-
|
|
3864
|
+
fs9.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
3865
|
+
fs9.writeFileSync(localFilePath, content, "utf-8");
|
|
3382
3866
|
}
|
|
3383
|
-
const displayPath =
|
|
3867
|
+
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
3384
3868
|
totalWrittenFiles.push(displayPath);
|
|
3385
|
-
p3.log.message(` ${
|
|
3869
|
+
p3.log.message(` ${pc14.green("+")} ${displayPath}`);
|
|
3386
3870
|
}
|
|
3387
3871
|
importedModules.add(modKey);
|
|
3388
3872
|
}
|
|
3389
|
-
await importModuleRecursive(type, name);
|
|
3873
|
+
await importModuleRecursive(type, name, name);
|
|
3390
3874
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3391
3875
|
if (missingNpm.length > 0) {
|
|
3392
3876
|
if (flags.skipInstall || flags.dryRun) {
|
|
3393
|
-
p3.log.info(`Required npm dependencies to install: ${
|
|
3877
|
+
p3.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
|
|
3394
3878
|
} else {
|
|
3395
3879
|
try {
|
|
3396
3880
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -3400,11 +3884,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3400
3884
|
}
|
|
3401
3885
|
}
|
|
3402
3886
|
if (flags.dryRun) {
|
|
3403
|
-
p3.log.info(
|
|
3887
|
+
p3.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
|
|
3404
3888
|
} else {
|
|
3889
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3890
|
+
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
|
|
3405
3891
|
p3.log.success(
|
|
3406
|
-
|
|
3407
|
-
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in
|
|
3892
|
+
pc14.green(
|
|
3893
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3408
3894
|
)
|
|
3409
3895
|
);
|
|
3410
3896
|
}
|
|
@@ -3413,24 +3899,70 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3413
3899
|
// src/cli.ts
|
|
3414
3900
|
function createCli() {
|
|
3415
3901
|
const cli = cac("mason");
|
|
3416
|
-
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) => {
|
|
3902
|
+
cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").action(async (args, flags) => {
|
|
3417
3903
|
if (args && args.length > 0) {
|
|
3418
3904
|
p3.log.error(
|
|
3419
|
-
|
|
3905
|
+
pc14.red(
|
|
3420
3906
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3421
3907
|
)
|
|
3422
3908
|
);
|
|
3423
3909
|
process.exit(1);
|
|
3424
3910
|
}
|
|
3425
3911
|
try {
|
|
3912
|
+
const projectRoot = findProjectRoot();
|
|
3913
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
3914
|
+
if (mode === "registry") {
|
|
3915
|
+
const choice2 = handleCancel(
|
|
3916
|
+
await p3.select({
|
|
3917
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
3918
|
+
options: [
|
|
3919
|
+
{
|
|
3920
|
+
value: "create_feature",
|
|
3921
|
+
label: "Initialize New Feature",
|
|
3922
|
+
hint: "Scaffold a new feature slice under features/"
|
|
3923
|
+
},
|
|
3924
|
+
{
|
|
3925
|
+
value: "create_shared",
|
|
3926
|
+
label: "Initialize New Shared Module",
|
|
3927
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
3928
|
+
},
|
|
3929
|
+
{
|
|
3930
|
+
value: "create_usecase",
|
|
3931
|
+
label: "Create New Use Case",
|
|
3932
|
+
hint: "Add a new use case to an existing feature"
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
value: "import",
|
|
3936
|
+
label: "Import from Remote Registry",
|
|
3937
|
+
hint: "Download a module from upstream"
|
|
3938
|
+
}
|
|
3939
|
+
]
|
|
3940
|
+
})
|
|
3941
|
+
);
|
|
3942
|
+
switch (choice2) {
|
|
3943
|
+
case "create_feature":
|
|
3944
|
+
await handleCreate("feature", void 0, flags);
|
|
3945
|
+
break;
|
|
3946
|
+
case "create_shared":
|
|
3947
|
+
await handleCreate("shared", void 0, flags);
|
|
3948
|
+
break;
|
|
3949
|
+
case "create_usecase":
|
|
3950
|
+
await handleCreate("usecase", void 0, flags);
|
|
3951
|
+
break;
|
|
3952
|
+
case "import":
|
|
3953
|
+
await handleImport(void 0, void 0, flags);
|
|
3954
|
+
break;
|
|
3955
|
+
}
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
3426
3958
|
const choice = handleCancel(
|
|
3427
3959
|
await p3.select({
|
|
3428
3960
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3429
3961
|
options: [
|
|
3430
3962
|
{
|
|
3431
3963
|
value: "create",
|
|
3432
|
-
label: "Create
|
|
3433
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
3964
|
+
label: "Create component",
|
|
3965
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3434
3966
|
},
|
|
3435
3967
|
{
|
|
3436
3968
|
value: "import",
|
|
@@ -3451,8 +3983,8 @@ function createCli() {
|
|
|
3451
3983
|
});
|
|
3452
3984
|
cli.command(
|
|
3453
3985
|
"create [type] [name]",
|
|
3454
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3455
|
-
).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 = {}) => {
|
|
3986
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
3987
|
+
).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 = {}) => {
|
|
3456
3988
|
try {
|
|
3457
3989
|
await handleCreate(type, name, flags);
|
|
3458
3990
|
} catch (err) {
|
|
@@ -3471,6 +4003,7 @@ function createCli() {
|
|
|
3471
4003
|
});
|
|
3472
4004
|
const createSubtypes = [
|
|
3473
4005
|
"feature",
|
|
4006
|
+
"shared",
|
|
3474
4007
|
"usecase",
|
|
3475
4008
|
"domain",
|
|
3476
4009
|
"infrastructure",
|
|
@@ -3491,7 +4024,7 @@ function createCli() {
|
|
|
3491
4024
|
});
|
|
3492
4025
|
}
|
|
3493
4026
|
cli.help();
|
|
3494
|
-
cli.version("1.0.
|
|
4027
|
+
cli.version("1.0.14");
|
|
3495
4028
|
return cli;
|
|
3496
4029
|
}
|
|
3497
4030
|
function handleCliError(err) {
|
|
@@ -3499,7 +4032,7 @@ function handleCliError(err) {
|
|
|
3499
4032
|
process.exit(0);
|
|
3500
4033
|
}
|
|
3501
4034
|
const message = err instanceof Error ? err.message : String(err);
|
|
3502
|
-
p3.log.error(
|
|
4035
|
+
p3.log.error(pc14.red(`Error: ${message}`));
|
|
3503
4036
|
process.exit(1);
|
|
3504
4037
|
}
|
|
3505
4038
|
async function runCli(argv = process.argv) {
|