@stackable-labs/cli-app-extension 1.2.0 → 1.3.0

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.
Files changed (2) hide show
  1. package/dist/index.js +385 -335
  2. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { render } from "ink";
6
6
 
7
7
  // src/App.tsx
8
8
  import { join as join2 } from "path";
9
- import { Box as Box13, Text as Text13, useApp } from "ink";
10
- import { useCallback, useState as useState9 } from "react";
9
+ import { Box as Box12, Text as Text12, useApp } from "ink";
10
+ import { useCallback, useState as useState8 } from "react";
11
11
 
12
12
  // src/components/Confirm.tsx
13
13
  import { Box as Box2, Text as Text2, useInput } from "ink";
@@ -43,8 +43,9 @@ var StepShell = ({ title, hint, children, footer, onBack, backFocused }) => {
43
43
 
44
44
  // src/components/Confirm.tsx
45
45
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
46
- var Confirm = ({ name, extensionPort, previewPort, targets, outputDir, onConfirm, onCancel, onBack }) => {
46
+ var Confirm = ({ name, extensionVersion, extensionPort, previewPort, targets, outputDir, onConfirm, onCancel, onBack }) => {
47
47
  const [backFocused, setBackFocused] = useState(false);
48
+ const [selected, setSelected] = useState("y");
48
49
  useInput((input, key) => {
49
50
  if (key.upArrow && onBack) {
50
51
  setBackFocused(true);
@@ -59,12 +60,21 @@ var Confirm = ({ name, extensionPort, previewPort, targets, outputDir, onConfirm
59
60
  return;
60
61
  }
61
62
  if (backFocused) return;
62
- if (input === "y" || key.return) {
63
- onConfirm();
63
+ if (key.leftArrow || key.rightArrow) {
64
+ setSelected((s) => s === "y" ? "n" : "y");
65
+ return;
66
+ }
67
+ if (input === "y") {
68
+ setSelected("y");
64
69
  return;
65
70
  }
66
71
  if (input === "n") {
67
- onCancel();
72
+ setSelected("n");
73
+ return;
74
+ }
75
+ if (key.return) {
76
+ if (selected === "y") onConfirm();
77
+ else onCancel();
68
78
  }
69
79
  });
70
80
  return /* @__PURE__ */ jsx2(
@@ -75,15 +85,20 @@ var Confirm = ({ name, extensionPort, previewPort, targets, outputDir, onConfirm
75
85
  onBack,
76
86
  backFocused,
77
87
  footer: /* @__PURE__ */ jsxs2(Text2, { children: [
78
- "Proceed? ",
79
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "green", children: "Y" }),
80
- "/",
81
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "red", children: "n" })
88
+ "Proceed?",
89
+ " ",
90
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "green", inverse: selected === "y", children: "Y" }),
91
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "/" }),
92
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "red", inverse: selected === "n", children: "n" })
82
93
  ] }),
83
94
  children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
84
95
  /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
85
96
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Name " }),
86
- /* @__PURE__ */ jsx2(Text2, { children: name })
97
+ /* @__PURE__ */ jsx2(Text2, { dimColor: !!extensionVersion, children: name })
98
+ ] }),
99
+ extensionVersion && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
100
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Version " }),
101
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: extensionVersion })
87
102
  ] }),
88
103
  /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
89
104
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Directory " }),
@@ -109,15 +124,55 @@ var Confirm = ({ name, extensionPort, previewPort, targets, outputDir, onConfirm
109
124
  );
110
125
  };
111
126
 
112
- // src/components/DirPrompt.tsx
113
- import { Box as Box4, Text as Text4 } from "ink";
127
+ // src/components/Done.tsx
128
+ import { Box as Box3, Text as Text3 } from "ink";
129
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
130
+ var Done = ({ name, outputDir }) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
131
+ /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
132
+ /* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: "\u2714" }),
133
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Extension scaffolded successfully!" })
134
+ ] }),
135
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
136
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
137
+ "Created: ",
138
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: name })
139
+ ] }),
140
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
141
+ "Location: ",
142
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: outputDir })
143
+ ] })
144
+ ] }),
145
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, children: [
146
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Next steps:" }),
147
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, gap: 1, children: [
148
+ /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
149
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "1." }),
150
+ /* @__PURE__ */ jsxs3(Text3, { children: [
151
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "cd " }),
152
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: outputDir })
153
+ ] })
154
+ ] }),
155
+ /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
156
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "2." }),
157
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "pnpm install" })
158
+ ] }),
159
+ /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
160
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "3." }),
161
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "pnpm dev" })
162
+ ] })
163
+ ] })
164
+ ] })
165
+ ] });
166
+
167
+ // src/components/NamePrompt.tsx
168
+ import { Box as Box5, Text as Text5 } from "ink";
114
169
  import TextInput from "ink-text-input";
115
170
  import { useState as useState3 } from "react";
116
171
 
117
172
  // src/components/BackableInput.tsx
118
- import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
173
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
119
174
  import { useState as useState2 } from "react";
120
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
175
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
121
176
  var BackableInput = ({ label, hint, onBack, children, error }) => {
122
177
  const [focus, setFocus] = useState2("input");
123
178
  useInput2((_input, key) => {
@@ -133,77 +188,17 @@ var BackableInput = ({ label, hint, onBack, children, error }) => {
133
188
  onBack?.();
134
189
  }
135
190
  });
136
- return /* @__PURE__ */ jsx3(StepShell, { title: label, hint, onBack, backFocused: focus === "back", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
191
+ return /* @__PURE__ */ jsx4(StepShell, { title: label, hint, onBack, backFocused: focus === "back", children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
137
192
  children(focus === "input"),
138
- error && /* @__PURE__ */ jsx3(Text3, { color: "red", children: error })
139
- ] }) });
140
- };
141
-
142
- // src/components/DirPrompt.tsx
143
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
144
- var DirPrompt = ({ defaultDir, onSubmit, onBack }) => {
145
- const [value, setValue] = useState3(defaultDir);
146
- const handleSubmit = (val) => {
147
- const trimmed = val.trim();
148
- if (trimmed.length === 0) {
149
- return;
150
- }
151
- onSubmit(trimmed);
152
- };
153
- return /* @__PURE__ */ jsx4(BackableInput, { label: "Output directory:", hint: "Press Enter to confirm", onBack, children: (isFocused) => /* @__PURE__ */ jsxs4(Box4, { children: [
154
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u2192 " }),
155
- /* @__PURE__ */ jsx4(TextInput, { value, onChange: setValue, onSubmit: handleSubmit, focus: isFocused })
193
+ error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error })
156
194
  ] }) });
157
195
  };
158
196
 
159
- // src/components/Done.tsx
160
- import { Box as Box5, Text as Text5 } from "ink";
161
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
162
- var Done = ({ name, outputDir }) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
163
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
164
- /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2714" }),
165
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Extension scaffolded successfully!" })
166
- ] }),
167
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
168
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
169
- "Created: ",
170
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: name })
171
- ] }),
172
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
173
- "Location: ",
174
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: outputDir })
175
- ] })
176
- ] }),
177
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, children: [
178
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Next steps:" }),
179
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, gap: 1, children: [
180
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
181
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "1." }),
182
- /* @__PURE__ */ jsxs5(Text5, { children: [
183
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "cd " }),
184
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: outputDir })
185
- ] })
186
- ] }),
187
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
188
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "2." }),
189
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "pnpm install" })
190
- ] }),
191
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
192
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "3." }),
193
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "pnpm dev" })
194
- ] })
195
- ] })
196
- ] })
197
- ] });
198
-
199
197
  // src/components/NamePrompt.tsx
200
- import { Box as Box6, Text as Text6 } from "ink";
201
- import TextInput2 from "ink-text-input";
202
- import { useState as useState4 } from "react";
203
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
198
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
204
199
  var NamePrompt = ({ initialValue = "", onSubmit, onBack }) => {
205
- const [value, setValue] = useState4(initialValue);
206
- const [error, setError] = useState4();
200
+ const [value, setValue] = useState3(initialValue);
201
+ const [error, setError] = useState3();
207
202
  const handleSubmit = (val) => {
208
203
  const trimmed = val.trim();
209
204
  if (trimmed.length === 0) {
@@ -213,88 +208,143 @@ var NamePrompt = ({ initialValue = "", onSubmit, onBack }) => {
213
208
  setError(void 0);
214
209
  onSubmit(trimmed);
215
210
  };
216
- return /* @__PURE__ */ jsx6(BackableInput, { label: "What is your Extension name?", hint: "Press Enter to confirm", onBack, error, children: (isFocused) => /* @__PURE__ */ jsxs6(Box6, { children: [
217
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "> " }),
218
- /* @__PURE__ */ jsx6(TextInput2, { value, onChange: setValue, onSubmit: handleSubmit, focus: isFocused })
211
+ return /* @__PURE__ */ jsx5(BackableInput, { label: "What is your Extension name?", hint: "Press Enter to confirm", onBack, error, children: (isFocused) => /* @__PURE__ */ jsxs5(Box5, { children: [
212
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "> " }),
213
+ /* @__PURE__ */ jsx5(TextInput, { value, onChange: setValue, onSubmit: handleSubmit, focus: isFocused })
219
214
  ] }) });
220
215
  };
221
216
 
222
- // src/components/PortsPrompt.tsx
223
- import { Box as Box7, Text as Text7 } from "ink";
224
- import TextInput3 from "ink-text-input";
225
- import { useState as useState5 } from "react";
226
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
217
+ // src/components/SettingsPrompt.tsx
218
+ import { Box as Box6, Text as Text6, useFocus, useFocusManager, useInput as useInput3 } from "ink";
219
+ import TextInput2 from "ink-text-input";
220
+ import { useState as useState4 } from "react";
221
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
227
222
  var DEFAULT_EXTENSION_PORT = 5173;
228
223
  var DEFAULT_PREVIEW_PORT = DEFAULT_EXTENSION_PORT + 1;
229
- var PortsPrompt = ({ onSubmit, onBack }) => {
230
- const [extensionPort, setExtensionPort] = useState5(String(DEFAULT_EXTENSION_PORT));
231
- const [previewPort, setPreviewPort] = useState5(String(DEFAULT_PREVIEW_PORT));
232
- const [step, setStep] = useState5("extension");
233
- const handleExtensionSubmit = (value) => {
234
- const trimmed = value.trim();
235
- const port = trimmed === "" ? DEFAULT_EXTENSION_PORT : parseInt(trimmed, 10);
236
- if (isNaN(port) || port < 1024 || port > 65535) {
237
- return;
224
+ var FieldRow = ({ label, value, onChange, onSubmit, onConfirm, placeholder, autoFocus, isFirst, isLast, onFocusBack }) => {
225
+ const { isFocused } = useFocus({ autoFocus });
226
+ const { focusNext, focusPrevious } = useFocusManager();
227
+ useInput3((_input, key) => {
228
+ if (!isFocused) return;
229
+ if (key.downArrow && !isLast) focusNext();
230
+ if (key.upArrow) {
231
+ if (isFirst && onFocusBack) {
232
+ onFocusBack();
233
+ } else if (!isFirst) {
234
+ focusPrevious();
235
+ }
238
236
  }
239
- setExtensionPort(String(port));
240
- setPreviewPort(String(port + 1));
241
- setStep("preview");
237
+ });
238
+ return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
239
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label }),
240
+ isFocused ? /* @__PURE__ */ jsxs6(Box6, { children: [
241
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2192 " }),
242
+ /* @__PURE__ */ jsx6(
243
+ TextInput2,
244
+ {
245
+ value,
246
+ onChange,
247
+ onSubmit: (val) => {
248
+ const result = onSubmit(val);
249
+ if (result) onConfirm(...result);
250
+ },
251
+ placeholder,
252
+ focus: isFocused
253
+ }
254
+ )
255
+ ] }) : /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: value })
256
+ ] });
257
+ };
258
+ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
259
+ const [backFocused, setBackFocused] = useState4(false);
260
+ const [extensionPort, setExtensionPort] = useState4(String(DEFAULT_EXTENSION_PORT));
261
+ const [previewPort, setPreviewPort] = useState4(String(DEFAULT_PREVIEW_PORT));
262
+ const [outputDir, setOutputDir] = useState4(defaultDir);
263
+ const handleFocusBack = () => {
264
+ if (onBack) setBackFocused(true);
242
265
  };
243
- const handlePreviewSubmit = (value) => {
244
- const extPort = parseInt(extensionPort, 10);
245
- const trimmed = value.trim();
246
- const prevPort = trimmed === "" ? extPort + 1 : parseInt(trimmed, 10);
247
- if (isNaN(prevPort) || prevPort < 1024 || prevPort > 65535) {
266
+ useInput3((_input, key) => {
267
+ if (key.downArrow && backFocused) {
268
+ setBackFocused(false);
248
269
  return;
249
270
  }
250
- onSubmit(extPort, prevPort);
251
- };
252
- const handleBack = () => {
253
- if (step === "preview") {
254
- setStep("extension");
255
- } else {
271
+ if (key.return && backFocused) {
256
272
  onBack?.();
273
+ return;
257
274
  }
275
+ });
276
+ const handleConfirm = (resolvedExtPort, resolvedPrevPort, resolvedDir) => {
277
+ const dir = resolvedDir.trim();
278
+ if (!dir) return;
279
+ onSubmit(parseInt(resolvedExtPort, 10), parseInt(resolvedPrevPort, 10), dir);
258
280
  };
259
- if (step === "extension") {
260
- return /* @__PURE__ */ jsx7(
261
- BackableInput,
262
- {
263
- label: "Extension dev Server port:",
264
- hint: "Press Enter to confirm",
265
- onBack: onBack ? handleBack : void 0,
266
- children: (isFocused) => /* @__PURE__ */ jsxs7(Box7, { children: [
267
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u2192 " }),
268
- /* @__PURE__ */ jsx7(
269
- TextInput3,
270
- {
271
- value: extensionPort,
272
- onChange: setExtensionPort,
273
- onSubmit: handleExtensionSubmit,
274
- placeholder: String(DEFAULT_EXTENSION_PORT),
275
- focus: isFocused
276
- }
277
- )
278
- ] })
279
- }
280
- );
281
- }
282
- return /* @__PURE__ */ jsx7(
283
- BackableInput,
281
+ const handleExtensionPortSubmit = (value) => {
282
+ const trimmed = value.trim();
283
+ const port = trimmed === "" ? DEFAULT_EXTENSION_PORT : parseInt(trimmed, 10);
284
+ if (isNaN(port) || port < 1024 || port > 65535) return false;
285
+ const newExtPort = String(port);
286
+ const newPrevPort = String(port + 1);
287
+ setExtensionPort(newExtPort);
288
+ setPreviewPort(newPrevPort);
289
+ return [newExtPort, newPrevPort, outputDir];
290
+ };
291
+ const handlePreviewPortSubmit = (value) => {
292
+ const extPort = parseInt(extensionPort, 10);
293
+ const trimmed = value.trim();
294
+ const port = trimmed === "" ? extPort + 1 : parseInt(trimmed, 10);
295
+ if (isNaN(port) || port < 1024 || port > 65535) return false;
296
+ const newPrevPort = String(port);
297
+ setPreviewPort(newPrevPort);
298
+ return [extensionPort, newPrevPort, outputDir];
299
+ };
300
+ const handleOutputDirSubmit = (value) => {
301
+ const trimmed = value.trim();
302
+ if (trimmed.length === 0) return false;
303
+ setOutputDir(trimmed);
304
+ return [extensionPort, previewPort, trimmed];
305
+ };
306
+ return /* @__PURE__ */ jsx6(
307
+ StepShell,
284
308
  {
285
- label: "Extension dev Preview port:",
286
- hint: "Press Enter to confirm",
287
- onBack: handleBack,
288
- children: (isFocused) => /* @__PURE__ */ jsxs7(Box7, { children: [
289
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u2192 " }),
290
- /* @__PURE__ */ jsx7(
291
- TextInput3,
309
+ title: "Project settings",
310
+ hint: "\u2191\u2193 or Tab to move between fields, Enter to confirm",
311
+ onBack,
312
+ backFocused,
313
+ children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
314
+ /* @__PURE__ */ jsx6(
315
+ FieldRow,
292
316
  {
317
+ label: "Extension port ",
318
+ value: extensionPort,
319
+ onChange: setExtensionPort,
320
+ onSubmit: handleExtensionPortSubmit,
321
+ onConfirm: handleConfirm,
322
+ placeholder: String(DEFAULT_EXTENSION_PORT),
323
+ autoFocus: true,
324
+ isFirst: true,
325
+ onFocusBack: handleFocusBack
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx6(
329
+ FieldRow,
330
+ {
331
+ label: "Preview port ",
293
332
  value: previewPort,
294
333
  onChange: setPreviewPort,
295
- onSubmit: handlePreviewSubmit,
296
- placeholder: String(DEFAULT_EXTENSION_PORT + 1),
297
- focus: isFocused
334
+ onSubmit: handlePreviewPortSubmit,
335
+ onConfirm: handleConfirm,
336
+ placeholder: String(DEFAULT_PREVIEW_PORT)
337
+ }
338
+ ),
339
+ /* @__PURE__ */ jsx6(
340
+ FieldRow,
341
+ {
342
+ label: "Output directory",
343
+ value: outputDir,
344
+ onChange: setOutputDir,
345
+ onSubmit: handleOutputDirSubmit,
346
+ onConfirm: handleConfirm,
347
+ isLast: true
298
348
  }
299
349
  )
300
350
  ] })
@@ -303,33 +353,33 @@ var PortsPrompt = ({ onSubmit, onBack }) => {
303
353
  };
304
354
 
305
355
  // src/components/ScaffoldProgress.tsx
306
- import { Box as Box8, Text as Text8 } from "ink";
356
+ import { Box as Box7, Text as Text7 } from "ink";
307
357
  import Spinner from "ink-spinner";
308
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
358
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
309
359
  var stepIcon = (status) => {
310
360
  switch (status) {
311
361
  case "running":
312
- return /* @__PURE__ */ jsx8(Spinner, { type: "dots" });
362
+ return /* @__PURE__ */ jsx7(Spinner, { type: "dots" });
313
363
  case "done":
314
- return /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2714" });
364
+ return /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2714" });
315
365
  case "error":
316
- return /* @__PURE__ */ jsx8(Text8, { color: "red", children: "\u2716" });
366
+ return /* @__PURE__ */ jsx7(Text7, { color: "red", children: "\u2716" });
317
367
  default:
318
- return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u25CB" });
368
+ return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u25CB" });
319
369
  }
320
370
  };
321
- var ScaffoldProgress = ({ steps }) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
322
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Scaffolding your Extension\u2026" }),
323
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: steps.map((step) => /* @__PURE__ */ jsxs8(Box8, { gap: 2, children: [
371
+ var ScaffoldProgress = ({ steps }) => /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
372
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Scaffolding your Extension\u2026" }),
373
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: steps.map((step) => /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
324
374
  stepIcon(step.status),
325
- /* @__PURE__ */ jsx8(Text8, { dimColor: step.status === "pending", color: step.status === "running" ? "cyan" : void 0, children: step.label })
375
+ /* @__PURE__ */ jsx7(Text7, { dimColor: step.status === "pending", color: step.status === "running" ? "cyan" : void 0, children: step.label })
326
376
  ] }, step.label)) })
327
377
  ] });
328
378
 
329
379
  // src/components/TargetSelect.tsx
330
- import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
331
- import { useState as useState6 } from "react";
332
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
380
+ import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
381
+ import { useState as useState5 } from "react";
382
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
333
383
  var TARGET_DESCRIPTIONS = {
334
384
  "slot.header": "Renders content in the panel header area",
335
385
  "slot.content": "Renders the main panel body (includes store + navigation state)",
@@ -337,13 +387,13 @@ var TARGET_DESCRIPTIONS = {
337
387
  "slot.footer-links": "Renders a link row in the global footer"
338
388
  };
339
389
  var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
340
- const [cursor, setCursor] = useState6(0);
341
- const [backFocused, setBackFocused] = useState6(false);
342
- const [selected, setSelected] = useState6(
390
+ const [cursor, setCursor] = useState5(0);
391
+ const [backFocused, setBackFocused] = useState5(false);
392
+ const [selected, setSelected] = useState5(
343
393
  new Set(preSelected ?? (availableTargets.includes("slot.content") ? ["slot.content"] : []))
344
394
  );
345
- const [error, setError] = useState6();
346
- useInput3((input, key) => {
395
+ const [error, setError] = useState5();
396
+ useInput4((input, key) => {
347
397
  if (key.upArrow) {
348
398
  if (cursor === 0 && onBack) {
349
399
  setBackFocused(true);
@@ -387,7 +437,7 @@ var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
387
437
  onSubmit([...selected]);
388
438
  }
389
439
  });
390
- return /* @__PURE__ */ jsxs9(
440
+ return /* @__PURE__ */ jsxs8(
391
441
  StepShell,
392
442
  {
393
443
  title: "Select Surface targets/slots",
@@ -395,28 +445,28 @@ var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
395
445
  onBack,
396
446
  backFocused,
397
447
  children: [
398
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", gap: 1, children: availableTargets.map((target, i) => {
448
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", gap: 1, children: availableTargets.map((target, i) => {
399
449
  const isSelected = selected.has(target);
400
450
  const isCursor = i === cursor && !backFocused;
401
- return /* @__PURE__ */ jsxs9(Box9, { gap: 1, children: [
402
- /* @__PURE__ */ jsx9(Text9, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
403
- /* @__PURE__ */ jsx9(Text9, { color: isSelected ? "green" : void 0, children: isSelected ? "\u25C9" : "\u25CB" }),
404
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
405
- /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: target }),
406
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: TARGET_DESCRIPTIONS[target] ?? target })
451
+ return /* @__PURE__ */ jsxs8(Box8, { gap: 1, children: [
452
+ /* @__PURE__ */ jsx8(Text8, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
453
+ /* @__PURE__ */ jsx8(Text8, { color: isSelected ? "green" : void 0, children: isSelected ? "\u25C9" : "\u25CB" }),
454
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
455
+ /* @__PURE__ */ jsx8(Text8, { bold: isSelected, children: target }),
456
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: TARGET_DESCRIPTIONS[target] ?? target })
407
457
  ] })
408
458
  ] }, target);
409
459
  }) }),
410
- error && /* @__PURE__ */ jsx9(Text9, { color: "red", children: error })
460
+ error && /* @__PURE__ */ jsx8(Text8, { color: "red", children: error })
411
461
  ]
412
462
  }
413
463
  );
414
464
  };
415
465
 
416
466
  // src/components/AppSelect.tsx
417
- import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
467
+ import { Box as Box10, Text as Text10, useInput as useInput5 } from "ink";
418
468
  import Spinner2 from "ink-spinner";
419
- import { useEffect, useState as useState7 } from "react";
469
+ import { useEffect, useState as useState6 } from "react";
420
470
 
421
471
  // src/lib/api.ts
422
472
  var DEFAULT_API_URL = "https://api.stackablelabs.io/app-extension/latest";
@@ -454,8 +504,8 @@ var fetchExtensions = async (appId) => {
454
504
  };
455
505
 
456
506
  // src/components/Banner.tsx
457
- import { Box as Box10, Text as Text10 } from "ink";
458
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
507
+ import { Box as Box9, Text as Text9 } from "ink";
508
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
459
509
  var WORDMARK = [
460
510
  " _ _ _ _ ",
461
511
  " ___| |_ __ _ ___| | ____ _| |__ | | ___",
@@ -491,23 +541,23 @@ var gradientColor = (row, col, rows, cols) => {
491
541
  var Banner = () => {
492
542
  const termWidth = process.stdout.columns ?? 80;
493
543
  const maxLen = Math.max(...WORDMARK.map((l) => l.length));
494
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
495
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2500".repeat(termWidth) }),
496
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingX: 1, paddingY: 1, children: WORDMARK.map((line, row) => /* @__PURE__ */ jsx10(Box10, { children: line.split("").map((ch, col) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: ch === " " ? void 0 : gradientColor(row, col, WORDMARK.length, maxLen), children: ch }, col)) }, row)) })
544
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
545
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2500".repeat(termWidth) }),
546
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", paddingX: 1, paddingY: 1, children: WORDMARK.map((line, row) => /* @__PURE__ */ jsx9(Box9, { children: line.split("").map((ch, col) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: ch === " " ? void 0 : gradientColor(row, col, WORDMARK.length, maxLen), children: ch }, col)) }, row)) })
497
547
  ] });
498
548
  };
499
549
 
500
550
  // src/components/AppSelect.tsx
501
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
551
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
502
552
  var AppSelect = ({ onSubmit }) => {
503
- const [apps, setApps] = useState7([]);
504
- const [loading, setLoading] = useState7(true);
505
- const [error, setError] = useState7();
506
- const [cursor, setCursor] = useState7(0);
553
+ const [apps, setApps] = useState6([]);
554
+ const [loading, setLoading] = useState6(true);
555
+ const [error, setError] = useState6();
556
+ const [cursor, setCursor] = useState6(0);
507
557
  useEffect(() => {
508
558
  fetchApps().then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
509
559
  }, []);
510
- useInput4((_, key) => {
560
+ useInput5((_, key) => {
511
561
  if (loading || error || apps.length === 0) return;
512
562
  if (key.upArrow) {
513
563
  setCursor((c) => Math.max(0, c - 1));
@@ -517,60 +567,59 @@ var AppSelect = ({ onSubmit }) => {
517
567
  onSubmit(apps[cursor]);
518
568
  }
519
569
  });
520
- if (loading) {
521
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
522
- /* @__PURE__ */ jsx11(Banner, {}),
523
- /* @__PURE__ */ jsxs11(Box11, { gap: 2, paddingX: 1, children: [
524
- /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
525
- /* @__PURE__ */ jsx11(Text11, { children: "Loading available Apps\u2026" })
526
- ] })
527
- ] });
528
- }
529
- if (error) {
530
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
531
- /* @__PURE__ */ jsx11(Text11, { color: "red", bold: true, children: "Failed to load apps" }),
532
- /* @__PURE__ */ jsx11(Text11, { color: "red", children: error })
533
- ] });
534
- }
535
- if (apps.length === 0) {
536
- return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "No apps available. Contact your administrator." });
537
- }
538
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
539
- /* @__PURE__ */ jsx11(Banner, {}),
540
- /* @__PURE__ */ jsx11(StepShell, { title: "Select the App you are building an Extension for:", children: /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: apps.map((app, i) => {
570
+ const renderContent = () => {
571
+ if (loading) {
572
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 2, children: [
573
+ /* @__PURE__ */ jsx10(Spinner2, { type: "dots" }),
574
+ /* @__PURE__ */ jsx10(Text10, { children: "Loading available Apps\u2026" })
575
+ ] });
576
+ }
577
+ if (error) {
578
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
579
+ /* @__PURE__ */ jsx10(Text10, { color: "red", bold: true, children: "Failed to load Apps" }),
580
+ /* @__PURE__ */ jsx10(Text10, { color: "red", children: error })
581
+ ] });
582
+ }
583
+ if (apps.length === 0) {
584
+ return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "No Apps available. Contact your administrator." });
585
+ }
586
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: apps.map((app, i) => {
541
587
  const isCursor = i === cursor;
542
- return /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
543
- /* @__PURE__ */ jsx11(Text11, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
544
- /* @__PURE__ */ jsx11(Text11, { bold: isCursor, children: app.name }),
545
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
588
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
589
+ /* @__PURE__ */ jsx10(Text10, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
590
+ /* @__PURE__ */ jsx10(Text10, { bold: isCursor, children: app.name }),
591
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
546
592
  "(",
547
593
  app.id,
548
594
  ")"
549
595
  ] })
550
596
  ] }, app.id);
551
- }) }) })
597
+ }) });
598
+ };
599
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
600
+ /* @__PURE__ */ jsx10(Banner, {}),
601
+ /* @__PURE__ */ jsx10(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
552
602
  ] });
553
603
  };
554
604
 
555
605
  // src/components/ExtensionSelect.tsx
556
- import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
606
+ import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
557
607
  import Spinner3 from "ink-spinner";
558
- import { useEffect as useEffect2, useState as useState8 } from "react";
559
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
608
+ import { useEffect as useEffect2, useState as useState7 } from "react";
609
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
560
610
  var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
561
- const [extensions, setExtensions] = useState8([]);
562
- const [loading, setLoading] = useState8(true);
563
- const [error, setError] = useState8();
564
- const [cursor, setCursor] = useState8(0);
565
- const [backFocused, setBackFocused] = useState8(false);
611
+ const [extensions, setExtensions] = useState7([]);
612
+ const [loading, setLoading] = useState7(true);
613
+ const [error, setError] = useState7();
614
+ const [cursor, setCursor] = useState7(0);
615
+ const [backFocused, setBackFocused] = useState7(false);
566
616
  useEffect2(() => {
567
617
  fetchExtensions(appId).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
568
618
  }, [appId]);
569
- useInput5((_, key) => {
570
- if (loading || error || extensions.length === 0) return;
619
+ useInput6((_, key) => {
571
620
  if (key.upArrow) {
572
- if (cursor === 0 && onBack) {
573
- setBackFocused(true);
621
+ if (loading || error || extensions.length === 0 || cursor === 0) {
622
+ if (onBack) setBackFocused(true);
574
623
  } else {
575
624
  setBackFocused(false);
576
625
  setCursor((c) => Math.max(0, c - 1));
@@ -580,7 +629,7 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
580
629
  if (key.downArrow) {
581
630
  if (backFocused) {
582
631
  setBackFocused(false);
583
- } else {
632
+ } else if (!loading && !error && extensions.length > 0) {
584
633
  setCursor((c) => Math.min(extensions.length - 1, c + 1));
585
634
  }
586
635
  return;
@@ -590,36 +639,45 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
590
639
  onBack?.();
591
640
  return;
592
641
  }
593
- onSubmit(extensions[cursor]);
642
+ if (!loading && !error && extensions.length > 0) {
643
+ onSubmit(extensions[cursor]);
644
+ }
594
645
  }
595
646
  });
596
- if (loading) {
597
- return /* @__PURE__ */ jsxs12(Box12, { gap: 2, paddingX: 1, children: [
598
- /* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
599
- /* @__PURE__ */ jsx12(Text12, { children: "Loading Extensions\u2026" })
600
- ] });
601
- }
602
- if (error) {
603
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
604
- /* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "Failed to load Extensions" }),
605
- /* @__PURE__ */ jsx12(Text12, { color: "red", children: error })
606
- ] });
607
- }
608
- if (extensions.length === 0) {
609
- return /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "No Extensions found for this App." });
610
- }
611
- return /* @__PURE__ */ jsx12(StepShell, { title: "Select an existing Extension to scaffold:", onBack, backFocused, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: extensions.map((ext, i) => {
612
- const isCursor = i === cursor && !backFocused;
613
- return /* @__PURE__ */ jsxs12(Box12, { gap: 1, children: [
614
- /* @__PURE__ */ jsx12(Text12, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
615
- /* @__PURE__ */ jsx12(Text12, { bold: isCursor, children: ext.manifest.name }),
616
- /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
617
- "(",
618
- ext.id,
619
- ")"
620
- ] })
621
- ] }, ext.id);
622
- }) }) });
647
+ const renderContent = () => {
648
+ if (loading) {
649
+ return /* @__PURE__ */ jsxs11(Box11, { gap: 2, children: [
650
+ /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }),
651
+ /* @__PURE__ */ jsx11(Text11, { children: "Loading Extensions\u2026" })
652
+ ] });
653
+ }
654
+ if (error) {
655
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
656
+ /* @__PURE__ */ jsx11(Text11, { color: "red", bold: true, children: "Failed to load Extensions" }),
657
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: error })
658
+ ] });
659
+ }
660
+ if (extensions.length === 0) {
661
+ return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "No Extensions found for this App." });
662
+ }
663
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: extensions.map((ext, i) => {
664
+ const isCursor = i === cursor && !backFocused;
665
+ return /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
666
+ /* @__PURE__ */ jsx11(Text11, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
667
+ /* @__PURE__ */ jsx11(Text11, { bold: isCursor, children: ext.manifest.name }),
668
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
669
+ "v",
670
+ ext.manifest.version
671
+ ] }),
672
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
673
+ "(",
674
+ ext.id,
675
+ ")"
676
+ ] })
677
+ ] }, ext.id);
678
+ }) });
679
+ };
680
+ return /* @__PURE__ */ jsx11(StepShell, { title: "Select an existing Extension to scaffold:", onBack, backFocused, children: renderContent() });
623
681
  };
624
682
 
625
683
  // src/constants.ts
@@ -956,20 +1014,24 @@ var scaffold = async (options) => {
956
1014
  };
957
1015
 
958
1016
  // src/App.tsx
959
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
960
- var STEP_ORDER_CREATE = ["app", "name", "targets", "ports", "dir", "confirm"];
961
- var STEP_ORDER_SCAFFOLD = ["app", "extensionSelect", "confirmName", "confirmTargets", "ports", "dir", "confirm"];
962
- var CREATE_STEPS = [
963
- { label: "Registering extension", status: "pending" },
964
- { label: "Fetching template", status: "pending" },
965
- { label: "Generating files", status: "pending" },
966
- { label: "Installing dependencies", status: "pending" }
967
- ];
968
- var SCAFFOLD_STEPS = [
969
- { label: "Fetching template", status: "pending" },
970
- { label: "Generating files", status: "pending" },
971
- { label: "Installing dependencies", status: "pending" }
972
- ];
1017
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1018
+ var STEPS = {
1019
+ ["create" /* CREATE */]: ["app", "name", "targets", "settings", "confirm"],
1020
+ ["scaffold" /* SCAFFOLD */]: ["app", "extensionSelect", "confirmTargets", "settings", "confirm"]
1021
+ };
1022
+ var PROGRESS_STEPS = {
1023
+ ["create" /* CREATE */]: [
1024
+ { label: "Registering extension", status: "pending" },
1025
+ { label: "Fetching template", status: "pending" },
1026
+ { label: "Generating files", status: "pending" },
1027
+ { label: "Installing dependencies", status: "pending" }
1028
+ ],
1029
+ ["scaffold" /* SCAFFOLD */]: [
1030
+ { label: "Fetching template", status: "pending" },
1031
+ { label: "Generating files", status: "pending" },
1032
+ { label: "Installing dependencies", status: "pending" }
1033
+ ]
1034
+ };
973
1035
  var toKebabCase2 = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
974
1036
  var derivePermissions2 = (targets) => {
975
1037
  const permissions = /* @__PURE__ */ new Set();
@@ -988,32 +1050,33 @@ var derivePermissions2 = (targets) => {
988
1050
  }
989
1051
  return [...permissions];
990
1052
  };
991
- var App = ({ mode, initialName, options }) => {
1053
+ var App = ({ command, initialName, options }) => {
992
1054
  const { exit } = useApp();
993
- const [step, setStep] = useState9("app");
994
- const [name, setName] = useState9(initialName ?? "");
995
- const [extensionId, setExtensionId] = useState9("");
996
- const [selectedApp, setSelectedApp] = useState9(null);
997
- const [extensionPort, setExtensionPort] = useState9(
1055
+ const [step, setStep] = useState8("app");
1056
+ const [name, setName] = useState8(initialName ?? "");
1057
+ const [extensionId, setExtensionId] = useState8("");
1058
+ const [extensionVersion, setExtensionVersion] = useState8("");
1059
+ const [selectedApp, setSelectedApp] = useState8(null);
1060
+ const [extensionPort, setExtensionPort] = useState8(
998
1061
  options?.extensionPort ? parseInt(options.extensionPort, 10) : 5173
999
1062
  );
1000
- const [previewPort, setPreviewPort] = useState9(
1063
+ const [previewPort, setPreviewPort] = useState8(
1001
1064
  options?.previewPort ? parseInt(options.previewPort, 10) : 5174
1002
1065
  );
1003
- const [targets, setTargets] = useState9([]);
1004
- const [outputDir, setOutputDir] = useState9("");
1005
- const [progressSteps, setProgressSteps] = useState9(mode === "create" ? CREATE_STEPS : SCAFFOLD_STEPS);
1006
- const [errorMessage, setErrorMessage] = useState9();
1066
+ const [targets, setTargets] = useState8([]);
1067
+ const [outputDir, setOutputDir] = useState8("");
1068
+ const [progressSteps, setProgressSteps] = useState8(PROGRESS_STEPS[command]);
1069
+ const [errorMessage, setErrorMessage] = useState8();
1007
1070
  const updateStep = useCallback((index, status) => {
1008
1071
  setProgressSteps((prev) => prev.map((s, i) => i === index ? { ...s, status } : s));
1009
1072
  }, []);
1010
1073
  const activeSteps = useCallback(() => {
1011
- const base = mode === "create" ? STEP_ORDER_CREATE : STEP_ORDER_SCAFFOLD;
1074
+ const base = STEPS[command];
1012
1075
  const skipped = /* @__PURE__ */ new Set();
1013
- if (mode === "create" && initialName) skipped.add("name");
1014
- if (options?.extensionPort || options?.previewPort) skipped.add("ports");
1076
+ if (command === "create" /* CREATE */ && initialName) skipped.add("name");
1077
+ if (options?.extensionPort || options?.previewPort) skipped.add("settings");
1015
1078
  return base.filter((s) => !skipped.has(s));
1016
- }, [mode, initialName, options?.extensionPort, options?.previewPort]);
1079
+ }, [command, initialName, options?.extensionPort, options?.previewPort]);
1017
1080
  const goBack = useCallback(() => {
1018
1081
  setStep((prev) => {
1019
1082
  const steps = activeSteps();
@@ -1023,7 +1086,7 @@ var App = ({ mode, initialName, options }) => {
1023
1086
  }, [activeSteps]);
1024
1087
  const handleAppSelect = (app) => {
1025
1088
  setSelectedApp(app);
1026
- if (mode === "scaffold") {
1089
+ if (command === "scaffold" /* SCAFFOLD */) {
1027
1090
  setStep("extensionSelect");
1028
1091
  return;
1029
1092
  }
@@ -1032,16 +1095,18 @@ var App = ({ mode, initialName, options }) => {
1032
1095
  const handleExtensionSelect = (ext) => {
1033
1096
  setName(ext.manifest.name);
1034
1097
  setExtensionId(ext.id);
1098
+ setExtensionVersion(ext.manifest.version);
1035
1099
  setTargets(ext.manifest.targets);
1036
- setStep("confirmName");
1037
- };
1038
- const handleConfirmName = (value) => {
1039
- setName(value);
1040
1100
  setStep("confirmTargets");
1041
1101
  };
1042
1102
  const handleConfirmTargets = (value) => {
1043
1103
  setTargets(value);
1044
- setStep(options?.extensionPort || options?.previewPort ? "dir" : "ports");
1104
+ if (options?.extensionPort || options?.previewPort) {
1105
+ setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
1106
+ setStep("confirm");
1107
+ } else {
1108
+ setStep("settings");
1109
+ }
1045
1110
  };
1046
1111
  const handleName = (value) => {
1047
1112
  setName(value);
@@ -1050,24 +1115,26 @@ var App = ({ mode, initialName, options }) => {
1050
1115
  };
1051
1116
  const handleTargets = (value) => {
1052
1117
  setTargets(value);
1053
- setStep(options?.extensionPort || options?.previewPort ? "dir" : "ports");
1118
+ if (options?.extensionPort || options?.previewPort) {
1119
+ setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
1120
+ setStep("confirm");
1121
+ } else {
1122
+ setStep("settings");
1123
+ }
1054
1124
  };
1055
- const handlePorts = (extPort, prevPort) => {
1125
+ const handleSettings = (extPort, prevPort, dir) => {
1056
1126
  setExtensionPort(extPort);
1057
1127
  setPreviewPort(prevPort);
1058
- setStep("dir");
1059
- };
1060
- const handleDir = (dir) => {
1061
1128
  setOutputDir(dir);
1062
1129
  setStep("confirm");
1063
1130
  };
1064
1131
  const handleConfirm = async () => {
1065
1132
  setStep("scaffolding");
1066
- setProgressSteps(mode === "create" ? CREATE_STEPS : SCAFFOLD_STEPS);
1133
+ setProgressSteps(PROGRESS_STEPS[command]);
1067
1134
  try {
1068
1135
  let resolvedExtensionId = extensionId || toKebabCase2(name);
1069
1136
  let scaffoldStepOffset = 0;
1070
- if (mode === "create") {
1137
+ if (command === "create" /* CREATE */) {
1071
1138
  scaffoldStepOffset = 1;
1072
1139
  updateStep(0, "running");
1073
1140
  const created = await createExtensionRemote(selectedApp.id, {
@@ -1116,10 +1183,10 @@ var App = ({ mode, initialName, options }) => {
1116
1183
  };
1117
1184
  switch (step) {
1118
1185
  case "app": {
1119
- return /* @__PURE__ */ jsx13(AppSelect, { onSubmit: handleAppSelect });
1186
+ return /* @__PURE__ */ jsx12(AppSelect, { onSubmit: handleAppSelect });
1120
1187
  }
1121
1188
  case "extensionSelect": {
1122
- return /* @__PURE__ */ jsx13(
1189
+ return /* @__PURE__ */ jsx12(
1123
1190
  ExtensionSelect,
1124
1191
  {
1125
1192
  appId: selectedApp.id,
@@ -1128,18 +1195,8 @@ var App = ({ mode, initialName, options }) => {
1128
1195
  }
1129
1196
  );
1130
1197
  }
1131
- case "confirmName": {
1132
- return /* @__PURE__ */ jsx13(
1133
- NamePrompt,
1134
- {
1135
- initialValue: name,
1136
- onSubmit: handleConfirmName,
1137
- onBack: goBack
1138
- }
1139
- );
1140
- }
1141
1198
  case "confirmTargets": {
1142
- return /* @__PURE__ */ jsx13(
1199
+ return /* @__PURE__ */ jsx12(
1143
1200
  TargetSelect,
1144
1201
  {
1145
1202
  availableTargets: selectedApp?.targets ?? [],
@@ -1150,7 +1207,7 @@ var App = ({ mode, initialName, options }) => {
1150
1207
  );
1151
1208
  }
1152
1209
  case "name": {
1153
- return /* @__PURE__ */ jsx13(
1210
+ return /* @__PURE__ */ jsx12(
1154
1211
  NamePrompt,
1155
1212
  {
1156
1213
  initialValue: name,
@@ -1160,7 +1217,7 @@ var App = ({ mode, initialName, options }) => {
1160
1217
  );
1161
1218
  }
1162
1219
  case "targets": {
1163
- return /* @__PURE__ */ jsx13(
1220
+ return /* @__PURE__ */ jsx12(
1164
1221
  TargetSelect,
1165
1222
  {
1166
1223
  availableTargets: selectedApp?.targets ?? [],
@@ -1169,27 +1226,18 @@ var App = ({ mode, initialName, options }) => {
1169
1226
  }
1170
1227
  );
1171
1228
  }
1172
- case "ports": {
1173
- return /* @__PURE__ */ jsx13(
1174
- PortsPrompt,
1175
- {
1176
- onSubmit: handlePorts,
1177
- onBack: goBack
1178
- }
1179
- );
1180
- }
1181
- case "dir": {
1182
- return /* @__PURE__ */ jsx13(
1183
- DirPrompt,
1229
+ case "settings": {
1230
+ return /* @__PURE__ */ jsx12(
1231
+ SettingsPrompt,
1184
1232
  {
1185
1233
  defaultDir: join2(process.cwd(), toKebabCase2(extensionId || name)),
1186
- onSubmit: handleDir,
1234
+ onSubmit: handleSettings,
1187
1235
  onBack: goBack
1188
1236
  }
1189
1237
  );
1190
1238
  }
1191
1239
  case "confirm": {
1192
- return /* @__PURE__ */ jsx13(
1240
+ return /* @__PURE__ */ jsx12(
1193
1241
  Confirm,
1194
1242
  {
1195
1243
  name,
@@ -1197,6 +1245,7 @@ var App = ({ mode, initialName, options }) => {
1197
1245
  previewPort,
1198
1246
  targets,
1199
1247
  outputDir,
1248
+ extensionVersion: command === "scaffold" /* SCAFFOLD */ ? extensionVersion : void 0,
1200
1249
  onConfirm: handleConfirm,
1201
1250
  onCancel: handleCancel,
1202
1251
  onBack: goBack
@@ -1204,30 +1253,31 @@ var App = ({ mode, initialName, options }) => {
1204
1253
  );
1205
1254
  }
1206
1255
  case "scaffolding": {
1207
- return /* @__PURE__ */ jsx13(ScaffoldProgress, { steps: progressSteps });
1256
+ return /* @__PURE__ */ jsx12(ScaffoldProgress, { steps: progressSteps });
1208
1257
  }
1209
1258
  case "done": {
1210
- return /* @__PURE__ */ jsx13(Done, { name, outputDir });
1259
+ return /* @__PURE__ */ jsx12(Done, { name, outputDir });
1211
1260
  }
1212
1261
  default: {
1213
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", gap: 1, children: [
1214
- /* @__PURE__ */ jsx13(Text13, { color: "red", bold: true, children: "Scaffold failed" }),
1215
- errorMessage && /* @__PURE__ */ jsx13(Text13, { color: "red", children: errorMessage })
1216
- ] });
1262
+ return /* @__PURE__ */ jsx12(StepShell, { title: "Scaffold failed", onBack: goBack, backFocused: false, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
1263
+ /* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "An error occurred" }),
1264
+ errorMessage && /* @__PURE__ */ jsx12(Text12, { color: "red", children: errorMessage })
1265
+ ] }) });
1217
1266
  }
1218
1267
  }
1219
1268
  };
1220
1269
 
1221
1270
  // src/index.tsx
1222
- import { jsx as jsx14 } from "react/jsx-runtime";
1271
+ import { jsx as jsx13 } from "react/jsx-runtime";
1223
1272
  program.name("stackable-extension").description("Stackable extension developer CLI");
1224
- program.command("create").description("Create a new extension project").argument("[name]", "Extension project name").option("--targets <targets>", "Comma-separated target slots").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((name, options) => {
1225
- render(/* @__PURE__ */ jsx14(App, { mode: "create", initialName: name, options }));
1273
+ program.command("create" /* CREATE */).description("Create a new Extension project").argument("[name]", "Extension project name").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((name, options) => {
1274
+ render(/* @__PURE__ */ jsx13(App, { command: "create" /* CREATE */, initialName: name, options }));
1226
1275
  });
1227
- program.command("scaffold").description("Scaffold a local project from an existing extension").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((options) => {
1228
- render(/* @__PURE__ */ jsx14(App, { mode: "scaffold", options }));
1276
+ program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((options) => {
1277
+ render(/* @__PURE__ */ jsx13(App, { command: "scaffold" /* SCAFFOLD */, options }));
1229
1278
  });
1230
1279
  if (process.argv[1]?.endsWith("create-extension")) {
1280
+ process.stderr.write("\u26A0\uFE0F DEPRECATED: `create-extension` is deprecated and will be removed in v2.0.0. Use `stackable-extension create` instead.\n");
1231
1281
  program.parse(["node", "stackable-extension", "create", ...process.argv.slice(2).filter((arg) => arg !== "--")]);
1232
1282
  } else {
1233
1283
  program.parse(process.argv.filter((arg) => arg !== "--"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/cli-app-extension",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
@@ -16,7 +16,6 @@
16
16
  "commander": "12.x",
17
17
  "giget": "3.x",
18
18
  "ink": "5.x",
19
- "ink-select-input": "6.x",
20
19
  "ink-spinner": "5.x",
21
20
  "ink-text-input": "6.x",
22
21
  "nypm": "0.4.x",