@stackable-labs/cli-app-extension 1.4.1 → 1.5.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.
- package/dist/index.js +986 -237
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -7,27 +7,33 @@ import { render } from "ink";
|
|
|
7
7
|
|
|
8
8
|
// src/App.tsx
|
|
9
9
|
import { join as join2 } from "path";
|
|
10
|
-
import { Box as
|
|
10
|
+
import { Box as Box13, Text as Text13, useApp } from "ink";
|
|
11
11
|
import { useCallback, useState as useState8 } from "react";
|
|
12
12
|
|
|
13
13
|
// src/components/Confirm.tsx
|
|
14
|
-
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
15
|
-
import
|
|
14
|
+
import { Box as Box2, Text as Text2, useFocus, useFocusManager, useInput as useInput2 } from "ink";
|
|
15
|
+
import TextInput from "ink-text-input";
|
|
16
|
+
import { useEffect, useState } from "react";
|
|
16
17
|
|
|
17
18
|
// src/components/StepShell.tsx
|
|
18
|
-
import { Box, Text } from "ink";
|
|
19
|
+
import { Box, Text, useInput } from "ink";
|
|
19
20
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
20
21
|
var divider = (width) => "\u2500".repeat(width);
|
|
21
22
|
var INNER_DIVIDER_WIDTH = 40;
|
|
22
|
-
var StepShell = ({ title, hint, children, footer, onBack
|
|
23
|
+
var StepShell = ({ title, hint, children, footer, onBack }) => {
|
|
23
24
|
const termWidth = process.stdout.columns ?? 80;
|
|
25
|
+
useInput((_, key) => {
|
|
26
|
+
if (onBack && key.escape) {
|
|
27
|
+
onBack();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 0, children: [
|
|
25
31
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: divider(termWidth) }),
|
|
26
32
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, gap: 1, children: [
|
|
27
33
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 0, children: [
|
|
28
34
|
onBack && /* @__PURE__ */ jsxs(Box, { gap: 1, marginBottom: 1, children: [
|
|
29
|
-
/* @__PURE__ */ jsx(Text, {
|
|
30
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
35
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2190 Back" }),
|
|
36
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "(Esc)" })
|
|
31
37
|
] }),
|
|
32
38
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: title }),
|
|
33
39
|
hint && /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
|
|
@@ -44,23 +50,74 @@ var StepShell = ({ title, hint, children, footer, onBack, backFocused }) => {
|
|
|
44
50
|
|
|
45
51
|
// src/components/Confirm.tsx
|
|
46
52
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
47
|
-
var
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
+
var compareSemver = (a, b) => {
|
|
54
|
+
const pa = a.split(".").map(Number);
|
|
55
|
+
const pb = b.split(".").map(Number);
|
|
56
|
+
for (let i = 0; i < 3; i++) {
|
|
57
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return 1;
|
|
58
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return -1;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
};
|
|
62
|
+
var VersionRow = ({ currentVersion, value, onChange, onFocusChange, onConfirm, onCancel, selected }) => {
|
|
63
|
+
const { isFocused } = useFocus();
|
|
64
|
+
const { focusPrevious, focusNext } = useFocusManager();
|
|
65
|
+
useInput2((_, key) => {
|
|
66
|
+
if (!isFocused) return;
|
|
67
|
+
if (key.upArrow || key.tab && key.shift) {
|
|
68
|
+
focusPrevious();
|
|
53
69
|
return;
|
|
54
70
|
}
|
|
55
|
-
if (key.
|
|
56
|
-
|
|
71
|
+
if (key.tab && !key.shift) {
|
|
72
|
+
focusNext();
|
|
57
73
|
return;
|
|
58
74
|
}
|
|
59
|
-
if (key.return
|
|
60
|
-
|
|
61
|
-
return;
|
|
75
|
+
if (key.return) {
|
|
76
|
+
const isValid2 = /^\d+\.\d+\.\d+$/.test(value) && compareSemver(value, currentVersion) > 0;
|
|
77
|
+
if (!isValid2) return;
|
|
78
|
+
if (selected === "y") onConfirm();
|
|
79
|
+
else onCancel();
|
|
62
80
|
}
|
|
63
|
-
|
|
81
|
+
});
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
onFocusChange(isFocused);
|
|
84
|
+
}, [isFocused, onFocusChange]);
|
|
85
|
+
const isValid = /^\d+\.\d+\.\d+$/.test(value) && compareSemver(value, currentVersion) > 0;
|
|
86
|
+
const showError = isFocused && value !== "" && !isValid;
|
|
87
|
+
const hasChange = value !== currentVersion;
|
|
88
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
89
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
90
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Version " }),
|
|
91
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
92
|
+
currentVersion,
|
|
93
|
+
hasChange ? " \u2192 " : ""
|
|
94
|
+
] }),
|
|
95
|
+
isFocused ? /* @__PURE__ */ jsx2(TextInput, { value, onChange, placeholder: value }) : hasChange && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: value }),
|
|
96
|
+
isFocused && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "(edit to override)" })
|
|
97
|
+
] }),
|
|
98
|
+
showError && /* @__PURE__ */ jsx2(Text2, { color: "red", children: " Version must be greater than " + currentVersion })
|
|
99
|
+
] });
|
|
100
|
+
};
|
|
101
|
+
var Confirm = ({
|
|
102
|
+
command,
|
|
103
|
+
name,
|
|
104
|
+
extensionVersion,
|
|
105
|
+
extensionPort,
|
|
106
|
+
previewPort,
|
|
107
|
+
targets,
|
|
108
|
+
outputDir,
|
|
109
|
+
bundleUrl,
|
|
110
|
+
enabled,
|
|
111
|
+
newVersion,
|
|
112
|
+
onVersionOverride,
|
|
113
|
+
onConfirm,
|
|
114
|
+
onCancel,
|
|
115
|
+
onBack
|
|
116
|
+
}) => {
|
|
117
|
+
const [selected, setSelected] = useState("y");
|
|
118
|
+
const [overrideFocused, setOverrideFocused] = useState(false);
|
|
119
|
+
useInput2((input, key) => {
|
|
120
|
+
if (overrideFocused) return;
|
|
64
121
|
if (key.leftArrow || key.rightArrow) {
|
|
65
122
|
setSelected((s) => s === "y" ? "n" : "y");
|
|
66
123
|
return;
|
|
@@ -74,17 +131,21 @@ var Confirm = ({ name, extensionVersion, extensionPort, previewPort, targets, ou
|
|
|
74
131
|
return;
|
|
75
132
|
}
|
|
76
133
|
if (key.return) {
|
|
77
|
-
if (selected === "y")
|
|
78
|
-
|
|
134
|
+
if (selected === "y") {
|
|
135
|
+
if (newVersion && extensionVersion && !/^\d+\.\d+\.\d+$/.test(newVersion)) return;
|
|
136
|
+
if (newVersion && extensionVersion && compareSemver(newVersion, extensionVersion) <= 0) return;
|
|
137
|
+
onConfirm();
|
|
138
|
+
} else {
|
|
139
|
+
onCancel();
|
|
140
|
+
}
|
|
79
141
|
}
|
|
80
142
|
});
|
|
81
143
|
return /* @__PURE__ */ jsx2(
|
|
82
144
|
StepShell,
|
|
83
145
|
{
|
|
84
|
-
title: "Ready to scaffold",
|
|
146
|
+
title: command === "update" /* UPDATE */ ? "Ready to update" : "Ready to scaffold",
|
|
85
147
|
hint: "Review your settings before proceeding",
|
|
86
148
|
onBack,
|
|
87
|
-
backFocused,
|
|
88
149
|
footer: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
89
150
|
"Proceed?",
|
|
90
151
|
" ",
|
|
@@ -97,22 +158,45 @@ var Confirm = ({ name, extensionVersion, extensionPort, previewPort, targets, ou
|
|
|
97
158
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Name " }),
|
|
98
159
|
/* @__PURE__ */ jsx2(Text2, { dimColor: !!extensionVersion, children: name })
|
|
99
160
|
] }),
|
|
100
|
-
extensionVersion && /*
|
|
161
|
+
extensionVersion && command === "update" /* UPDATE */ && newVersion && onVersionOverride && /* @__PURE__ */ jsx2(
|
|
162
|
+
VersionRow,
|
|
163
|
+
{
|
|
164
|
+
currentVersion: extensionVersion,
|
|
165
|
+
value: newVersion,
|
|
166
|
+
onChange: onVersionOverride,
|
|
167
|
+
onFocusChange: setOverrideFocused,
|
|
168
|
+
onConfirm,
|
|
169
|
+
onCancel,
|
|
170
|
+
selected
|
|
171
|
+
}
|
|
172
|
+
),
|
|
173
|
+
extensionVersion && !(command === "update" /* UPDATE */ && newVersion && onVersionOverride) && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
101
174
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Version " }),
|
|
102
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
176
|
+
extensionVersion,
|
|
177
|
+
newVersion && newVersion !== extensionVersion ? ` \u2192 ${newVersion}` : ""
|
|
178
|
+
] })
|
|
103
179
|
] }),
|
|
104
|
-
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
180
|
+
command !== "update" /* UPDATE */ && outputDir && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
105
181
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Directory " }),
|
|
106
182
|
/* @__PURE__ */ jsx2(Text2, { children: outputDir })
|
|
107
183
|
] }),
|
|
108
|
-
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
184
|
+
command !== "update" /* UPDATE */ && extensionPort !== void 0 && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
109
185
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Extension port" }),
|
|
110
186
|
/* @__PURE__ */ jsx2(Text2, { children: extensionPort })
|
|
111
187
|
] }),
|
|
112
|
-
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
188
|
+
command !== "update" /* UPDATE */ && previewPort !== void 0 && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
113
189
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Preview port " }),
|
|
114
190
|
/* @__PURE__ */ jsx2(Text2, { children: previewPort })
|
|
115
191
|
] }),
|
|
192
|
+
command === "update" /* UPDATE */ && bundleUrl && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
193
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Bundle URL " }),
|
|
194
|
+
/* @__PURE__ */ jsx2(Text2, { children: bundleUrl })
|
|
195
|
+
] }),
|
|
196
|
+
command === "update" /* UPDATE */ && enabled !== void 0 && /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
197
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Enabled " }),
|
|
198
|
+
/* @__PURE__ */ jsx2(Text2, { color: enabled ? "green" : "red", children: enabled ? "Yes" : "No" })
|
|
199
|
+
] }),
|
|
116
200
|
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
117
201
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Targets " }),
|
|
118
202
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: targets.map((t) => /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
@@ -167,39 +251,22 @@ var Done = ({ name, outputDir }) => /* @__PURE__ */ jsxs3(Box3, { flexDirection:
|
|
|
167
251
|
|
|
168
252
|
// src/components/NamePrompt.tsx
|
|
169
253
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
170
|
-
import
|
|
171
|
-
import { useState as
|
|
254
|
+
import TextInput2 from "ink-text-input";
|
|
255
|
+
import { useState as useState2 } from "react";
|
|
172
256
|
|
|
173
257
|
// src/components/BackableInput.tsx
|
|
174
|
-
import { Box as Box4, Text as Text4
|
|
175
|
-
import { useState as useState2 } from "react";
|
|
258
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
176
259
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
177
|
-
var BackableInput = ({ label, hint, onBack, children, error }) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
setFocus("back");
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (key.downArrow && focus === "back") {
|
|
185
|
-
setFocus("input");
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (key.return && focus === "back") {
|
|
189
|
-
onBack?.();
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
return /* @__PURE__ */ jsx4(StepShell, { title: label, hint, onBack, backFocused: focus === "back", children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
|
|
193
|
-
children(focus === "input"),
|
|
194
|
-
error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error })
|
|
195
|
-
] }) });
|
|
196
|
-
};
|
|
260
|
+
var BackableInput = ({ label, hint, onBack, children, error }) => /* @__PURE__ */ jsx4(StepShell, { title: label, hint, onBack, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
|
|
261
|
+
children(true),
|
|
262
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error })
|
|
263
|
+
] }) });
|
|
197
264
|
|
|
198
265
|
// src/components/NamePrompt.tsx
|
|
199
266
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
200
267
|
var NamePrompt = ({ initialValue = "", onSubmit, onBack }) => {
|
|
201
|
-
const [value, setValue] =
|
|
202
|
-
const [error, setError] =
|
|
268
|
+
const [value, setValue] = useState2(initialValue);
|
|
269
|
+
const [error, setError] = useState2();
|
|
203
270
|
const handleSubmit = (val) => {
|
|
204
271
|
const trimmed = val.trim();
|
|
205
272
|
if (trimmed.length === 0) {
|
|
@@ -211,37 +278,31 @@ var NamePrompt = ({ initialValue = "", onSubmit, onBack }) => {
|
|
|
211
278
|
};
|
|
212
279
|
return /* @__PURE__ */ jsx5(BackableInput, { label: "What is your Extension name?", hint: "Press Enter to confirm", onBack, error, children: (isFocused) => /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
213
280
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "> " }),
|
|
214
|
-
/* @__PURE__ */ jsx5(
|
|
281
|
+
/* @__PURE__ */ jsx5(TextInput2, { value, onChange: setValue, onSubmit: handleSubmit, focus: isFocused })
|
|
215
282
|
] }) });
|
|
216
283
|
};
|
|
217
284
|
|
|
218
285
|
// src/components/SettingsPrompt.tsx
|
|
219
|
-
import { Box as Box6, Text as Text6, useFocus, useFocusManager, useInput as useInput3 } from "ink";
|
|
220
|
-
import
|
|
221
|
-
import { useState as
|
|
286
|
+
import { Box as Box6, Text as Text6, useFocus as useFocus2, useFocusManager as useFocusManager2, useInput as useInput3 } from "ink";
|
|
287
|
+
import TextInput3 from "ink-text-input";
|
|
288
|
+
import { useState as useState3 } from "react";
|
|
222
289
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
223
290
|
var DEFAULT_EXTENSION_PORT = 5173;
|
|
224
291
|
var DEFAULT_PREVIEW_PORT = DEFAULT_EXTENSION_PORT + 1;
|
|
225
|
-
var FieldRow = ({ label, value, onChange, onSubmit, onConfirm, placeholder, autoFocus, isFirst, isLast
|
|
226
|
-
const { isFocused } =
|
|
227
|
-
const { focusNext, focusPrevious } =
|
|
292
|
+
var FieldRow = ({ label, value, onChange, onSubmit, onConfirm, placeholder, autoFocus, isFirst, isLast }) => {
|
|
293
|
+
const { isFocused } = useFocus2({ autoFocus });
|
|
294
|
+
const { focusNext, focusPrevious } = useFocusManager2();
|
|
228
295
|
useInput3((_input, key) => {
|
|
229
296
|
if (!isFocused) return;
|
|
230
297
|
if (key.downArrow && !isLast) focusNext();
|
|
231
|
-
if (key.upArrow)
|
|
232
|
-
if (isFirst && onFocusBack) {
|
|
233
|
-
onFocusBack();
|
|
234
|
-
} else if (!isFirst) {
|
|
235
|
-
focusPrevious();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
298
|
+
if (key.upArrow && !isFirst) focusPrevious();
|
|
238
299
|
});
|
|
239
300
|
return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
240
301
|
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label }),
|
|
241
302
|
isFocused ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
242
303
|
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2192 " }),
|
|
243
304
|
/* @__PURE__ */ jsx6(
|
|
244
|
-
|
|
305
|
+
TextInput3,
|
|
245
306
|
{
|
|
246
307
|
value,
|
|
247
308
|
onChange,
|
|
@@ -257,23 +318,9 @@ var FieldRow = ({ label, value, onChange, onSubmit, onConfirm, placeholder, auto
|
|
|
257
318
|
] });
|
|
258
319
|
};
|
|
259
320
|
var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
260
|
-
const [
|
|
261
|
-
const [
|
|
262
|
-
const [
|
|
263
|
-
const [outputDir, setOutputDir] = useState4(defaultDir);
|
|
264
|
-
const handleFocusBack = () => {
|
|
265
|
-
if (onBack) setBackFocused(true);
|
|
266
|
-
};
|
|
267
|
-
useInput3((_input, key) => {
|
|
268
|
-
if (key.downArrow && backFocused) {
|
|
269
|
-
setBackFocused(false);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (key.return && backFocused) {
|
|
273
|
-
onBack?.();
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
});
|
|
321
|
+
const [extensionPort, setExtensionPort] = useState3(String(DEFAULT_EXTENSION_PORT));
|
|
322
|
+
const [previewPort, setPreviewPort] = useState3(String(DEFAULT_PREVIEW_PORT));
|
|
323
|
+
const [outputDir, setOutputDir] = useState3(defaultDir);
|
|
277
324
|
const handleConfirm = (resolvedExtPort, resolvedPrevPort, resolvedDir) => {
|
|
278
325
|
const dir = resolvedDir.trim();
|
|
279
326
|
if (!dir) return;
|
|
@@ -310,7 +357,6 @@ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
|
310
357
|
title: "Project settings",
|
|
311
358
|
hint: "\u2191\u2193 or Tab to move between fields, Enter to confirm",
|
|
312
359
|
onBack,
|
|
313
|
-
backFocused,
|
|
314
360
|
children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
|
|
315
361
|
/* @__PURE__ */ jsx6(
|
|
316
362
|
FieldRow,
|
|
@@ -322,8 +368,7 @@ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
|
322
368
|
onConfirm: handleConfirm,
|
|
323
369
|
placeholder: String(DEFAULT_EXTENSION_PORT),
|
|
324
370
|
autoFocus: true,
|
|
325
|
-
isFirst: true
|
|
326
|
-
onFocusBack: handleFocusBack
|
|
371
|
+
isFirst: true
|
|
327
372
|
}
|
|
328
373
|
),
|
|
329
374
|
/* @__PURE__ */ jsx6(
|
|
@@ -353,34 +398,284 @@ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
|
353
398
|
);
|
|
354
399
|
};
|
|
355
400
|
|
|
401
|
+
// src/components/UpdateSettingsPrompt.tsx
|
|
402
|
+
import { Box as Box7, Text as Text7, useFocus as useFocus3, useFocusManager as useFocusManager3, useInput as useInput4 } from "ink";
|
|
403
|
+
import TextInput4 from "ink-text-input";
|
|
404
|
+
import { useState as useState4 } from "react";
|
|
405
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
406
|
+
var FieldRow2 = ({ label, value, onChange, placeholder, autoFocus, isFirst, isLast, onSubmitAll }) => {
|
|
407
|
+
const { isFocused } = useFocus3({ autoFocus });
|
|
408
|
+
const { focusNext, focusPrevious } = useFocusManager3();
|
|
409
|
+
useInput4((_, key) => {
|
|
410
|
+
if (!isFocused) return;
|
|
411
|
+
if (key.tab && !key.shift) {
|
|
412
|
+
if (!isLast) focusNext();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (key.tab && key.shift) {
|
|
416
|
+
if (!isFirst) focusPrevious();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (key.downArrow) {
|
|
420
|
+
if (!isLast) focusNext();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (key.upArrow) {
|
|
424
|
+
if (!isFirst) focusPrevious();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (key.return) {
|
|
428
|
+
onSubmitAll();
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
return /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
|
|
432
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !isFocused, bold: isFocused, children: label.padEnd(14) }),
|
|
433
|
+
isFocused ? /* @__PURE__ */ jsx7(TextInput4, { value, onChange, placeholder }) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: value || placeholder || "" })
|
|
434
|
+
] });
|
|
435
|
+
};
|
|
436
|
+
var TargetToggleRow = ({ targets, availableTargets, onToggle, autoFocus, isLast, onSubmitAll }) => {
|
|
437
|
+
const { isFocused } = useFocus3({ autoFocus });
|
|
438
|
+
const { focusNext, focusPrevious } = useFocusManager3();
|
|
439
|
+
const [cursor, setCursor] = useState4(0);
|
|
440
|
+
useInput4((input, key) => {
|
|
441
|
+
if (!isFocused) return;
|
|
442
|
+
if (key.tab && !key.shift) {
|
|
443
|
+
focusNext();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (key.tab && key.shift) {
|
|
447
|
+
if (cursor > 0) {
|
|
448
|
+
setCursor((c) => c - 1);
|
|
449
|
+
} else {
|
|
450
|
+
focusPrevious();
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (key.downArrow) {
|
|
455
|
+
if (cursor < availableTargets.length - 1) {
|
|
456
|
+
setCursor((c) => c + 1);
|
|
457
|
+
} else if (!isLast) {
|
|
458
|
+
focusNext();
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (key.upArrow) {
|
|
463
|
+
if (cursor > 0) {
|
|
464
|
+
setCursor((c) => c - 1);
|
|
465
|
+
} else {
|
|
466
|
+
focusPrevious();
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (input === " " && availableTargets[cursor]) {
|
|
471
|
+
onToggle(availableTargets[cursor]);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (key.return) {
|
|
475
|
+
onSubmitAll();
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
479
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !isFocused, bold: isFocused, children: "Targets".padEnd(14) }),
|
|
480
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 2, children: [
|
|
481
|
+
availableTargets.map((t, i) => {
|
|
482
|
+
const selected = targets.includes(t);
|
|
483
|
+
const isCursor = isFocused && i === cursor;
|
|
484
|
+
return /* @__PURE__ */ jsxs7(Box7, { gap: 1, children: [
|
|
485
|
+
/* @__PURE__ */ jsx7(Text7, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
486
|
+
/* @__PURE__ */ jsx7(Text7, { color: selected ? "green" : "gray", children: selected ? "\u25C9" : "\u25CB" }),
|
|
487
|
+
/* @__PURE__ */ jsx7(Text7, { bold: isCursor, children: t })
|
|
488
|
+
] }, t);
|
|
489
|
+
}),
|
|
490
|
+
isFocused && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " (space to toggle)" })
|
|
491
|
+
] })
|
|
492
|
+
] });
|
|
493
|
+
};
|
|
494
|
+
var EnabledToggleRow = ({ enabled, onToggle, autoFocus, isLast, onSubmitAll }) => {
|
|
495
|
+
const { isFocused } = useFocus3({ autoFocus });
|
|
496
|
+
const { focusNext, focusPrevious } = useFocusManager3();
|
|
497
|
+
useInput4((input, key) => {
|
|
498
|
+
if (!isFocused) return;
|
|
499
|
+
if (key.tab && !key.shift) {
|
|
500
|
+
if (!isLast) focusNext();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (key.tab && key.shift) {
|
|
504
|
+
focusPrevious();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (key.downArrow) {
|
|
508
|
+
if (!isLast) focusNext();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (key.upArrow) {
|
|
512
|
+
focusPrevious();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (input === " " || key.leftArrow || key.rightArrow) {
|
|
516
|
+
onToggle();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (key.return) {
|
|
520
|
+
onSubmitAll();
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
return /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
|
|
524
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !isFocused, bold: isFocused, children: "Enabled".padEnd(14) }),
|
|
525
|
+
/* @__PURE__ */ jsx7(Text7, { color: enabled ? "green" : "red", bold: isFocused, children: enabled ? "Yes" : "No" }),
|
|
526
|
+
isFocused && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(space/arrows to toggle)" })
|
|
527
|
+
] });
|
|
528
|
+
};
|
|
529
|
+
var ForceMajorToggleRow = ({ forceMajor, onToggle, autoFocus, isLast, onSubmitAll }) => {
|
|
530
|
+
const { isFocused } = useFocus3({ autoFocus });
|
|
531
|
+
const { focusPrevious, focusNext } = useFocusManager3();
|
|
532
|
+
useInput4((input, key) => {
|
|
533
|
+
if (!isFocused) return;
|
|
534
|
+
if (key.tab && !key.shift) {
|
|
535
|
+
if (!isLast) focusNext();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (key.tab && key.shift) {
|
|
539
|
+
focusPrevious();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (key.downArrow) {
|
|
543
|
+
if (!isLast) focusNext();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (key.upArrow) {
|
|
547
|
+
focusPrevious();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (input === " " || key.leftArrow || key.rightArrow) {
|
|
551
|
+
onToggle();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (key.return) {
|
|
555
|
+
onSubmitAll();
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
return /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
|
|
559
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !isFocused, bold: isFocused, children: "Force major".padEnd(14) }),
|
|
560
|
+
/* @__PURE__ */ jsx7(Text7, { color: forceMajor ? "yellow" : "gray", bold: isFocused, children: forceMajor ? "Yes" : "No" }),
|
|
561
|
+
isFocused && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(space/arrows to toggle)" })
|
|
562
|
+
] });
|
|
563
|
+
};
|
|
564
|
+
var UpdateSettingsPrompt = ({
|
|
565
|
+
name: initialName,
|
|
566
|
+
targets: initialTargets,
|
|
567
|
+
availableTargets,
|
|
568
|
+
bundleUrl: initialBundleUrl,
|
|
569
|
+
enabled: initialEnabled,
|
|
570
|
+
onSubmit,
|
|
571
|
+
onBack
|
|
572
|
+
}) => {
|
|
573
|
+
const [nameValue, setNameValue] = useState4(initialName);
|
|
574
|
+
const [targetsValue, setTargetsValue] = useState4(initialTargets);
|
|
575
|
+
const [bundleUrlValue, setBundleUrlValue] = useState4(initialBundleUrl);
|
|
576
|
+
const [enabledValue, setEnabledValue] = useState4(initialEnabled);
|
|
577
|
+
const [forceMajorValue, setForceMajorValue] = useState4(false);
|
|
578
|
+
const handleToggleTarget = (target) => {
|
|
579
|
+
setTargetsValue((prev) => prev.includes(target) ? prev.filter((t) => t !== target) : [...prev, target]);
|
|
580
|
+
};
|
|
581
|
+
const handleSubmitAll = () => {
|
|
582
|
+
if (targetsValue.length === 0) return;
|
|
583
|
+
onSubmit({
|
|
584
|
+
name: nameValue,
|
|
585
|
+
targets: targetsValue,
|
|
586
|
+
bundleUrl: bundleUrlValue,
|
|
587
|
+
enabled: enabledValue,
|
|
588
|
+
forceMajor: forceMajorValue
|
|
589
|
+
});
|
|
590
|
+
};
|
|
591
|
+
return /* @__PURE__ */ jsx7(
|
|
592
|
+
StepShell,
|
|
593
|
+
{
|
|
594
|
+
title: "Update Extension settings",
|
|
595
|
+
hint: "Tab/arrows to navigate, Enter to submit",
|
|
596
|
+
onBack,
|
|
597
|
+
children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
|
|
598
|
+
/* @__PURE__ */ jsx7(
|
|
599
|
+
FieldRow2,
|
|
600
|
+
{
|
|
601
|
+
label: "Name",
|
|
602
|
+
value: nameValue,
|
|
603
|
+
onChange: setNameValue,
|
|
604
|
+
placeholder: initialName,
|
|
605
|
+
autoFocus: true,
|
|
606
|
+
isFirst: true,
|
|
607
|
+
onSubmitAll: handleSubmitAll
|
|
608
|
+
}
|
|
609
|
+
),
|
|
610
|
+
/* @__PURE__ */ jsx7(
|
|
611
|
+
TargetToggleRow,
|
|
612
|
+
{
|
|
613
|
+
targets: targetsValue,
|
|
614
|
+
availableTargets,
|
|
615
|
+
onToggle: handleToggleTarget,
|
|
616
|
+
onSubmitAll: handleSubmitAll
|
|
617
|
+
}
|
|
618
|
+
),
|
|
619
|
+
/* @__PURE__ */ jsx7(
|
|
620
|
+
FieldRow2,
|
|
621
|
+
{
|
|
622
|
+
label: "Bundle URL",
|
|
623
|
+
value: bundleUrlValue,
|
|
624
|
+
onChange: setBundleUrlValue,
|
|
625
|
+
placeholder: "http://localhost:5173",
|
|
626
|
+
onSubmitAll: handleSubmitAll
|
|
627
|
+
}
|
|
628
|
+
),
|
|
629
|
+
/* @__PURE__ */ jsx7(
|
|
630
|
+
EnabledToggleRow,
|
|
631
|
+
{
|
|
632
|
+
enabled: enabledValue,
|
|
633
|
+
onToggle: () => setEnabledValue((v) => !v),
|
|
634
|
+
onSubmitAll: handleSubmitAll
|
|
635
|
+
}
|
|
636
|
+
),
|
|
637
|
+
/* @__PURE__ */ jsx7(
|
|
638
|
+
ForceMajorToggleRow,
|
|
639
|
+
{
|
|
640
|
+
forceMajor: forceMajorValue,
|
|
641
|
+
onToggle: () => setForceMajorValue((v) => !v),
|
|
642
|
+
isLast: true,
|
|
643
|
+
onSubmitAll: handleSubmitAll
|
|
644
|
+
}
|
|
645
|
+
)
|
|
646
|
+
] })
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
};
|
|
650
|
+
|
|
356
651
|
// src/components/ScaffoldProgress.tsx
|
|
357
|
-
import { Box as
|
|
652
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
358
653
|
import Spinner from "ink-spinner";
|
|
359
|
-
import { jsx as
|
|
654
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
360
655
|
var stepIcon = (status) => {
|
|
361
656
|
switch (status) {
|
|
362
657
|
case "running":
|
|
363
|
-
return /* @__PURE__ */
|
|
658
|
+
return /* @__PURE__ */ jsx8(Spinner, { type: "dots" });
|
|
364
659
|
case "done":
|
|
365
|
-
return /* @__PURE__ */
|
|
660
|
+
return /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2714" });
|
|
366
661
|
case "error":
|
|
367
|
-
return /* @__PURE__ */
|
|
662
|
+
return /* @__PURE__ */ jsx8(Text8, { color: "red", children: "\u2716" });
|
|
368
663
|
default:
|
|
369
|
-
return /* @__PURE__ */
|
|
664
|
+
return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u25CB" });
|
|
370
665
|
}
|
|
371
666
|
};
|
|
372
|
-
var ScaffoldProgress = ({ steps }) => /* @__PURE__ */
|
|
373
|
-
/* @__PURE__ */
|
|
374
|
-
/* @__PURE__ */
|
|
667
|
+
var ScaffoldProgress = ({ steps }) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
|
|
668
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Scaffolding your Extension\u2026" }),
|
|
669
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: steps.map((step) => /* @__PURE__ */ jsxs8(Box8, { gap: 2, children: [
|
|
375
670
|
stepIcon(step.status),
|
|
376
|
-
/* @__PURE__ */
|
|
671
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: step.status === "pending", color: step.status === "running" ? "cyan" : void 0, children: step.label })
|
|
377
672
|
] }, step.label)) })
|
|
378
673
|
] });
|
|
379
674
|
|
|
380
675
|
// src/components/TargetSelect.tsx
|
|
381
|
-
import { Box as
|
|
676
|
+
import { Box as Box9, Text as Text9, useInput as useInput5 } from "ink";
|
|
382
677
|
import { useState as useState5 } from "react";
|
|
383
|
-
import { jsx as
|
|
678
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
384
679
|
var TARGET_DESCRIPTIONS = {
|
|
385
680
|
"slot.header": "Renders content in the panel header area",
|
|
386
681
|
"slot.content": "Renders the main panel body (includes store + navigation state)",
|
|
@@ -389,30 +684,20 @@ var TARGET_DESCRIPTIONS = {
|
|
|
389
684
|
};
|
|
390
685
|
var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
|
|
391
686
|
const [cursor, setCursor] = useState5(0);
|
|
392
|
-
const [backFocused, setBackFocused] = useState5(false);
|
|
393
687
|
const [selected, setSelected] = useState5(
|
|
394
688
|
new Set(preSelected ?? (availableTargets.includes("slot.content") ? ["slot.content"] : []))
|
|
395
689
|
);
|
|
396
690
|
const [error, setError] = useState5();
|
|
397
|
-
|
|
691
|
+
useInput5((input, key) => {
|
|
398
692
|
if (key.upArrow) {
|
|
399
|
-
|
|
400
|
-
setBackFocused(true);
|
|
401
|
-
} else {
|
|
402
|
-
setBackFocused(false);
|
|
403
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
404
|
-
}
|
|
693
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
405
694
|
return;
|
|
406
695
|
}
|
|
407
696
|
if (key.downArrow) {
|
|
408
|
-
|
|
409
|
-
setBackFocused(false);
|
|
410
|
-
} else {
|
|
411
|
-
setCursor((c) => Math.min(availableTargets.length - 1, c + 1));
|
|
412
|
-
}
|
|
697
|
+
setCursor((c) => Math.min(availableTargets.length - 1, c + 1));
|
|
413
698
|
return;
|
|
414
699
|
}
|
|
415
|
-
if (input === " "
|
|
700
|
+
if (input === " ") {
|
|
416
701
|
const target = availableTargets[cursor];
|
|
417
702
|
setSelected((prev) => {
|
|
418
703
|
const next = new Set(prev);
|
|
@@ -427,10 +712,6 @@ var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
|
|
|
427
712
|
return;
|
|
428
713
|
}
|
|
429
714
|
if (key.return) {
|
|
430
|
-
if (backFocused) {
|
|
431
|
-
onBack?.();
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
715
|
if (selected.size === 0) {
|
|
435
716
|
setError("Select at least one Surface target/slot");
|
|
436
717
|
return;
|
|
@@ -438,36 +719,35 @@ var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
|
|
|
438
719
|
onSubmit([...selected]);
|
|
439
720
|
}
|
|
440
721
|
});
|
|
441
|
-
return /* @__PURE__ */
|
|
722
|
+
return /* @__PURE__ */ jsxs9(
|
|
442
723
|
StepShell,
|
|
443
724
|
{
|
|
444
725
|
title: "Select Surface targets/slots",
|
|
445
726
|
hint: "Space to toggle, Enter to confirm",
|
|
446
727
|
onBack,
|
|
447
|
-
backFocused,
|
|
448
728
|
children: [
|
|
449
|
-
/* @__PURE__ */
|
|
729
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", gap: 1, children: availableTargets.map((target, i) => {
|
|
450
730
|
const isSelected = selected.has(target);
|
|
451
|
-
const isCursor = i === cursor
|
|
452
|
-
return /* @__PURE__ */
|
|
453
|
-
/* @__PURE__ */
|
|
454
|
-
/* @__PURE__ */
|
|
455
|
-
/* @__PURE__ */
|
|
456
|
-
/* @__PURE__ */
|
|
457
|
-
/* @__PURE__ */
|
|
731
|
+
const isCursor = i === cursor;
|
|
732
|
+
return /* @__PURE__ */ jsxs9(Box9, { gap: 1, children: [
|
|
733
|
+
/* @__PURE__ */ jsx9(Text9, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
734
|
+
/* @__PURE__ */ jsx9(Text9, { color: isSelected ? "green" : void 0, children: isSelected ? "\u25C9" : "\u25CB" }),
|
|
735
|
+
/* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
736
|
+
/* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: target }),
|
|
737
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: TARGET_DESCRIPTIONS[target] ?? target })
|
|
458
738
|
] })
|
|
459
739
|
] }, target);
|
|
460
740
|
}) }),
|
|
461
|
-
error && /* @__PURE__ */
|
|
741
|
+
error && /* @__PURE__ */ jsx9(Text9, { color: "red", children: error })
|
|
462
742
|
]
|
|
463
743
|
}
|
|
464
744
|
);
|
|
465
745
|
};
|
|
466
746
|
|
|
467
747
|
// src/components/AppSelect.tsx
|
|
468
|
-
import { Box as
|
|
748
|
+
import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
|
|
469
749
|
import Spinner2 from "ink-spinner";
|
|
470
|
-
import { useEffect, useState as useState6 } from "react";
|
|
750
|
+
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
471
751
|
|
|
472
752
|
// src/lib/api.ts
|
|
473
753
|
var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
|
|
@@ -520,10 +800,22 @@ var fetchExtensions = async (appId) => {
|
|
|
520
800
|
])
|
|
521
801
|
);
|
|
522
802
|
};
|
|
803
|
+
var updateExtension = async (appId, extensionId, payload) => {
|
|
804
|
+
const baseUrl = getAdminApiBaseUrl();
|
|
805
|
+
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions/${extensionId}`, {
|
|
806
|
+
method: "PUT",
|
|
807
|
+
headers: { "content-type": "application/json" },
|
|
808
|
+
body: JSON.stringify(payload)
|
|
809
|
+
});
|
|
810
|
+
if (!res.ok) {
|
|
811
|
+
const body = await res.text();
|
|
812
|
+
throw new Error(`Failed to update extension: ${res.status} ${body}`);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
523
815
|
|
|
524
816
|
// src/components/Banner.tsx
|
|
525
|
-
import { Box as
|
|
526
|
-
import { jsx as
|
|
817
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
818
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
527
819
|
var WORDMARK = [
|
|
528
820
|
" _ _ _ _ ",
|
|
529
821
|
" ___| |_ __ _ ___| | ____ _| |__ | | ___",
|
|
@@ -559,23 +851,23 @@ var gradientColor = (row, col, rows, cols) => {
|
|
|
559
851
|
var Banner = () => {
|
|
560
852
|
const termWidth = process.stdout.columns ?? 80;
|
|
561
853
|
const maxLen = Math.max(...WORDMARK.map((l) => l.length));
|
|
562
|
-
return /* @__PURE__ */
|
|
563
|
-
/* @__PURE__ */
|
|
564
|
-
/* @__PURE__ */
|
|
854
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
855
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2500".repeat(termWidth) }),
|
|
856
|
+
/* @__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)) })
|
|
565
857
|
] });
|
|
566
858
|
};
|
|
567
859
|
|
|
568
860
|
// src/components/AppSelect.tsx
|
|
569
|
-
import { jsx as
|
|
861
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
570
862
|
var AppSelect = ({ onSubmit }) => {
|
|
571
863
|
const [apps, setApps] = useState6([]);
|
|
572
864
|
const [loading, setLoading] = useState6(true);
|
|
573
865
|
const [error, setError] = useState6();
|
|
574
866
|
const [cursor, setCursor] = useState6(0);
|
|
575
|
-
|
|
867
|
+
useEffect2(() => {
|
|
576
868
|
fetchApps().then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
577
869
|
}, []);
|
|
578
|
-
|
|
870
|
+
useInput6((_, key) => {
|
|
579
871
|
if (loading || error || apps.length === 0) return;
|
|
580
872
|
if (key.upArrow) {
|
|
581
873
|
setCursor((c) => Math.max(0, c - 1));
|
|
@@ -587,26 +879,26 @@ var AppSelect = ({ onSubmit }) => {
|
|
|
587
879
|
});
|
|
588
880
|
const renderContent = () => {
|
|
589
881
|
if (loading) {
|
|
590
|
-
return /* @__PURE__ */
|
|
591
|
-
/* @__PURE__ */
|
|
592
|
-
/* @__PURE__ */
|
|
882
|
+
return /* @__PURE__ */ jsxs11(Box11, { gap: 2, children: [
|
|
883
|
+
/* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
|
|
884
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Loading available Apps\u2026" })
|
|
593
885
|
] });
|
|
594
886
|
}
|
|
595
887
|
if (error) {
|
|
596
|
-
return /* @__PURE__ */
|
|
597
|
-
/* @__PURE__ */
|
|
598
|
-
/* @__PURE__ */
|
|
888
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
|
|
889
|
+
/* @__PURE__ */ jsx11(Text11, { color: "red", bold: true, children: "Failed to load Apps" }),
|
|
890
|
+
/* @__PURE__ */ jsx11(Text11, { color: "red", children: error })
|
|
599
891
|
] });
|
|
600
892
|
}
|
|
601
893
|
if (apps.length === 0) {
|
|
602
|
-
return /* @__PURE__ */
|
|
894
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "No Apps available. Contact your administrator." });
|
|
603
895
|
}
|
|
604
|
-
return /* @__PURE__ */
|
|
896
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: apps.map((app, i) => {
|
|
605
897
|
const isCursor = i === cursor;
|
|
606
|
-
return /* @__PURE__ */
|
|
607
|
-
/* @__PURE__ */
|
|
608
|
-
/* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
898
|
+
return /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
|
|
899
|
+
/* @__PURE__ */ jsx11(Text11, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
900
|
+
/* @__PURE__ */ jsx11(Text11, { bold: isCursor, children: app.name }),
|
|
901
|
+
/* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
610
902
|
"(",
|
|
611
903
|
app.id,
|
|
612
904
|
")"
|
|
@@ -614,49 +906,39 @@ var AppSelect = ({ onSubmit }) => {
|
|
|
614
906
|
] }, app.id);
|
|
615
907
|
}) });
|
|
616
908
|
};
|
|
617
|
-
return /* @__PURE__ */
|
|
618
|
-
/* @__PURE__ */
|
|
619
|
-
/* @__PURE__ */
|
|
909
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
910
|
+
/* @__PURE__ */ jsx11(Banner, {}),
|
|
911
|
+
/* @__PURE__ */ jsx11(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
|
|
620
912
|
] });
|
|
621
913
|
};
|
|
622
914
|
|
|
623
915
|
// src/components/ExtensionSelect.tsx
|
|
624
|
-
import { Box as
|
|
916
|
+
import { Box as Box12, Text as Text12, useInput as useInput7 } from "ink";
|
|
625
917
|
import Spinner3 from "ink-spinner";
|
|
626
|
-
import { useEffect as
|
|
627
|
-
import { jsx as
|
|
628
|
-
var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
|
|
918
|
+
import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
919
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
920
|
+
var ExtensionSelect = ({ appId, command, onSubmit, onBack }) => {
|
|
629
921
|
const [extensions, setExtensions] = useState7([]);
|
|
630
922
|
const [loading, setLoading] = useState7(true);
|
|
631
923
|
const [error, setError] = useState7();
|
|
632
924
|
const [cursor, setCursor] = useState7(0);
|
|
633
|
-
|
|
634
|
-
useEffect2(() => {
|
|
925
|
+
useEffect3(() => {
|
|
635
926
|
fetchExtensions(appId).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
636
927
|
}, [appId]);
|
|
637
|
-
|
|
928
|
+
useInput7((_, key) => {
|
|
638
929
|
if (key.upArrow) {
|
|
639
|
-
if (loading
|
|
640
|
-
if (onBack) setBackFocused(true);
|
|
641
|
-
} else {
|
|
642
|
-
setBackFocused(false);
|
|
930
|
+
if (!loading && !error && extensions.length > 0) {
|
|
643
931
|
setCursor((c) => Math.max(0, c - 1));
|
|
644
932
|
}
|
|
645
933
|
return;
|
|
646
934
|
}
|
|
647
935
|
if (key.downArrow) {
|
|
648
|
-
if (
|
|
649
|
-
setBackFocused(false);
|
|
650
|
-
} else if (!loading && !error && extensions.length > 0) {
|
|
936
|
+
if (!loading && !error && extensions.length > 0) {
|
|
651
937
|
setCursor((c) => Math.min(extensions.length - 1, c + 1));
|
|
652
938
|
}
|
|
653
939
|
return;
|
|
654
940
|
}
|
|
655
941
|
if (key.return) {
|
|
656
|
-
if (backFocused) {
|
|
657
|
-
onBack?.();
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
942
|
if (!loading && !error && extensions.length > 0) {
|
|
661
943
|
onSubmit(extensions[cursor]);
|
|
662
944
|
}
|
|
@@ -664,30 +946,30 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
|
|
|
664
946
|
});
|
|
665
947
|
const renderContent = () => {
|
|
666
948
|
if (loading) {
|
|
667
|
-
return /* @__PURE__ */
|
|
668
|
-
/* @__PURE__ */
|
|
669
|
-
/* @__PURE__ */
|
|
949
|
+
return /* @__PURE__ */ jsxs12(Box12, { gap: 2, children: [
|
|
950
|
+
/* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
|
|
951
|
+
/* @__PURE__ */ jsx12(Text12, { children: "Loading Extensions\u2026" })
|
|
670
952
|
] });
|
|
671
953
|
}
|
|
672
954
|
if (error) {
|
|
673
|
-
return /* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
675
|
-
/* @__PURE__ */
|
|
955
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
|
|
956
|
+
/* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "Failed to load Extensions" }),
|
|
957
|
+
/* @__PURE__ */ jsx12(Text12, { color: "red", children: error })
|
|
676
958
|
] });
|
|
677
959
|
}
|
|
678
960
|
if (extensions.length === 0) {
|
|
679
|
-
return /* @__PURE__ */
|
|
961
|
+
return /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "No Extensions found for this App." });
|
|
680
962
|
}
|
|
681
|
-
return /* @__PURE__ */
|
|
682
|
-
const isCursor = i === cursor
|
|
683
|
-
return /* @__PURE__ */
|
|
684
|
-
/* @__PURE__ */
|
|
685
|
-
/* @__PURE__ */
|
|
686
|
-
/* @__PURE__ */
|
|
963
|
+
return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: extensions.map((ext, i) => {
|
|
964
|
+
const isCursor = i === cursor;
|
|
965
|
+
return /* @__PURE__ */ jsxs12(Box12, { gap: 1, children: [
|
|
966
|
+
/* @__PURE__ */ jsx12(Text12, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
967
|
+
/* @__PURE__ */ jsx12(Text12, { bold: isCursor, children: ext.manifest.name }),
|
|
968
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
687
969
|
"v",
|
|
688
970
|
ext.manifest.version
|
|
689
971
|
] }),
|
|
690
|
-
/* @__PURE__ */
|
|
972
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
691
973
|
"(",
|
|
692
974
|
ext.id,
|
|
693
975
|
")"
|
|
@@ -695,7 +977,14 @@ var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
|
|
|
695
977
|
] }, ext.id);
|
|
696
978
|
}) });
|
|
697
979
|
};
|
|
698
|
-
return /* @__PURE__ */
|
|
980
|
+
return /* @__PURE__ */ jsx12(
|
|
981
|
+
StepShell,
|
|
982
|
+
{
|
|
983
|
+
title: command === "update" /* UPDATE */ ? "Select an Extension to update:" : "Select an existing Extension to scaffold:",
|
|
984
|
+
onBack,
|
|
985
|
+
children: renderContent()
|
|
986
|
+
}
|
|
987
|
+
);
|
|
699
988
|
};
|
|
700
989
|
|
|
701
990
|
// src/constants.ts
|
|
@@ -803,6 +1092,29 @@ var generateManifest = async (rootDir, extensionName, targets, permissions) => {
|
|
|
803
1092
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
804
1093
|
`);
|
|
805
1094
|
};
|
|
1095
|
+
var buildFooterSurface = (targets) => {
|
|
1096
|
+
const blocks = [];
|
|
1097
|
+
if (targets.includes("slot.footer")) {
|
|
1098
|
+
blocks.push(
|
|
1099
|
+
' <Surface id="slot.footer">\n <ui.Text className="text-xs">Powered by My Extension</ui.Text>\n </Surface>'
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
if (targets.includes("slot.footer-links")) {
|
|
1103
|
+
blocks.push(
|
|
1104
|
+
' <Surface id="slot.footer-links">\n <ui.FooterLink href="https://example.com">My Extension</ui.FooterLink>\n </Surface>'
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
return `import { ui, Surface } from '@stackable-labs/sdk-extension-react'
|
|
1108
|
+
|
|
1109
|
+
export function Footer() {
|
|
1110
|
+
return (
|
|
1111
|
+
<>
|
|
1112
|
+
${blocks.join("\n")}
|
|
1113
|
+
</>
|
|
1114
|
+
)
|
|
1115
|
+
}
|
|
1116
|
+
`;
|
|
1117
|
+
};
|
|
806
1118
|
var generateSurfaceFiles = async (rootDir, targets) => {
|
|
807
1119
|
const surfaceDir = join(rootDir, "packages/extension/src/surfaces");
|
|
808
1120
|
const wantsHeader = targets.includes("slot.header");
|
|
@@ -862,29 +1174,6 @@ export function Content() {
|
|
|
862
1174
|
);
|
|
863
1175
|
await upsertOrRemove(join(surfaceDir, "Footer.tsx"), wantsFooter, buildFooterSurface(targets));
|
|
864
1176
|
};
|
|
865
|
-
var buildFooterSurface = (targets) => {
|
|
866
|
-
const blocks = [];
|
|
867
|
-
if (targets.includes("slot.footer")) {
|
|
868
|
-
blocks.push(
|
|
869
|
-
' <Surface id="slot.footer">\n <ui.Text className="text-xs">Powered by My Extension</ui.Text>\n </Surface>'
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
if (targets.includes("slot.footer-links")) {
|
|
873
|
-
blocks.push(
|
|
874
|
-
' <Surface id="slot.footer-links">\n <ui.FooterLink href="https://example.com">My Extension</ui.FooterLink>\n </Surface>'
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
return `import { ui, Surface } from '@stackable-labs/sdk-extension-react'
|
|
878
|
-
|
|
879
|
-
export function Footer() {
|
|
880
|
-
return (
|
|
881
|
-
<>
|
|
882
|
-
${blocks.join("\n")}
|
|
883
|
-
</>
|
|
884
|
-
)
|
|
885
|
-
}
|
|
886
|
-
`;
|
|
887
|
-
};
|
|
888
1177
|
var rewriteExtensionIndex = async (rootDir, extensionId, targets) => {
|
|
889
1178
|
const indexPath = join(rootDir, "packages/extension/src/index.tsx");
|
|
890
1179
|
const imports = ["import { createExtension } from '@stackable-labs/sdk-extension-react'"];
|
|
@@ -1009,6 +1298,20 @@ VITE_PREVIEW_PORT=${previewPort}
|
|
|
1009
1298
|
`;
|
|
1010
1299
|
await writeFile(envPath, content);
|
|
1011
1300
|
};
|
|
1301
|
+
var updateGitignore = async (dir) => {
|
|
1302
|
+
const gitignorePath = join(dir, ".gitignore");
|
|
1303
|
+
let gitignore = "";
|
|
1304
|
+
try {
|
|
1305
|
+
gitignore = await readFile(gitignorePath, "utf8");
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
if (!gitignore.includes(".env.stackable")) {
|
|
1309
|
+
const newGitignore = gitignore ? `${gitignore}
|
|
1310
|
+
.env.stackable
|
|
1311
|
+
` : ".env.stackable\n";
|
|
1312
|
+
await writeFile(gitignorePath, newGitignore);
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1012
1315
|
var scaffold = async (options) => {
|
|
1013
1316
|
const { dir } = await downloadTemplate(TEMPLATE_SOURCE, {
|
|
1014
1317
|
dir: options.outputDir,
|
|
@@ -1028,14 +1331,18 @@ var scaffold = async (options) => {
|
|
|
1028
1331
|
await rewritePreviewApp(dir, selectedTargets, derivedPermissions);
|
|
1029
1332
|
await rewriteTurboJson(dir);
|
|
1030
1333
|
await writeEnvFile(dir, options.extensionPort, options.previewPort);
|
|
1334
|
+
await updateGitignore(dir);
|
|
1031
1335
|
return options;
|
|
1032
1336
|
};
|
|
1033
1337
|
|
|
1034
1338
|
// src/App.tsx
|
|
1035
|
-
import { jsx as
|
|
1339
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1036
1340
|
var STEPS = {
|
|
1037
1341
|
["create" /* CREATE */]: ["app", "name", "targets", "settings", "confirm"],
|
|
1038
|
-
["scaffold" /* SCAFFOLD */]: ["app", "extensionSelect", "confirmTargets", "settings", "confirm"]
|
|
1342
|
+
["scaffold" /* SCAFFOLD */]: ["app", "extensionSelect", "confirmTargets", "settings", "confirm"],
|
|
1343
|
+
["update" /* UPDATE */]: ["app", "extensionSelect", "updateSettings", "confirm"],
|
|
1344
|
+
["dev" /* DEV */]: []
|
|
1345
|
+
// Dev command has no wizard steps
|
|
1039
1346
|
};
|
|
1040
1347
|
var PROGRESS_STEPS = {
|
|
1041
1348
|
["create" /* CREATE */]: [
|
|
@@ -1048,7 +1355,12 @@ var PROGRESS_STEPS = {
|
|
|
1048
1355
|
{ label: "Fetching template", status: "pending" },
|
|
1049
1356
|
{ label: "Generating files", status: "pending" },
|
|
1050
1357
|
{ label: "Installing dependencies", status: "pending" }
|
|
1051
|
-
]
|
|
1358
|
+
],
|
|
1359
|
+
["update" /* UPDATE */]: [
|
|
1360
|
+
{ label: "Updating extension", status: "pending" }
|
|
1361
|
+
],
|
|
1362
|
+
["dev" /* DEV */]: []
|
|
1363
|
+
// Dev command has no progress steps
|
|
1052
1364
|
};
|
|
1053
1365
|
var toKebabCase2 = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1054
1366
|
var derivePermissions2 = (targets) => {
|
|
@@ -1068,12 +1380,22 @@ var derivePermissions2 = (targets) => {
|
|
|
1068
1380
|
}
|
|
1069
1381
|
return [...permissions];
|
|
1070
1382
|
};
|
|
1071
|
-
var App = ({ command, initialName, options }) => {
|
|
1383
|
+
var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
1072
1384
|
const { exit } = useApp();
|
|
1073
1385
|
const [step, setStep] = useState8("app");
|
|
1074
|
-
const [name, setName] = useState8(initialName ?? "");
|
|
1075
|
-
const [extensionId, setExtensionId] = useState8("");
|
|
1386
|
+
const [name, setName] = useState8(initialName ?? options?.name ?? "");
|
|
1387
|
+
const [extensionId, setExtensionId] = useState8(initialExtensionId ?? "");
|
|
1076
1388
|
const [extensionVersion, setExtensionVersion] = useState8("");
|
|
1389
|
+
const [bundleUrl, setBundleUrl] = useState8(options?.bundleUrl ?? "");
|
|
1390
|
+
const [enabled, setEnabled] = useState8(
|
|
1391
|
+
options?.enabled !== void 0 ? options.enabled !== "false" : true
|
|
1392
|
+
);
|
|
1393
|
+
const [versionOverride, setVersionOverride] = useState8(void 0);
|
|
1394
|
+
const [forceMajor, setForceMajor] = useState8(false);
|
|
1395
|
+
const [initialExtension, setInitialExtension] = useState8(null);
|
|
1396
|
+
const [targets, setTargets] = useState8(
|
|
1397
|
+
options?.targets ? options.targets.split(",").map((t) => t.trim()) : []
|
|
1398
|
+
);
|
|
1077
1399
|
const [selectedApp, setSelectedApp] = useState8(null);
|
|
1078
1400
|
const [extensionPort, setExtensionPort] = useState8(
|
|
1079
1401
|
options?.extensionPort ? parseInt(options.extensionPort, 10) : 5173
|
|
@@ -1081,7 +1403,6 @@ var App = ({ command, initialName, options }) => {
|
|
|
1081
1403
|
const [previewPort, setPreviewPort] = useState8(
|
|
1082
1404
|
options?.previewPort ? parseInt(options.previewPort, 10) : 5174
|
|
1083
1405
|
);
|
|
1084
|
-
const [targets, setTargets] = useState8([]);
|
|
1085
1406
|
const [outputDir, setOutputDir] = useState8("");
|
|
1086
1407
|
const [progressSteps, setProgressSteps] = useState8(PROGRESS_STEPS[command]);
|
|
1087
1408
|
const [errorMessage, setErrorMessage] = useState8();
|
|
@@ -1093,8 +1414,19 @@ var App = ({ command, initialName, options }) => {
|
|
|
1093
1414
|
const skipped = /* @__PURE__ */ new Set();
|
|
1094
1415
|
if (command === "create" /* CREATE */ && initialName) skipped.add("name");
|
|
1095
1416
|
if (options?.extensionPort || options?.previewPort) skipped.add("settings");
|
|
1417
|
+
if (command === "update" /* UPDATE */) {
|
|
1418
|
+
if (options?.appId) skipped.add("app");
|
|
1419
|
+
if (initialExtensionId) skipped.add("extensionSelect");
|
|
1420
|
+
}
|
|
1096
1421
|
return base.filter((s) => !skipped.has(s));
|
|
1097
|
-
}, [
|
|
1422
|
+
}, [
|
|
1423
|
+
command,
|
|
1424
|
+
initialName,
|
|
1425
|
+
initialExtensionId,
|
|
1426
|
+
options?.extensionPort,
|
|
1427
|
+
options?.previewPort,
|
|
1428
|
+
options?.appId
|
|
1429
|
+
]);
|
|
1098
1430
|
const goBack = useCallback(() => {
|
|
1099
1431
|
setStep((prev) => {
|
|
1100
1432
|
const steps = activeSteps();
|
|
@@ -1104,7 +1436,7 @@ var App = ({ command, initialName, options }) => {
|
|
|
1104
1436
|
}, [activeSteps]);
|
|
1105
1437
|
const handleAppSelect = (app) => {
|
|
1106
1438
|
setSelectedApp(app);
|
|
1107
|
-
if (command === "scaffold" /* SCAFFOLD */) {
|
|
1439
|
+
if (command === "scaffold" /* SCAFFOLD */ || command === "update" /* UPDATE */) {
|
|
1108
1440
|
setStep("extensionSelect");
|
|
1109
1441
|
return;
|
|
1110
1442
|
}
|
|
@@ -1115,7 +1447,18 @@ var App = ({ command, initialName, options }) => {
|
|
|
1115
1447
|
setExtensionId(ext.id);
|
|
1116
1448
|
setExtensionVersion(ext.manifest.version);
|
|
1117
1449
|
setTargets(ext.manifest.targets);
|
|
1118
|
-
|
|
1450
|
+
setBundleUrl(ext.bundleUrl);
|
|
1451
|
+
setEnabled(ext.enabled ?? true);
|
|
1452
|
+
setInitialExtension({
|
|
1453
|
+
name: ext.manifest.name,
|
|
1454
|
+
targets: ext.manifest.targets,
|
|
1455
|
+
bundleUrl: ext.bundleUrl
|
|
1456
|
+
});
|
|
1457
|
+
if (command === "scaffold" /* SCAFFOLD */) {
|
|
1458
|
+
setStep("confirmTargets");
|
|
1459
|
+
} else if (command === "update" /* UPDATE */) {
|
|
1460
|
+
setStep("updateSettings");
|
|
1461
|
+
}
|
|
1119
1462
|
};
|
|
1120
1463
|
const handleConfirmTargets = (value) => {
|
|
1121
1464
|
setTargets(value);
|
|
@@ -1146,7 +1489,45 @@ var App = ({ command, initialName, options }) => {
|
|
|
1146
1489
|
setOutputDir(dir);
|
|
1147
1490
|
setStep("confirm");
|
|
1148
1491
|
};
|
|
1492
|
+
const computeVersionBump = (currentVersion, newName, newTargets, newBundleUrl, isForceMajor) => {
|
|
1493
|
+
const [major, minor, patch] = currentVersion.split(".").map(Number);
|
|
1494
|
+
if (!initialExtension) return `${major}.${minor}.${patch + 1}`;
|
|
1495
|
+
if (isForceMajor) return `${major + 1}.0.0`;
|
|
1496
|
+
const bundleUrlChanged = newBundleUrl !== initialExtension.bundleUrl;
|
|
1497
|
+
if (bundleUrlChanged) return `${major + 1}.0.0`;
|
|
1498
|
+
const targetsChanged = newTargets.length !== initialExtension.targets.length || newTargets.some((t) => !initialExtension.targets.includes(t));
|
|
1499
|
+
if (targetsChanged) return `${major}.${minor + 1}.0`;
|
|
1500
|
+
const nameChanged = newName !== initialExtension.name;
|
|
1501
|
+
if (nameChanged) return `${major}.${minor}.${patch + 1}`;
|
|
1502
|
+
return currentVersion;
|
|
1503
|
+
};
|
|
1149
1504
|
const handleConfirm = async () => {
|
|
1505
|
+
if (command === "update" /* UPDATE */) {
|
|
1506
|
+
setStep("updating");
|
|
1507
|
+
setProgressSteps(PROGRESS_STEPS[command]);
|
|
1508
|
+
try {
|
|
1509
|
+
updateStep(0, "running");
|
|
1510
|
+
const resolvedVersion = options?.setVersion ?? versionOverride ?? computeVersionBump(extensionVersion, name, targets, bundleUrl, forceMajor);
|
|
1511
|
+
await updateExtension(selectedApp.id, extensionId, {
|
|
1512
|
+
manifest: {
|
|
1513
|
+
name,
|
|
1514
|
+
version: resolvedVersion,
|
|
1515
|
+
targets,
|
|
1516
|
+
permissions: derivePermissions2(targets)
|
|
1517
|
+
},
|
|
1518
|
+
bundleUrl: bundleUrl || void 0,
|
|
1519
|
+
enabled
|
|
1520
|
+
});
|
|
1521
|
+
updateStep(0, "done");
|
|
1522
|
+
setExtensionVersion(resolvedVersion);
|
|
1523
|
+
setStep("updateDone");
|
|
1524
|
+
} catch (err) {
|
|
1525
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1526
|
+
setErrorMessage(message);
|
|
1527
|
+
setStep("error");
|
|
1528
|
+
}
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1150
1531
|
setStep("scaffolding");
|
|
1151
1532
|
setProgressSteps(PROGRESS_STEPS[command]);
|
|
1152
1533
|
try {
|
|
@@ -1201,20 +1582,21 @@ var App = ({ command, initialName, options }) => {
|
|
|
1201
1582
|
};
|
|
1202
1583
|
switch (step) {
|
|
1203
1584
|
case "app": {
|
|
1204
|
-
return /* @__PURE__ */
|
|
1585
|
+
return /* @__PURE__ */ jsx13(AppSelect, { onSubmit: handleAppSelect });
|
|
1205
1586
|
}
|
|
1206
1587
|
case "extensionSelect": {
|
|
1207
|
-
return /* @__PURE__ */
|
|
1588
|
+
return /* @__PURE__ */ jsx13(
|
|
1208
1589
|
ExtensionSelect,
|
|
1209
1590
|
{
|
|
1210
1591
|
appId: selectedApp.id,
|
|
1592
|
+
command,
|
|
1211
1593
|
onSubmit: handleExtensionSelect,
|
|
1212
1594
|
onBack: goBack
|
|
1213
1595
|
}
|
|
1214
1596
|
);
|
|
1215
1597
|
}
|
|
1216
1598
|
case "confirmTargets": {
|
|
1217
|
-
return /* @__PURE__ */
|
|
1599
|
+
return /* @__PURE__ */ jsx13(
|
|
1218
1600
|
TargetSelect,
|
|
1219
1601
|
{
|
|
1220
1602
|
availableTargets: selectedApp?.targets ?? [],
|
|
@@ -1225,7 +1607,7 @@ var App = ({ command, initialName, options }) => {
|
|
|
1225
1607
|
);
|
|
1226
1608
|
}
|
|
1227
1609
|
case "name": {
|
|
1228
|
-
return /* @__PURE__ */
|
|
1610
|
+
return /* @__PURE__ */ jsx13(
|
|
1229
1611
|
NamePrompt,
|
|
1230
1612
|
{
|
|
1231
1613
|
initialValue: name,
|
|
@@ -1235,7 +1617,7 @@ var App = ({ command, initialName, options }) => {
|
|
|
1235
1617
|
);
|
|
1236
1618
|
}
|
|
1237
1619
|
case "targets": {
|
|
1238
|
-
return /* @__PURE__ */
|
|
1620
|
+
return /* @__PURE__ */ jsx13(
|
|
1239
1621
|
TargetSelect,
|
|
1240
1622
|
{
|
|
1241
1623
|
availableTargets: selectedApp?.targets ?? [],
|
|
@@ -1245,7 +1627,7 @@ var App = ({ command, initialName, options }) => {
|
|
|
1245
1627
|
);
|
|
1246
1628
|
}
|
|
1247
1629
|
case "settings": {
|
|
1248
|
-
return /* @__PURE__ */
|
|
1630
|
+
return /* @__PURE__ */ jsx13(
|
|
1249
1631
|
SettingsPrompt,
|
|
1250
1632
|
{
|
|
1251
1633
|
defaultDir: join2(process.cwd(), toKebabCase2(extensionId || name)),
|
|
@@ -1255,15 +1637,20 @@ var App = ({ command, initialName, options }) => {
|
|
|
1255
1637
|
);
|
|
1256
1638
|
}
|
|
1257
1639
|
case "confirm": {
|
|
1258
|
-
return /* @__PURE__ */
|
|
1640
|
+
return /* @__PURE__ */ jsx13(
|
|
1259
1641
|
Confirm,
|
|
1260
1642
|
{
|
|
1643
|
+
command,
|
|
1261
1644
|
name,
|
|
1262
1645
|
extensionPort,
|
|
1263
1646
|
previewPort,
|
|
1264
1647
|
targets,
|
|
1265
1648
|
outputDir,
|
|
1266
|
-
extensionVersion: command
|
|
1649
|
+
extensionVersion: command !== "create" /* CREATE */ ? extensionVersion : void 0,
|
|
1650
|
+
bundleUrl: command === "update" /* UPDATE */ ? bundleUrl : void 0,
|
|
1651
|
+
enabled: command === "update" /* UPDATE */ ? enabled : void 0,
|
|
1652
|
+
newVersion: command === "update" /* UPDATE */ ? options?.setVersion ?? versionOverride ?? computeVersionBump(extensionVersion, name, targets, bundleUrl, forceMajor) : void 0,
|
|
1653
|
+
onVersionOverride: command === "update" /* UPDATE */ ? setVersionOverride : void 0,
|
|
1267
1654
|
onConfirm: handleConfirm,
|
|
1268
1655
|
onCancel: handleCancel,
|
|
1269
1656
|
onBack: goBack
|
|
@@ -1271,29 +1658,391 @@ var App = ({ command, initialName, options }) => {
|
|
|
1271
1658
|
);
|
|
1272
1659
|
}
|
|
1273
1660
|
case "scaffolding": {
|
|
1274
|
-
return /* @__PURE__ */
|
|
1661
|
+
return /* @__PURE__ */ jsx13(ScaffoldProgress, { steps: progressSteps });
|
|
1662
|
+
}
|
|
1663
|
+
case "updateSettings": {
|
|
1664
|
+
return /* @__PURE__ */ jsx13(
|
|
1665
|
+
UpdateSettingsPrompt,
|
|
1666
|
+
{
|
|
1667
|
+
name,
|
|
1668
|
+
targets,
|
|
1669
|
+
availableTargets: selectedApp?.targets ?? [],
|
|
1670
|
+
bundleUrl,
|
|
1671
|
+
enabled,
|
|
1672
|
+
onSubmit: (updated) => {
|
|
1673
|
+
setName(updated.name);
|
|
1674
|
+
setTargets(updated.targets);
|
|
1675
|
+
setBundleUrl(updated.bundleUrl);
|
|
1676
|
+
setEnabled(updated.enabled);
|
|
1677
|
+
setForceMajor(updated.forceMajor);
|
|
1678
|
+
setVersionOverride(void 0);
|
|
1679
|
+
setStep("confirm");
|
|
1680
|
+
},
|
|
1681
|
+
onBack: goBack
|
|
1682
|
+
}
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
case "updating": {
|
|
1686
|
+
return /* @__PURE__ */ jsx13(ScaffoldProgress, { steps: progressSteps });
|
|
1687
|
+
}
|
|
1688
|
+
case "updateDone": {
|
|
1689
|
+
return /* @__PURE__ */ jsx13(StepShell, { title: "Extension updated", onBack: goBack, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1690
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", bold: true, children: "Extension updated successfully!" }),
|
|
1691
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
1692
|
+
"Name: ",
|
|
1693
|
+
name
|
|
1694
|
+
] }),
|
|
1695
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
1696
|
+
"ID: ",
|
|
1697
|
+
extensionId
|
|
1698
|
+
] }),
|
|
1699
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
1700
|
+
"Version: ",
|
|
1701
|
+
extensionVersion
|
|
1702
|
+
] })
|
|
1703
|
+
] }) });
|
|
1275
1704
|
}
|
|
1276
1705
|
case "done": {
|
|
1277
|
-
return /* @__PURE__ */
|
|
1706
|
+
return /* @__PURE__ */ jsx13(Done, { name, outputDir });
|
|
1707
|
+
}
|
|
1708
|
+
case "error": {
|
|
1709
|
+
return /* @__PURE__ */ jsx13(StepShell, { title: "Operation failed", onBack: goBack, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1710
|
+
/* @__PURE__ */ jsx13(Text13, { color: "red", bold: true, children: "An error occurred" }),
|
|
1711
|
+
errorMessage && /* @__PURE__ */ jsx13(Text13, { color: "red", children: errorMessage })
|
|
1712
|
+
] }) });
|
|
1278
1713
|
}
|
|
1279
1714
|
default: {
|
|
1280
|
-
return /* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
errorMessage && /* @__PURE__ */
|
|
1715
|
+
return /* @__PURE__ */ jsx13(StepShell, { title: "Scaffold failed", onBack: goBack, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1716
|
+
/* @__PURE__ */ jsx13(Text13, { color: "red", bold: true, children: "An error occurred" }),
|
|
1717
|
+
errorMessage && /* @__PURE__ */ jsx13(Text13, { color: "red", children: errorMessage })
|
|
1283
1718
|
] }) });
|
|
1284
1719
|
}
|
|
1285
1720
|
}
|
|
1286
1721
|
};
|
|
1287
1722
|
|
|
1723
|
+
// src/components/DevApp.tsx
|
|
1724
|
+
import { Box as Box16, Text as Text16 } from "ink";
|
|
1725
|
+
import { useState as useState10, useEffect as useEffect6, useCallback as useCallback2 } from "react";
|
|
1726
|
+
|
|
1727
|
+
// src/lib/devContext.ts
|
|
1728
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
1729
|
+
import { join as join3 } from "path";
|
|
1730
|
+
var parseEnvFile = (content) => {
|
|
1731
|
+
const lines = content.split("\n");
|
|
1732
|
+
const env = {};
|
|
1733
|
+
for (const line of lines) {
|
|
1734
|
+
const trimmed = line.trim();
|
|
1735
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
1736
|
+
const [key, ...rest] = trimmed.split("=");
|
|
1737
|
+
if (key && rest.length > 0) {
|
|
1738
|
+
env[key.trim()] = rest.join("=").trim();
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return env;
|
|
1743
|
+
};
|
|
1744
|
+
var readEnvFile = async (filePath) => {
|
|
1745
|
+
try {
|
|
1746
|
+
const content = await readFile2(filePath, "utf8");
|
|
1747
|
+
return parseEnvFile(content);
|
|
1748
|
+
} catch {
|
|
1749
|
+
return {};
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
var writeEnvFile2 = async (filePath, env) => {
|
|
1753
|
+
const lines = Object.entries(env).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
1754
|
+
await writeFile2(filePath, `${lines}
|
|
1755
|
+
`);
|
|
1756
|
+
};
|
|
1757
|
+
var readDevContext = async (projectRoot) => {
|
|
1758
|
+
const stackableEnv = await readEnvFile(join3(projectRoot, ".env.stackable"));
|
|
1759
|
+
const env = await readEnvFile(join3(projectRoot, ".env"));
|
|
1760
|
+
let extensionName = "Unknown Extension";
|
|
1761
|
+
try {
|
|
1762
|
+
const manifestPath = join3(projectRoot, "packages/extension/public/manifest.json");
|
|
1763
|
+
const manifestContent = await readFile2(manifestPath, "utf8");
|
|
1764
|
+
const manifest = JSON.parse(manifestContent);
|
|
1765
|
+
extensionName = manifest.name;
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
const extensionPort = parseInt(env.VITE_EXTENSION_PORT || stackableEnv.EXTENSION_PORT || "5173", 10);
|
|
1769
|
+
const previewPort = parseInt(env.VITE_PREVIEW_PORT || stackableEnv.PREVIEW_PORT || "5174", 10);
|
|
1770
|
+
return {
|
|
1771
|
+
projectRoot,
|
|
1772
|
+
extensionName,
|
|
1773
|
+
appId: stackableEnv.APP_ID || null,
|
|
1774
|
+
extensionId: stackableEnv.EXTENSION_ID || null,
|
|
1775
|
+
extensionPort,
|
|
1776
|
+
previewPort
|
|
1777
|
+
};
|
|
1778
|
+
};
|
|
1779
|
+
var writeDevContext = async (projectRoot, ctx) => {
|
|
1780
|
+
const env = {
|
|
1781
|
+
APP_ID: ctx.appId || "",
|
|
1782
|
+
EXTENSION_ID: ctx.extensionId || "",
|
|
1783
|
+
EXTENSION_PORT: ctx.extensionPort.toString(),
|
|
1784
|
+
PREVIEW_PORT: ctx.previewPort.toString()
|
|
1785
|
+
};
|
|
1786
|
+
await writeEnvFile2(join3(projectRoot, ".env.stackable"), env);
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// src/lib/tunnel.ts
|
|
1790
|
+
import { tunnel } from "cloudflared";
|
|
1791
|
+
var startTunnel = async (port) => {
|
|
1792
|
+
const { url, stop } = tunnel({ "--url": `http://localhost:${port}` });
|
|
1793
|
+
const resolvedUrl = await url;
|
|
1794
|
+
return { url: resolvedUrl, stop };
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
// src/lib/devServer.ts
|
|
1798
|
+
import { spawn } from "child_process";
|
|
1799
|
+
var startDevServer = (projectRoot) => {
|
|
1800
|
+
const child = spawn("pnpm", ["dev"], {
|
|
1801
|
+
cwd: projectRoot,
|
|
1802
|
+
stdio: "pipe"
|
|
1803
|
+
});
|
|
1804
|
+
const stop = () => {
|
|
1805
|
+
child.kill("SIGTERM");
|
|
1806
|
+
};
|
|
1807
|
+
return { process: child, stop };
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
// src/components/DevSetup.tsx
|
|
1811
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1812
|
+
import { useState as useState9, useEffect as useEffect4 } from "react";
|
|
1813
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1814
|
+
var DevSetup = ({ initialContext, onReady }) => {
|
|
1815
|
+
const [step, setStep] = useState9("app");
|
|
1816
|
+
const [selectedApp, setSelectedApp] = useState9(null);
|
|
1817
|
+
useEffect4(() => {
|
|
1818
|
+
if (initialContext.appId) {
|
|
1819
|
+
setStep("extension");
|
|
1820
|
+
}
|
|
1821
|
+
}, [initialContext.appId]);
|
|
1822
|
+
const handleAppSelect = (app) => {
|
|
1823
|
+
setSelectedApp(app);
|
|
1824
|
+
setStep("extension");
|
|
1825
|
+
};
|
|
1826
|
+
const handleExtensionSelect = (extension) => {
|
|
1827
|
+
onReady({
|
|
1828
|
+
appId: selectedApp?.id || initialContext.appId,
|
|
1829
|
+
extensionId: extension.id,
|
|
1830
|
+
appName: selectedApp?.name
|
|
1831
|
+
});
|
|
1832
|
+
};
|
|
1833
|
+
if (step === "app") {
|
|
1834
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
1835
|
+
/* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: "Select the App for your extension:" }) }),
|
|
1836
|
+
/* @__PURE__ */ jsx14(AppSelect, { onSubmit: handleAppSelect })
|
|
1837
|
+
] });
|
|
1838
|
+
}
|
|
1839
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
1840
|
+
/* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: "Select the Extension to develop:" }) }),
|
|
1841
|
+
/* @__PURE__ */ jsx14(
|
|
1842
|
+
ExtensionSelect,
|
|
1843
|
+
{
|
|
1844
|
+
appId: selectedApp?.id || initialContext.appId,
|
|
1845
|
+
onSubmit: handleExtensionSelect
|
|
1846
|
+
}
|
|
1847
|
+
)
|
|
1848
|
+
] });
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
// src/components/DevDashboard.tsx
|
|
1852
|
+
import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
|
|
1853
|
+
import { useEffect as useEffect5 } from "react";
|
|
1854
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1855
|
+
var DevDashboard = ({
|
|
1856
|
+
extensionName,
|
|
1857
|
+
extensionId,
|
|
1858
|
+
appId,
|
|
1859
|
+
appName,
|
|
1860
|
+
extensionPort,
|
|
1861
|
+
previewPort,
|
|
1862
|
+
tunnelUrl,
|
|
1863
|
+
bundleUrlUpdated,
|
|
1864
|
+
onQuit
|
|
1865
|
+
}) => {
|
|
1866
|
+
useEffect5(() => {
|
|
1867
|
+
const handler = () => {
|
|
1868
|
+
onQuit();
|
|
1869
|
+
};
|
|
1870
|
+
process.on("SIGINT", handler);
|
|
1871
|
+
return () => {
|
|
1872
|
+
process.off("SIGINT", handler);
|
|
1873
|
+
};
|
|
1874
|
+
}, [onQuit]);
|
|
1875
|
+
useInput8((input) => {
|
|
1876
|
+
if (input === "q") {
|
|
1877
|
+
onQuit();
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", padding: 1, children: [
|
|
1881
|
+
/* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { bold: true, color: "blue", children: "\u2500\u2500 stackable dev \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
|
|
1882
|
+
/* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", gap: 1, marginBottom: 1, children: [
|
|
1883
|
+
/* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
|
|
1884
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Extension:" }),
|
|
1885
|
+
/* @__PURE__ */ jsxs15(Text15, { children: [
|
|
1886
|
+
extensionName,
|
|
1887
|
+
" (",
|
|
1888
|
+
extensionId,
|
|
1889
|
+
")"
|
|
1890
|
+
] })
|
|
1891
|
+
] }),
|
|
1892
|
+
/* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
|
|
1893
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "App:" }),
|
|
1894
|
+
/* @__PURE__ */ jsx15(Text15, { children: appName || appId })
|
|
1895
|
+
] })
|
|
1896
|
+
] }),
|
|
1897
|
+
/* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", gap: 1, marginBottom: 1, children: [
|
|
1898
|
+
/* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
|
|
1899
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Local:" }),
|
|
1900
|
+
/* @__PURE__ */ jsxs15(Text15, { children: [
|
|
1901
|
+
"http://localhost:",
|
|
1902
|
+
extensionPort
|
|
1903
|
+
] })
|
|
1904
|
+
] }),
|
|
1905
|
+
/* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
|
|
1906
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Tunnel:" }),
|
|
1907
|
+
/* @__PURE__ */ jsx15(Text15, { children: tunnelUrl || "Connecting\u2026" })
|
|
1908
|
+
] }),
|
|
1909
|
+
/* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
|
|
1910
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Preview:" }),
|
|
1911
|
+
/* @__PURE__ */ jsxs15(Text15, { children: [
|
|
1912
|
+
"http://localhost:",
|
|
1913
|
+
previewPort
|
|
1914
|
+
] })
|
|
1915
|
+
] })
|
|
1916
|
+
] }),
|
|
1917
|
+
bundleUrlUpdated && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { color: "green", children: "bundleUrl updated \u2713" }) }),
|
|
1918
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Press Ctrl-C to quit" }) })
|
|
1919
|
+
] });
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1922
|
+
// src/components/DevApp.tsx
|
|
1923
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1924
|
+
var DevApp = ({ options = {} }) => {
|
|
1925
|
+
const [state, setState] = useState10("setup");
|
|
1926
|
+
const [devContext, setDevContext] = useState10(null);
|
|
1927
|
+
const [resolvedContext, setResolvedContext] = useState10(null);
|
|
1928
|
+
const [tunnelUrl, setTunnelUrl] = useState10(null);
|
|
1929
|
+
const [bundleUrlUpdated, setBundleUrlUpdated] = useState10(false);
|
|
1930
|
+
const [tunnelHandle, setTunnelHandle] = useState10(null);
|
|
1931
|
+
const [devServerHandle, setDevServerHandle] = useState10(null);
|
|
1932
|
+
const [originalBundleUrl, setOriginalBundleUrl] = useState10("");
|
|
1933
|
+
const useTunnel = options.tunnel !== false;
|
|
1934
|
+
const useUpdate = options.update !== false;
|
|
1935
|
+
const useRestore = options.restore !== false;
|
|
1936
|
+
useEffect6(() => {
|
|
1937
|
+
const projectRoot = options.dir || process.cwd();
|
|
1938
|
+
readDevContext(projectRoot).then((ctx) => {
|
|
1939
|
+
setDevContext(ctx);
|
|
1940
|
+
});
|
|
1941
|
+
}, [options.dir]);
|
|
1942
|
+
const handleSetupReady = useCallback2(async (resolved) => {
|
|
1943
|
+
if (!devContext) return;
|
|
1944
|
+
setResolvedContext(resolved);
|
|
1945
|
+
await writeDevContext(devContext.projectRoot, {
|
|
1946
|
+
...devContext,
|
|
1947
|
+
appId: resolved.appId,
|
|
1948
|
+
extensionId: resolved.extensionId
|
|
1949
|
+
});
|
|
1950
|
+
const extensionPort = options.extensionPort ? parseInt(options.extensionPort, 10) : devContext.extensionPort;
|
|
1951
|
+
const serverHandle = startDevServer(devContext.projectRoot);
|
|
1952
|
+
setDevServerHandle(serverHandle);
|
|
1953
|
+
if (useTunnel) {
|
|
1954
|
+
try {
|
|
1955
|
+
const tunnelResult = await startTunnel(extensionPort);
|
|
1956
|
+
setTunnelHandle(tunnelResult);
|
|
1957
|
+
setTunnelUrl(tunnelResult.url);
|
|
1958
|
+
if (useUpdate) {
|
|
1959
|
+
const originalUrl = `http://localhost:${extensionPort}`;
|
|
1960
|
+
setOriginalBundleUrl(originalUrl);
|
|
1961
|
+
await updateExtension(resolved.appId, resolved.extensionId, {
|
|
1962
|
+
bundleUrl: tunnelResult.url
|
|
1963
|
+
});
|
|
1964
|
+
setBundleUrlUpdated(true);
|
|
1965
|
+
}
|
|
1966
|
+
} catch (err) {
|
|
1967
|
+
console.error("Failed to start tunnel:", err);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
setState("running");
|
|
1971
|
+
}, [devContext, options.extensionPort, useTunnel, useUpdate]);
|
|
1972
|
+
useEffect6(() => {
|
|
1973
|
+
if (state === "setup" && devContext && devContext.appId && devContext.extensionId) {
|
|
1974
|
+
handleSetupReady({
|
|
1975
|
+
appId: devContext.appId,
|
|
1976
|
+
extensionId: devContext.extensionId
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
}, [state, devContext, handleSetupReady]);
|
|
1980
|
+
const handleQuit = async () => {
|
|
1981
|
+
if (!devContext || !resolvedContext) return;
|
|
1982
|
+
setState("stopping");
|
|
1983
|
+
try {
|
|
1984
|
+
if (useRestore && originalBundleUrl) {
|
|
1985
|
+
await updateExtension(resolvedContext.appId, resolvedContext.extensionId, {
|
|
1986
|
+
bundleUrl: originalBundleUrl
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
if (tunnelHandle) {
|
|
1990
|
+
tunnelHandle.stop();
|
|
1991
|
+
}
|
|
1992
|
+
if (devServerHandle) {
|
|
1993
|
+
devServerHandle.stop();
|
|
1994
|
+
}
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
console.error("Error during cleanup:", err);
|
|
1997
|
+
}
|
|
1998
|
+
process.exit(0);
|
|
1999
|
+
};
|
|
2000
|
+
if (state === "setup" && devContext) {
|
|
2001
|
+
if (!devContext.appId || !devContext.extensionId) {
|
|
2002
|
+
return /* @__PURE__ */ jsx16(
|
|
2003
|
+
DevSetup,
|
|
2004
|
+
{
|
|
2005
|
+
initialContext: devContext,
|
|
2006
|
+
onReady: handleSetupReady
|
|
2007
|
+
}
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
return null;
|
|
2011
|
+
}
|
|
2012
|
+
if (state === "running" && devContext && resolvedContext) {
|
|
2013
|
+
return /* @__PURE__ */ jsx16(
|
|
2014
|
+
DevDashboard,
|
|
2015
|
+
{
|
|
2016
|
+
extensionName: devContext.extensionName,
|
|
2017
|
+
extensionId: resolvedContext.extensionId,
|
|
2018
|
+
appId: resolvedContext.appId,
|
|
2019
|
+
appName: resolvedContext.appName,
|
|
2020
|
+
extensionPort: options.extensionPort ? parseInt(options.extensionPort, 10) : devContext.extensionPort,
|
|
2021
|
+
previewPort: options.previewPort ? parseInt(options.previewPort, 10) : devContext.previewPort,
|
|
2022
|
+
tunnelUrl,
|
|
2023
|
+
bundleUrlUpdated,
|
|
2024
|
+
onQuit: handleQuit
|
|
2025
|
+
}
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { children: "Loading..." }) });
|
|
2029
|
+
};
|
|
2030
|
+
|
|
1288
2031
|
// src/index.tsx
|
|
1289
|
-
import { jsx as
|
|
2032
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1290
2033
|
var require2 = createRequire(import.meta.url);
|
|
1291
2034
|
var { version } = require2("../package.json");
|
|
1292
2035
|
program.name("stackable-app-extension").description("Stackable app extension developer CLI").version(version);
|
|
1293
2036
|
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) => {
|
|
1294
|
-
render(/* @__PURE__ */
|
|
2037
|
+
render(/* @__PURE__ */ jsx17(App, { command: "create" /* CREATE */, initialName: name, options }));
|
|
1295
2038
|
});
|
|
1296
2039
|
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) => {
|
|
1297
|
-
render(/* @__PURE__ */
|
|
2040
|
+
render(/* @__PURE__ */ jsx17(App, { command: "scaffold" /* SCAFFOLD */, options }));
|
|
2041
|
+
});
|
|
2042
|
+
program.command("update" /* UPDATE */).description("Update an existing Extension").argument("[extensionId]", "Extension ID to update").option("--app-id <id>", "Skip app selection").option("--name <name>", "New extension name").option("--targets <targets>", "Comma-separated target slots (validated against app)").option("--bundle-url <url>", "New bundle URL").option("--enabled <bool>", "Enable/disable extension").option("--set-version <version>", "Explicit version (skips auto-compute)").action((extensionId, options) => {
|
|
2043
|
+
render(/* @__PURE__ */ jsx17(App, { command: "update" /* UPDATE */, initialExtensionId: extensionId, options }));
|
|
2044
|
+
});
|
|
2045
|
+
program.command("dev" /* DEV */).description("Start dev servers with a public tunnel").option("--dir <path>", "Project root (default: cwd)").option("--extension-port <port>", "Override extension port").option("--preview-port <port>", "Override preview port").option("--no-tunnel", "Skip tunnel, just run vite dev").option("--no-update", "Skip bundleUrl update on server").option("--no-restore", "On exit, keep tunnel URL as bundleUrl").action((options) => {
|
|
2046
|
+
render(/* @__PURE__ */ jsx17(DevApp, { options }));
|
|
1298
2047
|
});
|
|
1299
2048
|
program.parse(process.argv.filter((arg) => arg !== "--"));
|