@envpilot/cli 1.13.1 → 1.15.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.
|
@@ -7,7 +7,7 @@ function initSentry() {
|
|
|
7
7
|
Sentry.init({
|
|
8
8
|
dsn,
|
|
9
9
|
environment: "cli",
|
|
10
|
-
release: true ? "1.
|
|
10
|
+
release: true ? "1.15.0" : "0.0.0",
|
|
11
11
|
// All EnvPilot surfaces report to one Sentry project; the surface tag
|
|
12
12
|
// is how dashboards tell web / cli / extension events apart.
|
|
13
13
|
initialScope: { tags: { surface: "cli" } },
|
|
@@ -463,7 +463,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
463
463
|
async function openTUI() {
|
|
464
464
|
const [{ render }, { CLIApp }, { PressAnyKey }] = await Promise.all([
|
|
465
465
|
import("ink"),
|
|
466
|
-
import("./app-
|
|
466
|
+
import("./app-R4GNEYHB.js"),
|
|
467
467
|
import("./press-any-key-64XFP4O2.js")
|
|
468
468
|
]);
|
|
469
469
|
while (true) {
|
|
@@ -1040,7 +1040,10 @@ var refs = {
|
|
|
1040
1040
|
deviceSessionRecord: fnRef("deviceSessions:record"),
|
|
1041
1041
|
deviceSessionRevoke: fnRef(
|
|
1042
1042
|
"deviceSessions:revoke"
|
|
1043
|
-
)
|
|
1043
|
+
),
|
|
1044
|
+
pullValues: fnRef("variableValues:pullValues"),
|
|
1045
|
+
pushBulk: fnRef("variableValues:pushBulk"),
|
|
1046
|
+
createVariableRequest: fnRef("variableRequests:createWithValue")
|
|
1044
1047
|
};
|
|
1045
1048
|
async function convexQuery(ref, ...args) {
|
|
1046
1049
|
const client = await getConvexClient();
|
|
@@ -1050,6 +1053,10 @@ async function convexMutation(ref, ...args) {
|
|
|
1050
1053
|
const client = await getConvexClient();
|
|
1051
1054
|
return client.mutation(ref, ...args);
|
|
1052
1055
|
}
|
|
1056
|
+
async function convexAction(ref, ...args) {
|
|
1057
|
+
const client = await getConvexClient();
|
|
1058
|
+
return client.action(ref, ...args);
|
|
1059
|
+
}
|
|
1053
1060
|
async function recordDeviceSession(deviceName, sessionId) {
|
|
1054
1061
|
try {
|
|
1055
1062
|
await convexMutation(refs.deviceSessionRecord, {
|
|
@@ -1074,161 +1081,7 @@ function booleanFeature(resolved, key, fallback = false) {
|
|
|
1074
1081
|
const value = resolved?.features?.[key]?.value;
|
|
1075
1082
|
return typeof value === "boolean" ? value : fallback;
|
|
1076
1083
|
}
|
|
1077
|
-
function registrableDomain(hostname2) {
|
|
1078
|
-
const parts = hostname2.toLowerCase().split(".").filter(Boolean);
|
|
1079
|
-
if (parts.length <= 2) return parts.join(".");
|
|
1080
|
-
return parts.slice(-2).join(".");
|
|
1081
|
-
}
|
|
1082
|
-
var MAX_MANUAL_REDIRECTS = 5;
|
|
1083
1084
|
var APIClient = class {
|
|
1084
|
-
baseUrl;
|
|
1085
|
-
constructor(options) {
|
|
1086
|
-
this.baseUrl = options?.baseUrl ?? getApiUrl();
|
|
1087
|
-
}
|
|
1088
|
-
// ── Vault HTTP path (decrypted secret values) ──────────────────────────
|
|
1089
|
-
/**
|
|
1090
|
-
* Follow 3xx redirects manually, re-attaching Authorization only within the
|
|
1091
|
-
* same registrable domain (defends against the apex→www redirect that would
|
|
1092
|
-
* otherwise drop the header and yield a bogus 401).
|
|
1093
|
-
*/
|
|
1094
|
-
async fetchWithSafeRedirects(initialUrl, init2) {
|
|
1095
|
-
let currentUrl = initialUrl;
|
|
1096
|
-
let currentInit = { ...init2, redirect: "manual" };
|
|
1097
|
-
for (let hop = 0; hop < MAX_MANUAL_REDIRECTS; hop++) {
|
|
1098
|
-
const response = await fetch(currentUrl, currentInit);
|
|
1099
|
-
if (response.status < 300 || response.status >= 400) {
|
|
1100
|
-
return response;
|
|
1101
|
-
}
|
|
1102
|
-
const location = response.headers.get("location");
|
|
1103
|
-
if (!location) return response;
|
|
1104
|
-
const nextUrl = new URL(location, currentUrl);
|
|
1105
|
-
const prevHost = new URL(currentUrl).hostname;
|
|
1106
|
-
const sameSite = registrableDomain(nextUrl.hostname) === registrableDomain(prevHost);
|
|
1107
|
-
const headers = new Headers(currentInit.headers);
|
|
1108
|
-
if (!sameSite) headers.delete("Authorization");
|
|
1109
|
-
let nextMethod = (currentInit.method || "GET").toUpperCase();
|
|
1110
|
-
let nextBody = currentInit.body;
|
|
1111
|
-
if (response.status === 301 || response.status === 302 || response.status === 303) {
|
|
1112
|
-
if (nextMethod !== "GET" && nextMethod !== "HEAD") {
|
|
1113
|
-
nextMethod = "GET";
|
|
1114
|
-
nextBody = void 0;
|
|
1115
|
-
headers.delete("Content-Type");
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
currentUrl = nextUrl.toString();
|
|
1119
|
-
currentInit = {
|
|
1120
|
-
...currentInit,
|
|
1121
|
-
method: nextMethod,
|
|
1122
|
-
headers,
|
|
1123
|
-
body: nextBody,
|
|
1124
|
-
redirect: "manual"
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
throw new APIError(
|
|
1128
|
-
`Too many redirects while calling ${initialUrl}`,
|
|
1129
|
-
0,
|
|
1130
|
-
"TOO_MANY_REDIRECTS"
|
|
1131
|
-
);
|
|
1132
|
-
}
|
|
1133
|
-
/** Perform an authed vault request carrying a fresh WorkOS JWT bearer. */
|
|
1134
|
-
async vaultRequest(method, path, options) {
|
|
1135
|
-
const token = await ensureFreshAccessToken();
|
|
1136
|
-
const url = new URL(path, this.baseUrl);
|
|
1137
|
-
if (options?.params) {
|
|
1138
|
-
for (const [key, value] of Object.entries(options.params)) {
|
|
1139
|
-
url.searchParams.set(key, value);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
1143
|
-
method,
|
|
1144
|
-
headers: {
|
|
1145
|
-
"Content-Type": "application/json",
|
|
1146
|
-
Authorization: `Bearer ${token}`
|
|
1147
|
-
},
|
|
1148
|
-
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
1149
|
-
});
|
|
1150
|
-
return this.handleResponse(response);
|
|
1151
|
-
}
|
|
1152
|
-
isAuthRedirect(response, bodyText) {
|
|
1153
|
-
const location = response.headers.get("location") || "";
|
|
1154
|
-
const finalUrl = response.url || "";
|
|
1155
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1156
|
-
const preview = (bodyText || "").slice(0, 512).toLowerCase();
|
|
1157
|
-
return location.includes("authkit") || finalUrl.includes("authkit") || contentType.includes("text/html") && (preview.includes("authorization_session_id") || preview.includes("client_id=") || preview.includes("<!doctype html"));
|
|
1158
|
-
}
|
|
1159
|
-
async handleResponse(response) {
|
|
1160
|
-
if (!response.ok) {
|
|
1161
|
-
await this.handleError(response);
|
|
1162
|
-
}
|
|
1163
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1164
|
-
const body = await response.text();
|
|
1165
|
-
if (!contentType.includes("application/json")) {
|
|
1166
|
-
if (this.isAuthRedirect(response, body)) {
|
|
1167
|
-
throw new APIError(
|
|
1168
|
-
"Your CLI session is not authorized for this endpoint. Please run `envpilot login` and try again.",
|
|
1169
|
-
401,
|
|
1170
|
-
"AUTH_REDIRECT"
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
const preview = body.replace(/\s+/g, " ").slice(0, 160);
|
|
1174
|
-
throw new APIError(
|
|
1175
|
-
`Expected JSON but got ${contentType || "unknown content type"} from ${response.url}. Response starts with: ${preview}`,
|
|
1176
|
-
response.status || 500
|
|
1177
|
-
);
|
|
1178
|
-
}
|
|
1179
|
-
try {
|
|
1180
|
-
return JSON.parse(body);
|
|
1181
|
-
} catch {
|
|
1182
|
-
const preview = body.replace(/\s+/g, " ").slice(0, 160);
|
|
1183
|
-
throw new APIError(
|
|
1184
|
-
`Failed to parse JSON response from ${response.url}. Response starts with: ${preview}`,
|
|
1185
|
-
response.status || 500
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
async handleError(response) {
|
|
1190
|
-
const bodyText = await response.text();
|
|
1191
|
-
let message = `Request failed with status ${response.status}`;
|
|
1192
|
-
let code;
|
|
1193
|
-
try {
|
|
1194
|
-
const data = JSON.parse(bodyText);
|
|
1195
|
-
message = data.error || data.message || message;
|
|
1196
|
-
code = data.code;
|
|
1197
|
-
} catch {
|
|
1198
|
-
}
|
|
1199
|
-
if (response.status === 401) {
|
|
1200
|
-
if (this.isAuthRedirect(response, bodyText)) {
|
|
1201
|
-
throw new APIError(
|
|
1202
|
-
"Your CLI session is not authorized for this endpoint. Please run `envpilot login` and try again.",
|
|
1203
|
-
401,
|
|
1204
|
-
"AUTH_REDIRECT"
|
|
1205
|
-
);
|
|
1206
|
-
}
|
|
1207
|
-
throw new APIError(
|
|
1208
|
-
message || "Authentication failed. Please run `envpilot login`.",
|
|
1209
|
-
401,
|
|
1210
|
-
code || "UNAUTHORIZED"
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
if (response.status === 403 && code === "TIER_LIMIT_REACHED") {
|
|
1214
|
-
throw new APIError(
|
|
1215
|
-
message || "Tier limit reached. Run `envpilot usage` to see your plan limits.",
|
|
1216
|
-
403,
|
|
1217
|
-
"TIER_LIMIT_REACHED"
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
if (response.status === 403) {
|
|
1221
|
-
throw new APIError(message || "Access denied.", 403, code || "FORBIDDEN");
|
|
1222
|
-
}
|
|
1223
|
-
if (response.status === 402) {
|
|
1224
|
-
throw new APIError(
|
|
1225
|
-
message || "Tier limit reached. Run `envpilot usage` to see your plan limits.",
|
|
1226
|
-
402,
|
|
1227
|
-
"PAYMENT_REQUIRED"
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
throw new APIError(message, response.status, code);
|
|
1231
|
-
}
|
|
1232
1085
|
// ============================================
|
|
1233
1086
|
// High-level methods — DIRECT to Convex
|
|
1234
1087
|
// ============================================
|
|
@@ -1405,24 +1258,53 @@ var APIClient = class {
|
|
|
1405
1258
|
* Returns the variable list, the response meta (unified role / scope info),
|
|
1406
1259
|
* and any keys that failed vault decryption (skipped server-side).
|
|
1407
1260
|
*/
|
|
1408
|
-
async listVariables(projectId, environment,
|
|
1409
|
-
const
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1261
|
+
async listVariables(projectId, environment, _organizationId) {
|
|
1262
|
+
const result = await convexAction(refs.pullValues, {
|
|
1263
|
+
projectId,
|
|
1264
|
+
...environment ? { environment } : {}
|
|
1265
|
+
});
|
|
1266
|
+
const variables = result.variables.filter((row) => row.value !== "[DECRYPTION_FAILED]").map((row) => ({
|
|
1267
|
+
_id: row._id,
|
|
1268
|
+
key: row.key,
|
|
1269
|
+
value: row.value,
|
|
1270
|
+
// The CLI Variable type expects a single environment string. Echo the
|
|
1271
|
+
// requested one, else the first the variable belongs to.
|
|
1272
|
+
environment: environment ?? row.environments[0] ?? "development",
|
|
1273
|
+
projectId: row.projectId,
|
|
1274
|
+
description: row.description,
|
|
1275
|
+
isSensitive: row.isSensitive,
|
|
1276
|
+
version: row.version,
|
|
1277
|
+
createdAt: row.createdAt,
|
|
1278
|
+
updatedAt: row.updatedAt,
|
|
1279
|
+
access: row.access
|
|
1280
|
+
}));
|
|
1281
|
+
const meta = {
|
|
1282
|
+
total: variables.length,
|
|
1283
|
+
environment: environment ?? "all",
|
|
1284
|
+
role: result.meta.role,
|
|
1285
|
+
projectRole: result.meta.projectRole,
|
|
1286
|
+
unifiedRole: result.meta.unifiedRole,
|
|
1287
|
+
assigned: result.meta.assigned,
|
|
1288
|
+
grantOnly: result.meta.grantOnly,
|
|
1289
|
+
environmentScope: result.meta.environmentScope,
|
|
1290
|
+
hasWriteAccess: result.meta.hasWriteAccess,
|
|
1291
|
+
scopeRestricted: result.meta.scopeRestricted,
|
|
1292
|
+
decryptionFailures: result.meta.decryptionFailures
|
|
1293
|
+
};
|
|
1413
1294
|
return {
|
|
1414
|
-
variables
|
|
1415
|
-
meta
|
|
1416
|
-
decryptionFailures:
|
|
1295
|
+
variables,
|
|
1296
|
+
meta,
|
|
1297
|
+
decryptionFailures: result.meta.decryptionFailures ?? []
|
|
1417
1298
|
};
|
|
1418
1299
|
}
|
|
1419
1300
|
/** Bulk create/update variables (vault path — encrypts values server-side). */
|
|
1420
1301
|
async bulkUpsertVariables(data) {
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1302
|
+
return convexAction(refs.pushBulk, {
|
|
1303
|
+
projectId: data.projectId,
|
|
1304
|
+
environment: data.environment,
|
|
1305
|
+
variables: data.variables,
|
|
1306
|
+
mode: data.mode
|
|
1307
|
+
});
|
|
1426
1308
|
}
|
|
1427
1309
|
/**
|
|
1428
1310
|
* Submit a variable request (developers only). Goes over the vault path
|
|
@@ -1430,11 +1312,18 @@ var APIClient = class {
|
|
|
1430
1312
|
* owners/PMs/team leads get a 403 here and create variables directly.
|
|
1431
1313
|
*/
|
|
1432
1314
|
async createVariableRequest(data) {
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1315
|
+
const created = await convexAction(refs.createVariableRequest, {
|
|
1316
|
+
projectId: data.projectId,
|
|
1317
|
+
key: data.key,
|
|
1318
|
+
value: data.value,
|
|
1319
|
+
environments: data.environments,
|
|
1320
|
+
isSensitive: data.isSensitive,
|
|
1321
|
+
description: data.description
|
|
1322
|
+
});
|
|
1323
|
+
if (!created) {
|
|
1435
1324
|
throw new APIError("No variable request returned by server", 500);
|
|
1436
1325
|
}
|
|
1437
|
-
return
|
|
1326
|
+
return created;
|
|
1438
1327
|
}
|
|
1439
1328
|
};
|
|
1440
1329
|
function createAPIClient() {
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
getTopLevelCommandCatalog,
|
|
7
7
|
initSentry,
|
|
8
8
|
openTUI
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-VYHEVPJQ.js";
|
|
10
10
|
|
|
11
11
|
// src/lib/program.ts
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
|
|
14
14
|
// src/lib/cli-version.ts
|
|
15
|
-
var CLI_VERSION = true ? "1.
|
|
15
|
+
var CLI_VERSION = true ? "1.15.0" : "0.0.0";
|
|
16
16
|
|
|
17
17
|
// src/lib/version-check.ts
|
|
18
18
|
import chalk from "chalk";
|