create-basalt 1.2.0 → 1.2.2
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/README.md +6 -0
- package/dist/{chunk-YJ7RXFTU.js → chunk-XUW6ZAPV.js} +2 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +127 -119
- package/dist/index.d.ts +10 -114
- package/dist/index.js +58 -24
- package/dist/prompt.d.ts +57 -0
- package/dist/prompt.js +166 -0
- package/dist/templates-ui.d.ts +9 -0
- package/dist/templates-ui.js +684 -0
- package/dist/templates.d.ts +30 -0
- package/dist/templates.js +386 -0
- package/dist/wizard.d.ts +39 -0
- package/dist/wizard.js +95 -0
- package/package.json +8 -9
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { stdin, stdout } from 'node:process';
|
|
4
|
+
/** Thrown when the user aborts (Ctrl+C, or declines the final confirmation). */
|
|
5
|
+
export class WizardCancelledError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super('Cancelled.');
|
|
8
|
+
this.name = 'WizardCancelledError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const c = {
|
|
12
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
13
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
14
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
15
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A rich, dependency-free terminal prompter: arrow-key `select`/`multiselect`
|
|
19
|
+
* (raw mode), validated `text`, and `confirm`. Only usable in a TTY.
|
|
20
|
+
*/
|
|
21
|
+
export function ttyPrompter() {
|
|
22
|
+
return {
|
|
23
|
+
intro: (message) => stdout.write(`\n${c.bold(c.cyan('◆ ' + message))}\n\n`),
|
|
24
|
+
note: (message) => stdout.write(`${message}\n`),
|
|
25
|
+
outro: (message) => stdout.write(`\n${c.green('✓ ' + message)}\n`),
|
|
26
|
+
async text({ message, placeholder, initial, validate }) {
|
|
27
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
28
|
+
rl.on('SIGINT', () => {
|
|
29
|
+
rl.close();
|
|
30
|
+
throw new WizardCancelledError();
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
for (;;) {
|
|
34
|
+
const hint = placeholder ? c.dim(` (${placeholder})`) : '';
|
|
35
|
+
const answer = (await rl.question(`${c.cyan('?')} ${message}${hint}: `)).trim() || initial || '';
|
|
36
|
+
const error = validate?.(answer);
|
|
37
|
+
if (!error)
|
|
38
|
+
return answer;
|
|
39
|
+
stdout.write(` ${c.dim(error)}\n`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code === 'ABORT_ERR')
|
|
44
|
+
throw new WizardCancelledError();
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
rl.close();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
select({ message, choices, initial = 0 }) {
|
|
52
|
+
return keyboardSelect(message, choices, initial, false).then((v) => v[0]);
|
|
53
|
+
},
|
|
54
|
+
multiselect({ message, choices, initial = [] }) {
|
|
55
|
+
const preselected = choices.map((choice) => initial.includes(choice.value));
|
|
56
|
+
return keyboardSelect(message, choices, 0, true, preselected);
|
|
57
|
+
},
|
|
58
|
+
async confirm({ message, initial = false }) {
|
|
59
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
60
|
+
try {
|
|
61
|
+
const answer = (await rl.question(`${c.cyan('?')} ${message} ${c.dim(initial ? '(Y/n)' : '(y/N)')} `))
|
|
62
|
+
.trim()
|
|
63
|
+
.toLowerCase();
|
|
64
|
+
return answer ? answer.startsWith('y') : initial;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
rl.close();
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Shared arrow-key picker for select (returns one value) and multiselect
|
|
74
|
+
* (space toggles, enter confirms — returns the chosen values).
|
|
75
|
+
*/
|
|
76
|
+
function keyboardSelect(message, choices, initialIndex, multi, selected = choices.map(() => false)) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
let index = initialIndex;
|
|
79
|
+
let rendered = 0;
|
|
80
|
+
emitKeypressEvents(stdin);
|
|
81
|
+
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
82
|
+
if (stdin.isTTY)
|
|
83
|
+
stdin.setRawMode(true);
|
|
84
|
+
const render = () => {
|
|
85
|
+
if (rendered > 0)
|
|
86
|
+
stdout.write(`\x1b[${rendered}A\x1b[0J`);
|
|
87
|
+
const lines = [`${c.cyan('?')} ${message}${multi ? c.dim(' (space to toggle, enter to confirm)') : ''}`];
|
|
88
|
+
choices.forEach((choice, i) => {
|
|
89
|
+
const pointer = i === index ? c.cyan('❯') : ' ';
|
|
90
|
+
const box = multi ? (selected[i] ? c.green('◉') : '◯') + ' ' : '';
|
|
91
|
+
const label = i === index ? c.cyan(choice.label) : choice.label;
|
|
92
|
+
const hint = choice.hint ? c.dim(` — ${choice.hint}`) : '';
|
|
93
|
+
lines.push(`${pointer} ${box}${label}${hint}`);
|
|
94
|
+
});
|
|
95
|
+
stdout.write(lines.join('\n') + '\n');
|
|
96
|
+
rendered = lines.length;
|
|
97
|
+
};
|
|
98
|
+
const cleanup = () => {
|
|
99
|
+
stdin.off('keypress', onKey);
|
|
100
|
+
if (stdin.isTTY)
|
|
101
|
+
stdin.setRawMode(wasRaw);
|
|
102
|
+
stdin.pause();
|
|
103
|
+
};
|
|
104
|
+
const onKey = (_str, key) => {
|
|
105
|
+
if (key.ctrl && key.name === 'c') {
|
|
106
|
+
cleanup();
|
|
107
|
+
reject(new WizardCancelledError());
|
|
108
|
+
}
|
|
109
|
+
else if (key.name === 'up' || key.name === 'k') {
|
|
110
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
111
|
+
render();
|
|
112
|
+
}
|
|
113
|
+
else if (key.name === 'down' || key.name === 'j') {
|
|
114
|
+
index = (index + 1) % choices.length;
|
|
115
|
+
render();
|
|
116
|
+
}
|
|
117
|
+
else if (multi && key.name === 'space') {
|
|
118
|
+
selected[index] = !selected[index];
|
|
119
|
+
render();
|
|
120
|
+
}
|
|
121
|
+
else if (key.name === 'return') {
|
|
122
|
+
cleanup();
|
|
123
|
+
resolve(multi
|
|
124
|
+
? choices.filter((_, i) => selected[i]).map((choice) => choice.value)
|
|
125
|
+
: [choices[index].value]);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
stdin.resume();
|
|
129
|
+
stdin.on('keypress', onKey);
|
|
130
|
+
render();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A scripted prompter for tests: each call dequeues the next answer of its kind.
|
|
135
|
+
* `log` captures intro/note/outro lines so a test can assert the summary.
|
|
136
|
+
*/
|
|
137
|
+
export function scriptedPrompter(script) {
|
|
138
|
+
const text = [...(script.text ?? [])];
|
|
139
|
+
const select = [...(script.select ?? [])];
|
|
140
|
+
const multiselect = [...(script.multiselect ?? [])];
|
|
141
|
+
const confirm = [...(script.confirm ?? [])];
|
|
142
|
+
const log = [];
|
|
143
|
+
const next = (queue, kind) => {
|
|
144
|
+
if (queue.length === 0)
|
|
145
|
+
throw new Error(`scriptedPrompter: no more ${kind} answers`);
|
|
146
|
+
return queue.shift();
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
log,
|
|
150
|
+
intro: (m) => log.push(m),
|
|
151
|
+
note: (m) => log.push(m),
|
|
152
|
+
outro: (m) => log.push(m),
|
|
153
|
+
async text() {
|
|
154
|
+
return next(text, 'text');
|
|
155
|
+
},
|
|
156
|
+
async select() {
|
|
157
|
+
return next(select, 'select');
|
|
158
|
+
},
|
|
159
|
+
async multiselect() {
|
|
160
|
+
return next(multiselect, 'multiselect');
|
|
161
|
+
},
|
|
162
|
+
async confirm() {
|
|
163
|
+
return next(confirm, 'confirm');
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProjectOptions } from './templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* The `web/` frontend emitted when `--ui` is passed: a Vite + React app on
|
|
4
|
+
* @basaltkit/admin-shadcn (authentic shadcn/ui components) talking to the API
|
|
5
|
+
* through @basaltkit/sdk. With auth on, it ships a login/register gate and a
|
|
6
|
+
* small dashboard; otherwise a live status page. The Vite dev server proxies
|
|
7
|
+
* `/api` to the backend so there is no CORS to configure.
|
|
8
|
+
*/
|
|
9
|
+
export declare function uiFiles(options: ProjectOptions): Record<string, string>;
|