@octanejs/cli 0.0.1 → 0.0.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/package.json +1 -1
- package/src/commands/create.js +206 -0
- package/src/commands/init/index.js +282 -91
- package/src/commands/init/templates.js +153 -32
- package/src/data/octane-data.json +201 -50
- package/src/kernel/command.js +3 -0
- package/src/kernel/install.js +7 -0
- package/src/kernel/main.js +14 -0
- package/src/kernel/registry.js +5 -0
- package/src/kernel/ui.js +36 -0
package/src/kernel/command.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
* @property {CommandEntry[]} [subcommands]
|
|
19
19
|
* @property {boolean} [requiresProject] refuse to run outside a package.json,
|
|
20
20
|
* for commands that write into one
|
|
21
|
+
* @property {boolean} [passthrough] read the tokens after a bare `--` as
|
|
22
|
+
* `input.rest`. Without it they are a usage error rather than a silent drop,
|
|
23
|
+
* on the same reasoning as an unknown flag
|
|
21
24
|
* @property {(ctx: import('./context.js').Ctx, input: CommandInput) => Promise<CommandResult>} [run]
|
|
22
25
|
*/
|
|
23
26
|
|
package/src/kernel/install.js
CHANGED
|
@@ -8,6 +8,13 @@ const MANAGERS = {
|
|
|
8
8
|
bun: { add: ['add'], dev: '-d' },
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The managers this CLI knows how to drive. Derived rather than written out, so
|
|
13
|
+
* that a flag can never offer a manager `installCommand` would silently answer
|
|
14
|
+
* with npm's spelling.
|
|
15
|
+
*/
|
|
16
|
+
export const PACKAGE_MANAGERS = Object.keys(MANAGERS);
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* @param {import('./project.js').Project} project
|
|
13
20
|
* @param {string[]} names
|
package/src/kernel/main.js
CHANGED
|
@@ -109,6 +109,20 @@ async function dispatch(argv, options) {
|
|
|
109
109
|
return EXIT.OK;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// A command that has not asked for `rest` would drop these on the floor. That
|
|
113
|
+
// is the same failure the parser refuses for an unknown flag, and it arrives
|
|
114
|
+
// here by an ordinary route: npm eats the `--` and forwards what follows, so
|
|
115
|
+
// `npm create octane app -- --template spa` is correct and the identical line
|
|
116
|
+
// under pnpm or yarn hands the `--` straight through. Dropped silently, the
|
|
117
|
+
// run then fails saying --template is required, naming the one flag that was
|
|
118
|
+
// in fact supplied.
|
|
119
|
+
if (parsed.rest.length > 0 && !module.passthrough) {
|
|
120
|
+
throw usageError(
|
|
121
|
+
`Unexpected arguments after --: ${parsed.rest.join(' ')}`,
|
|
122
|
+
'npm needs `--` to forward flags. pnpm and yarn forward them already, so drop it there.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
// Commands that write into a project need one to exist. Without this they
|
|
113
127
|
// fail deep inside an fs call with a raw ENOENT stack.
|
|
114
128
|
if (module.requiresProject && ctx.project().manifestPath === null) {
|
package/src/kernel/registry.js
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* @type {import('./command.js').CommandEntry[]}
|
|
10
10
|
*/
|
|
11
11
|
export const COMMANDS = [
|
|
12
|
+
{
|
|
13
|
+
name: 'create',
|
|
14
|
+
summary: 'Create an Octane app in a new directory.',
|
|
15
|
+
load: () => import('../commands/create.js'),
|
|
16
|
+
},
|
|
12
17
|
{
|
|
13
18
|
name: 'init',
|
|
14
19
|
summary: 'Wire Octane into the project in this directory.',
|
package/src/kernel/ui.js
CHANGED
|
@@ -60,6 +60,20 @@ export function createUi({ mode, yes = false, stdout = process.stdout }) {
|
|
|
60
60
|
);
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Whether `--yes` already answers this question.
|
|
65
|
+
*
|
|
66
|
+
* It means "stop asking me", which is as true on a terminal as it is in a
|
|
67
|
+
* pipe. Consulting it only once the CLI had decided nobody was watching made
|
|
68
|
+
* the flag do nothing in the one place people type it by hand, and left a
|
|
69
|
+
* caller wanting an unattended run no choice but to claim there was no
|
|
70
|
+
* terminal, which costs the interactive rendering with it. A question with no
|
|
71
|
+
* default has nothing to answer with, so it is still asked.
|
|
72
|
+
*
|
|
73
|
+
* @param {unknown} initial
|
|
74
|
+
*/
|
|
75
|
+
const answered = (initial) => yes && initial !== undefined;
|
|
76
|
+
|
|
63
77
|
/** @param {unknown} value */
|
|
64
78
|
const guard = (value) => {
|
|
65
79
|
if (clack.isCancel(value)) throw new CliError('Cancelled.');
|
|
@@ -124,15 +138,36 @@ export function createUi({ mode, yes = false, stdout = process.stdout }) {
|
|
|
124
138
|
* @returns {Promise<string>}
|
|
125
139
|
*/
|
|
126
140
|
async select({ message, flag, options, initial }) {
|
|
141
|
+
if (answered(initial)) return String(initial);
|
|
127
142
|
if (mode !== 'interactive') return String(unattended(flag, initial));
|
|
128
143
|
return String(guard(await clack.select({ message, options, initialValue: initial })));
|
|
129
144
|
},
|
|
130
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A free-text answer. `initial` is what an empty submission means, not
|
|
148
|
+
* pre-filled input: someone accepting the offer by pressing enter and
|
|
149
|
+
* someone typing it out should land in the same place.
|
|
150
|
+
*
|
|
151
|
+
* @param {{ message: string, flag: string, placeholder?: string, initial?: string }} options
|
|
152
|
+
* @returns {Promise<string>}
|
|
153
|
+
*/
|
|
154
|
+
async text({ message, flag, placeholder, initial }) {
|
|
155
|
+
if (answered(initial)) return String(initial);
|
|
156
|
+
if (mode !== 'interactive') return String(unattended(flag, initial));
|
|
157
|
+
const answer = String(
|
|
158
|
+
guard(await clack.text({ message, placeholder, defaultValue: initial })),
|
|
159
|
+
);
|
|
160
|
+
return answer.trim() === '' && initial !== undefined ? initial : answer.trim();
|
|
161
|
+
},
|
|
162
|
+
|
|
131
163
|
/**
|
|
132
164
|
* @param {{ message: string, flag: string, options: Choice[], initial?: string[] }} options
|
|
133
165
|
* @returns {Promise<string[]>}
|
|
134
166
|
*/
|
|
135
167
|
async multiselect({ message, flag, options, initial }) {
|
|
168
|
+
// `answered` is a boolean, not a type guard, and this is the one prompt
|
|
169
|
+
// that hands its default straight back rather than coercing it.
|
|
170
|
+
if (answered(initial)) return /** @type {string[]} */ (initial);
|
|
136
171
|
if (mode !== 'interactive') {
|
|
137
172
|
return /** @type {string[]} */ (unattended(flag, initial));
|
|
138
173
|
}
|
|
@@ -146,6 +181,7 @@ export function createUi({ mode, yes = false, stdout = process.stdout }) {
|
|
|
146
181
|
* @returns {Promise<boolean>}
|
|
147
182
|
*/
|
|
148
183
|
async confirm({ message, flag, initial = true }) {
|
|
184
|
+
if (answered(initial)) return Boolean(initial);
|
|
149
185
|
if (mode !== 'interactive') return Boolean(unattended(flag, initial));
|
|
150
186
|
return Boolean(guard(await clack.confirm({ message, initialValue: initial })));
|
|
151
187
|
},
|