langaro-api 1.0.3 → 1.0.5
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/langaro-api.js +151 -45
- package/lib/cli/init.js +4 -0
- package/lib/cli/new.js +181 -34
- package/package.json +2 -2
package/bin/langaro-api.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
-
// Load config from langaro-api.config.js at cwd, or use defaults
|
|
7
6
|
function loadConfig() {
|
|
8
7
|
const configPath = path.resolve(process.cwd(), 'langaro-api.config.js');
|
|
9
8
|
if (fs.existsSync(configPath)) {
|
|
@@ -13,63 +12,170 @@ function loadConfig() {
|
|
|
13
12
|
return {};
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
// ──
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
// ── Interactive menu (arrow keys) ──
|
|
16
|
+
|
|
17
|
+
function singleSelect(question, options) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let cursor = 0;
|
|
20
|
+
// Count how many terminal lines the question occupies (including trailing newlines)
|
|
21
|
+
const questionLines = question.split('\n').length;
|
|
22
|
+
const totalLines = questionLines + options.length;
|
|
23
|
+
|
|
24
|
+
function render() {
|
|
25
|
+
process.stdout.write(`\x1b[${totalLines}A\x1b[J`);
|
|
26
|
+
console.log(question);
|
|
27
|
+
options.forEach((opt, i) => {
|
|
28
|
+
const pointer = i === cursor ? '\x1b[36m> \x1b[0m' : ' ';
|
|
29
|
+
const label = i === cursor ? `\x1b[36m${opt.label}\x1b[0m` : opt.label;
|
|
30
|
+
const desc = opt.desc ? `\x1b[90m — ${opt.desc}\x1b[0m` : '';
|
|
31
|
+
console.log(`${pointer}${label}${desc}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(question);
|
|
36
|
+
options.forEach((opt, i) => {
|
|
37
|
+
const pointer = i === cursor ? '\x1b[36m> \x1b[0m' : ' ';
|
|
38
|
+
const label = i === cursor ? `\x1b[36m${opt.label}\x1b[0m` : opt.label;
|
|
39
|
+
const desc = opt.desc ? `\x1b[90m — ${opt.desc}\x1b[0m` : '';
|
|
40
|
+
console.log(`${pointer}${label}${desc}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
process.stdin.setRawMode(true);
|
|
44
|
+
process.stdin.resume();
|
|
45
|
+
|
|
46
|
+
const onKeypress = (key) => {
|
|
47
|
+
if (key[0] === 27 && key[1] === 91 && key[2] === 65) {
|
|
48
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
49
|
+
render();
|
|
50
|
+
} else if (key[0] === 27 && key[1] === 91 && key[2] === 66) {
|
|
51
|
+
cursor = (cursor + 1) % options.length;
|
|
52
|
+
render();
|
|
53
|
+
} else if (key[0] === 13 || key[0] === 10) {
|
|
54
|
+
process.stdin.setRawMode(false);
|
|
55
|
+
process.stdin.removeListener('data', onKeypress);
|
|
56
|
+
resolve(options[cursor].value);
|
|
57
|
+
} else if (key[0] === 113 || key[0] === 3) {
|
|
58
|
+
process.stdin.setRawMode(false);
|
|
59
|
+
process.stdin.removeListener('data', onKeypress);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
process.stdin.on('data', onKeypress);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function confirm(question) {
|
|
69
|
+
return singleSelect(question, [
|
|
70
|
+
{ label: 'Yes, proceed' , value: true },
|
|
71
|
+
{ label: 'No, cancel', value: false },
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Command runners ──
|
|
76
|
+
|
|
77
|
+
function runGenerateTypes() {
|
|
78
|
+
const { generateTypes } = require('../lib/index');
|
|
31
79
|
const config = loadConfig();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
80
|
+
generateTypes(config);
|
|
81
|
+
console.log('[langaro-api] Types + JSDoc annotations generated.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function runWatch() {
|
|
35
85
|
const { generateTypes, getWatchDirs } = require('../lib/index');
|
|
36
86
|
|
|
37
|
-
function
|
|
87
|
+
function generate() {
|
|
38
88
|
const config = loadConfig();
|
|
39
89
|
generateTypes(config);
|
|
40
90
|
console.log('[langaro-api] Types + JSDoc annotations generated.');
|
|
41
91
|
}
|
|
42
92
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Watch mode
|
|
46
|
-
if (process.argv.includes('--watch')) {
|
|
47
|
-
const config = loadConfig();
|
|
48
|
-
const dirs = getWatchDirs(config);
|
|
93
|
+
generate();
|
|
49
94
|
|
|
50
|
-
|
|
95
|
+
const config = loadConfig();
|
|
96
|
+
const dirs = getWatchDirs(config);
|
|
97
|
+
let debounceTimer = null;
|
|
51
98
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
99
|
+
dirs.forEach((dir) => {
|
|
100
|
+
try {
|
|
101
|
+
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
102
|
+
if (filename && filename.endsWith('.js')) {
|
|
103
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
104
|
+
debounceTimer = setTimeout(() => {
|
|
105
|
+
try { generate(); } catch (err) { console.error('[langaro-api] Error:', err.message); }
|
|
106
|
+
}, 300);
|
|
59
107
|
}
|
|
60
|
-
}
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.warn(`[langaro-api] Could not watch ${dir}: ${err.message}`);
|
|
61
111
|
}
|
|
112
|
+
});
|
|
62
113
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
114
|
+
console.log(`[langaro-api] Watching ${dirs.length} directories for changes...`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function runInit() {
|
|
118
|
+
const ok = await confirm('\n\x1b[33mThis will create project files in the current directory.\x1b[0m Are you sure?\n');
|
|
119
|
+
if (!ok) { console.log('\nAborted.\n'); process.exit(0); }
|
|
120
|
+
const init = require('../lib/cli/init');
|
|
121
|
+
await init();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runMigrate() {
|
|
125
|
+
const ok = await confirm('\n\x1b[33mThis will replace your index.js files with langaro-api loaders.\x1b[0m Are you sure?\n');
|
|
126
|
+
if (!ok) { console.log('\nAborted.\n'); process.exit(0); }
|
|
127
|
+
const migrate = require('../lib/cli/migrate');
|
|
128
|
+
migrate();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runNew() {
|
|
132
|
+
const newCmd = require('../lib/cli/new');
|
|
133
|
+
const config = loadConfig();
|
|
134
|
+
await newCmd(config);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Main ──
|
|
72
138
|
|
|
73
|
-
|
|
139
|
+
async function main() {
|
|
140
|
+
const command = process.argv[2];
|
|
141
|
+
|
|
142
|
+
// Direct commands (for scripts, CI, --watch)
|
|
143
|
+
if (command === 'init') return runInit();
|
|
144
|
+
if (command === 'migrate') return runMigrate();
|
|
145
|
+
if (command === 'new') return runNew();
|
|
146
|
+
if (command === '--watch') return runWatch();
|
|
147
|
+
if (command === 'generate' || command === 'types') {
|
|
148
|
+
runGenerateTypes();
|
|
149
|
+
return process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// No args + non-TTY (e.g. piped, CI): just generate types
|
|
153
|
+
if (!process.stdin.isTTY) {
|
|
154
|
+
runGenerateTypes();
|
|
155
|
+
return process.exit(0);
|
|
74
156
|
}
|
|
157
|
+
|
|
158
|
+
// No args + TTY: interactive menu
|
|
159
|
+
console.log('\n\x1b[36m langaro-api\x1b[0m\n');
|
|
160
|
+
|
|
161
|
+
const choice = await singleSelect(' What would you like to do?\n', [
|
|
162
|
+
{ label: 'Generate types', value: 'types', desc: 'Generate .d.ts files and inject JSDoc annotations' },
|
|
163
|
+
{ label: 'New resource', value: 'new', desc: 'Scaffold a new controller, service, model, or route' },
|
|
164
|
+
{ label: 'Migrate project', value: 'migrate', desc: 'Replace index.js files with langaro-api loaders' },
|
|
165
|
+
{ label: 'Init new project', value: 'init', desc: 'Scaffold a full project from scratch' },
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
console.log('');
|
|
169
|
+
|
|
170
|
+
if (choice === 'types') { runGenerateTypes(); process.exit(0); }
|
|
171
|
+
if (choice === 'new') return runNew();
|
|
172
|
+
if (choice === 'migrate') return runMigrate();
|
|
173
|
+
if (choice === 'init') return runInit();
|
|
174
|
+
|
|
175
|
+
return undefined;
|
|
75
176
|
}
|
|
177
|
+
|
|
178
|
+
main().catch((err) => {
|
|
179
|
+
console.error(err);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
package/lib/cli/init.js
CHANGED
|
@@ -15,6 +15,10 @@ function ask(rl, question, defaultVal) {
|
|
|
15
15
|
|
|
16
16
|
function writeFile(root, filePath, content) {
|
|
17
17
|
const full = path.join(root, filePath);
|
|
18
|
+
if (fs.existsSync(full)) {
|
|
19
|
+
console.log(` \x1b[33mskip\x1b[0m ${filePath} (already exists)`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
19
23
|
fs.writeFileSync(full, content);
|
|
20
24
|
console.log(` \x1b[32mcreated\x1b[0m ${filePath}`);
|
package/lib/cli/new.js
CHANGED
|
@@ -83,59 +83,184 @@ function ask(rl, question) {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function
|
|
86
|
+
function singleSelect(question, allOptions) {
|
|
87
87
|
return new Promise((resolve) => {
|
|
88
|
-
const
|
|
88
|
+
const searchable = allOptions.length > 10;
|
|
89
|
+
let filter = '';
|
|
90
|
+
let filtered = allOptions;
|
|
89
91
|
let cursor = 0;
|
|
92
|
+
let lastRenderedLines = 0;
|
|
93
|
+
|
|
94
|
+
// Compute how many items we can show based on terminal height
|
|
95
|
+
// Reserve lines for: question + filter bar + "more above" + "more below" + 1 buffer
|
|
96
|
+
function getMaxVisible() {
|
|
97
|
+
const termRows = process.stdout.rows || 24;
|
|
98
|
+
const overhead = 1 + (searchable ? 1 : 0) + 2 + 1; // question + filter + above/below + buffer
|
|
99
|
+
return Math.max(3, Math.min(10, termRows - overhead));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getVisible() {
|
|
103
|
+
const maxVisible = getMaxVisible();
|
|
104
|
+
if (filtered.length <= maxVisible) return { items: filtered, startIdx: 0 };
|
|
105
|
+
let start = cursor - Math.floor(maxVisible / 2);
|
|
106
|
+
if (start < 0) start = 0;
|
|
107
|
+
if (start + maxVisible > filtered.length) start = filtered.length - maxVisible;
|
|
108
|
+
return { items: filtered.slice(start, start + maxVisible), startIdx: start };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function draw() {
|
|
112
|
+
if (lastRenderedLines > 0) {
|
|
113
|
+
// Cap move-up to terminal height to avoid glitches when output scrolled off screen
|
|
114
|
+
const moveUp = Math.min(lastRenderedLines, (process.stdout.rows || 24) - 1);
|
|
115
|
+
process.stdout.write(`\x1b[${moveUp}A\x1b[J`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const lines = [];
|
|
119
|
+
lines.push(question.trimEnd());
|
|
120
|
+
|
|
121
|
+
if (searchable) {
|
|
122
|
+
lines.push(` \x1b[90mType to filter:\x1b[0m ${filter}\x1b[90m|\x1b[0m`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { items, startIdx } = getVisible();
|
|
126
|
+
|
|
127
|
+
if (startIdx > 0) lines.push(` \x1b[90m ... ${startIdx} more above\x1b[0m`);
|
|
128
|
+
|
|
129
|
+
items.forEach((opt, i) => {
|
|
130
|
+
const realIdx = startIdx + i;
|
|
131
|
+
const pointer = realIdx === cursor ? '\x1b[36m> \x1b[0m' : ' ';
|
|
132
|
+
const label = realIdx === cursor ? `\x1b[36m${opt.label}\x1b[0m` : opt.label;
|
|
133
|
+
const desc = opt.desc ? `\x1b[90m — ${opt.desc}\x1b[0m` : '';
|
|
134
|
+
lines.push(`${pointer}${label}${desc}`);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const endIdx = startIdx + items.length;
|
|
138
|
+
if (endIdx < filtered.length) lines.push(` \x1b[90m ... ${filtered.length - endIdx} more below\x1b[0m`);
|
|
139
|
+
|
|
140
|
+
if (filtered.length === 0) lines.push(' \x1b[33mNo matches\x1b[0m');
|
|
90
141
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
142
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
143
|
+
lastRenderedLines = lines.length;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
draw();
|
|
147
|
+
|
|
148
|
+
process.stdin.setRawMode(true);
|
|
149
|
+
process.stdin.resume();
|
|
150
|
+
|
|
151
|
+
const onKeypress = (key) => {
|
|
152
|
+
if (key[0] === 27 && key[1] === 91 && key[2] === 65) {
|
|
153
|
+
if (filtered.length > 0) cursor = (cursor - 1 + filtered.length) % filtered.length;
|
|
154
|
+
draw();
|
|
155
|
+
} else if (key[0] === 27 && key[1] === 91 && key[2] === 66) {
|
|
156
|
+
if (filtered.length > 0) cursor = (cursor + 1) % filtered.length;
|
|
157
|
+
draw();
|
|
158
|
+
} else if (key[0] === 13 || key[0] === 10) {
|
|
159
|
+
process.stdin.setRawMode(false);
|
|
160
|
+
process.stdin.removeListener('data', onKeypress);
|
|
161
|
+
if (filtered.length > 0) resolve(filtered[cursor].value);
|
|
162
|
+
else process.exit(0);
|
|
163
|
+
} else if (key[0] === 3 || (!searchable && key[0] === 113)) {
|
|
164
|
+
process.stdin.setRawMode(false);
|
|
165
|
+
process.stdin.removeListener('data', onKeypress);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
} else if (searchable && (key[0] === 127 || key[0] === 8)) {
|
|
168
|
+
filter = filter.slice(0, -1);
|
|
169
|
+
filtered = allOptions.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase()));
|
|
170
|
+
cursor = 0;
|
|
171
|
+
draw();
|
|
172
|
+
} else if (searchable && key[0] >= 32 && key[0] < 127 && key.length === 1) {
|
|
173
|
+
filter += String.fromCharCode(key[0]);
|
|
174
|
+
filtered = allOptions.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase()));
|
|
175
|
+
cursor = 0;
|
|
176
|
+
draw();
|
|
95
177
|
}
|
|
178
|
+
};
|
|
96
179
|
|
|
97
|
-
|
|
180
|
+
process.stdin.on('data', onKeypress);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function fetchTablesFromDatabase() {
|
|
185
|
+
try {
|
|
186
|
+
// Resolve modules from the project's node_modules, not langaro-api's
|
|
187
|
+
const projectRoot = process.cwd();
|
|
188
|
+
const resolveFrom = (mod) => require(require.resolve(mod, { paths: [projectRoot] }));
|
|
189
|
+
|
|
190
|
+
resolveFrom('dotenv').config({ path: path.join(projectRoot, '.env') });
|
|
191
|
+
|
|
192
|
+
const {
|
|
193
|
+
DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
|
|
194
|
+
} = process.env;
|
|
195
|
+
|
|
196
|
+
if (!DB_NAME) {
|
|
197
|
+
console.log('\x1b[33m Could not connect to database. DB_NAME not found in .env\x1b[0m');
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const knex = resolveFrom('knex')({
|
|
202
|
+
client: 'mysql2',
|
|
203
|
+
connection: {
|
|
204
|
+
host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME, port: DB_PORT,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const results = await knex.raw(
|
|
209
|
+
'SELECT table_name FROM information_schema.tables WHERE table_schema = ?',
|
|
210
|
+
[DB_NAME],
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const tables = results[0].map((row) => row.TABLE_NAME).sort();
|
|
214
|
+
await knex.destroy();
|
|
215
|
+
return tables;
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.log(`\x1b[33m Could not connect to database: ${err.message}\x1b[0m`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function multiSelect(rl, question, options) {
|
|
223
|
+
return new Promise((resolve) => {
|
|
224
|
+
const selected = new Set(options.map((_, i) => i));
|
|
225
|
+
let cursor = 0;
|
|
226
|
+
let lastRenderedLines = 0;
|
|
227
|
+
|
|
228
|
+
function draw() {
|
|
229
|
+
if (lastRenderedLines > 0) {
|
|
230
|
+
const moveUp = Math.min(lastRenderedLines, (process.stdout.rows || 24) - 1);
|
|
231
|
+
process.stdout.write(`\x1b[${moveUp}A\x1b[J`);
|
|
232
|
+
}
|
|
233
|
+
const lines = [];
|
|
234
|
+
lines.push(question);
|
|
98
235
|
options.forEach((opt, i) => {
|
|
99
236
|
const check = selected.has(i) ? '\x1b[32m[x]\x1b[0m' : '[ ]';
|
|
100
237
|
const pointer = i === cursor ? '\x1b[36m> \x1b[0m' : ' ';
|
|
101
|
-
|
|
238
|
+
lines.push(`${pointer}${check} ${opt}`);
|
|
102
239
|
});
|
|
240
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
241
|
+
lastRenderedLines = lines.length;
|
|
103
242
|
}
|
|
104
243
|
|
|
105
|
-
|
|
106
|
-
console.log(question);
|
|
107
|
-
options.forEach((opt, i) => {
|
|
108
|
-
const check = selected.has(i) ? '\x1b[32m[x]\x1b[0m' : '[ ]';
|
|
109
|
-
const pointer = i === cursor ? '\x1b[36m> \x1b[0m' : ' ';
|
|
110
|
-
console.log(`${pointer}${check} ${opt}`);
|
|
111
|
-
});
|
|
244
|
+
draw();
|
|
112
245
|
|
|
113
246
|
process.stdin.setRawMode(true);
|
|
114
247
|
process.stdin.resume();
|
|
115
248
|
|
|
116
249
|
const onKeypress = (key) => {
|
|
117
|
-
// Up arrow
|
|
118
250
|
if (key[0] === 27 && key[1] === 91 && key[2] === 65) {
|
|
119
251
|
cursor = (cursor - 1 + options.length) % options.length;
|
|
120
|
-
|
|
121
|
-
// Down arrow
|
|
252
|
+
draw();
|
|
122
253
|
} else if (key[0] === 27 && key[1] === 91 && key[2] === 66) {
|
|
123
254
|
cursor = (cursor + 1) % options.length;
|
|
124
|
-
|
|
125
|
-
// Space — toggle selection
|
|
255
|
+
draw();
|
|
126
256
|
} else if (key[0] === 32) {
|
|
127
|
-
if (selected.has(cursor))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
selected.add(cursor);
|
|
131
|
-
}
|
|
132
|
-
render();
|
|
133
|
-
// Enter — confirm
|
|
257
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
258
|
+
else selected.add(cursor);
|
|
259
|
+
draw();
|
|
134
260
|
} else if (key[0] === 13 || key[0] === 10) {
|
|
135
261
|
process.stdin.setRawMode(false);
|
|
136
262
|
process.stdin.removeListener('data', onKeypress);
|
|
137
263
|
resolve(options.filter((_, i) => selected.has(i)));
|
|
138
|
-
// q or Ctrl+C — abort
|
|
139
264
|
} else if (key[0] === 113 || key[0] === 3) {
|
|
140
265
|
process.stdin.setRawMode(false);
|
|
141
266
|
process.stdin.removeListener('data', onKeypress);
|
|
@@ -152,24 +277,46 @@ async function run(config = {}) {
|
|
|
152
277
|
const root = cfg.root || process.cwd();
|
|
153
278
|
const resolve = (dir) => path.resolve(root, dir);
|
|
154
279
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
280
|
+
// Step 1: Choose how to pick the resource name
|
|
281
|
+
console.log('');
|
|
282
|
+
const source = await singleSelect('\x1b[36mHow to pick the resource name?\x1b[0m', [
|
|
283
|
+
{ label: 'Enter manually', value: 'manual', desc: 'Type a snake_case name' },
|
|
284
|
+
{ label: 'Fetch from database', value: 'db', desc: 'Select a table from your database' },
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
let name;
|
|
288
|
+
|
|
289
|
+
if (source === 'db') {
|
|
290
|
+
console.log('\n Connecting to database...');
|
|
291
|
+
const tables = await fetchTablesFromDatabase();
|
|
292
|
+
|
|
293
|
+
if (!tables || tables.length === 0) {
|
|
294
|
+
console.log('\x1b[31m No tables found.\x1b[0m Falling back to manual input.\n');
|
|
295
|
+
const rl = createInterface();
|
|
296
|
+
name = await ask(rl, '\x1b[36mResource name\x1b[0m (snake_case, e.g. products, user_invoices): ');
|
|
297
|
+
rl.close();
|
|
298
|
+
} else {
|
|
299
|
+
console.log('');
|
|
300
|
+
const tableOptions = tables.map((t) => ({ label: t, value: t }));
|
|
301
|
+
name = await singleSelect(` Found ${tables.length} tables. Select one:`, tableOptions);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
const rl = createInterface();
|
|
305
|
+
name = await ask(rl, '\n\x1b[36mResource name\x1b[0m (snake_case, e.g. products, user_invoices): ');
|
|
306
|
+
rl.close();
|
|
307
|
+
}
|
|
158
308
|
|
|
159
309
|
if (!name) {
|
|
160
310
|
console.log('No name provided. Aborting.');
|
|
161
|
-
rl.close();
|
|
162
311
|
process.exit(0);
|
|
163
312
|
}
|
|
164
313
|
|
|
165
314
|
// Validate name
|
|
166
315
|
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
167
316
|
console.log('\x1b[31mInvalid name.\x1b[0m Use snake_case (e.g. products, user_invoices)');
|
|
168
|
-
rl.close();
|
|
169
317
|
process.exit(1);
|
|
170
318
|
}
|
|
171
319
|
|
|
172
|
-
rl.close();
|
|
173
320
|
console.log('');
|
|
174
321
|
|
|
175
322
|
const choices = await multiSelect(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langaro-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Auto-generate TypeScript types, JSDoc annotations, and boilerplate loaders for knex-extended-crud projects",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"boilerplate"
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"knex-extended-crud": ">=2.0.
|
|
24
|
+
"knex-extended-crud": ">=2.0.31",
|
|
25
25
|
"cron": ">=3.0.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependenciesMeta": {
|