@solid-stack/mason 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +750 -217
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +747 -214
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/feature.d.ts.map +1 -1
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/core/sharedGenerator.d.ts +13 -0
- package/dist/core/sharedGenerator.d.ts.map +1 -0
- package/dist/index.cjs +770 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +758 -215
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +2 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var fs4 = require('fs');
|
|
4
|
+
var path2 = require('path');
|
|
5
5
|
var p4 = require('@clack/prompts');
|
|
6
|
-
var
|
|
6
|
+
var pc14 = require('picocolors');
|
|
7
7
|
var url = require('url');
|
|
8
8
|
var child_process = require('child_process');
|
|
9
|
+
var crypto = require('crypto');
|
|
9
10
|
var Handlebars = require('handlebars');
|
|
10
11
|
var cac = require('cac');
|
|
11
12
|
|
|
@@ -30,10 +31,10 @@ function _interopNamespace(e) {
|
|
|
30
31
|
return Object.freeze(n);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
var
|
|
34
|
-
var
|
|
34
|
+
var fs4__default = /*#__PURE__*/_interopDefault(fs4);
|
|
35
|
+
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
35
36
|
var p4__namespace = /*#__PURE__*/_interopNamespace(p4);
|
|
36
|
-
var
|
|
37
|
+
var pc14__default = /*#__PURE__*/_interopDefault(pc14);
|
|
37
38
|
var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
|
|
38
39
|
|
|
39
40
|
// src/utils/formatting.ts
|
|
@@ -104,8 +105,8 @@ function isValidIdentifier(str) {
|
|
|
104
105
|
}
|
|
105
106
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
106
107
|
const projectRoot = options.projectRoot || process.cwd();
|
|
107
|
-
const relPath =
|
|
108
|
-
const existedBefore =
|
|
108
|
+
const relPath = path2__default.default.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
109
|
+
const existedBefore = fs4__default.default.existsSync(absolutePath);
|
|
109
110
|
if (options.dryRun) {
|
|
110
111
|
return {
|
|
111
112
|
relativePath: relPath,
|
|
@@ -124,11 +125,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
124
125
|
existedBefore: true
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
|
-
const dir =
|
|
128
|
-
if (!
|
|
129
|
-
|
|
128
|
+
const dir = path2__default.default.dirname(absolutePath);
|
|
129
|
+
if (!fs4__default.default.existsSync(dir)) {
|
|
130
|
+
fs4__default.default.mkdirSync(dir, { recursive: true });
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
+
fs4__default.default.writeFileSync(absolutePath, content, "utf-8");
|
|
132
133
|
return {
|
|
133
134
|
relativePath: relPath,
|
|
134
135
|
absolutePath,
|
|
@@ -140,41 +141,114 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
140
141
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
141
142
|
console.log("");
|
|
142
143
|
if (isDryRun) {
|
|
143
|
-
p4__namespace.log.warn(
|
|
144
|
+
p4__namespace.log.warn(pc14__default.default.yellow(pc14__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
144
145
|
}
|
|
145
146
|
for (const f of files) {
|
|
146
147
|
if (f.action === "created") {
|
|
147
|
-
p4__namespace.log.success(`${
|
|
148
|
+
p4__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
|
|
148
149
|
} else if (f.action === "overwritten") {
|
|
149
|
-
p4__namespace.log.warn(`${
|
|
150
|
+
p4__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
|
|
150
151
|
} else if (f.action === "skipped") {
|
|
151
|
-
p4__namespace.log.info(`${
|
|
152
|
+
p4__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
152
153
|
} else {
|
|
153
|
-
p4__namespace.log.info(`${
|
|
154
|
+
p4__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(preview)")}`);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
158
|
+
function loadMasonConfig(startDir = process.cwd()) {
|
|
159
|
+
let current = path2__default.default.resolve(startDir);
|
|
160
|
+
while (true) {
|
|
161
|
+
const configPath = path2__default.default.join(current, "mason.config.json");
|
|
162
|
+
if (fs4__default.default.existsSync(configPath)) {
|
|
163
|
+
try {
|
|
164
|
+
return JSON.parse(fs4__default.default.readFileSync(configPath, "utf-8"));
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const parent = path2__default.default.dirname(current);
|
|
170
|
+
if (parent === current) break;
|
|
171
|
+
current = parent;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function getEffectiveMode(projectRoot, flags) {
|
|
176
|
+
if (flags?.registry) return "registry";
|
|
177
|
+
const config = loadMasonConfig(projectRoot);
|
|
178
|
+
if (config?.mode) return config.mode;
|
|
179
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "index.json")) && (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "features")) || fs4__default.default.existsSync(path2__default.default.join(projectRoot, "shared"))) && !fs4__default.default.existsSync(path2__default.default.join(projectRoot, "src"))) {
|
|
180
|
+
return "registry";
|
|
181
|
+
}
|
|
182
|
+
return "app";
|
|
183
|
+
}
|
|
184
|
+
function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
185
|
+
if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
|
|
186
|
+
if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
|
|
187
|
+
const config = loadMasonConfig(projectRoot);
|
|
188
|
+
if (config?.paths?.features) {
|
|
189
|
+
return path2__default.default.resolve(projectRoot, config.paths.features);
|
|
190
|
+
}
|
|
191
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
192
|
+
if (mode === "registry") {
|
|
193
|
+
return path2__default.default.join(projectRoot, "features");
|
|
194
|
+
}
|
|
195
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "src/features"))) {
|
|
196
|
+
return path2__default.default.join(projectRoot, "src/features");
|
|
197
|
+
}
|
|
198
|
+
return path2__default.default.join(projectRoot, "features");
|
|
199
|
+
}
|
|
200
|
+
function getConfiguredSharedDir(projectRoot, flags) {
|
|
201
|
+
if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
|
|
202
|
+
if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
|
|
203
|
+
const config = loadMasonConfig(projectRoot);
|
|
204
|
+
if (config?.paths?.shared) {
|
|
205
|
+
return path2__default.default.resolve(projectRoot, config.paths.shared);
|
|
206
|
+
}
|
|
207
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
208
|
+
if (mode === "registry") {
|
|
209
|
+
return path2__default.default.join(projectRoot, "shared");
|
|
210
|
+
}
|
|
211
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "src/shared"))) {
|
|
212
|
+
return path2__default.default.join(projectRoot, "src/shared");
|
|
213
|
+
}
|
|
214
|
+
return path2__default.default.join(projectRoot, "shared");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/utils/project.ts
|
|
157
218
|
function findProjectRoot(startDir = process.cwd()) {
|
|
158
|
-
let current =
|
|
219
|
+
let current = path2__default.default.resolve(startDir);
|
|
159
220
|
while (true) {
|
|
160
|
-
if (
|
|
221
|
+
if (fs4__default.default.existsSync(path2__default.default.join(current, "package.json"))) {
|
|
161
222
|
return current;
|
|
162
223
|
}
|
|
163
|
-
const parent =
|
|
224
|
+
const parent = path2__default.default.dirname(current);
|
|
164
225
|
if (parent === current) {
|
|
165
|
-
return
|
|
226
|
+
return path2__default.default.resolve(startDir);
|
|
166
227
|
}
|
|
167
228
|
current = parent;
|
|
168
229
|
}
|
|
169
230
|
}
|
|
170
|
-
function resolveFeaturesDir(projectRoot) {
|
|
171
|
-
return
|
|
231
|
+
function resolveFeaturesDir(projectRoot, flags) {
|
|
232
|
+
return getConfiguredFeaturesDir(projectRoot, flags);
|
|
233
|
+
}
|
|
234
|
+
function resolveSharedDir(projectRoot, flags) {
|
|
235
|
+
return getConfiguredSharedDir(projectRoot, flags);
|
|
236
|
+
}
|
|
237
|
+
function listExistingShared(projectRoot) {
|
|
238
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
239
|
+
if (!fs4__default.default.existsSync(sharedDir)) return [];
|
|
240
|
+
try {
|
|
241
|
+
const entries = fs4__default.default.readdirSync(sharedDir, { withFileTypes: true });
|
|
242
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
243
|
+
} catch {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
172
246
|
}
|
|
173
247
|
function listExistingFeatures(projectRoot) {
|
|
174
248
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
175
|
-
if (!
|
|
249
|
+
if (!fs4__default.default.existsSync(featuresDir)) return [];
|
|
176
250
|
try {
|
|
177
|
-
const entries =
|
|
251
|
+
const entries = fs4__default.default.readdirSync(featuresDir, { withFileTypes: true });
|
|
178
252
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
179
253
|
} catch {
|
|
180
254
|
return [];
|
|
@@ -184,14 +258,14 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
184
258
|
const results = [];
|
|
185
259
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
186
260
|
for (const feat of features) {
|
|
187
|
-
const domainDir =
|
|
188
|
-
if (!
|
|
261
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
262
|
+
if (!fs4__default.default.existsSync(domainDir)) continue;
|
|
189
263
|
try {
|
|
190
|
-
const files =
|
|
264
|
+
const files = fs4__default.default.readdirSync(domainDir);
|
|
191
265
|
for (const file of files) {
|
|
192
266
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
193
267
|
const name = file.replace(/\.ts$/, "");
|
|
194
|
-
const fullPath =
|
|
268
|
+
const fullPath = path2__default.default.join(domainDir, file);
|
|
195
269
|
if (/^I[A-Z]/.test(name)) {
|
|
196
270
|
results.push({
|
|
197
271
|
name,
|
|
@@ -200,7 +274,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
200
274
|
});
|
|
201
275
|
} else {
|
|
202
276
|
try {
|
|
203
|
-
const content =
|
|
277
|
+
const content = fs4__default.default.readFileSync(fullPath, "utf-8");
|
|
204
278
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
205
279
|
results.push({
|
|
206
280
|
name,
|
|
@@ -222,17 +296,17 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
222
296
|
const results = [];
|
|
223
297
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
224
298
|
for (const feat of features) {
|
|
225
|
-
const domainDir =
|
|
226
|
-
if (!
|
|
299
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
300
|
+
if (!fs4__default.default.existsSync(domainDir)) continue;
|
|
227
301
|
try {
|
|
228
|
-
const files =
|
|
302
|
+
const files = fs4__default.default.readdirSync(domainDir);
|
|
229
303
|
for (const file of files) {
|
|
230
304
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
231
305
|
const name = file.replace(/\.ts$/, "");
|
|
232
306
|
if (!/^I[A-Z]/.test(name)) {
|
|
233
307
|
results.push({
|
|
234
308
|
name,
|
|
235
|
-
fullPath:
|
|
309
|
+
fullPath: path2__default.default.join(domainDir, file),
|
|
236
310
|
feature: feat
|
|
237
311
|
});
|
|
238
312
|
}
|
|
@@ -247,16 +321,16 @@ function listUseCases(projectRoot, featureName) {
|
|
|
247
321
|
const results = [];
|
|
248
322
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
249
323
|
for (const feat of features) {
|
|
250
|
-
const useCasesDir =
|
|
251
|
-
if (!
|
|
324
|
+
const useCasesDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
325
|
+
if (!fs4__default.default.existsSync(useCasesDir)) continue;
|
|
252
326
|
try {
|
|
253
|
-
const files =
|
|
327
|
+
const files = fs4__default.default.readdirSync(useCasesDir);
|
|
254
328
|
for (const file of files) {
|
|
255
329
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
256
330
|
const name = file.replace(/\.ts$/, "");
|
|
257
331
|
results.push({
|
|
258
332
|
name,
|
|
259
|
-
fullPath:
|
|
333
|
+
fullPath: path2__default.default.join(useCasesDir, file),
|
|
260
334
|
feature: feat
|
|
261
335
|
});
|
|
262
336
|
}
|
|
@@ -281,13 +355,13 @@ function handleCancel(result) {
|
|
|
281
355
|
}
|
|
282
356
|
function printBanner() {
|
|
283
357
|
console.log("");
|
|
284
|
-
p4__namespace.intro(
|
|
358
|
+
p4__namespace.intro(pc14__default.default.bgCyan(pc14__default.default.black(" @solid-stack/mason ")));
|
|
285
359
|
}
|
|
286
360
|
async function promptProperties(options = {}) {
|
|
287
361
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
288
362
|
if (properties.length > 0) {
|
|
289
|
-
const summary = properties.map((
|
|
290
|
-
p4__namespace.log.info(
|
|
363
|
+
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
364
|
+
p4__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
291
365
|
const addMore = handleCancel(
|
|
292
366
|
await p4__namespace.confirm({
|
|
293
367
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -298,7 +372,7 @@ async function promptProperties(options = {}) {
|
|
|
298
372
|
return properties;
|
|
299
373
|
}
|
|
300
374
|
}
|
|
301
|
-
p4__namespace.log.info(
|
|
375
|
+
p4__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
302
376
|
while (true) {
|
|
303
377
|
const name = handleCancel(
|
|
304
378
|
await p4__namespace.text({
|
|
@@ -346,7 +420,7 @@ async function promptProperties(options = {}) {
|
|
|
346
420
|
optional
|
|
347
421
|
});
|
|
348
422
|
p4__namespace.log.success(
|
|
349
|
-
|
|
423
|
+
pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
350
424
|
);
|
|
351
425
|
}
|
|
352
426
|
return properties;
|
|
@@ -368,25 +442,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
|
368
442
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
369
443
|
}
|
|
370
444
|
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
371
|
-
return
|
|
445
|
+
return path2__default.default.resolve(baseDir, trimmed);
|
|
372
446
|
}
|
|
373
447
|
return trimmed;
|
|
374
448
|
}
|
|
375
449
|
function findLocalRegistry(startDir = process.cwd()) {
|
|
376
|
-
let current =
|
|
450
|
+
let current = path2__default.default.resolve(startDir);
|
|
377
451
|
while (true) {
|
|
378
|
-
const candidate =
|
|
379
|
-
if (
|
|
452
|
+
const candidate = path2__default.default.join(current, "mason-registry");
|
|
453
|
+
if (fs4__default.default.existsSync(candidate) && fs4__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
|
|
380
454
|
return candidate;
|
|
381
455
|
}
|
|
382
|
-
const parent =
|
|
456
|
+
const parent = path2__default.default.dirname(current);
|
|
383
457
|
if (parent === current) break;
|
|
384
458
|
current = parent;
|
|
385
459
|
}
|
|
386
460
|
return null;
|
|
387
461
|
}
|
|
388
462
|
function getRegistryBase(projectRoot, override) {
|
|
389
|
-
const rootDir = projectRoot ?
|
|
463
|
+
const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
|
|
390
464
|
if (override && override.trim()) {
|
|
391
465
|
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
392
466
|
}
|
|
@@ -394,30 +468,30 @@ function getRegistryBase(projectRoot, override) {
|
|
|
394
468
|
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
395
469
|
}
|
|
396
470
|
const configLocations = [
|
|
397
|
-
|
|
398
|
-
|
|
471
|
+
path2__default.default.join(rootDir, "mason.config.json"),
|
|
472
|
+
path2__default.default.join(process.cwd(), "mason.config.json")
|
|
399
473
|
];
|
|
400
474
|
for (const configPath of configLocations) {
|
|
401
|
-
if (
|
|
475
|
+
if (fs4__default.default.existsSync(configPath)) {
|
|
402
476
|
try {
|
|
403
|
-
const config = JSON.parse(
|
|
477
|
+
const config = JSON.parse(fs4__default.default.readFileSync(configPath, "utf-8"));
|
|
404
478
|
if (config.registry && typeof config.registry === "string") {
|
|
405
|
-
return normalizeRegistryUrl(config.registry.trim(),
|
|
479
|
+
return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
|
|
406
480
|
}
|
|
407
481
|
} catch {
|
|
408
482
|
}
|
|
409
483
|
}
|
|
410
484
|
}
|
|
411
485
|
const pkgLocations = [
|
|
412
|
-
|
|
413
|
-
|
|
486
|
+
path2__default.default.join(rootDir, "package.json"),
|
|
487
|
+
path2__default.default.join(process.cwd(), "package.json")
|
|
414
488
|
];
|
|
415
489
|
for (const pkgPath of pkgLocations) {
|
|
416
|
-
if (
|
|
490
|
+
if (fs4__default.default.existsSync(pkgPath)) {
|
|
417
491
|
try {
|
|
418
|
-
const pkg = JSON.parse(
|
|
492
|
+
const pkg = JSON.parse(fs4__default.default.readFileSync(pkgPath, "utf-8"));
|
|
419
493
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
420
|
-
return normalizeRegistryUrl(pkg.mason.registry.trim(),
|
|
494
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
|
|
421
495
|
}
|
|
422
496
|
} catch {
|
|
423
497
|
}
|
|
@@ -458,11 +532,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
458
532
|
if (baseDir.startsWith("file://")) {
|
|
459
533
|
baseDir = url.fileURLToPath(baseDir);
|
|
460
534
|
}
|
|
461
|
-
const filePath =
|
|
462
|
-
if (!
|
|
535
|
+
const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
|
|
536
|
+
if (!fs4__default.default.existsSync(filePath)) {
|
|
463
537
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
464
538
|
}
|
|
465
|
-
return
|
|
539
|
+
return fs4__default.default.promises.readFile(filePath, "utf-8");
|
|
466
540
|
}
|
|
467
541
|
async function fetchRegistryIndex(registryBase) {
|
|
468
542
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -524,26 +598,26 @@ function findClosestMatch(target, candidates) {
|
|
|
524
598
|
return null;
|
|
525
599
|
}
|
|
526
600
|
function detectPackageManager(projectRoot) {
|
|
527
|
-
if (
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
601
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
602
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
603
|
+
if (fs4__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lockb")) || fs4__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lock"))) return "bun";
|
|
530
604
|
return "npm";
|
|
531
605
|
}
|
|
532
606
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
533
607
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
534
|
-
const pkgJsonPath =
|
|
535
|
-
if (!
|
|
608
|
+
const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
|
|
609
|
+
if (!fs4__default.default.existsSync(pkgJsonPath)) {
|
|
536
610
|
return requiredPackages;
|
|
537
611
|
}
|
|
538
612
|
try {
|
|
539
|
-
const pkg = JSON.parse(
|
|
613
|
+
const pkg = JSON.parse(fs4__default.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
540
614
|
const installed = /* @__PURE__ */ new Set([
|
|
541
615
|
...Object.keys(pkg.dependencies || {}),
|
|
542
616
|
...Object.keys(pkg.devDependencies || {}),
|
|
543
617
|
...Object.keys(pkg.peerDependencies || {})
|
|
544
618
|
]);
|
|
545
|
-
return requiredPackages.filter((
|
|
546
|
-
const nameOnly =
|
|
619
|
+
return requiredPackages.filter((p16) => {
|
|
620
|
+
const nameOnly = p16.startsWith("@") ? `@${p16.slice(1).split("@")[0] || ""}` : p16.split("@")[0] || p16;
|
|
547
621
|
return !installed.has(nameOnly);
|
|
548
622
|
});
|
|
549
623
|
} catch {
|
|
@@ -568,12 +642,47 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
568
642
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
569
643
|
break;
|
|
570
644
|
}
|
|
571
|
-
p4__namespace.log.info(`Installing missing dependencies with ${manager}: ${
|
|
645
|
+
p4__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
|
|
572
646
|
child_process.execSync(installCmd, {
|
|
573
647
|
cwd: projectRoot,
|
|
574
648
|
stdio: "inherit"
|
|
575
649
|
});
|
|
576
650
|
}
|
|
651
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
652
|
+
if (!fs4__default.default.existsSync(dir)) return [];
|
|
653
|
+
const entries = fs4__default.default.readdirSync(dir, { withFileTypes: true });
|
|
654
|
+
const files = [];
|
|
655
|
+
for (const entry of entries) {
|
|
656
|
+
const fullPath = path2__default.default.join(dir, entry.name);
|
|
657
|
+
const relPath = path2__default.default.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
658
|
+
if (entry.isDirectory()) {
|
|
659
|
+
if (entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
663
|
+
} else if (entry.isFile()) {
|
|
664
|
+
if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
files.push(relPath);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return files.sort();
|
|
671
|
+
}
|
|
672
|
+
function computeDirectoryHash(dirPath, files) {
|
|
673
|
+
if (!fs4__default.default.existsSync(dirPath)) return "";
|
|
674
|
+
const hash = crypto.createHash("sha256");
|
|
675
|
+
const fileList = (files ?? getAllFiles(dirPath)).sort();
|
|
676
|
+
for (const relativePath of fileList) {
|
|
677
|
+
const absolutePath = path2__default.default.join(dirPath, relativePath);
|
|
678
|
+
if (!fs4__default.default.existsSync(absolutePath)) continue;
|
|
679
|
+
const content = fs4__default.default.readFileSync(absolutePath, "utf8");
|
|
680
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
681
|
+
hash.update(relativePath);
|
|
682
|
+
hash.update(normalizedContent);
|
|
683
|
+
}
|
|
684
|
+
return `sha256-${hash.digest("hex")}`;
|
|
685
|
+
}
|
|
577
686
|
var hbs = Handlebars__default.default.create();
|
|
578
687
|
hbs.registerHelper("eq", (a, b) => a === b);
|
|
579
688
|
hbs.registerHelper("ne", (a, b) => a !== b);
|
|
@@ -590,33 +699,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
590
699
|
});
|
|
591
700
|
var cachedTemplatesDir = null;
|
|
592
701
|
function getTemplatesDir() {
|
|
593
|
-
if (cachedTemplatesDir &&
|
|
702
|
+
if (cachedTemplatesDir && fs4__default.default.existsSync(cachedTemplatesDir)) {
|
|
594
703
|
return cachedTemplatesDir;
|
|
595
704
|
}
|
|
596
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
597
|
-
const cand1 =
|
|
598
|
-
if (
|
|
705
|
+
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('index.cjs', document.baseURI).href))));
|
|
706
|
+
const cand1 = path2__default.default.resolve(currentDir, "../../templates");
|
|
707
|
+
if (fs4__default.default.existsSync(cand1)) {
|
|
599
708
|
cachedTemplatesDir = cand1;
|
|
600
709
|
return cand1;
|
|
601
710
|
}
|
|
602
|
-
const cand2 =
|
|
603
|
-
if (
|
|
711
|
+
const cand2 = path2__default.default.resolve(currentDir, "../templates");
|
|
712
|
+
if (fs4__default.default.existsSync(cand2)) {
|
|
604
713
|
cachedTemplatesDir = cand2;
|
|
605
714
|
return cand2;
|
|
606
715
|
}
|
|
607
|
-
const cand3 =
|
|
608
|
-
if (
|
|
716
|
+
const cand3 = path2__default.default.resolve(currentDir, "templates");
|
|
717
|
+
if (fs4__default.default.existsSync(cand3)) {
|
|
609
718
|
cachedTemplatesDir = cand3;
|
|
610
719
|
return cand3;
|
|
611
720
|
}
|
|
612
721
|
let curr = currentDir;
|
|
613
722
|
while (true) {
|
|
614
|
-
const candTemplates =
|
|
615
|
-
if (
|
|
723
|
+
const candTemplates = path2__default.default.join(curr, "templates");
|
|
724
|
+
if (fs4__default.default.existsSync(candTemplates)) {
|
|
616
725
|
cachedTemplatesDir = candTemplates;
|
|
617
726
|
return candTemplates;
|
|
618
727
|
}
|
|
619
|
-
const parent =
|
|
728
|
+
const parent = path2__default.default.dirname(curr);
|
|
620
729
|
if (parent === curr) break;
|
|
621
730
|
curr = parent;
|
|
622
731
|
}
|
|
@@ -624,8 +733,8 @@ function getTemplatesDir() {
|
|
|
624
733
|
}
|
|
625
734
|
var templateCache = /* @__PURE__ */ new Map();
|
|
626
735
|
function loadTemplate(relativePath) {
|
|
627
|
-
const fullPath =
|
|
628
|
-
return
|
|
736
|
+
const fullPath = path2__default.default.isAbsolute(relativePath) ? relativePath : path2__default.default.join(getTemplatesDir(), relativePath);
|
|
737
|
+
return fs4__default.default.readFileSync(fullPath, "utf-8");
|
|
629
738
|
}
|
|
630
739
|
function renderTemplateFile(relativePath, context) {
|
|
631
740
|
let compiled = templateCache.get(relativePath);
|
|
@@ -710,8 +819,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
710
819
|
function generateDomain(options) {
|
|
711
820
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
712
821
|
const entityName = toPascalCase(options.name);
|
|
713
|
-
const targetDir = options.targetDir ||
|
|
714
|
-
const filePath =
|
|
822
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
823
|
+
const filePath = path2__default.default.join(targetDir, `${entityName}.ts`);
|
|
715
824
|
const code = generateDomainEntityCode(
|
|
716
825
|
entityName,
|
|
717
826
|
options.kind || "interface",
|
|
@@ -735,7 +844,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
735
844
|
const idKey = `${entityKey}Id`;
|
|
736
845
|
const listKey = `${entityKey}s`;
|
|
737
846
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
738
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
847
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join("; ") : "";
|
|
739
848
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
740
849
|
return {
|
|
741
850
|
name: m.name,
|
|
@@ -760,8 +869,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
760
869
|
function generateInterface(options) {
|
|
761
870
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
762
871
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
763
|
-
const targetDir = options.targetDir ||
|
|
764
|
-
const filePath =
|
|
872
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
873
|
+
const filePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
765
874
|
const code = generateInterfaceCode(
|
|
766
875
|
ifaceName,
|
|
767
876
|
options.kind || "abstractClass",
|
|
@@ -996,7 +1105,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
996
1105
|
};
|
|
997
1106
|
}
|
|
998
1107
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
999
|
-
if (customPath &&
|
|
1108
|
+
if (customPath && fs4__default.default.existsSync(customPath)) {
|
|
1000
1109
|
return { filePath: customPath, ifaceName };
|
|
1001
1110
|
}
|
|
1002
1111
|
const candidateNames = [
|
|
@@ -1006,22 +1115,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
1006
1115
|
];
|
|
1007
1116
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
1008
1117
|
for (const feat of features) {
|
|
1009
|
-
const domainDir =
|
|
1010
|
-
if (!
|
|
1118
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1119
|
+
if (!fs4__default.default.existsSync(domainDir)) continue;
|
|
1011
1120
|
for (const cand of candidateNames) {
|
|
1012
|
-
const p1 =
|
|
1013
|
-
if (
|
|
1121
|
+
const p1 = path2__default.default.join(domainDir, `${cand}.ts`);
|
|
1122
|
+
if (fs4__default.default.existsSync(p1)) {
|
|
1014
1123
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
1015
1124
|
}
|
|
1016
1125
|
}
|
|
1017
1126
|
}
|
|
1018
1127
|
for (const cand of candidateNames) {
|
|
1019
|
-
const p1 =
|
|
1020
|
-
if (
|
|
1021
|
-
const p22 =
|
|
1022
|
-
if (
|
|
1023
|
-
const p32 =
|
|
1024
|
-
if (
|
|
1128
|
+
const p1 = path2__default.default.join(projectRoot, "domain", `${cand}.ts`);
|
|
1129
|
+
if (fs4__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
1130
|
+
const p22 = path2__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
1131
|
+
if (fs4__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
1132
|
+
const p32 = path2__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
1133
|
+
if (fs4__default.default.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
1025
1134
|
}
|
|
1026
1135
|
return null;
|
|
1027
1136
|
}
|
|
@@ -1044,7 +1153,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
1044
1153
|
while (currentFile && !visited.has(currentFile)) {
|
|
1045
1154
|
visited.add(currentFile);
|
|
1046
1155
|
try {
|
|
1047
|
-
const content =
|
|
1156
|
+
const content = fs4__default.default.readFileSync(currentFile, "utf-8");
|
|
1048
1157
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
1049
1158
|
for (const m of parsed.methods) {
|
|
1050
1159
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1120,7 +1229,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
1120
1229
|
const entitiesVar = `${entityVar}s`;
|
|
1121
1230
|
const idKey = `${entityVar}Id`;
|
|
1122
1231
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
1123
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
1232
|
+
const entityImportPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1124
1233
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
1125
1234
|
for (const imp of domainImports) {
|
|
1126
1235
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -1169,7 +1278,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
1169
1278
|
if (baseEntity) {
|
|
1170
1279
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
1171
1280
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
1172
|
-
const importPath = ifaceImportPath ? `${
|
|
1281
|
+
const importPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1173
1282
|
finalDomainImports.push({
|
|
1174
1283
|
name: baseEntity,
|
|
1175
1284
|
from: importPath,
|
|
@@ -1214,8 +1323,8 @@ function generateInfrastructure(options) {
|
|
|
1214
1323
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1215
1324
|
const className = toPascalCase(options.name);
|
|
1216
1325
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
1217
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
1218
|
-
const filePath =
|
|
1326
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2__default.default.join(projectRoot, "infrastructure"));
|
|
1327
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
1219
1328
|
let code;
|
|
1220
1329
|
if (options.implementsInterface) {
|
|
1221
1330
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -1229,7 +1338,7 @@ function generateInfrastructure(options) {
|
|
|
1229
1338
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
1230
1339
|
let ifaceImportPath;
|
|
1231
1340
|
if (ifaceFilePath) {
|
|
1232
|
-
let rel =
|
|
1341
|
+
let rel = path2__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
1233
1342
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1234
1343
|
ifaceImportPath = rel;
|
|
1235
1344
|
} else if (isFeatureScope) {
|
|
@@ -1242,8 +1351,8 @@ function generateInfrastructure(options) {
|
|
|
1242
1351
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
1243
1352
|
let fromPath;
|
|
1244
1353
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
1245
|
-
const absTarget =
|
|
1246
|
-
let rel =
|
|
1354
|
+
const absTarget = path2__default.default.resolve(path2__default.default.dirname(ifaceFilePath), imp.specifier);
|
|
1355
|
+
let rel = path2__default.default.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
1247
1356
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1248
1357
|
fromPath = rel;
|
|
1249
1358
|
} else {
|
|
@@ -1316,22 +1425,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1316
1425
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1317
1426
|
}));
|
|
1318
1427
|
const domainTypesToImport = new Set(domainImports);
|
|
1319
|
-
for (const
|
|
1320
|
-
const rawType =
|
|
1428
|
+
for (const p16 of [...inputProps, ...outputProps]) {
|
|
1429
|
+
const rawType = p16.type.trim();
|
|
1321
1430
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1322
1431
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1323
1432
|
domainTypesToImport.add(baseType);
|
|
1324
1433
|
}
|
|
1325
1434
|
}
|
|
1326
|
-
const formattedInputProps = inputProps.map((
|
|
1327
|
-
name:
|
|
1328
|
-
optional:
|
|
1329
|
-
type:
|
|
1435
|
+
const formattedInputProps = inputProps.map((p16) => ({
|
|
1436
|
+
name: p16.name,
|
|
1437
|
+
optional: p16.optional,
|
|
1438
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1330
1439
|
}));
|
|
1331
|
-
const formattedOutputProps = outputProps.map((
|
|
1332
|
-
name:
|
|
1333
|
-
optional:
|
|
1334
|
-
type:
|
|
1440
|
+
const formattedOutputProps = outputProps.map((p16) => ({
|
|
1441
|
+
name: p16.name,
|
|
1442
|
+
optional: p16.optional,
|
|
1443
|
+
type: p16.optional && !p16.type.includes("undefined") ? `${p16.type} | undefined` : p16.type
|
|
1335
1444
|
}));
|
|
1336
1445
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1337
1446
|
key: toCamelCase(dep),
|
|
@@ -1430,8 +1539,8 @@ function generateUseCase(options) {
|
|
|
1430
1539
|
const files = [];
|
|
1431
1540
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1432
1541
|
const ucName = toPascalCase(options.name);
|
|
1433
|
-
const targetDir = options.targetDir ||
|
|
1434
|
-
const filePath =
|
|
1542
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1543
|
+
const filePath = path2__default.default.join(targetDir, `${ucName}.ts`);
|
|
1435
1544
|
const code = generateUseCaseCode(
|
|
1436
1545
|
ucName,
|
|
1437
1546
|
options.dependencies || [],
|
|
@@ -1447,7 +1556,7 @@ function generateUseCase(options) {
|
|
|
1447
1556
|
})
|
|
1448
1557
|
);
|
|
1449
1558
|
if (options.includeTest) {
|
|
1450
|
-
const testPath =
|
|
1559
|
+
const testPath = path2__default.default.join(targetDir, `${ucName}.test.ts`);
|
|
1451
1560
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1452
1561
|
files.push(
|
|
1453
1562
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1515,10 +1624,10 @@ function generateHttpHandler(options) {
|
|
|
1515
1624
|
const files = [];
|
|
1516
1625
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1517
1626
|
const className = toPascalCase(options.name);
|
|
1518
|
-
const pxDir =
|
|
1519
|
-
const handlersDir =
|
|
1520
|
-
const pxIndexPath =
|
|
1521
|
-
if (!
|
|
1627
|
+
const pxDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1628
|
+
const handlersDir = path2__default.default.join(pxDir, "handlers");
|
|
1629
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1630
|
+
if (!fs4__default.default.existsSync(pxIndexPath)) {
|
|
1522
1631
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1523
1632
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1524
1633
|
routePath: featurePath
|
|
@@ -1531,7 +1640,7 @@ function generateHttpHandler(options) {
|
|
|
1531
1640
|
})
|
|
1532
1641
|
);
|
|
1533
1642
|
}
|
|
1534
|
-
const handlerFilePath =
|
|
1643
|
+
const handlerFilePath = path2__default.default.join(handlersDir, `${className}.ts`);
|
|
1535
1644
|
const code = generateHttpHandlerCode(
|
|
1536
1645
|
className,
|
|
1537
1646
|
options.method || "get",
|
|
@@ -1552,11 +1661,11 @@ function generateHttpHandler(options) {
|
|
|
1552
1661
|
return files;
|
|
1553
1662
|
}
|
|
1554
1663
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1555
|
-
const providerPath =
|
|
1664
|
+
const providerPath = path2__default.default.join(featureDir, "diProvider.ts");
|
|
1556
1665
|
const pascalFeature = toPascalCase(featureName);
|
|
1557
1666
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1558
|
-
if (
|
|
1559
|
-
let content =
|
|
1667
|
+
if (fs4__default.default.existsSync(providerPath) && !options.overwrite) {
|
|
1668
|
+
let content = fs4__default.default.readFileSync(providerPath, "utf-8");
|
|
1560
1669
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1561
1670
|
for (const b of bindings) {
|
|
1562
1671
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1615,7 +1724,7 @@ function generateFeature(options) {
|
|
|
1615
1724
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1616
1725
|
const rawName = options.name.trim();
|
|
1617
1726
|
const featureName = toCamelCase(rawName);
|
|
1618
|
-
const targetDir = options.targetDir ||
|
|
1727
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1619
1728
|
const routePath = options.routePath || `/${featureName}`;
|
|
1620
1729
|
const rawEntities = [];
|
|
1621
1730
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1636,7 +1745,7 @@ function generateFeature(options) {
|
|
|
1636
1745
|
const domainFile = generateDomain({
|
|
1637
1746
|
name: ent,
|
|
1638
1747
|
feature: featureName,
|
|
1639
|
-
targetDir:
|
|
1748
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1640
1749
|
projectRoot,
|
|
1641
1750
|
dryRun: options.dryRun,
|
|
1642
1751
|
overwrite: options.overwrite,
|
|
@@ -1656,7 +1765,7 @@ function generateFeature(options) {
|
|
|
1656
1765
|
const ifaceFile = generateInterface({
|
|
1657
1766
|
name: ifaceName,
|
|
1658
1767
|
feature: featureName,
|
|
1659
|
-
targetDir:
|
|
1768
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1660
1769
|
entityName: ent,
|
|
1661
1770
|
projectRoot,
|
|
1662
1771
|
dryRun: options.dryRun,
|
|
@@ -1673,7 +1782,7 @@ function generateFeature(options) {
|
|
|
1673
1782
|
const stubFile = generateInfrastructure({
|
|
1674
1783
|
name: stubName,
|
|
1675
1784
|
feature: featureName,
|
|
1676
|
-
targetDir:
|
|
1785
|
+
targetDir: path2__default.default.join(targetDir, "infrastructure"),
|
|
1677
1786
|
kind: "stub",
|
|
1678
1787
|
implementsInterface: ifaceName,
|
|
1679
1788
|
entityName: ent,
|
|
@@ -1715,7 +1824,7 @@ function generateFeature(options) {
|
|
|
1715
1824
|
const ucFiles = generateUseCase({
|
|
1716
1825
|
name: ucName,
|
|
1717
1826
|
feature: featureName,
|
|
1718
|
-
targetDir:
|
|
1827
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1719
1828
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1720
1829
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1721
1830
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1760,7 +1869,7 @@ function generateFeature(options) {
|
|
|
1760
1869
|
const ucFiles = generateUseCase({
|
|
1761
1870
|
name: ucName,
|
|
1762
1871
|
feature: featureName,
|
|
1763
|
-
targetDir:
|
|
1872
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1764
1873
|
dependencies: deps,
|
|
1765
1874
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1766
1875
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1793,7 +1902,7 @@ function generateFeature(options) {
|
|
|
1793
1902
|
const ucFiles = generateUseCase({
|
|
1794
1903
|
name: ucName,
|
|
1795
1904
|
feature: featureName,
|
|
1796
|
-
targetDir:
|
|
1905
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1797
1906
|
dependencies: deps,
|
|
1798
1907
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1799
1908
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1826,7 +1935,7 @@ function generateFeature(options) {
|
|
|
1826
1935
|
const ucFiles = generateUseCase({
|
|
1827
1936
|
name: ucName,
|
|
1828
1937
|
feature: featureName,
|
|
1829
|
-
targetDir:
|
|
1938
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1830
1939
|
dependencies: deps,
|
|
1831
1940
|
inputProperties: [
|
|
1832
1941
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1870,7 +1979,7 @@ function generateFeature(options) {
|
|
|
1870
1979
|
const ucFiles = generateUseCase({
|
|
1871
1980
|
name: ucName,
|
|
1872
1981
|
feature: featureName,
|
|
1873
|
-
targetDir:
|
|
1982
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1874
1983
|
dependencies: deps,
|
|
1875
1984
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1876
1985
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1903,7 +2012,7 @@ function generateFeature(options) {
|
|
|
1903
2012
|
const ucFiles = generateUseCase({
|
|
1904
2013
|
name: ucName,
|
|
1905
2014
|
feature: featureName,
|
|
1906
|
-
targetDir:
|
|
2015
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1907
2016
|
dependencies: deps,
|
|
1908
2017
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1909
2018
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1940,7 +2049,7 @@ function generateFeature(options) {
|
|
|
1940
2049
|
const ucFiles = generateUseCase({
|
|
1941
2050
|
name: ucName,
|
|
1942
2051
|
feature: featureName,
|
|
1943
|
-
targetDir:
|
|
2052
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1944
2053
|
dependencies: customUc.dependencies || [],
|
|
1945
2054
|
inputProperties: customUc.inputProperties || [],
|
|
1946
2055
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1972,8 +2081,8 @@ function generateFeature(options) {
|
|
|
1972
2081
|
}
|
|
1973
2082
|
}
|
|
1974
2083
|
if (options.includeExpress !== false) {
|
|
1975
|
-
const pxDir =
|
|
1976
|
-
const pxIndexPath =
|
|
2084
|
+
const pxDir = path2__default.default.join(targetDir, "pxExpress");
|
|
2085
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1977
2086
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1978
2087
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1979
2088
|
files.push(
|
|
@@ -1999,6 +2108,7 @@ function generateFeature(options) {
|
|
|
1999
2108
|
// src/commands/feature.ts
|
|
2000
2109
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
2001
2110
|
const projectRoot = findProjectRoot();
|
|
2111
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2002
2112
|
let name = featureNameArg?.trim();
|
|
2003
2113
|
if (!name && flags.yes) {
|
|
2004
2114
|
name = "feature";
|
|
@@ -2016,9 +2126,10 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2016
2126
|
).trim();
|
|
2017
2127
|
}
|
|
2018
2128
|
const normalizedName = toCamelCase(name);
|
|
2019
|
-
const
|
|
2129
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
2130
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseFeaturesDir, normalizedName));
|
|
2020
2131
|
let targetDirRel = defaultDir;
|
|
2021
|
-
if (!flags.yes && !flags.target) {
|
|
2132
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2022
2133
|
targetDirRel = handleCancel(
|
|
2023
2134
|
await p4__namespace.text({
|
|
2024
2135
|
message: "Target directory for the feature:",
|
|
@@ -2027,7 +2138,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2027
2138
|
})
|
|
2028
2139
|
).trim();
|
|
2029
2140
|
}
|
|
2030
|
-
const targetDir = flags.target ?
|
|
2141
|
+
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);
|
|
2031
2142
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
2032
2143
|
let routePath = `/${normalizedName}`;
|
|
2033
2144
|
let includeDomain = true;
|
|
@@ -2248,7 +2359,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2248
2359
|
}
|
|
2249
2360
|
}
|
|
2250
2361
|
const s = p4__namespace.spinner();
|
|
2251
|
-
s.start(`Generating feature '${
|
|
2362
|
+
s.start(`Generating feature '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2252
2363
|
const options = {
|
|
2253
2364
|
name: normalizedName,
|
|
2254
2365
|
projectRoot,
|
|
@@ -2268,9 +2379,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2268
2379
|
includeDiProvider
|
|
2269
2380
|
};
|
|
2270
2381
|
const files = generateFeature(options);
|
|
2382
|
+
if (isRegistry) {
|
|
2383
|
+
const registryPath = path2__default.default.join(targetDir, "registry.json");
|
|
2384
|
+
if (!fs4__default.default.existsSync(registryPath)) {
|
|
2385
|
+
const manifest = {
|
|
2386
|
+
name: toKebabCase(normalizedName),
|
|
2387
|
+
type: "feature",
|
|
2388
|
+
description: flags.description || `${normalizedName} feature module`,
|
|
2389
|
+
version: "1.0.0",
|
|
2390
|
+
dependencies: {
|
|
2391
|
+
npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
|
|
2392
|
+
shared: [],
|
|
2393
|
+
features: []
|
|
2394
|
+
},
|
|
2395
|
+
files: []
|
|
2396
|
+
};
|
|
2397
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2398
|
+
const genFile = writeFileSafely(registryPath, content, {
|
|
2399
|
+
projectRoot,
|
|
2400
|
+
overwrite: options.overwrite,
|
|
2401
|
+
dryRun: options.dryRun
|
|
2402
|
+
});
|
|
2403
|
+
files.push(genFile);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2271
2406
|
s.stop("Feature scaffolded successfully!");
|
|
2272
2407
|
printGeneratedFiles(files, options.dryRun);
|
|
2273
|
-
p4__namespace.outro(
|
|
2408
|
+
p4__namespace.outro(
|
|
2409
|
+
pc14__default.default.green(
|
|
2410
|
+
`\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2411
|
+
)
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
function generateShared(options) {
|
|
2415
|
+
const files = [];
|
|
2416
|
+
const projectRoot = options.projectRoot || findProjectRoot();
|
|
2417
|
+
const rawName = options.name.trim();
|
|
2418
|
+
const pascalName = toPascalCase(rawName);
|
|
2419
|
+
const camelName = toCamelCase(rawName);
|
|
2420
|
+
const kebabName = toKebabCase(rawName);
|
|
2421
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveSharedDir(projectRoot), kebabName);
|
|
2422
|
+
const fileDefs = [];
|
|
2423
|
+
const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
|
|
2424
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2425
|
+
|
|
2426
|
+
@MakeInjectable
|
|
2427
|
+
export class ${pascalName} {
|
|
2428
|
+
public static deps = { ${camelName}Engine: I${pascalName}Engine };
|
|
2429
|
+
constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
|
|
2430
|
+
}
|
|
2431
|
+
`;
|
|
2432
|
+
fileDefs.push({
|
|
2433
|
+
absolutePath: path2__default.default.join(targetDir, `${pascalName}.ts`),
|
|
2434
|
+
content: facadeContent
|
|
2435
|
+
});
|
|
2436
|
+
const portContent = `export abstract class I${pascalName}Engine {
|
|
2437
|
+
// Define abstract port contracts for ${pascalName} here
|
|
2438
|
+
}
|
|
2439
|
+
`;
|
|
2440
|
+
fileDefs.push({
|
|
2441
|
+
absolutePath: path2__default.default.join(targetDir, "ports", `I${pascalName}Engine.ts`),
|
|
2442
|
+
content: portContent
|
|
2443
|
+
});
|
|
2444
|
+
const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2445
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2446
|
+
|
|
2447
|
+
@MakeInjectable
|
|
2448
|
+
export class Default${pascalName}Engine implements I${pascalName}Engine {
|
|
2449
|
+
// Default concrete implementation for production
|
|
2450
|
+
}
|
|
2451
|
+
`;
|
|
2452
|
+
fileDefs.push({
|
|
2453
|
+
absolutePath: path2__default.default.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
|
|
2454
|
+
content: concreteContent
|
|
2455
|
+
});
|
|
2456
|
+
if (options.includeStub !== false) {
|
|
2457
|
+
const stubContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2458
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2459
|
+
|
|
2460
|
+
@MakeInjectable
|
|
2461
|
+
export class Stub${pascalName}Engine implements I${pascalName}Engine {
|
|
2462
|
+
// In-memory or stub implementation for testing
|
|
2463
|
+
}
|
|
2464
|
+
`;
|
|
2465
|
+
fileDefs.push({
|
|
2466
|
+
absolutePath: path2__default.default.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
|
|
2467
|
+
content: stubContent
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
const providerContent = `import type { Container } from "@solid-stack/di";
|
|
2471
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2472
|
+
import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
|
|
2473
|
+
|
|
2474
|
+
export const diProvider = (c: Container): void => {
|
|
2475
|
+
c.provide(I${pascalName}Engine, Default${pascalName}Engine);
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2478
|
+
export default diProvider;
|
|
2479
|
+
`;
|
|
2480
|
+
fileDefs.push({
|
|
2481
|
+
absolutePath: path2__default.default.join(targetDir, "diProvider.ts"),
|
|
2482
|
+
content: providerContent
|
|
2483
|
+
});
|
|
2484
|
+
if (options.isRegistry) {
|
|
2485
|
+
const registryManifest = {
|
|
2486
|
+
name: kebabName,
|
|
2487
|
+
type: "shared",
|
|
2488
|
+
description: options.description || `${kebabName} shared module`,
|
|
2489
|
+
version: "1.0.0",
|
|
2490
|
+
dependencies: {
|
|
2491
|
+
npm: [],
|
|
2492
|
+
shared: [],
|
|
2493
|
+
features: []
|
|
2494
|
+
},
|
|
2495
|
+
files: []
|
|
2496
|
+
};
|
|
2497
|
+
fileDefs.push({
|
|
2498
|
+
absolutePath: path2__default.default.join(targetDir, "registry.json"),
|
|
2499
|
+
content: JSON.stringify(registryManifest, null, 2) + "\n"
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
for (const def of fileDefs) {
|
|
2503
|
+
const genFile = writeFileSafely(def.absolutePath, def.content, {
|
|
2504
|
+
projectRoot,
|
|
2505
|
+
overwrite: options.overwrite,
|
|
2506
|
+
dryRun: options.dryRun
|
|
2507
|
+
});
|
|
2508
|
+
files.push(genFile);
|
|
2509
|
+
}
|
|
2510
|
+
return files;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/commands/shared.ts
|
|
2514
|
+
async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
2515
|
+
const projectRoot = findProjectRoot();
|
|
2516
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2517
|
+
let name = sharedNameArg?.trim();
|
|
2518
|
+
if (!name && flags.yes) {
|
|
2519
|
+
name = "shared";
|
|
2520
|
+
}
|
|
2521
|
+
if (!name) {
|
|
2522
|
+
name = handleCancel(
|
|
2523
|
+
await p4__namespace.text({
|
|
2524
|
+
message: "What is the shared module name?",
|
|
2525
|
+
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2526
|
+
validate: (val) => {
|
|
2527
|
+
if (!val || !val.trim()) return "Shared module name cannot be empty.";
|
|
2528
|
+
return void 0;
|
|
2529
|
+
}
|
|
2530
|
+
})
|
|
2531
|
+
).trim();
|
|
2532
|
+
}
|
|
2533
|
+
const normalizedName = toKebabCase(name);
|
|
2534
|
+
const baseSharedDir = resolveSharedDir(projectRoot, flags);
|
|
2535
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseSharedDir, normalizedName));
|
|
2536
|
+
let targetDirRel = defaultDir;
|
|
2537
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2538
|
+
targetDirRel = handleCancel(
|
|
2539
|
+
await p4__namespace.text({
|
|
2540
|
+
message: "Target directory for the shared module:",
|
|
2541
|
+
initialValue: defaultDir,
|
|
2542
|
+
defaultValue: defaultDir
|
|
2543
|
+
})
|
|
2544
|
+
).trim();
|
|
2545
|
+
}
|
|
2546
|
+
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);
|
|
2547
|
+
let description = flags.description || "";
|
|
2548
|
+
let includeStub = true;
|
|
2549
|
+
if (!flags.yes) {
|
|
2550
|
+
description = handleCancel(
|
|
2551
|
+
await p4__namespace.text({
|
|
2552
|
+
message: "Module description (optional):",
|
|
2553
|
+
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2554
|
+
initialValue: ""
|
|
2555
|
+
})
|
|
2556
|
+
).trim();
|
|
2557
|
+
includeStub = handleCancel(
|
|
2558
|
+
await p4__namespace.confirm({
|
|
2559
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2560
|
+
initialValue: true
|
|
2561
|
+
})
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
const s = p4__namespace.spinner();
|
|
2565
|
+
s.start(`Generating shared module '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2566
|
+
const files = generateShared({
|
|
2567
|
+
name: normalizedName,
|
|
2568
|
+
description,
|
|
2569
|
+
projectRoot,
|
|
2570
|
+
targetDir,
|
|
2571
|
+
dryRun: Boolean(flags.dryRun),
|
|
2572
|
+
overwrite: Boolean(flags.overwrite),
|
|
2573
|
+
includeStub,
|
|
2574
|
+
isRegistry
|
|
2575
|
+
});
|
|
2576
|
+
s.stop("Shared module generated successfully!");
|
|
2577
|
+
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2578
|
+
p4__namespace.outro(
|
|
2579
|
+
pc14__default.default.green(
|
|
2580
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2581
|
+
)
|
|
2582
|
+
);
|
|
2274
2583
|
}
|
|
2275
2584
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2276
2585
|
const projectRoot = findProjectRoot();
|
|
@@ -2395,7 +2704,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2395
2704
|
}
|
|
2396
2705
|
}
|
|
2397
2706
|
const s = p4__namespace.spinner();
|
|
2398
|
-
s.start(`Generating use case '${
|
|
2707
|
+
s.start(`Generating use case '${pc14__default.default.cyan(ucName)}'...`);
|
|
2399
2708
|
const options = {
|
|
2400
2709
|
name: ucName,
|
|
2401
2710
|
feature,
|
|
@@ -2427,7 +2736,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2427
2736
|
}
|
|
2428
2737
|
s.stop("Use case generated successfully!");
|
|
2429
2738
|
printGeneratedFiles(files, options.dryRun);
|
|
2430
|
-
p4__namespace.outro(
|
|
2739
|
+
p4__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2431
2740
|
}
|
|
2432
2741
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2433
2742
|
const projectRoot = findProjectRoot();
|
|
@@ -2498,7 +2807,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2498
2807
|
});
|
|
2499
2808
|
}
|
|
2500
2809
|
const s = p4__namespace.spinner();
|
|
2501
|
-
s.start(`Generating domain model '${
|
|
2810
|
+
s.start(`Generating domain model '${pc14__default.default.cyan(entityName)}'...`);
|
|
2502
2811
|
const options = {
|
|
2503
2812
|
name: entityName,
|
|
2504
2813
|
feature,
|
|
@@ -2511,7 +2820,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2511
2820
|
const file = generateDomain(options);
|
|
2512
2821
|
s.stop("Domain model generated successfully!");
|
|
2513
2822
|
printGeneratedFiles([file], options.dryRun);
|
|
2514
|
-
p4__namespace.outro(
|
|
2823
|
+
p4__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2515
2824
|
}
|
|
2516
2825
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2517
2826
|
const projectRoot = findProjectRoot();
|
|
@@ -2680,7 +2989,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2680
2989
|
registerInProvider = !flags.noProvider;
|
|
2681
2990
|
}
|
|
2682
2991
|
const s = p4__namespace.spinner();
|
|
2683
|
-
s.start(`Generating infrastructure class '${
|
|
2992
|
+
s.start(`Generating infrastructure class '${pc14__default.default.cyan(className)}'...`);
|
|
2684
2993
|
const files = [];
|
|
2685
2994
|
const options = {
|
|
2686
2995
|
name: className,
|
|
@@ -2695,7 +3004,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2695
3004
|
const infraFile = generateInfrastructure(options);
|
|
2696
3005
|
files.push(infraFile);
|
|
2697
3006
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2698
|
-
const featureDir =
|
|
3007
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2699
3008
|
const providerFile = updateOrGenerateDiProvider(
|
|
2700
3009
|
featureDir,
|
|
2701
3010
|
feature,
|
|
@@ -2715,7 +3024,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2715
3024
|
}
|
|
2716
3025
|
s.stop("Infrastructure class generated successfully!");
|
|
2717
3026
|
printGeneratedFiles(files, options.dryRun);
|
|
2718
|
-
p4__namespace.outro(
|
|
3027
|
+
p4__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2719
3028
|
}
|
|
2720
3029
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2721
3030
|
const projectRoot = findProjectRoot();
|
|
@@ -2828,7 +3137,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2828
3137
|
}
|
|
2829
3138
|
}
|
|
2830
3139
|
const s = p4__namespace.spinner();
|
|
2831
|
-
s.start(`Generating interface '${
|
|
3140
|
+
s.start(`Generating interface '${pc14__default.default.cyan(ifaceName)}'...`);
|
|
2832
3141
|
const files = [];
|
|
2833
3142
|
const options = {
|
|
2834
3143
|
name: ifaceName,
|
|
@@ -2857,7 +3166,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2857
3166
|
files.push(stubFile);
|
|
2858
3167
|
}
|
|
2859
3168
|
if (registerInProvider && generateStub) {
|
|
2860
|
-
const featureDir =
|
|
3169
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2861
3170
|
const providerFile = updateOrGenerateDiProvider(
|
|
2862
3171
|
featureDir,
|
|
2863
3172
|
feature,
|
|
@@ -2877,7 +3186,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2877
3186
|
}
|
|
2878
3187
|
s.stop("Interface generated successfully!");
|
|
2879
3188
|
printGeneratedFiles(files, options.dryRun);
|
|
2880
|
-
p4__namespace.outro(
|
|
3189
|
+
p4__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2881
3190
|
}
|
|
2882
3191
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2883
3192
|
const projectRoot = findProjectRoot();
|
|
@@ -2994,7 +3303,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2994
3303
|
}
|
|
2995
3304
|
}
|
|
2996
3305
|
const s = p4__namespace.spinner();
|
|
2997
|
-
s.start(`Generating HTTP handler '${
|
|
3306
|
+
s.start(`Generating HTTP handler '${pc14__default.default.cyan(className)}'...`);
|
|
2998
3307
|
const options = {
|
|
2999
3308
|
name: className,
|
|
3000
3309
|
feature,
|
|
@@ -3012,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3012
3321
|
const files = generateHttpHandler(options);
|
|
3013
3322
|
s.stop("HTTP handler generated successfully!");
|
|
3014
3323
|
printGeneratedFiles(files, options.dryRun);
|
|
3015
|
-
p4__namespace.outro(
|
|
3324
|
+
p4__namespace.outro(pc14__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3016
3325
|
}
|
|
3017
3326
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3018
3327
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3022,7 +3331,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3022
3331
|
const formattedMethods = methods.map((m) => {
|
|
3023
3332
|
const isAsync = m.async !== false;
|
|
3024
3333
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3025
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3334
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p16) => `${p16.name}: ${p16.type}`).join(", ") : "";
|
|
3026
3335
|
return {
|
|
3027
3336
|
name: m.name,
|
|
3028
3337
|
isAsync,
|
|
@@ -3045,21 +3354,21 @@ function generateInjectable(options) {
|
|
|
3045
3354
|
const className = toPascalCase(options.name);
|
|
3046
3355
|
let targetDir = "";
|
|
3047
3356
|
if (options.customPath) {
|
|
3048
|
-
targetDir =
|
|
3357
|
+
targetDir = path2__default.default.isAbsolute(options.customPath) ? options.customPath : path2__default.default.join(projectRoot, options.customPath);
|
|
3049
3358
|
} else if (options.location === "feature-service" && options.feature) {
|
|
3050
|
-
targetDir =
|
|
3359
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
3051
3360
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
3052
|
-
targetDir =
|
|
3361
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
3053
3362
|
} else if (options.location === "module") {
|
|
3054
|
-
targetDir =
|
|
3363
|
+
targetDir = path2__default.default.join(projectRoot, "modules", className);
|
|
3055
3364
|
} else {
|
|
3056
|
-
targetDir = options.feature ?
|
|
3365
|
+
targetDir = options.feature ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2__default.default.join(projectRoot, "shared", className.toLowerCase());
|
|
3057
3366
|
}
|
|
3058
|
-
const filePath =
|
|
3367
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
3059
3368
|
let ifaceName;
|
|
3060
3369
|
if (options.generateInterface) {
|
|
3061
3370
|
ifaceName = `I${className}`;
|
|
3062
|
-
const ifacePath =
|
|
3371
|
+
const ifacePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
3063
3372
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
3064
3373
|
files.push(
|
|
3065
3374
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -3168,7 +3477,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3168
3477
|
);
|
|
3169
3478
|
}
|
|
3170
3479
|
const s = p4__namespace.spinner();
|
|
3171
|
-
s.start(`Generating injectable '${
|
|
3480
|
+
s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
|
|
3172
3481
|
const options = {
|
|
3173
3482
|
name: className,
|
|
3174
3483
|
feature,
|
|
@@ -3182,7 +3491,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3182
3491
|
const files = generateInjectable(options);
|
|
3183
3492
|
s.stop("Injectable class generated successfully!");
|
|
3184
3493
|
printGeneratedFiles(files, options.dryRun);
|
|
3185
|
-
p4__namespace.outro(
|
|
3494
|
+
p4__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
|
|
3186
3495
|
}
|
|
3187
3496
|
async function runInteractiveMenu(flags = {}) {
|
|
3188
3497
|
printBanner();
|
|
@@ -3195,6 +3504,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3195
3504
|
label: "Feature",
|
|
3196
3505
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
3197
3506
|
},
|
|
3507
|
+
{
|
|
3508
|
+
value: "shared",
|
|
3509
|
+
label: "Shared Module",
|
|
3510
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3511
|
+
},
|
|
3198
3512
|
{
|
|
3199
3513
|
value: "usecase",
|
|
3200
3514
|
label: "Use Case",
|
|
@@ -3232,6 +3546,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3232
3546
|
case "feature":
|
|
3233
3547
|
await runFeatureCommand(void 0, flags);
|
|
3234
3548
|
break;
|
|
3549
|
+
case "shared":
|
|
3550
|
+
await runSharedCommand(void 0, flags);
|
|
3551
|
+
break;
|
|
3235
3552
|
case "usecase":
|
|
3236
3553
|
await runUseCaseCommand(void 0, flags);
|
|
3237
3554
|
break;
|
|
@@ -3264,6 +3581,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3264
3581
|
case "feature":
|
|
3265
3582
|
await runFeatureCommand(name, flags);
|
|
3266
3583
|
break;
|
|
3584
|
+
case "shared":
|
|
3585
|
+
await runSharedCommand(name, flags);
|
|
3586
|
+
break;
|
|
3267
3587
|
case "usecase":
|
|
3268
3588
|
await runUseCaseCommand(name, flags);
|
|
3269
3589
|
break;
|
|
@@ -3285,20 +3605,171 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3285
3605
|
break;
|
|
3286
3606
|
default:
|
|
3287
3607
|
p4__namespace.log.error(
|
|
3288
|
-
|
|
3289
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3608
|
+
pc14__default.default.red(
|
|
3609
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3290
3610
|
)
|
|
3291
3611
|
);
|
|
3292
3612
|
process.exit(1);
|
|
3293
3613
|
}
|
|
3294
3614
|
}
|
|
3615
|
+
var importPrompts = {
|
|
3616
|
+
select: p4__namespace.select,
|
|
3617
|
+
text: p4__namespace.text
|
|
3618
|
+
};
|
|
3619
|
+
function rewriteImports(content, aliasMap) {
|
|
3620
|
+
if (aliasMap.size === 0) return content;
|
|
3621
|
+
let rewritten = content;
|
|
3622
|
+
for (const [oldImport, newImport] of aliasMap.entries()) {
|
|
3623
|
+
const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
|
|
3624
|
+
const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
|
|
3625
|
+
const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3626
|
+
const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
|
|
3627
|
+
rewritten = rewritten.replace(regex, rawNew);
|
|
3628
|
+
}
|
|
3629
|
+
return rewritten;
|
|
3630
|
+
}
|
|
3631
|
+
function getModuleBaseDir(projectRoot, modType) {
|
|
3632
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3633
|
+
return path2__default.default.join(projectRoot, "src", folder);
|
|
3634
|
+
}
|
|
3635
|
+
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3636
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3637
|
+
const inSrc = path2__default.default.join(projectRoot, "src", folder, modName);
|
|
3638
|
+
if (fs4__default.default.existsSync(inSrc)) return inSrc;
|
|
3639
|
+
const inRoot = path2__default.default.join(projectRoot, folder, modName);
|
|
3640
|
+
if (fs4__default.default.existsSync(inRoot)) return inRoot;
|
|
3641
|
+
return null;
|
|
3642
|
+
}
|
|
3643
|
+
function checkModuleExists(projectRoot, modType, modName) {
|
|
3644
|
+
return getExistingModuleDir(projectRoot, modType, modName) !== null;
|
|
3645
|
+
}
|
|
3646
|
+
async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
|
|
3647
|
+
const folder = depType === "shared" ? "shared" : "features";
|
|
3648
|
+
const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
|
|
3649
|
+
if (isAlreadyImportedThisRun) return;
|
|
3650
|
+
const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
|
|
3651
|
+
if (programmaticAlias) {
|
|
3652
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
|
|
3653
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3654
|
+
const displayDest = path2__default.default.relative(
|
|
3655
|
+
projectRoot,
|
|
3656
|
+
path2__default.default.join(baseDir, programmaticAlias)
|
|
3657
|
+
);
|
|
3658
|
+
p4__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3659
|
+
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3663
|
+
if (!existingDir) {
|
|
3664
|
+
p4__namespace.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc14__default.default.cyan(depName)}`);
|
|
3665
|
+
await importModuleRecursive(depType, depName, depName);
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
|
|
3669
|
+
(item) => item.name === depName
|
|
3670
|
+
);
|
|
3671
|
+
let remoteIntegrity = entry?.integrity;
|
|
3672
|
+
if (!remoteIntegrity) {
|
|
3673
|
+
try {
|
|
3674
|
+
const depManifest = await fetchModuleManifest(registryBase, depType, depName);
|
|
3675
|
+
remoteIntegrity = depManifest.integrity;
|
|
3676
|
+
} catch {
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
const localHash = computeDirectoryHash(existingDir);
|
|
3680
|
+
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3681
|
+
p4__namespace.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3682
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
if (flags.overwrite) {
|
|
3686
|
+
p4__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3687
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3688
|
+
const targetDir = path2__default.default.join(baseDir, depName);
|
|
3689
|
+
if (!flags.dryRun) {
|
|
3690
|
+
if (existingDir !== targetDir) {
|
|
3691
|
+
fs4__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3692
|
+
}
|
|
3693
|
+
fs4__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3694
|
+
}
|
|
3695
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
if (flags.yes) {
|
|
3699
|
+
p4__namespace.log.info(`Using existing '${depType}/${depName}'`);
|
|
3700
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
const choice = handleCancel(
|
|
3704
|
+
await importPrompts.select({
|
|
3705
|
+
message: `Local '${depType}/${depName}' differs from the registry version.`,
|
|
3706
|
+
options: [
|
|
3707
|
+
{
|
|
3708
|
+
value: "keep_existing",
|
|
3709
|
+
label: `Keep existing local version (Recommended)`
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
value: "rename",
|
|
3713
|
+
label: "Import fresh copy under a new name (alias)"
|
|
3714
|
+
},
|
|
3715
|
+
{
|
|
3716
|
+
value: "overwrite",
|
|
3717
|
+
label: "Overwrite local version with registry version"
|
|
3718
|
+
}
|
|
3719
|
+
]
|
|
3720
|
+
})
|
|
3721
|
+
);
|
|
3722
|
+
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3723
|
+
p4__namespace.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3724
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
if (choice === "rename") {
|
|
3728
|
+
const newName = handleCancel(
|
|
3729
|
+
await importPrompts.text({
|
|
3730
|
+
message: "What should we name the new copy?",
|
|
3731
|
+
placeholder: `${depName}-copy`,
|
|
3732
|
+
validate(val) {
|
|
3733
|
+
const v = val?.trim();
|
|
3734
|
+
if (!v) return "Name cannot be empty";
|
|
3735
|
+
if (v === depName) return `Name must be different from '${depName}'`;
|
|
3736
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
|
|
3737
|
+
return "Name can only contain alphanumeric characters, dashes, and underscores";
|
|
3738
|
+
}
|
|
3739
|
+
if (checkModuleExists(projectRoot, depType, v)) {
|
|
3740
|
+
return `'${v}' already exists in ${folder}, please choose another name`;
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
})
|
|
3744
|
+
).trim();
|
|
3745
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3746
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3747
|
+
const displayDest = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, newName));
|
|
3748
|
+
p4__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3749
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
if (choice === "overwrite") {
|
|
3753
|
+
p4__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3754
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3755
|
+
const targetDir = path2__default.default.join(baseDir, depName);
|
|
3756
|
+
if (!flags.dryRun) {
|
|
3757
|
+
if (existingDir !== targetDir) {
|
|
3758
|
+
fs4__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3759
|
+
}
|
|
3760
|
+
fs4__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3761
|
+
}
|
|
3762
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3295
3766
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3296
3767
|
const projectRoot = findProjectRoot();
|
|
3297
3768
|
let type = typeArg?.trim().toLowerCase();
|
|
3298
3769
|
if (type === "features") type = "feature";
|
|
3299
3770
|
if (!type) {
|
|
3300
3771
|
type = handleCancel(
|
|
3301
|
-
await
|
|
3772
|
+
await importPrompts.select({
|
|
3302
3773
|
message: "What kind of module would you like to import?",
|
|
3303
3774
|
options: [
|
|
3304
3775
|
{
|
|
@@ -3316,19 +3787,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3316
3787
|
);
|
|
3317
3788
|
}
|
|
3318
3789
|
if (type !== "shared" && type !== "feature") {
|
|
3319
|
-
p4__namespace.log.error(
|
|
3790
|
+
p4__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3320
3791
|
process.exit(1);
|
|
3321
3792
|
}
|
|
3322
3793
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3323
3794
|
const s = p4__namespace.spinner();
|
|
3324
|
-
s.start(`Connecting to registry at ${
|
|
3795
|
+
s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
|
|
3325
3796
|
let index;
|
|
3326
3797
|
try {
|
|
3327
3798
|
index = await fetchRegistryIndex(registryBase);
|
|
3328
3799
|
s.stop("Registry index loaded.");
|
|
3329
3800
|
} catch (err) {
|
|
3330
|
-
s.stop(
|
|
3331
|
-
p4__namespace.log.error(
|
|
3801
|
+
s.stop(pc14__default.default.red("Failed to load registry index."));
|
|
3802
|
+
p4__namespace.log.error(pc14__default.default.red(err.message || String(err)));
|
|
3332
3803
|
process.exit(1);
|
|
3333
3804
|
}
|
|
3334
3805
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3340,7 +3811,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3340
3811
|
return;
|
|
3341
3812
|
}
|
|
3342
3813
|
name = handleCancel(
|
|
3343
|
-
await
|
|
3814
|
+
await importPrompts.select({
|
|
3344
3815
|
message: `Select a ${type} to import:`,
|
|
3345
3816
|
options: availableList.map((item) => ({
|
|
3346
3817
|
value: item.name,
|
|
@@ -3352,71 +3823,94 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3352
3823
|
}
|
|
3353
3824
|
if (!candidateNames.includes(name)) {
|
|
3354
3825
|
const closest = findClosestMatch(name, candidateNames);
|
|
3355
|
-
p4__namespace.log.error(
|
|
3826
|
+
p4__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3356
3827
|
if (closest) {
|
|
3357
|
-
p4__namespace.log.info(
|
|
3828
|
+
p4__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
|
|
3358
3829
|
}
|
|
3359
3830
|
process.exit(1);
|
|
3360
3831
|
}
|
|
3361
3832
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3362
3833
|
const totalWrittenFiles = [];
|
|
3363
3834
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3364
|
-
async function importModuleRecursive(modType,
|
|
3365
|
-
const modKey = `${modType}:${
|
|
3835
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3836
|
+
const modKey = `${modType}:${targetName}`;
|
|
3366
3837
|
if (importedModules.has(modKey)) return;
|
|
3367
|
-
p4__namespace.log.step(
|
|
3838
|
+
p4__namespace.log.step(
|
|
3839
|
+
`Importing ${modType}: ${pc14__default.default.cyan(targetName)}${targetName !== registryName ? pc14__default.default.dim(` (from ${registryName})`) : ""}...`
|
|
3840
|
+
);
|
|
3368
3841
|
let manifest;
|
|
3369
3842
|
try {
|
|
3370
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3843
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3371
3844
|
} catch (err) {
|
|
3372
|
-
p4__namespace.log.error(
|
|
3845
|
+
p4__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3373
3846
|
process.exit(1);
|
|
3374
3847
|
}
|
|
3848
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3849
|
+
if (targetName !== registryName) {
|
|
3850
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3851
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3852
|
+
}
|
|
3375
3853
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3376
3854
|
for (const dep of sharedDeps) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3855
|
+
await resolveDependency(
|
|
3856
|
+
"shared",
|
|
3857
|
+
dep,
|
|
3858
|
+
aliasMap,
|
|
3859
|
+
projectRoot,
|
|
3860
|
+
flags,
|
|
3861
|
+
importModuleRecursive,
|
|
3862
|
+
importedModules,
|
|
3863
|
+
registryBase,
|
|
3864
|
+
index
|
|
3865
|
+
);
|
|
3382
3866
|
}
|
|
3383
3867
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3384
3868
|
for (const dep of featureDeps) {
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3869
|
+
await resolveDependency(
|
|
3870
|
+
"feature",
|
|
3871
|
+
dep,
|
|
3872
|
+
aliasMap,
|
|
3873
|
+
projectRoot,
|
|
3874
|
+
flags,
|
|
3875
|
+
importModuleRecursive,
|
|
3876
|
+
importedModules,
|
|
3877
|
+
registryBase,
|
|
3878
|
+
index
|
|
3879
|
+
);
|
|
3390
3880
|
}
|
|
3391
3881
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3392
3882
|
requiredNpmPackages.add(pkg);
|
|
3393
3883
|
}
|
|
3394
3884
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3395
|
-
const targetDir =
|
|
3885
|
+
const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3886
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3396
3887
|
for (const file of manifest.files || []) {
|
|
3397
3888
|
if (file === "registry.json") continue;
|
|
3398
|
-
const remoteFilePath = `${folder}/${
|
|
3399
|
-
const localFilePath =
|
|
3400
|
-
if (
|
|
3401
|
-
p4__namespace.log.warn(
|
|
3889
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3890
|
+
const localFilePath = path2__default.default.join(targetDir, file);
|
|
3891
|
+
if (fs4__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
3892
|
+
p4__namespace.log.warn(
|
|
3893
|
+
` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3894
|
+
);
|
|
3402
3895
|
continue;
|
|
3403
3896
|
}
|
|
3404
|
-
|
|
3897
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3898
|
+
content = rewriteImports(content, aliasMap);
|
|
3405
3899
|
if (!flags.dryRun) {
|
|
3406
|
-
|
|
3407
|
-
|
|
3900
|
+
fs4__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
|
|
3901
|
+
fs4__default.default.writeFileSync(localFilePath, content, "utf-8");
|
|
3408
3902
|
}
|
|
3409
|
-
const displayPath =
|
|
3903
|
+
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
3410
3904
|
totalWrittenFiles.push(displayPath);
|
|
3411
|
-
p4__namespace.log.message(` ${
|
|
3905
|
+
p4__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
|
|
3412
3906
|
}
|
|
3413
3907
|
importedModules.add(modKey);
|
|
3414
3908
|
}
|
|
3415
|
-
await importModuleRecursive(type, name);
|
|
3909
|
+
await importModuleRecursive(type, name, name);
|
|
3416
3910
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3417
3911
|
if (missingNpm.length > 0) {
|
|
3418
3912
|
if (flags.skipInstall || flags.dryRun) {
|
|
3419
|
-
p4__namespace.log.info(`Required npm dependencies to install: ${
|
|
3913
|
+
p4__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
|
|
3420
3914
|
} else {
|
|
3421
3915
|
try {
|
|
3422
3916
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -3426,11 +3920,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3426
3920
|
}
|
|
3427
3921
|
}
|
|
3428
3922
|
if (flags.dryRun) {
|
|
3429
|
-
p4__namespace.log.info(
|
|
3923
|
+
p4__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
3430
3924
|
} else {
|
|
3925
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3926
|
+
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, name));
|
|
3431
3927
|
p4__namespace.log.success(
|
|
3432
|
-
|
|
3433
|
-
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in
|
|
3928
|
+
pc14__default.default.green(
|
|
3929
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3434
3930
|
)
|
|
3435
3931
|
);
|
|
3436
3932
|
}
|
|
@@ -3439,24 +3935,70 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3439
3935
|
// src/cli.ts
|
|
3440
3936
|
function createCli() {
|
|
3441
3937
|
const cli = cac.cac("mason");
|
|
3442
|
-
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) => {
|
|
3938
|
+
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) => {
|
|
3443
3939
|
if (args && args.length > 0) {
|
|
3444
3940
|
p4__namespace.log.error(
|
|
3445
|
-
|
|
3941
|
+
pc14__default.default.red(
|
|
3446
3942
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3447
3943
|
)
|
|
3448
3944
|
);
|
|
3449
3945
|
process.exit(1);
|
|
3450
3946
|
}
|
|
3451
3947
|
try {
|
|
3948
|
+
const projectRoot = findProjectRoot();
|
|
3949
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
3950
|
+
if (mode === "registry") {
|
|
3951
|
+
const choice2 = handleCancel(
|
|
3952
|
+
await p4__namespace.select({
|
|
3953
|
+
message: "\u{1F3DB}\uFE0F Mason (Registry Mode): What would you like to scaffold?",
|
|
3954
|
+
options: [
|
|
3955
|
+
{
|
|
3956
|
+
value: "create_feature",
|
|
3957
|
+
label: "Initialize New Feature",
|
|
3958
|
+
hint: "Scaffold a new feature slice under features/"
|
|
3959
|
+
},
|
|
3960
|
+
{
|
|
3961
|
+
value: "create_shared",
|
|
3962
|
+
label: "Initialize New Shared Module",
|
|
3963
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
3964
|
+
},
|
|
3965
|
+
{
|
|
3966
|
+
value: "create_usecase",
|
|
3967
|
+
label: "Create New Use Case",
|
|
3968
|
+
hint: "Add a new use case to an existing feature"
|
|
3969
|
+
},
|
|
3970
|
+
{
|
|
3971
|
+
value: "import",
|
|
3972
|
+
label: "Import from Remote Registry",
|
|
3973
|
+
hint: "Download a module from upstream"
|
|
3974
|
+
}
|
|
3975
|
+
]
|
|
3976
|
+
})
|
|
3977
|
+
);
|
|
3978
|
+
switch (choice2) {
|
|
3979
|
+
case "create_feature":
|
|
3980
|
+
await handleCreate("feature", void 0, flags);
|
|
3981
|
+
break;
|
|
3982
|
+
case "create_shared":
|
|
3983
|
+
await handleCreate("shared", void 0, flags);
|
|
3984
|
+
break;
|
|
3985
|
+
case "create_usecase":
|
|
3986
|
+
await handleCreate("usecase", void 0, flags);
|
|
3987
|
+
break;
|
|
3988
|
+
case "import":
|
|
3989
|
+
await handleImport(void 0, void 0, flags);
|
|
3990
|
+
break;
|
|
3991
|
+
}
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3452
3994
|
const choice = handleCancel(
|
|
3453
3995
|
await p4__namespace.select({
|
|
3454
3996
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3455
3997
|
options: [
|
|
3456
3998
|
{
|
|
3457
3999
|
value: "create",
|
|
3458
|
-
label: "Create
|
|
3459
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
4000
|
+
label: "Create component",
|
|
4001
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3460
4002
|
},
|
|
3461
4003
|
{
|
|
3462
4004
|
value: "import",
|
|
@@ -3477,8 +4019,8 @@ function createCli() {
|
|
|
3477
4019
|
});
|
|
3478
4020
|
cli.command(
|
|
3479
4021
|
"create [type] [name]",
|
|
3480
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3481
|
-
).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 = {}) => {
|
|
4022
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
4023
|
+
).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 = {}) => {
|
|
3482
4024
|
try {
|
|
3483
4025
|
await handleCreate(type, name, flags);
|
|
3484
4026
|
} catch (err) {
|
|
@@ -3497,6 +4039,7 @@ function createCli() {
|
|
|
3497
4039
|
});
|
|
3498
4040
|
const createSubtypes = [
|
|
3499
4041
|
"feature",
|
|
4042
|
+
"shared",
|
|
3500
4043
|
"usecase",
|
|
3501
4044
|
"domain",
|
|
3502
4045
|
"infrastructure",
|
|
@@ -3517,7 +4060,7 @@ function createCli() {
|
|
|
3517
4060
|
});
|
|
3518
4061
|
}
|
|
3519
4062
|
cli.help();
|
|
3520
|
-
cli.version("1.0.
|
|
4063
|
+
cli.version("1.0.14");
|
|
3521
4064
|
return cli;
|
|
3522
4065
|
}
|
|
3523
4066
|
function handleCliError(err) {
|
|
@@ -3525,7 +4068,7 @@ function handleCliError(err) {
|
|
|
3525
4068
|
process.exit(0);
|
|
3526
4069
|
}
|
|
3527
4070
|
const message = err instanceof Error ? err.message : String(err);
|
|
3528
|
-
p4__namespace.log.error(
|
|
4071
|
+
p4__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
|
|
3529
4072
|
process.exit(1);
|
|
3530
4073
|
}
|
|
3531
4074
|
async function runCli(argv = process.argv) {
|
|
@@ -3535,6 +4078,7 @@ async function runCli(argv = process.argv) {
|
|
|
3535
4078
|
|
|
3536
4079
|
exports.DEFAULT_REGISTRY_BASE = DEFAULT_REGISTRY_BASE;
|
|
3537
4080
|
exports.PromptCancelledError = PromptCancelledError;
|
|
4081
|
+
exports.computeDirectoryHash = computeDirectoryHash;
|
|
3538
4082
|
exports.concreteInfraTemplate = concreteInfraTemplate;
|
|
3539
4083
|
exports.createCli = createCli;
|
|
3540
4084
|
exports.detectPackageManager = detectPackageManager;
|
|
@@ -3562,10 +4106,15 @@ exports.generateInjectable = generateInjectable;
|
|
|
3562
4106
|
exports.generateInjectableCode = generateInjectableCode;
|
|
3563
4107
|
exports.generateInterface = generateInterface;
|
|
3564
4108
|
exports.generateInterfaceCode = generateInterfaceCode;
|
|
4109
|
+
exports.generateShared = generateShared;
|
|
3565
4110
|
exports.generateStubCode = generateStubCode;
|
|
3566
4111
|
exports.generateUseCase = generateUseCase;
|
|
3567
4112
|
exports.generateUseCaseCode = generateUseCaseCode;
|
|
3568
4113
|
exports.generateUseCaseTestCode = generateUseCaseTestCode;
|
|
4114
|
+
exports.getAllFiles = getAllFiles;
|
|
4115
|
+
exports.getConfiguredFeaturesDir = getConfiguredFeaturesDir;
|
|
4116
|
+
exports.getConfiguredSharedDir = getConfiguredSharedDir;
|
|
4117
|
+
exports.getEffectiveMode = getEffectiveMode;
|
|
3569
4118
|
exports.getMissingNpmPackages = getMissingNpmPackages;
|
|
3570
4119
|
exports.getRegistryBase = getRegistryBase;
|
|
3571
4120
|
exports.getTemplatesDir = getTemplatesDir;
|
|
@@ -3583,8 +4132,10 @@ exports.levenshteinDistance = levenshteinDistance;
|
|
|
3583
4132
|
exports.listDomainEntities = listDomainEntities;
|
|
3584
4133
|
exports.listDomainInterfaces = listDomainInterfaces;
|
|
3585
4134
|
exports.listExistingFeatures = listExistingFeatures;
|
|
4135
|
+
exports.listExistingShared = listExistingShared;
|
|
3586
4136
|
exports.listUseCases = listUseCases;
|
|
3587
4137
|
exports.loadInterfaceHierarchy = loadInterfaceHierarchy;
|
|
4138
|
+
exports.loadMasonConfig = loadMasonConfig;
|
|
3588
4139
|
exports.loadTemplate = loadTemplate;
|
|
3589
4140
|
exports.normalizeRegistryUrl = normalizeRegistryUrl;
|
|
3590
4141
|
exports.parseInterfaceMethodsAndImports = parseInterfaceMethodsAndImports;
|
|
@@ -3595,7 +4146,9 @@ exports.removeInterfacePrefix = removeInterfacePrefix;
|
|
|
3595
4146
|
exports.renderTemplate = renderTemplate;
|
|
3596
4147
|
exports.renderTemplateFile = renderTemplateFile;
|
|
3597
4148
|
exports.resolveFeaturesDir = resolveFeaturesDir;
|
|
4149
|
+
exports.resolveSharedDir = resolveSharedDir;
|
|
3598
4150
|
exports.runCli = runCli;
|
|
4151
|
+
exports.runSharedCommand = runSharedCommand;
|
|
3599
4152
|
exports.stubInfraTemplate = stubInfraTemplate;
|
|
3600
4153
|
exports.synthesizeCrudMethods = synthesizeCrudMethods;
|
|
3601
4154
|
exports.toCamelCase = toCamelCase;
|