@ciwergrp/nuxid 1.1.1 → 1.1.3
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/bin/nuxid.mjs +9 -0
- package/console/request.mjs +14 -0
- package/console/resource.mjs +451 -0
- package/dist/module.json +1 -1
- package/package.json +1 -1
package/bin/nuxid.mjs
CHANGED
|
@@ -13,6 +13,7 @@ function printHelp() {
|
|
|
13
13
|
console.log('Usage: pnpm nuxid <command> [options]\n');
|
|
14
14
|
console.log('Commands:');
|
|
15
15
|
console.log(' make:request <name> Interactive generator for request validation files');
|
|
16
|
+
console.log(' make:resource <name> Interactive generator for api resource modules');
|
|
16
17
|
console.log('\nUse `pnpm nuxid <command> --help` for more information.\n');
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -31,6 +32,14 @@ if (command === 'make:request') {
|
|
|
31
32
|
|
|
32
33
|
child.on('exit', code => process.exit(code ?? 0));
|
|
33
34
|
}
|
|
35
|
+
else if (command === 'make:resource') {
|
|
36
|
+
const scriptPath = path.join(__dirname, '../console/resource.mjs');
|
|
37
|
+
const child = spawn(process.execPath, [scriptPath, ...rest], {
|
|
38
|
+
stdio: 'inherit',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.on('exit', code => process.exit(code ?? 0));
|
|
42
|
+
}
|
|
34
43
|
else if (command === '--help' || command === '-h') {
|
|
35
44
|
printHelp();
|
|
36
45
|
}
|
package/console/request.mjs
CHANGED
|
@@ -21,9 +21,18 @@ const availableRules = [
|
|
|
21
21
|
'alpha',
|
|
22
22
|
'alpha_num',
|
|
23
23
|
'alpha_dash',
|
|
24
|
+
'alphanumeric_space',
|
|
25
|
+
'alpha_space',
|
|
24
26
|
'numeric',
|
|
25
27
|
'integer',
|
|
28
|
+
'bail',
|
|
26
29
|
'date',
|
|
30
|
+
'before',
|
|
31
|
+
'after',
|
|
32
|
+
'array',
|
|
33
|
+
'url',
|
|
34
|
+
'ip',
|
|
35
|
+
'hex_color',
|
|
27
36
|
'min',
|
|
28
37
|
'max',
|
|
29
38
|
'same',
|
|
@@ -32,8 +41,10 @@ const availableRules = [
|
|
|
32
41
|
'gte',
|
|
33
42
|
'lt',
|
|
34
43
|
'lte',
|
|
44
|
+
'in',
|
|
35
45
|
'starts_with',
|
|
36
46
|
'ends_with',
|
|
47
|
+
'regex',
|
|
37
48
|
];
|
|
38
49
|
|
|
39
50
|
const parameterizedRules = new Set([
|
|
@@ -48,6 +59,9 @@ const parameterizedRules = new Set([
|
|
|
48
59
|
'starts_with',
|
|
49
60
|
'ends_with',
|
|
50
61
|
'regex',
|
|
62
|
+
'before',
|
|
63
|
+
'after',
|
|
64
|
+
'in',
|
|
51
65
|
]);
|
|
52
66
|
|
|
53
67
|
function toCamelCase(str) {
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { cwd } from 'node:process';
|
|
5
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
6
|
+
|
|
7
|
+
const colors = {
|
|
8
|
+
reset: '\x1B[0m',
|
|
9
|
+
cyan: '\x1B[36m',
|
|
10
|
+
yellow: '\x1B[33m',
|
|
11
|
+
green: '\x1B[32m',
|
|
12
|
+
red: '\x1B[31m',
|
|
13
|
+
dim: '\x1B[2m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function displayHeader(title) {
|
|
17
|
+
const width = title.length + 4;
|
|
18
|
+
const border = '─'.repeat(width);
|
|
19
|
+
console.log(`┌${border}┐`);
|
|
20
|
+
console.log(`│ ${colors.cyan}${title}${colors.reset} │`);
|
|
21
|
+
console.log(`└${border}┘\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadPrefixFromProjectConfig() {
|
|
25
|
+
const candidates = [
|
|
26
|
+
'nuxid.mjs',
|
|
27
|
+
'nuxid.cjs',
|
|
28
|
+
'nuxid.js',
|
|
29
|
+
'nuxid.ts',
|
|
30
|
+
'nuxid.json',
|
|
31
|
+
'nuxid.config.json',
|
|
32
|
+
'artisan.mjs',
|
|
33
|
+
'artisan.cjs',
|
|
34
|
+
'artisan.js',
|
|
35
|
+
'artisan.ts',
|
|
36
|
+
'artisan.json',
|
|
37
|
+
'artisan.config.json',
|
|
38
|
+
'package.json',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const file of candidates) {
|
|
42
|
+
const p = path.join(cwd(), file);
|
|
43
|
+
if (!fs.existsSync(p)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (file.endsWith('.json')) {
|
|
49
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
50
|
+
const obj = JSON.parse(raw);
|
|
51
|
+
if (file === 'package.json') {
|
|
52
|
+
if (obj?.nuxid && typeof obj.nuxid.prefix === 'string') {
|
|
53
|
+
return obj.nuxid.prefix;
|
|
54
|
+
}
|
|
55
|
+
if (obj?.artisan && typeof obj.artisan.prefix === 'string') {
|
|
56
|
+
return obj.artisan.prefix;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (typeof obj?.prefix === 'string') {
|
|
60
|
+
return obj.prefix;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (file.endsWith('.js') || file.endsWith('.mjs') || file.endsWith('.cjs') || file.endsWith('.ts')) {
|
|
65
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
66
|
+
const m = raw.match(/prefix\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
67
|
+
if (m && m[1] !== undefined) {
|
|
68
|
+
return m[1];
|
|
69
|
+
}
|
|
70
|
+
const mEmpty = raw.match(/prefix\s*:\s*['"`]['"`]/);
|
|
71
|
+
if (mEmpty) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error(`${colors.red}Failed to read config ${file}: ${err.message}${colors.reset}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function loadResourceCaseFromProjectConfig() {
|
|
85
|
+
const candidates = [
|
|
86
|
+
'nuxid.mjs',
|
|
87
|
+
'nuxid.cjs',
|
|
88
|
+
'nuxid.js',
|
|
89
|
+
'nuxid.ts',
|
|
90
|
+
'nuxid.json',
|
|
91
|
+
'nuxid.config.json',
|
|
92
|
+
'artisan.mjs',
|
|
93
|
+
'artisan.cjs',
|
|
94
|
+
'artisan.js',
|
|
95
|
+
'artisan.ts',
|
|
96
|
+
'artisan.json',
|
|
97
|
+
'artisan.config.json',
|
|
98
|
+
'package.json',
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const file of candidates) {
|
|
102
|
+
const p = path.join(cwd(), file);
|
|
103
|
+
if (!fs.existsSync(p)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (file.endsWith('.json')) {
|
|
109
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
110
|
+
const obj = JSON.parse(raw);
|
|
111
|
+
if (file === 'package.json') {
|
|
112
|
+
if (obj?.nuxid?.resourceCase) {
|
|
113
|
+
return obj.nuxid.resourceCase;
|
|
114
|
+
}
|
|
115
|
+
if (obj?.nuxid?.resource?.case) {
|
|
116
|
+
return obj.nuxid.resource.case;
|
|
117
|
+
}
|
|
118
|
+
if (obj?.artisan?.resourceCase) {
|
|
119
|
+
return obj.artisan.resourceCase;
|
|
120
|
+
}
|
|
121
|
+
if (obj?.artisan?.resource?.case) {
|
|
122
|
+
return obj.artisan.resource.case;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (obj?.resourceCase) {
|
|
126
|
+
return obj.resourceCase;
|
|
127
|
+
}
|
|
128
|
+
if (obj?.resource?.case) {
|
|
129
|
+
return obj.resource.case;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (file.endsWith('.js') || file.endsWith('.mjs') || file.endsWith('.cjs') || file.endsWith('.ts')) {
|
|
134
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
135
|
+
const m = raw.match(/resourceCase\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
136
|
+
if (m && m[1] !== undefined) {
|
|
137
|
+
return m[1];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error(`${colors.red}Failed to read config ${file}: ${err.message}${colors.reset}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toKebabCase(value) {
|
|
150
|
+
return value
|
|
151
|
+
.trim()
|
|
152
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
153
|
+
.replace(/[_\s]+/g, '-')
|
|
154
|
+
.replace(/-+/g, '-')
|
|
155
|
+
.toLowerCase();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function toWords(value) {
|
|
159
|
+
return value
|
|
160
|
+
.trim()
|
|
161
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
162
|
+
.replace(/[_\-\s]+/g, ' ')
|
|
163
|
+
.split(' ')
|
|
164
|
+
.map(word => word.toLowerCase())
|
|
165
|
+
.filter(Boolean);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function capitalize(value) {
|
|
169
|
+
return value ? value[0].toUpperCase() + value.slice(1) : '';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toCase(value, caseStyle) {
|
|
173
|
+
if (caseStyle === 'false') {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const words = toWords(value);
|
|
178
|
+
if (caseStyle === 'snake') {
|
|
179
|
+
return words.join('_');
|
|
180
|
+
}
|
|
181
|
+
if (caseStyle === 'upper-snake') {
|
|
182
|
+
return words.join('_').toUpperCase();
|
|
183
|
+
}
|
|
184
|
+
if (caseStyle === 'pascal') {
|
|
185
|
+
return words.map(capitalize).join('');
|
|
186
|
+
}
|
|
187
|
+
if (caseStyle === 'camel') {
|
|
188
|
+
const [first, ...rest] = words;
|
|
189
|
+
return `${first || ''}${rest.map(capitalize).join('')}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isValidIdentifier(name) {
|
|
196
|
+
return /^[A-Z_$][\w$]*$/i.test(name);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function toSafeIdentifier(name) {
|
|
200
|
+
const candidate = toCase(name, 'camel').replace(/[^\w$]/g, '');
|
|
201
|
+
if (candidate && isValidIdentifier(candidate)) {
|
|
202
|
+
return candidate;
|
|
203
|
+
}
|
|
204
|
+
return `_${candidate || 'module'}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeCaseOption(value) {
|
|
208
|
+
if (value === false) {
|
|
209
|
+
return 'false';
|
|
210
|
+
}
|
|
211
|
+
if (typeof value !== 'string') {
|
|
212
|
+
return 'camel';
|
|
213
|
+
}
|
|
214
|
+
const normalized = value.trim().toLowerCase();
|
|
215
|
+
if (['camel', 'snake', 'pascal', 'upper-snake', 'false'].includes(normalized)) {
|
|
216
|
+
return normalized;
|
|
217
|
+
}
|
|
218
|
+
return 'camel';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getCrudTemplate(moduleName, caseStyle) {
|
|
222
|
+
const useIndexName = toCase('useIndex', caseStyle);
|
|
223
|
+
const useShowName = toCase('useShow', caseStyle);
|
|
224
|
+
return `
|
|
225
|
+
import type { UseFetchOptions } from '#app';
|
|
226
|
+
import type { NitroFetchOptions, NitroFetchRequest } from 'nitropack';
|
|
227
|
+
|
|
228
|
+
export async function index(options: NitroFetchOptions<NitroFetchRequest> = {}) {
|
|
229
|
+
return $fetch('/api/${moduleName}', options);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function show(id: string, options: NitroFetchOptions<NitroFetchRequest> = {}) {
|
|
233
|
+
return $fetch(\`/api/${moduleName}/\${id}\`, options);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function ${useIndexName}<T = any>(options: UseFetchOptions<T> = {}) {
|
|
237
|
+
return useFetch<T>('/api/${moduleName}', options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function ${useShowName}<T = any>(id: string, options: UseFetchOptions<T> = {}) {
|
|
241
|
+
return useFetch<T>(\`/api/${moduleName}/\${id}\`, options);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function store(form: UseHttp, options: NitroFetchOptions<NitroFetchRequest> = {}) {
|
|
245
|
+
return form.post('/api/${moduleName}', options);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function update(form: UseHttp, id: string, options: NitroFetchOptions<NitroFetchRequest> = {}) {
|
|
249
|
+
return form.patch(\`/api/${moduleName}/\${id}\`, options);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function destroy(id: string, options: NitroFetchOptions<NitroFetchRequest> = {}) {
|
|
253
|
+
return $fetch(\`/api/${moduleName}/\${id}\`, { method: 'DELETE', ...options });
|
|
254
|
+
}
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getImmediateDirectories(dir) {
|
|
259
|
+
const results = [];
|
|
260
|
+
try {
|
|
261
|
+
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
262
|
+
for (const dirent of list) {
|
|
263
|
+
if (dirent.isDirectory()) {
|
|
264
|
+
results.push(path.resolve(dir, dirent.name));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.error(`${colors.red}Error reading directory ${dir}: ${error.message}${colors.reset}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function reindexApiModules(baseDir, caseStyle) {
|
|
276
|
+
const modulesDir = path.join(baseDir, 'api', 'modules');
|
|
277
|
+
const indexFile = path.join(baseDir, 'api', 'index.ts');
|
|
278
|
+
const directories = getImmediateDirectories(modulesDir);
|
|
279
|
+
const imports = [];
|
|
280
|
+
const exports = [];
|
|
281
|
+
|
|
282
|
+
directories.forEach((dir) => {
|
|
283
|
+
const folderName = path.basename(dir);
|
|
284
|
+
const exportName = toCase(folderName, caseStyle);
|
|
285
|
+
const safeAlias = isValidIdentifier(exportName) ? exportName : toSafeIdentifier(folderName);
|
|
286
|
+
const modulePath = `./modules/${folderName}/index`;
|
|
287
|
+
|
|
288
|
+
if (safeAlias) {
|
|
289
|
+
imports.push(`import * as ${safeAlias} from '${modulePath}';`);
|
|
290
|
+
if (isValidIdentifier(exportName)) {
|
|
291
|
+
exports.push(` ${exportName},`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
exports.push(` '${exportName}': ${safeAlias},`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const indexContent = `
|
|
300
|
+
${imports.join('\n')}
|
|
301
|
+
|
|
302
|
+
export const api = {
|
|
303
|
+
${exports.join('')}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export type Api = typeof api;`;
|
|
307
|
+
|
|
308
|
+
fs.writeFileSync(indexFile, indexContent.trim(), 'utf8');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function runLinter(filePath) {
|
|
312
|
+
console.log(`\n${colors.dim}Running linter...${colors.reset}`);
|
|
313
|
+
const command = `npx eslint --fix "${filePath}"`;
|
|
314
|
+
|
|
315
|
+
exec(command, (error, _stdout, stderr) => {
|
|
316
|
+
if (error) {
|
|
317
|
+
console.error(`\n${colors.red}Error running linter on ${filePath}: ${error.message}${colors.reset}`);
|
|
318
|
+
if (stderr) {
|
|
319
|
+
console.error(stderr);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
console.log(`${colors.green}✅ Linter finished successfully for: ${colors.dim}${filePath}${colors.reset}`);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function run() {
|
|
329
|
+
const argv = process.argv.slice(2);
|
|
330
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
331
|
+
console.log('\nUsage: pnpm nuxid make:resource [options]\n');
|
|
332
|
+
console.log('Options:');
|
|
333
|
+
console.log(' -a, --app <name> Target app under ./apps/<name>');
|
|
334
|
+
console.log(' -c, --case <style> Case for api index entries (camel, snake, pascal, upper-snake, false)');
|
|
335
|
+
console.log(' -f, --full Generate CRUD helpers');
|
|
336
|
+
console.log(' -p, --prefix <dir> Override the target prefix (default: project\'s config or \'app\')');
|
|
337
|
+
console.log(' -h, --help Show this help message\n');
|
|
338
|
+
console.log('Examples:');
|
|
339
|
+
console.log(' pnpm nuxid make:resource users --full');
|
|
340
|
+
console.log(' pnpm nuxid make:resource --full --app admin\n');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.clear();
|
|
345
|
+
displayHeader('Create New Resource Module');
|
|
346
|
+
|
|
347
|
+
const nameFromArgs = argv.find(arg => !arg.startsWith('-'));
|
|
348
|
+
const rawName = nameFromArgs || await input({
|
|
349
|
+
message: 'What is the name of the resource?',
|
|
350
|
+
validate: value => (value ? true : 'Name is required.'),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const moduleName = toKebabCase(rawName);
|
|
354
|
+
if (!moduleName) {
|
|
355
|
+
console.log(`\n${colors.dim}Invalid name provided. Exiting.${colors.reset}`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let appName;
|
|
360
|
+
let cliPrefix;
|
|
361
|
+
let cliCase;
|
|
362
|
+
for (let i = 0; i < argv.length; i++) {
|
|
363
|
+
const a = argv[i];
|
|
364
|
+
if (a.startsWith('--app=')) {
|
|
365
|
+
appName = a.split('=')[1];
|
|
366
|
+
}
|
|
367
|
+
else if (a === '--app' && argv[i + 1] !== undefined) {
|
|
368
|
+
appName = argv[i + 1];
|
|
369
|
+
}
|
|
370
|
+
else if (a.startsWith('-a=')) {
|
|
371
|
+
appName = a.split('=')[1];
|
|
372
|
+
}
|
|
373
|
+
else if (a === '-a' && argv[i + 1] !== undefined) {
|
|
374
|
+
appName = argv[i + 1];
|
|
375
|
+
}
|
|
376
|
+
else if (a.startsWith('--prefix=')) {
|
|
377
|
+
cliPrefix = a.split('=')[1];
|
|
378
|
+
}
|
|
379
|
+
else if (a === '--prefix' && argv[i + 1] !== undefined) {
|
|
380
|
+
cliPrefix = argv[i + 1];
|
|
381
|
+
}
|
|
382
|
+
else if (a.startsWith('-p=')) {
|
|
383
|
+
cliPrefix = a.split('=')[1];
|
|
384
|
+
}
|
|
385
|
+
else if (a === '-p' && argv[i + 1] !== undefined) {
|
|
386
|
+
cliPrefix = argv[i + 1];
|
|
387
|
+
}
|
|
388
|
+
else if (a.startsWith('--case=')) {
|
|
389
|
+
cliCase = a.split('=')[1];
|
|
390
|
+
}
|
|
391
|
+
else if (a === '--case' && argv[i + 1] !== undefined) {
|
|
392
|
+
cliCase = argv[i + 1];
|
|
393
|
+
}
|
|
394
|
+
else if (a.startsWith('-c=')) {
|
|
395
|
+
cliCase = a.split('=')[1];
|
|
396
|
+
}
|
|
397
|
+
else if (a === '-c' && argv[i + 1] !== undefined) {
|
|
398
|
+
cliCase = argv[i + 1];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const isFull = argv.includes('--full') || argv.includes('-f');
|
|
403
|
+
const configuredPrefix = loadPrefixFromProjectConfig();
|
|
404
|
+
const configuredCase = loadResourceCaseFromProjectConfig();
|
|
405
|
+
const prefix = cliPrefix !== undefined
|
|
406
|
+
? cliPrefix
|
|
407
|
+
: (typeof configuredPrefix === 'string' ? configuredPrefix : (appName ? 'app' : ''));
|
|
408
|
+
const caseStyle = normalizeCaseOption(cliCase ?? configuredCase ?? 'camel');
|
|
409
|
+
const baseDir = appName ? path.join(cwd(), 'apps', appName) : cwd();
|
|
410
|
+
const targetRoot = prefix === '' ? baseDir : path.join(baseDir, prefix);
|
|
411
|
+
const targetDir = path.join(targetRoot, 'api', 'modules', moduleName);
|
|
412
|
+
const targetPath = path.join(targetDir, 'index.ts');
|
|
413
|
+
|
|
414
|
+
const shownPrefix = prefix === '' ? '(project root)' : prefix;
|
|
415
|
+
const shownApp = appName ? `apps/${appName}` : '(project root)';
|
|
416
|
+
console.log(`${colors.dim}App: ${colors.cyan}${shownApp}${colors.reset}`);
|
|
417
|
+
console.log(`${colors.dim}Prefix: ${colors.cyan}${shownPrefix}${colors.reset}`);
|
|
418
|
+
console.log(`${colors.dim}Target: ${colors.cyan}${path.relative(cwd(), targetPath) || '.'}${colors.reset}`);
|
|
419
|
+
console.log(`${colors.dim}Mode: ${colors.cyan}${isFull ? 'full (CRUD)' : 'empty'}${colors.reset}`);
|
|
420
|
+
|
|
421
|
+
const writeFileAndLint = () => {
|
|
422
|
+
const content = isFull ? getCrudTemplate(moduleName, caseStyle).trim() : '';
|
|
423
|
+
fs.writeFileSync(targetPath, content, 'utf8');
|
|
424
|
+
console.log(`\n${colors.green}✅ Success! Created/updated module at: ${colors.dim}${path.relative(cwd(), targetPath)}${colors.reset}`);
|
|
425
|
+
reindexApiModules(targetRoot, caseStyle);
|
|
426
|
+
runLinter(targetPath);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (!fs.existsSync(targetDir)) {
|
|
430
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (fs.existsSync(targetPath)) {
|
|
434
|
+
const overwrite = await confirm({
|
|
435
|
+
message: 'index.ts already exists. Overwrite?',
|
|
436
|
+
default: false,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (overwrite) {
|
|
440
|
+
writeFileAndLint();
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
console.log(`\n${colors.dim}Operation cancelled.${colors.reset}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
writeFileAndLint();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
run();
|
package/dist/module.json
CHANGED