@solid-stack/mason 1.0.12 → 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 +1446 -318
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1442 -314
- 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 +1467 -318
- 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 +1453 -315
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +2 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
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,8 +419,8 @@ 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;
|
|
@@ -368,25 +442,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
|
368
442
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
369
443
|
}
|
|
370
444
|
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
371
|
-
return
|
|
445
|
+
return path2__default.default.resolve(baseDir, trimmed);
|
|
372
446
|
}
|
|
373
447
|
return trimmed;
|
|
374
448
|
}
|
|
375
449
|
function findLocalRegistry(startDir = process.cwd()) {
|
|
376
|
-
let current =
|
|
450
|
+
let current = path2__default.default.resolve(startDir);
|
|
377
451
|
while (true) {
|
|
378
|
-
const candidate =
|
|
379
|
-
if (
|
|
452
|
+
const candidate = path2__default.default.join(current, "mason-registry");
|
|
453
|
+
if (fs12__default.default.existsSync(candidate) && fs12__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
|
|
380
454
|
return candidate;
|
|
381
455
|
}
|
|
382
|
-
const parent =
|
|
456
|
+
const parent = path2__default.default.dirname(current);
|
|
383
457
|
if (parent === current) break;
|
|
384
458
|
current = parent;
|
|
385
459
|
}
|
|
386
460
|
return null;
|
|
387
461
|
}
|
|
388
462
|
function getRegistryBase(projectRoot, override) {
|
|
389
|
-
const rootDir = projectRoot ?
|
|
463
|
+
const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
|
|
390
464
|
if (override && override.trim()) {
|
|
391
465
|
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
392
466
|
}
|
|
@@ -394,30 +468,30 @@ function getRegistryBase(projectRoot, override) {
|
|
|
394
468
|
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
395
469
|
}
|
|
396
470
|
const configLocations = [
|
|
397
|
-
|
|
398
|
-
|
|
471
|
+
path2__default.default.join(rootDir, "mason.config.json"),
|
|
472
|
+
path2__default.default.join(process.cwd(), "mason.config.json")
|
|
399
473
|
];
|
|
400
474
|
for (const configPath of configLocations) {
|
|
401
|
-
if (
|
|
475
|
+
if (fs12__default.default.existsSync(configPath)) {
|
|
402
476
|
try {
|
|
403
|
-
const config = JSON.parse(
|
|
477
|
+
const config = JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
|
|
404
478
|
if (config.registry && typeof config.registry === "string") {
|
|
405
|
-
return normalizeRegistryUrl(config.registry.trim(),
|
|
479
|
+
return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
|
|
406
480
|
}
|
|
407
481
|
} catch {
|
|
408
482
|
}
|
|
409
483
|
}
|
|
410
484
|
}
|
|
411
485
|
const pkgLocations = [
|
|
412
|
-
|
|
413
|
-
|
|
486
|
+
path2__default.default.join(rootDir, "package.json"),
|
|
487
|
+
path2__default.default.join(process.cwd(), "package.json")
|
|
414
488
|
];
|
|
415
489
|
for (const pkgPath of pkgLocations) {
|
|
416
|
-
if (
|
|
490
|
+
if (fs12__default.default.existsSync(pkgPath)) {
|
|
417
491
|
try {
|
|
418
|
-
const pkg = JSON.parse(
|
|
492
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgPath, "utf-8"));
|
|
419
493
|
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
420
|
-
return normalizeRegistryUrl(pkg.mason.registry.trim(),
|
|
494
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
|
|
421
495
|
}
|
|
422
496
|
} catch {
|
|
423
497
|
}
|
|
@@ -458,11 +532,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
458
532
|
if (baseDir.startsWith("file://")) {
|
|
459
533
|
baseDir = url.fileURLToPath(baseDir);
|
|
460
534
|
}
|
|
461
|
-
const filePath =
|
|
462
|
-
if (!
|
|
535
|
+
const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
|
|
536
|
+
if (!fs12__default.default.existsSync(filePath)) {
|
|
463
537
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
464
538
|
}
|
|
465
|
-
return
|
|
539
|
+
return fs12__default.default.promises.readFile(filePath, "utf-8");
|
|
466
540
|
}
|
|
467
541
|
async function fetchRegistryIndex(registryBase) {
|
|
468
542
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -524,26 +598,26 @@ function findClosestMatch(target, candidates) {
|
|
|
524
598
|
return null;
|
|
525
599
|
}
|
|
526
600
|
function detectPackageManager(projectRoot) {
|
|
527
|
-
if (
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
601
|
+
if (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";
|
|
530
604
|
return "npm";
|
|
531
605
|
}
|
|
532
606
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
533
607
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
534
|
-
const pkgJsonPath =
|
|
535
|
-
if (!
|
|
608
|
+
const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
|
|
609
|
+
if (!fs12__default.default.existsSync(pkgJsonPath)) {
|
|
536
610
|
return requiredPackages;
|
|
537
611
|
}
|
|
538
612
|
try {
|
|
539
|
-
const pkg = JSON.parse(
|
|
613
|
+
const pkg = JSON.parse(fs12__default.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
540
614
|
const installed = /* @__PURE__ */ new Set([
|
|
541
615
|
...Object.keys(pkg.dependencies || {}),
|
|
542
616
|
...Object.keys(pkg.devDependencies || {}),
|
|
543
617
|
...Object.keys(pkg.peerDependencies || {})
|
|
544
618
|
]);
|
|
545
|
-
return requiredPackages.filter((
|
|
546
|
-
const nameOnly =
|
|
619
|
+
return requiredPackages.filter((p17) => {
|
|
620
|
+
const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
|
|
547
621
|
return !installed.has(nameOnly);
|
|
548
622
|
});
|
|
549
623
|
} catch {
|
|
@@ -568,12 +642,47 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
568
642
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
569
643
|
break;
|
|
570
644
|
}
|
|
571
|
-
|
|
645
|
+
p15__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
|
|
572
646
|
child_process.execSync(installCmd, {
|
|
573
647
|
cwd: projectRoot,
|
|
574
648
|
stdio: "inherit"
|
|
575
649
|
});
|
|
576
650
|
}
|
|
651
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
652
|
+
if (!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
|
+
}
|
|
577
686
|
var hbs = Handlebars__default.default.create();
|
|
578
687
|
hbs.registerHelper("eq", (a, b) => a === b);
|
|
579
688
|
hbs.registerHelper("ne", (a, b) => a !== b);
|
|
@@ -590,33 +699,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
590
699
|
});
|
|
591
700
|
var cachedTemplatesDir = null;
|
|
592
701
|
function getTemplatesDir() {
|
|
593
|
-
if (cachedTemplatesDir &&
|
|
702
|
+
if (cachedTemplatesDir && fs12__default.default.existsSync(cachedTemplatesDir)) {
|
|
594
703
|
return cachedTemplatesDir;
|
|
595
704
|
}
|
|
596
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
597
|
-
const cand1 =
|
|
598
|
-
if (
|
|
705
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
706
|
+
const cand1 = path2__default.default.resolve(currentDir, "../../templates");
|
|
707
|
+
if (fs12__default.default.existsSync(cand1)) {
|
|
599
708
|
cachedTemplatesDir = cand1;
|
|
600
709
|
return cand1;
|
|
601
710
|
}
|
|
602
|
-
const cand2 =
|
|
603
|
-
if (
|
|
711
|
+
const cand2 = path2__default.default.resolve(currentDir, "../templates");
|
|
712
|
+
if (fs12__default.default.existsSync(cand2)) {
|
|
604
713
|
cachedTemplatesDir = cand2;
|
|
605
714
|
return cand2;
|
|
606
715
|
}
|
|
607
|
-
const cand3 =
|
|
608
|
-
if (
|
|
716
|
+
const cand3 = path2__default.default.resolve(currentDir, "templates");
|
|
717
|
+
if (fs12__default.default.existsSync(cand3)) {
|
|
609
718
|
cachedTemplatesDir = cand3;
|
|
610
719
|
return cand3;
|
|
611
720
|
}
|
|
612
721
|
let curr = currentDir;
|
|
613
722
|
while (true) {
|
|
614
|
-
const candTemplates =
|
|
615
|
-
if (
|
|
723
|
+
const candTemplates = path2__default.default.join(curr, "templates");
|
|
724
|
+
if (fs12__default.default.existsSync(candTemplates)) {
|
|
616
725
|
cachedTemplatesDir = candTemplates;
|
|
617
726
|
return candTemplates;
|
|
618
727
|
}
|
|
619
|
-
const parent =
|
|
728
|
+
const parent = path2__default.default.dirname(curr);
|
|
620
729
|
if (parent === curr) break;
|
|
621
730
|
curr = parent;
|
|
622
731
|
}
|
|
@@ -624,8 +733,8 @@ function getTemplatesDir() {
|
|
|
624
733
|
}
|
|
625
734
|
var templateCache = /* @__PURE__ */ new Map();
|
|
626
735
|
function loadTemplate(relativePath) {
|
|
627
|
-
const fullPath =
|
|
628
|
-
return
|
|
736
|
+
const fullPath = path2__default.default.isAbsolute(relativePath) ? relativePath : path2__default.default.join(getTemplatesDir(), relativePath);
|
|
737
|
+
return fs12__default.default.readFileSync(fullPath, "utf-8");
|
|
629
738
|
}
|
|
630
739
|
function renderTemplateFile(relativePath, context) {
|
|
631
740
|
let compiled = templateCache.get(relativePath);
|
|
@@ -710,8 +819,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
710
819
|
function generateDomain(options) {
|
|
711
820
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
712
821
|
const entityName = toPascalCase(options.name);
|
|
713
|
-
const targetDir = options.targetDir ||
|
|
714
|
-
const filePath =
|
|
822
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
823
|
+
const filePath = path2__default.default.join(targetDir, `${entityName}.ts`);
|
|
715
824
|
const code = generateDomainEntityCode(
|
|
716
825
|
entityName,
|
|
717
826
|
options.kind || "interface",
|
|
@@ -735,7 +844,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
735
844
|
const idKey = `${entityKey}Id`;
|
|
736
845
|
const listKey = `${entityKey}s`;
|
|
737
846
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
738
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
847
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
|
|
739
848
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
740
849
|
return {
|
|
741
850
|
name: m.name,
|
|
@@ -760,8 +869,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
760
869
|
function generateInterface(options) {
|
|
761
870
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
762
871
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
763
|
-
const targetDir = options.targetDir ||
|
|
764
|
-
const filePath =
|
|
872
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
873
|
+
const filePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
765
874
|
const code = generateInterfaceCode(
|
|
766
875
|
ifaceName,
|
|
767
876
|
options.kind || "abstractClass",
|
|
@@ -996,7 +1105,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
996
1105
|
};
|
|
997
1106
|
}
|
|
998
1107
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
999
|
-
if (customPath &&
|
|
1108
|
+
if (customPath && fs12__default.default.existsSync(customPath)) {
|
|
1000
1109
|
return { filePath: customPath, ifaceName };
|
|
1001
1110
|
}
|
|
1002
1111
|
const candidateNames = [
|
|
@@ -1006,22 +1115,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
1006
1115
|
];
|
|
1007
1116
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
1008
1117
|
for (const feat of features) {
|
|
1009
|
-
const domainDir =
|
|
1010
|
-
if (!
|
|
1118
|
+
const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1119
|
+
if (!fs12__default.default.existsSync(domainDir)) continue;
|
|
1011
1120
|
for (const cand of candidateNames) {
|
|
1012
|
-
const p1 =
|
|
1013
|
-
if (
|
|
1121
|
+
const p1 = path2__default.default.join(domainDir, `${cand}.ts`);
|
|
1122
|
+
if (fs12__default.default.existsSync(p1)) {
|
|
1014
1123
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
1015
1124
|
}
|
|
1016
1125
|
}
|
|
1017
1126
|
}
|
|
1018
1127
|
for (const cand of candidateNames) {
|
|
1019
|
-
const p1 =
|
|
1020
|
-
if (
|
|
1021
|
-
const p22 =
|
|
1022
|
-
if (
|
|
1023
|
-
const p32 =
|
|
1024
|
-
if (
|
|
1128
|
+
const p1 = path2__default.default.join(projectRoot, "domain", `${cand}.ts`);
|
|
1129
|
+
if (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 };
|
|
1025
1134
|
}
|
|
1026
1135
|
return null;
|
|
1027
1136
|
}
|
|
@@ -1044,7 +1153,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
1044
1153
|
while (currentFile && !visited.has(currentFile)) {
|
|
1045
1154
|
visited.add(currentFile);
|
|
1046
1155
|
try {
|
|
1047
|
-
const content =
|
|
1156
|
+
const content = fs12__default.default.readFileSync(currentFile, "utf-8");
|
|
1048
1157
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
1049
1158
|
for (const m of parsed.methods) {
|
|
1050
1159
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1120,7 +1229,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
1120
1229
|
const entitiesVar = `${entityVar}s`;
|
|
1121
1230
|
const idKey = `${entityVar}Id`;
|
|
1122
1231
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
1123
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
1232
|
+
const entityImportPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1124
1233
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
1125
1234
|
for (const imp of domainImports) {
|
|
1126
1235
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -1169,7 +1278,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
1169
1278
|
if (baseEntity) {
|
|
1170
1279
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
1171
1280
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
1172
|
-
const importPath = ifaceImportPath ? `${
|
|
1281
|
+
const importPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1173
1282
|
finalDomainImports.push({
|
|
1174
1283
|
name: baseEntity,
|
|
1175
1284
|
from: importPath,
|
|
@@ -1214,8 +1323,8 @@ function generateInfrastructure(options) {
|
|
|
1214
1323
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1215
1324
|
const className = toPascalCase(options.name);
|
|
1216
1325
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
1217
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
1218
|
-
const filePath =
|
|
1326
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2__default.default.join(projectRoot, "infrastructure"));
|
|
1327
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
1219
1328
|
let code;
|
|
1220
1329
|
if (options.implementsInterface) {
|
|
1221
1330
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -1229,7 +1338,7 @@ function generateInfrastructure(options) {
|
|
|
1229
1338
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
1230
1339
|
let ifaceImportPath;
|
|
1231
1340
|
if (ifaceFilePath) {
|
|
1232
|
-
let rel =
|
|
1341
|
+
let rel = path2__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
1233
1342
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1234
1343
|
ifaceImportPath = rel;
|
|
1235
1344
|
} else if (isFeatureScope) {
|
|
@@ -1242,8 +1351,8 @@ function generateInfrastructure(options) {
|
|
|
1242
1351
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
1243
1352
|
let fromPath;
|
|
1244
1353
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
1245
|
-
const absTarget =
|
|
1246
|
-
let rel =
|
|
1354
|
+
const absTarget = path2__default.default.resolve(path2__default.default.dirname(ifaceFilePath), imp.specifier);
|
|
1355
|
+
let rel = path2__default.default.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
1247
1356
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1248
1357
|
fromPath = rel;
|
|
1249
1358
|
} else {
|
|
@@ -1316,22 +1425,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1316
1425
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1317
1426
|
}));
|
|
1318
1427
|
const domainTypesToImport = new Set(domainImports);
|
|
1319
|
-
for (const
|
|
1320
|
-
const rawType =
|
|
1428
|
+
for (const p17 of [...inputProps, ...outputProps]) {
|
|
1429
|
+
const rawType = p17.type.trim();
|
|
1321
1430
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1322
1431
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1323
1432
|
domainTypesToImport.add(baseType);
|
|
1324
1433
|
}
|
|
1325
1434
|
}
|
|
1326
|
-
const formattedInputProps = inputProps.map((
|
|
1327
|
-
name:
|
|
1328
|
-
optional:
|
|
1329
|
-
type:
|
|
1435
|
+
const formattedInputProps = inputProps.map((p17) => ({
|
|
1436
|
+
name: p17.name,
|
|
1437
|
+
optional: p17.optional,
|
|
1438
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1330
1439
|
}));
|
|
1331
|
-
const formattedOutputProps = outputProps.map((
|
|
1332
|
-
name:
|
|
1333
|
-
optional:
|
|
1334
|
-
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
|
|
1335
1444
|
}));
|
|
1336
1445
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1337
1446
|
key: toCamelCase(dep),
|
|
@@ -1430,8 +1539,8 @@ function generateUseCase(options) {
|
|
|
1430
1539
|
const files = [];
|
|
1431
1540
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1432
1541
|
const ucName = toPascalCase(options.name);
|
|
1433
|
-
const targetDir = options.targetDir ||
|
|
1434
|
-
const filePath =
|
|
1542
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1543
|
+
const filePath = path2__default.default.join(targetDir, `${ucName}.ts`);
|
|
1435
1544
|
const code = generateUseCaseCode(
|
|
1436
1545
|
ucName,
|
|
1437
1546
|
options.dependencies || [],
|
|
@@ -1447,7 +1556,7 @@ function generateUseCase(options) {
|
|
|
1447
1556
|
})
|
|
1448
1557
|
);
|
|
1449
1558
|
if (options.includeTest) {
|
|
1450
|
-
const testPath =
|
|
1559
|
+
const testPath = path2__default.default.join(targetDir, `${ucName}.test.ts`);
|
|
1451
1560
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1452
1561
|
files.push(
|
|
1453
1562
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1515,10 +1624,10 @@ function generateHttpHandler(options) {
|
|
|
1515
1624
|
const files = [];
|
|
1516
1625
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1517
1626
|
const className = toPascalCase(options.name);
|
|
1518
|
-
const pxDir =
|
|
1519
|
-
const handlersDir =
|
|
1520
|
-
const pxIndexPath =
|
|
1521
|
-
if (!
|
|
1627
|
+
const pxDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1628
|
+
const handlersDir = path2__default.default.join(pxDir, "handlers");
|
|
1629
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1630
|
+
if (!fs12__default.default.existsSync(pxIndexPath)) {
|
|
1522
1631
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1523
1632
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1524
1633
|
routePath: featurePath
|
|
@@ -1531,7 +1640,7 @@ function generateHttpHandler(options) {
|
|
|
1531
1640
|
})
|
|
1532
1641
|
);
|
|
1533
1642
|
}
|
|
1534
|
-
const handlerFilePath =
|
|
1643
|
+
const handlerFilePath = path2__default.default.join(handlersDir, `${className}.ts`);
|
|
1535
1644
|
const code = generateHttpHandlerCode(
|
|
1536
1645
|
className,
|
|
1537
1646
|
options.method || "get",
|
|
@@ -1552,11 +1661,11 @@ function generateHttpHandler(options) {
|
|
|
1552
1661
|
return files;
|
|
1553
1662
|
}
|
|
1554
1663
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1555
|
-
const providerPath =
|
|
1664
|
+
const providerPath = path2__default.default.join(featureDir, "diProvider.ts");
|
|
1556
1665
|
const pascalFeature = toPascalCase(featureName);
|
|
1557
1666
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1558
|
-
if (
|
|
1559
|
-
let content =
|
|
1667
|
+
if (fs12__default.default.existsSync(providerPath) && !options.overwrite) {
|
|
1668
|
+
let content = fs12__default.default.readFileSync(providerPath, "utf-8");
|
|
1560
1669
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1561
1670
|
for (const b of bindings) {
|
|
1562
1671
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1615,7 +1724,7 @@ function generateFeature(options) {
|
|
|
1615
1724
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1616
1725
|
const rawName = options.name.trim();
|
|
1617
1726
|
const featureName = toCamelCase(rawName);
|
|
1618
|
-
const targetDir = options.targetDir ||
|
|
1727
|
+
const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1619
1728
|
const routePath = options.routePath || `/${featureName}`;
|
|
1620
1729
|
const rawEntities = [];
|
|
1621
1730
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1636,7 +1745,7 @@ function generateFeature(options) {
|
|
|
1636
1745
|
const domainFile = generateDomain({
|
|
1637
1746
|
name: ent,
|
|
1638
1747
|
feature: featureName,
|
|
1639
|
-
targetDir:
|
|
1748
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1640
1749
|
projectRoot,
|
|
1641
1750
|
dryRun: options.dryRun,
|
|
1642
1751
|
overwrite: options.overwrite,
|
|
@@ -1656,7 +1765,7 @@ function generateFeature(options) {
|
|
|
1656
1765
|
const ifaceFile = generateInterface({
|
|
1657
1766
|
name: ifaceName,
|
|
1658
1767
|
feature: featureName,
|
|
1659
|
-
targetDir:
|
|
1768
|
+
targetDir: path2__default.default.join(targetDir, "domain"),
|
|
1660
1769
|
entityName: ent,
|
|
1661
1770
|
projectRoot,
|
|
1662
1771
|
dryRun: options.dryRun,
|
|
@@ -1673,7 +1782,7 @@ function generateFeature(options) {
|
|
|
1673
1782
|
const stubFile = generateInfrastructure({
|
|
1674
1783
|
name: stubName,
|
|
1675
1784
|
feature: featureName,
|
|
1676
|
-
targetDir:
|
|
1785
|
+
targetDir: path2__default.default.join(targetDir, "infrastructure"),
|
|
1677
1786
|
kind: "stub",
|
|
1678
1787
|
implementsInterface: ifaceName,
|
|
1679
1788
|
entityName: ent,
|
|
@@ -1715,7 +1824,7 @@ function generateFeature(options) {
|
|
|
1715
1824
|
const ucFiles = generateUseCase({
|
|
1716
1825
|
name: ucName,
|
|
1717
1826
|
feature: featureName,
|
|
1718
|
-
targetDir:
|
|
1827
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1719
1828
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1720
1829
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1721
1830
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1760,7 +1869,7 @@ function generateFeature(options) {
|
|
|
1760
1869
|
const ucFiles = generateUseCase({
|
|
1761
1870
|
name: ucName,
|
|
1762
1871
|
feature: featureName,
|
|
1763
|
-
targetDir:
|
|
1872
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1764
1873
|
dependencies: deps,
|
|
1765
1874
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1766
1875
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1793,7 +1902,7 @@ function generateFeature(options) {
|
|
|
1793
1902
|
const ucFiles = generateUseCase({
|
|
1794
1903
|
name: ucName,
|
|
1795
1904
|
feature: featureName,
|
|
1796
|
-
targetDir:
|
|
1905
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1797
1906
|
dependencies: deps,
|
|
1798
1907
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1799
1908
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1826,7 +1935,7 @@ function generateFeature(options) {
|
|
|
1826
1935
|
const ucFiles = generateUseCase({
|
|
1827
1936
|
name: ucName,
|
|
1828
1937
|
feature: featureName,
|
|
1829
|
-
targetDir:
|
|
1938
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1830
1939
|
dependencies: deps,
|
|
1831
1940
|
inputProperties: [
|
|
1832
1941
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1870,7 +1979,7 @@ function generateFeature(options) {
|
|
|
1870
1979
|
const ucFiles = generateUseCase({
|
|
1871
1980
|
name: ucName,
|
|
1872
1981
|
feature: featureName,
|
|
1873
|
-
targetDir:
|
|
1982
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1874
1983
|
dependencies: deps,
|
|
1875
1984
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1876
1985
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1903,7 +2012,7 @@ function generateFeature(options) {
|
|
|
1903
2012
|
const ucFiles = generateUseCase({
|
|
1904
2013
|
name: ucName,
|
|
1905
2014
|
feature: featureName,
|
|
1906
|
-
targetDir:
|
|
2015
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1907
2016
|
dependencies: deps,
|
|
1908
2017
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1909
2018
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1940,7 +2049,7 @@ function generateFeature(options) {
|
|
|
1940
2049
|
const ucFiles = generateUseCase({
|
|
1941
2050
|
name: ucName,
|
|
1942
2051
|
feature: featureName,
|
|
1943
|
-
targetDir:
|
|
2052
|
+
targetDir: path2__default.default.join(targetDir, "useCases"),
|
|
1944
2053
|
dependencies: customUc.dependencies || [],
|
|
1945
2054
|
inputProperties: customUc.inputProperties || [],
|
|
1946
2055
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1972,8 +2081,8 @@ function generateFeature(options) {
|
|
|
1972
2081
|
}
|
|
1973
2082
|
}
|
|
1974
2083
|
if (options.includeExpress !== false) {
|
|
1975
|
-
const pxDir =
|
|
1976
|
-
const pxIndexPath =
|
|
2084
|
+
const pxDir = path2__default.default.join(targetDir, "pxExpress");
|
|
2085
|
+
const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
|
|
1977
2086
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1978
2087
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1979
2088
|
files.push(
|
|
@@ -1999,13 +2108,14 @@ function generateFeature(options) {
|
|
|
1999
2108
|
// src/commands/feature.ts
|
|
2000
2109
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
2001
2110
|
const projectRoot = findProjectRoot();
|
|
2111
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2002
2112
|
let name = featureNameArg?.trim();
|
|
2003
2113
|
if (!name && flags.yes) {
|
|
2004
2114
|
name = "feature";
|
|
2005
2115
|
}
|
|
2006
2116
|
if (!name) {
|
|
2007
2117
|
name = handleCancel(
|
|
2008
|
-
await
|
|
2118
|
+
await p15__namespace.text({
|
|
2009
2119
|
message: "What is the feature name?",
|
|
2010
2120
|
placeholder: "e.g. users, auth, orders, billing",
|
|
2011
2121
|
validate: (val) => {
|
|
@@ -2016,18 +2126,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2016
2126
|
).trim();
|
|
2017
2127
|
}
|
|
2018
2128
|
const normalizedName = toCamelCase(name);
|
|
2019
|
-
const
|
|
2129
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
2130
|
+
const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseFeaturesDir, normalizedName));
|
|
2020
2131
|
let targetDirRel = defaultDir;
|
|
2021
|
-
if (!flags.yes && !flags.target) {
|
|
2132
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2022
2133
|
targetDirRel = handleCancel(
|
|
2023
|
-
await
|
|
2134
|
+
await p15__namespace.text({
|
|
2024
2135
|
message: "Target directory for the feature:",
|
|
2025
2136
|
initialValue: defaultDir,
|
|
2026
2137
|
defaultValue: defaultDir
|
|
2027
2138
|
})
|
|
2028
2139
|
).trim();
|
|
2029
2140
|
}
|
|
2030
|
-
const targetDir = flags.target ?
|
|
2141
|
+
const targetDir = flags.dest ? path2__default.default.resolve(projectRoot, flags.dest) : flags.target ? path2__default.default.resolve(projectRoot, flags.target) : path2__default.default.resolve(projectRoot, targetDirRel);
|
|
2031
2142
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
2032
2143
|
let routePath = `/${normalizedName}`;
|
|
2033
2144
|
let includeDomain = true;
|
|
@@ -2042,14 +2153,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2042
2153
|
const customUseCases = [];
|
|
2043
2154
|
if (!flags.yes) {
|
|
2044
2155
|
includeExpress = handleCancel(
|
|
2045
|
-
await
|
|
2156
|
+
await p15__namespace.confirm({
|
|
2046
2157
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
2047
2158
|
initialValue: true
|
|
2048
2159
|
})
|
|
2049
2160
|
);
|
|
2050
2161
|
if (includeExpress) {
|
|
2051
2162
|
routePath = handleCancel(
|
|
2052
|
-
await
|
|
2163
|
+
await p15__namespace.text({
|
|
2053
2164
|
message: "Express route prefix:",
|
|
2054
2165
|
initialValue: `/${normalizedName}`,
|
|
2055
2166
|
defaultValue: `/${normalizedName}`
|
|
@@ -2057,14 +2168,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2057
2168
|
).trim();
|
|
2058
2169
|
}
|
|
2059
2170
|
includeDomain = handleCancel(
|
|
2060
|
-
await
|
|
2171
|
+
await p15__namespace.confirm({
|
|
2061
2172
|
message: "Include Domain Entity/Entities?",
|
|
2062
2173
|
initialValue: true
|
|
2063
2174
|
})
|
|
2064
2175
|
);
|
|
2065
2176
|
if (includeDomain) {
|
|
2066
2177
|
const domainInput = handleCancel(
|
|
2067
|
-
await
|
|
2178
|
+
await p15__namespace.text({
|
|
2068
2179
|
message: "Domain entity name(s) (comma-separated):",
|
|
2069
2180
|
initialValue: defaultEntity,
|
|
2070
2181
|
defaultValue: defaultEntity,
|
|
@@ -2083,21 +2194,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2083
2194
|
entityNames = [defaultEntity];
|
|
2084
2195
|
}
|
|
2085
2196
|
includeInterface = handleCancel(
|
|
2086
|
-
await
|
|
2197
|
+
await p15__namespace.confirm({
|
|
2087
2198
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
2088
2199
|
initialValue: true
|
|
2089
2200
|
})
|
|
2090
2201
|
);
|
|
2091
2202
|
if (includeInterface) {
|
|
2092
2203
|
includeInfra = handleCancel(
|
|
2093
|
-
await
|
|
2204
|
+
await p15__namespace.confirm({
|
|
2094
2205
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
2095
2206
|
initialValue: true
|
|
2096
2207
|
})
|
|
2097
2208
|
);
|
|
2098
2209
|
}
|
|
2099
2210
|
includeUseCase = handleCancel(
|
|
2100
|
-
await
|
|
2211
|
+
await p15__namespace.confirm({
|
|
2101
2212
|
message: "Include Domain Use Cases?",
|
|
2102
2213
|
initialValue: true
|
|
2103
2214
|
})
|
|
@@ -2111,7 +2222,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2111
2222
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
2112
2223
|
];
|
|
2113
2224
|
crudOperations = handleCancel(
|
|
2114
|
-
await
|
|
2225
|
+
await p15__namespace.multiselect({
|
|
2115
2226
|
message: "Select standard CRUD use cases to generate:",
|
|
2116
2227
|
options: crudChoices,
|
|
2117
2228
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -2119,13 +2230,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2119
2230
|
})
|
|
2120
2231
|
);
|
|
2121
2232
|
includeTests = handleCancel(
|
|
2122
|
-
await
|
|
2233
|
+
await p15__namespace.confirm({
|
|
2123
2234
|
message: "Generate Vitest unit tests for use cases?",
|
|
2124
2235
|
initialValue: true
|
|
2125
2236
|
})
|
|
2126
2237
|
);
|
|
2127
2238
|
const wantCustom = handleCancel(
|
|
2128
|
-
await
|
|
2239
|
+
await p15__namespace.confirm({
|
|
2129
2240
|
message: "Do you want to add any custom use cases to this feature?",
|
|
2130
2241
|
initialValue: false
|
|
2131
2242
|
})
|
|
@@ -2135,7 +2246,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2135
2246
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
2136
2247
|
while (addMore) {
|
|
2137
2248
|
const customName = handleCancel(
|
|
2138
|
-
await
|
|
2249
|
+
await p15__namespace.text({
|
|
2139
2250
|
message: "Custom Use Case name:",
|
|
2140
2251
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
2141
2252
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2145,7 +2256,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2145
2256
|
let selectedDeps = [];
|
|
2146
2257
|
if (availableIfaces.length > 0) {
|
|
2147
2258
|
selectedDeps = handleCancel(
|
|
2148
|
-
await
|
|
2259
|
+
await p15__namespace.multiselect({
|
|
2149
2260
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
2150
2261
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
2151
2262
|
required: false
|
|
@@ -2154,7 +2265,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2154
2265
|
}
|
|
2155
2266
|
let customInputProps = [];
|
|
2156
2267
|
const hasInput = handleCancel(
|
|
2157
|
-
await
|
|
2268
|
+
await p15__namespace.confirm({
|
|
2158
2269
|
message: `Does ${customUcName} accept input arguments?`,
|
|
2159
2270
|
initialValue: true
|
|
2160
2271
|
})
|
|
@@ -2165,7 +2276,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2165
2276
|
}
|
|
2166
2277
|
let customOutputProps = [];
|
|
2167
2278
|
const hasCustomOutput = handleCancel(
|
|
2168
|
-
await
|
|
2279
|
+
await p15__namespace.confirm({
|
|
2169
2280
|
message: `Does ${customUcName} return custom output data?`,
|
|
2170
2281
|
initialValue: false
|
|
2171
2282
|
})
|
|
@@ -2175,7 +2286,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2175
2286
|
});
|
|
2176
2287
|
}
|
|
2177
2288
|
const customIncludeTest = handleCancel(
|
|
2178
|
-
await
|
|
2289
|
+
await p15__namespace.confirm({
|
|
2179
2290
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2180
2291
|
initialValue: true
|
|
2181
2292
|
})
|
|
@@ -2185,14 +2296,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2185
2296
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2186
2297
|
if (includeExpress) {
|
|
2187
2298
|
customCreateHttp = handleCancel(
|
|
2188
|
-
await
|
|
2299
|
+
await p15__namespace.confirm({
|
|
2189
2300
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2190
2301
|
initialValue: true
|
|
2191
2302
|
})
|
|
2192
2303
|
);
|
|
2193
2304
|
if (customCreateHttp) {
|
|
2194
2305
|
customHttpMethod = handleCancel(
|
|
2195
|
-
await
|
|
2306
|
+
await p15__namespace.select({
|
|
2196
2307
|
message: "HTTP method for route:",
|
|
2197
2308
|
options: [
|
|
2198
2309
|
{ value: "post", label: "POST" },
|
|
@@ -2205,7 +2316,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2205
2316
|
})
|
|
2206
2317
|
);
|
|
2207
2318
|
customHttpPath = handleCancel(
|
|
2208
|
-
await
|
|
2319
|
+
await p15__namespace.text({
|
|
2209
2320
|
message: "Route subpath (relative to feature prefix):",
|
|
2210
2321
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2211
2322
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2226,7 +2337,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2226
2337
|
validationFields: customInputProps
|
|
2227
2338
|
});
|
|
2228
2339
|
addMore = handleCancel(
|
|
2229
|
-
await
|
|
2340
|
+
await p15__namespace.confirm({
|
|
2230
2341
|
message: "Add another custom use case?",
|
|
2231
2342
|
initialValue: false
|
|
2232
2343
|
})
|
|
@@ -2235,7 +2346,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2235
2346
|
}
|
|
2236
2347
|
}
|
|
2237
2348
|
includeDiProvider = handleCancel(
|
|
2238
|
-
await
|
|
2349
|
+
await p15__namespace.confirm({
|
|
2239
2350
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2240
2351
|
initialValue: true
|
|
2241
2352
|
})
|
|
@@ -2247,8 +2358,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2247
2358
|
entityNames = [defaultEntity];
|
|
2248
2359
|
}
|
|
2249
2360
|
}
|
|
2250
|
-
const s =
|
|
2251
|
-
s.start(`Generating feature '${
|
|
2361
|
+
const s = p15__namespace.spinner();
|
|
2362
|
+
s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
|
|
2252
2363
|
const options = {
|
|
2253
2364
|
name: normalizedName,
|
|
2254
2365
|
projectRoot,
|
|
@@ -2268,9 +2379,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2268
2379
|
includeDiProvider
|
|
2269
2380
|
};
|
|
2270
2381
|
const files = generateFeature(options);
|
|
2382
|
+
if (isRegistry) {
|
|
2383
|
+
const registryPath = path2__default.default.join(targetDir, "registry.json");
|
|
2384
|
+
if (!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
|
+
}
|
|
2271
2406
|
s.stop("Feature scaffolded successfully!");
|
|
2272
2407
|
printGeneratedFiles(files, options.dryRun);
|
|
2273
|
-
|
|
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
|
+
);
|
|
2274
2583
|
}
|
|
2275
2584
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2276
2585
|
const projectRoot = findProjectRoot();
|
|
@@ -2281,14 +2590,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2281
2590
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2282
2591
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2283
2592
|
const selected = handleCancel(
|
|
2284
|
-
await
|
|
2593
|
+
await p15__namespace.select({
|
|
2285
2594
|
message: "Select target feature:",
|
|
2286
2595
|
options: featureOptions
|
|
2287
2596
|
})
|
|
2288
2597
|
);
|
|
2289
2598
|
if (selected === "__custom__") {
|
|
2290
2599
|
feature = handleCancel(
|
|
2291
|
-
await
|
|
2600
|
+
await p15__namespace.text({
|
|
2292
2601
|
message: "Enter feature name:",
|
|
2293
2602
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2294
2603
|
})
|
|
@@ -2298,7 +2607,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2298
2607
|
}
|
|
2299
2608
|
} else {
|
|
2300
2609
|
feature = handleCancel(
|
|
2301
|
-
await
|
|
2610
|
+
await p15__namespace.text({
|
|
2302
2611
|
message: "Enter target feature name:",
|
|
2303
2612
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2304
2613
|
})
|
|
@@ -2309,7 +2618,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2309
2618
|
let name = useCaseNameArg?.trim();
|
|
2310
2619
|
if (!name) {
|
|
2311
2620
|
name = handleCancel(
|
|
2312
|
-
await
|
|
2621
|
+
await p15__namespace.text({
|
|
2313
2622
|
message: "Use Case name:",
|
|
2314
2623
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2315
2624
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2325,7 +2634,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2325
2634
|
label: i.name
|
|
2326
2635
|
}));
|
|
2327
2636
|
selectedDeps = handleCancel(
|
|
2328
|
-
await
|
|
2637
|
+
await p15__namespace.multiselect({
|
|
2329
2638
|
message: "Select domain interfaces to inject into use case:",
|
|
2330
2639
|
options: multiOptions,
|
|
2331
2640
|
required: false
|
|
@@ -2340,7 +2649,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2340
2649
|
let httpPath = "/";
|
|
2341
2650
|
if (!flags.yes) {
|
|
2342
2651
|
const hasInput = handleCancel(
|
|
2343
|
-
await
|
|
2652
|
+
await p15__namespace.confirm({
|
|
2344
2653
|
message: "Does this use case accept input arguments?",
|
|
2345
2654
|
initialValue: true
|
|
2346
2655
|
})
|
|
@@ -2350,7 +2659,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2350
2659
|
});
|
|
2351
2660
|
}
|
|
2352
2661
|
const hasCustomOutput = handleCancel(
|
|
2353
|
-
await
|
|
2662
|
+
await p15__namespace.confirm({
|
|
2354
2663
|
message: "Does this use case return custom output data?",
|
|
2355
2664
|
initialValue: false
|
|
2356
2665
|
})
|
|
@@ -2360,20 +2669,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2360
2669
|
});
|
|
2361
2670
|
}
|
|
2362
2671
|
includeTest = handleCancel(
|
|
2363
|
-
await
|
|
2672
|
+
await p15__namespace.confirm({
|
|
2364
2673
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2365
2674
|
initialValue: true
|
|
2366
2675
|
})
|
|
2367
2676
|
);
|
|
2368
2677
|
createHttp = handleCancel(
|
|
2369
|
-
await
|
|
2678
|
+
await p15__namespace.confirm({
|
|
2370
2679
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2371
2680
|
initialValue: false
|
|
2372
2681
|
})
|
|
2373
2682
|
);
|
|
2374
2683
|
if (createHttp) {
|
|
2375
2684
|
httpMethod = handleCancel(
|
|
2376
|
-
await
|
|
2685
|
+
await p15__namespace.select({
|
|
2377
2686
|
message: "HTTP method for route:",
|
|
2378
2687
|
options: [
|
|
2379
2688
|
{ value: "post", label: "POST" },
|
|
@@ -2386,7 +2695,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2386
2695
|
})
|
|
2387
2696
|
);
|
|
2388
2697
|
httpPath = handleCancel(
|
|
2389
|
-
await
|
|
2698
|
+
await p15__namespace.text({
|
|
2390
2699
|
message: "Route subpath (relative to feature prefix):",
|
|
2391
2700
|
initialValue: "/",
|
|
2392
2701
|
defaultValue: "/"
|
|
@@ -2394,8 +2703,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2394
2703
|
).trim();
|
|
2395
2704
|
}
|
|
2396
2705
|
}
|
|
2397
|
-
const s =
|
|
2398
|
-
s.start(`Generating use case '${
|
|
2706
|
+
const s = p15__namespace.spinner();
|
|
2707
|
+
s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
|
|
2399
2708
|
const options = {
|
|
2400
2709
|
name: ucName,
|
|
2401
2710
|
feature,
|
|
@@ -2427,7 +2736,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2427
2736
|
}
|
|
2428
2737
|
s.stop("Use case generated successfully!");
|
|
2429
2738
|
printGeneratedFiles(files, options.dryRun);
|
|
2430
|
-
|
|
2739
|
+
p15__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2431
2740
|
}
|
|
2432
2741
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2433
2742
|
const projectRoot = findProjectRoot();
|
|
@@ -2438,14 +2747,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2438
2747
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2439
2748
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2440
2749
|
const selected = handleCancel(
|
|
2441
|
-
await
|
|
2750
|
+
await p15__namespace.select({
|
|
2442
2751
|
message: "Select target feature:",
|
|
2443
2752
|
options: featureOptions
|
|
2444
2753
|
})
|
|
2445
2754
|
);
|
|
2446
2755
|
if (selected === "__custom__") {
|
|
2447
2756
|
feature = handleCancel(
|
|
2448
|
-
await
|
|
2757
|
+
await p15__namespace.text({
|
|
2449
2758
|
message: "Enter feature name:",
|
|
2450
2759
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2451
2760
|
})
|
|
@@ -2455,7 +2764,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2455
2764
|
}
|
|
2456
2765
|
} else {
|
|
2457
2766
|
feature = handleCancel(
|
|
2458
|
-
await
|
|
2767
|
+
await p15__namespace.text({
|
|
2459
2768
|
message: "Enter target feature name:",
|
|
2460
2769
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2461
2770
|
})
|
|
@@ -2466,7 +2775,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2466
2775
|
let name = domainNameArg?.trim();
|
|
2467
2776
|
if (!name) {
|
|
2468
2777
|
name = handleCancel(
|
|
2469
|
-
await
|
|
2778
|
+
await p15__namespace.text({
|
|
2470
2779
|
message: "Domain model / entity name:",
|
|
2471
2780
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2472
2781
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2478,7 +2787,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2478
2787
|
let properties = [];
|
|
2479
2788
|
if (!flags.yes) {
|
|
2480
2789
|
kind = handleCancel(
|
|
2481
|
-
await
|
|
2790
|
+
await p15__namespace.select({
|
|
2482
2791
|
message: "Select domain model type:",
|
|
2483
2792
|
options: [
|
|
2484
2793
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2497,8 +2806,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2497
2806
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2498
2807
|
});
|
|
2499
2808
|
}
|
|
2500
|
-
const s =
|
|
2501
|
-
s.start(`Generating domain model '${
|
|
2809
|
+
const s = p15__namespace.spinner();
|
|
2810
|
+
s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
|
|
2502
2811
|
const options = {
|
|
2503
2812
|
name: entityName,
|
|
2504
2813
|
feature,
|
|
@@ -2511,7 +2820,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2511
2820
|
const file = generateDomain(options);
|
|
2512
2821
|
s.stop("Domain model generated successfully!");
|
|
2513
2822
|
printGeneratedFiles([file], options.dryRun);
|
|
2514
|
-
|
|
2823
|
+
p15__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2515
2824
|
}
|
|
2516
2825
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2517
2826
|
const projectRoot = findProjectRoot();
|
|
@@ -2520,7 +2829,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2520
2829
|
let feature = flags.feature?.trim();
|
|
2521
2830
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2522
2831
|
scope = handleCancel(
|
|
2523
|
-
await
|
|
2832
|
+
await p15__namespace.select({
|
|
2524
2833
|
message: "Where should this infrastructure implementation be placed?",
|
|
2525
2834
|
options: [
|
|
2526
2835
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2535,14 +2844,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2535
2844
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2536
2845
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2537
2846
|
const selected = handleCancel(
|
|
2538
|
-
await
|
|
2847
|
+
await p15__namespace.select({
|
|
2539
2848
|
message: "Select target feature:",
|
|
2540
2849
|
options: featureOptions
|
|
2541
2850
|
})
|
|
2542
2851
|
);
|
|
2543
2852
|
if (selected === "__custom__") {
|
|
2544
2853
|
feature = handleCancel(
|
|
2545
|
-
await
|
|
2854
|
+
await p15__namespace.text({
|
|
2546
2855
|
message: "Enter feature name:",
|
|
2547
2856
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2548
2857
|
})
|
|
@@ -2552,7 +2861,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2552
2861
|
}
|
|
2553
2862
|
} else {
|
|
2554
2863
|
feature = handleCancel(
|
|
2555
|
-
await
|
|
2864
|
+
await p15__namespace.text({
|
|
2556
2865
|
message: "Enter target feature name:",
|
|
2557
2866
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2558
2867
|
})
|
|
@@ -2565,7 +2874,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2565
2874
|
let name = classNameArg?.trim();
|
|
2566
2875
|
if (!name) {
|
|
2567
2876
|
name = handleCancel(
|
|
2568
|
-
await
|
|
2877
|
+
await p15__namespace.text({
|
|
2569
2878
|
message: "Infrastructure class name:",
|
|
2570
2879
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2571
2880
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2584,7 +2893,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2584
2893
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2585
2894
|
);
|
|
2586
2895
|
const ifaceChoice = handleCancel(
|
|
2587
|
-
await
|
|
2896
|
+
await p15__namespace.select({
|
|
2588
2897
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2589
2898
|
options: [
|
|
2590
2899
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2596,7 +2905,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2596
2905
|
);
|
|
2597
2906
|
if (ifaceChoice === "__custom__") {
|
|
2598
2907
|
implementsInterface = handleCancel(
|
|
2599
|
-
await
|
|
2908
|
+
await p15__namespace.text({
|
|
2600
2909
|
message: "Enter interface or abstract class name:",
|
|
2601
2910
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2602
2911
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2607,14 +2916,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2607
2916
|
}
|
|
2608
2917
|
} else {
|
|
2609
2918
|
const wantImpl = handleCancel(
|
|
2610
|
-
await
|
|
2919
|
+
await p15__namespace.confirm({
|
|
2611
2920
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2612
2921
|
initialValue: false
|
|
2613
2922
|
})
|
|
2614
2923
|
);
|
|
2615
2924
|
if (wantImpl) {
|
|
2616
2925
|
implementsInterface = handleCancel(
|
|
2617
|
-
await
|
|
2926
|
+
await p15__namespace.text({
|
|
2618
2927
|
message: "Enter interface or abstract class name:",
|
|
2619
2928
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2620
2929
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2634,7 +2943,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2634
2943
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2635
2944
|
if (allInterfaces.length > 0) {
|
|
2636
2945
|
const ifaceChoice = handleCancel(
|
|
2637
|
-
await
|
|
2946
|
+
await p15__namespace.select({
|
|
2638
2947
|
message: "Does this class implement an interface or abstract class?",
|
|
2639
2948
|
options: [
|
|
2640
2949
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2646,7 +2955,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2646
2955
|
);
|
|
2647
2956
|
if (ifaceChoice === "__custom__") {
|
|
2648
2957
|
implementsInterface = handleCancel(
|
|
2649
|
-
await
|
|
2958
|
+
await p15__namespace.text({
|
|
2650
2959
|
message: "Enter interface or abstract class name:",
|
|
2651
2960
|
placeholder: "e.g. IAuthRepository",
|
|
2652
2961
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2659,7 +2968,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2659
2968
|
}
|
|
2660
2969
|
if (!flags.yes && !flags.kind) {
|
|
2661
2970
|
kind = handleCancel(
|
|
2662
|
-
await
|
|
2971
|
+
await p15__namespace.select({
|
|
2663
2972
|
message: "Select implementation kind:",
|
|
2664
2973
|
options: [
|
|
2665
2974
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2670,7 +2979,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2670
2979
|
);
|
|
2671
2980
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2672
2981
|
registerInProvider = handleCancel(
|
|
2673
|
-
await
|
|
2982
|
+
await p15__namespace.confirm({
|
|
2674
2983
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2675
2984
|
initialValue: true
|
|
2676
2985
|
})
|
|
@@ -2679,8 +2988,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2679
2988
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2680
2989
|
registerInProvider = !flags.noProvider;
|
|
2681
2990
|
}
|
|
2682
|
-
const s =
|
|
2683
|
-
s.start(`Generating infrastructure class '${
|
|
2991
|
+
const s = p15__namespace.spinner();
|
|
2992
|
+
s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
|
|
2684
2993
|
const files = [];
|
|
2685
2994
|
const options = {
|
|
2686
2995
|
name: className,
|
|
@@ -2695,7 +3004,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2695
3004
|
const infraFile = generateInfrastructure(options);
|
|
2696
3005
|
files.push(infraFile);
|
|
2697
3006
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2698
|
-
const featureDir =
|
|
3007
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2699
3008
|
const providerFile = updateOrGenerateDiProvider(
|
|
2700
3009
|
featureDir,
|
|
2701
3010
|
feature,
|
|
@@ -2715,7 +3024,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2715
3024
|
}
|
|
2716
3025
|
s.stop("Infrastructure class generated successfully!");
|
|
2717
3026
|
printGeneratedFiles(files, options.dryRun);
|
|
2718
|
-
|
|
3027
|
+
p15__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2719
3028
|
}
|
|
2720
3029
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2721
3030
|
const projectRoot = findProjectRoot();
|
|
@@ -2726,14 +3035,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2726
3035
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2727
3036
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2728
3037
|
const selected = handleCancel(
|
|
2729
|
-
await
|
|
3038
|
+
await p15__namespace.select({
|
|
2730
3039
|
message: "Select target feature:",
|
|
2731
3040
|
options: featureOptions
|
|
2732
3041
|
})
|
|
2733
3042
|
);
|
|
2734
3043
|
if (selected === "__custom__") {
|
|
2735
3044
|
feature = handleCancel(
|
|
2736
|
-
await
|
|
3045
|
+
await p15__namespace.text({
|
|
2737
3046
|
message: "Enter feature name:",
|
|
2738
3047
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2739
3048
|
})
|
|
@@ -2743,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2743
3052
|
}
|
|
2744
3053
|
} else {
|
|
2745
3054
|
feature = handleCancel(
|
|
2746
|
-
await
|
|
3055
|
+
await p15__namespace.text({
|
|
2747
3056
|
message: "Enter target feature name:",
|
|
2748
3057
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2749
3058
|
})
|
|
@@ -2754,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2754
3063
|
let name = interfaceNameArg?.trim();
|
|
2755
3064
|
if (!name) {
|
|
2756
3065
|
name = handleCancel(
|
|
2757
|
-
await
|
|
3066
|
+
await p15__namespace.text({
|
|
2758
3067
|
message: "Domain interface name:",
|
|
2759
3068
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2760
3069
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2772,7 +3081,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2772
3081
|
if (!flags.yes) {
|
|
2773
3082
|
if (existingEntities.length > 0) {
|
|
2774
3083
|
const selectedEntity = handleCancel(
|
|
2775
|
-
await
|
|
3084
|
+
await p15__namespace.select({
|
|
2776
3085
|
message: "Select domain entity associated with this interface:",
|
|
2777
3086
|
options: [
|
|
2778
3087
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2783,7 +3092,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2783
3092
|
);
|
|
2784
3093
|
if (selectedEntity === "__custom__") {
|
|
2785
3094
|
entityName = handleCancel(
|
|
2786
|
-
await
|
|
3095
|
+
await p15__namespace.text({
|
|
2787
3096
|
message: "Enter entity name:",
|
|
2788
3097
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2789
3098
|
})
|
|
@@ -2793,7 +3102,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2793
3102
|
}
|
|
2794
3103
|
}
|
|
2795
3104
|
kind = handleCancel(
|
|
2796
|
-
await
|
|
3105
|
+
await p15__namespace.select({
|
|
2797
3106
|
message: "Interface definition style:",
|
|
2798
3107
|
options: [
|
|
2799
3108
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2803,7 +3112,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2803
3112
|
})
|
|
2804
3113
|
);
|
|
2805
3114
|
methodsPreset = handleCancel(
|
|
2806
|
-
await
|
|
3115
|
+
await p15__namespace.select({
|
|
2807
3116
|
message: "Method signature preset:",
|
|
2808
3117
|
options: [
|
|
2809
3118
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2813,22 +3122,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2813
3122
|
})
|
|
2814
3123
|
);
|
|
2815
3124
|
generateStub = handleCancel(
|
|
2816
|
-
await
|
|
3125
|
+
await p15__namespace.confirm({
|
|
2817
3126
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2818
3127
|
initialValue: true
|
|
2819
3128
|
})
|
|
2820
3129
|
);
|
|
2821
3130
|
if (generateStub) {
|
|
2822
3131
|
registerInProvider = handleCancel(
|
|
2823
|
-
await
|
|
3132
|
+
await p15__namespace.confirm({
|
|
2824
3133
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2825
3134
|
initialValue: true
|
|
2826
3135
|
})
|
|
2827
3136
|
);
|
|
2828
3137
|
}
|
|
2829
3138
|
}
|
|
2830
|
-
const s =
|
|
2831
|
-
s.start(`Generating interface '${
|
|
3139
|
+
const s = p15__namespace.spinner();
|
|
3140
|
+
s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
|
|
2832
3141
|
const files = [];
|
|
2833
3142
|
const options = {
|
|
2834
3143
|
name: ifaceName,
|
|
@@ -2857,7 +3166,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2857
3166
|
files.push(stubFile);
|
|
2858
3167
|
}
|
|
2859
3168
|
if (registerInProvider && generateStub) {
|
|
2860
|
-
const featureDir =
|
|
3169
|
+
const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
|
|
2861
3170
|
const providerFile = updateOrGenerateDiProvider(
|
|
2862
3171
|
featureDir,
|
|
2863
3172
|
feature,
|
|
@@ -2877,7 +3186,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2877
3186
|
}
|
|
2878
3187
|
s.stop("Interface generated successfully!");
|
|
2879
3188
|
printGeneratedFiles(files, options.dryRun);
|
|
2880
|
-
|
|
3189
|
+
p15__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2881
3190
|
}
|
|
2882
3191
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2883
3192
|
const projectRoot = findProjectRoot();
|
|
@@ -2888,14 +3197,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2888
3197
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2889
3198
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2890
3199
|
const selected = handleCancel(
|
|
2891
|
-
await
|
|
3200
|
+
await p15__namespace.select({
|
|
2892
3201
|
message: "Select target feature:",
|
|
2893
3202
|
options: featureOptions
|
|
2894
3203
|
})
|
|
2895
3204
|
);
|
|
2896
3205
|
if (selected === "__custom__") {
|
|
2897
3206
|
feature = handleCancel(
|
|
2898
|
-
await
|
|
3207
|
+
await p15__namespace.text({
|
|
2899
3208
|
message: "Enter feature name:",
|
|
2900
3209
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2901
3210
|
})
|
|
@@ -2905,7 +3214,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2905
3214
|
}
|
|
2906
3215
|
} else {
|
|
2907
3216
|
feature = handleCancel(
|
|
2908
|
-
await
|
|
3217
|
+
await p15__namespace.text({
|
|
2909
3218
|
message: "Enter target feature name:",
|
|
2910
3219
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2911
3220
|
})
|
|
@@ -2916,7 +3225,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2916
3225
|
let name = handlerNameArg?.trim();
|
|
2917
3226
|
if (!name) {
|
|
2918
3227
|
name = handleCancel(
|
|
2919
|
-
await
|
|
3228
|
+
await p15__namespace.text({
|
|
2920
3229
|
message: "HTTP Route Handler class name:",
|
|
2921
3230
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2922
3231
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2935,7 +3244,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2935
3244
|
let validationFields = [];
|
|
2936
3245
|
if (!flags.yes) {
|
|
2937
3246
|
method = handleCancel(
|
|
2938
|
-
await
|
|
3247
|
+
await p15__namespace.select({
|
|
2939
3248
|
message: "HTTP Method:",
|
|
2940
3249
|
options: [
|
|
2941
3250
|
{ value: "get", label: "GET" },
|
|
@@ -2949,7 +3258,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2949
3258
|
);
|
|
2950
3259
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2951
3260
|
subPath = handleCancel(
|
|
2952
|
-
await
|
|
3261
|
+
await p15__namespace.text({
|
|
2953
3262
|
message: "Route subpath (relative to feature prefix):",
|
|
2954
3263
|
initialValue: defaultPath,
|
|
2955
3264
|
defaultValue: defaultPath
|
|
@@ -2960,7 +3269,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2960
3269
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2961
3270
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2962
3271
|
const chosenUc = handleCancel(
|
|
2963
|
-
await
|
|
3272
|
+
await p15__namespace.select({
|
|
2964
3273
|
message: "Connect this handler to a domain Use Case?",
|
|
2965
3274
|
options: ucOptions,
|
|
2966
3275
|
initialValue: ucOptions[0]?.value
|
|
@@ -2971,7 +3280,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2971
3280
|
}
|
|
2972
3281
|
}
|
|
2973
3282
|
includeValidation = handleCancel(
|
|
2974
|
-
await
|
|
3283
|
+
await p15__namespace.confirm({
|
|
2975
3284
|
message: "Include Zod inbound request validation?",
|
|
2976
3285
|
initialValue: method !== "delete"
|
|
2977
3286
|
})
|
|
@@ -2979,7 +3288,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2979
3288
|
if (includeValidation) {
|
|
2980
3289
|
const defaultSource = method === "get" ? "query" : "body";
|
|
2981
3290
|
validationSource = handleCancel(
|
|
2982
|
-
await
|
|
3291
|
+
await p15__namespace.select({
|
|
2983
3292
|
message: "Validation data source:",
|
|
2984
3293
|
options: [
|
|
2985
3294
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -2993,8 +3302,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2993
3302
|
});
|
|
2994
3303
|
}
|
|
2995
3304
|
}
|
|
2996
|
-
const s =
|
|
2997
|
-
s.start(`Generating HTTP handler '${
|
|
3305
|
+
const s = p15__namespace.spinner();
|
|
3306
|
+
s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
|
|
2998
3307
|
const options = {
|
|
2999
3308
|
name: className,
|
|
3000
3309
|
feature,
|
|
@@ -3012,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3012
3321
|
const files = generateHttpHandler(options);
|
|
3013
3322
|
s.stop("HTTP handler generated successfully!");
|
|
3014
3323
|
printGeneratedFiles(files, options.dryRun);
|
|
3015
|
-
|
|
3324
|
+
p15__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3016
3325
|
}
|
|
3017
3326
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3018
3327
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3022,7 +3331,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
3022
3331
|
const formattedMethods = methods.map((m) => {
|
|
3023
3332
|
const isAsync = m.async !== false;
|
|
3024
3333
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
3025
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3334
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
|
|
3026
3335
|
return {
|
|
3027
3336
|
name: m.name,
|
|
3028
3337
|
isAsync,
|
|
@@ -3045,21 +3354,21 @@ function generateInjectable(options) {
|
|
|
3045
3354
|
const className = toPascalCase(options.name);
|
|
3046
3355
|
let targetDir = "";
|
|
3047
3356
|
if (options.customPath) {
|
|
3048
|
-
targetDir =
|
|
3357
|
+
targetDir = path2__default.default.isAbsolute(options.customPath) ? options.customPath : path2__default.default.join(projectRoot, options.customPath);
|
|
3049
3358
|
} else if (options.location === "feature-service" && options.feature) {
|
|
3050
|
-
targetDir =
|
|
3359
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
3051
3360
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
3052
|
-
targetDir =
|
|
3361
|
+
targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
3053
3362
|
} else if (options.location === "module") {
|
|
3054
|
-
targetDir =
|
|
3363
|
+
targetDir = path2__default.default.join(projectRoot, "modules", className);
|
|
3055
3364
|
} else {
|
|
3056
|
-
targetDir = options.feature ?
|
|
3365
|
+
targetDir = options.feature ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2__default.default.join(projectRoot, "shared", className.toLowerCase());
|
|
3057
3366
|
}
|
|
3058
|
-
const filePath =
|
|
3367
|
+
const filePath = path2__default.default.join(targetDir, `${className}.ts`);
|
|
3059
3368
|
let ifaceName;
|
|
3060
3369
|
if (options.generateInterface) {
|
|
3061
3370
|
ifaceName = `I${className}`;
|
|
3062
|
-
const ifacePath =
|
|
3371
|
+
const ifacePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
|
|
3063
3372
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
3064
3373
|
files.push(
|
|
3065
3374
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -3092,7 +3401,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3092
3401
|
let name = classNameArg?.trim();
|
|
3093
3402
|
if (!name) {
|
|
3094
3403
|
name = handleCancel(
|
|
3095
|
-
await
|
|
3404
|
+
await p15__namespace.text({
|
|
3096
3405
|
message: "Injectable class name:",
|
|
3097
3406
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3098
3407
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3105,7 +3414,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3105
3414
|
let customPath;
|
|
3106
3415
|
if (!flags.yes) {
|
|
3107
3416
|
location = handleCancel(
|
|
3108
|
-
await
|
|
3417
|
+
await p15__namespace.select({
|
|
3109
3418
|
message: "Select target architectural location:",
|
|
3110
3419
|
options: [
|
|
3111
3420
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3123,14 +3432,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3123
3432
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3124
3433
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3125
3434
|
const selected = handleCancel(
|
|
3126
|
-
await
|
|
3435
|
+
await p15__namespace.select({
|
|
3127
3436
|
message: "Select target feature:",
|
|
3128
3437
|
options: featureOptions
|
|
3129
3438
|
})
|
|
3130
3439
|
);
|
|
3131
3440
|
if (selected === "__custom__") {
|
|
3132
3441
|
feature = handleCancel(
|
|
3133
|
-
await
|
|
3442
|
+
await p15__namespace.text({
|
|
3134
3443
|
message: "Enter feature name:",
|
|
3135
3444
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3136
3445
|
})
|
|
@@ -3140,7 +3449,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3140
3449
|
}
|
|
3141
3450
|
} else {
|
|
3142
3451
|
feature = handleCancel(
|
|
3143
|
-
await
|
|
3452
|
+
await p15__namespace.text({
|
|
3144
3453
|
message: "Enter target feature name:",
|
|
3145
3454
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3146
3455
|
})
|
|
@@ -3150,7 +3459,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3150
3459
|
feature = toCamelCase(feature);
|
|
3151
3460
|
} else if (location === "custom") {
|
|
3152
3461
|
customPath = handleCancel(
|
|
3153
|
-
await
|
|
3462
|
+
await p15__namespace.text({
|
|
3154
3463
|
message: "Enter destination directory path:",
|
|
3155
3464
|
placeholder: "e.g. src/services or lib/utils",
|
|
3156
3465
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3161,14 +3470,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3161
3470
|
let generateInterface2 = false;
|
|
3162
3471
|
if (!flags.yes) {
|
|
3163
3472
|
generateInterface2 = handleCancel(
|
|
3164
|
-
await
|
|
3473
|
+
await p15__namespace.confirm({
|
|
3165
3474
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3166
3475
|
initialValue: false
|
|
3167
3476
|
})
|
|
3168
3477
|
);
|
|
3169
3478
|
}
|
|
3170
|
-
const s =
|
|
3171
|
-
s.start(`Generating injectable '${
|
|
3479
|
+
const s = p15__namespace.spinner();
|
|
3480
|
+
s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
|
|
3172
3481
|
const options = {
|
|
3173
3482
|
name: className,
|
|
3174
3483
|
feature,
|
|
@@ -3182,12 +3491,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3182
3491
|
const files = generateInjectable(options);
|
|
3183
3492
|
s.stop("Injectable class generated successfully!");
|
|
3184
3493
|
printGeneratedFiles(files, options.dryRun);
|
|
3185
|
-
|
|
3494
|
+
p15__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
|
|
3186
3495
|
}
|
|
3187
3496
|
async function runInteractiveMenu(flags = {}) {
|
|
3188
3497
|
printBanner();
|
|
3189
3498
|
const choice = handleCancel(
|
|
3190
|
-
await
|
|
3499
|
+
await p15__namespace.select({
|
|
3191
3500
|
message: "What would you like to create in your architecture?",
|
|
3192
3501
|
options: [
|
|
3193
3502
|
{
|
|
@@ -3195,6 +3504,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3195
3504
|
label: "Feature",
|
|
3196
3505
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
3197
3506
|
},
|
|
3507
|
+
{
|
|
3508
|
+
value: "shared",
|
|
3509
|
+
label: "Shared Module",
|
|
3510
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3511
|
+
},
|
|
3198
3512
|
{
|
|
3199
3513
|
value: "usecase",
|
|
3200
3514
|
label: "Use Case",
|
|
@@ -3232,6 +3546,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3232
3546
|
case "feature":
|
|
3233
3547
|
await runFeatureCommand(void 0, flags);
|
|
3234
3548
|
break;
|
|
3549
|
+
case "shared":
|
|
3550
|
+
await runSharedCommand(void 0, flags);
|
|
3551
|
+
break;
|
|
3235
3552
|
case "usecase":
|
|
3236
3553
|
await runUseCaseCommand(void 0, flags);
|
|
3237
3554
|
break;
|
|
@@ -3264,6 +3581,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3264
3581
|
case "feature":
|
|
3265
3582
|
await runFeatureCommand(name, flags);
|
|
3266
3583
|
break;
|
|
3584
|
+
case "shared":
|
|
3585
|
+
await runSharedCommand(name, flags);
|
|
3586
|
+
break;
|
|
3267
3587
|
case "usecase":
|
|
3268
3588
|
await runUseCaseCommand(name, flags);
|
|
3269
3589
|
break;
|
|
@@ -3284,21 +3604,172 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3284
3604
|
await runInjectableCommand(name, flags);
|
|
3285
3605
|
break;
|
|
3286
3606
|
default:
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
`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.`
|
|
3290
3610
|
)
|
|
3291
3611
|
);
|
|
3292
3612
|
process.exit(1);
|
|
3293
3613
|
}
|
|
3294
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
|
+
}
|
|
3295
3766
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3296
3767
|
const projectRoot = findProjectRoot();
|
|
3297
3768
|
let type = typeArg?.trim().toLowerCase();
|
|
3298
3769
|
if (type === "features") type = "feature";
|
|
3299
3770
|
if (!type) {
|
|
3300
3771
|
type = handleCancel(
|
|
3301
|
-
await
|
|
3772
|
+
await importPrompts.select({
|
|
3302
3773
|
message: "What kind of module would you like to import?",
|
|
3303
3774
|
options: [
|
|
3304
3775
|
{
|
|
@@ -3316,19 +3787,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3316
3787
|
);
|
|
3317
3788
|
}
|
|
3318
3789
|
if (type !== "shared" && type !== "feature") {
|
|
3319
|
-
|
|
3790
|
+
p15__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3320
3791
|
process.exit(1);
|
|
3321
3792
|
}
|
|
3322
3793
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3323
|
-
const s =
|
|
3324
|
-
s.start(`Connecting to registry at ${
|
|
3794
|
+
const s = p15__namespace.spinner();
|
|
3795
|
+
s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
|
|
3325
3796
|
let index;
|
|
3326
3797
|
try {
|
|
3327
3798
|
index = await fetchRegistryIndex(registryBase);
|
|
3328
3799
|
s.stop("Registry index loaded.");
|
|
3329
3800
|
} catch (err) {
|
|
3330
|
-
s.stop(
|
|
3331
|
-
|
|
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)));
|
|
3332
3803
|
process.exit(1);
|
|
3333
3804
|
}
|
|
3334
3805
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3336,11 +3807,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3336
3807
|
let name = nameArg?.trim();
|
|
3337
3808
|
if (!name) {
|
|
3338
3809
|
if (availableList.length === 0) {
|
|
3339
|
-
|
|
3810
|
+
p15__namespace.log.warn(`No ${type} modules available in the registry.`);
|
|
3340
3811
|
return;
|
|
3341
3812
|
}
|
|
3342
3813
|
name = handleCancel(
|
|
3343
|
-
await
|
|
3814
|
+
await importPrompts.select({
|
|
3344
3815
|
message: `Select a ${type} to import:`,
|
|
3345
3816
|
options: availableList.map((item) => ({
|
|
3346
3817
|
value: item.name,
|
|
@@ -3352,122 +3823,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3352
3823
|
}
|
|
3353
3824
|
if (!candidateNames.includes(name)) {
|
|
3354
3825
|
const closest = findClosestMatch(name, candidateNames);
|
|
3355
|
-
|
|
3826
|
+
p15__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3356
3827
|
if (closest) {
|
|
3357
|
-
|
|
3828
|
+
p15__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
|
|
3358
3829
|
}
|
|
3359
3830
|
process.exit(1);
|
|
3360
3831
|
}
|
|
3361
3832
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3362
3833
|
const totalWrittenFiles = [];
|
|
3363
3834
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3364
|
-
async function importModuleRecursive(modType,
|
|
3365
|
-
const modKey = `${modType}:${
|
|
3835
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3836
|
+
const modKey = `${modType}:${targetName}`;
|
|
3366
3837
|
if (importedModules.has(modKey)) return;
|
|
3367
|
-
|
|
3838
|
+
p15__namespace.log.step(
|
|
3839
|
+
`Importing ${modType}: ${pc15__default.default.cyan(targetName)}${targetName !== registryName ? pc15__default.default.dim(` (from ${registryName})`) : ""}...`
|
|
3840
|
+
);
|
|
3368
3841
|
let manifest;
|
|
3369
3842
|
try {
|
|
3370
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3843
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3371
3844
|
} catch (err) {
|
|
3372
|
-
|
|
3845
|
+
p15__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3373
3846
|
process.exit(1);
|
|
3374
3847
|
}
|
|
3848
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3849
|
+
if (targetName !== registryName) {
|
|
3850
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3851
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3852
|
+
}
|
|
3375
3853
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3376
3854
|
for (const dep of sharedDeps) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3855
|
+
await resolveDependency(
|
|
3856
|
+
"shared",
|
|
3857
|
+
dep,
|
|
3858
|
+
aliasMap,
|
|
3859
|
+
projectRoot,
|
|
3860
|
+
flags,
|
|
3861
|
+
importModuleRecursive,
|
|
3862
|
+
importedModules,
|
|
3863
|
+
registryBase,
|
|
3864
|
+
index
|
|
3865
|
+
);
|
|
3382
3866
|
}
|
|
3383
3867
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3384
3868
|
for (const dep of featureDeps) {
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3869
|
+
await resolveDependency(
|
|
3870
|
+
"feature",
|
|
3871
|
+
dep,
|
|
3872
|
+
aliasMap,
|
|
3873
|
+
projectRoot,
|
|
3874
|
+
flags,
|
|
3875
|
+
importModuleRecursive,
|
|
3876
|
+
importedModules,
|
|
3877
|
+
registryBase,
|
|
3878
|
+
index
|
|
3879
|
+
);
|
|
3390
3880
|
}
|
|
3391
3881
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3392
3882
|
requiredNpmPackages.add(pkg);
|
|
3393
3883
|
}
|
|
3394
3884
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3395
|
-
const targetDir =
|
|
3885
|
+
const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3886
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3396
3887
|
for (const file of manifest.files || []) {
|
|
3397
3888
|
if (file === "registry.json") continue;
|
|
3398
|
-
const remoteFilePath = `${folder}/${
|
|
3399
|
-
const localFilePath =
|
|
3400
|
-
if (
|
|
3401
|
-
|
|
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
|
+
);
|
|
3402
3895
|
continue;
|
|
3403
3896
|
}
|
|
3404
|
-
|
|
3897
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3898
|
+
content = rewriteImports(content, aliasMap);
|
|
3405
3899
|
if (!flags.dryRun) {
|
|
3406
|
-
|
|
3407
|
-
|
|
3900
|
+
fs12__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
|
|
3901
|
+
fs12__default.default.writeFileSync(localFilePath, content, "utf-8");
|
|
3408
3902
|
}
|
|
3409
|
-
const displayPath =
|
|
3903
|
+
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
3410
3904
|
totalWrittenFiles.push(displayPath);
|
|
3411
|
-
|
|
3905
|
+
p15__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
|
|
3412
3906
|
}
|
|
3413
3907
|
importedModules.add(modKey);
|
|
3414
3908
|
}
|
|
3415
|
-
await importModuleRecursive(type, name);
|
|
3909
|
+
await importModuleRecursive(type, name, name);
|
|
3416
3910
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3417
3911
|
if (missingNpm.length > 0) {
|
|
3418
3912
|
if (flags.skipInstall || flags.dryRun) {
|
|
3419
|
-
|
|
3913
|
+
p15__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
|
|
3420
3914
|
} else {
|
|
3421
3915
|
try {
|
|
3422
3916
|
installNpmPackages(projectRoot, missingNpm);
|
|
3423
3917
|
} catch (err) {
|
|
3424
|
-
|
|
3918
|
+
p15__namespace.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3425
3919
|
}
|
|
3426
3920
|
}
|
|
3427
3921
|
}
|
|
3428
3922
|
if (flags.dryRun) {
|
|
3429
|
-
|
|
3923
|
+
p15__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
3430
3924
|
} else {
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
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})`
|
|
3434
3930
|
)
|
|
3435
3931
|
);
|
|
3436
3932
|
}
|
|
3437
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
|
+
}
|
|
3438
4463
|
|
|
3439
4464
|
// src/cli.ts
|
|
3440
4465
|
function createCli() {
|
|
3441
4466
|
const cli = cac.cac("mason");
|
|
3442
|
-
cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").action(async (args, flags) => {
|
|
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) => {
|
|
3443
4468
|
if (args && args.length > 0) {
|
|
3444
|
-
|
|
3445
|
-
|
|
4469
|
+
p15__namespace.log.error(
|
|
4470
|
+
pc15__default.default.red(
|
|
3446
4471
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3447
4472
|
)
|
|
3448
4473
|
);
|
|
3449
4474
|
process.exit(1);
|
|
3450
4475
|
}
|
|
3451
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
|
+
}
|
|
3452
4563
|
const choice = handleCancel(
|
|
3453
|
-
await
|
|
4564
|
+
await p15__namespace.select({
|
|
3454
4565
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3455
4566
|
options: [
|
|
3456
4567
|
{
|
|
3457
4568
|
value: "create",
|
|
3458
4569
|
label: "Create local component",
|
|
3459
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
4570
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3460
4571
|
},
|
|
3461
4572
|
{
|
|
3462
4573
|
value: "import",
|
|
3463
4574
|
label: "Import from registry",
|
|
3464
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"
|
|
3465
4581
|
}
|
|
3466
4582
|
]
|
|
3467
4583
|
})
|
|
3468
4584
|
);
|
|
3469
4585
|
if (choice === "create") {
|
|
3470
4586
|
await handleCreate(void 0, void 0, flags);
|
|
4587
|
+
} else if (choice === "registry") {
|
|
4588
|
+
await handleRegistry(void 0, void 0, void 0, flags);
|
|
3471
4589
|
} else {
|
|
3472
4590
|
await handleImport(void 0, void 0, flags);
|
|
3473
4591
|
}
|
|
@@ -3477,8 +4595,8 @@ function createCli() {
|
|
|
3477
4595
|
});
|
|
3478
4596
|
cli.command(
|
|
3479
4597
|
"create [type] [name]",
|
|
3480
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3481
|
-
).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("-f, --feature <feature>", "Target feature name").option("-t, --target <dir>", "Override target directory").option("--express", "Include Express presentation layer (pxExpress)").option("--no-express", "Skip Express presentation layer").option("-i, --interface <interface>", "Interface or abstract class to implement").option("-k, --kind <kind>", "Implementation kind ('stub' or 'concrete')").option("-g, --global", "Place in global infrastructure/ instead of feature").option("-m, --method <method>", "HTTP method (get, post, put, patch, delete)").option("-p, --path <path>", "Route subpath (e.g. /:id)").option("-u, --usecase <usecase>", "Connected use case name").option("--message <message>", "Custom success message").option("--domainNames <names>", "Comma-separated domain entity names").example("mason create").example("mason create feature users").example("mason create usecase PlaceOrder -f orders").example("mason create domain User -f users").example("mason create infrastructure PostgresUserRepository -f users -i IUserRepository").example("mason create interface IUserRepository -f users").example("mason create httpHandler GetUserHttp -f users -m get -p /:id").example("mason create injectable PasswordHasher").action(async (type, name, flags = {}) => {
|
|
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 = {}) => {
|
|
3482
4600
|
try {
|
|
3483
4601
|
await handleCreate(type, name, flags);
|
|
3484
4602
|
} catch (err) {
|
|
@@ -3495,8 +4613,28 @@ function createCli() {
|
|
|
3495
4613
|
handleCliError(err);
|
|
3496
4614
|
}
|
|
3497
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
|
+
});
|
|
3498
4635
|
const createSubtypes = [
|
|
3499
4636
|
"feature",
|
|
4637
|
+
"shared",
|
|
3500
4638
|
"usecase",
|
|
3501
4639
|
"domain",
|
|
3502
4640
|
"infrastructure",
|
|
@@ -3517,7 +4655,7 @@ function createCli() {
|
|
|
3517
4655
|
});
|
|
3518
4656
|
}
|
|
3519
4657
|
cli.help();
|
|
3520
|
-
cli.version("1.0.
|
|
4658
|
+
cli.version("1.0.13");
|
|
3521
4659
|
return cli;
|
|
3522
4660
|
}
|
|
3523
4661
|
function handleCliError(err) {
|
|
@@ -3525,7 +4663,7 @@ function handleCliError(err) {
|
|
|
3525
4663
|
process.exit(0);
|
|
3526
4664
|
}
|
|
3527
4665
|
const message = err instanceof Error ? err.message : String(err);
|
|
3528
|
-
|
|
4666
|
+
p15__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
|
|
3529
4667
|
process.exit(1);
|
|
3530
4668
|
}
|
|
3531
4669
|
async function runCli(argv = process.argv) {
|
|
@@ -3535,6 +4673,7 @@ async function runCli(argv = process.argv) {
|
|
|
3535
4673
|
|
|
3536
4674
|
exports.DEFAULT_REGISTRY_BASE = DEFAULT_REGISTRY_BASE;
|
|
3537
4675
|
exports.PromptCancelledError = PromptCancelledError;
|
|
4676
|
+
exports.computeDirectoryHash = computeDirectoryHash;
|
|
3538
4677
|
exports.concreteInfraTemplate = concreteInfraTemplate;
|
|
3539
4678
|
exports.createCli = createCli;
|
|
3540
4679
|
exports.detectPackageManager = detectPackageManager;
|
|
@@ -3562,16 +4701,22 @@ exports.generateInjectable = generateInjectable;
|
|
|
3562
4701
|
exports.generateInjectableCode = generateInjectableCode;
|
|
3563
4702
|
exports.generateInterface = generateInterface;
|
|
3564
4703
|
exports.generateInterfaceCode = generateInterfaceCode;
|
|
4704
|
+
exports.generateShared = generateShared;
|
|
3565
4705
|
exports.generateStubCode = generateStubCode;
|
|
3566
4706
|
exports.generateUseCase = generateUseCase;
|
|
3567
4707
|
exports.generateUseCaseCode = generateUseCaseCode;
|
|
3568
4708
|
exports.generateUseCaseTestCode = generateUseCaseTestCode;
|
|
4709
|
+
exports.getAllFiles = getAllFiles;
|
|
4710
|
+
exports.getConfiguredFeaturesDir = getConfiguredFeaturesDir;
|
|
4711
|
+
exports.getConfiguredSharedDir = getConfiguredSharedDir;
|
|
4712
|
+
exports.getEffectiveMode = getEffectiveMode;
|
|
3569
4713
|
exports.getMissingNpmPackages = getMissingNpmPackages;
|
|
3570
4714
|
exports.getRegistryBase = getRegistryBase;
|
|
3571
4715
|
exports.getTemplatesDir = getTemplatesDir;
|
|
3572
4716
|
exports.handleCancel = handleCancel;
|
|
3573
4717
|
exports.handleCreate = handleCreate;
|
|
3574
4718
|
exports.handleImport = handleImport;
|
|
4719
|
+
exports.handleRegistry = handleRegistry;
|
|
3575
4720
|
exports.hbs = hbs;
|
|
3576
4721
|
exports.httpHandlerTemplate = httpHandlerTemplate;
|
|
3577
4722
|
exports.injectableInterfaceTemplate = injectableInterfaceTemplate;
|
|
@@ -3583,8 +4728,10 @@ exports.levenshteinDistance = levenshteinDistance;
|
|
|
3583
4728
|
exports.listDomainEntities = listDomainEntities;
|
|
3584
4729
|
exports.listDomainInterfaces = listDomainInterfaces;
|
|
3585
4730
|
exports.listExistingFeatures = listExistingFeatures;
|
|
4731
|
+
exports.listExistingShared = listExistingShared;
|
|
3586
4732
|
exports.listUseCases = listUseCases;
|
|
3587
4733
|
exports.loadInterfaceHierarchy = loadInterfaceHierarchy;
|
|
4734
|
+
exports.loadMasonConfig = loadMasonConfig;
|
|
3588
4735
|
exports.loadTemplate = loadTemplate;
|
|
3589
4736
|
exports.normalizeRegistryUrl = normalizeRegistryUrl;
|
|
3590
4737
|
exports.parseInterfaceMethodsAndImports = parseInterfaceMethodsAndImports;
|
|
@@ -3595,7 +4742,9 @@ exports.removeInterfacePrefix = removeInterfacePrefix;
|
|
|
3595
4742
|
exports.renderTemplate = renderTemplate;
|
|
3596
4743
|
exports.renderTemplateFile = renderTemplateFile;
|
|
3597
4744
|
exports.resolveFeaturesDir = resolveFeaturesDir;
|
|
4745
|
+
exports.resolveSharedDir = resolveSharedDir;
|
|
3598
4746
|
exports.runCli = runCli;
|
|
4747
|
+
exports.runSharedCommand = runSharedCommand;
|
|
3599
4748
|
exports.stubInfraTemplate = stubInfraTemplate;
|
|
3600
4749
|
exports.synthesizeCrudMethods = synthesizeCrudMethods;
|
|
3601
4750
|
exports.toCamelCase = toCamelCase;
|