@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 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 authorization = await startDeviceAuthorization(apiUrl);
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;