@stackable-labs/cli-app-extension 3.2.0 → 3.4.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 +100 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import { render, useApp, Box, Text, useInput, useFocus, useFocusManager } from '
|
|
|
5
5
|
import { SURFACE_TARGET, TEMPLATE_FLAVORS } from '@stackable-labs/sdk-extension-contracts';
|
|
6
6
|
import { unlink, readFile, mkdir, writeFile, rm, readdir, rmdir } from 'fs/promises';
|
|
7
7
|
import { join, dirname, relative } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
8
9
|
import Spinner5 from 'ink-spinner';
|
|
9
10
|
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
10
11
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
12
|
import TextInput from 'ink-text-input';
|
|
12
|
-
import { homedir } from 'os';
|
|
13
13
|
import { execFile, spawn, execSync } from 'child_process';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import { installDependencies } from 'nypm';
|
|
@@ -22,6 +22,97 @@ import { createServer } from 'http';
|
|
|
22
22
|
import open from 'open';
|
|
23
23
|
import https from 'https';
|
|
24
24
|
|
|
25
|
+
// ../../lib/utils-auth/src/constants.ts
|
|
26
|
+
var STANDALONE_CLIENT_DATA = {
|
|
27
|
+
STACKABLE_LABS: {
|
|
28
|
+
CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
|
|
29
|
+
MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
|
|
30
|
+
},
|
|
31
|
+
STACKABLE_OPS: {
|
|
32
|
+
MCP: { name: "@stackable-ops/mcp", authFile: "mcp-ops-auth.json" }
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var projectField = (field) => Object.fromEntries(
|
|
36
|
+
Object.entries(STANDALONE_CLIENT_DATA).map(([org, roles]) => [
|
|
37
|
+
org,
|
|
38
|
+
Object.fromEntries(Object.entries(roles).map(([role, entry]) => [role, entry[field]]))
|
|
39
|
+
])
|
|
40
|
+
);
|
|
41
|
+
var STANDALONE_CLIENT = projectField("name");
|
|
42
|
+
var STANDALONE_CLIENT_AUTH_FILE = projectField("authFile");
|
|
43
|
+
Object.values(STANDALONE_CLIENT_DATA).flatMap((roles) => Object.values(roles).map((entry) => entry.name));
|
|
44
|
+
|
|
45
|
+
// ../../lib/utils-auth/src/cli/shared.ts
|
|
46
|
+
var AUTH_DIR = join(homedir(), ".stackable");
|
|
47
|
+
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
|
|
48
|
+
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
|
|
49
|
+
var resolveAuthFile = (filename) => join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
|
|
50
|
+
var readAuthState = async (filename) => {
|
|
51
|
+
try {
|
|
52
|
+
const content = await readFile(resolveAuthFile(filename), "utf8");
|
|
53
|
+
return JSON.parse(content);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var writeAuthState = async (state, filename) => {
|
|
59
|
+
await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
|
|
60
|
+
await writeFile(resolveAuthFile(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
61
|
+
};
|
|
62
|
+
var clearAuthState = async (filename) => {
|
|
63
|
+
try {
|
|
64
|
+
await unlink(resolveAuthFile(filename));
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var decodeJwtPayload = (token) => {
|
|
69
|
+
try {
|
|
70
|
+
const [, payload] = token.split(".");
|
|
71
|
+
if (!payload) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
75
|
+
return JSON.parse(json);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var getToken = async (filename) => {
|
|
81
|
+
const state = await readAuthState(filename);
|
|
82
|
+
if (!state) {
|
|
83
|
+
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
84
|
+
}
|
|
85
|
+
const payload = decodeJwtPayload(state.token);
|
|
86
|
+
if (payload?.exp && typeof payload.exp === "number") {
|
|
87
|
+
if (Date.now() >= payload.exp * 1e3) {
|
|
88
|
+
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return state.token;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ../../lib/contracts/src/base.ts
|
|
95
|
+
var asClerkUserId = (value) => value;
|
|
96
|
+
var asClerkOrgId = (value) => value;
|
|
97
|
+
|
|
98
|
+
// ../../lib/contracts/src/fields.ts
|
|
99
|
+
var FIELD_TYPE = {
|
|
100
|
+
TEXTAREA: "textarea",
|
|
101
|
+
TEXT: "text",
|
|
102
|
+
EMAIL: "email",
|
|
103
|
+
/** Non-rendered field — value still persists. Use with `lookup` to derive the
|
|
104
|
+
* value at save time from other fields via the resolver registry. */
|
|
105
|
+
HIDDEN: "hidden"
|
|
106
|
+
};
|
|
107
|
+
var SECUREABLE_FIELD_TYPE = {
|
|
108
|
+
TEXT: FIELD_TYPE.TEXT,
|
|
109
|
+
TEXTAREA: FIELD_TYPE.TEXTAREA,
|
|
110
|
+
EMAIL: FIELD_TYPE.EMAIL,
|
|
111
|
+
HIDDEN: FIELD_TYPE.HIDDEN
|
|
112
|
+
};
|
|
113
|
+
var typeAllowsSecret = (fieldType) => Object.values(SECUREABLE_FIELD_TYPE).includes(fieldType);
|
|
114
|
+
var allowsSecret = (field) => typeAllowsSecret(field.type);
|
|
115
|
+
|
|
25
116
|
// ../../lib/utils-js/src/crypto.ts
|
|
26
117
|
var getCrypto = () => {
|
|
27
118
|
if (typeof globalThis !== "undefined" && globalThis.crypto) {
|
|
@@ -614,28 +705,6 @@ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
|
614
705
|
);
|
|
615
706
|
};
|
|
616
707
|
|
|
617
|
-
// ../../lib/contracts/src/base.ts
|
|
618
|
-
var asClerkUserId = (value) => value;
|
|
619
|
-
var asClerkOrgId = (value) => value;
|
|
620
|
-
|
|
621
|
-
// ../../lib/contracts/src/fields.ts
|
|
622
|
-
var FIELD_TYPE = {
|
|
623
|
-
TEXTAREA: "textarea",
|
|
624
|
-
TEXT: "text",
|
|
625
|
-
EMAIL: "email",
|
|
626
|
-
/** Non-rendered field — value still persists. Use with `lookup` to derive the
|
|
627
|
-
* value at save time from other fields via the resolver registry. */
|
|
628
|
-
HIDDEN: "hidden"
|
|
629
|
-
};
|
|
630
|
-
var SECUREABLE_FIELD_TYPE = {
|
|
631
|
-
TEXT: FIELD_TYPE.TEXT,
|
|
632
|
-
TEXTAREA: FIELD_TYPE.TEXTAREA,
|
|
633
|
-
EMAIL: FIELD_TYPE.EMAIL,
|
|
634
|
-
HIDDEN: FIELD_TYPE.HIDDEN
|
|
635
|
-
};
|
|
636
|
-
var typeAllowsSecret = (fieldType) => Object.values(SECUREABLE_FIELD_TYPE).includes(fieldType);
|
|
637
|
-
var allowsSecret = (field) => typeAllowsSecret(field.type);
|
|
638
|
-
|
|
639
708
|
// src/lib/manifest-diff.ts
|
|
640
709
|
var diffItems = (previous, next, getKey) => {
|
|
641
710
|
const prevMap = new Map(previous.map((item) => [getKey(item), item]));
|
|
@@ -1199,78 +1268,19 @@ var TemplateSelect = ({ onSubmit, onBack }) => {
|
|
|
1199
1268
|
);
|
|
1200
1269
|
};
|
|
1201
1270
|
|
|
1202
|
-
// ../../lib/utils-auth/src/constants.ts
|
|
1203
|
-
var STANDALONE_CLIENT_DATA = {
|
|
1204
|
-
CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
|
|
1205
|
-
MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
|
|
1206
|
-
};
|
|
1207
|
-
var STANDALONE_CLIENT = Object.fromEntries(
|
|
1208
|
-
Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.name])
|
|
1209
|
-
);
|
|
1210
|
-
var STANDALONE_CLIENT_AUTH_FILE = Object.fromEntries(
|
|
1211
|
-
Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.authFile])
|
|
1212
|
-
);
|
|
1213
|
-
Object.values(STANDALONE_CLIENT);
|
|
1214
|
-
|
|
1215
1271
|
// ../../lib/utils-auth/src/index.ts
|
|
1216
|
-
var
|
|
1217
|
-
var
|
|
1218
|
-
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
|
|
1219
|
-
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.MCP);
|
|
1220
|
-
var resolveAuthFile = (filename) => join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
|
|
1221
|
-
var readAuthState = async (filename) => {
|
|
1222
|
-
try {
|
|
1223
|
-
const content = await readFile(resolveAuthFile(filename), "utf8");
|
|
1224
|
-
return JSON.parse(content);
|
|
1225
|
-
} catch {
|
|
1226
|
-
return null;
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
var writeAuthState = async (state, filename) => {
|
|
1230
|
-
await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
|
|
1231
|
-
await writeFile(resolveAuthFile(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
1232
|
-
};
|
|
1233
|
-
var clearAuthState = async (filename) => {
|
|
1234
|
-
try {
|
|
1235
|
-
await unlink(resolveAuthFile(filename));
|
|
1236
|
-
} catch {
|
|
1237
|
-
}
|
|
1238
|
-
};
|
|
1239
|
-
var decodeJwtPayload = (token) => {
|
|
1240
|
-
try {
|
|
1241
|
-
const [, payload] = token.split(".");
|
|
1242
|
-
if (!payload) {
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
1246
|
-
return JSON.parse(json);
|
|
1247
|
-
} catch {
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1251
|
-
var getToken = async (filename) => {
|
|
1252
|
-
const state = await readAuthState(filename);
|
|
1253
|
-
if (!state) {
|
|
1254
|
-
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
1255
|
-
}
|
|
1256
|
-
const payload = decodeJwtPayload(state.token);
|
|
1257
|
-
if (payload?.exp && typeof payload.exp === "number") {
|
|
1258
|
-
if (Date.now() >= payload.exp * 1e3) {
|
|
1259
|
-
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
return state.token;
|
|
1263
|
-
};
|
|
1272
|
+
var deriveDcrClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
|
|
1273
|
+
var deriveClientId = async (clientName) => deriveDcrClientId(clientName);
|
|
1264
1274
|
|
|
1265
1275
|
// src/lib/api.ts
|
|
1266
|
-
var
|
|
1276
|
+
var DEFAULT_ADMIN_LABS_API_URL = "https://api-x.stackablelabs.io/admin";
|
|
1267
1277
|
var DEFAULT_PUBLIC_API_URL = "https://api.stackablelabs.io/app-extension/latest";
|
|
1268
|
-
var getAdminApiBaseUrl = () => process.env.
|
|
1278
|
+
var getAdminApiBaseUrl = () => process.env.ADMIN_LABS_API_BASE_URL ?? DEFAULT_ADMIN_LABS_API_URL;
|
|
1269
1279
|
var getPublicApiBaseUrl = () => process.env.PUBLIC_API_BASE_URL ?? DEFAULT_PUBLIC_API_URL;
|
|
1270
1280
|
var authHeaders = (token) => ({
|
|
1271
1281
|
authorization: `Bearer ${token}`,
|
|
1272
1282
|
"content-type": "application/json",
|
|
1273
|
-
"x-client-name": STANDALONE_CLIENT.CLI
|
|
1283
|
+
"x-client-name": STANDALONE_CLIENT.STACKABLE_LABS.CLI
|
|
1274
1284
|
});
|
|
1275
1285
|
var fetchApps = async (token) => {
|
|
1276
1286
|
const baseUrl = getAdminApiBaseUrl();
|
|
@@ -1278,7 +1288,7 @@ var fetchApps = async (token) => {
|
|
|
1278
1288
|
headers: authHeaders(token)
|
|
1279
1289
|
});
|
|
1280
1290
|
if (!res.ok) {
|
|
1281
|
-
throw new Error(`Failed to fetch apps${baseUrl !==
|
|
1291
|
+
throw new Error(`Failed to fetch apps${baseUrl !== DEFAULT_ADMIN_LABS_API_URL ? ` (from ${baseUrl})` : ""}: ${res.status} ${res.statusText}`);
|
|
1282
1292
|
}
|
|
1283
1293
|
const adminApps = await res.json();
|
|
1284
1294
|
return adminApps.filter((app) => app.enabled).map(({ id, name, targets, iconUrl }) => ({
|
|
@@ -3716,7 +3726,7 @@ var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
|
|
|
3716
3726
|
${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>` : ""}
|
|
3717
3727
|
</body></html>`;
|
|
3718
3728
|
var performCLIOAuthFlow = async ({ dashboardUrl, adminApiBaseUrl }) => {
|
|
3719
|
-
const clientId = await deriveClientId(STANDALONE_CLIENT.CLI);
|
|
3729
|
+
const clientId = await deriveClientId(STANDALONE_CLIENT.STACKABLE_LABS.CLI);
|
|
3720
3730
|
const codeVerifier = getVerifier();
|
|
3721
3731
|
const codeChallenge = await getDigest(codeVerifier, "base64url");
|
|
3722
3732
|
const state = getNonce();
|
|
@@ -4001,7 +4011,7 @@ var checkForUpdate = (currentVersion) => {
|
|
|
4001
4011
|
var require2 = createRequire(import.meta.url);
|
|
4002
4012
|
var { version } = require2("../package.json");
|
|
4003
4013
|
checkForUpdate(version);
|
|
4004
|
-
var DASHBOARD_URL = process.env.
|
|
4014
|
+
var DASHBOARD_URL = process.env.ADMIN_LABS_DASHBOARD_URL ?? "https://admin.stackablelabs.com";
|
|
4005
4015
|
var parseTemplateFlavor = (value) => {
|
|
4006
4016
|
if (!TEMPLATE_FLAVORS.includes(value)) {
|
|
4007
4017
|
throw new InvalidArgumentError(`Invalid template flavor "${value}". Valid: ${TEMPLATE_FLAVORS.join(", ")}`);
|