runwork 0.27.0 → 0.27.1
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.
|
@@ -229,14 +229,17 @@ export declare const integrationApiSchema: z.ZodObject<{
|
|
|
229
229
|
method: z.ZodDefault<z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE"]>>;
|
|
230
230
|
endpoint: z.ZodString;
|
|
231
231
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
232
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
232
233
|
}, "strip", z.ZodTypeAny, {
|
|
233
234
|
endpoint: string;
|
|
234
235
|
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
|
|
235
236
|
data?: Record<string, unknown> | undefined;
|
|
237
|
+
headers?: Record<string, string> | undefined;
|
|
236
238
|
}, {
|
|
237
239
|
endpoint: string;
|
|
238
240
|
data?: Record<string, unknown> | undefined;
|
|
239
241
|
method?: "POST" | "GET" | "PUT" | "PATCH" | "DELETE" | undefined;
|
|
242
|
+
headers?: Record<string, string> | undefined;
|
|
240
243
|
}>;
|
|
241
244
|
/**
|
|
242
245
|
* Get tool definitions for integration access
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* - Creates WorkflowInstance DOs
|
|
7
7
|
* - Maintains an index for querying workflows
|
|
8
8
|
* - Routes signals to correct instances
|
|
9
|
+
*
|
|
10
|
+
* Index storage: one storage key per instance entry (`index:<instanceId>`),
|
|
11
|
+
* read back with a prefix list. A single-value index hits the 2 MB per-value
|
|
12
|
+
* limit of SQLite-backed DO storage at roughly 11,000 entries, after which every
|
|
13
|
+
* write fails with SQLITE_TOOBIG. Apps deployed before the per-key layout still
|
|
14
|
+
* hold the legacy `workflow_index` value; it is migrated on first load.
|
|
15
|
+
*
|
|
16
|
+
* Retention: terminal entries older than the retention window are pruned from
|
|
17
|
+
* a self-armed alarm, and the index is trimmed inline when a write pushes it
|
|
18
|
+
* past the entry cap, so the index stays bounded without an external caller.
|
|
9
19
|
*/
|
|
10
20
|
import { DurableObject } from 'cloudflare:workers';
|
|
11
21
|
import type { NativeWorkflowStatus, NativeWorkflowConfig, ListWorkflowsOptions, WorkflowInstanceInfo, WorkflowInstanceStub } from './core-workflow-types';
|
|
@@ -19,8 +29,14 @@ import { type Env } from './core-utils';
|
|
|
19
29
|
*/
|
|
20
30
|
export declare class WorkflowCoordinator extends DurableObject<Env> {
|
|
21
31
|
private index;
|
|
32
|
+
/** Entry cap applied inline on writes and by the retention alarm. */
|
|
33
|
+
readonly retentionMaxEntries = 10000;
|
|
22
34
|
/**
|
|
23
|
-
* Create a new workflow instance
|
|
35
|
+
* Create a new workflow instance.
|
|
36
|
+
*
|
|
37
|
+
* The index entry and stored params are written before the instance starts,
|
|
38
|
+
* so a completion notification from a fast workflow can never race ahead of
|
|
39
|
+
* its own pending entry, and an index failure surfaces before any work runs.
|
|
24
40
|
*/
|
|
25
41
|
create(workflowName: string, params: Record<string, unknown>, config?: Partial<NativeWorkflowConfig>, metadata?: Record<string, unknown>): Promise<string>;
|
|
26
42
|
/**
|
|
@@ -65,14 +81,36 @@ export declare class WorkflowCoordinator extends DurableObject<Env> {
|
|
|
65
81
|
byWorkflow: Record<string, number>;
|
|
66
82
|
}>;
|
|
67
83
|
/**
|
|
68
|
-
* Clean up completed/old workflow entries
|
|
69
|
-
*
|
|
84
|
+
* Clean up completed/old workflow entries.
|
|
85
|
+
*
|
|
86
|
+
* Removes terminal entries older than `olderThanMs`, every completed entry
|
|
87
|
+
* unless `keepCompleted` is set, and then the oldest terminal entries until
|
|
88
|
+
* the index fits within `maxEntries`. Active entries (pending, running,
|
|
89
|
+
* sleeping, waiting, paused) are never removed. Runs from the retention alarm
|
|
90
|
+
* and inline on writes; explicit calls remain supported.
|
|
70
91
|
*/
|
|
71
92
|
cleanup(options?: {
|
|
72
93
|
olderThanMs?: number;
|
|
73
94
|
keepCompleted?: boolean;
|
|
74
95
|
maxEntries?: number;
|
|
75
96
|
}): Promise<number>;
|
|
97
|
+
/**
|
|
98
|
+
* Retention alarm: prune old terminal entries and re-arm while the index
|
|
99
|
+
* still holds anything.
|
|
100
|
+
*/
|
|
101
|
+
alarm(): Promise<void>;
|
|
76
102
|
private ensureIndexLoaded;
|
|
77
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Move a legacy single-value index into per-entry keys. Per-entry values
|
|
105
|
+
* already present win, since they were written after the legacy value.
|
|
106
|
+
* An unreadable legacy value is dropped: the workflow instances themselves
|
|
107
|
+
* still hold their state, only the listing loses history.
|
|
108
|
+
*/
|
|
109
|
+
private migrateLegacyIndex;
|
|
110
|
+
private deleteLegacyIndexKey;
|
|
111
|
+
/**
|
|
112
|
+
* Remove index entries and their stored params, in storage batches.
|
|
113
|
+
*/
|
|
114
|
+
private removeEntries;
|
|
115
|
+
private ensureRetentionAlarm;
|
|
78
116
|
}
|
|
@@ -28,7 +28,12 @@ export interface NangoProxyOptions {
|
|
|
28
28
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
29
29
|
endpoint: string;
|
|
30
30
|
providerConfigKey: string;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Request body. Encoded per the Content-Type in `headers`: form-encoded with
|
|
33
|
+
* bracket notation for application/x-www-form-urlencoded, JSON otherwise.
|
|
34
|
+
* A string is sent verbatim.
|
|
35
|
+
*/
|
|
36
|
+
data?: Record<string, unknown> | string;
|
|
32
37
|
headers?: Record<string, string>;
|
|
33
38
|
params?: Record<string, string>;
|
|
34
39
|
retries?: number;
|
package/dist/index.js
CHANGED
|
@@ -427,6 +427,124 @@ function safeRm(path) {
|
|
|
427
427
|
var MAX_REDIRECTS = 10, transportOverride = "auto";
|
|
428
428
|
var init_http = () => {};
|
|
429
429
|
|
|
430
|
+
// ../../shared/utils/integration-body.ts
|
|
431
|
+
function findHeader(headers, name) {
|
|
432
|
+
if (!headers)
|
|
433
|
+
return;
|
|
434
|
+
const wanted = name.toLowerCase();
|
|
435
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
436
|
+
if (key.toLowerCase() === wanted)
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
function setHeader(headers, name, value) {
|
|
442
|
+
const wanted = name.toLowerCase();
|
|
443
|
+
const next = {};
|
|
444
|
+
for (const [key, existing] of Object.entries(headers)) {
|
|
445
|
+
if (key.toLowerCase() !== wanted)
|
|
446
|
+
next[key] = existing;
|
|
447
|
+
}
|
|
448
|
+
next[name] = value;
|
|
449
|
+
return next;
|
|
450
|
+
}
|
|
451
|
+
function mediaType(contentType) {
|
|
452
|
+
return (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
453
|
+
}
|
|
454
|
+
function isJsonContentType(contentType) {
|
|
455
|
+
const type = mediaType(contentType);
|
|
456
|
+
return type === JSON_CONTENT_TYPE || type.endsWith("+json");
|
|
457
|
+
}
|
|
458
|
+
function isFormContentType(contentType) {
|
|
459
|
+
return mediaType(contentType) === FORM_CONTENT_TYPE;
|
|
460
|
+
}
|
|
461
|
+
function isMultipartContentType(contentType) {
|
|
462
|
+
return mediaType(contentType) === "multipart/form-data";
|
|
463
|
+
}
|
|
464
|
+
function appendFormValue(out, key, value) {
|
|
465
|
+
if (value === undefined)
|
|
466
|
+
return;
|
|
467
|
+
if (value === null) {
|
|
468
|
+
out.append(key, "");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (Array.isArray(value)) {
|
|
472
|
+
value.forEach((item, index) => appendFormValue(out, `${key}[${index}]`, item));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (typeof value === "object") {
|
|
476
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
477
|
+
appendFormValue(out, `${key}[${childKey}]`, childValue);
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
out.append(key, String(value));
|
|
482
|
+
}
|
|
483
|
+
function encodeFormBody(value) {
|
|
484
|
+
if (typeof value === "string")
|
|
485
|
+
return value;
|
|
486
|
+
if (value === undefined || value === null)
|
|
487
|
+
return "";
|
|
488
|
+
const out = new URLSearchParams;
|
|
489
|
+
if (Array.isArray(value) || typeof value === "object") {
|
|
490
|
+
for (const [key, child] of Object.entries(value)) {
|
|
491
|
+
appendFormValue(out, key, child);
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
out.append("value", String(value));
|
|
495
|
+
}
|
|
496
|
+
return out.toString();
|
|
497
|
+
}
|
|
498
|
+
function looksLikeJson(text) {
|
|
499
|
+
const trimmed = text.trim();
|
|
500
|
+
if (!(trimmed.startsWith("{") || trimmed.startsWith("[")))
|
|
501
|
+
return false;
|
|
502
|
+
try {
|
|
503
|
+
JSON.parse(trimmed);
|
|
504
|
+
return true;
|
|
505
|
+
} catch {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function encodeIntegrationBody(body, contentType) {
|
|
510
|
+
if (body === undefined || body === null) {
|
|
511
|
+
return { body: undefined, contentType };
|
|
512
|
+
}
|
|
513
|
+
if (isMultipartContentType(contentType)) {
|
|
514
|
+
throw new Error("multipart/form-data bodies are not supported by the integrations proxy. Send application/x-www-form-urlencoded or JSON instead.");
|
|
515
|
+
}
|
|
516
|
+
if (typeof body === "string") {
|
|
517
|
+
if (contentType)
|
|
518
|
+
return { body, contentType };
|
|
519
|
+
return { body, contentType: looksLikeJson(body) ? JSON_CONTENT_TYPE : TEXT_CONTENT_TYPE };
|
|
520
|
+
}
|
|
521
|
+
if (isFormContentType(contentType)) {
|
|
522
|
+
return { body: encodeFormBody(body), contentType };
|
|
523
|
+
}
|
|
524
|
+
return { body: JSON.stringify(body), contentType: contentType ?? JSON_CONTENT_TYPE };
|
|
525
|
+
}
|
|
526
|
+
function encodeIntegrationRequest(body, headers = {}) {
|
|
527
|
+
const encoded = encodeIntegrationBody(body, findHeader(headers, "Content-Type"));
|
|
528
|
+
if (encoded.body === undefined) {
|
|
529
|
+
return { body: undefined, headers };
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
body: encoded.body,
|
|
533
|
+
headers: setHeader(headers, "Content-Type", encoded.contentType ?? JSON_CONTENT_TYPE)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function parseBodyArgument(text, contentType) {
|
|
537
|
+
try {
|
|
538
|
+
return JSON.parse(text);
|
|
539
|
+
} catch {
|
|
540
|
+
if (contentType && !isJsonContentType(contentType)) {
|
|
541
|
+
return text;
|
|
542
|
+
}
|
|
543
|
+
throw new Error('Body is not valid JSON. To send a raw or form-encoded body, set a matching Content-Type header (for example --header "Content-Type: application/x-www-form-urlencoded").');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
var JSON_CONTENT_TYPE = "application/json", FORM_CONTENT_TYPE = "application/x-www-form-urlencoded", TEXT_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
547
|
+
|
|
430
548
|
// src/api/client.ts
|
|
431
549
|
class ApiClient {
|
|
432
550
|
baseUrl;
|
|
@@ -868,22 +986,22 @@ class ApiClient {
|
|
|
868
986
|
return res.data;
|
|
869
987
|
}
|
|
870
988
|
async callIntegrationProxy(integrationDbId, method, path, opts) {
|
|
871
|
-
const targetPath = opts?.query ? `${path}
|
|
989
|
+
const targetPath = opts?.query ? `${path}${path.includes("?") ? "&" : "?"}${opts.query}` : path;
|
|
872
990
|
const url = `${this.baseUrl}/api/proxy/integrations${targetPath}`;
|
|
873
|
-
const
|
|
991
|
+
const upperMethod = method.toUpperCase();
|
|
992
|
+
const hasBody = opts?.body !== undefined && opts?.body !== null && upperMethod !== "GET" && upperMethod !== "HEAD";
|
|
993
|
+
const encoded = encodeIntegrationRequest(hasBody ? opts?.body : undefined, {
|
|
874
994
|
"X-Workspace-Integration-Id": integrationDbId,
|
|
875
|
-
...
|
|
876
|
-
};
|
|
995
|
+
...opts?.headers || {}
|
|
996
|
+
});
|
|
997
|
+
const headers = encoded.headers;
|
|
877
998
|
if (this.apiKey) {
|
|
878
999
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
879
1000
|
}
|
|
880
|
-
if (opts?.body) {
|
|
881
|
-
headers["Content-Type"] = "application/json";
|
|
882
|
-
}
|
|
883
1001
|
const response = await httpFetch(url, {
|
|
884
|
-
method,
|
|
1002
|
+
method: upperMethod,
|
|
885
1003
|
headers,
|
|
886
|
-
body:
|
|
1004
|
+
body: encoded.body
|
|
887
1005
|
});
|
|
888
1006
|
if (!response.ok) {
|
|
889
1007
|
const body = await response.text();
|
|
@@ -6212,6 +6330,16 @@ export declare class WorkflowInstanceDO extends DurableObject<Env> {
|
|
|
6212
6330
|
* - Creates WorkflowInstance DOs
|
|
6213
6331
|
* - Maintains an index for querying workflows
|
|
6214
6332
|
* - Routes signals to correct instances
|
|
6333
|
+
*
|
|
6334
|
+
* Index storage: one storage key per instance entry (\`index:<instanceId>\`),
|
|
6335
|
+
* read back with a prefix list. A single-value index hits the 2 MB per-value
|
|
6336
|
+
* limit of SQLite-backed DO storage at roughly 11,000 entries, after which every
|
|
6337
|
+
* write fails with SQLITE_TOOBIG. Apps deployed before the per-key layout still
|
|
6338
|
+
* hold the legacy \`workflow_index\` value; it is migrated on first load.
|
|
6339
|
+
*
|
|
6340
|
+
* Retention: terminal entries older than the retention window are pruned from
|
|
6341
|
+
* a self-armed alarm, and the index is trimmed inline when a write pushes it
|
|
6342
|
+
* past the entry cap, so the index stays bounded without an external caller.
|
|
6215
6343
|
*/
|
|
6216
6344
|
import { DurableObject } from 'cloudflare:workers';
|
|
6217
6345
|
import type { NativeWorkflowStatus, NativeWorkflowConfig, ListWorkflowsOptions, WorkflowInstanceInfo, WorkflowInstanceStub } from './core-workflow-types';
|
|
@@ -6225,8 +6353,14 @@ import { type Env } from './core-utils';
|
|
|
6225
6353
|
*/
|
|
6226
6354
|
export declare class WorkflowCoordinator extends DurableObject<Env> {
|
|
6227
6355
|
private index;
|
|
6356
|
+
/** Entry cap applied inline on writes and by the retention alarm. */
|
|
6357
|
+
readonly retentionMaxEntries = 10000;
|
|
6228
6358
|
/**
|
|
6229
|
-
* Create a new workflow instance
|
|
6359
|
+
* Create a new workflow instance.
|
|
6360
|
+
*
|
|
6361
|
+
* The index entry and stored params are written before the instance starts,
|
|
6362
|
+
* so a completion notification from a fast workflow can never race ahead of
|
|
6363
|
+
* its own pending entry, and an index failure surfaces before any work runs.
|
|
6230
6364
|
*/
|
|
6231
6365
|
create(workflowName: string, params: Record<string, unknown>, config?: Partial<NativeWorkflowConfig>, metadata?: Record<string, unknown>): Promise<string>;
|
|
6232
6366
|
/**
|
|
@@ -6271,16 +6405,38 @@ export declare class WorkflowCoordinator extends DurableObject<Env> {
|
|
|
6271
6405
|
byWorkflow: Record<string, number>;
|
|
6272
6406
|
}>;
|
|
6273
6407
|
/**
|
|
6274
|
-
* Clean up completed/old workflow entries
|
|
6275
|
-
*
|
|
6408
|
+
* Clean up completed/old workflow entries.
|
|
6409
|
+
*
|
|
6410
|
+
* Removes terminal entries older than \`olderThanMs\`, every completed entry
|
|
6411
|
+
* unless \`keepCompleted\` is set, and then the oldest terminal entries until
|
|
6412
|
+
* the index fits within \`maxEntries\`. Active entries (pending, running,
|
|
6413
|
+
* sleeping, waiting, paused) are never removed. Runs from the retention alarm
|
|
6414
|
+
* and inline on writes; explicit calls remain supported.
|
|
6276
6415
|
*/
|
|
6277
6416
|
cleanup(options?: {
|
|
6278
6417
|
olderThanMs?: number;
|
|
6279
6418
|
keepCompleted?: boolean;
|
|
6280
6419
|
maxEntries?: number;
|
|
6281
6420
|
}): Promise<number>;
|
|
6421
|
+
/**
|
|
6422
|
+
* Retention alarm: prune old terminal entries and re-arm while the index
|
|
6423
|
+
* still holds anything.
|
|
6424
|
+
*/
|
|
6425
|
+
alarm(): Promise<void>;
|
|
6282
6426
|
private ensureIndexLoaded;
|
|
6283
|
-
|
|
6427
|
+
/**
|
|
6428
|
+
* Move a legacy single-value index into per-entry keys. Per-entry values
|
|
6429
|
+
* already present win, since they were written after the legacy value.
|
|
6430
|
+
* An unreadable legacy value is dropped: the workflow instances themselves
|
|
6431
|
+
* still hold their state, only the listing loses history.
|
|
6432
|
+
*/
|
|
6433
|
+
private migrateLegacyIndex;
|
|
6434
|
+
private deleteLegacyIndexKey;
|
|
6435
|
+
/**
|
|
6436
|
+
* Remove index entries and their stored params, in storage batches.
|
|
6437
|
+
*/
|
|
6438
|
+
private removeEntries;
|
|
6439
|
+
private ensureRetentionAlarm;
|
|
6284
6440
|
}
|
|
6285
6441
|
`,
|
|
6286
6442
|
"core-integration-entities.d.ts": `/**
|
|
@@ -7962,14 +8118,17 @@ export declare const integrationApiSchema: z.ZodObject<{
|
|
|
7962
8118
|
method: z.ZodDefault<z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE"]>>;
|
|
7963
8119
|
endpoint: z.ZodString;
|
|
7964
8120
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
8121
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
7965
8122
|
}, "strip", z.ZodTypeAny, {
|
|
7966
8123
|
endpoint: string;
|
|
7967
8124
|
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
|
|
7968
8125
|
data?: Record<string, unknown> | undefined;
|
|
8126
|
+
headers?: Record<string, string> | undefined;
|
|
7969
8127
|
}, {
|
|
7970
8128
|
endpoint: string;
|
|
7971
8129
|
data?: Record<string, unknown> | undefined;
|
|
7972
8130
|
method?: "POST" | "GET" | "PUT" | "PATCH" | "DELETE" | undefined;
|
|
8131
|
+
headers?: Record<string, string> | undefined;
|
|
7973
8132
|
}>;
|
|
7974
8133
|
/**
|
|
7975
8134
|
* Get tool definitions for integration access
|
|
@@ -8205,7 +8364,7 @@ function createKeyboardListener() {
|
|
|
8205
8364
|
}
|
|
8206
8365
|
|
|
8207
8366
|
// src/generated/version.ts
|
|
8208
|
-
var VERSION = "0.27.
|
|
8367
|
+
var VERSION = "0.27.1";
|
|
8209
8368
|
|
|
8210
8369
|
// src/commands/dev.ts
|
|
8211
8370
|
var exports_dev = {};
|
|
@@ -19885,17 +20044,10 @@ async function parseCurlToRequest(curlStr) {
|
|
|
19885
20044
|
try {
|
|
19886
20045
|
body = JSON.parse(dataStr);
|
|
19887
20046
|
} catch {
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
if (eqIdx > 0) {
|
|
19893
|
-
formData[decodeURIComponent(pair.slice(0, eqIdx))] = decodeURIComponent(pair.slice(eqIdx + 1));
|
|
19894
|
-
}
|
|
19895
|
-
}
|
|
19896
|
-
body = formData;
|
|
19897
|
-
} else {
|
|
19898
|
-
body = dataStr;
|
|
20047
|
+
body = dataStr;
|
|
20048
|
+
const hasContentType = Object.keys(filteredHeaders).some((k) => k.toLowerCase() === "content-type");
|
|
20049
|
+
if (!hasContentType) {
|
|
20050
|
+
filteredHeaders["Content-Type"] = "application/x-www-form-urlencoded";
|
|
19899
20051
|
}
|
|
19900
20052
|
}
|
|
19901
20053
|
}
|
|
@@ -20021,16 +20173,19 @@ Used by your team (${teamOnly.length} more):
|
|
|
20021
20173
|
process.exit(1);
|
|
20022
20174
|
}
|
|
20023
20175
|
});
|
|
20024
|
-
var callCommand = new Command10("call").description("Make a proxy call to a connected integration").argument("<integration>", "Integration name (e.g., hubspot, slack)").argument("[method]", "HTTP method (GET, POST, PUT, DELETE)").argument("[path]", "API path (e.g., /crm/v3/contacts)").option("--workspace <name-or-id>", "Workspace name or ID").option("--body <
|
|
20176
|
+
var callCommand = new Command10("call").description("Make a proxy call to a connected integration").argument("<integration>", "Integration name (e.g., hubspot, slack)").argument("[method]", "HTTP method (GET, POST, PUT, DELETE)").argument("[path]", "API path (e.g., /crm/v3/contacts)").option("--workspace <name-or-id>", "Workspace name or ID").option("--body <body>", "Request body: JSON, or a raw string when a non-JSON Content-Type header is set").option("--form", "Send the body as application/x-www-form-urlencoded (Stripe, Twilio, OAuth token endpoints). Accepts a JSON object (form-encoded, nested keys in bracket notation) or an already-encoded a=b&c=d string").option("--header <header>", "Request header (repeatable). Content-Type decides how --body is encoded", (val, prev) => [...prev, val], []).option("--query <query>", "Query string (e.g., limit=10&offset=0)").option("--curl <command>", 'Parse a curl command (paste from Chrome DevTools "Copy as cURL")').option("--curl-file <file>", "Read curl command from a file").action(async (integration, method, path2, opts, command) => {
|
|
20025
20177
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20026
20178
|
const credentials = requireAuth();
|
|
20027
20179
|
const client = new ApiClient(credentials);
|
|
20028
20180
|
const { workspaceId } = await resolveWorkspace2(client, opts);
|
|
20029
20181
|
let finalMethod;
|
|
20030
20182
|
let finalPath;
|
|
20031
|
-
|
|
20183
|
+
let headers = {};
|
|
20032
20184
|
let body;
|
|
20033
20185
|
let query = opts.query;
|
|
20186
|
+
if (opts.form) {
|
|
20187
|
+
headers = setHeader(headers, "Content-Type", FORM_CONTENT_TYPE);
|
|
20188
|
+
}
|
|
20034
20189
|
if (opts.curl || opts.curlFile) {
|
|
20035
20190
|
let curlStr = opts.curl;
|
|
20036
20191
|
if (opts.curlFile) {
|
|
@@ -20047,7 +20202,7 @@ var callCommand = new Command10("call").description("Make a proxy call to a conn
|
|
|
20047
20202
|
finalPath = result.path;
|
|
20048
20203
|
if (result.query)
|
|
20049
20204
|
query = query || result.query;
|
|
20050
|
-
|
|
20205
|
+
headers = opts.form ? setHeader({ ...result.headers }, "Content-Type", FORM_CONTENT_TYPE) : { ...result.headers };
|
|
20051
20206
|
body = result.body;
|
|
20052
20207
|
} catch (err) {
|
|
20053
20208
|
console.error("Failed to parse curl command:", err instanceof Error ? err.message : err);
|
|
@@ -20058,13 +20213,13 @@ var callCommand = new Command10("call").description("Make a proxy call to a conn
|
|
|
20058
20213
|
finalPath = path2;
|
|
20059
20214
|
for (const h of opts.header || []) {
|
|
20060
20215
|
const [key, ...rest] = h.split(":");
|
|
20061
|
-
headers
|
|
20216
|
+
headers = setHeader(headers, key.trim(), rest.join(":").trim());
|
|
20062
20217
|
}
|
|
20063
20218
|
if (opts.body) {
|
|
20064
20219
|
try {
|
|
20065
|
-
body =
|
|
20066
|
-
} catch {
|
|
20067
|
-
console.error("Invalid
|
|
20220
|
+
body = parseBodyArgument(opts.body, findHeader(headers, "Content-Type"));
|
|
20221
|
+
} catch (err) {
|
|
20222
|
+
console.error(err instanceof Error ? err.message : "Invalid --body");
|
|
20068
20223
|
process.exit(1);
|
|
20069
20224
|
}
|
|
20070
20225
|
}
|
|
@@ -21819,7 +21974,7 @@ function generateIntroSkill(ctx) {
|
|
|
21819
21974
|
lines.push("| `runwork entities list` | Browse data across apps |");
|
|
21820
21975
|
lines.push("| `runwork entities records <name>` | Query entity records |");
|
|
21821
21976
|
lines.push("| `runwork workflows trigger <name>` | Trigger a workflow |");
|
|
21822
|
-
lines.push("| `runwork integrations call <id> <method> <path>` | Call an integration API |");
|
|
21977
|
+
lines.push("| `runwork integrations call <id> <method> <path>` | Call an integration API (`--body` JSON; `--form` for form-encoded APIs such as Stripe) |");
|
|
21823
21978
|
lines.push("| `runwork integrations search <query>` | Search 3,200+ available integrations |");
|
|
21824
21979
|
lines.push("| `runwork logs` | View app logs |");
|
|
21825
21980
|
lines.push("| `runwork doctor` | Check system health (`--fix` auto-remediates git auth/remote; `--check <names>` scopes output) |");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runwork",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Runwork, Inc. <info@runwork.ai> (https://www.runwork.ai)",
|