@stackable-labs/cli-app-extension 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +535 -281
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -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 (
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
"
|
|
81
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
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/
|
|
113
|
-
import { Box as
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
139
|
-
] }) });
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// src/components/DirPrompt.tsx
|
|
143
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
144
|
-
var DirPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
145
|
-
const [value, setValue] = useState3(defaultDir);
|
|
146
|
-
const handleSubmit = (val) => {
|
|
147
|
-
const trimmed = val.trim();
|
|
148
|
-
if (trimmed.length === 0) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
onSubmit(trimmed);
|
|
152
|
-
};
|
|
153
|
-
return /* @__PURE__ */ jsx4(BackableInput, { label: "Output directory:", hint: "Press Enter to confirm", onBack, children: (isFocused) => /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
154
|
-
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u2192 " }),
|
|
155
|
-
/* @__PURE__ */ jsx4(TextInput, { value, onChange: setValue, onSubmit: handleSubmit, focus: isFocused })
|
|
193
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error })
|
|
156
194
|
] }) });
|
|
157
195
|
};
|
|
158
196
|
|
|
159
|
-
// src/components/Done.tsx
|
|
160
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
161
|
-
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
162
|
-
var Done = ({ name, outputDir }) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
163
|
-
/* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
164
|
-
/* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2714" }),
|
|
165
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Extension scaffolded successfully!" })
|
|
166
|
-
] }),
|
|
167
|
-
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
|
|
168
|
-
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
169
|
-
"Created: ",
|
|
170
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: name })
|
|
171
|
-
] }),
|
|
172
|
-
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
173
|
-
"Location: ",
|
|
174
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: outputDir })
|
|
175
|
-
] })
|
|
176
|
-
] }),
|
|
177
|
-
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, children: [
|
|
178
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Next steps:" }),
|
|
179
|
-
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, gap: 1, children: [
|
|
180
|
-
/* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
181
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "1." }),
|
|
182
|
-
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
183
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "cd " }),
|
|
184
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: outputDir })
|
|
185
|
-
] })
|
|
186
|
-
] }),
|
|
187
|
-
/* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
188
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "2." }),
|
|
189
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "pnpm install" })
|
|
190
|
-
] }),
|
|
191
|
-
/* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
192
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "3." }),
|
|
193
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "pnpm dev" })
|
|
194
|
-
] })
|
|
195
|
-
] })
|
|
196
|
-
] })
|
|
197
|
-
] });
|
|
198
|
-
|
|
199
197
|
// src/components/NamePrompt.tsx
|
|
200
|
-
import {
|
|
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] =
|
|
206
|
-
const [error, setError] =
|
|
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__ */
|
|
217
|
-
/* @__PURE__ */
|
|
218
|
-
/* @__PURE__ */
|
|
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/
|
|
223
|
-
import { Box as
|
|
224
|
-
import
|
|
225
|
-
import { useState as
|
|
226
|
-
import { jsx as
|
|
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
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
hint: "
|
|
287
|
-
onBack
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
/* @__PURE__ */
|
|
291
|
-
|
|
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:
|
|
296
|
-
|
|
297
|
-
|
|
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,47 +353,47 @@ var PortsPrompt = ({ onSubmit, onBack }) => {
|
|
|
303
353
|
};
|
|
304
354
|
|
|
305
355
|
// src/components/ScaffoldProgress.tsx
|
|
306
|
-
import { Box as
|
|
356
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
307
357
|
import Spinner from "ink-spinner";
|
|
308
|
-
import { jsx as
|
|
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__ */
|
|
362
|
+
return /* @__PURE__ */ jsx7(Spinner, { type: "dots" });
|
|
313
363
|
case "done":
|
|
314
|
-
return /* @__PURE__ */
|
|
364
|
+
return /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2714" });
|
|
315
365
|
case "error":
|
|
316
|
-
return /* @__PURE__ */
|
|
366
|
+
return /* @__PURE__ */ jsx7(Text7, { color: "red", children: "\u2716" });
|
|
317
367
|
default:
|
|
318
|
-
return /* @__PURE__ */
|
|
368
|
+
return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u25CB" });
|
|
319
369
|
}
|
|
320
370
|
};
|
|
321
|
-
var ScaffoldProgress = ({ steps }) => /* @__PURE__ */
|
|
322
|
-
/* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
331
|
-
import { useState as
|
|
332
|
-
import { jsx as
|
|
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)",
|
|
336
386
|
"slot.footer": "Renders a footer bar at the bottom of the panel",
|
|
337
387
|
"slot.footer-links": "Renders a link row in the global footer"
|
|
338
388
|
};
|
|
339
|
-
var TargetSelect = ({ availableTargets, onSubmit, onBack }) => {
|
|
340
|
-
const [cursor, setCursor] =
|
|
341
|
-
const [backFocused, setBackFocused] =
|
|
342
|
-
const [selected, setSelected] =
|
|
343
|
-
new Set(availableTargets.includes("slot.content") ? ["slot.content"] : [])
|
|
389
|
+
var TargetSelect = ({ availableTargets, preSelected, onSubmit, onBack }) => {
|
|
390
|
+
const [cursor, setCursor] = useState5(0);
|
|
391
|
+
const [backFocused, setBackFocused] = useState5(false);
|
|
392
|
+
const [selected, setSelected] = useState5(
|
|
393
|
+
new Set(preSelected ?? (availableTargets.includes("slot.content") ? ["slot.content"] : []))
|
|
344
394
|
);
|
|
345
|
-
const [error, setError] =
|
|
346
|
-
|
|
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, onSubmit, onBack }) => {
|
|
|
387
437
|
onSubmit([...selected]);
|
|
388
438
|
}
|
|
389
439
|
});
|
|
390
|
-
return /* @__PURE__ */
|
|
440
|
+
return /* @__PURE__ */ jsxs8(
|
|
391
441
|
StepShell,
|
|
392
442
|
{
|
|
393
443
|
title: "Select Surface targets/slots",
|
|
@@ -395,44 +445,67 @@ var TargetSelect = ({ availableTargets, onSubmit, onBack }) => {
|
|
|
395
445
|
onBack,
|
|
396
446
|
backFocused,
|
|
397
447
|
children: [
|
|
398
|
-
/* @__PURE__ */
|
|
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__ */
|
|
402
|
-
/* @__PURE__ */
|
|
403
|
-
/* @__PURE__ */
|
|
404
|
-
/* @__PURE__ */
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
418
|
-
import SelectInput from "ink-select-input";
|
|
467
|
+
import { Box as Box10, Text as Text10, useInput as useInput5 } from "ink";
|
|
419
468
|
import Spinner2 from "ink-spinner";
|
|
420
|
-
import { useEffect, useState as
|
|
469
|
+
import { useEffect, useState as useState6 } from "react";
|
|
421
470
|
|
|
422
471
|
// src/lib/api.ts
|
|
423
|
-
var
|
|
472
|
+
var DEFAULT_API_URL = "https://api.stackablelabs.io/app-extension/latest";
|
|
473
|
+
var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
|
|
474
|
+
var getApiBaseUrl = () => process.env.API_BASE_URL ?? DEFAULT_API_URL;
|
|
475
|
+
var getAdminApiBaseUrl = () => process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
|
|
424
476
|
var fetchApps = async () => {
|
|
425
|
-
const baseURL = `${
|
|
477
|
+
const baseURL = `${getApiBaseUrl()}/apps`;
|
|
426
478
|
const res = await fetch(baseURL);
|
|
427
479
|
if (!res.ok) {
|
|
428
|
-
throw new Error(`Failed to fetch apps
|
|
480
|
+
throw new Error(`Failed to fetch apps: ${res.status} ${res.statusText}`);
|
|
481
|
+
}
|
|
482
|
+
return res.json();
|
|
483
|
+
};
|
|
484
|
+
var createExtensionRemote = async (appId, payload) => {
|
|
485
|
+
const baseUrl = getAdminApiBaseUrl();
|
|
486
|
+
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`, {
|
|
487
|
+
method: "POST",
|
|
488
|
+
headers: { "content-type": "application/json" },
|
|
489
|
+
body: JSON.stringify(payload)
|
|
490
|
+
});
|
|
491
|
+
if (!res.ok) {
|
|
492
|
+
const body = await res.text();
|
|
493
|
+
throw new Error(`Failed to create extension: ${res.status} ${body}`);
|
|
494
|
+
}
|
|
495
|
+
return res.json();
|
|
496
|
+
};
|
|
497
|
+
var fetchExtensions = async (appId) => {
|
|
498
|
+
const baseUrl = getApiBaseUrl();
|
|
499
|
+
const res = await fetch(`${baseUrl}/extensions/${appId}`);
|
|
500
|
+
if (!res.ok) {
|
|
501
|
+
throw new Error(`Failed to fetch extensions: ${res.status} ${res.statusText}`);
|
|
429
502
|
}
|
|
430
503
|
return res.json();
|
|
431
504
|
};
|
|
432
505
|
|
|
433
506
|
// src/components/Banner.tsx
|
|
434
|
-
import { Box as
|
|
435
|
-
import { jsx as
|
|
507
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
508
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
436
509
|
var WORDMARK = [
|
|
437
510
|
" _ _ _ _ ",
|
|
438
511
|
" ___| |_ __ _ ___| | ____ _| |__ | | ___",
|
|
@@ -468,53 +541,155 @@ var gradientColor = (row, col, rows, cols) => {
|
|
|
468
541
|
var Banner = () => {
|
|
469
542
|
const termWidth = process.stdout.columns ?? 80;
|
|
470
543
|
const maxLen = Math.max(...WORDMARK.map((l) => l.length));
|
|
471
|
-
return /* @__PURE__ */
|
|
472
|
-
/* @__PURE__ */
|
|
473
|
-
/* @__PURE__ */
|
|
544
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
545
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2500".repeat(termWidth) }),
|
|
546
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", paddingX: 1, paddingY: 1, children: WORDMARK.map((line, row) => /* @__PURE__ */ jsx9(Box9, { children: line.split("").map((ch, col) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: ch === " " ? void 0 : gradientColor(row, col, WORDMARK.length, maxLen), children: ch }, col)) }, row)) })
|
|
474
547
|
] });
|
|
475
548
|
};
|
|
476
549
|
|
|
477
550
|
// src/components/AppSelect.tsx
|
|
478
|
-
import { jsx as
|
|
551
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
479
552
|
var AppSelect = ({ onSubmit }) => {
|
|
480
|
-
const [apps, setApps] =
|
|
481
|
-
const [loading, setLoading] =
|
|
482
|
-
const [error, setError] =
|
|
553
|
+
const [apps, setApps] = useState6([]);
|
|
554
|
+
const [loading, setLoading] = useState6(true);
|
|
555
|
+
const [error, setError] = useState6();
|
|
556
|
+
const [cursor, setCursor] = useState6(0);
|
|
483
557
|
useEffect(() => {
|
|
484
558
|
fetchApps().then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
485
559
|
}, []);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
/* @__PURE__ */
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
560
|
+
useInput5((_, key) => {
|
|
561
|
+
if (loading || error || apps.length === 0) return;
|
|
562
|
+
if (key.upArrow) {
|
|
563
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
564
|
+
} else if (key.downArrow) {
|
|
565
|
+
setCursor((c) => Math.min(apps.length - 1, c + 1));
|
|
566
|
+
} else if (key.return) {
|
|
567
|
+
onSubmit(apps[cursor]);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
const renderContent = () => {
|
|
571
|
+
if (loading) {
|
|
572
|
+
return /* @__PURE__ */ jsxs10(Box10, { gap: 2, children: [
|
|
573
|
+
/* @__PURE__ */ jsx10(Spinner2, { type: "dots" }),
|
|
574
|
+
/* @__PURE__ */ jsx10(Text10, { children: "Loading available Apps\u2026" })
|
|
575
|
+
] });
|
|
576
|
+
}
|
|
577
|
+
if (error) {
|
|
578
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
|
|
579
|
+
/* @__PURE__ */ jsx10(Text10, { color: "red", bold: true, children: "Failed to load Apps" }),
|
|
580
|
+
/* @__PURE__ */ jsx10(Text10, { color: "red", children: error })
|
|
581
|
+
] });
|
|
582
|
+
}
|
|
583
|
+
if (apps.length === 0) {
|
|
584
|
+
return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "No Apps available. Contact your administrator." });
|
|
585
|
+
}
|
|
586
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: apps.map((app, i) => {
|
|
587
|
+
const isCursor = i === cursor;
|
|
588
|
+
return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
|
|
589
|
+
/* @__PURE__ */ jsx10(Text10, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
590
|
+
/* @__PURE__ */ jsx10(Text10, { bold: isCursor, children: app.name }),
|
|
591
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
592
|
+
"(",
|
|
593
|
+
app.id,
|
|
594
|
+
")"
|
|
595
|
+
] })
|
|
596
|
+
] }, app.id);
|
|
597
|
+
}) });
|
|
511
598
|
};
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
/* @__PURE__ */
|
|
514
|
-
/* @__PURE__ */
|
|
599
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
600
|
+
/* @__PURE__ */ jsx10(Banner, {}),
|
|
601
|
+
/* @__PURE__ */ jsx10(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
|
|
515
602
|
] });
|
|
516
603
|
};
|
|
517
604
|
|
|
605
|
+
// src/components/ExtensionSelect.tsx
|
|
606
|
+
import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
|
|
607
|
+
import Spinner3 from "ink-spinner";
|
|
608
|
+
import { useEffect as useEffect2, useState as useState7 } from "react";
|
|
609
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
610
|
+
var ExtensionSelect = ({ appId, onSubmit, onBack }) => {
|
|
611
|
+
const [extensions, setExtensions] = useState7([]);
|
|
612
|
+
const [loading, setLoading] = useState7(true);
|
|
613
|
+
const [error, setError] = useState7();
|
|
614
|
+
const [cursor, setCursor] = useState7(0);
|
|
615
|
+
const [backFocused, setBackFocused] = useState7(false);
|
|
616
|
+
useEffect2(() => {
|
|
617
|
+
fetchExtensions(appId).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
618
|
+
}, [appId]);
|
|
619
|
+
useInput6((_, key) => {
|
|
620
|
+
if (key.upArrow) {
|
|
621
|
+
if (loading || error || extensions.length === 0 || cursor === 0) {
|
|
622
|
+
if (onBack) setBackFocused(true);
|
|
623
|
+
} else {
|
|
624
|
+
setBackFocused(false);
|
|
625
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (key.downArrow) {
|
|
630
|
+
if (backFocused) {
|
|
631
|
+
setBackFocused(false);
|
|
632
|
+
} else if (!loading && !error && extensions.length > 0) {
|
|
633
|
+
setCursor((c) => Math.min(extensions.length - 1, c + 1));
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (key.return) {
|
|
638
|
+
if (backFocused) {
|
|
639
|
+
onBack?.();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (!loading && !error && extensions.length > 0) {
|
|
643
|
+
onSubmit(extensions[cursor]);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
const renderContent = () => {
|
|
648
|
+
if (loading) {
|
|
649
|
+
return /* @__PURE__ */ jsxs11(Box11, { gap: 2, children: [
|
|
650
|
+
/* @__PURE__ */ jsx11(Spinner3, { type: "dots" }),
|
|
651
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Loading Extensions\u2026" })
|
|
652
|
+
] });
|
|
653
|
+
}
|
|
654
|
+
if (error) {
|
|
655
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
|
|
656
|
+
/* @__PURE__ */ jsx11(Text11, { color: "red", bold: true, children: "Failed to load Extensions" }),
|
|
657
|
+
/* @__PURE__ */ jsx11(Text11, { color: "red", children: error })
|
|
658
|
+
] });
|
|
659
|
+
}
|
|
660
|
+
if (extensions.length === 0) {
|
|
661
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "No Extensions found for this App." });
|
|
662
|
+
}
|
|
663
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: extensions.map((ext, i) => {
|
|
664
|
+
const isCursor = i === cursor && !backFocused;
|
|
665
|
+
return /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
|
|
666
|
+
/* @__PURE__ */ jsx11(Text11, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
|
|
667
|
+
/* @__PURE__ */ jsx11(Text11, { bold: isCursor, children: ext.manifest.name }),
|
|
668
|
+
/* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
669
|
+
"v",
|
|
670
|
+
ext.manifest.version
|
|
671
|
+
] }),
|
|
672
|
+
/* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
673
|
+
"(",
|
|
674
|
+
ext.id,
|
|
675
|
+
")"
|
|
676
|
+
] })
|
|
677
|
+
] }, ext.id);
|
|
678
|
+
}) });
|
|
679
|
+
};
|
|
680
|
+
return /* @__PURE__ */ jsx11(StepShell, { title: "Select an existing Extension to scaffold:", onBack, backFocused, children: renderContent() });
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// src/constants.ts
|
|
684
|
+
var TEMPLATE_SOURCE = "github:stackable-labs/templates/app-extension";
|
|
685
|
+
var DEFAULT_PERMISSIONS = ["context:read"];
|
|
686
|
+
var TARGET_PERMISSION_MAP = {
|
|
687
|
+
"slot.header": ["context:read"],
|
|
688
|
+
"slot.content": ["context:read", "data:query", "actions:toast", "actions:invoke"],
|
|
689
|
+
"slot.footer": [],
|
|
690
|
+
"slot.footer-links": []
|
|
691
|
+
};
|
|
692
|
+
|
|
518
693
|
// src/lib/postScaffold.ts
|
|
519
694
|
import { execFile } from "child_process";
|
|
520
695
|
import { promisify } from "util";
|
|
@@ -541,18 +716,6 @@ var postScaffold = async (options) => {
|
|
|
541
716
|
import { readFile, readdir, rm, writeFile } from "fs/promises";
|
|
542
717
|
import { join } from "path";
|
|
543
718
|
import { downloadTemplate } from "giget";
|
|
544
|
-
|
|
545
|
-
// src/constants.ts
|
|
546
|
-
var TEMPLATE_SOURCE = "github:stackable-labs/templates/app-extension";
|
|
547
|
-
var DEFAULT_PERMISSIONS = ["context:read"];
|
|
548
|
-
var TARGET_PERMISSION_MAP = {
|
|
549
|
-
"slot.header": ["context:read"],
|
|
550
|
-
"slot.content": ["context:read", "data:query", "actions:toast", "actions:invoke"],
|
|
551
|
-
"slot.footer": [],
|
|
552
|
-
"slot.footer-links": []
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
// src/lib/scaffold.ts
|
|
556
719
|
var normalizeTargets = (targets) => Array.from(new Set(targets));
|
|
557
720
|
var derivePermissions = (targets) => {
|
|
558
721
|
const permissions = /* @__PURE__ */ new Set();
|
|
@@ -737,7 +900,7 @@ var rewritePreviewApp = async (rootDir, targets, permissions) => {
|
|
|
737
900
|
}
|
|
738
901
|
const appContent = `import { ExtensionProvider, ExtensionSlot } from '@stackable-labs/sdk-extension-host'
|
|
739
902
|
import type { CapabilityHandlers } from '@stackable-labs/sdk-extension-host'
|
|
740
|
-
import
|
|
903
|
+
import hostComponents from '@stackable-labs/embeddables/components'
|
|
741
904
|
import type {
|
|
742
905
|
${typeImports.join(",\n ")}
|
|
743
906
|
} from '@stackable-labs/sdk-extension-contracts'
|
|
@@ -852,18 +1015,47 @@ var scaffold = async (options) => {
|
|
|
852
1015
|
|
|
853
1016
|
// src/App.tsx
|
|
854
1017
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
855
|
-
var
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
]
|
|
1018
|
+
var STEPS = {
|
|
1019
|
+
["create" /* CREATE */]: ["app", "name", "targets", "settings", "confirm"],
|
|
1020
|
+
["scaffold" /* SCAFFOLD */]: ["app", "extensionSelect", "confirmTargets", "settings", "confirm"]
|
|
1021
|
+
};
|
|
1022
|
+
var PROGRESS_STEPS = {
|
|
1023
|
+
["create" /* CREATE */]: [
|
|
1024
|
+
{ label: "Registering extension", status: "pending" },
|
|
1025
|
+
{ label: "Fetching template", status: "pending" },
|
|
1026
|
+
{ label: "Generating files", status: "pending" },
|
|
1027
|
+
{ label: "Installing dependencies", status: "pending" }
|
|
1028
|
+
],
|
|
1029
|
+
["scaffold" /* SCAFFOLD */]: [
|
|
1030
|
+
{ label: "Fetching template", status: "pending" },
|
|
1031
|
+
{ label: "Generating files", status: "pending" },
|
|
1032
|
+
{ label: "Installing dependencies", status: "pending" }
|
|
1033
|
+
]
|
|
1034
|
+
};
|
|
861
1035
|
var toKebabCase2 = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
862
|
-
var
|
|
1036
|
+
var derivePermissions2 = (targets) => {
|
|
1037
|
+
const permissions = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (const target of targets) {
|
|
1039
|
+
const mapped = TARGET_PERMISSION_MAP[target];
|
|
1040
|
+
if (mapped && mapped.length > 0) {
|
|
1041
|
+
for (const permission of mapped) {
|
|
1042
|
+
permissions.add(permission);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (permissions.size === 0) {
|
|
1047
|
+
for (const permission of DEFAULT_PERMISSIONS) {
|
|
1048
|
+
permissions.add(permission);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return [...permissions];
|
|
1052
|
+
};
|
|
1053
|
+
var App = ({ command, initialName, options }) => {
|
|
863
1054
|
const { exit } = useApp();
|
|
864
1055
|
const [step, setStep] = useState8("app");
|
|
865
1056
|
const [name, setName] = useState8(initialName ?? "");
|
|
866
1057
|
const [extensionId, setExtensionId] = useState8("");
|
|
1058
|
+
const [extensionVersion, setExtensionVersion] = useState8("");
|
|
867
1059
|
const [selectedApp, setSelectedApp] = useState8(null);
|
|
868
1060
|
const [extensionPort, setExtensionPort] = useState8(
|
|
869
1061
|
options?.extensionPort ? parseInt(options.extensionPort, 10) : 5173
|
|
@@ -873,25 +1065,49 @@ var App = ({ initialName, options }) => {
|
|
|
873
1065
|
);
|
|
874
1066
|
const [targets, setTargets] = useState8([]);
|
|
875
1067
|
const [outputDir, setOutputDir] = useState8("");
|
|
876
|
-
const [progressSteps, setProgressSteps] = useState8(
|
|
1068
|
+
const [progressSteps, setProgressSteps] = useState8(PROGRESS_STEPS[command]);
|
|
877
1069
|
const [errorMessage, setErrorMessage] = useState8();
|
|
878
1070
|
const updateStep = useCallback((index, status) => {
|
|
879
1071
|
setProgressSteps((prev) => prev.map((s, i) => i === index ? { ...s, status } : s));
|
|
880
1072
|
}, []);
|
|
1073
|
+
const activeSteps = useCallback(() => {
|
|
1074
|
+
const base = STEPS[command];
|
|
1075
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
1076
|
+
if (command === "create" /* CREATE */ && initialName) skipped.add("name");
|
|
1077
|
+
if (options?.extensionPort || options?.previewPort) skipped.add("settings");
|
|
1078
|
+
return base.filter((s) => !skipped.has(s));
|
|
1079
|
+
}, [command, initialName, options?.extensionPort, options?.previewPort]);
|
|
881
1080
|
const goBack = useCallback(() => {
|
|
882
|
-
const skippedSteps = /* @__PURE__ */ new Set();
|
|
883
|
-
if (initialName) skippedSteps.add("name");
|
|
884
|
-
if (options?.extensionPort || options?.previewPort) skippedSteps.add("ports");
|
|
885
|
-
const activeSteps = STEP_ORDER.filter((s) => !skippedSteps.has(s));
|
|
886
1081
|
setStep((prev) => {
|
|
887
|
-
const
|
|
888
|
-
|
|
1082
|
+
const steps = activeSteps();
|
|
1083
|
+
const idx = steps.indexOf(prev);
|
|
1084
|
+
return idx > 0 ? steps[idx - 1] : prev;
|
|
889
1085
|
});
|
|
890
|
-
}, [
|
|
1086
|
+
}, [activeSteps]);
|
|
891
1087
|
const handleAppSelect = (app) => {
|
|
892
1088
|
setSelectedApp(app);
|
|
1089
|
+
if (command === "scaffold" /* SCAFFOLD */) {
|
|
1090
|
+
setStep("extensionSelect");
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
893
1093
|
setStep(initialName ? "targets" : "name");
|
|
894
1094
|
};
|
|
1095
|
+
const handleExtensionSelect = (ext) => {
|
|
1096
|
+
setName(ext.manifest.name);
|
|
1097
|
+
setExtensionId(ext.id);
|
|
1098
|
+
setExtensionVersion(ext.manifest.version);
|
|
1099
|
+
setTargets(ext.manifest.targets);
|
|
1100
|
+
setStep("confirmTargets");
|
|
1101
|
+
};
|
|
1102
|
+
const handleConfirmTargets = (value) => {
|
|
1103
|
+
setTargets(value);
|
|
1104
|
+
if (options?.extensionPort || options?.previewPort) {
|
|
1105
|
+
setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
|
|
1106
|
+
setStep("confirm");
|
|
1107
|
+
} else {
|
|
1108
|
+
setStep("settings");
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
895
1111
|
const handleName = (value) => {
|
|
896
1112
|
setName(value);
|
|
897
1113
|
setExtensionId(toKebabCase2(value));
|
|
@@ -899,45 +1115,61 @@ var App = ({ initialName, options }) => {
|
|
|
899
1115
|
};
|
|
900
1116
|
const handleTargets = (value) => {
|
|
901
1117
|
setTargets(value);
|
|
902
|
-
|
|
1118
|
+
if (options?.extensionPort || options?.previewPort) {
|
|
1119
|
+
setOutputDir(join2(process.cwd(), toKebabCase2(extensionId || name)));
|
|
1120
|
+
setStep("confirm");
|
|
1121
|
+
} else {
|
|
1122
|
+
setStep("settings");
|
|
1123
|
+
}
|
|
903
1124
|
};
|
|
904
|
-
const
|
|
1125
|
+
const handleSettings = (extPort, prevPort, dir) => {
|
|
905
1126
|
setExtensionPort(extPort);
|
|
906
1127
|
setPreviewPort(prevPort);
|
|
907
|
-
setStep("dir");
|
|
908
|
-
};
|
|
909
|
-
const handleDir = (dir) => {
|
|
910
1128
|
setOutputDir(dir);
|
|
911
1129
|
setStep("confirm");
|
|
912
1130
|
};
|
|
913
1131
|
const handleConfirm = async () => {
|
|
914
1132
|
setStep("scaffolding");
|
|
915
|
-
setProgressSteps([
|
|
916
|
-
{ label: "Fetching template", status: "running" },
|
|
917
|
-
{ label: "Generating files", status: "pending" },
|
|
918
|
-
{ label: "Installing dependencies", status: "pending" }
|
|
919
|
-
]);
|
|
1133
|
+
setProgressSteps(PROGRESS_STEPS[command]);
|
|
920
1134
|
try {
|
|
921
|
-
|
|
1135
|
+
let resolvedExtensionId = extensionId || toKebabCase2(name);
|
|
1136
|
+
let scaffoldStepOffset = 0;
|
|
1137
|
+
if (command === "create" /* CREATE */) {
|
|
1138
|
+
scaffoldStepOffset = 1;
|
|
1139
|
+
updateStep(0, "running");
|
|
1140
|
+
const created = await createExtensionRemote(selectedApp.id, {
|
|
1141
|
+
manifest: {
|
|
1142
|
+
name,
|
|
1143
|
+
version: "0.0.0",
|
|
1144
|
+
targets,
|
|
1145
|
+
permissions: derivePermissions2(targets)
|
|
1146
|
+
},
|
|
1147
|
+
bundleUrl: `http://localhost:${extensionPort}`
|
|
1148
|
+
});
|
|
1149
|
+
resolvedExtensionId = created.id;
|
|
1150
|
+
setExtensionId(created.id);
|
|
1151
|
+
updateStep(0, "done");
|
|
1152
|
+
}
|
|
1153
|
+
updateStep(scaffoldStepOffset + 0, "running");
|
|
922
1154
|
await scaffold({
|
|
923
1155
|
appId: selectedApp.id,
|
|
924
1156
|
name,
|
|
925
|
-
extensionId,
|
|
1157
|
+
extensionId: resolvedExtensionId,
|
|
926
1158
|
targets,
|
|
927
1159
|
outputDir,
|
|
928
1160
|
extensionPort,
|
|
929
1161
|
previewPort
|
|
930
1162
|
});
|
|
931
|
-
updateStep(0, "done");
|
|
932
|
-
updateStep(1, "running");
|
|
1163
|
+
updateStep(scaffoldStepOffset + 0, "done");
|
|
1164
|
+
updateStep(scaffoldStepOffset + 1, "running");
|
|
933
1165
|
await new Promise((r) => setTimeout(r, 200));
|
|
934
|
-
updateStep(1, "done");
|
|
1166
|
+
updateStep(scaffoldStepOffset + 1, "done");
|
|
935
1167
|
if (!options?.skipInstall) {
|
|
936
|
-
updateStep(2, "running");
|
|
1168
|
+
updateStep(scaffoldStepOffset + 2, "running");
|
|
937
1169
|
await postScaffold({ outputDir, skipInstall: false, skipGit: options?.skipGit });
|
|
938
|
-
updateStep(2, "done");
|
|
1170
|
+
updateStep(scaffoldStepOffset + 2, "done");
|
|
939
1171
|
} else {
|
|
940
|
-
updateStep(2, "done");
|
|
1172
|
+
updateStep(scaffoldStepOffset + 2, "done");
|
|
941
1173
|
}
|
|
942
1174
|
setStep("done");
|
|
943
1175
|
} catch (err) {
|
|
@@ -953,41 +1185,53 @@ var App = ({ initialName, options }) => {
|
|
|
953
1185
|
case "app": {
|
|
954
1186
|
return /* @__PURE__ */ jsx12(AppSelect, { onSubmit: handleAppSelect });
|
|
955
1187
|
}
|
|
956
|
-
case "
|
|
1188
|
+
case "extensionSelect": {
|
|
957
1189
|
return /* @__PURE__ */ jsx12(
|
|
958
|
-
|
|
1190
|
+
ExtensionSelect,
|
|
959
1191
|
{
|
|
960
|
-
|
|
961
|
-
onSubmit:
|
|
1192
|
+
appId: selectedApp.id,
|
|
1193
|
+
onSubmit: handleExtensionSelect,
|
|
962
1194
|
onBack: goBack
|
|
963
1195
|
}
|
|
964
1196
|
);
|
|
965
1197
|
}
|
|
966
|
-
case "
|
|
1198
|
+
case "confirmTargets": {
|
|
967
1199
|
return /* @__PURE__ */ jsx12(
|
|
968
1200
|
TargetSelect,
|
|
969
1201
|
{
|
|
970
1202
|
availableTargets: selectedApp?.targets ?? [],
|
|
971
|
-
|
|
1203
|
+
preSelected: targets,
|
|
1204
|
+
onSubmit: handleConfirmTargets,
|
|
1205
|
+
onBack: goBack
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
case "name": {
|
|
1210
|
+
return /* @__PURE__ */ jsx12(
|
|
1211
|
+
NamePrompt,
|
|
1212
|
+
{
|
|
1213
|
+
initialValue: name,
|
|
1214
|
+
onSubmit: handleName,
|
|
972
1215
|
onBack: goBack
|
|
973
1216
|
}
|
|
974
1217
|
);
|
|
975
1218
|
}
|
|
976
|
-
case "
|
|
1219
|
+
case "targets": {
|
|
977
1220
|
return /* @__PURE__ */ jsx12(
|
|
978
|
-
|
|
1221
|
+
TargetSelect,
|
|
979
1222
|
{
|
|
980
|
-
|
|
1223
|
+
availableTargets: selectedApp?.targets ?? [],
|
|
1224
|
+
onSubmit: handleTargets,
|
|
981
1225
|
onBack: goBack
|
|
982
1226
|
}
|
|
983
1227
|
);
|
|
984
1228
|
}
|
|
985
|
-
case "
|
|
1229
|
+
case "settings": {
|
|
986
1230
|
return /* @__PURE__ */ jsx12(
|
|
987
|
-
|
|
1231
|
+
SettingsPrompt,
|
|
988
1232
|
{
|
|
989
|
-
defaultDir: join2(process.cwd(), toKebabCase2(name)),
|
|
990
|
-
onSubmit:
|
|
1233
|
+
defaultDir: join2(process.cwd(), toKebabCase2(extensionId || name)),
|
|
1234
|
+
onSubmit: handleSettings,
|
|
991
1235
|
onBack: goBack
|
|
992
1236
|
}
|
|
993
1237
|
);
|
|
@@ -1001,6 +1245,7 @@ var App = ({ initialName, options }) => {
|
|
|
1001
1245
|
previewPort,
|
|
1002
1246
|
targets,
|
|
1003
1247
|
outputDir,
|
|
1248
|
+
extensionVersion: command === "scaffold" /* SCAFFOLD */ ? extensionVersion : void 0,
|
|
1004
1249
|
onConfirm: handleConfirm,
|
|
1005
1250
|
onCancel: handleCancel,
|
|
1006
1251
|
onBack: goBack
|
|
@@ -1014,17 +1259,26 @@ var App = ({ initialName, options }) => {
|
|
|
1014
1259
|
return /* @__PURE__ */ jsx12(Done, { name, outputDir });
|
|
1015
1260
|
}
|
|
1016
1261
|
default: {
|
|
1017
|
-
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1018
|
-
/* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "
|
|
1262
|
+
return /* @__PURE__ */ jsx12(StepShell, { title: "Scaffold failed", onBack: goBack, backFocused: false, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1263
|
+
/* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "An error occurred" }),
|
|
1019
1264
|
errorMessage && /* @__PURE__ */ jsx12(Text12, { color: "red", children: errorMessage })
|
|
1020
|
-
] });
|
|
1265
|
+
] }) });
|
|
1021
1266
|
}
|
|
1022
1267
|
}
|
|
1023
1268
|
};
|
|
1024
1269
|
|
|
1025
1270
|
// src/index.tsx
|
|
1026
1271
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1027
|
-
program.name("
|
|
1028
|
-
|
|
1272
|
+
program.name("stackable-extension").description("Stackable extension developer CLI");
|
|
1273
|
+
program.command("create" /* CREATE */).description("Create a new Extension project").argument("[name]", "Extension project name").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((name, options) => {
|
|
1274
|
+
render(/* @__PURE__ */ jsx13(App, { command: "create" /* CREATE */, initialName: name, options }));
|
|
1275
|
+
});
|
|
1276
|
+
program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview host dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((options) => {
|
|
1277
|
+
render(/* @__PURE__ */ jsx13(App, { command: "scaffold" /* SCAFFOLD */, options }));
|
|
1029
1278
|
});
|
|
1030
|
-
|
|
1279
|
+
if (process.argv[1]?.endsWith("create-extension")) {
|
|
1280
|
+
process.stderr.write("\u26A0\uFE0F DEPRECATED: `create-extension` is deprecated and will be removed in v2.0.0. Use `stackable-extension create` instead.\n");
|
|
1281
|
+
program.parse(["node", "stackable-extension", "create", ...process.argv.slice(2).filter((arg) => arg !== "--")]);
|
|
1282
|
+
} else {
|
|
1283
|
+
program.parse(process.argv.filter((arg) => arg !== "--"));
|
|
1284
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackable-labs/cli-app-extension",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-extension": "./dist/index.js",
|
|
8
|
-
"
|
|
8
|
+
"stackable-extension": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"commander": "12.x",
|
|
17
17
|
"giget": "3.x",
|
|
18
18
|
"ink": "5.x",
|
|
19
|
-
"ink-select-input": "6.x",
|
|
20
19
|
"ink-spinner": "5.x",
|
|
21
20
|
"ink-text-input": "6.x",
|
|
22
21
|
"nypm": "0.4.x",
|