@octanejs/cli 0.0.0 → 0.0.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.
@@ -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
@@ -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
  },