@eide/foir-cli 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +52 -29
- package/dist/lib/config-helpers.d.ts +39 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2949,12 +2949,14 @@ function createOperationsMethods(client) {
|
|
|
2949
2949
|
timeoutMs: params.timeoutMs,
|
|
2950
2950
|
inputSchema: params.inputSchema,
|
|
2951
2951
|
outputSchema: params.outputSchema,
|
|
2952
|
-
streamConfig: params.streamConfig,
|
|
2953
2952
|
quotas: params.quotas,
|
|
2954
2953
|
retryPolicy: params.retryPolicy,
|
|
2954
|
+
callbackTimeoutRetryPolicy: params.callbackTimeoutRetryPolicy,
|
|
2955
2955
|
allowedRoles: params.allowedRoles ?? [],
|
|
2956
2956
|
precondition: params.precondition,
|
|
2957
|
-
configId: params.configId
|
|
2957
|
+
configId: params.configId,
|
|
2958
|
+
supportsAsyncCallback: params.supportsAsyncCallback,
|
|
2959
|
+
callbackTtlSeconds: params.callbackTtlSeconds
|
|
2958
2960
|
})
|
|
2959
2961
|
);
|
|
2960
2962
|
return resp.operation ?? null;
|
|
@@ -2969,11 +2971,13 @@ function createOperationsMethods(client) {
|
|
|
2969
2971
|
timeoutMs: params.timeoutMs,
|
|
2970
2972
|
inputSchema: params.inputSchema,
|
|
2971
2973
|
outputSchema: params.outputSchema,
|
|
2972
|
-
streamConfig: params.streamConfig,
|
|
2973
2974
|
quotas: params.quotas,
|
|
2974
2975
|
retryPolicy: params.retryPolicy,
|
|
2976
|
+
callbackTimeoutRetryPolicy: params.callbackTimeoutRetryPolicy,
|
|
2975
2977
|
precondition: params.precondition,
|
|
2976
|
-
isActive: params.isActive
|
|
2978
|
+
isActive: params.isActive,
|
|
2979
|
+
supportsAsyncCallback: params.supportsAsyncCallback,
|
|
2980
|
+
callbackTtlSeconds: params.callbackTtlSeconds
|
|
2977
2981
|
})
|
|
2978
2982
|
);
|
|
2979
2983
|
return resp.operation ?? null;
|
|
@@ -4969,6 +4973,12 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4969
4973
|
}
|
|
4970
4974
|
const ex = existingByKey.get(op.key);
|
|
4971
4975
|
const endpoint = resolveEndpoint(op.endpoint, operationBaseUrl);
|
|
4976
|
+
const supportsAsyncCallback = op.mode === "async";
|
|
4977
|
+
if (supportsAsyncCallback && op.timeoutMs && op.timeoutMs > 1e4) {
|
|
4978
|
+
console.warn(
|
|
4979
|
+
`\u26A0 operation "${op.key}": mode=async but timeoutMs=${op.timeoutMs} \u2014 ack should return in <10s; long timeouts mask slow extensions`
|
|
4980
|
+
);
|
|
4981
|
+
}
|
|
4972
4982
|
if (ex) {
|
|
4973
4983
|
const empty = {};
|
|
4974
4984
|
await client.operations.updateOperation({
|
|
@@ -4979,11 +4989,13 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4979
4989
|
timeoutMs: op.timeoutMs,
|
|
4980
4990
|
inputSchema: op.inputSchema ?? empty,
|
|
4981
4991
|
outputSchema: op.outputSchema ?? empty,
|
|
4982
|
-
streamConfig: op.streamConfig ?? empty,
|
|
4983
4992
|
quotas: op.quotas ?? empty,
|
|
4984
4993
|
retryPolicy: op.retryPolicy ?? empty,
|
|
4994
|
+
callbackTimeoutRetryPolicy: op.callbackTimeoutRetryPolicy ?? empty,
|
|
4985
4995
|
precondition: op.precondition ?? empty,
|
|
4986
|
-
isActive: op.isActive
|
|
4996
|
+
isActive: op.isActive,
|
|
4997
|
+
supportsAsyncCallback,
|
|
4998
|
+
callbackTtlSeconds: op.callbackTtlSeconds
|
|
4987
4999
|
});
|
|
4988
5000
|
summary.operations.updated++;
|
|
4989
5001
|
} else {
|
|
@@ -4997,12 +5009,14 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4997
5009
|
timeoutMs: op.timeoutMs,
|
|
4998
5010
|
inputSchema: op.inputSchema,
|
|
4999
5011
|
outputSchema: op.outputSchema,
|
|
5000
|
-
streamConfig: op.streamConfig,
|
|
5001
5012
|
quotas: op.quotas,
|
|
5002
5013
|
retryPolicy: op.retryPolicy,
|
|
5014
|
+
callbackTimeoutRetryPolicy: op.callbackTimeoutRetryPolicy,
|
|
5003
5015
|
allowedRoles: op.allowedRoles,
|
|
5004
5016
|
precondition: op.precondition,
|
|
5005
|
-
configId
|
|
5017
|
+
configId,
|
|
5018
|
+
supportsAsyncCallback,
|
|
5019
|
+
callbackTtlSeconds: op.callbackTtlSeconds
|
|
5006
5020
|
});
|
|
5007
5021
|
summary.operations.created++;
|
|
5008
5022
|
}
|
|
@@ -5571,6 +5585,24 @@ function discoverConfigFile() {
|
|
|
5571
5585
|
}
|
|
5572
5586
|
return null;
|
|
5573
5587
|
}
|
|
5588
|
+
function syncEnvVar(envPath, key, value) {
|
|
5589
|
+
const envContent = existsSync4(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
5590
|
+
const pattern = new RegExp(`^${key}=(.*)$`, "m");
|
|
5591
|
+
const currentMatch = envContent.match(pattern);
|
|
5592
|
+
const currentValue = currentMatch?.[1];
|
|
5593
|
+
if (currentValue === value) {
|
|
5594
|
+
return "unchanged";
|
|
5595
|
+
}
|
|
5596
|
+
if (currentMatch) {
|
|
5597
|
+
const updated = envContent.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
|
|
5598
|
+
writeFileSync2(envPath, updated, "utf-8");
|
|
5599
|
+
return "replaced";
|
|
5600
|
+
}
|
|
5601
|
+
const needsNewline = envContent && !envContent.endsWith("\n");
|
|
5602
|
+
writeFileSync2(envPath, (needsNewline ? envContent + "\n" : envContent) + `${key}=${value}
|
|
5603
|
+
`, "utf-8");
|
|
5604
|
+
return "written";
|
|
5605
|
+
}
|
|
5574
5606
|
function writeEnvVar(envPath, key, value) {
|
|
5575
5607
|
let content = "";
|
|
5576
5608
|
if (existsSync4(envPath)) {
|
|
@@ -5672,31 +5704,22 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
5672
5704
|
const { secret: signingSecret } = await client.operations.getSigningSecret();
|
|
5673
5705
|
if (signingSecret) {
|
|
5674
5706
|
const envPath2 = resolve4(opts.env ?? ".env");
|
|
5675
|
-
const
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
if (
|
|
5680
|
-
const updated = envContent.replace(
|
|
5681
|
-
/^FOIR_WEBHOOK_SECRET=.*$/m,
|
|
5682
|
-
`FOIR_WEBHOOK_SECRET=${signingSecret}`
|
|
5683
|
-
);
|
|
5684
|
-
writeFileSync2(envPath2, updated, "utf-8");
|
|
5707
|
+
const syncAndLog = (key, label) => {
|
|
5708
|
+
const result = syncEnvVar(envPath2, key, signingSecret);
|
|
5709
|
+
if (result === "unchanged") {
|
|
5710
|
+
console.log(chalk6.dim(` ${label}: ${key} already up to date, skipped`));
|
|
5711
|
+
} else if (result === "replaced") {
|
|
5685
5712
|
console.log(
|
|
5686
|
-
chalk6.yellow(
|
|
5713
|
+
chalk6.yellow(`\u27F3 ${label}`) + chalk6.dim(` \u2192 ${key} updated in ${envPath2}`)
|
|
5687
5714
|
);
|
|
5688
5715
|
} else {
|
|
5689
|
-
|
|
5690
|
-
key
|
|
5691
|
-
|
|
5692
|
-
label: "Webhook signing secret"
|
|
5693
|
-
});
|
|
5716
|
+
console.log(
|
|
5717
|
+
chalk6.green(`\u2713 ${label}`) + chalk6.dim(` \u2192 ${key} written to ${envPath2}`)
|
|
5718
|
+
);
|
|
5694
5719
|
}
|
|
5695
|
-
}
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
);
|
|
5699
|
-
}
|
|
5720
|
+
};
|
|
5721
|
+
syncAndLog("FOIR_WEBHOOK_SECRET", "Webhook signing secret");
|
|
5722
|
+
syncAndLog("FOIR_SIGNING_SECRET", "Callback signing secret");
|
|
5700
5723
|
}
|
|
5701
5724
|
if (envWrites.length > 0) {
|
|
5702
5725
|
console.log();
|
|
@@ -90,8 +90,6 @@ interface ApplyConfigOperationInput {
|
|
|
90
90
|
timeoutMs?: number;
|
|
91
91
|
inputSchema?: Record<string, unknown>;
|
|
92
92
|
outputSchema?: Record<string, unknown>;
|
|
93
|
-
/** Streaming configuration for SSE/chunked responses. */
|
|
94
|
-
streamConfig?: Record<string, unknown>;
|
|
95
93
|
/** Usage quota rules (rate limits per customer/user/tenant). */
|
|
96
94
|
quotas?: {
|
|
97
95
|
rules: QuotaRule[];
|
|
@@ -102,6 +100,45 @@ interface ApplyConfigOperationInput {
|
|
|
102
100
|
allowedRoles?: string[];
|
|
103
101
|
/** Precondition that must be met before execution (e.g., segment membership). */
|
|
104
102
|
precondition?: Precondition;
|
|
103
|
+
/**
|
|
104
|
+
* Execution mode the operation supports. `sync` blocks the platform worker
|
|
105
|
+
* on the HTTP call (≤30s realistically). `async` dispatches with
|
|
106
|
+
* 202-ack semantics — the extension returns 202 immediately and calls
|
|
107
|
+
* back via the typed GraphQL mutation when work completes. Use `async`
|
|
108
|
+
* for long-running work that exceeds the 30s wall clock.
|
|
109
|
+
*
|
|
110
|
+
* There is only one flavour of async on the platform: the callback-based
|
|
111
|
+
* handshake. The prior "fire-and-forget" meaning of async was retired
|
|
112
|
+
* in 2026-04 — every ASYNC dispatch now carries a scoped callback token
|
|
113
|
+
* and the platform tracks the execution to terminal state.
|
|
114
|
+
*
|
|
115
|
+
* Default: `sync`. The CLI maps this to `supportsAsyncCallback` on the
|
|
116
|
+
* platform Operation row — the operation can still be dispatched in SYNC
|
|
117
|
+
* mode at runtime when callers want a blocking call.
|
|
118
|
+
*/
|
|
119
|
+
mode?: 'sync' | 'async';
|
|
120
|
+
/**
|
|
121
|
+
* How long the platform waits for a callback before marking the execution
|
|
122
|
+
* `timed_out` and (per retry policy) re-queueing or sending to DLQ. Falls
|
|
123
|
+
* back to project setting `operations.callback_default_ttl`, then 24h.
|
|
124
|
+
* Clamped to [5m, 7d]. Only meaningful when `mode === 'async'`.
|
|
125
|
+
*/
|
|
126
|
+
callbackTtlSeconds?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Retry policy applied specifically when the callback times out. Distinct
|
|
129
|
+
* from `retryPolicy`, which governs HTTP dispatch failures (network, 5xx).
|
|
130
|
+
* Splitting these lets operators retry dispatch failures aggressively while
|
|
131
|
+
* being conservative about callback timeouts — re-running an hour-long job
|
|
132
|
+
* that timed out because the upstream was slow tends to burn another hour
|
|
133
|
+
* and hit the same slowness.
|
|
134
|
+
*
|
|
135
|
+
* Shape mirrors `retryPolicy` (`{ maxRetries }`). Falls back to
|
|
136
|
+
* `retryPolicy` when unset, then to the watchdog default (3).
|
|
137
|
+
* Set `{ maxRetries: 0 }` to opt out of retry on timeout entirely.
|
|
138
|
+
*/
|
|
139
|
+
callbackTimeoutRetryPolicy?: {
|
|
140
|
+
maxRetries?: number;
|
|
141
|
+
};
|
|
105
142
|
}
|
|
106
143
|
interface ApplyConfigSegmentInput {
|
|
107
144
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eide/foir-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@bufbuild/protovalidate": "^1.1.1",
|
|
51
51
|
"@connectrpc/connect": "^2.0.0",
|
|
52
52
|
"@connectrpc/connect-node": "^2.0.0",
|
|
53
|
-
"@eide/foir-proto-ts": "^0.
|
|
53
|
+
"@eide/foir-proto-ts": "^0.16.0",
|
|
54
54
|
"chalk": "^5.3.0",
|
|
55
55
|
"commander": "^12.1.0",
|
|
56
56
|
"dotenv": "^16.4.5",
|