@stackable-labs/cli-app-extension 1.18.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 +287 -92
- package/package.json +1 -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";
|
|
@@ -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,7 +1726,7 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1716
1726
|
};
|
|
1717
1727
|
switch (step) {
|
|
1718
1728
|
case "app": {
|
|
1719
|
-
return /* @__PURE__ */ jsx13(AppSelect, { token, onSubmit: handleAppSelect });
|
|
1729
|
+
return /* @__PURE__ */ jsx13(AppSelect, { token, userId, orgId, onSubmit: handleAppSelect });
|
|
1720
1730
|
}
|
|
1721
1731
|
case "extensionSelect": {
|
|
1722
1732
|
return /* @__PURE__ */ jsx13(
|
|
@@ -1856,7 +1866,7 @@ var App = ({ command, token, initialName, initialExtensionId, options }) => {
|
|
|
1856
1866
|
};
|
|
1857
1867
|
|
|
1858
1868
|
// src/components/DevApp.tsx
|
|
1859
|
-
import { Box as Box16, Text as Text16 } from "ink";
|
|
1869
|
+
import { Box as Box16, Text as Text16, useInput as useInput9 } from "ink";
|
|
1860
1870
|
import { useRef, useState as useState10, useEffect as useEffect6, useCallback as useCallback2 } from "react";
|
|
1861
1871
|
|
|
1862
1872
|
// src/lib/tunnel.ts
|
|
@@ -1937,6 +1947,8 @@ import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
|
|
|
1937
1947
|
import { useEffect as useEffect5 } from "react";
|
|
1938
1948
|
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1939
1949
|
var DevDashboard = ({
|
|
1950
|
+
userId,
|
|
1951
|
+
orgId,
|
|
1940
1952
|
extensionName,
|
|
1941
1953
|
extensionId,
|
|
1942
1954
|
appId,
|
|
@@ -1962,7 +1974,7 @@ var DevDashboard = ({
|
|
|
1962
1974
|
}
|
|
1963
1975
|
});
|
|
1964
1976
|
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
|
|
1965
|
-
/* @__PURE__ */ jsx15(Banner, {}),
|
|
1977
|
+
/* @__PURE__ */ jsx15(Banner, { userId, orgId }),
|
|
1966
1978
|
/* @__PURE__ */ jsxs15(
|
|
1967
1979
|
StepShell,
|
|
1968
1980
|
{
|
|
@@ -2030,7 +2042,7 @@ var DevDashboard = ({
|
|
|
2030
2042
|
|
|
2031
2043
|
// src/components/DevApp.tsx
|
|
2032
2044
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2033
|
-
var DevApp = ({ token, options = {} }) => {
|
|
2045
|
+
var DevApp = ({ token, userId, orgId, options = {} }) => {
|
|
2034
2046
|
const [state, setState] = useState10("setup");
|
|
2035
2047
|
const [devContext, setDevContext] = useState10(null);
|
|
2036
2048
|
const [resolvedContext, setResolvedContext] = useState10(null);
|
|
@@ -2123,6 +2135,15 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2123
2135
|
console.log("[dev] Done");
|
|
2124
2136
|
process.exit(0);
|
|
2125
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
|
+
});
|
|
2126
2147
|
if (state === "setup" && devContext) {
|
|
2127
2148
|
if (!devContext.appId || !devContext.extensionId) {
|
|
2128
2149
|
return /* @__PURE__ */ jsx16(
|
|
@@ -2140,6 +2161,8 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2140
2161
|
return /* @__PURE__ */ jsx16(
|
|
2141
2162
|
DevDashboard,
|
|
2142
2163
|
{
|
|
2164
|
+
userId,
|
|
2165
|
+
orgId,
|
|
2143
2166
|
extensionName: devContext.extensionName,
|
|
2144
2167
|
extensionId: resolvedContext.extensionId,
|
|
2145
2168
|
appId: resolvedContext.appId,
|
|
@@ -2158,6 +2181,13 @@ var DevApp = ({ token, options = {} }) => {
|
|
|
2158
2181
|
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { children: "Loading..." }) });
|
|
2159
2182
|
};
|
|
2160
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
|
+
|
|
2161
2191
|
// src/lib/auth.ts
|
|
2162
2192
|
import { readFile as readFile3, writeFile as writeFile3, mkdir, unlink } from "fs/promises";
|
|
2163
2193
|
import { join as join4 } from "path";
|
|
@@ -2195,17 +2225,217 @@ var decodeJwtPayload = (token) => {
|
|
|
2195
2225
|
var getToken = async () => {
|
|
2196
2226
|
const state = await readAuthState();
|
|
2197
2227
|
if (!state) {
|
|
2198
|
-
throw new Error("Not authenticated. Run `stackable auth login` first.");
|
|
2228
|
+
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
2199
2229
|
}
|
|
2200
2230
|
const payload = decodeJwtPayload(state.token);
|
|
2201
2231
|
if (payload?.exp && typeof payload.exp === "number") {
|
|
2202
2232
|
if (Date.now() >= payload.exp * 1e3) {
|
|
2203
|
-
throw new Error("Session expired. Run `stackable auth login` to re-authenticate.");
|
|
2233
|
+
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
2204
2234
|
}
|
|
2205
2235
|
}
|
|
2206
2236
|
return state.token;
|
|
2207
2237
|
};
|
|
2208
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
|
+
|
|
2209
2439
|
// src/lib/versionCheck.ts
|
|
2210
2440
|
import https from "https";
|
|
2211
2441
|
var PACKAGE_NAME = "@stackable-labs/cli-app-extension";
|
|
@@ -2254,112 +2484,77 @@ var checkForUpdate = (currentVersion) => {
|
|
|
2254
2484
|
};
|
|
2255
2485
|
|
|
2256
2486
|
// src/index.tsx
|
|
2257
|
-
import { jsx as
|
|
2487
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2258
2488
|
var require2 = createRequire(import.meta.url);
|
|
2259
2489
|
var { version } = require2("../package.json");
|
|
2260
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
|
+
};
|
|
2261
2503
|
program.name("stackable-app-extension").description("Stackable Labs - App Extension developer CLI").version(version);
|
|
2262
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) => {
|
|
2263
|
-
const
|
|
2264
|
-
|
|
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 }));
|
|
2265
2511
|
});
|
|
2266
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) => {
|
|
2267
|
-
const
|
|
2268
|
-
|
|
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 }));
|
|
2269
2519
|
});
|
|
2270
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) => {
|
|
2271
|
-
const
|
|
2272
|
-
|
|
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 }));
|
|
2273
2527
|
});
|
|
2274
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) => {
|
|
2275
|
-
const
|
|
2276
|
-
|
|
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 });
|
|
2277
2535
|
});
|
|
2278
2536
|
var DASHBOARD_URL = process.env.ADMIN_DASHBOARD_URL ?? "https://admin.stackablelabs.io";
|
|
2279
2537
|
var auth = program.command("auth").description("Manage CLI authentication");
|
|
2280
|
-
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2281
2538
|
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}`);
|
|
2539
|
+
render(/* @__PURE__ */ jsx20(AuthLogin, { dashboardUrl: DASHBOARD_URL }));
|
|
2345
2540
|
});
|
|
2346
2541
|
auth.command("logout").description("Clear stored CLI credentials").action(async () => {
|
|
2347
2542
|
await clearAuthState();
|
|
2348
|
-
|
|
2543
|
+
render(/* @__PURE__ */ jsx20(AuthLogout, {}));
|
|
2349
2544
|
});
|
|
2350
2545
|
auth.command("status").description("Show current authentication status").action(async () => {
|
|
2351
2546
|
const state = await readAuthState();
|
|
2352
2547
|
if (!state) {
|
|
2353
|
-
|
|
2548
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "not-logged-in" }));
|
|
2354
2549
|
return;
|
|
2355
2550
|
}
|
|
2356
2551
|
const [, payloadB64] = state.token.split(".");
|
|
2357
2552
|
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2358
2553
|
const expiry = payload.exp ? new Date(payload.exp * 1e3) : null;
|
|
2359
2554
|
if (expiry && Date.now() >= expiry.getTime()) {
|
|
2360
|
-
|
|
2555
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "expired", expiry }));
|
|
2361
2556
|
return;
|
|
2362
2557
|
}
|
|
2363
|
-
|
|
2558
|
+
render(/* @__PURE__ */ jsx20(AuthStatus, { state: "authenticated", userId: state.userId, orgId: state.orgId, expiry }));
|
|
2364
2559
|
});
|
|
2365
2560
|
program.parse(process.argv.filter((arg) => arg !== "--"));
|