@verentis/cli 0.2.6 → 0.2.8

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
@@ -246,15 +246,16 @@ ${truncate(text)}` : ""}`);
246
246
  }
247
247
  const headers = { Authorization: `Bearer ${token}` };
248
248
  let body;
249
+ const isFormData = options.rawBody instanceof FormData;
249
250
  if (options.rawBody !== void 0) {
250
251
  body = options.rawBody;
251
- headers["Content-Type"] = options.contentType ?? "application/octet-stream";
252
+ if (!isFormData) headers["Content-Type"] = options.contentType ?? "application/octet-stream";
252
253
  } else if (options.body !== void 0) {
253
254
  body = JSON.stringify(options.body);
254
255
  headers["Content-Type"] = "application/json";
255
256
  }
256
257
  for (const [key, value] of Object.entries(options.headers ?? {})) headers[key] = value;
257
- const res = await fetch(url, { method, headers, body, ...insecureFetchOptions(insecure), ...options.rawBody !== void 0 ? { duplex: "half" } : {} });
258
+ const res = await fetch(url, { method, headers, body, ...insecureFetchOptions(insecure), ...options.rawBody !== void 0 && !isFormData ? { duplex: "half" } : {} });
258
259
  if (!res.ok) {
259
260
  const text = await res.text().catch(() => "");
260
261
  throw new ApiError(res.status, `${method} ${path} failed (${res.status}). ${hintFor(res.status)}${formatErrorBody(text)}`);
@@ -1038,6 +1039,7 @@ function validateManifest(manifest) {
1038
1039
  validateApplicationLaunch(spec, issues);
1039
1040
  }
1040
1041
  validateInstallDirectives(spec.install, issues);
1042
+ validateSettingDeclarations(kind, spec, issues);
1041
1043
  for (const message of validateMarketplaceSection(manifest.marketplace)) error(message);
1042
1044
  const packaging = manifest.packaging ?? {};
1043
1045
  if (!packaging.publisher) warning("packaging.publisher is not set \u2014 `verentis pack`/`publish` will refuse the package");
@@ -1120,6 +1122,72 @@ function validateInstallDirectives(install, issues) {
1120
1122
  }
1121
1123
  });
1122
1124
  }
1125
+ var SETTING_KEY_PATTERN = /^[a-z0-9]+([.-][a-z0-9]+)*$/;
1126
+ var ENV_VAR_PATTERN = /^[A-Z][A-Z0-9_]*$/;
1127
+ var SETTING_TYPES = ["string", "number", "boolean", "select"];
1128
+ function validateSettingDeclarations(kind, spec, issues) {
1129
+ const settings2 = spec.settings;
1130
+ if (settings2 === void 0) return;
1131
+ const error = (message) => issues.push({ level: "error", message });
1132
+ if (!Array.isArray(settings2)) {
1133
+ error("spec.settings must be a list of setting declarations");
1134
+ return;
1135
+ }
1136
+ const seen = /* @__PURE__ */ new Set();
1137
+ settings2.forEach((declaration, index) => {
1138
+ const at = `spec.settings[${index}]`;
1139
+ if (!declaration || typeof declaration !== "object" || Array.isArray(declaration)) {
1140
+ error(`${at} must be an object with at least a 'key'`);
1141
+ return;
1142
+ }
1143
+ if (!declaration.key || typeof declaration.key !== "string") {
1144
+ error(`${at}.key is required (short key, stored namespaced as '{metadata.name}.{key}')`);
1145
+ } else if (!SETTING_KEY_PATTERN.test(declaration.key)) {
1146
+ error(`${at}.key '${declaration.key}' must be lowercase kebab/dot notation (e.g. 'api-key' or 'smtp.host')`);
1147
+ } else if (seen.has(declaration.key)) {
1148
+ error(`${at}.key '${declaration.key}' is declared more than once`);
1149
+ } else {
1150
+ seen.add(declaration.key);
1151
+ }
1152
+ if (declaration.env !== void 0) {
1153
+ if (typeof declaration.env !== "string" || !ENV_VAR_PATTERN.test(declaration.env)) {
1154
+ error(`${at}.env must be an environment variable name (UPPER_SNAKE_CASE, e.g. 'MY_API_KEY')`);
1155
+ } else if (declaration.env.startsWith("VERENTIS_")) {
1156
+ error(`${at}.env must not use the reserved 'VERENTIS_' prefix`);
1157
+ }
1158
+ }
1159
+ for (const flag of ["secret", "required"]) {
1160
+ if (declaration[flag] !== void 0 && typeof declaration[flag] !== "boolean") {
1161
+ error(`${at}.${flag} must be a boolean`);
1162
+ }
1163
+ }
1164
+ const type = declaration.type ?? "string";
1165
+ if (typeof type !== "string" || !SETTING_TYPES.includes(type)) {
1166
+ error(`${at}.type must be one of ${SETTING_TYPES.join(", ")} (got '${String(declaration.type)}')`);
1167
+ }
1168
+ if (declaration.secret === true && declaration.default !== void 0) {
1169
+ error(`${at} declares a default for a secret \u2014 secrets must never ship default values`);
1170
+ }
1171
+ if (type === "select") {
1172
+ if (!Array.isArray(declaration.options) || declaration.options.length === 0) {
1173
+ error(`${at}.options is required for type 'select'`);
1174
+ }
1175
+ } else if (declaration.options !== void 0) {
1176
+ error(`${at}.options is only allowed for type 'select'`);
1177
+ }
1178
+ });
1179
+ if (kind === "ExecutionEngine") {
1180
+ const declaresSecret = settings2.some(
1181
+ (declaration) => declaration && typeof declaration === "object" && declaration.secret === true
1182
+ );
1183
+ const runtimes = spec.runtimes ?? {};
1184
+ if (declaresSecret && !runtimes["docker"]) {
1185
+ error(
1186
+ "spec.settings declares secrets but the engine has no docker runtime \u2014 secrets are server-only and cannot be provisioned into browser (wasm) runs"
1187
+ );
1188
+ }
1189
+ }
1190
+ }
1123
1191
 
1124
1192
  // src/commands/dev.ts
1125
1193
  async function resolveDevScopes(flags) {
@@ -1479,15 +1547,15 @@ var normalizeRemote = (path) => {
1479
1547
  return trimmed;
1480
1548
  };
1481
1549
  async function uploadWithHeaders(api, workspaceId, remotePath, localPath, options) {
1482
- const stream = Readable.toWeb(createReadStream(localPath));
1550
+ const buffer = await readFile(localPath);
1551
+ const form = new FormData();
1552
+ form.append("file", new Blob([buffer], { type: options.contentType ?? "application/octet-stream" }), basename(localPath));
1553
+ form.append("metadata", JSON.stringify({ branch: options.branch, contentType: options.contentType }));
1483
1554
  const res = await api.requestRaw("POST", fillPath(files.upload.path, { path: normalizeRemote(remotePath) }), {
1484
1555
  scopes: [...files.upload.scopes],
1485
1556
  tokenWorkspaceId: workspaceId,
1486
- rawBody: stream,
1487
- contentType: options.contentType ?? "application/octet-stream",
1557
+ rawBody: form,
1488
1558
  headers: {
1489
- "X-Branch": options.branch,
1490
- "Content-Disposition": `attachment; filename="${basename(localPath)}"`,
1491
1559
  ...options.autoExtract ? { "X-Auto-Extract": "true" } : {}
1492
1560
  }
1493
1561
  });