@stackable-labs/cli-app-extension 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +101 -93
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,13 +3,13 @@ import { createRequire } from 'module';
|
|
|
3
3
|
import { program, InvalidArgumentError } from 'commander';
|
|
4
4
|
import { render, useApp, Box, Text, useInput, useFocus, useFocusManager } from 'ink';
|
|
5
5
|
import { SURFACE_TARGET, TEMPLATE_FLAVORS } from '@stackable-labs/sdk-extension-contracts';
|
|
6
|
-
import { unlink, readFile, mkdir, writeFile, rm, readdir, rmdir } from 'fs/promises';
|
|
7
6
|
import { join, dirname, relative } from 'path';
|
|
7
|
+
import { unlink, readFile, mkdir, writeFile, rm, readdir, rmdir } from 'fs/promises';
|
|
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';
|
|
@@ -66,6 +66,99 @@ var toKebabCase = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "
|
|
|
66
66
|
|
|
67
67
|
// ../../lib/utils-js/src/promise.ts
|
|
68
68
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
|
+
|
|
70
|
+
// ../../lib/utils-auth/src/constants.ts
|
|
71
|
+
var STANDALONE_CLIENT_DATA = {
|
|
72
|
+
STACKABLE_LABS: {
|
|
73
|
+
CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
|
|
74
|
+
MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
|
|
75
|
+
},
|
|
76
|
+
STACKABLE_OPS: {
|
|
77
|
+
MCP: { name: "@stackable-ops/mcp", authFile: "mcp-ops-auth.json" }
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var projectField = (field) => Object.fromEntries(
|
|
81
|
+
Object.entries(STANDALONE_CLIENT_DATA).map(([org, roles]) => [
|
|
82
|
+
org,
|
|
83
|
+
Object.fromEntries(Object.entries(roles).map(([role, entry]) => [role, entry[field]]))
|
|
84
|
+
])
|
|
85
|
+
);
|
|
86
|
+
var STANDALONE_CLIENT = projectField("name");
|
|
87
|
+
var STANDALONE_CLIENT_AUTH_FILE = projectField("authFile");
|
|
88
|
+
Object.values(STANDALONE_CLIENT_DATA).flatMap((roles) => Object.values(roles).map((entry) => entry.name));
|
|
89
|
+
var AUTH_DIR = join(homedir(), ".stackable");
|
|
90
|
+
var resolveAuthFile = (filename) => join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
|
|
91
|
+
var readAuthState = async (filename) => {
|
|
92
|
+
try {
|
|
93
|
+
const content = await readFile(resolveAuthFile(filename), "utf8");
|
|
94
|
+
return JSON.parse(content);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var writeAuthState = async (state, filename) => {
|
|
100
|
+
await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
|
|
101
|
+
await writeFile(resolveAuthFile(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
102
|
+
};
|
|
103
|
+
var clearAuthState = async (filename) => {
|
|
104
|
+
try {
|
|
105
|
+
await unlink(resolveAuthFile(filename));
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var decodeJwtPayload = (token) => {
|
|
110
|
+
try {
|
|
111
|
+
const [, payload] = token.split(".");
|
|
112
|
+
if (!payload) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
116
|
+
return JSON.parse(json);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var getToken = async (filename) => {
|
|
122
|
+
const state = await readAuthState(filename);
|
|
123
|
+
if (!state) {
|
|
124
|
+
throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
|
|
125
|
+
}
|
|
126
|
+
const payload = decodeJwtPayload(state.token);
|
|
127
|
+
if (payload?.exp && typeof payload.exp === "number") {
|
|
128
|
+
if (Date.now() >= payload.exp * 1e3) {
|
|
129
|
+
throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return state.token;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// ../../lib/contracts/src/base.ts
|
|
136
|
+
var asClerkUserId = (value) => value;
|
|
137
|
+
var asClerkOrgId = (value) => value;
|
|
138
|
+
|
|
139
|
+
// ../../lib/contracts/src/fields.ts
|
|
140
|
+
var FIELD_TYPE = {
|
|
141
|
+
TEXTAREA: "textarea",
|
|
142
|
+
TEXT: "text",
|
|
143
|
+
EMAIL: "email",
|
|
144
|
+
/** Non-rendered field — value still persists. Use with `lookup` to derive the
|
|
145
|
+
* value at save time from other fields via the resolver registry. */
|
|
146
|
+
HIDDEN: "hidden"
|
|
147
|
+
};
|
|
148
|
+
var SECUREABLE_FIELD_TYPE = {
|
|
149
|
+
TEXT: FIELD_TYPE.TEXT,
|
|
150
|
+
TEXTAREA: FIELD_TYPE.TEXTAREA,
|
|
151
|
+
EMAIL: FIELD_TYPE.EMAIL,
|
|
152
|
+
HIDDEN: FIELD_TYPE.HIDDEN
|
|
153
|
+
};
|
|
154
|
+
var typeAllowsSecret = (fieldType) => Object.values(SECUREABLE_FIELD_TYPE).includes(fieldType);
|
|
155
|
+
var allowsSecret = (field) => typeAllowsSecret(field.type);
|
|
156
|
+
|
|
157
|
+
// ../../lib/utils-auth/src/index.ts
|
|
158
|
+
var deriveDcrClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
|
|
159
|
+
var deriveClientId = async (clientName) => deriveDcrClientId(clientName);
|
|
160
|
+
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
|
|
161
|
+
join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
|
|
69
162
|
var WORDMARK = [
|
|
70
163
|
" _ _ _ _ ",
|
|
71
164
|
" ___| |_ __ _ ___| | ____ _| |__ | | ___",
|
|
@@ -614,28 +707,6 @@ var SettingsPrompt = ({ defaultDir, onSubmit, onBack }) => {
|
|
|
614
707
|
);
|
|
615
708
|
};
|
|
616
709
|
|
|
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
710
|
// src/lib/manifest-diff.ts
|
|
640
711
|
var diffItems = (previous, next, getKey) => {
|
|
641
712
|
const prevMap = new Map(previous.map((item) => [getKey(item), item]));
|
|
@@ -1199,78 +1270,15 @@ var TemplateSelect = ({ onSubmit, onBack }) => {
|
|
|
1199
1270
|
);
|
|
1200
1271
|
};
|
|
1201
1272
|
|
|
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
|
-
// ../../lib/utils-auth/src/index.ts
|
|
1216
|
-
var deriveClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
|
|
1217
|
-
var AUTH_DIR = join(homedir(), ".stackable");
|
|
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
|
-
};
|
|
1264
|
-
|
|
1265
1273
|
// src/lib/api.ts
|
|
1266
|
-
var
|
|
1274
|
+
var DEFAULT_ADMIN_LABS_API_URL = "https://api-x.stackablelabs.io/admin";
|
|
1267
1275
|
var DEFAULT_PUBLIC_API_URL = "https://api.stackablelabs.io/app-extension/latest";
|
|
1268
|
-
var getAdminApiBaseUrl = () => process.env.
|
|
1276
|
+
var getAdminApiBaseUrl = () => process.env.ADMIN_LABS_API_BASE_URL ?? DEFAULT_ADMIN_LABS_API_URL;
|
|
1269
1277
|
var getPublicApiBaseUrl = () => process.env.PUBLIC_API_BASE_URL ?? DEFAULT_PUBLIC_API_URL;
|
|
1270
1278
|
var authHeaders = (token) => ({
|
|
1271
1279
|
authorization: `Bearer ${token}`,
|
|
1272
1280
|
"content-type": "application/json",
|
|
1273
|
-
"x-client-name": STANDALONE_CLIENT.CLI
|
|
1281
|
+
"x-client-name": STANDALONE_CLIENT.STACKABLE_LABS.CLI
|
|
1274
1282
|
});
|
|
1275
1283
|
var fetchApps = async (token) => {
|
|
1276
1284
|
const baseUrl = getAdminApiBaseUrl();
|
|
@@ -1278,7 +1286,7 @@ var fetchApps = async (token) => {
|
|
|
1278
1286
|
headers: authHeaders(token)
|
|
1279
1287
|
});
|
|
1280
1288
|
if (!res.ok) {
|
|
1281
|
-
throw new Error(`Failed to fetch apps${baseUrl !==
|
|
1289
|
+
throw new Error(`Failed to fetch apps${baseUrl !== DEFAULT_ADMIN_LABS_API_URL ? ` (from ${baseUrl})` : ""}: ${res.status} ${res.statusText}`);
|
|
1282
1290
|
}
|
|
1283
1291
|
const adminApps = await res.json();
|
|
1284
1292
|
return adminApps.filter((app) => app.enabled).map(({ id, name, targets, iconUrl }) => ({
|
|
@@ -3716,7 +3724,7 @@ var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
|
|
|
3716
3724
|
${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
3725
|
</body></html>`;
|
|
3718
3726
|
var performCLIOAuthFlow = async ({ dashboardUrl, adminApiBaseUrl }) => {
|
|
3719
|
-
const clientId = await deriveClientId(STANDALONE_CLIENT.CLI);
|
|
3727
|
+
const clientId = await deriveClientId(STANDALONE_CLIENT.STACKABLE_LABS.CLI);
|
|
3720
3728
|
const codeVerifier = getVerifier();
|
|
3721
3729
|
const codeChallenge = await getDigest(codeVerifier, "base64url");
|
|
3722
3730
|
const state = getNonce();
|
|
@@ -4001,7 +4009,7 @@ var checkForUpdate = (currentVersion) => {
|
|
|
4001
4009
|
var require2 = createRequire(import.meta.url);
|
|
4002
4010
|
var { version } = require2("../package.json");
|
|
4003
4011
|
checkForUpdate(version);
|
|
4004
|
-
var DASHBOARD_URL = process.env.
|
|
4012
|
+
var DASHBOARD_URL = process.env.ADMIN_LABS_DASHBOARD_URL ?? "https://admin.stackablelabs.com";
|
|
4005
4013
|
var parseTemplateFlavor = (value) => {
|
|
4006
4014
|
if (!TEMPLATE_FLAVORS.includes(value)) {
|
|
4007
4015
|
throw new InvalidArgumentError(`Invalid template flavor "${value}". Valid: ${TEMPLATE_FLAVORS.join(", ")}`);
|