@solid-stack/mason 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1508 -315
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1504 -311
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/feature.d.ts.map +1 -1
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/registry.d.ts +64 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/core/sharedGenerator.d.ts +13 -0
- package/dist/core/sharedGenerator.d.ts.map +1 -0
- package/dist/index.cjs +1531 -315
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1515 -312
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +20 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
3
|
+
var fs12 = require('fs');
|
|
4
|
+
var path2 = require('path');
|
|
5
|
+
var p15 = require('@clack/prompts');
|
|
6
|
+
var pc15 = 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
|
|
35
|
-
var
|
|
36
|
-
var
|
|
34
|
+
var fs12__default = /*#__PURE__*/_interopDefault(fs12);
|
|
35
|
+
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
36
|
+
var p15__namespace = /*#__PURE__*/_interopNamespace(p15);
|
|
37
|
+
var pc15__default = /*#__PURE__*/_interopDefault(pc15);
|
|
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 = fs12__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 (!fs12__default.default.existsSync(dir)) {
|
|
130
|
+
fs12__default.default.mkdirSync(dir, { recursive: true });
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
+
fs12__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
|
-
|
|
144
|
+
p15__namespace.log.warn(pc15__default.default.yellow(pc15__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
|
-
|
|
148
|
+
p15__namespace.log.success(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(created)")}`);
|
|
148
149
|
} else if (f.action === "overwritten") {
|
|
149
|
-
|
|
150
|
+
p15__namespace.log.warn(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(overwritten)")}`);
|
|
150
151
|
} else if (f.action === "skipped") {
|
|
151
|
-
|
|
152
|
+
p15__namespace.log.info(`${pc15__default.default.dim(f.relativePath)} ${pc15__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
152
153
|
} else {
|
|
153
|
-
|
|
154
|
+
p15__namespace.log.info(`${pc15__default.default.bold(f.relativePath)} ${pc15__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 (fs12__default.default.existsSync(configPath)) {
|
|
163
|
+
try {
|
|
164
|
+
return JSON.parse(fs12__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 (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "index.json")) && (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "features")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "shared"))) && !fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src"))) {
|
|
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 (fs12__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 (fs12__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 (fs12__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 (!fs12__default.default.existsSync(sharedDir)) return [];
|
|
240
|
+
try {
|
|
241
|
+
const entries = fs12__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 (!fs12__default.default.existsSync(featuresDir)) return [];
|
|
176
250
|
try {
|
|
177
|
-
const entries =
|
|
251
|
+
const entries = fs12__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 (!fs12__default.default.existsSync(domainDir)) continue;
|
|
189
263
|
try {
|
|
190
|
-
const files =
|
|
264
|
+
const files = fs12__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 = fs12__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 (!fs12__default.default.existsSync(domainDir)) continue;
|
|
227
301
|
try {
|
|
228
|
-
const files =
|
|
302
|
+
const files = fs12__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 (!fs12__default.default.existsSync(useCasesDir)) continue;
|
|
252
326
|
try {
|
|
253
|
-
const files =
|
|
327
|
+
const files = fs12__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
|
}
|
|
@@ -273,23 +347,23 @@ var PromptCancelledError = class extends Error {
|
|
|
273
347
|
}
|
|
274
348
|
};
|
|
275
349
|
function handleCancel(result) {
|
|
276
|
-
if (
|
|
277
|
-
|
|
350
|
+
if (p15__namespace.isCancel(result)) {
|
|
351
|
+
p15__namespace.cancel("Operation cancelled.");
|
|
278
352
|
throw new PromptCancelledError();
|
|
279
353
|
}
|
|
280
354
|
return result;
|
|
281
355
|
}
|
|
282
356
|
function printBanner() {
|
|
283
357
|
console.log("");
|
|
284
|
-
|
|
358
|
+
p15__namespace.intro(pc15__default.default.bgCyan(pc15__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
|
-
|
|
363
|
+
const summary = properties.map((p17) => `${p17.name}: ${p17.type}${p17.optional ? "?" : ""}`).join(", ");
|
|
364
|
+
p15__namespace.log.info(pc15__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
291
365
|
const addMore = handleCancel(
|
|
292
|
-
await
|
|
366
|
+
await p15__namespace.confirm({
|
|
293
367
|
message: "Would you like to add custom fields/properties?",
|
|
294
368
|
initialValue: false
|
|
295
369
|
})
|
|
@@ -298,10 +372,10 @@ async function promptProperties(options = {}) {
|
|
|
298
372
|
return properties;
|
|
299
373
|
}
|
|
300
374
|
}
|
|
301
|
-
|
|
375
|
+
p15__namespace.log.info(pc15__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
302
376
|
while (true) {
|
|
303
377
|
const name = handleCancel(
|
|
304
|
-
await
|
|
378
|
+
await p15__namespace.text({
|
|
305
379
|
message: "Property name (or press Enter to finish):",
|
|
306
380
|
placeholder: "e.g. title, email, status"
|
|
307
381
|
})
|
|
@@ -310,7 +384,7 @@ async function promptProperties(options = {}) {
|
|
|
310
384
|
break;
|
|
311
385
|
}
|
|
312
386
|
const type = handleCancel(
|
|
313
|
-
await
|
|
387
|
+
await p15__namespace.select({
|
|
314
388
|
message: `Type for property "${name.trim()}":`,
|
|
315
389
|
options: [
|
|
316
390
|
{ value: "string", label: "string" },
|
|
@@ -327,7 +401,7 @@ async function promptProperties(options = {}) {
|
|
|
327
401
|
let finalType = type;
|
|
328
402
|
if (type === "custom") {
|
|
329
403
|
finalType = handleCancel(
|
|
330
|
-
await
|
|
404
|
+
await p15__namespace.text({
|
|
331
405
|
message: `Enter custom type for "${name.trim()}":`,
|
|
332
406
|
placeholder: "e.g. UserRole, string | null",
|
|
333
407
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -335,7 +409,7 @@ async function promptProperties(options = {}) {
|
|
|
335
409
|
);
|
|
336
410
|
}
|
|
337
411
|
const optional = handleCancel(
|
|
338
|
-
await
|
|
412
|
+
await p15__namespace.confirm({
|
|
339
413
|
message: `Is "${name.trim()}" optional?`,
|
|
340
414
|
initialValue: false
|
|
341
415
|
})
|
|
@@ -345,47 +419,112 @@ async function promptProperties(options = {}) {
|
|
|
345
419
|
type: finalType.trim(),
|
|
346
420
|
optional
|
|
347
421
|
});
|
|
348
|
-
|
|
349
|
-
|
|
422
|
+
p15__namespace.log.success(
|
|
423
|
+
pc15__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
350
424
|
);
|
|
351
425
|
}
|
|
352
426
|
return properties;
|
|
353
427
|
}
|
|
354
428
|
var DEFAULT_REGISTRY_BASE = "https://raw.githubusercontent.com/solid-stack-digital/mason-registry/main";
|
|
429
|
+
function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
430
|
+
let trimmed = urlOrPath.trim();
|
|
431
|
+
const gitSshRegex = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
|
|
432
|
+
const sshMatch = trimmed.match(gitSshRegex);
|
|
433
|
+
if (sshMatch && sshMatch[1] && sshMatch[2]) {
|
|
434
|
+
return `https://raw.githubusercontent.com/${sshMatch[1]}/${sshMatch[2]}/main`;
|
|
435
|
+
}
|
|
436
|
+
const githubWebRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?\/?$/;
|
|
437
|
+
const webMatch = trimmed.match(githubWebRegex);
|
|
438
|
+
if (webMatch && webMatch[1] && webMatch[2]) {
|
|
439
|
+
const owner = webMatch[1];
|
|
440
|
+
const repo = webMatch[2];
|
|
441
|
+
const branch = webMatch[3] || "main";
|
|
442
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
443
|
+
}
|
|
444
|
+
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
445
|
+
return path2__default.default.resolve(baseDir, trimmed);
|
|
446
|
+
}
|
|
447
|
+
return trimmed;
|
|
448
|
+
}
|
|
449
|
+
function findLocalRegistry(startDir = process.cwd()) {
|
|
450
|
+
let current = path2__default.default.resolve(startDir);
|
|
451
|
+
while (true) {
|
|
452
|
+
const candidate = path2__default.default.join(current, "mason-registry");
|
|
453
|
+
if (fs12__default.default.existsSync(candidate) && fs12__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
|
|
454
|
+
return candidate;
|
|
455
|
+
}
|
|
456
|
+
const parent = path2__default.default.dirname(current);
|
|
457
|
+
if (parent === current) break;
|
|
458
|
+
current = parent;
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
355
462
|
function getRegistryBase(projectRoot, override) {
|
|
463
|
+
const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
|
|
356
464
|
if (override && override.trim()) {
|
|
357
|
-
return override.trim();
|
|
465
|
+
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
358
466
|
}
|
|
359
467
|
if (process.env.MASON_REGISTRY_URL && process.env.MASON_REGISTRY_URL.trim()) {
|
|
360
|
-
return process.env.MASON_REGISTRY_URL.trim();
|
|
468
|
+
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
361
469
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
470
|
+
const configLocations = [
|
|
471
|
+
path2__default.default.join(rootDir, "mason.config.json"),
|
|
472
|
+
path2__default.default.join(process.cwd(), "mason.config.json")
|
|
473
|
+
];
|
|
474
|
+
for (const configPath of configLocations) {
|
|
475
|
+
if (fs12__default.default.existsSync(configPath)) {
|
|
365
476
|
try {
|
|
366
|
-
const config = JSON.parse(
|
|
477
|
+
const config = JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
|
|
367
478
|
if (config.registry && typeof config.registry === "string") {
|
|
368
|
-
return config.registry.trim();
|
|
479
|
+
return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
|
|
369
480
|
}
|
|
370
481
|
} catch {
|
|
371
482
|
}
|
|
372
483
|
}
|
|
373
484
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
485
|
+
const pkgLocations = [
|
|
486
|
+
path2__default.default.join(rootDir, "package.json"),
|
|
487
|
+
path2__default.default.join(process.cwd(), "package.json")
|
|
488
|
+
];
|
|
489
|
+
for (const pkgPath of pkgLocations) {
|
|
490
|
+
if (fs12__default.default.existsSync(pkgPath)) {
|
|
491
|
+
try {
|
|
492
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgPath, "utf-8"));
|
|
493
|
+
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
494
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
|
|
495
|
+
}
|
|
496
|
+
} catch {
|
|
497
|
+
}
|
|
378
498
|
}
|
|
379
499
|
}
|
|
500
|
+
const localRegistry = findLocalRegistry(rootDir);
|
|
501
|
+
if (localRegistry) {
|
|
502
|
+
return localRegistry;
|
|
503
|
+
}
|
|
380
504
|
return DEFAULT_REGISTRY_BASE;
|
|
381
505
|
}
|
|
382
506
|
async function fetchRegistryFile(registryBase, relPath) {
|
|
383
507
|
const cleanRelPath = relPath.replace(/^\/+/, "");
|
|
384
508
|
if (registryBase.startsWith("http://") || registryBase.startsWith("https://")) {
|
|
385
509
|
const url = `${registryBase.replace(/\/+$/, "")}/${cleanRelPath}`;
|
|
386
|
-
const
|
|
510
|
+
const headers = {};
|
|
511
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
512
|
+
if (token) {
|
|
513
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
514
|
+
}
|
|
515
|
+
const res = await fetch(url, { headers });
|
|
387
516
|
if (!res.ok) {
|
|
388
|
-
|
|
517
|
+
let extraHint = "";
|
|
518
|
+
if (res.status === 404) {
|
|
519
|
+
extraHint = `
|
|
520
|
+
Possible reasons:
|
|
521
|
+
\u2022 If using a private GitHub repository, ensure GITHUB_TOKEN is set in your environment.
|
|
522
|
+
\u2022 If your registry has not yet been pushed to GitHub, point to your local directory:
|
|
523
|
+
- CLI flag: mason import ... --registry /path/to/mason-registry
|
|
524
|
+
- Environment: export MASON_REGISTRY_URL=/path/to/mason-registry
|
|
525
|
+
- Config file: Create mason.config.json with { "registry": "/path/to/mason-registry" }`;
|
|
526
|
+
}
|
|
527
|
+
throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})${extraHint}`);
|
|
389
528
|
}
|
|
390
529
|
return await res.text();
|
|
391
530
|
}
|
|
@@ -393,11 +532,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
393
532
|
if (baseDir.startsWith("file://")) {
|
|
394
533
|
baseDir = url.fileURLToPath(baseDir);
|
|
395
534
|
}
|
|
396
|
-
const filePath =
|
|
397
|
-
if (!
|
|
535
|
+
const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
|
|
536
|
+
if (!fs12__default.default.existsSync(filePath)) {
|
|
398
537
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
399
538
|
}
|
|
400
|
-
return
|
|
539
|
+
return fs12__default.default.promises.readFile(filePath, "utf-8");
|
|
401
540
|
}
|
|
402
541
|
async function fetchRegistryIndex(registryBase) {
|
|
403
542
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -459,26 +598,26 @@ function findClosestMatch(target, candidates) {
|
|
|
459
598
|
return null;
|
|
460
599
|
}
|
|
461
600
|
function detectPackageManager(projectRoot) {
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
464
|
-
if (
|
|
601
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
602
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
603
|
+
if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lockb")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lock"))) return "bun";
|
|
465
604
|
return "npm";
|
|
466
605
|
}
|
|
467
606
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
468
607
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
469
|
-
const pkgJsonPath =
|
|
470
|
-
if (!
|
|
608
|
+
const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
|
|
609
|
+
if (!fs12__default.default.existsSync(pkgJsonPath)) {
|
|
471
610
|
return requiredPackages;
|
|
472
611
|
}
|
|
473
612
|
try {
|
|
474
|
-
const pkg = JSON.parse(
|
|
613
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
475
614
|
const installed = /* @__PURE__ */ new Set([
|
|
476
615
|
...Object.keys(pkg.dependencies || {}),
|
|
477
616
|
...Object.keys(pkg.devDependencies || {}),
|
|
478
617
|
...Object.keys(pkg.peerDependencies || {})
|
|
479
618
|
]);
|
|
480
|
-
return requiredPackages.filter((
|
|
481
|
-
const nameOnly =
|
|
619
|
+
return requiredPackages.filter((p17) => {
|
|
620
|
+
const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
|
|
482
621
|
return !installed.has(nameOnly);
|
|
483
622
|
});
|
|
484
623
|
} catch {
|
|
@@ -503,12 +642,47 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
503
642
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
504
643
|
break;
|
|
505
644
|
}
|
|
506
|
-
|
|
645
|
+
p15__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
|
|
507
646
|
child_process.execSync(installCmd, {
|
|
508
647
|
cwd: projectRoot,
|
|
509
648
|
stdio: "inherit"
|
|
510
649
|
});
|
|
511
650
|
}
|
|
651
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
652
|
+
if (!fs12__default.default.existsSync(dir)) return [];
|
|
653
|
+
const entries = fs12__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 (!fs12__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 (!fs12__default.default.existsSync(absolutePath)) continue;
|
|
679
|
+
const content = fs12__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
|
+
}
|
|
512
686
|
var hbs = Handlebars__default.default.create();
|
|
513
687
|
hbs.registerHelper("eq", (a, b) => a === b);
|
|
514
688
|
hbs.registerHelper("ne", (a, b) => a !== b);
|
|
@@ -525,33 +699,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
525
699
|
});
|
|
526
700
|
var cachedTemplatesDir = null;
|
|
527
701
|
function getTemplatesDir() {
|
|
528
|
-
if (cachedTemplatesDir &&
|
|
702
|
+
if (cachedTemplatesDir && fs12__default.default.existsSync(cachedTemplatesDir)) {
|
|
529
703
|
return cachedTemplatesDir;
|
|
530
704
|
}
|
|
531
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
532
|
-
const cand1 =
|
|
533
|
-
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 (fs12__default.default.existsSync(cand1)) {
|
|
534
708
|
cachedTemplatesDir = cand1;
|
|
535
709
|
return cand1;
|
|
536
710
|
}
|
|
537
|
-
const cand2 =
|
|
538
|
-
if (
|
|
711
|
+
const cand2 = path2__default.default.resolve(currentDir, "../templates");
|
|
712
|
+
if (fs12__default.default.existsSync(cand2)) {
|
|
539
713
|
cachedTemplatesDir = cand2;
|
|
540
714
|
return cand2;
|
|
541
715
|
}
|
|
542
|
-
const cand3 =
|
|
543
|
-
if (
|
|
716
|
+
const cand3 = path2__default.default.resolve(currentDir, "templates");
|
|
717
|
+
if (fs12__default.default.existsSync(cand3)) {
|
|
544
718
|
cachedTemplatesDir = cand3;
|
|
545
719
|
return cand3;
|
|
546
720
|
}
|
|
547
721
|
let curr = currentDir;
|
|
548
722
|
while (true) {
|
|
549
|
-
const candTemplates =
|
|
550
|
-
if (
|
|
723
|
+
const candTemplates = path2__default.default.join(curr, "templates");
|
|
724
|
+
if (fs12__default.default.existsSync(candTemplates)) {
|
|
551
725
|
cachedTemplatesDir = candTemplates;
|
|
552
726
|
return candTemplates;
|
|
553
727
|
}
|
|
554
|
-
const parent =
|
|
728
|
+
const parent = path2__default.default.dirname(curr);
|
|
555
729
|
if (parent === curr) break;
|
|
556
730
|
curr = parent;
|
|
557
731
|
}
|
|
@@ -559,8 +733,8 @@ function getTemplatesDir() {
|
|
|
559
733
|
}
|
|
560
734
|
var templateCache = /* @__PURE__ */ new Map();
|
|
561
735
|
function loadTemplate(relativePath) {
|
|
562
|
-
const fullPath =
|
|
563
|
-
return
|
|
736
|
+
const fullPath = path2__default.default.isAbsolute(relativePath) ? relativePath : path2__default.default.join(getTemplatesDir(), relativePath);
|
|
737
|
+
return fs12__default.default.readFileSync(fullPath, "utf-8");
|
|
564
738
|
}
|
|
565
739
|
function renderTemplateFile(relativePath, context) {
|
|
566
740
|
let compiled = templateCache.get(relativePath);
|
|
@@ -645,8 +819,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
645
819
|
function generateDomain(options) {
|
|
646
820
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
647
821
|
const entityName = toPascalCase(options.name);
|
|
648
|
-
const targetDir = options.targetDir ||
|
|
649
|
-
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`);
|
|
650
824
|
const code = generateDomainEntityCode(
|
|
651
825
|
entityName,
|
|
652
826
|
options.kind || "interface",
|
|
@@ -670,7 +844,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
670
844
|
const idKey = `${entityKey}Id`;
|
|
671
845
|
const listKey = `${entityKey}s`;
|
|
672
846
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
673
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
847
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
|
|
674
848
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
675
849
|
return {
|
|
676
850
|
name: m.name,
|
|
@@ -695,8 +869,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
695
869
|
function generateInterface(options) {
|
|
696
870
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
697
871
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
698
|
-
const targetDir = options.targetDir ||
|
|
699
|
-
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`);
|
|
700
874
|
const code = generateInterfaceCode(
|
|
701
875
|
ifaceName,
|
|
702
876
|
options.kind || "abstractClass",
|
|
@@ -931,7 +1105,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
931
1105
|
};
|
|
932
1106
|
}
|
|
933
1107
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
934
|
-
if (customPath &&
|
|
1108
|
+
if (customPath && fs12__default.default.existsSync(customPath)) {
|
|
935
1109
|
return { filePath: customPath, ifaceName };
|
|
936
1110
|
}
|
|
937
1111
|
const candidateNames = [
|
|
@@ -941,22 +1115,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
941
1115
|
];
|
|
942
1116
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
943
1117
|
for (const feat of features) {
|
|
944
|
-
const domainDir =
|
|
945
|
-
if (!
|
|
1118
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1119
|
+
if (!fs12__default.default.existsSync(domainDir)) continue;
|
|
946
1120
|
for (const cand of candidateNames) {
|
|
947
|
-
const p1 =
|
|
948
|
-
if (
|
|
1121
|
+
const p1 = path2__default.default.join(domainDir, `${cand}.ts`);
|
|
1122
|
+
if (fs12__default.default.existsSync(p1)) {
|
|
949
1123
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
950
1124
|
}
|
|
951
1125
|
}
|
|
952
1126
|
}
|
|
953
1127
|
for (const cand of candidateNames) {
|
|
954
|
-
const p1 =
|
|
955
|
-
if (
|
|
956
|
-
const p22 =
|
|
957
|
-
if (
|
|
958
|
-
const p32 =
|
|
959
|
-
if (
|
|
1128
|
+
const p1 = path2__default.default.join(projectRoot, "domain", `${cand}.ts`);
|
|
1129
|
+
if (fs12__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
1130
|
+
const p22 = path2__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
1131
|
+
if (fs12__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
1132
|
+
const p32 = path2__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
1133
|
+
if (fs12__default.default.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
960
1134
|
}
|
|
961
1135
|
return null;
|
|
962
1136
|
}
|
|
@@ -979,7 +1153,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
979
1153
|
while (currentFile && !visited.has(currentFile)) {
|
|
980
1154
|
visited.add(currentFile);
|
|
981
1155
|
try {
|
|
982
|
-
const content =
|
|
1156
|
+
const content = fs12__default.default.readFileSync(currentFile, "utf-8");
|
|
983
1157
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
984
1158
|
for (const m of parsed.methods) {
|
|
985
1159
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1055,7 +1229,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
1055
1229
|
const entitiesVar = `${entityVar}s`;
|
|
1056
1230
|
const idKey = `${entityVar}Id`;
|
|
1057
1231
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
1058
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
1232
|
+
const entityImportPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1059
1233
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
1060
1234
|
for (const imp of domainImports) {
|
|
1061
1235
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -1104,7 +1278,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
1104
1278
|
if (baseEntity) {
|
|
1105
1279
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
1106
1280
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
1107
|
-
const importPath = ifaceImportPath ? `${
|
|
1281
|
+
const importPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1108
1282
|
finalDomainImports.push({
|
|
1109
1283
|
name: baseEntity,
|
|
1110
1284
|
from: importPath,
|
|
@@ -1149,8 +1323,8 @@ function generateInfrastructure(options) {
|
|
|
1149
1323
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1150
1324
|
const className = toPascalCase(options.name);
|
|
1151
1325
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
1152
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
1153
|
-
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`);
|
|
1154
1328
|
let code;
|
|
1155
1329
|
if (options.implementsInterface) {
|
|
1156
1330
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -1164,7 +1338,7 @@ function generateInfrastructure(options) {
|
|
|
1164
1338
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
1165
1339
|
let ifaceImportPath;
|
|
1166
1340
|
if (ifaceFilePath) {
|
|
1167
|
-
let rel =
|
|
1341
|
+
let rel = path2__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
1168
1342
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1169
1343
|
ifaceImportPath = rel;
|
|
1170
1344
|
} else if (isFeatureScope) {
|
|
@@ -1177,8 +1351,8 @@ function generateInfrastructure(options) {
|
|
|
1177
1351
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
1178
1352
|
let fromPath;
|
|
1179
1353
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
1180
|
-
const absTarget =
|
|
1181
|
-
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, "/");
|
|
1182
1356
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1183
1357
|
fromPath = rel;
|
|
1184
1358
|
} else {
|
|
@@ -1251,22 +1425,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1251
1425
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1252
1426
|
}));
|
|
1253
1427
|
const domainTypesToImport = new Set(domainImports);
|
|
1254
|
-
for (const
|
|
1255
|
-
const rawType =
|
|
1428
|
+
for (const p17 of [...inputProps, ...outputProps]) {
|
|
1429
|
+
const rawType = p17.type.trim();
|
|
1256
1430
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1257
1431
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1258
1432
|
domainTypesToImport.add(baseType);
|
|
1259
1433
|
}
|
|
1260
1434
|
}
|
|
1261
|
-
const formattedInputProps = inputProps.map((
|
|
1262
|
-
name:
|
|
1263
|
-
optional:
|
|
1264
|
-
type:
|
|
1435
|
+
const formattedInputProps = inputProps.map((p17) => ({
|
|
1436
|
+
name: p17.name,
|
|
1437
|
+
optional: p17.optional,
|
|
1438
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1265
1439
|
}));
|
|
1266
|
-
const formattedOutputProps = outputProps.map((
|
|
1267
|
-
name:
|
|
1268
|
-
optional:
|
|
1269
|
-
type:
|
|
1440
|
+
const formattedOutputProps = outputProps.map((p17) => ({
|
|
1441
|
+
name: p17.name,
|
|
1442
|
+
optional: p17.optional,
|
|
1443
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1270
1444
|
}));
|
|
1271
1445
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1272
1446
|
key: toCamelCase(dep),
|
|
@@ -1365,8 +1539,8 @@ function generateUseCase(options) {
|
|
|
1365
1539
|
const files = [];
|
|
1366
1540
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1367
1541
|
const ucName = toPascalCase(options.name);
|
|
1368
|
-
const targetDir = options.targetDir ||
|
|
1369
|
-
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`);
|
|
1370
1544
|
const code = generateUseCaseCode(
|
|
1371
1545
|
ucName,
|
|
1372
1546
|
options.dependencies || [],
|
|
@@ -1382,7 +1556,7 @@ function generateUseCase(options) {
|
|
|
1382
1556
|
})
|
|
1383
1557
|
);
|
|
1384
1558
|
if (options.includeTest) {
|
|
1385
|
-
const testPath =
|
|
1559
|
+
const testPath = path2__default.default.join(targetDir, `${ucName}.test.ts`);
|
|
1386
1560
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1387
1561
|
files.push(
|
|
1388
1562
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1450,10 +1624,10 @@ function generateHttpHandler(options) {
|
|
|
1450
1624
|
const files = [];
|
|
1451
1625
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1452
1626
|
const className = toPascalCase(options.name);
|
|
1453
|
-
const pxDir =
|
|
1454
|
-
const handlersDir =
|
|
1455
|
-
const pxIndexPath =
|
|
1456
|
-
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 (!fs12__default.default.existsSync(pxIndexPath)) {
|
|
1457
1631
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1458
1632
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1459
1633
|
routePath: featurePath
|
|
@@ -1466,7 +1640,7 @@ function generateHttpHandler(options) {
|
|
|
1466
1640
|
})
|
|
1467
1641
|
);
|
|
1468
1642
|
}
|
|
1469
|
-
const handlerFilePath =
|
|
1643
|
+
const handlerFilePath = path2__default.default.join(handlersDir, `${className}.ts`);
|
|
1470
1644
|
const code = generateHttpHandlerCode(
|
|
1471
1645
|
className,
|
|
1472
1646
|
options.method || "get",
|
|
@@ -1487,11 +1661,11 @@ function generateHttpHandler(options) {
|
|
|
1487
1661
|
return files;
|
|
1488
1662
|
}
|
|
1489
1663
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1490
|
-
const providerPath =
|
|
1664
|
+
const providerPath = path2__default.default.join(featureDir, "diProvider.ts");
|
|
1491
1665
|
const pascalFeature = toPascalCase(featureName);
|
|
1492
1666
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1493
|
-
if (
|
|
1494
|
-
let content =
|
|
1667
|
+
if (fs12__default.default.existsSync(providerPath) && !options.overwrite) {
|
|
1668
|
+
let content = fs12__default.default.readFileSync(providerPath, "utf-8");
|
|
1495
1669
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1496
1670
|
for (const b of bindings) {
|
|
1497
1671
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1550,7 +1724,7 @@ function generateFeature(options) {
|
|
|
1550
1724
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1551
1725
|
const rawName = options.name.trim();
|
|
1552
1726
|
const featureName = toCamelCase(rawName);
|
|
1553
|
-
const targetDir = options.targetDir ||
|
|
1727
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1554
1728
|
const routePath = options.routePath || `/${featureName}`;
|
|
1555
1729
|
const rawEntities = [];
|
|
1556
1730
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1571,7 +1745,7 @@ function generateFeature(options) {
|
|
|
1571
1745
|
const domainFile = generateDomain({
|
|
1572
1746
|
name: ent,
|
|
1573
1747
|
feature: featureName,
|
|
1574
|
-
targetDir:
|
|
1748
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1575
1749
|
projectRoot,
|
|
1576
1750
|
dryRun: options.dryRun,
|
|
1577
1751
|
overwrite: options.overwrite,
|
|
@@ -1591,7 +1765,7 @@ function generateFeature(options) {
|
|
|
1591
1765
|
const ifaceFile = generateInterface({
|
|
1592
1766
|
name: ifaceName,
|
|
1593
1767
|
feature: featureName,
|
|
1594
|
-
targetDir:
|
|
1768
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1595
1769
|
entityName: ent,
|
|
1596
1770
|
projectRoot,
|
|
1597
1771
|
dryRun: options.dryRun,
|
|
@@ -1608,7 +1782,7 @@ function generateFeature(options) {
|
|
|
1608
1782
|
const stubFile = generateInfrastructure({
|
|
1609
1783
|
name: stubName,
|
|
1610
1784
|
feature: featureName,
|
|
1611
|
-
targetDir:
|
|
1785
|
+
targetDir: path2__default.default.join(targetDir, "infrastructure"),
|
|
1612
1786
|
kind: "stub",
|
|
1613
1787
|
implementsInterface: ifaceName,
|
|
1614
1788
|
entityName: ent,
|
|
@@ -1650,7 +1824,7 @@ function generateFeature(options) {
|
|
|
1650
1824
|
const ucFiles = generateUseCase({
|
|
1651
1825
|
name: ucName,
|
|
1652
1826
|
feature: featureName,
|
|
1653
|
-
targetDir:
|
|
1827
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1654
1828
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1655
1829
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1656
1830
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1695,7 +1869,7 @@ function generateFeature(options) {
|
|
|
1695
1869
|
const ucFiles = generateUseCase({
|
|
1696
1870
|
name: ucName,
|
|
1697
1871
|
feature: featureName,
|
|
1698
|
-
targetDir:
|
|
1872
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1699
1873
|
dependencies: deps,
|
|
1700
1874
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1701
1875
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1728,7 +1902,7 @@ function generateFeature(options) {
|
|
|
1728
1902
|
const ucFiles = generateUseCase({
|
|
1729
1903
|
name: ucName,
|
|
1730
1904
|
feature: featureName,
|
|
1731
|
-
targetDir:
|
|
1905
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1732
1906
|
dependencies: deps,
|
|
1733
1907
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1734
1908
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1761,7 +1935,7 @@ function generateFeature(options) {
|
|
|
1761
1935
|
const ucFiles = generateUseCase({
|
|
1762
1936
|
name: ucName,
|
|
1763
1937
|
feature: featureName,
|
|
1764
|
-
targetDir:
|
|
1938
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1765
1939
|
dependencies: deps,
|
|
1766
1940
|
inputProperties: [
|
|
1767
1941
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1805,7 +1979,7 @@ function generateFeature(options) {
|
|
|
1805
1979
|
const ucFiles = generateUseCase({
|
|
1806
1980
|
name: ucName,
|
|
1807
1981
|
feature: featureName,
|
|
1808
|
-
targetDir:
|
|
1982
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1809
1983
|
dependencies: deps,
|
|
1810
1984
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1811
1985
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1838,7 +2012,7 @@ function generateFeature(options) {
|
|
|
1838
2012
|
const ucFiles = generateUseCase({
|
|
1839
2013
|
name: ucName,
|
|
1840
2014
|
feature: featureName,
|
|
1841
|
-
targetDir:
|
|
2015
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1842
2016
|
dependencies: deps,
|
|
1843
2017
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1844
2018
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1875,7 +2049,7 @@ function generateFeature(options) {
|
|
|
1875
2049
|
const ucFiles = generateUseCase({
|
|
1876
2050
|
name: ucName,
|
|
1877
2051
|
feature: featureName,
|
|
1878
|
-
targetDir:
|
|
2052
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1879
2053
|
dependencies: customUc.dependencies || [],
|
|
1880
2054
|
inputProperties: customUc.inputProperties || [],
|
|
1881
2055
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1907,8 +2081,8 @@ function generateFeature(options) {
|
|
|
1907
2081
|
}
|
|
1908
2082
|
}
|
|
1909
2083
|
if (options.includeExpress !== false) {
|
|
1910
|
-
const pxDir =
|
|
1911
|
-
const pxIndexPath =
|
|
2084
|
+
const pxDir = path2__default.default.join(targetDir, "pxExpress");
|
|
2085
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1912
2086
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1913
2087
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1914
2088
|
files.push(
|
|
@@ -1934,13 +2108,14 @@ function generateFeature(options) {
|
|
|
1934
2108
|
// src/commands/feature.ts
|
|
1935
2109
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
1936
2110
|
const projectRoot = findProjectRoot();
|
|
2111
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
1937
2112
|
let name = featureNameArg?.trim();
|
|
1938
2113
|
if (!name && flags.yes) {
|
|
1939
2114
|
name = "feature";
|
|
1940
2115
|
}
|
|
1941
2116
|
if (!name) {
|
|
1942
2117
|
name = handleCancel(
|
|
1943
|
-
await
|
|
2118
|
+
await p15__namespace.text({
|
|
1944
2119
|
message: "What is the feature name?",
|
|
1945
2120
|
placeholder: "e.g. users, auth, orders, billing",
|
|
1946
2121
|
validate: (val) => {
|
|
@@ -1951,18 +2126,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1951
2126
|
).trim();
|
|
1952
2127
|
}
|
|
1953
2128
|
const normalizedName = toCamelCase(name);
|
|
1954
|
-
const
|
|
2129
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
2130
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseFeaturesDir, normalizedName));
|
|
1955
2131
|
let targetDirRel = defaultDir;
|
|
1956
|
-
if (!flags.yes && !flags.target) {
|
|
2132
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1957
2133
|
targetDirRel = handleCancel(
|
|
1958
|
-
await
|
|
2134
|
+
await p15__namespace.text({
|
|
1959
2135
|
message: "Target directory for the feature:",
|
|
1960
2136
|
initialValue: defaultDir,
|
|
1961
2137
|
defaultValue: defaultDir
|
|
1962
2138
|
})
|
|
1963
2139
|
).trim();
|
|
1964
2140
|
}
|
|
1965
|
-
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);
|
|
1966
2142
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
1967
2143
|
let routePath = `/${normalizedName}`;
|
|
1968
2144
|
let includeDomain = true;
|
|
@@ -1977,14 +2153,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1977
2153
|
const customUseCases = [];
|
|
1978
2154
|
if (!flags.yes) {
|
|
1979
2155
|
includeExpress = handleCancel(
|
|
1980
|
-
await
|
|
2156
|
+
await p15__namespace.confirm({
|
|
1981
2157
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
1982
2158
|
initialValue: true
|
|
1983
2159
|
})
|
|
1984
2160
|
);
|
|
1985
2161
|
if (includeExpress) {
|
|
1986
2162
|
routePath = handleCancel(
|
|
1987
|
-
await
|
|
2163
|
+
await p15__namespace.text({
|
|
1988
2164
|
message: "Express route prefix:",
|
|
1989
2165
|
initialValue: `/${normalizedName}`,
|
|
1990
2166
|
defaultValue: `/${normalizedName}`
|
|
@@ -1992,14 +2168,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1992
2168
|
).trim();
|
|
1993
2169
|
}
|
|
1994
2170
|
includeDomain = handleCancel(
|
|
1995
|
-
await
|
|
2171
|
+
await p15__namespace.confirm({
|
|
1996
2172
|
message: "Include Domain Entity/Entities?",
|
|
1997
2173
|
initialValue: true
|
|
1998
2174
|
})
|
|
1999
2175
|
);
|
|
2000
2176
|
if (includeDomain) {
|
|
2001
2177
|
const domainInput = handleCancel(
|
|
2002
|
-
await
|
|
2178
|
+
await p15__namespace.text({
|
|
2003
2179
|
message: "Domain entity name(s) (comma-separated):",
|
|
2004
2180
|
initialValue: defaultEntity,
|
|
2005
2181
|
defaultValue: defaultEntity,
|
|
@@ -2018,21 +2194,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2018
2194
|
entityNames = [defaultEntity];
|
|
2019
2195
|
}
|
|
2020
2196
|
includeInterface = handleCancel(
|
|
2021
|
-
await
|
|
2197
|
+
await p15__namespace.confirm({
|
|
2022
2198
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
2023
2199
|
initialValue: true
|
|
2024
2200
|
})
|
|
2025
2201
|
);
|
|
2026
2202
|
if (includeInterface) {
|
|
2027
2203
|
includeInfra = handleCancel(
|
|
2028
|
-
await
|
|
2204
|
+
await p15__namespace.confirm({
|
|
2029
2205
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
2030
2206
|
initialValue: true
|
|
2031
2207
|
})
|
|
2032
2208
|
);
|
|
2033
2209
|
}
|
|
2034
2210
|
includeUseCase = handleCancel(
|
|
2035
|
-
await
|
|
2211
|
+
await p15__namespace.confirm({
|
|
2036
2212
|
message: "Include Domain Use Cases?",
|
|
2037
2213
|
initialValue: true
|
|
2038
2214
|
})
|
|
@@ -2046,7 +2222,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2046
2222
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
2047
2223
|
];
|
|
2048
2224
|
crudOperations = handleCancel(
|
|
2049
|
-
await
|
|
2225
|
+
await p15__namespace.multiselect({
|
|
2050
2226
|
message: "Select standard CRUD use cases to generate:",
|
|
2051
2227
|
options: crudChoices,
|
|
2052
2228
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -2054,13 +2230,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2054
2230
|
})
|
|
2055
2231
|
);
|
|
2056
2232
|
includeTests = handleCancel(
|
|
2057
|
-
await
|
|
2233
|
+
await p15__namespace.confirm({
|
|
2058
2234
|
message: "Generate Vitest unit tests for use cases?",
|
|
2059
2235
|
initialValue: true
|
|
2060
2236
|
})
|
|
2061
2237
|
);
|
|
2062
2238
|
const wantCustom = handleCancel(
|
|
2063
|
-
await
|
|
2239
|
+
await p15__namespace.confirm({
|
|
2064
2240
|
message: "Do you want to add any custom use cases to this feature?",
|
|
2065
2241
|
initialValue: false
|
|
2066
2242
|
})
|
|
@@ -2070,7 +2246,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2070
2246
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
2071
2247
|
while (addMore) {
|
|
2072
2248
|
const customName = handleCancel(
|
|
2073
|
-
await
|
|
2249
|
+
await p15__namespace.text({
|
|
2074
2250
|
message: "Custom Use Case name:",
|
|
2075
2251
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
2076
2252
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2080,7 +2256,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2080
2256
|
let selectedDeps = [];
|
|
2081
2257
|
if (availableIfaces.length > 0) {
|
|
2082
2258
|
selectedDeps = handleCancel(
|
|
2083
|
-
await
|
|
2259
|
+
await p15__namespace.multiselect({
|
|
2084
2260
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
2085
2261
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
2086
2262
|
required: false
|
|
@@ -2089,7 +2265,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2089
2265
|
}
|
|
2090
2266
|
let customInputProps = [];
|
|
2091
2267
|
const hasInput = handleCancel(
|
|
2092
|
-
await
|
|
2268
|
+
await p15__namespace.confirm({
|
|
2093
2269
|
message: `Does ${customUcName} accept input arguments?`,
|
|
2094
2270
|
initialValue: true
|
|
2095
2271
|
})
|
|
@@ -2100,7 +2276,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2100
2276
|
}
|
|
2101
2277
|
let customOutputProps = [];
|
|
2102
2278
|
const hasCustomOutput = handleCancel(
|
|
2103
|
-
await
|
|
2279
|
+
await p15__namespace.confirm({
|
|
2104
2280
|
message: `Does ${customUcName} return custom output data?`,
|
|
2105
2281
|
initialValue: false
|
|
2106
2282
|
})
|
|
@@ -2110,7 +2286,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2110
2286
|
});
|
|
2111
2287
|
}
|
|
2112
2288
|
const customIncludeTest = handleCancel(
|
|
2113
|
-
await
|
|
2289
|
+
await p15__namespace.confirm({
|
|
2114
2290
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2115
2291
|
initialValue: true
|
|
2116
2292
|
})
|
|
@@ -2120,14 +2296,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2120
2296
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2121
2297
|
if (includeExpress) {
|
|
2122
2298
|
customCreateHttp = handleCancel(
|
|
2123
|
-
await
|
|
2299
|
+
await p15__namespace.confirm({
|
|
2124
2300
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2125
2301
|
initialValue: true
|
|
2126
2302
|
})
|
|
2127
2303
|
);
|
|
2128
2304
|
if (customCreateHttp) {
|
|
2129
2305
|
customHttpMethod = handleCancel(
|
|
2130
|
-
await
|
|
2306
|
+
await p15__namespace.select({
|
|
2131
2307
|
message: "HTTP method for route:",
|
|
2132
2308
|
options: [
|
|
2133
2309
|
{ value: "post", label: "POST" },
|
|
@@ -2140,7 +2316,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2140
2316
|
})
|
|
2141
2317
|
);
|
|
2142
2318
|
customHttpPath = handleCancel(
|
|
2143
|
-
await
|
|
2319
|
+
await p15__namespace.text({
|
|
2144
2320
|
message: "Route subpath (relative to feature prefix):",
|
|
2145
2321
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2146
2322
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2161,7 +2337,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2161
2337
|
validationFields: customInputProps
|
|
2162
2338
|
});
|
|
2163
2339
|
addMore = handleCancel(
|
|
2164
|
-
await
|
|
2340
|
+
await p15__namespace.confirm({
|
|
2165
2341
|
message: "Add another custom use case?",
|
|
2166
2342
|
initialValue: false
|
|
2167
2343
|
})
|
|
@@ -2170,7 +2346,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2170
2346
|
}
|
|
2171
2347
|
}
|
|
2172
2348
|
includeDiProvider = handleCancel(
|
|
2173
|
-
await
|
|
2349
|
+
await p15__namespace.confirm({
|
|
2174
2350
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2175
2351
|
initialValue: true
|
|
2176
2352
|
})
|
|
@@ -2182,8 +2358,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2182
2358
|
entityNames = [defaultEntity];
|
|
2183
2359
|
}
|
|
2184
2360
|
}
|
|
2185
|
-
const s =
|
|
2186
|
-
s.start(`Generating feature '${
|
|
2361
|
+
const s = p15__namespace.spinner();
|
|
2362
|
+
s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
|
|
2187
2363
|
const options = {
|
|
2188
2364
|
name: normalizedName,
|
|
2189
2365
|
projectRoot,
|
|
@@ -2203,9 +2379,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2203
2379
|
includeDiProvider
|
|
2204
2380
|
};
|
|
2205
2381
|
const files = generateFeature(options);
|
|
2382
|
+
if (isRegistry) {
|
|
2383
|
+
const registryPath = path2__default.default.join(targetDir, "registry.json");
|
|
2384
|
+
if (!fs12__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
|
+
}
|
|
2206
2406
|
s.stop("Feature scaffolded successfully!");
|
|
2207
2407
|
printGeneratedFiles(files, options.dryRun);
|
|
2208
|
-
|
|
2408
|
+
p15__namespace.outro(
|
|
2409
|
+
pc15__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 p15__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 p15__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 p15__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 p15__namespace.confirm({
|
|
2559
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2560
|
+
initialValue: true
|
|
2561
|
+
})
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
const s = p15__namespace.spinner();
|
|
2565
|
+
s.start(`Generating shared module '${pc15__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
|
+
p15__namespace.outro(
|
|
2579
|
+
pc15__default.default.green(
|
|
2580
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2581
|
+
)
|
|
2582
|
+
);
|
|
2209
2583
|
}
|
|
2210
2584
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2211
2585
|
const projectRoot = findProjectRoot();
|
|
@@ -2216,14 +2590,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2216
2590
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2217
2591
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2218
2592
|
const selected = handleCancel(
|
|
2219
|
-
await
|
|
2593
|
+
await p15__namespace.select({
|
|
2220
2594
|
message: "Select target feature:",
|
|
2221
2595
|
options: featureOptions
|
|
2222
2596
|
})
|
|
2223
2597
|
);
|
|
2224
2598
|
if (selected === "__custom__") {
|
|
2225
2599
|
feature = handleCancel(
|
|
2226
|
-
await
|
|
2600
|
+
await p15__namespace.text({
|
|
2227
2601
|
message: "Enter feature name:",
|
|
2228
2602
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2229
2603
|
})
|
|
@@ -2233,7 +2607,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2233
2607
|
}
|
|
2234
2608
|
} else {
|
|
2235
2609
|
feature = handleCancel(
|
|
2236
|
-
await
|
|
2610
|
+
await p15__namespace.text({
|
|
2237
2611
|
message: "Enter target feature name:",
|
|
2238
2612
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2239
2613
|
})
|
|
@@ -2244,7 +2618,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2244
2618
|
let name = useCaseNameArg?.trim();
|
|
2245
2619
|
if (!name) {
|
|
2246
2620
|
name = handleCancel(
|
|
2247
|
-
await
|
|
2621
|
+
await p15__namespace.text({
|
|
2248
2622
|
message: "Use Case name:",
|
|
2249
2623
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2250
2624
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2260,7 +2634,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2260
2634
|
label: i.name
|
|
2261
2635
|
}));
|
|
2262
2636
|
selectedDeps = handleCancel(
|
|
2263
|
-
await
|
|
2637
|
+
await p15__namespace.multiselect({
|
|
2264
2638
|
message: "Select domain interfaces to inject into use case:",
|
|
2265
2639
|
options: multiOptions,
|
|
2266
2640
|
required: false
|
|
@@ -2275,7 +2649,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2275
2649
|
let httpPath = "/";
|
|
2276
2650
|
if (!flags.yes) {
|
|
2277
2651
|
const hasInput = handleCancel(
|
|
2278
|
-
await
|
|
2652
|
+
await p15__namespace.confirm({
|
|
2279
2653
|
message: "Does this use case accept input arguments?",
|
|
2280
2654
|
initialValue: true
|
|
2281
2655
|
})
|
|
@@ -2285,7 +2659,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2285
2659
|
});
|
|
2286
2660
|
}
|
|
2287
2661
|
const hasCustomOutput = handleCancel(
|
|
2288
|
-
await
|
|
2662
|
+
await p15__namespace.confirm({
|
|
2289
2663
|
message: "Does this use case return custom output data?",
|
|
2290
2664
|
initialValue: false
|
|
2291
2665
|
})
|
|
@@ -2295,20 +2669,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2295
2669
|
});
|
|
2296
2670
|
}
|
|
2297
2671
|
includeTest = handleCancel(
|
|
2298
|
-
await
|
|
2672
|
+
await p15__namespace.confirm({
|
|
2299
2673
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2300
2674
|
initialValue: true
|
|
2301
2675
|
})
|
|
2302
2676
|
);
|
|
2303
2677
|
createHttp = handleCancel(
|
|
2304
|
-
await
|
|
2678
|
+
await p15__namespace.confirm({
|
|
2305
2679
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2306
2680
|
initialValue: false
|
|
2307
2681
|
})
|
|
2308
2682
|
);
|
|
2309
2683
|
if (createHttp) {
|
|
2310
2684
|
httpMethod = handleCancel(
|
|
2311
|
-
await
|
|
2685
|
+
await p15__namespace.select({
|
|
2312
2686
|
message: "HTTP method for route:",
|
|
2313
2687
|
options: [
|
|
2314
2688
|
{ value: "post", label: "POST" },
|
|
@@ -2321,7 +2695,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2321
2695
|
})
|
|
2322
2696
|
);
|
|
2323
2697
|
httpPath = handleCancel(
|
|
2324
|
-
await
|
|
2698
|
+
await p15__namespace.text({
|
|
2325
2699
|
message: "Route subpath (relative to feature prefix):",
|
|
2326
2700
|
initialValue: "/",
|
|
2327
2701
|
defaultValue: "/"
|
|
@@ -2329,8 +2703,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2329
2703
|
).trim();
|
|
2330
2704
|
}
|
|
2331
2705
|
}
|
|
2332
|
-
const s =
|
|
2333
|
-
s.start(`Generating use case '${
|
|
2706
|
+
const s = p15__namespace.spinner();
|
|
2707
|
+
s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
|
|
2334
2708
|
const options = {
|
|
2335
2709
|
name: ucName,
|
|
2336
2710
|
feature,
|
|
@@ -2362,7 +2736,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2362
2736
|
}
|
|
2363
2737
|
s.stop("Use case generated successfully!");
|
|
2364
2738
|
printGeneratedFiles(files, options.dryRun);
|
|
2365
|
-
|
|
2739
|
+
p15__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2366
2740
|
}
|
|
2367
2741
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2368
2742
|
const projectRoot = findProjectRoot();
|
|
@@ -2373,14 +2747,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2373
2747
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2374
2748
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2375
2749
|
const selected = handleCancel(
|
|
2376
|
-
await
|
|
2750
|
+
await p15__namespace.select({
|
|
2377
2751
|
message: "Select target feature:",
|
|
2378
2752
|
options: featureOptions
|
|
2379
2753
|
})
|
|
2380
2754
|
);
|
|
2381
2755
|
if (selected === "__custom__") {
|
|
2382
2756
|
feature = handleCancel(
|
|
2383
|
-
await
|
|
2757
|
+
await p15__namespace.text({
|
|
2384
2758
|
message: "Enter feature name:",
|
|
2385
2759
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2386
2760
|
})
|
|
@@ -2390,7 +2764,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2390
2764
|
}
|
|
2391
2765
|
} else {
|
|
2392
2766
|
feature = handleCancel(
|
|
2393
|
-
await
|
|
2767
|
+
await p15__namespace.text({
|
|
2394
2768
|
message: "Enter target feature name:",
|
|
2395
2769
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2396
2770
|
})
|
|
@@ -2401,7 +2775,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2401
2775
|
let name = domainNameArg?.trim();
|
|
2402
2776
|
if (!name) {
|
|
2403
2777
|
name = handleCancel(
|
|
2404
|
-
await
|
|
2778
|
+
await p15__namespace.text({
|
|
2405
2779
|
message: "Domain model / entity name:",
|
|
2406
2780
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2407
2781
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2413,7 +2787,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2413
2787
|
let properties = [];
|
|
2414
2788
|
if (!flags.yes) {
|
|
2415
2789
|
kind = handleCancel(
|
|
2416
|
-
await
|
|
2790
|
+
await p15__namespace.select({
|
|
2417
2791
|
message: "Select domain model type:",
|
|
2418
2792
|
options: [
|
|
2419
2793
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2432,8 +2806,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2432
2806
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2433
2807
|
});
|
|
2434
2808
|
}
|
|
2435
|
-
const s =
|
|
2436
|
-
s.start(`Generating domain model '${
|
|
2809
|
+
const s = p15__namespace.spinner();
|
|
2810
|
+
s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
|
|
2437
2811
|
const options = {
|
|
2438
2812
|
name: entityName,
|
|
2439
2813
|
feature,
|
|
@@ -2446,7 +2820,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2446
2820
|
const file = generateDomain(options);
|
|
2447
2821
|
s.stop("Domain model generated successfully!");
|
|
2448
2822
|
printGeneratedFiles([file], options.dryRun);
|
|
2449
|
-
|
|
2823
|
+
p15__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2450
2824
|
}
|
|
2451
2825
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2452
2826
|
const projectRoot = findProjectRoot();
|
|
@@ -2455,7 +2829,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2455
2829
|
let feature = flags.feature?.trim();
|
|
2456
2830
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2457
2831
|
scope = handleCancel(
|
|
2458
|
-
await
|
|
2832
|
+
await p15__namespace.select({
|
|
2459
2833
|
message: "Where should this infrastructure implementation be placed?",
|
|
2460
2834
|
options: [
|
|
2461
2835
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2470,14 +2844,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2470
2844
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2471
2845
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2472
2846
|
const selected = handleCancel(
|
|
2473
|
-
await
|
|
2847
|
+
await p15__namespace.select({
|
|
2474
2848
|
message: "Select target feature:",
|
|
2475
2849
|
options: featureOptions
|
|
2476
2850
|
})
|
|
2477
2851
|
);
|
|
2478
2852
|
if (selected === "__custom__") {
|
|
2479
2853
|
feature = handleCancel(
|
|
2480
|
-
await
|
|
2854
|
+
await p15__namespace.text({
|
|
2481
2855
|
message: "Enter feature name:",
|
|
2482
2856
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2483
2857
|
})
|
|
@@ -2487,7 +2861,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2487
2861
|
}
|
|
2488
2862
|
} else {
|
|
2489
2863
|
feature = handleCancel(
|
|
2490
|
-
await
|
|
2864
|
+
await p15__namespace.text({
|
|
2491
2865
|
message: "Enter target feature name:",
|
|
2492
2866
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2493
2867
|
})
|
|
@@ -2500,7 +2874,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2500
2874
|
let name = classNameArg?.trim();
|
|
2501
2875
|
if (!name) {
|
|
2502
2876
|
name = handleCancel(
|
|
2503
|
-
await
|
|
2877
|
+
await p15__namespace.text({
|
|
2504
2878
|
message: "Infrastructure class name:",
|
|
2505
2879
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2506
2880
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2519,7 +2893,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2519
2893
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2520
2894
|
);
|
|
2521
2895
|
const ifaceChoice = handleCancel(
|
|
2522
|
-
await
|
|
2896
|
+
await p15__namespace.select({
|
|
2523
2897
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2524
2898
|
options: [
|
|
2525
2899
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2531,7 +2905,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2531
2905
|
);
|
|
2532
2906
|
if (ifaceChoice === "__custom__") {
|
|
2533
2907
|
implementsInterface = handleCancel(
|
|
2534
|
-
await
|
|
2908
|
+
await p15__namespace.text({
|
|
2535
2909
|
message: "Enter interface or abstract class name:",
|
|
2536
2910
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2537
2911
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2542,14 +2916,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2542
2916
|
}
|
|
2543
2917
|
} else {
|
|
2544
2918
|
const wantImpl = handleCancel(
|
|
2545
|
-
await
|
|
2919
|
+
await p15__namespace.confirm({
|
|
2546
2920
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2547
2921
|
initialValue: false
|
|
2548
2922
|
})
|
|
2549
2923
|
);
|
|
2550
2924
|
if (wantImpl) {
|
|
2551
2925
|
implementsInterface = handleCancel(
|
|
2552
|
-
await
|
|
2926
|
+
await p15__namespace.text({
|
|
2553
2927
|
message: "Enter interface or abstract class name:",
|
|
2554
2928
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2555
2929
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2569,7 +2943,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2569
2943
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2570
2944
|
if (allInterfaces.length > 0) {
|
|
2571
2945
|
const ifaceChoice = handleCancel(
|
|
2572
|
-
await
|
|
2946
|
+
await p15__namespace.select({
|
|
2573
2947
|
message: "Does this class implement an interface or abstract class?",
|
|
2574
2948
|
options: [
|
|
2575
2949
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2581,7 +2955,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2581
2955
|
);
|
|
2582
2956
|
if (ifaceChoice === "__custom__") {
|
|
2583
2957
|
implementsInterface = handleCancel(
|
|
2584
|
-
await
|
|
2958
|
+
await p15__namespace.text({
|
|
2585
2959
|
message: "Enter interface or abstract class name:",
|
|
2586
2960
|
placeholder: "e.g. IAuthRepository",
|
|
2587
2961
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2594,7 +2968,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2594
2968
|
}
|
|
2595
2969
|
if (!flags.yes && !flags.kind) {
|
|
2596
2970
|
kind = handleCancel(
|
|
2597
|
-
await
|
|
2971
|
+
await p15__namespace.select({
|
|
2598
2972
|
message: "Select implementation kind:",
|
|
2599
2973
|
options: [
|
|
2600
2974
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2605,7 +2979,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2605
2979
|
);
|
|
2606
2980
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2607
2981
|
registerInProvider = handleCancel(
|
|
2608
|
-
await
|
|
2982
|
+
await p15__namespace.confirm({
|
|
2609
2983
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2610
2984
|
initialValue: true
|
|
2611
2985
|
})
|
|
@@ -2614,8 +2988,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2614
2988
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2615
2989
|
registerInProvider = !flags.noProvider;
|
|
2616
2990
|
}
|
|
2617
|
-
const s =
|
|
2618
|
-
s.start(`Generating infrastructure class '${
|
|
2991
|
+
const s = p15__namespace.spinner();
|
|
2992
|
+
s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
|
|
2619
2993
|
const files = [];
|
|
2620
2994
|
const options = {
|
|
2621
2995
|
name: className,
|
|
@@ -2630,7 +3004,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2630
3004
|
const infraFile = generateInfrastructure(options);
|
|
2631
3005
|
files.push(infraFile);
|
|
2632
3006
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2633
|
-
const featureDir =
|
|
3007
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2634
3008
|
const providerFile = updateOrGenerateDiProvider(
|
|
2635
3009
|
featureDir,
|
|
2636
3010
|
feature,
|
|
@@ -2650,7 +3024,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2650
3024
|
}
|
|
2651
3025
|
s.stop("Infrastructure class generated successfully!");
|
|
2652
3026
|
printGeneratedFiles(files, options.dryRun);
|
|
2653
|
-
|
|
3027
|
+
p15__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2654
3028
|
}
|
|
2655
3029
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2656
3030
|
const projectRoot = findProjectRoot();
|
|
@@ -2661,14 +3035,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2661
3035
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2662
3036
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2663
3037
|
const selected = handleCancel(
|
|
2664
|
-
await
|
|
3038
|
+
await p15__namespace.select({
|
|
2665
3039
|
message: "Select target feature:",
|
|
2666
3040
|
options: featureOptions
|
|
2667
3041
|
})
|
|
2668
3042
|
);
|
|
2669
3043
|
if (selected === "__custom__") {
|
|
2670
3044
|
feature = handleCancel(
|
|
2671
|
-
await
|
|
3045
|
+
await p15__namespace.text({
|
|
2672
3046
|
message: "Enter feature name:",
|
|
2673
3047
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2674
3048
|
})
|
|
@@ -2678,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2678
3052
|
}
|
|
2679
3053
|
} else {
|
|
2680
3054
|
feature = handleCancel(
|
|
2681
|
-
await
|
|
3055
|
+
await p15__namespace.text({
|
|
2682
3056
|
message: "Enter target feature name:",
|
|
2683
3057
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2684
3058
|
})
|
|
@@ -2689,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2689
3063
|
let name = interfaceNameArg?.trim();
|
|
2690
3064
|
if (!name) {
|
|
2691
3065
|
name = handleCancel(
|
|
2692
|
-
await
|
|
3066
|
+
await p15__namespace.text({
|
|
2693
3067
|
message: "Domain interface name:",
|
|
2694
3068
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2695
3069
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2707,7 +3081,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2707
3081
|
if (!flags.yes) {
|
|
2708
3082
|
if (existingEntities.length > 0) {
|
|
2709
3083
|
const selectedEntity = handleCancel(
|
|
2710
|
-
await
|
|
3084
|
+
await p15__namespace.select({
|
|
2711
3085
|
message: "Select domain entity associated with this interface:",
|
|
2712
3086
|
options: [
|
|
2713
3087
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2718,7 +3092,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2718
3092
|
);
|
|
2719
3093
|
if (selectedEntity === "__custom__") {
|
|
2720
3094
|
entityName = handleCancel(
|
|
2721
|
-
await
|
|
3095
|
+
await p15__namespace.text({
|
|
2722
3096
|
message: "Enter entity name:",
|
|
2723
3097
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2724
3098
|
})
|
|
@@ -2728,7 +3102,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2728
3102
|
}
|
|
2729
3103
|
}
|
|
2730
3104
|
kind = handleCancel(
|
|
2731
|
-
await
|
|
3105
|
+
await p15__namespace.select({
|
|
2732
3106
|
message: "Interface definition style:",
|
|
2733
3107
|
options: [
|
|
2734
3108
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2738,7 +3112,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2738
3112
|
})
|
|
2739
3113
|
);
|
|
2740
3114
|
methodsPreset = handleCancel(
|
|
2741
|
-
await
|
|
3115
|
+
await p15__namespace.select({
|
|
2742
3116
|
message: "Method signature preset:",
|
|
2743
3117
|
options: [
|
|
2744
3118
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2748,22 +3122,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2748
3122
|
})
|
|
2749
3123
|
);
|
|
2750
3124
|
generateStub = handleCancel(
|
|
2751
|
-
await
|
|
3125
|
+
await p15__namespace.confirm({
|
|
2752
3126
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2753
3127
|
initialValue: true
|
|
2754
3128
|
})
|
|
2755
3129
|
);
|
|
2756
3130
|
if (generateStub) {
|
|
2757
3131
|
registerInProvider = handleCancel(
|
|
2758
|
-
await
|
|
3132
|
+
await p15__namespace.confirm({
|
|
2759
3133
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2760
3134
|
initialValue: true
|
|
2761
3135
|
})
|
|
2762
3136
|
);
|
|
2763
3137
|
}
|
|
2764
3138
|
}
|
|
2765
|
-
const s =
|
|
2766
|
-
s.start(`Generating interface '${
|
|
3139
|
+
const s = p15__namespace.spinner();
|
|
3140
|
+
s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
|
|
2767
3141
|
const files = [];
|
|
2768
3142
|
const options = {
|
|
2769
3143
|
name: ifaceName,
|
|
@@ -2792,7 +3166,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2792
3166
|
files.push(stubFile);
|
|
2793
3167
|
}
|
|
2794
3168
|
if (registerInProvider && generateStub) {
|
|
2795
|
-
const featureDir =
|
|
3169
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2796
3170
|
const providerFile = updateOrGenerateDiProvider(
|
|
2797
3171
|
featureDir,
|
|
2798
3172
|
feature,
|
|
@@ -2812,7 +3186,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2812
3186
|
}
|
|
2813
3187
|
s.stop("Interface generated successfully!");
|
|
2814
3188
|
printGeneratedFiles(files, options.dryRun);
|
|
2815
|
-
|
|
3189
|
+
p15__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2816
3190
|
}
|
|
2817
3191
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2818
3192
|
const projectRoot = findProjectRoot();
|
|
@@ -2823,14 +3197,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2823
3197
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2824
3198
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2825
3199
|
const selected = handleCancel(
|
|
2826
|
-
await
|
|
3200
|
+
await p15__namespace.select({
|
|
2827
3201
|
message: "Select target feature:",
|
|
2828
3202
|
options: featureOptions
|
|
2829
3203
|
})
|
|
2830
3204
|
);
|
|
2831
3205
|
if (selected === "__custom__") {
|
|
2832
3206
|
feature = handleCancel(
|
|
2833
|
-
await
|
|
3207
|
+
await p15__namespace.text({
|
|
2834
3208
|
message: "Enter feature name:",
|
|
2835
3209
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2836
3210
|
})
|
|
@@ -2840,7 +3214,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2840
3214
|
}
|
|
2841
3215
|
} else {
|
|
2842
3216
|
feature = handleCancel(
|
|
2843
|
-
await
|
|
3217
|
+
await p15__namespace.text({
|
|
2844
3218
|
message: "Enter target feature name:",
|
|
2845
3219
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2846
3220
|
})
|
|
@@ -2851,7 +3225,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2851
3225
|
let name = handlerNameArg?.trim();
|
|
2852
3226
|
if (!name) {
|
|
2853
3227
|
name = handleCancel(
|
|
2854
|
-
await
|
|
3228
|
+
await p15__namespace.text({
|
|
2855
3229
|
message: "HTTP Route Handler class name:",
|
|
2856
3230
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2857
3231
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2870,7 +3244,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2870
3244
|
let validationFields = [];
|
|
2871
3245
|
if (!flags.yes) {
|
|
2872
3246
|
method = handleCancel(
|
|
2873
|
-
await
|
|
3247
|
+
await p15__namespace.select({
|
|
2874
3248
|
message: "HTTP Method:",
|
|
2875
3249
|
options: [
|
|
2876
3250
|
{ value: "get", label: "GET" },
|
|
@@ -2884,7 +3258,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2884
3258
|
);
|
|
2885
3259
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2886
3260
|
subPath = handleCancel(
|
|
2887
|
-
await
|
|
3261
|
+
await p15__namespace.text({
|
|
2888
3262
|
message: "Route subpath (relative to feature prefix):",
|
|
2889
3263
|
initialValue: defaultPath,
|
|
2890
3264
|
defaultValue: defaultPath
|
|
@@ -2895,7 +3269,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2895
3269
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2896
3270
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2897
3271
|
const chosenUc = handleCancel(
|
|
2898
|
-
await
|
|
3272
|
+
await p15__namespace.select({
|
|
2899
3273
|
message: "Connect this handler to a domain Use Case?",
|
|
2900
3274
|
options: ucOptions,
|
|
2901
3275
|
initialValue: ucOptions[0]?.value
|
|
@@ -2906,7 +3280,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2906
3280
|
}
|
|
2907
3281
|
}
|
|
2908
3282
|
includeValidation = handleCancel(
|
|
2909
|
-
await
|
|
3283
|
+
await p15__namespace.confirm({
|
|
2910
3284
|
message: "Include Zod inbound request validation?",
|
|
2911
3285
|
initialValue: method !== "delete"
|
|
2912
3286
|
})
|
|
@@ -2914,7 +3288,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2914
3288
|
if (includeValidation) {
|
|
2915
3289
|
const defaultSource = method === "get" ? "query" : "body";
|
|
2916
3290
|
validationSource = handleCancel(
|
|
2917
|
-
await
|
|
3291
|
+
await p15__namespace.select({
|
|
2918
3292
|
message: "Validation data source:",
|
|
2919
3293
|
options: [
|
|
2920
3294
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -2928,8 +3302,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2928
3302
|
});
|
|
2929
3303
|
}
|
|
2930
3304
|
}
|
|
2931
|
-
const s =
|
|
2932
|
-
s.start(`Generating HTTP handler '${
|
|
3305
|
+
const s = p15__namespace.spinner();
|
|
3306
|
+
s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
|
|
2933
3307
|
const options = {
|
|
2934
3308
|
name: className,
|
|
2935
3309
|
feature,
|
|
@@ -2947,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2947
3321
|
const files = generateHttpHandler(options);
|
|
2948
3322
|
s.stop("HTTP handler generated successfully!");
|
|
2949
3323
|
printGeneratedFiles(files, options.dryRun);
|
|
2950
|
-
|
|
3324
|
+
p15__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
2951
3325
|
}
|
|
2952
3326
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
2953
3327
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -2957,7 +3331,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
2957
3331
|
const formattedMethods = methods.map((m) => {
|
|
2958
3332
|
const isAsync = m.async !== false;
|
|
2959
3333
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
2960
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3334
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
|
|
2961
3335
|
return {
|
|
2962
3336
|
name: m.name,
|
|
2963
3337
|
isAsync,
|
|
@@ -2980,21 +3354,21 @@ function generateInjectable(options) {
|
|
|
2980
3354
|
const className = toPascalCase(options.name);
|
|
2981
3355
|
let targetDir = "";
|
|
2982
3356
|
if (options.customPath) {
|
|
2983
|
-
targetDir =
|
|
3357
|
+
targetDir = path2__default.default.isAbsolute(options.customPath) ? options.customPath : path2__default.default.join(projectRoot, options.customPath);
|
|
2984
3358
|
} else if (options.location === "feature-service" && options.feature) {
|
|
2985
|
-
targetDir =
|
|
3359
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
2986
3360
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
2987
|
-
targetDir =
|
|
3361
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
2988
3362
|
} else if (options.location === "module") {
|
|
2989
|
-
targetDir =
|
|
3363
|
+
targetDir = path2__default.default.join(projectRoot, "modules", className);
|
|
2990
3364
|
} else {
|
|
2991
|
-
targetDir = options.feature ?
|
|
3365
|
+
targetDir = options.feature ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2__default.default.join(projectRoot, "shared", className.toLowerCase());
|
|
2992
3366
|
}
|
|
2993
|
-
const filePath =
|
|
3367
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
2994
3368
|
let ifaceName;
|
|
2995
3369
|
if (options.generateInterface) {
|
|
2996
3370
|
ifaceName = `I${className}`;
|
|
2997
|
-
const ifacePath =
|
|
3371
|
+
const ifacePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
2998
3372
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
2999
3373
|
files.push(
|
|
3000
3374
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -3027,7 +3401,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3027
3401
|
let name = classNameArg?.trim();
|
|
3028
3402
|
if (!name) {
|
|
3029
3403
|
name = handleCancel(
|
|
3030
|
-
await
|
|
3404
|
+
await p15__namespace.text({
|
|
3031
3405
|
message: "Injectable class name:",
|
|
3032
3406
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3033
3407
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3040,7 +3414,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3040
3414
|
let customPath;
|
|
3041
3415
|
if (!flags.yes) {
|
|
3042
3416
|
location = handleCancel(
|
|
3043
|
-
await
|
|
3417
|
+
await p15__namespace.select({
|
|
3044
3418
|
message: "Select target architectural location:",
|
|
3045
3419
|
options: [
|
|
3046
3420
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3058,14 +3432,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3058
3432
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3059
3433
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3060
3434
|
const selected = handleCancel(
|
|
3061
|
-
await
|
|
3435
|
+
await p15__namespace.select({
|
|
3062
3436
|
message: "Select target feature:",
|
|
3063
3437
|
options: featureOptions
|
|
3064
3438
|
})
|
|
3065
3439
|
);
|
|
3066
3440
|
if (selected === "__custom__") {
|
|
3067
3441
|
feature = handleCancel(
|
|
3068
|
-
await
|
|
3442
|
+
await p15__namespace.text({
|
|
3069
3443
|
message: "Enter feature name:",
|
|
3070
3444
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3071
3445
|
})
|
|
@@ -3075,7 +3449,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3075
3449
|
}
|
|
3076
3450
|
} else {
|
|
3077
3451
|
feature = handleCancel(
|
|
3078
|
-
await
|
|
3452
|
+
await p15__namespace.text({
|
|
3079
3453
|
message: "Enter target feature name:",
|
|
3080
3454
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3081
3455
|
})
|
|
@@ -3085,7 +3459,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3085
3459
|
feature = toCamelCase(feature);
|
|
3086
3460
|
} else if (location === "custom") {
|
|
3087
3461
|
customPath = handleCancel(
|
|
3088
|
-
await
|
|
3462
|
+
await p15__namespace.text({
|
|
3089
3463
|
message: "Enter destination directory path:",
|
|
3090
3464
|
placeholder: "e.g. src/services or lib/utils",
|
|
3091
3465
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3096,14 +3470,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3096
3470
|
let generateInterface2 = false;
|
|
3097
3471
|
if (!flags.yes) {
|
|
3098
3472
|
generateInterface2 = handleCancel(
|
|
3099
|
-
await
|
|
3473
|
+
await p15__namespace.confirm({
|
|
3100
3474
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3101
3475
|
initialValue: false
|
|
3102
3476
|
})
|
|
3103
3477
|
);
|
|
3104
3478
|
}
|
|
3105
|
-
const s =
|
|
3106
|
-
s.start(`Generating injectable '${
|
|
3479
|
+
const s = p15__namespace.spinner();
|
|
3480
|
+
s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
|
|
3107
3481
|
const options = {
|
|
3108
3482
|
name: className,
|
|
3109
3483
|
feature,
|
|
@@ -3117,12 +3491,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3117
3491
|
const files = generateInjectable(options);
|
|
3118
3492
|
s.stop("Injectable class generated successfully!");
|
|
3119
3493
|
printGeneratedFiles(files, options.dryRun);
|
|
3120
|
-
|
|
3494
|
+
p15__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
|
|
3121
3495
|
}
|
|
3122
3496
|
async function runInteractiveMenu(flags = {}) {
|
|
3123
3497
|
printBanner();
|
|
3124
3498
|
const choice = handleCancel(
|
|
3125
|
-
await
|
|
3499
|
+
await p15__namespace.select({
|
|
3126
3500
|
message: "What would you like to create in your architecture?",
|
|
3127
3501
|
options: [
|
|
3128
3502
|
{
|
|
@@ -3130,6 +3504,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3130
3504
|
label: "Feature",
|
|
3131
3505
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
3132
3506
|
},
|
|
3507
|
+
{
|
|
3508
|
+
value: "shared",
|
|
3509
|
+
label: "Shared Module",
|
|
3510
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3511
|
+
},
|
|
3133
3512
|
{
|
|
3134
3513
|
value: "usecase",
|
|
3135
3514
|
label: "Use Case",
|
|
@@ -3167,6 +3546,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3167
3546
|
case "feature":
|
|
3168
3547
|
await runFeatureCommand(void 0, flags);
|
|
3169
3548
|
break;
|
|
3549
|
+
case "shared":
|
|
3550
|
+
await runSharedCommand(void 0, flags);
|
|
3551
|
+
break;
|
|
3170
3552
|
case "usecase":
|
|
3171
3553
|
await runUseCaseCommand(void 0, flags);
|
|
3172
3554
|
break;
|
|
@@ -3199,6 +3581,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3199
3581
|
case "feature":
|
|
3200
3582
|
await runFeatureCommand(name, flags);
|
|
3201
3583
|
break;
|
|
3584
|
+
case "shared":
|
|
3585
|
+
await runSharedCommand(name, flags);
|
|
3586
|
+
break;
|
|
3202
3587
|
case "usecase":
|
|
3203
3588
|
await runUseCaseCommand(name, flags);
|
|
3204
3589
|
break;
|
|
@@ -3219,21 +3604,172 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3219
3604
|
await runInjectableCommand(name, flags);
|
|
3220
3605
|
break;
|
|
3221
3606
|
default:
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3607
|
+
p15__namespace.log.error(
|
|
3608
|
+
pc15__default.default.red(
|
|
3609
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3225
3610
|
)
|
|
3226
3611
|
);
|
|
3227
3612
|
process.exit(1);
|
|
3228
3613
|
}
|
|
3229
3614
|
}
|
|
3615
|
+
var importPrompts = {
|
|
3616
|
+
select: p15__namespace.select,
|
|
3617
|
+
text: p15__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 (fs12__default.default.existsSync(inSrc)) return inSrc;
|
|
3639
|
+
const inRoot = path2__default.default.join(projectRoot, folder, modName);
|
|
3640
|
+
if (fs12__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
|
+
p15__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
|
+
p15__namespace.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc15__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
|
+
p15__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
|
+
p15__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
|
+
fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3692
|
+
}
|
|
3693
|
+
fs12__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
|
+
p15__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
|
+
p15__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
|
+
p15__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3749
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
if (choice === "overwrite") {
|
|
3753
|
+
p15__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
|
+
fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
|
|
3759
|
+
}
|
|
3760
|
+
fs12__default.default.rmSync(targetDir, { recursive: true, force: true });
|
|
3761
|
+
}
|
|
3762
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3230
3766
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3231
3767
|
const projectRoot = findProjectRoot();
|
|
3232
3768
|
let type = typeArg?.trim().toLowerCase();
|
|
3233
3769
|
if (type === "features") type = "feature";
|
|
3234
3770
|
if (!type) {
|
|
3235
3771
|
type = handleCancel(
|
|
3236
|
-
await
|
|
3772
|
+
await importPrompts.select({
|
|
3237
3773
|
message: "What kind of module would you like to import?",
|
|
3238
3774
|
options: [
|
|
3239
3775
|
{
|
|
@@ -3251,19 +3787,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3251
3787
|
);
|
|
3252
3788
|
}
|
|
3253
3789
|
if (type !== "shared" && type !== "feature") {
|
|
3254
|
-
|
|
3790
|
+
p15__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3255
3791
|
process.exit(1);
|
|
3256
3792
|
}
|
|
3257
3793
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3258
|
-
const s =
|
|
3259
|
-
s.start(`Connecting to registry at ${
|
|
3794
|
+
const s = p15__namespace.spinner();
|
|
3795
|
+
s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
|
|
3260
3796
|
let index;
|
|
3261
3797
|
try {
|
|
3262
3798
|
index = await fetchRegistryIndex(registryBase);
|
|
3263
3799
|
s.stop("Registry index loaded.");
|
|
3264
3800
|
} catch (err) {
|
|
3265
|
-
s.stop(
|
|
3266
|
-
|
|
3801
|
+
s.stop(pc15__default.default.red("Failed to load registry index."));
|
|
3802
|
+
p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
|
|
3267
3803
|
process.exit(1);
|
|
3268
3804
|
}
|
|
3269
3805
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3271,11 +3807,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3271
3807
|
let name = nameArg?.trim();
|
|
3272
3808
|
if (!name) {
|
|
3273
3809
|
if (availableList.length === 0) {
|
|
3274
|
-
|
|
3810
|
+
p15__namespace.log.warn(`No ${type} modules available in the registry.`);
|
|
3275
3811
|
return;
|
|
3276
3812
|
}
|
|
3277
3813
|
name = handleCancel(
|
|
3278
|
-
await
|
|
3814
|
+
await importPrompts.select({
|
|
3279
3815
|
message: `Select a ${type} to import:`,
|
|
3280
3816
|
options: availableList.map((item) => ({
|
|
3281
3817
|
value: item.name,
|
|
@@ -3287,122 +3823,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3287
3823
|
}
|
|
3288
3824
|
if (!candidateNames.includes(name)) {
|
|
3289
3825
|
const closest = findClosestMatch(name, candidateNames);
|
|
3290
|
-
|
|
3826
|
+
p15__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3291
3827
|
if (closest) {
|
|
3292
|
-
|
|
3828
|
+
p15__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
|
|
3293
3829
|
}
|
|
3294
3830
|
process.exit(1);
|
|
3295
3831
|
}
|
|
3296
3832
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3297
3833
|
const totalWrittenFiles = [];
|
|
3298
3834
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3299
|
-
async function importModuleRecursive(modType,
|
|
3300
|
-
const modKey = `${modType}:${
|
|
3835
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3836
|
+
const modKey = `${modType}:${targetName}`;
|
|
3301
3837
|
if (importedModules.has(modKey)) return;
|
|
3302
|
-
|
|
3838
|
+
p15__namespace.log.step(
|
|
3839
|
+
`Importing ${modType}: ${pc15__default.default.cyan(targetName)}${targetName !== registryName ? pc15__default.default.dim(` (from ${registryName})`) : ""}...`
|
|
3840
|
+
);
|
|
3303
3841
|
let manifest;
|
|
3304
3842
|
try {
|
|
3305
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3843
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3306
3844
|
} catch (err) {
|
|
3307
|
-
|
|
3845
|
+
p15__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3308
3846
|
process.exit(1);
|
|
3309
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
|
+
}
|
|
3310
3853
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3311
3854
|
for (const dep of sharedDeps) {
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3855
|
+
await resolveDependency(
|
|
3856
|
+
"shared",
|
|
3857
|
+
dep,
|
|
3858
|
+
aliasMap,
|
|
3859
|
+
projectRoot,
|
|
3860
|
+
flags,
|
|
3861
|
+
importModuleRecursive,
|
|
3862
|
+
importedModules,
|
|
3863
|
+
registryBase,
|
|
3864
|
+
index
|
|
3865
|
+
);
|
|
3317
3866
|
}
|
|
3318
3867
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3319
3868
|
for (const dep of featureDeps) {
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3869
|
+
await resolveDependency(
|
|
3870
|
+
"feature",
|
|
3871
|
+
dep,
|
|
3872
|
+
aliasMap,
|
|
3873
|
+
projectRoot,
|
|
3874
|
+
flags,
|
|
3875
|
+
importModuleRecursive,
|
|
3876
|
+
importedModules,
|
|
3877
|
+
registryBase,
|
|
3878
|
+
index
|
|
3879
|
+
);
|
|
3325
3880
|
}
|
|
3326
3881
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3327
3882
|
requiredNpmPackages.add(pkg);
|
|
3328
3883
|
}
|
|
3329
3884
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3330
|
-
const targetDir =
|
|
3885
|
+
const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3886
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3331
3887
|
for (const file of manifest.files || []) {
|
|
3332
3888
|
if (file === "registry.json") continue;
|
|
3333
|
-
const remoteFilePath = `${folder}/${
|
|
3334
|
-
const localFilePath =
|
|
3335
|
-
if (
|
|
3336
|
-
|
|
3889
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3890
|
+
const localFilePath = path2__default.default.join(targetDir, file);
|
|
3891
|
+
if (fs12__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
3892
|
+
p15__namespace.log.warn(
|
|
3893
|
+
` \u21B3 Skipping existing file: ${pc15__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3894
|
+
);
|
|
3337
3895
|
continue;
|
|
3338
3896
|
}
|
|
3339
|
-
|
|
3897
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3898
|
+
content = rewriteImports(content, aliasMap);
|
|
3340
3899
|
if (!flags.dryRun) {
|
|
3341
|
-
|
|
3342
|
-
|
|
3900
|
+
fs12__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
|
|
3901
|
+
fs12__default.default.writeFileSync(localFilePath, content, "utf-8");
|
|
3343
3902
|
}
|
|
3344
|
-
const displayPath =
|
|
3903
|
+
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
3345
3904
|
totalWrittenFiles.push(displayPath);
|
|
3346
|
-
|
|
3905
|
+
p15__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
|
|
3347
3906
|
}
|
|
3348
3907
|
importedModules.add(modKey);
|
|
3349
3908
|
}
|
|
3350
|
-
await importModuleRecursive(type, name);
|
|
3909
|
+
await importModuleRecursive(type, name, name);
|
|
3351
3910
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3352
3911
|
if (missingNpm.length > 0) {
|
|
3353
3912
|
if (flags.skipInstall || flags.dryRun) {
|
|
3354
|
-
|
|
3913
|
+
p15__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
|
|
3355
3914
|
} else {
|
|
3356
3915
|
try {
|
|
3357
3916
|
installNpmPackages(projectRoot, missingNpm);
|
|
3358
3917
|
} catch (err) {
|
|
3359
|
-
|
|
3918
|
+
p15__namespace.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3360
3919
|
}
|
|
3361
3920
|
}
|
|
3362
3921
|
}
|
|
3363
3922
|
if (flags.dryRun) {
|
|
3364
|
-
|
|
3923
|
+
p15__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
3365
3924
|
} else {
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3925
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3926
|
+
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, name));
|
|
3927
|
+
p15__namespace.log.success(
|
|
3928
|
+
pc15__default.default.green(
|
|
3929
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3369
3930
|
)
|
|
3370
3931
|
);
|
|
3371
3932
|
}
|
|
3372
3933
|
}
|
|
3934
|
+
function loadMasterIndex(projectRoot) {
|
|
3935
|
+
const indexPath = path2__default.default.join(projectRoot, "index.json");
|
|
3936
|
+
if (!fs12__default.default.existsSync(indexPath)) {
|
|
3937
|
+
const initialIndex = { shared: [], features: [] };
|
|
3938
|
+
fs12__default.default.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
|
|
3939
|
+
return initialIndex;
|
|
3940
|
+
}
|
|
3941
|
+
return JSON.parse(fs12__default.default.readFileSync(indexPath, "utf-8"));
|
|
3942
|
+
}
|
|
3943
|
+
function saveMasterIndex(projectRoot, index) {
|
|
3944
|
+
const indexPath = path2__default.default.join(projectRoot, "index.json");
|
|
3945
|
+
index.shared.sort((a, b) => a.name.localeCompare(b.name));
|
|
3946
|
+
index.features.sort((a, b) => a.name.localeCompare(b.name));
|
|
3947
|
+
fs12__default.default.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
3948
|
+
}
|
|
3949
|
+
function discoverDiskModules(projectRoot) {
|
|
3950
|
+
const results = [];
|
|
3951
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
3952
|
+
if (fs12__default.default.existsSync(sharedDir)) {
|
|
3953
|
+
const entries = fs12__default.default.readdirSync(sharedDir, { withFileTypes: true });
|
|
3954
|
+
for (const entry of entries) {
|
|
3955
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3956
|
+
results.push({ name: entry.name, type: "shared", dir: path2__default.default.join(sharedDir, entry.name) });
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
3961
|
+
if (fs12__default.default.existsSync(featuresDir)) {
|
|
3962
|
+
const entries = fs12__default.default.readdirSync(featuresDir, { withFileTypes: true });
|
|
3963
|
+
for (const entry of entries) {
|
|
3964
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3965
|
+
results.push({ name: entry.name, type: "feature", dir: path2__default.default.join(featuresDir, entry.name) });
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
return results;
|
|
3970
|
+
}
|
|
3971
|
+
function checkGitStatusForDir(projectRoot, dirPath) {
|
|
3972
|
+
try {
|
|
3973
|
+
const relPath = path2__default.default.relative(projectRoot, dirPath);
|
|
3974
|
+
const output = child_process.execSync(`git status --porcelain -- "${relPath}"`, {
|
|
3975
|
+
cwd: projectRoot,
|
|
3976
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3977
|
+
encoding: "utf-8"
|
|
3978
|
+
}).trim();
|
|
3979
|
+
if (!output) {
|
|
3980
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3981
|
+
}
|
|
3982
|
+
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
3983
|
+
return { hasChanges: true, filesCount: lines.length };
|
|
3984
|
+
} catch {
|
|
3985
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
function getRegistryDiff(projectRoot) {
|
|
3989
|
+
const index = loadMasterIndex(projectRoot);
|
|
3990
|
+
const diskModules = discoverDiskModules(projectRoot);
|
|
3991
|
+
const diskMap = /* @__PURE__ */ new Map();
|
|
3992
|
+
for (const mod of diskModules) {
|
|
3993
|
+
diskMap.set(`${mod.type}:${mod.name}`, mod);
|
|
3994
|
+
}
|
|
3995
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3996
|
+
for (const item of index.shared) {
|
|
3997
|
+
allKeys.add(`shared:${item.name}`);
|
|
3998
|
+
}
|
|
3999
|
+
for (const item of index.features) {
|
|
4000
|
+
allKeys.add(`feature:${item.name}`);
|
|
4001
|
+
}
|
|
4002
|
+
for (const mod of diskModules) {
|
|
4003
|
+
allKeys.add(`${mod.type}:${mod.name}`);
|
|
4004
|
+
}
|
|
4005
|
+
const statuses = [];
|
|
4006
|
+
for (const key of allKeys) {
|
|
4007
|
+
const [typeStr, name] = key.split(":");
|
|
4008
|
+
const type = typeStr;
|
|
4009
|
+
const registeredEntry = (type === "shared" ? index.shared : index.features).find(
|
|
4010
|
+
(e) => e.name === name
|
|
4011
|
+
);
|
|
4012
|
+
const isRegistered = Boolean(registeredEntry);
|
|
4013
|
+
const diskInfo = diskMap.get(key);
|
|
4014
|
+
const existsOnDisk = Boolean(diskInfo && fs12__default.default.existsSync(diskInfo.dir));
|
|
4015
|
+
let hasUncommittedGitChanges = false;
|
|
4016
|
+
let uncommittedFilesCount = 0;
|
|
4017
|
+
let hasIntegrityDrift = false;
|
|
4018
|
+
let localIntegrity;
|
|
4019
|
+
if (existsOnDisk && diskInfo) {
|
|
4020
|
+
const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
|
|
4021
|
+
hasUncommittedGitChanges = git.hasChanges;
|
|
4022
|
+
uncommittedFilesCount = git.filesCount;
|
|
4023
|
+
localIntegrity = computeDirectoryHash(diskInfo.dir);
|
|
4024
|
+
if (isRegistered && registeredEntry?.integrity) {
|
|
4025
|
+
hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
statuses.push({
|
|
4029
|
+
name,
|
|
4030
|
+
type,
|
|
4031
|
+
isRegistered,
|
|
4032
|
+
existsOnDisk,
|
|
4033
|
+
hasUncommittedGitChanges,
|
|
4034
|
+
uncommittedFilesCount,
|
|
4035
|
+
hasIntegrityDrift,
|
|
4036
|
+
localIntegrity,
|
|
4037
|
+
registeredIntegrity: registeredEntry?.integrity
|
|
4038
|
+
});
|
|
4039
|
+
}
|
|
4040
|
+
return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
4041
|
+
}
|
|
4042
|
+
async function registerModule(typeArg, nameArg, flags = {}) {
|
|
4043
|
+
const projectRoot = findProjectRoot();
|
|
4044
|
+
const index = loadMasterIndex(projectRoot);
|
|
4045
|
+
let type = typeArg?.trim();
|
|
4046
|
+
if (!type) {
|
|
4047
|
+
type = handleCancel(
|
|
4048
|
+
await p15__namespace.select({
|
|
4049
|
+
message: "Module type to register:",
|
|
4050
|
+
options: [
|
|
4051
|
+
{ value: "feature", label: "Feature" },
|
|
4052
|
+
{ value: "shared", label: "Shared Module" }
|
|
4053
|
+
]
|
|
4054
|
+
})
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4058
|
+
const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
|
|
4059
|
+
const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
|
|
4060
|
+
let name = nameArg?.trim();
|
|
4061
|
+
if (!name) {
|
|
4062
|
+
if (unregistered.length > 0) {
|
|
4063
|
+
const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
|
|
4064
|
+
opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
|
|
4065
|
+
const choice = handleCancel(
|
|
4066
|
+
await p15__namespace.select({
|
|
4067
|
+
message: `Select a ${type} to register:`,
|
|
4068
|
+
options: opts
|
|
4069
|
+
})
|
|
4070
|
+
);
|
|
4071
|
+
if (choice === "__custom__") {
|
|
4072
|
+
name = handleCancel(
|
|
4073
|
+
await p15__namespace.text({
|
|
4074
|
+
message: `Enter ${type} name:`,
|
|
4075
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4076
|
+
})
|
|
4077
|
+
).trim();
|
|
4078
|
+
} else {
|
|
4079
|
+
name = choice;
|
|
4080
|
+
}
|
|
4081
|
+
} else {
|
|
4082
|
+
name = handleCancel(
|
|
4083
|
+
await p15__namespace.text({
|
|
4084
|
+
message: `Enter ${type} name to register:`,
|
|
4085
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4086
|
+
})
|
|
4087
|
+
).trim();
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4091
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4092
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4093
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4094
|
+
process.exit(1);
|
|
4095
|
+
}
|
|
4096
|
+
const files = getAllFiles(modDir);
|
|
4097
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4098
|
+
const manifestPath = path2__default.default.join(modDir, "registry.json");
|
|
4099
|
+
let manifest;
|
|
4100
|
+
if (fs12__default.default.existsSync(manifestPath)) {
|
|
4101
|
+
manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
|
|
4102
|
+
} else {
|
|
4103
|
+
manifest = {
|
|
4104
|
+
name,
|
|
4105
|
+
type,
|
|
4106
|
+
description: `${name} ${type} module`,
|
|
4107
|
+
version: "1.0.0",
|
|
4108
|
+
dependencies: {
|
|
4109
|
+
npm: [],
|
|
4110
|
+
shared: [],
|
|
4111
|
+
features: []
|
|
4112
|
+
},
|
|
4113
|
+
files: []
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
manifest.name = name;
|
|
4117
|
+
manifest.type = type;
|
|
4118
|
+
manifest.files = files;
|
|
4119
|
+
manifest.integrity = integrity;
|
|
4120
|
+
fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4121
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4122
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4123
|
+
if (existingIdx >= 0) {
|
|
4124
|
+
targetList[existingIdx] = {
|
|
4125
|
+
name,
|
|
4126
|
+
description: manifest.description,
|
|
4127
|
+
version: manifest.version,
|
|
4128
|
+
integrity
|
|
4129
|
+
};
|
|
4130
|
+
} else {
|
|
4131
|
+
targetList.push({
|
|
4132
|
+
name,
|
|
4133
|
+
description: manifest.description,
|
|
4134
|
+
version: manifest.version,
|
|
4135
|
+
integrity
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
saveMasterIndex(projectRoot, index);
|
|
4139
|
+
p15__namespace.log.success(
|
|
4140
|
+
pc15__default.default.green(
|
|
4141
|
+
`\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
|
|
4142
|
+
)
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
async function commitModule(typeArg, nameArg, flags = {}) {
|
|
4146
|
+
const projectRoot = findProjectRoot();
|
|
4147
|
+
let type = typeArg?.trim();
|
|
4148
|
+
if (!type) {
|
|
4149
|
+
type = handleCancel(
|
|
4150
|
+
await p15__namespace.select({
|
|
4151
|
+
message: "Module type to commit:",
|
|
4152
|
+
options: [
|
|
4153
|
+
{ value: "feature", label: "Feature" },
|
|
4154
|
+
{ value: "shared", label: "Shared Module" }
|
|
4155
|
+
]
|
|
4156
|
+
})
|
|
4157
|
+
);
|
|
4158
|
+
}
|
|
4159
|
+
let name = nameArg?.trim();
|
|
4160
|
+
if (!name) {
|
|
4161
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4162
|
+
const opts = diskModules.map((m) => {
|
|
4163
|
+
const git = checkGitStatusForDir(projectRoot, m.dir);
|
|
4164
|
+
return {
|
|
4165
|
+
value: m.name,
|
|
4166
|
+
label: m.name,
|
|
4167
|
+
hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
|
|
4168
|
+
};
|
|
4169
|
+
});
|
|
4170
|
+
name = handleCancel(
|
|
4171
|
+
await p15__namespace.select({
|
|
4172
|
+
message: `Select ${type} to commit:`,
|
|
4173
|
+
options: opts
|
|
4174
|
+
})
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4178
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4179
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4180
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4181
|
+
process.exit(1);
|
|
4182
|
+
}
|
|
4183
|
+
const relModDir = path2__default.default.relative(projectRoot, modDir);
|
|
4184
|
+
const gitStatus = checkGitStatusForDir(projectRoot, modDir);
|
|
4185
|
+
if (!gitStatus.hasChanges) {
|
|
4186
|
+
p15__namespace.log.warn(pc15__default.default.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
let message = flags.message?.trim();
|
|
4190
|
+
if (!message) {
|
|
4191
|
+
message = handleCancel(
|
|
4192
|
+
await p15__namespace.text({
|
|
4193
|
+
message: `Commit message for ${type}/${name}:`,
|
|
4194
|
+
placeholder: `e.g. feat(${name}): update use cases and dependencies`,
|
|
4195
|
+
validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
|
|
4196
|
+
})
|
|
4197
|
+
).trim();
|
|
4198
|
+
}
|
|
4199
|
+
const s = p15__namespace.spinner();
|
|
4200
|
+
s.start(`Committing '${type}/${name}' to git repository...`);
|
|
4201
|
+
try {
|
|
4202
|
+
child_process.execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
4203
|
+
child_process.execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
|
|
4204
|
+
cwd: projectRoot,
|
|
4205
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4206
|
+
});
|
|
4207
|
+
s.stop(`Changes for '${type}/${name}' committed to git.`);
|
|
4208
|
+
p15__namespace.log.success(pc15__default.default.green(`\u2713 Committed '${type}/${name}': "${message}"`));
|
|
4209
|
+
} catch (err) {
|
|
4210
|
+
s.stop(pc15__default.default.red("Git commit failed."));
|
|
4211
|
+
p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
|
|
4212
|
+
process.exit(1);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
async function registerModuleChanges(typeArg, nameArg, flags = {}) {
|
|
4216
|
+
const projectRoot = findProjectRoot();
|
|
4217
|
+
const index = loadMasterIndex(projectRoot);
|
|
4218
|
+
let type = typeArg?.trim();
|
|
4219
|
+
if (!type) {
|
|
4220
|
+
type = handleCancel(
|
|
4221
|
+
await p15__namespace.select({
|
|
4222
|
+
message: "Module type to update:",
|
|
4223
|
+
options: [
|
|
4224
|
+
{ value: "feature", label: "Feature" },
|
|
4225
|
+
{ value: "shared", label: "Shared Module" }
|
|
4226
|
+
]
|
|
4227
|
+
})
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
let name = nameArg?.trim();
|
|
4231
|
+
if (!name) {
|
|
4232
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4233
|
+
const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
|
|
4234
|
+
const opts = diskModules.map((m) => {
|
|
4235
|
+
const d = diffs.find((item) => item.name === m.name);
|
|
4236
|
+
return {
|
|
4237
|
+
value: m.name,
|
|
4238
|
+
label: m.name,
|
|
4239
|
+
hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
|
|
4240
|
+
};
|
|
4241
|
+
});
|
|
4242
|
+
name = handleCancel(
|
|
4243
|
+
await p15__namespace.select({
|
|
4244
|
+
message: `Select ${type} to register changes:`,
|
|
4245
|
+
options: opts
|
|
4246
|
+
})
|
|
4247
|
+
);
|
|
4248
|
+
}
|
|
4249
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4250
|
+
const modDir = path2__default.default.join(folder, name);
|
|
4251
|
+
if (!fs12__default.default.existsSync(modDir)) {
|
|
4252
|
+
p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
|
|
4253
|
+
process.exit(1);
|
|
4254
|
+
}
|
|
4255
|
+
const files = getAllFiles(modDir);
|
|
4256
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4257
|
+
const manifestPath = path2__default.default.join(modDir, "registry.json");
|
|
4258
|
+
let manifest;
|
|
4259
|
+
if (fs12__default.default.existsSync(manifestPath)) {
|
|
4260
|
+
manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
|
|
4261
|
+
} else {
|
|
4262
|
+
manifest = {
|
|
4263
|
+
name,
|
|
4264
|
+
type,
|
|
4265
|
+
description: `${name} ${type} module`,
|
|
4266
|
+
version: "1.0.0",
|
|
4267
|
+
dependencies: { npm: [], shared: [], features: [] },
|
|
4268
|
+
files: []
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
manifest.files = files;
|
|
4272
|
+
manifest.integrity = integrity;
|
|
4273
|
+
if (flags.version) {
|
|
4274
|
+
manifest.version = flags.version;
|
|
4275
|
+
}
|
|
4276
|
+
fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4277
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4278
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4279
|
+
if (existingIdx >= 0) {
|
|
4280
|
+
targetList[existingIdx] = {
|
|
4281
|
+
name,
|
|
4282
|
+
description: manifest.description,
|
|
4283
|
+
version: manifest.version,
|
|
4284
|
+
integrity
|
|
4285
|
+
};
|
|
4286
|
+
} else {
|
|
4287
|
+
targetList.push({
|
|
4288
|
+
name,
|
|
4289
|
+
description: manifest.description,
|
|
4290
|
+
version: manifest.version,
|
|
4291
|
+
integrity
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4294
|
+
saveMasterIndex(projectRoot, index);
|
|
4295
|
+
p15__namespace.log.success(
|
|
4296
|
+
pc15__default.default.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
async function unregisterModule(typeArg, nameArg, flags = {}) {
|
|
4300
|
+
const projectRoot = findProjectRoot();
|
|
4301
|
+
const index = loadMasterIndex(projectRoot);
|
|
4302
|
+
let type = typeArg?.trim();
|
|
4303
|
+
if (!type) {
|
|
4304
|
+
type = handleCancel(
|
|
4305
|
+
await p15__namespace.select({
|
|
4306
|
+
message: "Module type to unregister:",
|
|
4307
|
+
options: [
|
|
4308
|
+
{ value: "feature", label: "Feature" },
|
|
4309
|
+
{ value: "shared", label: "Shared Module" }
|
|
4310
|
+
]
|
|
4311
|
+
})
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
const registeredList = type === "shared" ? index.shared : index.features;
|
|
4315
|
+
if (registeredList.length === 0) {
|
|
4316
|
+
p15__namespace.log.warn(`No registered ${type} modules found in index.json.`);
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
let name = nameArg?.trim();
|
|
4320
|
+
if (!name) {
|
|
4321
|
+
name = handleCancel(
|
|
4322
|
+
await p15__namespace.select({
|
|
4323
|
+
message: `Select ${type} to unregister from index.json:`,
|
|
4324
|
+
options: registeredList.map((item) => ({
|
|
4325
|
+
value: item.name,
|
|
4326
|
+
label: item.name,
|
|
4327
|
+
hint: item.description
|
|
4328
|
+
}))
|
|
4329
|
+
})
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
if (type === "shared") {
|
|
4333
|
+
index.shared = index.shared.filter((item) => item.name !== name);
|
|
4334
|
+
} else {
|
|
4335
|
+
index.features = index.features.filter((item) => item.name !== name);
|
|
4336
|
+
}
|
|
4337
|
+
saveMasterIndex(projectRoot, index);
|
|
4338
|
+
p15__namespace.log.success(
|
|
4339
|
+
pc15__default.default.green(
|
|
4340
|
+
`\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
|
|
4341
|
+
)
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4344
|
+
function diffRegistry(flags = {}) {
|
|
4345
|
+
const projectRoot = findProjectRoot();
|
|
4346
|
+
const diffs = getRegistryDiff(projectRoot);
|
|
4347
|
+
p15__namespace.log.step(pc15__default.default.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
|
|
4348
|
+
const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
|
|
4349
|
+
const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
|
|
4350
|
+
const committedDrift = diffs.filter(
|
|
4351
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
|
|
4352
|
+
);
|
|
4353
|
+
const upToDate = diffs.filter(
|
|
4354
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
|
|
4355
|
+
);
|
|
4356
|
+
const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
|
|
4357
|
+
if (unregistered.length > 0) {
|
|
4358
|
+
p15__namespace.log.message(pc15__default.default.yellow(`
|
|
4359
|
+
\u{1F4E6} Unregistered Modules (${unregistered.length}):`));
|
|
4360
|
+
for (const mod of unregistered) {
|
|
4361
|
+
p15__namespace.log.message(` ${pc15__default.default.red("\u25CF")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(not registered in index.json)")}`);
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
if (uncommitted.length > 0) {
|
|
4365
|
+
p15__namespace.log.message(pc15__default.default.yellow(`
|
|
4366
|
+
\u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
|
|
4367
|
+
for (const mod of uncommitted) {
|
|
4368
|
+
p15__namespace.log.message(
|
|
4369
|
+
` ${pc15__default.default.yellow("\u25B2")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
if (committedDrift.length > 0) {
|
|
4374
|
+
p15__namespace.log.message(pc15__default.default.cyan(`
|
|
4375
|
+
\u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
|
|
4376
|
+
for (const mod of committedDrift) {
|
|
4377
|
+
p15__namespace.log.message(
|
|
4378
|
+
` ${pc15__default.default.cyan("\u25C6")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(
|
|
4379
|
+
`local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
|
|
4380
|
+
)}`
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
if (missing.length > 0) {
|
|
4385
|
+
p15__namespace.log.message(pc15__default.default.red(`
|
|
4386
|
+
\u26A0\uFE0F Missing on Disk (${missing.length}):`));
|
|
4387
|
+
for (const mod of missing) {
|
|
4388
|
+
p15__namespace.log.message(` ${pc15__default.default.red("\u2715")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(in index.json, but folder missing)")}`);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
if (upToDate.length > 0) {
|
|
4392
|
+
p15__namespace.log.message(pc15__default.default.green(`
|
|
4393
|
+
\u2713 Up to Date & Registered (${upToDate.length}):`));
|
|
4394
|
+
for (const mod of upToDate) {
|
|
4395
|
+
p15__namespace.log.message(` ${pc15__default.default.green("\u2714")} ${mod.type}/${mod.name} ${pc15__default.default.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
p15__namespace.log.message("");
|
|
4399
|
+
}
|
|
4400
|
+
async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
|
|
4401
|
+
let action = actionArg?.trim().toLowerCase();
|
|
4402
|
+
if (!action) {
|
|
4403
|
+
action = handleCancel(
|
|
4404
|
+
await p15__namespace.select({
|
|
4405
|
+
message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
|
|
4406
|
+
options: [
|
|
4407
|
+
{
|
|
4408
|
+
value: "diff",
|
|
4409
|
+
label: "Inspect Diff & Status",
|
|
4410
|
+
hint: "Summary of unregistered, uncommitted, and drifted modules"
|
|
4411
|
+
},
|
|
4412
|
+
{
|
|
4413
|
+
value: "register",
|
|
4414
|
+
label: "Register Module",
|
|
4415
|
+
hint: "Add an existing module to index.json with integrity checksum"
|
|
4416
|
+
},
|
|
4417
|
+
{
|
|
4418
|
+
value: "commit",
|
|
4419
|
+
label: "Commit Changes",
|
|
4420
|
+
hint: "Commit a module's working changes to git in the background"
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
value: "register-changes",
|
|
4424
|
+
label: "Register Changes",
|
|
4425
|
+
hint: "Update index.json & registry.json with latest checksums"
|
|
4426
|
+
},
|
|
4427
|
+
{
|
|
4428
|
+
value: "unregister",
|
|
4429
|
+
label: "Unregister Module",
|
|
4430
|
+
hint: "Remove module from index.json (source code stays on disk)"
|
|
4431
|
+
}
|
|
4432
|
+
]
|
|
4433
|
+
})
|
|
4434
|
+
);
|
|
4435
|
+
}
|
|
4436
|
+
switch (action) {
|
|
4437
|
+
case "diff":
|
|
4438
|
+
case "status":
|
|
4439
|
+
diffRegistry(flags);
|
|
4440
|
+
break;
|
|
4441
|
+
case "register":
|
|
4442
|
+
await registerModule(typeArg, nameArg, flags);
|
|
4443
|
+
break;
|
|
4444
|
+
case "commit":
|
|
4445
|
+
await commitModule(typeArg, nameArg, flags);
|
|
4446
|
+
break;
|
|
4447
|
+
case "register-changes":
|
|
4448
|
+
case "update":
|
|
4449
|
+
await registerModuleChanges(typeArg, nameArg, flags);
|
|
4450
|
+
break;
|
|
4451
|
+
case "unregister":
|
|
4452
|
+
await unregisterModule(typeArg, nameArg, flags);
|
|
4453
|
+
break;
|
|
4454
|
+
default:
|
|
4455
|
+
p15__namespace.log.error(
|
|
4456
|
+
pc15__default.default.red(
|
|
4457
|
+
`Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
|
|
4458
|
+
)
|
|
4459
|
+
);
|
|
4460
|
+
process.exit(1);
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
3373
4463
|
|
|
3374
4464
|
// src/cli.ts
|
|
3375
4465
|
function createCli() {
|
|
3376
4466
|
const cli = cac.cac("mason");
|
|
3377
|
-
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) => {
|
|
4467
|
+
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) => {
|
|
3378
4468
|
if (args && args.length > 0) {
|
|
3379
|
-
|
|
3380
|
-
|
|
4469
|
+
p15__namespace.log.error(
|
|
4470
|
+
pc15__default.default.red(
|
|
3381
4471
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3382
4472
|
)
|
|
3383
4473
|
);
|
|
3384
4474
|
process.exit(1);
|
|
3385
4475
|
}
|
|
3386
4476
|
try {
|
|
4477
|
+
const projectRoot = findProjectRoot();
|
|
4478
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
4479
|
+
if (mode === "registry") {
|
|
4480
|
+
const choice2 = handleCancel(
|
|
4481
|
+
await p15__namespace.select({
|
|
4482
|
+
message: "\u{1F3DB}\uFE0F Mason Registry Maintainer: What would you like to do?",
|
|
4483
|
+
options: [
|
|
4484
|
+
{
|
|
4485
|
+
value: "diff",
|
|
4486
|
+
label: "Inspect Diff & Status",
|
|
4487
|
+
hint: "Overview of unregistered, uncommitted, or drifted modules"
|
|
4488
|
+
},
|
|
4489
|
+
{
|
|
4490
|
+
value: "create_feature",
|
|
4491
|
+
label: "Initialize New Feature",
|
|
4492
|
+
hint: "Scaffold a new feature slice under features/"
|
|
4493
|
+
},
|
|
4494
|
+
{
|
|
4495
|
+
value: "create_shared",
|
|
4496
|
+
label: "Initialize New Shared Module",
|
|
4497
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
4498
|
+
},
|
|
4499
|
+
{
|
|
4500
|
+
value: "create_usecase",
|
|
4501
|
+
label: "Create New Use Case",
|
|
4502
|
+
hint: "Add a new use case to an existing feature"
|
|
4503
|
+
},
|
|
4504
|
+
{
|
|
4505
|
+
value: "register",
|
|
4506
|
+
label: "Register Module",
|
|
4507
|
+
hint: "Add a module to index.json with integrity checksum"
|
|
4508
|
+
},
|
|
4509
|
+
{
|
|
4510
|
+
value: "commit",
|
|
4511
|
+
label: "Commit Module Changes",
|
|
4512
|
+
hint: "Commit working changes in a module to git"
|
|
4513
|
+
},
|
|
4514
|
+
{
|
|
4515
|
+
value: "register_changes",
|
|
4516
|
+
label: "Register Changes",
|
|
4517
|
+
hint: "Recompute checksums and update index.json"
|
|
4518
|
+
},
|
|
4519
|
+
{
|
|
4520
|
+
value: "unregister",
|
|
4521
|
+
label: "Unregister Module",
|
|
4522
|
+
hint: "Remove from index.json but keep source on disk"
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
value: "import",
|
|
4526
|
+
label: "Import from Remote Registry",
|
|
4527
|
+
hint: "Download a module from upstream"
|
|
4528
|
+
}
|
|
4529
|
+
]
|
|
4530
|
+
})
|
|
4531
|
+
);
|
|
4532
|
+
switch (choice2) {
|
|
4533
|
+
case "diff":
|
|
4534
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4535
|
+
break;
|
|
4536
|
+
case "create_feature":
|
|
4537
|
+
await handleCreate("feature", void 0, flags);
|
|
4538
|
+
break;
|
|
4539
|
+
case "create_shared":
|
|
4540
|
+
await handleCreate("shared", void 0, flags);
|
|
4541
|
+
break;
|
|
4542
|
+
case "create_usecase":
|
|
4543
|
+
await handleCreate("usecase", void 0, flags);
|
|
4544
|
+
break;
|
|
4545
|
+
case "register":
|
|
4546
|
+
await handleRegistry("register", void 0, void 0, flags);
|
|
4547
|
+
break;
|
|
4548
|
+
case "commit":
|
|
4549
|
+
await handleRegistry("commit", void 0, void 0, flags);
|
|
4550
|
+
break;
|
|
4551
|
+
case "register_changes":
|
|
4552
|
+
await handleRegistry("register-changes", void 0, void 0, flags);
|
|
4553
|
+
break;
|
|
4554
|
+
case "unregister":
|
|
4555
|
+
await handleRegistry("unregister", void 0, void 0, flags);
|
|
4556
|
+
break;
|
|
4557
|
+
case "import":
|
|
4558
|
+
await handleImport(void 0, void 0, flags);
|
|
4559
|
+
break;
|
|
4560
|
+
}
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
3387
4563
|
const choice = handleCancel(
|
|
3388
|
-
await
|
|
4564
|
+
await p15__namespace.select({
|
|
3389
4565
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3390
4566
|
options: [
|
|
3391
4567
|
{
|
|
3392
4568
|
value: "create",
|
|
3393
4569
|
label: "Create local component",
|
|
3394
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
4570
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3395
4571
|
},
|
|
3396
4572
|
{
|
|
3397
4573
|
value: "import",
|
|
3398
4574
|
label: "Import from registry",
|
|
3399
4575
|
hint: "Download and link pre-built shared modules or features from the registry"
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
value: "registry",
|
|
4579
|
+
label: "Registry management",
|
|
4580
|
+
hint: "Inspect diff, register modules, or commit module changes"
|
|
3400
4581
|
}
|
|
3401
4582
|
]
|
|
3402
4583
|
})
|
|
3403
4584
|
);
|
|
3404
4585
|
if (choice === "create") {
|
|
3405
4586
|
await handleCreate(void 0, void 0, flags);
|
|
4587
|
+
} else if (choice === "registry") {
|
|
4588
|
+
await handleRegistry(void 0, void 0, void 0, flags);
|
|
3406
4589
|
} else {
|
|
3407
4590
|
await handleImport(void 0, void 0, flags);
|
|
3408
4591
|
}
|
|
@@ -3412,8 +4595,8 @@ function createCli() {
|
|
|
3412
4595
|
});
|
|
3413
4596
|
cli.command(
|
|
3414
4597
|
"create [type] [name]",
|
|
3415
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3416
|
-
).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 = {}) => {
|
|
4598
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
4599
|
+
).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 = {}) => {
|
|
3417
4600
|
try {
|
|
3418
4601
|
await handleCreate(type, name, flags);
|
|
3419
4602
|
} catch (err) {
|
|
@@ -3430,8 +4613,28 @@ function createCli() {
|
|
|
3430
4613
|
handleCliError(err);
|
|
3431
4614
|
}
|
|
3432
4615
|
});
|
|
4616
|
+
cli.command(
|
|
4617
|
+
"registry [action] [type] [name]",
|
|
4618
|
+
"Registry & version management (diff, register, commit, register-changes, unregister)"
|
|
4619
|
+
).option("-m, --message <message>", "Commit message for 'commit' command").option("-y, --yes", "Accept defaults and skip interactive prompts").example("mason registry diff").example("mason registry register feature authn").example("mason registry commit shared hasher -m 'Add bcrypt engine'").example("mason registry register-changes shared hasher").example("mason registry unregister feature oldFeature").action(
|
|
4620
|
+
async (action, type, name, flags = {}) => {
|
|
4621
|
+
try {
|
|
4622
|
+
await handleRegistry(action, type, name, flags);
|
|
4623
|
+
} catch (err) {
|
|
4624
|
+
handleCliError(err);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
);
|
|
4628
|
+
cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
|
|
4629
|
+
try {
|
|
4630
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4631
|
+
} catch (err) {
|
|
4632
|
+
handleCliError(err);
|
|
4633
|
+
}
|
|
4634
|
+
});
|
|
3433
4635
|
const createSubtypes = [
|
|
3434
4636
|
"feature",
|
|
4637
|
+
"shared",
|
|
3435
4638
|
"usecase",
|
|
3436
4639
|
"domain",
|
|
3437
4640
|
"infrastructure",
|
|
@@ -3452,7 +4655,7 @@ function createCli() {
|
|
|
3452
4655
|
});
|
|
3453
4656
|
}
|
|
3454
4657
|
cli.help();
|
|
3455
|
-
cli.version("1.0.
|
|
4658
|
+
cli.version("1.0.13");
|
|
3456
4659
|
return cli;
|
|
3457
4660
|
}
|
|
3458
4661
|
function handleCliError(err) {
|
|
@@ -3460,7 +4663,7 @@ function handleCliError(err) {
|
|
|
3460
4663
|
process.exit(0);
|
|
3461
4664
|
}
|
|
3462
4665
|
const message = err instanceof Error ? err.message : String(err);
|
|
3463
|
-
|
|
4666
|
+
p15__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
|
|
3464
4667
|
process.exit(1);
|
|
3465
4668
|
}
|
|
3466
4669
|
async function runCli(argv = process.argv) {
|
|
@@ -3470,6 +4673,7 @@ async function runCli(argv = process.argv) {
|
|
|
3470
4673
|
|
|
3471
4674
|
exports.DEFAULT_REGISTRY_BASE = DEFAULT_REGISTRY_BASE;
|
|
3472
4675
|
exports.PromptCancelledError = PromptCancelledError;
|
|
4676
|
+
exports.computeDirectoryHash = computeDirectoryHash;
|
|
3473
4677
|
exports.concreteInfraTemplate = concreteInfraTemplate;
|
|
3474
4678
|
exports.createCli = createCli;
|
|
3475
4679
|
exports.detectPackageManager = detectPackageManager;
|
|
@@ -3484,6 +4688,7 @@ exports.fetchRegistryFile = fetchRegistryFile;
|
|
|
3484
4688
|
exports.fetchRegistryIndex = fetchRegistryIndex;
|
|
3485
4689
|
exports.findClosestMatch = findClosestMatch;
|
|
3486
4690
|
exports.findInterfaceFile = findInterfaceFile;
|
|
4691
|
+
exports.findLocalRegistry = findLocalRegistry;
|
|
3487
4692
|
exports.findProjectRoot = findProjectRoot;
|
|
3488
4693
|
exports.generateConcreteInfraCode = generateConcreteInfraCode;
|
|
3489
4694
|
exports.generateDomain = generateDomain;
|
|
@@ -3496,16 +4701,22 @@ exports.generateInjectable = generateInjectable;
|
|
|
3496
4701
|
exports.generateInjectableCode = generateInjectableCode;
|
|
3497
4702
|
exports.generateInterface = generateInterface;
|
|
3498
4703
|
exports.generateInterfaceCode = generateInterfaceCode;
|
|
4704
|
+
exports.generateShared = generateShared;
|
|
3499
4705
|
exports.generateStubCode = generateStubCode;
|
|
3500
4706
|
exports.generateUseCase = generateUseCase;
|
|
3501
4707
|
exports.generateUseCaseCode = generateUseCaseCode;
|
|
3502
4708
|
exports.generateUseCaseTestCode = generateUseCaseTestCode;
|
|
4709
|
+
exports.getAllFiles = getAllFiles;
|
|
4710
|
+
exports.getConfiguredFeaturesDir = getConfiguredFeaturesDir;
|
|
4711
|
+
exports.getConfiguredSharedDir = getConfiguredSharedDir;
|
|
4712
|
+
exports.getEffectiveMode = getEffectiveMode;
|
|
3503
4713
|
exports.getMissingNpmPackages = getMissingNpmPackages;
|
|
3504
4714
|
exports.getRegistryBase = getRegistryBase;
|
|
3505
4715
|
exports.getTemplatesDir = getTemplatesDir;
|
|
3506
4716
|
exports.handleCancel = handleCancel;
|
|
3507
4717
|
exports.handleCreate = handleCreate;
|
|
3508
4718
|
exports.handleImport = handleImport;
|
|
4719
|
+
exports.handleRegistry = handleRegistry;
|
|
3509
4720
|
exports.hbs = hbs;
|
|
3510
4721
|
exports.httpHandlerTemplate = httpHandlerTemplate;
|
|
3511
4722
|
exports.injectableInterfaceTemplate = injectableInterfaceTemplate;
|
|
@@ -3517,9 +4728,12 @@ exports.levenshteinDistance = levenshteinDistance;
|
|
|
3517
4728
|
exports.listDomainEntities = listDomainEntities;
|
|
3518
4729
|
exports.listDomainInterfaces = listDomainInterfaces;
|
|
3519
4730
|
exports.listExistingFeatures = listExistingFeatures;
|
|
4731
|
+
exports.listExistingShared = listExistingShared;
|
|
3520
4732
|
exports.listUseCases = listUseCases;
|
|
3521
4733
|
exports.loadInterfaceHierarchy = loadInterfaceHierarchy;
|
|
4734
|
+
exports.loadMasonConfig = loadMasonConfig;
|
|
3522
4735
|
exports.loadTemplate = loadTemplate;
|
|
4736
|
+
exports.normalizeRegistryUrl = normalizeRegistryUrl;
|
|
3523
4737
|
exports.parseInterfaceMethodsAndImports = parseInterfaceMethodsAndImports;
|
|
3524
4738
|
exports.printBanner = printBanner;
|
|
3525
4739
|
exports.printGeneratedFiles = printGeneratedFiles;
|
|
@@ -3528,7 +4742,9 @@ exports.removeInterfacePrefix = removeInterfacePrefix;
|
|
|
3528
4742
|
exports.renderTemplate = renderTemplate;
|
|
3529
4743
|
exports.renderTemplateFile = renderTemplateFile;
|
|
3530
4744
|
exports.resolveFeaturesDir = resolveFeaturesDir;
|
|
4745
|
+
exports.resolveSharedDir = resolveSharedDir;
|
|
3531
4746
|
exports.runCli = runCli;
|
|
4747
|
+
exports.runSharedCommand = runSharedCommand;
|
|
3532
4748
|
exports.stubInfraTemplate = stubInfraTemplate;
|
|
3533
4749
|
exports.synthesizeCrudMethods = synthesizeCrudMethods;
|
|
3534
4750
|
exports.toCamelCase = toCamelCase;
|