@stackable-labs/cli-app-extension 1.17.0 → 1.19.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 +385 -35
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -752,9 +752,17 @@ import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
|
752
752
|
// src/lib/api.ts
|
|
753
753
|
var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
|
|
754
754
|
var getAdminApiBaseUrl = () => process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
|
|
755
|
-
var
|
|
755
|
+
var CLI_CLIENT_NAME = "@stackable-labs/cli-app-extension";
|
|
756
|
+
var authHeaders = (token) => ({
|
|
757
|
+
authorization: `Bearer ${token}`,
|
|
758
|
+
"content-type": "application/json",
|
|
759
|
+
"x-client-name": CLI_CLIENT_NAME
|
|
760
|
+
});
|
|
761
|
+
var fetchApps = async (token) => {
|
|
756
762
|
const baseUrl = getAdminApiBaseUrl();
|
|
757
|
-
const res = await fetch(`${baseUrl}/app-extension
|
|
763
|
+
const res = await fetch(`${baseUrl}/app-extension`, {
|
|
764
|
+
headers: authHeaders(token)
|
|
765
|
+
});
|
|
758
766
|
if (!res.ok) {
|
|
759
767
|
throw new Error(`Failed to fetch apps${baseUrl !== DEFAULT_ADMIN_API_URL ? ` (from ${baseUrl})` : ""}: ${res.status} ${res.statusText}`);
|
|
760
768
|
}
|
|
@@ -767,11 +775,11 @@ var fetchApps = async () => {
|
|
|
767
775
|
iconUrl
|
|
768
776
|
}));
|
|
769
777
|
};
|
|
770
|
-
var createExtensionRemote = async (appId, payload) => {
|
|
778
|
+
var createExtensionRemote = async (appId, payload, token) => {
|
|
771
779
|
const baseUrl = getAdminApiBaseUrl();
|
|
772
780
|
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`, {
|
|
773
781
|
method: "POST",
|
|
774
|
-
headers:
|
|
782
|
+
headers: authHeaders(token),
|
|
775
783
|
body: JSON.stringify(payload)
|
|
776
784
|
});
|
|
777
785
|
if (!res.ok) {
|
|
@@ -780,9 +788,11 @@ var createExtensionRemote = async (appId, payload) => {
|
|
|
780
788
|
}
|
|
781
789
|
return res.json();
|
|
782
790
|
};
|
|
783
|
-
var fetchExtensions = async (appId) => {
|
|
791
|
+
var fetchExtensions = async (appId, token) => {
|
|
784
792
|
const baseUrl = getAdminApiBaseUrl();
|
|
785
|
-
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions
|
|
793
|
+
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`, {
|
|
794
|
+
headers: authHeaders(token)
|
|
795
|
+
});
|
|
786
796
|
if (!res.ok) {
|
|
787
797
|
throw new Error(`Failed to fetch Extensions: ${res.status} ${res.statusText}`);
|
|
788
798
|
}
|
|
@@ -800,11 +810,11 @@ var fetchExtensions = async (appId) => {
|
|
|
800
810
|
])
|
|
801
811
|
);
|
|
802
812
|
};
|
|
803
|
-
var updateExtension = async (appId, extensionId, payload) => {
|
|
813
|
+
var updateExtension = async (appId, extensionId, payload, token) => {
|
|
804
814
|
const baseUrl = getAdminApiBaseUrl();
|
|
805
815
|
const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions/${extensionId}`, {
|
|
806
816
|
method: "PUT",
|
|
807
|
-
headers:
|
|
817
|
+
headers: authHeaders(token),
|
|
808
818
|
body: JSON.stringify(payload)
|
|
809
819
|
});
|
|
810
820
|
if (!res.ok) {
|
|
@@ -848,25 +858,37 @@ var gradientColor = (row, col, rows, cols) => {
|
|
|
848
858
|
const hi = Math.min(lo + 1, COLORS.length - 1);
|
|
849
859
|
return lerp(COLORS[lo], COLORS[hi], idx - lo);
|
|
850
860
|
};
|
|
851
|
-
var Banner = () => {
|
|
861
|
+
var Banner = ({ userId, orgId } = {}) => {
|
|
852
862
|
const termWidth = process.stdout.columns ?? 80;
|
|
853
863
|
const maxLen = Math.max(...WORDMARK.map((l) => l.length));
|
|
854
864
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
855
865
|
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2500".repeat(termWidth) }),
|
|
856
|
-
/* @__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
|
+
] })
|
|
857
879
|
] });
|
|
858
880
|
};
|
|
859
881
|
|
|
860
882
|
// src/components/AppSelect.tsx
|
|
861
883
|
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
862
|
-
var AppSelect = ({ onSubmit }) => {
|
|
884
|
+
var AppSelect = ({ token, userId, orgId, onSubmit }) => {
|
|
863
885
|
const [apps, setApps] = useState6([]);
|
|
864
886
|
const [loading, setLoading] = useState6(true);
|
|
865
887
|
const [error, setError] = useState6();
|
|
866
888
|
const [cursor, setCursor] = useState6(0);
|
|
867
889
|
useEffect2(() => {
|
|
868
|
-
fetchApps().then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
869
|
-
}, []);
|
|
890
|
+
fetchApps(token).then(setApps).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
891
|
+
}, [token]);
|
|
870
892
|
useInput6((_, key) => {
|
|
871
893
|
if (loading || error || apps.length === 0) return;
|
|
872
894
|
if (key.upArrow) {
|
|
@@ -907,7 +929,7 @@ var AppSelect = ({ onSubmit }) => {
|
|
|
907
929
|
}) });
|
|
908
930
|
};
|
|
909
931
|
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
910
|
-
/* @__PURE__ */ jsx11(Banner, {}),
|
|
932
|
+
/* @__PURE__ */ jsx11(Banner, { userId, orgId }),
|
|
911
933
|
/* @__PURE__ */ jsx11(StepShell, { title: "Select the App you are building an Extension for:", children: renderContent() })
|
|
912
934
|
] });
|
|
913
935
|
};
|
|
@@ -917,14 +939,14 @@ import { Box as Box12, Text as Text12, useInput as useInput7 } from "ink";
|
|
|
917
939
|
import Spinner3 from "ink-spinner";
|
|
918
940
|
import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
919
941
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
920
|
-
var ExtensionSelect = ({ appId, command, onSubmit, onBack }) => {
|
|
942
|
+
var ExtensionSelect = ({ appId, token, command, onSubmit, onBack }) => {
|
|
921
943
|
const [extensions, setExtensions] = useState7([]);
|
|
922
944
|
const [loading, setLoading] = useState7(true);
|
|
923
945
|
const [error, setError] = useState7();
|
|
924
946
|
const [cursor, setCursor] = useState7(0);
|
|
925
947
|
useEffect3(() => {
|
|
926
|
-
fetchExtensions(appId).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
927
|
-
}, [appId]);
|
|
948
|
+
fetchExtensions(appId, token).then((byId) => setExtensions(Object.values(byId))).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
|
|
949
|
+
}, [appId, token]);
|
|
928
950
|
useInput7((_, key) => {
|
|
929
951
|
if (key.upArrow) {
|
|
930
952
|
if (!loading && !error && extensions.length > 0) {
|
|
@@ -1074,6 +1096,7 @@ var readDevContext = async (projectRoot) => {
|
|
|
1074
1096
|
appId: stackableEnv.APP_ID || null,
|
|
1075
1097
|
extensionId: stackableEnv.EXTENSION_ID || null,
|
|
1076
1098
|
appName: stackableEnv.APP_NAME || null,
|
|
1099
|
+
orgId: stackableEnv.ORG_ID || null,
|
|
1077
1100
|
extensionPort,
|
|
1078
1101
|
previewPort
|
|
1079
1102
|
};
|
|
@@ -1498,7 +1521,7 @@ var derivePermissions2 = (targets) => {
|
|
|
1498
1521
|
}
|
|
1499
1522
|
return [...permissions];
|
|
1500
1523
|
};
|
|
1501
|
-
var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
1524
|
+
var App = ({ command, token, userId, orgId, initialName, initialExtensionId, options }) => {
|
|
1502
1525
|
const { exit } = useApp();
|
|
1503
1526
|
const [step, setStep] = useState8("app");
|
|
1504
1527
|
const [name, setName] = useState8(initialName ?? options?.name ?? "");
|
|
@@ -1636,7 +1659,7 @@ var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
|
1636
1659
|
},
|
|
1637
1660
|
bundleUrl: bundleUrl || void 0,
|
|
1638
1661
|
enabled
|
|
1639
|
-
});
|
|
1662
|
+
}, token);
|
|
1640
1663
|
updateStep(0, "done");
|
|
1641
1664
|
setExtensionVersion(resolvedVersion);
|
|
1642
1665
|
setStep("updateDone");
|
|
@@ -1664,7 +1687,7 @@ var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
|
1664
1687
|
allowedDomains: []
|
|
1665
1688
|
},
|
|
1666
1689
|
bundleUrl: `http://localhost:${extensionPort}`
|
|
1667
|
-
});
|
|
1690
|
+
}, token);
|
|
1668
1691
|
resolvedExtensionId = created.id;
|
|
1669
1692
|
setExtensionId(created.id);
|
|
1670
1693
|
updateStep(0, "done");
|
|
@@ -1703,13 +1726,14 @@ var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
|
1703
1726
|
};
|
|
1704
1727
|
switch (step) {
|
|
1705
1728
|
case "app": {
|
|
1706
|
-
return /* @__PURE__ */ jsx13(AppSelect, { onSubmit: handleAppSelect });
|
|
1729
|
+
return /* @__PURE__ */ jsx13(AppSelect, { token, userId, orgId, onSubmit: handleAppSelect });
|
|
1707
1730
|
}
|
|
1708
1731
|
case "extensionSelect": {
|
|
1709
1732
|
return /* @__PURE__ */ jsx13(
|
|
1710
1733
|
ExtensionSelect,
|
|
1711
1734
|
{
|
|
1712
1735
|
appId: selectedApp.id,
|
|
1736
|
+
token,
|
|
1713
1737
|
command,
|
|
1714
1738
|
onSubmit: handleExtensionSelect,
|
|
1715
1739
|
onBack: goBack
|
|
@@ -1842,7 +1866,7 @@ var App = ({ command, initialName, initialExtensionId, options }) => {
|
|
|
1842
1866
|
};
|
|
1843
1867
|
|
|
1844
1868
|
// src/components/DevApp.tsx
|
|
1845
|
-
import { Box as Box16, Text as Text16 } from "ink";
|
|
1869
|
+
import { Box as Box16, Text as Text16, useInput as useInput9 } from "ink";
|
|
1846
1870
|
import { useRef, useState as useState10, useEffect as useEffect6, useCallback as useCallback2 } from "react";
|
|
1847
1871
|
|
|
1848
1872
|
// src/lib/tunnel.ts
|
|
@@ -1880,7 +1904,7 @@ var startDevServer = (projectRoot) => {
|
|
|
1880
1904
|
import { Box as Box14, Text as Text14 } from "ink";
|
|
1881
1905
|
import { useState as useState9, useEffect as useEffect4 } from "react";
|
|
1882
1906
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1883
|
-
var DevSetup = ({ initialContext, onReady }) => {
|
|
1907
|
+
var DevSetup = ({ initialContext, token, onReady }) => {
|
|
1884
1908
|
const [step, setStep] = useState9("app");
|
|
1885
1909
|
const [selectedApp, setSelectedApp] = useState9(null);
|
|
1886
1910
|
useEffect4(() => {
|
|
@@ -1902,7 +1926,7 @@ var DevSetup = ({ initialContext, onReady }) => {
|
|
|
1902
1926
|
if (step === "app") {
|
|
1903
1927
|
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
1904
1928
|
/* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: "Select the App for your extension:" }) }),
|
|
1905
|
-
/* @__PURE__ */ jsx14(AppSelect, { onSubmit: handleAppSelect })
|
|
1929
|
+
/* @__PURE__ */ jsx14(AppSelect, { token, onSubmit: handleAppSelect })
|
|
1906
1930
|
] });
|
|
1907
1931
|
}
|
|
1908
1932
|
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
@@ -1911,6 +1935,7 @@ var DevSetup = ({ initialContext, onReady }) => {
|
|
|
1911
1935
|
ExtensionSelect,
|
|
1912
1936
|
{
|
|
1913
1937
|
appId: selectedApp?.id || initialContext.appId,
|
|
1938
|
+
token,
|
|
1914
1939
|
onSubmit: handleExtensionSelect
|
|
1915
1940
|
}
|
|
1916
1941
|
)
|
|
@@ -1922,6 +1947,8 @@ import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
|
|
|
1922
1947
|
import { useEffect as useEffect5 } from "react";
|
|
1923
1948
|
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1924
1949
|
var DevDashboard = ({
|
|
1950
|
+
userId,
|
|
1951
|
+
orgId,
|
|
1925
1952
|
extensionName,
|
|
1926
1953
|
extensionId,
|
|
1927
1954
|
appId,
|
|
@@ -1947,7 +1974,7 @@ var DevDashboard = ({
|
|
|
1947
1974
|
}
|
|
1948
1975
|
});
|
|
1949
1976
|
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
|
|
1950
|
-
/* @__PURE__ */ jsx15(Banner, {}),
|
|
1977
|
+
/* @__PURE__ */ jsx15(Banner, { userId, orgId }),
|
|
1951
1978
|
/* @__PURE__ */ jsxs15(
|
|
1952
1979
|
StepShell,
|
|
1953
1980
|
{
|
|
@@ -2015,7 +2042,7 @@ var DevDashboard = ({
|
|
|
2015
2042
|
|
|
2016
2043
|
// src/components/DevApp.tsx
|
|
2017
2044
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2018
|
-
var DevApp = ({ options = {} }) => {
|
|
2045
|
+
var DevApp = ({ token, userId, orgId, options = {} }) => {
|
|
2019
2046
|
const [state, setState] = useState10("setup");
|
|
2020
2047
|
const [devContext, setDevContext] = useState10(null);
|
|
2021
2048
|
const [resolvedContext, setResolvedContext] = useState10(null);
|
|
@@ -2108,12 +2135,22 @@ var DevApp = ({ options = {} }) => {
|
|
|
2108
2135
|
console.log("[dev] Done");
|
|
2109
2136
|
process.exit(0);
|
|
2110
2137
|
};
|
|
2138
|
+
useInput9((input, key) => {
|
|
2139
|
+
if (input === "c" && key.ctrl) {
|
|
2140
|
+
if (state === "running") {
|
|
2141
|
+
handleQuit();
|
|
2142
|
+
} else {
|
|
2143
|
+
process.exit(0);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2111
2147
|
if (state === "setup" && devContext) {
|
|
2112
2148
|
if (!devContext.appId || !devContext.extensionId) {
|
|
2113
2149
|
return /* @__PURE__ */ jsx16(
|
|
2114
2150
|
DevSetup,
|
|
2115
2151
|
{
|
|
2116
2152
|
initialContext: devContext,
|
|
2153
|
+
token,
|
|
2117
2154
|
onReady: handleSetupReady
|
|
2118
2155
|
}
|
|
2119
2156
|
);
|
|
@@ -2124,6 +2161,8 @@ var DevApp = ({ options = {} }) => {
|
|
|
2124
2161
|
return /* @__PURE__ */ jsx16(
|
|
2125
2162
|
DevDashboard,
|
|
2126
2163
|
{
|
|
2164
|
+
userId,
|
|
2165
|
+
orgId,
|
|
2127
2166
|
extensionName: devContext.extensionName,
|
|
2128
2167
|
extensionId: resolvedContext.extensionId,
|
|
2129
2168
|
appId: resolvedContext.appId,
|
|
@@ -2142,6 +2181,261 @@ var DevApp = ({ options = {} }) => {
|
|
|
2142
2181
|
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { children: "Loading..." }) });
|
|
2143
2182
|
};
|
|
2144
2183
|
|
|
2184
|
+
// src/components/AuthLogin.tsx
|
|
2185
|
+
import { createServer } from "http";
|
|
2186
|
+
import { Box as Box17, Text as Text17, useApp as useApp2 } from "ink";
|
|
2187
|
+
import Spinner4 from "ink-spinner";
|
|
2188
|
+
import open from "open";
|
|
2189
|
+
import { useState as useState11, useEffect as useEffect7 } from "react";
|
|
2190
|
+
|
|
2191
|
+
// src/lib/auth.ts
|
|
2192
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir, unlink } from "fs/promises";
|
|
2193
|
+
import { join as join4 } from "path";
|
|
2194
|
+
import { homedir } from "os";
|
|
2195
|
+
var AUTH_DIR = join4(homedir(), ".stackable");
|
|
2196
|
+
var AUTH_FILE = join4(AUTH_DIR, "auth.json");
|
|
2197
|
+
var readAuthState = async () => {
|
|
2198
|
+
try {
|
|
2199
|
+
const content = await readFile3(AUTH_FILE, "utf8");
|
|
2200
|
+
return JSON.parse(content);
|
|
2201
|
+
} catch {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
var writeAuthState = async (state) => {
|
|
2206
|
+
await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
|
|
2207
|
+
await writeFile3(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
2208
|
+
};
|
|
2209
|
+
var clearAuthState = async () => {
|
|
2210
|
+
try {
|
|
2211
|
+
await unlink(AUTH_FILE);
|
|
2212
|
+
} catch {
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
var decodeJwtPayload = (token) => {
|
|
2216
|
+
try {
|
|
2217
|
+
const [, payload] = token.split(".");
|
|
2218
|
+
if (!payload) return null;
|
|
2219
|
+
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
2220
|
+
return JSON.parse(json);
|
|
2221
|
+
} catch {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
var getToken = async () => {
|
|
2226
|
+
const state = await readAuthState();
|
|
2227
|
+
if (!state) {
|
|
2228
|
+
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
2229
|
+
}
|
|
2230
|
+
const payload = decodeJwtPayload(state.token);
|
|
2231
|
+
if (payload?.exp && typeof payload.exp === "number") {
|
|
2232
|
+
if (Date.now() >= payload.exp * 1e3) {
|
|
2233
|
+
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
return state.token;
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// src/components/AuthLogin.tsx
|
|
2240
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2241
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2242
|
+
var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
|
|
2243
|
+
<html><head><meta charset="utf-8"><title>Stackable CLI</title>
|
|
2244
|
+
<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}
|
|
2245
|
+
.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>
|
|
2246
|
+
</head><body><div class="card"><h2>${heading}</h2><p>${sub}</p><p class="hint" id="h"></p></div>
|
|
2247
|
+
${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>` : ""}
|
|
2248
|
+
</body></html>`;
|
|
2249
|
+
var AuthLogin = ({ dashboardUrl }) => {
|
|
2250
|
+
const { exit } = useApp2();
|
|
2251
|
+
const [state, setState] = useState11("waiting");
|
|
2252
|
+
const [loginUrl, setLoginUrl] = useState11("");
|
|
2253
|
+
const [userIdLabel, setUserIdLabel] = useState11("");
|
|
2254
|
+
const [orgIdLabel, setOrgIdLabel] = useState11("");
|
|
2255
|
+
const [errorMessage, setErrorMessage] = useState11("");
|
|
2256
|
+
useEffect7(() => {
|
|
2257
|
+
let server;
|
|
2258
|
+
let timeout;
|
|
2259
|
+
const run = async () => {
|
|
2260
|
+
let resolveToken;
|
|
2261
|
+
let rejectToken;
|
|
2262
|
+
const tokenPromise = new Promise((resolve, reject) => {
|
|
2263
|
+
resolveToken = resolve;
|
|
2264
|
+
rejectToken = reject;
|
|
2265
|
+
});
|
|
2266
|
+
server = createServer((req, res) => {
|
|
2267
|
+
const url2 = new URL(req.url, "http://localhost");
|
|
2268
|
+
if (url2.pathname === "/callback") {
|
|
2269
|
+
const error = url2.searchParams.get("error");
|
|
2270
|
+
if (error) {
|
|
2271
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2272
|
+
res.end(callbackPage("Authentication failed", "You can close this tab."));
|
|
2273
|
+
rejectToken(new Error(error));
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const token2 = url2.searchParams.get("token");
|
|
2277
|
+
if (token2) {
|
|
2278
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2279
|
+
res.end(callbackPage("CLI authenticated", "You can return to your terminal.", dashboardUrl));
|
|
2280
|
+
resolveToken(token2);
|
|
2281
|
+
} else {
|
|
2282
|
+
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
2283
|
+
res.end("Missing token");
|
|
2284
|
+
}
|
|
2285
|
+
} else {
|
|
2286
|
+
res.writeHead(404);
|
|
2287
|
+
res.end();
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
2291
|
+
const port = server.address().port;
|
|
2292
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
2293
|
+
const url = `${dashboardUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
2294
|
+
setLoginUrl(url);
|
|
2295
|
+
await open(url);
|
|
2296
|
+
timeout = setTimeout(() => {
|
|
2297
|
+
server.close();
|
|
2298
|
+
rejectToken(new Error("Login timed out. Please try again."));
|
|
2299
|
+
}, LOGIN_TIMEOUT_MS);
|
|
2300
|
+
let token;
|
|
2301
|
+
try {
|
|
2302
|
+
token = await tokenPromise;
|
|
2303
|
+
} catch (err) {
|
|
2304
|
+
clearTimeout(timeout);
|
|
2305
|
+
server.close();
|
|
2306
|
+
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
2307
|
+
setState("error");
|
|
2308
|
+
exit();
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
clearTimeout(timeout);
|
|
2312
|
+
server.close();
|
|
2313
|
+
const [, payloadB64] = token.split(".");
|
|
2314
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2315
|
+
await writeAuthState({
|
|
2316
|
+
token,
|
|
2317
|
+
userId: payload.sub,
|
|
2318
|
+
orgId: payload.orgId
|
|
2319
|
+
});
|
|
2320
|
+
setUserIdLabel(payload.sub);
|
|
2321
|
+
setOrgIdLabel(payload.orgId);
|
|
2322
|
+
setState("success");
|
|
2323
|
+
exit();
|
|
2324
|
+
};
|
|
2325
|
+
run();
|
|
2326
|
+
return () => {
|
|
2327
|
+
clearTimeout(timeout);
|
|
2328
|
+
server?.close();
|
|
2329
|
+
};
|
|
2330
|
+
}, []);
|
|
2331
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
|
|
2332
|
+
/* @__PURE__ */ jsx17(Banner, {}),
|
|
2333
|
+
/* @__PURE__ */ jsxs16(StepShell, { title: "Authenticate with Stackable", children: [
|
|
2334
|
+
state === "waiting" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
|
|
2335
|
+
/* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2336
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
|
|
2337
|
+
/* @__PURE__ */ jsx17(Text17, { children: "Waiting for browser authentication..." })
|
|
2338
|
+
] }),
|
|
2339
|
+
loginUrl && /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
|
|
2340
|
+
" ",
|
|
2341
|
+
loginUrl
|
|
2342
|
+
] })
|
|
2343
|
+
] }),
|
|
2344
|
+
state === "success" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
|
|
2345
|
+
/* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
|
|
2346
|
+
/* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
|
|
2347
|
+
/* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "User:" }),
|
|
2348
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: userIdLabel })
|
|
2349
|
+
] }),
|
|
2350
|
+
/* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
|
|
2351
|
+
/* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "Org: " }),
|
|
2352
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: orgIdLabel })
|
|
2353
|
+
] })
|
|
2354
|
+
] }),
|
|
2355
|
+
/* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2356
|
+
/* @__PURE__ */ jsx17(Text17, { color: "green", bold: true, children: "\u2714" }),
|
|
2357
|
+
/* @__PURE__ */ jsx17(Text17, { bold: true, children: "Authenticated" })
|
|
2358
|
+
] })
|
|
2359
|
+
] }),
|
|
2360
|
+
state === "error" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
|
|
2361
|
+
/* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2716" }),
|
|
2362
|
+
/* @__PURE__ */ jsx17(Text17, { children: errorMessage })
|
|
2363
|
+
] })
|
|
2364
|
+
] })
|
|
2365
|
+
] });
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2368
|
+
// src/components/AuthLogout.tsx
|
|
2369
|
+
import { useEffect as useEffect8 } from "react";
|
|
2370
|
+
import { Box as Box18, Text as Text18, useApp as useApp3 } from "ink";
|
|
2371
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2372
|
+
var AuthLogout = () => {
|
|
2373
|
+
const { exit } = useApp3();
|
|
2374
|
+
useEffect8(() => {
|
|
2375
|
+
exit();
|
|
2376
|
+
}, [exit]);
|
|
2377
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
|
|
2378
|
+
/* @__PURE__ */ jsx18(Banner, {}),
|
|
2379
|
+
/* @__PURE__ */ jsx18(StepShell, { title: "Authenticate with Stackable", children: /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
|
|
2380
|
+
/* @__PURE__ */ jsx18(Text18, { color: "green", bold: true, children: "\u2714" }),
|
|
2381
|
+
/* @__PURE__ */ jsx18(Text18, { bold: true, children: "Logged out" })
|
|
2382
|
+
] }) })
|
|
2383
|
+
] });
|
|
2384
|
+
};
|
|
2385
|
+
|
|
2386
|
+
// src/components/AuthStatus.tsx
|
|
2387
|
+
import { useEffect as useEffect9 } from "react";
|
|
2388
|
+
import { Box as Box19, Text as Text19, useApp as useApp4 } from "ink";
|
|
2389
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2390
|
+
var AuthStatus = ({ state, userId, orgId, expiry }) => {
|
|
2391
|
+
const { exit } = useApp4();
|
|
2392
|
+
useEffect9(() => {
|
|
2393
|
+
exit();
|
|
2394
|
+
}, [exit]);
|
|
2395
|
+
return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
|
|
2396
|
+
/* @__PURE__ */ jsx19(Banner, {}),
|
|
2397
|
+
/* @__PURE__ */ jsxs18(StepShell, { title: "Authenticate with Stackable", children: [
|
|
2398
|
+
state === "authenticated" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
|
|
2400
|
+
/* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
|
|
2401
|
+
/* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "User:" }),
|
|
2402
|
+
/* @__PURE__ */ jsx19(Text19, { color: "cyan", children: userId })
|
|
2403
|
+
] }),
|
|
2404
|
+
/* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
|
|
2405
|
+
/* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Org: " }),
|
|
2406
|
+
/* @__PURE__ */ jsx19(Text19, { color: "cyan", children: orgId })
|
|
2407
|
+
] }),
|
|
2408
|
+
expiry && /* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
|
|
2409
|
+
/* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Exp: " }),
|
|
2410
|
+
/* @__PURE__ */ jsx19(Text19, { color: "cyan", children: expiry.toLocaleDateString() })
|
|
2411
|
+
] })
|
|
2412
|
+
] }),
|
|
2413
|
+
/* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
|
|
2414
|
+
/* @__PURE__ */ jsx19(Text19, { color: "green", bold: true, children: "\u2714" }),
|
|
2415
|
+
/* @__PURE__ */ jsx19(Text19, { bold: true, children: "Authenticated" })
|
|
2416
|
+
] })
|
|
2417
|
+
] }),
|
|
2418
|
+
state === "expired" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
|
|
2419
|
+
/* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
|
|
2420
|
+
/* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2716" }),
|
|
2421
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2422
|
+
"Session expired",
|
|
2423
|
+
expiry ? ` (${expiry.toLocaleDateString()})` : ""
|
|
2424
|
+
] })
|
|
2425
|
+
] }),
|
|
2426
|
+
/* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Run `stackable-app-extension auth login` to re-authenticate." })
|
|
2427
|
+
] }),
|
|
2428
|
+
state === "not-logged-in" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
|
|
2429
|
+
/* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
|
|
2430
|
+
/* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2716" }),
|
|
2431
|
+
/* @__PURE__ */ jsx19(Text19, { children: "Not logged in" })
|
|
2432
|
+
] }),
|
|
2433
|
+
/* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Run `stackable-app-extension auth login`" })
|
|
2434
|
+
] })
|
|
2435
|
+
] })
|
|
2436
|
+
] });
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2145
2439
|
// src/lib/versionCheck.ts
|
|
2146
2440
|
import https from "https";
|
|
2147
2441
|
var PACKAGE_NAME = "@stackable-labs/cli-app-extension";
|
|
@@ -2190,21 +2484,77 @@ var checkForUpdate = (currentVersion) => {
|
|
|
2190
2484
|
};
|
|
2191
2485
|
|
|
2192
2486
|
// src/index.tsx
|
|
2193
|
-
import { jsx as
|
|
2487
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2194
2488
|
var require2 = createRequire(import.meta.url);
|
|
2195
2489
|
var { version } = require2("../package.json");
|
|
2196
2490
|
checkForUpdate(version);
|
|
2491
|
+
var ensureToken = async () => {
|
|
2492
|
+
try {
|
|
2493
|
+
const token = await getToken();
|
|
2494
|
+
const state = await readAuthState();
|
|
2495
|
+
return { token, userId: state.userId, orgId: state.orgId };
|
|
2496
|
+
} catch (err) {
|
|
2497
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2498
|
+
const isExpired = message.toLowerCase().includes("expired");
|
|
2499
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: isExpired ? "expired" : "not-logged-in" }));
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2197
2503
|
program.name("stackable-app-extension").description("Stackable Labs - App Extension developer CLI").version(version);
|
|
2198
|
-
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((name, options) => {
|
|
2199
|
-
|
|
2504
|
+
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) => {
|
|
2505
|
+
const auth2 = await ensureToken();
|
|
2506
|
+
if (!auth2) {
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
const { token, userId, orgId } = auth2;
|
|
2510
|
+
render(/* @__PURE__ */ jsx20(App, { command: "create" /* CREATE */, initialName: name, options, token, userId, orgId }));
|
|
2200
2511
|
});
|
|
2201
|
-
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((options) => {
|
|
2202
|
-
|
|
2512
|
+
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) => {
|
|
2513
|
+
const auth2 = await ensureToken();
|
|
2514
|
+
if (!auth2) {
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
const { token, userId, orgId } = auth2;
|
|
2518
|
+
render(/* @__PURE__ */ jsx20(App, { command: "scaffold" /* SCAFFOLD */, options, token, userId, orgId }));
|
|
2519
|
+
});
|
|
2520
|
+
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) => {
|
|
2521
|
+
const auth2 = await ensureToken();
|
|
2522
|
+
if (!auth2) {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
const { token, userId, orgId } = auth2;
|
|
2526
|
+
render(/* @__PURE__ */ jsx20(App, { command: "update" /* UPDATE */, initialExtensionId: extensionId, options, token, userId, orgId }));
|
|
2527
|
+
});
|
|
2528
|
+
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) => {
|
|
2529
|
+
const auth2 = await ensureToken();
|
|
2530
|
+
if (!auth2) {
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
const { token, userId, orgId } = auth2;
|
|
2534
|
+
render(/* @__PURE__ */ jsx20(DevApp, { options, token, userId, orgId }), { exitOnCtrlC: false });
|
|
2535
|
+
});
|
|
2536
|
+
var DASHBOARD_URL = process.env.ADMIN_DASHBOARD_URL ?? "https://admin.stackablelabs.io";
|
|
2537
|
+
var auth = program.command("auth").description("Manage CLI authentication");
|
|
2538
|
+
auth.command("login").description("Authenticate with Stackable via browser").action(async () => {
|
|
2539
|
+
render(/* @__PURE__ */ jsx20(AuthLogin, { dashboardUrl: DASHBOARD_URL }));
|
|
2203
2540
|
});
|
|
2204
|
-
|
|
2205
|
-
|
|
2541
|
+
auth.command("logout").description("Clear stored CLI credentials").action(async () => {
|
|
2542
|
+
await clearAuthState();
|
|
2543
|
+
render(/* @__PURE__ */ jsx20(AuthLogout, {}));
|
|
2206
2544
|
});
|
|
2207
|
-
|
|
2208
|
-
|
|
2545
|
+
auth.command("status").description("Show current authentication status").action(async () => {
|
|
2546
|
+
const state = await readAuthState();
|
|
2547
|
+
if (!state) {
|
|
2548
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "not-logged-in" }));
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
const [, payloadB64] = state.token.split(".");
|
|
2552
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2553
|
+
const expiry = payload.exp ? new Date(payload.exp * 1e3) : null;
|
|
2554
|
+
if (expiry && Date.now() >= expiry.getTime()) {
|
|
2555
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "expired", expiry }));
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "authenticated", userId: state.userId, orgId: state.orgId, expiry }));
|
|
2209
2559
|
});
|
|
2210
2560
|
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.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"ink-spinner": "5.x",
|
|
20
20
|
"ink-text-input": "6.x",
|
|
21
21
|
"nypm": "0.4.x",
|
|
22
|
+
"open": "10.x",
|
|
22
23
|
"react": "18.x"
|
|
23
24
|
},
|
|
24
25
|
"description": "CLI for scaffolding Stackable extension projects.",
|