@stackable-labs/cli-app-extension 1.2.0 → 1.4.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 +437 -375
  2. package/package.json +2 -4
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 })
193
+ error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error })
139
194
  ] }) });
140
195
  };
141
196
 
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 })
156
- ] }) });
157
- };
158
-
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,
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,
292
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,41 +445,46 @@ 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
- var DEFAULT_API_URL = "https://api.stackablelabs.io/app-extension/latest";
423
472
  var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
424
- var getApiBaseUrl = () => process.env.API_BASE_URL ?? DEFAULT_API_URL;
425
473
  var getAdminApiBaseUrl = () => process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
426
474
  var fetchApps = async () => {
427
- const baseURL = `${getApiBaseUrl()}/apps`;
428
- const res = await fetch(baseURL);
475
+ const baseUrl = getAdminApiBaseUrl();
476
+ const res = await fetch(`${baseUrl}/app-extension`);
429
477
  if (!res.ok) {
430
- throw new Error(`Failed to fetch apps: ${res.status} ${res.statusText}`);
478
+ throw new Error(`Failed to fetch apps${baseUrl !== DEFAULT_ADMIN_API_URL ? ` (from ${baseUrl})` : ""}: ${res.status} ${res.statusText}`);
431
479
  }
432
- return res.json();
480
+ const adminApps = await res.json();
481
+ return adminApps.filter((app) => app.enabled).map(({ id, name, targets, iconUrl }) => ({
482
+ id,
483
+ name,
484
+ targets,
485
+ enabled: true,
486
+ iconUrl
487
+ }));
433
488
  };
434
489
  var createExtensionRemote = async (appId, payload) => {
435
490
  const baseUrl = getAdminApiBaseUrl();
@@ -445,17 +500,29 @@ var createExtensionRemote = async (appId, payload) => {
445
500
  return res.json();
446
501
  };
447
502
  var fetchExtensions = async (appId) => {
448
- const baseUrl = getApiBaseUrl();
449
- const res = await fetch(`${baseUrl}/extensions/${appId}`);
503
+ const baseUrl = getAdminApiBaseUrl();
504
+ const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`);
450
505
  if (!res.ok) {
451
506
  throw new Error(`Failed to fetch extensions: ${res.status} ${res.statusText}`);
452
507
  }
453
- return res.json();
508
+ const adminExtensions = await res.json();
509
+ return Object.fromEntries(
510
+ adminExtensions.filter((ext) => ext.enabled).map(({ id, manifest, bundleUrl, iconUrl }) => [
511
+ id,
512
+ {
513
+ id,
514
+ manifest,
515
+ bundleUrl,
516
+ enabled: true,
517
+ iconUrl
518
+ }
519
+ ])
520
+ );
454
521
  };
455
522
 
456
523
  // 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";
524
+ import { Box as Box9, Text as Text9 } from "ink";
525
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
459
526
  var WORDMARK = [
460
527
  " _ _ _ _ ",
461
528
  " ___| |_ __ _ ___| | ____ _| |__ | | ___",
@@ -491,23 +558,23 @@ var gradientColor = (row, col, rows, cols) => {
491
558
  var Banner = () => {
492
559
  const termWidth = process.stdout.columns ?? 80;
493
560
  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)) })
561
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
562
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2500".repeat(termWidth) }),
563
+ /* @__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
564
  ] });
498
565
  };
499
566
 
500
567
  // src/components/AppSelect.tsx
501
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
568
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
502
569
  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);
570
+ const [apps, setApps] = useState6([]);
571
+ const [loading, setLoading] = useState6(true);
572
+ const [error, setError] = useState6();
573
+ const [cursor, setCursor] = useState6(0);
507
574
  useEffect(() => {
508
575
  fetchApps().then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
509
576
  }, []);
510
- useInput4((_, key) => {
577
+ useInput5((_, key) => {
511
578
  if (loading || error || apps.length === 0) return;
512
579
  if (key.upArrow) {
513
580
  setCursor((c) => Math.max(0, c - 1));
@@ -517,60 +584,59 @@ var AppSelect = ({ onSubmit }) => {
517
584
  onSubmit(apps[cursor]);
518
585
  }
519
586
  });
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) => {
587
+ const renderContent = () => {
588
+ if (loading) {
589
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 2, children: [
590
+ /* @__PURE__ */ jsx10(Spinner2, { type: "dots" }),
591
+ /* @__PURE__ */ jsx10(Text10, { children: "Loading available Apps\u2026" })
592
+ ] });
593
+ }
594
+ if (error) {
595
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
596
+ /* @__PURE__ */ jsx10(Text10, { color: "red", bold: true, children: "Failed to load Apps" }),
597
+ /* @__PURE__ */ jsx10(Text10, { color: "red", children: error })
598
+ ] });
599
+ }
600
+ if (apps.length === 0) {
601
+ return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "No Apps available. Contact your administrator." });
602
+ }
603
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: apps.map((app, i) => {
541
604
  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: [
605
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
606
+ /* @__PURE__ */ jsx10(Text10, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
607
+ /* @__PURE__ */ jsx10(Text10, { bold: isCursor, children: app.name }),
608
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
546
609
  "(",
547
610
  app.id,
548
611
  ")"
549
612
  ] })
550
613
  ] }, app.id);
551
- }) }) })
614
+ }) });
615
+ };
616
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
617
+ /* @__PURE__ */ jsx10(Banner, {}),
618
+ /* @__PURE__ */ jsx10(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
552
619
  ] });
553
620
  };
554
621
 
555
622
  // src/components/ExtensionSelect.tsx
556
- import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
623
+ import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
557
624
  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";
625
+ import { useEffect as useEffect2, useState as useState7 } from "react";
626
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
560
627
  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);
628
+ const [extensions, setExtensions] = useState7([]);
629
+ const [loading, setLoading] = useState7(true);
630
+ const [error, setError] = useState7();
631
+ const [cursor, setCursor] = useState7(0);
632
+ const [backFocused, setBackFocused] = useState7(false);
566
633
  useEffect2(() => {
567
634
  fetchExtensions(appId).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
568
635
  }, [appId]);
569
- useInput5((_, key) => {
570
- if (loading || error || extensions.length === 0) return;
636
+ useInput6((_, key) => {
571
637
  if (key.upArrow) {
572
- if (cursor === 0 && onBack) {
573
- setBackFocused(true);
638
+ if (loading || error || extensions.length === 0 || cursor === 0) {
639
+ if (onBack) setBackFocused(true);
574
640
  } else {
575
641
  setBackFocused(false);
576
642
  setCursor((c) => Math.max(0, c - 1));
@@ -580,7 +646,7 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
580
646
  if (key.downArrow) {
581
647
  if (backFocused) {
582
648
  setBackFocused(false);
583
- } else {
649
+ } else if (!loading && !error && extensions.length > 0) {
584
650
  setCursor((c) => Math.min(extensions.length - 1, c + 1));
585
651
  }
586
652
  return;
@@ -590,36 +656,45 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
590
656
  onBack?.();
591
657
  return;
592
658
  }
593
- onSubmit(extensions[cursor]);
659
+ if (!loading && !error && extensions.length > 0) {
660
+ onSubmit(extensions[cursor]);
661
+ }
594
662
  }
595
663
  });
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
- }) }) });
664
+ const renderContent = () => {
665
+ if (loading) {
666
+ return /* @__PURE__ */ jsxs11(Box11, { gap: 2, children: [
667
+ /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }),
668
+ /* @__PURE__ */ jsx11(Text11, { children: "Loading Extensions\u2026" })
669
+ ] });
670
+ }
671
+ if (error) {
672
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
673
+ /* @__PURE__ */ jsx11(Text11, { color: "red", bold: true, children: "Failed to load Extensions" }),
674
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: error })
675
+ ] });
676
+ }
677
+ if (extensions.length === 0) {
678
+ return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "No Extensions found for this App." });
679
+ }
680
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: extensions.map((ext, i) => {
681
+ const isCursor = i === cursor && !backFocused;
682
+ return /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
683
+ /* @__PURE__ */ jsx11(Text11, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
684
+ /* @__PURE__ */ jsx11(Text11, { bold: isCursor, children: ext.manifest.name }),
685
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
686
+ "v",
687
+ ext.manifest.version
688
+ ] }),
689
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
690
+ "(",
691
+ ext.id,
692
+ ")"
693
+ ] })
694
+ ] }, ext.id);
695
+ }) });
696
+ };
697
+ return /* @__PURE__ */ jsx11(StepShell, { title: "Select an existing Extension to scaffold:", onBack, backFocused, children: renderContent() });
623
698
  };
624
699
 
625
700
  // src/constants.ts
@@ -658,6 +733,8 @@ var postScaffold = async (options) => {
658
733
  import { readFile, readdir, rm, writeFile } from "fs/promises";
659
734
  import { join } from "path";
660
735
  import { downloadTemplate } from "giget";
736
+ var toKebabCase = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
737
+ var isTextFile = (filePath) => /\.(ts|tsx|js|jsx|json|md|html|yml|yaml|env|gitignore|nvmrc)$/i.test(filePath);
661
738
  var normalizeTargets = (targets) => Array.from(new Set(targets));
662
739
  var derivePermissions = (targets) => {
663
740
  const permissions = /* @__PURE__ */ new Set();
@@ -673,7 +750,29 @@ var derivePermissions = (targets) => {
673
750
  }
674
751
  return [...permissions];
675
752
  };
676
- var toKebabCase = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
753
+ var upsertOrRemove = async (filePath, shouldExist, content) => {
754
+ if (shouldExist) {
755
+ await writeFile(filePath, content);
756
+ return;
757
+ }
758
+ await rm(filePath, { force: true });
759
+ };
760
+ var walkFiles = async (rootDir) => {
761
+ const entries = await readdir(rootDir, { withFileTypes: true });
762
+ const files = [];
763
+ for (const entry of entries) {
764
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
765
+ continue;
766
+ }
767
+ const fullPath = join(rootDir, entry.name);
768
+ if (entry.isDirectory()) {
769
+ files.push(...await walkFiles(fullPath));
770
+ continue;
771
+ }
772
+ files.push(fullPath);
773
+ }
774
+ return files;
775
+ };
677
776
  var replacePlaceholders = async (rootDir, replacements) => {
678
777
  const files = await walkFiles(rootDir);
679
778
  for (const filePath of files) {
@@ -902,23 +1001,6 @@ var rewriteTurboJson = async (rootDir) => {
902
1001
  await writeFile(turboPath, `${JSON.stringify(turbo, null, 2)}
903
1002
  `);
904
1003
  };
905
- var walkFiles = async (rootDir) => {
906
- const entries = await readdir(rootDir, { withFileTypes: true });
907
- const files = [];
908
- for (const entry of entries) {
909
- if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
910
- continue;
911
- }
912
- const fullPath = join(rootDir, entry.name);
913
- if (entry.isDirectory()) {
914
- files.push(...await walkFiles(fullPath));
915
- continue;
916
- }
917
- files.push(fullPath);
918
- }
919
- return files;
920
- };
921
- var isTextFile = (filePath) => /\.(ts|tsx|js|jsx|json|md|html|yml|yaml|env|gitignore|nvmrc)$/i.test(filePath);
922
1004
  var writeEnvFile = async (dir, extensionPort, previewPort) => {
923
1005
  const envPath = join(dir, ".env");
924
1006
  const content = `VITE_EXTENSION_PORT=${extensionPort}
@@ -926,13 +1008,6 @@ VITE_PREVIEW_PORT=${previewPort}
926
1008
  `;
927
1009
  await writeFile(envPath, content);
928
1010
  };
929
- var upsertOrRemove = async (filePath, shouldExist, content) => {
930
- if (shouldExist) {
931
- await writeFile(filePath, content);
932
- return;
933
- }
934
- await rm(filePath, { force: true });
935
- };
936
1011
  var scaffold = async (options) => {
937
1012
  const { dir } = await downloadTemplate(TEMPLATE_SOURCE, {
938
1013
  dir: options.outputDir,
@@ -956,20 +1031,24 @@ var scaffold = async (options) => {
956
1031
  };
957
1032
 
958
1033
  // 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
- ];
1034
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1035
+ var STEPS = {
1036
+ ["create" /* CREATE */]: ["app", "name", "targets", "settings", "confirm"],
1037
+ ["scaffold" /* SCAFFOLD */]: ["app", "extensionSelect", "confirmTargets", "settings", "confirm"]
1038
+ };
1039
+ var PROGRESS_STEPS = {
1040
+ ["create" /* CREATE */]: [
1041
+ { label: "Registering extension", status: "pending" },
1042
+ { label: "Fetching template", status: "pending" },
1043
+ { label: "Generating files", status: "pending" },
1044
+ { label: "Installing dependencies", status: "pending" }
1045
+ ],
1046
+ ["scaffold" /* SCAFFOLD */]: [
1047
+ { label: "Fetching template", status: "pending" },
1048
+ { label: "Generating files", status: "pending" },
1049
+ { label: "Installing dependencies", status: "pending" }
1050
+ ]
1051
+ };
973
1052
  var toKebabCase2 = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
974
1053
  var derivePermissions2 = (targets) => {
975
1054
  const permissions = /* @__PURE__ */ new Set();
@@ -988,32 +1067,33 @@ var derivePermissions2 = (targets) => {
988
1067
  }
989
1068
  return [...permissions];
990
1069
  };
991
- var App = ({ mode, initialName, options }) => {
1070
+ var App = ({ command, initialName, options }) => {
992
1071
  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(
1072
+ const [step, setStep] = useState8("app");
1073
+ const [name, setName] = useState8(initialName ?? "");
1074
+ const [extensionId, setExtensionId] = useState8("");
1075
+ const [extensionVersion, setExtensionVersion] = useState8("");
1076
+ const [selectedApp, setSelectedApp] = useState8(null);
1077
+ const [extensionPort, setExtensionPort] = useState8(
998
1078
  options?.extensionPort ? parseInt(options.extensionPort, 10) : 5173
999
1079
  );
1000
- const [previewPort, setPreviewPort] = useState9(
1080
+ const [previewPort, setPreviewPort] = useState8(
1001
1081
  options?.previewPort ? parseInt(options.previewPort, 10) : 5174
1002
1082
  );
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();
1083
+ const [targets, setTargets] = useState8([]);
1084
+ const [outputDir, setOutputDir] = useState8("");
1085
+ const [progressSteps, setProgressSteps] = useState8(PROGRESS_STEPS[command]);
1086
+ const [errorMessage, setErrorMessage] = useState8();
1007
1087
  const updateStep = useCallback((index, status) => {
1008
1088
  setProgressSteps((prev) => prev.map((s, i) => i === index ? { ...s, status } : s));
1009
1089
  }, []);
1010
1090
  const activeSteps = useCallback(() => {
1011
- const base = mode === "create" ? STEP_ORDER_CREATE : STEP_ORDER_SCAFFOLD;
1091
+ const base = STEPS[command];
1012
1092
  const skipped = /* @__PURE__ */ new Set();
1013
- if (mode === "create" && initialName) skipped.add("name");
1014
- if (options?.extensionPort || options?.previewPort) skipped.add("ports");
1093
+ if (command === "create" /* CREATE */ && initialName) skipped.add("name");
1094
+ if (options?.extensionPort || options?.previewPort) skipped.add("settings");
1015
1095
  return base.filter((s) => !skipped.has(s));
1016
- }, [mode, initialName, options?.extensionPort, options?.previewPort]);
1096
+ }, [command, initialName, options?.extensionPort, options?.previewPort]);
1017
1097
  const goBack = useCallback(() => {
1018
1098
  setStep((prev) => {
1019
1099
  const steps = activeSteps();
@@ -1023,7 +1103,7 @@ var App = ({ mode, initialName, options }) => {
1023
1103
  }, [activeSteps]);
1024
1104
  const handleAppSelect = (app) => {
1025
1105
  setSelectedApp(app);
1026
- if (mode === "scaffold") {
1106
+ if (command === "scaffold" /* SCAFFOLD */) {
1027
1107
  setStep("extensionSelect");
1028
1108
  return;
1029
1109
  }
@@ -1032,16 +1112,18 @@ var App = ({ mode, initialName, options }) => {
1032
1112
  const handleExtensionSelect = (ext) => {
1033
1113
  setName(ext.manifest.name);
1034
1114
  setExtensionId(ext.id);
1115
+ setExtensionVersion(ext.manifest.version);
1035
1116
  setTargets(ext.manifest.targets);
1036
- setStep("confirmName");
1037
- };
1038
- const handleConfirmName = (value) => {
1039
- setName(value);
1040
1117
  setStep("confirmTargets");
1041
1118
  };
1042
1119
  const handleConfirmTargets = (value) => {
1043
1120
  setTargets(value);
1044
- setStep(options?.extensionPort || options?.previewPort ? "dir" : "ports");
1121
+ if (options?.extensionPort || options?.previewPort) {
1122
+ setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
1123
+ setStep("confirm");
1124
+ } else {
1125
+ setStep("settings");
1126
+ }
1045
1127
  };
1046
1128
  const handleName = (value) => {
1047
1129
  setName(value);
@@ -1050,24 +1132,26 @@ var App = ({ mode, initialName, options }) => {
1050
1132
  };
1051
1133
  const handleTargets = (value) => {
1052
1134
  setTargets(value);
1053
- setStep(options?.extensionPort || options?.previewPort ? "dir" : "ports");
1135
+ if (options?.extensionPort || options?.previewPort) {
1136
+ setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
1137
+ setStep("confirm");
1138
+ } else {
1139
+ setStep("settings");
1140
+ }
1054
1141
  };
1055
- const handlePorts = (extPort, prevPort) => {
1142
+ const handleSettings = (extPort, prevPort, dir) => {
1056
1143
  setExtensionPort(extPort);
1057
1144
  setPreviewPort(prevPort);
1058
- setStep("dir");
1059
- };
1060
- const handleDir = (dir) => {
1061
1145
  setOutputDir(dir);
1062
1146
  setStep("confirm");
1063
1147
  };
1064
1148
  const handleConfirm = async () => {
1065
1149
  setStep("scaffolding");
1066
- setProgressSteps(mode === "create" ? CREATE_STEPS : SCAFFOLD_STEPS);
1150
+ setProgressSteps(PROGRESS_STEPS[command]);
1067
1151
  try {
1068
1152
  let resolvedExtensionId = extensionId || toKebabCase2(name);
1069
1153
  let scaffoldStepOffset = 0;
1070
- if (mode === "create") {
1154
+ if (command === "create" /* CREATE */) {
1071
1155
  scaffoldStepOffset = 1;
1072
1156
  updateStep(0, "running");
1073
1157
  const created = await createExtensionRemote(selectedApp.id, {
@@ -1116,10 +1200,10 @@ var App = ({ mode, initialName, options }) => {
1116
1200
  };
1117
1201
  switch (step) {
1118
1202
  case "app": {
1119
- return /* @__PURE__ */ jsx13(AppSelect, { onSubmit: handleAppSelect });
1203
+ return /* @__PURE__ */ jsx12(AppSelect, { onSubmit: handleAppSelect });
1120
1204
  }
1121
1205
  case "extensionSelect": {
1122
- return /* @__PURE__ */ jsx13(
1206
+ return /* @__PURE__ */ jsx12(
1123
1207
  ExtensionSelect,
1124
1208
  {
1125
1209
  appId: selectedApp.id,
@@ -1128,18 +1212,8 @@ var App = ({ mode, initialName, options }) => {
1128
1212
  }
1129
1213
  );
1130
1214
  }
1131
- case "confirmName": {
1132
- return /* @__PURE__ */ jsx13(
1133
- NamePrompt,
1134
- {
1135
- initialValue: name,
1136
- onSubmit: handleConfirmName,
1137
- onBack: goBack
1138
- }
1139
- );
1140
- }
1141
1215
  case "confirmTargets": {
1142
- return /* @__PURE__ */ jsx13(
1216
+ return /* @__PURE__ */ jsx12(
1143
1217
  TargetSelect,
1144
1218
  {
1145
1219
  availableTargets: selectedApp?.targets ?? [],
@@ -1150,7 +1224,7 @@ var App = ({ mode, initialName, options }) => {
1150
1224
  );
1151
1225
  }
1152
1226
  case "name": {
1153
- return /* @__PURE__ */ jsx13(
1227
+ return /* @__PURE__ */ jsx12(
1154
1228
  NamePrompt,
1155
1229
  {
1156
1230
  initialValue: name,
@@ -1160,7 +1234,7 @@ var App = ({ mode, initialName, options }) => {
1160
1234
  );
1161
1235
  }
1162
1236
  case "targets": {
1163
- return /* @__PURE__ */ jsx13(
1237
+ return /* @__PURE__ */ jsx12(
1164
1238
  TargetSelect,
1165
1239
  {
1166
1240
  availableTargets: selectedApp?.targets ?? [],
@@ -1169,27 +1243,18 @@ var App = ({ mode, initialName, options }) => {
1169
1243
  }
1170
1244
  );
1171
1245
  }
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,
1246
+ case "settings": {
1247
+ return /* @__PURE__ */ jsx12(
1248
+ SettingsPrompt,
1184
1249
  {
1185
1250
  defaultDir: join2(process.cwd(), toKebabCase2(extensionId || name)),
1186
- onSubmit: handleDir,
1251
+ onSubmit: handleSettings,
1187
1252
  onBack: goBack
1188
1253
  }
1189
1254
  );
1190
1255
  }
1191
1256
  case "confirm": {
1192
- return /* @__PURE__ */ jsx13(
1257
+ return /* @__PURE__ */ jsx12(
1193
1258
  Confirm,
1194
1259
  {
1195
1260
  name,
@@ -1197,6 +1262,7 @@ var App = ({ mode, initialName, options }) => {
1197
1262
  previewPort,
1198
1263
  targets,
1199
1264
  outputDir,
1265
+ extensionVersion: command === "scaffold" /* SCAFFOLD */ ? extensionVersion : void 0,
1200
1266
  onConfirm: handleConfirm,
1201
1267
  onCancel: handleCancel,
1202
1268
  onBack: goBack
@@ -1204,31 +1270,27 @@ var App = ({ mode, initialName, options }) => {
1204
1270
  );
1205
1271
  }
1206
1272
  case "scaffolding": {
1207
- return /* @__PURE__ */ jsx13(ScaffoldProgress, { steps: progressSteps });
1273
+ return /* @__PURE__ */ jsx12(ScaffoldProgress, { steps: progressSteps });
1208
1274
  }
1209
1275
  case "done": {
1210
- return /* @__PURE__ */ jsx13(Done, { name, outputDir });
1276
+ return /* @__PURE__ */ jsx12(Done, { name, outputDir });
1211
1277
  }
1212
1278
  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
- ] });
1279
+ return /* @__PURE__ */ jsx12(StepShell, { title: "Scaffold failed", onBack: goBack, backFocused: false, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
1280
+ /* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "An error occurred" }),
1281
+ errorMessage && /* @__PURE__ */ jsx12(Text12, { color: "red", children: errorMessage })
1282
+ ] }) });
1217
1283
  }
1218
1284
  }
1219
1285
  };
1220
1286
 
1221
1287
  // src/index.tsx
1222
- import { jsx as jsx14 } from "react/jsx-runtime";
1223
- 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 }));
1288
+ import { jsx as jsx13 } from "react/jsx-runtime";
1289
+ program.name("stackable-app-extension").description("Stackable app extension developer CLI");
1290
+ 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) => {
1291
+ render(/* @__PURE__ */ jsx13(App, { command: "create" /* CREATE */, initialName: name, options }));
1226
1292
  });
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 }));
1293
+ 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) => {
1294
+ render(/* @__PURE__ */ jsx13(App, { command: "scaffold" /* SCAFFOLD */, options }));
1229
1295
  });
1230
- if (process.argv[1]?.endsWith("create-extension")) {
1231
- program.parse(["node", "stackable-extension", "create", ...process.argv.slice(2).filter((arg) => arg !== "--")]);
1232
- } else {
1233
- program.parse(process.argv.filter((arg) => arg !== "--"));
1234
- }
1296
+ program.parse(process.argv.filter((arg) => arg !== "--"));
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@stackable-labs/cli-app-extension",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
7
- "create-extension": "./dist/index.js",
8
- "stackable-extension": "./dist/index.js"
7
+ "stackable-app-extension": "./dist/index.js"
9
8
  },
10
9
  "files": [
11
10
  "dist/",
@@ -16,7 +15,6 @@
16
15
  "commander": "12.x",
17
16
  "giget": "3.x",
18
17
  "ink": "5.x",
19
- "ink-select-input": "6.x",
20
18
  "ink-spinner": "5.x",
21
19
  "ink-text-input": "6.x",
22
20
  "nypm": "0.4.x",