ignitify 1.2.2 → 2.0.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/README.md +2 -2
- package/ignitify.cjs +1066 -256
- package/package.json +1 -1
package/ignitify.cjs
CHANGED
|
@@ -1136,14 +1136,18 @@ function fill(pattern, ...params) {
|
|
|
1136
1136
|
if (i !== params.length) throw new Error(`route ${pattern}: takes ${i} parameter(s), given ${params.length}`);
|
|
1137
1137
|
return out2;
|
|
1138
1138
|
}
|
|
1139
|
+
function isValidIdempotencyKey(key) {
|
|
1140
|
+
return IDEMPOTENCY_KEY_PATTERN.test(key);
|
|
1141
|
+
}
|
|
1139
1142
|
function isTerminalRunStatus(status) {
|
|
1140
1143
|
return TERMINAL_RUN_STATUSES.includes(status);
|
|
1141
1144
|
}
|
|
1142
|
-
var DOT_SEGMENTS, TENANT_ROUTES, KEY_EXPIRED_ERROR, TENANT_PATHS, API_VERSION, API_VERSION_HEADER, CLIENT_FLOOR_HEADER, CLI_VERSION_HEADER, TERMINAL_RUN_STATUSES, init_api_routes = __esm({ "../../packages/contract/api-routes.ts"() {
|
|
1145
|
+
var DOT_SEGMENTS, TENANT_ROUTES, KEY_EXPIRED_ERROR, TENANT_PATHS, API_VERSION, API_VERSION_HEADER, CLIENT_FLOOR_HEADER, RETRY_AFTER_HEADER, CLI_VERSION_HEADER, IDEMPOTENCY_KEY_HEADER, IDEMPOTENT_REPLAYED_HEADER, IDEMPOTENCY_KEY_PATTERN, TERMINAL_RUN_STATUSES, init_api_routes = __esm({ "../../packages/contract/api-routes.ts"() {
|
|
1143
1146
|
"use strict";
|
|
1144
1147
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1145
1148
|
DOT_SEGMENTS = /* @__PURE__ */ new Set([".", ".."]);
|
|
1146
|
-
TENANT_ROUTES = { TASKS: "/tasks", TASK_BY_ID: "/tasks/:id", TASK_DELETION_PREVIEW: "/tasks/:id/deletion-preview", DEPLOYMENTS: "/deployments", DEPLOYMENT_BY_ID: "/deployments/:id", RUNS: "/runs", RUN_BY_ID: "/runs/:id", RUN_CANCEL: "/runs/:id/cancel", WHOAMI: "/whoami", PACKAGE_ADVICE: "/packages/advice", VMS: "/vms", VM_BY_ID: "/vms/:id", HOOKS: "/hooks", TRIGGER: "/hooks/trigger", TRIGGER_HOOK: "/hooks/trigger/:hookId", SECRETS: "/secrets", SECRET_BY_NAME: "/secrets/:name", SCHEDULES: "/schedules", SCHEDULE_BY_ID: "/schedules/:id", USAGE: "/usage", RUN_LOGS: "/runs/:id/logs", DEPLOYMENT_LOGS: "/deployments/:id/logs", DEPLOYMENT_HOOKS: "/deployments/:id/hooks", DEPLOYMENT_NOTIFY: "/deployments/:id/notify", HOOK_BY_ID: "/hooks/:id", READYZ: "/readyz", LIVEZ: "/livez", AUTH_DEVICE: "/auth/device", AUTH_DEVICE_TOKEN: "/auth/device/token", AUTH_KEY: "/auth/key" }, KEY_EXPIRED_ERROR = "API key has expired", TENANT_PATHS = { TASKS: TENANT_ROUTES.TASKS, TASK_BY_ID: (id) => fill(TENANT_ROUTES.TASK_BY_ID, id), TASK_DELETION_PREVIEW: (id) => fill(TENANT_ROUTES.TASK_DELETION_PREVIEW, id), DEPLOYMENTS: TENANT_ROUTES.DEPLOYMENTS, DEPLOYMENT_BY_ID: (id) => fill(TENANT_ROUTES.DEPLOYMENT_BY_ID, id), DEPLOYMENT_LOGS: (id) => fill(TENANT_ROUTES.DEPLOYMENT_LOGS, id), RUNS: TENANT_ROUTES.RUNS, RUN_BY_ID: (id) => fill(TENANT_ROUTES.RUN_BY_ID, id), RUN_CANCEL: (id) => fill(TENANT_ROUTES.RUN_CANCEL, id), RUN_LOGS: (id) => fill(TENANT_ROUTES.RUN_LOGS, id), WHOAMI: TENANT_ROUTES.WHOAMI, PACKAGE_ADVICE: TENANT_ROUTES.PACKAGE_ADVICE, VMS: TENANT_ROUTES.VMS, VM_BY_ID: (id) => fill(TENANT_ROUTES.VM_BY_ID, id), HOOKS: TENANT_ROUTES.HOOKS, TRIGGER: TENANT_ROUTES.TRIGGER, TRIGGER_HOOK: (hookId) => fill(TENANT_ROUTES.TRIGGER_HOOK, hookId), SECRETS: TENANT_ROUTES.SECRETS, SECRET_BY_NAME: (name) => fill(TENANT_ROUTES.SECRET_BY_NAME, name), SCHEDULES: TENANT_ROUTES.SCHEDULES, SCHEDULE_BY_ID: (id) => fill(TENANT_ROUTES.SCHEDULE_BY_ID, id), USAGE: TENANT_ROUTES.USAGE, DEPLOYMENT_HOOKS: (id) => fill(TENANT_ROUTES.DEPLOYMENT_HOOKS, id), DEPLOYMENT_NOTIFY: (id) => fill(TENANT_ROUTES.DEPLOYMENT_NOTIFY, id), HOOK_BY_ID: (id) => fill(TENANT_ROUTES.HOOK_BY_ID, id), READYZ: TENANT_ROUTES.READYZ, LIVEZ: TENANT_ROUTES.LIVEZ, AUTH_DEVICE: TENANT_ROUTES.AUTH_DEVICE, AUTH_DEVICE_TOKEN: TENANT_ROUTES.AUTH_DEVICE_TOKEN, AUTH_KEY: TENANT_ROUTES.AUTH_KEY }, API_VERSION = "1", API_VERSION_HEADER = "X-Api-Version", CLIENT_FLOOR_HEADER = "X-Client-Floor", CLI_VERSION_HEADER = "X-Ignitify-Cli",
|
|
1149
|
+
TENANT_ROUTES = { TASKS: "/tasks", TASK_BY_ID: "/tasks/:id", TASK_DELETION_PREVIEW: "/tasks/:id/deletion-preview", DEPLOYMENTS: "/deployments", DEPLOYMENT_BY_ID: "/deployments/:id", RUNS: "/runs", RUN_BY_ID: "/runs/:id", RUN_CANCEL: "/runs/:id/cancel", WHOAMI: "/whoami", PACKAGE_ADVICE: "/packages/advice", VMS: "/vms", VM_BY_ID: "/vms/:id", HOOKS: "/hooks", TRIGGER: "/hooks/trigger", TRIGGER_HOOK: "/hooks/trigger/:hookId", SECRETS: "/secrets", SECRET_BY_NAME: "/secrets/:name", SCHEDULES: "/schedules", SCHEDULE_BY_ID: "/schedules/:id", USAGE: "/usage", USAGE_SERIES: "/usage/series", RUN_LOGS: "/runs/:id/logs", RUN_TIMELINE: "/runs/:id/timeline", RUN_FAILURES: "/runs/failures", DEPLOYMENT_LOGS: "/deployments/:id/logs", DEPLOYMENT_HOOKS: "/deployments/:id/hooks", DEPLOYMENT_NOTIFY: "/deployments/:id/notify", HOOK_BY_ID: "/hooks/:id", READYZ: "/readyz", LIVEZ: "/livez", AUTH_DEVICE: "/auth/device", AUTH_DEVICE_TOKEN: "/auth/device/token", AUTH_KEY: "/auth/key", NOTIFICATIONS: "/notifications", USERS: "/users", USER_BY_ID: "/users/:id", GROUPS: "/groups", GROUP_BY_ID: "/groups/:id", GROUP_PERMISSION: "/groups/:id/permissions/:permission", GROUP_USER: "/groups/:id/users/:user", KEYS: "/keys", KEY_BY_ID: "/keys/:id" }, KEY_EXPIRED_ERROR = "API key has expired", TENANT_PATHS = { TASKS: TENANT_ROUTES.TASKS, TASK_BY_ID: (id) => fill(TENANT_ROUTES.TASK_BY_ID, id), TASK_DELETION_PREVIEW: (id) => fill(TENANT_ROUTES.TASK_DELETION_PREVIEW, id), DEPLOYMENTS: TENANT_ROUTES.DEPLOYMENTS, DEPLOYMENT_BY_ID: (id) => fill(TENANT_ROUTES.DEPLOYMENT_BY_ID, id), DEPLOYMENT_LOGS: (id) => fill(TENANT_ROUTES.DEPLOYMENT_LOGS, id), RUNS: TENANT_ROUTES.RUNS, RUN_BY_ID: (id) => fill(TENANT_ROUTES.RUN_BY_ID, id), RUN_CANCEL: (id) => fill(TENANT_ROUTES.RUN_CANCEL, id), RUN_LOGS: (id) => fill(TENANT_ROUTES.RUN_LOGS, id), RUN_TIMELINE: (id) => fill(TENANT_ROUTES.RUN_TIMELINE, id), RUN_FAILURES: TENANT_ROUTES.RUN_FAILURES, WHOAMI: TENANT_ROUTES.WHOAMI, PACKAGE_ADVICE: TENANT_ROUTES.PACKAGE_ADVICE, VMS: TENANT_ROUTES.VMS, VM_BY_ID: (id) => fill(TENANT_ROUTES.VM_BY_ID, id), HOOKS: TENANT_ROUTES.HOOKS, TRIGGER: TENANT_ROUTES.TRIGGER, TRIGGER_HOOK: (hookId) => fill(TENANT_ROUTES.TRIGGER_HOOK, hookId), SECRETS: TENANT_ROUTES.SECRETS, SECRET_BY_NAME: (name) => fill(TENANT_ROUTES.SECRET_BY_NAME, name), SCHEDULES: TENANT_ROUTES.SCHEDULES, SCHEDULE_BY_ID: (id) => fill(TENANT_ROUTES.SCHEDULE_BY_ID, id), USAGE: TENANT_ROUTES.USAGE, USAGE_SERIES: TENANT_ROUTES.USAGE_SERIES, DEPLOYMENT_HOOKS: (id) => fill(TENANT_ROUTES.DEPLOYMENT_HOOKS, id), DEPLOYMENT_NOTIFY: (id) => fill(TENANT_ROUTES.DEPLOYMENT_NOTIFY, id), HOOK_BY_ID: (id) => fill(TENANT_ROUTES.HOOK_BY_ID, id), READYZ: TENANT_ROUTES.READYZ, LIVEZ: TENANT_ROUTES.LIVEZ, AUTH_DEVICE: TENANT_ROUTES.AUTH_DEVICE, AUTH_DEVICE_TOKEN: TENANT_ROUTES.AUTH_DEVICE_TOKEN, AUTH_KEY: TENANT_ROUTES.AUTH_KEY, NOTIFICATIONS: TENANT_ROUTES.NOTIFICATIONS, USERS: TENANT_ROUTES.USERS, USER_BY_ID: (name) => fill(TENANT_ROUTES.USER_BY_ID, name), GROUPS: TENANT_ROUTES.GROUPS, GROUP_BY_ID: (name) => fill(TENANT_ROUTES.GROUP_BY_ID, name), GROUP_PERMISSION: (name, permission) => fill(TENANT_ROUTES.GROUP_PERMISSION, name, permission), GROUP_USER: (name, user) => fill(TENANT_ROUTES.GROUP_USER, name, user), KEYS: TENANT_ROUTES.KEYS, KEY_BY_ID: (id) => fill(TENANT_ROUTES.KEY_BY_ID, id) }, API_VERSION = "1", API_VERSION_HEADER = "X-Api-Version", CLIENT_FLOOR_HEADER = "X-Client-Floor", RETRY_AFTER_HEADER = "Retry-After", CLI_VERSION_HEADER = "X-Ignitify-Cli", IDEMPOTENCY_KEY_HEADER = "Idempotency-Key", IDEMPOTENT_REPLAYED_HEADER = "Idempotent-Replayed", IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9._:-]{8,255}$/;
|
|
1150
|
+
TERMINAL_RUN_STATUSES = ["COMPLETED", "FAILED", "CANCELLED"];
|
|
1147
1151
|
} });
|
|
1148
1152
|
var CALLBACK_PATH_PREFIX, init_callback_path = __esm({ "../../packages/contract/callback-path.ts"() {
|
|
1149
1153
|
"use strict";
|
|
@@ -1158,6 +1162,10 @@ var init_limits = __esm({ "../../packages/contract/limits.ts"() {
|
|
|
1158
1162
|
"use strict";
|
|
1159
1163
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1160
1164
|
} });
|
|
1165
|
+
var init_currencies = __esm({ "../../packages/contract/currencies.ts"() {
|
|
1166
|
+
"use strict";
|
|
1167
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1168
|
+
} });
|
|
1161
1169
|
var TASK_CONFIG_FILENAMES, RUNTIME_REGISTRY, RUNTIME_IDS, LIVE_RUNTIME_IDS, isRuntimeId, TASK_CONFIG_AJV_OPTIONS, TASK_CONFIG_SCHEMA, BUILD_REFUSAL_STAGES, BUILD_STAGES, init_task_config = __esm({ "../../packages/contract/task-config.ts"() {
|
|
1162
1170
|
"use strict";
|
|
1163
1171
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
@@ -1302,10 +1310,10 @@ var init_package_advice = __esm({ "../../packages/contract/package-advice.ts"()
|
|
|
1302
1310
|
"use strict";
|
|
1303
1311
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1304
1312
|
} });
|
|
1305
|
-
var USAGE_AXES, USAGE_DETAIL_COMMAND, RESETS_AT_NEXT_RENEWAL, init_usage = __esm({ "../../packages/contract/usage.ts"() {
|
|
1313
|
+
var USAGE_AXES, AXIS_UNIT, USAGE_DETAIL_COMMAND, RESETS_AT_NEXT_RENEWAL, init_usage = __esm({ "../../packages/contract/usage.ts"() {
|
|
1306
1314
|
"use strict";
|
|
1307
1315
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1308
|
-
USAGE_AXES = ["compute", "network", "network_in", "disk", "scratch", "runs", "pin", "build"], USAGE_DETAIL_COMMAND = "ignitify quota", RESETS_AT_NEXT_RENEWAL = "your next paid renewal";
|
|
1316
|
+
USAGE_AXES = ["compute", "network", "network_in", "disk", "scratch", "runs", "pin", "build"], AXIS_UNIT = { compute: "ms", network: "bytes", network_in: "bytes", disk: "byte-hours", scratch: "byte-hours", runs: "runs", pin: "ms", build: "ms" }, USAGE_DETAIL_COMMAND = "ignitify quota", RESETS_AT_NEXT_RENEWAL = "your next paid renewal";
|
|
1309
1317
|
} });
|
|
1310
1318
|
function formatUserCode(code) {
|
|
1311
1319
|
if (code.length !== 8) return code;
|
|
@@ -1324,12 +1332,65 @@ var init_backoff = __esm({ "../../packages/contract/backoff.ts"() {
|
|
|
1324
1332
|
"use strict";
|
|
1325
1333
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1326
1334
|
} });
|
|
1335
|
+
var init_tenant_account = __esm({ "../../packages/contract/tenant-account.ts"() {
|
|
1336
|
+
"use strict";
|
|
1337
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1338
|
+
} });
|
|
1339
|
+
function retryAfterMs(value, nowMs) {
|
|
1340
|
+
if (value === null || value.trim() === "") return null;
|
|
1341
|
+
let text = value.trim(), ms = /^\d+$/.test(text) ? Number(text) * 1e3 : /[a-z]/i.test(text) ? Date.parse(text) - nowMs : Number.NaN;
|
|
1342
|
+
return Number.isNaN(ms) ? null : Math.min(Math.max(2e3, ms), WEB_POLL.BACKOFF_CEILING_MS);
|
|
1343
|
+
}
|
|
1344
|
+
var WEB_POLL, NO_TASK_NAME_TEXT, OBS_QUERY, FAILURE_CLASSES, FAILURE_CLASS_TEXT, OBS_REFUSAL, init_tenant_telemetry = __esm({ "../../packages/contract/tenant-telemetry.ts"() {
|
|
1345
|
+
"use strict";
|
|
1346
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1347
|
+
WEB_POLL = { LIVE_MS: 5e3, SUMMARY_MS: 6e4, JITTER: 0.1, BACKOFF_CEILING_MS: 6e4 };
|
|
1348
|
+
NO_TASK_NAME_TEXT = "(no task name)", OBS_QUERY = { CURSOR: "cursor", WAIT_SEC: "wait_sec", FROM_OFFSET: "from_offset", STATUS: "status", DAYS: "days" }, FAILURE_CLASSES = ["task", "limit", "cancelled", "platform"], FAILURE_CLASS_TEXT = { task: { label: "Your task", hint: "The code or the deployment failed: an exit code, a crash, a timeout." }, limit: { label: "A limit", hint: "A limit of your plan stopped the run. You can change it." }, cancelled: { label: "Cancelled", hint: "Someone asked the run to stop." }, platform: { label: "The platform", hint: "Something on our side failed. We see these too." } }, OBS_REFUSAL = { NEVER_RAN: { error: "run-never-ran", detail: "this run was not dispatched to a VM" }, INVALID_CURSOR: { error: "invalid-cursor", detail: "from_offset must be 0 or a next_offset that this run's output returned" }, AGENT_OUTDATED_CURSOR: { error: "agent-outdated-cursor", detail: "the host that ran this run cannot read output from an offset; read it without from_offset" }, HOST_GONE: { error: "host-gone", detail: "the host that ran this VM is no longer in the fleet" }, OUTPUT_GONE: { error: "no-file", detail: "the host no longer holds this run's output" }, INVALID_TIMELINE_CURSOR: { error: "invalid-timeline-cursor", detail: "cursor must be a cursor that this run's timeline returned" }, INVALID_DAYS: { error: "invalid-days", detail: "days must be a whole number of days, 1 or more" }, INVALID_WAIT: { error: "invalid-wait", detail: "wait_sec must be whole seconds from 0 to 20" }, WAIT_REFUSED: { error: "wait-refused", detail: "too many open waits; retry after the Retry-After seconds" }, UNAVAILABLE: { error: "obs-unavailable", detail: "the read cannot be served now; retry after the Retry-After seconds" } };
|
|
1349
|
+
} });
|
|
1350
|
+
function isPermission(value) {
|
|
1351
|
+
return typeof value == "string" && PERMISSIONS.has(value);
|
|
1352
|
+
}
|
|
1353
|
+
var PERMISSION, PERMISSIONS, PERMISSION_DESCRIPTION, ACCESS_MARKER, ACCESS_REFUSAL, ACCESS_NAME_PATTERN, ACCESS_NAME_RULE, KEY_LIST_QUERY, init_tenant_access = __esm({ "../../packages/contract/tenant-access.ts"() {
|
|
1354
|
+
"use strict";
|
|
1355
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1356
|
+
PERMISSION = { TASKS_READ: "tasks.read", TASKS_WRITE: "tasks.write", DEPLOYMENTS_READ: "deployments.read", DEPLOYMENTS_WRITE: "deployments.write", RUNS_READ: "runs.read", RUNS_WRITE: "runs.write", SECRETS_READ: "secrets.read", SECRETS_WRITE: "secrets.write", HOOKS_READ: "hooks.read", HOOKS_WRITE: "hooks.write", USAGE_READ: "usage.read" }, PERMISSIONS = new Set(Object.values(PERMISSION));
|
|
1357
|
+
PERMISSION_DESCRIPTION = { [PERMISSION.TASKS_READ]: "Read tasks", [PERMISSION.TASKS_WRITE]: "Create and delete tasks", [PERMISSION.DEPLOYMENTS_READ]: "Read deployments and build logs", [PERMISSION.DEPLOYMENTS_WRITE]: "Deploy, delete a deployment, ask for package advice", [PERMISSION.RUNS_READ]: "Read runs, logs, timeline, failures, schedules, VMs", [PERMISSION.RUNS_WRITE]: "Start, cancel, delete runs; cancel schedules; stop VMs", [PERMISSION.SECRETS_READ]: "List secret names (values are never readable)", [PERMISSION.SECRETS_WRITE]: "Set and delete secrets", [PERMISSION.HOOKS_READ]: "List hooks", [PERMISSION.HOOKS_WRITE]: "Create and delete hooks; set notify", [PERMISSION.USAGE_READ]: "Usage, series, notifications" }, ACCESS_MARKER = { ANY_KEY: "any-key", OWNER: "owner" }, ACCESS_REFUSAL = { PERMISSION_DENIED: "permission-denied", OWNER_REQUIRED: "owner-required", USER_NOT_FOUND: "user-not-found", GROUP_NOT_FOUND: "group-not-found", NAME_TAKEN: "name-taken", UNKNOWN_PERMISSION: "unknown-permission" }, ACCESS_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/, ACCESS_NAME_RULE = "1 to 63 lower case letters, digits and '-', starting with a letter or digit", KEY_LIST_QUERY = { USER: "user" };
|
|
1358
|
+
} });
|
|
1359
|
+
var NOTIFICATION_KINDS, NOTIFICATION_KIND_INFO, ALERT_KINDS, CRITICAL_ALERT_KINDS, NOTIFICATION_FILTERS, NOTIFICATION_RETENTION_DAYS, NOTIFICATION_PAGE_MAX, NOTIFICATION_PAGE_DEFAULT, NOTIFICATION_QUERY, NOTIFICATION_REFUSAL, init_tenant_notifications = __esm({ "../../packages/contract/tenant-notifications.ts"() {
|
|
1360
|
+
"use strict";
|
|
1361
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1362
|
+
init_api_routes();
|
|
1363
|
+
init_tenant_telemetry();
|
|
1364
|
+
NOTIFICATION_KINDS = ["limit.reached", "limit.near", "runs.failing", "callback.failing", "schedule.missed", "pin.lost", "plan.changed"], NOTIFICATION_KIND_INFO = { "limit.reached": { class: "alert", severity: "critical", label: "Monthly limit reached" }, "limit.near": { class: "notification", severity: "warning", label: "Monthly limit near" }, "runs.failing": { class: "alert", severity: "warning", label: "Runs failing" }, "callback.failing": { class: "alert", severity: "warning", label: "Run callbacks failing" }, "schedule.missed": { class: "notification", severity: "warning", label: "Schedule did not run" }, "pin.lost": { class: "notification", severity: "warning", label: "Pinned VM lost" }, "plan.changed": { class: "notification", severity: "info", label: "Plan changed" } }, ALERT_KINDS = NOTIFICATION_KINDS.filter((k) => NOTIFICATION_KIND_INFO[k].class === "alert"), CRITICAL_ALERT_KINDS = ALERT_KINDS.filter((k) => NOTIFICATION_KIND_INFO[k].severity === "critical"), NOTIFICATION_FILTERS = ["all", "unread", "open"], NOTIFICATION_RETENTION_DAYS = 30, NOTIFICATION_PAGE_MAX = 100, NOTIFICATION_PAGE_DEFAULT = 25, NOTIFICATION_QUERY = { FILTER: "filter", KIND: "kind", CURSOR: OBS_QUERY.CURSOR, LIMIT: "limit" }, NOTIFICATION_REFUSAL = { INVALID_FILTER: { error: "invalid-filter", detail: `filter must be one of: ${NOTIFICATION_FILTERS.join(", ")}` }, INVALID_KIND: { error: "invalid-kind", detail: `kind must be one of: ${NOTIFICATION_KINDS.join(", ")}` }, INVALID_LIMIT: { error: "invalid-limit", detail: "limit must be a whole number, 1 or more" }, INVALID_CURSOR: { error: "invalid-cursor", detail: "cursor must be a cursor that this list returned" } };
|
|
1365
|
+
} });
|
|
1366
|
+
var SITE_LINKS, init_web_nav = __esm({ "../../packages/contract/web-nav.ts"() {
|
|
1367
|
+
"use strict";
|
|
1368
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1369
|
+
SITE_LINKS = { DOCS: "https://ignitify.dev/docs", CLI_REFERENCE: "https://ignitify.dev/docs/cli/all", PRICING: "https://ignitify.dev/pricing" };
|
|
1370
|
+
} });
|
|
1371
|
+
var init_web_frame = __esm({ "../../packages/contract/web-frame.ts"() {
|
|
1372
|
+
"use strict";
|
|
1373
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1374
|
+
init_api_routes();
|
|
1375
|
+
init_tenant_account();
|
|
1376
|
+
init_web_nav();
|
|
1377
|
+
} });
|
|
1378
|
+
function stripControl(text, keep = KEEP_NONE) {
|
|
1379
|
+
return text.replace(CONTROL_UNIT, (unit) => keep(unit) ? unit : unit.startsWith("\r") ? `
|
|
1380
|
+
` : "");
|
|
1381
|
+
}
|
|
1382
|
+
var CONTROL_UNIT, KEEP_NONE, init_control_text = __esm({ "../../packages/contract/control-text.ts"() {
|
|
1383
|
+
"use strict";
|
|
1384
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1385
|
+
CONTROL_UNIT = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)?|[PX^_][^\x1b]*(?:\x1b\\)?|[ -/]*[0-~])?|\r\n?|[\u0000-\u0008\u000b\u000c\u000e-\u001a\u001c-\u001f\u007f-\u009f]/g, KEEP_NONE = () => false;
|
|
1386
|
+
} });
|
|
1327
1387
|
var init_tenant = __esm({ "../../packages/contract/tenant/index.ts"() {
|
|
1328
1388
|
"use strict";
|
|
1329
1389
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1330
1390
|
init_api_routes();
|
|
1331
1391
|
init_callback_path();
|
|
1332
1392
|
init_limits();
|
|
1393
|
+
init_currencies();
|
|
1333
1394
|
init_task_config();
|
|
1334
1395
|
init_source_manifest();
|
|
1335
1396
|
init_system_packages();
|
|
@@ -1340,21 +1401,28 @@ var init_tenant = __esm({ "../../packages/contract/tenant/index.ts"() {
|
|
|
1340
1401
|
init_digest();
|
|
1341
1402
|
init_device_code();
|
|
1342
1403
|
init_backoff();
|
|
1404
|
+
init_tenant_account();
|
|
1405
|
+
init_tenant_telemetry();
|
|
1406
|
+
init_tenant_access();
|
|
1407
|
+
init_tenant_notifications();
|
|
1408
|
+
init_web_nav();
|
|
1409
|
+
init_web_frame();
|
|
1410
|
+
init_control_text();
|
|
1343
1411
|
} });
|
|
1344
1412
|
function valueFlags(map2) {
|
|
1345
1413
|
return Object.fromEntries(Object.keys(map2).map((flag) => [flag, "value"]));
|
|
1346
1414
|
}
|
|
1347
|
-
var COMMANDS, IGNORE_FILE, STATE, CREDENTIALS, LOGIN, LOGIN_VIEW, NETWORK, ENV, DEFAULT_TASKS_DIR, DEFAULTS, UPGRADE_COMMAND, PAYLOAD_LIMIT_FALLBACK_BYTES, RESULT_LIMIT_FALLBACK_BYTES, RUN_LIST_FLAG_PARAMS, VM_LIST_FLAG_PARAMS, GLOBAL_FLAGS,
|
|
1415
|
+
var COMMANDS, IGNORE_FILE, STATE, CREDENTIALS, LOGIN, LOGIN_VIEW, NETWORK, ENV, DEFAULT_TASKS_DIR, RELEASE_BUILD, DEFAULTS, UPGRADE_COMMAND, PAYLOAD_LIMIT_FALLBACK_BYTES, RESULT_LIMIT_FALLBACK_BYTES, RUN_LIST_FLAG_PARAMS, VM_LIST_FLAG_PARAMS, GLOBAL_FLAGS, SHORT_FLAGS, VERSION_TOKENS, VALIDATE, ADVICE, INTEGRATION, NO_FLAGS, DIR, PAGE, YES, RAW, FOLLOW_FLAG, DAYS_FLAG, USAGE_FLAG_PARAMS, USAGE_FILTERS, NOTIFICATION_LIST_FLAG_PARAMS, COMMAND_META, VERSION_META, KEY_REVOKE_COMMAND, MESSAGES, ACCESS, RUN_STATUS, ACTIVE_RUN_STATUSES, DEPLOYMENT_STATUS, POLL, WAIT, FOLLOW, MS_PER_SECOND, BUSY_READ, FOLLOW_MESSAGES, init_constants = __esm({ "src/constants/index.ts"() {
|
|
1348
1416
|
"use strict";
|
|
1349
1417
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1350
1418
|
init_http_client();
|
|
1351
1419
|
init_tenant();
|
|
1352
1420
|
init_tenant();
|
|
1353
1421
|
init_tenant();
|
|
1354
|
-
COMMANDS = { HELP: "help", LOGIN: "login", LOGOUT: "logout", WHOAMI: "whoami", QUOTA: "quota", TASK_CREATE: "task:create", TASK_LIST: "task:list", TASK_GET: "task:get", TASK_DELETE: "task:delete", TASK_VALIDATE: "task:validate", TASK_PACK: "task:pack", TASK_DEPLOY: "task:deploy", TASK_RUN: "task:run", DEPLOYMENT_LIST: "deployment:list", DEPLOYMENT_GET: "deployment:get", DEPLOYMENT_LOGS: "deployment:logs", DEPLOYMENT_DELETE: "deployment:delete", RUN_LIST: "run:list", RUN_GET: "run:get", RUN_LOGS: "run:logs", RUN_DELETE: "run:delete", RUN_CANCEL: "run:cancel", VM_LIST: "vm:list", VM_GET: "vm:get", VM_DELETE: "vm:delete", SECRET_SET: "secret:set", SECRET_LIST: "secret:list", SECRET_DELETE: "secret:delete", SCHEDULE_LIST: "schedule:list", SCHEDULE_CANCEL: "schedule:cancel", HOOK_CREATE: "hook:create", HOOK_LIST: "hook:list", HOOK_DELETE: "hook:delete", NOTIFY_SET: "notify:set", INTEGRATION_ADD: "integration:add", USAGE: "usage", USAGE_EXPORT: "usage:export" }, IGNORE_FILE = ".ignitifyignore", STATE = { DIR: ".ignitify", FILE: "state.json", GITIGNORE_FILE: ".gitignore", GITIGNORE_TEXT: `# written by ignitify: local state, never commit
|
|
1422
|
+
COMMANDS = { HELP: "help", LOGIN: "login", LOGOUT: "logout", WHOAMI: "whoami", QUOTA: "quota", TASK_CREATE: "task:create", TASK_LIST: "task:list", TASK_GET: "task:get", TASK_DELETE: "task:delete", TASK_VALIDATE: "task:validate", TASK_PACK: "task:pack", TASK_DEPLOY: "task:deploy", TASK_RUN: "task:run", DEPLOYMENT_LIST: "deployment:list", DEPLOYMENT_GET: "deployment:get", DEPLOYMENT_LOGS: "deployment:logs", DEPLOYMENT_DELETE: "deployment:delete", RUN_LIST: "run:list", RUN_GET: "run:get", RUN_LOGS: "run:logs", RUN_TIMELINE: "run:timeline", RUN_FAILURES: "run:failures", RUN_DELETE: "run:delete", RUN_CANCEL: "run:cancel", VM_LIST: "vm:list", VM_GET: "vm:get", VM_DELETE: "vm:delete", SECRET_SET: "secret:set", SECRET_LIST: "secret:list", SECRET_DELETE: "secret:delete", SCHEDULE_LIST: "schedule:list", SCHEDULE_CANCEL: "schedule:cancel", HOOK_CREATE: "hook:create", HOOK_LIST: "hook:list", HOOK_DELETE: "hook:delete", NOTIFY_SET: "notify:set", INTEGRATION_ADD: "integration:add", USAGE: "usage", USAGE_EXPORT: "usage:export", USAGE_SERIES: "usage:series", NOTIFICATION_LIST: "notification:list", PERMISSION_LIST: "permission:list", USER_CREATE: "user:create", USER_LIST: "user:list", USER_GET: "user:get", USER_REMOVE: "user:remove", GROUP_CREATE: "group:create", GROUP_LIST: "group:list", GROUP_GET: "group:get", GROUP_GRANT: "group:grant", GROUP_REVOKE: "group:revoke", GROUP_ADD_USER: "group:add-user", GROUP_REMOVE_USER: "group:remove-user", GROUP_DELETE: "group:delete", KEY_CREATE: "key:create", KEY_LIST: "key:list", KEY_REVOKE: "key:revoke" }, IGNORE_FILE = ".ignitifyignore", STATE = { DIR: ".ignitify", FILE: "state.json", GITIGNORE_FILE: ".gitignore", GITIGNORE_TEXT: `# written by ignitify: local state, never commit
|
|
1355
1423
|
*
|
|
1356
|
-
` }, CREDENTIALS = { DIR_NAME: "ignitify", FILE_NAME: "credentials.json", DIR_MODE: 448, FILE_MODE: 384 }, LOGIN = { MAX_RETRIES: 3, KEY_TTL_DAYS: 90, MIN_POLL_INTERVAL_SEC: 5, BROWSER_SCHEMES: ["https:", "http:"] }, LOGIN_VIEW = { TITLE: "Sign in to Ignitify", OPEN_PAGE: "Open this page:", ASK_OPERATOR: "Give this code to your operator, who approves it:", CHECK_CODE: "Check this code", SAME_CODE: "The page must show the same code.", WAITING_STATIC: (minutes) => `Waiting for approval. The code expires in ${minutes} minutes.`, WAITING_LIVE: (left) => `Waiting for approval... code expires in ${left}`, CANCELLED: "Sign-in cancelled. Nothing was saved.", SIGNED_IN: "Signed in", SIGNED_OUT: "Signed out", KEY_EXPIRES: (days) => `${days} days after last use; each use renews it`, YES: "yes", ROWS: { ACCOUNT: "Account", ACCOUNT_ID: "Account id", KEY_LABEL: "Key label", KEY_EXPIRES: "Key expires", SAVED_TO: "Saved to", REVOKED: "Revoked", DELETED: "Deleted" } }, NETWORK = { REASONS: { ECONNREFUSED: "nothing accepts connections there", ENOTFOUND: "the host name is unknown", EAI_AGAIN: "the host name lookup failed", ETIMEDOUT: "the connection timed out", UND_ERR_CONNECT_TIMEOUT: "the connection timed out", ECONNRESET: "the connection was reset" }, TLS_WORDS: "the TLS certificate was not accepted", TLS_CODES: ["CERT_HAS_EXPIRED", "CERT_NOT_YET_VALID", "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"], TLS_CODE_PREFIXES: ["ERR_TLS_", "ERR_SSL_"], CODE_PATTERN: /^[A-Z][A-Z0-9_]*$/, DETAIL_MAX_CHARS: 80 }, ENV = { TASKS_DIR: "IGNITIFY_TASKS_DIR", KEY: "IGNITIFY_API_KEY" }, DEFAULT_TASKS_DIR = "tasks", DEFAULTS = { API_URL: "https://api.ignitify.dev" }, UPGRADE_COMMAND = "npm install --global ignitify@latest", PAYLOAD_LIMIT_FALLBACK_BYTES = 10240, RESULT_LIMIT_FALLBACK_BYTES = 10240, RUN_LIST_FLAG_PARAMS = { status: "status", "deployment-id": "deployment_id", "task-name": "task_name", preset: "preset", "correlation-id": "correlation_id", "vm-id": "vm_id", ...PAGE_FLAG_PARAMS }, VM_LIST_FLAG_PARAMS = { "deployment-id": "deployment_id", "task-name": "task_name", preset: "preset", "older-than": "older_than", ...PAGE_FLAG_PARAMS }, GLOBAL_FLAGS = { json: "bool", help: "bool" },
|
|
1357
|
-
VALIDATE = { VALID: "task config valid" }, ADVICE = { FLAG: "review-packages", TIMEOUT_MS: 6e4, FAULT_STATUSES: [400, 401, 403], SECONDS_ONLY_BELOW: 60, SECONDS_PER_MINUTE: 60, NO_PACKAGES: "this task lists no build.system_packages", SKIPPED: (reason) => `AI check skipped: ${reason}`, RATE_LIMITED: (retryAfterS) => `rate limited, try again in ${retryAfterS} s`, UNAVAILABLE: (reason) => `the review is unavailable (${reason})`, NO_ANSWER: (status, message) => status === null ? message : `server answered ${status}`, FAULT: (status, body) => `AI check failed (HTTP ${status}): ${body}`, BAD_NAMES: (bad) => `not valid package names: ${bad.join(", ")}`, TOO_MANY: (count, max) => `${count} packages listed, the most is ${max}`, BASIS: { measured: "measured", model: "model estimate" }, NO_FIT: (driveMib, capMib) => `does not fit: ${driveMib} MiB of packages against the ${capMib} MiB image store of your tier`, TITLE: "Package review", NOTE: "AI advice only. A suggestion is not checked against Debian.", COLUMNS: { PACKAGE: "Package", VERDICT: "Verdict", SIZE: "Size", NOTE: "Note", SUGGEST: "Suggest" }, NOT_DOUBLE_CHECKED: "not double-checked by the second model", RESULT_CLEAN: (total) => `No concerns found in ${total} package${total === 1 ? "" : "s"}.`, RESULT_FLAGGED: (total, flagged) => `${total} package${total === 1 ? "" : "s"} checked, ${flagged} to look at.`, NOT_ANSWERED: (names) => `No AI answer for: ${names.join(", ")}. The review could not judge ${names.length === 1 ? "it" : "them"}.`, BUILD_TITLE: "Build time", BUILD_LABELS: { ADDED: "Added", APPLIES: "Applies to", SIZE: "Packages size", LEFT_OUT: "Left out" }, BUILD_NONE: "nothing: your tenant already holds the drive for this list", BUILD_ADDED: (range, basis) => `about ${range} (${basis})`, BUILD_APPLIES: "the first deploy of this list. A later code-only redeploy reuses the drive.", BUILD_SIZE: (mib, capMib) => capMib === null ? `${mib} MiB` : `${mib} MiB of ${capMib} MiB (the image store of your tier)`, BUILD_LEFT_OUT: (names) => `${names.join(", ")} (unusable, or size unknown)` }, INTEGRATION = { PACKAGE_JSON: "package.json", NODE_MODULES: "node_modules", RUNTIME: "node", NPM: "npm", TARBALL_EXT: ".tgz", PACKED_ROOT: "package", PACK_TMP_PREFIX: "ignitify-integration-pack-", PACK_ARGS: (pkg, destDir, taskDir) => ["pack", pkg, "--pack-destination", destDir, "--prefix", taskDir], INSTALL_ARGS: (spec, taskDir) => ["install", spec, "--save-prod", "--ignore-scripts", "--prefix", taskDir], PACKED_MAX_BYTES: 8 * 1024 * 1024, ZLIB_TOO_BIG: "ERR_BUFFER_TOO_LARGE", VERSION_PATTERN: /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, QUESTION: "Apply?", REGISTRY_URL: "https://registry.npmjs.org", REGISTRY_ACCEPT: "application/vnd.npm.install-v1+json", REGISTRY_TIMEOUT_MS: 5e3, NOTED_FIELDS: ["execution.network.type", "run.preset"], USAGE: "usage: ignitify integration:add <name> --task <slug> [--yes | --dry-run] [--dir <base-dir>]", TASK_REQUIRED: "--task <slug> is required: the task folder to add the integration to", YES_WITH_DRY_RUN: "--yes and --dry-run cannot be used together", BAD_NAME: (name, pattern) => `integration name ${JSON.stringify(name)} must match ${pattern}`, NOT_NODE: (runtime) => `integrations work with Node tasks only; this task has runtime "${runtime}"`, NO_PACKAGE_JSON: (taskDir) => `${taskDir} has no package.json: run npm init in the task folder first; nothing was changed`, NOT_INTERACTIVE: "Refusing to proceed: not a TTY. Re-run with --yes to apply the plan, or with --dry-run to see it.", PACK_FAILED: (pkg, code) => `npm pack ${pkg} failed${code === null ? "" : ` (exit ${code})`}; nothing in the task was changed`, INSTALL_FAILED: (spec, code) => `npm install ${spec} failed${code === null ? "" : ` (exit ${code})`}; task.yaml was not changed`, INSTALLED: (spec) => `${spec} installed`, BAD_VERSION: (pkg, version2) => `${pkg}: its package.json version ${JSON.stringify(version2)} is not a plain x.y.z version; nothing in the task was changed`, PACKED_TOO_BIG: (file, max) => `${file} unpacks to more than ${max} bytes: refused, nothing in the task was changed`, INSTALL_ONLY: (spec) => `installs ${spec}; task.yaml unchanged`, NOT_AN_INTEGRATION: (pkg, file) => `${pkg} has no ${file}: it is not an integration package`, BAD_MANIFEST: (pkg, issue) => `${pkg} integration.yaml: ${issue}`, UNREADABLE: (file, reason) => `cannot read ${file}: ${reason}`, WRITE_MISMATCH: (file) => `the edited ${file} does not read back as the plan; nothing was written`, YOU_ADD: "you add:", NO_CHANGES: "task.yaml already has every setting this integration needs; nothing to write", DRY_RUN: "dry run: task.yaml was not changed", WRITTEN: (file) => `${file} updated`, NOT_INSTALLED: (pkgs) => `${pkgs.join(", ")} ${pkgs.length === 1 ? "is" : "are"} in package.json but not in node_modules: run npm install, then task:validate again`, OBSOLETE: (pkg, version2, latest) => `${pkg}@${version2} is obsolete: use ${latest}`, OBSOLETE_SKIPPED: (reason) => `obsolete-version check skipped: ${reason}`, REGISTRY_UNREACHABLE: "the npm registry did not answer (offline?)", REGISTRY_NOT_FOUND: (pkg) => `${pkg} is not on the npm registry` }, NO_FLAGS = {}, DIR = { dir: "value" }, PAGE = valueFlags(PAGE_FLAG_PARAMS), YES = { yes: "bool" }, RAW = { raw: "bool" }, USAGE_FLAG_PARAMS = { period: "period", axis: "axis", vm: "vm", run: "run" }, USAGE_FILTERS = valueFlags(USAGE_FLAG_PARAMS), COMMAND_META = { [COMMANDS.HELP]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.LOGIN]: { flags: { ...SERVER_FLAGS, "no-browser": "bool", name: "value" }, positionals: 0, confirms: false }, [COMMANDS.LOGOUT]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.WHOAMI]: { flags: SERVER_FLAGS, positionals: 0, confirms: false }, [COMMANDS.QUOTA]: { flags: SERVER_FLAGS, positionals: 0, confirms: false }, [COMMANDS.TASK_CREATE]: { flags: { ...SERVER_FLAGS, ...DIR, lang: "value", local: "bool", "no-init": "bool", "no-scaffold": "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_LIST]: { flags: { ...SERVER_FLAGS, ...PAGE }, positionals: 0, confirms: false }, [COMMANDS.TASK_GET]: { flags: { ...SERVER_FLAGS, ...DIR }, positionals: 1, confirms: false }, [COMMANDS.TASK_DELETE]: { flags: { ...SERVER_FLAGS, ...DIR, ...YES }, positionals: 1, confirms: true }, [COMMANDS.TASK_VALIDATE]: { flags: { ...SERVER_FLAGS, ...DIR, [ADVICE.FLAG]: "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_PACK]: { flags: DIR, positionals: 1, confirms: false }, [COMMANDS.TASK_DEPLOY]: { flags: { ...SERVER_FLAGS, ...DIR, check: "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_RUN]: { flags: { deployment: "value", payload: "value", "timeout-sec": "value", "on-busy": "value", at: "value", wait: "bool", ...DIR, ...SERVER_FLAGS }, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_LIST]: { flags: { ...SERVER_FLAGS, ...PAGE }, positionals: 0, confirms: false }, [COMMANDS.DEPLOYMENT_GET]: { flags: SERVER_FLAGS, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_LOGS]: { flags: { ...SERVER_FLAGS, ...RAW }, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_DELETE]: { flags: { ...SERVER_FLAGS, ...YES }, positionals: 1, confirms: true }, [COMMANDS.RUN_LIST]: { flags: { ...SERVER_FLAGS, ...valueFlags(RUN_LIST_FLAG_PARAMS) }, positionals: 0, confirms: false }, [COMMANDS.RUN_GET]: { flags: { ...SERVER_FLAGS, full: "bool" }, positionals: 1, confirms: false }, [COMMANDS.RUN_LOGS]: { flags: { ...SERVER_FLAGS, ...RAW }, positionals: 1, confirms: false }, [COMMANDS.RUN_DELETE]: { flags: { ...SERVER_FLAGS, ...YES }, positionals: 1, confirms: true }, [COMMANDS.RUN_CANCEL]: { flags: { ...SERVER_FLAGS, ...YES, force: "bool", grace: "value" }, positionals: 1, confirms: true }, [COMMANDS.VM_LIST]: { flags: { ...SERVER_FLAGS, ...valueFlags(VM_LIST_FLAG_PARAMS), pinned: "bool" }, positionals: 0, confirms: false }, [COMMANDS.VM_GET]: { flags: SERVER_FLAGS, positionals: 1, confirms: false }, [COMMANDS.VM_DELETE]: { flags: { ...SERVER_FLAGS, ...YES, force: "bool" }, positionals: 1, confirms: true }, [COMMANDS.SECRET_SET]: { flags: { ...SERVER_FLAGS, stdin: "bool", value: "value" }, positionals: 1, confirms: false, secretArgv: true }, [COMMANDS.SECRET_LIST]: { flags: SERVER_FLAGS, positionals: 0, confirms: false }, [COMMANDS.SECRET_DELETE]: { flags: { ...SERVER_FLAGS, ...YES }, positionals: 1, confirms: true }, [COMMANDS.SCHEDULE_LIST]: { flags: SERVER_FLAGS, positionals: 0, confirms: false }, [COMMANDS.SCHEDULE_CANCEL]: { flags: { ...SERVER_FLAGS, ...YES }, positionals: 1, confirms: true }, [COMMANDS.HOOK_CREATE]: { flags: { ...SERVER_FLAGS, "task-name": "value" }, positionals: 1, confirms: false }, [COMMANDS.HOOK_LIST]: { flags: { ...SERVER_FLAGS, ...PAGE }, positionals: 1, confirms: false }, [COMMANDS.HOOK_DELETE]: { flags: { ...SERVER_FLAGS, ...YES }, positionals: 1, confirms: true }, [COMMANDS.NOTIFY_SET]: { flags: { ...SERVER_FLAGS, url: "value", clear: "bool", "secret-stdin": "bool", secret: "value" }, positionals: 1, confirms: false, secretArgv: true }, [COMMANDS.INTEGRATION_ADD]: { flags: { ...DIR, ...YES, task: "value", "dry-run": "bool" }, positionals: 1, confirms: true }, [COMMANDS.USAGE]: { flags: { ...SERVER_FLAGS, ...USAGE_FILTERS, runs: "bool", vms: "bool" }, positionals: 0, confirms: false }, [COMMANDS.USAGE_EXPORT]: { flags: { ...SERVER_FLAGS, ...USAGE_FILTERS, csv: "bool" }, positionals: 0, confirms: false } }, VERSION_META = { flags: SERVER_FLAGS, positionals: 0, confirms: false }, MESSAGES = { NOT_SIGNED_IN: "Not signed in. Run 'ignitify login', or set the IGNITIFY_API_KEY environment variable.", CREDENTIALS_SYMLINK: (file) => `${file} is a symbolic link. Remove it and run 'ignitify login' again.`, CREDENTIALS_NOT_FILE: (file) => `${file} is not a regular file. Remove it and run 'ignitify login' again.`, CREDENTIALS_NOT_OWNED: (file) => `${file} belongs to another user. Remove it and run 'ignitify login' again.`, CREDENTIALS_TOO_OPEN: (file, mode) => `${file} can be read by other users (mode 0${mode}). Run: chmod 600 ${file}`, CREDENTIALS_UNREADABLE: (file) => `${file} is not a valid credentials file. Remove it and run 'ignitify login' again.`, CREDENTIALS_OTHER_URL: (fileUrl, url) => `The saved key is for ${fileUrl}, not ${url}. Run 'ignitify login --api ${url}'.`, CREDENTIALS_DIR_NOT_DIR: (dir) => `${dir} is not a folder. Remove it and run 'ignitify login' again.`, API_URL_NOT_URL: (url) => `The API address "${url}" is not a URL.`, API_URL_NOT_HTTPS: (url) => `The API address ${url} is not https. The CLI sends your key only over https, or over plain http to this machine (localhost, 127.0.0.1, ::1).`, LOGIN_REPLACED: (label) => `Note: replaced the key "${label}" of another machine. Use 'ignitify login --name <other-label>' to keep both.`, LOGIN_EXPIRED: (code) => `The code ${code} expired before it was approved. Run 'ignitify login' again.`, LOGIN_REFUSED: (reason) => `The sign-in was refused: ${reason}.`, LOGIN_UNKNOWN: "The sign-in code is unknown or was already used. Run 'ignitify login' again.", LOGIN_BAD_ANSWER: "The server sent a sign-in answer with no usable key. Nothing was saved.", LOGIN_BAD_START: "The server sent a sign-in start that the CLI cannot use (no code, or no wait times). Nothing was started.", ENV_KEY_OVERRIDES_LOGIN: "Note: the IGNITIFY_API_KEY environment variable is set, so it is used instead of your saved login.", KEY_EXPIRED_HINT: "Run 'ignitify login' to sign in again.", LOGIN_TOO_MANY: "Too many sign-in attempts from this network. Try again later.", LOGIN_CAP: (limit) => `This account already has the most API keys its plan allows${limit === null ? "" : ` (${limit})`}. Remove one, or approve with "replace", then run 'ignitify login' again.`, CANNOT_REACH: (url, detail) => `Cannot reach ${url}${detail === null ? "" : ` (${detail})`}. Check your network and run 'ignitify login' again.`, UNREACHABLE: (origin, detail) => `Cannot reach ${origin}${detail === null ? "" : ` (${detail})`}.`, LOGOUT_NOTHING: "Not signed in. There are no saved credentials.", LOGOUT_ENV_NOTE: "Note: the IGNITIFY_API_KEY environment variable is still set. Logout does not change it.", LOGOUT_NOT_REVOKED: (label) => `Warning: the server did not revoke the key, so it may still work. The saved credentials were deleted. Ask your operator to revoke the key "${label}".`, SLUG_MISSING: (source) => `'slug' is required in ${source}`, SLUG_INVALID: (source, reason) => `${source}: 'slug' ${reason}`, PRESET_INVALID: "run.preset must be a non-empty string", PRESET_UNKNOWN: (source, name, valid) => `${source}: unknown run.preset "${name}" (valid: ${valid.join(", ")}). An unrecognised preset would silently fall back to the platform default, giving you a different instance class than you declared.`, TIMEOUT_INVALID: "run.timeout_sec must be a positive number", TASK_ID_REQUIRED: "a <task-id> is required", TASK_ID_INVALID: (id, reason) => `task id ${JSON.stringify(id)} ${reason}`, ERROR: (message) => `Error: ${message}`, ERROR_PREFIX: "Error:", SERVER_TRACE_ID: (id) => `server trace id: ${id}`, ARG_REQUIRED: (label) => `${label} is required`, UNKNOWN_COMMAND: "unknown command", SEE_HELP: "Run 'ignitify help' to see every command.", UNKNOWN_SHORT_FLAG: (short, long, command) => `unknown flag ${short} (--${long}) for ${command}`, GRACE_INVALID: (got) => `--grace must be a whole number of milliseconds, 0 or more (got ${JSON.stringify(got)})`, GRACE_WITH_FORCE: "--grace has no effect with --force (force stops the run now)", TASK_LIMITS: (payload, result) => `payload max ${payload}, result max ${result}; over the limit the run fails`, PAYLOAD_OVER_LIMIT: (bytes, limit) => `payload is ${bytes} bytes; the limit is ${limit} bytes`, PAYLOAD_REFUSED_BY_SERVER: (limit) => `the server refused the payload as too large; the limit is ${limit} bytes`, DEFAULT_PAYLOAD_OVER_LIMIT: (source, bytes, limit) => `${source}: run.payload is ${bytes} bytes; the offline limit is ${limit} bytes (the server may allow more)`, IGNORED_BY_FILE: `ignored by ${IGNORE_FILE}`, IGNORED_COUNT: (n) => `${n} path(s) left out by ${IGNORE_FILE}`, TASK_DELETED: "task deleted", TASK_LINK_REMOVED: "Removed local link", TASK_LINK_NOT_REMOVED: (link, reason) => `Warning: the task is deleted, but its local link was not removed (${reason}). Remove ${link} by hand.`, DEPLOY_DELETED: "deployment deleted", RUN_DELETED: "run deleted", ABORTED: "Aborted.", CONFIRM_PROCEED: "Proceed?", SECRET_SET_USAGE: "usage: ignitify secret:set <NAME> --stdin (the value is read from stdin)", SECRET_VALUE_EMPTY: "secret:set read an empty value from stdin \u2014 nothing was stored (pipe the value in: `--stdin < value-file`)", SECRET_VALUE_ON_ARGV: `secret:set takes no value on argv: it is visible in \`ps\` and is written to your shell history. Pipe it on stdin, e.g.
|
|
1424
|
+
` }, CREDENTIALS = { DIR_NAME: "ignitify", FILE_NAME: "credentials.json", DIR_MODE: 448, FILE_MODE: 384 }, LOGIN = { MAX_RETRIES: 3, KEY_TTL_DAYS: 90, MIN_POLL_INTERVAL_SEC: 5, BROWSER_SCHEMES: ["https:", "http:"] }, LOGIN_VIEW = { TITLE: "Sign in to Ignitify", OPEN_PAGE: "Open this page:", ASK_OPERATOR: "Give this code to your operator, who approves it:", CHECK_CODE: "Check this code", SAME_CODE: "The page must show the same code.", WAITING_STATIC: (minutes) => `Waiting for approval. The code expires in ${minutes} minutes.`, WAITING_LIVE: (left) => `Waiting for approval... code expires in ${left}`, CANCELLED: "Sign-in cancelled. Nothing was saved.", SIGNED_IN: "Signed in", SIGNED_OUT: "Signed out", KEY_EXPIRES: (days) => `${days} days after last use; each use renews it`, YES: "yes", ROWS: { ACCOUNT: "Account", ACCOUNT_ID: "Account id", KEY_LABEL: "Key label", KEY_EXPIRES: "Key expires", SAVED_TO: "Saved to", REVOKED: "Revoked", DELETED: "Deleted" } }, NETWORK = { REASONS: { ECONNREFUSED: "nothing accepts connections there", ENOTFOUND: "the host name is unknown", EAI_AGAIN: "the host name lookup failed", ETIMEDOUT: "the connection timed out", UND_ERR_CONNECT_TIMEOUT: "the connection timed out", ECONNRESET: "the connection was reset" }, TLS_WORDS: "the TLS certificate was not accepted", TLS_CODES: ["CERT_HAS_EXPIRED", "CERT_NOT_YET_VALID", "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"], TLS_CODE_PREFIXES: ["ERR_TLS_", "ERR_SSL_"], CODE_PATTERN: /^[A-Z][A-Z0-9_]*$/, DETAIL_MAX_CHARS: 80 }, ENV = { TASKS_DIR: "IGNITIFY_TASKS_DIR", KEY: "IGNITIFY_API_KEY", DEV_API_URL: "IGNITIFY_DEV_API_URL" }, DEFAULT_TASKS_DIR = "tasks", RELEASE_BUILD = true, DEFAULTS = { API_URL: "https://api.ignitify.dev" }, UPGRADE_COMMAND = "npm install --global ignitify@latest", PAYLOAD_LIMIT_FALLBACK_BYTES = 10240, RESULT_LIMIT_FALLBACK_BYTES = 10240, RUN_LIST_FLAG_PARAMS = { status: "status", "deployment-id": "deployment_id", "task-name": "task_name", preset: "preset", "correlation-id": "correlation_id", "vm-id": "vm_id", ...PAGE_FLAG_PARAMS }, VM_LIST_FLAG_PARAMS = { "deployment-id": "deployment_id", "task-name": "task_name", preset: "preset", "older-than": "older_than", ...PAGE_FLAG_PARAMS }, GLOBAL_FLAGS = { json: "bool", help: "bool" }, SHORT_FLAGS = { h: "help", y: "yes" }, VERSION_TOKENS = ["--version", "-v"];
|
|
1425
|
+
VALIDATE = { VALID: "task config valid" }, ADVICE = { FLAG: "review-packages", TIMEOUT_MS: 6e4, FAULT_STATUSES: [400, 401, 403], SECONDS_ONLY_BELOW: 60, SECONDS_PER_MINUTE: 60, NO_PACKAGES: "this task lists no build.system_packages", SKIPPED: (reason) => `AI check skipped: ${reason}`, RATE_LIMITED: (retryAfterS) => `rate limited, try again in ${retryAfterS} s`, UNAVAILABLE: (reason) => `the review is unavailable (${reason})`, NO_ANSWER: (status, message) => status === null ? message : `server answered ${status}`, FAULT: (status, body) => `AI check failed (HTTP ${status}): ${body}`, BAD_NAMES: (bad) => `not valid package names: ${bad.join(", ")}`, TOO_MANY: (count, max) => `${count} packages listed, the most is ${max}`, BASIS: { measured: "measured", model: "model estimate" }, NO_FIT: (driveMib, capMib) => `does not fit: ${driveMib} MiB of packages against the ${capMib} MiB image store of your tier`, TITLE: "Package review", NOTE: "AI advice only. A suggestion is not checked against Debian.", COLUMNS: { PACKAGE: "Package", VERDICT: "Verdict", SIZE: "Size", NOTE: "Note", SUGGEST: "Suggest" }, NOT_DOUBLE_CHECKED: "not double-checked by the second model", RESULT_CLEAN: (total) => `No concerns found in ${total} package${total === 1 ? "" : "s"}.`, RESULT_FLAGGED: (total, flagged) => `${total} package${total === 1 ? "" : "s"} checked, ${flagged} to look at.`, NOT_ANSWERED: (names) => `No AI answer for: ${names.join(", ")}. The review could not judge ${names.length === 1 ? "it" : "them"}.`, BUILD_TITLE: "Build time", BUILD_LABELS: { ADDED: "Added", APPLIES: "Applies to", SIZE: "Packages size", LEFT_OUT: "Left out" }, BUILD_NONE: "nothing: your tenant already holds the drive for this list", BUILD_ADDED: (range, basis) => `about ${range} (${basis})`, BUILD_APPLIES: "the first deploy of this list. A later code-only redeploy reuses the drive.", BUILD_SIZE: (mib, capMib) => capMib === null ? `${mib} MiB` : `${mib} MiB of ${capMib} MiB (the image store of your tier)`, BUILD_LEFT_OUT: (names) => `${names.join(", ")} (unusable, or size unknown)` }, INTEGRATION = { PACKAGE_JSON: "package.json", NODE_MODULES: "node_modules", RUNTIME: "node", NPM: "npm", TARBALL_EXT: ".tgz", PACKED_ROOT: "package", PACK_TMP_PREFIX: "ignitify-integration-pack-", PACK_ARGS: (pkg, destDir, taskDir) => ["pack", pkg, "--pack-destination", destDir, "--prefix", taskDir], INSTALL_ARGS: (spec, taskDir) => ["install", spec, "--save-prod", "--ignore-scripts", "--prefix", taskDir], PACKED_MAX_BYTES: 8 * 1024 * 1024, ZLIB_TOO_BIG: "ERR_BUFFER_TOO_LARGE", VERSION_PATTERN: /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, QUESTION: "Apply?", REGISTRY_URL: "https://registry.npmjs.org", REGISTRY_ACCEPT: "application/vnd.npm.install-v1+json", REGISTRY_TIMEOUT_MS: 5e3, NOTED_FIELDS: ["execution.network.type", "run.preset"], USAGE: "usage: ignitify integration:add <name> --task <slug> [--yes | --dry-run] [--dir <base-dir>]", TASK_REQUIRED: "--task <slug> is required: the task folder to add the integration to", YES_WITH_DRY_RUN: "--yes and --dry-run cannot be used together", BAD_NAME: (name, pattern) => `integration name ${JSON.stringify(name)} must match ${pattern}`, NOT_NODE: (runtime) => `integrations work with Node tasks only; this task has runtime "${runtime}"`, NO_PACKAGE_JSON: (taskDir) => `${taskDir} has no package.json: run npm init in the task folder first; nothing was changed`, NOT_INTERACTIVE: "Refusing to proceed: not a TTY. Re-run with --yes to apply the plan, or with --dry-run to see it.", PACK_FAILED: (pkg, code) => `npm pack ${pkg} failed${code === null ? "" : ` (exit ${code})`}; nothing in the task was changed`, INSTALL_FAILED: (spec, code) => `npm install ${spec} failed${code === null ? "" : ` (exit ${code})`}; task.yaml was not changed`, INSTALLED: (spec) => `${spec} installed`, BAD_VERSION: (pkg, version2) => `${pkg}: its package.json version ${JSON.stringify(version2)} is not a plain x.y.z version; nothing in the task was changed`, PACKED_TOO_BIG: (file, max) => `${file} unpacks to more than ${max} bytes: refused, nothing in the task was changed`, INSTALL_ONLY: (spec) => `installs ${spec}; task.yaml unchanged`, NOT_AN_INTEGRATION: (pkg, file) => `${pkg} has no ${file}: it is not an integration package`, BAD_MANIFEST: (pkg, issue) => `${pkg} integration.yaml: ${issue}`, UNREADABLE: (file, reason) => `cannot read ${file}: ${reason}`, WRITE_MISMATCH: (file) => `the edited ${file} does not read back as the plan; nothing was written`, YOU_ADD: "you add:", NO_CHANGES: "task.yaml already has every setting this integration needs; nothing to write", DRY_RUN: "dry run: task.yaml was not changed", WRITTEN: (file) => `${file} updated`, NOT_INSTALLED: (pkgs) => `${pkgs.join(", ")} ${pkgs.length === 1 ? "is" : "are"} in package.json but not in node_modules: run npm install, then task:validate again`, OBSOLETE: (pkg, version2, latest) => `${pkg}@${version2} is obsolete: use ${latest}`, OBSOLETE_SKIPPED: (reason) => `obsolete-version check skipped: ${reason}`, REGISTRY_UNREACHABLE: "the npm registry did not answer (offline?)", REGISTRY_NOT_FOUND: (pkg) => `${pkg} is not on the npm registry` }, NO_FLAGS = {}, DIR = { dir: "value" }, PAGE = valueFlags(PAGE_FLAG_PARAMS), YES = { yes: "bool" }, RAW = { raw: "bool" }, FOLLOW_FLAG = { follow: "bool" }, DAYS_FLAG = { days: "value" }, USAGE_FLAG_PARAMS = { period: "period", axis: "axis", vm: "vm", run: "run" }, USAGE_FILTERS = valueFlags(USAGE_FLAG_PARAMS), NOTIFICATION_LIST_FLAG_PARAMS = { kind: NOTIFICATION_QUERY.KIND, limit: NOTIFICATION_QUERY.LIMIT }, COMMAND_META = { [COMMANDS.HELP]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.LOGIN]: { flags: { "no-browser": "bool", name: "value" }, positionals: 0, confirms: false }, [COMMANDS.LOGOUT]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.WHOAMI]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.QUOTA]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.TASK_CREATE]: { flags: { ...DIR, lang: "value", local: "bool", "no-init": "bool", "no-scaffold": "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_LIST]: { flags: PAGE, positionals: 0, confirms: false }, [COMMANDS.TASK_GET]: { flags: DIR, positionals: 1, confirms: false }, [COMMANDS.TASK_DELETE]: { flags: { ...DIR, ...YES }, positionals: 1, confirms: true }, [COMMANDS.TASK_VALIDATE]: { flags: { ...DIR, [ADVICE.FLAG]: "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_PACK]: { flags: DIR, positionals: 1, confirms: false }, [COMMANDS.TASK_DEPLOY]: { flags: { ...DIR, check: "bool" }, positionals: 1, confirms: false }, [COMMANDS.TASK_RUN]: { flags: { deployment: "value", payload: "value", "timeout-sec": "value", "on-busy": "value", at: "value", "idempotency-key": "value", wait: "bool", ...DIR }, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_LIST]: { flags: PAGE, positionals: 0, confirms: false }, [COMMANDS.DEPLOYMENT_GET]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_LOGS]: { flags: RAW, positionals: 1, confirms: false }, [COMMANDS.DEPLOYMENT_DELETE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.RUN_LIST]: { flags: valueFlags(RUN_LIST_FLAG_PARAMS), positionals: 0, confirms: false }, [COMMANDS.RUN_GET]: { flags: { full: "bool" }, positionals: 1, confirms: false }, [COMMANDS.RUN_LOGS]: { flags: { ...RAW, ...FOLLOW_FLAG, tail: "value" }, positionals: 1, confirms: false }, [COMMANDS.RUN_TIMELINE]: { flags: FOLLOW_FLAG, positionals: 1, confirms: false }, [COMMANDS.RUN_FAILURES]: { flags: DAYS_FLAG, positionals: 0, confirms: false }, [COMMANDS.RUN_DELETE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.RUN_CANCEL]: { flags: { ...YES, force: "bool", grace: "value" }, positionals: 1, confirms: true }, [COMMANDS.VM_LIST]: { flags: { ...valueFlags(VM_LIST_FLAG_PARAMS), pinned: "bool" }, positionals: 0, confirms: false }, [COMMANDS.VM_GET]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.VM_DELETE]: { flags: { ...YES, force: "bool" }, positionals: 1, confirms: true }, [COMMANDS.SECRET_SET]: { flags: { stdin: "bool", value: "value" }, positionals: 1, confirms: false, secretArgv: true }, [COMMANDS.SECRET_LIST]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.SECRET_DELETE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.SCHEDULE_LIST]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.SCHEDULE_CANCEL]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.HOOK_CREATE]: { flags: { "task-name": "value" }, positionals: 1, confirms: false }, [COMMANDS.HOOK_LIST]: { flags: PAGE, positionals: 1, confirms: false }, [COMMANDS.HOOK_DELETE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.NOTIFY_SET]: { flags: { url: "value", clear: "bool", "secret-stdin": "bool", secret: "value" }, positionals: 1, confirms: false, secretArgv: true }, [COMMANDS.INTEGRATION_ADD]: { flags: { ...DIR, ...YES, task: "value", "dry-run": "bool" }, positionals: 1, confirms: true }, [COMMANDS.USAGE]: { flags: { ...USAGE_FILTERS, runs: "bool", vms: "bool" }, positionals: 0, confirms: false }, [COMMANDS.USAGE_EXPORT]: { flags: { ...USAGE_FILTERS, csv: "bool" }, positionals: 0, confirms: false }, [COMMANDS.USAGE_SERIES]: { flags: DAYS_FLAG, positionals: 0, confirms: false }, [COMMANDS.NOTIFICATION_LIST]: { flags: { ...valueFlags(NOTIFICATION_LIST_FLAG_PARAMS), unread: "bool", open: "bool" }, positionals: 0, confirms: false }, [COMMANDS.PERMISSION_LIST]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.USER_CREATE]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.USER_LIST]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.USER_GET]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.USER_REMOVE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.GROUP_CREATE]: { flags: { permissions: "value" }, positionals: 1, confirms: false }, [COMMANDS.GROUP_LIST]: { flags: NO_FLAGS, positionals: 0, confirms: false }, [COMMANDS.GROUP_GET]: { flags: NO_FLAGS, positionals: 1, confirms: false }, [COMMANDS.GROUP_GRANT]: { flags: NO_FLAGS, positionals: 2, confirms: false }, [COMMANDS.GROUP_REVOKE]: { flags: NO_FLAGS, positionals: 2, confirms: false }, [COMMANDS.GROUP_ADD_USER]: { flags: NO_FLAGS, positionals: 2, confirms: false }, [COMMANDS.GROUP_REMOVE_USER]: { flags: NO_FLAGS, positionals: 2, confirms: false }, [COMMANDS.GROUP_DELETE]: { flags: YES, positionals: 1, confirms: true }, [COMMANDS.KEY_CREATE]: { flags: { user: "value", label: "value", out: "value" }, positionals: 0, confirms: false }, [COMMANDS.KEY_LIST]: { flags: { user: "value" }, positionals: 0, confirms: false }, [COMMANDS.KEY_REVOKE]: { flags: YES, positionals: 1, confirms: true } }, VERSION_META = { flags: NO_FLAGS, positionals: 0, confirms: false }, KEY_REVOKE_COMMAND = (id) => `ignitify ${COMMANDS.KEY_REVOKE} ${id}`, MESSAGES = { NOT_SIGNED_IN: "Not signed in. Run 'ignitify login', or set the IGNITIFY_API_KEY environment variable.", CREDENTIALS_SYMLINK: (file) => `${file} is a symbolic link. Remove it and run 'ignitify login' again.`, CREDENTIALS_NOT_FILE: (file) => `${file} is not a regular file. Remove it and run 'ignitify login' again.`, CREDENTIALS_NOT_OWNED: (file) => `${file} belongs to another user. Remove it and run 'ignitify login' again.`, CREDENTIALS_TOO_OPEN: (file, mode) => `${file} can be read by other users (mode 0${mode}). Run: chmod 600 ${file}`, CREDENTIALS_UNREADABLE: (file) => `${file} is not a valid credentials file. Remove it and run 'ignitify login' again.`, CREDENTIALS_OTHER_URL: (fileUrl, url) => `The saved key is for ${fileUrl}, not ${url}. Run 'ignitify login'.`, CREDENTIALS_DIR_NOT_DIR: (dir) => `${dir} is not a folder. Remove it and run 'ignitify login' again.`, API_URL_NOT_URL: (url) => `The API address "${url}" is not a URL.`, API_URL_NOT_HTTPS: (url) => `The API address ${url} is not https. The CLI sends your key only over https, or over plain http to this machine (localhost, 127.0.0.1, ::1).`, LOGIN_REPLACED: (label) => `Note: replaced the key "${label}" of another machine. Use 'ignitify login --name <other-label>' to keep both.`, LOGIN_EXPIRED: (code) => `The code ${code} expired before it was approved. Run 'ignitify login' again.`, LOGIN_REFUSED: (reason) => `The sign-in was refused: ${reason}.`, LOGIN_UNKNOWN: "The sign-in code is unknown or was already used. Run 'ignitify login' again.", LOGIN_BAD_ANSWER: "The server sent a sign-in answer with no usable key. Nothing was saved.", LOGIN_BAD_START: "The server sent a sign-in start that the CLI cannot use (no code, or no wait times). Nothing was started.", ENV_KEY_OVERRIDES_LOGIN: "Note: the IGNITIFY_API_KEY environment variable is set, so it is used instead of your saved login.", KEY_EXPIRED_HINT: "Run 'ignitify login' to sign in again.", LOGIN_TOO_MANY: "Too many sign-in attempts from this network. Try again later.", LOGIN_CAP: (limit) => `This account already has the most API keys its plan allows${limit === null ? "" : ` (${limit})`}. Remove one, or approve with "replace", then run 'ignitify login' again.`, CANNOT_REACH: (url, detail) => `Cannot reach ${url}${detail === null ? "" : ` (${detail})`}. Check your network and run 'ignitify login' again.`, UNREACHABLE: (origin, detail) => `Cannot reach ${origin}${detail === null ? "" : ` (${detail})`}.`, LOGOUT_NOTHING: "Not signed in. There are no saved credentials.", LOGOUT_ENV_NOTE: "Note: the IGNITIFY_API_KEY environment variable is still set. Logout does not change it.", LOGOUT_NOT_REVOKED: (label) => `Warning: the server did not revoke the key, so it may still work. The saved credentials were deleted. Ask your operator to revoke the key "${label}".`, SLUG_MISSING: (source) => `'slug' is required in ${source}`, SLUG_INVALID: (source, reason) => `${source}: 'slug' ${reason}`, PRESET_INVALID: "run.preset must be a non-empty string", PRESET_UNKNOWN: (source, name, valid) => `${source}: unknown run.preset "${name}" (valid: ${valid.join(", ")}). An unrecognised preset would silently fall back to the platform default, giving you a different instance class than you declared.`, TIMEOUT_INVALID: "run.timeout_sec must be a positive number", TASK_ID_REQUIRED: "a <task-id> is required", TASK_ID_INVALID: (id, reason) => `task id ${JSON.stringify(id)} ${reason}`, TASK_NOT_REGISTERED: (taskId, dirFlag) => `this folder is not registered yet (no task_id in .ignitify/state.json). Run: ignitify task:create ${taskId} --no-scaffold${dirFlag === void 0 ? "" : ` --dir ${dirFlag}`}, then deploy again.`, ERROR: (message) => `Error: ${message}`, ERROR_PREFIX: "Error:", SERVER_TRACE_ID: (id) => `server trace id: ${id}`, ARG_REQUIRED: (label) => `${label} is required`, UNKNOWN_COMMAND: "unknown command", SEE_HELP: "Run 'ignitify help' to see every command.", UNKNOWN_SHORT_FLAG: (short, long, command) => `unknown flag ${short} (--${long}) for ${command}`, GRACE_INVALID: (got) => `--grace must be a whole number of milliseconds, 0 or more (got ${JSON.stringify(got)})`, GRACE_WITH_FORCE: "--grace has no effect with --force (force stops the run now)", TASK_LIMITS: (payload, result) => `payload max ${payload}, result max ${result}; over the limit the run fails`, PAYLOAD_OVER_LIMIT: (bytes, limit) => `payload is ${bytes} bytes; the limit is ${limit} bytes`, PAYLOAD_REFUSED_BY_SERVER: (limit) => `the server refused the payload as too large; the limit is ${limit} bytes`, DEFAULT_PAYLOAD_OVER_LIMIT: (source, bytes, limit) => `${source}: run.payload is ${bytes} bytes; the offline limit is ${limit} bytes (the server may allow more)`, IGNORED_BY_FILE: `ignored by ${IGNORE_FILE}`, IGNORED_COUNT: (n) => `${n} path(s) left out by ${IGNORE_FILE}`, TASK_DELETED: "task deleted", TASK_LINK_REMOVED: "Removed local link", TASK_LINK_NOT_REMOVED: (link, reason) => `Warning: the task is deleted, but its local link was not removed (${reason}). Remove ${link} by hand.`, DEPLOY_DELETED: "deployment deleted", RUN_DELETED: "run deleted", ABORTED: "Aborted.", CONFIRM_PROCEED: "Proceed?", SECRET_SET_USAGE: "usage: ignitify secret:set <NAME> --stdin (the value is read from stdin)", SECRET_VALUE_EMPTY: "secret:set read an empty value from stdin \u2014 nothing was stored (pipe the value in: `--stdin < value-file`)", SECRET_VALUE_ON_ARGV: `secret:set takes no value on argv: it is visible in \`ps\` and is written to your shell history. Pipe it on stdin, e.g.
|
|
1358
1426
|
ignitify secret:set <NAME> --stdin < value-file
|
|
1359
1427
|
printf %s "$VALUE" | ignitify secret:set <NAME> --stdin
|
|
1360
1428
|
\u26A0\uFE0F The value you just typed is already in your history \u2014 rotate it.`, NOTIFY_SECRET_ON_ARGV: '--secret is not accepted: a value on argv is visible in `ps` and is written to your shell history. Use --secret-stdin, e.g.\n printf %s "$SECRET" | ignitify notify:set <deployment-id> --url <https://\u2026> --secret-stdin\n\u26A0\uFE0F The value you just typed is already in your history \u2014 rotate it.', HOOK_CREATE_USAGE: "usage: ignitify hook:create <deployment-id> --task-name <name>", HOOK_TASK_NAME_REQUIRED: `--task-name <name> is required: a hook starts a run of ONE task in the deployment, and a run without a task name fails.
|
|
@@ -1362,14 +1430,15 @@ usage: ignitify hook:create <deployment-id> --task-name <name>`, HOOK_LIST_USAGE
|
|
|
1362
1430
|
ignitify notify:set <deployment-id> --clear
|
|
1363
1431
|
--secret-stdin reads the signing secret (at least ${32} characters) from stdin; it is
|
|
1364
1432
|
required with --url. There is no --secret flag: a value on argv is visible in \`ps\`
|
|
1365
|
-
and lands in your shell history.`, NOTIFY_URL_NEEDS_SECRET: "--url needs --secret-stdin: the server refuses a notify target with no signing secret.", NOTIFY_SECRET_EMPTY: "--secret-stdin read an empty value from stdin.", NOTIFY_URL_WITH_CLEAR: "--url and --clear cannot be used together.", NOTIFY_SECRET_STDIN_VALUE: "--secret-stdin takes no value: pipe the secret on stdin.", HOOK_SECRET_SHOWN_ONCE: "\u26A0\uFE0F The secret below is shown ONCE and cannot be read back \u2014 hook:list never returns it. Store it now; if it is lost, delete the hook and create a new one.", HOOK_PAYLOAD_LIMIT: (limit) => `A webhook body over ${limit} is refused with 413 before the signature is checked.`, SECRET_DELETE_USAGE: "usage: ignitify secret:delete <NAME>", USAGE_EXPORT_USAGE: "usage: ignitify usage:export [--period YYYY-MM] (--json|--csv) (name exactly one format)", LATEST_DEPLOY_BUILDING: (task, deploymentId) => `the latest deploy of ${task} is still building (${deploymentId}). Wait for it ('ignitify deployment:get ${deploymentId}'), or pass --deployment <id> to run an older one.`, LATEST_DEPLOY_FAILED: (task, reason) => `the latest deploy of ${task} failed: ${reason ?? "no reason recorded"}. Fix the task and run 'ignitify task:deploy ${task}' again, or pass --deployment <id> to run an older one.`, RUN_AT_WITH_WAIT: "--wait cannot be used with --at: a scheduled run has no run id until it fires. Drop --wait; follow it later with 'ignitify schedule:list' and 'ignitify run:list'.", SECRET_STORED: (name) => `secret ${name} stored`, SECRET_REMOVED: (name) => `secret ${name} removed` }, RUN_STATUS = { PENDING: "PENDING", PROVISIONING: "PROVISIONING", RUNNING: "RUNNING", RETRYING: "RETRYING", COMPLETED: "COMPLETED", FAILED: "FAILED", CANCELLED: "CANCELLED" }, ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([RUN_STATUS.PENDING, RUN_STATUS.PROVISIONING, RUN_STATUS.RUNNING, RUN_STATUS.RETRYING]), DEPLOYMENT_STATUS = { BUILDING: "BUILDING", READY: "READY", FAILED: "FAILED", ARCHIVED: "ARCHIVED" }, POLL = { DEPLOY: { BASE_MS: 500, MAX_MS: 1e3, TIMEOUT_MS: 900 * 1e3 }, RUN: { BASE_MS: 500, MAX_MS: 3e3, TIMEOUT_MS: 1800 * 1e3 }, ERROR: { MIN_TRIES: 3, GRACE_MS: 6e4 } }, WAIT = { UPLOADING: (slug2, size) => `Uploading ${slug2}${size ? ` (${size})` : ""}`, UPLOAD_FAILED: "Upload failed", BUILDING: (deploymentId) => `Building image ${deploymentId}`, BUILT: "Image built", REUSED: "Image already built \u2014 reused", BUILD_FAILED: "Build failed", BUILD_FAILED_DETAIL: (deploymentId, reason) => `deployment ${deploymentId} failed to build \u2014 ${reason}`, BUILD_TIMEOUT: "Still building after 15 minutes \u2014 stopped waiting", BUILD_TIMEOUT_DETAIL: (deploymentId) => `deployment ${deploymentId} is still building after 15 minutes. The build has NOT been cancelled \u2014 check it with 'ignitify deployment:get ${deploymentId}'.`, RUN: (runId, status) => `Run ${runId} ${status}`, RUN_SUBMITTED: (runId) => `Run ${runId} submitted`, RUN_ENDED: (status) => `Run ${status}`, RUN_TIMEOUT: (status) => `Run is still ${status} after 30 minutes \u2014 stopped waiting`, RUN_TIMEOUT_DETAIL: (runId) => `The run has NOT been cancelled \u2014 check it with 'ignitify run:get ${runId}'.`, HINT_RETRYING: "lost contact with the server \u2014 retrying", LOST: "Lost contact with the server \u2014 stopped waiting", LOST_BUILD_DETAIL: (deploymentId, cause) => `stopped waiting for deployment ${deploymentId}: ${cause}. The build has NOT been cancelled \u2014 check it with 'ignitify deployment:get ${deploymentId}'.`, LOST_RUN_DETAIL: (runId, cause) => `stopped waiting for run ${runId}: ${cause}. The run has NOT been cancelled \u2014 check it with 'ignitify run:get ${runId}'.`, HINT_BUILD: "Ctrl+C stops waiting, not the build", HINT_RUN: "Ctrl+C stops waiting, not the run", INTERRUPTED_BUILD: (deploymentId, started) => `Stopped waiting. The build ${started ? "keeps running" : "is queued"} on the server \u2014 nothing was cancelled.
|
|
1433
|
+
and lands in your shell history.`, NOTIFY_URL_NEEDS_SECRET: "--url needs --secret-stdin: the server refuses a notify target with no signing secret.", NOTIFY_SECRET_EMPTY: "--secret-stdin read an empty value from stdin.", NOTIFY_URL_WITH_CLEAR: "--url and --clear cannot be used together.", NOTIFY_SECRET_STDIN_VALUE: "--secret-stdin takes no value: pipe the secret on stdin.", HOOK_SECRET_SHOWN_ONCE: "\u26A0\uFE0F The secret below is shown ONCE and cannot be read back \u2014 hook:list never returns it. Store it now; if it is lost, delete the hook and create a new one.", HOOK_SECRET_REDACTED: (id) => `the server answered without the secret (a replay of an earlier request), and it is not shown again. Delete ${id} with 'ignitify hook:delete ${id}' and create another to get a new secret.`, HOOK_PAYLOAD_LIMIT: (limit) => `A webhook body over ${limit} is refused with 413 before the signature is checked.`, SECRET_DELETE_USAGE: "usage: ignitify secret:delete <NAME>", USAGE_EXPORT_USAGE: "usage: ignitify usage:export [--period YYYY-MM] (--json|--csv) (name exactly one format)", DAYS_INVALID: (raw) => `--days must be a whole number of days, 1 or more (got ${JSON.stringify(raw)})`, NOTIFICATION_ONE_VIEW: "Choose --unread or --open, not both.", NOTIFICATION_MORE: (max) => `Older notifications exist. Raise --limit (at most ${max}), or open Notifications in the app.`, NOTIFICATION_MORE_IN_APP: "Older notifications exist. Open Notifications in the app to see them.", LATEST_DEPLOY_BUILDING: (task, deploymentId) => `the latest deploy of ${task} is still building (${deploymentId}). Wait for it ('ignitify deployment:get ${deploymentId}'), or pass --deployment <id> to run an older one.`, LATEST_DEPLOY_FAILED: (task, reason) => `the latest deploy of ${task} failed: ${reason ?? "no reason recorded"}. Fix the task and run 'ignitify task:deploy ${task}' again, or pass --deployment <id> to run an older one.`, RUN_AT_WITH_WAIT: "--wait cannot be used with --at: a scheduled run has no run id until it fires. Drop --wait; follow it later with 'ignitify schedule:list' and 'ignitify run:list'.", IDEMPOTENCY_KEY_INVALID: (key) => `--idempotency-key must be 8 to 255 characters of A-Z a-z 0-9 . _ : -, got: ${key}`, RUN_REPLAYED: "this is the answer to an earlier request with the same --idempotency-key; no new run was started.", IDEMPOTENCY_KEY_REUSED_HINT: "Use a new --idempotency-key for a different run. Reuse a key only to retry the same run.", IDEMPOTENCY_IN_PROGRESS_HINT: "If an earlier request with this key is still running, retry with the same key shortly.", SECRET_STORED: (name) => `secret ${name} stored`, SECRET_REMOVED: (name) => `secret ${name} removed`, ACCESS_NAME_INVALID: (kind, name) => `${kind} name ${JSON.stringify(name)} is not valid: use ${ACCESS_NAME_RULE}`, PERMISSION_UNKNOWN: (permission) => `unknown permission ${JSON.stringify(permission)}. Run 'ignitify permission:list' to see the permissions.`, PERMISSION_NOT_GRANTABLE: (marker) => `${JSON.stringify(marker)} is not a permission and cannot be granted: only owner keys manage access.`, PERMISSIONS_REQUIRED: "--permissions <p1,p2,\u2026> is required: name at least one permission, comma-separated.", KEY_USER_REQUIRED: "--user <name> is required: a key belongs to one user. Owner keys come from ignitify login.", KEY_LABEL_REQUIRED: "--label <text> is required: it tells you later where the key is used.", KEY_SHOWN_ONCE: "\u26A0\uFE0F The key below is shown ONCE and cannot be read back. Hand it over now; if it is lost, revoke it and create another.", KEY_OUT_EXISTS: (file) => `${file} already exists. key:create --out writes a new file only and never follows a link. Choose another name; no key was created.`, KEY_OUT_FAILED: (file, reason) => `cannot create ${file} (${reason}); no key was created.`, KEY_OUT_WRITE_FAILED: (file, reason, id) => `cannot write ${file} (${reason}). Key ${id} was created but its value is lost: revoke it with '${KEY_REVOKE_COMMAND(id)}' and create another.`, KEY_WRITTEN: (id, file) => `key ${id} written to ${file} (mode 0600). Hand the file over, then delete your copy.`, KEY_REDACTED: (id) => `the server answered without the key (a replay of an earlier request). Revoke ${id} with '${KEY_REVOKE_COMMAND(id)}' and create another.`, USER_CREATED: (name) => `user ${name} created`, USER_REMOVED: (name, keys) => `user ${name} removed; ${keys} key(s) revoked`, GROUP_CREATED: (name) => `group ${name} created`, GROUP_GRANTED: (name, permission) => `group ${name} now has ${permission}`, GROUP_REVOKED: (name, permission) => `group ${name} no longer has ${permission}`, GROUP_USER_ADDED: (group, user) => `user ${user} added to group ${group}`, GROUP_USER_REMOVED: (group, user) => `user ${user} removed from group ${group}`, GROUP_DELETED: (name) => `group ${name} deleted`, KEY_REVOKED: (id) => `key ${id} revoked`, ACCESS_PERMISSION_DENIED: (needed) => needed === null ? "permission denied: this key lacks the permission this command needs. Ask the owner to grant it to one of your groups." : `permission denied: this key needs the ${needed} permission. Ask the owner to grant it to one of your groups.`, ACCESS_OWNER_REQUIRED: "owner key required: only owner keys manage users, groups and keys. This is a user key.", ACCESS_REFUSED: (error, reason) => `${error} (${reason})` }, ACCESS = { OWNER: "owner", SEPARATOR: ",", USER: "user", GROUP: "group", TASKS_WRITE_NOTE: "tasks.write can delete deployments and runs too: deleting a task deletes its deployments, cancels and deletes their runs and stops their VMs. Grant it only where deployments.write and runs.write are acceptable too.", SECRETS_READ_NOTE: "deployments.write plus runs.write can read secret values in practice: deploy code that prints its environment, then run it.", HOOKS_WRITE_NOTE: "hooks.write can start runs (a hook trigger starts a run) and can send run results to any URL. Grant it only where runs.write is acceptable too." }, RUN_STATUS = { PENDING: "PENDING", PROVISIONING: "PROVISIONING", RUNNING: "RUNNING", RETRYING: "RETRYING", COMPLETED: "COMPLETED", FAILED: "FAILED", CANCELLED: "CANCELLED" }, ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([RUN_STATUS.PENDING, RUN_STATUS.PROVISIONING, RUN_STATUS.RUNNING, RUN_STATUS.RETRYING]), DEPLOYMENT_STATUS = { BUILDING: "BUILDING", READY: "READY", FAILED: "FAILED", ARCHIVED: "ARCHIVED" }, POLL = { DEPLOY: { BASE_MS: 500, MAX_MS: 1e3, TIMEOUT_MS: 900 * 1e3 }, RUN: { BASE_MS: 500, MAX_MS: 3e3, TIMEOUT_MS: 1800 * 1e3 }, ERROR: { MIN_TRIES: 3, GRACE_MS: 6e4 } }, WAIT = { UPLOADING: (slug2, size) => `Uploading ${slug2}${size ? ` (${size})` : ""}`, UPLOAD_FAILED: "Upload failed", BUILDING: (deploymentId) => `Building image ${deploymentId}`, BUILT: "Image built", REUSED: "Image already built \u2014 reused", BUILD_FAILED: "Build failed", BUILD_FAILED_DETAIL: (deploymentId, reason) => `deployment ${deploymentId} failed to build \u2014 ${reason}`, BUILD_TIMEOUT: "Still building after 15 minutes \u2014 stopped waiting", BUILD_TIMEOUT_DETAIL: (deploymentId) => `deployment ${deploymentId} is still building after 15 minutes. The build has NOT been cancelled \u2014 check it with 'ignitify deployment:get ${deploymentId}'.`, RUN: (runId, status) => `Run ${runId} ${status}`, RUN_SUBMITTED: (runId) => `Run ${runId} submitted`, RUN_ENDED: (status) => `Run ${status}`, RUN_TIMEOUT: (status) => `Run is still ${status} after 30 minutes \u2014 stopped waiting`, RUN_TIMEOUT_DETAIL: (runId) => `The run has NOT been cancelled \u2014 check it with 'ignitify run:get ${runId}'.`, HINT_RETRYING: "lost contact with the server \u2014 retrying", LOST: "Lost contact with the server \u2014 stopped waiting", LOST_BUILD_DETAIL: (deploymentId, cause) => `stopped waiting for deployment ${deploymentId}: ${cause}. The build has NOT been cancelled \u2014 check it with 'ignitify deployment:get ${deploymentId}'.`, LOST_RUN_DETAIL: (runId, cause) => `stopped waiting for run ${runId}: ${cause}. The run has NOT been cancelled \u2014 check it with 'ignitify run:get ${runId}'.`, HINT_BUILD: "Ctrl+C stops waiting, not the build", HINT_RUN: "Ctrl+C stops waiting, not the run", INTERRUPTED_BUILD: (deploymentId, started) => `Stopped waiting. The build ${started ? "keeps running" : "is queued"} on the server \u2014 nothing was cancelled.
|
|
1366
1434
|
Check it with: ignitify deployment:get ${deploymentId}`, INTERRUPTED_RUN: (runId) => `Stopped waiting. The run keeps going on the server \u2014 nothing was cancelled.
|
|
1367
|
-
Check it with: ignitify run:get ${runId}` };
|
|
1435
|
+
Check it with: ignitify run:get ${runId}` }, FOLLOW = { WAIT_SEC: 20, IDLE_MS: 2e3, RETRY_FALLBACK_MS: 2e3 }, MS_PER_SECOND = 1e3, BUSY_READ = { MAX_RETRIES: 3, NOTE: (seconds) => `server busy, retrying in ${seconds}s` }, FOLLOW_MESSAGES = { AGENT_OUTDATED: "the host is running an older agent; try again after the update, or read it without --follow or --tail", API_NO_CURSOR: "this API cannot follow output yet; run the command without --follow or --tail", TAIL_INVALID: (raw) => `--tail must be a whole number of lines, 1 or more (got ${JSON.stringify(raw)})`, INTERRUPTED: (runId) => `Stopped following. The run keeps going on the server \u2014 nothing was cancelled.
|
|
1436
|
+
Check it with: ignitify run:get ${runId}`, STILL_LIVE: (status) => `Run ${status} \u2014 still live; add --follow to watch it` };
|
|
1368
1437
|
} });
|
|
1369
1438
|
var ANSI, GLYPHS, LAYOUT, LABELS, EMPTY_LISTS, DISPLAY_CAPS, TITLES, PER_BRAIN_MARKER, PER_BRAIN_FOOTNOTE, POLICY_UNIT_NOTES, HIDDEN_FIELDS, RESULT_MESSAGES, RUN_CANCEL_OUTCOME, init_constants2 = __esm({ "src/output/constants.ts"() {
|
|
1370
1439
|
"use strict";
|
|
1371
1440
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1372
|
-
ANSI = { reset: "\x1B[0m", bold: "\x1B[1m", dim: "\x1B[2m", underline: "\x1B[4m", red: "\x1B[31m", green: "\x1B[32m", yellow: "\x1B[33m", cyan: "\x1B[36m", clearLine: "\r\x1B[2K", hideCursor: "\x1B[?25l", showCursor: "\x1B[?25h" }, GLYPHS = { unicode: { ok: "\u2713", err: "\u2717", warn: "\u26A0", bullet: "\u2022", ellipsis: "\u2026", dash: "\u2014", rule: "\u2500", box: { tl: "\u250C", tr: "\u2510", bl: "\u2514", br: "\u2518", h: "\u2500", v: "\u2502" }, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] }, ascii: { ok: "OK", err: "x", warn: "!", bullet: "*", ellipsis: "...", dash: "-", rule: "-", box: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" }, frames: ["-", "\\", "|", "/"] } }, LAYOUT = { MAX_WIDTH: 80, INDENT: " ", GAP: " ", MIN_COLUMN: 8, SPINNER_MS: 100, NO_UNICODE_TERMS: ["dumb", "linux"] }, LABELS = { NEXT_STEP: "Next Step:", SYSTEM_RESOURCES: "System Resources:", RESULT: "Result:", MORE_LINES: (n) => `\u2026 ${n} more lines (run:get --full shows all)`, BUILD_LOG: "Build log:", PLATFORM_BUILT_IMAGE: "Image: built and stored by the platform; there is nothing for you to download.", EARLIER_LINES: (n) => `\u2026 ${n} earlier lines (deployment:get <id> shows all)`, NO_RESULTS: "(No results)", EMPTY: "(empty)" }, EMPTY_LISTS = { ANY: { message: "(No results)" }, TASKS: { message: "No tasks yet.", next: "Create one with: ignitify task:create <task-id>" }, DEPLOYMENTS: { message: "No deployments yet.", next: "Build one with: ignitify task:deploy <task-id>" }, RUNS: { message: "No runs yet.", next: "Start one with: ignitify task:run <task-id>" }, VMS: { message: "No live VMs." }, SCHEDULES: { message: "No scheduled runs.", next: "Schedule one with: ignitify task:run <task-id> --at <iso>" }, HOOKS: { message: "No webhooks yet.", next: "Create one with: ignitify hook:create <deployment-id> --task-name <name>" } }, DISPLAY_CAPS = { RESULT_MAX_LINES: 40, BUILD_LOG_TAIL_LINES: 30, ERROR_MAX_CHARS: 2e3 }, TITLES = { TENANT_PROFILE: "Tenant Profile", TASK_REGISTERED: "Task Registered", SOURCE_PACK: "Source Upload (nothing built, nothing sent)", DEPLOYMENT_CREATED: "Deployment Created", RUN_SUBMITTED: "Run Submitted", RUN_DETAILS: "Run Details", DEPLOYMENT_DETAILS: "Deployment Details", VM_DETAILS: "VM Details", USAGE: "Usage & Limits", UNIT_WEIGHTS: "Units per run (by preset)", ENTITLEMENTS: "Entitlements (tier policy)", ENFORCED: "Enforced Limits (what this server applies)" }, PER_BRAIN_MARKER = "(per server)", PER_BRAIN_FOOTNOTE = " (per server) \u2014 each server applies this limit on its own, so the limit across all servers is at least this.", POLICY_UNIT_NOTES = { max_compute_seconds_month: "(run seconds, not the VM time `usage` shows as compute)" }, HIDDEN_FIELDS = { SCALAR: ["payload", "result", "slug"], LIST: ["payload", "result", "execution"] }, RESULT_MESSAGES = { TASK_GET_OFFLINE: (reason) => `Note: ${reason.replace(/\.$/, "")}. The server sections show UNKNOWN.`, TASK_CREATED_LOCAL: (lang, taskId, dir) => `Created ${lang} task ${taskId} at ${dir} (local only \u2014 not registered)`, HOOK_CREATED_TITLE: "Webhook Created", HOOK_SECRET_KEY: "secret (shown ONCE)", HOOK_DELETED: (id) => `Webhook deleted: ${id}`, NOTIFY_SET: (deploymentId, url) => `Notify URL for ${deploymentId} set to ${url}`, NOTIFY_CLEARED: (deploymentId) => `Notify URL for ${deploymentId} cleared`, SCHEDULE_CANCELLED: (id) => `Schedule cancelled: ${id}`, SCHEDULE_CANCEL_PREVIEW: (id) => `Will cancel schedule ${id} \u2014 it will never fire.`, SCHEDULE_NOT_CANCELLED: (id) => `Schedule ${id} was NOT cancelled: not found, already fired, or currently firing.`, HOOK_DELETE_PREVIEW: (id) => `Will delete webhook ${id}. A sender that posts to it is refused from then on.`, SECRET_DELETE_PREVIEW: (name) => `Will delete secret ${name}. Its value cannot be read back.`, DEPLOYMENT_DELETE_PREVIEW: (id) => `Will soft-delete deployment ${id} and all its runs (cascade).`, RUN_DELETE_PREVIEW: (id) => `Will soft-delete run ${id}.`, RUN_ALREADY_OVER: (id, status) => `Run ${id} is already ${status} \u2014 nothing to cancel.`, RUN_CANCEL_PREVIEW: (id, force) => force ? `Will cancel run ${id} immediately (force: destroys the VM even if it is pinned).` : `Will ask run ${id} to stop gracefully; it is killed if it does not finish in time.`, RUN_STOPPING: (id, grace, was) => `Run ${id} is stopping${grace}${was} \u2014 still RUNNING until it finishes or the grace expires.`, RUN_CANCELLED: (id, was) => `Run cancelled: ${id}${was}`, LOG_TRUNCATED: (lines) => `truncated \u2014 ${lines} line(s) shown; the log was cut at the size cap.`, NO_SECRETS: "(no secrets)", NO_SECRETS_NEXT: "Store one with: ignitify secret:set <NAME> --stdin" }, RUN_CANCEL_OUTCOME = { ALREADY_TERMINAL: "already-terminal", DRAINING: "draining", CANCELLED: "cancelled" };
|
|
1441
|
+
ANSI = { reset: "\x1B[0m", bold: "\x1B[1m", dim: "\x1B[2m", underline: "\x1B[4m", red: "\x1B[31m", green: "\x1B[32m", yellow: "\x1B[33m", cyan: "\x1B[36m", clearLine: "\r\x1B[2K", hideCursor: "\x1B[?25l", showCursor: "\x1B[?25h" }, GLYPHS = { unicode: { ok: "\u2713", err: "\u2717", warn: "\u26A0", bullet: "\u2022", ellipsis: "\u2026", dash: "\u2014", rule: "\u2500", box: { tl: "\u250C", tr: "\u2510", bl: "\u2514", br: "\u2518", h: "\u2500", v: "\u2502" }, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] }, ascii: { ok: "OK", err: "x", warn: "!", bullet: "*", ellipsis: "...", dash: "-", rule: "-", box: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" }, frames: ["-", "\\", "|", "/"] } }, LAYOUT = { MAX_WIDTH: 80, INDENT: " ", GAP: " ", MIN_COLUMN: 8, SPINNER_MS: 100, NO_UNICODE_TERMS: ["dumb", "linux"] }, LABELS = { NEXT_STEP: "Next Step:", SYSTEM_RESOURCES: "System Resources:", RESULT: "Result:", MORE_LINES: (n) => `\u2026 ${n} more lines (run:get --full shows all)`, BUILD_LOG: "Build log:", PLATFORM_BUILT_IMAGE: "Image: built and stored by the platform; there is nothing for you to download.", EARLIER_LINES: (n) => `\u2026 ${n} earlier lines (deployment:get <id> shows all)`, NO_RESULTS: "(No results)", EMPTY: "(empty)" }, EMPTY_LISTS = { ANY: { message: "(No results)" }, TASKS: { message: "No tasks yet.", next: "Create one with: ignitify task:create <task-id>" }, DEPLOYMENTS: { message: "No deployments yet.", next: "Build one with: ignitify task:deploy <task-id>" }, RUNS: { message: "No runs yet.", next: "Start one with: ignitify task:run <task-id>" }, VMS: { message: "No live VMs." }, SCHEDULES: { message: "No scheduled runs.", next: "Schedule one with: ignitify task:run <task-id> --at <iso>" }, HOOKS: { message: "No webhooks yet.", next: "Create one with: ignitify hook:create <deployment-id> --task-name <name>" }, USERS: { message: "No users yet.", next: "Create one with: ignitify user:create <name>" }, GROUPS: { message: "No groups yet.", next: "Create one with: ignitify group:create <name> --permissions <p1,p2>" }, KEYS: { message: "No live keys." } }, DISPLAY_CAPS = { RESULT_MAX_LINES: 40, BUILD_LOG_TAIL_LINES: 30, ERROR_MAX_CHARS: 2e3 }, TITLES = { TENANT_PROFILE: "Tenant Profile", KEY_ACCESS: "This Key", TASK_REGISTERED: "Task Registered", SOURCE_PACK: "Source Upload (nothing built, nothing sent)", DEPLOYMENT_CREATED: "Deployment Created", RUN_SUBMITTED: "Run Submitted", RUN_DETAILS: "Run Details", DEPLOYMENT_DETAILS: "Deployment Details", VM_DETAILS: "VM Details", USAGE: "Usage & Limits", UNIT_WEIGHTS: "Units per run (by preset)", ENTITLEMENTS: "Entitlements (tier policy)", ENFORCED: "Enforced Limits (what this server applies)" }, PER_BRAIN_MARKER = "(per server)", PER_BRAIN_FOOTNOTE = " (per server) \u2014 each server applies this limit on its own, so the limit across all servers is at least this.", POLICY_UNIT_NOTES = { max_compute_seconds_month: "(run seconds, not the VM time `usage` shows as compute)" }, HIDDEN_FIELDS = { SCALAR: ["payload", "result", "slug"], LIST: ["payload", "result", "execution"] }, RESULT_MESSAGES = { TASK_GET_OFFLINE: (reason) => `Note: ${reason.replace(/\.$/, "")}. The server sections show UNKNOWN.`, TASK_CREATED_LOCAL: (lang, taskId, dir) => `Created ${lang} task ${taskId} at ${dir} (local only \u2014 not registered)`, HOOK_CREATED_TITLE: "Webhook Created", HOOK_SECRET_KEY: "secret (shown ONCE)", HOOK_DELETED: (id) => `Webhook deleted: ${id}`, NOTIFY_SET: (deploymentId, url) => `Notify URL for ${deploymentId} set to ${url}`, NOTIFY_CLEARED: (deploymentId) => `Notify URL for ${deploymentId} cleared`, SCHEDULE_CANCELLED: (id) => `Schedule cancelled: ${id}`, SCHEDULE_CANCEL_PREVIEW: (id) => `Will cancel schedule ${id} \u2014 it will never fire.`, SCHEDULE_NOT_CANCELLED: (id) => `Schedule ${id} was NOT cancelled: not found, already fired, or currently firing.`, HOOK_DELETE_PREVIEW: (id) => `Will delete webhook ${id}. A sender that posts to it is refused from then on.`, SECRET_DELETE_PREVIEW: (name) => `Will delete secret ${name}. Its value cannot be read back.`, DEPLOYMENT_DELETE_PREVIEW: (id) => `Will soft-delete deployment ${id} and all its runs (cascade).`, RUN_DELETE_PREVIEW: (id) => `Will soft-delete run ${id}.`, RUN_ALREADY_OVER: (id, status) => `Run ${id} is already ${status} \u2014 nothing to cancel.`, RUN_CANCEL_PREVIEW: (id, force) => force ? `Will cancel run ${id} immediately (force: destroys the VM even if it is pinned).` : `Will ask run ${id} to stop gracefully; it is killed if it does not finish in time.`, RUN_STOPPING: (id, grace, was) => `Run ${id} is stopping${grace}${was} \u2014 still RUNNING until it finishes or the grace expires.`, RUN_CANCELLED: (id, was) => `Run cancelled: ${id}${was}`, LOG_TRUNCATED: (lines) => `truncated \u2014 ${lines} line(s) shown; the log was cut at the size cap.`, USER_REMOVE_PREVIEW: (name) => `Will remove user ${name} and revoke every key of the user. The keys stop working at once.`, GROUP_DELETE_PREVIEW: (name) => `Will delete group ${name}. Its users lose the permissions it grants; users and keys stay.`, KEY_REVOKE_PREVIEW: (id) => `Will revoke key ${id}. A program that uses it is refused from then on.`, KEY_CREATED_TITLE: "Key Created", KEY_RAW_KEY: "key (shown ONCE)", USER_KEYS_TITLE: "Keys", NONE: "-", NO_SECRETS: "(no secrets)", NO_SECRETS_NEXT: "Store one with: ignitify secret:set <NAME> --stdin" }, RUN_CANCEL_OUTCOME = { ALREADY_TERMINAL: "already-terminal", DRAINING: "draining", CANCELLED: "cancelled" };
|
|
1373
1442
|
} });
|
|
1374
1443
|
function makeStyle(o) {
|
|
1375
1444
|
let p = (...codes) => paint(o.colour, ...codes), columns = Math.max(o.width, 1);
|
|
@@ -1399,15 +1468,15 @@ function paintStatus(st, status) {
|
|
|
1399
1468
|
let s = String(status).toUpperCase(), tone = STATUS_TONE[s];
|
|
1400
1469
|
return tone === "ok" ? st.green(s) : tone === "busy" ? st.yellow(s) : tone === "bad" ? st.red(s) : st.dim(s);
|
|
1401
1470
|
}
|
|
1402
|
-
var paint, PLAIN, resolveStyle, plain, plainLines,
|
|
1471
|
+
var paint, PLAIN, resolveStyle, plain, plainLines, SGR, isColour, terminalSafe, stripAnsi, visibleLength, padVisible, cut, STATUS_TONE, init_style = __esm({ "src/output/style.ts"() {
|
|
1403
1472
|
"use strict";
|
|
1404
1473
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1474
|
+
init_tenant();
|
|
1405
1475
|
init_constants2();
|
|
1406
1476
|
paint = (on, ...codes) => (s) => on && s !== "" ? `${codes.join("")}${s}${ANSI.reset}` : s;
|
|
1407
1477
|
PLAIN = makeStyle({ colour: false, unicode: true, width: LAYOUT.MAX_WIDTH });
|
|
1408
1478
|
resolveStyle = (f) => makeStyle({ colour: wantsColour(f), unicode: wantsUnicode(f), width: f.columns !== void 0 && f.columns > 0 ? f.columns : LAYOUT.MAX_WIDTH });
|
|
1409
|
-
plain = (s) => s.replace(/[\u0000-\u001f\u007f-\u009f]/g, ""), plainLines = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, ""),
|
|
1410
|
-
` : ""), stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, ""), visibleLength = (s) => stripAnsi(s).length, padVisible = (s, width) => s + " ".repeat(Math.max(0, width - visibleLength(s))), cut = (s, max, ellipsis) => s.length <= max || max <= ellipsis.length ? s : `${s.slice(0, max - ellipsis.length)}${ellipsis}`;
|
|
1479
|
+
plain = (s) => s.replace(/[\u0000-\u001f\u007f-\u009f]/g, ""), plainLines = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, ""), SGR = /^\x1b\[[0-9;:]*m$/, isColour = (unit) => SGR.test(unit), terminalSafe = (s, keepColour) => keepColour ? stripControl(s, isColour) : stripControl(s), stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, ""), visibleLength = (s) => stripAnsi(s).length, padVisible = (s, width) => s + " ".repeat(Math.max(0, width - visibleLength(s))), cut = (s, max, ellipsis) => s.length <= max || max <= ellipsis.length ? s : `${s.slice(0, max - ellipsis.length)}${ellipsis}`;
|
|
1411
1480
|
STATUS_TONE = { COMPLETED: "ok", READY: "ok", ACTIVE: "ok", DONE: "ok", OK: "ok", PENDING: "busy", PROVISIONING: "busy", RUNNING: "busy", RETRYING: "busy", BUILDING: "busy", FAILED: "bad" };
|
|
1412
1481
|
} });
|
|
1413
1482
|
function cellText(v) {
|
|
@@ -1501,6 +1570,94 @@ var heading, successLine, errorMark, warnMark, noteLine, emptyState, init_blocks
|
|
|
1501
1570
|
heading = (st, title, ok2 = false) => ok2 ? st.bold(st.green(`${st.glyphs.ok} ${title}`)) : st.bold(title), successLine = (st, text) => `${st.green(st.glyphs.ok)} ${text}`, errorMark = (st, text) => `${st.red(st.glyphs.err)} ${text}`, warnMark = (st, text) => `${st.yellow(st.glyphs.warn)} ${text}`, noteLine = (st, text) => st.yellow(text), emptyState = (st, message, next) => next === void 0 ? st.dim(message) : `${st.dim(message)}
|
|
1502
1571
|
${st.dim(next)}`;
|
|
1503
1572
|
} });
|
|
1573
|
+
function checkAccessName(kind, name) {
|
|
1574
|
+
return ACCESS_NAME_PATTERN.test(name) ? { ok: true, value: name } : { ok: false, message: MESSAGES.ACCESS_NAME_INVALID(kind, name) };
|
|
1575
|
+
}
|
|
1576
|
+
function checkPermission(permission) {
|
|
1577
|
+
return isPermission(permission) ? { ok: true, value: permission } : { ok: false, message: MARKERS.includes(permission) ? MESSAGES.PERMISSION_NOT_GRANTABLE(permission) : MESSAGES.PERMISSION_UNKNOWN(permission) };
|
|
1578
|
+
}
|
|
1579
|
+
function parsePermissions(raw) {
|
|
1580
|
+
let names = (raw ?? "").split(ACCESS.SEPARATOR).map((p) => p.trim()).filter((p) => p !== "");
|
|
1581
|
+
if (names.length === 0) return { ok: false, message: MESSAGES.PERMISSIONS_REQUIRED };
|
|
1582
|
+
let out2 = [];
|
|
1583
|
+
for (let name of names) {
|
|
1584
|
+
let checked = checkPermission(name);
|
|
1585
|
+
if (!checked.ok) return checked;
|
|
1586
|
+
out2.includes(checked.value) || out2.push(checked.value);
|
|
1587
|
+
}
|
|
1588
|
+
return { ok: true, value: out2 };
|
|
1589
|
+
}
|
|
1590
|
+
function permissionNote(permission) {
|
|
1591
|
+
if (permission === PERMISSION.TASKS_WRITE) return ACCESS.TASKS_WRITE_NOTE;
|
|
1592
|
+
if (permission === PERMISSION.SECRETS_READ) return ACCESS.SECRETS_READ_NOTE;
|
|
1593
|
+
if (permission === PERMISSION.HOOKS_WRITE) return ACCESS.HOOKS_WRITE_NOTE;
|
|
1594
|
+
}
|
|
1595
|
+
function permissionRows() {
|
|
1596
|
+
return CATALOGUE.map((permission) => {
|
|
1597
|
+
let note = permissionNote(permission);
|
|
1598
|
+
return { permission, description: PERMISSION_DESCRIPTION[permission], ...note === void 0 ? {} : { note } };
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
function accessRefusalText(e) {
|
|
1602
|
+
let body = httpErrorJson(e)?.body, reason = body?.reason;
|
|
1603
|
+
if (typeof reason != "string" || !REFUSALS.includes(reason)) return null;
|
|
1604
|
+
if (reason === ACCESS_REFUSAL.PERMISSION_DENIED) {
|
|
1605
|
+
let needed = body?.needed;
|
|
1606
|
+
return MESSAGES.ACCESS_PERMISSION_DENIED(typeof needed == "string" && needed !== "" ? needed : null);
|
|
1607
|
+
}
|
|
1608
|
+
if (reason === ACCESS_REFUSAL.OWNER_REQUIRED) return MESSAGES.ACCESS_OWNER_REQUIRED;
|
|
1609
|
+
let error = body?.error;
|
|
1610
|
+
return MESSAGES.ACCESS_REFUSED(typeof error == "string" && error !== "" ? error : reason, reason);
|
|
1611
|
+
}
|
|
1612
|
+
var CATALOGUE, MARKERS, REFUSALS, keyUser, init_access = __esm({ "src/core/access.ts"() {
|
|
1613
|
+
"use strict";
|
|
1614
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1615
|
+
init_http_client();
|
|
1616
|
+
init_tenant();
|
|
1617
|
+
init_constants();
|
|
1618
|
+
CATALOGUE = Object.values(PERMISSION), MARKERS = Object.values(ACCESS_MARKER), REFUSALS = Object.values(ACCESS_REFUSAL);
|
|
1619
|
+
keyUser = (user) => user ?? ACCESS.OWNER;
|
|
1620
|
+
} });
|
|
1621
|
+
function renderPermissions(st, rows) {
|
|
1622
|
+
return renderTable(st, [{ header: "permission", keep: true }, { header: "allows", wrap: true }], rows.map((r) => [r.permission, r.note === void 0 ? r.description : `${r.description}. Note: ${r.note}`]));
|
|
1623
|
+
}
|
|
1624
|
+
function renderUsers(st, users) {
|
|
1625
|
+
return users.length === 0 ? emptyState(st, EMPTY_LISTS.USERS.message, EMPTY_LISTS.USERS.next) : renderTable(st, [{ header: "name", keep: true }, { header: "resource_id", keep: true }, { header: "created_at", keep: true }], users.map((u) => [u.name, u.resource_id, u.created_at]));
|
|
1626
|
+
}
|
|
1627
|
+
function renderKeys(st, keys) {
|
|
1628
|
+
return keys.length === 0 ? emptyState(st, EMPTY_LISTS.KEYS.message) : renderTable(st, [{ header: "resource_id", keep: true }, { header: "user", keep: true }, { header: "label" }, { header: "created_at", keep: true }, { header: "last_used_at", keep: true }], keys.map((k) => [k.resource_id, keyUser(k.user), k.label, k.created_at, k.last_used_at]));
|
|
1629
|
+
}
|
|
1630
|
+
function renderUser(st, user) {
|
|
1631
|
+
return `${renderFields(st, { name: user.name, resource_id: user.resource_id, created_at: user.created_at, groups: joined(user.groups), permissions: joined(user.permissions) })}
|
|
1632
|
+
|
|
1633
|
+
${heading(st, RESULT_MESSAGES.USER_KEYS_TITLE)}
|
|
1634
|
+
${renderKeys(st, user.keys)}`;
|
|
1635
|
+
}
|
|
1636
|
+
function renderGroups(st, groups) {
|
|
1637
|
+
return groups.length === 0 ? emptyState(st, EMPTY_LISTS.GROUPS.message, EMPTY_LISTS.GROUPS.next) : renderTable(st, [{ header: "name", keep: true }, { header: "permissions", wrap: true }, { header: "created_at", keep: true }], groups.map((g) => [g.name, joined(g.permissions), g.created_at]));
|
|
1638
|
+
}
|
|
1639
|
+
function renderGroup(st, group) {
|
|
1640
|
+
return renderFields(st, { name: group.name, resource_id: group.resource_id, created_at: group.created_at, permissions: joined(group.permissions), users: joined(group.users) });
|
|
1641
|
+
}
|
|
1642
|
+
function renderKeyAccess(st, access) {
|
|
1643
|
+
return renderFields(st, { key: access.key, user: keyUser(access.user), groups: joined(access.groups), permissions: joined(access.permissions) });
|
|
1644
|
+
}
|
|
1645
|
+
function renderKeyCreated(st, created) {
|
|
1646
|
+
let { raw_key, secret_redacted: _redacted, ...rest } = created, rows = { ...rest, [RESULT_MESSAGES.KEY_RAW_KEY]: raw_key }, paint2 = { [RESULT_MESSAGES.KEY_RAW_KEY]: (s) => st.bold(st.yellow(s)) };
|
|
1647
|
+
return `
|
|
1648
|
+
${heading(st, RESULT_MESSAGES.KEY_CREATED_TITLE, true)}
|
|
1649
|
+
${renderFields(st, rows, void 0, paint2)}
|
|
1650
|
+
`;
|
|
1651
|
+
}
|
|
1652
|
+
var joined, init_access2 = __esm({ "src/output/access.ts"() {
|
|
1653
|
+
"use strict";
|
|
1654
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
1655
|
+
init_access();
|
|
1656
|
+
init_blocks();
|
|
1657
|
+
init_constants2();
|
|
1658
|
+
init_table();
|
|
1659
|
+
joined = (items) => items.length === 0 ? RESULT_MESSAGES.NONE : items.join(", ");
|
|
1660
|
+
} });
|
|
1504
1661
|
function isStorageMib(value) {
|
|
1505
1662
|
return typeof value == "number" && Number.isInteger(value) && value > 0;
|
|
1506
1663
|
}
|
|
@@ -1636,6 +1793,7 @@ var sectionHeader, layout, filterScalars, filterDisplayKeys, BYTE_UNITS2, format
|
|
|
1636
1793
|
init_table();
|
|
1637
1794
|
init_blocks();
|
|
1638
1795
|
init_style();
|
|
1796
|
+
init_access2();
|
|
1639
1797
|
init_constants2();
|
|
1640
1798
|
init_tenant();
|
|
1641
1799
|
init_execution();
|
|
@@ -1652,10 +1810,10 @@ ${heading(st, title, success)}
|
|
|
1652
1810
|
return `${exp === 0 ? value : value.toFixed(1)} ${BYTE_UNITS2[exp]}`;
|
|
1653
1811
|
}, quotaLine = (used, max) => `${used} / ${max ?? "unlimited"}`, storeLine = (used, max) => max === void 0 ? formatBytes(used) : `${formatBytes(used)} / ${max === null ? "unlimited" : formatBytes(max)}`, renderWhoami = (data, st = PLAIN) => {
|
|
1654
1812
|
if (!data) return "";
|
|
1655
|
-
let { limits, usage, tier, policy, bound_by, status_reason, policy_scope, enforced, unit_weights, capacity, ...identity } = data, identityRows = { ...identity };
|
|
1813
|
+
let { limits, usage, tier, policy, bound_by, status_reason, policy_scope, enforced, unit_weights, capacity, access, ...identity } = data, identityRows = { ...identity };
|
|
1656
1814
|
typeof status_reason == "string" && status_reason !== "" && (identityRows.status_reason = status_reason), tier && (identityRows.tier = `${tier.name} v${tier.version}`), bound_by && (identityRows.bound_by = bound_by);
|
|
1657
1815
|
let out2 = layout(sectionHeader(st, TITLES.TENANT_PROFILE), renderFields(st, filterDisplayKeys(identityRows)));
|
|
1658
|
-
return (limits || usage) && (out2 += renderUsageBlocks(st, limits, usage)), out2 += capacityBlock(st, capacity), hasUnits(limits) && unit_weights && (out2 += renderUnitWeights(st, unit_weights)), policy && (out2 += renderPolicyBlock(st, policy, policy_scope?.per_brain ?? [])), enforced && enforced.length > 0 && (out2 += renderEnforcedBlock(st, enforced)), out2;
|
|
1816
|
+
return access && (out2 += layout(sectionHeader(st, TITLES.KEY_ACCESS), renderKeyAccess(st, access))), (limits || usage) && (out2 += renderUsageBlocks(st, limits, usage)), out2 += capacityBlock(st, capacity), hasUnits(limits) && unit_weights && (out2 += renderUnitWeights(st, unit_weights)), policy && (out2 += renderPolicyBlock(st, policy, policy_scope?.per_brain ?? [])), enforced && enforced.length > 0 && (out2 += renderEnforcedBlock(st, enforced)), out2;
|
|
1659
1817
|
}, hasUnits = (limits) => typeof limits?.max_units == "number", renderUnitWeights = (st, weights) => layout(sectionHeader(st, TITLES.UNIT_WEIGHTS), renderFields(st, Object.fromEntries(Object.entries(weights).map(([p, w]) => [p, String(w)])))), showEnforced = (v) => v === null ? "\u2014" : String(v), renderEnforcedBlock = (st, rows) => {
|
|
1660
1818
|
let enforcedText = (r) => {
|
|
1661
1819
|
let value = showEnforced(r.enforced);
|
|
@@ -1879,7 +2037,7 @@ ${notice()}
|
|
|
1879
2037
|
process.removeListener("SIGINT", handler);
|
|
1880
2038
|
}
|
|
1881
2039
|
}
|
|
1882
|
-
var spinnerGlyph, realEvery, NOOP, init_progress = __esm({ "src/output/progress.ts"() {
|
|
2040
|
+
var spinnerGlyph, realEvery, NOOP, NO_PROGRESS, init_progress = __esm({ "src/output/progress.ts"() {
|
|
1883
2041
|
"use strict";
|
|
1884
2042
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1885
2043
|
init_constants2();
|
|
@@ -1895,13 +2053,15 @@ var spinnerGlyph, realEvery, NOOP, init_progress = __esm({ "src/output/progress.
|
|
|
1895
2053
|
}, fail() {
|
|
1896
2054
|
}, stop() {
|
|
1897
2055
|
} };
|
|
2056
|
+
NO_PROGRESS = { stop: () => {
|
|
2057
|
+
} };
|
|
1898
2058
|
} });
|
|
1899
|
-
var AXES, DIR2, YES2, RAW2, PAGE2, VERSION_HELP, COMMAND_HELP, HELP_PAGE, init_help = __esm({ "src/constants/help.ts"() {
|
|
2059
|
+
var OWNER_ONLY, PERMISSION_NOTES, AXES, DIR2, YES2, RAW2, SERIES_DAYS, FAILURES_DAYS, PAGE2, VERSION_HELP, COMMAND_HELP, HELP_PAGE, init_help = __esm({ "src/constants/help.ts"() {
|
|
1900
2060
|
"use strict";
|
|
1901
2061
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
1902
2062
|
init_tenant();
|
|
1903
2063
|
init_constants();
|
|
1904
|
-
AXES = USAGE_AXES.join("|"), DIR2 = { dir: { text: "Folder that holds your task folders.", default: `$${ENV.TASKS_DIR}, else ${DEFAULT_TASKS_DIR}` } }, YES2 = { yes: { text: "Skip the question (needed when not interactive)." } }, RAW2 = { raw: { text: "Print the bytes exactly as received. Without it, the colours are kept on a colour terminal and every other control code is removed." } }, PAGE2 = { limit: { text: "Rows on the page. The server sets the default and the largest value." }, "after-id": { text: "Page cursor: the id of the last row of the page before." }, after: { text: "Only rows after this ISO 8601 time." } }, VERSION_HELP = { summary: "Print the client version, and the API version if the server answers.", usage: ["ignitify --version"], notes: ["Needs no key. Warns when this client is older than the API supports."] }, COMMAND_HELP = { [COMMANDS.HELP]: { summary: "Print the usage of every command, or of one command.", usage: ["ignitify help [<command>]"], args: { command: "A command name. Leave it out to print every command." }, notes: ["`ignitify <command> --help` prints the same page."], examples: ["ignitify help task:run"] }, [COMMANDS.LOGIN]: { summary: "Sign this machine in and save its API key.", usage: ["ignitify login [--name <label>] [--no-browser]"], flags: { name: { text: "A label for this machine's key; keeps two machines with one hostname apart.", default: "the hostname" }, "no-browser": { text: "Print the sign-in page, but do not open it." } }, notes: ["The key is saved in ~/.config/ignitify/credentials.json (mode 0600)."], examples: ["ignitify login", "ignitify login --name build-server --no-browser"] }, [COMMANDS.LOGOUT]: { summary: "Revoke this machine's key and delete the saved file.", usage: ["ignitify logout"] }, [COMMANDS.WHOAMI]: { summary: "Show who your key belongs to, your limits and usage.", usage: ["ignitify whoami"] }, [COMMANDS.QUOTA]: { summary: "Show what you hold now against your limits.", usage: ["ignitify quota"] }, [COMMANDS.TASK_CREATE]: { summary: "Register a task name and write its starter files.", usage: ["ignitify task:create <task-id> [--lang node|python|go|rust] [--local] [--no-init] [--no-scaffold] [--dir <base-dir>]"], args: { "task-id": "The task name: also the folder name and the slug in task.yaml." }, flags: { lang: { text: "Language of the starter files.", default: "node" }, local: { text: "Write the files only. Calls no server and needs no key." }, "no-init": { text: "Write the short starter that has only run(), without init code." }, "no-scaffold": { text: "Register the task name but write no files." }, ...DIR2 }, examples: ["ignitify task:create image-resize", "ignitify task:create image-resize --lang python --local"] }, [COMMANDS.TASK_LIST]: { summary: "List your tasks, one page at a time.", usage: ["ignitify task:list [--limit N] [--after-id <id>] [--after <iso>]"], flags: PAGE2 }, [COMMANDS.TASK_GET]: { summary: "Report a task's deploys, runs and next-step verdicts.", usage: ["ignitify task:get <task-id> [--dir <base-dir>]"], args: { "task-id": "The task folder name." }, flags: DIR2, notes: ["Shows whether the next deploy builds a new image and the next run boots a new VM, with the reasons."] }, [COMMANDS.TASK_VALIDATE]: { summary: "Check task.yaml on your machine; builds nothing.", usage: ["ignitify task:validate <task-id> [--dir <base-dir>] [--review-packages]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, "review-packages": { text: "Then ask the server for an AI review of build.system_packages." } }, notes: ["The review is advice only: the exit code never changes.", "Only package names and the runtime id leave this machine; never code, slug or secrets.", "It also checks that task.yaml still covers each @ignitify-integrations/* package in package.json. A listed package that is not in node_modules stops the check: run npm install first.", "With integrations installed, it sends their package names to the public npm registry (registry.npmjs.org) to find obsolete versions: a warning only. With no network, that warning is skipped with a note."] }, [COMMANDS.TASK_PACK]: { summary: "List the files task:deploy would upload; builds nothing.", usage: ["ignitify task:pack <task-id> [--dir <base-dir>]"], args: { "task-id": "The task folder name." }, flags: DIR2 }, [COMMANDS.TASK_DEPLOY]: { summary: "Upload the task source and wait until the image is built.", usage: ["ignitify task:deploy <task-id> [--dir <base-dir>] [--check]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, check: { text: "Run every local check, then stop. Nothing is uploaded." } }, notes: ["Ctrl+C stops waiting, not the build."], examples: ["ignitify task:deploy image-resize", "ignitify task:deploy image-resize --check"] }, [COMMANDS.TASK_RUN]: { summary: "Start one run of a task from its latest deployment.", usage: ["ignitify task:run <task-id> [--dir <base-dir>] [--deployment <id>] [--payload <json>] [--timeout-sec <n>] [--on-busy queue|reject] [--at <iso>] [--wait]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, deployment: { text: "Run this deployment instead of the task's latest deploy.", default: "the task's latest deploy" }, payload: { text: "JSON input for this run only.", default: "run.payload from task.yaml" }, "timeout-sec": { text: "Time limit in seconds for this run only.", default: "run.timeout_sec from task.yaml" }, "on-busy": { text: "When the run cannot start at once: queue waits for room (a limited time), reject fails at once.", default: "queue" }, at: { text: "Schedule the run for this ISO 8601 time. No run exists until it fires." }, wait: { text: "Follow the run to its end, then print it with its result." } }, notes: ["With --wait the exit code is 0 only when the run is COMPLETED. Ctrl+C stops waiting, not the run.", "--payload and --timeout-sec apply to this run only; task.yaml is never written.", "A bidirectional task runs one VM at a time; --on-busy covers that case too."], examples: ["ignitify task:run image-resize --wait", `ignitify task:run image-resize --payload '{"width":320}'`, "ignitify task:run image-resize --at 2027-01-15T03:00:00Z"] }, [COMMANDS.TASK_DELETE]: { summary: "Delete a task with all its deployments and runs.", usage: ["ignitify task:delete <task-id> [--dir <base-dir>] [--yes] [--json]"], args: { "task-id": "The task folder name, or the task id." }, flags: { ...DIR2, ...YES2, json: { text: "Print the plan and the result as one JSON document." } }, notes: ["Also removes the .ignitify/ link of every local folder linked to it; your files are kept."] }, [COMMANDS.DEPLOYMENT_LIST]: { summary: "List your deployments with the size of each image.", usage: ["ignitify deployment:list [--limit N] [--after-id <id>] [--after <iso>]"], flags: PAGE2 }, [COMMANDS.DEPLOYMENT_GET]: { summary: "Show one deployment: status, size, integrity.", usage: ["ignitify deployment:get <deployment-id>"], args: { "deployment-id": "The deployment id, from task:deploy or deployment:list." } }, [COMMANDS.DEPLOYMENT_LOGS]: { summary: "Print the build log of a deployment, as the build wrote it.", usage: ["ignitify deployment:logs <deployment-id> [--raw]"], args: { "deployment-id": "The deployment id." }, flags: RAW2, notes: ["The output goes to stdout; a truncated answer says so on stderr."] }, [COMMANDS.DEPLOYMENT_DELETE]: { summary: "Delete a deployment together with its runs.", usage: ["ignitify deployment:delete <deployment-id> [--yes]"], args: { "deployment-id": "The deployment id." }, flags: YES2 }, [COMMANDS.RUN_LIST]: { summary: "List your runs, with filters.", usage: ["ignitify run:list [--status S] [--deployment-id D] [--task-name N] [--preset P] [--correlation-id C] [--vm-id V] [--limit N] [--after-id <id>] [--after <iso>]"], flags: { status: { text: "Only runs in this status, for example FAILED." }, "deployment-id": { text: "Only runs of this deployment." }, "task-name": { text: "Only runs of this task." }, preset: { text: "Only runs on this instance class." }, "correlation-id": { text: "Only runs with this correlation id." }, "vm-id": { text: "Only runs that used this VM." }, ...PAGE2 } }, [COMMANDS.RUN_GET]: { summary: "Show one run: status, timing and the result of the task.", usage: ["ignitify run:get <run-id> [--full]"], args: { "run-id": "The run id, from task:run or run:list." }, flags: { full: { text: "Print the whole run as JSON, every field the API returns." } } }, [COMMANDS.RUN_LOGS]: { summary: "Print the console output of a run, as the task wrote it.", usage: ["ignitify run:logs <run-id> [--raw]"], args: { "run-id": "The run id." }, flags: RAW2, notes: ["The output goes to stdout; a truncated answer says so on stderr."] }, [COMMANDS.RUN_DELETE]: { summary: "Delete the record of a run; does not stop a live run.", usage: ["ignitify run:delete <run-id> [--yes]"], args: { "run-id": "The run id." }, flags: YES2 }, [COMMANDS.RUN_CANCEL]: { summary: "Stop a live run, gracefully by default.", usage: ["ignitify run:cancel <run-id> [--yes] [--force] [--grace <ms>]"], args: { "run-id": "The run id." }, flags: { ...YES2, force: { text: "Kill the run now. Also needed when the run is on a pinned VM." }, grace: { text: "Ask for a longer wind-down, in milliseconds. The server limits it." } } }, [COMMANDS.VM_LIST]: { summary: "List your live VMs with their state, age and idle time.", usage: ["ignitify vm:list [--pinned] [--deployment-id D] [--task-name N] [--preset P] [--older-than 24h] [--limit N] [--after-id <vm-id>] [--after <iso>]"], flags: { pinned: { text: "Only pinned VMs: they stay up until you delete them." }, "deployment-id": { text: "Only VMs of this deployment." }, "task-name": { text: "Only VMs of this task." }, preset: { text: "Only VMs of this instance class." }, "older-than": { text: "Only VMs older than this age, for example 24h." }, limit: PAGE2.limit, "after-id": { text: "Page cursor: the VM id of the last row of the page before." }, after: { text: "Only VMs booted after this ISO 8601 time." } } }, [COMMANDS.VM_GET]: { summary: "Show one VM: residency, state, age and its task nature.", usage: ["ignitify vm:get <vm-id>"], args: { "vm-id": "The VM id, from vm:list." } }, [COMMANDS.VM_DELETE]: { summary: "Tear down a VM by hand.", usage: ["ignitify vm:delete <vm-id> [--yes] [--force]"], args: { "vm-id": "The VM id." }, flags: { ...YES2, force: { text: "Allow the delete of a pinned VM. It does not skip the question." } } }, [COMMANDS.SECRET_SET]: { summary: "Store a secret value under a name, read from stdin.", usage: ["ignitify secret:set <NAME> --stdin"], args: { NAME: "The secret name. List it in `secrets` in task.yaml to use it." }, flags: { stdin: { text: "Read the value from stdin. A value on the command line is refused." } }, examples: ['printf %s "$VALUE" | ignitify secret:set DB_PASSWORD --stdin'] }, [COMMANDS.SECRET_LIST]: { summary: "List the names of your secrets.", usage: ["ignitify secret:list"] }, [COMMANDS.SECRET_DELETE]: { summary: "Delete a secret.", usage: ["ignitify secret:delete <NAME> [--yes]"], args: { NAME: "The secret name." }, flags: YES2 }, [COMMANDS.INTEGRATION_ADD]: { summary: "Add an integration package to a Node task and write its settings into task.yaml.", usage: ["ignitify integration:add <name> --task <slug> [--yes | --dry-run] [--dir <base-dir>]"], args: { name: "The integration name. The npm package is @ignitify-integrations/<name>." }, flags: { task: { text: "The task folder name to add the integration to." }, yes: { text: "Apply the plan without the question (needed when not interactive)." }, "dry-run": { text: "Show the plan, then stop. Nothing in the task is changed." }, ...DIR2 }, notes: ["It downloads the latest @ignitify-integrations/<name> with npm pack into a temporary folder and reads the plan from that file. Nothing in the task changes before you answer y.", "The plan shows every task.yaml change, then what you add yourself: secret values and hosts that differ per task.", "After y (or with --yes), it runs npm install @ignitify-integrations/<name>@<that version> in the task folder (a normal dependency, with --ignore-scripts as the build uses), then writes task.yaml. If the install fails, task.yaml is not written.", "An N, --dry-run or a refusal changes nothing: no package.json, lockfile, node_modules or task.yaml change.", "A setting that conflicts with task.yaml, or more egress hosts than the platform allows, is refused and nothing is written.", "Adding the same integration again changes nothing. To upgrade, add it again. To remove it, run npm uninstall; the settings it added stay yours to delete."], examples: ["ignitify integration:add s3-storage --task launch-event", "ignitify integration:add stream --task launch-event --dry-run"] }, [COMMANDS.SCHEDULE_LIST]: { summary: "List runs that wait for a time, soonest first.", usage: ["ignitify schedule:list"] }, [COMMANDS.SCHEDULE_CANCEL]: { summary: "Cancel one scheduled run so that it never fires.", usage: ["ignitify schedule:cancel <sch-id> [--yes]"], args: { "sch-id": "The schedule id, from task:run --at or schedule:list." }, flags: YES2, notes: ['"0 rows" means it is already firing or fired.'] }, [COMMANDS.HOOK_CREATE]: { summary: "Create a webhook that starts a run of one task.", usage: ["ignitify hook:create <deployment-id> --task-name <name>"], args: { "deployment-id": "The deployment the webhook belongs to." }, flags: { "task-name": { text: "The task that each run starts. Required." } }, notes: ["The reply carries a `secret` shown ONCE: hook:list never returns it."] }, [COMMANDS.HOOK_LIST]: { summary: "List the webhooks of a deployment.", usage: ["ignitify hook:list <deployment-id> [--limit N] [--after-id <id>] [--after <iso>]"], args: { "deployment-id": "The deployment id." }, flags: PAGE2 }, [COMMANDS.HOOK_DELETE]: { summary: "Delete a webhook.", usage: ["ignitify hook:delete <hook-resource-id> [--yes]"], args: { "hook-resource-id": "The HOOK's id (hok_\u2026), not the deployment's." }, flags: YES2 }, [COMMANDS.NOTIFY_SET]: { summary: "Set or clear the callback URL of a deployment.", usage: ["ignitify notify:set <deployment-id> --url <https://\u2026> --secret-stdin", "ignitify notify:set <deployment-id> --clear"], args: { "deployment-id": "The deployment id." }, flags: { url: { text: "The HTTPS address to call. Needs --secret-stdin." }, "secret-stdin": { text: "Read the signing secret from stdin. Needed with --url." }, clear: { text: "Remove the callback URL. Cannot be used with --url." } }, notes: ["There is no --secret flag: a value on argv is in `ps` and in your shell history."] }, [COMMANDS.USAGE]: { summary: "Show what you used in one month, by axis.", usage: [`ignitify usage [--period YYYY-MM] [--axis ${AXES}] [--vm <vm-id>] [--run <run-id>] [--runs|--vms] [--json]`], flags: { period: { text: "The month to show, as YYYY-MM.", default: "this month" }, axis: { text: "Show one axis only." }, vm: { text: "Only the usage of this VM." }, run: { text: "Only the usage of this run." }, runs: { text: "Add the lines behind the totals, one per run." }, vms: { text: "Add the lines behind the totals, one per VM." }, json: { text: "Print the report as JSON." } } }, [COMMANDS.USAGE_EXPORT]: { summary: "Export the usage lines of a month as JSON or CSV.", usage: [`ignitify usage:export [--period YYYY-MM] [--axis ${AXES}] [--vm <vm-id>] [--run <run-id>] (--json|--csv)`], flags: { period: { text: "The month to export, as YYYY-MM.", default: "this month" }, axis: { text: "Export one axis only." }, vm: { text: "Only the lines of this VM." }, run: { text: "Only the lines of this run." }, json: { text: "Print the lines as a JSON array." }, csv: { text: "Print the lines as CSV." } }, notes: ["Name exactly one format: --json or --csv."] } }, HELP_PAGE = { TITLE: "ignitify", TAGLINE: "create, deploy and run tasks", USAGE: "ignitify <command> [arguments] [flags]", USAGE_NOTE: "Every command is <resource>:<verb>.", QUICK_START: [{ command: "ignitify login", text: "Sign this machine in." }, { command: "ignitify task:create my-task", text: "Register a task and write its files." }, { command: "ignitify task:deploy my-task", text: "Build it on the platform." }, { command: "ignitify task:run my-task --wait", text: "Run it and print the result." }], GROUPS: [{ title: "Tasks", resources: ["task"] }, { title: "Deployments", resources: ["deployment"] }, { title: "Runs", resources: ["run"] }, { title: "VMs", resources: ["vm"] }, { title: "Secrets", resources: ["secret"] }, { title: "Integrations", resources: ["integration"] }, { title: "Schedules", resources: ["schedule"] }, { title: "Webhooks", resources: ["hook", "notify"] }, { title: "Account and usage", resources: ["login", "logout", "whoami", "quota", "usage", "help"] }], GLOBAL_FLAGS: [{ flag: "--json", text: "Print the result as one JSON document on stdout." }, { flag: "-h, --help", text: "Print help for the command." }, { flag: "-y, --yes", text: "Answer yes to a delete's question (for scripts)." }, { flag: "-v, --version", text: "Print the client and API version (first word only)." }], ENV: [{ name: ENV.KEY, text: "Your API key, for CI. Wins over the saved login." }, { name: ENV.TASKS_DIR, text: `Folder that holds your task folders (default: ${DEFAULT_TASKS_DIR}).` }, { name: "NO_COLOR", text: "Set to any value to turn colour off." }], NOTES: ["Delete commands always ask first; --force unlocks a refusal, it does not skip the question.", "A flag or argument a command does not take is refused, not ignored.", "On error the CLI prints a clean message and exits non-zero."], DOCS: "https://ignitify.dev/docs", MORE: "Run 'ignitify <command> --help' for the flags of one command.", H: { USAGE: "Usage", QUICK_START: "Quick start", GLOBAL_FLAGS: "Global flags", ENV: "Environment", NOTES: "Good to know", DOCS: "Docs", DESCRIPTION: "Description", ARGUMENTS: "Arguments", FLAGS: "Flags", EXAMPLES: "Examples" }, COLUMNS: { COMMAND: "COMMAND", WHAT: "WHAT IT DOES", FLAG: "FLAG", VALUE: "VALUE", DEFAULT: "DEFAULT", DESCRIPTION: "DESCRIPTION", NAME: "NAME", ARG: "ARGUMENT" }, SWITCH: "switch", DEFAULT: (value) => `Default: ${value}.`, REQUIRED: "required" };
|
|
2064
|
+
OWNER_ONLY = "Needs an owner key (from ignitify login). A user key is refused with owner-required.", PERMISSION_NOTES = [ACCESS.TASKS_WRITE_NOTE, ACCESS.SECRETS_READ_NOTE, ACCESS.HOOKS_WRITE_NOTE], AXES = USAGE_AXES.join("|"), DIR2 = { dir: { text: "Folder that holds your task folders.", default: `$${ENV.TASKS_DIR}, else ${DEFAULT_TASKS_DIR}` } }, YES2 = { yes: { text: "Skip the question (needed when not interactive)." } }, RAW2 = { raw: { text: "Print the bytes exactly as received. Without it, the colours are kept on a colour terminal and every other control code is removed." } }, SERIES_DAYS = { days: { text: `How many UTC days to show, today included. More than ${92} is read as ${92}.`, default: `${30}` } }, FAILURES_DAYS = { days: { text: `How many UTC days to show, today included. More than ${31} is read as ${31}.`, default: `${30}` } }, PAGE2 = { limit: { text: "Rows on the page. The server sets the default and the largest value." }, "after-id": { text: "Page cursor: the id of the last row of the page before." }, after: { text: "Only rows after this ISO 8601 time." } }, VERSION_HELP = { summary: "Print the client version, and the API version if the server answers.", usage: ["ignitify --version"], notes: ["Needs no key. Warns when this client is older than the API supports."] }, COMMAND_HELP = { [COMMANDS.HELP]: { summary: "Print the usage of every command, or of one command.", usage: ["ignitify help [<command>]"], args: { command: "A command name. Leave it out to print every command." }, notes: ["`ignitify <command> --help` prints the same page."], examples: ["ignitify help task:run"] }, [COMMANDS.LOGIN]: { summary: "Sign this machine in and save its API key.", usage: ["ignitify login [--name <label>] [--no-browser]"], flags: { name: { text: "A label for this machine's key; keeps two machines with one hostname apart.", default: "the hostname" }, "no-browser": { text: "Print the sign-in page, but do not open it." } }, notes: ["The key is saved in ~/.config/ignitify/credentials.json (mode 0600)."], examples: ["ignitify login", "ignitify login --name build-server --no-browser"] }, [COMMANDS.LOGOUT]: { summary: "Revoke this machine's key and delete the saved file.", usage: ["ignitify logout"] }, [COMMANDS.WHOAMI]: { summary: "Show who your key belongs to, your limits and usage.", usage: ["ignitify whoami"] }, [COMMANDS.QUOTA]: { summary: "Show what you hold now against your limits.", usage: ["ignitify quota"] }, [COMMANDS.TASK_CREATE]: { summary: "Register a task name and write its starter files.", usage: ["ignitify task:create <task-id> [--lang node|python|go|rust] [--local] [--no-init] [--no-scaffold] [--dir <base-dir>]"], args: { "task-id": "The task name: also the folder name and the slug in task.yaml." }, flags: { lang: { text: "Language of the starter files.", default: "node" }, local: { text: "Write the files only. Calls no server and needs no key." }, "no-init": { text: "Write the short starter that has only run(), without init code." }, "no-scaffold": { text: "Register the task name but write no files." }, ...DIR2 }, examples: ["ignitify task:create image-resize", "ignitify task:create image-resize --lang python --local"] }, [COMMANDS.TASK_LIST]: { summary: "List your tasks, one page at a time.", usage: ["ignitify task:list [--limit N] [--after-id <id>] [--after <iso>]"], flags: PAGE2 }, [COMMANDS.TASK_GET]: { summary: "Report a task's deploys, runs and next-step verdicts.", usage: ["ignitify task:get <task-id> [--dir <base-dir>]"], args: { "task-id": "The task folder name." }, flags: DIR2, notes: ["Shows whether the next deploy builds a new image and the next run boots a new VM, with the reasons."] }, [COMMANDS.TASK_VALIDATE]: { summary: "Check task.yaml on your machine; builds nothing.", usage: ["ignitify task:validate <task-id> [--dir <base-dir>] [--review-packages]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, "review-packages": { text: "Then ask the server for an AI review of build.system_packages." } }, notes: ["The review is advice only: the exit code never changes.", "Only package names and the runtime id leave this machine; never code, slug or secrets.", "It also checks that task.yaml still covers each @ignitify-integrations/* package in package.json. A listed package that is not in node_modules stops the check: run npm install first.", "With integrations installed, it sends their package names to the public npm registry (registry.npmjs.org) to find obsolete versions: a warning only. With no network, that warning is skipped with a note."] }, [COMMANDS.TASK_PACK]: { summary: "List the files task:deploy would upload; builds nothing.", usage: ["ignitify task:pack <task-id> [--dir <base-dir>]"], args: { "task-id": "The task folder name." }, flags: DIR2 }, [COMMANDS.TASK_DEPLOY]: { summary: "Upload the task source and wait until the image is built.", usage: ["ignitify task:deploy <task-id> [--dir <base-dir>] [--check]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, check: { text: "Run every local check, then stop. Nothing is uploaded." } }, notes: ["Ctrl+C stops waiting, not the build."], examples: ["ignitify task:deploy image-resize", "ignitify task:deploy image-resize --check"] }, [COMMANDS.TASK_RUN]: { summary: "Start one run of a task from its latest deployment.", usage: ["ignitify task:run <task-id> [--dir <base-dir>] [--deployment <id>] [--payload <json>] [--timeout-sec <n>] [--on-busy queue|reject] [--at <iso>] [--idempotency-key <key>] [--wait]"], args: { "task-id": "The task folder name." }, flags: { ...DIR2, deployment: { text: "Run this deployment instead of the task's latest deploy.", default: "the task's latest deploy" }, payload: { text: "JSON input for this run only.", default: "run.payload from task.yaml" }, "timeout-sec": { text: "Time limit in seconds for this run only.", default: "run.timeout_sec from task.yaml" }, "on-busy": { text: "When the run cannot start at once: queue waits for room (a limited time), reject fails at once.", default: "queue" }, at: { text: "Schedule the run for this ISO 8601 time. No run exists until it fires." }, "idempotency-key": { text: "Start at most one run for this key (8 to 255 characters of A-Z a-z 0-9 . _ : -). A retry with the same key gets the first answer again." }, wait: { text: "Follow the run to its end, then print it with its result." } }, notes: ["With --wait the exit code is 0 only when the run is COMPLETED. Ctrl+C stops waiting, not the run.", "--payload and --timeout-sec apply to this run only; task.yaml is never written.", "A bidirectional task runs one VM at a time; --on-busy covers that case too.", "Use --idempotency-key when a script retries after a network error. Use a new key for each new run; the same key with a different run is refused. Pin --deployment in such a script: a new deploy between two tries changes the request."], examples: ["ignitify task:run image-resize --wait", `ignitify task:run image-resize --payload '{"width":320}'`, "ignitify task:run image-resize --at 2027-01-15T03:00:00Z"] }, [COMMANDS.TASK_DELETE]: { summary: "Delete a task with all its deployments and runs.", usage: ["ignitify task:delete <task-id> [--dir <base-dir>] [--yes] [--json]"], args: { "task-id": "The task folder name, or the task id." }, flags: { ...DIR2, ...YES2, json: { text: "Print the plan and the result as one JSON document." } }, notes: ["Also removes the .ignitify/ link of every local folder linked to it; your files are kept."] }, [COMMANDS.DEPLOYMENT_LIST]: { summary: "List your deployments with the size of each image.", usage: ["ignitify deployment:list [--limit N] [--after-id <id>] [--after <iso>]"], flags: PAGE2 }, [COMMANDS.DEPLOYMENT_GET]: { summary: "Show one deployment: status, size, integrity.", usage: ["ignitify deployment:get <deployment-id>"], args: { "deployment-id": "The deployment id, from task:deploy or deployment:list." } }, [COMMANDS.DEPLOYMENT_LOGS]: { summary: "Print the build log of a deployment, as the build wrote it.", usage: ["ignitify deployment:logs <deployment-id> [--raw]"], args: { "deployment-id": "The deployment id." }, flags: RAW2, notes: ["The output goes to stdout; a truncated answer says so on stderr."] }, [COMMANDS.DEPLOYMENT_DELETE]: { summary: "Delete a deployment together with its runs.", usage: ["ignitify deployment:delete <deployment-id> [--yes]"], args: { "deployment-id": "The deployment id." }, flags: YES2 }, [COMMANDS.RUN_LIST]: { summary: "List your runs, with filters.", usage: ["ignitify run:list [--status S] [--deployment-id D] [--task-name N] [--preset P] [--correlation-id C] [--vm-id V] [--limit N] [--after-id <id>] [--after <iso>]"], flags: { status: { text: "Only runs in this status, for example FAILED." }, "deployment-id": { text: "Only runs of this deployment." }, "task-name": { text: "Only runs of this task." }, preset: { text: "Only runs on this instance class." }, "correlation-id": { text: "Only runs with this correlation id." }, "vm-id": { text: "Only runs that used this VM." }, ...PAGE2 } }, [COMMANDS.RUN_GET]: { summary: "Show one run: status, timing and the result of the task.", usage: ["ignitify run:get <run-id> [--full]"], args: { "run-id": "The run id, from task:run or run:list." }, flags: { full: { text: "Print the whole run as JSON, every field the API returns." } } }, [COMMANDS.RUN_LOGS]: { summary: "Print the console output of a run, as the task wrote it.", usage: ["ignitify run:logs <run-id> [--follow] [--tail N] [--raw]"], args: { "run-id": "The run id." }, flags: { ...RAW2, follow: { text: "Keep printing new output until the run has ended." }, tail: { text: "Print only the last N lines of the output so far. With --follow, go on from there." } }, notes: ["Output is best-effort. The host keeps it for 30 days at most. A read that is cut at the size cap says so on stderr. Use --follow to watch a live run.", "The output goes to stdout. With --json and --follow or --tail, each chunk is one JSON line with the offset to read from next.", "Ctrl+C stops following, not the run."], examples: ["ignitify run:logs <run-id> --follow", "ignitify run:logs <run-id> --tail 50"] }, [COMMANDS.RUN_TIMELINE]: { summary: "Show what happened to a run: one line per event, then how it ended.", usage: ["ignitify run:timeline <run-id> [--follow]"], args: { "run-id": "The run id." }, flags: { follow: { text: "Keep printing new events until the run has ended." } }, notes: ["When the run has ended, the exit code is 0 only when it is COMPLETED. A live run read without --follow exits 0.", "With --json --follow, each page of events is one JSON line. Ctrl+C stops following, not the run."], examples: ["ignitify run:timeline <run-id> --follow"] }, [COMMANDS.RUN_FAILURES]: { summary: "Show how your runs failed: by class, then the tasks that fail most.", usage: ["ignitify run:failures [--days N] [--json]"], flags: { ...FAILURES_DAYS, json: { text: "Print the report as JSON." } }, notes: ["A failure is a run that ended FAILED or CANCELLED. The task list leaves out cancelled runs.", "Classes: task (your code or deployment), limit (a plan limit), cancelled, platform (our side)."], examples: ["ignitify run:failures", "ignitify run:failures --days 7"] }, [COMMANDS.NOTIFICATION_LIST]: { summary: "List your notifications and alerts, newest first.", usage: ["ignitify notification:list [--unread | --open] [--kind <kind>] [--limit N] [--json]"], flags: { unread: { text: "Only the notifications you have not read." }, open: { text: "Only the alerts that are still open." }, kind: { text: `Only this kind: ${NOTIFICATION_KINDS.join(", ")}.` }, limit: { text: `How many to show. More than ${NOTIFICATION_PAGE_MAX} is read as ${NOTIFICATION_PAGE_MAX}.`, default: `${NOTIFICATION_PAGE_DEFAULT}` }, json: { text: "Print the page as JSON." } }, notes: [`We keep notifications for ${NOTIFICATION_RETENTION_DAYS} days. Mark them read in the app; this command does not change them.`], examples: ["ignitify notification:list --unread", "ignitify notification:list --open --json"] }, [COMMANDS.RUN_DELETE]: { summary: "Delete the record of a run; does not stop a live run.", usage: ["ignitify run:delete <run-id> [--yes]"], args: { "run-id": "The run id." }, flags: YES2 }, [COMMANDS.RUN_CANCEL]: { summary: "Stop a live run, gracefully by default.", usage: ["ignitify run:cancel <run-id> [--yes] [--force] [--grace <ms>]"], args: { "run-id": "The run id." }, flags: { ...YES2, force: { text: "Kill the run now. Also needed when the run is on a pinned VM." }, grace: { text: "Ask for a longer wind-down, in milliseconds. The server limits it." } } }, [COMMANDS.VM_LIST]: { summary: "List your live VMs with their state, age and idle time.", usage: ["ignitify vm:list [--pinned] [--deployment-id D] [--task-name N] [--preset P] [--older-than 24h] [--limit N] [--after-id <vm-id>] [--after <iso>]"], flags: { pinned: { text: "Only pinned VMs: they stay up until you delete them." }, "deployment-id": { text: "Only VMs of this deployment." }, "task-name": { text: "Only VMs of this task." }, preset: { text: "Only VMs of this instance class." }, "older-than": { text: "Only VMs older than this age, for example 24h." }, limit: PAGE2.limit, "after-id": { text: "Page cursor: the VM id of the last row of the page before." }, after: { text: "Only VMs booted after this ISO 8601 time." } } }, [COMMANDS.VM_GET]: { summary: "Show one VM: residency, state, age and its task nature.", usage: ["ignitify vm:get <vm-id>"], args: { "vm-id": "The VM id, from vm:list." } }, [COMMANDS.VM_DELETE]: { summary: "Tear down a VM by hand.", usage: ["ignitify vm:delete <vm-id> [--yes] [--force]"], args: { "vm-id": "The VM id." }, flags: { ...YES2, force: { text: "Allow the delete of a pinned VM. It does not skip the question." } } }, [COMMANDS.SECRET_SET]: { summary: "Store a secret value under a name, read from stdin.", usage: ["ignitify secret:set <NAME> --stdin"], args: { NAME: "The secret name. List it in `secrets` in task.yaml to use it." }, flags: { stdin: { text: "Read the value from stdin. A value on the command line is refused." } }, examples: ['printf %s "$VALUE" | ignitify secret:set DB_PASSWORD --stdin'] }, [COMMANDS.SECRET_LIST]: { summary: "List the names of your secrets.", usage: ["ignitify secret:list"] }, [COMMANDS.SECRET_DELETE]: { summary: "Delete a secret.", usage: ["ignitify secret:delete <NAME> [--yes]"], args: { NAME: "The secret name." }, flags: YES2 }, [COMMANDS.INTEGRATION_ADD]: { summary: "Add an integration package to a Node task and write its settings into task.yaml.", usage: ["ignitify integration:add <name> --task <slug> [--yes | --dry-run] [--dir <base-dir>]"], args: { name: "The integration name. The npm package is @ignitify-integrations/<name>." }, flags: { task: { text: "The task folder name to add the integration to." }, yes: { text: "Apply the plan without the question (needed when not interactive)." }, "dry-run": { text: "Show the plan, then stop. Nothing in the task is changed." }, ...DIR2 }, notes: ["It downloads the latest @ignitify-integrations/<name> with npm pack into a temporary folder and reads the plan from that file. Nothing in the task changes before you answer y.", "The plan shows every task.yaml change, then what you add yourself: secret values and hosts that differ per task.", "After y (or with --yes), it runs npm install @ignitify-integrations/<name>@<that version> in the task folder (a normal dependency, with --ignore-scripts as the build uses), then writes task.yaml. If the install fails, task.yaml is not written.", "An N, --dry-run or a refusal changes nothing: no package.json, lockfile, node_modules or task.yaml change.", "A setting that conflicts with task.yaml, or more egress hosts than the platform allows, is refused and nothing is written.", "Adding the same integration again changes nothing. To upgrade, add it again. To remove it, run npm uninstall; the settings it added stay yours to delete."], examples: ["ignitify integration:add s3-storage --task launch-event", "ignitify integration:add stream --task launch-event --dry-run"] }, [COMMANDS.SCHEDULE_LIST]: { summary: "List runs that wait for a time, soonest first.", usage: ["ignitify schedule:list"] }, [COMMANDS.SCHEDULE_CANCEL]: { summary: "Cancel one scheduled run so that it never fires.", usage: ["ignitify schedule:cancel <sch-id> [--yes]"], args: { "sch-id": "The schedule id, from task:run --at or schedule:list." }, flags: YES2, notes: ['"0 rows" means it is already firing or fired.'] }, [COMMANDS.HOOK_CREATE]: { summary: "Create a webhook that starts a run of one task.", usage: ["ignitify hook:create <deployment-id> --task-name <name>"], args: { "deployment-id": "The deployment the webhook belongs to." }, flags: { "task-name": { text: "The task that each run starts. Required." } }, notes: ["The reply carries a `secret` shown ONCE: hook:list never returns it."] }, [COMMANDS.HOOK_LIST]: { summary: "List the webhooks of a deployment.", usage: ["ignitify hook:list <deployment-id> [--limit N] [--after-id <id>] [--after <iso>]"], args: { "deployment-id": "The deployment id." }, flags: PAGE2 }, [COMMANDS.HOOK_DELETE]: { summary: "Delete a webhook.", usage: ["ignitify hook:delete <hook-resource-id> [--yes]"], args: { "hook-resource-id": "The HOOK's id (hok_\u2026), not the deployment's." }, flags: YES2 }, [COMMANDS.NOTIFY_SET]: { summary: "Set or clear the callback URL of a deployment.", usage: ["ignitify notify:set <deployment-id> --url <https://\u2026> --secret-stdin", "ignitify notify:set <deployment-id> --clear"], args: { "deployment-id": "The deployment id." }, flags: { url: { text: "The HTTPS address to call. Needs --secret-stdin." }, "secret-stdin": { text: "Read the signing secret from stdin. Needed with --url." }, clear: { text: "Remove the callback URL. Cannot be used with --url." } }, notes: ["There is no --secret flag: a value on argv is in `ps` and in your shell history."] }, [COMMANDS.USAGE]: { summary: "Show what you used in one month, by axis.", usage: [`ignitify usage [--period YYYY-MM] [--axis ${AXES}] [--vm <vm-id>] [--run <run-id>] [--runs|--vms] [--json]`], flags: { period: { text: "The month to show, as YYYY-MM.", default: "this month" }, axis: { text: "Show one axis only." }, vm: { text: "Only the usage of this VM." }, run: { text: "Only the usage of this run." }, runs: { text: "Add the lines behind the totals, one per run." }, vms: { text: "Add the lines behind the totals, one per VM." }, json: { text: "Print the report as JSON." } } }, [COMMANDS.USAGE_EXPORT]: { summary: "Export the usage lines of a month as JSON or CSV.", usage: [`ignitify usage:export [--period YYYY-MM] [--axis ${AXES}] [--vm <vm-id>] [--run <run-id>] (--json|--csv)`], flags: { period: { text: "The month to export, as YYYY-MM.", default: "this month" }, axis: { text: "Export one axis only." }, vm: { text: "Only the lines of this VM." }, run: { text: "Only the lines of this run." }, json: { text: "Print the lines as a JSON array." }, csv: { text: "Print the lines as CSV." } }, notes: ["Name exactly one format: --json or --csv."] }, [COMMANDS.USAGE_SERIES]: { summary: "Show your usage per day: runs, run durations, compute and egress.", usage: ["ignitify usage:series [--days N] [--json]"], flags: { ...SERIES_DAYS, json: { text: "Print the series as JSON." } }, notes: ["Days are UTC. A run counts on the day it ended. Compute counts when the VM closes, so a running VM shows none yet."], examples: ["ignitify usage:series", "ignitify usage:series --days 7 --json"] }, [COMMANDS.PERMISSION_LIST]: { summary: "List every permission a group can grant, and what it allows.", usage: ["ignitify permission:list [--json]"], notes: ["Needs no key and no server: the list is built into the CLI.", ...PERMISSION_NOTES] }, [COMMANDS.USER_CREATE]: { summary: "Create a user: a name that holds keys and is in groups.", usage: ["ignitify user:create <name> [--json]"], args: { name: "1 to 63 lower case letters, digits and -, unique in your account." }, notes: ["A user has no password and cannot sign in. Add it to a group to give it permissions.", OWNER_ONLY], examples: ["ignitify user:create github-ci"] }, [COMMANDS.USER_LIST]: { summary: "List your users.", usage: ["ignitify user:list [--json]"], notes: [OWNER_ONLY] }, [COMMANDS.USER_GET]: { summary: "Show one user: its groups, permissions and live keys.", usage: ["ignitify user:get <name> [--json]"], args: { name: "The user name." }, notes: [OWNER_ONLY] }, [COMMANDS.USER_REMOVE]: { summary: "Remove a user and revoke all of its keys.", usage: ["ignitify user:remove <name> [--yes]"], args: { name: "The user name." }, flags: YES2, notes: ["Every key of the user stops working at once. The name can be used again.", OWNER_ONLY] }, [COMMANDS.GROUP_CREATE]: { summary: "Create a group with a set of permissions.", usage: ["ignitify group:create <name> --permissions <p1,p2,\u2026> [--json]"], args: { name: "1 to 63 lower case letters, digits and -, unique in your account." }, flags: { permissions: { text: "The permissions the group grants, comma-separated. Required. See permission:list." } }, notes: [...PERMISSION_NOTES, OWNER_ONLY], examples: ["ignitify group:create readers --permissions tasks.read,deployments.read,runs.read,usage.read", "ignitify group:create ci --permissions tasks.read,deployments.read,deployments.write,runs.read,runs.write"] }, [COMMANDS.GROUP_LIST]: { summary: "List your groups and their permissions.", usage: ["ignitify group:list [--json]"], notes: [OWNER_ONLY] }, [COMMANDS.GROUP_GET]: { summary: "Show one group: its permissions and users.", usage: ["ignitify group:get <name> [--json]"], args: { name: "The group name." }, notes: [OWNER_ONLY] }, [COMMANDS.GROUP_GRANT]: { summary: "Add one permission to a group.", usage: ["ignitify group:grant <name> <permission>"], args: { name: "The group name.", permission: "One permission from permission:list." }, notes: [...PERMISSION_NOTES, OWNER_ONLY], examples: ["ignitify group:grant readers secrets.read"] }, [COMMANDS.GROUP_REVOKE]: { summary: "Remove one permission from a group.", usage: ["ignitify group:revoke <name> <permission>"], args: { name: "The group name.", permission: "The permission to remove." }, notes: ["The users of the group lose it at their next request.", OWNER_ONLY] }, [COMMANDS.GROUP_ADD_USER]: { summary: "Put a user in a group.", usage: ["ignitify group:add-user <group> <user>"], args: { group: "The group name.", user: "The user name." }, notes: [OWNER_ONLY], examples: ["ignitify group:add-user ci github-ci"] }, [COMMANDS.GROUP_REMOVE_USER]: { summary: "Take a user out of a group.", usage: ["ignitify group:remove-user <group> <user>"], args: { group: "The group name.", user: "The user name." }, notes: ["The user and its keys stay; they lose what the group granted.", OWNER_ONLY] }, [COMMANDS.GROUP_DELETE]: { summary: "Delete a group. Its users and keys stay.", usage: ["ignitify group:delete <name> [--yes]"], args: { name: "The group name." }, flags: YES2, notes: ["The users of the group lose the permissions it granted.", OWNER_ONLY] }, [COMMANDS.KEY_CREATE]: { summary: "Create an API key for a user.", usage: ["ignitify key:create --user <name> --label <text> [--out <file>] [--json]"], flags: { user: { text: "The user the key belongs to. Required." }, label: { text: "A name for the key, such as where it is used. Required." }, out: { text: "Write the key to this new file (mode 0600) and print only its id. Fails when the file exists." } }, notes: ["The key is shown once. Without --out it is printed; hand it over by hand and do not keep a copy.", "Owner keys come from ignitify login. This command makes user keys only.", OWNER_ONLY], examples: ["ignitify key:create --user github-ci --label github --out ./github-ci.key"] }, [COMMANDS.KEY_LIST]: { summary: "List the live keys of your account. Never a raw key.", usage: ["ignitify key:list [--user <name>] [--json]"], flags: { user: { text: "Only the keys of this user." } }, notes: ["The user column says owner for an owner key.", OWNER_ONLY] }, [COMMANDS.KEY_REVOKE]: { summary: "Revoke a key. It stops working at once.", usage: ["ignitify key:revoke <key-resource-id> [--yes]"], args: { "key-resource-id": "The key id, from key:list." }, flags: YES2, notes: [OWNER_ONLY] } }, HELP_PAGE = { TITLE: "ignitify", TAGLINE: "create, deploy and run tasks", USAGE: "ignitify <command> [arguments] [flags]", USAGE_NOTE: "Every command is <resource>:<verb>.", QUICK_START: [{ command: "ignitify login", text: "Sign this machine in." }, { command: "ignitify task:create my-task", text: "Register a task and write its files." }, { command: "ignitify task:deploy my-task", text: "Build it on the platform." }, { command: "ignitify task:run my-task --wait", text: "Run it and print the result." }], GROUPS: [{ title: "Tasks", resources: ["task"] }, { title: "Deployments", resources: ["deployment"] }, { title: "Runs", resources: ["run"] }, { title: "VMs", resources: ["vm"] }, { title: "Secrets", resources: ["secret"] }, { title: "Integrations", resources: ["integration"] }, { title: "Schedules", resources: ["schedule"] }, { title: "Webhooks", resources: ["hook", "notify"] }, { title: "Access", resources: ["permission", "user", "group", "key"] }, { title: "Account and usage", resources: ["login", "logout", "whoami", "quota", "usage", "notification", "help"] }], GLOBAL_FLAGS: [{ flag: "--json", text: "Print the result as one JSON document on stdout." }, { flag: "-h, --help", text: "Print help for the command." }, { flag: "-y, --yes", text: "Answer yes to a delete's question (for scripts)." }, { flag: "-v, --version", text: "Print the client and API version (first word only)." }], ENV: [{ name: ENV.KEY, text: "Your API key, for CI. Wins over the saved login." }, { name: ENV.TASKS_DIR, text: `Folder that holds your task folders (default: ${DEFAULT_TASKS_DIR}).` }, { name: "NO_COLOR", text: "Set to any value to turn colour off." }], NOTES: ["Delete commands always ask first; --force unlocks a refusal, it does not skip the question.", "A flag or argument a command does not take is refused, not ignored.", "On error the CLI prints a clean message and exits non-zero."], DOCS: SITE_LINKS.DOCS, MORE: "Run 'ignitify <command> --help' for the flags of one command.", H: { USAGE: "Usage", QUICK_START: "Quick start", GLOBAL_FLAGS: "Global flags", ENV: "Environment", NOTES: "Good to know", DOCS: "Docs", DESCRIPTION: "Description", ARGUMENTS: "Arguments", FLAGS: "Flags", EXAMPLES: "Examples" }, COLUMNS: { COMMAND: "COMMAND", WHAT: "WHAT IT DOES", FLAG: "FLAG", VALUE: "VALUE", DEFAULT: "DEFAULT", DESCRIPTION: "DESCRIPTION", NAME: "NAME", ARG: "ARGUMENT" }, SWITCH: "switch", DEFAULT: (value) => `Default: ${value}.`, REQUIRED: "required" };
|
|
1905
2065
|
} });
|
|
1906
2066
|
function entryOf(name) {
|
|
1907
2067
|
if (name === VERSION_NAME) return { help: VERSION_HELP, meta: VERSION_META };
|
|
@@ -1942,7 +2102,7 @@ function placeholderOf(forms, flag) {
|
|
|
1942
2102
|
}
|
|
1943
2103
|
function flagRows(help, meta) {
|
|
1944
2104
|
let words = help.flags ?? {}, known = { ...meta.flags, ...GLOBAL_FLAGS }, shortOf = Object.fromEntries(Object.entries(SHORT_FLAGS).map(([s, l]) => [l, s]));
|
|
1945
|
-
return Object.entries(words).filter(([flag]) => Object.hasOwn(known, flag)
|
|
2105
|
+
return Object.entries(words).filter(([flag]) => Object.hasOwn(known, flag)).map(([flag, w]) => {
|
|
1946
2106
|
let short = shortOf[flag], value = known[flag] === "value" ? placeholderOf(help.usage, flag) : HELP_PAGE.SWITCH, text = w.default === void 0 ? w.text : `${w.text} ${HELP_PAGE.DEFAULT(w.default)}`;
|
|
1947
2107
|
return [short === void 0 ? `--${flag}` : `-${short}, --${flag}`, value, text];
|
|
1948
2108
|
});
|
|
@@ -2068,11 +2228,17 @@ function explainLimitRefusal(e) {
|
|
|
2068
2228
|
return typeof summary != "string" || summary === "" ? null : [`\u2717 Run refused \u2014 a monthly limit is reached (${typeof reason == "string" ? reason : "429"}).`, ` ${summary}`].join(`
|
|
2069
2229
|
`);
|
|
2070
2230
|
}
|
|
2071
|
-
|
|
2231
|
+
function idempotencyRefusal(e) {
|
|
2232
|
+
let refused2 = httpErrorJson(e), hint = refused2 ? IDEMPOTENCY_REFUSAL_HINTS[refused2.status] : void 0, error = refused2?.body.error;
|
|
2233
|
+
return !refused2 || hint === void 0 || typeof error != "string" || error === "" ? null : { error, hint };
|
|
2234
|
+
}
|
|
2235
|
+
var IDEMPOTENCY_REFUSAL_HINTS, isPayloadTooLarge, init_run_refusal = __esm({ "src/output/run-refusal.ts"() {
|
|
2072
2236
|
"use strict";
|
|
2073
2237
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
2074
2238
|
init_http_client();
|
|
2239
|
+
init_constants();
|
|
2075
2240
|
init_tenant();
|
|
2241
|
+
IDEMPOTENCY_REFUSAL_HINTS = { 409: MESSAGES.IDEMPOTENCY_IN_PROGRESS_HINT, 422: MESSAGES.IDEMPOTENCY_KEY_REUSED_HINT };
|
|
2076
2242
|
isPayloadTooLarge = (e) => e instanceof HttpError && e.status === 413;
|
|
2077
2243
|
} });
|
|
2078
2244
|
function renderUsageReport(st, r, opts = { lines: false }) {
|
|
@@ -2096,6 +2262,77 @@ var init_usage2 = __esm({ "src/output/usage.ts"() {
|
|
|
2096
2262
|
init_constants2();
|
|
2097
2263
|
init_table();
|
|
2098
2264
|
} });
|
|
2265
|
+
function attrText(value) {
|
|
2266
|
+
return value === null || typeof value != "object" ? typeof value == "string" && !/^[\w.:/@+-]+$/.test(value) ? JSON.stringify(value) : String(value) : JSON.stringify(value.text);
|
|
2267
|
+
}
|
|
2268
|
+
function timelineLine(event) {
|
|
2269
|
+
let attrs = Object.entries(event.attrs).filter(([key, value]) => key !== CAUSE_ATTR && value !== null).map(([key, value]) => `${key}=${attrText(value)}`), line = [event.at, event.label, ...attrs.length > 0 ? [attrs.join(" ")] : []].join(" ");
|
|
2270
|
+
return terminalSafe(line, false).replace(/[\n\t]/g, " ");
|
|
2271
|
+
}
|
|
2272
|
+
var CAUSE_ATTR, init_timeline = __esm({ "src/output/timeline.ts"() {
|
|
2273
|
+
"use strict";
|
|
2274
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2275
|
+
init_style();
|
|
2276
|
+
CAUSE_ATTR = "cause";
|
|
2277
|
+
} });
|
|
2278
|
+
function renderUsageSeries(st, r) {
|
|
2279
|
+
let I2 = LAYOUT.INDENT, statusColumns = TERMINAL_RUN_STATUSES.map((s) => ({ header: s, keep: true })), table = renderTable(st, [{ header: "DAY", keep: true }, ...statusColumns, { header: "P50", keep: true }, { header: "P95", keep: true }, { header: "COMPUTE", keep: true }, { header: "EGRESS", keep: true }], r.series.map((d) => [d.day, ...TERMINAL_RUN_STATUSES.map((s) => runsIn(d, s)), durationText(st, d.duration_ms_p50), durationText(st, d.duration_ms_p95), formatUsageQuantity(d.compute_ms, AXIS_UNIT.compute), formatUsageQuantity(d.egress_bytes, AXIS_UNIT.network)]), { indent: I2 });
|
|
2280
|
+
return [heading(st, `usage per day, ${windowText(r.days)}`), "", table, "", st.dim(`${I2}note: runs count on the day they ended; P50 and P95 are the run durations of that day.`), st.dim(`${I2} \`compute\` = VM time, counted when the VM closes. The egress of a past day can still grow.`)].join(`
|
|
2281
|
+
`);
|
|
2282
|
+
}
|
|
2283
|
+
function renderFailures(st, r) {
|
|
2284
|
+
let I2 = LAYOUT.INDENT, out2 = [heading(st, `failed and cancelled runs, ${windowText(r.days)}`), ""];
|
|
2285
|
+
return out2.push(renderTable(st, [{ header: "CLASS", keep: true }, { header: "RUNS", keep: true }, { header: "MEANING" }], FAILURE_CLASSES.map((c) => [c, r.by_class[c], FAILURE_CLASS_TEXT[c].hint]), { indent: I2 }), ""), r.top_tasks.length === 0 ? (out2.push(st.dim(`${I2}No task failed in this window.`)), out2.join(`
|
|
2286
|
+
`)) : (out2.push(renderTable(st, [{ header: "TASK" }, { header: "FAILURES", keep: true }], r.top_tasks.map((t) => [t.task_name === null ? NO_TASK_NAME_TEXT : terminalSafe(t.task_name, false), t.failures]), { indent: I2 }), "", st.dim(`${I2}note: the task list leaves out cancelled runs.`)), out2.join(`
|
|
2287
|
+
`));
|
|
2288
|
+
}
|
|
2289
|
+
var windowText, runsIn, durationText, init_reports = __esm({ "src/output/reports.ts"() {
|
|
2290
|
+
"use strict";
|
|
2291
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2292
|
+
init_tenant();
|
|
2293
|
+
init_http_client();
|
|
2294
|
+
init_blocks();
|
|
2295
|
+
init_constants2();
|
|
2296
|
+
init_style();
|
|
2297
|
+
init_table();
|
|
2298
|
+
windowText = (days) => `last ${days} ${days === 1 ? "day" : "days"} (UTC)`, runsIn = (day, status) => day.runs[status] ?? 0, durationText = (st, ms) => ms === null ? st.glyphs.dash : formatUsageQuantity(ms, AXIS_UNIT.compute);
|
|
2299
|
+
} });
|
|
2300
|
+
function severityWord(st, s) {
|
|
2301
|
+
let word = s.toUpperCase();
|
|
2302
|
+
return s === "critical" ? st.red(word) : s === "warning" ? st.yellow(word) : st.dim(word);
|
|
2303
|
+
}
|
|
2304
|
+
function factsOf(n) {
|
|
2305
|
+
let facts = [];
|
|
2306
|
+
return n.read || facts.push("unread"), n.state === "open" && facts.push("open"), n.state === "resolved" && n.resolved_at !== null && facts.push(`resolved ${terminalSafe(n.resolved_at, false)}`), n.occurrences > 1 && facts.push(`${n.occurrences} times`), facts;
|
|
2307
|
+
}
|
|
2308
|
+
function commandOf(a) {
|
|
2309
|
+
return a.type === "open-run" ? `ignitify ${COMMANDS.RUN_GET} ${terminalSafe(a.run_id, false)}` : a.type === "usage" ? `ignitify ${COMMANDS.QUOTA}` : null;
|
|
2310
|
+
}
|
|
2311
|
+
function card(st, n) {
|
|
2312
|
+
let I2 = LAYOUT.INDENT, title = terminalSafe(n.title, false), facts = factsOf(n), head = `${I2}${severityWord(st, n.severity)} ${n.read ? title : st.bold(title)}${facts.length > 0 ? st.dim(` (${facts.join(", ")})`) : ""}`, text = wrapText(terminalSafe(n.text, false), st.width - 2 * I2.length).map((l) => `${I2}${I2}${l}`), ids = [n.id, n.last_at, ...n.subject === null ? [] : [`${n.subject.type} ${n.subject.id}`]].map((s) => terminalSafe(s, false)), next = (n.actions ?? []).flatMap((a) => {
|
|
2313
|
+
let cmd = commandOf(a);
|
|
2314
|
+
return cmd === null ? [] : [`${I2}${I2}next: ${cmd}`];
|
|
2315
|
+
});
|
|
2316
|
+
return [head, ...text, st.dim(`${I2}${I2}${ids.join(" ")}`), ...next].join(`
|
|
2317
|
+
`);
|
|
2318
|
+
}
|
|
2319
|
+
function renderNotifications(st, r) {
|
|
2320
|
+
let I2 = LAYOUT.INDENT;
|
|
2321
|
+
return r.items.length === 0 ? `${heading(st, "notifications")}
|
|
2322
|
+
|
|
2323
|
+
${st.dim(`${I2}No notifications.`)}` : [heading(st, "notifications, newest first"), "", r.items.map((n) => card(st, n)).join(`
|
|
2324
|
+
|
|
2325
|
+
`)].join(`
|
|
2326
|
+
`);
|
|
2327
|
+
}
|
|
2328
|
+
var init_notifications = __esm({ "src/output/notifications.ts"() {
|
|
2329
|
+
"use strict";
|
|
2330
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2331
|
+
init_constants();
|
|
2332
|
+
init_blocks();
|
|
2333
|
+
init_constants2();
|
|
2334
|
+
init_style();
|
|
2335
|
+
} });
|
|
2099
2336
|
var init_output = __esm({ "src/output/index.ts"() {
|
|
2100
2337
|
"use strict";
|
|
2101
2338
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
@@ -2113,6 +2350,10 @@ var init_output = __esm({ "src/output/index.ts"() {
|
|
|
2113
2350
|
init_deletion_plan();
|
|
2114
2351
|
init_run_refusal();
|
|
2115
2352
|
init_usage2();
|
|
2353
|
+
init_timeline();
|
|
2354
|
+
init_reports();
|
|
2355
|
+
init_notifications();
|
|
2356
|
+
init_access2();
|
|
2116
2357
|
} });
|
|
2117
2358
|
function planAdvice(config) {
|
|
2118
2359
|
let declared = config.build?.system_packages ?? [];
|
|
@@ -2132,127 +2373,31 @@ var init_package_advice3 = __esm({ "src/core/package-advice.ts"() {
|
|
|
2132
2373
|
init_constants();
|
|
2133
2374
|
} });
|
|
2134
2375
|
var version, init_package = __esm({ "package.json"() {
|
|
2135
|
-
version = "
|
|
2136
|
-
} });
|
|
2137
|
-
var byCreatedAtDesc, init_sort = __esm({ "src/utils/sort.ts"() {
|
|
2138
|
-
"use strict";
|
|
2139
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2140
|
-
byCreatedAtDesc = (rows) => [...rows].sort((a, b) => (b.created_at ?? "").localeCompare(a.created_at ?? ""));
|
|
2376
|
+
version = "2.0.0";
|
|
2141
2377
|
} });
|
|
2142
|
-
function
|
|
2143
|
-
return
|
|
2144
|
-
}
|
|
2145
|
-
function boolFlag(args, key) {
|
|
2146
|
-
return args.options[key] === true;
|
|
2147
|
-
}
|
|
2148
|
-
function requiredArg(args) {
|
|
2149
|
-
return args.positionals[0];
|
|
2150
|
-
}
|
|
2151
|
-
function requireArg(args, label) {
|
|
2152
|
-
let id = requiredArg(args);
|
|
2153
|
-
return id || (fail(MESSAGES.ARG_REQUIRED(label)), null);
|
|
2154
|
-
}
|
|
2155
|
-
var init_args2 = __esm({ "src/utils/args.ts"() {
|
|
2156
|
-
"use strict";
|
|
2157
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2158
|
-
init_constants();
|
|
2159
|
-
init_output();
|
|
2160
|
-
init_http_client();
|
|
2161
|
-
init_http_client();
|
|
2162
|
-
} });
|
|
2163
|
-
function traceLine(e) {
|
|
2164
|
-
return e instanceof HttpError && e.traceId ? MESSAGES.SERVER_TRACE_ID(e.traceId) : null;
|
|
2165
|
-
}
|
|
2166
|
-
function keyExpiredHint(e) {
|
|
2167
|
-
let refused2 = httpErrorJson(e);
|
|
2168
|
-
return refused2?.status === 401 && refused2.body.error === KEY_EXPIRED_ERROR ? MESSAGES.KEY_EXPIRED_HINT : null;
|
|
2378
|
+
function apiUrlFor(env, release = RELEASE_BUILD) {
|
|
2379
|
+
return release ? DEFAULTS.API_URL : env[ENV.DEV_API_URL] || DEFAULTS.API_URL;
|
|
2169
2380
|
}
|
|
2170
|
-
function
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
return await body();
|
|
2179
|
-
} catch (e) {
|
|
2180
|
-
printError(e);
|
|
2181
|
-
return;
|
|
2381
|
+
function apiUrlRefusal(url) {
|
|
2382
|
+
switch (keyUrlRefusal(url)) {
|
|
2383
|
+
case "not-url":
|
|
2384
|
+
return MESSAGES.API_URL_NOT_URL(url);
|
|
2385
|
+
case "insecure":
|
|
2386
|
+
return MESSAGES.API_URL_NOT_HTTPS(url);
|
|
2387
|
+
case null:
|
|
2388
|
+
return null;
|
|
2182
2389
|
}
|
|
2183
2390
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
init_tenant();
|
|
2189
|
-
init_constants();
|
|
2190
|
-
init_output();
|
|
2191
|
-
} });
|
|
2192
|
-
var import_node_readline, ttyPrompter, init_prompt = __esm({ "src/io/prompt.ts"() {
|
|
2193
|
-
"use strict";
|
|
2194
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2195
|
-
import_node_readline = __toESM(require("node:readline"), 1);
|
|
2196
|
-
init_output();
|
|
2197
|
-
ttyPrompter = { async confirm(question) {
|
|
2198
|
-
if (!process.stdin.isTTY) return fail("Refusing to proceed: not a TTY. Re-run with --yes to confirm non-interactively."), false;
|
|
2199
|
-
let rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
2200
|
-
try {
|
|
2201
|
-
let answer = await new Promise((resolve) => rl.question(`${question} [y/N] `, resolve));
|
|
2202
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
2203
|
-
} finally {
|
|
2204
|
-
rl.close();
|
|
2205
|
-
}
|
|
2206
|
-
} };
|
|
2207
|
-
} });
|
|
2208
|
-
async function confirmOrAbort(args, prompter = ttyPrompter, question = MESSAGES.CONFIRM_PROCEED) {
|
|
2209
|
-
return boolFlag(args, "yes") || await prompter.confirm(question) ? true : (say(MESSAGES.ABORTED), false);
|
|
2210
|
-
}
|
|
2211
|
-
var init_prompt_flow = __esm({ "src/utils/prompt-flow.ts"() {
|
|
2212
|
-
"use strict";
|
|
2213
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2214
|
-
init_prompt();
|
|
2215
|
-
init_constants();
|
|
2216
|
-
init_args2();
|
|
2217
|
-
init_manager();
|
|
2218
|
-
} });
|
|
2219
|
-
var REAL_CLOCK, init_clock = __esm({ "src/utils/clock.ts"() {
|
|
2220
|
-
"use strict";
|
|
2221
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2222
|
-
REAL_CLOCK = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), now: () => Date.now() };
|
|
2223
|
-
} });
|
|
2224
|
-
function snakeCaseKeys(value) {
|
|
2225
|
-
return Array.isArray(value) ? value.map(snakeCaseKeys) : typeof value != "object" || value === null ? value : Object.fromEntries(Object.entries(value).map(([k, v]) => [k.replace(/([a-z0-9])([A-Z])/g, (_, a, b) => `${a}_${b.toLowerCase()}`), snakeCaseKeys(v)]));
|
|
2226
|
-
}
|
|
2227
|
-
var init_snake_case = __esm({ "src/utils/snake-case.ts"() {
|
|
2228
|
-
"use strict";
|
|
2229
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2230
|
-
} });
|
|
2231
|
-
function nextPageNoteFor2(args, list2, rows) {
|
|
2232
|
-
return nextPageNoteFor(PROGRAM, args, { ...list2, flags: [...Object.keys(list2.flags), ...REPEATED_GLOBAL_FLAGS] }, rows, effectivePageLimit(strOpt(args, "limit")));
|
|
2233
|
-
}
|
|
2234
|
-
function limitRefusal(args) {
|
|
2235
|
-
let raw = args.options.limit;
|
|
2236
|
-
return typeof raw == "string" ? pageLimitRefusal(raw) : null;
|
|
2391
|
+
function assertApiUrl(url) {
|
|
2392
|
+
let refusal = apiUrlRefusal(url);
|
|
2393
|
+
if (refusal !== null) throw new Error(refusal);
|
|
2394
|
+
return url;
|
|
2237
2395
|
}
|
|
2238
|
-
var
|
|
2396
|
+
var init_api_url = __esm({ "src/core/api-url.ts"() {
|
|
2239
2397
|
"use strict";
|
|
2240
2398
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
2241
|
-
init_tenant();
|
|
2242
2399
|
init_http_client();
|
|
2243
|
-
|
|
2244
|
-
PROGRAM = "ignitify", REPEATED_GLOBAL_FLAGS = ["api"];
|
|
2245
|
-
} });
|
|
2246
|
-
var init_utils = __esm({ "src/utils/index.ts"() {
|
|
2247
|
-
"use strict";
|
|
2248
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2249
|
-
init_sort();
|
|
2250
|
-
init_args2();
|
|
2251
|
-
init_guard();
|
|
2252
|
-
init_prompt_flow();
|
|
2253
|
-
init_clock();
|
|
2254
|
-
init_snake_case();
|
|
2255
|
-
init_page2();
|
|
2400
|
+
init_constants();
|
|
2256
2401
|
} });
|
|
2257
2402
|
function floorWarning(client, floor) {
|
|
2258
2403
|
if (!floor) return null;
|
|
@@ -2280,8 +2425,8 @@ async function describeServer(baseUrl, fetchImpl = fetch, check = noteServerVers
|
|
|
2280
2425
|
let floor = res.headers.get(CLIENT_FLOOR_HEADER);
|
|
2281
2426
|
return `API: ${api} at ${baseUrl}${floor ? ` (minimum client ${floor})` : ""}`;
|
|
2282
2427
|
}
|
|
2283
|
-
async function runVersion(
|
|
2284
|
-
let baseUrl =
|
|
2428
|
+
async function runVersion(fetchImpl = fetch, env = process.env) {
|
|
2429
|
+
let baseUrl = apiUrlFor(env), st = outStyle();
|
|
2285
2430
|
return out(`${st.bold(`ignitify ${CLIENT_VERSION}`)} ${st.dim(`(built for API ${API_VERSION})`)}`), out(st.dim(await describeServer(baseUrl, fetchImpl))), 0;
|
|
2286
2431
|
}
|
|
2287
2432
|
var CLIENT_VERSION, PROBE_TIMEOUT_MS, noteServerVersion, init_version = __esm({ "src/io/version.ts"() {
|
|
@@ -2291,7 +2436,7 @@ var CLIENT_VERSION, PROBE_TIMEOUT_MS, noteServerVersion, init_version = __esm({
|
|
|
2291
2436
|
init_tenant();
|
|
2292
2437
|
init_constants();
|
|
2293
2438
|
init_semver();
|
|
2294
|
-
|
|
2439
|
+
init_api_url();
|
|
2295
2440
|
init_output();
|
|
2296
2441
|
CLIENT_VERSION = version, PROBE_TIMEOUT_MS = 3e3;
|
|
2297
2442
|
noteServerVersion = createStalenessCheck(CLIENT_VERSION, (msg) => warn(msg));
|
|
@@ -2360,16 +2505,25 @@ function readCredentials(file = credentialsPath(), fsx = import_node_fs2.default
|
|
|
2360
2505
|
fsx.closeSync(fd);
|
|
2361
2506
|
}
|
|
2362
2507
|
}
|
|
2363
|
-
function
|
|
2364
|
-
|
|
2365
|
-
|
|
2508
|
+
function createPrivateFile(file, fsx = import_node_fs2.default) {
|
|
2509
|
+
return fsx.openSync(file, "wx", CREDENTIALS.FILE_MODE);
|
|
2510
|
+
}
|
|
2511
|
+
function discardPrivateFile(fd, file, fsx = import_node_fs2.default) {
|
|
2512
|
+
fsx.closeSync(fd), fsx.unlinkSync(file);
|
|
2513
|
+
}
|
|
2514
|
+
function fillPrivateFile(fd, file, text, fsx = import_node_fs2.default) {
|
|
2366
2515
|
try {
|
|
2367
|
-
fsx.writeSync(fd,
|
|
2368
|
-
`), fsx.fsyncSync(fd);
|
|
2516
|
+
fsx.writeSync(fd, text), fsx.fsyncSync(fd);
|
|
2369
2517
|
} catch (e) {
|
|
2370
|
-
throw
|
|
2518
|
+
throw discardPrivateFile(fd, file, fsx), e;
|
|
2371
2519
|
}
|
|
2372
2520
|
fsx.closeSync(fd);
|
|
2521
|
+
}
|
|
2522
|
+
function writeCredentialsAtomic(creds, file = credentialsPath(), fsx = import_node_fs2.default) {
|
|
2523
|
+
secureCredentialsDir(import_node_path.default.dirname(file), fsx);
|
|
2524
|
+
let temp = `${file}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
2525
|
+
fillPrivateFile(createPrivateFile(temp, fsx), temp, `${JSON.stringify(creds, null, 2)}
|
|
2526
|
+
`, fsx);
|
|
2373
2527
|
try {
|
|
2374
2528
|
fsx.renameSync(temp, file);
|
|
2375
2529
|
} catch (e) {
|
|
@@ -2440,29 +2594,8 @@ var UnreachableError, init_unreachable = __esm({ "src/core/unreachable.ts"() {
|
|
|
2440
2594
|
}
|
|
2441
2595
|
};
|
|
2442
2596
|
} });
|
|
2443
|
-
function
|
|
2444
|
-
|
|
2445
|
-
case "not-url":
|
|
2446
|
-
return MESSAGES.API_URL_NOT_URL(url);
|
|
2447
|
-
case "insecure":
|
|
2448
|
-
return MESSAGES.API_URL_NOT_HTTPS(url);
|
|
2449
|
-
case null:
|
|
2450
|
-
return null;
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
function assertApiUrl(url) {
|
|
2454
|
-
let refusal = apiUrlRefusal(url);
|
|
2455
|
-
if (refusal !== null) throw new Error(refusal);
|
|
2456
|
-
return url;
|
|
2457
|
-
}
|
|
2458
|
-
var init_api_url = __esm({ "src/core/api-url.ts"() {
|
|
2459
|
-
"use strict";
|
|
2460
|
-
init_define_IGNITIFY_CLIENT_FILES();
|
|
2461
|
-
init_http_client();
|
|
2462
|
-
init_constants();
|
|
2463
|
-
} });
|
|
2464
|
-
function resolveApiConfig(apiOverride, env = process.env, read = () => readCredentials(), note = noteEnvOverride) {
|
|
2465
|
-
let baseUrl = assertApiUrl(apiOverride ?? DEFAULTS.API_URL), envKey = env[ENV.KEY];
|
|
2597
|
+
function resolveApiConfig(env = process.env, read = () => readCredentials(), note = noteEnvOverride) {
|
|
2598
|
+
let baseUrl = assertApiUrl(apiUrlFor(env)), envKey = env[ENV.KEY];
|
|
2466
2599
|
if (envKey) return envOverridesLogin(baseUrl, readOrNone(read)) && note(MESSAGES.ENV_KEY_OVERRIDES_LOGIN), { baseUrl, token: envKey };
|
|
2467
2600
|
let saved = read();
|
|
2468
2601
|
if (saved.kind === "none") throw new Error(MESSAGES.NOT_SIGNED_IN);
|
|
@@ -2536,9 +2669,11 @@ async function postDeployment(cfg, taskId, manifest, tar, fetchImpl) {
|
|
|
2536
2669
|
let form = new FormData();
|
|
2537
2670
|
return form.set("task_id", taskId), form.set("manifest", JSON.stringify(manifest)), form.set("bundle", new Blob([new Uint8Array(tar)], { type: "application/x-tar" }), "bundle.tar"), await request(`${cfg.baseUrl}${TENANT_PATHS.DEPLOYMENTS}`, { method: "POST", headers: authHeaders(cfg), body: form, fetchImpl: viaApi(fetchImpl), timeoutMs: 3e5, ...readHeaders });
|
|
2538
2671
|
}
|
|
2539
|
-
function postRun(cfg, run2, fetchImpl) {
|
|
2540
|
-
let body = { deployment_id: run2.deploymentId, task_name: run2.taskName, payload: run2.payload, preset: run2.preset, timeout_sec: run2.timeoutSec, execution: run2.execution, at: run2.at };
|
|
2541
|
-
return apiPost(cfg, TENANT_PATHS.RUNS, body, fetchImpl)
|
|
2672
|
+
async function postRun(cfg, run2, fetchImpl) {
|
|
2673
|
+
let body = { deployment_id: run2.deploymentId, task_name: run2.taskName, payload: run2.payload, preset: run2.preset, timeout_sec: run2.timeoutSec, execution: run2.execution, at: run2.at }, replayed = false;
|
|
2674
|
+
return { answer: await apiPost(cfg, TENANT_PATHS.RUNS, body, fetchImpl, void 0, { headers: idempotencyHeaders(run2.idempotencyKey), onHeaders: (headers) => {
|
|
2675
|
+
replayed = isReplayedAnswer(headers);
|
|
2676
|
+
} }), replayed };
|
|
2542
2677
|
}
|
|
2543
2678
|
async function listSchedules(cfg, fetchImpl) {
|
|
2544
2679
|
return apiGet(cfg, TENANT_PATHS.SCHEDULES, fetchImpl);
|
|
@@ -2549,8 +2684,10 @@ function deleteSchedule(cfg, scheduleId, fetchImpl) {
|
|
|
2549
2684
|
function apiGet(cfg, path12, fetchImpl) {
|
|
2550
2685
|
return request(`${cfg.baseUrl}${path12}`, { headers: authHeaders(cfg), fetchImpl: viaApi(fetchImpl), ...readHeaders });
|
|
2551
2686
|
}
|
|
2552
|
-
function apiPost(cfg, path12, body, fetchImpl, timeoutMs) {
|
|
2553
|
-
return request(`${cfg.baseUrl}${path12}`, { method: "POST", headers: { ...authHeaders(cfg), "content-type": "application/json" }, body, fetchImpl: viaApi(fetchImpl), timeoutMs,
|
|
2687
|
+
function apiPost(cfg, path12, body, fetchImpl, timeoutMs, extras = {}) {
|
|
2688
|
+
return request(`${cfg.baseUrl}${path12}`, { method: "POST", headers: { ...authHeaders(cfg), "content-type": "application/json", ...extras.headers }, body, fetchImpl: viaApi(fetchImpl), timeoutMs, onHeaders: (headers) => {
|
|
2689
|
+
readHeaders.onHeaders(headers), extras.onHeaders?.(headers);
|
|
2690
|
+
} });
|
|
2554
2691
|
}
|
|
2555
2692
|
function apiDelete(cfg, path12, fetchImpl) {
|
|
2556
2693
|
return request(`${cfg.baseUrl}${path12}`, { method: "DELETE", headers: authHeaders(cfg), fetchImpl: viaApi(fetchImpl), ...readHeaders });
|
|
@@ -2564,6 +2701,15 @@ function getWhoami(cfg, fetchImpl) {
|
|
|
2564
2701
|
function getUsage(cfg, filters2 = {}, fetchImpl) {
|
|
2565
2702
|
return apiGet(cfg, `${TENANT_PATHS.USAGE}${buildQuery(filters2)}`, fetchImpl);
|
|
2566
2703
|
}
|
|
2704
|
+
function getUsageSeries(cfg, days, fetchImpl) {
|
|
2705
|
+
return apiGet(cfg, `${TENANT_PATHS.USAGE_SERIES}${daysQuery(days)}`, fetchImpl);
|
|
2706
|
+
}
|
|
2707
|
+
function getRunFailures(cfg, days, fetchImpl) {
|
|
2708
|
+
return apiGet(cfg, `${TENANT_PATHS.RUN_FAILURES}${daysQuery(days)}`, fetchImpl);
|
|
2709
|
+
}
|
|
2710
|
+
function listNotifications(cfg, params, fetchImpl) {
|
|
2711
|
+
return apiGet(cfg, `${TENANT_PATHS.NOTIFICATIONS}${buildQuery(params)}`, fetchImpl);
|
|
2712
|
+
}
|
|
2567
2713
|
function setSecret(cfg, name, value, fetchImpl) {
|
|
2568
2714
|
return apiPost(cfg, TENANT_PATHS.SECRETS, { name, value }, fetchImpl);
|
|
2569
2715
|
}
|
|
@@ -2585,6 +2731,52 @@ function deleteHook(cfg, hookId, fetchImpl) {
|
|
|
2585
2731
|
function setNotify(cfg, deploymentId, body, fetchImpl) {
|
|
2586
2732
|
return apiPut(cfg, TENANT_PATHS.DEPLOYMENT_NOTIFY(deploymentId), body, fetchImpl);
|
|
2587
2733
|
}
|
|
2734
|
+
function createUser(cfg, name, fetchImpl) {
|
|
2735
|
+
return apiPost(cfg, TENANT_PATHS.USERS, { name }, fetchImpl);
|
|
2736
|
+
}
|
|
2737
|
+
function listUsers(cfg, fetchImpl) {
|
|
2738
|
+
return apiGet(cfg, TENANT_PATHS.USERS, fetchImpl);
|
|
2739
|
+
}
|
|
2740
|
+
function getUser(cfg, name, fetchImpl) {
|
|
2741
|
+
return apiGet(cfg, TENANT_PATHS.USER_BY_ID(name), fetchImpl);
|
|
2742
|
+
}
|
|
2743
|
+
function deleteUser(cfg, name, fetchImpl) {
|
|
2744
|
+
return apiDelete(cfg, TENANT_PATHS.USER_BY_ID(name), fetchImpl);
|
|
2745
|
+
}
|
|
2746
|
+
function createGroup(cfg, name, permissions, fetchImpl) {
|
|
2747
|
+
return apiPost(cfg, TENANT_PATHS.GROUPS, { name, permissions }, fetchImpl);
|
|
2748
|
+
}
|
|
2749
|
+
function listGroups(cfg, fetchImpl) {
|
|
2750
|
+
return apiGet(cfg, TENANT_PATHS.GROUPS, fetchImpl);
|
|
2751
|
+
}
|
|
2752
|
+
function getGroup(cfg, name, fetchImpl) {
|
|
2753
|
+
return apiGet(cfg, TENANT_PATHS.GROUP_BY_ID(name), fetchImpl);
|
|
2754
|
+
}
|
|
2755
|
+
function deleteGroup(cfg, name, fetchImpl) {
|
|
2756
|
+
return apiDelete(cfg, TENANT_PATHS.GROUP_BY_ID(name), fetchImpl);
|
|
2757
|
+
}
|
|
2758
|
+
function grantGroupPermission(cfg, name, permission, fetchImpl) {
|
|
2759
|
+
return apiPut(cfg, TENANT_PATHS.GROUP_PERMISSION(name, permission), void 0, fetchImpl);
|
|
2760
|
+
}
|
|
2761
|
+
function revokeGroupPermission(cfg, name, permission, fetchImpl) {
|
|
2762
|
+
return apiDelete(cfg, TENANT_PATHS.GROUP_PERMISSION(name, permission), fetchImpl);
|
|
2763
|
+
}
|
|
2764
|
+
function addGroupUser(cfg, name, user, fetchImpl) {
|
|
2765
|
+
return apiPut(cfg, TENANT_PATHS.GROUP_USER(name, user), void 0, fetchImpl);
|
|
2766
|
+
}
|
|
2767
|
+
function removeGroupUser(cfg, name, user, fetchImpl) {
|
|
2768
|
+
return apiDelete(cfg, TENANT_PATHS.GROUP_USER(name, user), fetchImpl);
|
|
2769
|
+
}
|
|
2770
|
+
function createKey(cfg, body, fetchImpl) {
|
|
2771
|
+
return apiPost(cfg, TENANT_PATHS.KEYS, body, fetchImpl);
|
|
2772
|
+
}
|
|
2773
|
+
function listKeys(cfg, user, fetchImpl) {
|
|
2774
|
+
let q = buildQuery({ [KEY_LIST_QUERY.USER]: user });
|
|
2775
|
+
return apiGet(cfg, `${TENANT_PATHS.KEYS}${q}`, fetchImpl);
|
|
2776
|
+
}
|
|
2777
|
+
function deleteKey(cfg, keyId, fetchImpl) {
|
|
2778
|
+
return apiDelete(cfg, TENANT_PATHS.KEY_BY_ID(keyId), fetchImpl);
|
|
2779
|
+
}
|
|
2588
2780
|
async function postPackageAdvice(cfg, body, fetchImpl) {
|
|
2589
2781
|
try {
|
|
2590
2782
|
return { kind: "ok", response: await apiPost(cfg, TENANT_PATHS.PACKAGE_ADVICE, body, fetchImpl, ADVICE.TIMEOUT_MS) };
|
|
@@ -2632,46 +2824,192 @@ function getApiRun(cfg, runId, fetchImpl) {
|
|
|
2632
2824
|
function getApiRunLogs(cfg, runId, fetchImpl) {
|
|
2633
2825
|
return apiGet(cfg, TENANT_PATHS.RUN_LOGS(runId), fetchImpl);
|
|
2634
2826
|
}
|
|
2827
|
+
async function apiFollowGet(cfg, path12, fetchImpl) {
|
|
2828
|
+
let retryAfter = null;
|
|
2829
|
+
try {
|
|
2830
|
+
return { ok: true, data: await request(`${cfg.baseUrl}${path12}`, { headers: authHeaders(cfg), fetchImpl: viaApi(fetchImpl), onHeaders: (headers) => {
|
|
2831
|
+
readHeaders.onHeaders(headers), retryAfter = headers.get(RETRY_AFTER_HEADER);
|
|
2832
|
+
} }) };
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
return { ok: false, error, retryAfterMs: retryAfterMs(retryAfter, Date.now()) };
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
function readRunTimeline(cfg, runId, query, fetchImpl) {
|
|
2838
|
+
let q = buildQuery({ [OBS_QUERY.CURSOR]: query.cursor ?? void 0, [OBS_QUERY.WAIT_SEC]: query.waitSec === void 0 ? void 0 : String(query.waitSec) });
|
|
2839
|
+
return apiFollowGet(cfg, `${TENANT_PATHS.RUN_TIMELINE(runId)}${q}`, fetchImpl);
|
|
2840
|
+
}
|
|
2841
|
+
function readRunOutput(cfg, runId, fromOffset, fetchImpl) {
|
|
2842
|
+
return apiFollowGet(cfg, `${TENANT_PATHS.RUN_LOGS(runId)}${buildQuery({ [OBS_QUERY.FROM_OFFSET]: String(fromOffset) })}`, fetchImpl);
|
|
2843
|
+
}
|
|
2844
|
+
function readRunList(cfg, params = {}, fetchImpl) {
|
|
2845
|
+
return apiFollowGet(cfg, `${TENANT_PATHS.RUNS}${buildQuery(params)}`, fetchImpl);
|
|
2846
|
+
}
|
|
2847
|
+
function readRunForFollow(cfg, runId, fetchImpl) {
|
|
2848
|
+
return apiFollowGet(cfg, TENANT_PATHS.RUN_BY_ID(runId), fetchImpl);
|
|
2849
|
+
}
|
|
2635
2850
|
function deleteRun(cfg, runId, fetchImpl) {
|
|
2636
2851
|
return apiDelete(cfg, TENANT_PATHS.RUN_BY_ID(runId), fetchImpl);
|
|
2637
2852
|
}
|
|
2638
|
-
function cancelApiRun(cfg, runId, force = false, graceMs, fetchImpl) {
|
|
2639
|
-
let params = new URLSearchParams();
|
|
2640
|
-
force && params.set("force", "true"), graceMs !== void 0 && params.set("grace_ms", String(graceMs));
|
|
2641
|
-
let query = params.size > 0 ? `?${params}` : "";
|
|
2642
|
-
return apiPost(cfg, `${TENANT_PATHS.RUN_CANCEL(runId)}${query}`, void 0, fetchImpl);
|
|
2853
|
+
function cancelApiRun(cfg, runId, force = false, graceMs, fetchImpl) {
|
|
2854
|
+
let params = new URLSearchParams();
|
|
2855
|
+
force && params.set("force", "true"), graceMs !== void 0 && params.set("grace_ms", String(graceMs));
|
|
2856
|
+
let query = params.size > 0 ? `?${params}` : "";
|
|
2857
|
+
return apiPost(cfg, `${TENANT_PATHS.RUN_CANCEL(runId)}${query}`, void 0, fetchImpl);
|
|
2858
|
+
}
|
|
2859
|
+
async function listApiVms(cfg, params = {}, fetchImpl) {
|
|
2860
|
+
return apiGet(cfg, `${TENANT_PATHS.VMS}${buildQuery(params)}`, fetchImpl);
|
|
2861
|
+
}
|
|
2862
|
+
async function getApiVm(cfg, vmId, fetchImpl) {
|
|
2863
|
+
return apiGet(cfg, TENANT_PATHS.VM_BY_ID(vmId), fetchImpl);
|
|
2864
|
+
}
|
|
2865
|
+
function deleteVm(cfg, vmId, force = false, fetchImpl) {
|
|
2866
|
+
return apiDelete(cfg, `${TENANT_PATHS.VM_BY_ID(vmId)}${forceQuery(force)}`, fetchImpl);
|
|
2867
|
+
}
|
|
2868
|
+
var readHeaders, idempotencyHeaders, isReplayedAnswer, noteEnvOverride, versionHeader, BadAnswerError, daysQuery, init_api = __esm({ "src/io/api.ts"() {
|
|
2869
|
+
"use strict";
|
|
2870
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2871
|
+
init_http_client();
|
|
2872
|
+
init_output();
|
|
2873
|
+
init_constants();
|
|
2874
|
+
init_package_advice3();
|
|
2875
|
+
init_version();
|
|
2876
|
+
init_credentials();
|
|
2877
|
+
init_unreachable();
|
|
2878
|
+
init_api_url();
|
|
2879
|
+
init_tenant();
|
|
2880
|
+
readHeaders = { onHeaders: noteServerVersion }, idempotencyHeaders = (key) => key === void 0 ? {} : { [IDEMPOTENCY_KEY_HEADER]: key }, isReplayedAnswer = (headers) => headers.get(IDEMPOTENT_REPLAYED_HEADER) === "true";
|
|
2881
|
+
noteEnvOverride = /* @__PURE__ */ (() => {
|
|
2882
|
+
let noted = false;
|
|
2883
|
+
return (msg) => {
|
|
2884
|
+
noted || (noted = true, warn(msg));
|
|
2885
|
+
};
|
|
2886
|
+
})();
|
|
2887
|
+
versionHeader = { [CLI_VERSION_HEADER]: CLIENT_VERSION };
|
|
2888
|
+
BadAnswerError = class extends Error {
|
|
2889
|
+
};
|
|
2890
|
+
daysQuery = (days) => buildQuery({ [OBS_QUERY.DAYS]: days === null ? void 0 : String(days) });
|
|
2891
|
+
} });
|
|
2892
|
+
var byCreatedAtDesc, init_sort = __esm({ "src/utils/sort.ts"() {
|
|
2893
|
+
"use strict";
|
|
2894
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2895
|
+
byCreatedAtDesc = (rows) => [...rows].sort((a, b) => (b.created_at ?? "").localeCompare(a.created_at ?? ""));
|
|
2896
|
+
} });
|
|
2897
|
+
function parseWholeCount(raw) {
|
|
2898
|
+
if (!/^[1-9][0-9]*$/.test(raw)) return null;
|
|
2899
|
+
let n = Number(raw);
|
|
2900
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
2901
|
+
}
|
|
2902
|
+
function boolFlag(args, key) {
|
|
2903
|
+
return args.options[key] === true;
|
|
2904
|
+
}
|
|
2905
|
+
function requiredArg(args) {
|
|
2906
|
+
return args.positionals[0];
|
|
2907
|
+
}
|
|
2908
|
+
function requireArg(args, label) {
|
|
2909
|
+
let id = requiredArg(args);
|
|
2910
|
+
return id || (fail(MESSAGES.ARG_REQUIRED(label)), null);
|
|
2911
|
+
}
|
|
2912
|
+
var init_args2 = __esm({ "src/utils/args.ts"() {
|
|
2913
|
+
"use strict";
|
|
2914
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2915
|
+
init_constants();
|
|
2916
|
+
init_output();
|
|
2917
|
+
init_http_client();
|
|
2918
|
+
} });
|
|
2919
|
+
function traceLine(e) {
|
|
2920
|
+
return e instanceof HttpError && e.traceId ? MESSAGES.SERVER_TRACE_ID(e.traceId) : null;
|
|
2921
|
+
}
|
|
2922
|
+
function keyExpiredHint(e) {
|
|
2923
|
+
let refused2 = httpErrorJson(e);
|
|
2924
|
+
return refused2?.status === 401 && refused2.body.error === KEY_EXPIRED_ERROR ? MESSAGES.KEY_EXPIRED_HINT : null;
|
|
2643
2925
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2926
|
+
function printError(e) {
|
|
2927
|
+
fail(accessRefusalText(e) ?? errText(e), { hint: keyExpiredHint(e), trace: traceLine(e) });
|
|
2646
2928
|
}
|
|
2647
|
-
async function
|
|
2648
|
-
return
|
|
2929
|
+
async function guard(body) {
|
|
2930
|
+
return await guardValue(body) ?? 1;
|
|
2649
2931
|
}
|
|
2650
|
-
function
|
|
2651
|
-
|
|
2932
|
+
async function guardValue(body) {
|
|
2933
|
+
try {
|
|
2934
|
+
return await body();
|
|
2935
|
+
} catch (e) {
|
|
2936
|
+
printError(e);
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2652
2939
|
}
|
|
2653
|
-
var
|
|
2940
|
+
var init_guard = __esm({ "src/utils/guard.ts"() {
|
|
2654
2941
|
"use strict";
|
|
2655
2942
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
2656
2943
|
init_http_client();
|
|
2944
|
+
init_tenant();
|
|
2945
|
+
init_constants();
|
|
2946
|
+
init_output();
|
|
2947
|
+
init_access();
|
|
2948
|
+
} });
|
|
2949
|
+
var import_node_readline, ttyPrompter, init_prompt = __esm({ "src/io/prompt.ts"() {
|
|
2950
|
+
"use strict";
|
|
2951
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2952
|
+
import_node_readline = __toESM(require("node:readline"), 1);
|
|
2657
2953
|
init_output();
|
|
2954
|
+
ttyPrompter = { async confirm(question) {
|
|
2955
|
+
if (!process.stdin.isTTY) return fail("Refusing to proceed: not a TTY. Re-run with --yes to confirm non-interactively."), false;
|
|
2956
|
+
let rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
2957
|
+
try {
|
|
2958
|
+
let answer = await new Promise((resolve) => rl.question(`${question} [y/N] `, resolve));
|
|
2959
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
2960
|
+
} finally {
|
|
2961
|
+
rl.close();
|
|
2962
|
+
}
|
|
2963
|
+
} };
|
|
2964
|
+
} });
|
|
2965
|
+
async function confirmOrAbort(args, prompter = ttyPrompter, question = MESSAGES.CONFIRM_PROCEED) {
|
|
2966
|
+
return boolFlag(args, "yes") || await prompter.confirm(question) ? true : (say(MESSAGES.ABORTED), false);
|
|
2967
|
+
}
|
|
2968
|
+
var init_prompt_flow = __esm({ "src/utils/prompt-flow.ts"() {
|
|
2969
|
+
"use strict";
|
|
2970
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2971
|
+
init_prompt();
|
|
2658
2972
|
init_constants();
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2973
|
+
init_args2();
|
|
2974
|
+
init_manager();
|
|
2975
|
+
} });
|
|
2976
|
+
var REAL_CLOCK, init_clock = __esm({ "src/utils/clock.ts"() {
|
|
2977
|
+
"use strict";
|
|
2978
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2979
|
+
REAL_CLOCK = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), now: () => Date.now() };
|
|
2980
|
+
} });
|
|
2981
|
+
function snakeCaseKeys(value) {
|
|
2982
|
+
return Array.isArray(value) ? value.map(snakeCaseKeys) : typeof value != "object" || value === null ? value : Object.fromEntries(Object.entries(value).map(([k, v]) => [k.replace(/([a-z0-9])([A-Z])/g, (_, a, b) => `${a}_${b.toLowerCase()}`), snakeCaseKeys(v)]));
|
|
2983
|
+
}
|
|
2984
|
+
var init_snake_case = __esm({ "src/utils/snake-case.ts"() {
|
|
2985
|
+
"use strict";
|
|
2986
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2987
|
+
} });
|
|
2988
|
+
function nextPageNoteFor2(args, list2, rows) {
|
|
2989
|
+
return nextPageNoteFor(PROGRAM, args, { ...list2, flags: Object.keys(list2.flags) }, rows, effectivePageLimit(strOpt(args, "limit")));
|
|
2990
|
+
}
|
|
2991
|
+
function limitRefusal(args) {
|
|
2992
|
+
let raw = args.options.limit;
|
|
2993
|
+
return typeof raw == "string" ? pageLimitRefusal(raw) : null;
|
|
2994
|
+
}
|
|
2995
|
+
var PROGRAM, init_page2 = __esm({ "src/utils/page.ts"() {
|
|
2996
|
+
"use strict";
|
|
2997
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
2664
2998
|
init_tenant();
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2999
|
+
init_http_client();
|
|
3000
|
+
init_args2();
|
|
3001
|
+
PROGRAM = "ignitify";
|
|
3002
|
+
} });
|
|
3003
|
+
var init_utils = __esm({ "src/utils/index.ts"() {
|
|
3004
|
+
"use strict";
|
|
3005
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
3006
|
+
init_sort();
|
|
3007
|
+
init_args2();
|
|
3008
|
+
init_guard();
|
|
3009
|
+
init_prompt_flow();
|
|
3010
|
+
init_clock();
|
|
3011
|
+
init_snake_case();
|
|
3012
|
+
init_page2();
|
|
2675
3013
|
} });
|
|
2676
3014
|
function payloadRefusal(payload, limit) {
|
|
2677
3015
|
let bytes = payloadBytes(payload);
|
|
@@ -2685,8 +3023,19 @@ var payloadBytes, limitOrFallback, formatLimit, limitsNote, init_payload_limit =
|
|
|
2685
3023
|
payloadBytes = (payload) => Buffer.byteLength(JSON.stringify(payload) ?? "", "utf8"), limitOrFallback = (limit, fallback) => typeof limit == "number" && Number.isFinite(limit) && limit > 0 ? limit : fallback;
|
|
2686
3024
|
formatLimit = (bytes) => formatBytes(bytes).replace(/\.0 /, " "), limitsNote = () => MESSAGES.TASK_LIMITS(formatLimit(PAYLOAD_LIMIT_FALLBACK_BYTES), formatLimit(RESULT_LIMIT_FALLBACK_BYTES));
|
|
2687
3025
|
} });
|
|
2688
|
-
function apiConfig(
|
|
2689
|
-
return resolveApiConfig(
|
|
3026
|
+
function apiConfig() {
|
|
3027
|
+
return resolveApiConfig();
|
|
3028
|
+
}
|
|
3029
|
+
function accessNameArg(raw, kind, label) {
|
|
3030
|
+
if (raw === void 0 || raw === "") return fail(MESSAGES.ARG_REQUIRED(label)), null;
|
|
3031
|
+
let checked = checkAccessName(kind, raw);
|
|
3032
|
+
return checked.ok ? checked.value : (fail(checked.message), null);
|
|
3033
|
+
}
|
|
3034
|
+
function daysOption(args) {
|
|
3035
|
+
let raw = strOpt(args, "days");
|
|
3036
|
+
if (raw === void 0) return { ok: true, days: null };
|
|
3037
|
+
let days = parseWholeCount(raw);
|
|
3038
|
+
return days === null ? (fail(MESSAGES.DAYS_INVALID(raw)), { ok: false }) : { ok: true, days };
|
|
2690
3039
|
}
|
|
2691
3040
|
async function payloadLimit(cfg) {
|
|
2692
3041
|
let whoami = await getWhoami(cfg).catch(() => null);
|
|
@@ -2696,6 +3045,10 @@ function consoleLogText(bytes, out2) {
|
|
|
2696
3045
|
return out2.raw ? bytes : terminalSafe(bytes, out2.colour);
|
|
2697
3046
|
}
|
|
2698
3047
|
function printConsoleLog(data, raw, colour = outStyle().colour) {
|
|
3048
|
+
if (isJsonOutput()) {
|
|
3049
|
+
emit(data);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
2699
3052
|
if (writeRaw(consoleLogText(typeof data?.bytes == "string" ? data.bytes : "", { raw, colour })), data?.truncated === true) {
|
|
2700
3053
|
let lines = typeof data.lines == "number" ? data.lines : 0;
|
|
2701
3054
|
tell(`
|
|
@@ -2709,7 +3062,7 @@ function noteNextPage(args, list2, rows) {
|
|
|
2709
3062
|
async function runPagedList(args, verb) {
|
|
2710
3063
|
let badLimit = limitRefusal(args);
|
|
2711
3064
|
return badLimit !== null ? (fail(badLimit), 1) : guard(async () => {
|
|
2712
|
-
let data = await verb.fetchPage(apiConfig(
|
|
3065
|
+
let data = await verb.fetchPage(apiConfig(), { ...flagParams(args, verb.list.flags), ...verb.extra });
|
|
2713
3066
|
return verb.show(data), noteNextPage(args, verb.list, verb.rowsOf(data)), 0;
|
|
2714
3067
|
});
|
|
2715
3068
|
}
|
|
@@ -2718,19 +3071,20 @@ var init_shared = __esm({ "src/commands/shared.ts"() {
|
|
|
2718
3071
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
2719
3072
|
init_api();
|
|
2720
3073
|
init_payload_limit();
|
|
3074
|
+
init_access();
|
|
2721
3075
|
init_constants();
|
|
2722
3076
|
init_utils();
|
|
2723
3077
|
init_output();
|
|
2724
3078
|
} });
|
|
2725
3079
|
async function runWhoami(args) {
|
|
2726
3080
|
return guard(async () => {
|
|
2727
|
-
let cfg = apiConfig(
|
|
3081
|
+
let cfg = apiConfig();
|
|
2728
3082
|
return CliOutputManager.render("whoami", await getWhoami(cfg)), 0;
|
|
2729
3083
|
});
|
|
2730
3084
|
}
|
|
2731
3085
|
async function runUsage(args) {
|
|
2732
3086
|
return guard(async () => {
|
|
2733
|
-
let cfg = apiConfig(
|
|
3087
|
+
let cfg = apiConfig();
|
|
2734
3088
|
return CliOutputManager.render("quota", await getWhoami(cfg)), 0;
|
|
2735
3089
|
});
|
|
2736
3090
|
}
|
|
@@ -2752,14 +3106,16 @@ function decidePollError(e, failuresInARow, failingForMs) {
|
|
|
2752
3106
|
return isTransientError(e) ? failuresInARow >= POLL.ERROR.MIN_TRIES && failingForMs >= POLL.ERROR.GRACE_MS ? "give-up" : "retry" : "give-up";
|
|
2753
3107
|
}
|
|
2754
3108
|
async function pollUntil(loop) {
|
|
2755
|
-
let sleep = loop.clock?.sleep ?? REAL_CLOCK.sleep, now = loop.clock?.now ?? REAL_CLOCK.now, startedAt = now(), failures = 0, firstFailureAt = 0;
|
|
3109
|
+
let sleep = loop.clock?.sleep ?? REAL_CLOCK.sleep, now = loop.clock?.now ?? REAL_CLOCK.now, startedAt = now(), decideError = loop.decideError === void 0 ? fixedRetry(loop.retryDelayMs) : loop.decideError, failures = 0, firstFailureAt = 0;
|
|
2756
3110
|
for (let attempt = 0; ; ) {
|
|
2757
3111
|
let view;
|
|
2758
3112
|
try {
|
|
2759
3113
|
view = await loop.fetch();
|
|
2760
3114
|
} catch (error) {
|
|
2761
|
-
|
|
2762
|
-
|
|
3115
|
+
failures === 0 && (firstFailureAt = now()), failures += 1;
|
|
3116
|
+
let next = decideError(error, failures, now() - firstFailureAt);
|
|
3117
|
+
if (next.kind === "give-up") return { kind: "lost", error };
|
|
3118
|
+
next.counted || (failures = 0), loop.onRetry?.(error, next.delayMs), await sleep(next.delayMs);
|
|
2763
3119
|
continue;
|
|
2764
3120
|
}
|
|
2765
3121
|
failures = 0, loop.onView?.(view);
|
|
@@ -2768,7 +3124,7 @@ async function pollUntil(loop) {
|
|
|
2768
3124
|
loop.onWait?.(view, attempt), await sleep(action.delayMs), attempt += 1;
|
|
2769
3125
|
}
|
|
2770
3126
|
}
|
|
2771
|
-
var isWait, isLost, init_poll = __esm({ "src/core/poll.ts"() {
|
|
3127
|
+
var isWait, isLost, fixedRetry, init_poll = __esm({ "src/core/poll.ts"() {
|
|
2772
3128
|
"use strict";
|
|
2773
3129
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
2774
3130
|
init_http_client();
|
|
@@ -2776,6 +3132,7 @@ var isWait, isLost, init_poll = __esm({ "src/core/poll.ts"() {
|
|
|
2776
3132
|
init_constants();
|
|
2777
3133
|
init_utils();
|
|
2778
3134
|
isWait = (action) => action.kind === "wait", isLost = (end) => "kind" in end && end.kind === "lost";
|
|
3135
|
+
fixedRetry = (delayMs) => (error, failuresInARow, failingForMs) => decidePollError(error, failuresInARow, failingForMs) === "give-up" ? { kind: "give-up" } : { kind: "retry", delayMs, counted: true };
|
|
2779
3136
|
} });
|
|
2780
3137
|
function shortenHome(file, home) {
|
|
2781
3138
|
if (home === "" || !file.startsWith(home)) return file;
|
|
@@ -2867,7 +3224,7 @@ async function pollForKey(baseUrl, started, deadline, deps) {
|
|
|
2867
3224
|
}
|
|
2868
3225
|
async function runLogin(args, deps = {}) {
|
|
2869
3226
|
return guard(async () => {
|
|
2870
|
-
let clock = deps.clock ?? REAL_CLOCK, baseUrl = assertApiUrl(
|
|
3227
|
+
let clock = deps.clock ?? REAL_CLOCK, baseUrl = assertApiUrl(apiUrlFor(deps.env ?? process.env)), label = strOpt(args, "name") ?? (deps.hostname ?? import_node_os2.default.hostname)(), file = deps.file ?? credentialsPath(deps.env), started = await withRetries(baseUrl, clock, START_RETRY_MS, () => startDeviceLogin({ baseUrl }, { label, cli_version: CLIENT_VERSION }, deps.fetchImpl)).catch((e) => {
|
|
2871
3228
|
throw e instanceof HttpError && e.status === 429 ? new Error(MESSAGES.LOGIN_TOO_MANY, { cause: e }) : e;
|
|
2872
3229
|
}), env = deps.env ?? process.env, st = screenStyle(deps.screen, env), url = started.verification_uri_complete;
|
|
2873
3230
|
if (say(renderLoginStart(st, { code: formatUserCode(started.user_code), url })), say(""), url !== null) {
|
|
@@ -2935,7 +3292,7 @@ function selectLines(lines, runs, vms) {
|
|
|
2935
3292
|
}
|
|
2936
3293
|
async function runUsageShow(args) {
|
|
2937
3294
|
return guard(async () => {
|
|
2938
|
-
let report = await getUsage(apiConfig(
|
|
3295
|
+
let report = await getUsage(apiConfig(), filters(args));
|
|
2939
3296
|
if (boolFlag(args, "json")) return emit(report), 0;
|
|
2940
3297
|
let runs = boolFlag(args, "runs"), vms = boolFlag(args, "vms"), shown = { ...report, lines: selectLines(report.lines, runs, vms) };
|
|
2941
3298
|
return out(renderUsageReport(outStyle(), shown, { lines: runs || vms })), 0;
|
|
@@ -2945,10 +3302,18 @@ async function runUsageExport(args) {
|
|
|
2945
3302
|
return guard(async () => {
|
|
2946
3303
|
let json2 = boolFlag(args, "json"), csv = boolFlag(args, "csv");
|
|
2947
3304
|
if (json2 === csv) return tell(MESSAGES.USAGE_EXPORT_USAGE), 1;
|
|
2948
|
-
let report = await getUsage(apiConfig(
|
|
3305
|
+
let report = await getUsage(apiConfig(), filters(args));
|
|
2949
3306
|
return out(json2 ? JSON.stringify(report.lines, null, 2) : toCsv(report)), 0;
|
|
2950
3307
|
});
|
|
2951
3308
|
}
|
|
3309
|
+
async function runUsageSeries(args) {
|
|
3310
|
+
return guard(async () => {
|
|
3311
|
+
let days = daysOption(args);
|
|
3312
|
+
if (!days.ok) return 1;
|
|
3313
|
+
let series = await getUsageSeries(apiConfig(), days.days);
|
|
3314
|
+
return emit(series, (st) => renderUsageSeries(st, series)), 0;
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
2952
3317
|
var filters, init_usage_report = __esm({ "src/commands/usage-report.ts"() {
|
|
2953
3318
|
"use strict";
|
|
2954
3319
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
@@ -3001,8 +3366,10 @@ function parseRunOptions(args, config) {
|
|
|
3001
3366
|
if (onBusy !== void 0 && onBusy !== "queue" && onBusy !== "reject") return { ok: false, error: `--on-busy must be 'queue' or 'reject', got: ${onBusy}` };
|
|
3002
3367
|
let at = strOpt(args, "at");
|
|
3003
3368
|
if (at !== void 0 && Number.isNaN(new Date(at).getTime())) return { ok: false, error: `--at is not a valid ISO-8601 timestamp: ${at}` };
|
|
3369
|
+
let idempotencyKey = strOpt(args, "idempotency-key");
|
|
3370
|
+
if (idempotencyKey !== void 0 && !isValidIdempotencyKey(idempotencyKey)) return { ok: false, error: MESSAGES.IDEMPOTENCY_KEY_INVALID(idempotencyKey) };
|
|
3004
3371
|
let wait = boolFlag(args, "wait");
|
|
3005
|
-
return at !== void 0 && wait ? { ok: false, error: MESSAGES.RUN_AT_WITH_WAIT } : { ok: true, deploymentFlag: strOpt(args, "deployment"), wait, run: { payload: payload.value, preset: config.run?.preset, timeoutSec: timeoutOverride ?? config.run?.timeout_sec, execution: onBusy === void 0 ? config.execution : { ...config.execution ?? {}, on_busy: onBusy }, at } };
|
|
3372
|
+
return at !== void 0 && wait ? { ok: false, error: MESSAGES.RUN_AT_WITH_WAIT } : { ok: true, deploymentFlag: strOpt(args, "deployment"), wait, run: { payload: payload.value, preset: config.run?.preset, timeoutSec: timeoutOverride ?? config.run?.timeout_sec, execution: onBusy === void 0 ? config.execution : { ...config.execution ?? {}, on_busy: onBusy }, at, idempotencyKey } };
|
|
3006
3373
|
}
|
|
3007
3374
|
var VALUE_FLAGS, init_run_options = __esm({ "src/core/run-options.ts"() {
|
|
3008
3375
|
"use strict";
|
|
@@ -9015,6 +9382,71 @@ var import_fs3, import_path3, init_scaffold = __esm({ "src/scaffold/index.ts"()
|
|
|
9015
9382
|
init_templates();
|
|
9016
9383
|
init_vendor();
|
|
9017
9384
|
} });
|
|
9385
|
+
function replyOrThrow(reply) {
|
|
9386
|
+
if (!reply.ok) throw new FollowReadError(reply.error, reply.retryAfterMs);
|
|
9387
|
+
return reply.data;
|
|
9388
|
+
}
|
|
9389
|
+
function followStopOf(e) {
|
|
9390
|
+
let code = refusalCode(e);
|
|
9391
|
+
return code === OBS_REFUSAL.AGENT_OUTDATED_CURSOR.error ? "agent-outdated" : code === OBS_REFUSAL.NEVER_RAN.error ? "not-started" : "error";
|
|
9392
|
+
}
|
|
9393
|
+
function decidePacedRetry(e, retryAfterMs2, failuresInARow, failingForMs, fallbackMs) {
|
|
9394
|
+
return decidePollError(e, failuresInARow, failingForMs) === "give-up" ? { kind: "give-up" } : { kind: "retry", delayMs: isBusyAnswer(e) ? retryAfterMs2 ?? fallbackMs : fallbackMs, counted: true };
|
|
9395
|
+
}
|
|
9396
|
+
function decideBusyReadError(e, retryAfterMs2, failuresInARow, failingForMs) {
|
|
9397
|
+
return !isBusyAnswer(e) || failuresInARow > BUSY_READ.MAX_RETRIES ? { kind: "give-up" } : decidePacedRetry(e, retryAfterMs2, failuresInARow, failingForMs, FOLLOW.RETRY_FALLBACK_MS);
|
|
9398
|
+
}
|
|
9399
|
+
function decideFollowError(e, retryAfterMs2, failuresInARow, failingForMs) {
|
|
9400
|
+
return followStopOf(e) !== "error" ? { kind: "give-up" } : isWaitRefused(e) ? { kind: "retry", delayMs: retryAfterMs2 ?? FOLLOW.RETRY_FALLBACK_MS, counted: false } : decidePacedRetry(e, retryAfterMs2, failuresInARow, failingForMs, FOLLOW.RETRY_FALLBACK_MS);
|
|
9401
|
+
}
|
|
9402
|
+
function placeOutputPage(fromOffset, reply) {
|
|
9403
|
+
let next = reply.next_offset, bytes = typeof reply.bytes == "string" ? reply.bytes : "";
|
|
9404
|
+
return !isOffset(next) || next < fromOffset || bytes !== "" && next === fromOffset ? { kind: "no-cursor" } : { kind: "page", bytes, nextOffset: next, full: reply.truncated === true && next > fromOffset };
|
|
9405
|
+
}
|
|
9406
|
+
function decideOutputStep(page, endedAt, readAt) {
|
|
9407
|
+
return page.full ? "read-now" : page.bytes === "" && endedAt !== null && readAt - endedAt >= FOLLOW.IDLE_MS ? "stop" : "wait";
|
|
9408
|
+
}
|
|
9409
|
+
function timelineDone(reply, follow, held) {
|
|
9410
|
+
return reply.complete ? true : !follow && nextCursor(held, reply) === held;
|
|
9411
|
+
}
|
|
9412
|
+
function eventsToPrint(page, printedTail) {
|
|
9413
|
+
let fresh = (e) => !printedTail.has(e.id), tail = page.tail.filter(fresh), settledIds = new Set(page.events.map((e) => e.id));
|
|
9414
|
+
return { print: [...page.events.filter(fresh), ...tail], printedTail: new Set([...printedTail, ...tail.map((e) => e.id)].filter((id) => !settledIds.has(id))) };
|
|
9415
|
+
}
|
|
9416
|
+
function lastLines(text, n) {
|
|
9417
|
+
if (n <= 0) return "";
|
|
9418
|
+
let at = text.endsWith(`
|
|
9419
|
+
`) ? text.length - 1 : text.length;
|
|
9420
|
+
for (let seen = 0; seen < n; seen += 1) {
|
|
9421
|
+
let nl = at > 0 ? text.lastIndexOf(`
|
|
9422
|
+
`, at - 1) : -1;
|
|
9423
|
+
if (nl < 0) return text;
|
|
9424
|
+
at = nl;
|
|
9425
|
+
}
|
|
9426
|
+
return text.slice(at + 1);
|
|
9427
|
+
}
|
|
9428
|
+
var FollowReadError, isOffset, refusalCode, isWaitRefused, isBusyAnswer, nextCursor, idleRestMs, init_follow = __esm({ "src/core/follow.ts"() {
|
|
9429
|
+
"use strict";
|
|
9430
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
9431
|
+
init_http_client();
|
|
9432
|
+
init_tenant();
|
|
9433
|
+
init_constants();
|
|
9434
|
+
init_poll();
|
|
9435
|
+
FollowReadError = class extends Error {
|
|
9436
|
+
error;
|
|
9437
|
+
retryAfterMs;
|
|
9438
|
+
constructor(error, retryAfterMs2) {
|
|
9439
|
+
super(error instanceof Error ? error.message : String(error)), this.name = "FollowReadError", this.error = error, this.retryAfterMs = retryAfterMs2;
|
|
9440
|
+
}
|
|
9441
|
+
};
|
|
9442
|
+
isOffset = (v) => typeof v == "number" && Number.isSafeInteger(v) && v >= 0, refusalCode = (e) => {
|
|
9443
|
+
let code = httpErrorJson(e)?.body.error;
|
|
9444
|
+
return typeof code == "string" ? code : null;
|
|
9445
|
+
}, isWaitRefused = (e) => refusalCode(e) === OBS_REFUSAL.WAIT_REFUSED.error;
|
|
9446
|
+
isBusyAnswer = (e) => e instanceof HttpError && (e.status === 429 || e.status === 503);
|
|
9447
|
+
nextCursor = (held, reply) => reply.cursor ?? held;
|
|
9448
|
+
idleRestMs = (startedAt, now) => Math.max(0, FOLLOW.IDLE_MS - (now - startedAt));
|
|
9449
|
+
} });
|
|
9018
9450
|
function decidePoll(status, elapsedMs, attempt, failureReason) {
|
|
9019
9451
|
return status === READY ? { kind: "ready" } : status === FAILED ? { kind: "failed", reason: failureReason ?? "the build failed without recording a reason" } : status !== BUILDING ? { kind: "failed", reason: `deployment entered an unexpected status: ${status}` } : elapsedMs >= POLL.DEPLOY.TIMEOUT_MS ? { kind: "timeout" } : { kind: "wait", delayMs: nextDelayMs(attempt) };
|
|
9020
9452
|
}
|
|
@@ -9041,14 +9473,19 @@ var BUILDING, READY, FAILED, init_deploy_poll = __esm({ "src/core/deploy-poll.ts
|
|
|
9041
9473
|
function decideRunPoll(status, elapsedMs, attempt) {
|
|
9042
9474
|
return isTerminalRunStatus(status) ? { kind: "settled", status } : elapsedMs >= POLL.RUN.TIMEOUT_MS ? { kind: "timeout", status } : { kind: "wait", delayMs: backoffDelayMs(attempt, POLL.RUN) };
|
|
9043
9475
|
}
|
|
9476
|
+
function decideRunPollError(e, failuresInARow, failingForMs) {
|
|
9477
|
+
let [error, retryAfterMs2] = e instanceof FollowReadError ? [e.error, e.retryAfterMs] : [e, null];
|
|
9478
|
+
return decidePacedRetry(error, retryAfterMs2, failuresInARow, failingForMs, POLL.RUN.MAX_MS);
|
|
9479
|
+
}
|
|
9044
9480
|
async function pollUntilSettled(deps) {
|
|
9045
|
-
let statusOf = (run2) => run2.status ?? "UNKNOWN", end = await pollUntil({ fetch: deps.fetchRun,
|
|
9046
|
-
return isLost(end) ? end : { action: end.action, run: end.view };
|
|
9481
|
+
let statusOf = (run2) => run2.status ?? "UNKNOWN", end = await pollUntil({ fetch: deps.fetchRun, decideError: decideRunPollError, onRetry: deps.onRetry, decide: (run2, elapsedMs, attempt) => decideRunPoll(statusOf(run2), elapsedMs, attempt), onView: (run2) => deps.onStatus?.(statusOf(run2)), clock: deps });
|
|
9482
|
+
return isLost(end) ? end.error instanceof FollowReadError ? { kind: "lost", error: end.error.error } : end : { action: end.action, run: end.view };
|
|
9047
9483
|
}
|
|
9048
9484
|
var init_run_poll = __esm({ "src/core/run-poll.ts"() {
|
|
9049
9485
|
"use strict";
|
|
9050
9486
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
9051
9487
|
init_constants();
|
|
9488
|
+
init_follow();
|
|
9052
9489
|
init_poll();
|
|
9053
9490
|
} });
|
|
9054
9491
|
async function waitForBuild(cfg, deploymentId, progress, reused = false) {
|
|
@@ -9076,7 +9513,7 @@ ${st.bold(LABELS.BUILD_LOG)}
|
|
|
9076
9513
|
return built.kind === "timeout" ? (progress.fail(WAIT.BUILD_TIMEOUT), fail(WAIT.BUILD_TIMEOUT_DETAIL(deploymentId)), 1) : (progress.succeed(reused && !building ? WAIT.REUSED : WAIT.BUILT), 0);
|
|
9077
9514
|
}
|
|
9078
9515
|
async function waitForRun(cfg, runId) {
|
|
9079
|
-
let progress = startProgress(WAIT.RUN_SUBMITTED(runId)), lastStatus, settled = await withInterruptNotice(progress, () => WAIT.INTERRUPTED_RUN(runId), () => pollUntilSettled({ fetchRun: () =>
|
|
9516
|
+
let progress = startProgress(WAIT.RUN_SUBMITTED(runId)), lastStatus, settled = await withInterruptNotice(progress, () => WAIT.INTERRUPTED_RUN(runId), () => pollUntilSettled({ fetchRun: async () => replyOrThrow(await readRunForFollow(cfg, runId)), onStatus: (status) => {
|
|
9080
9517
|
lastStatus = status, progress.update(WAIT.RUN(runId, status), WAIT.HINT_RUN);
|
|
9081
9518
|
}, onRetry: () => progress.update(lastStatus === void 0 ? WAIT.RUN_SUBMITTED(runId) : WAIT.RUN(runId, lastStatus), WAIT.HINT_RETRYING) }));
|
|
9082
9519
|
if (isLost(settled)) return progress.fail(WAIT.LOST), fail(WAIT.LOST_RUN_DETAIL(runId, errText(settled.error))), 1;
|
|
@@ -9089,6 +9526,7 @@ var init_wait = __esm({ "src/commands/wait.ts"() {
|
|
|
9089
9526
|
"use strict";
|
|
9090
9527
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
9091
9528
|
init_api();
|
|
9529
|
+
init_follow();
|
|
9092
9530
|
init_deploy_poll();
|
|
9093
9531
|
init_run_poll();
|
|
9094
9532
|
init_poll();
|
|
@@ -13318,7 +13756,7 @@ async function runIntegrationAdd(args, cwd = process.cwd(), deps = REAL_INTEGRAT
|
|
|
13318
13756
|
if (!task) return 1;
|
|
13319
13757
|
if (printWarnings([...task.warnings]), task.config.runtime !== INTEGRATION.RUNTIME) return fail(INTEGRATION.NOT_NODE(task.config.runtime)), 1;
|
|
13320
13758
|
let taskDir = import_node_fs9.default.realpathSync(task.taskDir);
|
|
13321
|
-
if (readTaskPackageJson(taskDir) === null) return fail(INTEGRATION.NO_PACKAGE_JSON(taskDir)), 1;
|
|
13759
|
+
if (readTaskPackageJson(taskDir) === null) return fail(INTEGRATION.NO_PACKAGE_JSON(task.taskDir)), 1;
|
|
13322
13760
|
let pkg = packageName(name);
|
|
13323
13761
|
return guard(() => withPackDir((packDir) => addIntegration(args, taskDir, task.config, pkg, packDir, dryRun, deps)));
|
|
13324
13762
|
}
|
|
@@ -13400,7 +13838,7 @@ async function printPackageAdvice(args, config, fetchImpl) {
|
|
|
13400
13838
|
let plan = planAdvice(config);
|
|
13401
13839
|
if (plan.kind === "skip") return say(ADVICE.SKIPPED(plan.reason));
|
|
13402
13840
|
try {
|
|
13403
|
-
let outcome = await postPackageAdvice(apiConfig(
|
|
13841
|
+
let outcome = await postPackageAdvice(apiConfig(), plan.request, fetchImpl), { lines, fault } = renderAdvice(outcome, screenStyle());
|
|
13404
13842
|
for (let line of lines) (fault ? tell : say)(line);
|
|
13405
13843
|
} catch (e) {
|
|
13406
13844
|
fail(errText(e));
|
|
@@ -13413,12 +13851,12 @@ async function runTaskValidate(args, cwd = process.cwd(), fetchImpl) {
|
|
|
13413
13851
|
async function runTaskGet(args, cwd = process.cwd()) {
|
|
13414
13852
|
let task = await prepareTask(args, cwd);
|
|
13415
13853
|
if (!task) return 1;
|
|
13416
|
-
let { slug: slug2 } = task, state = readState(task.taskDir), check = checkSourceUpload(task.taskDir), localContentHash = check.ok ? check.source.contentHash : null, cfg = offlineTolerantConfig(
|
|
13854
|
+
let { slug: slug2 } = task, state = readState(task.taskDir), check = checkSourceUpload(task.taskDir), localContentHash = check.ok ? check.source.contentHash : null, cfg = offlineTolerantConfig(), orNull = (p) => p.catch(() => null), [whoami, deploymentsRes, runsRes] = cfg ? await Promise.all([orNull(getWhoami(cfg)), orNull(listApiDeploymentsForTask(cfg, slug2, void 0, { limit: TASK_REPORT_PAGE })), orNull(listApiRuns(cfg, { task_name: slug2, limit: String(TASK_REPORT_PAGE) }))]) : [null, null, null], report = buildTaskReport({ slug: slug2, declared: task.config.execution, declaredPreset: task.config.run?.preset, stateDeploymentId: state.deployment_id ?? null, localContentHash, deployments: deploymentsRes?.deployments ?? null, runs: runsRes?.runs ?? null, pageLimit: TASK_REPORT_PAGE, usage: whoami?.usage ?? null });
|
|
13417
13855
|
return emit(snakeCaseKeys(report), () => formatTaskReport(report)), 0;
|
|
13418
13856
|
}
|
|
13419
|
-
function offlineTolerantConfig(
|
|
13857
|
+
function offlineTolerantConfig() {
|
|
13420
13858
|
try {
|
|
13421
|
-
return apiConfig(
|
|
13859
|
+
return apiConfig();
|
|
13422
13860
|
} catch (e) {
|
|
13423
13861
|
return warn(RESULT_MESSAGES.TASK_GET_OFFLINE(errText(e))), null;
|
|
13424
13862
|
}
|
|
@@ -13436,8 +13874,8 @@ async function runTaskPack(args, cwd = process.cwd()) {
|
|
|
13436
13874
|
return CliOutputManager.render("task:pack", { task_id: config.slug, files: entries.map((e) => ({ path: e.path, size: e.size })), total_bytes: totalBytes, ignored, content_hash: contentHash }), 0;
|
|
13437
13875
|
}
|
|
13438
13876
|
async function runTaskDeploy(args, cwd = process.cwd()) {
|
|
13439
|
-
let taskDir = getTaskDir(args, cwd);
|
|
13440
|
-
if (!taskDir) return 1;
|
|
13877
|
+
let taskDir = getTaskDir(args, cwd), { taskId, dirFlag } = getTaskPath(args, cwd);
|
|
13878
|
+
if (!taskDir || !taskId) return 1;
|
|
13441
13879
|
let prep = args.options.check ? checkSourceUpload(taskDir) : prepareSourceUpload(taskDir);
|
|
13442
13880
|
if (printWarnings([...prep.warnings]), !prep.ok) return printErrors([...prep.errors]), 1;
|
|
13443
13881
|
let left = "upload" in prep ? prep.upload.ignored : prep.source.ignored;
|
|
@@ -13446,9 +13884,9 @@ async function runTaskDeploy(args, cwd = process.cwd()) {
|
|
|
13446
13884
|
return emit({ task_id: config.slug, valid: true, total_bytes: totalBytes, content_hash: contentHash }, (st) => successLine(st, VALIDATE.VALID)), 0;
|
|
13447
13885
|
}
|
|
13448
13886
|
let state = readState(taskDir);
|
|
13449
|
-
if (!state.task_id) return fail(
|
|
13887
|
+
if (!state.task_id) return fail(MESSAGES.TASK_NOT_REGISTERED(taskId, dirFlag)), 1;
|
|
13450
13888
|
let { upload } = prep;
|
|
13451
|
-
return uploadAndWait(apiConfig(
|
|
13889
|
+
return uploadAndWait(apiConfig(), taskDir, state.task_id, upload.manifest, upload.tar, upload.slug);
|
|
13452
13890
|
}
|
|
13453
13891
|
async function uploadAndWait(cfg, taskDir, taskId, manifest, tar, slug2) {
|
|
13454
13892
|
let progress = startProgress(WAIT.UPLOADING(slug2, formatBytes(tar.length))), res, whoami;
|
|
@@ -13467,7 +13905,7 @@ async function runTaskRun(args, cwd = process.cwd()) {
|
|
|
13467
13905
|
printWarnings([...task.warnings]);
|
|
13468
13906
|
let opts = parseRunOptions(args, task.config);
|
|
13469
13907
|
if (!opts.ok) return fail(opts.error), 1;
|
|
13470
|
-
let { run: run2 } = opts, taskName = task.slug, cfg = apiConfig(
|
|
13908
|
+
let { run: run2 } = opts, taskName = task.slug, cfg = apiConfig();
|
|
13471
13909
|
if (payloadBytes(run2.payload) > PAYLOAD_LIMIT_FALLBACK_BYTES) {
|
|
13472
13910
|
let refusal = payloadRefusal(run2.payload, await payloadLimit(cfg));
|
|
13473
13911
|
if (refusal !== null) return fail(refusal), 1;
|
|
@@ -13480,17 +13918,21 @@ async function runTaskRun(args, cwd = process.cwd()) {
|
|
|
13480
13918
|
if (refusal !== null) return fail(refusal), 1;
|
|
13481
13919
|
}
|
|
13482
13920
|
return resolved.source === "server" && say(`Using deployment ${deploymentId} (resolved from the server \u2014 no local state).`), await guard(async () => {
|
|
13483
|
-
let
|
|
13921
|
+
let posted;
|
|
13484
13922
|
try {
|
|
13485
|
-
|
|
13923
|
+
posted = await postRun(cfg, { deploymentId, taskName, ...run2 });
|
|
13486
13924
|
} catch (e) {
|
|
13487
13925
|
if (isJsonOutput()) throw e;
|
|
13488
13926
|
let trace = traceLine(e);
|
|
13489
13927
|
if (isPayloadTooLarge(e)) return fail(MESSAGES.PAYLOAD_REFUSED_BY_SERVER(await payloadLimit(cfg)), { trace }), 1;
|
|
13928
|
+
let keyed = run2.idempotencyKey === void 0 ? null : idempotencyRefusal(e);
|
|
13929
|
+
if (keyed) return fail(keyed.error, { hint: keyed.hint, trace }), 1;
|
|
13490
13930
|
let explained = explainRunRefusal(e) ?? explainLimitRefusal(e);
|
|
13491
13931
|
if (!explained) throw e;
|
|
13492
13932
|
return tell(explained), trace !== null && tell(errStyle().dim(trace)), 1;
|
|
13493
13933
|
}
|
|
13934
|
+
let res = posted.answer;
|
|
13935
|
+
posted.replayed && warn(MESSAGES.RUN_REPLAYED);
|
|
13494
13936
|
let answer = classifyRunAnswer(res, run2.at, opts.wait);
|
|
13495
13937
|
switch (answer.kind) {
|
|
13496
13938
|
case "skew-ran-now":
|
|
@@ -13532,7 +13974,7 @@ async function runTaskCreate(args, cwd = process.cwd()) {
|
|
|
13532
13974
|
return err2 ? (fail(err2), 1) : (emit({ slug: taskId, runtime: lang, local_dir: taskDir, registered: false }, (st) => successLine(st, RESULT_MESSAGES.TASK_CREATED_LOCAL(lang, taskId, taskDir))), say(screenStyle().dim(limitsNote())), 0);
|
|
13533
13975
|
}
|
|
13534
13976
|
return guard(async () => {
|
|
13535
|
-
let cfg = apiConfig(
|
|
13977
|
+
let cfg = apiConfig(), res = await createTask(cfg, taskId);
|
|
13536
13978
|
writeState(taskDir, { task_id: res.task_id, task_resource_id: res.resource_id });
|
|
13537
13979
|
let local_dir;
|
|
13538
13980
|
if (!args.options["no-scaffold"]) {
|
|
@@ -13549,11 +13991,11 @@ async function runTaskDelete(args, cwd = process.cwd(), prompter = ttyPrompter)
|
|
|
13549
13991
|
let id = requiredArg(args);
|
|
13550
13992
|
if (!id) return fail(MESSAGES.TASK_ID_REQUIRED), 1;
|
|
13551
13993
|
let plan = await guardValue(async () => {
|
|
13552
|
-
let baseDir = tasksBaseDir(args, cwd), cfg = apiConfig(
|
|
13994
|
+
let baseDir = tasksBaseDir(args, cwd), cfg = apiConfig(), { taskId, runs, deploymentCount, runCount } = await surveyTaskContents(cfg, id), folders = readTaskFolders(baseDir);
|
|
13553
13995
|
return planTaskDeletion({ taskSlug: id, taskId, runs, deploymentCount, runCount, folders });
|
|
13554
13996
|
});
|
|
13555
13997
|
return plan === void 0 || (printDeletionPlan(plan), !await confirmOrAbort(args, prompter)) ? 1 : guard(async () => {
|
|
13556
|
-
let cfg = apiConfig(
|
|
13998
|
+
let cfg = apiConfig(), d = (await deleteTask(cfg, id)).deleted;
|
|
13557
13999
|
d ? sayOk(`${MESSAGES.TASK_DELETED}: ${d.task_id} (${deletedCounts([["deployments", d.deployments], ["runs", d.runs], ["vms", d.vms], ["forfeited pins", d.forfeited_pins]])})`) : sayOk(MESSAGES.TASK_DELETED);
|
|
13558
14000
|
for (let dir of plan.unlinkDirs) {
|
|
13559
14001
|
let link = taskLinkPath(dir);
|
|
@@ -13599,18 +14041,18 @@ function runDeploymentList(args) {
|
|
|
13599
14041
|
async function runDeploymentGet(args) {
|
|
13600
14042
|
let id = requireArg(args, "deployment-id");
|
|
13601
14043
|
return id ? guard(async () => {
|
|
13602
|
-
let cfg = apiConfig(
|
|
14044
|
+
let cfg = apiConfig();
|
|
13603
14045
|
return CliOutputManager.render("deployment:get", await getApiDeployment(cfg, id)), 0;
|
|
13604
14046
|
}) : 1;
|
|
13605
14047
|
}
|
|
13606
14048
|
async function runDeploymentLogs(args) {
|
|
13607
14049
|
let id = requireArg(args, "deployment-id");
|
|
13608
|
-
return id ? guard(async () => (printConsoleLog(await getApiDeploymentLogs(apiConfig(
|
|
14050
|
+
return id ? guard(async () => (printConsoleLog(await getApiDeploymentLogs(apiConfig(), id), boolFlag(args, "raw")), 0)) : 1;
|
|
13609
14051
|
}
|
|
13610
14052
|
async function runDeploymentDelete(args, prompter = ttyPrompter) {
|
|
13611
14053
|
let id = requireArg(args, "deployment-id");
|
|
13612
14054
|
return id ? guard(async () => {
|
|
13613
|
-
let cfg = apiConfig(
|
|
14055
|
+
let cfg = apiConfig();
|
|
13614
14056
|
if (say(RESULT_MESSAGES.DEPLOYMENT_DELETE_PREVIEW(id)), !await confirmOrAbort(args, prompter)) return 1;
|
|
13615
14057
|
let res = await deleteDeployment(cfg, id);
|
|
13616
14058
|
emit(res);
|
|
@@ -13639,27 +14081,122 @@ var WHOLE_MS, init_grace = __esm({ "src/core/grace.ts"() {
|
|
|
13639
14081
|
init_constants();
|
|
13640
14082
|
WHOLE_MS = /^\d+$/;
|
|
13641
14083
|
} });
|
|
14084
|
+
async function readWithRetry(read, clock) {
|
|
14085
|
+
let wait = true, end = await pollUntil({ fetch: async () => replyOrThrow(await read(wait)), decideError: (e, failures, failingForMs) => e instanceof FollowReadError ? decideFollowError(e.error, e.retryAfterMs, failures, failingForMs) : { kind: "give-up" }, onRetry: (e) => {
|
|
14086
|
+
e instanceof FollowReadError && isWaitRefused(e.error) && (wait = false);
|
|
14087
|
+
}, decide: () => ({ kind: "read" }), clock });
|
|
14088
|
+
if (!isLost(end)) return { ok: true, data: end.view };
|
|
14089
|
+
let error = end.error instanceof FollowReadError ? end.error.error : end.error, stop = followStopOf(error);
|
|
14090
|
+
return { ok: false, failure: stop === "agent-outdated" ? { kind: stop } : { kind: stop, error } };
|
|
14091
|
+
}
|
|
14092
|
+
async function readBusyRetried(read, clock, onRetry = () => {
|
|
14093
|
+
}) {
|
|
14094
|
+
let end = await pollUntil({ fetch: async () => replyOrThrow(await read()), decideError: (e, failures, failingForMs) => e instanceof FollowReadError ? decideBusyReadError(e.error, e.retryAfterMs, failures, failingForMs) : { kind: "give-up" }, onRetry: (_e, delayMs) => onRetry(delayMs), decide: () => ({ kind: "read" }), clock });
|
|
14095
|
+
if (!isLost(end)) return end.view;
|
|
14096
|
+
throw end.error instanceof FollowReadError ? end.error.error : end.error;
|
|
14097
|
+
}
|
|
14098
|
+
async function readTimeline(loop) {
|
|
14099
|
+
let cursor = null;
|
|
14100
|
+
for (; ; ) {
|
|
14101
|
+
let held = cursor, startedAt = loop.clock.now(), read = await readWithRetry((wait) => loop.read(held, loop.follow && wait), loop.clock);
|
|
14102
|
+
if (!read.ok) return { ok: false, failure: read.failure };
|
|
14103
|
+
if (loop.onPage(read.data), cursor = nextCursor(held, read.data), timelineDone(read.data, loop.follow, held)) return { ok: true, last: read.data };
|
|
14104
|
+
if (loop.follow && read.data.events.length === 0) {
|
|
14105
|
+
let rest = idleRestMs(startedAt, loop.clock.now());
|
|
14106
|
+
rest > 0 && await loop.clock.sleep(rest);
|
|
14107
|
+
}
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
async function readOutput(loop) {
|
|
14111
|
+
let offset = 0, held = loop.tail === null ? null : "", runId = "", checkRun = loop.follow, endedAt = null;
|
|
14112
|
+
for (; ; ) {
|
|
14113
|
+
if (checkRun && endedAt === null) {
|
|
14114
|
+
let run2 = await readWithRetry(loop.readRun, loop.clock);
|
|
14115
|
+
if (!run2.ok) return { ok: false, failure: run2.failure };
|
|
14116
|
+
isTerminalRunStatus(run2.data.status) && (endedAt = loop.clock.now());
|
|
14117
|
+
}
|
|
14118
|
+
let from = offset, readAt = loop.clock.now(), read = await readWithRetry(() => loop.readPage(from), loop.clock);
|
|
14119
|
+
if (!read.ok && !(loop.follow && read.failure.kind === "not-started")) return { ok: false, failure: read.failure };
|
|
14120
|
+
let placed = read.ok ? placeOutputPage(from, read.data) : { kind: "page", bytes: "", nextOffset: from, full: false };
|
|
14121
|
+
if (placed.kind === "no-cursor") return { ok: false, failure: { kind: "no-cursor" } };
|
|
14122
|
+
if (read.ok && (runId = read.data.run_id), offset = placed.nextOffset, held !== null && loop.tail !== null ? (held = lastLines(held + placed.bytes, loop.tail), placed.full || (held !== "" && loop.onChunk({ runId, bytes: held, nextOffset: offset }), held = null)) : placed.bytes !== "" && loop.onChunk({ runId, bytes: placed.bytes, nextOffset: offset }), !loop.follow) {
|
|
14123
|
+
if (placed.full) continue;
|
|
14124
|
+
return { ok: true };
|
|
14125
|
+
}
|
|
14126
|
+
let step = decideOutputStep(placed, endedAt, readAt);
|
|
14127
|
+
if (step === "stop") return { ok: true };
|
|
14128
|
+
checkRun = step === "wait", step === "wait" && await loop.clock.sleep(FOLLOW.IDLE_MS);
|
|
14129
|
+
}
|
|
14130
|
+
}
|
|
14131
|
+
var init_run_follow = __esm({ "src/commands/run-follow.ts"() {
|
|
14132
|
+
"use strict";
|
|
14133
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14134
|
+
init_constants();
|
|
14135
|
+
init_follow();
|
|
14136
|
+
init_poll();
|
|
14137
|
+
} });
|
|
13642
14138
|
function runRunList(args) {
|
|
13643
|
-
return runPagedList(args, { list: { command: COMMANDS.RUN_LIST, flags: RUN_LIST_FLAG_PARAMS }, fetchPage:
|
|
14139
|
+
return runPagedList(args, { list: { command: COMMANDS.RUN_LIST, flags: RUN_LIST_FLAG_PARAMS }, fetchPage: (cfg, params) => readBusyRetried(() => readRunList(cfg, params), REAL_CLOCK, noteBusyRetry), rowsOf: (data) => data.runs, show: (data) => CliOutputManager.render("run:list", data.runs) });
|
|
13644
14140
|
}
|
|
13645
14141
|
async function runRunGet(args) {
|
|
13646
14142
|
let id = requireArg(args, "run-id");
|
|
13647
14143
|
return id ? guard(async () => {
|
|
13648
|
-
let cfg = apiConfig(
|
|
14144
|
+
let cfg = apiConfig(), data = await readBusyRetried(() => readRunForFollow(cfg, id), REAL_CLOCK, noteBusyRetry);
|
|
13649
14145
|
return boolFlag(args, "full") ? out(JSON.stringify(data, null, 2)) : CliOutputManager.render("run:get", data), 0;
|
|
13650
14146
|
}) : 1;
|
|
13651
14147
|
}
|
|
13652
14148
|
async function runRunLogs(args) {
|
|
13653
14149
|
let id = requireArg(args, "run-id");
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
14150
|
+
if (!id) return 1;
|
|
14151
|
+
let follow = boolFlag(args, "follow"), raw = boolFlag(args, "raw");
|
|
14152
|
+
return guard(async () => {
|
|
14153
|
+
let tailRaw = strOpt(args, "tail"), tail = tailRaw === void 0 ? null : parseWholeCount(tailRaw);
|
|
14154
|
+
if (tailRaw !== void 0 && tail === null) return fail(FOLLOW_MESSAGES.TAIL_INVALID(tailRaw)), 1;
|
|
14155
|
+
let cfg = apiConfig(), json2 = isJsonOutput();
|
|
14156
|
+
if (!follow && tail === null) return printConsoleLog(await getApiRunLogs(cfg, id), raw), 0;
|
|
14157
|
+
let colour = outStyle().colour, read = () => readOutput({ readPage: (fromOffset) => readRunOutput(cfg, id, fromOffset), readRun: () => readRunForFollow(cfg, id), onChunk: (chunk) => json2 ? out(JSON.stringify(outputDocument(chunk))) : writeRaw(consoleLogText(chunk.bytes, { raw, colour })), follow, tail, clock: REAL_CLOCK }), end = follow ? await withInterruptNotice(NO_PROGRESS, () => FOLLOW_MESSAGES.INTERRUPTED(id), read) : await read();
|
|
14158
|
+
return end.ok ? 0 : failFollow(end.failure);
|
|
14159
|
+
});
|
|
14160
|
+
}
|
|
14161
|
+
function failFollow(failure) {
|
|
14162
|
+
if (failure.kind === "agent-outdated") fail(FOLLOW_MESSAGES.AGENT_OUTDATED);
|
|
14163
|
+
else if (failure.kind === "no-cursor") fail(FOLLOW_MESSAGES.API_NO_CURSOR);
|
|
14164
|
+
else throw failure.error;
|
|
14165
|
+
return 1;
|
|
14166
|
+
}
|
|
14167
|
+
async function runRunTimeline(args) {
|
|
14168
|
+
let id = requireArg(args, "run-id");
|
|
14169
|
+
if (!id) return 1;
|
|
14170
|
+
let follow = boolFlag(args, "follow");
|
|
14171
|
+
return guard(async () => {
|
|
14172
|
+
let cfg = apiConfig(), json2 = isJsonOutput(), events = [], printedTail = /* @__PURE__ */ new Set(), onPage = (page) => {
|
|
14173
|
+
if (json2 && follow) (page.events.length > 0 || page.complete) && out(JSON.stringify(page));
|
|
14174
|
+
else if (json2) events.push(...page.events);
|
|
14175
|
+
else {
|
|
14176
|
+
let next = eventsToPrint(page, printedTail);
|
|
14177
|
+
printedTail = next.printedTail;
|
|
14178
|
+
for (let event of next.print) out(timelineLine(event));
|
|
14179
|
+
}
|
|
14180
|
+
}, read = () => readTimeline({ read: (cursor, wait) => readRunTimeline(cfg, id, { cursor, waitSec: wait ? FOLLOW.WAIT_SEC : void 0 }), onPage, follow, clock: REAL_CLOCK }), end = follow ? await withInterruptNotice(NO_PROGRESS, () => FOLLOW_MESSAGES.INTERRUPTED(id), read) : await read();
|
|
14181
|
+
if (!end.ok) return failFollow(end.failure);
|
|
14182
|
+
let { run: run2 } = end.last;
|
|
14183
|
+
json2 && !follow && emit({ ...end.last, events });
|
|
14184
|
+
let ended = isTerminalRunStatus(run2.status), status = terminalSafe(run2.status, false);
|
|
14185
|
+
return json2 || out(ended ? WAIT.RUN_ENDED(status) : FOLLOW_MESSAGES.STILL_LIVE(status)), !ended || run2.status === RUN_STATUS.COMPLETED ? 0 : 1;
|
|
14186
|
+
});
|
|
14187
|
+
}
|
|
14188
|
+
async function runRunFailures(args) {
|
|
14189
|
+
return guard(async () => {
|
|
14190
|
+
let days = daysOption(args);
|
|
14191
|
+
if (!days.ok) return 1;
|
|
14192
|
+
let report = await getRunFailures(apiConfig(), days.days);
|
|
14193
|
+
return emit(report, (st) => renderFailures(st, report)), 0;
|
|
14194
|
+
});
|
|
13658
14195
|
}
|
|
13659
14196
|
async function runRunDelete(args, prompter = ttyPrompter) {
|
|
13660
14197
|
let id = requireArg(args, "run-id");
|
|
13661
14198
|
return id ? guard(async () => {
|
|
13662
|
-
let cfg = apiConfig(
|
|
14199
|
+
let cfg = apiConfig();
|
|
13663
14200
|
return say(RESULT_MESSAGES.RUN_DELETE_PREVIEW(id)), await confirmOrAbort(args, prompter) ? (emit(await deleteRun(cfg, id)), sayOk(`${MESSAGES.RUN_DELETED}: ${id}`), 0) : 1;
|
|
13664
14201
|
}) : 1;
|
|
13665
14202
|
}
|
|
@@ -13670,7 +14207,10 @@ async function runRunCancel(args, prompter = ttyPrompter) {
|
|
|
13670
14207
|
return guard(async () => {
|
|
13671
14208
|
let grace = parseGraceMs(strOpt(args, "grace"), force);
|
|
13672
14209
|
if (!grace.ok) return fail(grace.error), 1;
|
|
13673
|
-
let graceMs = grace.graceMs, cfg = apiConfig(
|
|
14210
|
+
let graceMs = grace.graceMs, cfg = apiConfig(), run2 = await getApiRun(cfg, id).catch((e) => {
|
|
14211
|
+
if (isTransientError(e)) return null;
|
|
14212
|
+
throw e;
|
|
14213
|
+
});
|
|
13674
14214
|
if (run2?.status && isTerminalRunStatus(run2.status)) return emit({ run_id: id, outcome: RUN_CANCEL_OUTCOME.ALREADY_TERMINAL, status: run2.status }), say(RESULT_MESSAGES.RUN_ALREADY_OVER(id, run2.status)), 0;
|
|
13675
14215
|
if (say(RESULT_MESSAGES.RUN_CANCEL_PREVIEW(id, force)), !await confirmOrAbort(args, prompter)) return 1;
|
|
13676
14216
|
let res = await cancelApiRun(cfg, id, force, graceMs);
|
|
@@ -13683,7 +14223,7 @@ async function runRunCancel(args, prompter = ttyPrompter) {
|
|
|
13683
14223
|
return sayOk(RESULT_MESSAGES.RUN_CANCELLED(id, was)), 0;
|
|
13684
14224
|
});
|
|
13685
14225
|
}
|
|
13686
|
-
var init_run = __esm({ "src/commands/run.ts"() {
|
|
14226
|
+
var noteBusyRetry, outputDocument, init_run = __esm({ "src/commands/run.ts"() {
|
|
13687
14227
|
"use strict";
|
|
13688
14228
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
13689
14229
|
init_prompt();
|
|
@@ -13692,7 +14232,15 @@ var init_run = __esm({ "src/commands/run.ts"() {
|
|
|
13692
14232
|
init_utils();
|
|
13693
14233
|
init_output();
|
|
13694
14234
|
init_grace();
|
|
14235
|
+
init_follow();
|
|
14236
|
+
init_poll();
|
|
13695
14237
|
init_shared();
|
|
14238
|
+
init_run_follow();
|
|
14239
|
+
noteBusyRetry = (delayMs) => {
|
|
14240
|
+
tell(BUSY_READ.NOTE(Math.ceil(delayMs / MS_PER_SECOND)));
|
|
14241
|
+
};
|
|
14242
|
+
outputDocument = (chunk) => ({ run_id: chunk.runId, bytes: chunk.bytes, lines: chunk.bytes.split(`
|
|
14243
|
+
`).length - 1, truncated: false, next_offset: chunk.nextOffset });
|
|
13696
14244
|
} });
|
|
13697
14245
|
function decideVmDelete(vm, force, vmId) {
|
|
13698
14246
|
if (vm.residency === "pinned" && !force) {
|
|
@@ -13714,7 +14262,7 @@ function runVmList(args) {
|
|
|
13714
14262
|
async function runVmGet(args) {
|
|
13715
14263
|
let id = requireArg(args, "vm-id");
|
|
13716
14264
|
return id ? guard(async () => {
|
|
13717
|
-
let cfg = apiConfig(
|
|
14265
|
+
let cfg = apiConfig(), data = await getApiVm(cfg, id);
|
|
13718
14266
|
return CliOutputManager.render("vm:get", { ...data }), 0;
|
|
13719
14267
|
}) : 1;
|
|
13720
14268
|
}
|
|
@@ -13723,7 +14271,7 @@ async function runVmDelete(args, prompter = ttyPrompter) {
|
|
|
13723
14271
|
if (!id) return 1;
|
|
13724
14272
|
let force = boolFlag(args, "force");
|
|
13725
14273
|
return guard(async () => {
|
|
13726
|
-
let cfg = apiConfig(
|
|
14274
|
+
let cfg = apiConfig(), vm = await getApiVm(cfg, id), decision = decideVmDelete(vm, force, id);
|
|
13727
14275
|
if (decision.refuse) return fail(decision.message), 1;
|
|
13728
14276
|
if (say(decision.notice), !await confirmOrAbort(args, prompter)) return 1;
|
|
13729
14277
|
let res = await deleteVm(cfg, id, force);
|
|
@@ -13751,20 +14299,20 @@ async function runSecretSet(args) {
|
|
|
13751
14299
|
if (args.options.stdin !== true) return tell(MESSAGES.SECRET_SET_USAGE), 1;
|
|
13752
14300
|
let value = import_fs5.default.readFileSync(0, "utf8").replace(/\n$/, "");
|
|
13753
14301
|
return value === "" ? (fail(MESSAGES.SECRET_VALUE_EMPTY), 1) : guard(async () => {
|
|
13754
|
-
let cfg = apiConfig(
|
|
14302
|
+
let cfg = apiConfig(), res = await setSecret(cfg, name, value);
|
|
13755
14303
|
return emit(res), sayOk(MESSAGES.SECRET_STORED(name)), 0;
|
|
13756
14304
|
});
|
|
13757
14305
|
}
|
|
13758
14306
|
async function runSecretList(args) {
|
|
13759
14307
|
return guard(async () => {
|
|
13760
|
-
let cfg = apiConfig(
|
|
14308
|
+
let cfg = apiConfig(), data = await listSecrets(cfg);
|
|
13761
14309
|
return emit(data, (st) => data.secrets.length === 0 ? emptyState(st, RESULT_MESSAGES.NO_SECRETS, RESULT_MESSAGES.NO_SECRETS_NEXT) : renderTable(st, [{ header: "name", keep: true }, { header: "updated_at", keep: true }], data.secrets.map((x) => [x.name, x.updated_at]))), 0;
|
|
13762
14310
|
});
|
|
13763
14311
|
}
|
|
13764
14312
|
async function runSecretDelete(args, prompter = ttyPrompter) {
|
|
13765
14313
|
let name = args.positionals[0];
|
|
13766
14314
|
return name ? guard(async () => {
|
|
13767
|
-
let cfg = apiConfig(
|
|
14315
|
+
let cfg = apiConfig();
|
|
13768
14316
|
if (tell(RESULT_MESSAGES.SECRET_DELETE_PREVIEW(name)), !await confirmOrAbort(args, prompter)) return 1;
|
|
13769
14317
|
let res = await rmSecret(cfg, name);
|
|
13770
14318
|
return emit(res), sayOk(MESSAGES.SECRET_REMOVED(name)), 0;
|
|
@@ -13784,14 +14332,14 @@ var import_fs5, secretValueOnArgv, init_secret = __esm({ "src/commands/secret.ts
|
|
|
13784
14332
|
} });
|
|
13785
14333
|
async function runScheduleList(args) {
|
|
13786
14334
|
return guard(async () => {
|
|
13787
|
-
let cfg = apiConfig(
|
|
14335
|
+
let cfg = apiConfig(), res = await listSchedules(cfg);
|
|
13788
14336
|
return CliOutputManager.render("schedule:list", res.schedules.map((row) => ({ ...row }))), 0;
|
|
13789
14337
|
});
|
|
13790
14338
|
}
|
|
13791
14339
|
async function runScheduleCancel(args, prompter = ttyPrompter) {
|
|
13792
14340
|
let id = requireArg(args, "schedule-id");
|
|
13793
14341
|
return id ? guard(async () => {
|
|
13794
|
-
let cfg = apiConfig(
|
|
14342
|
+
let cfg = apiConfig();
|
|
13795
14343
|
if (say(RESULT_MESSAGES.SCHEDULE_CANCEL_PREVIEW(id)), !await confirmOrAbort(args, prompter)) return 1;
|
|
13796
14344
|
try {
|
|
13797
14345
|
await deleteSchedule(cfg, id);
|
|
@@ -13812,12 +14360,37 @@ var init_schedule = __esm({ "src/commands/schedule.ts"() {
|
|
|
13812
14360
|
init_output();
|
|
13813
14361
|
init_shared();
|
|
13814
14362
|
} });
|
|
14363
|
+
function notificationView(unread, open) {
|
|
14364
|
+
return unread && open ? null : unread ? "unread" : open ? "open" : "all";
|
|
14365
|
+
}
|
|
14366
|
+
function notificationMoreNote(rawLimit) {
|
|
14367
|
+
return (rawLimit === void 0 ? NOTIFICATION_PAGE_DEFAULT : Number(rawLimit)) < NOTIFICATION_PAGE_MAX ? MESSAGES.NOTIFICATION_MORE(NOTIFICATION_PAGE_MAX) : MESSAGES.NOTIFICATION_MORE_IN_APP;
|
|
14368
|
+
}
|
|
14369
|
+
async function runNotificationList(args) {
|
|
14370
|
+
let view = notificationView(boolFlag(args, "unread"), boolFlag(args, "open"));
|
|
14371
|
+
if (view === null) return fail(MESSAGES.NOTIFICATION_ONE_VIEW), 1;
|
|
14372
|
+
let badLimit = limitRefusal(args);
|
|
14373
|
+
return badLimit !== null ? (fail(badLimit), 1) : guard(async () => {
|
|
14374
|
+
let page = await listNotifications(apiConfig(), { ...flagParams(args, NOTIFICATION_LIST_FLAG_PARAMS), [NOTIFICATION_QUERY.FILTER]: view });
|
|
14375
|
+
return emit(page, (st) => renderNotifications(st, page)), page.next_cursor !== null && warn(notificationMoreNote(strOpt(args, "limit"))), 0;
|
|
14376
|
+
});
|
|
14377
|
+
}
|
|
14378
|
+
var init_notification = __esm({ "src/commands/notification.ts"() {
|
|
14379
|
+
"use strict";
|
|
14380
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14381
|
+
init_tenant();
|
|
14382
|
+
init_api();
|
|
14383
|
+
init_utils();
|
|
14384
|
+
init_output();
|
|
14385
|
+
init_constants();
|
|
14386
|
+
init_shared();
|
|
14387
|
+
} });
|
|
13815
14388
|
function withAbsoluteUrl(created, baseUrl) {
|
|
13816
14389
|
let { url } = created;
|
|
13817
14390
|
return typeof url != "string" || !url.startsWith("/") ? created : { ...created, url: `${baseUrl.replace(/\/+$/, "")}${url}` };
|
|
13818
14391
|
}
|
|
13819
14392
|
function renderHookCreated(st, created) {
|
|
13820
|
-
let { secret, ...rest } = created, rows = { ...rest, [RESULT_MESSAGES.HOOK_SECRET_KEY]: secret }, paint2 = { [RESULT_MESSAGES.HOOK_SECRET_KEY]: (s) => st.bold(st.yellow(s)) };
|
|
14393
|
+
let { secret, secret_redacted: _redacted, ...rest } = created, rows = { ...rest, [RESULT_MESSAGES.HOOK_SECRET_KEY]: secret }, paint2 = { [RESULT_MESSAGES.HOOK_SECRET_KEY]: (s) => st.bold(st.yellow(s)) };
|
|
13821
14394
|
return `
|
|
13822
14395
|
${heading(st, RESULT_MESSAGES.HOOK_CREATED_TITLE, true)}
|
|
13823
14396
|
${renderFields(st, rows, void 0, paint2)}
|
|
@@ -13828,8 +14401,8 @@ async function runHookCreate(args) {
|
|
|
13828
14401
|
if (!deploymentId) return tell(MESSAGES.HOOK_CREATE_USAGE), 1;
|
|
13829
14402
|
let taskName = args.options["task-name"];
|
|
13830
14403
|
return typeof taskName != "string" || taskName.trim() === "" ? (fail(MESSAGES.HOOK_TASK_NAME_REQUIRED), 1) : guard(async () => {
|
|
13831
|
-
let cfg = apiConfig(
|
|
13832
|
-
return warn(MESSAGES.HOOK_SECRET_SHOWN_ONCE), tell(MESSAGES.HOOK_PAYLOAD_LIMIT(formatLimit(await payloadLimit(cfg)))), emit(created, (st) => renderHookCreated(st, created)), 0;
|
|
14404
|
+
let cfg = apiConfig(), created = withAbsoluteUrl(await createHook(cfg, deploymentId, { task_name: taskName }), cfg.baseUrl);
|
|
14405
|
+
return created.secret === null ? (fail(MESSAGES.HOOK_SECRET_REDACTED(created.resource_id)), emit(created), 1) : (warn(MESSAGES.HOOK_SECRET_SHOWN_ONCE), tell(MESSAGES.HOOK_PAYLOAD_LIMIT(formatLimit(await payloadLimit(cfg)))), emit(created, (st) => renderHookCreated(st, created)), 0);
|
|
13833
14406
|
});
|
|
13834
14407
|
}
|
|
13835
14408
|
async function runHookList(args) {
|
|
@@ -13837,14 +14410,14 @@ async function runHookList(args) {
|
|
|
13837
14410
|
if (!deploymentId) return tell(MESSAGES.HOOK_LIST_USAGE), 1;
|
|
13838
14411
|
let badLimit = limitRefusal(args);
|
|
13839
14412
|
return badLimit !== null ? (fail(badLimit), 1) : guard(async () => {
|
|
13840
|
-
let cfg = apiConfig(
|
|
14413
|
+
let cfg = apiConfig(), res = await listHooks(cfg, deploymentId, flagParams(args, PAGE_FLAG_PARAMS)), hooks = Array.isArray(res.hooks) ? res.hooks : [];
|
|
13841
14414
|
return emit(res, (st) => renderList(hooks.map((h) => ({ ...h })), st, EMPTY_LISTS.HOOKS)), noteNextPage(args, { command: COMMANDS.HOOK_LIST, flags: PAGE_FLAG_PARAMS }, hooks), 0;
|
|
13842
14415
|
});
|
|
13843
14416
|
}
|
|
13844
14417
|
async function runHookDelete(args, prompter = ttyPrompter) {
|
|
13845
14418
|
let hookId = args.positionals[0];
|
|
13846
14419
|
return hookId ? guard(async () => {
|
|
13847
|
-
let cfg = apiConfig(
|
|
14420
|
+
let cfg = apiConfig();
|
|
13848
14421
|
return tell(RESULT_MESSAGES.HOOK_DELETE_PREVIEW(hookId)), await confirmOrAbort(args, prompter) ? (emit(await deleteHook(cfg, hookId)), sayOk(RESULT_MESSAGES.HOOK_DELETED(hookId)), 0) : 1;
|
|
13849
14422
|
}) : (tell(MESSAGES.HOOK_DELETE_USAGE), 1);
|
|
13850
14423
|
}
|
|
@@ -13856,7 +14429,7 @@ async function runNotifySet(args) {
|
|
|
13856
14429
|
if (refusal !== null) return refuseNotifySet(refusal);
|
|
13857
14430
|
let secret = args.options["secret-stdin"] === true ? import_fs6.default.readFileSync(0, "utf8").replace(/\n$/, "") : void 0;
|
|
13858
14431
|
return secret === "" ? refuseNotifySet(MESSAGES.NOTIFY_SECRET_EMPTY) : guard(async () => {
|
|
13859
|
-
let cfg = apiConfig(
|
|
14432
|
+
let cfg = apiConfig(), target = clear || typeof url != "string" ? null : url;
|
|
13860
14433
|
return emit(await setNotify(cfg, deploymentId, { url: target, ...secret !== void 0 ? { secret } : {} })), sayOk(target === null ? RESULT_MESSAGES.NOTIFY_CLEARED(deploymentId) : RESULT_MESSAGES.NOTIFY_SET(deploymentId, target)), 0;
|
|
13861
14434
|
});
|
|
13862
14435
|
}
|
|
@@ -13879,6 +14452,198 @@ var import_fs6, init_hook = __esm({ "src/commands/hook.ts"() {
|
|
|
13879
14452
|
init_shared();
|
|
13880
14453
|
init_output();
|
|
13881
14454
|
} });
|
|
14455
|
+
async function runPermissionList(_args) {
|
|
14456
|
+
let permissions = permissionRows();
|
|
14457
|
+
return emit({ permissions }, (st) => renderPermissions(st, permissions)), 0;
|
|
14458
|
+
}
|
|
14459
|
+
var init_permission = __esm({ "src/commands/permission.ts"() {
|
|
14460
|
+
"use strict";
|
|
14461
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14462
|
+
init_access();
|
|
14463
|
+
init_output();
|
|
14464
|
+
} });
|
|
14465
|
+
async function runUserCreate(args) {
|
|
14466
|
+
let name = accessNameArg(args.positionals[0], ACCESS.USER, "name");
|
|
14467
|
+
return name === null ? 1 : guard(async () => {
|
|
14468
|
+
let created = await createUser(apiConfig(), name);
|
|
14469
|
+
return emit(created, (st) => renderFields(st, { ...created })), sayOk(MESSAGES.USER_CREATED(created.name)), 0;
|
|
14470
|
+
});
|
|
14471
|
+
}
|
|
14472
|
+
async function runUserList(_args) {
|
|
14473
|
+
return guard(async () => {
|
|
14474
|
+
let data = await listUsers(apiConfig());
|
|
14475
|
+
return emit(data, (st) => renderUsers(st, data.users)), 0;
|
|
14476
|
+
});
|
|
14477
|
+
}
|
|
14478
|
+
async function runUserGet(args) {
|
|
14479
|
+
let name = accessNameArg(args.positionals[0], ACCESS.USER, "name");
|
|
14480
|
+
return name === null ? 1 : guard(async () => {
|
|
14481
|
+
let user = await getUser(apiConfig(), name);
|
|
14482
|
+
return emit(user, (st) => renderUser(st, user)), 0;
|
|
14483
|
+
});
|
|
14484
|
+
}
|
|
14485
|
+
async function runUserRemove(args, prompter = ttyPrompter) {
|
|
14486
|
+
let name = accessNameArg(args.positionals[0], ACCESS.USER, "name");
|
|
14487
|
+
return name === null ? 1 : guard(async () => {
|
|
14488
|
+
let cfg = apiConfig();
|
|
14489
|
+
if (tell(RESULT_MESSAGES.USER_REMOVE_PREVIEW(name)), !await confirmOrAbort(args, prompter)) return 1;
|
|
14490
|
+
let res = await deleteUser(cfg, name);
|
|
14491
|
+
return emit(res), sayOk(MESSAGES.USER_REMOVED(res.removed, res.revoked_keys.length)), 0;
|
|
14492
|
+
});
|
|
14493
|
+
}
|
|
14494
|
+
var init_user = __esm({ "src/commands/user.ts"() {
|
|
14495
|
+
"use strict";
|
|
14496
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14497
|
+
init_api();
|
|
14498
|
+
init_prompt();
|
|
14499
|
+
init_constants();
|
|
14500
|
+
init_utils();
|
|
14501
|
+
init_output();
|
|
14502
|
+
init_shared();
|
|
14503
|
+
} });
|
|
14504
|
+
async function groupWrite(write, done) {
|
|
14505
|
+
return guard(async () => {
|
|
14506
|
+
let group = await write(apiConfig());
|
|
14507
|
+
return emit(group, (st) => renderGroup(st, group)), sayOk(done), 0;
|
|
14508
|
+
});
|
|
14509
|
+
}
|
|
14510
|
+
function permissionArg(raw) {
|
|
14511
|
+
if (raw === void 0 || raw === "") return fail(MESSAGES.ARG_REQUIRED("permission")), null;
|
|
14512
|
+
let checked = checkPermission(raw);
|
|
14513
|
+
return checked.ok ? checked.value : (fail(checked.message), null);
|
|
14514
|
+
}
|
|
14515
|
+
async function runGroupCreate(args) {
|
|
14516
|
+
let name = accessNameArg(args.positionals[0], ACCESS.GROUP, "name");
|
|
14517
|
+
if (name === null) return 1;
|
|
14518
|
+
let permissions = parsePermissions(strOpt(args, "permissions"));
|
|
14519
|
+
return permissions.ok ? groupWrite((cfg) => createGroup(cfg, name, permissions.value), MESSAGES.GROUP_CREATED(name)) : (fail(permissions.message), 1);
|
|
14520
|
+
}
|
|
14521
|
+
async function runGroupList(_args) {
|
|
14522
|
+
return guard(async () => {
|
|
14523
|
+
let data = await listGroups(apiConfig());
|
|
14524
|
+
return emit(data, (st) => renderGroups(st, data.groups)), 0;
|
|
14525
|
+
});
|
|
14526
|
+
}
|
|
14527
|
+
async function runGroupGet(args) {
|
|
14528
|
+
let name = accessNameArg(args.positionals[0], ACCESS.GROUP, "name");
|
|
14529
|
+
return name === null ? 1 : guard(async () => {
|
|
14530
|
+
let group = await getGroup(apiConfig(), name);
|
|
14531
|
+
return emit(group, (st) => renderGroup(st, group)), 0;
|
|
14532
|
+
});
|
|
14533
|
+
}
|
|
14534
|
+
async function runGroupGrant(args) {
|
|
14535
|
+
let name = accessNameArg(args.positionals[0], ACCESS.GROUP, "name");
|
|
14536
|
+
if (name === null) return 1;
|
|
14537
|
+
let permission = permissionArg(args.positionals[1]);
|
|
14538
|
+
return permission === null ? 1 : groupWrite((cfg) => grantGroupPermission(cfg, name, permission), MESSAGES.GROUP_GRANTED(name, permission));
|
|
14539
|
+
}
|
|
14540
|
+
async function runGroupRevoke(args) {
|
|
14541
|
+
let name = accessNameArg(args.positionals[0], ACCESS.GROUP, "name");
|
|
14542
|
+
if (name === null) return 1;
|
|
14543
|
+
let permission = permissionArg(args.positionals[1]);
|
|
14544
|
+
return permission === null ? 1 : groupWrite((cfg) => revokeGroupPermission(cfg, name, permission), MESSAGES.GROUP_REVOKED(name, permission));
|
|
14545
|
+
}
|
|
14546
|
+
async function runGroupAddUser(args) {
|
|
14547
|
+
let group = accessNameArg(args.positionals[0], ACCESS.GROUP, "group");
|
|
14548
|
+
if (group === null) return 1;
|
|
14549
|
+
let user = accessNameArg(args.positionals[1], ACCESS.USER, "user");
|
|
14550
|
+
return user === null ? 1 : groupWrite((cfg) => addGroupUser(cfg, group, user), MESSAGES.GROUP_USER_ADDED(group, user));
|
|
14551
|
+
}
|
|
14552
|
+
async function runGroupRemoveUser(args) {
|
|
14553
|
+
let group = accessNameArg(args.positionals[0], ACCESS.GROUP, "group");
|
|
14554
|
+
if (group === null) return 1;
|
|
14555
|
+
let user = accessNameArg(args.positionals[1], ACCESS.USER, "user");
|
|
14556
|
+
return user === null ? 1 : groupWrite((cfg) => removeGroupUser(cfg, group, user), MESSAGES.GROUP_USER_REMOVED(group, user));
|
|
14557
|
+
}
|
|
14558
|
+
async function runGroupDelete(args, prompter = ttyPrompter) {
|
|
14559
|
+
let name = accessNameArg(args.positionals[0], ACCESS.GROUP, "name");
|
|
14560
|
+
return name === null ? 1 : guard(async () => {
|
|
14561
|
+
let cfg = apiConfig();
|
|
14562
|
+
if (tell(RESULT_MESSAGES.GROUP_DELETE_PREVIEW(name)), !await confirmOrAbort(args, prompter)) return 1;
|
|
14563
|
+
let res = await deleteGroup(cfg, name);
|
|
14564
|
+
return emit(res), sayOk(MESSAGES.GROUP_DELETED(res.deleted)), 0;
|
|
14565
|
+
});
|
|
14566
|
+
}
|
|
14567
|
+
var init_group = __esm({ "src/commands/group.ts"() {
|
|
14568
|
+
"use strict";
|
|
14569
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14570
|
+
init_api();
|
|
14571
|
+
init_prompt();
|
|
14572
|
+
init_constants();
|
|
14573
|
+
init_access();
|
|
14574
|
+
init_utils();
|
|
14575
|
+
init_output();
|
|
14576
|
+
init_shared();
|
|
14577
|
+
} });
|
|
14578
|
+
function keyFileDocument(created, out2) {
|
|
14579
|
+
let { raw_key: _raw, ...rest } = created;
|
|
14580
|
+
return { ...rest, out: out2 };
|
|
14581
|
+
}
|
|
14582
|
+
function openOutFile(out2) {
|
|
14583
|
+
try {
|
|
14584
|
+
return { fd: createPrivateFile(out2), path: out2 };
|
|
14585
|
+
} catch (e) {
|
|
14586
|
+
return fail(errCode(e) === "EEXIST" ? MESSAGES.KEY_OUT_EXISTS(out2) : MESSAGES.KEY_OUT_FAILED(out2, errText(e))), null;
|
|
14587
|
+
}
|
|
14588
|
+
}
|
|
14589
|
+
async function runKeyCreate(args) {
|
|
14590
|
+
let rawUser = strOpt(args, "user");
|
|
14591
|
+
if (rawUser === void 0 || rawUser === "") return fail(MESSAGES.KEY_USER_REQUIRED), 1;
|
|
14592
|
+
let user = accessNameArg(rawUser, ACCESS.USER, "--user");
|
|
14593
|
+
if (user === null) return 1;
|
|
14594
|
+
let label = strOpt(args, "label")?.trim() ?? "";
|
|
14595
|
+
if (label === "") return fail(MESSAGES.KEY_LABEL_REQUIRED), 1;
|
|
14596
|
+
let out2 = strOpt(args, "out");
|
|
14597
|
+
return guard(async () => {
|
|
14598
|
+
let cfg = apiConfig(), file = null;
|
|
14599
|
+
if (out2 !== void 0 && (file = openOutFile(out2), file === null)) return 1;
|
|
14600
|
+
let created;
|
|
14601
|
+
try {
|
|
14602
|
+
created = await createKey(cfg, { user, label });
|
|
14603
|
+
} catch (e) {
|
|
14604
|
+
throw file !== null && discardPrivateFile(file.fd, file.path), e;
|
|
14605
|
+
}
|
|
14606
|
+
if (created.raw_key === null) return file !== null && discardPrivateFile(file.fd, file.path), fail(MESSAGES.KEY_REDACTED(created.resource_id)), 1;
|
|
14607
|
+
if (file !== null) {
|
|
14608
|
+
try {
|
|
14609
|
+
fillPrivateFile(file.fd, file.path, `${created.raw_key}
|
|
14610
|
+
`);
|
|
14611
|
+
} catch (e) {
|
|
14612
|
+
return fail(MESSAGES.KEY_OUT_WRITE_FAILED(file.path, errText(e), created.resource_id)), 1;
|
|
14613
|
+
}
|
|
14614
|
+
return emit(keyFileDocument(created, file.path)), sayOk(MESSAGES.KEY_WRITTEN(created.resource_id, file.path)), 0;
|
|
14615
|
+
}
|
|
14616
|
+
return warn(MESSAGES.KEY_SHOWN_ONCE), emit(created, (st) => renderKeyCreated(st, created)), 0;
|
|
14617
|
+
});
|
|
14618
|
+
}
|
|
14619
|
+
async function runKeyList(args) {
|
|
14620
|
+
let rawUser = strOpt(args, "user"), user = rawUser === void 0 ? void 0 : accessNameArg(rawUser, ACCESS.USER, "--user");
|
|
14621
|
+
return user === null ? 1 : guard(async () => {
|
|
14622
|
+
let data = await listKeys(apiConfig(), user);
|
|
14623
|
+
return emit(data, (st) => renderKeys(st, data.keys)), 0;
|
|
14624
|
+
});
|
|
14625
|
+
}
|
|
14626
|
+
async function runKeyRevoke(args, prompter = ttyPrompter) {
|
|
14627
|
+
let id = requireArg(args, "key-resource-id");
|
|
14628
|
+
return id ? guard(async () => {
|
|
14629
|
+
let cfg = apiConfig();
|
|
14630
|
+
if (tell(RESULT_MESSAGES.KEY_REVOKE_PREVIEW(id)), !await confirmOrAbort(args, prompter)) return 1;
|
|
14631
|
+
let res = await deleteKey(cfg, id);
|
|
14632
|
+
return emit(res), sayOk(MESSAGES.KEY_REVOKED(res.revoked)), 0;
|
|
14633
|
+
}) : 1;
|
|
14634
|
+
}
|
|
14635
|
+
var init_key = __esm({ "src/commands/key.ts"() {
|
|
14636
|
+
"use strict";
|
|
14637
|
+
init_define_IGNITIFY_CLIENT_FILES();
|
|
14638
|
+
init_tenant();
|
|
14639
|
+
init_api();
|
|
14640
|
+
init_credentials();
|
|
14641
|
+
init_prompt();
|
|
14642
|
+
init_constants();
|
|
14643
|
+
init_utils();
|
|
14644
|
+
init_output();
|
|
14645
|
+
init_shared();
|
|
14646
|
+
} });
|
|
13882
14647
|
var init_commands = __esm({ "src/commands/index.ts"() {
|
|
13883
14648
|
"use strict";
|
|
13884
14649
|
init_define_IGNITIFY_CLIENT_FILES();
|
|
@@ -13892,7 +14657,12 @@ var init_commands = __esm({ "src/commands/index.ts"() {
|
|
|
13892
14657
|
init_secret();
|
|
13893
14658
|
init_schedule();
|
|
13894
14659
|
init_integration();
|
|
14660
|
+
init_notification();
|
|
13895
14661
|
init_hook();
|
|
14662
|
+
init_permission();
|
|
14663
|
+
init_user();
|
|
14664
|
+
init_group();
|
|
14665
|
+
init_key();
|
|
13896
14666
|
init_decide_vm_delete();
|
|
13897
14667
|
} });
|
|
13898
14668
|
function metaFor(command) {
|
|
@@ -13924,7 +14694,7 @@ function checkInvocation(command, meta, pass) {
|
|
|
13924
14694
|
for (let key of Object.keys(args.options)) {
|
|
13925
14695
|
if (Object.hasOwn(flags, key)) continue;
|
|
13926
14696
|
let short = typed[key];
|
|
13927
|
-
return short !== void 0 ? MESSAGES.UNKNOWN_SHORT_FLAG(short, key, command) : unknownFlagRefusal(key, command, Object.keys(meta.flags), Object.keys(GLOBAL_FLAGS)
|
|
14697
|
+
return short !== void 0 ? MESSAGES.UNKNOWN_SHORT_FLAG(short, key, command) : unknownFlagRefusal(key, command, Object.keys(meta.flags), Object.keys(GLOBAL_FLAGS));
|
|
13928
14698
|
}
|
|
13929
14699
|
let badShort = pass.unknown[0];
|
|
13930
14700
|
return badShort !== void 0 ? unknownFlagText(badShort, command) : meta.secretArgv === true ? null : shapeRefusal(command, args, flags, meta.positionals);
|
|
@@ -13962,7 +14732,7 @@ async function dispatch(plan) {
|
|
|
13962
14732
|
case "unknown":
|
|
13963
14733
|
return fail(`${MESSAGES.UNKNOWN_COMMAND}: ${plan.command}`, { hint: MESSAGES.SEE_HELP }), 1;
|
|
13964
14734
|
case "version":
|
|
13965
|
-
return runVersion(
|
|
14735
|
+
return runVersion();
|
|
13966
14736
|
case "run":
|
|
13967
14737
|
return route(plan.command, plan.args);
|
|
13968
14738
|
}
|
|
@@ -13993,6 +14763,8 @@ function route(command, args) {
|
|
|
13993
14763
|
return runUsageShow(args);
|
|
13994
14764
|
case COMMANDS.USAGE_EXPORT:
|
|
13995
14765
|
return runUsageExport(args);
|
|
14766
|
+
case COMMANDS.USAGE_SERIES:
|
|
14767
|
+
return runUsageSeries(args);
|
|
13996
14768
|
case COMMANDS.TASK_CREATE:
|
|
13997
14769
|
return runTaskCreate(args);
|
|
13998
14770
|
case COMMANDS.TASK_LIST:
|
|
@@ -14013,6 +14785,10 @@ function route(command, args) {
|
|
|
14013
14785
|
return runRunGet(args);
|
|
14014
14786
|
case COMMANDS.RUN_LOGS:
|
|
14015
14787
|
return runRunLogs(args);
|
|
14788
|
+
case COMMANDS.RUN_TIMELINE:
|
|
14789
|
+
return runRunTimeline(args);
|
|
14790
|
+
case COMMANDS.RUN_FAILURES:
|
|
14791
|
+
return runRunFailures(args);
|
|
14016
14792
|
case COMMANDS.RUN_DELETE:
|
|
14017
14793
|
return runRunDelete(args);
|
|
14018
14794
|
case COMMANDS.RUN_CANCEL:
|
|
@@ -14043,6 +14819,40 @@ function route(command, args) {
|
|
|
14043
14819
|
return runNotifySet(args);
|
|
14044
14820
|
case COMMANDS.INTEGRATION_ADD:
|
|
14045
14821
|
return runIntegrationAdd(args);
|
|
14822
|
+
case COMMANDS.NOTIFICATION_LIST:
|
|
14823
|
+
return runNotificationList(args);
|
|
14824
|
+
case COMMANDS.PERMISSION_LIST:
|
|
14825
|
+
return runPermissionList(args);
|
|
14826
|
+
case COMMANDS.USER_CREATE:
|
|
14827
|
+
return runUserCreate(args);
|
|
14828
|
+
case COMMANDS.USER_LIST:
|
|
14829
|
+
return runUserList(args);
|
|
14830
|
+
case COMMANDS.USER_GET:
|
|
14831
|
+
return runUserGet(args);
|
|
14832
|
+
case COMMANDS.USER_REMOVE:
|
|
14833
|
+
return runUserRemove(args);
|
|
14834
|
+
case COMMANDS.GROUP_CREATE:
|
|
14835
|
+
return runGroupCreate(args);
|
|
14836
|
+
case COMMANDS.GROUP_LIST:
|
|
14837
|
+
return runGroupList(args);
|
|
14838
|
+
case COMMANDS.GROUP_GET:
|
|
14839
|
+
return runGroupGet(args);
|
|
14840
|
+
case COMMANDS.GROUP_GRANT:
|
|
14841
|
+
return runGroupGrant(args);
|
|
14842
|
+
case COMMANDS.GROUP_REVOKE:
|
|
14843
|
+
return runGroupRevoke(args);
|
|
14844
|
+
case COMMANDS.GROUP_ADD_USER:
|
|
14845
|
+
return runGroupAddUser(args);
|
|
14846
|
+
case COMMANDS.GROUP_REMOVE_USER:
|
|
14847
|
+
return runGroupRemoveUser(args);
|
|
14848
|
+
case COMMANDS.GROUP_DELETE:
|
|
14849
|
+
return runGroupDelete(args);
|
|
14850
|
+
case COMMANDS.KEY_CREATE:
|
|
14851
|
+
return runKeyCreate(args);
|
|
14852
|
+
case COMMANDS.KEY_LIST:
|
|
14853
|
+
return runKeyList(args);
|
|
14854
|
+
case COMMANDS.KEY_REVOKE:
|
|
14855
|
+
return runKeyRevoke(args);
|
|
14046
14856
|
}
|
|
14047
14857
|
}
|
|
14048
14858
|
async function main(argv) {
|