@kaelen-ai/cli 0.1.15 → 0.1.17
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 +1866 -407
- 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) {
|
|
@@ -1000,11 +1231,14 @@ function validateToolCapability(appName, resourceName, tool, errors, seenCapabil
|
|
|
1000
1231
|
errors.push(`tool "${expected}" capability mode does not match the tool mode`);
|
|
1001
1232
|
}
|
|
1002
1233
|
const audit = stringField2(capability, "audit");
|
|
1003
|
-
if (
|
|
1004
|
-
errors.push(`
|
|
1234
|
+
if (audit !== "finish_only" && audit !== "pre_call") {
|
|
1235
|
+
errors.push(`tool "${expected}" capability audit must be finish_only or pre_call`);
|
|
1005
1236
|
}
|
|
1006
|
-
if (mode === "
|
|
1007
|
-
errors.push(`
|
|
1237
|
+
if (mode === "write" && audit !== "pre_call") {
|
|
1238
|
+
errors.push(`write tool "${expected}" must use pre_call audit`);
|
|
1239
|
+
}
|
|
1240
|
+
if (mode === "read" && audit !== "finish_only") {
|
|
1241
|
+
errors.push(`read tool "${expected}" must use finish_only audit`);
|
|
1008
1242
|
}
|
|
1009
1243
|
const exposure = stringField2(capability, "credential_exposure");
|
|
1010
1244
|
if (exposure !== "none" && exposure !== "stdlib") {
|
|
@@ -1056,11 +1290,11 @@ function validateManifest(manifest) {
|
|
|
1056
1290
|
const root = isRecord2(manifest) ? manifest : {};
|
|
1057
1291
|
const errors = [];
|
|
1058
1292
|
const seenCapabilities = /* @__PURE__ */ new Set();
|
|
1059
|
-
if (root.contract_version !== "io.invocation.
|
|
1060
|
-
errors.push("contract_version must be io.invocation.
|
|
1293
|
+
if (root.contract_version !== "io.invocation.v2") {
|
|
1294
|
+
errors.push("contract_version must be io.invocation.v2");
|
|
1061
1295
|
}
|
|
1062
|
-
if (root.snapshot_version !== "io_runtime.snapshot.
|
|
1063
|
-
errors.push("snapshot_version must be io_runtime.snapshot.
|
|
1296
|
+
if (root.snapshot_version !== "io_runtime.snapshot.v2") {
|
|
1297
|
+
errors.push("snapshot_version must be io_runtime.snapshot.v2");
|
|
1064
1298
|
}
|
|
1065
1299
|
const secretBundles = arrayField(root, "secret_bundles").filter(isRecord2);
|
|
1066
1300
|
duplicateNames(secretBundles, "secret bundle", errors);
|
|
@@ -1075,6 +1309,14 @@ function validateManifest(manifest) {
|
|
|
1075
1309
|
const secretNames = new Set(
|
|
1076
1310
|
secretBundles.map((entry) => stringField2(entry, "name")).filter((name) => !!name)
|
|
1077
1311
|
);
|
|
1312
|
+
const secretBundleSlots = /* @__PURE__ */ new Map();
|
|
1313
|
+
for (const bundle of secretBundles) {
|
|
1314
|
+
const bundleName = stringField2(bundle, "name");
|
|
1315
|
+
if (!bundleName) continue;
|
|
1316
|
+
const slots = bundle.slots;
|
|
1317
|
+
if (!isRecord2(slots)) continue;
|
|
1318
|
+
secretBundleSlots.set(bundleName, new Set(Object.keys(slots)));
|
|
1319
|
+
}
|
|
1078
1320
|
const declaredEndpoints = validateEndpointsSection(
|
|
1079
1321
|
arrayField(root, "endpoints"),
|
|
1080
1322
|
errors
|
|
@@ -1121,6 +1363,17 @@ function validateManifest(manifest) {
|
|
|
1121
1363
|
} else {
|
|
1122
1364
|
validateResourceCapability(appName, resourceName, capability, errors);
|
|
1123
1365
|
}
|
|
1366
|
+
const resourceKindForConfig = stringField2(resource, "kind") ?? "";
|
|
1367
|
+
validateHttpLikeConfig(
|
|
1368
|
+
appName,
|
|
1369
|
+
resourceName,
|
|
1370
|
+
resourceKindForConfig,
|
|
1371
|
+
resource.config,
|
|
1372
|
+
secretNames,
|
|
1373
|
+
secretBundle,
|
|
1374
|
+
secretBundleSlots,
|
|
1375
|
+
errors
|
|
1376
|
+
);
|
|
1124
1377
|
const tools = toolEntries.filter(isRecord2);
|
|
1125
1378
|
duplicateNames(tools, `resource "${appName}/${resourceName}" tool`, errors);
|
|
1126
1379
|
for (const tool of tools) {
|
|
@@ -1588,6 +1841,12 @@ var RUNTIME_MEMORY_SIGNALS = {
|
|
|
1588
1841
|
onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
|
|
1589
1842
|
onRoutineBroken: "knowledge:routine.broken"
|
|
1590
1843
|
};
|
|
1844
|
+
var RUNTIME_SDK_PASSTHROUGH_EXPORTS = /* @__PURE__ */ new Set([
|
|
1845
|
+
"google",
|
|
1846
|
+
"microsoft",
|
|
1847
|
+
"httpApi",
|
|
1848
|
+
"graphqlApi"
|
|
1849
|
+
]);
|
|
1591
1850
|
var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
|
|
1592
1851
|
"ioSchema",
|
|
1593
1852
|
"zodSchema",
|
|
@@ -1608,7 +1867,8 @@ var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
|
|
|
1608
1867
|
"graphql",
|
|
1609
1868
|
"http",
|
|
1610
1869
|
"calendar",
|
|
1611
|
-
"contacts"
|
|
1870
|
+
"contacts",
|
|
1871
|
+
...RUNTIME_SDK_PASSTHROUGH_EXPORTS
|
|
1612
1872
|
]);
|
|
1613
1873
|
var COMPILED_AWAY_EXPORTS = /* @__PURE__ */ new Set(["behavior", "conversation"]);
|
|
1614
1874
|
function normalizeSdkSpecifier(specifier) {
|
|
@@ -1870,6 +2130,29 @@ ${entries.map(([key, value]) => ` ${key}: Object.freeze({ name: ${JSON.stringif
|
|
|
1870
2130
|
}`
|
|
1871
2131
|
);
|
|
1872
2132
|
}
|
|
2133
|
+
const needsPassthroughHelper = [...RUNTIME_SDK_PASSTHROUGH_EXPORTS].some(
|
|
2134
|
+
(name) => usage.exports.has(name)
|
|
2135
|
+
);
|
|
2136
|
+
if (needsPassthroughHelper) {
|
|
2137
|
+
lines.push(
|
|
2138
|
+
`function passthroughFrozen(options) {
|
|
2139
|
+
if (options && typeof options === "object" && !Array.isArray(options)) {
|
|
2140
|
+
return Object.freeze({ ...options });
|
|
2141
|
+
}
|
|
2142
|
+
return Object.freeze({});
|
|
2143
|
+
}`
|
|
2144
|
+
);
|
|
2145
|
+
for (const passthrough of RUNTIME_SDK_PASSTHROUGH_EXPORTS) {
|
|
2146
|
+
if (!usage.exports.has(passthrough)) {
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
lines.push(
|
|
2150
|
+
`export function ${passthrough}(options) {
|
|
2151
|
+
return passthroughFrozen(options);
|
|
2152
|
+
}`
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
1873
2156
|
return lines.join("\n\n");
|
|
1874
2157
|
}
|
|
1875
2158
|
function sdkAliasPlugin() {
|
|
@@ -2208,64 +2491,513 @@ async function generateManifest(input) {
|
|
|
2208
2491
|
);
|
|
2209
2492
|
return m;
|
|
2210
2493
|
}
|
|
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
|
-
});
|
|
2494
|
+
|
|
2495
|
+
// src/build/stable-json.ts
|
|
2496
|
+
function stableStringify(value, indent) {
|
|
2497
|
+
return JSON.stringify(canonicalize(value), null, indent);
|
|
2226
2498
|
}
|
|
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
|
-
);
|
|
2499
|
+
function canonicalize(value) {
|
|
2500
|
+
if (value === null) return null;
|
|
2501
|
+
if (Array.isArray(value)) {
|
|
2502
|
+
return value.map(canonicalize);
|
|
2235
2503
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2504
|
+
if (typeof value === "object") {
|
|
2505
|
+
const obj = value;
|
|
2506
|
+
const sorted = {};
|
|
2507
|
+
for (const key of Object.keys(obj).sort()) {
|
|
2508
|
+
sorted[key] = canonicalize(obj[key]);
|
|
2509
|
+
}
|
|
2510
|
+
return sorted;
|
|
2242
2511
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2512
|
+
return value;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/build/catalog.ts
|
|
2516
|
+
var CATALOG_CONTRACT_VERSION = "io.catalog.v1";
|
|
2517
|
+
var CATALOG_SNAPSHOT_VERSION = "io_runtime.snapshot.v2";
|
|
2518
|
+
var WEBHOOK_RESOURCE_KIND = "webhook";
|
|
2519
|
+
var PLATFORM_TOOL_KINDS = /* @__PURE__ */ new Set(["email", "calendar", "contacts"]);
|
|
2520
|
+
var STREAM_PUBLISH_KINDS = /* @__PURE__ */ new Set([
|
|
2521
|
+
"kafka",
|
|
2522
|
+
"rabbitmq",
|
|
2523
|
+
"redis",
|
|
2524
|
+
"pubsub",
|
|
2525
|
+
"sqs",
|
|
2526
|
+
"websocket"
|
|
2527
|
+
]);
|
|
2528
|
+
function sha256Hex(value) {
|
|
2529
|
+
return `sha256:${createHash("sha256").update(stableStringify(value)).digest("hex")}`;
|
|
2530
|
+
}
|
|
2531
|
+
function isRecord3(value) {
|
|
2532
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2533
|
+
}
|
|
2534
|
+
function getString(record, key) {
|
|
2535
|
+
const value = record[key];
|
|
2536
|
+
return typeof value === "string" ? value : void 0;
|
|
2537
|
+
}
|
|
2538
|
+
function getRecord(record, key) {
|
|
2539
|
+
const value = record[key];
|
|
2540
|
+
return isRecord3(value) ? value : void 0;
|
|
2541
|
+
}
|
|
2542
|
+
function getArray(record, key) {
|
|
2543
|
+
const value = record[key];
|
|
2544
|
+
return Array.isArray(value) ? value : [];
|
|
2545
|
+
}
|
|
2546
|
+
function getNumber(record, key) {
|
|
2547
|
+
const value = record[key];
|
|
2548
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2549
|
+
}
|
|
2550
|
+
function countPlaceholders2(format) {
|
|
2551
|
+
let count = 0;
|
|
2552
|
+
let idx = 0;
|
|
2553
|
+
while ((idx = format.indexOf("{}", idx)) !== -1) {
|
|
2554
|
+
count++;
|
|
2555
|
+
idx += 2;
|
|
2556
|
+
}
|
|
2557
|
+
return count;
|
|
2558
|
+
}
|
|
2559
|
+
function deriveAuditRequirement(effect) {
|
|
2560
|
+
return effect === "write" ? "pre_call" : "finish_only";
|
|
2561
|
+
}
|
|
2562
|
+
function deriveEffect(toolMode) {
|
|
2563
|
+
return toolMode === "read" ? "read" : "write";
|
|
2564
|
+
}
|
|
2565
|
+
function normalizeCredentialHeaders(declared, resourceId, context, warnings) {
|
|
2566
|
+
if (!Array.isArray(declared)) return [];
|
|
2567
|
+
const out = [];
|
|
2568
|
+
for (const entry of declared) {
|
|
2569
|
+
if (!isRecord3(entry)) {
|
|
2570
|
+
warnings.push(`${context}: skipped non-object credential header entry`);
|
|
2571
|
+
continue;
|
|
2246
2572
|
}
|
|
2247
|
-
const
|
|
2248
|
-
if (
|
|
2249
|
-
|
|
2573
|
+
const name = getString(entry, "name");
|
|
2574
|
+
if (!name) {
|
|
2575
|
+
warnings.push(`${context}: credential header missing name`);
|
|
2576
|
+
continue;
|
|
2250
2577
|
}
|
|
2251
|
-
const
|
|
2252
|
-
if (
|
|
2253
|
-
|
|
2578
|
+
const source = getRecord(entry, "source");
|
|
2579
|
+
if (!source) {
|
|
2580
|
+
warnings.push(`${context}: credential header "${name}" missing source`);
|
|
2581
|
+
continue;
|
|
2254
2582
|
}
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2583
|
+
const sourceKind = getString(source, "kind");
|
|
2584
|
+
let normalizedSource;
|
|
2585
|
+
if (sourceKind === "credential") {
|
|
2586
|
+
const field = getString(source, "field");
|
|
2587
|
+
if (!field) {
|
|
2588
|
+
warnings.push(
|
|
2589
|
+
`${context}: credential header "${name}" credential source missing field`
|
|
2590
|
+
);
|
|
2591
|
+
continue;
|
|
2592
|
+
}
|
|
2593
|
+
normalizedSource = { kind: "credential", resource_id: resourceId, field };
|
|
2594
|
+
} else if (sourceKind === "rawSecret" || sourceKind === "raw_secret") {
|
|
2595
|
+
const secretName = getString(source, "name");
|
|
2596
|
+
if (!secretName) {
|
|
2597
|
+
warnings.push(
|
|
2598
|
+
`${context}: credential header "${name}" raw_secret source missing name`
|
|
2599
|
+
);
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
normalizedSource = { kind: "raw_secret", name: secretName };
|
|
2603
|
+
} else {
|
|
2604
|
+
warnings.push(
|
|
2605
|
+
`${context}: credential header "${name}" has unsupported source kind "${sourceKind}"`
|
|
2606
|
+
);
|
|
2607
|
+
continue;
|
|
2258
2608
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2609
|
+
const format = getString(entry, "format") ?? "{}";
|
|
2610
|
+
if (countPlaceholders2(format) !== 1) {
|
|
2611
|
+
warnings.push(
|
|
2612
|
+
`${context}: credential header "${name}" format must contain exactly one {} placeholder`
|
|
2613
|
+
);
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
out.push({ name, source: normalizedSource, format });
|
|
2265
2617
|
}
|
|
2266
|
-
return
|
|
2618
|
+
return out;
|
|
2267
2619
|
}
|
|
2268
|
-
function
|
|
2620
|
+
function normalizeRequestMapping(declared) {
|
|
2621
|
+
if (!isRecord3(declared)) {
|
|
2622
|
+
return { path_params: [], query: [] };
|
|
2623
|
+
}
|
|
2624
|
+
const pathParams = collectMappingValues(declared["pathParams"]);
|
|
2625
|
+
const query = collectMappingValues(declared["query"]);
|
|
2626
|
+
const body = declared["body"];
|
|
2627
|
+
const bodyValue = typeof body === "string" ? body : body === null ? null : void 0;
|
|
2628
|
+
return {
|
|
2629
|
+
path_params: pathParams,
|
|
2630
|
+
query,
|
|
2631
|
+
...bodyValue !== void 0 ? { body: bodyValue } : {}
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
function collectMappingValues(value) {
|
|
2635
|
+
if (!Array.isArray(value)) return [];
|
|
2636
|
+
const out = [];
|
|
2637
|
+
for (const entry of value) {
|
|
2638
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2639
|
+
out.push(entry);
|
|
2640
|
+
} else if (isRecord3(entry)) {
|
|
2641
|
+
const from = getString(entry, "from");
|
|
2642
|
+
if (!from?.trim()) continue;
|
|
2643
|
+
const alias = getString(entry, "as");
|
|
2644
|
+
out.push(alias?.trim() ? { from, as: alias } : from);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
return out;
|
|
2648
|
+
}
|
|
2649
|
+
function buildExecutor(ctx) {
|
|
2650
|
+
if (PLATFORM_TOOL_KINDS.has(ctx.resourceKind)) {
|
|
2651
|
+
return {
|
|
2652
|
+
kind: "platform_tool",
|
|
2653
|
+
provider_kind: ctx.providerKind,
|
|
2654
|
+
backend_kind: ctx.backendKind,
|
|
2655
|
+
operation: ctx.toolName
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
if (ctx.resourceKind === WEBHOOK_RESOURCE_KIND && ctx.toolName === "respond") {
|
|
2659
|
+
return { kind: "webhook_response" };
|
|
2660
|
+
}
|
|
2661
|
+
if (STREAM_PUBLISH_KINDS.has(ctx.resourceKind)) {
|
|
2662
|
+
return {
|
|
2663
|
+
kind: "stream_publish",
|
|
2664
|
+
provider_kind: ctx.providerKind,
|
|
2665
|
+
backend_kind: ctx.backendKind,
|
|
2666
|
+
destination_ref: ctx.configHash
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
if (ctx.resourceKind === "http") {
|
|
2670
|
+
return buildHttpRequestExecutor(ctx);
|
|
2671
|
+
}
|
|
2672
|
+
if (ctx.resourceKind === "graphql") {
|
|
2673
|
+
return buildGraphQlRequestExecutor(ctx);
|
|
2674
|
+
}
|
|
2675
|
+
if (ctx.resourceKind === "httpApi") {
|
|
2676
|
+
return buildHttpOperationExecutor(ctx);
|
|
2677
|
+
}
|
|
2678
|
+
if (ctx.resourceKind === "graphqlApi") {
|
|
2679
|
+
return buildGraphQlOperationExecutor(ctx);
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
kind: "unsupported",
|
|
2683
|
+
reason: `unknown_resource_kind:${ctx.resourceKind}`
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
function buildHttpRequestExecutor(ctx) {
|
|
2687
|
+
const config = ctx.resourceConfig;
|
|
2688
|
+
const url = getString(config, "url") ?? "";
|
|
2689
|
+
const method = (getString(config, "method") ?? "GET").toUpperCase();
|
|
2690
|
+
const headers = getRecord(config, "headers") ?? {};
|
|
2691
|
+
const queryParams = getRecord(config, "queryParams") ?? {};
|
|
2692
|
+
const body = config["body"];
|
|
2693
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2694
|
+
config["credentialHeaders"],
|
|
2695
|
+
ctx.resourceId,
|
|
2696
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2697
|
+
ctx.warnings
|
|
2698
|
+
);
|
|
2699
|
+
return {
|
|
2700
|
+
kind: "http_request",
|
|
2701
|
+
url,
|
|
2702
|
+
method,
|
|
2703
|
+
headers_ref: ctx.registerBlob(headers),
|
|
2704
|
+
credential_headers: credentialHeaders,
|
|
2705
|
+
query_params_ref: ctx.registerBlob(queryParams),
|
|
2706
|
+
body_ref: ctx.registerBlob(body ?? null),
|
|
2707
|
+
timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
function buildGraphQlRequestExecutor(ctx) {
|
|
2711
|
+
const config = ctx.resourceConfig;
|
|
2712
|
+
const endpoint = getString(config, "endpoint") ?? "";
|
|
2713
|
+
const query = getString(config, "query") ?? "";
|
|
2714
|
+
const headers = getRecord(config, "headers") ?? {};
|
|
2715
|
+
const variables = getRecord(config, "variables") ?? {};
|
|
2716
|
+
const operationName = getString(config, "operationName") ?? null;
|
|
2717
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2718
|
+
config["credentialHeaders"],
|
|
2719
|
+
ctx.resourceId,
|
|
2720
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2721
|
+
ctx.warnings
|
|
2722
|
+
);
|
|
2723
|
+
return {
|
|
2724
|
+
kind: "graphql_request",
|
|
2725
|
+
endpoint,
|
|
2726
|
+
query_ref: ctx.registerBlob(query),
|
|
2727
|
+
operation_name: operationName,
|
|
2728
|
+
variables_ref: ctx.registerBlob(variables),
|
|
2729
|
+
headers_ref: ctx.registerBlob(headers),
|
|
2730
|
+
credential_headers: credentialHeaders,
|
|
2731
|
+
timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
function buildHttpOperationExecutor(ctx) {
|
|
2735
|
+
const config = ctx.resourceConfig;
|
|
2736
|
+
const ops = getRecord(config, "operations") ?? {};
|
|
2737
|
+
const opSpec = getRecord(ops, ctx.toolName);
|
|
2738
|
+
if (!opSpec) {
|
|
2739
|
+
ctx.warnings.push(
|
|
2740
|
+
`tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
|
|
2741
|
+
);
|
|
2742
|
+
return {
|
|
2743
|
+
kind: "unsupported",
|
|
2744
|
+
reason: `httpApi_missing_op:${ctx.toolName}`
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
const baseUrl = getString(config, "baseUrl") ?? "";
|
|
2748
|
+
const resourceHeaders = getRecord(config, "headers") ?? {};
|
|
2749
|
+
const opHeaders = getRecord(opSpec, "headers") ?? {};
|
|
2750
|
+
const mergedHeaders = { ...resourceHeaders, ...opHeaders };
|
|
2751
|
+
const opCredentialHeaders = opSpec["credentialHeaders"];
|
|
2752
|
+
const resourceCredentialHeaders = config["credentialHeaders"];
|
|
2753
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2754
|
+
opCredentialHeaders ?? resourceCredentialHeaders,
|
|
2755
|
+
ctx.resourceId,
|
|
2756
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2757
|
+
ctx.warnings
|
|
2758
|
+
);
|
|
2759
|
+
const method = (getString(opSpec, "method") ?? "GET").toUpperCase();
|
|
2760
|
+
const path = getString(opSpec, "path") ?? "";
|
|
2761
|
+
const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
|
|
2762
|
+
return {
|
|
2763
|
+
kind: "http_operation",
|
|
2764
|
+
base_url: baseUrl,
|
|
2765
|
+
method,
|
|
2766
|
+
path,
|
|
2767
|
+
headers_ref: ctx.registerBlob(mergedHeaders),
|
|
2768
|
+
credential_headers: credentialHeaders,
|
|
2769
|
+
timeout_seconds: timeoutSeconds,
|
|
2770
|
+
request_mapping: normalizeRequestMapping(opSpec["requestMapping"])
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
function buildGraphQlOperationExecutor(ctx) {
|
|
2774
|
+
const config = ctx.resourceConfig;
|
|
2775
|
+
const ops = getRecord(config, "operations") ?? {};
|
|
2776
|
+
const opSpec = getRecord(ops, ctx.toolName);
|
|
2777
|
+
if (!opSpec) {
|
|
2778
|
+
ctx.warnings.push(
|
|
2779
|
+
`tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
|
|
2780
|
+
);
|
|
2781
|
+
return {
|
|
2782
|
+
kind: "unsupported",
|
|
2783
|
+
reason: `graphqlApi_missing_op:${ctx.toolName}`
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
const endpoint = getString(config, "endpoint") ?? "";
|
|
2787
|
+
const resourceHeaders = getRecord(config, "headers") ?? {};
|
|
2788
|
+
const opHeaders = getRecord(opSpec, "headers") ?? {};
|
|
2789
|
+
const mergedHeaders = { ...resourceHeaders, ...opHeaders };
|
|
2790
|
+
const opCredentialHeaders = opSpec["credentialHeaders"];
|
|
2791
|
+
const resourceCredentialHeaders = config["credentialHeaders"];
|
|
2792
|
+
const credentialHeaders = normalizeCredentialHeaders(
|
|
2793
|
+
opCredentialHeaders ?? resourceCredentialHeaders,
|
|
2794
|
+
ctx.resourceId,
|
|
2795
|
+
`tool "${ctx.resourceId}.${ctx.toolName}"`,
|
|
2796
|
+
ctx.warnings
|
|
2797
|
+
);
|
|
2798
|
+
const query = getString(opSpec, "query") ?? "";
|
|
2799
|
+
const operationName = getString(opSpec, "operationName") ?? null;
|
|
2800
|
+
const variablesMapping = getString(opSpec, "variablesMapping") ?? "input";
|
|
2801
|
+
const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
|
|
2802
|
+
return {
|
|
2803
|
+
kind: "graphql_operation",
|
|
2804
|
+
endpoint,
|
|
2805
|
+
query_ref: ctx.registerBlob(query),
|
|
2806
|
+
operation_name: operationName,
|
|
2807
|
+
headers_ref: ctx.registerBlob(mergedHeaders),
|
|
2808
|
+
credential_headers: credentialHeaders,
|
|
2809
|
+
timeout_seconds: timeoutSeconds,
|
|
2810
|
+
variables_mapping: variablesMapping
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function generateCatalog(manifest) {
|
|
2814
|
+
const warnings = [];
|
|
2815
|
+
const resources = {};
|
|
2816
|
+
const signals2 = {};
|
|
2817
|
+
const tools = {};
|
|
2818
|
+
const schemas = {};
|
|
2819
|
+
const registerBlob = (value) => {
|
|
2820
|
+
const hash = sha256Hex(value ?? {});
|
|
2821
|
+
if (!(hash in schemas)) {
|
|
2822
|
+
schemas[hash] = value ?? {};
|
|
2823
|
+
}
|
|
2824
|
+
return hash;
|
|
2825
|
+
};
|
|
2826
|
+
const apps = getArray(manifest, "apps").filter(isRecord3);
|
|
2827
|
+
for (const app of apps) {
|
|
2828
|
+
const appName = getString(app, "name");
|
|
2829
|
+
if (!appName) continue;
|
|
2830
|
+
const appResources2 = getArray(app, "resources").filter(isRecord3);
|
|
2831
|
+
for (const resource of appResources2) {
|
|
2832
|
+
const resourceName = getString(resource, "name");
|
|
2833
|
+
const resourceKind = getString(resource, "kind") ?? "";
|
|
2834
|
+
const backend = getString(resource, "backend") ?? "";
|
|
2835
|
+
const providerKind = getString(resource, "provider_kind") ?? resourceKind;
|
|
2836
|
+
if (!resourceName) continue;
|
|
2837
|
+
const resourceId = `${appName}.${resourceName}`;
|
|
2838
|
+
const config = getRecord(resource, "config") ?? {};
|
|
2839
|
+
const configHash = registerBlob(config);
|
|
2840
|
+
const capability = getRecord(resource, "capability") ?? {};
|
|
2841
|
+
const bindingScope = getString(capability, "binding_scope") ?? "project";
|
|
2842
|
+
const credentialSource = getString(capability, "credential_source") ?? "none";
|
|
2843
|
+
const requiresBindingValue = capability.requires_binding;
|
|
2844
|
+
const credentialExposure = requiresBindingValue === true ? "stdlib" : "none";
|
|
2845
|
+
const resourceSignals = getArray(resource, "signals").filter(isRecord3);
|
|
2846
|
+
const resourceTools2 = getArray(resource, "tools").filter(isRecord3);
|
|
2847
|
+
const emittedSignalIds = [];
|
|
2848
|
+
for (const signal of resourceSignals) {
|
|
2849
|
+
const signalName = getString(signal, "name");
|
|
2850
|
+
if (!signalName) continue;
|
|
2851
|
+
const signalId = `${resourceId}.${signalName}`;
|
|
2852
|
+
const signalScope = getString(signal, "scope") === "principal" ? "principal" : "project";
|
|
2853
|
+
const signalSchema = getRecord(signal, "schema") ?? {};
|
|
2854
|
+
const schemaRef = registerBlob(signalSchema);
|
|
2855
|
+
signals2[signalId] = {
|
|
2856
|
+
id: signalId,
|
|
2857
|
+
resource_id: resourceId,
|
|
2858
|
+
name: signalName,
|
|
2859
|
+
event_name: getString(signal, "event_name") ?? "",
|
|
2860
|
+
scope: signalScope,
|
|
2861
|
+
mode: getString(signal, "mode") ?? "",
|
|
2862
|
+
schema_ref: schemaRef
|
|
2863
|
+
};
|
|
2864
|
+
emittedSignalIds.push(signalId);
|
|
2865
|
+
}
|
|
2866
|
+
const emittedToolIds = [];
|
|
2867
|
+
for (const tool of resourceTools2) {
|
|
2868
|
+
const toolName = getString(tool, "name");
|
|
2869
|
+
if (!toolName) continue;
|
|
2870
|
+
const toolId = `${resourceId}.${toolName}`;
|
|
2871
|
+
const effect = deriveEffect(tool.mode);
|
|
2872
|
+
const toolCapability = getRecord(tool, "capability") ?? {};
|
|
2873
|
+
const toolBackendKind = getString(toolCapability, "backend_kind") ?? backend;
|
|
2874
|
+
const toolProviderKind = getString(toolCapability, "provider_kind") ?? providerKind;
|
|
2875
|
+
const toolCredentialExposure = getString(toolCapability, "credential_exposure") ?? "none";
|
|
2876
|
+
const argsSchemaRef = registerBlob(
|
|
2877
|
+
getRecord(tool, "args_schema") ?? {}
|
|
2878
|
+
);
|
|
2879
|
+
const resultSchemaRef = registerBlob(
|
|
2880
|
+
getRecord(tool, "result_schema") ?? {}
|
|
2881
|
+
);
|
|
2882
|
+
const executor = buildExecutor({
|
|
2883
|
+
resourceId,
|
|
2884
|
+
resourceKind,
|
|
2885
|
+
providerKind: toolProviderKind,
|
|
2886
|
+
backendKind: toolBackendKind,
|
|
2887
|
+
toolName,
|
|
2888
|
+
resourceConfig: config,
|
|
2889
|
+
configHash,
|
|
2890
|
+
registerBlob,
|
|
2891
|
+
warnings
|
|
2892
|
+
});
|
|
2893
|
+
if (executor.kind === "unsupported") {
|
|
2894
|
+
warnings.push(
|
|
2895
|
+
`tool "${toolId}" executor deferred: ${executor.reason}`
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
tools[toolId] = {
|
|
2899
|
+
id: toolId,
|
|
2900
|
+
resource_id: resourceId,
|
|
2901
|
+
name: toolName,
|
|
2902
|
+
effect,
|
|
2903
|
+
audit_requirement: deriveAuditRequirement(effect),
|
|
2904
|
+
credential_exposure: toolCredentialExposure,
|
|
2905
|
+
args_schema_ref: argsSchemaRef,
|
|
2906
|
+
result_schema_ref: resultSchemaRef,
|
|
2907
|
+
executor
|
|
2908
|
+
};
|
|
2909
|
+
emittedToolIds.push(toolId);
|
|
2910
|
+
}
|
|
2911
|
+
resources[resourceId] = {
|
|
2912
|
+
id: resourceId,
|
|
2913
|
+
application_name: appName,
|
|
2914
|
+
resource_name: resourceName,
|
|
2915
|
+
resource_kind: resourceKind,
|
|
2916
|
+
provider_kind: providerKind,
|
|
2917
|
+
backend_kind: backend,
|
|
2918
|
+
binding_scope: bindingScope,
|
|
2919
|
+
credential_source: credentialSource,
|
|
2920
|
+
credential_exposure: credentialExposure,
|
|
2921
|
+
config_ref: configHash,
|
|
2922
|
+
signals: emittedSignalIds,
|
|
2923
|
+
tools: emittedToolIds
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
const catalogBody = {
|
|
2928
|
+
contract_version: CATALOG_CONTRACT_VERSION,
|
|
2929
|
+
snapshot_version: CATALOG_SNAPSHOT_VERSION,
|
|
2930
|
+
resources,
|
|
2931
|
+
signals: signals2,
|
|
2932
|
+
tools,
|
|
2933
|
+
schemas
|
|
2934
|
+
};
|
|
2935
|
+
const catalogHash = sha256Hex(catalogBody);
|
|
2936
|
+
return {
|
|
2937
|
+
catalog: {
|
|
2938
|
+
...catalogBody,
|
|
2939
|
+
catalog_hash: catalogHash
|
|
2940
|
+
},
|
|
2941
|
+
warnings
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
var INTERNAL_CONFIG = "__io_cli_compiled_config";
|
|
2945
|
+
var INTERNAL_RUN = "__io_cli_compiled_run";
|
|
2946
|
+
var INTERNAL_ON_START = "__io_cli_compiled_on_start";
|
|
2947
|
+
var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
|
|
2948
|
+
var INTERNAL_ON_END = "__io_cli_compiled_on_end";
|
|
2949
|
+
var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
|
|
2950
|
+
function conversationChannel(name) {
|
|
2951
|
+
return `session:${name}`;
|
|
2952
|
+
}
|
|
2953
|
+
function parseModule2(source) {
|
|
2954
|
+
return acorn.parse(source, {
|
|
2955
|
+
ecmaVersion: "latest",
|
|
2956
|
+
sourceType: "module"
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
function rootStatementFor(source, statementStart, kind, name) {
|
|
2960
|
+
let ast;
|
|
2961
|
+
try {
|
|
2962
|
+
ast = parseModule2(source);
|
|
2963
|
+
} catch (error) {
|
|
2964
|
+
throw new BuildError(
|
|
2965
|
+
`failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
const body = ast.body;
|
|
2969
|
+
const stmt = body.find(
|
|
2970
|
+
(candidate) => candidate.start === statementStart
|
|
2971
|
+
);
|
|
2972
|
+
if (stmt) {
|
|
2973
|
+
return stmt;
|
|
2974
|
+
}
|
|
2975
|
+
const fallback = body.find((candidate) => {
|
|
2976
|
+
if (candidate.type !== "ExpressionStatement") {
|
|
2977
|
+
return false;
|
|
2978
|
+
}
|
|
2979
|
+
const expression = candidate.expression;
|
|
2980
|
+
if (expression.type !== "CallExpression") {
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
const args = expression.arguments;
|
|
2984
|
+
if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
|
|
2985
|
+
return false;
|
|
2986
|
+
}
|
|
2987
|
+
const callee = expression.callee;
|
|
2988
|
+
if (callee.type !== "Identifier") {
|
|
2989
|
+
return false;
|
|
2990
|
+
}
|
|
2991
|
+
return callee.name === kind && args[0].value === name;
|
|
2992
|
+
});
|
|
2993
|
+
if (!fallback) {
|
|
2994
|
+
throw new BuildError(
|
|
2995
|
+
`failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
return fallback;
|
|
2999
|
+
}
|
|
3000
|
+
function rootSpecNode(stmt) {
|
|
2269
3001
|
if (stmt.type !== "ExpressionStatement") {
|
|
2270
3002
|
throw new BuildError("compiled target root must be an expression statement");
|
|
2271
3003
|
}
|
|
@@ -2567,13 +3299,90 @@ function pruneUnusedTopLevel(source, seeds) {
|
|
|
2567
3299
|
}
|
|
2568
3300
|
return applyEdits2(source, edits);
|
|
2569
3301
|
}
|
|
3302
|
+
function parseDurationMs(value) {
|
|
3303
|
+
if (value === void 0) return void 0;
|
|
3304
|
+
if (typeof value === "number") {
|
|
3305
|
+
if (Number.isInteger(value) && value > 0) return value;
|
|
3306
|
+
throw new BuildError("execution.timeout must be a positive integer number of milliseconds");
|
|
3307
|
+
}
|
|
3308
|
+
if (typeof value !== "string") {
|
|
3309
|
+
throw new BuildError("execution.timeout must be a duration string or milliseconds");
|
|
3310
|
+
}
|
|
3311
|
+
const match = value.trim().toLowerCase().match(/^(\d+)\s*(ms|s|m|h)$/);
|
|
3312
|
+
if (!match) {
|
|
3313
|
+
throw new BuildError("execution.timeout must use ms, s, m, or h units");
|
|
3314
|
+
}
|
|
3315
|
+
const amount = Number(match[1]);
|
|
3316
|
+
const unit = match[2];
|
|
3317
|
+
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
|
|
3318
|
+
return amount * multiplier;
|
|
3319
|
+
}
|
|
3320
|
+
function parseMemoryMb(value) {
|
|
3321
|
+
if (value === void 0) return void 0;
|
|
3322
|
+
if (typeof value === "number") {
|
|
3323
|
+
if (Number.isInteger(value) && value > 0) return value;
|
|
3324
|
+
throw new BuildError("execution.memory must be a positive integer number of megabytes");
|
|
3325
|
+
}
|
|
3326
|
+
if (typeof value !== "string") {
|
|
3327
|
+
throw new BuildError("execution.memory must be a size string or megabytes");
|
|
3328
|
+
}
|
|
3329
|
+
const match = value.trim().toLowerCase().match(/^(\d+)\s*(mb|m|gb|g)$/);
|
|
3330
|
+
if (!match) {
|
|
3331
|
+
throw new BuildError("execution.memory must use mb or gb units");
|
|
3332
|
+
}
|
|
3333
|
+
const amount = Number(match[1]);
|
|
3334
|
+
const unit = match[2];
|
|
3335
|
+
return unit === "gb" || unit === "g" ? amount * 1024 : amount;
|
|
3336
|
+
}
|
|
3337
|
+
function nonNegativeInteger(value, field) {
|
|
3338
|
+
if (value === void 0) return void 0;
|
|
3339
|
+
if (Number.isInteger(value) && value >= 0) return value;
|
|
3340
|
+
throw new BuildError(`${field} must be a non-negative integer`);
|
|
3341
|
+
}
|
|
3342
|
+
function normalizeBudgetMap(value, field) {
|
|
3343
|
+
if (value === void 0) return void 0;
|
|
3344
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3345
|
+
throw new BuildError(`${field} must be an object`);
|
|
3346
|
+
}
|
|
3347
|
+
const out = {};
|
|
3348
|
+
for (const [key, budget] of Object.entries(value)) {
|
|
3349
|
+
if (!key.trim()) {
|
|
3350
|
+
throw new BuildError(`${field} keys must be non-empty strings`);
|
|
3351
|
+
}
|
|
3352
|
+
out[key] = nonNegativeInteger(budget, `${field}.${key}`);
|
|
3353
|
+
}
|
|
3354
|
+
return out;
|
|
3355
|
+
}
|
|
3356
|
+
function normalizeRateBudget(rateBudget) {
|
|
3357
|
+
if (rateBudget === void 0) return void 0;
|
|
3358
|
+
const perApplication = normalizeBudgetMap(
|
|
3359
|
+
rateBudget.perApplication,
|
|
3360
|
+
"execution.rateBudget.perApplication"
|
|
3361
|
+
);
|
|
3362
|
+
const perCapability = normalizeBudgetMap(
|
|
3363
|
+
rateBudget.perCapability,
|
|
3364
|
+
"execution.rateBudget.perCapability"
|
|
3365
|
+
);
|
|
3366
|
+
const out = {
|
|
3367
|
+
...perApplication ? { per_application: perApplication } : {},
|
|
3368
|
+
...perCapability ? { per_capability: perCapability } : {}
|
|
3369
|
+
};
|
|
3370
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3371
|
+
}
|
|
2570
3372
|
function compactExecution(execution) {
|
|
2571
3373
|
if (!execution) {
|
|
2572
3374
|
return void 0;
|
|
2573
3375
|
}
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
);
|
|
3376
|
+
const timeoutMs = parseDurationMs(execution.timeout);
|
|
3377
|
+
const memoryMb = parseMemoryMb(execution.memory);
|
|
3378
|
+
const maxToolCalls = nonNegativeInteger(execution.maxToolCalls, "execution.maxToolCalls");
|
|
3379
|
+
const rateBudget = normalizeRateBudget(execution.rateBudget);
|
|
3380
|
+
const compact = {
|
|
3381
|
+
...timeoutMs !== void 0 ? { timeout_ms: timeoutMs } : {},
|
|
3382
|
+
...memoryMb !== void 0 ? { memory_mb: memoryMb } : {},
|
|
3383
|
+
...maxToolCalls !== void 0 ? { max_tool_calls: maxToolCalls } : {},
|
|
3384
|
+
...rateBudget !== void 0 ? { rate_budget: rateBudget } : {}
|
|
3385
|
+
};
|
|
2577
3386
|
return Object.keys(compact).length > 0 ? compact : void 0;
|
|
2578
3387
|
}
|
|
2579
3388
|
function buildBehaviorReplacement(source, spec, behavior) {
|
|
@@ -2583,8 +3392,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2583
3392
|
}
|
|
2584
3393
|
const execution = compactExecution(behavior.execution);
|
|
2585
3394
|
const runSource = functionSourceFromProperty(source, run, "run");
|
|
2586
|
-
const trackingId = findProperty(spec, "trackingId");
|
|
2587
|
-
const trackingIdSource = trackingId ? functionSourceFromProperty(source, trackingId, "trackingId") : void 0;
|
|
2588
3395
|
const applications = findProperty(spec, "applications");
|
|
2589
3396
|
const applicationsSource = applications ? expressionSourceFromProperty(source, applications, "applications") : void 0;
|
|
2590
3397
|
const configEntries = [
|
|
@@ -2595,9 +3402,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2595
3402
|
if (execution) {
|
|
2596
3403
|
configEntries.push(`execution: ${JSON.stringify(execution)}`);
|
|
2597
3404
|
}
|
|
2598
|
-
if (behavior.messageHistory !== void 0) {
|
|
2599
|
-
configEntries.push(`messageHistory: ${JSON.stringify(behavior.messageHistory)}`);
|
|
2600
|
-
}
|
|
2601
3405
|
if (applicationsSource) {
|
|
2602
3406
|
configEntries.push(`applications: ${INTERNAL_APPLICATIONS}`);
|
|
2603
3407
|
}
|
|
@@ -2606,17 +3410,14 @@ function buildBehaviorReplacement(source, spec, behavior) {
|
|
|
2606
3410
|
replacement: [
|
|
2607
3411
|
...applicationsSource ? [`const ${INTERNAL_APPLICATIONS} = ${applicationsSource};`] : [],
|
|
2608
3412
|
`const ${INTERNAL_CONFIG} = ${configSource};`,
|
|
2609
|
-
`const ${INTERNAL_TRACKING_ID} = ${trackingIdSource ?? "undefined"};`,
|
|
2610
3413
|
`const ${INTERNAL_RUN} = ${runSource};`,
|
|
2611
|
-
`export { ${INTERNAL_CONFIG} as config, ${
|
|
3414
|
+
`export { ${INTERNAL_CONFIG} as config, ${INTERNAL_RUN} as run };`
|
|
2612
3415
|
].join("\n"),
|
|
2613
3416
|
refs: /* @__PURE__ */ new Set([
|
|
2614
3417
|
INTERNAL_CONFIG,
|
|
2615
|
-
INTERNAL_TRACKING_ID,
|
|
2616
3418
|
INTERNAL_RUN,
|
|
2617
3419
|
...applicationsSource ? [INTERNAL_APPLICATIONS] : [],
|
|
2618
3420
|
...refsFromExpressionSource(runSource),
|
|
2619
|
-
...trackingIdSource ? refsFromExpressionSource(trackingIdSource) : [],
|
|
2620
3421
|
...applicationsSource ? refsFromExpressionSource(applicationsSource) : []
|
|
2621
3422
|
])
|
|
2622
3423
|
};
|
|
@@ -2756,7 +3557,15 @@ async function buildPipeline(config, cwd = process.cwd(), minify = false, buildD
|
|
|
2756
3557
|
conversationBundles
|
|
2757
3558
|
});
|
|
2758
3559
|
validateManifest(manifest);
|
|
2759
|
-
|
|
3560
|
+
const { catalog: catalog2, warnings } = generateCatalog(manifest);
|
|
3561
|
+
if (warnings.length > 0) {
|
|
3562
|
+
throw new BuildError(
|
|
3563
|
+
`Catalog generation produced ${warnings.length} unrecoverable issue${warnings.length === 1 ? "" : "s"} \u2014 refusing to ship a partial catalog:
|
|
3564
|
+
${warnings.map((w) => ` - ${w}`).join("\n")}`
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
await writeFile(join(outDir, "catalog.json"), stableStringify(catalog2, 2), "utf-8");
|
|
3568
|
+
return { manifest, catalog: catalog2, outDir };
|
|
2760
3569
|
}
|
|
2761
3570
|
async function createStagingDir() {
|
|
2762
3571
|
return mkdtemp(join(tmpdir(), "io-cli-"));
|
|
@@ -2820,8 +3629,8 @@ async function buildCommand(options) {
|
|
|
2820
3629
|
async function zipBuildOutput(buildDir, outPath) {
|
|
2821
3630
|
const output = createWriteStream(outPath);
|
|
2822
3631
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
2823
|
-
const done = new Promise((
|
|
2824
|
-
output.on("close",
|
|
3632
|
+
const done = new Promise((resolve5, reject) => {
|
|
3633
|
+
output.on("close", resolve5);
|
|
2825
3634
|
archive.on("error", reject);
|
|
2826
3635
|
});
|
|
2827
3636
|
archive.pipe(output);
|
|
@@ -2850,13 +3659,13 @@ async function addDirectory(archive, baseDir, prefix, skipRelativePath) {
|
|
|
2850
3659
|
}
|
|
2851
3660
|
|
|
2852
3661
|
// src/deploy/diff.ts
|
|
2853
|
-
function
|
|
3662
|
+
function stableStringify2(value) {
|
|
2854
3663
|
if (Array.isArray(value)) {
|
|
2855
|
-
return `[${value.map(
|
|
3664
|
+
return `[${value.map(stableStringify2).join(",")}]`;
|
|
2856
3665
|
}
|
|
2857
3666
|
if (value && typeof value === "object") {
|
|
2858
3667
|
const record = value;
|
|
2859
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${
|
|
3668
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(record[key])}`).join(",")}}`;
|
|
2860
3669
|
}
|
|
2861
3670
|
return JSON.stringify(value);
|
|
2862
3671
|
}
|
|
@@ -2882,7 +3691,7 @@ function diffEntries(local, remote) {
|
|
|
2882
3691
|
for (const { name, value } of local) {
|
|
2883
3692
|
if (!remoteMap.has(name)) {
|
|
2884
3693
|
entries.push({ name, kind: "added" });
|
|
2885
|
-
} else if (
|
|
3694
|
+
} else if (stableStringify2(value) !== stableStringify2(remoteMap.get(name))) {
|
|
2886
3695
|
entries.push({ name, kind: "changed" });
|
|
2887
3696
|
}
|
|
2888
3697
|
}
|
|
@@ -2995,6 +3804,77 @@ function diffManifests(local, remote) {
|
|
|
2995
3804
|
hasChanges
|
|
2996
3805
|
};
|
|
2997
3806
|
}
|
|
3807
|
+
|
|
3808
|
+
// src/deploy/catalog-diff.ts
|
|
3809
|
+
function stableStringify3(value) {
|
|
3810
|
+
if (Array.isArray(value)) {
|
|
3811
|
+
return `[${value.map(stableStringify3).join(",")}]`;
|
|
3812
|
+
}
|
|
3813
|
+
if (value && typeof value === "object") {
|
|
3814
|
+
const record = value;
|
|
3815
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
|
|
3816
|
+
}
|
|
3817
|
+
return JSON.stringify(value);
|
|
3818
|
+
}
|
|
3819
|
+
function diffSection(local, remote) {
|
|
3820
|
+
const localMap = local ?? {};
|
|
3821
|
+
const remoteMap = remote ?? {};
|
|
3822
|
+
const entries = [];
|
|
3823
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3824
|
+
for (const id of Object.keys(localMap).sort()) {
|
|
3825
|
+
seen.add(id);
|
|
3826
|
+
if (!(id in remoteMap)) {
|
|
3827
|
+
entries.push({ id, kind: "added" });
|
|
3828
|
+
} else if (stableStringify3(localMap[id]) !== stableStringify3(remoteMap[id])) {
|
|
3829
|
+
entries.push({ id, kind: "changed" });
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
for (const id of Object.keys(remoteMap).sort()) {
|
|
3833
|
+
if (!seen.has(id) && !(id in localMap)) {
|
|
3834
|
+
entries.push({ id, kind: "removed" });
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
return entries;
|
|
3838
|
+
}
|
|
3839
|
+
function schemaDiffCounts(local, remote) {
|
|
3840
|
+
const localKeys = new Set(Object.keys(local ?? {}));
|
|
3841
|
+
const remoteKeys = new Set(Object.keys(remote ?? {}));
|
|
3842
|
+
let added = 0;
|
|
3843
|
+
let removed = 0;
|
|
3844
|
+
for (const k of localKeys) {
|
|
3845
|
+
if (!remoteKeys.has(k)) added++;
|
|
3846
|
+
}
|
|
3847
|
+
for (const k of remoteKeys) {
|
|
3848
|
+
if (!localKeys.has(k)) removed++;
|
|
3849
|
+
}
|
|
3850
|
+
return { added, removed };
|
|
3851
|
+
}
|
|
3852
|
+
function diffCatalogs(local, remote) {
|
|
3853
|
+
const localResources = local?.resources ?? {};
|
|
3854
|
+
const remoteResources = remote?.resources ?? {};
|
|
3855
|
+
const localSignals = local?.signals ?? {};
|
|
3856
|
+
const remoteSignals = remote?.signals ?? {};
|
|
3857
|
+
const localTools = local?.tools ?? {};
|
|
3858
|
+
const remoteTools = remote?.tools ?? {};
|
|
3859
|
+
const localSchemas = local?.schemas ?? {};
|
|
3860
|
+
const remoteSchemas = remote?.schemas ?? {};
|
|
3861
|
+
const resources = diffSection(localResources, remoteResources);
|
|
3862
|
+
const signals2 = diffSection(localSignals, remoteSignals);
|
|
3863
|
+
const tools = diffSection(localTools, remoteTools);
|
|
3864
|
+
const schemas = schemaDiffCounts(localSchemas, remoteSchemas);
|
|
3865
|
+
const localHash = local?.catalog_hash ?? null;
|
|
3866
|
+
const remoteHash = remote?.catalog_hash ?? null;
|
|
3867
|
+
const hashChanged = (localHash ?? remoteHash) !== null && localHash !== remoteHash;
|
|
3868
|
+
const hasChanges = resources.length > 0 || signals2.length > 0 || tools.length > 0 || schemas.added > 0 || schemas.removed > 0 || hashChanged;
|
|
3869
|
+
return {
|
|
3870
|
+
hashChanged,
|
|
3871
|
+
resources,
|
|
3872
|
+
signals: signals2,
|
|
3873
|
+
tools,
|
|
3874
|
+
schemas,
|
|
3875
|
+
hasChanges
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
2998
3878
|
var SYMBOLS = {
|
|
2999
3879
|
added: chalk12.green("+ "),
|
|
3000
3880
|
changed: chalk12.yellow("~ "),
|
|
@@ -3014,7 +3894,12 @@ var SECTIONS = [
|
|
|
3014
3894
|
{ key: "targets", label: "Targets" },
|
|
3015
3895
|
{ key: "subscriptions", label: "Subscriptions" }
|
|
3016
3896
|
];
|
|
3017
|
-
|
|
3897
|
+
var CATALOG_SECTIONS = [
|
|
3898
|
+
{ key: "resources", label: "Catalog resources" },
|
|
3899
|
+
{ key: "signals", label: "Catalog signals" },
|
|
3900
|
+
{ key: "tools", label: "Catalog tools" }
|
|
3901
|
+
];
|
|
3902
|
+
function formatPlan(diff, catalogDiff) {
|
|
3018
3903
|
const lines = [];
|
|
3019
3904
|
for (const { key, label } of SECTIONS) {
|
|
3020
3905
|
const entries = diff[key];
|
|
@@ -3029,15 +3914,44 @@ function formatPlan(diff) {
|
|
|
3029
3914
|
}
|
|
3030
3915
|
}
|
|
3031
3916
|
}
|
|
3917
|
+
if (catalogDiff) {
|
|
3918
|
+
lines.push("");
|
|
3919
|
+
lines.push(
|
|
3920
|
+
chalk12.bold(
|
|
3921
|
+
catalogDiff.hashChanged ? " Catalog (hash changed):" : " Catalog:"
|
|
3922
|
+
)
|
|
3923
|
+
);
|
|
3924
|
+
for (const { key, label } of CATALOG_SECTIONS) {
|
|
3925
|
+
const entries = catalogDiff[key];
|
|
3926
|
+
lines.push(` ${label}:`);
|
|
3927
|
+
if (entries.length === 0) {
|
|
3928
|
+
lines.push(chalk12.gray(" (no changes)"));
|
|
3929
|
+
} else {
|
|
3930
|
+
for (const e of entries) {
|
|
3931
|
+
lines.push(
|
|
3932
|
+
` ${SYMBOLS[e.kind]}${e.id} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
|
|
3933
|
+
);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
const schemaLine = catalogDiff.schemas;
|
|
3938
|
+
if (schemaLine.added > 0 || schemaLine.removed > 0) {
|
|
3939
|
+
lines.push(
|
|
3940
|
+
` ${chalk12.gray("Schemas:")} ${chalk12.green(`+${schemaLine.added}`)} ${chalk12.red(`-${schemaLine.removed}`)}`
|
|
3941
|
+
);
|
|
3942
|
+
} else {
|
|
3943
|
+
lines.push(` ${chalk12.gray("Schemas: (no changes)")}`);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3032
3946
|
return lines.join("\n");
|
|
3033
3947
|
}
|
|
3034
3948
|
async function confirmPlan(skip) {
|
|
3035
3949
|
if (skip) return true;
|
|
3036
3950
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3037
|
-
return new Promise((
|
|
3951
|
+
return new Promise((resolve5) => {
|
|
3038
3952
|
rl.question("\nApply these changes? (y/n) ", (answer) => {
|
|
3039
3953
|
rl.close();
|
|
3040
|
-
|
|
3954
|
+
resolve5(answer.trim().toLowerCase() === "y");
|
|
3041
3955
|
});
|
|
3042
3956
|
});
|
|
3043
3957
|
}
|
|
@@ -3111,40 +4025,6 @@ function socketAuthParams(creds) {
|
|
|
3111
4025
|
return { authorization: `Bearer ${creds.token}` };
|
|
3112
4026
|
}
|
|
3113
4027
|
|
|
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
4028
|
// src/api/client.ts
|
|
3149
4029
|
var REFRESH_MUTATION = `
|
|
3150
4030
|
mutation Refresh($input: RefreshAuthInput!) {
|
|
@@ -3245,8 +4125,32 @@ function graphqlErrorMessage(errors) {
|
|
|
3245
4125
|
}
|
|
3246
4126
|
return null;
|
|
3247
4127
|
}
|
|
3248
|
-
function
|
|
3249
|
-
return
|
|
4128
|
+
function isPlainObject(value) {
|
|
4129
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4130
|
+
}
|
|
4131
|
+
function isCatalogShape(value) {
|
|
4132
|
+
if (!isPlainObject(value)) return false;
|
|
4133
|
+
return value.contract_version === "io.catalog.v1" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isPlainObject(value.resources) && isPlainObject(value.signals) && isPlainObject(value.tools) && isPlainObject(value.schemas);
|
|
4134
|
+
}
|
|
4135
|
+
function isManifestShape(value) {
|
|
4136
|
+
if (!isPlainObject(value)) return false;
|
|
4137
|
+
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);
|
|
4138
|
+
}
|
|
4139
|
+
function parseRemoteCatalog(catalogJson) {
|
|
4140
|
+
let parsed;
|
|
4141
|
+
try {
|
|
4142
|
+
parsed = JSON.parse(catalogJson);
|
|
4143
|
+
} catch (err) {
|
|
4144
|
+
throw new ConfigError(
|
|
4145
|
+
`Remote deployment catalog was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
if (!isCatalogShape(parsed)) {
|
|
4149
|
+
throw new ConfigError(
|
|
4150
|
+
"Remote deployment catalog does not match the io.catalog.v1 catalog shape."
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
return parsed;
|
|
3250
4154
|
}
|
|
3251
4155
|
function parseRemoteManifest(snapshotJson) {
|
|
3252
4156
|
let parsed;
|
|
@@ -3257,11 +4161,22 @@ function parseRemoteManifest(snapshotJson) {
|
|
|
3257
4161
|
`Remote deployment snapshot was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
3258
4162
|
);
|
|
3259
4163
|
}
|
|
3260
|
-
if (!
|
|
3261
|
-
throw new ConfigError(
|
|
4164
|
+
if (!isManifestShape(parsed)) {
|
|
4165
|
+
throw new ConfigError(
|
|
4166
|
+
"Remote deployment snapshot does not match the io_runtime.snapshot.v2 manifest shape."
|
|
4167
|
+
);
|
|
3262
4168
|
}
|
|
3263
4169
|
return parsed;
|
|
3264
4170
|
}
|
|
4171
|
+
var EMPTY_MANIFEST = {
|
|
4172
|
+
contract_version: "io.invocation.v2",
|
|
4173
|
+
snapshot_version: "io_runtime.snapshot.v2",
|
|
4174
|
+
secret_bundles: [],
|
|
4175
|
+
apps: [],
|
|
4176
|
+
endpoints: [],
|
|
4177
|
+
targets: [],
|
|
4178
|
+
subscriptions: []
|
|
4179
|
+
};
|
|
3265
4180
|
async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
|
|
3266
4181
|
const response = await fetch(`${trimBaseUrl(apiUrl)}/graphql`, {
|
|
3267
4182
|
method: "POST",
|
|
@@ -3280,6 +4195,15 @@ async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
|
|
|
3280
4195
|
}
|
|
3281
4196
|
return payload.data;
|
|
3282
4197
|
}
|
|
4198
|
+
function serializePrincipalInput(input) {
|
|
4199
|
+
return {
|
|
4200
|
+
...input.externalId !== void 0 ? { externalId: input.externalId } : {},
|
|
4201
|
+
...input.state !== void 0 ? {
|
|
4202
|
+
state: input.state.toUpperCase()
|
|
4203
|
+
} : {},
|
|
4204
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
3283
4207
|
async function refreshAndSave(apiUrl, refreshToken) {
|
|
3284
4208
|
let data;
|
|
3285
4209
|
try {
|
|
@@ -3478,6 +4402,23 @@ function createClient(config) {
|
|
|
3478
4402
|
);
|
|
3479
4403
|
return data.createProject;
|
|
3480
4404
|
}
|
|
4405
|
+
async function deleteProject(organizationId, projectIdValue) {
|
|
4406
|
+
const data = await graphql(
|
|
4407
|
+
`
|
|
4408
|
+
mutation DeleteProject($organizationId: ID!, $id: ID!) {
|
|
4409
|
+
deleteProject(organizationId: $organizationId, id: $id) {
|
|
4410
|
+
id
|
|
4411
|
+
name
|
|
4412
|
+
slug
|
|
4413
|
+
description
|
|
4414
|
+
organizationId
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
`,
|
|
4418
|
+
{ organizationId, id: projectIdValue }
|
|
4419
|
+
);
|
|
4420
|
+
return data.deleteProject;
|
|
4421
|
+
}
|
|
3481
4422
|
async function principals2(projectIdValue) {
|
|
3482
4423
|
const data = await graphql(
|
|
3483
4424
|
`
|
|
@@ -3512,7 +4453,10 @@ function createClient(config) {
|
|
|
3512
4453
|
}
|
|
3513
4454
|
}
|
|
3514
4455
|
`,
|
|
3515
|
-
{
|
|
4456
|
+
{
|
|
4457
|
+
projectId: projectIdValue,
|
|
4458
|
+
input: serializePrincipalInput(input)
|
|
4459
|
+
}
|
|
3516
4460
|
);
|
|
3517
4461
|
return data.createPrincipal;
|
|
3518
4462
|
}
|
|
@@ -3531,7 +4475,11 @@ function createClient(config) {
|
|
|
3531
4475
|
}
|
|
3532
4476
|
}
|
|
3533
4477
|
`,
|
|
3534
|
-
{
|
|
4478
|
+
{
|
|
4479
|
+
projectId: projectIdValue,
|
|
4480
|
+
id,
|
|
4481
|
+
input: serializePrincipalInput(input)
|
|
4482
|
+
}
|
|
3535
4483
|
);
|
|
3536
4484
|
return data.updatePrincipal ?? null;
|
|
3537
4485
|
}
|
|
@@ -3607,28 +4555,34 @@ function createClient(config) {
|
|
|
3607
4555
|
return data.deleteSecret === true;
|
|
3608
4556
|
}
|
|
3609
4557
|
async function fetchRemoteManifest(projectIdValue) {
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
data = await graphql(
|
|
3613
|
-
`
|
|
4558
|
+
const data = await graphql(
|
|
4559
|
+
`
|
|
3614
4560
|
query ActiveDeploymentSnapshot($projectId: ID!) {
|
|
3615
4561
|
activeDeployment(projectId: $projectId) {
|
|
3616
4562
|
snapshotJson
|
|
3617
4563
|
}
|
|
3618
4564
|
}
|
|
3619
4565
|
`,
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
} catch (err) {
|
|
3623
|
-
if (isMissingSnapshotJsonFieldError(err)) {
|
|
3624
|
-
return {};
|
|
3625
|
-
}
|
|
3626
|
-
throw err;
|
|
3627
|
-
}
|
|
4566
|
+
{ projectId: projectIdValue }
|
|
4567
|
+
);
|
|
3628
4568
|
const snapshotJson = data.activeDeployment?.snapshotJson;
|
|
3629
|
-
return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) :
|
|
4569
|
+
return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : EMPTY_MANIFEST;
|
|
3630
4570
|
}
|
|
3631
|
-
async function
|
|
4571
|
+
async function fetchRemoteCatalog(projectIdValue) {
|
|
4572
|
+
const data = await graphql(
|
|
4573
|
+
`
|
|
4574
|
+
query ActiveDeploymentCatalog($projectId: ID!) {
|
|
4575
|
+
activeDeployment(projectId: $projectId) {
|
|
4576
|
+
catalogJson
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
`,
|
|
4580
|
+
{ projectId: projectIdValue }
|
|
4581
|
+
);
|
|
4582
|
+
const catalogJson = data.activeDeployment?.catalogJson;
|
|
4583
|
+
return typeof catalogJson === "string" && catalogJson.trim() !== "" ? parseRemoteCatalog(catalogJson) : null;
|
|
4584
|
+
}
|
|
4585
|
+
async function deployBundle(bundlePath, manifest, catalog2) {
|
|
3632
4586
|
const MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
|
|
3633
4587
|
const fileStat = await stat(bundlePath);
|
|
3634
4588
|
if (fileStat.size > MAX_BUNDLE_BYTES) {
|
|
@@ -3666,7 +4620,8 @@ function createClient(config) {
|
|
|
3666
4620
|
projectId: projectIdValue,
|
|
3667
4621
|
bundleHash: upload.bundleHash,
|
|
3668
4622
|
revision: nextRevision,
|
|
3669
|
-
snapshotJson:
|
|
4623
|
+
snapshotJson: stableStringify(manifest),
|
|
4624
|
+
catalogJson: stableStringify(catalog2)
|
|
3670
4625
|
}
|
|
3671
4626
|
}
|
|
3672
4627
|
);
|
|
@@ -3677,7 +4632,7 @@ function createClient(config) {
|
|
|
3677
4632
|
if (!input.principalId || input.principalId.trim() === "") {
|
|
3678
4633
|
throw new ConfigError("principalId is required to emit a signal");
|
|
3679
4634
|
}
|
|
3680
|
-
const eventName =
|
|
4635
|
+
const eventName = normalizeRuntimeSignalName(input.signal);
|
|
3681
4636
|
if (!eventName) {
|
|
3682
4637
|
throw new ConfigError(
|
|
3683
4638
|
"signal name must be a bare event name like smoke.start or use the signal: namespace"
|
|
@@ -3708,7 +4663,6 @@ function createClient(config) {
|
|
|
3708
4663
|
eventName,
|
|
3709
4664
|
payloadJson: JSON.stringify(input.payload ?? {}),
|
|
3710
4665
|
metadataJson: JSON.stringify(input.metadata ?? {}),
|
|
3711
|
-
correlationId: input.correlationId,
|
|
3712
4666
|
causationId: input.causationId,
|
|
3713
4667
|
idempotencyKey: input.idempotencyKey,
|
|
3714
4668
|
sourceName: input.sourceName
|
|
@@ -3730,6 +4684,37 @@ function createClient(config) {
|
|
|
3730
4684
|
);
|
|
3731
4685
|
return data.activeRevision;
|
|
3732
4686
|
}
|
|
4687
|
+
async function reconcileProject(projectIdValue) {
|
|
4688
|
+
const data = await graphql(
|
|
4689
|
+
`
|
|
4690
|
+
mutation ReconcileProject($projectId: ID!) {
|
|
4691
|
+
reconcileProject(projectId: $projectId)
|
|
4692
|
+
}
|
|
4693
|
+
`,
|
|
4694
|
+
{ projectId: projectIdValue }
|
|
4695
|
+
);
|
|
4696
|
+
return data.reconcileProject === true;
|
|
4697
|
+
}
|
|
4698
|
+
async function deploymentStatus(projectIdValue, revision) {
|
|
4699
|
+
const data = await graphql(
|
|
4700
|
+
`
|
|
4701
|
+
query DeploymentStatus($projectId: ID!, $revision: Int!) {
|
|
4702
|
+
deploymentStatus(projectId: $projectId, revision: $revision) {
|
|
4703
|
+
id
|
|
4704
|
+
projectId
|
|
4705
|
+
revision
|
|
4706
|
+
bundleHash
|
|
4707
|
+
status
|
|
4708
|
+
activatedAt
|
|
4709
|
+
insertedAt
|
|
4710
|
+
updatedAt
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
`,
|
|
4714
|
+
{ projectId: projectIdValue, revision }
|
|
4715
|
+
);
|
|
4716
|
+
return data.deploymentStatus;
|
|
4717
|
+
}
|
|
3733
4718
|
async function quotaStatuses() {
|
|
3734
4719
|
const data = await graphql(
|
|
3735
4720
|
`
|
|
@@ -3781,6 +4766,7 @@ function createClient(config) {
|
|
|
3781
4766
|
organizations,
|
|
3782
4767
|
projects: projects2,
|
|
3783
4768
|
createProject,
|
|
4769
|
+
deleteProject,
|
|
3784
4770
|
principals: principals2,
|
|
3785
4771
|
createPrincipal,
|
|
3786
4772
|
updatePrincipal,
|
|
@@ -3789,14 +4775,245 @@ function createClient(config) {
|
|
|
3789
4775
|
putSecret,
|
|
3790
4776
|
deleteSecret,
|
|
3791
4777
|
fetchRemoteManifest,
|
|
4778
|
+
fetchRemoteCatalog,
|
|
3792
4779
|
deployBundle,
|
|
4780
|
+
deploymentStatus,
|
|
4781
|
+
reconcileProject,
|
|
3793
4782
|
emitSignal,
|
|
3794
4783
|
quotaStatuses,
|
|
3795
4784
|
projectEvents
|
|
3796
4785
|
};
|
|
3797
4786
|
}
|
|
4787
|
+
Object.assign(globalThis, { WebSocket });
|
|
4788
|
+
var SessionTokenNotSupportedError = class extends Error {
|
|
4789
|
+
constructor(message) {
|
|
4790
|
+
super(
|
|
4791
|
+
message ?? "WebSocket subscriptions require API key authentication on the current io_runtime socket contract."
|
|
4792
|
+
);
|
|
4793
|
+
this.name = "SessionTokenNotSupportedError";
|
|
4794
|
+
}
|
|
4795
|
+
};
|
|
4796
|
+
function buildSocketUrl(apiUrl) {
|
|
4797
|
+
const url = new URL(apiUrl);
|
|
4798
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
4799
|
+
const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
|
|
4800
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
4801
|
+
url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
|
|
4802
|
+
url.search = "";
|
|
4803
|
+
url.hash = "";
|
|
4804
|
+
return url.toString();
|
|
4805
|
+
}
|
|
4806
|
+
function createSocketClient(options) {
|
|
4807
|
+
let phoenixSocket = null;
|
|
4808
|
+
let absintheSocket = null;
|
|
4809
|
+
const notifiers = /* @__PURE__ */ new Set();
|
|
4810
|
+
function ensureConnection() {
|
|
4811
|
+
if (!options.credentials.apiKey) {
|
|
4812
|
+
throw new SessionTokenNotSupportedError();
|
|
4813
|
+
}
|
|
4814
|
+
if (!phoenixSocket || !absintheSocket) {
|
|
4815
|
+
phoenixSocket = new Socket(buildSocketUrl(options.apiUrl), {
|
|
4816
|
+
params: socketAuthParams(options.credentials),
|
|
4817
|
+
timeout: options.timeoutMs ?? 1e4
|
|
4818
|
+
});
|
|
4819
|
+
absintheSocket = AbsintheSocket.create(phoenixSocket);
|
|
4820
|
+
phoenixSocket.connect();
|
|
4821
|
+
}
|
|
4822
|
+
return { absintheSocket };
|
|
4823
|
+
}
|
|
4824
|
+
function tearDownIfIdle() {
|
|
4825
|
+
if (notifiers.size === 0 && phoenixSocket) {
|
|
4826
|
+
phoenixSocket.disconnect();
|
|
4827
|
+
phoenixSocket = null;
|
|
4828
|
+
absintheSocket = null;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
return {
|
|
4832
|
+
subscribe(operation, variables, observer) {
|
|
4833
|
+
const conn = ensureConnection();
|
|
4834
|
+
const notifier = AbsintheSocket.send(conn.absintheSocket, {
|
|
4835
|
+
operation,
|
|
4836
|
+
variables
|
|
4837
|
+
});
|
|
4838
|
+
notifiers.add(notifier);
|
|
4839
|
+
AbsintheSocket.observe(conn.absintheSocket, notifier, {
|
|
4840
|
+
onAbort: (err) => {
|
|
4841
|
+
notifiers.delete(notifier);
|
|
4842
|
+
if (observer.onAbort) {
|
|
4843
|
+
observer.onAbort(err);
|
|
4844
|
+
} else {
|
|
4845
|
+
observer.onError?.(err);
|
|
4846
|
+
}
|
|
4847
|
+
tearDownIfIdle();
|
|
4848
|
+
},
|
|
4849
|
+
onCancel: () => {
|
|
4850
|
+
notifiers.delete(notifier);
|
|
4851
|
+
tearDownIfIdle();
|
|
4852
|
+
},
|
|
4853
|
+
onError: (err) => {
|
|
4854
|
+
observer.onError?.(err);
|
|
4855
|
+
},
|
|
4856
|
+
onResult: (result) => {
|
|
4857
|
+
observer.onResult?.(result);
|
|
4858
|
+
},
|
|
4859
|
+
onStart: () => {
|
|
4860
|
+
observer.onStart?.();
|
|
4861
|
+
}
|
|
4862
|
+
});
|
|
4863
|
+
return () => {
|
|
4864
|
+
if (!notifiers.has(notifier)) return;
|
|
4865
|
+
notifiers.delete(notifier);
|
|
4866
|
+
try {
|
|
4867
|
+
AbsintheSocket.cancel(conn.absintheSocket, notifier);
|
|
4868
|
+
} catch {
|
|
4869
|
+
}
|
|
4870
|
+
tearDownIfIdle();
|
|
4871
|
+
};
|
|
4872
|
+
},
|
|
4873
|
+
close() {
|
|
4874
|
+
notifiers.clear();
|
|
4875
|
+
if (phoenixSocket) {
|
|
4876
|
+
phoenixSocket.disconnect();
|
|
4877
|
+
}
|
|
4878
|
+
phoenixSocket = null;
|
|
4879
|
+
absintheSocket = null;
|
|
4880
|
+
}
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
// src/api/watcher.ts
|
|
4885
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
4886
|
+
var DEFAULT_SEEN_ID_LIMIT = 1e3;
|
|
4887
|
+
var DEFAULT_POLL_SAFETY_LOOPS = 10;
|
|
4888
|
+
function installSigintHandler(onTerminate) {
|
|
4889
|
+
const handler = () => {
|
|
4890
|
+
onTerminate();
|
|
4891
|
+
};
|
|
4892
|
+
process.once("SIGINT", handler);
|
|
4893
|
+
return () => {
|
|
4894
|
+
process.removeListener("SIGINT", handler);
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
4897
|
+
async function watch(plan, handlers) {
|
|
4898
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4899
|
+
const seenOrder = [];
|
|
4900
|
+
const seenLimit = plan.seenIdLimit ?? DEFAULT_SEEN_ID_LIMIT;
|
|
4901
|
+
function rememberId(id) {
|
|
4902
|
+
if (seenIds.has(id)) return;
|
|
4903
|
+
seenIds.add(id);
|
|
4904
|
+
seenOrder.push(id);
|
|
4905
|
+
while (seenOrder.length > seenLimit) {
|
|
4906
|
+
const evicted = seenOrder.shift();
|
|
4907
|
+
if (evicted) seenIds.delete(evicted);
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
function handle(event) {
|
|
4911
|
+
const id = plan.getId(event);
|
|
4912
|
+
if (seenIds.has(id)) return false;
|
|
4913
|
+
rememberId(id);
|
|
4914
|
+
handlers.onEvent(event);
|
|
4915
|
+
return true;
|
|
4916
|
+
}
|
|
4917
|
+
let cursor = plan.initialSince ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4918
|
+
function advanceCursor(event) {
|
|
4919
|
+
if (!plan.getCursor) return;
|
|
4920
|
+
const next = plan.getCursor(event);
|
|
4921
|
+
if (next > cursor) cursor = next;
|
|
4922
|
+
}
|
|
4923
|
+
if (plan.snapshot) {
|
|
4924
|
+
const initial = await plan.snapshot();
|
|
4925
|
+
for (const event of initial) {
|
|
4926
|
+
handle(event);
|
|
4927
|
+
advanceCursor(event);
|
|
4928
|
+
if (plan.isTerminal?.(event)) return;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
return new Promise((resolve5, reject) => {
|
|
4932
|
+
let cleanupSigint;
|
|
4933
|
+
let subscribeHandle;
|
|
4934
|
+
let pollTimer;
|
|
4935
|
+
let terminated = false;
|
|
4936
|
+
function finalize(action) {
|
|
4937
|
+
if (terminated) return;
|
|
4938
|
+
terminated = true;
|
|
4939
|
+
subscribeHandle?.unsubscribe();
|
|
4940
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
4941
|
+
cleanupSigint?.();
|
|
4942
|
+
action();
|
|
4943
|
+
}
|
|
4944
|
+
cleanupSigint = installSigintHandler(() => {
|
|
4945
|
+
finalize(() => resolve5());
|
|
4946
|
+
});
|
|
4947
|
+
function processLiveEvent(event) {
|
|
4948
|
+
const accepted = handle(event);
|
|
4949
|
+
if (accepted) advanceCursor(event);
|
|
4950
|
+
if (plan.isTerminal?.(event)) finalize(() => resolve5());
|
|
4951
|
+
}
|
|
4952
|
+
async function startPolling() {
|
|
4953
|
+
if (!plan.poll) {
|
|
4954
|
+
finalize(() => reject(new Error("No polling fallback configured for this watch.")));
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
handlers.onConnected?.("polling");
|
|
4958
|
+
const interval = plan.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
4959
|
+
const safetyLoops = plan.pollBatchSafetyLoops ?? DEFAULT_POLL_SAFETY_LOOPS;
|
|
4960
|
+
const loop = async () => {
|
|
4961
|
+
if (terminated) return;
|
|
4962
|
+
try {
|
|
4963
|
+
for (let i = 0; i < safetyLoops; i += 1) {
|
|
4964
|
+
const batch = await plan.poll(cursor);
|
|
4965
|
+
let anyNew = false;
|
|
4966
|
+
for (const event of batch) {
|
|
4967
|
+
if (handle(event)) anyNew = true;
|
|
4968
|
+
advanceCursor(event);
|
|
4969
|
+
if (plan.isTerminal?.(event)) {
|
|
4970
|
+
finalize(() => resolve5());
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
if (!anyNew) break;
|
|
4975
|
+
}
|
|
4976
|
+
} catch (err) {
|
|
4977
|
+
finalize(
|
|
4978
|
+
() => reject(err instanceof Error ? err : new Error(String(err)))
|
|
4979
|
+
);
|
|
4980
|
+
return;
|
|
4981
|
+
}
|
|
4982
|
+
pollTimer = setTimeout(loop, interval);
|
|
4983
|
+
};
|
|
4984
|
+
void loop();
|
|
4985
|
+
}
|
|
4986
|
+
if (!plan.subscribe) {
|
|
4987
|
+
void startPolling();
|
|
4988
|
+
return;
|
|
4989
|
+
}
|
|
4990
|
+
try {
|
|
4991
|
+
subscribeHandle = plan.subscribe(
|
|
4992
|
+
processLiveEvent,
|
|
4993
|
+
(err) => {
|
|
4994
|
+
if (err instanceof SessionTokenNotSupportedError && plan.poll) {
|
|
4995
|
+
subscribeHandle = void 0;
|
|
4996
|
+
void startPolling();
|
|
4997
|
+
return;
|
|
4998
|
+
}
|
|
4999
|
+
finalize(() => reject(err));
|
|
5000
|
+
},
|
|
5001
|
+
() => handlers.onConnected?.("subscription")
|
|
5002
|
+
);
|
|
5003
|
+
} catch (err) {
|
|
5004
|
+
if (err instanceof SessionTokenNotSupportedError && plan.poll) {
|
|
5005
|
+
void startPolling();
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
5008
|
+
finalize(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
5009
|
+
}
|
|
5010
|
+
});
|
|
5011
|
+
}
|
|
3798
5012
|
|
|
3799
5013
|
// src/commands/deploy.ts
|
|
5014
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["active", "failed", "superseded"]);
|
|
5015
|
+
var WATCH_POLL_INTERVAL_MS = 2e3;
|
|
5016
|
+
var WATCH_DEADLINE_MS = 5 * 6e4;
|
|
3800
5017
|
async function deployCommand(options) {
|
|
3801
5018
|
let stagingDir;
|
|
3802
5019
|
try {
|
|
@@ -3805,7 +5022,7 @@ async function deployCommand(options) {
|
|
|
3805
5022
|
});
|
|
3806
5023
|
console.log(chalk12.gray("Planning deployment..."));
|
|
3807
5024
|
stagingDir = await createStagingDir();
|
|
3808
|
-
const { manifest } = await buildPipeline(
|
|
5025
|
+
const { manifest, catalog: catalog2 } = await buildPipeline(
|
|
3809
5026
|
config,
|
|
3810
5027
|
process.cwd(),
|
|
3811
5028
|
options.minify ?? true,
|
|
@@ -3824,13 +5041,17 @@ async function deployCommand(options) {
|
|
|
3824
5041
|
projectId: config.projectId
|
|
3825
5042
|
});
|
|
3826
5043
|
console.log(chalk12.gray("\nComparing with remote..."));
|
|
3827
|
-
const remoteManifest = await client.fetchRemoteManifest(
|
|
5044
|
+
const remoteManifest = await client.fetchRemoteManifest(
|
|
5045
|
+
client.projectId()
|
|
5046
|
+
);
|
|
5047
|
+
const remoteCatalog = await client.fetchRemoteCatalog(client.projectId());
|
|
3828
5048
|
const diff = diffManifests(manifest, remoteManifest);
|
|
3829
|
-
|
|
5049
|
+
const catalogDiff = diffCatalogs(catalog2, remoteCatalog);
|
|
5050
|
+
if (!diff.hasChanges && !catalogDiff.hasChanges) {
|
|
3830
5051
|
console.log(chalk12.green("\n\u2713 No changes to deploy."));
|
|
3831
5052
|
return;
|
|
3832
5053
|
}
|
|
3833
|
-
console.log("\n" + formatPlan(diff));
|
|
5054
|
+
console.log("\n" + formatPlan(diff, catalogDiff));
|
|
3834
5055
|
const confirmed = await confirmPlan(options.yes ?? false);
|
|
3835
5056
|
if (!confirmed) {
|
|
3836
5057
|
console.log(chalk12.yellow("Deploy cancelled."));
|
|
@@ -3839,11 +5060,14 @@ async function deployCommand(options) {
|
|
|
3839
5060
|
console.log(chalk12.gray("\nApplying deployment..."));
|
|
3840
5061
|
const zipPath = join(stagingDir, "deploy.zip");
|
|
3841
5062
|
await zipBuildOutput(stagingDir, zipPath);
|
|
3842
|
-
const revision = await client.deployBundle(zipPath, manifest);
|
|
5063
|
+
const revision = await client.deployBundle(zipPath, manifest, catalog2);
|
|
3843
5064
|
console.log(
|
|
3844
5065
|
chalk12.green(`
|
|
3845
|
-
\u2713 Deployment
|
|
5066
|
+
\u2713 Deployment submitted (revision ${revision.revision}, ${revision.status})`)
|
|
3846
5067
|
);
|
|
5068
|
+
if (options.watch) {
|
|
5069
|
+
await watchDeployment(client, revision);
|
|
5070
|
+
}
|
|
3847
5071
|
} catch (err) {
|
|
3848
5072
|
if (err instanceof CliError) {
|
|
3849
5073
|
console.error(chalk12.red(err.message));
|
|
@@ -3854,6 +5078,72 @@ async function deployCommand(options) {
|
|
|
3854
5078
|
await cleanupStagingDir(stagingDir);
|
|
3855
5079
|
}
|
|
3856
5080
|
}
|
|
5081
|
+
function colorStatus(status) {
|
|
5082
|
+
switch (status) {
|
|
5083
|
+
case "active":
|
|
5084
|
+
return chalk12.green(status);
|
|
5085
|
+
case "failed":
|
|
5086
|
+
return chalk12.red(status);
|
|
5087
|
+
case "superseded":
|
|
5088
|
+
return chalk12.yellow(status);
|
|
5089
|
+
case "staged":
|
|
5090
|
+
return chalk12.cyan(status);
|
|
5091
|
+
case "draft":
|
|
5092
|
+
default:
|
|
5093
|
+
return chalk12.gray(status);
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
async function watchDeployment(client, initial) {
|
|
5097
|
+
if (TERMINAL_STATUSES.has(initial.status)) {
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
console.log(chalk12.gray(`
|
|
5101
|
+
Watching revision ${initial.revision}\u2026`));
|
|
5102
|
+
let lastStatus = initial.status;
|
|
5103
|
+
const startedAt = Date.now();
|
|
5104
|
+
await watch(
|
|
5105
|
+
{
|
|
5106
|
+
poll: async () => {
|
|
5107
|
+
if (Date.now() - startedAt > WATCH_DEADLINE_MS) {
|
|
5108
|
+
throw new Error(
|
|
5109
|
+
`Deployment did not reach a terminal status within ${WATCH_DEADLINE_MS / 1e3}s.`
|
|
5110
|
+
);
|
|
5111
|
+
}
|
|
5112
|
+
const next = await client.deploymentStatus(
|
|
5113
|
+
client.projectId(),
|
|
5114
|
+
initial.revision
|
|
5115
|
+
);
|
|
5116
|
+
return next ? [next] : [];
|
|
5117
|
+
},
|
|
5118
|
+
pollIntervalMs: WATCH_POLL_INTERVAL_MS,
|
|
5119
|
+
getId: (rev) => `${rev.revision}:${rev.status}:${rev.updatedAt}`,
|
|
5120
|
+
isTerminal: (rev) => TERMINAL_STATUSES.has(rev.status)
|
|
5121
|
+
},
|
|
5122
|
+
{
|
|
5123
|
+
onEvent: (rev) => {
|
|
5124
|
+
if (rev.status !== lastStatus) {
|
|
5125
|
+
console.log(
|
|
5126
|
+
` revision ${rev.revision} \u2192 ${colorStatus(rev.status)}`
|
|
5127
|
+
);
|
|
5128
|
+
lastStatus = rev.status;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
);
|
|
5133
|
+
if (lastStatus === "active") {
|
|
5134
|
+
console.log(chalk12.green(`
|
|
5135
|
+
\u2713 Deployment active (revision ${initial.revision}).`));
|
|
5136
|
+
} else if (lastStatus === "failed") {
|
|
5137
|
+
console.log(chalk12.red(`
|
|
5138
|
+
\u2717 Deployment failed (revision ${initial.revision}).`));
|
|
5139
|
+
process.exit(1);
|
|
5140
|
+
} else if (lastStatus === "superseded") {
|
|
5141
|
+
console.log(
|
|
5142
|
+
chalk12.yellow(`
|
|
5143
|
+
! Revision ${initial.revision} was superseded by a later deploy.`)
|
|
5144
|
+
);
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
3857
5147
|
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
3858
5148
|
<html>
|
|
3859
5149
|
<head><title>IO CLI</title><style>
|
|
@@ -4092,10 +5382,10 @@ async function whoamiCommand() {
|
|
|
4092
5382
|
var CONFIG_FILE = "io.config.json";
|
|
4093
5383
|
async function prompt(question) {
|
|
4094
5384
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4095
|
-
return new Promise((
|
|
5385
|
+
return new Promise((resolve5) => {
|
|
4096
5386
|
rl.question(question, (answer) => {
|
|
4097
5387
|
rl.close();
|
|
4098
|
-
|
|
5388
|
+
resolve5(answer.trim());
|
|
4099
5389
|
});
|
|
4100
5390
|
});
|
|
4101
5391
|
}
|
|
@@ -4271,6 +5561,55 @@ async function projectsListCommand() {
|
|
|
4271
5561
|
throw err;
|
|
4272
5562
|
}
|
|
4273
5563
|
}
|
|
5564
|
+
async function confirmDelete(projectName, projectId) {
|
|
5565
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5566
|
+
return new Promise((resolve5) => {
|
|
5567
|
+
rl.question(
|
|
5568
|
+
`Delete project "${projectName}" (${projectId})? Type the project name to confirm: `,
|
|
5569
|
+
(answer) => {
|
|
5570
|
+
rl.close();
|
|
5571
|
+
resolve5(answer.trim() === projectName);
|
|
5572
|
+
}
|
|
5573
|
+
);
|
|
5574
|
+
});
|
|
5575
|
+
}
|
|
5576
|
+
async function projectsDeleteCommand(id, options = {}) {
|
|
5577
|
+
try {
|
|
5578
|
+
const config = await loadConfig();
|
|
5579
|
+
const client = createClient(config);
|
|
5580
|
+
const organizationId = options.organizationId ?? await resolveOrganizationId(client);
|
|
5581
|
+
const projects2 = await client.projects(organizationId);
|
|
5582
|
+
const target = projects2.find((p) => p.id === id);
|
|
5583
|
+
if (!target) {
|
|
5584
|
+
throw new ConfigError(
|
|
5585
|
+
`Project "${id}" not found under organization ${organizationId}.`
|
|
5586
|
+
);
|
|
5587
|
+
}
|
|
5588
|
+
if (!options.yes) {
|
|
5589
|
+
const confirmed = await confirmDelete(target.name, target.id);
|
|
5590
|
+
if (!confirmed) {
|
|
5591
|
+
console.log(chalk12.yellow("Delete cancelled (name did not match)."));
|
|
5592
|
+
return;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
const deleted = await client.deleteProject(organizationId, target.id);
|
|
5596
|
+
console.log(chalk12.green(`\u2713 Deleted project: ${deleted.name}`));
|
|
5597
|
+
console.log(chalk12.gray(` ID: ${deleted.id}`));
|
|
5598
|
+
if (config.projectId === deleted.id) {
|
|
5599
|
+
console.log(
|
|
5600
|
+
chalk12.yellow(
|
|
5601
|
+
"\n io.config.json still references this project \u2014 clear the projectId field before redeploying."
|
|
5602
|
+
)
|
|
5603
|
+
);
|
|
5604
|
+
}
|
|
5605
|
+
} catch (err) {
|
|
5606
|
+
if (err instanceof CliError) {
|
|
5607
|
+
console.error(chalk12.red(err.message));
|
|
5608
|
+
process.exit(1);
|
|
5609
|
+
}
|
|
5610
|
+
throw err;
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
4274
5613
|
async function projectsCreateCommand(name) {
|
|
4275
5614
|
try {
|
|
4276
5615
|
const config = await loadConfig();
|
|
@@ -4495,8 +5834,9 @@ async function quotaCommand() {
|
|
|
4495
5834
|
throw err;
|
|
4496
5835
|
}
|
|
4497
5836
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
5837
|
+
|
|
5838
|
+
// src/api/subscription.ts
|
|
5839
|
+
var ACTIVITY_EVENT_SUBSCRIPTION = `
|
|
4500
5840
|
subscription ActivityEventReceived($projectId: ID!, $input: ActivityEventFilterInput) {
|
|
4501
5841
|
activityEventReceived(projectId: $projectId, input: $input) {
|
|
4502
5842
|
id
|
|
@@ -4517,56 +5857,6 @@ var ACTIVITY_SUBSCRIPTION = `
|
|
|
4517
5857
|
}
|
|
4518
5858
|
}
|
|
4519
5859
|
`;
|
|
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
5860
|
|
|
4571
5861
|
// src/activity/presentation.ts
|
|
4572
5862
|
var DISPLAY_NAME_NAMESPACES = /* @__PURE__ */ new Set([
|
|
@@ -4892,7 +6182,6 @@ var LEVEL_COLORS = {
|
|
|
4892
6182
|
scheduled: chalk12.magenta
|
|
4893
6183
|
};
|
|
4894
6184
|
var FOLLOW_POLL_INTERVAL_MS = 2e3;
|
|
4895
|
-
var FOLLOW_SEEN_ID_LIMIT = 1e3;
|
|
4896
6185
|
var FOLLOW_BATCH_LIMIT = 200;
|
|
4897
6186
|
function colorLevel(level) {
|
|
4898
6187
|
if (!level) return chalk12.gray("\u2014");
|
|
@@ -4943,74 +6232,13 @@ function formatEvent(event) {
|
|
|
4943
6232
|
detail ? chalk12.gray(detail) : ""
|
|
4944
6233
|
].join(" ").trimEnd();
|
|
4945
6234
|
}
|
|
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) {
|
|
6235
|
+
function followPollInput(input, since) {
|
|
4973
6236
|
return {
|
|
4974
6237
|
...input,
|
|
4975
6238
|
since,
|
|
4976
6239
|
limit: Math.max(input?.limit ?? 0, FOLLOW_BATCH_LIMIT)
|
|
4977
6240
|
};
|
|
4978
6241
|
}
|
|
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
6242
|
async function logsCommand(options) {
|
|
5015
6243
|
try {
|
|
5016
6244
|
const config = await loadConfig();
|
|
@@ -5026,18 +6254,18 @@ async function logsCommand(options) {
|
|
|
5026
6254
|
const since = parseSince(options.since);
|
|
5027
6255
|
if (since) input.since = since;
|
|
5028
6256
|
}
|
|
5029
|
-
const
|
|
5030
|
-
if (
|
|
6257
|
+
const initialEvents = await client.projectEvents(pid, input);
|
|
6258
|
+
if (initialEvents.length === 0 && !options.follow) {
|
|
5031
6259
|
console.log(chalk12.gray("No events found."));
|
|
5032
6260
|
return;
|
|
5033
6261
|
}
|
|
5034
|
-
if (
|
|
6262
|
+
if (initialEvents.length > 0) {
|
|
5035
6263
|
console.log(chalk12.bold(`
|
|
5036
|
-
Activity Log \u2014 ${
|
|
6264
|
+
Activity Log \u2014 ${initialEvents.length} events
|
|
5037
6265
|
`));
|
|
5038
6266
|
console.log(formatHeader());
|
|
5039
6267
|
console.log(chalk12.gray(` ${"\u2500".repeat(92)}`));
|
|
5040
|
-
for (const event of
|
|
6268
|
+
for (const event of initialEvents) {
|
|
5041
6269
|
console.log(formatEvent(event));
|
|
5042
6270
|
}
|
|
5043
6271
|
}
|
|
@@ -5047,7 +6275,7 @@ Activity Log \u2014 ${events.length} events
|
|
|
5047
6275
|
client,
|
|
5048
6276
|
pid,
|
|
5049
6277
|
input,
|
|
5050
|
-
|
|
6278
|
+
initialEvents,
|
|
5051
6279
|
followStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5052
6280
|
);
|
|
5053
6281
|
} else {
|
|
@@ -5067,88 +6295,68 @@ async function tailEvents(apiUrl, client, projectId, input, initialEvents, follo
|
|
|
5067
6295
|
throw new ConfigError("Not authenticated. Run `io login` first.");
|
|
5068
6296
|
}
|
|
5069
6297
|
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
|
-
|
|
6298
|
+
const initialSince = initialEvents.reduce(
|
|
6299
|
+
(latest, event) => event.occurredAt > latest ? event.occurredAt : latest,
|
|
6300
|
+
followStartedAt
|
|
6301
|
+
);
|
|
6302
|
+
const socketClient = createSocketClient({ apiUrl, credentials: creds });
|
|
6303
|
+
const subscriptionVars = input;
|
|
6304
|
+
let liveStarted = false;
|
|
6305
|
+
try {
|
|
6306
|
+
await watch(
|
|
6307
|
+
{
|
|
6308
|
+
subscribe: (onEvent, onError, onConnected) => {
|
|
6309
|
+
const unsubscribe = socketClient.subscribe(
|
|
6310
|
+
ACTIVITY_EVENT_SUBSCRIPTION,
|
|
6311
|
+
{ projectId, input: subscriptionVars },
|
|
6312
|
+
{
|
|
6313
|
+
onStart: onConnected,
|
|
6314
|
+
onResult: (result) => {
|
|
6315
|
+
const event = result.data?.activityEventReceived;
|
|
6316
|
+
if (event) onEvent(event);
|
|
6317
|
+
},
|
|
6318
|
+
onError,
|
|
6319
|
+
onAbort: onError
|
|
6320
|
+
}
|
|
6321
|
+
);
|
|
6322
|
+
return { unsubscribe };
|
|
6323
|
+
},
|
|
6324
|
+
poll: async (since) => {
|
|
6325
|
+
return client.projectEvents(projectId, followPollInput(input, since));
|
|
6326
|
+
},
|
|
6327
|
+
pollIntervalMs: FOLLOW_POLL_INTERVAL_MS,
|
|
6328
|
+
initialSince,
|
|
6329
|
+
getId: (event) => event.id,
|
|
6330
|
+
getCursor: (event) => event.occurredAt,
|
|
6331
|
+
seenIdLimit: 1e3
|
|
6332
|
+
},
|
|
6333
|
+
{
|
|
5083
6334
|
onEvent: (event) => {
|
|
5084
|
-
if (
|
|
5085
|
-
|
|
6335
|
+
if (!liveStarted) {
|
|
6336
|
+
liveStarted = true;
|
|
5086
6337
|
}
|
|
5087
6338
|
console.log(formatEvent(event));
|
|
5088
|
-
seenIds2.add(event.id);
|
|
5089
|
-
seenOrder2.push(event.id);
|
|
5090
|
-
trimSeenIds(seenIds2, seenOrder2);
|
|
5091
6339
|
},
|
|
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
|
-
});
|
|
6340
|
+
onConnected: (mode) => {
|
|
6341
|
+
if (mode === "subscription") {
|
|
6342
|
+
console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
|
|
6343
|
+
} else {
|
|
6344
|
+
console.log(
|
|
6345
|
+
chalk12.green(
|
|
6346
|
+
" \u25CF Connected \u2014 polling for live events with your authenticated session\n"
|
|
6347
|
+
)
|
|
6348
|
+
);
|
|
6349
|
+
}
|
|
5112
6350
|
},
|
|
5113
|
-
onError: (
|
|
6351
|
+
onError: (err) => {
|
|
5114
6352
|
console.error(chalk12.red(`
|
|
5115
|
-
Connection error: ${
|
|
5116
|
-
removeSigintHandler2();
|
|
5117
|
-
disconnect?.();
|
|
5118
|
-
reject(error);
|
|
6353
|
+
Connection error: ${err.message}`));
|
|
5119
6354
|
}
|
|
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
6355
|
}
|
|
5148
|
-
|
|
5149
|
-
|
|
6356
|
+
);
|
|
6357
|
+
console.log(chalk12.gray("\n Disconnected."));
|
|
5150
6358
|
} finally {
|
|
5151
|
-
|
|
6359
|
+
socketClient.close();
|
|
5152
6360
|
}
|
|
5153
6361
|
}
|
|
5154
6362
|
function parseSince(value) {
|
|
@@ -5181,7 +6389,7 @@ async function signalEmitCommand(name, options) {
|
|
|
5181
6389
|
try {
|
|
5182
6390
|
const config = await loadConfig({ projectId: options.projectId });
|
|
5183
6391
|
const client = createClient(config);
|
|
5184
|
-
const signal =
|
|
6392
|
+
const signal = normalizeRuntimeSignalName(name);
|
|
5185
6393
|
if (!signal) {
|
|
5186
6394
|
throw new ConfigError(
|
|
5187
6395
|
"signal name must be a bare event name like smoke.start or use the signal: namespace"
|
|
@@ -5197,7 +6405,6 @@ async function signalEmitCommand(name, options) {
|
|
|
5197
6405
|
principalId: options.principalId,
|
|
5198
6406
|
payload,
|
|
5199
6407
|
metadata,
|
|
5200
|
-
correlationId: options.correlationId,
|
|
5201
6408
|
causationId: options.causationId,
|
|
5202
6409
|
idempotencyKey: options.idempotencyKey,
|
|
5203
6410
|
sourceName: options.sourceName
|
|
@@ -5223,19 +6430,268 @@ async function signalEmitCommand(name, options) {
|
|
|
5223
6430
|
throw err;
|
|
5224
6431
|
}
|
|
5225
6432
|
}
|
|
6433
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
6434
|
+
var POLL_BATCH_LIMIT = 200;
|
|
6435
|
+
function formatEvent2(event) {
|
|
6436
|
+
const time = new Date(event.occurredAt).toLocaleTimeString("en-GB", {
|
|
6437
|
+
hour12: false
|
|
6438
|
+
});
|
|
6439
|
+
const status = inferActivityEventStatus(event);
|
|
6440
|
+
const category = inferActivityEventCategory(event);
|
|
6441
|
+
const name = inferActivityEventName(event);
|
|
6442
|
+
const detail = inferActivityEventDetail(event) ?? "";
|
|
6443
|
+
return ` ${chalk12.gray(time)} ${chalk12.cyan(category)} ${name}${detail ? ` ${chalk12.gray(detail)}` : ""}${status ? ` ${chalk12.gray(`(${status})`)}` : ""}`;
|
|
6444
|
+
}
|
|
6445
|
+
async function reconcileCommand(options) {
|
|
6446
|
+
try {
|
|
6447
|
+
const config = await loadConfig();
|
|
6448
|
+
const client = createClient(config);
|
|
6449
|
+
const projectId = options.projectId ?? client.projectId();
|
|
6450
|
+
console.log(chalk12.gray(`Requesting reconciliation for project ${projectId}\u2026`));
|
|
6451
|
+
const accepted = await client.reconcileProject(projectId);
|
|
6452
|
+
if (!accepted) {
|
|
6453
|
+
throw new CliError(
|
|
6454
|
+
"Runtime rejected the reconcile request.",
|
|
6455
|
+
"RECONCILE_REJECTED"
|
|
6456
|
+
);
|
|
6457
|
+
}
|
|
6458
|
+
console.log(chalk12.green("\u2713 Reconcile requested."));
|
|
6459
|
+
if (!options.watch) {
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
const creds = await loadCredentials();
|
|
6463
|
+
if (!creds?.token) {
|
|
6464
|
+
throw new ConfigError("Not authenticated. Run `io login` first.");
|
|
6465
|
+
}
|
|
6466
|
+
console.log(
|
|
6467
|
+
chalk12.gray("\nTailing activity for this project (Ctrl+C to stop)\u2026\n")
|
|
6468
|
+
);
|
|
6469
|
+
const socketClient = createSocketClient({
|
|
6470
|
+
apiUrl: config.apiUrl,
|
|
6471
|
+
credentials: creds
|
|
6472
|
+
});
|
|
6473
|
+
const initialSince = (/* @__PURE__ */ new Date()).toISOString();
|
|
6474
|
+
try {
|
|
6475
|
+
await watch(
|
|
6476
|
+
{
|
|
6477
|
+
subscribe: (onEvent, onError, onConnected) => {
|
|
6478
|
+
const unsubscribe = socketClient.subscribe(
|
|
6479
|
+
ACTIVITY_EVENT_SUBSCRIPTION,
|
|
6480
|
+
{ projectId, input: { limit: POLL_BATCH_LIMIT } },
|
|
6481
|
+
{
|
|
6482
|
+
onStart: onConnected,
|
|
6483
|
+
onResult: (result) => {
|
|
6484
|
+
const event = result.data?.activityEventReceived;
|
|
6485
|
+
if (event) onEvent(event);
|
|
6486
|
+
},
|
|
6487
|
+
onError,
|
|
6488
|
+
onAbort: onError
|
|
6489
|
+
}
|
|
6490
|
+
);
|
|
6491
|
+
return { unsubscribe };
|
|
6492
|
+
},
|
|
6493
|
+
poll: async (since) => {
|
|
6494
|
+
return client.projectEvents(projectId, {
|
|
6495
|
+
since,
|
|
6496
|
+
limit: POLL_BATCH_LIMIT
|
|
6497
|
+
});
|
|
6498
|
+
},
|
|
6499
|
+
pollIntervalMs: POLL_INTERVAL_MS,
|
|
6500
|
+
initialSince,
|
|
6501
|
+
getId: (event) => event.id,
|
|
6502
|
+
getCursor: (event) => event.occurredAt
|
|
6503
|
+
},
|
|
6504
|
+
{
|
|
6505
|
+
onEvent: (event) => console.log(formatEvent2(event)),
|
|
6506
|
+
onConnected: (mode) => {
|
|
6507
|
+
if (mode === "subscription") {
|
|
6508
|
+
console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
|
|
6509
|
+
} else {
|
|
6510
|
+
console.log(
|
|
6511
|
+
chalk12.green(
|
|
6512
|
+
" \u25CF Connected \u2014 polling for live events with your authenticated session\n"
|
|
6513
|
+
)
|
|
6514
|
+
);
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
}
|
|
6518
|
+
);
|
|
6519
|
+
console.log(chalk12.gray("\nDisconnected."));
|
|
6520
|
+
} finally {
|
|
6521
|
+
socketClient.close();
|
|
6522
|
+
}
|
|
6523
|
+
} catch (err) {
|
|
6524
|
+
if (err instanceof CliError) {
|
|
6525
|
+
console.error(chalk12.red(err.message));
|
|
6526
|
+
process.exit(1);
|
|
6527
|
+
}
|
|
6528
|
+
throw err;
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
function isRecord4(value) {
|
|
6532
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6533
|
+
}
|
|
6534
|
+
function isCatalogShape2(value) {
|
|
6535
|
+
if (!isRecord4(value)) return false;
|
|
6536
|
+
return value.contract_version === "io.catalog.v1" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isRecord4(value.resources) && isRecord4(value.signals) && isRecord4(value.tools) && isRecord4(value.schemas);
|
|
6537
|
+
}
|
|
6538
|
+
async function readLocalCatalog(buildDir) {
|
|
6539
|
+
const path = resolve(process.cwd(), buildDir, "catalog.json");
|
|
6540
|
+
let raw;
|
|
6541
|
+
try {
|
|
6542
|
+
raw = await readFile(path, "utf-8");
|
|
6543
|
+
} catch (err) {
|
|
6544
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
6545
|
+
throw new CliError(
|
|
6546
|
+
`Local catalog not found at ${path}. Run \`io build\` or \`io deploy\` first.`,
|
|
6547
|
+
"CATALOG_NOT_FOUND"
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
throw err;
|
|
6551
|
+
}
|
|
6552
|
+
let parsed;
|
|
6553
|
+
try {
|
|
6554
|
+
parsed = JSON.parse(raw);
|
|
6555
|
+
} catch (err) {
|
|
6556
|
+
throw new CliError(
|
|
6557
|
+
`Local catalog at ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
6558
|
+
"CATALOG_INVALID_JSON"
|
|
6559
|
+
);
|
|
6560
|
+
}
|
|
6561
|
+
if (!isCatalogShape2(parsed)) {
|
|
6562
|
+
throw new CliError(
|
|
6563
|
+
`Local catalog at ${path} does not match the io.catalog.v1 catalog shape.`,
|
|
6564
|
+
"CATALOG_SHAPE_MISMATCH"
|
|
6565
|
+
);
|
|
6566
|
+
}
|
|
6567
|
+
return parsed;
|
|
6568
|
+
}
|
|
6569
|
+
function describeExecutor(executor) {
|
|
6570
|
+
switch (executor.kind) {
|
|
6571
|
+
case "platform_tool":
|
|
6572
|
+
return `${executor.provider_kind}.${executor.backend_kind}.${executor.operation}`;
|
|
6573
|
+
case "stream_publish":
|
|
6574
|
+
return `${executor.provider_kind}.${executor.backend_kind} \u2192 ${executor.destination_ref.slice(0, 19)}\u2026`;
|
|
6575
|
+
case "webhook_response":
|
|
6576
|
+
return "webhook response";
|
|
6577
|
+
case "http_request":
|
|
6578
|
+
return `${executor.method} ${executor.url}`;
|
|
6579
|
+
case "graphql_request":
|
|
6580
|
+
return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
|
|
6581
|
+
case "http_operation":
|
|
6582
|
+
return `${executor.method} ${executor.base_url}${executor.path}`;
|
|
6583
|
+
case "graphql_operation":
|
|
6584
|
+
return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
|
|
6585
|
+
case "unsupported":
|
|
6586
|
+
return chalk12.red(`unsupported (${executor.reason})`);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
function formatCatalog(catalog2, label) {
|
|
6590
|
+
const lines = [];
|
|
6591
|
+
lines.push(chalk12.bold(`
|
|
6592
|
+
Catalog (${label})`));
|
|
6593
|
+
lines.push(
|
|
6594
|
+
` ${chalk12.gray("contract_version:")} ${catalog2.contract_version}`
|
|
6595
|
+
);
|
|
6596
|
+
lines.push(
|
|
6597
|
+
` ${chalk12.gray("snapshot_version:")} ${catalog2.snapshot_version}`
|
|
6598
|
+
);
|
|
6599
|
+
lines.push(` ${chalk12.gray("catalog_hash: ")} ${catalog2.catalog_hash}`);
|
|
6600
|
+
lines.push("");
|
|
6601
|
+
lines.push(chalk12.bold(" Counts"));
|
|
6602
|
+
lines.push(` resources: ${Object.keys(catalog2.resources).length}`);
|
|
6603
|
+
lines.push(` signals: ${Object.keys(catalog2.signals).length}`);
|
|
6604
|
+
lines.push(` tools: ${Object.keys(catalog2.tools).length}`);
|
|
6605
|
+
lines.push(` schemas: ${Object.keys(catalog2.schemas).length}`);
|
|
6606
|
+
const resourceIds = Object.keys(catalog2.resources).sort();
|
|
6607
|
+
if (resourceIds.length > 0) {
|
|
6608
|
+
lines.push("");
|
|
6609
|
+
lines.push(chalk12.bold(" Resources"));
|
|
6610
|
+
for (const id of resourceIds) {
|
|
6611
|
+
const resource = catalog2.resources[id];
|
|
6612
|
+
lines.push(
|
|
6613
|
+
` ${chalk12.cyan(resource.id)} ${chalk12.gray(
|
|
6614
|
+
`${resource.resource_kind}/${resource.backend_kind} scope=${resource.binding_scope} cred=${resource.credential_source}`
|
|
6615
|
+
)}`
|
|
6616
|
+
);
|
|
6617
|
+
for (const signalId of resource.signals) {
|
|
6618
|
+
const signal = catalog2.signals[signalId];
|
|
6619
|
+
if (!signal) continue;
|
|
6620
|
+
lines.push(
|
|
6621
|
+
` ${chalk12.magenta("signal")} ${signal.name} ${chalk12.gray(
|
|
6622
|
+
`(${signal.mode}, scope=${signal.scope})`
|
|
6623
|
+
)}`
|
|
6624
|
+
);
|
|
6625
|
+
}
|
|
6626
|
+
for (const toolId of resource.tools) {
|
|
6627
|
+
const tool = catalog2.tools[toolId];
|
|
6628
|
+
if (!tool) continue;
|
|
6629
|
+
lines.push(
|
|
6630
|
+
` ${chalk12.yellow("tool ")} ${tool.name} ${chalk12.gray(
|
|
6631
|
+
`(${tool.effect}, audit=${tool.audit_requirement}) ${describeExecutor(tool.executor)}`
|
|
6632
|
+
)}`
|
|
6633
|
+
);
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
lines.push("");
|
|
6638
|
+
return lines.join("\n");
|
|
6639
|
+
}
|
|
6640
|
+
async function catalogShowCommand(options) {
|
|
6641
|
+
try {
|
|
6642
|
+
let catalog2;
|
|
6643
|
+
let label;
|
|
6644
|
+
if (options.remote) {
|
|
6645
|
+
const config = await loadConfig();
|
|
6646
|
+
if (!config.apiUrl || !config.projectId) {
|
|
6647
|
+
throw new CliError(
|
|
6648
|
+
"No apiUrl or projectId configured. Run `io login` and set a project first.",
|
|
6649
|
+
"CONFIG_MISSING"
|
|
6650
|
+
);
|
|
6651
|
+
}
|
|
6652
|
+
const client = createClient(config);
|
|
6653
|
+
const remote = await client.fetchRemoteCatalog(client.projectId());
|
|
6654
|
+
if (!remote) {
|
|
6655
|
+
console.log(
|
|
6656
|
+
chalk12.gray("No active deployment catalog for this project yet.")
|
|
6657
|
+
);
|
|
6658
|
+
return;
|
|
6659
|
+
}
|
|
6660
|
+
catalog2 = remote;
|
|
6661
|
+
label = `remote: project ${client.projectId()}`;
|
|
6662
|
+
} else {
|
|
6663
|
+
const buildDir = options.buildDir ?? ".io";
|
|
6664
|
+
catalog2 = await readLocalCatalog(buildDir);
|
|
6665
|
+
label = `local: ${join(buildDir, "catalog.json")}`;
|
|
6666
|
+
}
|
|
6667
|
+
if (options.json) {
|
|
6668
|
+
process.stdout.write(stableStringify(catalog2, 2));
|
|
6669
|
+
process.stdout.write("\n");
|
|
6670
|
+
return;
|
|
6671
|
+
}
|
|
6672
|
+
process.stdout.write(formatCatalog(catalog2, label));
|
|
6673
|
+
} catch (err) {
|
|
6674
|
+
if (err instanceof CliError) {
|
|
6675
|
+
console.error(chalk12.red(err.message));
|
|
6676
|
+
process.exit(1);
|
|
6677
|
+
}
|
|
6678
|
+
throw err;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
5226
6681
|
|
|
5227
6682
|
// src/index.ts
|
|
5228
6683
|
var program = new Command();
|
|
5229
|
-
program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.
|
|
6684
|
+
program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.17");
|
|
5230
6685
|
program.command("init").description("Initialize io.config.json for the current project").option("--yes", "Overwrite existing config without prompting").action(initCommand);
|
|
5231
6686
|
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);
|
|
6687
|
+
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
6688
|
program.command("login").description("Authenticate with the IO platform").option("--token <value>", "API token for CI/CD environments").action(loginCommand);
|
|
5234
6689
|
program.command("logout").description("Remove stored credentials").action(logoutCommand);
|
|
5235
6690
|
program.command("whoami").description("Show current authentication status").action(whoamiCommand);
|
|
5236
6691
|
var projects = program.command("projects").description("Manage projects");
|
|
5237
6692
|
projects.command("list").description("List all projects").action(projectsListCommand);
|
|
5238
6693
|
projects.command("create <name>").description("Create a new project").action(projectsCreateCommand);
|
|
6694
|
+
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
6695
|
var secrets = program.command("secrets").description("Manage project secrets");
|
|
5240
6696
|
secrets.command("set <name> <value>").description("Set a secret value").option("--target <targets...>", "Restrict to specific targets").action(secretsSetCommand);
|
|
5241
6697
|
secrets.command("list").description("List all secrets").action(secretsListCommand);
|
|
@@ -5246,9 +6702,12 @@ principals.command("create <external-id>").description("Create a principal in th
|
|
|
5246
6702
|
principals.command("update <id>").description("Update a principal").option("--project-id <id>", "Override the configured project ID").option("--external-id <external-id>", "Replace the external ID").option("--state <state>", "Principal state: active, inactive, or archived").option("--metadata <json>", "Replace principal metadata with a JSON object").action(principalsUpdateCommand);
|
|
5247
6703
|
principals.command("delete <id>").description("Delete a principal").option("--project-id <id>", "Override the configured project ID").action(principalsDeleteCommand);
|
|
5248
6704
|
var signals = program.command("signals").description("Emit runtime signals");
|
|
5249
|
-
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("--
|
|
6705
|
+
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("--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
6706
|
program.command("quota").description("Show current quota usage for the active organization").action(quotaCommand);
|
|
5251
6707
|
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);
|
|
6708
|
+
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);
|
|
6709
|
+
var catalog = program.command("catalog").description("Inspect the v2 deployment catalog");
|
|
6710
|
+
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
6711
|
program.parse();
|
|
5253
6712
|
//# sourceMappingURL=index.js.map
|
|
5254
6713
|
//# sourceMappingURL=index.js.map
|