@solid-stack/mason 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1508 -315
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1504 -311
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/feature.d.ts.map +1 -1
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/registry.d.ts +64 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/core/sharedGenerator.d.ts +13 -0
- package/dist/core/sharedGenerator.d.ts.map +1 -0
- package/dist/index.cjs +1531 -315
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1515 -312
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +35 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/integrity.d.ts +11 -0
- package/dist/utils/integrity.d.ts.map +1 -0
- package/dist/utils/project.d.ts +18 -2
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/registry.d.ts +20 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
1
|
+
import fs12 from 'fs';
|
|
2
|
+
import path2 from 'path';
|
|
3
|
+
import * as p15 from '@clack/prompts';
|
|
4
|
+
import pc15 from 'picocolors';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
7
8
|
import Handlebars from 'handlebars';
|
|
8
9
|
import { cac } from 'cac';
|
|
9
10
|
|
|
@@ -75,8 +76,8 @@ function isValidIdentifier(str) {
|
|
|
75
76
|
}
|
|
76
77
|
function writeFileSafely(absolutePath, content, options = {}) {
|
|
77
78
|
const projectRoot = options.projectRoot || process.cwd();
|
|
78
|
-
const relPath =
|
|
79
|
-
const existedBefore =
|
|
79
|
+
const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
80
|
+
const existedBefore = fs12.existsSync(absolutePath);
|
|
80
81
|
if (options.dryRun) {
|
|
81
82
|
return {
|
|
82
83
|
relativePath: relPath,
|
|
@@ -95,11 +96,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
95
96
|
existedBefore: true
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
|
-
const dir =
|
|
99
|
-
if (!
|
|
100
|
-
|
|
99
|
+
const dir = path2.dirname(absolutePath);
|
|
100
|
+
if (!fs12.existsSync(dir)) {
|
|
101
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
101
102
|
}
|
|
102
|
-
|
|
103
|
+
fs12.writeFileSync(absolutePath, content, "utf-8");
|
|
103
104
|
return {
|
|
104
105
|
relativePath: relPath,
|
|
105
106
|
absolutePath,
|
|
@@ -111,41 +112,114 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
111
112
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
112
113
|
console.log("");
|
|
113
114
|
if (isDryRun) {
|
|
114
|
-
|
|
115
|
+
p15.log.warn(pc15.yellow(pc15.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
115
116
|
}
|
|
116
117
|
for (const f of files) {
|
|
117
118
|
if (f.action === "created") {
|
|
118
|
-
|
|
119
|
+
p15.log.success(`${pc15.bold(f.relativePath)} ${pc15.dim("(created)")}`);
|
|
119
120
|
} else if (f.action === "overwritten") {
|
|
120
|
-
|
|
121
|
+
p15.log.warn(`${pc15.bold(f.relativePath)} ${pc15.dim("(overwritten)")}`);
|
|
121
122
|
} else if (f.action === "skipped") {
|
|
122
|
-
|
|
123
|
+
p15.log.info(`${pc15.dim(f.relativePath)} ${pc15.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
123
124
|
} else {
|
|
124
|
-
|
|
125
|
+
p15.log.info(`${pc15.bold(f.relativePath)} ${pc15.dim("(preview)")}`);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
function loadMasonConfig(startDir = process.cwd()) {
|
|
130
|
+
let current = path2.resolve(startDir);
|
|
131
|
+
while (true) {
|
|
132
|
+
const configPath = path2.join(current, "mason.config.json");
|
|
133
|
+
if (fs12.existsSync(configPath)) {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const parent = path2.dirname(current);
|
|
141
|
+
if (parent === current) break;
|
|
142
|
+
current = parent;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function getEffectiveMode(projectRoot, flags) {
|
|
147
|
+
if (flags?.registry) return "registry";
|
|
148
|
+
const config = loadMasonConfig(projectRoot);
|
|
149
|
+
if (config?.mode) return config.mode;
|
|
150
|
+
if (fs12.existsSync(path2.join(projectRoot, "index.json")) && (fs12.existsSync(path2.join(projectRoot, "features")) || fs12.existsSync(path2.join(projectRoot, "shared"))) && !fs12.existsSync(path2.join(projectRoot, "src"))) {
|
|
151
|
+
return "registry";
|
|
152
|
+
}
|
|
153
|
+
return "app";
|
|
154
|
+
}
|
|
155
|
+
function getConfiguredFeaturesDir(projectRoot, flags) {
|
|
156
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
157
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
158
|
+
const config = loadMasonConfig(projectRoot);
|
|
159
|
+
if (config?.paths?.features) {
|
|
160
|
+
return path2.resolve(projectRoot, config.paths.features);
|
|
161
|
+
}
|
|
162
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
163
|
+
if (mode === "registry") {
|
|
164
|
+
return path2.join(projectRoot, "features");
|
|
165
|
+
}
|
|
166
|
+
if (fs12.existsSync(path2.join(projectRoot, "src/features"))) {
|
|
167
|
+
return path2.join(projectRoot, "src/features");
|
|
168
|
+
}
|
|
169
|
+
return path2.join(projectRoot, "features");
|
|
170
|
+
}
|
|
171
|
+
function getConfiguredSharedDir(projectRoot, flags) {
|
|
172
|
+
if (flags?.target) return path2.resolve(projectRoot, flags.target);
|
|
173
|
+
if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
|
|
174
|
+
const config = loadMasonConfig(projectRoot);
|
|
175
|
+
if (config?.paths?.shared) {
|
|
176
|
+
return path2.resolve(projectRoot, config.paths.shared);
|
|
177
|
+
}
|
|
178
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
179
|
+
if (mode === "registry") {
|
|
180
|
+
return path2.join(projectRoot, "shared");
|
|
181
|
+
}
|
|
182
|
+
if (fs12.existsSync(path2.join(projectRoot, "src/shared"))) {
|
|
183
|
+
return path2.join(projectRoot, "src/shared");
|
|
184
|
+
}
|
|
185
|
+
return path2.join(projectRoot, "shared");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/utils/project.ts
|
|
128
189
|
function findProjectRoot(startDir = process.cwd()) {
|
|
129
|
-
let current =
|
|
190
|
+
let current = path2.resolve(startDir);
|
|
130
191
|
while (true) {
|
|
131
|
-
if (
|
|
192
|
+
if (fs12.existsSync(path2.join(current, "package.json"))) {
|
|
132
193
|
return current;
|
|
133
194
|
}
|
|
134
|
-
const parent =
|
|
195
|
+
const parent = path2.dirname(current);
|
|
135
196
|
if (parent === current) {
|
|
136
|
-
return
|
|
197
|
+
return path2.resolve(startDir);
|
|
137
198
|
}
|
|
138
199
|
current = parent;
|
|
139
200
|
}
|
|
140
201
|
}
|
|
141
|
-
function resolveFeaturesDir(projectRoot) {
|
|
142
|
-
return
|
|
202
|
+
function resolveFeaturesDir(projectRoot, flags) {
|
|
203
|
+
return getConfiguredFeaturesDir(projectRoot, flags);
|
|
204
|
+
}
|
|
205
|
+
function resolveSharedDir(projectRoot, flags) {
|
|
206
|
+
return getConfiguredSharedDir(projectRoot, flags);
|
|
207
|
+
}
|
|
208
|
+
function listExistingShared(projectRoot) {
|
|
209
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
210
|
+
if (!fs12.existsSync(sharedDir)) return [];
|
|
211
|
+
try {
|
|
212
|
+
const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
|
|
213
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
214
|
+
} catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
143
217
|
}
|
|
144
218
|
function listExistingFeatures(projectRoot) {
|
|
145
219
|
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
146
|
-
if (!
|
|
220
|
+
if (!fs12.existsSync(featuresDir)) return [];
|
|
147
221
|
try {
|
|
148
|
-
const entries =
|
|
222
|
+
const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
|
|
149
223
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
150
224
|
} catch {
|
|
151
225
|
return [];
|
|
@@ -155,14 +229,14 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
155
229
|
const results = [];
|
|
156
230
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
157
231
|
for (const feat of features) {
|
|
158
|
-
const domainDir =
|
|
159
|
-
if (!
|
|
232
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
233
|
+
if (!fs12.existsSync(domainDir)) continue;
|
|
160
234
|
try {
|
|
161
|
-
const files =
|
|
235
|
+
const files = fs12.readdirSync(domainDir);
|
|
162
236
|
for (const file of files) {
|
|
163
237
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
164
238
|
const name = file.replace(/\.ts$/, "");
|
|
165
|
-
const fullPath =
|
|
239
|
+
const fullPath = path2.join(domainDir, file);
|
|
166
240
|
if (/^I[A-Z]/.test(name)) {
|
|
167
241
|
results.push({
|
|
168
242
|
name,
|
|
@@ -171,7 +245,7 @@ function listDomainInterfaces(projectRoot, featureName) {
|
|
|
171
245
|
});
|
|
172
246
|
} else {
|
|
173
247
|
try {
|
|
174
|
-
const content =
|
|
248
|
+
const content = fs12.readFileSync(fullPath, "utf-8");
|
|
175
249
|
if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
|
|
176
250
|
results.push({
|
|
177
251
|
name,
|
|
@@ -193,17 +267,17 @@ function listDomainEntities(projectRoot, featureName) {
|
|
|
193
267
|
const results = [];
|
|
194
268
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
195
269
|
for (const feat of features) {
|
|
196
|
-
const domainDir =
|
|
197
|
-
if (!
|
|
270
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
271
|
+
if (!fs12.existsSync(domainDir)) continue;
|
|
198
272
|
try {
|
|
199
|
-
const files =
|
|
273
|
+
const files = fs12.readdirSync(domainDir);
|
|
200
274
|
for (const file of files) {
|
|
201
275
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
202
276
|
const name = file.replace(/\.ts$/, "");
|
|
203
277
|
if (!/^I[A-Z]/.test(name)) {
|
|
204
278
|
results.push({
|
|
205
279
|
name,
|
|
206
|
-
fullPath:
|
|
280
|
+
fullPath: path2.join(domainDir, file),
|
|
207
281
|
feature: feat
|
|
208
282
|
});
|
|
209
283
|
}
|
|
@@ -218,16 +292,16 @@ function listUseCases(projectRoot, featureName) {
|
|
|
218
292
|
const results = [];
|
|
219
293
|
const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
|
|
220
294
|
for (const feat of features) {
|
|
221
|
-
const useCasesDir =
|
|
222
|
-
if (!
|
|
295
|
+
const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
|
|
296
|
+
if (!fs12.existsSync(useCasesDir)) continue;
|
|
223
297
|
try {
|
|
224
|
-
const files =
|
|
298
|
+
const files = fs12.readdirSync(useCasesDir);
|
|
225
299
|
for (const file of files) {
|
|
226
300
|
if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
|
|
227
301
|
const name = file.replace(/\.ts$/, "");
|
|
228
302
|
results.push({
|
|
229
303
|
name,
|
|
230
|
-
fullPath:
|
|
304
|
+
fullPath: path2.join(useCasesDir, file),
|
|
231
305
|
feature: feat
|
|
232
306
|
});
|
|
233
307
|
}
|
|
@@ -244,23 +318,23 @@ var PromptCancelledError = class extends Error {
|
|
|
244
318
|
}
|
|
245
319
|
};
|
|
246
320
|
function handleCancel(result) {
|
|
247
|
-
if (
|
|
248
|
-
|
|
321
|
+
if (p15.isCancel(result)) {
|
|
322
|
+
p15.cancel("Operation cancelled.");
|
|
249
323
|
throw new PromptCancelledError();
|
|
250
324
|
}
|
|
251
325
|
return result;
|
|
252
326
|
}
|
|
253
327
|
function printBanner() {
|
|
254
328
|
console.log("");
|
|
255
|
-
|
|
329
|
+
p15.intro(pc15.bgCyan(pc15.black(" @solid-stack/mason ")));
|
|
256
330
|
}
|
|
257
331
|
async function promptProperties(options = {}) {
|
|
258
332
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
259
333
|
if (properties.length > 0) {
|
|
260
|
-
const summary = properties.map((
|
|
261
|
-
|
|
334
|
+
const summary = properties.map((p17) => `${p17.name}: ${p17.type}${p17.optional ? "?" : ""}`).join(", ");
|
|
335
|
+
p15.log.info(pc15.dim(`Pre-configured fields: ${summary}`));
|
|
262
336
|
const addMore = handleCancel(
|
|
263
|
-
await
|
|
337
|
+
await p15.confirm({
|
|
264
338
|
message: "Would you like to add custom fields/properties?",
|
|
265
339
|
initialValue: false
|
|
266
340
|
})
|
|
@@ -269,10 +343,10 @@ async function promptProperties(options = {}) {
|
|
|
269
343
|
return properties;
|
|
270
344
|
}
|
|
271
345
|
}
|
|
272
|
-
|
|
346
|
+
p15.log.info(pc15.cyan("Enter properties (leave name blank when finished):"));
|
|
273
347
|
while (true) {
|
|
274
348
|
const name = handleCancel(
|
|
275
|
-
await
|
|
349
|
+
await p15.text({
|
|
276
350
|
message: "Property name (or press Enter to finish):",
|
|
277
351
|
placeholder: "e.g. title, email, status"
|
|
278
352
|
})
|
|
@@ -281,7 +355,7 @@ async function promptProperties(options = {}) {
|
|
|
281
355
|
break;
|
|
282
356
|
}
|
|
283
357
|
const type = handleCancel(
|
|
284
|
-
await
|
|
358
|
+
await p15.select({
|
|
285
359
|
message: `Type for property "${name.trim()}":`,
|
|
286
360
|
options: [
|
|
287
361
|
{ value: "string", label: "string" },
|
|
@@ -298,7 +372,7 @@ async function promptProperties(options = {}) {
|
|
|
298
372
|
let finalType = type;
|
|
299
373
|
if (type === "custom") {
|
|
300
374
|
finalType = handleCancel(
|
|
301
|
-
await
|
|
375
|
+
await p15.text({
|
|
302
376
|
message: `Enter custom type for "${name.trim()}":`,
|
|
303
377
|
placeholder: "e.g. UserRole, string | null",
|
|
304
378
|
validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
|
|
@@ -306,7 +380,7 @@ async function promptProperties(options = {}) {
|
|
|
306
380
|
);
|
|
307
381
|
}
|
|
308
382
|
const optional = handleCancel(
|
|
309
|
-
await
|
|
383
|
+
await p15.confirm({
|
|
310
384
|
message: `Is "${name.trim()}" optional?`,
|
|
311
385
|
initialValue: false
|
|
312
386
|
})
|
|
@@ -316,47 +390,112 @@ async function promptProperties(options = {}) {
|
|
|
316
390
|
type: finalType.trim(),
|
|
317
391
|
optional
|
|
318
392
|
});
|
|
319
|
-
|
|
320
|
-
|
|
393
|
+
p15.log.success(
|
|
394
|
+
pc15.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
321
395
|
);
|
|
322
396
|
}
|
|
323
397
|
return properties;
|
|
324
398
|
}
|
|
325
399
|
var DEFAULT_REGISTRY_BASE = "https://raw.githubusercontent.com/solid-stack-digital/mason-registry/main";
|
|
400
|
+
function normalizeRegistryUrl(urlOrPath, baseDir) {
|
|
401
|
+
let trimmed = urlOrPath.trim();
|
|
402
|
+
const gitSshRegex = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
|
|
403
|
+
const sshMatch = trimmed.match(gitSshRegex);
|
|
404
|
+
if (sshMatch && sshMatch[1] && sshMatch[2]) {
|
|
405
|
+
return `https://raw.githubusercontent.com/${sshMatch[1]}/${sshMatch[2]}/main`;
|
|
406
|
+
}
|
|
407
|
+
const githubWebRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?\/?$/;
|
|
408
|
+
const webMatch = trimmed.match(githubWebRegex);
|
|
409
|
+
if (webMatch && webMatch[1] && webMatch[2]) {
|
|
410
|
+
const owner = webMatch[1];
|
|
411
|
+
const repo = webMatch[2];
|
|
412
|
+
const branch = webMatch[3] || "main";
|
|
413
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
414
|
+
}
|
|
415
|
+
if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
|
|
416
|
+
return path2.resolve(baseDir, trimmed);
|
|
417
|
+
}
|
|
418
|
+
return trimmed;
|
|
419
|
+
}
|
|
420
|
+
function findLocalRegistry(startDir = process.cwd()) {
|
|
421
|
+
let current = path2.resolve(startDir);
|
|
422
|
+
while (true) {
|
|
423
|
+
const candidate = path2.join(current, "mason-registry");
|
|
424
|
+
if (fs12.existsSync(candidate) && fs12.existsSync(path2.join(candidate, "index.json"))) {
|
|
425
|
+
return candidate;
|
|
426
|
+
}
|
|
427
|
+
const parent = path2.dirname(current);
|
|
428
|
+
if (parent === current) break;
|
|
429
|
+
current = parent;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
326
433
|
function getRegistryBase(projectRoot, override) {
|
|
434
|
+
const rootDir = projectRoot ? path2.resolve(projectRoot) : process.cwd();
|
|
327
435
|
if (override && override.trim()) {
|
|
328
|
-
return override.trim();
|
|
436
|
+
return normalizeRegistryUrl(override.trim(), rootDir);
|
|
329
437
|
}
|
|
330
438
|
if (process.env.MASON_REGISTRY_URL && process.env.MASON_REGISTRY_URL.trim()) {
|
|
331
|
-
return process.env.MASON_REGISTRY_URL.trim();
|
|
439
|
+
return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
|
|
332
440
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
441
|
+
const configLocations = [
|
|
442
|
+
path2.join(rootDir, "mason.config.json"),
|
|
443
|
+
path2.join(process.cwd(), "mason.config.json")
|
|
444
|
+
];
|
|
445
|
+
for (const configPath of configLocations) {
|
|
446
|
+
if (fs12.existsSync(configPath)) {
|
|
336
447
|
try {
|
|
337
|
-
const config = JSON.parse(
|
|
448
|
+
const config = JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
338
449
|
if (config.registry && typeof config.registry === "string") {
|
|
339
|
-
return config.registry.trim();
|
|
450
|
+
return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
|
|
340
451
|
}
|
|
341
452
|
} catch {
|
|
342
453
|
}
|
|
343
454
|
}
|
|
344
455
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
456
|
+
const pkgLocations = [
|
|
457
|
+
path2.join(rootDir, "package.json"),
|
|
458
|
+
path2.join(process.cwd(), "package.json")
|
|
459
|
+
];
|
|
460
|
+
for (const pkgPath of pkgLocations) {
|
|
461
|
+
if (fs12.existsSync(pkgPath)) {
|
|
462
|
+
try {
|
|
463
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
464
|
+
if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
|
|
465
|
+
return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
349
469
|
}
|
|
350
470
|
}
|
|
471
|
+
const localRegistry = findLocalRegistry(rootDir);
|
|
472
|
+
if (localRegistry) {
|
|
473
|
+
return localRegistry;
|
|
474
|
+
}
|
|
351
475
|
return DEFAULT_REGISTRY_BASE;
|
|
352
476
|
}
|
|
353
477
|
async function fetchRegistryFile(registryBase, relPath) {
|
|
354
478
|
const cleanRelPath = relPath.replace(/^\/+/, "");
|
|
355
479
|
if (registryBase.startsWith("http://") || registryBase.startsWith("https://")) {
|
|
356
480
|
const url = `${registryBase.replace(/\/+$/, "")}/${cleanRelPath}`;
|
|
357
|
-
const
|
|
481
|
+
const headers = {};
|
|
482
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
483
|
+
if (token) {
|
|
484
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
485
|
+
}
|
|
486
|
+
const res = await fetch(url, { headers });
|
|
358
487
|
if (!res.ok) {
|
|
359
|
-
|
|
488
|
+
let extraHint = "";
|
|
489
|
+
if (res.status === 404) {
|
|
490
|
+
extraHint = `
|
|
491
|
+
Possible reasons:
|
|
492
|
+
\u2022 If using a private GitHub repository, ensure GITHUB_TOKEN is set in your environment.
|
|
493
|
+
\u2022 If your registry has not yet been pushed to GitHub, point to your local directory:
|
|
494
|
+
- CLI flag: mason import ... --registry /path/to/mason-registry
|
|
495
|
+
- Environment: export MASON_REGISTRY_URL=/path/to/mason-registry
|
|
496
|
+
- Config file: Create mason.config.json with { "registry": "/path/to/mason-registry" }`;
|
|
497
|
+
}
|
|
498
|
+
throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})${extraHint}`);
|
|
360
499
|
}
|
|
361
500
|
return await res.text();
|
|
362
501
|
}
|
|
@@ -364,11 +503,11 @@ async function fetchRegistryFile(registryBase, relPath) {
|
|
|
364
503
|
if (baseDir.startsWith("file://")) {
|
|
365
504
|
baseDir = fileURLToPath(baseDir);
|
|
366
505
|
}
|
|
367
|
-
const filePath =
|
|
368
|
-
if (!
|
|
506
|
+
const filePath = path2.resolve(baseDir, cleanRelPath);
|
|
507
|
+
if (!fs12.existsSync(filePath)) {
|
|
369
508
|
throw new Error(`Registry file not found at ${filePath}`);
|
|
370
509
|
}
|
|
371
|
-
return
|
|
510
|
+
return fs12.promises.readFile(filePath, "utf-8");
|
|
372
511
|
}
|
|
373
512
|
async function fetchRegistryIndex(registryBase) {
|
|
374
513
|
const content = await fetchRegistryFile(registryBase, "index.json");
|
|
@@ -430,26 +569,26 @@ function findClosestMatch(target, candidates) {
|
|
|
430
569
|
return null;
|
|
431
570
|
}
|
|
432
571
|
function detectPackageManager(projectRoot) {
|
|
433
|
-
if (
|
|
434
|
-
if (
|
|
435
|
-
if (
|
|
572
|
+
if (fs12.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
573
|
+
if (fs12.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
574
|
+
if (fs12.existsSync(path2.join(projectRoot, "bun.lockb")) || fs12.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
|
|
436
575
|
return "npm";
|
|
437
576
|
}
|
|
438
577
|
function getMissingNpmPackages(projectRoot, requiredPackages) {
|
|
439
578
|
if (!requiredPackages || requiredPackages.length === 0) return [];
|
|
440
|
-
const pkgJsonPath =
|
|
441
|
-
if (!
|
|
579
|
+
const pkgJsonPath = path2.join(projectRoot, "package.json");
|
|
580
|
+
if (!fs12.existsSync(pkgJsonPath)) {
|
|
442
581
|
return requiredPackages;
|
|
443
582
|
}
|
|
444
583
|
try {
|
|
445
|
-
const pkg = JSON.parse(
|
|
584
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgJsonPath, "utf-8"));
|
|
446
585
|
const installed = /* @__PURE__ */ new Set([
|
|
447
586
|
...Object.keys(pkg.dependencies || {}),
|
|
448
587
|
...Object.keys(pkg.devDependencies || {}),
|
|
449
588
|
...Object.keys(pkg.peerDependencies || {})
|
|
450
589
|
]);
|
|
451
|
-
return requiredPackages.filter((
|
|
452
|
-
const nameOnly =
|
|
590
|
+
return requiredPackages.filter((p17) => {
|
|
591
|
+
const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
|
|
453
592
|
return !installed.has(nameOnly);
|
|
454
593
|
});
|
|
455
594
|
} catch {
|
|
@@ -474,12 +613,47 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
474
613
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
475
614
|
break;
|
|
476
615
|
}
|
|
477
|
-
|
|
616
|
+
p15.log.info(`Installing missing dependencies with ${manager}: ${pc15.cyan(packages.join(", "))}`);
|
|
478
617
|
execSync(installCmd, {
|
|
479
618
|
cwd: projectRoot,
|
|
480
619
|
stdio: "inherit"
|
|
481
620
|
});
|
|
482
621
|
}
|
|
622
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
623
|
+
if (!fs12.existsSync(dir)) return [];
|
|
624
|
+
const entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
625
|
+
const files = [];
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
const fullPath = path2.join(dir, entry.name);
|
|
628
|
+
const relPath = path2.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
629
|
+
if (entry.isDirectory()) {
|
|
630
|
+
if (entry.name === "__tests__" || entry.name.startsWith(".")) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
634
|
+
} else if (entry.isFile()) {
|
|
635
|
+
if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
files.push(relPath);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return files.sort();
|
|
642
|
+
}
|
|
643
|
+
function computeDirectoryHash(dirPath, files) {
|
|
644
|
+
if (!fs12.existsSync(dirPath)) return "";
|
|
645
|
+
const hash = createHash("sha256");
|
|
646
|
+
const fileList = (files ?? getAllFiles(dirPath)).sort();
|
|
647
|
+
for (const relativePath of fileList) {
|
|
648
|
+
const absolutePath = path2.join(dirPath, relativePath);
|
|
649
|
+
if (!fs12.existsSync(absolutePath)) continue;
|
|
650
|
+
const content = fs12.readFileSync(absolutePath, "utf8");
|
|
651
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
652
|
+
hash.update(relativePath);
|
|
653
|
+
hash.update(normalizedContent);
|
|
654
|
+
}
|
|
655
|
+
return `sha256-${hash.digest("hex")}`;
|
|
656
|
+
}
|
|
483
657
|
var hbs = Handlebars.create();
|
|
484
658
|
hbs.registerHelper("eq", (a, b) => a === b);
|
|
485
659
|
hbs.registerHelper("ne", (a, b) => a !== b);
|
|
@@ -496,33 +670,33 @@ hbs.registerHelper("join", (arr, sep) => {
|
|
|
496
670
|
});
|
|
497
671
|
var cachedTemplatesDir = null;
|
|
498
672
|
function getTemplatesDir() {
|
|
499
|
-
if (cachedTemplatesDir &&
|
|
673
|
+
if (cachedTemplatesDir && fs12.existsSync(cachedTemplatesDir)) {
|
|
500
674
|
return cachedTemplatesDir;
|
|
501
675
|
}
|
|
502
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname :
|
|
503
|
-
const cand1 =
|
|
504
|
-
if (
|
|
676
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
|
|
677
|
+
const cand1 = path2.resolve(currentDir, "../../templates");
|
|
678
|
+
if (fs12.existsSync(cand1)) {
|
|
505
679
|
cachedTemplatesDir = cand1;
|
|
506
680
|
return cand1;
|
|
507
681
|
}
|
|
508
|
-
const cand2 =
|
|
509
|
-
if (
|
|
682
|
+
const cand2 = path2.resolve(currentDir, "../templates");
|
|
683
|
+
if (fs12.existsSync(cand2)) {
|
|
510
684
|
cachedTemplatesDir = cand2;
|
|
511
685
|
return cand2;
|
|
512
686
|
}
|
|
513
|
-
const cand3 =
|
|
514
|
-
if (
|
|
687
|
+
const cand3 = path2.resolve(currentDir, "templates");
|
|
688
|
+
if (fs12.existsSync(cand3)) {
|
|
515
689
|
cachedTemplatesDir = cand3;
|
|
516
690
|
return cand3;
|
|
517
691
|
}
|
|
518
692
|
let curr = currentDir;
|
|
519
693
|
while (true) {
|
|
520
|
-
const candTemplates =
|
|
521
|
-
if (
|
|
694
|
+
const candTemplates = path2.join(curr, "templates");
|
|
695
|
+
if (fs12.existsSync(candTemplates)) {
|
|
522
696
|
cachedTemplatesDir = candTemplates;
|
|
523
697
|
return candTemplates;
|
|
524
698
|
}
|
|
525
|
-
const parent =
|
|
699
|
+
const parent = path2.dirname(curr);
|
|
526
700
|
if (parent === curr) break;
|
|
527
701
|
curr = parent;
|
|
528
702
|
}
|
|
@@ -530,8 +704,8 @@ function getTemplatesDir() {
|
|
|
530
704
|
}
|
|
531
705
|
var templateCache = /* @__PURE__ */ new Map();
|
|
532
706
|
function loadTemplate(relativePath) {
|
|
533
|
-
const fullPath =
|
|
534
|
-
return
|
|
707
|
+
const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
|
|
708
|
+
return fs12.readFileSync(fullPath, "utf-8");
|
|
535
709
|
}
|
|
536
710
|
function renderTemplateFile(relativePath, context) {
|
|
537
711
|
let compiled = templateCache.get(relativePath);
|
|
@@ -616,8 +790,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
|
|
|
616
790
|
function generateDomain(options) {
|
|
617
791
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
618
792
|
const entityName = toPascalCase(options.name);
|
|
619
|
-
const targetDir = options.targetDir ||
|
|
620
|
-
const filePath =
|
|
793
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
794
|
+
const filePath = path2.join(targetDir, `${entityName}.ts`);
|
|
621
795
|
const code = generateDomainEntityCode(
|
|
622
796
|
entityName,
|
|
623
797
|
options.kind || "interface",
|
|
@@ -641,7 +815,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
641
815
|
const idKey = `${entityKey}Id`;
|
|
642
816
|
const listKey = `${entityKey}s`;
|
|
643
817
|
const formattedCustomMethods = customMethods.map((m) => {
|
|
644
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
818
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
|
|
645
819
|
const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
|
|
646
820
|
return {
|
|
647
821
|
name: m.name,
|
|
@@ -666,8 +840,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
|
|
|
666
840
|
function generateInterface(options) {
|
|
667
841
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
668
842
|
const ifaceName = ensureInterfacePrefix(options.name);
|
|
669
|
-
const targetDir = options.targetDir ||
|
|
670
|
-
const filePath =
|
|
843
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
|
|
844
|
+
const filePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
671
845
|
const code = generateInterfaceCode(
|
|
672
846
|
ifaceName,
|
|
673
847
|
options.kind || "abstractClass",
|
|
@@ -902,7 +1076,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
|
|
|
902
1076
|
};
|
|
903
1077
|
}
|
|
904
1078
|
function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
905
|
-
if (customPath &&
|
|
1079
|
+
if (customPath && fs12.existsSync(customPath)) {
|
|
906
1080
|
return { filePath: customPath, ifaceName };
|
|
907
1081
|
}
|
|
908
1082
|
const candidateNames = [
|
|
@@ -912,22 +1086,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
|
|
|
912
1086
|
];
|
|
913
1087
|
const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
|
|
914
1088
|
for (const feat of features) {
|
|
915
|
-
const domainDir =
|
|
916
|
-
if (!
|
|
1089
|
+
const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
|
|
1090
|
+
if (!fs12.existsSync(domainDir)) continue;
|
|
917
1091
|
for (const cand of candidateNames) {
|
|
918
|
-
const p1 =
|
|
919
|
-
if (
|
|
1092
|
+
const p1 = path2.join(domainDir, `${cand}.ts`);
|
|
1093
|
+
if (fs12.existsSync(p1)) {
|
|
920
1094
|
return { filePath: p1, ifaceName: cand, feature: feat };
|
|
921
1095
|
}
|
|
922
1096
|
}
|
|
923
1097
|
}
|
|
924
1098
|
for (const cand of candidateNames) {
|
|
925
|
-
const p1 =
|
|
926
|
-
if (
|
|
927
|
-
const p22 =
|
|
928
|
-
if (
|
|
929
|
-
const p32 =
|
|
930
|
-
if (
|
|
1099
|
+
const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
|
|
1100
|
+
if (fs12.existsSync(p1)) return { filePath: p1, ifaceName: cand };
|
|
1101
|
+
const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
|
|
1102
|
+
if (fs12.existsSync(p22)) return { filePath: p22, ifaceName: cand };
|
|
1103
|
+
const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
|
|
1104
|
+
if (fs12.existsSync(p32)) return { filePath: p32, ifaceName: cand };
|
|
931
1105
|
}
|
|
932
1106
|
return null;
|
|
933
1107
|
}
|
|
@@ -950,7 +1124,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
|
|
|
950
1124
|
while (currentFile && !visited.has(currentFile)) {
|
|
951
1125
|
visited.add(currentFile);
|
|
952
1126
|
try {
|
|
953
|
-
const content =
|
|
1127
|
+
const content = fs12.readFileSync(currentFile, "utf-8");
|
|
954
1128
|
const parsed = parseInterfaceMethodsAndImports(content, currentName);
|
|
955
1129
|
for (const m of parsed.methods) {
|
|
956
1130
|
if (!allMethods.some((existing) => existing.name === m.name)) {
|
|
@@ -1026,7 +1200,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
|
|
|
1026
1200
|
const entitiesVar = `${entityVar}s`;
|
|
1027
1201
|
const idKey = `${entityVar}Id`;
|
|
1028
1202
|
const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
|
|
1029
|
-
const entityImportPath = ifaceImportPath ? `${
|
|
1203
|
+
const entityImportPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1030
1204
|
const importsByFrom = /* @__PURE__ */ new Map();
|
|
1031
1205
|
for (const imp of domainImports) {
|
|
1032
1206
|
if (imp.name === ifaceName || imp.name === baseEntity) continue;
|
|
@@ -1075,7 +1249,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
|
|
|
1075
1249
|
if (baseEntity) {
|
|
1076
1250
|
finalMethods = synthesizeCrudMethods(baseEntity);
|
|
1077
1251
|
if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
|
|
1078
|
-
const importPath = ifaceImportPath ? `${
|
|
1252
|
+
const importPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
|
|
1079
1253
|
finalDomainImports.push({
|
|
1080
1254
|
name: baseEntity,
|
|
1081
1255
|
from: importPath,
|
|
@@ -1120,8 +1294,8 @@ function generateInfrastructure(options) {
|
|
|
1120
1294
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1121
1295
|
const className = toPascalCase(options.name);
|
|
1122
1296
|
const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
|
|
1123
|
-
const targetDir = options.targetDir || (isFeatureScope ?
|
|
1124
|
-
const filePath =
|
|
1297
|
+
const targetDir = options.targetDir || (isFeatureScope ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2.join(projectRoot, "infrastructure"));
|
|
1298
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
1125
1299
|
let code;
|
|
1126
1300
|
if (options.implementsInterface) {
|
|
1127
1301
|
const rawIfaceName = options.implementsInterface;
|
|
@@ -1135,7 +1309,7 @@ function generateInfrastructure(options) {
|
|
|
1135
1309
|
const ifaceFilePath = hierarchy.ifaceFilePath;
|
|
1136
1310
|
let ifaceImportPath;
|
|
1137
1311
|
if (ifaceFilePath) {
|
|
1138
|
-
let rel =
|
|
1312
|
+
let rel = path2.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
1139
1313
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1140
1314
|
ifaceImportPath = rel;
|
|
1141
1315
|
} else if (isFeatureScope) {
|
|
@@ -1148,8 +1322,8 @@ function generateInfrastructure(options) {
|
|
|
1148
1322
|
if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
|
|
1149
1323
|
let fromPath;
|
|
1150
1324
|
if (imp.specifier.startsWith(".") && ifaceFilePath) {
|
|
1151
|
-
const absTarget =
|
|
1152
|
-
let rel =
|
|
1325
|
+
const absTarget = path2.resolve(path2.dirname(ifaceFilePath), imp.specifier);
|
|
1326
|
+
let rel = path2.relative(targetDir, absTarget).replace(/\\/g, "/");
|
|
1153
1327
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1154
1328
|
fromPath = rel;
|
|
1155
1329
|
} else {
|
|
@@ -1222,22 +1396,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
|
|
|
1222
1396
|
from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
|
|
1223
1397
|
}));
|
|
1224
1398
|
const domainTypesToImport = new Set(domainImports);
|
|
1225
|
-
for (const
|
|
1226
|
-
const rawType =
|
|
1399
|
+
for (const p17 of [...inputProps, ...outputProps]) {
|
|
1400
|
+
const rawType = p17.type.trim();
|
|
1227
1401
|
const baseType = rawType.replace(/\[\]$/, "").trim();
|
|
1228
1402
|
if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
|
|
1229
1403
|
domainTypesToImport.add(baseType);
|
|
1230
1404
|
}
|
|
1231
1405
|
}
|
|
1232
|
-
const formattedInputProps = inputProps.map((
|
|
1233
|
-
name:
|
|
1234
|
-
optional:
|
|
1235
|
-
type:
|
|
1406
|
+
const formattedInputProps = inputProps.map((p17) => ({
|
|
1407
|
+
name: p17.name,
|
|
1408
|
+
optional: p17.optional,
|
|
1409
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1236
1410
|
}));
|
|
1237
|
-
const formattedOutputProps = outputProps.map((
|
|
1238
|
-
name:
|
|
1239
|
-
optional:
|
|
1240
|
-
type:
|
|
1411
|
+
const formattedOutputProps = outputProps.map((p17) => ({
|
|
1412
|
+
name: p17.name,
|
|
1413
|
+
optional: p17.optional,
|
|
1414
|
+
type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
|
|
1241
1415
|
}));
|
|
1242
1416
|
const formattedDeps = dependencies.map((dep) => ({
|
|
1243
1417
|
key: toCamelCase(dep),
|
|
@@ -1336,8 +1510,8 @@ function generateUseCase(options) {
|
|
|
1336
1510
|
const files = [];
|
|
1337
1511
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1338
1512
|
const ucName = toPascalCase(options.name);
|
|
1339
|
-
const targetDir = options.targetDir ||
|
|
1340
|
-
const filePath =
|
|
1513
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
|
|
1514
|
+
const filePath = path2.join(targetDir, `${ucName}.ts`);
|
|
1341
1515
|
const code = generateUseCaseCode(
|
|
1342
1516
|
ucName,
|
|
1343
1517
|
options.dependencies || [],
|
|
@@ -1353,7 +1527,7 @@ function generateUseCase(options) {
|
|
|
1353
1527
|
})
|
|
1354
1528
|
);
|
|
1355
1529
|
if (options.includeTest) {
|
|
1356
|
-
const testPath =
|
|
1530
|
+
const testPath = path2.join(targetDir, `${ucName}.test.ts`);
|
|
1357
1531
|
const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
|
|
1358
1532
|
files.push(
|
|
1359
1533
|
writeFileSafely(testPath, testCode, {
|
|
@@ -1421,10 +1595,10 @@ function generateHttpHandler(options) {
|
|
|
1421
1595
|
const files = [];
|
|
1422
1596
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1423
1597
|
const className = toPascalCase(options.name);
|
|
1424
|
-
const pxDir =
|
|
1425
|
-
const handlersDir =
|
|
1426
|
-
const pxIndexPath =
|
|
1427
|
-
if (!
|
|
1598
|
+
const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
|
|
1599
|
+
const handlersDir = path2.join(pxDir, "handlers");
|
|
1600
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1601
|
+
if (!fs12.existsSync(pxIndexPath)) {
|
|
1428
1602
|
const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
|
|
1429
1603
|
const pxIndexContent = renderTemplate(expressIndexTemplate, {
|
|
1430
1604
|
routePath: featurePath
|
|
@@ -1437,7 +1611,7 @@ function generateHttpHandler(options) {
|
|
|
1437
1611
|
})
|
|
1438
1612
|
);
|
|
1439
1613
|
}
|
|
1440
|
-
const handlerFilePath =
|
|
1614
|
+
const handlerFilePath = path2.join(handlersDir, `${className}.ts`);
|
|
1441
1615
|
const code = generateHttpHandlerCode(
|
|
1442
1616
|
className,
|
|
1443
1617
|
options.method || "get",
|
|
@@ -1458,11 +1632,11 @@ function generateHttpHandler(options) {
|
|
|
1458
1632
|
return files;
|
|
1459
1633
|
}
|
|
1460
1634
|
function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
|
|
1461
|
-
const providerPath =
|
|
1635
|
+
const providerPath = path2.join(featureDir, "diProvider.ts");
|
|
1462
1636
|
const pascalFeature = toPascalCase(featureName);
|
|
1463
1637
|
const providerModuleName = `${pascalFeature}Provider`;
|
|
1464
|
-
if (
|
|
1465
|
-
let content =
|
|
1638
|
+
if (fs12.existsSync(providerPath) && !options.overwrite) {
|
|
1639
|
+
let content = fs12.readFileSync(providerPath, "utf-8");
|
|
1466
1640
|
content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
|
|
1467
1641
|
for (const b of bindings) {
|
|
1468
1642
|
const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
|
|
@@ -1521,7 +1695,7 @@ function generateFeature(options) {
|
|
|
1521
1695
|
const projectRoot = options.projectRoot || findProjectRoot();
|
|
1522
1696
|
const rawName = options.name.trim();
|
|
1523
1697
|
const featureName = toCamelCase(rawName);
|
|
1524
|
-
const targetDir = options.targetDir ||
|
|
1698
|
+
const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), featureName);
|
|
1525
1699
|
const routePath = options.routePath || `/${featureName}`;
|
|
1526
1700
|
const rawEntities = [];
|
|
1527
1701
|
if (options.entityNames && options.entityNames.length > 0) {
|
|
@@ -1542,7 +1716,7 @@ function generateFeature(options) {
|
|
|
1542
1716
|
const domainFile = generateDomain({
|
|
1543
1717
|
name: ent,
|
|
1544
1718
|
feature: featureName,
|
|
1545
|
-
targetDir:
|
|
1719
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1546
1720
|
projectRoot,
|
|
1547
1721
|
dryRun: options.dryRun,
|
|
1548
1722
|
overwrite: options.overwrite,
|
|
@@ -1562,7 +1736,7 @@ function generateFeature(options) {
|
|
|
1562
1736
|
const ifaceFile = generateInterface({
|
|
1563
1737
|
name: ifaceName,
|
|
1564
1738
|
feature: featureName,
|
|
1565
|
-
targetDir:
|
|
1739
|
+
targetDir: path2.join(targetDir, "domain"),
|
|
1566
1740
|
entityName: ent,
|
|
1567
1741
|
projectRoot,
|
|
1568
1742
|
dryRun: options.dryRun,
|
|
@@ -1579,7 +1753,7 @@ function generateFeature(options) {
|
|
|
1579
1753
|
const stubFile = generateInfrastructure({
|
|
1580
1754
|
name: stubName,
|
|
1581
1755
|
feature: featureName,
|
|
1582
|
-
targetDir:
|
|
1756
|
+
targetDir: path2.join(targetDir, "infrastructure"),
|
|
1583
1757
|
kind: "stub",
|
|
1584
1758
|
implementsInterface: ifaceName,
|
|
1585
1759
|
entityName: ent,
|
|
@@ -1621,7 +1795,7 @@ function generateFeature(options) {
|
|
|
1621
1795
|
const ucFiles = generateUseCase({
|
|
1622
1796
|
name: ucName,
|
|
1623
1797
|
feature: featureName,
|
|
1624
|
-
targetDir:
|
|
1798
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1625
1799
|
dependencies: options.includeInterface !== false ? [ifaceName] : [],
|
|
1626
1800
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1627
1801
|
outputProperties: [{ name: entVar, type: primaryEntity }],
|
|
@@ -1666,7 +1840,7 @@ function generateFeature(options) {
|
|
|
1666
1840
|
const ucFiles = generateUseCase({
|
|
1667
1841
|
name: ucName,
|
|
1668
1842
|
feature: featureName,
|
|
1669
|
-
targetDir:
|
|
1843
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1670
1844
|
dependencies: deps,
|
|
1671
1845
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1672
1846
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1699,7 +1873,7 @@ function generateFeature(options) {
|
|
|
1699
1873
|
const ucFiles = generateUseCase({
|
|
1700
1874
|
name: ucName,
|
|
1701
1875
|
feature: featureName,
|
|
1702
|
-
targetDir:
|
|
1876
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1703
1877
|
dependencies: deps,
|
|
1704
1878
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1705
1879
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1732,7 +1906,7 @@ function generateFeature(options) {
|
|
|
1732
1906
|
const ucFiles = generateUseCase({
|
|
1733
1907
|
name: ucName,
|
|
1734
1908
|
feature: featureName,
|
|
1735
|
-
targetDir:
|
|
1909
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1736
1910
|
dependencies: deps,
|
|
1737
1911
|
inputProperties: [
|
|
1738
1912
|
{ name: "page", type: "number", optional: true },
|
|
@@ -1776,7 +1950,7 @@ function generateFeature(options) {
|
|
|
1776
1950
|
const ucFiles = generateUseCase({
|
|
1777
1951
|
name: ucName,
|
|
1778
1952
|
feature: featureName,
|
|
1779
|
-
targetDir:
|
|
1953
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1780
1954
|
dependencies: deps,
|
|
1781
1955
|
inputProperties: [{ name: entVar, type: ent }],
|
|
1782
1956
|
outputProperties: [{ name: entVar, type: ent }],
|
|
@@ -1809,7 +1983,7 @@ function generateFeature(options) {
|
|
|
1809
1983
|
const ucFiles = generateUseCase({
|
|
1810
1984
|
name: ucName,
|
|
1811
1985
|
feature: featureName,
|
|
1812
|
-
targetDir:
|
|
1986
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1813
1987
|
dependencies: deps,
|
|
1814
1988
|
inputProperties: [{ name: idParam, type: "string" }],
|
|
1815
1989
|
outputProperties: [{ name: "success", type: "boolean" }],
|
|
@@ -1846,7 +2020,7 @@ function generateFeature(options) {
|
|
|
1846
2020
|
const ucFiles = generateUseCase({
|
|
1847
2021
|
name: ucName,
|
|
1848
2022
|
feature: featureName,
|
|
1849
|
-
targetDir:
|
|
2023
|
+
targetDir: path2.join(targetDir, "useCases"),
|
|
1850
2024
|
dependencies: customUc.dependencies || [],
|
|
1851
2025
|
inputProperties: customUc.inputProperties || [],
|
|
1852
2026
|
outputProperties: customUc.outputProperties || [],
|
|
@@ -1878,8 +2052,8 @@ function generateFeature(options) {
|
|
|
1878
2052
|
}
|
|
1879
2053
|
}
|
|
1880
2054
|
if (options.includeExpress !== false) {
|
|
1881
|
-
const pxDir =
|
|
1882
|
-
const pxIndexPath =
|
|
2055
|
+
const pxDir = path2.join(targetDir, "pxExpress");
|
|
2056
|
+
const pxIndexPath = path2.join(pxDir, "index.ts");
|
|
1883
2057
|
if (!files.some((f) => f.absolutePath === pxIndexPath)) {
|
|
1884
2058
|
const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
|
|
1885
2059
|
files.push(
|
|
@@ -1905,13 +2079,14 @@ function generateFeature(options) {
|
|
|
1905
2079
|
// src/commands/feature.ts
|
|
1906
2080
|
async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
1907
2081
|
const projectRoot = findProjectRoot();
|
|
2082
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
1908
2083
|
let name = featureNameArg?.trim();
|
|
1909
2084
|
if (!name && flags.yes) {
|
|
1910
2085
|
name = "feature";
|
|
1911
2086
|
}
|
|
1912
2087
|
if (!name) {
|
|
1913
2088
|
name = handleCancel(
|
|
1914
|
-
await
|
|
2089
|
+
await p15.text({
|
|
1915
2090
|
message: "What is the feature name?",
|
|
1916
2091
|
placeholder: "e.g. users, auth, orders, billing",
|
|
1917
2092
|
validate: (val) => {
|
|
@@ -1922,18 +2097,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1922
2097
|
).trim();
|
|
1923
2098
|
}
|
|
1924
2099
|
const normalizedName = toCamelCase(name);
|
|
1925
|
-
const
|
|
2100
|
+
const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
|
|
2101
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseFeaturesDir, normalizedName));
|
|
1926
2102
|
let targetDirRel = defaultDir;
|
|
1927
|
-
if (!flags.yes && !flags.target) {
|
|
2103
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
1928
2104
|
targetDirRel = handleCancel(
|
|
1929
|
-
await
|
|
2105
|
+
await p15.text({
|
|
1930
2106
|
message: "Target directory for the feature:",
|
|
1931
2107
|
initialValue: defaultDir,
|
|
1932
2108
|
defaultValue: defaultDir
|
|
1933
2109
|
})
|
|
1934
2110
|
).trim();
|
|
1935
2111
|
}
|
|
1936
|
-
const targetDir = flags.target ?
|
|
2112
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
1937
2113
|
let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
|
|
1938
2114
|
let routePath = `/${normalizedName}`;
|
|
1939
2115
|
let includeDomain = true;
|
|
@@ -1948,14 +2124,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1948
2124
|
const customUseCases = [];
|
|
1949
2125
|
if (!flags.yes) {
|
|
1950
2126
|
includeExpress = handleCancel(
|
|
1951
|
-
await
|
|
2127
|
+
await p15.confirm({
|
|
1952
2128
|
message: "Include Express Presentation layer (pxExpress routes & handlers)?",
|
|
1953
2129
|
initialValue: true
|
|
1954
2130
|
})
|
|
1955
2131
|
);
|
|
1956
2132
|
if (includeExpress) {
|
|
1957
2133
|
routePath = handleCancel(
|
|
1958
|
-
await
|
|
2134
|
+
await p15.text({
|
|
1959
2135
|
message: "Express route prefix:",
|
|
1960
2136
|
initialValue: `/${normalizedName}`,
|
|
1961
2137
|
defaultValue: `/${normalizedName}`
|
|
@@ -1963,14 +2139,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1963
2139
|
).trim();
|
|
1964
2140
|
}
|
|
1965
2141
|
includeDomain = handleCancel(
|
|
1966
|
-
await
|
|
2142
|
+
await p15.confirm({
|
|
1967
2143
|
message: "Include Domain Entity/Entities?",
|
|
1968
2144
|
initialValue: true
|
|
1969
2145
|
})
|
|
1970
2146
|
);
|
|
1971
2147
|
if (includeDomain) {
|
|
1972
2148
|
const domainInput = handleCancel(
|
|
1973
|
-
await
|
|
2149
|
+
await p15.text({
|
|
1974
2150
|
message: "Domain entity name(s) (comma-separated):",
|
|
1975
2151
|
initialValue: defaultEntity,
|
|
1976
2152
|
defaultValue: defaultEntity,
|
|
@@ -1989,21 +2165,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
1989
2165
|
entityNames = [defaultEntity];
|
|
1990
2166
|
}
|
|
1991
2167
|
includeInterface = handleCancel(
|
|
1992
|
-
await
|
|
2168
|
+
await p15.confirm({
|
|
1993
2169
|
message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
|
|
1994
2170
|
initialValue: true
|
|
1995
2171
|
})
|
|
1996
2172
|
);
|
|
1997
2173
|
if (includeInterface) {
|
|
1998
2174
|
includeInfra = handleCancel(
|
|
1999
|
-
await
|
|
2175
|
+
await p15.confirm({
|
|
2000
2176
|
message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
|
|
2001
2177
|
initialValue: true
|
|
2002
2178
|
})
|
|
2003
2179
|
);
|
|
2004
2180
|
}
|
|
2005
2181
|
includeUseCase = handleCancel(
|
|
2006
|
-
await
|
|
2182
|
+
await p15.confirm({
|
|
2007
2183
|
message: "Include Domain Use Cases?",
|
|
2008
2184
|
initialValue: true
|
|
2009
2185
|
})
|
|
@@ -2017,7 +2193,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2017
2193
|
{ value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
|
|
2018
2194
|
];
|
|
2019
2195
|
crudOperations = handleCancel(
|
|
2020
|
-
await
|
|
2196
|
+
await p15.multiselect({
|
|
2021
2197
|
message: "Select standard CRUD use cases to generate:",
|
|
2022
2198
|
options: crudChoices,
|
|
2023
2199
|
initialValues: ["create", "read", "list", "update", "delete"],
|
|
@@ -2025,13 +2201,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2025
2201
|
})
|
|
2026
2202
|
);
|
|
2027
2203
|
includeTests = handleCancel(
|
|
2028
|
-
await
|
|
2204
|
+
await p15.confirm({
|
|
2029
2205
|
message: "Generate Vitest unit tests for use cases?",
|
|
2030
2206
|
initialValue: true
|
|
2031
2207
|
})
|
|
2032
2208
|
);
|
|
2033
2209
|
const wantCustom = handleCancel(
|
|
2034
|
-
await
|
|
2210
|
+
await p15.confirm({
|
|
2035
2211
|
message: "Do you want to add any custom use cases to this feature?",
|
|
2036
2212
|
initialValue: false
|
|
2037
2213
|
})
|
|
@@ -2041,7 +2217,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2041
2217
|
const availableIfaces = entityNames.map((e) => `I${e}Repository`);
|
|
2042
2218
|
while (addMore) {
|
|
2043
2219
|
const customName = handleCancel(
|
|
2044
|
-
await
|
|
2220
|
+
await p15.text({
|
|
2045
2221
|
message: "Custom Use Case name:",
|
|
2046
2222
|
placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
|
|
2047
2223
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2051,7 +2227,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2051
2227
|
let selectedDeps = [];
|
|
2052
2228
|
if (availableIfaces.length > 0) {
|
|
2053
2229
|
selectedDeps = handleCancel(
|
|
2054
|
-
await
|
|
2230
|
+
await p15.multiselect({
|
|
2055
2231
|
message: `Select domain interfaces to inject into ${customUcName}:`,
|
|
2056
2232
|
options: availableIfaces.map((i) => ({ value: i, label: i })),
|
|
2057
2233
|
required: false
|
|
@@ -2060,7 +2236,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2060
2236
|
}
|
|
2061
2237
|
let customInputProps = [];
|
|
2062
2238
|
const hasInput = handleCancel(
|
|
2063
|
-
await
|
|
2239
|
+
await p15.confirm({
|
|
2064
2240
|
message: `Does ${customUcName} accept input arguments?`,
|
|
2065
2241
|
initialValue: true
|
|
2066
2242
|
})
|
|
@@ -2071,7 +2247,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2071
2247
|
}
|
|
2072
2248
|
let customOutputProps = [];
|
|
2073
2249
|
const hasCustomOutput = handleCancel(
|
|
2074
|
-
await
|
|
2250
|
+
await p15.confirm({
|
|
2075
2251
|
message: `Does ${customUcName} return custom output data?`,
|
|
2076
2252
|
initialValue: false
|
|
2077
2253
|
})
|
|
@@ -2081,7 +2257,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2081
2257
|
});
|
|
2082
2258
|
}
|
|
2083
2259
|
const customIncludeTest = handleCancel(
|
|
2084
|
-
await
|
|
2260
|
+
await p15.confirm({
|
|
2085
2261
|
message: `Generate Vitest unit test for ${customUcName}?`,
|
|
2086
2262
|
initialValue: true
|
|
2087
2263
|
})
|
|
@@ -2091,14 +2267,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2091
2267
|
let customHttpPath = `/${toKebabCase(customUcName)}`;
|
|
2092
2268
|
if (includeExpress) {
|
|
2093
2269
|
customCreateHttp = handleCancel(
|
|
2094
|
-
await
|
|
2270
|
+
await p15.confirm({
|
|
2095
2271
|
message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
|
|
2096
2272
|
initialValue: true
|
|
2097
2273
|
})
|
|
2098
2274
|
);
|
|
2099
2275
|
if (customCreateHttp) {
|
|
2100
2276
|
customHttpMethod = handleCancel(
|
|
2101
|
-
await
|
|
2277
|
+
await p15.select({
|
|
2102
2278
|
message: "HTTP method for route:",
|
|
2103
2279
|
options: [
|
|
2104
2280
|
{ value: "post", label: "POST" },
|
|
@@ -2111,7 +2287,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2111
2287
|
})
|
|
2112
2288
|
);
|
|
2113
2289
|
customHttpPath = handleCancel(
|
|
2114
|
-
await
|
|
2290
|
+
await p15.text({
|
|
2115
2291
|
message: "Route subpath (relative to feature prefix):",
|
|
2116
2292
|
initialValue: `/${toKebabCase(customUcName)}`,
|
|
2117
2293
|
defaultValue: `/${toKebabCase(customUcName)}`
|
|
@@ -2132,7 +2308,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2132
2308
|
validationFields: customInputProps
|
|
2133
2309
|
});
|
|
2134
2310
|
addMore = handleCancel(
|
|
2135
|
-
await
|
|
2311
|
+
await p15.confirm({
|
|
2136
2312
|
message: "Add another custom use case?",
|
|
2137
2313
|
initialValue: false
|
|
2138
2314
|
})
|
|
@@ -2141,7 +2317,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2141
2317
|
}
|
|
2142
2318
|
}
|
|
2143
2319
|
includeDiProvider = handleCancel(
|
|
2144
|
-
await
|
|
2320
|
+
await p15.confirm({
|
|
2145
2321
|
message: "Generate / update feature DI provider (diProvider.ts)?",
|
|
2146
2322
|
initialValue: true
|
|
2147
2323
|
})
|
|
@@ -2153,8 +2329,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2153
2329
|
entityNames = [defaultEntity];
|
|
2154
2330
|
}
|
|
2155
2331
|
}
|
|
2156
|
-
const s =
|
|
2157
|
-
s.start(`Generating feature '${
|
|
2332
|
+
const s = p15.spinner();
|
|
2333
|
+
s.start(`Generating feature '${pc15.cyan(normalizedName)}'...`);
|
|
2158
2334
|
const options = {
|
|
2159
2335
|
name: normalizedName,
|
|
2160
2336
|
projectRoot,
|
|
@@ -2174,9 +2350,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2174
2350
|
includeDiProvider
|
|
2175
2351
|
};
|
|
2176
2352
|
const files = generateFeature(options);
|
|
2353
|
+
if (isRegistry) {
|
|
2354
|
+
const registryPath = path2.join(targetDir, "registry.json");
|
|
2355
|
+
if (!fs12.existsSync(registryPath)) {
|
|
2356
|
+
const manifest = {
|
|
2357
|
+
name: toKebabCase(normalizedName),
|
|
2358
|
+
type: "feature",
|
|
2359
|
+
description: flags.description || `${normalizedName} feature module`,
|
|
2360
|
+
version: "1.0.0",
|
|
2361
|
+
dependencies: {
|
|
2362
|
+
npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
|
|
2363
|
+
shared: [],
|
|
2364
|
+
features: []
|
|
2365
|
+
},
|
|
2366
|
+
files: []
|
|
2367
|
+
};
|
|
2368
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2369
|
+
const genFile = writeFileSafely(registryPath, content, {
|
|
2370
|
+
projectRoot,
|
|
2371
|
+
overwrite: options.overwrite,
|
|
2372
|
+
dryRun: options.dryRun
|
|
2373
|
+
});
|
|
2374
|
+
files.push(genFile);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2177
2377
|
s.stop("Feature scaffolded successfully!");
|
|
2178
2378
|
printGeneratedFiles(files, options.dryRun);
|
|
2179
|
-
|
|
2379
|
+
p15.outro(
|
|
2380
|
+
pc15.green(
|
|
2381
|
+
`\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2382
|
+
)
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
function generateShared(options) {
|
|
2386
|
+
const files = [];
|
|
2387
|
+
const projectRoot = options.projectRoot || findProjectRoot();
|
|
2388
|
+
const rawName = options.name.trim();
|
|
2389
|
+
const pascalName = toPascalCase(rawName);
|
|
2390
|
+
const camelName = toCamelCase(rawName);
|
|
2391
|
+
const kebabName = toKebabCase(rawName);
|
|
2392
|
+
const targetDir = options.targetDir || path2.join(resolveSharedDir(projectRoot), kebabName);
|
|
2393
|
+
const fileDefs = [];
|
|
2394
|
+
const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
|
|
2395
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2396
|
+
|
|
2397
|
+
@MakeInjectable
|
|
2398
|
+
export class ${pascalName} {
|
|
2399
|
+
public static deps = { ${camelName}Engine: I${pascalName}Engine };
|
|
2400
|
+
constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
|
|
2401
|
+
}
|
|
2402
|
+
`;
|
|
2403
|
+
fileDefs.push({
|
|
2404
|
+
absolutePath: path2.join(targetDir, `${pascalName}.ts`),
|
|
2405
|
+
content: facadeContent
|
|
2406
|
+
});
|
|
2407
|
+
const portContent = `export abstract class I${pascalName}Engine {
|
|
2408
|
+
// Define abstract port contracts for ${pascalName} here
|
|
2409
|
+
}
|
|
2410
|
+
`;
|
|
2411
|
+
fileDefs.push({
|
|
2412
|
+
absolutePath: path2.join(targetDir, "ports", `I${pascalName}Engine.ts`),
|
|
2413
|
+
content: portContent
|
|
2414
|
+
});
|
|
2415
|
+
const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2416
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2417
|
+
|
|
2418
|
+
@MakeInjectable
|
|
2419
|
+
export class Default${pascalName}Engine implements I${pascalName}Engine {
|
|
2420
|
+
// Default concrete implementation for production
|
|
2421
|
+
}
|
|
2422
|
+
`;
|
|
2423
|
+
fileDefs.push({
|
|
2424
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
|
|
2425
|
+
content: concreteContent
|
|
2426
|
+
});
|
|
2427
|
+
if (options.includeStub !== false) {
|
|
2428
|
+
const stubContent = `import { MakeInjectable } from "@solid-stack/di";
|
|
2429
|
+
import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
|
|
2430
|
+
|
|
2431
|
+
@MakeInjectable
|
|
2432
|
+
export class Stub${pascalName}Engine implements I${pascalName}Engine {
|
|
2433
|
+
// In-memory or stub implementation for testing
|
|
2434
|
+
}
|
|
2435
|
+
`;
|
|
2436
|
+
fileDefs.push({
|
|
2437
|
+
absolutePath: path2.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
|
|
2438
|
+
content: stubContent
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
const providerContent = `import type { Container } from "@solid-stack/di";
|
|
2442
|
+
import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
|
|
2443
|
+
import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
|
|
2444
|
+
|
|
2445
|
+
export const diProvider = (c: Container): void => {
|
|
2446
|
+
c.provide(I${pascalName}Engine, Default${pascalName}Engine);
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
export default diProvider;
|
|
2450
|
+
`;
|
|
2451
|
+
fileDefs.push({
|
|
2452
|
+
absolutePath: path2.join(targetDir, "diProvider.ts"),
|
|
2453
|
+
content: providerContent
|
|
2454
|
+
});
|
|
2455
|
+
if (options.isRegistry) {
|
|
2456
|
+
const registryManifest = {
|
|
2457
|
+
name: kebabName,
|
|
2458
|
+
type: "shared",
|
|
2459
|
+
description: options.description || `${kebabName} shared module`,
|
|
2460
|
+
version: "1.0.0",
|
|
2461
|
+
dependencies: {
|
|
2462
|
+
npm: [],
|
|
2463
|
+
shared: [],
|
|
2464
|
+
features: []
|
|
2465
|
+
},
|
|
2466
|
+
files: []
|
|
2467
|
+
};
|
|
2468
|
+
fileDefs.push({
|
|
2469
|
+
absolutePath: path2.join(targetDir, "registry.json"),
|
|
2470
|
+
content: JSON.stringify(registryManifest, null, 2) + "\n"
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
for (const def of fileDefs) {
|
|
2474
|
+
const genFile = writeFileSafely(def.absolutePath, def.content, {
|
|
2475
|
+
projectRoot,
|
|
2476
|
+
overwrite: options.overwrite,
|
|
2477
|
+
dryRun: options.dryRun
|
|
2478
|
+
});
|
|
2479
|
+
files.push(genFile);
|
|
2480
|
+
}
|
|
2481
|
+
return files;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/commands/shared.ts
|
|
2485
|
+
async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
2486
|
+
const projectRoot = findProjectRoot();
|
|
2487
|
+
const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
|
|
2488
|
+
let name = sharedNameArg?.trim();
|
|
2489
|
+
if (!name && flags.yes) {
|
|
2490
|
+
name = "shared";
|
|
2491
|
+
}
|
|
2492
|
+
if (!name) {
|
|
2493
|
+
name = handleCancel(
|
|
2494
|
+
await p15.text({
|
|
2495
|
+
message: "What is the shared module name?",
|
|
2496
|
+
placeholder: "e.g. hasher, time, uuid, cache, logger",
|
|
2497
|
+
validate: (val) => {
|
|
2498
|
+
if (!val || !val.trim()) return "Shared module name cannot be empty.";
|
|
2499
|
+
return void 0;
|
|
2500
|
+
}
|
|
2501
|
+
})
|
|
2502
|
+
).trim();
|
|
2503
|
+
}
|
|
2504
|
+
const normalizedName = toKebabCase(name);
|
|
2505
|
+
const baseSharedDir = resolveSharedDir(projectRoot, flags);
|
|
2506
|
+
const defaultDir = path2.relative(projectRoot, path2.join(baseSharedDir, normalizedName));
|
|
2507
|
+
let targetDirRel = defaultDir;
|
|
2508
|
+
if (!flags.yes && !flags.target && !flags.dest) {
|
|
2509
|
+
targetDirRel = handleCancel(
|
|
2510
|
+
await p15.text({
|
|
2511
|
+
message: "Target directory for the shared module:",
|
|
2512
|
+
initialValue: defaultDir,
|
|
2513
|
+
defaultValue: defaultDir
|
|
2514
|
+
})
|
|
2515
|
+
).trim();
|
|
2516
|
+
}
|
|
2517
|
+
const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
|
|
2518
|
+
let description = flags.description || "";
|
|
2519
|
+
let includeStub = true;
|
|
2520
|
+
if (!flags.yes) {
|
|
2521
|
+
description = handleCancel(
|
|
2522
|
+
await p15.text({
|
|
2523
|
+
message: "Module description (optional):",
|
|
2524
|
+
placeholder: "e.g. In-memory caching and Redis port abstraction",
|
|
2525
|
+
initialValue: ""
|
|
2526
|
+
})
|
|
2527
|
+
).trim();
|
|
2528
|
+
includeStub = handleCancel(
|
|
2529
|
+
await p15.confirm({
|
|
2530
|
+
message: "Include stub implementation in infrastructure/?",
|
|
2531
|
+
initialValue: true
|
|
2532
|
+
})
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
const s = p15.spinner();
|
|
2536
|
+
s.start(`Generating shared module '${pc15.cyan(normalizedName)}'...`);
|
|
2537
|
+
const files = generateShared({
|
|
2538
|
+
name: normalizedName,
|
|
2539
|
+
description,
|
|
2540
|
+
projectRoot,
|
|
2541
|
+
targetDir,
|
|
2542
|
+
dryRun: Boolean(flags.dryRun),
|
|
2543
|
+
overwrite: Boolean(flags.overwrite),
|
|
2544
|
+
includeStub,
|
|
2545
|
+
isRegistry
|
|
2546
|
+
});
|
|
2547
|
+
s.stop("Shared module generated successfully!");
|
|
2548
|
+
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2549
|
+
p15.outro(
|
|
2550
|
+
pc15.green(
|
|
2551
|
+
`\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2552
|
+
)
|
|
2553
|
+
);
|
|
2180
2554
|
}
|
|
2181
2555
|
async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
2182
2556
|
const projectRoot = findProjectRoot();
|
|
@@ -2187,14 +2561,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2187
2561
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2188
2562
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2189
2563
|
const selected = handleCancel(
|
|
2190
|
-
await
|
|
2564
|
+
await p15.select({
|
|
2191
2565
|
message: "Select target feature:",
|
|
2192
2566
|
options: featureOptions
|
|
2193
2567
|
})
|
|
2194
2568
|
);
|
|
2195
2569
|
if (selected === "__custom__") {
|
|
2196
2570
|
feature = handleCancel(
|
|
2197
|
-
await
|
|
2571
|
+
await p15.text({
|
|
2198
2572
|
message: "Enter feature name:",
|
|
2199
2573
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2200
2574
|
})
|
|
@@ -2204,7 +2578,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2204
2578
|
}
|
|
2205
2579
|
} else {
|
|
2206
2580
|
feature = handleCancel(
|
|
2207
|
-
await
|
|
2581
|
+
await p15.text({
|
|
2208
2582
|
message: "Enter target feature name:",
|
|
2209
2583
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2210
2584
|
})
|
|
@@ -2215,7 +2589,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2215
2589
|
let name = useCaseNameArg?.trim();
|
|
2216
2590
|
if (!name) {
|
|
2217
2591
|
name = handleCancel(
|
|
2218
|
-
await
|
|
2592
|
+
await p15.text({
|
|
2219
2593
|
message: "Use Case name:",
|
|
2220
2594
|
placeholder: "e.g. CreateUser, GetUser, CancelOrder",
|
|
2221
2595
|
validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
|
|
@@ -2231,7 +2605,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2231
2605
|
label: i.name
|
|
2232
2606
|
}));
|
|
2233
2607
|
selectedDeps = handleCancel(
|
|
2234
|
-
await
|
|
2608
|
+
await p15.multiselect({
|
|
2235
2609
|
message: "Select domain interfaces to inject into use case:",
|
|
2236
2610
|
options: multiOptions,
|
|
2237
2611
|
required: false
|
|
@@ -2246,7 +2620,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2246
2620
|
let httpPath = "/";
|
|
2247
2621
|
if (!flags.yes) {
|
|
2248
2622
|
const hasInput = handleCancel(
|
|
2249
|
-
await
|
|
2623
|
+
await p15.confirm({
|
|
2250
2624
|
message: "Does this use case accept input arguments?",
|
|
2251
2625
|
initialValue: true
|
|
2252
2626
|
})
|
|
@@ -2256,7 +2630,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2256
2630
|
});
|
|
2257
2631
|
}
|
|
2258
2632
|
const hasCustomOutput = handleCancel(
|
|
2259
|
-
await
|
|
2633
|
+
await p15.confirm({
|
|
2260
2634
|
message: "Does this use case return custom output data?",
|
|
2261
2635
|
initialValue: false
|
|
2262
2636
|
})
|
|
@@ -2266,20 +2640,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2266
2640
|
});
|
|
2267
2641
|
}
|
|
2268
2642
|
includeTest = handleCancel(
|
|
2269
|
-
await
|
|
2643
|
+
await p15.confirm({
|
|
2270
2644
|
message: "Generate Vitest unit test skeleton (.test.ts)?",
|
|
2271
2645
|
initialValue: true
|
|
2272
2646
|
})
|
|
2273
2647
|
);
|
|
2274
2648
|
createHttp = handleCancel(
|
|
2275
|
-
await
|
|
2649
|
+
await p15.confirm({
|
|
2276
2650
|
message: "Generate a matching ExpressRoute HTTP handler?",
|
|
2277
2651
|
initialValue: false
|
|
2278
2652
|
})
|
|
2279
2653
|
);
|
|
2280
2654
|
if (createHttp) {
|
|
2281
2655
|
httpMethod = handleCancel(
|
|
2282
|
-
await
|
|
2656
|
+
await p15.select({
|
|
2283
2657
|
message: "HTTP method for route:",
|
|
2284
2658
|
options: [
|
|
2285
2659
|
{ value: "post", label: "POST" },
|
|
@@ -2292,7 +2666,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2292
2666
|
})
|
|
2293
2667
|
);
|
|
2294
2668
|
httpPath = handleCancel(
|
|
2295
|
-
await
|
|
2669
|
+
await p15.text({
|
|
2296
2670
|
message: "Route subpath (relative to feature prefix):",
|
|
2297
2671
|
initialValue: "/",
|
|
2298
2672
|
defaultValue: "/"
|
|
@@ -2300,8 +2674,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2300
2674
|
).trim();
|
|
2301
2675
|
}
|
|
2302
2676
|
}
|
|
2303
|
-
const s =
|
|
2304
|
-
s.start(`Generating use case '${
|
|
2677
|
+
const s = p15.spinner();
|
|
2678
|
+
s.start(`Generating use case '${pc15.cyan(ucName)}'...`);
|
|
2305
2679
|
const options = {
|
|
2306
2680
|
name: ucName,
|
|
2307
2681
|
feature,
|
|
@@ -2333,7 +2707,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2333
2707
|
}
|
|
2334
2708
|
s.stop("Use case generated successfully!");
|
|
2335
2709
|
printGeneratedFiles(files, options.dryRun);
|
|
2336
|
-
|
|
2710
|
+
p15.outro(pc15.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2337
2711
|
}
|
|
2338
2712
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2339
2713
|
const projectRoot = findProjectRoot();
|
|
@@ -2344,14 +2718,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2344
2718
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2345
2719
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2346
2720
|
const selected = handleCancel(
|
|
2347
|
-
await
|
|
2721
|
+
await p15.select({
|
|
2348
2722
|
message: "Select target feature:",
|
|
2349
2723
|
options: featureOptions
|
|
2350
2724
|
})
|
|
2351
2725
|
);
|
|
2352
2726
|
if (selected === "__custom__") {
|
|
2353
2727
|
feature = handleCancel(
|
|
2354
|
-
await
|
|
2728
|
+
await p15.text({
|
|
2355
2729
|
message: "Enter feature name:",
|
|
2356
2730
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2357
2731
|
})
|
|
@@ -2361,7 +2735,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2361
2735
|
}
|
|
2362
2736
|
} else {
|
|
2363
2737
|
feature = handleCancel(
|
|
2364
|
-
await
|
|
2738
|
+
await p15.text({
|
|
2365
2739
|
message: "Enter target feature name:",
|
|
2366
2740
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2367
2741
|
})
|
|
@@ -2372,7 +2746,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2372
2746
|
let name = domainNameArg?.trim();
|
|
2373
2747
|
if (!name) {
|
|
2374
2748
|
name = handleCancel(
|
|
2375
|
-
await
|
|
2749
|
+
await p15.text({
|
|
2376
2750
|
message: "Domain model / entity name:",
|
|
2377
2751
|
placeholder: "e.g. User, Order, Product, Address",
|
|
2378
2752
|
validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
|
|
@@ -2384,7 +2758,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2384
2758
|
let properties = [];
|
|
2385
2759
|
if (!flags.yes) {
|
|
2386
2760
|
kind = handleCancel(
|
|
2387
|
-
await
|
|
2761
|
+
await p15.select({
|
|
2388
2762
|
message: "Select domain model type:",
|
|
2389
2763
|
options: [
|
|
2390
2764
|
{ value: "interface", label: "TypeScript Interface (pure data structure)" },
|
|
@@ -2403,8 +2777,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2403
2777
|
defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
|
|
2404
2778
|
});
|
|
2405
2779
|
}
|
|
2406
|
-
const s =
|
|
2407
|
-
s.start(`Generating domain model '${
|
|
2780
|
+
const s = p15.spinner();
|
|
2781
|
+
s.start(`Generating domain model '${pc15.cyan(entityName)}'...`);
|
|
2408
2782
|
const options = {
|
|
2409
2783
|
name: entityName,
|
|
2410
2784
|
feature,
|
|
@@ -2417,7 +2791,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2417
2791
|
const file = generateDomain(options);
|
|
2418
2792
|
s.stop("Domain model generated successfully!");
|
|
2419
2793
|
printGeneratedFiles([file], options.dryRun);
|
|
2420
|
-
|
|
2794
|
+
p15.outro(pc15.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2421
2795
|
}
|
|
2422
2796
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2423
2797
|
const projectRoot = findProjectRoot();
|
|
@@ -2426,7 +2800,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2426
2800
|
let feature = flags.feature?.trim();
|
|
2427
2801
|
if (!flags.yes && !flags.global && !flags.feature) {
|
|
2428
2802
|
scope = handleCancel(
|
|
2429
|
-
await
|
|
2803
|
+
await p15.select({
|
|
2430
2804
|
message: "Where should this infrastructure implementation be placed?",
|
|
2431
2805
|
options: [
|
|
2432
2806
|
{ value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
|
|
@@ -2441,14 +2815,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2441
2815
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2442
2816
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2443
2817
|
const selected = handleCancel(
|
|
2444
|
-
await
|
|
2818
|
+
await p15.select({
|
|
2445
2819
|
message: "Select target feature:",
|
|
2446
2820
|
options: featureOptions
|
|
2447
2821
|
})
|
|
2448
2822
|
);
|
|
2449
2823
|
if (selected === "__custom__") {
|
|
2450
2824
|
feature = handleCancel(
|
|
2451
|
-
await
|
|
2825
|
+
await p15.text({
|
|
2452
2826
|
message: "Enter feature name:",
|
|
2453
2827
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2454
2828
|
})
|
|
@@ -2458,7 +2832,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2458
2832
|
}
|
|
2459
2833
|
} else {
|
|
2460
2834
|
feature = handleCancel(
|
|
2461
|
-
await
|
|
2835
|
+
await p15.text({
|
|
2462
2836
|
message: "Enter target feature name:",
|
|
2463
2837
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2464
2838
|
})
|
|
@@ -2471,7 +2845,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2471
2845
|
let name = classNameArg?.trim();
|
|
2472
2846
|
if (!name) {
|
|
2473
2847
|
name = handleCancel(
|
|
2474
|
-
await
|
|
2848
|
+
await p15.text({
|
|
2475
2849
|
message: "Infrastructure class name:",
|
|
2476
2850
|
placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
|
|
2477
2851
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -2490,7 +2864,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2490
2864
|
(i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
|
|
2491
2865
|
);
|
|
2492
2866
|
const ifaceChoice = handleCancel(
|
|
2493
|
-
await
|
|
2867
|
+
await p15.select({
|
|
2494
2868
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2495
2869
|
options: [
|
|
2496
2870
|
...interfaces.map((i) => ({ value: i.name, label: i.name })),
|
|
@@ -2502,7 +2876,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2502
2876
|
);
|
|
2503
2877
|
if (ifaceChoice === "__custom__") {
|
|
2504
2878
|
implementsInterface = handleCancel(
|
|
2505
|
-
await
|
|
2879
|
+
await p15.text({
|
|
2506
2880
|
message: "Enter interface or abstract class name:",
|
|
2507
2881
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2508
2882
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2513,14 +2887,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2513
2887
|
}
|
|
2514
2888
|
} else {
|
|
2515
2889
|
const wantImpl = handleCancel(
|
|
2516
|
-
await
|
|
2890
|
+
await p15.confirm({
|
|
2517
2891
|
message: "Does this class implement a domain interface or abstract class?",
|
|
2518
2892
|
initialValue: false
|
|
2519
2893
|
})
|
|
2520
2894
|
);
|
|
2521
2895
|
if (wantImpl) {
|
|
2522
2896
|
implementsInterface = handleCancel(
|
|
2523
|
-
await
|
|
2897
|
+
await p15.text({
|
|
2524
2898
|
message: "Enter interface or abstract class name:",
|
|
2525
2899
|
placeholder: "e.g. IUserRepository, BaseStorageAdapter",
|
|
2526
2900
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2540,7 +2914,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2540
2914
|
const allInterfaces = listDomainInterfaces(projectRoot);
|
|
2541
2915
|
if (allInterfaces.length > 0) {
|
|
2542
2916
|
const ifaceChoice = handleCancel(
|
|
2543
|
-
await
|
|
2917
|
+
await p15.select({
|
|
2544
2918
|
message: "Does this class implement an interface or abstract class?",
|
|
2545
2919
|
options: [
|
|
2546
2920
|
...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
|
|
@@ -2552,7 +2926,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2552
2926
|
);
|
|
2553
2927
|
if (ifaceChoice === "__custom__") {
|
|
2554
2928
|
implementsInterface = handleCancel(
|
|
2555
|
-
await
|
|
2929
|
+
await p15.text({
|
|
2556
2930
|
message: "Enter interface or abstract class name:",
|
|
2557
2931
|
placeholder: "e.g. IAuthRepository",
|
|
2558
2932
|
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
@@ -2565,7 +2939,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2565
2939
|
}
|
|
2566
2940
|
if (!flags.yes && !flags.kind) {
|
|
2567
2941
|
kind = handleCancel(
|
|
2568
|
-
await
|
|
2942
|
+
await p15.select({
|
|
2569
2943
|
message: "Select implementation kind:",
|
|
2570
2944
|
options: [
|
|
2571
2945
|
{ value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
|
|
@@ -2576,7 +2950,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2576
2950
|
);
|
|
2577
2951
|
if (scope === "feature" && feature && implementsInterface) {
|
|
2578
2952
|
registerInProvider = handleCancel(
|
|
2579
|
-
await
|
|
2953
|
+
await p15.confirm({
|
|
2580
2954
|
message: "Register this implementation in feature diProvider.ts?",
|
|
2581
2955
|
initialValue: true
|
|
2582
2956
|
})
|
|
@@ -2585,8 +2959,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2585
2959
|
} else if (flags.yes && scope === "feature" && feature && implementsInterface) {
|
|
2586
2960
|
registerInProvider = !flags.noProvider;
|
|
2587
2961
|
}
|
|
2588
|
-
const s =
|
|
2589
|
-
s.start(`Generating infrastructure class '${
|
|
2962
|
+
const s = p15.spinner();
|
|
2963
|
+
s.start(`Generating infrastructure class '${pc15.cyan(className)}'...`);
|
|
2590
2964
|
const files = [];
|
|
2591
2965
|
const options = {
|
|
2592
2966
|
name: className,
|
|
@@ -2601,7 +2975,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2601
2975
|
const infraFile = generateInfrastructure(options);
|
|
2602
2976
|
files.push(infraFile);
|
|
2603
2977
|
if (registerInProvider && scope === "feature" && feature && implementsInterface) {
|
|
2604
|
-
const featureDir =
|
|
2978
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2605
2979
|
const providerFile = updateOrGenerateDiProvider(
|
|
2606
2980
|
featureDir,
|
|
2607
2981
|
feature,
|
|
@@ -2621,7 +2995,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2621
2995
|
}
|
|
2622
2996
|
s.stop("Infrastructure class generated successfully!");
|
|
2623
2997
|
printGeneratedFiles(files, options.dryRun);
|
|
2624
|
-
|
|
2998
|
+
p15.outro(pc15.green(`Infrastructure class '${className}' created!`));
|
|
2625
2999
|
}
|
|
2626
3000
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2627
3001
|
const projectRoot = findProjectRoot();
|
|
@@ -2632,14 +3006,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2632
3006
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2633
3007
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2634
3008
|
const selected = handleCancel(
|
|
2635
|
-
await
|
|
3009
|
+
await p15.select({
|
|
2636
3010
|
message: "Select target feature:",
|
|
2637
3011
|
options: featureOptions
|
|
2638
3012
|
})
|
|
2639
3013
|
);
|
|
2640
3014
|
if (selected === "__custom__") {
|
|
2641
3015
|
feature = handleCancel(
|
|
2642
|
-
await
|
|
3016
|
+
await p15.text({
|
|
2643
3017
|
message: "Enter feature name:",
|
|
2644
3018
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2645
3019
|
})
|
|
@@ -2649,7 +3023,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2649
3023
|
}
|
|
2650
3024
|
} else {
|
|
2651
3025
|
feature = handleCancel(
|
|
2652
|
-
await
|
|
3026
|
+
await p15.text({
|
|
2653
3027
|
message: "Enter target feature name:",
|
|
2654
3028
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2655
3029
|
})
|
|
@@ -2660,7 +3034,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2660
3034
|
let name = interfaceNameArg?.trim();
|
|
2661
3035
|
if (!name) {
|
|
2662
3036
|
name = handleCancel(
|
|
2663
|
-
await
|
|
3037
|
+
await p15.text({
|
|
2664
3038
|
message: "Domain interface name:",
|
|
2665
3039
|
placeholder: "e.g. IUserRepository, IPaymentService",
|
|
2666
3040
|
validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
|
|
@@ -2678,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2678
3052
|
if (!flags.yes) {
|
|
2679
3053
|
if (existingEntities.length > 0) {
|
|
2680
3054
|
const selectedEntity = handleCancel(
|
|
2681
|
-
await
|
|
3055
|
+
await p15.select({
|
|
2682
3056
|
message: "Select domain entity associated with this interface:",
|
|
2683
3057
|
options: [
|
|
2684
3058
|
...existingEntities.map((e) => ({ value: e.name, label: e.name })),
|
|
@@ -2689,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2689
3063
|
);
|
|
2690
3064
|
if (selectedEntity === "__custom__") {
|
|
2691
3065
|
entityName = handleCancel(
|
|
2692
|
-
await
|
|
3066
|
+
await p15.text({
|
|
2693
3067
|
message: "Enter entity name:",
|
|
2694
3068
|
defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
|
|
2695
3069
|
})
|
|
@@ -2699,7 +3073,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2699
3073
|
}
|
|
2700
3074
|
}
|
|
2701
3075
|
kind = handleCancel(
|
|
2702
|
-
await
|
|
3076
|
+
await p15.select({
|
|
2703
3077
|
message: "Interface definition style:",
|
|
2704
3078
|
options: [
|
|
2705
3079
|
{ value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
|
|
@@ -2709,7 +3083,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2709
3083
|
})
|
|
2710
3084
|
);
|
|
2711
3085
|
methodsPreset = handleCancel(
|
|
2712
|
-
await
|
|
3086
|
+
await p15.select({
|
|
2713
3087
|
message: "Method signature preset:",
|
|
2714
3088
|
options: [
|
|
2715
3089
|
{ value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
|
|
@@ -2719,22 +3093,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2719
3093
|
})
|
|
2720
3094
|
);
|
|
2721
3095
|
generateStub = handleCancel(
|
|
2722
|
-
await
|
|
3096
|
+
await p15.confirm({
|
|
2723
3097
|
message: "Auto-generate Infrastructure Stub implementation?",
|
|
2724
3098
|
initialValue: true
|
|
2725
3099
|
})
|
|
2726
3100
|
);
|
|
2727
3101
|
if (generateStub) {
|
|
2728
3102
|
registerInProvider = handleCancel(
|
|
2729
|
-
await
|
|
3103
|
+
await p15.confirm({
|
|
2730
3104
|
message: "Auto-register interface & stub in feature diProvider.ts?",
|
|
2731
3105
|
initialValue: true
|
|
2732
3106
|
})
|
|
2733
3107
|
);
|
|
2734
3108
|
}
|
|
2735
3109
|
}
|
|
2736
|
-
const s =
|
|
2737
|
-
s.start(`Generating interface '${
|
|
3110
|
+
const s = p15.spinner();
|
|
3111
|
+
s.start(`Generating interface '${pc15.cyan(ifaceName)}'...`);
|
|
2738
3112
|
const files = [];
|
|
2739
3113
|
const options = {
|
|
2740
3114
|
name: ifaceName,
|
|
@@ -2763,7 +3137,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2763
3137
|
files.push(stubFile);
|
|
2764
3138
|
}
|
|
2765
3139
|
if (registerInProvider && generateStub) {
|
|
2766
|
-
const featureDir =
|
|
3140
|
+
const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
|
|
2767
3141
|
const providerFile = updateOrGenerateDiProvider(
|
|
2768
3142
|
featureDir,
|
|
2769
3143
|
feature,
|
|
@@ -2783,7 +3157,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2783
3157
|
}
|
|
2784
3158
|
s.stop("Interface generated successfully!");
|
|
2785
3159
|
printGeneratedFiles(files, options.dryRun);
|
|
2786
|
-
|
|
3160
|
+
p15.outro(pc15.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2787
3161
|
}
|
|
2788
3162
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2789
3163
|
const projectRoot = findProjectRoot();
|
|
@@ -2794,14 +3168,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2794
3168
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
2795
3169
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
2796
3170
|
const selected = handleCancel(
|
|
2797
|
-
await
|
|
3171
|
+
await p15.select({
|
|
2798
3172
|
message: "Select target feature:",
|
|
2799
3173
|
options: featureOptions
|
|
2800
3174
|
})
|
|
2801
3175
|
);
|
|
2802
3176
|
if (selected === "__custom__") {
|
|
2803
3177
|
feature = handleCancel(
|
|
2804
|
-
await
|
|
3178
|
+
await p15.text({
|
|
2805
3179
|
message: "Enter feature name:",
|
|
2806
3180
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2807
3181
|
})
|
|
@@ -2811,7 +3185,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2811
3185
|
}
|
|
2812
3186
|
} else {
|
|
2813
3187
|
feature = handleCancel(
|
|
2814
|
-
await
|
|
3188
|
+
await p15.text({
|
|
2815
3189
|
message: "Enter target feature name:",
|
|
2816
3190
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
2817
3191
|
})
|
|
@@ -2822,7 +3196,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2822
3196
|
let name = handlerNameArg?.trim();
|
|
2823
3197
|
if (!name) {
|
|
2824
3198
|
name = handleCancel(
|
|
2825
|
-
await
|
|
3199
|
+
await p15.text({
|
|
2826
3200
|
message: "HTTP Route Handler class name:",
|
|
2827
3201
|
placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
|
|
2828
3202
|
validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
|
|
@@ -2841,7 +3215,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2841
3215
|
let validationFields = [];
|
|
2842
3216
|
if (!flags.yes) {
|
|
2843
3217
|
method = handleCancel(
|
|
2844
|
-
await
|
|
3218
|
+
await p15.select({
|
|
2845
3219
|
message: "HTTP Method:",
|
|
2846
3220
|
options: [
|
|
2847
3221
|
{ value: "get", label: "GET" },
|
|
@@ -2855,7 +3229,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2855
3229
|
);
|
|
2856
3230
|
const defaultPath = method === "get" ? "/" : "/";
|
|
2857
3231
|
subPath = handleCancel(
|
|
2858
|
-
await
|
|
3232
|
+
await p15.text({
|
|
2859
3233
|
message: "Route subpath (relative to feature prefix):",
|
|
2860
3234
|
initialValue: defaultPath,
|
|
2861
3235
|
defaultValue: defaultPath
|
|
@@ -2866,7 +3240,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2866
3240
|
const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
|
|
2867
3241
|
ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
|
|
2868
3242
|
const chosenUc = handleCancel(
|
|
2869
|
-
await
|
|
3243
|
+
await p15.select({
|
|
2870
3244
|
message: "Connect this handler to a domain Use Case?",
|
|
2871
3245
|
options: ucOptions,
|
|
2872
3246
|
initialValue: ucOptions[0]?.value
|
|
@@ -2877,7 +3251,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2877
3251
|
}
|
|
2878
3252
|
}
|
|
2879
3253
|
includeValidation = handleCancel(
|
|
2880
|
-
await
|
|
3254
|
+
await p15.confirm({
|
|
2881
3255
|
message: "Include Zod inbound request validation?",
|
|
2882
3256
|
initialValue: method !== "delete"
|
|
2883
3257
|
})
|
|
@@ -2885,7 +3259,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2885
3259
|
if (includeValidation) {
|
|
2886
3260
|
const defaultSource = method === "get" ? "query" : "body";
|
|
2887
3261
|
validationSource = handleCancel(
|
|
2888
|
-
await
|
|
3262
|
+
await p15.select({
|
|
2889
3263
|
message: "Validation data source:",
|
|
2890
3264
|
options: [
|
|
2891
3265
|
{ value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
|
|
@@ -2899,8 +3273,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2899
3273
|
});
|
|
2900
3274
|
}
|
|
2901
3275
|
}
|
|
2902
|
-
const s =
|
|
2903
|
-
s.start(`Generating HTTP handler '${
|
|
3276
|
+
const s = p15.spinner();
|
|
3277
|
+
s.start(`Generating HTTP handler '${pc15.cyan(className)}'...`);
|
|
2904
3278
|
const options = {
|
|
2905
3279
|
name: className,
|
|
2906
3280
|
feature,
|
|
@@ -2918,7 +3292,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
2918
3292
|
const files = generateHttpHandler(options);
|
|
2919
3293
|
s.stop("HTTP handler generated successfully!");
|
|
2920
3294
|
printGeneratedFiles(files, options.dryRun);
|
|
2921
|
-
|
|
3295
|
+
p15.outro(pc15.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
2922
3296
|
}
|
|
2923
3297
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
2924
3298
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -2928,7 +3302,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
|
|
|
2928
3302
|
const formattedMethods = methods.map((m) => {
|
|
2929
3303
|
const isAsync = m.async !== false;
|
|
2930
3304
|
const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
|
|
2931
|
-
const params = m.params && m.params.length > 0 ? m.params.map((
|
|
3305
|
+
const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
|
|
2932
3306
|
return {
|
|
2933
3307
|
name: m.name,
|
|
2934
3308
|
isAsync,
|
|
@@ -2951,21 +3325,21 @@ function generateInjectable(options) {
|
|
|
2951
3325
|
const className = toPascalCase(options.name);
|
|
2952
3326
|
let targetDir = "";
|
|
2953
3327
|
if (options.customPath) {
|
|
2954
|
-
targetDir =
|
|
3328
|
+
targetDir = path2.isAbsolute(options.customPath) ? options.customPath : path2.join(projectRoot, options.customPath);
|
|
2955
3329
|
} else if (options.location === "feature-service" && options.feature) {
|
|
2956
|
-
targetDir =
|
|
3330
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "services");
|
|
2957
3331
|
} else if (options.location === "feature-transformer" && options.feature) {
|
|
2958
|
-
targetDir =
|
|
3332
|
+
targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
|
|
2959
3333
|
} else if (options.location === "module") {
|
|
2960
|
-
targetDir =
|
|
3334
|
+
targetDir = path2.join(projectRoot, "modules", className);
|
|
2961
3335
|
} else {
|
|
2962
|
-
targetDir = options.feature ?
|
|
3336
|
+
targetDir = options.feature ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2.join(projectRoot, "shared", className.toLowerCase());
|
|
2963
3337
|
}
|
|
2964
|
-
const filePath =
|
|
3338
|
+
const filePath = path2.join(targetDir, `${className}.ts`);
|
|
2965
3339
|
let ifaceName;
|
|
2966
3340
|
if (options.generateInterface) {
|
|
2967
3341
|
ifaceName = `I${className}`;
|
|
2968
|
-
const ifacePath =
|
|
3342
|
+
const ifacePath = path2.join(targetDir, `${ifaceName}.ts`);
|
|
2969
3343
|
const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
|
|
2970
3344
|
files.push(
|
|
2971
3345
|
writeFileSafely(ifacePath, ifaceContent, {
|
|
@@ -2998,7 +3372,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
2998
3372
|
let name = classNameArg?.trim();
|
|
2999
3373
|
if (!name) {
|
|
3000
3374
|
name = handleCancel(
|
|
3001
|
-
await
|
|
3375
|
+
await p15.text({
|
|
3002
3376
|
message: "Injectable class name:",
|
|
3003
3377
|
placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
|
|
3004
3378
|
validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
|
|
@@ -3011,7 +3385,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3011
3385
|
let customPath;
|
|
3012
3386
|
if (!flags.yes) {
|
|
3013
3387
|
location = handleCancel(
|
|
3014
|
-
await
|
|
3388
|
+
await p15.select({
|
|
3015
3389
|
message: "Select target architectural location:",
|
|
3016
3390
|
options: [
|
|
3017
3391
|
{ value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
|
|
@@ -3029,14 +3403,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3029
3403
|
const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
|
|
3030
3404
|
featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
|
|
3031
3405
|
const selected = handleCancel(
|
|
3032
|
-
await
|
|
3406
|
+
await p15.select({
|
|
3033
3407
|
message: "Select target feature:",
|
|
3034
3408
|
options: featureOptions
|
|
3035
3409
|
})
|
|
3036
3410
|
);
|
|
3037
3411
|
if (selected === "__custom__") {
|
|
3038
3412
|
feature = handleCancel(
|
|
3039
|
-
await
|
|
3413
|
+
await p15.text({
|
|
3040
3414
|
message: "Enter feature name:",
|
|
3041
3415
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3042
3416
|
})
|
|
@@ -3046,7 +3420,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3046
3420
|
}
|
|
3047
3421
|
} else {
|
|
3048
3422
|
feature = handleCancel(
|
|
3049
|
-
await
|
|
3423
|
+
await p15.text({
|
|
3050
3424
|
message: "Enter target feature name:",
|
|
3051
3425
|
validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
|
|
3052
3426
|
})
|
|
@@ -3056,7 +3430,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3056
3430
|
feature = toCamelCase(feature);
|
|
3057
3431
|
} else if (location === "custom") {
|
|
3058
3432
|
customPath = handleCancel(
|
|
3059
|
-
await
|
|
3433
|
+
await p15.text({
|
|
3060
3434
|
message: "Enter destination directory path:",
|
|
3061
3435
|
placeholder: "e.g. src/services or lib/utils",
|
|
3062
3436
|
validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
|
|
@@ -3067,14 +3441,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3067
3441
|
let generateInterface2 = false;
|
|
3068
3442
|
if (!flags.yes) {
|
|
3069
3443
|
generateInterface2 = handleCancel(
|
|
3070
|
-
await
|
|
3444
|
+
await p15.confirm({
|
|
3071
3445
|
message: `Generate abstract class interface (I${className}) as runtime DI token?`,
|
|
3072
3446
|
initialValue: false
|
|
3073
3447
|
})
|
|
3074
3448
|
);
|
|
3075
3449
|
}
|
|
3076
|
-
const s =
|
|
3077
|
-
s.start(`Generating injectable '${
|
|
3450
|
+
const s = p15.spinner();
|
|
3451
|
+
s.start(`Generating injectable '${pc15.cyan(className)}'...`);
|
|
3078
3452
|
const options = {
|
|
3079
3453
|
name: className,
|
|
3080
3454
|
feature,
|
|
@@ -3088,12 +3462,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3088
3462
|
const files = generateInjectable(options);
|
|
3089
3463
|
s.stop("Injectable class generated successfully!");
|
|
3090
3464
|
printGeneratedFiles(files, options.dryRun);
|
|
3091
|
-
|
|
3465
|
+
p15.outro(pc15.green(`Injectable '${className}' created successfully!`));
|
|
3092
3466
|
}
|
|
3093
3467
|
async function runInteractiveMenu(flags = {}) {
|
|
3094
3468
|
printBanner();
|
|
3095
3469
|
const choice = handleCancel(
|
|
3096
|
-
await
|
|
3470
|
+
await p15.select({
|
|
3097
3471
|
message: "What would you like to create in your architecture?",
|
|
3098
3472
|
options: [
|
|
3099
3473
|
{
|
|
@@ -3101,6 +3475,11 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3101
3475
|
label: "Feature",
|
|
3102
3476
|
hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
|
|
3103
3477
|
},
|
|
3478
|
+
{
|
|
3479
|
+
value: "shared",
|
|
3480
|
+
label: "Shared Module",
|
|
3481
|
+
hint: "Foundational utility or port with ports, infrastructure & diProvider"
|
|
3482
|
+
},
|
|
3104
3483
|
{
|
|
3105
3484
|
value: "usecase",
|
|
3106
3485
|
label: "Use Case",
|
|
@@ -3138,6 +3517,9 @@ async function runInteractiveMenu(flags = {}) {
|
|
|
3138
3517
|
case "feature":
|
|
3139
3518
|
await runFeatureCommand(void 0, flags);
|
|
3140
3519
|
break;
|
|
3520
|
+
case "shared":
|
|
3521
|
+
await runSharedCommand(void 0, flags);
|
|
3522
|
+
break;
|
|
3141
3523
|
case "usecase":
|
|
3142
3524
|
await runUseCaseCommand(void 0, flags);
|
|
3143
3525
|
break;
|
|
@@ -3170,6 +3552,9 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3170
3552
|
case "feature":
|
|
3171
3553
|
await runFeatureCommand(name, flags);
|
|
3172
3554
|
break;
|
|
3555
|
+
case "shared":
|
|
3556
|
+
await runSharedCommand(name, flags);
|
|
3557
|
+
break;
|
|
3173
3558
|
case "usecase":
|
|
3174
3559
|
await runUseCaseCommand(name, flags);
|
|
3175
3560
|
break;
|
|
@@ -3190,21 +3575,172 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3190
3575
|
await runInjectableCommand(name, flags);
|
|
3191
3576
|
break;
|
|
3192
3577
|
default:
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
`Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3578
|
+
p15.log.error(
|
|
3579
|
+
pc15.red(
|
|
3580
|
+
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3196
3581
|
)
|
|
3197
3582
|
);
|
|
3198
3583
|
process.exit(1);
|
|
3199
3584
|
}
|
|
3200
3585
|
}
|
|
3586
|
+
var importPrompts = {
|
|
3587
|
+
select: p15.select,
|
|
3588
|
+
text: p15.text
|
|
3589
|
+
};
|
|
3590
|
+
function rewriteImports(content, aliasMap) {
|
|
3591
|
+
if (aliasMap.size === 0) return content;
|
|
3592
|
+
let rewritten = content;
|
|
3593
|
+
for (const [oldImport, newImport] of aliasMap.entries()) {
|
|
3594
|
+
const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
|
|
3595
|
+
const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
|
|
3596
|
+
const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3597
|
+
const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
|
|
3598
|
+
rewritten = rewritten.replace(regex, rawNew);
|
|
3599
|
+
}
|
|
3600
|
+
return rewritten;
|
|
3601
|
+
}
|
|
3602
|
+
function getModuleBaseDir(projectRoot, modType) {
|
|
3603
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3604
|
+
return path2.join(projectRoot, "src", folder);
|
|
3605
|
+
}
|
|
3606
|
+
function getExistingModuleDir(projectRoot, modType, modName) {
|
|
3607
|
+
const folder = modType === "shared" ? "shared" : "features";
|
|
3608
|
+
const inSrc = path2.join(projectRoot, "src", folder, modName);
|
|
3609
|
+
if (fs12.existsSync(inSrc)) return inSrc;
|
|
3610
|
+
const inRoot = path2.join(projectRoot, folder, modName);
|
|
3611
|
+
if (fs12.existsSync(inRoot)) return inRoot;
|
|
3612
|
+
return null;
|
|
3613
|
+
}
|
|
3614
|
+
function checkModuleExists(projectRoot, modType, modName) {
|
|
3615
|
+
return getExistingModuleDir(projectRoot, modType, modName) !== null;
|
|
3616
|
+
}
|
|
3617
|
+
async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
|
|
3618
|
+
const folder = depType === "shared" ? "shared" : "features";
|
|
3619
|
+
const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
|
|
3620
|
+
if (isAlreadyImportedThisRun) return;
|
|
3621
|
+
const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
|
|
3622
|
+
if (programmaticAlias) {
|
|
3623
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
|
|
3624
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3625
|
+
const displayDest = path2.relative(
|
|
3626
|
+
projectRoot,
|
|
3627
|
+
path2.join(baseDir, programmaticAlias)
|
|
3628
|
+
);
|
|
3629
|
+
p15.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3630
|
+
await importModuleRecursive(depType, depName, programmaticAlias);
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
3633
|
+
const existingDir = getExistingModuleDir(projectRoot, depType, depName);
|
|
3634
|
+
if (!existingDir) {
|
|
3635
|
+
p15.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc15.cyan(depName)}`);
|
|
3636
|
+
await importModuleRecursive(depType, depName, depName);
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
|
|
3640
|
+
(item) => item.name === depName
|
|
3641
|
+
);
|
|
3642
|
+
let remoteIntegrity = entry?.integrity;
|
|
3643
|
+
if (!remoteIntegrity) {
|
|
3644
|
+
try {
|
|
3645
|
+
const depManifest = await fetchModuleManifest(registryBase, depType, depName);
|
|
3646
|
+
remoteIntegrity = depManifest.integrity;
|
|
3647
|
+
} catch {
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
const localHash = computeDirectoryHash(existingDir);
|
|
3651
|
+
if (remoteIntegrity && localHash === remoteIntegrity) {
|
|
3652
|
+
p15.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
|
|
3653
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
if (flags.overwrite) {
|
|
3657
|
+
p15.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
|
|
3658
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3659
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3660
|
+
if (!flags.dryRun) {
|
|
3661
|
+
if (existingDir !== targetDir) {
|
|
3662
|
+
fs12.rmSync(existingDir, { recursive: true, force: true });
|
|
3663
|
+
}
|
|
3664
|
+
fs12.rmSync(targetDir, { recursive: true, force: true });
|
|
3665
|
+
}
|
|
3666
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
if (flags.yes) {
|
|
3670
|
+
p15.log.info(`Using existing '${depType}/${depName}'`);
|
|
3671
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
const choice = handleCancel(
|
|
3675
|
+
await importPrompts.select({
|
|
3676
|
+
message: `Local '${depType}/${depName}' differs from the registry version.`,
|
|
3677
|
+
options: [
|
|
3678
|
+
{
|
|
3679
|
+
value: "keep_existing",
|
|
3680
|
+
label: `Keep existing local version (Recommended)`
|
|
3681
|
+
},
|
|
3682
|
+
{
|
|
3683
|
+
value: "rename",
|
|
3684
|
+
label: "Import fresh copy under a new name (alias)"
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
value: "overwrite",
|
|
3688
|
+
label: "Overwrite local version with registry version"
|
|
3689
|
+
}
|
|
3690
|
+
]
|
|
3691
|
+
})
|
|
3692
|
+
);
|
|
3693
|
+
if (choice === "keep_existing" || choice === "use_existing") {
|
|
3694
|
+
p15.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
|
|
3695
|
+
importedModules.add(`${depType}:${depName}`);
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
if (choice === "rename") {
|
|
3699
|
+
const newName = handleCancel(
|
|
3700
|
+
await importPrompts.text({
|
|
3701
|
+
message: "What should we name the new copy?",
|
|
3702
|
+
placeholder: `${depName}-copy`,
|
|
3703
|
+
validate(val) {
|
|
3704
|
+
const v = val?.trim();
|
|
3705
|
+
if (!v) return "Name cannot be empty";
|
|
3706
|
+
if (v === depName) return `Name must be different from '${depName}'`;
|
|
3707
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
|
|
3708
|
+
return "Name can only contain alphanumeric characters, dashes, and underscores";
|
|
3709
|
+
}
|
|
3710
|
+
if (checkModuleExists(projectRoot, depType, v)) {
|
|
3711
|
+
return `'${v}' already exists in ${folder}, please choose another name`;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
})
|
|
3715
|
+
).trim();
|
|
3716
|
+
aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
|
|
3717
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3718
|
+
const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
|
|
3719
|
+
p15.log.step(`Importing '${depName}' into '${displayDest}'...`);
|
|
3720
|
+
await importModuleRecursive(depType, depName, newName);
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
if (choice === "overwrite") {
|
|
3724
|
+
p15.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
|
|
3725
|
+
const baseDir = getModuleBaseDir(projectRoot, depType);
|
|
3726
|
+
const targetDir = path2.join(baseDir, depName);
|
|
3727
|
+
if (!flags.dryRun) {
|
|
3728
|
+
if (existingDir !== targetDir) {
|
|
3729
|
+
fs12.rmSync(existingDir, { recursive: true, force: true });
|
|
3730
|
+
}
|
|
3731
|
+
fs12.rmSync(targetDir, { recursive: true, force: true });
|
|
3732
|
+
}
|
|
3733
|
+
await importModuleRecursive(depType, depName, depName, { overwrite: true });
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3201
3737
|
async function handleImport(typeArg, nameArg, flags = {}) {
|
|
3202
3738
|
const projectRoot = findProjectRoot();
|
|
3203
3739
|
let type = typeArg?.trim().toLowerCase();
|
|
3204
3740
|
if (type === "features") type = "feature";
|
|
3205
3741
|
if (!type) {
|
|
3206
3742
|
type = handleCancel(
|
|
3207
|
-
await
|
|
3743
|
+
await importPrompts.select({
|
|
3208
3744
|
message: "What kind of module would you like to import?",
|
|
3209
3745
|
options: [
|
|
3210
3746
|
{
|
|
@@ -3222,19 +3758,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3222
3758
|
);
|
|
3223
3759
|
}
|
|
3224
3760
|
if (type !== "shared" && type !== "feature") {
|
|
3225
|
-
|
|
3761
|
+
p15.log.error(pc15.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3226
3762
|
process.exit(1);
|
|
3227
3763
|
}
|
|
3228
3764
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3229
|
-
const s =
|
|
3230
|
-
s.start(`Connecting to registry at ${
|
|
3765
|
+
const s = p15.spinner();
|
|
3766
|
+
s.start(`Connecting to registry at ${pc15.dim(registryBase)}...`);
|
|
3231
3767
|
let index;
|
|
3232
3768
|
try {
|
|
3233
3769
|
index = await fetchRegistryIndex(registryBase);
|
|
3234
3770
|
s.stop("Registry index loaded.");
|
|
3235
3771
|
} catch (err) {
|
|
3236
|
-
s.stop(
|
|
3237
|
-
|
|
3772
|
+
s.stop(pc15.red("Failed to load registry index."));
|
|
3773
|
+
p15.log.error(pc15.red(err.message || String(err)));
|
|
3238
3774
|
process.exit(1);
|
|
3239
3775
|
}
|
|
3240
3776
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3242,11 +3778,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3242
3778
|
let name = nameArg?.trim();
|
|
3243
3779
|
if (!name) {
|
|
3244
3780
|
if (availableList.length === 0) {
|
|
3245
|
-
|
|
3781
|
+
p15.log.warn(`No ${type} modules available in the registry.`);
|
|
3246
3782
|
return;
|
|
3247
3783
|
}
|
|
3248
3784
|
name = handleCancel(
|
|
3249
|
-
await
|
|
3785
|
+
await importPrompts.select({
|
|
3250
3786
|
message: `Select a ${type} to import:`,
|
|
3251
3787
|
options: availableList.map((item) => ({
|
|
3252
3788
|
value: item.name,
|
|
@@ -3258,122 +3794,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3258
3794
|
}
|
|
3259
3795
|
if (!candidateNames.includes(name)) {
|
|
3260
3796
|
const closest = findClosestMatch(name, candidateNames);
|
|
3261
|
-
|
|
3797
|
+
p15.log.error(pc15.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3262
3798
|
if (closest) {
|
|
3263
|
-
|
|
3799
|
+
p15.log.info(pc15.yellow(`Did you mean '${pc15.cyan(closest)}'?`));
|
|
3264
3800
|
}
|
|
3265
3801
|
process.exit(1);
|
|
3266
3802
|
}
|
|
3267
3803
|
const importedModules = /* @__PURE__ */ new Set();
|
|
3268
3804
|
const totalWrittenFiles = [];
|
|
3269
3805
|
const requiredNpmPackages = /* @__PURE__ */ new Set();
|
|
3270
|
-
async function importModuleRecursive(modType,
|
|
3271
|
-
const modKey = `${modType}:${
|
|
3806
|
+
async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
|
|
3807
|
+
const modKey = `${modType}:${targetName}`;
|
|
3272
3808
|
if (importedModules.has(modKey)) return;
|
|
3273
|
-
|
|
3809
|
+
p15.log.step(
|
|
3810
|
+
`Importing ${modType}: ${pc15.cyan(targetName)}${targetName !== registryName ? pc15.dim(` (from ${registryName})`) : ""}...`
|
|
3811
|
+
);
|
|
3274
3812
|
let manifest;
|
|
3275
3813
|
try {
|
|
3276
|
-
manifest = await fetchModuleManifest(registryBase, modType,
|
|
3814
|
+
manifest = await fetchModuleManifest(registryBase, modType, registryName);
|
|
3277
3815
|
} catch (err) {
|
|
3278
|
-
|
|
3816
|
+
p15.log.error(pc15.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
|
|
3279
3817
|
process.exit(1);
|
|
3280
3818
|
}
|
|
3819
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3820
|
+
if (targetName !== registryName) {
|
|
3821
|
+
const folder2 = modType === "shared" ? "shared" : "features";
|
|
3822
|
+
aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
|
|
3823
|
+
}
|
|
3281
3824
|
const sharedDeps = manifest.dependencies?.shared || [];
|
|
3282
3825
|
for (const dep of sharedDeps) {
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3826
|
+
await resolveDependency(
|
|
3827
|
+
"shared",
|
|
3828
|
+
dep,
|
|
3829
|
+
aliasMap,
|
|
3830
|
+
projectRoot,
|
|
3831
|
+
flags,
|
|
3832
|
+
importModuleRecursive,
|
|
3833
|
+
importedModules,
|
|
3834
|
+
registryBase,
|
|
3835
|
+
index
|
|
3836
|
+
);
|
|
3288
3837
|
}
|
|
3289
3838
|
const featureDeps = manifest.dependencies?.features || [];
|
|
3290
3839
|
for (const dep of featureDeps) {
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3840
|
+
await resolveDependency(
|
|
3841
|
+
"feature",
|
|
3842
|
+
dep,
|
|
3843
|
+
aliasMap,
|
|
3844
|
+
projectRoot,
|
|
3845
|
+
flags,
|
|
3846
|
+
importModuleRecursive,
|
|
3847
|
+
importedModules,
|
|
3848
|
+
registryBase,
|
|
3849
|
+
index
|
|
3850
|
+
);
|
|
3296
3851
|
}
|
|
3297
3852
|
for (const pkg of manifest.dependencies?.npm || []) {
|
|
3298
3853
|
requiredNpmPackages.add(pkg);
|
|
3299
3854
|
}
|
|
3300
3855
|
const folder = modType === "shared" ? "shared" : "features";
|
|
3301
|
-
const targetDir =
|
|
3856
|
+
const targetDir = path2.join(getModuleBaseDir(projectRoot, modType), targetName);
|
|
3857
|
+
const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
|
|
3302
3858
|
for (const file of manifest.files || []) {
|
|
3303
3859
|
if (file === "registry.json") continue;
|
|
3304
|
-
const remoteFilePath = `${folder}/${
|
|
3305
|
-
const localFilePath =
|
|
3306
|
-
if (
|
|
3307
|
-
|
|
3860
|
+
const remoteFilePath = `${folder}/${registryName}/${file}`;
|
|
3861
|
+
const localFilePath = path2.join(targetDir, file);
|
|
3862
|
+
if (fs12.existsSync(localFilePath) && !isOverwrite) {
|
|
3863
|
+
p15.log.warn(
|
|
3864
|
+
` \u21B3 Skipping existing file: ${pc15.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
3865
|
+
);
|
|
3308
3866
|
continue;
|
|
3309
3867
|
}
|
|
3310
|
-
|
|
3868
|
+
let content = await fetchRegistryFile(registryBase, remoteFilePath);
|
|
3869
|
+
content = rewriteImports(content, aliasMap);
|
|
3311
3870
|
if (!flags.dryRun) {
|
|
3312
|
-
|
|
3313
|
-
|
|
3871
|
+
fs12.mkdirSync(path2.dirname(localFilePath), { recursive: true });
|
|
3872
|
+
fs12.writeFileSync(localFilePath, content, "utf-8");
|
|
3314
3873
|
}
|
|
3315
|
-
const displayPath =
|
|
3874
|
+
const displayPath = path2.relative(projectRoot, localFilePath);
|
|
3316
3875
|
totalWrittenFiles.push(displayPath);
|
|
3317
|
-
|
|
3876
|
+
p15.log.message(` ${pc15.green("+")} ${displayPath}`);
|
|
3318
3877
|
}
|
|
3319
3878
|
importedModules.add(modKey);
|
|
3320
3879
|
}
|
|
3321
|
-
await importModuleRecursive(type, name);
|
|
3880
|
+
await importModuleRecursive(type, name, name);
|
|
3322
3881
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
3323
3882
|
if (missingNpm.length > 0) {
|
|
3324
3883
|
if (flags.skipInstall || flags.dryRun) {
|
|
3325
|
-
|
|
3884
|
+
p15.log.info(`Required npm dependencies to install: ${pc15.cyan(missingNpm.join(", "))}`);
|
|
3326
3885
|
} else {
|
|
3327
3886
|
try {
|
|
3328
3887
|
installNpmPackages(projectRoot, missingNpm);
|
|
3329
3888
|
} catch (err) {
|
|
3330
|
-
|
|
3889
|
+
p15.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
|
|
3331
3890
|
}
|
|
3332
3891
|
}
|
|
3333
3892
|
}
|
|
3334
3893
|
if (flags.dryRun) {
|
|
3335
|
-
|
|
3894
|
+
p15.log.info(pc15.yellow("Dry run complete: No files were written to disk."));
|
|
3336
3895
|
} else {
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3896
|
+
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
3897
|
+
const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
|
|
3898
|
+
p15.log.success(
|
|
3899
|
+
pc15.green(
|
|
3900
|
+
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
3340
3901
|
)
|
|
3341
3902
|
);
|
|
3342
3903
|
}
|
|
3343
3904
|
}
|
|
3905
|
+
function loadMasterIndex(projectRoot) {
|
|
3906
|
+
const indexPath = path2.join(projectRoot, "index.json");
|
|
3907
|
+
if (!fs12.existsSync(indexPath)) {
|
|
3908
|
+
const initialIndex = { shared: [], features: [] };
|
|
3909
|
+
fs12.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
|
|
3910
|
+
return initialIndex;
|
|
3911
|
+
}
|
|
3912
|
+
return JSON.parse(fs12.readFileSync(indexPath, "utf-8"));
|
|
3913
|
+
}
|
|
3914
|
+
function saveMasterIndex(projectRoot, index) {
|
|
3915
|
+
const indexPath = path2.join(projectRoot, "index.json");
|
|
3916
|
+
index.shared.sort((a, b) => a.name.localeCompare(b.name));
|
|
3917
|
+
index.features.sort((a, b) => a.name.localeCompare(b.name));
|
|
3918
|
+
fs12.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
3919
|
+
}
|
|
3920
|
+
function discoverDiskModules(projectRoot) {
|
|
3921
|
+
const results = [];
|
|
3922
|
+
const sharedDir = resolveSharedDir(projectRoot);
|
|
3923
|
+
if (fs12.existsSync(sharedDir)) {
|
|
3924
|
+
const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
|
|
3925
|
+
for (const entry of entries) {
|
|
3926
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3927
|
+
results.push({ name: entry.name, type: "shared", dir: path2.join(sharedDir, entry.name) });
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
const featuresDir = resolveFeaturesDir(projectRoot);
|
|
3932
|
+
if (fs12.existsSync(featuresDir)) {
|
|
3933
|
+
const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
|
|
3934
|
+
for (const entry of entries) {
|
|
3935
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
3936
|
+
results.push({ name: entry.name, type: "feature", dir: path2.join(featuresDir, entry.name) });
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
return results;
|
|
3941
|
+
}
|
|
3942
|
+
function checkGitStatusForDir(projectRoot, dirPath) {
|
|
3943
|
+
try {
|
|
3944
|
+
const relPath = path2.relative(projectRoot, dirPath);
|
|
3945
|
+
const output = execSync(`git status --porcelain -- "${relPath}"`, {
|
|
3946
|
+
cwd: projectRoot,
|
|
3947
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3948
|
+
encoding: "utf-8"
|
|
3949
|
+
}).trim();
|
|
3950
|
+
if (!output) {
|
|
3951
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3952
|
+
}
|
|
3953
|
+
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
3954
|
+
return { hasChanges: true, filesCount: lines.length };
|
|
3955
|
+
} catch {
|
|
3956
|
+
return { hasChanges: false, filesCount: 0 };
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
function getRegistryDiff(projectRoot) {
|
|
3960
|
+
const index = loadMasterIndex(projectRoot);
|
|
3961
|
+
const diskModules = discoverDiskModules(projectRoot);
|
|
3962
|
+
const diskMap = /* @__PURE__ */ new Map();
|
|
3963
|
+
for (const mod of diskModules) {
|
|
3964
|
+
diskMap.set(`${mod.type}:${mod.name}`, mod);
|
|
3965
|
+
}
|
|
3966
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3967
|
+
for (const item of index.shared) {
|
|
3968
|
+
allKeys.add(`shared:${item.name}`);
|
|
3969
|
+
}
|
|
3970
|
+
for (const item of index.features) {
|
|
3971
|
+
allKeys.add(`feature:${item.name}`);
|
|
3972
|
+
}
|
|
3973
|
+
for (const mod of diskModules) {
|
|
3974
|
+
allKeys.add(`${mod.type}:${mod.name}`);
|
|
3975
|
+
}
|
|
3976
|
+
const statuses = [];
|
|
3977
|
+
for (const key of allKeys) {
|
|
3978
|
+
const [typeStr, name] = key.split(":");
|
|
3979
|
+
const type = typeStr;
|
|
3980
|
+
const registeredEntry = (type === "shared" ? index.shared : index.features).find(
|
|
3981
|
+
(e) => e.name === name
|
|
3982
|
+
);
|
|
3983
|
+
const isRegistered = Boolean(registeredEntry);
|
|
3984
|
+
const diskInfo = diskMap.get(key);
|
|
3985
|
+
const existsOnDisk = Boolean(diskInfo && fs12.existsSync(diskInfo.dir));
|
|
3986
|
+
let hasUncommittedGitChanges = false;
|
|
3987
|
+
let uncommittedFilesCount = 0;
|
|
3988
|
+
let hasIntegrityDrift = false;
|
|
3989
|
+
let localIntegrity;
|
|
3990
|
+
if (existsOnDisk && diskInfo) {
|
|
3991
|
+
const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
|
|
3992
|
+
hasUncommittedGitChanges = git.hasChanges;
|
|
3993
|
+
uncommittedFilesCount = git.filesCount;
|
|
3994
|
+
localIntegrity = computeDirectoryHash(diskInfo.dir);
|
|
3995
|
+
if (isRegistered && registeredEntry?.integrity) {
|
|
3996
|
+
hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
statuses.push({
|
|
4000
|
+
name,
|
|
4001
|
+
type,
|
|
4002
|
+
isRegistered,
|
|
4003
|
+
existsOnDisk,
|
|
4004
|
+
hasUncommittedGitChanges,
|
|
4005
|
+
uncommittedFilesCount,
|
|
4006
|
+
hasIntegrityDrift,
|
|
4007
|
+
localIntegrity,
|
|
4008
|
+
registeredIntegrity: registeredEntry?.integrity
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
4012
|
+
}
|
|
4013
|
+
async function registerModule(typeArg, nameArg, flags = {}) {
|
|
4014
|
+
const projectRoot = findProjectRoot();
|
|
4015
|
+
const index = loadMasterIndex(projectRoot);
|
|
4016
|
+
let type = typeArg?.trim();
|
|
4017
|
+
if (!type) {
|
|
4018
|
+
type = handleCancel(
|
|
4019
|
+
await p15.select({
|
|
4020
|
+
message: "Module type to register:",
|
|
4021
|
+
options: [
|
|
4022
|
+
{ value: "feature", label: "Feature" },
|
|
4023
|
+
{ value: "shared", label: "Shared Module" }
|
|
4024
|
+
]
|
|
4025
|
+
})
|
|
4026
|
+
);
|
|
4027
|
+
}
|
|
4028
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4029
|
+
const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
|
|
4030
|
+
const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
|
|
4031
|
+
let name = nameArg?.trim();
|
|
4032
|
+
if (!name) {
|
|
4033
|
+
if (unregistered.length > 0) {
|
|
4034
|
+
const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
|
|
4035
|
+
opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
|
|
4036
|
+
const choice = handleCancel(
|
|
4037
|
+
await p15.select({
|
|
4038
|
+
message: `Select a ${type} to register:`,
|
|
4039
|
+
options: opts
|
|
4040
|
+
})
|
|
4041
|
+
);
|
|
4042
|
+
if (choice === "__custom__") {
|
|
4043
|
+
name = handleCancel(
|
|
4044
|
+
await p15.text({
|
|
4045
|
+
message: `Enter ${type} name:`,
|
|
4046
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4047
|
+
})
|
|
4048
|
+
).trim();
|
|
4049
|
+
} else {
|
|
4050
|
+
name = choice;
|
|
4051
|
+
}
|
|
4052
|
+
} else {
|
|
4053
|
+
name = handleCancel(
|
|
4054
|
+
await p15.text({
|
|
4055
|
+
message: `Enter ${type} name to register:`,
|
|
4056
|
+
validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
|
|
4057
|
+
})
|
|
4058
|
+
).trim();
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4062
|
+
const modDir = path2.join(folder, name);
|
|
4063
|
+
if (!fs12.existsSync(modDir)) {
|
|
4064
|
+
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4065
|
+
process.exit(1);
|
|
4066
|
+
}
|
|
4067
|
+
const files = getAllFiles(modDir);
|
|
4068
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4069
|
+
const manifestPath = path2.join(modDir, "registry.json");
|
|
4070
|
+
let manifest;
|
|
4071
|
+
if (fs12.existsSync(manifestPath)) {
|
|
4072
|
+
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4073
|
+
} else {
|
|
4074
|
+
manifest = {
|
|
4075
|
+
name,
|
|
4076
|
+
type,
|
|
4077
|
+
description: `${name} ${type} module`,
|
|
4078
|
+
version: "1.0.0",
|
|
4079
|
+
dependencies: {
|
|
4080
|
+
npm: [],
|
|
4081
|
+
shared: [],
|
|
4082
|
+
features: []
|
|
4083
|
+
},
|
|
4084
|
+
files: []
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
manifest.name = name;
|
|
4088
|
+
manifest.type = type;
|
|
4089
|
+
manifest.files = files;
|
|
4090
|
+
manifest.integrity = integrity;
|
|
4091
|
+
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4092
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4093
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4094
|
+
if (existingIdx >= 0) {
|
|
4095
|
+
targetList[existingIdx] = {
|
|
4096
|
+
name,
|
|
4097
|
+
description: manifest.description,
|
|
4098
|
+
version: manifest.version,
|
|
4099
|
+
integrity
|
|
4100
|
+
};
|
|
4101
|
+
} else {
|
|
4102
|
+
targetList.push({
|
|
4103
|
+
name,
|
|
4104
|
+
description: manifest.description,
|
|
4105
|
+
version: manifest.version,
|
|
4106
|
+
integrity
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
saveMasterIndex(projectRoot, index);
|
|
4110
|
+
p15.log.success(
|
|
4111
|
+
pc15.green(
|
|
4112
|
+
`\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
|
|
4113
|
+
)
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
async function commitModule(typeArg, nameArg, flags = {}) {
|
|
4117
|
+
const projectRoot = findProjectRoot();
|
|
4118
|
+
let type = typeArg?.trim();
|
|
4119
|
+
if (!type) {
|
|
4120
|
+
type = handleCancel(
|
|
4121
|
+
await p15.select({
|
|
4122
|
+
message: "Module type to commit:",
|
|
4123
|
+
options: [
|
|
4124
|
+
{ value: "feature", label: "Feature" },
|
|
4125
|
+
{ value: "shared", label: "Shared Module" }
|
|
4126
|
+
]
|
|
4127
|
+
})
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
let name = nameArg?.trim();
|
|
4131
|
+
if (!name) {
|
|
4132
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4133
|
+
const opts = diskModules.map((m) => {
|
|
4134
|
+
const git = checkGitStatusForDir(projectRoot, m.dir);
|
|
4135
|
+
return {
|
|
4136
|
+
value: m.name,
|
|
4137
|
+
label: m.name,
|
|
4138
|
+
hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
|
|
4139
|
+
};
|
|
4140
|
+
});
|
|
4141
|
+
name = handleCancel(
|
|
4142
|
+
await p15.select({
|
|
4143
|
+
message: `Select ${type} to commit:`,
|
|
4144
|
+
options: opts
|
|
4145
|
+
})
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4149
|
+
const modDir = path2.join(folder, name);
|
|
4150
|
+
if (!fs12.existsSync(modDir)) {
|
|
4151
|
+
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4152
|
+
process.exit(1);
|
|
4153
|
+
}
|
|
4154
|
+
const relModDir = path2.relative(projectRoot, modDir);
|
|
4155
|
+
const gitStatus = checkGitStatusForDir(projectRoot, modDir);
|
|
4156
|
+
if (!gitStatus.hasChanges) {
|
|
4157
|
+
p15.log.warn(pc15.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
let message = flags.message?.trim();
|
|
4161
|
+
if (!message) {
|
|
4162
|
+
message = handleCancel(
|
|
4163
|
+
await p15.text({
|
|
4164
|
+
message: `Commit message for ${type}/${name}:`,
|
|
4165
|
+
placeholder: `e.g. feat(${name}): update use cases and dependencies`,
|
|
4166
|
+
validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
|
|
4167
|
+
})
|
|
4168
|
+
).trim();
|
|
4169
|
+
}
|
|
4170
|
+
const s = p15.spinner();
|
|
4171
|
+
s.start(`Committing '${type}/${name}' to git repository...`);
|
|
4172
|
+
try {
|
|
4173
|
+
execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
4174
|
+
execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
|
|
4175
|
+
cwd: projectRoot,
|
|
4176
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4177
|
+
});
|
|
4178
|
+
s.stop(`Changes for '${type}/${name}' committed to git.`);
|
|
4179
|
+
p15.log.success(pc15.green(`\u2713 Committed '${type}/${name}': "${message}"`));
|
|
4180
|
+
} catch (err) {
|
|
4181
|
+
s.stop(pc15.red("Git commit failed."));
|
|
4182
|
+
p15.log.error(pc15.red(err.message || String(err)));
|
|
4183
|
+
process.exit(1);
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
async function registerModuleChanges(typeArg, nameArg, flags = {}) {
|
|
4187
|
+
const projectRoot = findProjectRoot();
|
|
4188
|
+
const index = loadMasterIndex(projectRoot);
|
|
4189
|
+
let type = typeArg?.trim();
|
|
4190
|
+
if (!type) {
|
|
4191
|
+
type = handleCancel(
|
|
4192
|
+
await p15.select({
|
|
4193
|
+
message: "Module type to update:",
|
|
4194
|
+
options: [
|
|
4195
|
+
{ value: "feature", label: "Feature" },
|
|
4196
|
+
{ value: "shared", label: "Shared Module" }
|
|
4197
|
+
]
|
|
4198
|
+
})
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4201
|
+
let name = nameArg?.trim();
|
|
4202
|
+
if (!name) {
|
|
4203
|
+
const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
|
|
4204
|
+
const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
|
|
4205
|
+
const opts = diskModules.map((m) => {
|
|
4206
|
+
const d = diffs.find((item) => item.name === m.name);
|
|
4207
|
+
return {
|
|
4208
|
+
value: m.name,
|
|
4209
|
+
label: m.name,
|
|
4210
|
+
hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
|
|
4211
|
+
};
|
|
4212
|
+
});
|
|
4213
|
+
name = handleCancel(
|
|
4214
|
+
await p15.select({
|
|
4215
|
+
message: `Select ${type} to register changes:`,
|
|
4216
|
+
options: opts
|
|
4217
|
+
})
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
4220
|
+
const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
|
|
4221
|
+
const modDir = path2.join(folder, name);
|
|
4222
|
+
if (!fs12.existsSync(modDir)) {
|
|
4223
|
+
p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
|
|
4224
|
+
process.exit(1);
|
|
4225
|
+
}
|
|
4226
|
+
const files = getAllFiles(modDir);
|
|
4227
|
+
const integrity = computeDirectoryHash(modDir, files);
|
|
4228
|
+
const manifestPath = path2.join(modDir, "registry.json");
|
|
4229
|
+
let manifest;
|
|
4230
|
+
if (fs12.existsSync(manifestPath)) {
|
|
4231
|
+
manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
|
|
4232
|
+
} else {
|
|
4233
|
+
manifest = {
|
|
4234
|
+
name,
|
|
4235
|
+
type,
|
|
4236
|
+
description: `${name} ${type} module`,
|
|
4237
|
+
version: "1.0.0",
|
|
4238
|
+
dependencies: { npm: [], shared: [], features: [] },
|
|
4239
|
+
files: []
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
manifest.files = files;
|
|
4243
|
+
manifest.integrity = integrity;
|
|
4244
|
+
if (flags.version) {
|
|
4245
|
+
manifest.version = flags.version;
|
|
4246
|
+
}
|
|
4247
|
+
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4248
|
+
const targetList = type === "shared" ? index.shared : index.features;
|
|
4249
|
+
const existingIdx = targetList.findIndex((item) => item.name === name);
|
|
4250
|
+
if (existingIdx >= 0) {
|
|
4251
|
+
targetList[existingIdx] = {
|
|
4252
|
+
name,
|
|
4253
|
+
description: manifest.description,
|
|
4254
|
+
version: manifest.version,
|
|
4255
|
+
integrity
|
|
4256
|
+
};
|
|
4257
|
+
} else {
|
|
4258
|
+
targetList.push({
|
|
4259
|
+
name,
|
|
4260
|
+
description: manifest.description,
|
|
4261
|
+
version: manifest.version,
|
|
4262
|
+
integrity
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
saveMasterIndex(projectRoot, index);
|
|
4266
|
+
p15.log.success(
|
|
4267
|
+
pc15.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
|
|
4268
|
+
);
|
|
4269
|
+
}
|
|
4270
|
+
async function unregisterModule(typeArg, nameArg, flags = {}) {
|
|
4271
|
+
const projectRoot = findProjectRoot();
|
|
4272
|
+
const index = loadMasterIndex(projectRoot);
|
|
4273
|
+
let type = typeArg?.trim();
|
|
4274
|
+
if (!type) {
|
|
4275
|
+
type = handleCancel(
|
|
4276
|
+
await p15.select({
|
|
4277
|
+
message: "Module type to unregister:",
|
|
4278
|
+
options: [
|
|
4279
|
+
{ value: "feature", label: "Feature" },
|
|
4280
|
+
{ value: "shared", label: "Shared Module" }
|
|
4281
|
+
]
|
|
4282
|
+
})
|
|
4283
|
+
);
|
|
4284
|
+
}
|
|
4285
|
+
const registeredList = type === "shared" ? index.shared : index.features;
|
|
4286
|
+
if (registeredList.length === 0) {
|
|
4287
|
+
p15.log.warn(`No registered ${type} modules found in index.json.`);
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
let name = nameArg?.trim();
|
|
4291
|
+
if (!name) {
|
|
4292
|
+
name = handleCancel(
|
|
4293
|
+
await p15.select({
|
|
4294
|
+
message: `Select ${type} to unregister from index.json:`,
|
|
4295
|
+
options: registeredList.map((item) => ({
|
|
4296
|
+
value: item.name,
|
|
4297
|
+
label: item.name,
|
|
4298
|
+
hint: item.description
|
|
4299
|
+
}))
|
|
4300
|
+
})
|
|
4301
|
+
);
|
|
4302
|
+
}
|
|
4303
|
+
if (type === "shared") {
|
|
4304
|
+
index.shared = index.shared.filter((item) => item.name !== name);
|
|
4305
|
+
} else {
|
|
4306
|
+
index.features = index.features.filter((item) => item.name !== name);
|
|
4307
|
+
}
|
|
4308
|
+
saveMasterIndex(projectRoot, index);
|
|
4309
|
+
p15.log.success(
|
|
4310
|
+
pc15.green(
|
|
4311
|
+
`\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
|
|
4312
|
+
)
|
|
4313
|
+
);
|
|
4314
|
+
}
|
|
4315
|
+
function diffRegistry(flags = {}) {
|
|
4316
|
+
const projectRoot = findProjectRoot();
|
|
4317
|
+
const diffs = getRegistryDiff(projectRoot);
|
|
4318
|
+
p15.log.step(pc15.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
|
|
4319
|
+
const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
|
|
4320
|
+
const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
|
|
4321
|
+
const committedDrift = diffs.filter(
|
|
4322
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
|
|
4323
|
+
);
|
|
4324
|
+
const upToDate = diffs.filter(
|
|
4325
|
+
(d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
|
|
4326
|
+
);
|
|
4327
|
+
const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
|
|
4328
|
+
if (unregistered.length > 0) {
|
|
4329
|
+
p15.log.message(pc15.yellow(`
|
|
4330
|
+
\u{1F4E6} Unregistered Modules (${unregistered.length}):`));
|
|
4331
|
+
for (const mod of unregistered) {
|
|
4332
|
+
p15.log.message(` ${pc15.red("\u25CF")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(not registered in index.json)")}`);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
if (uncommitted.length > 0) {
|
|
4336
|
+
p15.log.message(pc15.yellow(`
|
|
4337
|
+
\u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
|
|
4338
|
+
for (const mod of uncommitted) {
|
|
4339
|
+
p15.log.message(
|
|
4340
|
+
` ${pc15.yellow("\u25B2")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
|
|
4341
|
+
);
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
if (committedDrift.length > 0) {
|
|
4345
|
+
p15.log.message(pc15.cyan(`
|
|
4346
|
+
\u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
|
|
4347
|
+
for (const mod of committedDrift) {
|
|
4348
|
+
p15.log.message(
|
|
4349
|
+
` ${pc15.cyan("\u25C6")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(
|
|
4350
|
+
`local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
|
|
4351
|
+
)}`
|
|
4352
|
+
);
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
if (missing.length > 0) {
|
|
4356
|
+
p15.log.message(pc15.red(`
|
|
4357
|
+
\u26A0\uFE0F Missing on Disk (${missing.length}):`));
|
|
4358
|
+
for (const mod of missing) {
|
|
4359
|
+
p15.log.message(` ${pc15.red("\u2715")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(in index.json, but folder missing)")}`);
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
if (upToDate.length > 0) {
|
|
4363
|
+
p15.log.message(pc15.green(`
|
|
4364
|
+
\u2713 Up to Date & Registered (${upToDate.length}):`));
|
|
4365
|
+
for (const mod of upToDate) {
|
|
4366
|
+
p15.log.message(` ${pc15.green("\u2714")} ${mod.type}/${mod.name} ${pc15.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
p15.log.message("");
|
|
4370
|
+
}
|
|
4371
|
+
async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
|
|
4372
|
+
let action = actionArg?.trim().toLowerCase();
|
|
4373
|
+
if (!action) {
|
|
4374
|
+
action = handleCancel(
|
|
4375
|
+
await p15.select({
|
|
4376
|
+
message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
|
|
4377
|
+
options: [
|
|
4378
|
+
{
|
|
4379
|
+
value: "diff",
|
|
4380
|
+
label: "Inspect Diff & Status",
|
|
4381
|
+
hint: "Summary of unregistered, uncommitted, and drifted modules"
|
|
4382
|
+
},
|
|
4383
|
+
{
|
|
4384
|
+
value: "register",
|
|
4385
|
+
label: "Register Module",
|
|
4386
|
+
hint: "Add an existing module to index.json with integrity checksum"
|
|
4387
|
+
},
|
|
4388
|
+
{
|
|
4389
|
+
value: "commit",
|
|
4390
|
+
label: "Commit Changes",
|
|
4391
|
+
hint: "Commit a module's working changes to git in the background"
|
|
4392
|
+
},
|
|
4393
|
+
{
|
|
4394
|
+
value: "register-changes",
|
|
4395
|
+
label: "Register Changes",
|
|
4396
|
+
hint: "Update index.json & registry.json with latest checksums"
|
|
4397
|
+
},
|
|
4398
|
+
{
|
|
4399
|
+
value: "unregister",
|
|
4400
|
+
label: "Unregister Module",
|
|
4401
|
+
hint: "Remove module from index.json (source code stays on disk)"
|
|
4402
|
+
}
|
|
4403
|
+
]
|
|
4404
|
+
})
|
|
4405
|
+
);
|
|
4406
|
+
}
|
|
4407
|
+
switch (action) {
|
|
4408
|
+
case "diff":
|
|
4409
|
+
case "status":
|
|
4410
|
+
diffRegistry(flags);
|
|
4411
|
+
break;
|
|
4412
|
+
case "register":
|
|
4413
|
+
await registerModule(typeArg, nameArg, flags);
|
|
4414
|
+
break;
|
|
4415
|
+
case "commit":
|
|
4416
|
+
await commitModule(typeArg, nameArg, flags);
|
|
4417
|
+
break;
|
|
4418
|
+
case "register-changes":
|
|
4419
|
+
case "update":
|
|
4420
|
+
await registerModuleChanges(typeArg, nameArg, flags);
|
|
4421
|
+
break;
|
|
4422
|
+
case "unregister":
|
|
4423
|
+
await unregisterModule(typeArg, nameArg, flags);
|
|
4424
|
+
break;
|
|
4425
|
+
default:
|
|
4426
|
+
p15.log.error(
|
|
4427
|
+
pc15.red(
|
|
4428
|
+
`Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
|
|
4429
|
+
)
|
|
4430
|
+
);
|
|
4431
|
+
process.exit(1);
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
3344
4434
|
|
|
3345
4435
|
// src/cli.ts
|
|
3346
4436
|
function createCli() {
|
|
3347
4437
|
const cli = cac("mason");
|
|
3348
|
-
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) => {
|
|
4438
|
+
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) => {
|
|
3349
4439
|
if (args && args.length > 0) {
|
|
3350
|
-
|
|
3351
|
-
|
|
4440
|
+
p15.log.error(
|
|
4441
|
+
pc15.red(
|
|
3352
4442
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
3353
4443
|
)
|
|
3354
4444
|
);
|
|
3355
4445
|
process.exit(1);
|
|
3356
4446
|
}
|
|
3357
4447
|
try {
|
|
4448
|
+
const projectRoot = findProjectRoot();
|
|
4449
|
+
const mode = getEffectiveMode(projectRoot, flags);
|
|
4450
|
+
if (mode === "registry") {
|
|
4451
|
+
const choice2 = handleCancel(
|
|
4452
|
+
await p15.select({
|
|
4453
|
+
message: "\u{1F3DB}\uFE0F Mason Registry Maintainer: What would you like to do?",
|
|
4454
|
+
options: [
|
|
4455
|
+
{
|
|
4456
|
+
value: "diff",
|
|
4457
|
+
label: "Inspect Diff & Status",
|
|
4458
|
+
hint: "Overview of unregistered, uncommitted, or drifted modules"
|
|
4459
|
+
},
|
|
4460
|
+
{
|
|
4461
|
+
value: "create_feature",
|
|
4462
|
+
label: "Initialize New Feature",
|
|
4463
|
+
hint: "Scaffold a new feature slice under features/"
|
|
4464
|
+
},
|
|
4465
|
+
{
|
|
4466
|
+
value: "create_shared",
|
|
4467
|
+
label: "Initialize New Shared Module",
|
|
4468
|
+
hint: "Scaffold a new shared utility/port under shared/"
|
|
4469
|
+
},
|
|
4470
|
+
{
|
|
4471
|
+
value: "create_usecase",
|
|
4472
|
+
label: "Create New Use Case",
|
|
4473
|
+
hint: "Add a new use case to an existing feature"
|
|
4474
|
+
},
|
|
4475
|
+
{
|
|
4476
|
+
value: "register",
|
|
4477
|
+
label: "Register Module",
|
|
4478
|
+
hint: "Add a module to index.json with integrity checksum"
|
|
4479
|
+
},
|
|
4480
|
+
{
|
|
4481
|
+
value: "commit",
|
|
4482
|
+
label: "Commit Module Changes",
|
|
4483
|
+
hint: "Commit working changes in a module to git"
|
|
4484
|
+
},
|
|
4485
|
+
{
|
|
4486
|
+
value: "register_changes",
|
|
4487
|
+
label: "Register Changes",
|
|
4488
|
+
hint: "Recompute checksums and update index.json"
|
|
4489
|
+
},
|
|
4490
|
+
{
|
|
4491
|
+
value: "unregister",
|
|
4492
|
+
label: "Unregister Module",
|
|
4493
|
+
hint: "Remove from index.json but keep source on disk"
|
|
4494
|
+
},
|
|
4495
|
+
{
|
|
4496
|
+
value: "import",
|
|
4497
|
+
label: "Import from Remote Registry",
|
|
4498
|
+
hint: "Download a module from upstream"
|
|
4499
|
+
}
|
|
4500
|
+
]
|
|
4501
|
+
})
|
|
4502
|
+
);
|
|
4503
|
+
switch (choice2) {
|
|
4504
|
+
case "diff":
|
|
4505
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4506
|
+
break;
|
|
4507
|
+
case "create_feature":
|
|
4508
|
+
await handleCreate("feature", void 0, flags);
|
|
4509
|
+
break;
|
|
4510
|
+
case "create_shared":
|
|
4511
|
+
await handleCreate("shared", void 0, flags);
|
|
4512
|
+
break;
|
|
4513
|
+
case "create_usecase":
|
|
4514
|
+
await handleCreate("usecase", void 0, flags);
|
|
4515
|
+
break;
|
|
4516
|
+
case "register":
|
|
4517
|
+
await handleRegistry("register", void 0, void 0, flags);
|
|
4518
|
+
break;
|
|
4519
|
+
case "commit":
|
|
4520
|
+
await handleRegistry("commit", void 0, void 0, flags);
|
|
4521
|
+
break;
|
|
4522
|
+
case "register_changes":
|
|
4523
|
+
await handleRegistry("register-changes", void 0, void 0, flags);
|
|
4524
|
+
break;
|
|
4525
|
+
case "unregister":
|
|
4526
|
+
await handleRegistry("unregister", void 0, void 0, flags);
|
|
4527
|
+
break;
|
|
4528
|
+
case "import":
|
|
4529
|
+
await handleImport(void 0, void 0, flags);
|
|
4530
|
+
break;
|
|
4531
|
+
}
|
|
4532
|
+
return;
|
|
4533
|
+
}
|
|
3358
4534
|
const choice = handleCancel(
|
|
3359
|
-
await
|
|
4535
|
+
await p15.select({
|
|
3360
4536
|
message: "\u{1F9F1} Mason: What would you like to do?",
|
|
3361
4537
|
options: [
|
|
3362
4538
|
{
|
|
3363
4539
|
value: "create",
|
|
3364
4540
|
label: "Create local component",
|
|
3365
|
-
hint: "Scaffold a new feature, use case, domain entity,
|
|
4541
|
+
hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
|
|
3366
4542
|
},
|
|
3367
4543
|
{
|
|
3368
4544
|
value: "import",
|
|
3369
4545
|
label: "Import from registry",
|
|
3370
4546
|
hint: "Download and link pre-built shared modules or features from the registry"
|
|
4547
|
+
},
|
|
4548
|
+
{
|
|
4549
|
+
value: "registry",
|
|
4550
|
+
label: "Registry management",
|
|
4551
|
+
hint: "Inspect diff, register modules, or commit module changes"
|
|
3371
4552
|
}
|
|
3372
4553
|
]
|
|
3373
4554
|
})
|
|
3374
4555
|
);
|
|
3375
4556
|
if (choice === "create") {
|
|
3376
4557
|
await handleCreate(void 0, void 0, flags);
|
|
4558
|
+
} else if (choice === "registry") {
|
|
4559
|
+
await handleRegistry(void 0, void 0, void 0, flags);
|
|
3377
4560
|
} else {
|
|
3378
4561
|
await handleImport(void 0, void 0, flags);
|
|
3379
4562
|
}
|
|
@@ -3383,8 +4566,8 @@ function createCli() {
|
|
|
3383
4566
|
});
|
|
3384
4567
|
cli.command(
|
|
3385
4568
|
"create [type] [name]",
|
|
3386
|
-
"Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
|
|
3387
|
-
).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 = {}) => {
|
|
4569
|
+
"Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
|
|
4570
|
+
).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 = {}) => {
|
|
3388
4571
|
try {
|
|
3389
4572
|
await handleCreate(type, name, flags);
|
|
3390
4573
|
} catch (err) {
|
|
@@ -3401,8 +4584,28 @@ function createCli() {
|
|
|
3401
4584
|
handleCliError(err);
|
|
3402
4585
|
}
|
|
3403
4586
|
});
|
|
4587
|
+
cli.command(
|
|
4588
|
+
"registry [action] [type] [name]",
|
|
4589
|
+
"Registry & version management (diff, register, commit, register-changes, unregister)"
|
|
4590
|
+
).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(
|
|
4591
|
+
async (action, type, name, flags = {}) => {
|
|
4592
|
+
try {
|
|
4593
|
+
await handleRegistry(action, type, name, flags);
|
|
4594
|
+
} catch (err) {
|
|
4595
|
+
handleCliError(err);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
);
|
|
4599
|
+
cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
|
|
4600
|
+
try {
|
|
4601
|
+
await handleRegistry("diff", void 0, void 0, flags);
|
|
4602
|
+
} catch (err) {
|
|
4603
|
+
handleCliError(err);
|
|
4604
|
+
}
|
|
4605
|
+
});
|
|
3404
4606
|
const createSubtypes = [
|
|
3405
4607
|
"feature",
|
|
4608
|
+
"shared",
|
|
3406
4609
|
"usecase",
|
|
3407
4610
|
"domain",
|
|
3408
4611
|
"infrastructure",
|
|
@@ -3423,7 +4626,7 @@ function createCli() {
|
|
|
3423
4626
|
});
|
|
3424
4627
|
}
|
|
3425
4628
|
cli.help();
|
|
3426
|
-
cli.version("1.0.
|
|
4629
|
+
cli.version("1.0.13");
|
|
3427
4630
|
return cli;
|
|
3428
4631
|
}
|
|
3429
4632
|
function handleCliError(err) {
|
|
@@ -3431,7 +4634,7 @@ function handleCliError(err) {
|
|
|
3431
4634
|
process.exit(0);
|
|
3432
4635
|
}
|
|
3433
4636
|
const message = err instanceof Error ? err.message : String(err);
|
|
3434
|
-
|
|
4637
|
+
p15.log.error(pc15.red(`Error: ${message}`));
|
|
3435
4638
|
process.exit(1);
|
|
3436
4639
|
}
|
|
3437
4640
|
async function runCli(argv = process.argv) {
|
|
@@ -3439,6 +4642,6 @@ async function runCli(argv = process.argv) {
|
|
|
3439
4642
|
cli.parse(argv, { run: true });
|
|
3440
4643
|
}
|
|
3441
4644
|
|
|
3442
|
-
export { DEFAULT_REGISTRY_BASE, PromptCancelledError, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listUseCases, loadInterfaceHierarchy, loadTemplate, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, runCli, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
4645
|
+
export { DEFAULT_REGISTRY_BASE, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, handleRegistry, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, resolveSharedDir, runCli, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
|
|
3443
4646
|
//# sourceMappingURL=index.js.map
|
|
3444
4647
|
//# sourceMappingURL=index.js.map
|