@verentis/cli 0.2.5 → 0.2.7
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 +91 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { mkdir, stat, writeFile, readFile, readdir, realpath, mkdtemp, rm, glob } from 'fs/promises';
|
|
4
|
+
import { Agent } from 'https';
|
|
4
5
|
import { tmpdir, homedir } from 'os';
|
|
5
6
|
import { resolve, basename, dirname, posix, join, isAbsolute, extname, relative, sep } from 'path';
|
|
6
7
|
import { spawn, spawnSync } from 'child_process';
|
|
@@ -12,6 +13,10 @@ import { pipeline } from 'stream/promises';
|
|
|
12
13
|
import * as tar from 'tar';
|
|
13
14
|
import { fileURLToPath } from 'url';
|
|
14
15
|
|
|
16
|
+
var insecureAgent = new Agent({ rejectUnauthorized: false });
|
|
17
|
+
function insecureFetchOptions(insecure) {
|
|
18
|
+
return insecure ? { dispatcher: insecureAgent } : {};
|
|
19
|
+
}
|
|
15
20
|
var configDir = () => process.env.VERENTIS_CONFIG_DIR ?? join(homedir(), ".verentis");
|
|
16
21
|
var configPath = () => join(configDir(), "config.json");
|
|
17
22
|
var keysDir = () => join(configDir(), "keys");
|
|
@@ -53,11 +58,12 @@ function isExpired(token, skewSeconds = 60) {
|
|
|
53
58
|
|
|
54
59
|
// src/auth/deviceFlow.ts
|
|
55
60
|
var CLI_CLIENT_ID = "Verentis.Cli";
|
|
56
|
-
async function startDeviceAuthorization(apiUrl) {
|
|
61
|
+
async function startDeviceAuthorization(apiUrl, insecure) {
|
|
57
62
|
const res = await fetch(`${apiUrl}/connect/device/authorize`, {
|
|
58
63
|
method: "POST",
|
|
59
64
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
60
|
-
body: new URLSearchParams({ client_id: CLI_CLIENT_ID })
|
|
65
|
+
body: new URLSearchParams({ client_id: CLI_CLIENT_ID }),
|
|
66
|
+
...insecureFetchOptions(insecure)
|
|
61
67
|
});
|
|
62
68
|
if (!res.ok) {
|
|
63
69
|
const text = await res.text().catch(() => "");
|
|
@@ -65,7 +71,7 @@ async function startDeviceAuthorization(apiUrl) {
|
|
|
65
71
|
}
|
|
66
72
|
return await res.json();
|
|
67
73
|
}
|
|
68
|
-
async function pollForIdentityToken(apiUrl, authorization, onPoll) {
|
|
74
|
+
async function pollForIdentityToken(apiUrl, authorization, insecure, onPoll) {
|
|
69
75
|
let intervalMs = Math.max(authorization.interval, 1) * 1e3;
|
|
70
76
|
const deadline = Date.now() + authorization.expires_in * 1e3;
|
|
71
77
|
while (Date.now() < deadline) {
|
|
@@ -78,7 +84,8 @@ async function pollForIdentityToken(apiUrl, authorization, onPoll) {
|
|
|
78
84
|
grant_type: "device_code",
|
|
79
85
|
device_code: authorization.device_code,
|
|
80
86
|
client_id: CLI_CLIENT_ID
|
|
81
|
-
})
|
|
87
|
+
}),
|
|
88
|
+
...insecureFetchOptions(insecure)
|
|
82
89
|
});
|
|
83
90
|
let payload;
|
|
84
91
|
try {
|
|
@@ -107,7 +114,7 @@ async function pollForIdentityToken(apiUrl, authorization, onPoll) {
|
|
|
107
114
|
}
|
|
108
115
|
var RefreshTokenError = class extends Error {
|
|
109
116
|
};
|
|
110
|
-
async function refreshIdentityToken(apiUrl, refreshToken) {
|
|
117
|
+
async function refreshIdentityToken(apiUrl, refreshToken, insecure) {
|
|
111
118
|
const res = await fetch(`${apiUrl}/connect/token`, {
|
|
112
119
|
method: "POST",
|
|
113
120
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
@@ -115,7 +122,8 @@ async function refreshIdentityToken(apiUrl, refreshToken) {
|
|
|
115
122
|
grant_type: "refresh_token",
|
|
116
123
|
refresh_token: refreshToken,
|
|
117
124
|
client_id: CLI_CLIENT_ID
|
|
118
|
-
})
|
|
125
|
+
}),
|
|
126
|
+
...insecureFetchOptions(insecure)
|
|
119
127
|
});
|
|
120
128
|
let payload;
|
|
121
129
|
try {
|
|
@@ -146,6 +154,7 @@ var AUDIENCE = "verentis";
|
|
|
146
154
|
async function createApiClient(overrides) {
|
|
147
155
|
const config = { ...await loadConfig(), ...overrides };
|
|
148
156
|
const apiUrl = requireApiUrl(config);
|
|
157
|
+
const insecure = config.insecure;
|
|
149
158
|
const tokenCache = /* @__PURE__ */ new Map();
|
|
150
159
|
const envIdentity = process.env.VERENTIS_TOKEN;
|
|
151
160
|
let currentIdentity = envIdentity ?? config.identityToken;
|
|
@@ -172,7 +181,7 @@ async function createApiClient(overrides) {
|
|
|
172
181
|
if (!refreshInflight) {
|
|
173
182
|
refreshInflight = (async () => {
|
|
174
183
|
try {
|
|
175
|
-
const refreshed = await refreshIdentityToken(apiUrl, currentRefresh);
|
|
184
|
+
const refreshed = await refreshIdentityToken(apiUrl, currentRefresh, insecure);
|
|
176
185
|
currentIdentity = refreshed.idToken;
|
|
177
186
|
currentRefresh = refreshed.refreshToken ?? currentRefresh;
|
|
178
187
|
await persistTokens(refreshed.idToken, refreshed.refreshToken);
|
|
@@ -217,7 +226,8 @@ async function createApiClient(overrides) {
|
|
|
217
226
|
...options.workspaceId ? { workspaceId: options.workspaceId } : {},
|
|
218
227
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
219
228
|
...options.resourceId ? { resourceId: options.resourceId, resourceKind: options.resourceKind ?? "Node" } : {}
|
|
220
|
-
})
|
|
229
|
+
}),
|
|
230
|
+
...insecureFetchOptions(insecure)
|
|
221
231
|
});
|
|
222
232
|
if (!res.ok) {
|
|
223
233
|
const text = await res.text().catch(() => "");
|
|
@@ -244,7 +254,7 @@ ${truncate(text)}` : ""}`);
|
|
|
244
254
|
headers["Content-Type"] = "application/json";
|
|
245
255
|
}
|
|
246
256
|
for (const [key, value] of Object.entries(options.headers ?? {})) headers[key] = value;
|
|
247
|
-
const res = await fetch(url, { method, headers, body, ...options.rawBody !== void 0 ? { duplex: "half" } : {} });
|
|
257
|
+
const res = await fetch(url, { method, headers, body, ...insecureFetchOptions(insecure), ...options.rawBody !== void 0 ? { duplex: "half" } : {} });
|
|
248
258
|
if (!res.ok) {
|
|
249
259
|
const text = await res.text().catch(() => "");
|
|
250
260
|
throw new ApiError(res.status, `${method} ${path} failed (${res.status}). ${hintFor(res.status)}${formatErrorBody(text)}`);
|
|
@@ -1028,6 +1038,7 @@ function validateManifest(manifest) {
|
|
|
1028
1038
|
validateApplicationLaunch(spec, issues);
|
|
1029
1039
|
}
|
|
1030
1040
|
validateInstallDirectives(spec.install, issues);
|
|
1041
|
+
validateSettingDeclarations(kind, spec, issues);
|
|
1031
1042
|
for (const message of validateMarketplaceSection(manifest.marketplace)) error(message);
|
|
1032
1043
|
const packaging = manifest.packaging ?? {};
|
|
1033
1044
|
if (!packaging.publisher) warning("packaging.publisher is not set \u2014 `verentis pack`/`publish` will refuse the package");
|
|
@@ -1110,6 +1121,72 @@ function validateInstallDirectives(install, issues) {
|
|
|
1110
1121
|
}
|
|
1111
1122
|
});
|
|
1112
1123
|
}
|
|
1124
|
+
var SETTING_KEY_PATTERN = /^[a-z0-9]+([.-][a-z0-9]+)*$/;
|
|
1125
|
+
var ENV_VAR_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
1126
|
+
var SETTING_TYPES = ["string", "number", "boolean", "select"];
|
|
1127
|
+
function validateSettingDeclarations(kind, spec, issues) {
|
|
1128
|
+
const settings2 = spec.settings;
|
|
1129
|
+
if (settings2 === void 0) return;
|
|
1130
|
+
const error = (message) => issues.push({ level: "error", message });
|
|
1131
|
+
if (!Array.isArray(settings2)) {
|
|
1132
|
+
error("spec.settings must be a list of setting declarations");
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1136
|
+
settings2.forEach((declaration, index) => {
|
|
1137
|
+
const at = `spec.settings[${index}]`;
|
|
1138
|
+
if (!declaration || typeof declaration !== "object" || Array.isArray(declaration)) {
|
|
1139
|
+
error(`${at} must be an object with at least a 'key'`);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (!declaration.key || typeof declaration.key !== "string") {
|
|
1143
|
+
error(`${at}.key is required (short key, stored namespaced as '{metadata.name}.{key}')`);
|
|
1144
|
+
} else if (!SETTING_KEY_PATTERN.test(declaration.key)) {
|
|
1145
|
+
error(`${at}.key '${declaration.key}' must be lowercase kebab/dot notation (e.g. 'api-key' or 'smtp.host')`);
|
|
1146
|
+
} else if (seen.has(declaration.key)) {
|
|
1147
|
+
error(`${at}.key '${declaration.key}' is declared more than once`);
|
|
1148
|
+
} else {
|
|
1149
|
+
seen.add(declaration.key);
|
|
1150
|
+
}
|
|
1151
|
+
if (declaration.env !== void 0) {
|
|
1152
|
+
if (typeof declaration.env !== "string" || !ENV_VAR_PATTERN.test(declaration.env)) {
|
|
1153
|
+
error(`${at}.env must be an environment variable name (UPPER_SNAKE_CASE, e.g. 'MY_API_KEY')`);
|
|
1154
|
+
} else if (declaration.env.startsWith("VERENTIS_")) {
|
|
1155
|
+
error(`${at}.env must not use the reserved 'VERENTIS_' prefix`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const flag of ["secret", "required"]) {
|
|
1159
|
+
if (declaration[flag] !== void 0 && typeof declaration[flag] !== "boolean") {
|
|
1160
|
+
error(`${at}.${flag} must be a boolean`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const type = declaration.type ?? "string";
|
|
1164
|
+
if (typeof type !== "string" || !SETTING_TYPES.includes(type)) {
|
|
1165
|
+
error(`${at}.type must be one of ${SETTING_TYPES.join(", ")} (got '${String(declaration.type)}')`);
|
|
1166
|
+
}
|
|
1167
|
+
if (declaration.secret === true && declaration.default !== void 0) {
|
|
1168
|
+
error(`${at} declares a default for a secret \u2014 secrets must never ship default values`);
|
|
1169
|
+
}
|
|
1170
|
+
if (type === "select") {
|
|
1171
|
+
if (!Array.isArray(declaration.options) || declaration.options.length === 0) {
|
|
1172
|
+
error(`${at}.options is required for type 'select'`);
|
|
1173
|
+
}
|
|
1174
|
+
} else if (declaration.options !== void 0) {
|
|
1175
|
+
error(`${at}.options is only allowed for type 'select'`);
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
if (kind === "ExecutionEngine") {
|
|
1179
|
+
const declaresSecret = settings2.some(
|
|
1180
|
+
(declaration) => declaration && typeof declaration === "object" && declaration.secret === true
|
|
1181
|
+
);
|
|
1182
|
+
const runtimes = spec.runtimes ?? {};
|
|
1183
|
+
if (declaresSecret && !runtimes["docker"]) {
|
|
1184
|
+
error(
|
|
1185
|
+
"spec.settings declares secrets but the engine has no docker runtime \u2014 secrets are server-only and cannot be provisioned into browser (wasm) runs"
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1113
1190
|
|
|
1114
1191
|
// src/commands/dev.ts
|
|
1115
1192
|
async function resolveDevScopes(flags) {
|
|
@@ -1917,10 +1994,11 @@ function registerKeygen(program2) {
|
|
|
1917
1994
|
|
|
1918
1995
|
// src/commands/login.ts
|
|
1919
1996
|
function registerLogin(program2) {
|
|
1920
|
-
program2.command("login").description("Sign in to Verentis (browser device flow by default; --api-key/--token for non-interactive use)").option("--api-url <url>", "Verentis API gateway URL").option("--api-key <key>", "Verentis API key (vrt_...)").option("--token <token>", "Verentis identity token (exchanged per request for scoped access tokens)").option("--sealing-key <key>", "the platform's base64 X25519 sealing PUBLIC key (used by `pack --encrypt`)").action(async (options) => {
|
|
1997
|
+
program2.command("login").description("Sign in to Verentis (browser device flow by default; --api-key/--token for non-interactive use)").option("--api-url <url>", "Verentis API gateway URL").option("--api-key <key>", "Verentis API key (vrt_...)").option("--token <token>", "Verentis identity token (exchanged per request for scoped access tokens)").option("--sealing-key <key>", "the platform's base64 X25519 sealing PUBLIC key (used by `pack --encrypt`)").option("--insecure", "skip TLS certificate verification (for local dev with self-signed certs)").action(async (options) => {
|
|
1921
1998
|
const config = await loadConfig();
|
|
1922
1999
|
if (options.apiUrl) config.apiUrl = options.apiUrl;
|
|
1923
2000
|
if (options.sealingKey) config.sealingPublicKey = options.sealingKey;
|
|
2001
|
+
if (options.insecure !== void 0) config.insecure = options.insecure;
|
|
1924
2002
|
if (!config.apiUrl) throw new Error("An API URL is required. Pass --api-url <url>.");
|
|
1925
2003
|
if (options.apiKey) {
|
|
1926
2004
|
if (!options.apiKey.startsWith("vrt_")) throw new Error('API keys start with "vrt_".');
|
|
@@ -1940,14 +2018,15 @@ function registerLogin(program2) {
|
|
|
1940
2018
|
return;
|
|
1941
2019
|
}
|
|
1942
2020
|
const apiUrl = config.apiUrl.replace(/\/+$/, "");
|
|
1943
|
-
const
|
|
2021
|
+
const insecure = config.insecure;
|
|
2022
|
+
const authorization = await startDeviceAuthorization(apiUrl, insecure);
|
|
1944
2023
|
console.log("To sign in, open this URL in a browser:");
|
|
1945
2024
|
console.log(`
|
|
1946
2025
|
${authorization.verification_uri_complete ?? authorization.verification_uri}
|
|
1947
2026
|
`);
|
|
1948
2027
|
console.log(`and confirm the code: ${authorization.user_code}`);
|
|
1949
2028
|
console.log("\nWaiting for approval\u2026");
|
|
1950
|
-
const identity = await pollForIdentityToken(apiUrl, authorization, () => process.stderr.write("."));
|
|
2029
|
+
const identity = await pollForIdentityToken(apiUrl, authorization, insecure, () => process.stderr.write("."));
|
|
1951
2030
|
process.stderr.write("\n");
|
|
1952
2031
|
config.identityToken = identity.idToken;
|
|
1953
2032
|
if (identity.refreshToken) config.refreshToken = identity.refreshToken;
|