@kaelen-ai/cli 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1841 -399
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,16 +9,16 @@ import { transform, build } from 'esbuild';
|
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
11
|
import { URL as URL$1, pathToFileURL } from 'url';
|
|
12
|
+
import { randomBytes, createHash } from 'crypto';
|
|
12
13
|
import { tmpdir, homedir } from 'os';
|
|
13
14
|
import { createWriteStream } from 'fs';
|
|
14
15
|
import archiver from 'archiver';
|
|
15
16
|
import { createInterface } from 'readline';
|
|
16
|
-
import open from 'open';
|
|
17
|
-
import { createServer } from 'http';
|
|
18
|
-
import { randomBytes, createHash } from 'crypto';
|
|
19
17
|
import WebSocket from 'ws';
|
|
20
18
|
import { Socket } from 'phoenix';
|
|
21
19
|
import * as AbsintheSocket from '@absinthe/socket';
|
|
20
|
+
import open from 'open';
|
|
21
|
+
import { createServer } from 'http';
|
|
22
22
|
|
|
23
23
|
// src/errors.ts
|
|
24
24
|
var CliError = class extends Error {
|
|
@@ -159,7 +159,14 @@ function validChannelPattern(value) {
|
|
|
159
159
|
|
|
160
160
|
// src/build/parser.ts
|
|
161
161
|
var SDK_PACKAGES = /* @__PURE__ */ new Set(["@io/sdk", "@kaelen-ai/sdk", "@eleven-am/sdk"]);
|
|
162
|
-
var EXECUTION_KEYS = /* @__PURE__ */ new Set([
|
|
162
|
+
var EXECUTION_KEYS = /* @__PURE__ */ new Set([
|
|
163
|
+
"memory",
|
|
164
|
+
"timeout",
|
|
165
|
+
"maxToolCalls",
|
|
166
|
+
"rateBudget",
|
|
167
|
+
"retries",
|
|
168
|
+
"concurrency"
|
|
169
|
+
]);
|
|
163
170
|
function loc(node, file) {
|
|
164
171
|
const position = node.loc?.start;
|
|
165
172
|
return {
|
|
@@ -311,7 +318,7 @@ function extractOnField(propValue, file, sdkImports) {
|
|
|
311
318
|
loc(propValue, file)
|
|
312
319
|
);
|
|
313
320
|
}
|
|
314
|
-
function extractExecutionConfig(properties) {
|
|
321
|
+
function extractExecutionConfig(properties, file) {
|
|
315
322
|
const config = {};
|
|
316
323
|
for (const prop of properties) {
|
|
317
324
|
if (prop.type === "SpreadElement") {
|
|
@@ -321,10 +328,7 @@ function extractExecutionConfig(properties) {
|
|
|
321
328
|
if (!keyName || !EXECUTION_KEYS.has(keyName)) {
|
|
322
329
|
continue;
|
|
323
330
|
}
|
|
324
|
-
|
|
325
|
-
if (value.type === "Literal") {
|
|
326
|
-
config[keyName] = value.value;
|
|
327
|
-
}
|
|
331
|
+
config[keyName] = extractStaticValue(prop.value, file);
|
|
328
332
|
}
|
|
329
333
|
return Object.keys(config).length > 0 ? config : void 0;
|
|
330
334
|
}
|
|
@@ -364,7 +368,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
|
|
|
364
368
|
const properties = spec.properties;
|
|
365
369
|
let onRaw;
|
|
366
370
|
let hasRun = false;
|
|
367
|
-
let messageHistory;
|
|
368
371
|
for (const prop of properties) {
|
|
369
372
|
if (prop.type === "SpreadElement") {
|
|
370
373
|
continue;
|
|
@@ -377,23 +380,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
|
|
|
377
380
|
onRaw = extractOnField(prop.value, file, sdkImports);
|
|
378
381
|
} else if (keyName === "run") {
|
|
379
382
|
hasRun = true;
|
|
380
|
-
} else if (keyName === "trackingId") {
|
|
381
|
-
const value = prop.value;
|
|
382
|
-
if (value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression") {
|
|
383
|
-
throw new BuildError(
|
|
384
|
-
`behavior '${name}' trackingId must be a function in ${file}:${location.line}`,
|
|
385
|
-
location
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
} else if (keyName === "messageHistory") {
|
|
389
|
-
const value = prop.value;
|
|
390
|
-
if (value.type !== "Literal" || typeof value.value !== "number" || !Number.isInteger(value.value) || value.value <= 0) {
|
|
391
|
-
throw new BuildError(
|
|
392
|
-
`behavior '${name}' messageHistory must be a positive integer literal in ${file}:${location.line}`,
|
|
393
|
-
location
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
messageHistory = value.value;
|
|
397
383
|
}
|
|
398
384
|
}
|
|
399
385
|
if (!onRaw) {
|
|
@@ -402,14 +388,13 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
|
|
|
402
388
|
if (!hasRun) {
|
|
403
389
|
throw new BuildError(`behavior '${name}' is missing a run function`, location);
|
|
404
390
|
}
|
|
405
|
-
const execution = extractExecutionConfig(properties);
|
|
391
|
+
const execution = extractExecutionConfig(properties, file);
|
|
406
392
|
const bounds = findStatementBounds(ancestors);
|
|
407
393
|
out.push({
|
|
408
394
|
name,
|
|
409
395
|
onRaw,
|
|
410
396
|
on: "",
|
|
411
397
|
execution,
|
|
412
|
-
messageHistory,
|
|
413
398
|
location,
|
|
414
399
|
statementStart: bounds.start,
|
|
415
400
|
statementEnd: bounds.end
|
|
@@ -490,7 +475,7 @@ function parseConversationCall(node, args, ancestors, file, out) {
|
|
|
490
475
|
location
|
|
491
476
|
);
|
|
492
477
|
}
|
|
493
|
-
const execution = extractExecutionConfig(properties);
|
|
478
|
+
const execution = extractExecutionConfig(properties, file);
|
|
494
479
|
const bounds = findStatementBounds(ancestors);
|
|
495
480
|
out.push({
|
|
496
481
|
name,
|
|
@@ -565,11 +550,7 @@ var MEMORY_EVENT_MAP = {
|
|
|
565
550
|
onTensionEmerged: "knowledge:tension.emerged",
|
|
566
551
|
onTensionEscalated: "knowledge:tension.escalated",
|
|
567
552
|
onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
|
|
568
|
-
onRoutineBroken: "knowledge:routine.broken"
|
|
569
|
-
onContradiction: "knowledge:contradiction",
|
|
570
|
-
onDeadlinePassed: "knowledge:deadline_passed",
|
|
571
|
-
onIntentionExpired: "knowledge:intention_expired",
|
|
572
|
-
onTemporalDueSoon: "knowledge:temporal_due_soon"
|
|
553
|
+
onRoutineBroken: "knowledge:routine.broken"
|
|
573
554
|
};
|
|
574
555
|
function resolveAll(files) {
|
|
575
556
|
const signalVarMap = /* @__PURE__ */ new Map();
|
|
@@ -854,6 +835,228 @@ function validateEndpointsSection(endpoints, errors) {
|
|
|
854
835
|
var RESERVED_BUNDLE_PREFIX = "__kaelen.";
|
|
855
836
|
var BINDING_SCOPES = /* @__PURE__ */ new Set(["project", "principal"]);
|
|
856
837
|
var CREDENTIAL_SOURCES = /* @__PURE__ */ new Set(["none", "platform", "secret_bundle"]);
|
|
838
|
+
var TARGET_EXECUTION_KEYS = /* @__PURE__ */ new Set([
|
|
839
|
+
"applications",
|
|
840
|
+
"endpoints",
|
|
841
|
+
"secret_names",
|
|
842
|
+
"timeout_ms",
|
|
843
|
+
"memory_mb",
|
|
844
|
+
"max_tool_calls",
|
|
845
|
+
"rate_budget"
|
|
846
|
+
]);
|
|
847
|
+
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
848
|
+
"authorization",
|
|
849
|
+
"proxy-authorization",
|
|
850
|
+
"proxy-authenticate",
|
|
851
|
+
"www-authenticate",
|
|
852
|
+
"cookie",
|
|
853
|
+
"set-cookie",
|
|
854
|
+
"x-api-key",
|
|
855
|
+
"x-auth-token",
|
|
856
|
+
"x-access-token",
|
|
857
|
+
"x-secret",
|
|
858
|
+
"x-secret-key",
|
|
859
|
+
"x-amz-security-token"
|
|
860
|
+
]);
|
|
861
|
+
var HTTP_LIKE_KINDS = /* @__PURE__ */ new Set(["http", "graphql", "httpApi", "graphqlApi"]);
|
|
862
|
+
var OP_RESOURCE_KINDS = /* @__PURE__ */ new Set(["httpApi", "graphqlApi"]);
|
|
863
|
+
function countPlaceholders(format) {
|
|
864
|
+
let count = 0;
|
|
865
|
+
let idx = 0;
|
|
866
|
+
while ((idx = format.indexOf("{}", idx)) !== -1) {
|
|
867
|
+
count++;
|
|
868
|
+
idx += 2;
|
|
869
|
+
}
|
|
870
|
+
return count;
|
|
871
|
+
}
|
|
872
|
+
function validateStaticHeaders(owner, headers, errors) {
|
|
873
|
+
if (headers === void 0) return;
|
|
874
|
+
if (!isRecord2(headers)) {
|
|
875
|
+
errors.push(`${owner} headers must be an object`);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
for (const name of Object.keys(headers)) {
|
|
879
|
+
if (SECRET_HEADER_NAMES.has(name.toLowerCase())) {
|
|
880
|
+
errors.push(
|
|
881
|
+
`${owner} headers entry "${name}" is secret-bearing; declare it via credentialHeaders instead`
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function validateCredentialHeadersBlock(owner, declared, errors, secretBundleNames, resourceSecretBundle, secretBundleSlots) {
|
|
887
|
+
if (declared === void 0) return;
|
|
888
|
+
if (!Array.isArray(declared)) {
|
|
889
|
+
errors.push(`${owner} credentialHeaders must be an array`);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
for (const entry of declared) {
|
|
893
|
+
if (!isRecord2(entry)) {
|
|
894
|
+
errors.push(`${owner} credentialHeaders entry must be an object`);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const name = stringField2(entry, "name");
|
|
898
|
+
if (!name) {
|
|
899
|
+
errors.push(`${owner} credentialHeaders entry is missing a string name`);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (SECRET_HEADER_NAMES.has(name.toLowerCase())) ;
|
|
903
|
+
const source = entry.source;
|
|
904
|
+
if (!isRecord2(source)) {
|
|
905
|
+
errors.push(
|
|
906
|
+
`${owner} credentialHeaders "${name}" source must be an object`
|
|
907
|
+
);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const kind = stringField2(source, "kind");
|
|
911
|
+
if (kind === "credential") {
|
|
912
|
+
const field = stringField2(source, "field");
|
|
913
|
+
if (!field) {
|
|
914
|
+
errors.push(
|
|
915
|
+
`${owner} credentialHeaders "${name}" credential source missing field`
|
|
916
|
+
);
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (resourceSecretBundle === void 0) {
|
|
920
|
+
errors.push(
|
|
921
|
+
`${owner} credentialHeaders "${name}" uses credential source but resource declares no auth/secret_bundle`
|
|
922
|
+
);
|
|
923
|
+
} else if (!resourceSecretBundle.startsWith(RESERVED_BUNDLE_PREFIX)) {
|
|
924
|
+
const slots = secretBundleSlots.get(resourceSecretBundle);
|
|
925
|
+
if (slots && !slots.has(field)) {
|
|
926
|
+
errors.push(
|
|
927
|
+
`${owner} credentialHeaders "${name}" credential field "${field}" is not declared in secret bundle "${resourceSecretBundle}"`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
} else if (kind === "rawSecret" || kind === "raw_secret") {
|
|
932
|
+
const secretName = stringField2(source, "name");
|
|
933
|
+
if (!secretName) {
|
|
934
|
+
errors.push(
|
|
935
|
+
`${owner} credentialHeaders "${name}" raw_secret source missing name`
|
|
936
|
+
);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (!secretName.startsWith(RESERVED_BUNDLE_PREFIX) && !secretBundleNames.has(secretName)) {
|
|
940
|
+
errors.push(
|
|
941
|
+
`${owner} credentialHeaders "${name}" references unknown raw_secret "${secretName}"`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
errors.push(
|
|
946
|
+
`${owner} credentialHeaders "${name}" source.kind must be credential or rawSecret`
|
|
947
|
+
);
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
const format = entry.format;
|
|
951
|
+
if (format !== void 0) {
|
|
952
|
+
if (typeof format !== "string" || countPlaceholders(format) !== 1) {
|
|
953
|
+
errors.push(
|
|
954
|
+
`${owner} credentialHeaders "${name}" format must contain exactly one {} placeholder`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function validateRequestMappingBlock(owner, declared, errors) {
|
|
961
|
+
if (declared === void 0) return;
|
|
962
|
+
if (!isRecord2(declared)) {
|
|
963
|
+
errors.push(`${owner} requestMapping must be an object`);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
for (const key of ["pathParams", "query"]) {
|
|
967
|
+
const value = declared[key];
|
|
968
|
+
if (value === void 0) continue;
|
|
969
|
+
if (!Array.isArray(value)) {
|
|
970
|
+
errors.push(`${owner} requestMapping.${key} must be an array`);
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
for (const entry of value) {
|
|
974
|
+
if (typeof entry === "string") {
|
|
975
|
+
if (!entry.trim()) {
|
|
976
|
+
errors.push(
|
|
977
|
+
`${owner} requestMapping.${key} entries must be non-empty strings or {from, as?} objects`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (isRecord2(entry)) {
|
|
983
|
+
if (!stringField2(entry, "from")) {
|
|
984
|
+
errors.push(
|
|
985
|
+
`${owner} requestMapping.${key} entry.from must be a non-empty string`
|
|
986
|
+
);
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (entry.as !== void 0 && (typeof entry.as !== "string" || !entry.as.trim())) {
|
|
990
|
+
errors.push(
|
|
991
|
+
`${owner} requestMapping.${key} entry.as must be a non-empty string when provided`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
errors.push(
|
|
997
|
+
`${owner} requestMapping.${key} entries must be strings or {from, as?} objects`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (declared.body !== void 0 && typeof declared.body !== "string" && declared.body !== null) {
|
|
1002
|
+
errors.push(`${owner} requestMapping.body must be a string or null`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function validateHttpLikeConfig(appName, resourceName, resourceKind, config, secretBundleNames, resourceSecretBundle, secretBundleSlots, errors) {
|
|
1006
|
+
if (!HTTP_LIKE_KINDS.has(resourceKind)) return;
|
|
1007
|
+
if (!isRecord2(config)) return;
|
|
1008
|
+
const base = `resource "${appName}/${resourceName}"`;
|
|
1009
|
+
validateStaticHeaders(base, config.headers, errors);
|
|
1010
|
+
validateCredentialHeadersBlock(
|
|
1011
|
+
base,
|
|
1012
|
+
config.credentialHeaders,
|
|
1013
|
+
errors,
|
|
1014
|
+
secretBundleNames,
|
|
1015
|
+
resourceSecretBundle,
|
|
1016
|
+
secretBundleSlots
|
|
1017
|
+
);
|
|
1018
|
+
if (resourceKind === "http") {
|
|
1019
|
+
validateRequestMappingBlock(base, config.requestMapping, errors);
|
|
1020
|
+
}
|
|
1021
|
+
if (resourceKind === "graphql") {
|
|
1022
|
+
if (config.variablesMapping !== void 0 && typeof config.variablesMapping !== "string") {
|
|
1023
|
+
errors.push(`${base} variablesMapping must be a string`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (OP_RESOURCE_KINDS.has(resourceKind)) {
|
|
1027
|
+
const ops = config.operations;
|
|
1028
|
+
if (ops !== void 0 && !isRecord2(ops)) {
|
|
1029
|
+
errors.push(`${base} operations must be an object`);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (isRecord2(ops)) {
|
|
1033
|
+
for (const [opName, opSpec] of Object.entries(ops)) {
|
|
1034
|
+
const owner = `${base} operation "${opName}"`;
|
|
1035
|
+
if (!isRecord2(opSpec)) {
|
|
1036
|
+
errors.push(`${owner} must be an object`);
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
validateStaticHeaders(owner, opSpec.headers, errors);
|
|
1040
|
+
validateCredentialHeadersBlock(
|
|
1041
|
+
owner,
|
|
1042
|
+
opSpec.credentialHeaders,
|
|
1043
|
+
errors,
|
|
1044
|
+
secretBundleNames,
|
|
1045
|
+
resourceSecretBundle,
|
|
1046
|
+
secretBundleSlots
|
|
1047
|
+
);
|
|
1048
|
+
if (resourceKind === "httpApi") {
|
|
1049
|
+
validateRequestMappingBlock(owner, opSpec.requestMapping, errors);
|
|
1050
|
+
}
|
|
1051
|
+
if (resourceKind === "graphqlApi") {
|
|
1052
|
+
if (opSpec.variablesMapping !== void 0 && typeof opSpec.variablesMapping !== "string") {
|
|
1053
|
+
errors.push(`${owner} variablesMapping must be a string`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
857
1060
|
function isRecord2(value) {
|
|
858
1061
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
859
1062
|
}
|
|
@@ -865,12 +1068,6 @@ function stringField2(record, key) {
|
|
|
865
1068
|
const value = record[key];
|
|
866
1069
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
867
1070
|
}
|
|
868
|
-
function scopeListField(record, key) {
|
|
869
|
-
const value = record[key];
|
|
870
|
-
if (value === void 0) return void 0;
|
|
871
|
-
if (!Array.isArray(value)) return [];
|
|
872
|
-
return value.flatMap((entry) => scopeEntryName(entry) ?? []);
|
|
873
|
-
}
|
|
874
1071
|
function scopeEntryName(entry) {
|
|
875
1072
|
if (typeof entry === "string" && entry.trim()) return entry;
|
|
876
1073
|
if (isRecord2(entry)) return stringField2(entry, "name");
|
|
@@ -916,6 +1113,11 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
|
|
|
916
1113
|
return;
|
|
917
1114
|
}
|
|
918
1115
|
const executionRecord = isRecord2(execution) ? execution : {};
|
|
1116
|
+
for (const key of Object.keys(executionRecord)) {
|
|
1117
|
+
if (!TARGET_EXECUTION_KEYS.has(key)) {
|
|
1118
|
+
errors.push(`${owner} execution contains unsupported key "${key}"`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
919
1121
|
const applicationRefs = validateScopeList(
|
|
920
1122
|
owner,
|
|
921
1123
|
"execution.applications",
|
|
@@ -938,31 +1140,60 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
|
|
|
938
1140
|
errors.push(`${owner} references undeclared endpoint "${ref}"`);
|
|
939
1141
|
}
|
|
940
1142
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1143
|
+
const secretRefs = validateScopeList(
|
|
1144
|
+
owner,
|
|
1145
|
+
"execution.secret_names",
|
|
1146
|
+
executionRecord.secret_names,
|
|
1147
|
+
errors
|
|
1148
|
+
);
|
|
1149
|
+
for (const ref of secretRefs) {
|
|
1150
|
+
if (ref.startsWith(RESERVED_BUNDLE_PREFIX)) {
|
|
1151
|
+
errors.push(
|
|
1152
|
+
`${owner} execution.secret_names entry "${ref}" uses the reserved "${RESERVED_BUNDLE_PREFIX}" prefix`
|
|
1153
|
+
);
|
|
949
1154
|
}
|
|
950
1155
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1156
|
+
validatePositiveInteger(owner, "execution.timeout_ms", executionRecord.timeout_ms, errors);
|
|
1157
|
+
validatePositiveInteger(owner, "execution.memory_mb", executionRecord.memory_mb, errors);
|
|
1158
|
+
validateNonNegativeInteger(owner, "execution.max_tool_calls", executionRecord.max_tool_calls, errors);
|
|
1159
|
+
validateRateBudget(owner, executionRecord.rate_budget, errors);
|
|
1160
|
+
if (target.endpoints !== void 0) {
|
|
1161
|
+
errors.push(`${owner} endpoints is no longer supported; use execution.endpoints`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function validateRateBudget(owner, value, errors) {
|
|
1165
|
+
if (value === void 0) return;
|
|
1166
|
+
if (!isRecord2(value)) {
|
|
1167
|
+
errors.push(`${owner} execution.rate_budget must be an object`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
validateBudgetMap(owner, "execution.rate_budget.per_application", value.per_application, errors);
|
|
1171
|
+
validateBudgetMap(owner, "execution.rate_budget.per_capability", value.per_capability, errors);
|
|
1172
|
+
}
|
|
1173
|
+
function validateBudgetMap(owner, field, value, errors) {
|
|
1174
|
+
if (value === void 0) return;
|
|
1175
|
+
if (!isRecord2(value)) {
|
|
1176
|
+
errors.push(`${owner} ${field} must be an object`);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
for (const [key, budget] of Object.entries(value)) {
|
|
1180
|
+
if (!key.trim()) {
|
|
1181
|
+
errors.push(`${owner} ${field} keys must be non-empty strings`);
|
|
1182
|
+
continue;
|
|
965
1183
|
}
|
|
1184
|
+
validateNonNegativeInteger(owner, `${field}.${key}`, budget, errors);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function validatePositiveInteger(owner, field, value, errors) {
|
|
1188
|
+
if (value === void 0) return;
|
|
1189
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1190
|
+
errors.push(`${owner} ${field} must be a positive integer`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function validateNonNegativeInteger(owner, field, value, errors) {
|
|
1194
|
+
if (value === void 0) return;
|
|
1195
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1196
|
+
errors.push(`${owner} ${field} must be a non-negative integer`);
|
|
966
1197
|
}
|
|
967
1198
|
}
|
|
968
1199
|
function capabilityId(applicationName, resourceName, toolName) {
|
|
@@ -1056,11 +1287,11 @@ function validateManifest(manifest) {
|
|
|
1056
1287
|
const root = isRecord2(manifest) ? manifest : {};
|
|
1057
1288
|
const errors = [];
|
|
1058
1289
|
const seenCapabilities = /* @__PURE__ */ new Set();
|
|
1059
|
-
if (root.contract_version !== "io.invocation.
|
|
1060
|
-
errors.push("contract_version must be io.invocation.
|
|
1290
|
+
if (root.contract_version !== "io.invocation.v2") {
|
|
1291
|
+
errors.push("contract_version must be io.invocation.v2");
|
|
1061
1292
|
}
|
|
1062
|
-
if (root.snapshot_version !== "io_runtime.snapshot.
|
|
1063
|
-
errors.push("snapshot_version must be io_runtime.snapshot.
|
|
1293
|
+
if (root.snapshot_version !== "io_runtime.snapshot.v2") {
|
|
1294
|
+
errors.push("snapshot_version must be io_runtime.snapshot.v2");
|
|
1064
1295
|
}
|
|
1065
1296
|
const secretBundles = arrayField(root, "secret_bundles").filter(isRecord2);
|
|
1066
1297
|
duplicateNames(secretBundles, "secret bundle", errors);
|
|
@@ -1075,6 +1306,14 @@ function validateManifest(manifest) {
|
|
|
1075
1306
|
const secretNames = new Set(
|
|
1076
1307
|
secretBundles.map((entry) => stringField2(entry, "name")).filter((name) => !!name)
|
|
1077
1308
|
);
|
|
1309
|
+
const secretBundleSlots = /* @__PURE__ */ new Map();
|
|
1310
|
+
for (const bundle of secretBundles) {
|
|
1311
|
+
const bundleName = stringField2(bundle, "name");
|
|
1312
|
+
if (!bundleName) continue;
|
|
1313
|
+
const slots = bundle.slots;
|
|
1314
|
+
if (!isRecord2(slots)) continue;
|
|
1315
|
+
secretBundleSlots.set(bundleName, new Set(Object.keys(slots)));
|
|
1316
|
+
}
|
|
1078
1317
|
const declaredEndpoints = validateEndpointsSection(
|
|
1079
1318
|
arrayField(root, "endpoints"),
|
|
1080
1319
|
errors
|
|
@@ -1121,6 +1360,17 @@ function validateManifest(manifest) {
|
|
|
1121
1360
|
} else {
|
|
1122
1361
|
validateResourceCapability(appName, resourceName, capability, errors);
|
|
1123
1362
|
}
|
|
1363
|
+
const resourceKindForConfig = stringField2(resource, "kind") ?? "";
|
|
1364
|
+
validateHttpLikeConfig(
|
|
1365
|
+
appName,
|
|
1366
|
+
resourceName,
|
|
1367
|
+
resourceKindForConfig,
|
|
1368
|
+
resource.config,
|
|
1369
|
+
secretNames,
|
|
1370
|
+
secretBundle,
|
|
1371
|
+
secretBundleSlots,
|
|
1372
|
+
errors
|
|
1373
|
+
);
|
|
1124
1374
|
const tools = toolEntries.filter(isRecord2);
|
|
1125
1375
|
duplicateNames(tools, `resource "${appName}/${resourceName}" tool`, errors);
|
|
1126
1376
|
for (const tool of tools) {
|
|
@@ -1588,6 +1838,12 @@ var RUNTIME_MEMORY_SIGNALS = {
|
|
|
1588
1838
|
onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
|
|
1589
1839
|
onRoutineBroken: "knowledge:routine.broken"
|
|
1590
1840
|
};
|
|
1841
|
+
var RUNTIME_SDK_PASSTHROUGH_EXPORTS = /* @__PURE__ */ new Set([
|
|
1842
|
+
"google",
|
|
1843
|
+
"microsoft",
|
|
1844
|
+
"httpApi",
|
|
1845
|
+
"graphqlApi"
|
|
1846
|
+
]);
|
|
1591
1847
|
var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
|
|
1592
1848
|
"ioSchema",
|
|
1593
1849
|
"zodSchema",
|
|
@@ -1608,7 +1864,8 @@ var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
|
|
|
1608
1864
|
"graphql",
|
|
1609
1865
|
"http",
|
|
1610
1866
|
"calendar",
|
|
1611
|
-
"contacts"
|
|
1867
|
+
"contacts",
|
|
1868
|
+
...RUNTIME_SDK_PASSTHROUGH_EXPORTS
|
|
1612
1869
|
]);
|
|
1613
1870
|
var COMPILED_AWAY_EXPORTS = /* @__PURE__ */ new Set(["behavior", "conversation"]);
|
|
1614
1871
|
function normalizeSdkSpecifier(specifier) {
|
|
@@ -1870,6 +2127,29 @@ ${entries.map(([key, value]) => ` ${key}: Object.freeze({ name: ${JSON.stringif
|
|
|
1870
2127
|
}`
|
|
1871
2128
|
);
|
|
1872
2129
|
}
|
|
2130
|
+
const needsPassthroughHelper = [...RUNTIME_SDK_PASSTHROUGH_EXPORTS].some(
|
|
2131
|
+
(name) => usage.exports.has(name)
|
|
2132
|
+
);
|
|
2133
|
+
if (needsPassthroughHelper) {
|
|
2134
|
+
lines.push(
|
|
2135
|
+
`function passthroughFrozen(options) {
|
|
2136
|
+
if (options && typeof options === "object" && !Array.isArray(options)) {
|
|
2137
|
+
return Object.freeze({ ...options });
|
|
2138
|
+
}
|
|
2139
|
+
return Object.freeze({});
|
|
2140
|
+
}`
|
|
2141
|
+
);
|
|
2142
|
+
for (const passthrough of RUNTIME_SDK_PASSTHROUGH_EXPORTS) {
|
|
2143
|
+
if (!usage.exports.has(passthrough)) {
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
lines.push(
|
|
2147
|
+
`export function ${passthrough}(options) {
|
|
2148
|
+
return passthroughFrozen(options);
|
|
2149
|
+
}`
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
1873
2153
|
return lines.join("\n\n");
|
|
1874
2154
|
}
|
|
1875
2155
|
function sdkAliasPlugin() {
|
|
@@ -2208,65 +2488,514 @@ async function generateManifest(input) {
|
|
|
2208
2488
|
);
|
|
2209
2489
|
return m;
|
|
2210
2490
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
|
|
2216
|
-
var INTERNAL_ON_END = "__io_cli_compiled_on_end";
|
|
2217
|
-
var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
|
|
2218
|
-
function conversationChannel(name) {
|
|
2219
|
-
return `session:${name}`;
|
|
2220
|
-
}
|
|
2221
|
-
function parseModule2(source) {
|
|
2222
|
-
return acorn.parse(source, {
|
|
2223
|
-
ecmaVersion: "latest",
|
|
2224
|
-
sourceType: "module"
|
|
2225
|
-
});
|
|
2491
|
+
|
|
2492
|
+
// src/build/stable-json.ts
|
|
2493
|
+
function stableStringify(value, indent) {
|
|
2494
|
+
return JSON.stringify(canonicalize(value), null, indent);
|
|
2226
2495
|
}
|
|
2227
|
-
function
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
} catch (error) {
|
|
2232
|
-
throw new BuildError(
|
|
2233
|
-
`failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
|
|
2234
|
-
);
|
|
2496
|
+
function canonicalize(value) {
|
|
2497
|
+
if (value === null) return null;
|
|
2498
|
+
if (Array.isArray(value)) {
|
|
2499
|
+
return value.map(canonicalize);
|
|
2235
2500
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2501
|
+
if (typeof value === "object") {
|
|
2502
|
+
const obj = value;
|
|
2503
|
+
const sorted = {};
|
|
2504
|
+
for (const key of Object.keys(obj).sort()) {
|
|
2505
|
+
sorted[key] = canonicalize(obj[key]);
|
|
2506
|
+
}
|
|
2507
|
+
return sorted;
|
|
2242
2508
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2509
|
+
return value;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
// src/build/catalog.ts
|
|
2513
|
+
var CATALOG_CONTRACT_VERSION = "io.invocation.v2";
|
|
2514
|
+
var CATALOG_SNAPSHOT_VERSION = "io_runtime.snapshot.v2";
|
|
2515
|
+
var WEBHOOK_RESOURCE_KIND = "webhook";
|
|
2516
|
+
var PLATFORM_TOOL_KINDS = /* @__PURE__ */ new Set(["email", "calendar", "contacts"]);
|
|
2517
|
+
var STREAM_PUBLISH_KINDS = /* @__PURE__ */ new Set([
|
|
2518
|
+
"kafka",
|
|
2519
|
+
"rabbitmq",
|
|
2520
|
+
"redis",
|
|
2521
|
+
"pubsub",
|
|
2522
|
+
"sqs",
|
|
2523
|
+
"websocket"
|
|
2524
|
+
]);
|
|
2525
|
+
function sha256Hex(value) {
|
|
2526
|
+
return `sha256:${createHash("sha256").update(stableStringify(value)).digest("hex")}`;
|
|
2527
|
+
}
|
|
2528
|
+
function isRecord3(value) {
|
|
2529
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2530
|
+
}
|
|
2531
|
+
function getString(record, key) {
|
|
2532
|
+
const value = record[key];
|
|
2533
|
+
return typeof value === "string" ? value : void 0;
|
|
2534
|
+
}
|
|
2535
|
+
function getRecord(record, key) {
|
|
2536
|
+
const value = record[key];
|
|
2537
|
+
return isRecord3(value) ? value : void 0;
|
|
2538
|
+
}
|
|
2539
|
+
function getArray(record, key) {
|
|
2540
|
+
const value = record[key];
|
|
2541
|
+
return Array.isArray(value) ? value : [];
|
|
2542
|
+
}
|
|
2543
|
+
function getNumber(record, key) {
|
|
2544
|
+
const value = record[key];
|
|
2545
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2546
|
+
}
|
|
2547
|
+
function countPlaceholders2(format) {
|
|
2548
|
+
let count = 0;
|
|
2549
|
+
let idx = 0;
|
|
2550
|
+
while ((idx = format.indexOf("{}", idx)) !== -1) {
|
|
2551
|
+
count++;
|
|
2552
|
+
idx += 2;
|
|
2553
|
+
}
|
|
2554
|
+
return count;
|
|
2555
|
+
}
|
|
2556
|
+
function deriveAuditRequirement(effect) {
|
|
2557
|
+
return effect === "write" ? "pre_call" : "finish_only";
|
|
2558
|
+
}
|
|
2559
|
+
function deriveEffect(toolMode) {
|
|
2560
|
+
return toolMode === "read" ? "read" : "write";
|
|
2561
|
+
}
|
|
2562
|
+
function normalizeCredentialHeaders(declared, resourceId, context, warnings) {
|
|
2563
|
+
if (!Array.isArray(declared)) return [];
|
|
2564
|
+
const out = [];
|
|
2565
|
+
for (const entry of declared) {
|
|
2566
|
+
if (!isRecord3(entry)) {
|
|
2567
|
+
warnings.push(`${context}: skipped non-object credential header entry`);
|
|
2568
|
+
continue;
|
|
2246
2569
|
}
|
|
2247
|
-
const
|
|
2248
|
-
if (
|
|
2249
|
-
|
|
2570
|
+
const name = getString(entry, "name");
|
|
2571
|
+
if (!name) {
|
|
2572
|
+
warnings.push(`${context}: credential header missing name`);
|
|
2573
|
+
continue;
|
|
2250
2574
|
}
|
|
2251
|
-
const
|
|
2252
|
-
if (
|
|
2253
|
-
|
|
2575
|
+
const source = getRecord(entry, "source");
|
|
2576
|
+
if (!source) {
|
|
2577
|
+
warnings.push(`${context}: credential header "${name}" missing source`);
|
|
2578
|
+
continue;
|
|
2254
2579
|
}
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2580
|
+
const sourceKind = getString(source, "kind");
|
|
2581
|
+
let normalizedSource;
|
|
2582
|
+
if (sourceKind === "credential") {
|
|
2583
|
+
const field = getString(source, "field");
|
|
2584
|
+
if (!field) {
|
|
2585
|
+
warnings.push(
|
|
2586
|
+
`${context}: credential header "${name}" credential source missing field`
|
|
2587
|
+
);
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
normalizedSource = { kind: "credential", resource_id: resourceId, field };
|
|
2591
|
+
} else if (sourceKind === "rawSecret" || sourceKind === "raw_secret") {
|
|
2592
|
+
const secretName = getString(source, "name");
|
|
2593
|
+
if (!secretName) {
|
|
2594
|
+
warnings.push(
|
|
2595
|
+
`${context}: credential header "${name}" raw_secret source missing name`
|
|
2596
|
+
);
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
normalizedSource = { kind: "raw_secret", name: secretName };
|
|
2600
|
+
} else {
|
|
2601
|
+
warnings.push(
|
|
2602
|
+
`${context}: credential header "${name}" has unsupported source kind "${sourceKind}"`
|
|
2603
|
+
);
|
|
2604
|
+
continue;
|
|
2258
2605
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2606
|
+
const format = getString(entry, "format") ?? "{}";
|
|
2607
|
+
if (countPlaceholders2(format) !== 1) {
|
|
2608
|
+
warnings.push(
|
|
2609
|
+
`${context}: credential header "${name}" format must contain exactly one {} placeholder`
|
|
2610
|
+
);
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
out.push({ name, source: normalizedSource, format });
|
|
2265
2614
|
}
|
|
2266
|
-
return
|
|
2615
|
+
return out;
|
|
2267
2616
|
}
|
|
2268
|
-
function
|
|
2269
|
-
if (
|
|
2617
|
+
function normalizeRequestMapping(declared) {
|
|
2618
|
+
if (!isRecord3(declared)) {
|
|
2619
|
+
return { path_params: [], query: [] };
|
|
2620
|
+
}
|
|
2621
|
+
const pathParams = collectMappingValues(declared["pathParams"]);
|
|
2622
|
+
const query = collectMappingValues(declared["query"]);
|
|
2623
|
+
const body = declared["body"];
|
|
2624
|
+
const bodyValue = typeof body === "string" ? body : body === null ? null : void 0;
|
|
2625
|
+
return {
|
|
2626
|
+
path_params: pathParams,
|
|
2627
|
+
query,
|
|
2628
|
+
...bodyValue !== void 0 ? { body: bodyValue } : {}
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function collectMappingValues(value) {
|
|
2632
|
+
if (!Array.isArray(value)) return [];
|
|
2633
|
+
const out = [];
|
|
2634
|
+
for (const entry of value) {
|
|
2635
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2636
|
+
out.push(entry);
|
|
2637
|
+
} else if (isRecord3(entry)) {
|
|
2638
|
+
const from = getString(entry, "from");
|
|
2639
|
+
if (!from?.trim()) continue;
|
|
2640
|
+
const alias = getString(entry, "as");
|
|
2641
|
+
out.push(alias?.trim() ? { from, as: alias } : from);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
return out;
|
|
2645
|
+
}
|
|
2646
|
+
function buildExecutor(ctx) {
|
|
2647
|
+
if (PLATFORM_TOOL_KINDS.has(ctx.resourceKind)) {
|
|
2648
|
+
return {
|
|
2649
|
+
kind: "platform_tool",
|
|
2650
|
+
provider_kind: ctx.providerKind,
|
|
2651
|
+
backend_kind: ctx.backendKind,
|
|
2652
|
+
operation: ctx.toolName
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
if (ctx.resourceKind === WEBHOOK_RESOURCE_KIND && ctx.toolName === "respond") {
|
|
2656
|
+
return { kind: "webhook_response" };
|
|
2657
|
+
}
|
|
2658
|
+
if (STREAM_PUBLISH_KINDS.has(ctx.resourceKind)) {
|
|
2659
|
+
return {
|
|
2660
|
+
kind: "stream_publish",
|
|
2661
|
+
provider_kind: ctx.providerKind,
|
|
2662
|
+
backend_kind: ctx.backendKind,
|
|
2663
|
+
destination_ref: ctx.configHash
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
if (ctx.resourceKind === "http") {
|
|
2667
|
+
return buildHttpRequestExecutor(ctx);
|
|
2668
|
+
}
|
|
2669
|
+
if (ctx.resourceKind === "graphql") {
|
|
2670
|
+
return buildGraphQlRequestExecutor(ctx);
|
|
2671
|
+
}
|
|
2672
|
+
if (ctx.resourceKind === "httpApi") {
|
|
2673
|
+
return buildHttpOperationExecutor(ctx);
|
|
2674
|
+
}
|
|
2675
|
+
if (ctx.resourceKind === "graphqlApi") {
|
|
2676
|
+
return buildGraphQlOperationExecutor(ctx);
|
|
2677
|
+
}
|
|
2678
|
+
return {
|
|
2679
|
+
kind: "unsupported",
|
|
2680
|
+
reason: `unknown_resource_kind:${ctx.resourceKind}`
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
function buildHttpRequestExecutor(ctx) {
|
|
2684
|
+
const config = ctx.resourceConfig;
|
|
2685
|
+
const url = getString(config, "url") ?? "";
|
|
2686
|
+
const method = (getString(config, "method") ?? "GET").toUpperCase();
|
|
2687
|
+
const headers = getRecord(config, "headers") ?? {};
|
|
2688
|
+
const queryParams = getRecord(config, "queryParams") ?? {};
|
|
2689
|
+
const body = config["body"];
|
|
2690
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2691
|
+
config["credentialHeaders"],
|
|
2692
|
+
ctx.resourceId,
|
|
2693
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2694
|
+
ctx.warnings
|
|
2695
|
+
);
|
|
2696
|
+
return {
|
|
2697
|
+
kind: "http_request",
|
|
2698
|
+
url,
|
|
2699
|
+
method,
|
|
2700
|
+
headers_ref: ctx.registerBlob(headers),
|
|
2701
|
+
credential_headers: credentialHeaders,
|
|
2702
|
+
query_params_ref: ctx.registerBlob(queryParams),
|
|
2703
|
+
body_ref: ctx.registerBlob(body ?? null),
|
|
2704
|
+
timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
function buildGraphQlRequestExecutor(ctx) {
|
|
2708
|
+
const config = ctx.resourceConfig;
|
|
2709
|
+
const endpoint = getString(config, "endpoint") ?? "";
|
|
2710
|
+
const query = getString(config, "query") ?? "";
|
|
2711
|
+
const headers = getRecord(config, "headers") ?? {};
|
|
2712
|
+
const variables = getRecord(config, "variables") ?? {};
|
|
2713
|
+
const operationName = getString(config, "operationName") ?? null;
|
|
2714
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2715
|
+
config["credentialHeaders"],
|
|
2716
|
+
ctx.resourceId,
|
|
2717
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2718
|
+
ctx.warnings
|
|
2719
|
+
);
|
|
2720
|
+
return {
|
|
2721
|
+
kind: "graphql_request",
|
|
2722
|
+
endpoint,
|
|
2723
|
+
query_ref: ctx.registerBlob(query),
|
|
2724
|
+
operation_name: operationName,
|
|
2725
|
+
variables_ref: ctx.registerBlob(variables),
|
|
2726
|
+
headers_ref: ctx.registerBlob(headers),
|
|
2727
|
+
credential_headers: credentialHeaders,
|
|
2728
|
+
timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
function buildHttpOperationExecutor(ctx) {
|
|
2732
|
+
const config = ctx.resourceConfig;
|
|
2733
|
+
const ops = getRecord(config, "operations") ?? {};
|
|
2734
|
+
const opSpec = getRecord(ops, ctx.toolName);
|
|
2735
|
+
if (!opSpec) {
|
|
2736
|
+
ctx.warnings.push(
|
|
2737
|
+
`tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
|
|
2738
|
+
);
|
|
2739
|
+
return {
|
|
2740
|
+
kind: "unsupported",
|
|
2741
|
+
reason: `httpApi_missing_op:${ctx.toolName}`
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
const baseUrl = getString(config, "baseUrl") ?? "";
|
|
2745
|
+
const resourceHeaders = getRecord(config, "headers") ?? {};
|
|
2746
|
+
const opHeaders = getRecord(opSpec, "headers") ?? {};
|
|
2747
|
+
const mergedHeaders = { ...resourceHeaders, ...opHeaders };
|
|
2748
|
+
const opCredentialHeaders = opSpec["credentialHeaders"];
|
|
2749
|
+
const resourceCredentialHeaders = config["credentialHeaders"];
|
|
2750
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2751
|
+
opCredentialHeaders ?? resourceCredentialHeaders,
|
|
2752
|
+
ctx.resourceId,
|
|
2753
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2754
|
+
ctx.warnings
|
|
2755
|
+
);
|
|
2756
|
+
const method = (getString(opSpec, "method") ?? "GET").toUpperCase();
|
|
2757
|
+
const path = getString(opSpec, "path") ?? "";
|
|
2758
|
+
const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
|
|
2759
|
+
return {
|
|
2760
|
+
kind: "http_operation",
|
|
2761
|
+
base_url: baseUrl,
|
|
2762
|
+
method,
|
|
2763
|
+
path,
|
|
2764
|
+
headers_ref: ctx.registerBlob(mergedHeaders),
|
|
2765
|
+
credential_headers: credentialHeaders,
|
|
2766
|
+
timeout_seconds: timeoutSeconds,
|
|
2767
|
+
request_mapping: normalizeRequestMapping(opSpec["requestMapping"])
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
function buildGraphQlOperationExecutor(ctx) {
|
|
2771
|
+
const config = ctx.resourceConfig;
|
|
2772
|
+
const ops = getRecord(config, "operations") ?? {};
|
|
2773
|
+
const opSpec = getRecord(ops, ctx.toolName);
|
|
2774
|
+
if (!opSpec) {
|
|
2775
|
+
ctx.warnings.push(
|
|
2776
|
+
`tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
|
|
2777
|
+
);
|
|
2778
|
+
return {
|
|
2779
|
+
kind: "unsupported",
|
|
2780
|
+
reason: `graphqlApi_missing_op:${ctx.toolName}`
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
const endpoint = getString(config, "endpoint") ?? "";
|
|
2784
|
+
const resourceHeaders = getRecord(config, "headers") ?? {};
|
|
2785
|
+
const opHeaders = getRecord(opSpec, "headers") ?? {};
|
|
2786
|
+
const mergedHeaders = { ...resourceHeaders, ...opHeaders };
|
|
2787
|
+
const opCredentialHeaders = opSpec["credentialHeaders"];
|
|
2788
|
+
const resourceCredentialHeaders = config["credentialHeaders"];
|
|
2789
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2790
|
+
opCredentialHeaders ?? resourceCredentialHeaders,
|
|
2791
|
+
ctx.resourceId,
|
|
2792
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2793
|
+
ctx.warnings
|
|
2794
|
+
);
|
|
2795
|
+
const query = getString(opSpec, "query") ?? "";
|
|
2796
|
+
const operationName = getString(opSpec, "operationName") ?? null;
|
|
2797
|
+
const variablesMapping = getString(opSpec, "variablesMapping") ?? "input";
|
|
2798
|
+
const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
|
|
2799
|
+
return {
|
|
2800
|
+
kind: "graphql_operation",
|
|
2801
|
+
endpoint,
|
|
2802
|
+
query_ref: ctx.registerBlob(query),
|
|
2803
|
+
operation_name: operationName,
|
|
2804
|
+
headers_ref: ctx.registerBlob(mergedHeaders),
|
|
2805
|
+
credential_headers: credentialHeaders,
|
|
2806
|
+
timeout_seconds: timeoutSeconds,
|
|
2807
|
+
variables_mapping: variablesMapping
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
function generateCatalog(manifest) {
|
|
2811
|
+
const warnings = [];
|
|
2812
|
+
const resources = {};
|
|
2813
|
+
const signals2 = {};
|
|
2814
|
+
const tools = {};
|
|
2815
|
+
const schemas = {};
|
|
2816
|
+
const registerBlob = (value) => {
|
|
2817
|
+
const hash = sha256Hex(value ?? {});
|
|
2818
|
+
if (!(hash in schemas)) {
|
|
2819
|
+
schemas[hash] = value ?? {};
|
|
2820
|
+
}
|
|
2821
|
+
return hash;
|
|
2822
|
+
};
|
|
2823
|
+
const apps = getArray(manifest, "apps").filter(isRecord3);
|
|
2824
|
+
for (const app of apps) {
|
|
2825
|
+
const appName = getString(app, "name");
|
|
2826
|
+
if (!appName) continue;
|
|
2827
|
+
const appResources2 = getArray(app, "resources").filter(isRecord3);
|
|
2828
|
+
for (const resource of appResources2) {
|
|
2829
|
+
const resourceName = getString(resource, "name");
|
|
2830
|
+
const resourceKind = getString(resource, "kind") ?? "";
|
|
2831
|
+
const backend = getString(resource, "backend") ?? "";
|
|
2832
|
+
const providerKind = getString(resource, "provider_kind") ?? resourceKind;
|
|
2833
|
+
if (!resourceName) continue;
|
|
2834
|
+
const resourceId = `${appName}.${resourceName}`;
|
|
2835
|
+
const config = getRecord(resource, "config") ?? {};
|
|
2836
|
+
const configHash = registerBlob(config);
|
|
2837
|
+
const capability = getRecord(resource, "capability") ?? {};
|
|
2838
|
+
const bindingScope = getString(capability, "binding_scope") ?? "project";
|
|
2839
|
+
const credentialSource = getString(capability, "credential_source") ?? "none";
|
|
2840
|
+
const requiresBindingValue = capability.requires_binding;
|
|
2841
|
+
const credentialExposure = requiresBindingValue === true ? "stdlib" : "none";
|
|
2842
|
+
const resourceSignals = getArray(resource, "signals").filter(isRecord3);
|
|
2843
|
+
const resourceTools2 = getArray(resource, "tools").filter(isRecord3);
|
|
2844
|
+
const emittedSignalIds = [];
|
|
2845
|
+
for (const signal of resourceSignals) {
|
|
2846
|
+
const signalName = getString(signal, "name");
|
|
2847
|
+
if (!signalName) continue;
|
|
2848
|
+
const signalId = `${resourceId}.${signalName}`;
|
|
2849
|
+
const signalScope = getString(signal, "scope") === "principal" ? "principal" : "project";
|
|
2850
|
+
const signalSchema = getRecord(signal, "schema") ?? {};
|
|
2851
|
+
const schemaRef = registerBlob(signalSchema);
|
|
2852
|
+
signals2[signalId] = {
|
|
2853
|
+
id: signalId,
|
|
2854
|
+
resource_id: resourceId,
|
|
2855
|
+
name: signalName,
|
|
2856
|
+
event_name: getString(signal, "event_name") ?? "",
|
|
2857
|
+
scope: signalScope,
|
|
2858
|
+
mode: getString(signal, "mode") ?? "",
|
|
2859
|
+
schema_ref: schemaRef
|
|
2860
|
+
};
|
|
2861
|
+
emittedSignalIds.push(signalId);
|
|
2862
|
+
}
|
|
2863
|
+
const emittedToolIds = [];
|
|
2864
|
+
for (const tool of resourceTools2) {
|
|
2865
|
+
const toolName = getString(tool, "name");
|
|
2866
|
+
if (!toolName) continue;
|
|
2867
|
+
const toolId = `${resourceId}.${toolName}`;
|
|
2868
|
+
const effect = deriveEffect(tool.mode);
|
|
2869
|
+
const toolCapability = getRecord(tool, "capability") ?? {};
|
|
2870
|
+
const toolBackendKind = getString(toolCapability, "backend_kind") ?? backend;
|
|
2871
|
+
const toolProviderKind = getString(toolCapability, "provider_kind") ?? providerKind;
|
|
2872
|
+
const toolCredentialExposure = getString(toolCapability, "credential_exposure") ?? "none";
|
|
2873
|
+
const argsSchemaRef = registerBlob(
|
|
2874
|
+
getRecord(tool, "args_schema") ?? {}
|
|
2875
|
+
);
|
|
2876
|
+
const resultSchemaRef = registerBlob(
|
|
2877
|
+
getRecord(tool, "result_schema") ?? {}
|
|
2878
|
+
);
|
|
2879
|
+
const executor = buildExecutor({
|
|
2880
|
+
resourceId,
|
|
2881
|
+
resourceKind,
|
|
2882
|
+
providerKind: toolProviderKind,
|
|
2883
|
+
backendKind: toolBackendKind,
|
|
2884
|
+
toolName,
|
|
2885
|
+
resourceConfig: config,
|
|
2886
|
+
configHash,
|
|
2887
|
+
registerBlob,
|
|
2888
|
+
warnings
|
|
2889
|
+
});
|
|
2890
|
+
if (executor.kind === "unsupported") {
|
|
2891
|
+
warnings.push(
|
|
2892
|
+
`tool "${toolId}" executor deferred: ${executor.reason}`
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
tools[toolId] = {
|
|
2896
|
+
id: toolId,
|
|
2897
|
+
resource_id: resourceId,
|
|
2898
|
+
name: toolName,
|
|
2899
|
+
effect,
|
|
2900
|
+
audit_requirement: deriveAuditRequirement(effect),
|
|
2901
|
+
credential_exposure: toolCredentialExposure,
|
|
2902
|
+
args_schema_ref: argsSchemaRef,
|
|
2903
|
+
result_schema_ref: resultSchemaRef,
|
|
2904
|
+
executor
|
|
2905
|
+
};
|
|
2906
|
+
emittedToolIds.push(toolId);
|
|
2907
|
+
}
|
|
2908
|
+
resources[resourceId] = {
|
|
2909
|
+
id: resourceId,
|
|
2910
|
+
application_name: appName,
|
|
2911
|
+
resource_name: resourceName,
|
|
2912
|
+
resource_kind: resourceKind,
|
|
2913
|
+
provider_kind: providerKind,
|
|
2914
|
+
backend_kind: backend,
|
|
2915
|
+
binding_scope: bindingScope,
|
|
2916
|
+
credential_source: credentialSource,
|
|
2917
|
+
credential_exposure: credentialExposure,
|
|
2918
|
+
config_ref: configHash,
|
|
2919
|
+
signals: emittedSignalIds,
|
|
2920
|
+
tools: emittedToolIds
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
const catalogBody = {
|
|
2925
|
+
contract_version: CATALOG_CONTRACT_VERSION,
|
|
2926
|
+
snapshot_version: CATALOG_SNAPSHOT_VERSION,
|
|
2927
|
+
resources,
|
|
2928
|
+
signals: signals2,
|
|
2929
|
+
tools,
|
|
2930
|
+
schemas
|
|
2931
|
+
};
|
|
2932
|
+
const catalogHash = sha256Hex(catalogBody);
|
|
2933
|
+
return {
|
|
2934
|
+
catalog: {
|
|
2935
|
+
...catalogBody,
|
|
2936
|
+
catalog_hash: catalogHash
|
|
2937
|
+
},
|
|
2938
|
+
warnings
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
var INTERNAL_CONFIG = "__io_cli_compiled_config";
|
|
2942
|
+
var INTERNAL_RUN = "__io_cli_compiled_run";
|
|
2943
|
+
var INTERNAL_ON_START = "__io_cli_compiled_on_start";
|
|
2944
|
+
var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
|
|
2945
|
+
var INTERNAL_ON_END = "__io_cli_compiled_on_end";
|
|
2946
|
+
var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
|
|
2947
|
+
function conversationChannel(name) {
|
|
2948
|
+
return `session:${name}`;
|
|
2949
|
+
}
|
|
2950
|
+
function parseModule2(source) {
|
|
2951
|
+
return acorn.parse(source, {
|
|
2952
|
+
ecmaVersion: "latest",
|
|
2953
|
+
sourceType: "module"
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
function rootStatementFor(source, statementStart, kind, name) {
|
|
2957
|
+
let ast;
|
|
2958
|
+
try {
|
|
2959
|
+
ast = parseModule2(source);
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
throw new BuildError(
|
|
2962
|
+
`failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
const body = ast.body;
|
|
2966
|
+
const stmt = body.find(
|
|
2967
|
+
(candidate) => candidate.start === statementStart
|
|
2968
|
+
);
|
|
2969
|
+
if (stmt) {
|
|
2970
|
+
return stmt;
|
|
2971
|
+
}
|
|
2972
|
+
const fallback = body.find((candidate) => {
|
|
2973
|
+
if (candidate.type !== "ExpressionStatement") {
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
const expression = candidate.expression;
|
|
2977
|
+
if (expression.type !== "CallExpression") {
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
const args = expression.arguments;
|
|
2981
|
+
if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
const callee = expression.callee;
|
|
2985
|
+
if (callee.type !== "Identifier") {
|
|
2986
|
+
return false;
|
|
2987
|
+
}
|
|
2988
|
+
return callee.name === kind && args[0].value === name;
|
|
2989
|
+
});
|
|
2990
|
+
if (!fallback) {
|
|
2991
|
+
throw new BuildError(
|
|
2992
|
+
`failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
return fallback;
|
|
2996
|
+
}
|
|
2997
|
+
function rootSpecNode(stmt) {
|
|
2998
|
+
if (stmt.type !== "ExpressionStatement") {
|
|
2270
2999
|
throw new BuildError("compiled target root must be an expression statement");
|
|
2271
3000
|
}
|
|
2272
3001
|
const expression = stmt.expression;
|
|
@@ -2567,13 +3296,90 @@ function pruneUnusedTopLevel(source, seeds) {
|
|
|
2567
3296
|
}
|
|
2568
3297
|
return applyEdits2(source, edits);
|
|
2569
3298
|
}
|
|
3299
|
+
function parseDurationMs(value) {
|
|
3300
|
+
if (value === void 0) return void 0;
|
|
3301
|
+
if (typeof value === "number") {
|
|
3302
|
+
if (Number.isInteger(value) && value > 0) return value;
|
|
3303
|
+
throw new BuildError("execution.timeout must be a positive integer number of milliseconds");
|
|
3304
|
+
}
|
|
3305
|
+
if (typeof value !== "string") {
|
|
3306
|
+
throw new BuildError("execution.timeout must be a duration string or milliseconds");
|
|
3307
|
+
}
|
|
3308
|
+
const match = value.trim().toLowerCase().match(/^(\d+)\s*(ms|s|m|h)$/);
|
|
3309
|
+
if (!match) {
|
|
3310
|
+
throw new BuildError("execution.timeout must use ms, s, m, or h units");
|
|
3311
|
+
}
|
|
3312
|
+
const amount = Number(match[1]);
|
|
3313
|
+
const unit = match[2];
|
|
3314
|
+
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
|
|
3315
|
+
return amount * multiplier;
|
|
3316
|
+
}
|
|
3317
|
+
function parseMemoryMb(value) {
|
|
3318
|
+
if (value === void 0) return void 0;
|
|
3319
|
+
if (typeof value === "number") {
|
|
3320
|
+
if (Number.isInteger(value) && value > 0) return value;
|
|
3321
|
+
throw new BuildError("execution.memory must be a positive integer number of megabytes");
|
|
3322
|
+
}
|
|
3323
|
+
if (typeof value !== "string") {
|
|
3324
|
+
throw new BuildError("execution.memory must be a size string or megabytes");
|
|
3325
|
+
}
|
|
3326
|
+
const match = value.trim().toLowerCase().match(/^(\d+)\s*(mb|m|gb|g)$/);
|
|
3327
|
+
if (!match) {
|
|
3328
|
+
throw new BuildError("execution.memory must use mb or gb units");
|
|
3329
|
+
}
|
|
3330
|
+
const amount = Number(match[1]);
|
|
3331
|
+
const unit = match[2];
|
|
3332
|
+
return unit === "gb" || unit === "g" ? amount * 1024 : amount;
|
|
3333
|
+
}
|
|
3334
|
+
function nonNegativeInteger(value, field) {
|
|
3335
|
+
if (value === void 0) return void 0;
|
|
3336
|
+
if (Number.isInteger(value) && value >= 0) return value;
|
|
3337
|
+
throw new BuildError(`${field} must be a non-negative integer`);
|
|
3338
|
+
}
|
|
3339
|
+
function normalizeBudgetMap(value, field) {
|
|
3340
|
+
if (value === void 0) return void 0;
|
|
3341
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3342
|
+
throw new BuildError(`${field} must be an object`);
|
|
3343
|
+
}
|
|
3344
|
+
const out = {};
|
|
3345
|
+
for (const [key, budget] of Object.entries(value)) {
|
|
3346
|
+
if (!key.trim()) {
|
|
3347
|
+
throw new BuildError(`${field} keys must be non-empty strings`);
|
|
3348
|
+
}
|
|
3349
|
+
out[key] = nonNegativeInteger(budget, `${field}.${key}`);
|
|
3350
|
+
}
|
|
3351
|
+
return out;
|
|
3352
|
+
}
|
|
3353
|
+
function normalizeRateBudget(rateBudget) {
|
|
3354
|
+
if (rateBudget === void 0) return void 0;
|
|
3355
|
+
const perApplication = normalizeBudgetMap(
|
|
3356
|
+
rateBudget.perApplication,
|
|
3357
|
+
"execution.rateBudget.perApplication"
|
|
3358
|
+
);
|
|
3359
|
+
const perCapability = normalizeBudgetMap(
|
|
3360
|
+
rateBudget.perCapability,
|
|
3361
|
+
"execution.rateBudget.perCapability"
|
|
3362
|
+
);
|
|
3363
|
+
const out = {
|
|
3364
|
+
...perApplication ? { per_application: perApplication } : {},
|
|
3365
|
+
...perCapability ? { per_capability: perCapability } : {}
|
|
3366
|
+
};
|
|
3367
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3368
|
+
}
|
|
2570
3369
|
function compactExecution(execution) {
|
|
2571
3370
|
if (!execution) {
|
|
2572
3371
|
return void 0;
|
|
2573
3372
|
}
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
);
|
|
3373
|
+
const timeoutMs = parseDurationMs(execution.timeout);
|
|
3374
|
+
const memoryMb = parseMemoryMb(execution.memory);
|
|
3375
|
+
const maxToolCalls = nonNegativeInteger(execution.maxToolCalls, "execution.maxToolCalls");
|
|
3376
|
+
const rateBudget = normalizeRateBudget(execution.rateBudget);
|
|
3377
|
+
const compact = {
|
|
3378
|
+
...timeoutMs !== void 0 ? { timeout_ms: timeoutMs } : {},
|
|
3379
|
+
...memoryMb !== void 0 ? { memory_mb: memoryMb } : {},
|
|
3380
|
+
...maxToolCalls !== void 0 ? { max_tool_calls: maxToolCalls } : {},
|
|
3381
|
+
...rateBudget !== void 0 ? { rate_budget: rateBudget } : {}
|
|
3382
|
+
};
|
|
2577
3383
|
return Object.keys(compact).length > 0 ? compact : void 0;
|
|
2578
3384
|
}
|
|
2579
3385
|
function buildBehaviorReplacement(source, spec, behavior) {
|
|
@@ -2583,8 +3389,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2583
3389
|
}
|
|
2584
3390
|
const execution = compactExecution(behavior.execution);
|
|
2585
3391
|
const runSource = functionSourceFromProperty(source, run, "run");
|
|
2586
|
-
const trackingId = findProperty(spec, "trackingId");
|
|
2587
|
-
const trackingIdSource = trackingId ? functionSourceFromProperty(source, trackingId, "trackingId") : void 0;
|
|
2588
3392
|
const applications = findProperty(spec, "applications");
|
|
2589
3393
|
const applicationsSource = applications ? expressionSourceFromProperty(source, applications, "applications") : void 0;
|
|
2590
3394
|
const configEntries = [
|
|
@@ -2595,9 +3399,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2595
3399
|
if (execution) {
|
|
2596
3400
|
configEntries.push(`execution: ${JSON.stringify(execution)}`);
|
|
2597
3401
|
}
|
|
2598
|
-
if (behavior.messageHistory !== void 0) {
|
|
2599
|
-
configEntries.push(`messageHistory: ${JSON.stringify(behavior.messageHistory)}`);
|
|
2600
|
-
}
|
|
2601
3402
|
if (applicationsSource) {
|
|
2602
3403
|
configEntries.push(`applications: ${INTERNAL_APPLICATIONS}`);
|
|
2603
3404
|
}
|
|
@@ -2606,17 +3407,14 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2606
3407
|
replacement: [
|
|
2607
3408
|
...applicationsSource ? [`const ${INTERNAL_APPLICATIONS} = ${applicationsSource};`] : [],
|
|
2608
3409
|
`const ${INTERNAL_CONFIG} = ${configSource};`,
|
|
2609
|
-
`const ${INTERNAL_TRACKING_ID} = ${trackingIdSource ?? "undefined"};`,
|
|
2610
3410
|
`const ${INTERNAL_RUN} = ${runSource};`,
|
|
2611
|
-
`export { ${INTERNAL_CONFIG} as config, ${
|
|
3411
|
+
`export { ${INTERNAL_CONFIG} as config, ${INTERNAL_RUN} as run };`
|
|
2612
3412
|
].join("\n"),
|
|
2613
3413
|
refs: /* @__PURE__ */ new Set([
|
|
2614
3414
|
INTERNAL_CONFIG,
|
|
2615
|
-
INTERNAL_TRACKING_ID,
|
|
2616
3415
|
INTERNAL_RUN,
|
|
2617
3416
|
...applicationsSource ? [INTERNAL_APPLICATIONS] : [],
|
|
2618
3417
|
...refsFromExpressionSource(runSource),
|
|
2619
|
-
...trackingIdSource ? refsFromExpressionSource(trackingIdSource) : [],
|
|
2620
3418
|
...applicationsSource ? refsFromExpressionSource(applicationsSource) : []
|
|
2621
3419
|
])
|
|
2622
3420
|
};
|
|
@@ -2756,7 +3554,15 @@ async function buildPipeline(config, cwd = process.cwd(), minify = false, buildD
|
|
|
2756
3554
|
conversationBundles
|
|
2757
3555
|
});
|
|
2758
3556
|
validateManifest(manifest);
|
|
2759
|
-
|
|
3557
|
+
const { catalog: catalog2, warnings } = generateCatalog(manifest);
|
|
3558
|
+
if (warnings.length > 0) {
|
|
3559
|
+
throw new BuildError(
|
|
3560
|
+
`Catalog generation produced ${warnings.length} unrecoverable issue${warnings.length === 1 ? "" : "s"} \u2014 refusing to ship a partial catalog:
|
|
3561
|
+
${warnings.map((w) => ` - ${w}`).join("\n")}`
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
await writeFile(join(outDir, "catalog.json"), stableStringify(catalog2, 2), "utf-8");
|
|
3565
|
+
return { manifest, catalog: catalog2, outDir };
|
|
2760
3566
|
}
|
|
2761
3567
|
async function createStagingDir() {
|
|
2762
3568
|
return mkdtemp(join(tmpdir(), "io-cli-"));
|
|
@@ -2820,8 +3626,8 @@ async function buildCommand(options) {
|
|
|
2820
3626
|
async function zipBuildOutput(buildDir, outPath) {
|
|
2821
3627
|
const output = createWriteStream(outPath);
|
|
2822
3628
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
2823
|
-
const done = new Promise((
|
|
2824
|
-
output.on("close",
|
|
3629
|
+
const done = new Promise((resolve5, reject) => {
|
|
3630
|
+
output.on("close", resolve5);
|
|
2825
3631
|
archive.on("error", reject);
|
|
2826
3632
|
});
|
|
2827
3633
|
archive.pipe(output);
|
|
@@ -2850,13 +3656,13 @@ async function addDirectory(archive, baseDir, prefix, skipRelativePath) {
|
|
|
2850
3656
|
}
|
|
2851
3657
|
|
|
2852
3658
|
// src/deploy/diff.ts
|
|
2853
|
-
function
|
|
3659
|
+
function stableStringify2(value) {
|
|
2854
3660
|
if (Array.isArray(value)) {
|
|
2855
|
-
return `[${value.map(
|
|
3661
|
+
return `[${value.map(stableStringify2).join(",")}]`;
|
|
2856
3662
|
}
|
|
2857
3663
|
if (value && typeof value === "object") {
|
|
2858
3664
|
const record = value;
|
|
2859
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${
|
|
3665
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(record[key])}`).join(",")}}`;
|
|
2860
3666
|
}
|
|
2861
3667
|
return JSON.stringify(value);
|
|
2862
3668
|
}
|
|
@@ -2882,7 +3688,7 @@ function diffEntries(local, remote) {
|
|
|
2882
3688
|
for (const { name, value } of local) {
|
|
2883
3689
|
if (!remoteMap.has(name)) {
|
|
2884
3690
|
entries.push({ name, kind: "added" });
|
|
2885
|
-
} else if (
|
|
3691
|
+
} else if (stableStringify2(value) !== stableStringify2(remoteMap.get(name))) {
|
|
2886
3692
|
entries.push({ name, kind: "changed" });
|
|
2887
3693
|
}
|
|
2888
3694
|
}
|
|
@@ -2995,6 +3801,77 @@ function diffManifests(local, remote) {
|
|
|
2995
3801
|
hasChanges
|
|
2996
3802
|
};
|
|
2997
3803
|
}
|
|
3804
|
+
|
|
3805
|
+
// src/deploy/catalog-diff.ts
|
|
3806
|
+
function stableStringify3(value) {
|
|
3807
|
+
if (Array.isArray(value)) {
|
|
3808
|
+
return `[${value.map(stableStringify3).join(",")}]`;
|
|
3809
|
+
}
|
|
3810
|
+
if (value && typeof value === "object") {
|
|
3811
|
+
const record = value;
|
|
3812
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
|
|
3813
|
+
}
|
|
3814
|
+
return JSON.stringify(value);
|
|
3815
|
+
}
|
|
3816
|
+
function diffSection(local, remote) {
|
|
3817
|
+
const localMap = local ?? {};
|
|
3818
|
+
const remoteMap = remote ?? {};
|
|
3819
|
+
const entries = [];
|
|
3820
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3821
|
+
for (const id of Object.keys(localMap).sort()) {
|
|
3822
|
+
seen.add(id);
|
|
3823
|
+
if (!(id in remoteMap)) {
|
|
3824
|
+
entries.push({ id, kind: "added" });
|
|
3825
|
+
} else if (stableStringify3(localMap[id]) !== stableStringify3(remoteMap[id])) {
|
|
3826
|
+
entries.push({ id, kind: "changed" });
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const id of Object.keys(remoteMap).sort()) {
|
|
3830
|
+
if (!seen.has(id) && !(id in localMap)) {
|
|
3831
|
+
entries.push({ id, kind: "removed" });
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
return entries;
|
|
3835
|
+
}
|
|
3836
|
+
function schemaDiffCounts(local, remote) {
|
|
3837
|
+
const localKeys = new Set(Object.keys(local ?? {}));
|
|
3838
|
+
const remoteKeys = new Set(Object.keys(remote ?? {}));
|
|
3839
|
+
let added = 0;
|
|
3840
|
+
let removed = 0;
|
|
3841
|
+
for (const k of localKeys) {
|
|
3842
|
+
if (!remoteKeys.has(k)) added++;
|
|
3843
|
+
}
|
|
3844
|
+
for (const k of remoteKeys) {
|
|
3845
|
+
if (!localKeys.has(k)) removed++;
|
|
3846
|
+
}
|
|
3847
|
+
return { added, removed };
|
|
3848
|
+
}
|
|
3849
|
+
function diffCatalogs(local, remote) {
|
|
3850
|
+
const localResources = local?.resources ?? {};
|
|
3851
|
+
const remoteResources = remote?.resources ?? {};
|
|
3852
|
+
const localSignals = local?.signals ?? {};
|
|
3853
|
+
const remoteSignals = remote?.signals ?? {};
|
|
3854
|
+
const localTools = local?.tools ?? {};
|
|
3855
|
+
const remoteTools = remote?.tools ?? {};
|
|
3856
|
+
const localSchemas = local?.schemas ?? {};
|
|
3857
|
+
const remoteSchemas = remote?.schemas ?? {};
|
|
3858
|
+
const resources = diffSection(localResources, remoteResources);
|
|
3859
|
+
const signals2 = diffSection(localSignals, remoteSignals);
|
|
3860
|
+
const tools = diffSection(localTools, remoteTools);
|
|
3861
|
+
const schemas = schemaDiffCounts(localSchemas, remoteSchemas);
|
|
3862
|
+
const localHash = local?.catalog_hash ?? null;
|
|
3863
|
+
const remoteHash = remote?.catalog_hash ?? null;
|
|
3864
|
+
const hashChanged = (localHash ?? remoteHash) !== null && localHash !== remoteHash;
|
|
3865
|
+
const hasChanges = resources.length > 0 || signals2.length > 0 || tools.length > 0 || schemas.added > 0 || schemas.removed > 0 || hashChanged;
|
|
3866
|
+
return {
|
|
3867
|
+
hashChanged,
|
|
3868
|
+
resources,
|
|
3869
|
+
signals: signals2,
|
|
3870
|
+
tools,
|
|
3871
|
+
schemas,
|
|
3872
|
+
hasChanges
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
2998
3875
|
var SYMBOLS = {
|
|
2999
3876
|
added: chalk12.green("+ "),
|
|
3000
3877
|
changed: chalk12.yellow("~ "),
|
|
@@ -3014,7 +3891,12 @@ var SECTIONS = [
|
|
|
3014
3891
|
{ key: "targets", label: "Targets" },
|
|
3015
3892
|
{ key: "subscriptions", label: "Subscriptions" }
|
|
3016
3893
|
];
|
|
3017
|
-
|
|
3894
|
+
var CATALOG_SECTIONS = [
|
|
3895
|
+
{ key: "resources", label: "Catalog resources" },
|
|
3896
|
+
{ key: "signals", label: "Catalog signals" },
|
|
3897
|
+
{ key: "tools", label: "Catalog tools" }
|
|
3898
|
+
];
|
|
3899
|
+
function formatPlan(diff, catalogDiff) {
|
|
3018
3900
|
const lines = [];
|
|
3019
3901
|
for (const { key, label } of SECTIONS) {
|
|
3020
3902
|
const entries = diff[key];
|
|
@@ -3029,15 +3911,44 @@ function formatPlan(diff) {
|
|
|
3029
3911
|
}
|
|
3030
3912
|
}
|
|
3031
3913
|
}
|
|
3914
|
+
if (catalogDiff) {
|
|
3915
|
+
lines.push("");
|
|
3916
|
+
lines.push(
|
|
3917
|
+
chalk12.bold(
|
|
3918
|
+
catalogDiff.hashChanged ? " Catalog (hash changed):" : " Catalog:"
|
|
3919
|
+
)
|
|
3920
|
+
);
|
|
3921
|
+
for (const { key, label } of CATALOG_SECTIONS) {
|
|
3922
|
+
const entries = catalogDiff[key];
|
|
3923
|
+
lines.push(` ${label}:`);
|
|
3924
|
+
if (entries.length === 0) {
|
|
3925
|
+
lines.push(chalk12.gray(" (no changes)"));
|
|
3926
|
+
} else {
|
|
3927
|
+
for (const e of entries) {
|
|
3928
|
+
lines.push(
|
|
3929
|
+
` ${SYMBOLS[e.kind]}${e.id} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
|
|
3930
|
+
);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
const schemaLine = catalogDiff.schemas;
|
|
3935
|
+
if (schemaLine.added > 0 || schemaLine.removed > 0) {
|
|
3936
|
+
lines.push(
|
|
3937
|
+
` ${chalk12.gray("Schemas:")} ${chalk12.green(`+${schemaLine.added}`)} ${chalk12.red(`-${schemaLine.removed}`)}`
|
|
3938
|
+
);
|
|
3939
|
+
} else {
|
|
3940
|
+
lines.push(` ${chalk12.gray("Schemas: (no changes)")}`);
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3032
3943
|
return lines.join("\n");
|
|
3033
3944
|
}
|
|
3034
3945
|
async function confirmPlan(skip) {
|
|
3035
3946
|
if (skip) return true;
|
|
3036
3947
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3037
|
-
return new Promise((
|
|
3948
|
+
return new Promise((resolve5) => {
|
|
3038
3949
|
rl.question("\nApply these changes? (y/n) ", (answer) => {
|
|
3039
3950
|
rl.close();
|
|
3040
|
-
|
|
3951
|
+
resolve5(answer.trim().toLowerCase() === "y");
|
|
3041
3952
|
});
|
|
3042
3953
|
});
|
|
3043
3954
|
}
|
|
@@ -3111,40 +4022,6 @@ function socketAuthParams(creds) {
|
|
|
3111
4022
|
return { authorization: `Bearer ${creds.token}` };
|
|
3112
4023
|
}
|
|
3113
4024
|
|
|
3114
|
-
// src/build/stable-json.ts
|
|
3115
|
-
function stableStringify2(value, indent) {
|
|
3116
|
-
return JSON.stringify(canonicalize(value), null, indent);
|
|
3117
|
-
}
|
|
3118
|
-
function canonicalize(value) {
|
|
3119
|
-
if (value === null) return null;
|
|
3120
|
-
if (Array.isArray(value)) {
|
|
3121
|
-
return value.map(canonicalize);
|
|
3122
|
-
}
|
|
3123
|
-
if (typeof value === "object") {
|
|
3124
|
-
const obj = value;
|
|
3125
|
-
const sorted = {};
|
|
3126
|
-
for (const key of Object.keys(obj).sort()) {
|
|
3127
|
-
sorted[key] = canonicalize(obj[key]);
|
|
3128
|
-
}
|
|
3129
|
-
return sorted;
|
|
3130
|
-
}
|
|
3131
|
-
return value;
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
// src/runtime-signal-name.ts
|
|
3135
|
-
var EVENT_NAME_REGEX2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
3136
|
-
var SIGNAL_NAME_REGEX = /^signal:[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
3137
|
-
function normalizeRuntimeSignalName2(value) {
|
|
3138
|
-
const trimmed = value.trim();
|
|
3139
|
-
if (EVENT_NAME_REGEX2.test(trimmed)) {
|
|
3140
|
-
return `signal:${trimmed}`;
|
|
3141
|
-
}
|
|
3142
|
-
if (SIGNAL_NAME_REGEX.test(trimmed)) {
|
|
3143
|
-
return trimmed;
|
|
3144
|
-
}
|
|
3145
|
-
return null;
|
|
3146
|
-
}
|
|
3147
|
-
|
|
3148
4025
|
// src/api/client.ts
|
|
3149
4026
|
var REFRESH_MUTATION = `
|
|
3150
4027
|
mutation Refresh($input: RefreshAuthInput!) {
|
|
@@ -3245,8 +4122,32 @@ function graphqlErrorMessage(errors) {
|
|
|
3245
4122
|
}
|
|
3246
4123
|
return null;
|
|
3247
4124
|
}
|
|
3248
|
-
function
|
|
3249
|
-
return
|
|
4125
|
+
function isPlainObject(value) {
|
|
4126
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4127
|
+
}
|
|
4128
|
+
function isCatalogShape(value) {
|
|
4129
|
+
if (!isPlainObject(value)) return false;
|
|
4130
|
+
return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isPlainObject(value.resources) && isPlainObject(value.signals) && isPlainObject(value.tools) && isPlainObject(value.schemas);
|
|
4131
|
+
}
|
|
4132
|
+
function isManifestShape(value) {
|
|
4133
|
+
if (!isPlainObject(value)) return false;
|
|
4134
|
+
return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && Array.isArray(value.secret_bundles) && Array.isArray(value.apps) && Array.isArray(value.endpoints) && Array.isArray(value.targets) && Array.isArray(value.subscriptions);
|
|
4135
|
+
}
|
|
4136
|
+
function parseRemoteCatalog(catalogJson) {
|
|
4137
|
+
let parsed;
|
|
4138
|
+
try {
|
|
4139
|
+
parsed = JSON.parse(catalogJson);
|
|
4140
|
+
} catch (err) {
|
|
4141
|
+
throw new ConfigError(
|
|
4142
|
+
`Remote deployment catalog was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
if (!isCatalogShape(parsed)) {
|
|
4146
|
+
throw new ConfigError(
|
|
4147
|
+
"Remote deployment catalog does not match the io.invocation.v2 catalog shape."
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
return parsed;
|
|
3250
4151
|
}
|
|
3251
4152
|
function parseRemoteManifest(snapshotJson) {
|
|
3252
4153
|
let parsed;
|
|
@@ -3257,11 +4158,22 @@ function parseRemoteManifest(snapshotJson) {
|
|
|
3257
4158
|
`Remote deployment snapshot was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
3258
4159
|
);
|
|
3259
4160
|
}
|
|
3260
|
-
if (!
|
|
3261
|
-
throw new ConfigError(
|
|
4161
|
+
if (!isManifestShape(parsed)) {
|
|
4162
|
+
throw new ConfigError(
|
|
4163
|
+
"Remote deployment snapshot does not match the io_runtime.snapshot.v2 manifest shape."
|
|
4164
|
+
);
|
|
3262
4165
|
}
|
|
3263
4166
|
return parsed;
|
|
3264
4167
|
}
|
|
4168
|
+
var EMPTY_MANIFEST = {
|
|
4169
|
+
contract_version: "io.invocation.v2",
|
|
4170
|
+
snapshot_version: "io_runtime.snapshot.v2",
|
|
4171
|
+
secret_bundles: [],
|
|
4172
|
+
apps: [],
|
|
4173
|
+
endpoints: [],
|
|
4174
|
+
targets: [],
|
|
4175
|
+
subscriptions: []
|
|
4176
|
+
};
|
|
3265
4177
|
async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
|
|
3266
4178
|
const response = await fetch(`${trimBaseUrl(apiUrl)}/graphql`, {
|
|
3267
4179
|
method: "POST",
|
|
@@ -3478,6 +4390,23 @@ function createClient(config) {
|
|
|
3478
4390
|
);
|
|
3479
4391
|
return data.createProject;
|
|
3480
4392
|
}
|
|
4393
|
+
async function deleteProject(organizationId, projectIdValue) {
|
|
4394
|
+
const data = await graphql(
|
|
4395
|
+
`
|
|
4396
|
+
mutation DeleteProject($organizationId: ID!, $id: ID!) {
|
|
4397
|
+
deleteProject(organizationId: $organizationId, id: $id) {
|
|
4398
|
+
id
|
|
4399
|
+
name
|
|
4400
|
+
slug
|
|
4401
|
+
description
|
|
4402
|
+
organizationId
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
`,
|
|
4406
|
+
{ organizationId, id: projectIdValue }
|
|
4407
|
+
);
|
|
4408
|
+
return data.deleteProject;
|
|
4409
|
+
}
|
|
3481
4410
|
async function principals2(projectIdValue) {
|
|
3482
4411
|
const data = await graphql(
|
|
3483
4412
|
`
|
|
@@ -3607,28 +4536,34 @@ function createClient(config) {
|
|
|
3607
4536
|
return data.deleteSecret === true;
|
|
3608
4537
|
}
|
|
3609
4538
|
async function fetchRemoteManifest(projectIdValue) {
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
data = await graphql(
|
|
3613
|
-
`
|
|
4539
|
+
const data = await graphql(
|
|
4540
|
+
`
|
|
3614
4541
|
query ActiveDeploymentSnapshot($projectId: ID!) {
|
|
3615
4542
|
activeDeployment(projectId: $projectId) {
|
|
3616
4543
|
snapshotJson
|
|
3617
4544
|
}
|
|
3618
4545
|
}
|
|
3619
4546
|
`,
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
} catch (err) {
|
|
3623
|
-
if (isMissingSnapshotJsonFieldError(err)) {
|
|
3624
|
-
return {};
|
|
3625
|
-
}
|
|
3626
|
-
throw err;
|
|
3627
|
-
}
|
|
4547
|
+
{ projectId: projectIdValue }
|
|
4548
|
+
);
|
|
3628
4549
|
const snapshotJson = data.activeDeployment?.snapshotJson;
|
|
3629
|
-
return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) :
|
|
4550
|
+
return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : EMPTY_MANIFEST;
|
|
4551
|
+
}
|
|
4552
|
+
async function fetchRemoteCatalog(projectIdValue) {
|
|
4553
|
+
const data = await graphql(
|
|
4554
|
+
`
|
|
4555
|
+
query ActiveDeploymentCatalog($projectId: ID!) {
|
|
4556
|
+
activeDeployment(projectId: $projectId) {
|
|
4557
|
+
catalogJson
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
`,
|
|
4561
|
+
{ projectId: projectIdValue }
|
|
4562
|
+
);
|
|
4563
|
+
const catalogJson = data.activeDeployment?.catalogJson;
|
|
4564
|
+
return typeof catalogJson === "string" && catalogJson.trim() !== "" ? parseRemoteCatalog(catalogJson) : null;
|
|
3630
4565
|
}
|
|
3631
|
-
async function deployBundle(bundlePath, manifest) {
|
|
4566
|
+
async function deployBundle(bundlePath, manifest, catalog2) {
|
|
3632
4567
|
const MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
|
|
3633
4568
|
const fileStat = await stat(bundlePath);
|
|
3634
4569
|
if (fileStat.size > MAX_BUNDLE_BYTES) {
|
|
@@ -3666,7 +4601,8 @@ function createClient(config) {
|
|
|
3666
4601
|
projectId: projectIdValue,
|
|
3667
4602
|
bundleHash: upload.bundleHash,
|
|
3668
4603
|
revision: nextRevision,
|
|
3669
|
-
snapshotJson:
|
|
4604
|
+
snapshotJson: stableStringify(manifest),
|
|
4605
|
+
catalogJson: stableStringify(catalog2)
|
|
3670
4606
|
}
|
|
3671
4607
|
}
|
|
3672
4608
|
);
|
|
@@ -3677,7 +4613,7 @@ function createClient(config) {
|
|
|
3677
4613
|
if (!input.principalId || input.principalId.trim() === "") {
|
|
3678
4614
|
throw new ConfigError("principalId is required to emit a signal");
|
|
3679
4615
|
}
|
|
3680
|
-
const eventName =
|
|
4616
|
+
const eventName = normalizeRuntimeSignalName(input.signal);
|
|
3681
4617
|
if (!eventName) {
|
|
3682
4618
|
throw new ConfigError(
|
|
3683
4619
|
"signal name must be a bare event name like smoke.start or use the signal: namespace"
|
|
@@ -3730,6 +4666,37 @@ function createClient(config) {
|
|
|
3730
4666
|
);
|
|
3731
4667
|
return data.activeRevision;
|
|
3732
4668
|
}
|
|
4669
|
+
async function reconcileProject(projectIdValue) {
|
|
4670
|
+
const data = await graphql(
|
|
4671
|
+
`
|
|
4672
|
+
mutation ReconcileProject($projectId: ID!) {
|
|
4673
|
+
reconcileProject(projectId: $projectId)
|
|
4674
|
+
}
|
|
4675
|
+
`,
|
|
4676
|
+
{ projectId: projectIdValue }
|
|
4677
|
+
);
|
|
4678
|
+
return data.reconcileProject === true;
|
|
4679
|
+
}
|
|
4680
|
+
async function deploymentStatus(projectIdValue, revision) {
|
|
4681
|
+
const data = await graphql(
|
|
4682
|
+
`
|
|
4683
|
+
query DeploymentStatus($projectId: ID!, $revision: Int!) {
|
|
4684
|
+
deploymentStatus(projectId: $projectId, revision: $revision) {
|
|
4685
|
+
id
|
|
4686
|
+
projectId
|
|
4687
|
+
revision
|
|
4688
|
+
bundleHash
|
|
4689
|
+
status
|
|
4690
|
+
activatedAt
|
|
4691
|
+
insertedAt
|
|
4692
|
+
updatedAt
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
`,
|
|
4696
|
+
{ projectId: projectIdValue, revision }
|
|
4697
|
+
);
|
|
4698
|
+
return data.deploymentStatus;
|
|
4699
|
+
}
|
|
3733
4700
|
async function quotaStatuses() {
|
|
3734
4701
|
const data = await graphql(
|
|
3735
4702
|
`
|
|
@@ -3781,6 +4748,7 @@ function createClient(config) {
|
|
|
3781
4748
|
organizations,
|
|
3782
4749
|
projects: projects2,
|
|
3783
4750
|
createProject,
|
|
4751
|
+
deleteProject,
|
|
3784
4752
|
principals: principals2,
|
|
3785
4753
|
createPrincipal,
|
|
3786
4754
|
updatePrincipal,
|
|
@@ -3789,14 +4757,245 @@ function createClient(config) {
|
|
|
3789
4757
|
putSecret,
|
|
3790
4758
|
deleteSecret,
|
|
3791
4759
|
fetchRemoteManifest,
|
|
4760
|
+
fetchRemoteCatalog,
|
|
3792
4761
|
deployBundle,
|
|
4762
|
+
deploymentStatus,
|
|
4763
|
+
reconcileProject,
|
|
3793
4764
|
emitSignal,
|
|
3794
4765
|
quotaStatuses,
|
|
3795
4766
|
projectEvents
|
|
3796
4767
|
};
|
|
3797
4768
|
}
|
|
4769
|
+
Object.assign(globalThis, { WebSocket });
|
|
4770
|
+
var SessionTokenNotSupportedError = class extends Error {
|
|
4771
|
+
constructor(message) {
|
|
4772
|
+
super(
|
|
4773
|
+
message ?? "WebSocket subscriptions require API key authentication on the current io_runtime socket contract."
|
|
4774
|
+
);
|
|
4775
|
+
this.name = "SessionTokenNotSupportedError";
|
|
4776
|
+
}
|
|
4777
|
+
};
|
|
4778
|
+
function buildSocketUrl(apiUrl) {
|
|
4779
|
+
const url = new URL(apiUrl);
|
|
4780
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
4781
|
+
const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
|
|
4782
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
4783
|
+
url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
|
|
4784
|
+
url.search = "";
|
|
4785
|
+
url.hash = "";
|
|
4786
|
+
return url.toString();
|
|
4787
|
+
}
|
|
4788
|
+
function createSocketClient(options) {
|
|
4789
|
+
let phoenixSocket = null;
|
|
4790
|
+
let absintheSocket = null;
|
|
4791
|
+
const notifiers = /* @__PURE__ */ new Set();
|
|
4792
|
+
function ensureConnection() {
|
|
4793
|
+
if (!options.credentials.apiKey) {
|
|
4794
|
+
throw new SessionTokenNotSupportedError();
|
|
4795
|
+
}
|
|
4796
|
+
if (!phoenixSocket || !absintheSocket) {
|
|
4797
|
+
phoenixSocket = new Socket(buildSocketUrl(options.apiUrl), {
|
|
4798
|
+
params: socketAuthParams(options.credentials),
|
|
4799
|
+
timeout: options.timeoutMs ?? 1e4
|
|
4800
|
+
});
|
|
4801
|
+
absintheSocket = AbsintheSocket.create(phoenixSocket);
|
|
4802
|
+
phoenixSocket.connect();
|
|
4803
|
+
}
|
|
4804
|
+
return { absintheSocket };
|
|
4805
|
+
}
|
|
4806
|
+
function tearDownIfIdle() {
|
|
4807
|
+
if (notifiers.size === 0 && phoenixSocket) {
|
|
4808
|
+
phoenixSocket.disconnect();
|
|
4809
|
+
phoenixSocket = null;
|
|
4810
|
+
absintheSocket = null;
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
return {
|
|
4814
|
+
subscribe(operation, variables, observer) {
|
|
4815
|
+
const conn = ensureConnection();
|
|
4816
|
+
const notifier = AbsintheSocket.send(conn.absintheSocket, {
|
|
4817
|
+
operation,
|
|
4818
|
+
variables
|
|
4819
|
+
});
|
|
4820
|
+
notifiers.add(notifier);
|
|
4821
|
+
AbsintheSocket.observe(conn.absintheSocket, notifier, {
|
|
4822
|
+
onAbort: (err) => {
|
|
4823
|
+
notifiers.delete(notifier);
|
|
4824
|
+
if (observer.onAbort) {
|
|
4825
|
+
observer.onAbort(err);
|
|
4826
|
+
} else {
|
|
4827
|
+
observer.onError?.(err);
|
|
4828
|
+
}
|
|
4829
|
+
tearDownIfIdle();
|
|
4830
|
+
},
|
|
4831
|
+
onCancel: () => {
|
|
4832
|
+
notifiers.delete(notifier);
|
|
4833
|
+
tearDownIfIdle();
|
|
4834
|
+
},
|
|
4835
|
+
onError: (err) => {
|
|
4836
|
+
observer.onError?.(err);
|
|
4837
|
+
},
|
|
4838
|
+
onResult: (result) => {
|
|
4839
|
+
observer.onResult?.(result);
|
|
4840
|
+
},
|
|
4841
|
+
onStart: () => {
|
|
4842
|
+
observer.onStart?.();
|
|
4843
|
+
}
|
|
4844
|
+
});
|
|
4845
|
+
return () => {
|
|
4846
|
+
if (!notifiers.has(notifier)) return;
|
|
4847
|
+
notifiers.delete(notifier);
|
|
4848
|
+
try {
|
|
4849
|
+
AbsintheSocket.cancel(conn.absintheSocket, notifier);
|
|
4850
|
+
} catch {
|
|
4851
|
+
}
|
|
4852
|
+
tearDownIfIdle();
|
|
4853
|
+
};
|
|
4854
|
+
},
|
|
4855
|
+
close() {
|
|
4856
|
+
notifiers.clear();
|
|
4857
|
+
if (phoenixSocket) {
|
|
4858
|
+
phoenixSocket.disconnect();
|
|
4859
|
+
}
|
|
4860
|
+
phoenixSocket = null;
|
|
4861
|
+
absintheSocket = null;
|
|
4862
|
+
}
|
|
4863
|
+
};
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
// src/api/watcher.ts
|
|
4867
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
4868
|
+
var DEFAULT_SEEN_ID_LIMIT = 1e3;
|
|
4869
|
+
var DEFAULT_POLL_SAFETY_LOOPS = 10;
|
|
4870
|
+
function installSigintHandler(onTerminate) {
|
|
4871
|
+
const handler = () => {
|
|
4872
|
+
onTerminate();
|
|
4873
|
+
};
|
|
4874
|
+
process.once("SIGINT", handler);
|
|
4875
|
+
return () => {
|
|
4876
|
+
process.removeListener("SIGINT", handler);
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4879
|
+
async function watch(plan, handlers) {
|
|
4880
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4881
|
+
const seenOrder = [];
|
|
4882
|
+
const seenLimit = plan.seenIdLimit ?? DEFAULT_SEEN_ID_LIMIT;
|
|
4883
|
+
function rememberId(id) {
|
|
4884
|
+
if (seenIds.has(id)) return;
|
|
4885
|
+
seenIds.add(id);
|
|
4886
|
+
seenOrder.push(id);
|
|
4887
|
+
while (seenOrder.length > seenLimit) {
|
|
4888
|
+
const evicted = seenOrder.shift();
|
|
4889
|
+
if (evicted) seenIds.delete(evicted);
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
function handle(event) {
|
|
4893
|
+
const id = plan.getId(event);
|
|
4894
|
+
if (seenIds.has(id)) return false;
|
|
4895
|
+
rememberId(id);
|
|
4896
|
+
handlers.onEvent(event);
|
|
4897
|
+
return true;
|
|
4898
|
+
}
|
|
4899
|
+
let cursor = plan.initialSince ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4900
|
+
function advanceCursor(event) {
|
|
4901
|
+
if (!plan.getCursor) return;
|
|
4902
|
+
const next = plan.getCursor(event);
|
|
4903
|
+
if (next > cursor) cursor = next;
|
|
4904
|
+
}
|
|
4905
|
+
if (plan.snapshot) {
|
|
4906
|
+
const initial = await plan.snapshot();
|
|
4907
|
+
for (const event of initial) {
|
|
4908
|
+
handle(event);
|
|
4909
|
+
advanceCursor(event);
|
|
4910
|
+
if (plan.isTerminal?.(event)) return;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
return new Promise((resolve5, reject) => {
|
|
4914
|
+
let cleanupSigint;
|
|
4915
|
+
let subscribeHandle;
|
|
4916
|
+
let pollTimer;
|
|
4917
|
+
let terminated = false;
|
|
4918
|
+
function finalize(action) {
|
|
4919
|
+
if (terminated) return;
|
|
4920
|
+
terminated = true;
|
|
4921
|
+
subscribeHandle?.unsubscribe();
|
|
4922
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
4923
|
+
cleanupSigint?.();
|
|
4924
|
+
action();
|
|
4925
|
+
}
|
|
4926
|
+
cleanupSigint = installSigintHandler(() => {
|
|
4927
|
+
finalize(() => resolve5());
|
|
4928
|
+
});
|
|
4929
|
+
function processLiveEvent(event) {
|
|
4930
|
+
const accepted = handle(event);
|
|
4931
|
+
if (accepted) advanceCursor(event);
|
|
4932
|
+
if (plan.isTerminal?.(event)) finalize(() => resolve5());
|
|
4933
|
+
}
|
|
4934
|
+
async function startPolling() {
|
|
4935
|
+
if (!plan.poll) {
|
|
4936
|
+
finalize(() => reject(new Error("No polling fallback configured for this watch.")));
|
|
4937
|
+
return;
|
|
4938
|
+
}
|
|
4939
|
+
handlers.onConnected?.("polling");
|
|
4940
|
+
const interval = plan.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
4941
|
+
const safetyLoops = plan.pollBatchSafetyLoops ?? DEFAULT_POLL_SAFETY_LOOPS;
|
|
4942
|
+
const loop = async () => {
|
|
4943
|
+
if (terminated) return;
|
|
4944
|
+
try {
|
|
4945
|
+
for (let i = 0; i < safetyLoops; i += 1) {
|
|
4946
|
+
const batch = await plan.poll(cursor);
|
|
4947
|
+
let anyNew = false;
|
|
4948
|
+
for (const event of batch) {
|
|
4949
|
+
if (handle(event)) anyNew = true;
|
|
4950
|
+
advanceCursor(event);
|
|
4951
|
+
if (plan.isTerminal?.(event)) {
|
|
4952
|
+
finalize(() => resolve5());
|
|
4953
|
+
return;
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
if (!anyNew) break;
|
|
4957
|
+
}
|
|
4958
|
+
} catch (err) {
|
|
4959
|
+
finalize(
|
|
4960
|
+
() => reject(err instanceof Error ? err : new Error(String(err)))
|
|
4961
|
+
);
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
pollTimer = setTimeout(loop, interval);
|
|
4965
|
+
};
|
|
4966
|
+
void loop();
|
|
4967
|
+
}
|
|
4968
|
+
if (!plan.subscribe) {
|
|
4969
|
+
void startPolling();
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
try {
|
|
4973
|
+
subscribeHandle = plan.subscribe(
|
|
4974
|
+
processLiveEvent,
|
|
4975
|
+
(err) => {
|
|
4976
|
+
if (err instanceof SessionTokenNotSupportedError && plan.poll) {
|
|
4977
|
+
subscribeHandle = void 0;
|
|
4978
|
+
void startPolling();
|
|
4979
|
+
return;
|
|
4980
|
+
}
|
|
4981
|
+
finalize(() => reject(err));
|
|
4982
|
+
},
|
|
4983
|
+
() => handlers.onConnected?.("subscription")
|
|
4984
|
+
);
|
|
4985
|
+
} catch (err) {
|
|
4986
|
+
if (err instanceof SessionTokenNotSupportedError && plan.poll) {
|
|
4987
|
+
void startPolling();
|
|
4988
|
+
return;
|
|
4989
|
+
}
|
|
4990
|
+
finalize(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
4991
|
+
}
|
|
4992
|
+
});
|
|
4993
|
+
}
|
|
3798
4994
|
|
|
3799
4995
|
// src/commands/deploy.ts
|
|
4996
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["active", "failed", "superseded"]);
|
|
4997
|
+
var WATCH_POLL_INTERVAL_MS = 2e3;
|
|
4998
|
+
var WATCH_DEADLINE_MS = 5 * 6e4;
|
|
3800
4999
|
async function deployCommand(options) {
|
|
3801
5000
|
let stagingDir;
|
|
3802
5001
|
try {
|
|
@@ -3805,7 +5004,7 @@ async function deployCommand(options) {
|
|
|
3805
5004
|
});
|
|
3806
5005
|
console.log(chalk12.gray("Planning deployment..."));
|
|
3807
5006
|
stagingDir = await createStagingDir();
|
|
3808
|
-
const { manifest } = await buildPipeline(
|
|
5007
|
+
const { manifest, catalog: catalog2 } = await buildPipeline(
|
|
3809
5008
|
config,
|
|
3810
5009
|
process.cwd(),
|
|
3811
5010
|
options.minify ?? true,
|
|
@@ -3824,13 +5023,17 @@ async function deployCommand(options) {
|
|
|
3824
5023
|
projectId: config.projectId
|
|
3825
5024
|
});
|
|
3826
5025
|
console.log(chalk12.gray("\nComparing with remote..."));
|
|
3827
|
-
const remoteManifest = await client.fetchRemoteManifest(
|
|
5026
|
+
const remoteManifest = await client.fetchRemoteManifest(
|
|
5027
|
+
client.projectId()
|
|
5028
|
+
);
|
|
5029
|
+
const remoteCatalog = await client.fetchRemoteCatalog(client.projectId());
|
|
3828
5030
|
const diff = diffManifests(manifest, remoteManifest);
|
|
3829
|
-
|
|
5031
|
+
const catalogDiff = diffCatalogs(catalog2, remoteCatalog);
|
|
5032
|
+
if (!diff.hasChanges && !catalogDiff.hasChanges) {
|
|
3830
5033
|
console.log(chalk12.green("\n\u2713 No changes to deploy."));
|
|
3831
5034
|
return;
|
|
3832
5035
|
}
|
|
3833
|
-
console.log("\n" + formatPlan(diff));
|
|
5036
|
+
console.log("\n" + formatPlan(diff, catalogDiff));
|
|
3834
5037
|
const confirmed = await confirmPlan(options.yes ?? false);
|
|
3835
5038
|
if (!confirmed) {
|
|
3836
5039
|
console.log(chalk12.yellow("Deploy cancelled."));
|
|
@@ -3839,11 +5042,14 @@ async function deployCommand(options) {
|
|
|
3839
5042
|
console.log(chalk12.gray("\nApplying deployment..."));
|
|
3840
5043
|
const zipPath = join(stagingDir, "deploy.zip");
|
|
3841
5044
|
await zipBuildOutput(stagingDir, zipPath);
|
|
3842
|
-
const revision = await client.deployBundle(zipPath, manifest);
|
|
5045
|
+
const revision = await client.deployBundle(zipPath, manifest, catalog2);
|
|
3843
5046
|
console.log(
|
|
3844
5047
|
chalk12.green(`
|
|
3845
|
-
\u2713 Deployment
|
|
5048
|
+
\u2713 Deployment submitted (revision ${revision.revision}, ${revision.status})`)
|
|
3846
5049
|
);
|
|
5050
|
+
if (options.watch) {
|
|
5051
|
+
await watchDeployment(client, revision);
|
|
5052
|
+
}
|
|
3847
5053
|
} catch (err) {
|
|
3848
5054
|
if (err instanceof CliError) {
|
|
3849
5055
|
console.error(chalk12.red(err.message));
|
|
@@ -3854,6 +5060,72 @@ async function deployCommand(options) {
|
|
|
3854
5060
|
await cleanupStagingDir(stagingDir);
|
|
3855
5061
|
}
|
|
3856
5062
|
}
|
|
5063
|
+
function colorStatus(status) {
|
|
5064
|
+
switch (status) {
|
|
5065
|
+
case "active":
|
|
5066
|
+
return chalk12.green(status);
|
|
5067
|
+
case "failed":
|
|
5068
|
+
return chalk12.red(status);
|
|
5069
|
+
case "superseded":
|
|
5070
|
+
return chalk12.yellow(status);
|
|
5071
|
+
case "staged":
|
|
5072
|
+
return chalk12.cyan(status);
|
|
5073
|
+
case "draft":
|
|
5074
|
+
default:
|
|
5075
|
+
return chalk12.gray(status);
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
async function watchDeployment(client, initial) {
|
|
5079
|
+
if (TERMINAL_STATUSES.has(initial.status)) {
|
|
5080
|
+
return;
|
|
5081
|
+
}
|
|
5082
|
+
console.log(chalk12.gray(`
|
|
5083
|
+
Watching revision ${initial.revision}\u2026`));
|
|
5084
|
+
let lastStatus = initial.status;
|
|
5085
|
+
const startedAt = Date.now();
|
|
5086
|
+
await watch(
|
|
5087
|
+
{
|
|
5088
|
+
poll: async () => {
|
|
5089
|
+
if (Date.now() - startedAt > WATCH_DEADLINE_MS) {
|
|
5090
|
+
throw new Error(
|
|
5091
|
+
`Deployment did not reach a terminal status within ${WATCH_DEADLINE_MS / 1e3}s.`
|
|
5092
|
+
);
|
|
5093
|
+
}
|
|
5094
|
+
const next = await client.deploymentStatus(
|
|
5095
|
+
client.projectId(),
|
|
5096
|
+
initial.revision
|
|
5097
|
+
);
|
|
5098
|
+
return next ? [next] : [];
|
|
5099
|
+
},
|
|
5100
|
+
pollIntervalMs: WATCH_POLL_INTERVAL_MS,
|
|
5101
|
+
getId: (rev) => `${rev.revision}:${rev.status}:${rev.updatedAt}`,
|
|
5102
|
+
isTerminal: (rev) => TERMINAL_STATUSES.has(rev.status)
|
|
5103
|
+
},
|
|
5104
|
+
{
|
|
5105
|
+
onEvent: (rev) => {
|
|
5106
|
+
if (rev.status !== lastStatus) {
|
|
5107
|
+
console.log(
|
|
5108
|
+
` revision ${rev.revision} \u2192 ${colorStatus(rev.status)}`
|
|
5109
|
+
);
|
|
5110
|
+
lastStatus = rev.status;
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
);
|
|
5115
|
+
if (lastStatus === "active") {
|
|
5116
|
+
console.log(chalk12.green(`
|
|
5117
|
+
\u2713 Deployment active (revision ${initial.revision}).`));
|
|
5118
|
+
} else if (lastStatus === "failed") {
|
|
5119
|
+
console.log(chalk12.red(`
|
|
5120
|
+
\u2717 Deployment failed (revision ${initial.revision}).`));
|
|
5121
|
+
process.exit(1);
|
|
5122
|
+
} else if (lastStatus === "superseded") {
|
|
5123
|
+
console.log(
|
|
5124
|
+
chalk12.yellow(`
|
|
5125
|
+
! Revision ${initial.revision} was superseded by a later deploy.`)
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
3857
5129
|
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
3858
5130
|
<html>
|
|
3859
5131
|
<head><title>IO CLI</title><style>
|
|
@@ -4092,10 +5364,10 @@ async function whoamiCommand() {
|
|
|
4092
5364
|
var CONFIG_FILE = "io.config.json";
|
|
4093
5365
|
async function prompt(question) {
|
|
4094
5366
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4095
|
-
return new Promise((
|
|
5367
|
+
return new Promise((resolve5) => {
|
|
4096
5368
|
rl.question(question, (answer) => {
|
|
4097
5369
|
rl.close();
|
|
4098
|
-
|
|
5370
|
+
resolve5(answer.trim());
|
|
4099
5371
|
});
|
|
4100
5372
|
});
|
|
4101
5373
|
}
|
|
@@ -4271,6 +5543,55 @@ async function projectsListCommand() {
|
|
|
4271
5543
|
throw err;
|
|
4272
5544
|
}
|
|
4273
5545
|
}
|
|
5546
|
+
async function confirmDelete(projectName, projectId) {
|
|
5547
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5548
|
+
return new Promise((resolve5) => {
|
|
5549
|
+
rl.question(
|
|
5550
|
+
`Delete project "${projectName}" (${projectId})? Type the project name to confirm: `,
|
|
5551
|
+
(answer) => {
|
|
5552
|
+
rl.close();
|
|
5553
|
+
resolve5(answer.trim() === projectName);
|
|
5554
|
+
}
|
|
5555
|
+
);
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
async function projectsDeleteCommand(id, options = {}) {
|
|
5559
|
+
try {
|
|
5560
|
+
const config = await loadConfig();
|
|
5561
|
+
const client = createClient(config);
|
|
5562
|
+
const organizationId = options.organizationId ?? await resolveOrganizationId(client);
|
|
5563
|
+
const projects2 = await client.projects(organizationId);
|
|
5564
|
+
const target = projects2.find((p) => p.id === id);
|
|
5565
|
+
if (!target) {
|
|
5566
|
+
throw new ConfigError(
|
|
5567
|
+
`Project "${id}" not found under organization ${organizationId}.`
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
if (!options.yes) {
|
|
5571
|
+
const confirmed = await confirmDelete(target.name, target.id);
|
|
5572
|
+
if (!confirmed) {
|
|
5573
|
+
console.log(chalk12.yellow("Delete cancelled (name did not match)."));
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
const deleted = await client.deleteProject(organizationId, target.id);
|
|
5578
|
+
console.log(chalk12.green(`\u2713 Deleted project: ${deleted.name}`));
|
|
5579
|
+
console.log(chalk12.gray(` ID: ${deleted.id}`));
|
|
5580
|
+
if (config.projectId === deleted.id) {
|
|
5581
|
+
console.log(
|
|
5582
|
+
chalk12.yellow(
|
|
5583
|
+
"\n io.config.json still references this project \u2014 clear the projectId field before redeploying."
|
|
5584
|
+
)
|
|
5585
|
+
);
|
|
5586
|
+
}
|
|
5587
|
+
} catch (err) {
|
|
5588
|
+
if (err instanceof CliError) {
|
|
5589
|
+
console.error(chalk12.red(err.message));
|
|
5590
|
+
process.exit(1);
|
|
5591
|
+
}
|
|
5592
|
+
throw err;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
4274
5595
|
async function projectsCreateCommand(name) {
|
|
4275
5596
|
try {
|
|
4276
5597
|
const config = await loadConfig();
|
|
@@ -4495,8 +5816,9 @@ async function quotaCommand() {
|
|
|
4495
5816
|
throw err;
|
|
4496
5817
|
}
|
|
4497
5818
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
5819
|
+
|
|
5820
|
+
// src/api/subscription.ts
|
|
5821
|
+
var ACTIVITY_EVENT_SUBSCRIPTION = `
|
|
4500
5822
|
subscription ActivityEventReceived($projectId: ID!, $input: ActivityEventFilterInput) {
|
|
4501
5823
|
activityEventReceived(projectId: $projectId, input: $input) {
|
|
4502
5824
|
id
|
|
@@ -4517,56 +5839,6 @@ var ACTIVITY_SUBSCRIPTION = `
|
|
|
4517
5839
|
}
|
|
4518
5840
|
}
|
|
4519
5841
|
`;
|
|
4520
|
-
function buildActivityEventsSocketUrl(apiUrl) {
|
|
4521
|
-
const url = new URL(apiUrl);
|
|
4522
|
-
const basePath = url.pathname.replace(/\/+$/, "");
|
|
4523
|
-
const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
|
|
4524
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
4525
|
-
url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
|
|
4526
|
-
url.search = "";
|
|
4527
|
-
url.hash = "";
|
|
4528
|
-
return url.toString();
|
|
4529
|
-
}
|
|
4530
|
-
function subscribeToActivityEvents(options) {
|
|
4531
|
-
if (!options.credentials.apiKey) {
|
|
4532
|
-
throw new ConfigError(
|
|
4533
|
-
"Live activity subscriptions require API key authentication on the current io_beam socket contract."
|
|
4534
|
-
);
|
|
4535
|
-
}
|
|
4536
|
-
const phoenixSocket = new Socket(buildActivityEventsSocketUrl(options.apiUrl), {
|
|
4537
|
-
params: socketAuthParams(options.credentials),
|
|
4538
|
-
timeout: 1e4
|
|
4539
|
-
});
|
|
4540
|
-
const absintheSocket = AbsintheSocket.create(phoenixSocket);
|
|
4541
|
-
const notifier = AbsintheSocket.send(absintheSocket, {
|
|
4542
|
-
operation: ACTIVITY_SUBSCRIPTION,
|
|
4543
|
-
variables: { projectId: options.projectId, input: options.input }
|
|
4544
|
-
});
|
|
4545
|
-
const observer = {
|
|
4546
|
-
onAbort: (error) => {
|
|
4547
|
-
options.onError?.(error);
|
|
4548
|
-
},
|
|
4549
|
-
onCancel: () => {
|
|
4550
|
-
},
|
|
4551
|
-
onError: (error) => {
|
|
4552
|
-
options.onError?.(error);
|
|
4553
|
-
},
|
|
4554
|
-
onResult: (result) => {
|
|
4555
|
-
const event = result.data?.activityEventReceived;
|
|
4556
|
-
if (event) {
|
|
4557
|
-
options.onEvent(event);
|
|
4558
|
-
}
|
|
4559
|
-
},
|
|
4560
|
-
onStart: () => {
|
|
4561
|
-
options.onConnected?.();
|
|
4562
|
-
}
|
|
4563
|
-
};
|
|
4564
|
-
AbsintheSocket.observe(absintheSocket, notifier, observer);
|
|
4565
|
-
phoenixSocket.connect();
|
|
4566
|
-
return () => {
|
|
4567
|
-
phoenixSocket.disconnect();
|
|
4568
|
-
};
|
|
4569
|
-
}
|
|
4570
5842
|
|
|
4571
5843
|
// src/activity/presentation.ts
|
|
4572
5844
|
var DISPLAY_NAME_NAMESPACES = /* @__PURE__ */ new Set([
|
|
@@ -4892,7 +6164,6 @@ var LEVEL_COLORS = {
|
|
|
4892
6164
|
scheduled: chalk12.magenta
|
|
4893
6165
|
};
|
|
4894
6166
|
var FOLLOW_POLL_INTERVAL_MS = 2e3;
|
|
4895
|
-
var FOLLOW_SEEN_ID_LIMIT = 1e3;
|
|
4896
6167
|
var FOLLOW_BATCH_LIMIT = 200;
|
|
4897
6168
|
function colorLevel(level) {
|
|
4898
6169
|
if (!level) return chalk12.gray("\u2014");
|
|
@@ -4943,74 +6214,13 @@ function formatEvent(event) {
|
|
|
4943
6214
|
detail ? chalk12.gray(detail) : ""
|
|
4944
6215
|
].join(" ").trimEnd();
|
|
4945
6216
|
}
|
|
4946
|
-
function
|
|
4947
|
-
return events.reduce((latest, event) => {
|
|
4948
|
-
return event.occurredAt > latest ? event.occurredAt : latest;
|
|
4949
|
-
}, fallback);
|
|
4950
|
-
}
|
|
4951
|
-
function trimSeenIds(seenIds, order) {
|
|
4952
|
-
while (order.length > FOLLOW_SEEN_ID_LIMIT) {
|
|
4953
|
-
const evicted = order.shift();
|
|
4954
|
-
if (evicted) {
|
|
4955
|
-
seenIds.delete(evicted);
|
|
4956
|
-
}
|
|
4957
|
-
}
|
|
4958
|
-
}
|
|
4959
|
-
function orderEvents(events) {
|
|
4960
|
-
return [...events].sort((left, right) => {
|
|
4961
|
-
if (left.occurredAt === right.occurredAt) {
|
|
4962
|
-
return left.id.localeCompare(right.id);
|
|
4963
|
-
}
|
|
4964
|
-
return left.occurredAt.localeCompare(right.occurredAt);
|
|
4965
|
-
});
|
|
4966
|
-
}
|
|
4967
|
-
function wait(ms) {
|
|
4968
|
-
return new Promise((resolve4) => {
|
|
4969
|
-
setTimeout(resolve4, ms);
|
|
4970
|
-
});
|
|
4971
|
-
}
|
|
4972
|
-
function followBatchInput(input, since) {
|
|
6217
|
+
function followPollInput(input, since) {
|
|
4973
6218
|
return {
|
|
4974
6219
|
...input,
|
|
4975
6220
|
since,
|
|
4976
6221
|
limit: Math.max(input?.limit ?? 0, FOLLOW_BATCH_LIMIT)
|
|
4977
6222
|
};
|
|
4978
6223
|
}
|
|
4979
|
-
async function drainFollowEvents(client, projectId, input, since, seenIds, seenOrder) {
|
|
4980
|
-
let cursor = since;
|
|
4981
|
-
const events = [];
|
|
4982
|
-
for (let i = 0; i < 10; i += 1) {
|
|
4983
|
-
const batch = orderEvents(
|
|
4984
|
-
await client.projectEvents(projectId, followBatchInput(input, cursor))
|
|
4985
|
-
);
|
|
4986
|
-
const unseen = batch.filter((event) => !seenIds.has(event.id));
|
|
4987
|
-
if (unseen.length === 0) {
|
|
4988
|
-
return { events, since: cursor };
|
|
4989
|
-
}
|
|
4990
|
-
events.push(...unseen);
|
|
4991
|
-
cursor = latestOccurredAt(unseen, cursor);
|
|
4992
|
-
for (const event of unseen) {
|
|
4993
|
-
seenIds.add(event.id);
|
|
4994
|
-
seenOrder.push(event.id);
|
|
4995
|
-
}
|
|
4996
|
-
trimSeenIds(seenIds, seenOrder);
|
|
4997
|
-
if (batch.length < FOLLOW_BATCH_LIMIT) {
|
|
4998
|
-
return { events, since: cursor };
|
|
4999
|
-
}
|
|
5000
|
-
}
|
|
5001
|
-
return { events, since: cursor };
|
|
5002
|
-
}
|
|
5003
|
-
function installSigintHandler(onExit) {
|
|
5004
|
-
const handleSigint = () => {
|
|
5005
|
-
console.log(chalk12.gray("\n Disconnected."));
|
|
5006
|
-
onExit();
|
|
5007
|
-
process.exit(0);
|
|
5008
|
-
};
|
|
5009
|
-
process.once("SIGINT", handleSigint);
|
|
5010
|
-
return () => {
|
|
5011
|
-
process.removeListener("SIGINT", handleSigint);
|
|
5012
|
-
};
|
|
5013
|
-
}
|
|
5014
6224
|
async function logsCommand(options) {
|
|
5015
6225
|
try {
|
|
5016
6226
|
const config = await loadConfig();
|
|
@@ -5026,18 +6236,18 @@ async function logsCommand(options) {
|
|
|
5026
6236
|
const since = parseSince(options.since);
|
|
5027
6237
|
if (since) input.since = since;
|
|
5028
6238
|
}
|
|
5029
|
-
const
|
|
5030
|
-
if (
|
|
6239
|
+
const initialEvents = await client.projectEvents(pid, input);
|
|
6240
|
+
if (initialEvents.length === 0 && !options.follow) {
|
|
5031
6241
|
console.log(chalk12.gray("No events found."));
|
|
5032
6242
|
return;
|
|
5033
6243
|
}
|
|
5034
|
-
if (
|
|
6244
|
+
if (initialEvents.length > 0) {
|
|
5035
6245
|
console.log(chalk12.bold(`
|
|
5036
|
-
Activity Log \u2014 ${
|
|
6246
|
+
Activity Log \u2014 ${initialEvents.length} events
|
|
5037
6247
|
`));
|
|
5038
6248
|
console.log(formatHeader());
|
|
5039
6249
|
console.log(chalk12.gray(` ${"\u2500".repeat(92)}`));
|
|
5040
|
-
for (const event of
|
|
6250
|
+
for (const event of initialEvents) {
|
|
5041
6251
|
console.log(formatEvent(event));
|
|
5042
6252
|
}
|
|
5043
6253
|
}
|
|
@@ -5047,7 +6257,7 @@ Activity Log \u2014 ${events.length} events
|
|
|
5047
6257
|
client,
|
|
5048
6258
|
pid,
|
|
5049
6259
|
input,
|
|
5050
|
-
|
|
6260
|
+
initialEvents,
|
|
5051
6261
|
followStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5052
6262
|
);
|
|
5053
6263
|
} else {
|
|
@@ -5067,88 +6277,68 @@ async function tailEvents(apiUrl, client, projectId, input, initialEvents, follo
|
|
|
5067
6277
|
throw new ConfigError("Not authenticated. Run `io login` first.");
|
|
5068
6278
|
}
|
|
5069
6279
|
console.log(chalk12.gray("\n Waiting for events... (Ctrl+C to stop)\n"));
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
6280
|
+
const initialSince = initialEvents.reduce(
|
|
6281
|
+
(latest, event) => event.occurredAt > latest ? event.occurredAt : latest,
|
|
6282
|
+
followStartedAt
|
|
6283
|
+
);
|
|
6284
|
+
const socketClient = createSocketClient({ apiUrl, credentials: creds });
|
|
6285
|
+
const subscriptionVars = input;
|
|
6286
|
+
let liveStarted = false;
|
|
6287
|
+
try {
|
|
6288
|
+
await watch(
|
|
6289
|
+
{
|
|
6290
|
+
subscribe: (onEvent, onError, onConnected) => {
|
|
6291
|
+
const unsubscribe = socketClient.subscribe(
|
|
6292
|
+
ACTIVITY_EVENT_SUBSCRIPTION,
|
|
6293
|
+
{ projectId, input: subscriptionVars },
|
|
6294
|
+
{
|
|
6295
|
+
onStart: onConnected,
|
|
6296
|
+
onResult: (result) => {
|
|
6297
|
+
const event = result.data?.activityEventReceived;
|
|
6298
|
+
if (event) onEvent(event);
|
|
6299
|
+
},
|
|
6300
|
+
onError,
|
|
6301
|
+
onAbort: onError
|
|
6302
|
+
}
|
|
6303
|
+
);
|
|
6304
|
+
return { unsubscribe };
|
|
6305
|
+
},
|
|
6306
|
+
poll: async (since) => {
|
|
6307
|
+
return client.projectEvents(projectId, followPollInput(input, since));
|
|
6308
|
+
},
|
|
6309
|
+
pollIntervalMs: FOLLOW_POLL_INTERVAL_MS,
|
|
6310
|
+
initialSince,
|
|
6311
|
+
getId: (event) => event.id,
|
|
6312
|
+
getCursor: (event) => event.occurredAt,
|
|
6313
|
+
seenIdLimit: 1e3
|
|
6314
|
+
},
|
|
6315
|
+
{
|
|
5083
6316
|
onEvent: (event) => {
|
|
5084
|
-
if (
|
|
5085
|
-
|
|
6317
|
+
if (!liveStarted) {
|
|
6318
|
+
liveStarted = true;
|
|
5086
6319
|
}
|
|
5087
6320
|
console.log(formatEvent(event));
|
|
5088
|
-
seenIds2.add(event.id);
|
|
5089
|
-
seenOrder2.push(event.id);
|
|
5090
|
-
trimSeenIds(seenIds2, seenOrder2);
|
|
5091
6321
|
},
|
|
5092
|
-
onConnected: () => {
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
for (const event of events) {
|
|
5103
|
-
console.log(formatEvent(event));
|
|
5104
|
-
}
|
|
5105
|
-
}).catch((error) => {
|
|
5106
|
-
console.error(chalk12.red(`
|
|
5107
|
-
Connection error: ${error.message}`));
|
|
5108
|
-
removeSigintHandler2();
|
|
5109
|
-
disconnect?.();
|
|
5110
|
-
reject(error);
|
|
5111
|
-
});
|
|
6322
|
+
onConnected: (mode) => {
|
|
6323
|
+
if (mode === "subscription") {
|
|
6324
|
+
console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
|
|
6325
|
+
} else {
|
|
6326
|
+
console.log(
|
|
6327
|
+
chalk12.green(
|
|
6328
|
+
" \u25CF Connected \u2014 polling for live events with your authenticated session\n"
|
|
6329
|
+
)
|
|
6330
|
+
);
|
|
6331
|
+
}
|
|
5112
6332
|
},
|
|
5113
|
-
onError: (
|
|
6333
|
+
onError: (err) => {
|
|
5114
6334
|
console.error(chalk12.red(`
|
|
5115
|
-
Connection error: ${
|
|
5116
|
-
removeSigintHandler2();
|
|
5117
|
-
disconnect?.();
|
|
5118
|
-
reject(error);
|
|
6335
|
+
Connection error: ${err.message}`));
|
|
5119
6336
|
}
|
|
5120
|
-
});
|
|
5121
|
-
});
|
|
5122
|
-
}
|
|
5123
|
-
console.log(
|
|
5124
|
-
chalk12.green(" \u25CF Connected \u2014 polling for live events with your authenticated session\n")
|
|
5125
|
-
);
|
|
5126
|
-
const seenIds = new Set(initialEvents.map((event) => event.id));
|
|
5127
|
-
const seenOrder = initialEvents.map((event) => event.id);
|
|
5128
|
-
let since = latestOccurredAt(initialEvents, followStartedAt);
|
|
5129
|
-
const removeSigintHandler = installSigintHandler(() => {
|
|
5130
|
-
});
|
|
5131
|
-
try {
|
|
5132
|
-
for (; ; ) {
|
|
5133
|
-
const batch = await drainFollowEvents(
|
|
5134
|
-
client,
|
|
5135
|
-
projectId,
|
|
5136
|
-
input,
|
|
5137
|
-
since,
|
|
5138
|
-
seenIds,
|
|
5139
|
-
seenOrder
|
|
5140
|
-
);
|
|
5141
|
-
const nextEvents = batch.events;
|
|
5142
|
-
for (const event of nextEvents) {
|
|
5143
|
-
console.log(formatEvent(event));
|
|
5144
|
-
}
|
|
5145
|
-
if (nextEvents.length > 0) {
|
|
5146
|
-
since = batch.since;
|
|
5147
6337
|
}
|
|
5148
|
-
|
|
5149
|
-
|
|
6338
|
+
);
|
|
6339
|
+
console.log(chalk12.gray("\n Disconnected."));
|
|
5150
6340
|
} finally {
|
|
5151
|
-
|
|
6341
|
+
socketClient.close();
|
|
5152
6342
|
}
|
|
5153
6343
|
}
|
|
5154
6344
|
function parseSince(value) {
|
|
@@ -5181,7 +6371,7 @@ async function signalEmitCommand(name, options) {
|
|
|
5181
6371
|
try {
|
|
5182
6372
|
const config = await loadConfig({ projectId: options.projectId });
|
|
5183
6373
|
const client = createClient(config);
|
|
5184
|
-
const signal =
|
|
6374
|
+
const signal = normalizeRuntimeSignalName(name);
|
|
5185
6375
|
if (!signal) {
|
|
5186
6376
|
throw new ConfigError(
|
|
5187
6377
|
"signal name must be a bare event name like smoke.start or use the signal: namespace"
|
|
@@ -5223,19 +6413,268 @@ async function signalEmitCommand(name, options) {
|
|
|
5223
6413
|
throw err;
|
|
5224
6414
|
}
|
|
5225
6415
|
}
|
|
6416
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
6417
|
+
var POLL_BATCH_LIMIT = 200;
|
|
6418
|
+
function formatEvent2(event) {
|
|
6419
|
+
const time = new Date(event.occurredAt).toLocaleTimeString("en-GB", {
|
|
6420
|
+
hour12: false
|
|
6421
|
+
});
|
|
6422
|
+
const status = inferActivityEventStatus(event);
|
|
6423
|
+
const category = inferActivityEventCategory(event);
|
|
6424
|
+
const name = inferActivityEventName(event);
|
|
6425
|
+
const detail = inferActivityEventDetail(event) ?? "";
|
|
6426
|
+
return ` ${chalk12.gray(time)} ${chalk12.cyan(category)} ${name}${detail ? ` ${chalk12.gray(detail)}` : ""}${status ? ` ${chalk12.gray(`(${status})`)}` : ""}`;
|
|
6427
|
+
}
|
|
6428
|
+
async function reconcileCommand(options) {
|
|
6429
|
+
try {
|
|
6430
|
+
const config = await loadConfig();
|
|
6431
|
+
const client = createClient(config);
|
|
6432
|
+
const projectId = options.projectId ?? client.projectId();
|
|
6433
|
+
console.log(chalk12.gray(`Requesting reconciliation for project ${projectId}\u2026`));
|
|
6434
|
+
const accepted = await client.reconcileProject(projectId);
|
|
6435
|
+
if (!accepted) {
|
|
6436
|
+
throw new CliError(
|
|
6437
|
+
"Runtime rejected the reconcile request.",
|
|
6438
|
+
"RECONCILE_REJECTED"
|
|
6439
|
+
);
|
|
6440
|
+
}
|
|
6441
|
+
console.log(chalk12.green("\u2713 Reconcile requested."));
|
|
6442
|
+
if (!options.watch) {
|
|
6443
|
+
return;
|
|
6444
|
+
}
|
|
6445
|
+
const creds = await loadCredentials();
|
|
6446
|
+
if (!creds?.token) {
|
|
6447
|
+
throw new ConfigError("Not authenticated. Run `io login` first.");
|
|
6448
|
+
}
|
|
6449
|
+
console.log(
|
|
6450
|
+
chalk12.gray("\nTailing activity for this project (Ctrl+C to stop)\u2026\n")
|
|
6451
|
+
);
|
|
6452
|
+
const socketClient = createSocketClient({
|
|
6453
|
+
apiUrl: config.apiUrl,
|
|
6454
|
+
credentials: creds
|
|
6455
|
+
});
|
|
6456
|
+
const initialSince = (/* @__PURE__ */ new Date()).toISOString();
|
|
6457
|
+
try {
|
|
6458
|
+
await watch(
|
|
6459
|
+
{
|
|
6460
|
+
subscribe: (onEvent, onError, onConnected) => {
|
|
6461
|
+
const unsubscribe = socketClient.subscribe(
|
|
6462
|
+
ACTIVITY_EVENT_SUBSCRIPTION,
|
|
6463
|
+
{ projectId, input: { limit: POLL_BATCH_LIMIT } },
|
|
6464
|
+
{
|
|
6465
|
+
onStart: onConnected,
|
|
6466
|
+
onResult: (result) => {
|
|
6467
|
+
const event = result.data?.activityEventReceived;
|
|
6468
|
+
if (event) onEvent(event);
|
|
6469
|
+
},
|
|
6470
|
+
onError,
|
|
6471
|
+
onAbort: onError
|
|
6472
|
+
}
|
|
6473
|
+
);
|
|
6474
|
+
return { unsubscribe };
|
|
6475
|
+
},
|
|
6476
|
+
poll: async (since) => {
|
|
6477
|
+
return client.projectEvents(projectId, {
|
|
6478
|
+
since,
|
|
6479
|
+
limit: POLL_BATCH_LIMIT
|
|
6480
|
+
});
|
|
6481
|
+
},
|
|
6482
|
+
pollIntervalMs: POLL_INTERVAL_MS,
|
|
6483
|
+
initialSince,
|
|
6484
|
+
getId: (event) => event.id,
|
|
6485
|
+
getCursor: (event) => event.occurredAt
|
|
6486
|
+
},
|
|
6487
|
+
{
|
|
6488
|
+
onEvent: (event) => console.log(formatEvent2(event)),
|
|
6489
|
+
onConnected: (mode) => {
|
|
6490
|
+
if (mode === "subscription") {
|
|
6491
|
+
console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
|
|
6492
|
+
} else {
|
|
6493
|
+
console.log(
|
|
6494
|
+
chalk12.green(
|
|
6495
|
+
" \u25CF Connected \u2014 polling for live events with your authenticated session\n"
|
|
6496
|
+
)
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
);
|
|
6502
|
+
console.log(chalk12.gray("\nDisconnected."));
|
|
6503
|
+
} finally {
|
|
6504
|
+
socketClient.close();
|
|
6505
|
+
}
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
if (err instanceof CliError) {
|
|
6508
|
+
console.error(chalk12.red(err.message));
|
|
6509
|
+
process.exit(1);
|
|
6510
|
+
}
|
|
6511
|
+
throw err;
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
function isRecord4(value) {
|
|
6515
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6516
|
+
}
|
|
6517
|
+
function isCatalogShape2(value) {
|
|
6518
|
+
if (!isRecord4(value)) return false;
|
|
6519
|
+
return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isRecord4(value.resources) && isRecord4(value.signals) && isRecord4(value.tools) && isRecord4(value.schemas);
|
|
6520
|
+
}
|
|
6521
|
+
async function readLocalCatalog(buildDir) {
|
|
6522
|
+
const path = resolve(process.cwd(), buildDir, "catalog.json");
|
|
6523
|
+
let raw;
|
|
6524
|
+
try {
|
|
6525
|
+
raw = await readFile(path, "utf-8");
|
|
6526
|
+
} catch (err) {
|
|
6527
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
6528
|
+
throw new CliError(
|
|
6529
|
+
`Local catalog not found at ${path}. Run \`io build\` or \`io deploy\` first.`,
|
|
6530
|
+
"CATALOG_NOT_FOUND"
|
|
6531
|
+
);
|
|
6532
|
+
}
|
|
6533
|
+
throw err;
|
|
6534
|
+
}
|
|
6535
|
+
let parsed;
|
|
6536
|
+
try {
|
|
6537
|
+
parsed = JSON.parse(raw);
|
|
6538
|
+
} catch (err) {
|
|
6539
|
+
throw new CliError(
|
|
6540
|
+
`Local catalog at ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
6541
|
+
"CATALOG_INVALID_JSON"
|
|
6542
|
+
);
|
|
6543
|
+
}
|
|
6544
|
+
if (!isCatalogShape2(parsed)) {
|
|
6545
|
+
throw new CliError(
|
|
6546
|
+
`Local catalog at ${path} does not match the io.invocation.v2 catalog shape.`,
|
|
6547
|
+
"CATALOG_SHAPE_MISMATCH"
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
return parsed;
|
|
6551
|
+
}
|
|
6552
|
+
function describeExecutor(executor) {
|
|
6553
|
+
switch (executor.kind) {
|
|
6554
|
+
case "platform_tool":
|
|
6555
|
+
return `${executor.provider_kind}.${executor.backend_kind}.${executor.operation}`;
|
|
6556
|
+
case "stream_publish":
|
|
6557
|
+
return `${executor.provider_kind}.${executor.backend_kind} \u2192 ${executor.destination_ref.slice(0, 19)}\u2026`;
|
|
6558
|
+
case "webhook_response":
|
|
6559
|
+
return "webhook response";
|
|
6560
|
+
case "http_request":
|
|
6561
|
+
return `${executor.method} ${executor.url}`;
|
|
6562
|
+
case "graphql_request":
|
|
6563
|
+
return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
|
|
6564
|
+
case "http_operation":
|
|
6565
|
+
return `${executor.method} ${executor.base_url}${executor.path}`;
|
|
6566
|
+
case "graphql_operation":
|
|
6567
|
+
return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
|
|
6568
|
+
case "unsupported":
|
|
6569
|
+
return chalk12.red(`unsupported (${executor.reason})`);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
function formatCatalog(catalog2, label) {
|
|
6573
|
+
const lines = [];
|
|
6574
|
+
lines.push(chalk12.bold(`
|
|
6575
|
+
Catalog (${label})`));
|
|
6576
|
+
lines.push(
|
|
6577
|
+
` ${chalk12.gray("contract_version:")} ${catalog2.contract_version}`
|
|
6578
|
+
);
|
|
6579
|
+
lines.push(
|
|
6580
|
+
` ${chalk12.gray("snapshot_version:")} ${catalog2.snapshot_version}`
|
|
6581
|
+
);
|
|
6582
|
+
lines.push(` ${chalk12.gray("catalog_hash: ")} ${catalog2.catalog_hash}`);
|
|
6583
|
+
lines.push("");
|
|
6584
|
+
lines.push(chalk12.bold(" Counts"));
|
|
6585
|
+
lines.push(` resources: ${Object.keys(catalog2.resources).length}`);
|
|
6586
|
+
lines.push(` signals: ${Object.keys(catalog2.signals).length}`);
|
|
6587
|
+
lines.push(` tools: ${Object.keys(catalog2.tools).length}`);
|
|
6588
|
+
lines.push(` schemas: ${Object.keys(catalog2.schemas).length}`);
|
|
6589
|
+
const resourceIds = Object.keys(catalog2.resources).sort();
|
|
6590
|
+
if (resourceIds.length > 0) {
|
|
6591
|
+
lines.push("");
|
|
6592
|
+
lines.push(chalk12.bold(" Resources"));
|
|
6593
|
+
for (const id of resourceIds) {
|
|
6594
|
+
const resource = catalog2.resources[id];
|
|
6595
|
+
lines.push(
|
|
6596
|
+
` ${chalk12.cyan(resource.id)} ${chalk12.gray(
|
|
6597
|
+
`${resource.resource_kind}/${resource.backend_kind} scope=${resource.binding_scope} cred=${resource.credential_source}`
|
|
6598
|
+
)}`
|
|
6599
|
+
);
|
|
6600
|
+
for (const signalId of resource.signals) {
|
|
6601
|
+
const signal = catalog2.signals[signalId];
|
|
6602
|
+
if (!signal) continue;
|
|
6603
|
+
lines.push(
|
|
6604
|
+
` ${chalk12.magenta("signal")} ${signal.name} ${chalk12.gray(
|
|
6605
|
+
`(${signal.mode}, scope=${signal.scope})`
|
|
6606
|
+
)}`
|
|
6607
|
+
);
|
|
6608
|
+
}
|
|
6609
|
+
for (const toolId of resource.tools) {
|
|
6610
|
+
const tool = catalog2.tools[toolId];
|
|
6611
|
+
if (!tool) continue;
|
|
6612
|
+
lines.push(
|
|
6613
|
+
` ${chalk12.yellow("tool ")} ${tool.name} ${chalk12.gray(
|
|
6614
|
+
`(${tool.effect}, audit=${tool.audit_requirement}) ${describeExecutor(tool.executor)}`
|
|
6615
|
+
)}`
|
|
6616
|
+
);
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
lines.push("");
|
|
6621
|
+
return lines.join("\n");
|
|
6622
|
+
}
|
|
6623
|
+
async function catalogShowCommand(options) {
|
|
6624
|
+
try {
|
|
6625
|
+
let catalog2;
|
|
6626
|
+
let label;
|
|
6627
|
+
if (options.remote) {
|
|
6628
|
+
const config = await loadConfig();
|
|
6629
|
+
if (!config.apiUrl || !config.projectId) {
|
|
6630
|
+
throw new CliError(
|
|
6631
|
+
"No apiUrl or projectId configured. Run `io login` and set a project first.",
|
|
6632
|
+
"CONFIG_MISSING"
|
|
6633
|
+
);
|
|
6634
|
+
}
|
|
6635
|
+
const client = createClient(config);
|
|
6636
|
+
const remote = await client.fetchRemoteCatalog(client.projectId());
|
|
6637
|
+
if (!remote) {
|
|
6638
|
+
console.log(
|
|
6639
|
+
chalk12.gray("No active deployment catalog for this project yet.")
|
|
6640
|
+
);
|
|
6641
|
+
return;
|
|
6642
|
+
}
|
|
6643
|
+
catalog2 = remote;
|
|
6644
|
+
label = `remote: project ${client.projectId()}`;
|
|
6645
|
+
} else {
|
|
6646
|
+
const buildDir = options.buildDir ?? ".io";
|
|
6647
|
+
catalog2 = await readLocalCatalog(buildDir);
|
|
6648
|
+
label = `local: ${join(buildDir, "catalog.json")}`;
|
|
6649
|
+
}
|
|
6650
|
+
if (options.json) {
|
|
6651
|
+
process.stdout.write(stableStringify(catalog2, 2));
|
|
6652
|
+
process.stdout.write("\n");
|
|
6653
|
+
return;
|
|
6654
|
+
}
|
|
6655
|
+
process.stdout.write(formatCatalog(catalog2, label));
|
|
6656
|
+
} catch (err) {
|
|
6657
|
+
if (err instanceof CliError) {
|
|
6658
|
+
console.error(chalk12.red(err.message));
|
|
6659
|
+
process.exit(1);
|
|
6660
|
+
}
|
|
6661
|
+
throw err;
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
5226
6664
|
|
|
5227
6665
|
// src/index.ts
|
|
5228
6666
|
var program = new Command();
|
|
5229
|
-
program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.
|
|
6667
|
+
program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.16");
|
|
5230
6668
|
program.command("init").description("Initialize io.config.json for the current project").option("--yes", "Overwrite existing config without prompting").action(initCommand);
|
|
5231
6669
|
program.command("build").description("Build behaviors from the io/ directory").option("--dir <path>", "Source directory", "io").option("--minify", "Minify bundles", false).action(buildCommand);
|
|
5232
|
-
program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").action(deployCommand);
|
|
6670
|
+
program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").option("--watch", "Tail deployment status until it reaches a terminal state").action(deployCommand);
|
|
5233
6671
|
program.command("login").description("Authenticate with the IO platform").option("--token <value>", "API token for CI/CD environments").action(loginCommand);
|
|
5234
6672
|
program.command("logout").description("Remove stored credentials").action(logoutCommand);
|
|
5235
6673
|
program.command("whoami").description("Show current authentication status").action(whoamiCommand);
|
|
5236
6674
|
var projects = program.command("projects").description("Manage projects");
|
|
5237
6675
|
projects.command("list").description("List all projects").action(projectsListCommand);
|
|
5238
6676
|
projects.command("create <name>").description("Create a new project").action(projectsCreateCommand);
|
|
6677
|
+
projects.command("delete <id>").description("Delete a project (requires typing the project name)").option("--yes", "Skip the name confirmation prompt").option("--organization-id <id>", "Organization that owns the project").action(projectsDeleteCommand);
|
|
5239
6678
|
var secrets = program.command("secrets").description("Manage project secrets");
|
|
5240
6679
|
secrets.command("set <name> <value>").description("Set a secret value").option("--target <targets...>", "Restrict to specific targets").action(secretsSetCommand);
|
|
5241
6680
|
secrets.command("list").description("List all secrets").action(secretsListCommand);
|
|
@@ -5249,6 +6688,9 @@ var signals = program.command("signals").description("Emit runtime signals");
|
|
|
5249
6688
|
signals.command("emit <name>").description("Emit a signal into the active project's event stream").option("--payload <json>", "Signal payload as a JSON object", "{}").option("--metadata <json>", "Signal metadata as a JSON object", "{}").option("--project-id <id>", "Override the configured project ID").requiredOption("--principal-id <id>", "Project principal ID for the emitted signal").option("--correlation-id <id>", "Correlation ID to attach to the signal").option("--causation-id <id>", "Causation ID to attach to the signal").option("--idempotency-key <key>", "Optional idempotency key").option("--source-name <name>", "Optional source name for the emitted signal").option("--json", "Print the raw emitted event as JSON", false).action(signalEmitCommand);
|
|
5250
6689
|
program.command("quota").description("Show current quota usage for the active organization").action(quotaCommand);
|
|
5251
6690
|
program.command("logs").description("Show recent activity events").option("--limit <count>", "Number of events to show", "50").option("--type <types...>", "Filter by event types").option("--since <duration>", "Show events since (e.g. 15m, 1h, 7d)").option("--follow", "Tail events in real-time").action(logsCommand);
|
|
6691
|
+
program.command("reconcile").description("Force runtime to reconcile resources for the active project").option("--project-id <id>", "Override the configured project ID").option("--watch", "Tail activity events after the reconcile request").action(reconcileCommand);
|
|
6692
|
+
var catalog = program.command("catalog").description("Inspect the v2 deployment catalog");
|
|
6693
|
+
catalog.command("show").description("Print the local or remote deployment catalog").option("--remote", "Show the active deployment catalog from runtime").option("--json", "Print the raw catalog JSON").option("--build-dir <path>", "Local build directory", ".io").action(catalogShowCommand);
|
|
5252
6694
|
program.parse();
|
|
5253
6695
|
//# sourceMappingURL=index.js.map
|
|
5254
6696
|
//# sourceMappingURL=index.js.map
|