@stackable-labs/cli-app-extension 1.18.0 → 1.20.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 +517 -123
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { createServer } from "http";
|
|
6
5
|
import { program } from "commander";
|
|
7
6
|
import { render } from "ink";
|
|
8
|
-
import open from "open";
|
|
9
7
|
|
|
10
8
|
// src/App.tsx
|
|
11
9
|
import { join as join3 } from "path";
|
|
@@ -777,7 +775,7 @@ var fetchApps = async (token) => {
|
|
|
777
775
|
iconUrl
|
|
778
776
|
}));
|
|
779
777
|
};
|
|
780
|
-
var createExtensionRemote = async (appId,
|
|
778
|
+
var createExtensionRemote = async (appId, token, payload) => {
|
|
781
779
|
const baseUrl = getAdminApiBaseUrl();
|
|
782
780
|
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`, {
|
|
783
781
|
method: "POST",
|
|
@@ -860,25 +858,37 @@ var gradientColor = (row, col, rows, cols) => {
|
|
|
860
858
|
const hi = Math.min(lo + 1, COLORS.length - 1);
|
|
861
859
|
return lerp(COLORS[lo], COLORS[hi], idx - lo);
|
|
862
860
|
};
|
|
863
|
-
var Banner = () => {
|
|
861
|
+
var Banner = ({ userId, orgId } = {}) => {
|
|
864
862
|
const termWidth = process.stdout.columns ?? 80;
|
|
865
863
|
const maxLen = Math.max(...WORDMARK.map((l) => l.length));
|
|
866
864
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
867
865
|
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2500".repeat(termWidth) }),
|
|
868
|
-
/* @__PURE__ */
|
|
866
|
+
/* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
867
|
+
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)),
|
|
868
|
+
userId && orgId && /* @__PURE__ */ jsxs10(Box10, { gap: 2, marginTop: 1, children: [
|
|
869
|
+
/* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
|
|
870
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "User:" }),
|
|
871
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: userId })
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
|
|
874
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Org:" }),
|
|
875
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: orgId })
|
|
876
|
+
] })
|
|
877
|
+
] })
|
|
878
|
+
] })
|
|
869
879
|
] });
|
|
870
880
|
};
|
|
871
881
|
|
|
872
882
|
// src/components/AppSelect.tsx
|
|
873
883
|
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
874
|
-
var AppSelect = ({ token, onSubmit }) => {
|
|
884
|
+
var AppSelect = ({ token, userId, orgId, onSubmit }) => {
|
|
875
885
|
const [apps, setApps] = useState6([]);
|
|
876
886
|
const [loading, setLoading] = useState6(true);
|
|
877
887
|
const [error, setError] = useState6();
|
|
878
888
|
const [cursor, setCursor] = useState6(0);
|
|
879
889
|
useEffect2(() => {
|
|
880
890
|
fetchApps(token).then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
881
|
-
}, []);
|
|
891
|
+
}, [token]);
|
|
882
892
|
useInput6((_, key) => {
|
|
883
893
|
if (loading || error || apps.length === 0) return;
|
|
884
894
|
if (key.upArrow) {
|
|
@@ -919,7 +929,7 @@ var AppSelect = ({ token, onSubmit }) => {
|
|
|
919
929
|
}) });
|
|
920
930
|
};
|
|
921
931
|
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
922
|
-
/* @__PURE__ */ jsx11(Banner, {}),
|
|
932
|
+
/* @__PURE__ */ jsx11(Banner, { userId, orgId }),
|
|
923
933
|
/* @__PURE__ */ jsx11(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
|
|
924
934
|
] });
|
|
925
935
|
};
|
|
@@ -936,7 +946,7 @@ var ExtensionSelect = ({ appId, token, command, onSubmit, onBack }) => {
|
|
|
936
946
|
const [cursor, setCursor] = useState7(0);
|
|
937
947
|
useEffect3(() => {
|
|
938
948
|
fetchExtensions(appId, token).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
939
|
-
}, [appId]);
|
|
949
|
+
}, [appId, token]);
|
|
940
950
|
useInput7((_, key) => {
|
|
941
951
|
if (key.upArrow) {
|
|
942
952
|
if (!loading && !error && extensions.length > 0) {
|
|
@@ -1511,7 +1521,7 @@ var derivePermissions2 = (targets) => {
|
|
|
1511
1521
|
}
|
|
1512
1522
|
return [...permissions];
|
|
1513
1523
|
};
|
|
1514
|
-
var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
1524
|
+
var App = ({ command, token, userId, orgId, initialName, initialExtensionId, options }) => {
|
|
1515
1525
|
const { exit } = useApp();
|
|
1516
1526
|
const [step, setStep] = useState8("app");
|
|
1517
1527
|
const [name, setName] = useState8(initialName ?? options?.name ?? "");
|
|
@@ -1716,17 +1726,25 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1716
1726
|
};
|
|
1717
1727
|
switch (step) {
|
|
1718
1728
|
case "app": {
|
|
1719
|
-
return /* @__PURE__ */ jsx13(
|
|
1729
|
+
return /* @__PURE__ */ jsx13(
|
|
1730
|
+
AppSelect,
|
|
1731
|
+
{
|
|
1732
|
+
token,
|
|
1733
|
+
orgId,
|
|
1734
|
+
userId,
|
|
1735
|
+
onSubmit: handleAppSelect
|
|
1736
|
+
}
|
|
1737
|
+
);
|
|
1720
1738
|
}
|
|
1721
1739
|
case "extensionSelect": {
|
|
1722
1740
|
return /* @__PURE__ */ jsx13(
|
|
1723
1741
|
ExtensionSelect,
|
|
1724
1742
|
{
|
|
1725
1743
|
appId: selectedApp.id,
|
|
1726
|
-
token,
|
|
1727
1744
|
command,
|
|
1728
|
-
|
|
1729
|
-
onBack: goBack
|
|
1745
|
+
token,
|
|
1746
|
+
onBack: goBack,
|
|
1747
|
+
onSubmit: handleExtensionSelect
|
|
1730
1748
|
}
|
|
1731
1749
|
);
|
|
1732
1750
|
}
|
|
@@ -1736,8 +1754,8 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1736
1754
|
{
|
|
1737
1755
|
availableTargets: selectedApp?.targets ?? [],
|
|
1738
1756
|
preSelected: targets,
|
|
1739
|
-
|
|
1740
|
-
|
|
1757
|
+
onBack: goBack,
|
|
1758
|
+
onSubmit: handleConfirmTargets
|
|
1741
1759
|
}
|
|
1742
1760
|
);
|
|
1743
1761
|
}
|
|
@@ -1746,8 +1764,8 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1746
1764
|
NamePrompt,
|
|
1747
1765
|
{
|
|
1748
1766
|
initialValue: name,
|
|
1749
|
-
|
|
1750
|
-
|
|
1767
|
+
onBack: goBack,
|
|
1768
|
+
onSubmit: handleName
|
|
1751
1769
|
}
|
|
1752
1770
|
);
|
|
1753
1771
|
}
|
|
@@ -1777,10 +1795,10 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1777
1795
|
{
|
|
1778
1796
|
command,
|
|
1779
1797
|
name,
|
|
1780
|
-
extensionPort,
|
|
1781
|
-
previewPort,
|
|
1782
1798
|
targets,
|
|
1783
1799
|
outputDir,
|
|
1800
|
+
previewPort,
|
|
1801
|
+
extensionPort,
|
|
1784
1802
|
extensionVersion: command !== "create" /* CREATE */ ? extensionVersion : void 0,
|
|
1785
1803
|
bundleUrl: command === "update" /* UPDATE */ ? bundleUrl : void 0,
|
|
1786
1804
|
enabled: command === "update" /* UPDATE */ ? enabled : void 0,
|
|
@@ -1801,9 +1819,9 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1801
1819
|
{
|
|
1802
1820
|
name,
|
|
1803
1821
|
targets,
|
|
1804
|
-
availableTargets: selectedApp?.targets ?? [],
|
|
1805
|
-
bundleUrl,
|
|
1806
1822
|
enabled,
|
|
1823
|
+
bundleUrl,
|
|
1824
|
+
availableTargets: selectedApp?.targets ?? [],
|
|
1807
1825
|
onSubmit: (updated) => {
|
|
1808
1826
|
setName(updated.name);
|
|
1809
1827
|
setTargets(updated.targets);
|
|
@@ -1856,8 +1874,8 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1856
1874
|
};
|
|
1857
1875
|
|
|
1858
1876
|
// src/components/DevApp.tsx
|
|
1859
|
-
import { Box as Box16, Text as Text16 } from "ink";
|
|
1860
1877
|
import { useRef, useState as useState10, useEffect as useEffect6, useCallback as useCallback2 } from "react";
|
|
1878
|
+
import { useInput as useInput9, Box as Box16, Text as Text16 } from "ink";
|
|
1861
1879
|
|
|
1862
1880
|
// src/lib/tunnel.ts
|
|
1863
1881
|
import { Tunnel } from "cloudflared";
|
|
@@ -1916,7 +1934,13 @@ var DevSetup = ({ initialContext, token, onReady }) => {
|
|
|
1916
1934
|
if (step === "app") {
|
|
1917
1935
|
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
1918
1936
|
/* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: "Select the App for your extension:" }) }),
|
|
1919
|
-
/* @__PURE__ */ jsx14(
|
|
1937
|
+
/* @__PURE__ */ jsx14(
|
|
1938
|
+
AppSelect,
|
|
1939
|
+
{
|
|
1940
|
+
token,
|
|
1941
|
+
onSubmit: handleAppSelect
|
|
1942
|
+
}
|
|
1943
|
+
)
|
|
1920
1944
|
] });
|
|
1921
1945
|
}
|
|
1922
1946
|
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
@@ -1924,8 +1948,8 @@ var DevSetup = ({ initialContext, token, onReady }) => {
|
|
|
1924
1948
|
/* @__PURE__ */ jsx14(
|
|
1925
1949
|
ExtensionSelect,
|
|
1926
1950
|
{
|
|
1927
|
-
appId: selectedApp?.id || initialContext.appId,
|
|
1928
1951
|
token,
|
|
1952
|
+
appId: selectedApp?.id || initialContext.appId,
|
|
1929
1953
|
onSubmit: handleExtensionSelect
|
|
1930
1954
|
}
|
|
1931
1955
|
)
|
|
@@ -1937,14 +1961,16 @@ import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
|
|
|
1937
1961
|
import { useEffect as useEffect5 } from "react";
|
|
1938
1962
|
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1939
1963
|
var DevDashboard = ({
|
|
1940
|
-
|
|
1941
|
-
|
|
1964
|
+
previewTunnelUrl,
|
|
1965
|
+
tunnelUrl,
|
|
1966
|
+
userId,
|
|
1967
|
+
orgId,
|
|
1942
1968
|
appId,
|
|
1943
1969
|
appName,
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
previewPort,
|
|
1970
|
+
extensionId,
|
|
1971
|
+
extensionName,
|
|
1947
1972
|
extensionPort,
|
|
1973
|
+
previewPort,
|
|
1948
1974
|
onQuit
|
|
1949
1975
|
}) => {
|
|
1950
1976
|
useEffect5(() => {
|
|
@@ -1962,7 +1988,7 @@ var DevDashboard = ({
|
|
|
1962
1988
|
}
|
|
1963
1989
|
});
|
|
1964
1990
|
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
|
|
1965
|
-
/* @__PURE__ */ jsx15(Banner, {}),
|
|
1991
|
+
/* @__PURE__ */ jsx15(Banner, { userId, orgId }),
|
|
1966
1992
|
/* @__PURE__ */ jsxs15(
|
|
1967
1993
|
StepShell,
|
|
1968
1994
|
{
|
|
@@ -2030,7 +2056,7 @@ var DevDashboard = ({
|
|
|
2030
2056
|
|
|
2031
2057
|
// src/components/DevApp.tsx
|
|
2032
2058
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2033
|
-
var DevApp = ({ token, options = {} }) => {
|
|
2059
|
+
var DevApp = ({ token, userId, orgId, options = {} }) => {
|
|
2034
2060
|
const [state, setState] = useState10("setup");
|
|
2035
2061
|
const [devContext, setDevContext] = useState10(null);
|
|
2036
2062
|
const [resolvedContext, setResolvedContext] = useState10(null);
|
|
@@ -2123,13 +2149,22 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2123
2149
|
console.log("[dev] Done");
|
|
2124
2150
|
process.exit(0);
|
|
2125
2151
|
};
|
|
2152
|
+
useInput9((input, key) => {
|
|
2153
|
+
if (input === "c" && key.ctrl) {
|
|
2154
|
+
if (state === "running") {
|
|
2155
|
+
handleQuit();
|
|
2156
|
+
} else {
|
|
2157
|
+
process.exit(0);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2126
2161
|
if (state === "setup" && devContext) {
|
|
2127
2162
|
if (!devContext.appId || !devContext.extensionId) {
|
|
2128
2163
|
return /* @__PURE__ */ jsx16(
|
|
2129
2164
|
DevSetup,
|
|
2130
2165
|
{
|
|
2131
|
-
initialContext: devContext,
|
|
2132
2166
|
token,
|
|
2167
|
+
initialContext: devContext,
|
|
2133
2168
|
onReady: handleSetupReady
|
|
2134
2169
|
}
|
|
2135
2170
|
);
|
|
@@ -2140,14 +2175,16 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2140
2175
|
return /* @__PURE__ */ jsx16(
|
|
2141
2176
|
DevDashboard,
|
|
2142
2177
|
{
|
|
2143
|
-
|
|
2144
|
-
|
|
2178
|
+
previewTunnelUrl,
|
|
2179
|
+
tunnelUrl,
|
|
2180
|
+
orgId,
|
|
2181
|
+
userId,
|
|
2145
2182
|
appId: resolvedContext.appId,
|
|
2146
2183
|
appName: resolvedContext.appName,
|
|
2184
|
+
extensionName: devContext.extensionName,
|
|
2185
|
+
extensionId: resolvedContext.extensionId,
|
|
2147
2186
|
extensionPort: options.extensionPort ? parseInt(options.extensionPort, 10) : devContext.extensionPort,
|
|
2148
2187
|
previewPort: options.previewPort ? parseInt(options.previewPort, 10) : devContext.previewPort,
|
|
2149
|
-
tunnelUrl,
|
|
2150
|
-
previewTunnelUrl,
|
|
2151
2188
|
onQuit: handleQuit
|
|
2152
2189
|
}
|
|
2153
2190
|
);
|
|
@@ -2158,12 +2195,138 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2158
2195
|
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { children: "Loading..." }) });
|
|
2159
2196
|
};
|
|
2160
2197
|
|
|
2198
|
+
// src/components/AIScaffold.tsx
|
|
2199
|
+
import { Box as Box17, Text as Text17, useApp as useApp2 } from "ink";
|
|
2200
|
+
import Spinner4 from "ink-spinner";
|
|
2201
|
+
import { useState as useState11, useEffect as useEffect7 } from "react";
|
|
2202
|
+
|
|
2203
|
+
// src/lib/aiDocs.ts
|
|
2204
|
+
import { existsSync, readFileSync } from "fs";
|
|
2205
|
+
import { join as join4, dirname } from "path";
|
|
2206
|
+
import { mkdir, writeFile as writeFile3 } from "fs/promises";
|
|
2207
|
+
import AdmZip from "adm-zip";
|
|
2208
|
+
var DEFAULT_STATIC_CDN_URL = "https://static.stackablelabs.io";
|
|
2209
|
+
var AI_DOCS_FILENAME = "extension-ai-docs.zip";
|
|
2210
|
+
var getStaticCdnBaseUrl = () => process.env.STATIC_CDN_BASE_URL ?? DEFAULT_STATIC_CDN_URL;
|
|
2211
|
+
var isValidManifest = (data) => {
|
|
2212
|
+
if (!data || typeof data !== "object") return false;
|
|
2213
|
+
const m = data;
|
|
2214
|
+
return typeof m.name === "string" && m.name.length > 0 && typeof m.version === "string" && Array.isArray(m.targets) && Array.isArray(m.permissions) && Array.isArray(m.allowedDomains);
|
|
2215
|
+
};
|
|
2216
|
+
var isExtensionProject = (dir) => {
|
|
2217
|
+
const manifestPath = join4(dir, "packages/extension/public/manifest.json");
|
|
2218
|
+
if (!existsSync(manifestPath)) {
|
|
2219
|
+
return { valid: false, reason: "No manifest.json found at packages/extension/public/manifest.json \u2014 is this an Extension project?" };
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
const raw = readFileSync(manifestPath, "utf8");
|
|
2223
|
+
const data = JSON.parse(raw);
|
|
2224
|
+
if (!isValidManifest(data)) {
|
|
2225
|
+
return { valid: false, reason: "Invalid manifest: missing required fields (name, version, targets, permissions, allowedDomains)" };
|
|
2226
|
+
}
|
|
2227
|
+
} catch {
|
|
2228
|
+
return { valid: false, reason: "Failed to parse manifest.json" };
|
|
2229
|
+
}
|
|
2230
|
+
return { valid: true };
|
|
2231
|
+
};
|
|
2232
|
+
var downloadAndExtractAiDocs = async (targetDir, version2) => {
|
|
2233
|
+
const baseUrl = getStaticCdnBaseUrl();
|
|
2234
|
+
const zipUrl = `${baseUrl}/ai-docs/${version2}/${AI_DOCS_FILENAME}`;
|
|
2235
|
+
const response = await fetch(zipUrl);
|
|
2236
|
+
if (!response.ok) {
|
|
2237
|
+
throw new Error(`Failed to download AI docs from ${zipUrl}: ${response.status} ${response.statusText}`);
|
|
2238
|
+
}
|
|
2239
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
2240
|
+
const zip = new AdmZip(buffer);
|
|
2241
|
+
const entries = zip.getEntries();
|
|
2242
|
+
const extractedFiles = [];
|
|
2243
|
+
for (const entry of entries) {
|
|
2244
|
+
if (entry.isDirectory) continue;
|
|
2245
|
+
const targetPath = join4(targetDir, entry.entryName);
|
|
2246
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
2247
|
+
await writeFile3(targetPath, entry.getData());
|
|
2248
|
+
extractedFiles.push(entry.entryName);
|
|
2249
|
+
}
|
|
2250
|
+
return { files: extractedFiles.sort() };
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
// src/components/AIScaffold.tsx
|
|
2254
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2255
|
+
var AIScaffold = ({ version: version2 }) => {
|
|
2256
|
+
const { exit } = useApp2();
|
|
2257
|
+
const [state, setState] = useState11("validating");
|
|
2258
|
+
const [files, setFiles] = useState11([]);
|
|
2259
|
+
const [errorMessage, setErrorMessage] = useState11("");
|
|
2260
|
+
useEffect7(() => {
|
|
2261
|
+
const run = async () => {
|
|
2262
|
+
const projectDir = process.cwd();
|
|
2263
|
+
const check = isExtensionProject(projectDir);
|
|
2264
|
+
if (!check.valid) {
|
|
2265
|
+
setErrorMessage(check.reason);
|
|
2266
|
+
setState("error");
|
|
2267
|
+
exit();
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
setState("downloading");
|
|
2271
|
+
try {
|
|
2272
|
+
const result = await downloadAndExtractAiDocs(projectDir, version2);
|
|
2273
|
+
setFiles(result.files);
|
|
2274
|
+
setState("done");
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
2277
|
+
setState("error");
|
|
2278
|
+
}
|
|
2279
|
+
exit();
|
|
2280
|
+
};
|
|
2281
|
+
run();
|
|
2282
|
+
}, []);
|
|
2283
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
|
|
2284
|
+
/* @__PURE__ */ jsx17(Banner, {}),
|
|
2285
|
+
/* @__PURE__ */ jsxs16(StepShell, { title: "AI Editor Config", children: [
|
|
2286
|
+
state === "validating" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2287
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
|
|
2288
|
+
/* @__PURE__ */ jsx17(Text17, { children: "Checking project..." })
|
|
2289
|
+
] }),
|
|
2290
|
+
state === "downloading" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2291
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
|
|
2292
|
+
/* @__PURE__ */ jsxs16(Text17, { children: [
|
|
2293
|
+
"Downloading AI editor configs (",
|
|
2294
|
+
version2,
|
|
2295
|
+
")..."
|
|
2296
|
+
] })
|
|
2297
|
+
] }),
|
|
2298
|
+
state === "done" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
|
|
2299
|
+
/* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2300
|
+
/* @__PURE__ */ jsx17(Text17, { color: "green", bold: true, children: "\u2714" }),
|
|
2301
|
+
/* @__PURE__ */ jsxs16(Text17, { bold: true, children: [
|
|
2302
|
+
"AI editor configs installed (",
|
|
2303
|
+
files.length,
|
|
2304
|
+
" files)"
|
|
2305
|
+
] })
|
|
2306
|
+
] }),
|
|
2307
|
+
/* @__PURE__ */ jsx17(Box17, { flexDirection: "column", marginLeft: 2, children: files.map((f) => /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: f }, f)) })
|
|
2308
|
+
] }),
|
|
2309
|
+
state === "error" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2310
|
+
/* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2716" }),
|
|
2311
|
+
/* @__PURE__ */ jsx17(Text17, { children: errorMessage })
|
|
2312
|
+
] })
|
|
2313
|
+
] })
|
|
2314
|
+
] });
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
// src/components/AuthLogin.tsx
|
|
2318
|
+
import { createServer } from "http";
|
|
2319
|
+
import { Box as Box18, Text as Text18, useApp as useApp3 } from "ink";
|
|
2320
|
+
import Spinner5 from "ink-spinner";
|
|
2321
|
+
import open from "open";
|
|
2322
|
+
import { useState as useState12, useEffect as useEffect8 } from "react";
|
|
2323
|
+
|
|
2161
2324
|
// src/lib/auth.ts
|
|
2162
|
-
import { readFile as readFile3, writeFile as
|
|
2163
|
-
import { join as
|
|
2325
|
+
import { readFile as readFile3, writeFile as writeFile4, mkdir as mkdir2, unlink } from "fs/promises";
|
|
2326
|
+
import { join as join5 } from "path";
|
|
2164
2327
|
import { homedir } from "os";
|
|
2165
|
-
var AUTH_DIR =
|
|
2166
|
-
var AUTH_FILE =
|
|
2328
|
+
var AUTH_DIR = join5(homedir(), ".stackable");
|
|
2329
|
+
var AUTH_FILE = join5(AUTH_DIR, "auth.json");
|
|
2167
2330
|
var readAuthState = async () => {
|
|
2168
2331
|
try {
|
|
2169
2332
|
const content = await readFile3(AUTH_FILE, "utf8");
|
|
@@ -2173,8 +2336,8 @@ var readAuthState = async () => {
|
|
|
2173
2336
|
}
|
|
2174
2337
|
};
|
|
2175
2338
|
var writeAuthState = async (state) => {
|
|
2176
|
-
await
|
|
2177
|
-
await
|
|
2339
|
+
await mkdir2(AUTH_DIR, { recursive: true, mode: 448 });
|
|
2340
|
+
await writeFile4(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
2178
2341
|
};
|
|
2179
2342
|
var clearAuthState = async () => {
|
|
2180
2343
|
try {
|
|
@@ -2185,7 +2348,9 @@ var clearAuthState = async () => {
|
|
|
2185
2348
|
var decodeJwtPayload = (token) => {
|
|
2186
2349
|
try {
|
|
2187
2350
|
const [, payload] = token.split(".");
|
|
2188
|
-
if (!payload)
|
|
2351
|
+
if (!payload) {
|
|
2352
|
+
return null;
|
|
2353
|
+
}
|
|
2189
2354
|
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
2190
2355
|
return JSON.parse(json);
|
|
2191
2356
|
} catch {
|
|
@@ -2195,17 +2360,217 @@ var decodeJwtPayload = (token) => {
|
|
|
2195
2360
|
var getToken = async () => {
|
|
2196
2361
|
const state = await readAuthState();
|
|
2197
2362
|
if (!state) {
|
|
2198
|
-
throw new Error("Not authenticated. Run `stackable auth login` first.");
|
|
2363
|
+
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
2199
2364
|
}
|
|
2200
2365
|
const payload = decodeJwtPayload(state.token);
|
|
2201
2366
|
if (payload?.exp && typeof payload.exp === "number") {
|
|
2202
2367
|
if (Date.now() >= payload.exp * 1e3) {
|
|
2203
|
-
throw new Error("Session expired. Run `stackable auth login` to re-authenticate.");
|
|
2368
|
+
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
2204
2369
|
}
|
|
2205
2370
|
}
|
|
2206
2371
|
return state.token;
|
|
2207
2372
|
};
|
|
2208
2373
|
|
|
2374
|
+
// src/components/AuthLogin.tsx
|
|
2375
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2376
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2377
|
+
var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
|
|
2378
|
+
<html><head><meta charset="utf-8"><title>Stackable CLI</title>
|
|
2379
|
+
<style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0a0a0a;color:#fafafa;font-family:system-ui,sans-serif}
|
|
2380
|
+
.card{text-align:center;padding:2rem}.card h2{margin:0 0 .5rem}.card p{color:#888;margin:0}.hint{margin-top:1rem;color:#555;font-size:.85rem}</style>
|
|
2381
|
+
</head><body><div class="card"><h2>${heading}</h2><p>${sub}</p><p class="hint" id="h"></p></div>
|
|
2382
|
+
${redirectUrl ? `<script>(function(){var s=3,el=document.getElementById('h');function t(){if(s<=0){location.href='${redirectUrl}';return}el.textContent='Redirecting in '+s+'s\u2026';s--;setTimeout(t,1000)}t()})()</script>` : ""}
|
|
2383
|
+
</body></html>`;
|
|
2384
|
+
var AuthLogin = ({ dashboardUrl }) => {
|
|
2385
|
+
const { exit } = useApp3();
|
|
2386
|
+
const [state, setState] = useState12("waiting");
|
|
2387
|
+
const [loginUrl, setLoginUrl] = useState12("");
|
|
2388
|
+
const [userIdLabel, setUserIdLabel] = useState12("");
|
|
2389
|
+
const [orgIdLabel, setOrgIdLabel] = useState12("");
|
|
2390
|
+
const [errorMessage, setErrorMessage] = useState12("");
|
|
2391
|
+
useEffect8(() => {
|
|
2392
|
+
let server;
|
|
2393
|
+
let timeout;
|
|
2394
|
+
const run = async () => {
|
|
2395
|
+
let resolveToken;
|
|
2396
|
+
let rejectToken;
|
|
2397
|
+
const tokenPromise = new Promise((resolve, reject) => {
|
|
2398
|
+
resolveToken = resolve;
|
|
2399
|
+
rejectToken = reject;
|
|
2400
|
+
});
|
|
2401
|
+
server = createServer((req, res) => {
|
|
2402
|
+
const url2 = new URL(req.url, "http://localhost");
|
|
2403
|
+
if (url2.pathname === "/callback") {
|
|
2404
|
+
const error = url2.searchParams.get("error");
|
|
2405
|
+
if (error) {
|
|
2406
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2407
|
+
res.end(callbackPage("Authentication failed", "You can close this tab."));
|
|
2408
|
+
rejectToken(new Error(error));
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const token2 = url2.searchParams.get("token");
|
|
2412
|
+
if (token2) {
|
|
2413
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2414
|
+
res.end(callbackPage("CLI authenticated", "You can return to your terminal.", dashboardUrl));
|
|
2415
|
+
resolveToken(token2);
|
|
2416
|
+
} else {
|
|
2417
|
+
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
2418
|
+
res.end("Missing token");
|
|
2419
|
+
}
|
|
2420
|
+
} else {
|
|
2421
|
+
res.writeHead(404);
|
|
2422
|
+
res.end();
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
2426
|
+
const port = server.address().port;
|
|
2427
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
2428
|
+
const url = `${dashboardUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
2429
|
+
setLoginUrl(url);
|
|
2430
|
+
await open(url);
|
|
2431
|
+
timeout = setTimeout(() => {
|
|
2432
|
+
server.close();
|
|
2433
|
+
rejectToken(new Error("Login timed out. Please try again."));
|
|
2434
|
+
}, LOGIN_TIMEOUT_MS);
|
|
2435
|
+
let token;
|
|
2436
|
+
try {
|
|
2437
|
+
token = await tokenPromise;
|
|
2438
|
+
} catch (err) {
|
|
2439
|
+
clearTimeout(timeout);
|
|
2440
|
+
server.close();
|
|
2441
|
+
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
2442
|
+
setState("error");
|
|
2443
|
+
exit();
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
clearTimeout(timeout);
|
|
2447
|
+
server.close();
|
|
2448
|
+
const [, payloadB64] = token.split(".");
|
|
2449
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2450
|
+
await writeAuthState({
|
|
2451
|
+
token,
|
|
2452
|
+
userId: payload.sub,
|
|
2453
|
+
orgId: payload.orgId
|
|
2454
|
+
});
|
|
2455
|
+
setUserIdLabel(payload.sub);
|
|
2456
|
+
setOrgIdLabel(payload.orgId);
|
|
2457
|
+
setState("success");
|
|
2458
|
+
exit();
|
|
2459
|
+
};
|
|
2460
|
+
run();
|
|
2461
|
+
return () => {
|
|
2462
|
+
clearTimeout(timeout);
|
|
2463
|
+
server?.close();
|
|
2464
|
+
};
|
|
2465
|
+
}, []);
|
|
2466
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
|
|
2467
|
+
/* @__PURE__ */ jsx18(Banner, {}),
|
|
2468
|
+
/* @__PURE__ */ jsxs17(StepShell, { title: "Authenticate with Stackable", children: [
|
|
2469
|
+
state === "waiting" && /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", gap: 1, children: [
|
|
2470
|
+
/* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
|
|
2471
|
+
/* @__PURE__ */ jsx18(Text18, { color: "cyan", children: /* @__PURE__ */ jsx18(Spinner5, { type: "dots" }) }),
|
|
2472
|
+
/* @__PURE__ */ jsx18(Text18, { children: "Waiting for browser authentication..." })
|
|
2473
|
+
] }),
|
|
2474
|
+
loginUrl && /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
|
|
2475
|
+
" ",
|
|
2476
|
+
loginUrl
|
|
2477
|
+
] })
|
|
2478
|
+
] }),
|
|
2479
|
+
state === "success" && /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", gap: 1, children: [
|
|
2480
|
+
/* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
|
|
2481
|
+
/* @__PURE__ */ jsxs17(Box18, { gap: 2, children: [
|
|
2482
|
+
/* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "User:" }),
|
|
2483
|
+
/* @__PURE__ */ jsx18(Text18, { color: "cyan", children: userIdLabel })
|
|
2484
|
+
] }),
|
|
2485
|
+
/* @__PURE__ */ jsxs17(Box18, { gap: 2, children: [
|
|
2486
|
+
/* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "Org: " }),
|
|
2487
|
+
/* @__PURE__ */ jsx18(Text18, { color: "cyan", children: orgIdLabel })
|
|
2488
|
+
] })
|
|
2489
|
+
] }),
|
|
2490
|
+
/* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
|
|
2491
|
+
/* @__PURE__ */ jsx18(Text18, { color: "green", bold: true, children: "\u2714" }),
|
|
2492
|
+
/* @__PURE__ */ jsx18(Text18, { bold: true, children: "Authenticated" })
|
|
2493
|
+
] })
|
|
2494
|
+
] }),
|
|
2495
|
+
state === "error" && /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
|
|
2496
|
+
/* @__PURE__ */ jsx18(Text18, { color: "red", children: "\u2716" }),
|
|
2497
|
+
/* @__PURE__ */ jsx18(Text18, { children: errorMessage })
|
|
2498
|
+
] })
|
|
2499
|
+
] })
|
|
2500
|
+
] });
|
|
2501
|
+
};
|
|
2502
|
+
|
|
2503
|
+
// src/components/AuthLogout.tsx
|
|
2504
|
+
import { useEffect as useEffect9 } from "react";
|
|
2505
|
+
import { Box as Box19, Text as Text19, useApp as useApp4 } from "ink";
|
|
2506
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2507
|
+
var AuthLogout = () => {
|
|
2508
|
+
const { exit } = useApp4();
|
|
2509
|
+
useEffect9(() => {
|
|
2510
|
+
exit();
|
|
2511
|
+
}, [exit]);
|
|
2512
|
+
return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
|
|
2513
|
+
/* @__PURE__ */ jsx19(Banner, {}),
|
|
2514
|
+
/* @__PURE__ */ jsx19(StepShell, { title: "Authenticate with Stackable", children: /* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
|
|
2515
|
+
/* @__PURE__ */ jsx19(Text19, { color: "green", bold: true, children: "\u2714" }),
|
|
2516
|
+
/* @__PURE__ */ jsx19(Text19, { bold: true, children: "Logged out" })
|
|
2517
|
+
] }) })
|
|
2518
|
+
] });
|
|
2519
|
+
};
|
|
2520
|
+
|
|
2521
|
+
// src/components/AuthStatus.tsx
|
|
2522
|
+
import { useEffect as useEffect10 } from "react";
|
|
2523
|
+
import { useApp as useApp5, Box as Box20, Text as Text20 } from "ink";
|
|
2524
|
+
import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2525
|
+
var AuthStatus = ({ state, userId, orgId, expiry }) => {
|
|
2526
|
+
const { exit } = useApp5();
|
|
2527
|
+
useEffect10(() => {
|
|
2528
|
+
exit();
|
|
2529
|
+
}, [exit]);
|
|
2530
|
+
return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
|
|
2531
|
+
/* @__PURE__ */ jsx20(Banner, {}),
|
|
2532
|
+
/* @__PURE__ */ jsxs19(StepShell, { title: "Authenticate with Stackable", children: [
|
|
2533
|
+
state === "authenticated" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
|
|
2534
|
+
/* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
|
|
2535
|
+
/* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
|
|
2536
|
+
/* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "User:" }),
|
|
2537
|
+
/* @__PURE__ */ jsx20(Text20, { color: "cyan", children: userId })
|
|
2538
|
+
] }),
|
|
2539
|
+
/* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
|
|
2540
|
+
/* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Org: " }),
|
|
2541
|
+
/* @__PURE__ */ jsx20(Text20, { color: "cyan", children: orgId })
|
|
2542
|
+
] }),
|
|
2543
|
+
expiry && /* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
|
|
2544
|
+
/* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Exp: " }),
|
|
2545
|
+
/* @__PURE__ */ jsx20(Text20, { color: "cyan", children: expiry.toLocaleDateString() })
|
|
2546
|
+
] })
|
|
2547
|
+
] }),
|
|
2548
|
+
/* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
|
|
2549
|
+
/* @__PURE__ */ jsx20(Text20, { color: "green", bold: true, children: "\u2714" }),
|
|
2550
|
+
/* @__PURE__ */ jsx20(Text20, { bold: true, children: "Authenticated" })
|
|
2551
|
+
] })
|
|
2552
|
+
] }),
|
|
2553
|
+
state === "expired" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
|
|
2554
|
+
/* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
|
|
2555
|
+
/* @__PURE__ */ jsx20(Text20, { color: "red", children: "\u2716" }),
|
|
2556
|
+
/* @__PURE__ */ jsxs19(Text20, { children: [
|
|
2557
|
+
"Session expired",
|
|
2558
|
+
expiry ? ` (${expiry.toLocaleDateString()})` : ""
|
|
2559
|
+
] })
|
|
2560
|
+
] }),
|
|
2561
|
+
/* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Run `stackable-app-extension auth login` to re-authenticate." })
|
|
2562
|
+
] }),
|
|
2563
|
+
state === "not-logged-in" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
|
|
2564
|
+
/* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
|
|
2565
|
+
/* @__PURE__ */ jsx20(Text20, { color: "red", children: "\u2716" }),
|
|
2566
|
+
/* @__PURE__ */ jsx20(Text20, { children: "Not logged in" })
|
|
2567
|
+
] }),
|
|
2568
|
+
/* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Run `stackable-app-extension auth login`" })
|
|
2569
|
+
] })
|
|
2570
|
+
] })
|
|
2571
|
+
] });
|
|
2572
|
+
};
|
|
2573
|
+
|
|
2209
2574
|
// src/lib/versionCheck.ts
|
|
2210
2575
|
import https from "https";
|
|
2211
2576
|
var PACKAGE_NAME = "@stackable-labs/cli-app-extension";
|
|
@@ -2254,112 +2619,141 @@ var checkForUpdate = (currentVersion) => {
|
|
|
2254
2619
|
};
|
|
2255
2620
|
|
|
2256
2621
|
// src/index.tsx
|
|
2257
|
-
import { jsx as
|
|
2622
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
2258
2623
|
var require2 = createRequire(import.meta.url);
|
|
2259
2624
|
var { version } = require2("../package.json");
|
|
2260
2625
|
checkForUpdate(version);
|
|
2626
|
+
var ensureToken = async () => {
|
|
2627
|
+
try {
|
|
2628
|
+
const token = await getToken();
|
|
2629
|
+
const state = await readAuthState();
|
|
2630
|
+
return { token, userId: state.userId, orgId: state.orgId };
|
|
2631
|
+
} catch (err) {
|
|
2632
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2633
|
+
const isExpired = message.toLowerCase().includes("expired");
|
|
2634
|
+
render(
|
|
2635
|
+
/* @__PURE__ */ jsx21(AuthStatus, { state: isExpired ? "expired" : "not-logged-in" })
|
|
2636
|
+
);
|
|
2637
|
+
return null;
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2261
2640
|
program.name("stackable-app-extension").description("Stackable Labs - App Extension developer CLI").version(version);
|
|
2262
2641
|
program.command("create" /* CREATE */).description("Create a new Extension project").argument("[name]", "Extension project name").option("--extension-port <port>", "Extension dev server port (default: 6543)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action(async (name, options) => {
|
|
2263
|
-
const
|
|
2264
|
-
|
|
2642
|
+
const auth2 = await ensureToken();
|
|
2643
|
+
if (!auth2) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
const { token, userId, orgId } = auth2;
|
|
2647
|
+
render(
|
|
2648
|
+
/* @__PURE__ */ jsx21(
|
|
2649
|
+
App,
|
|
2650
|
+
{
|
|
2651
|
+
command: "create" /* CREATE */,
|
|
2652
|
+
initialName: name,
|
|
2653
|
+
options,
|
|
2654
|
+
token,
|
|
2655
|
+
orgId,
|
|
2656
|
+
userId
|
|
2657
|
+
}
|
|
2658
|
+
)
|
|
2659
|
+
);
|
|
2265
2660
|
});
|
|
2266
2661
|
program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 6543)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action(async (options) => {
|
|
2267
|
-
const
|
|
2268
|
-
|
|
2662
|
+
const auth2 = await ensureToken();
|
|
2663
|
+
if (!auth2) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
const { token, userId, orgId } = auth2;
|
|
2667
|
+
render(
|
|
2668
|
+
/* @__PURE__ */ jsx21(
|
|
2669
|
+
App,
|
|
2670
|
+
{
|
|
2671
|
+
command: "scaffold" /* SCAFFOLD */,
|
|
2672
|
+
options,
|
|
2673
|
+
token,
|
|
2674
|
+
orgId,
|
|
2675
|
+
userId
|
|
2676
|
+
}
|
|
2677
|
+
)
|
|
2678
|
+
);
|
|
2269
2679
|
});
|
|
2270
2680
|
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(async (extensionId, options) => {
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2681
|
+
const auth2 = await ensureToken();
|
|
2682
|
+
if (!auth2) {
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
const { token, userId, orgId } = auth2;
|
|
2686
|
+
render(
|
|
2687
|
+
/* @__PURE__ */ jsx21(
|
|
2688
|
+
App,
|
|
2689
|
+
{
|
|
2690
|
+
command: "update" /* UPDATE */,
|
|
2691
|
+
initialExtensionId: extensionId,
|
|
2692
|
+
options,
|
|
2693
|
+
token,
|
|
2694
|
+
userId,
|
|
2695
|
+
orgId
|
|
2696
|
+
}
|
|
2697
|
+
)
|
|
2698
|
+
);
|
|
2273
2699
|
});
|
|
2274
2700
|
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").action(async (options) => {
|
|
2275
|
-
const
|
|
2276
|
-
|
|
2701
|
+
const auth2 = await ensureToken();
|
|
2702
|
+
if (!auth2) {
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const { token, userId, orgId } = auth2;
|
|
2706
|
+
render(
|
|
2707
|
+
/* @__PURE__ */ jsx21(
|
|
2708
|
+
DevApp,
|
|
2709
|
+
{
|
|
2710
|
+
options,
|
|
2711
|
+
token,
|
|
2712
|
+
userId,
|
|
2713
|
+
orgId
|
|
2714
|
+
}
|
|
2715
|
+
),
|
|
2716
|
+
{ exitOnCtrlC: false }
|
|
2717
|
+
);
|
|
2277
2718
|
});
|
|
2278
2719
|
var DASHBOARD_URL = process.env.ADMIN_DASHBOARD_URL ?? "https://admin.stackablelabs.io";
|
|
2279
2720
|
var auth = program.command("auth").description("Manage CLI authentication");
|
|
2280
|
-
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2281
2721
|
auth.command("login").description("Authenticate with Stackable via browser").action(async () => {
|
|
2282
|
-
|
|
2283
|
-
let rejectToken;
|
|
2284
|
-
const tokenPromise = new Promise((resolve, reject) => {
|
|
2285
|
-
resolveToken = resolve;
|
|
2286
|
-
rejectToken = reject;
|
|
2287
|
-
});
|
|
2288
|
-
const server = createServer((req, res) => {
|
|
2289
|
-
const url = new URL(req.url, "http://localhost");
|
|
2290
|
-
if (url.pathname === "/callback") {
|
|
2291
|
-
const error = url.searchParams.get("error");
|
|
2292
|
-
if (error) {
|
|
2293
|
-
res.writeHead(200, { "content-type": "text/html" });
|
|
2294
|
-
res.end("<html><body><h2>Authentication failed \u2014 you can close this tab.</h2></body></html>");
|
|
2295
|
-
rejectToken(new Error(error));
|
|
2296
|
-
return;
|
|
2297
|
-
}
|
|
2298
|
-
const token2 = url.searchParams.get("token");
|
|
2299
|
-
if (token2) {
|
|
2300
|
-
res.writeHead(200, { "content-type": "text/html" });
|
|
2301
|
-
res.end("<html><body><h2>CLI authenticated \u2014 you can close this tab.</h2></body></html>");
|
|
2302
|
-
resolveToken(token2);
|
|
2303
|
-
} else {
|
|
2304
|
-
res.writeHead(400, { "content-type": "text/plain" });
|
|
2305
|
-
res.end("Missing token");
|
|
2306
|
-
}
|
|
2307
|
-
} else {
|
|
2308
|
-
res.writeHead(404);
|
|
2309
|
-
res.end();
|
|
2310
|
-
}
|
|
2311
|
-
});
|
|
2312
|
-
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
2313
|
-
const port = server.address().port;
|
|
2314
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
2315
|
-
const loginUrl = `${DASHBOARD_URL}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
2316
|
-
console.log(`
|
|
2317
|
-
Opening browser to authenticate...
|
|
2318
|
-
${loginUrl}
|
|
2319
|
-
`);
|
|
2320
|
-
await open(loginUrl);
|
|
2321
|
-
const timeout = setTimeout(() => {
|
|
2322
|
-
server.close();
|
|
2323
|
-
rejectToken(new Error("Login timed out. Please try again."));
|
|
2324
|
-
}, LOGIN_TIMEOUT_MS);
|
|
2325
|
-
let token;
|
|
2326
|
-
try {
|
|
2327
|
-
token = await tokenPromise;
|
|
2328
|
-
} catch (err) {
|
|
2329
|
-
clearTimeout(timeout);
|
|
2330
|
-
server.close();
|
|
2331
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
2332
|
-
process.exit(1);
|
|
2333
|
-
}
|
|
2334
|
-
clearTimeout(timeout);
|
|
2335
|
-
server.close();
|
|
2336
|
-
const [, payloadB64] = token.split(".");
|
|
2337
|
-
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2338
|
-
await writeAuthState({
|
|
2339
|
-
token,
|
|
2340
|
-
userId: payload.sub,
|
|
2341
|
-
orgId: payload.orgId,
|
|
2342
|
-
orgSlug: payload.orgSlug
|
|
2343
|
-
});
|
|
2344
|
-
console.log(`Logged in \u2014 org: ${payload.orgSlug ?? payload.orgId}`);
|
|
2722
|
+
render(/* @__PURE__ */ jsx21(AuthLogin, { dashboardUrl: DASHBOARD_URL }));
|
|
2345
2723
|
});
|
|
2346
2724
|
auth.command("logout").description("Clear stored CLI credentials").action(async () => {
|
|
2347
2725
|
await clearAuthState();
|
|
2348
|
-
|
|
2726
|
+
render(/* @__PURE__ */ jsx21(AuthLogout, {}));
|
|
2349
2727
|
});
|
|
2350
2728
|
auth.command("status").description("Show current authentication status").action(async () => {
|
|
2351
2729
|
const state = await readAuthState();
|
|
2352
2730
|
if (!state) {
|
|
2353
|
-
|
|
2731
|
+
render(
|
|
2732
|
+
/* @__PURE__ */ jsx21(AuthStatus, { state: "not-logged-in" })
|
|
2733
|
+
);
|
|
2354
2734
|
return;
|
|
2355
2735
|
}
|
|
2356
2736
|
const [, payloadB64] = state.token.split(".");
|
|
2357
2737
|
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2358
2738
|
const expiry = payload.exp ? new Date(payload.exp * 1e3) : null;
|
|
2359
2739
|
if (expiry && Date.now() >= expiry.getTime()) {
|
|
2360
|
-
|
|
2740
|
+
render(/* @__PURE__ */ jsx21(AuthStatus, { state: "expired", expiry }));
|
|
2361
2741
|
return;
|
|
2362
2742
|
}
|
|
2363
|
-
|
|
2743
|
+
render(
|
|
2744
|
+
/* @__PURE__ */ jsx21(
|
|
2745
|
+
AuthStatus,
|
|
2746
|
+
{
|
|
2747
|
+
state: "authenticated",
|
|
2748
|
+
userId: state.userId,
|
|
2749
|
+
orgId: state.orgId,
|
|
2750
|
+
expiry
|
|
2751
|
+
}
|
|
2752
|
+
)
|
|
2753
|
+
);
|
|
2754
|
+
});
|
|
2755
|
+
var ai = program.command("ai").description("AI editor configuration tools");
|
|
2756
|
+
ai.command("scaffold").description("Download AI editor config files into your Extension project").option("--version <version>", 'AI docs version (semver or "latest")', "latest").action(async (options) => {
|
|
2757
|
+
render(/* @__PURE__ */ jsx21(AIScaffold, { version: options.version }));
|
|
2364
2758
|
});
|
|
2365
2759
|
program.parse(process.argv.filter((arg) => arg !== "--"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackable-labs/cli-app-extension",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"LICENSE"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@stackable-labs/lib-contracts": "workspace:*",
|
|
16
|
+
"adm-zip": "0.x",
|
|
15
17
|
"cloudflared": "0.x",
|
|
16
18
|
"commander": "12.x",
|
|
17
19
|
"giget": "3.x",
|