@topolo/cli 0.6.2 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/generate-wrangler.mjs +17 -1
- package/dist/index.js +491 -69
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -109,9 +109,13 @@ this too:
|
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
111
|
topolo actions call app_topolo_mail.messages.send --data '{"mailboxId":"primary","subject":"Hello"}' --confirm
|
|
112
|
+
topolo actions upload app_topolo_brand.brand_assets.upload --file ./logo.png --data '{"assetId":"asset_1"}' --confirm
|
|
113
|
+
topolo actions upload app_topolo_chat.uploads.request --file ./launch.mp4 --data '{}' --complete-data '{"conversationKind":"channel","conversationId":"channel_1"}' --confirm
|
|
112
114
|
topolo api mail POST /api/mail/mailboxes/primary/messages --data '{"subject":"Hello"}' --confirm
|
|
113
115
|
```
|
|
114
116
|
|
|
117
|
+
`actions upload` requires the resolved action to publish an upload contract. It reads the file, infers common MIME types or accepts `--content-type`, then follows either the declared inline-base64 mapping or the complete initiate/upload/finish URL-session flow. `--complete-data` supplies only fields required by a declared completion action; the upload/session id is mapped from the initiation response by contract.
|
|
118
|
+
|
|
115
119
|
## Commands
|
|
116
120
|
|
|
117
121
|
### `topolo whoami`
|
|
@@ -288,6 +288,7 @@ function emitWranglerToml(targetName, target) {
|
|
|
288
288
|
lines.push(`directory = ${tomlString(target.assets.directory || target.outputDir || 'dist')}`);
|
|
289
289
|
if (target.assets.binding) lines.push(`binding = ${tomlString(target.assets.binding)}`);
|
|
290
290
|
else lines.push(`binding = "ASSETS"`);
|
|
291
|
+
if (target.assets.htmlHandling) lines.push(`html_handling = ${tomlString(target.assets.htmlHandling)}`);
|
|
291
292
|
if (target.assets.notFoundHandling) lines.push(`not_found_handling = ${tomlString(target.assets.notFoundHandling)}`);
|
|
292
293
|
if (target.assets.runWorkerFirst !== undefined) lines.push(`run_worker_first = ${tomlString(target.assets.runWorkerFirst)}`);
|
|
293
294
|
lines.push('');
|
|
@@ -390,6 +391,7 @@ function emitWranglerToml(targetName, target) {
|
|
|
390
391
|
lines.push(`[env.${env}.assets]`);
|
|
391
392
|
lines.push(`directory = ${tomlString(target.assets.directory || target.outputDir || 'dist')}`);
|
|
392
393
|
lines.push(`binding = ${tomlString(target.assets.binding || 'ASSETS')}`);
|
|
394
|
+
if (target.assets.htmlHandling) lines.push(`html_handling = ${tomlString(target.assets.htmlHandling)}`);
|
|
393
395
|
if (target.assets.notFoundHandling) lines.push(`not_found_handling = ${tomlString(target.assets.notFoundHandling)}`);
|
|
394
396
|
if (target.assets.runWorkerFirst !== undefined) lines.push(`run_worker_first = ${tomlString(target.assets.runWorkerFirst)}`);
|
|
395
397
|
lines.push('');
|
|
@@ -572,6 +574,20 @@ function emitBindings(lines, bindings, env) {
|
|
|
572
574
|
lines.push(`service = ${tomlString(service)}`);
|
|
573
575
|
lines.push('');
|
|
574
576
|
}
|
|
577
|
+
// Cloudflare Secrets Store bindings — the durable data-at-rest key escrow
|
|
578
|
+
// (docs/data-at-rest-encryption-architecture.md). Store ids differ per env
|
|
579
|
+
// (per-env Cloudflare accounts), so the shape mirrors d1/kv: per-env config
|
|
580
|
+
// objects under the binding.
|
|
581
|
+
for (const s of bindings.secretsStore || []) {
|
|
582
|
+
if (!bindingAppliesToEnv(s, env)) continue;
|
|
583
|
+
const cfg = s[env];
|
|
584
|
+
if (!cfg) continue;
|
|
585
|
+
lines.push(`[[env.${env}.secrets_store_secrets]]`);
|
|
586
|
+
lines.push(`binding = ${tomlString(s.binding)}`);
|
|
587
|
+
lines.push(`store_id = ${tomlString(cfg.storeId)}`);
|
|
588
|
+
lines.push(`secret_name = ${tomlString(cfg.secretName)}`);
|
|
589
|
+
lines.push('');
|
|
590
|
+
}
|
|
575
591
|
for (const a of bindings.ai || []) {
|
|
576
592
|
lines.push(`[env.${env}.ai]`);
|
|
577
593
|
lines.push(`binding = ${tomlString(a.binding)}`);
|
|
@@ -595,7 +611,7 @@ function emitCloudcontrol() {
|
|
|
595
611
|
deployableTargets.flatMap((target) => target.environments || ENVS),
|
|
596
612
|
);
|
|
597
613
|
cc.deploy_method = deployableTargets.length ? 'wrangler' : 'none';
|
|
598
|
-
cc.ci_provider = existing.ci_provider || '
|
|
614
|
+
cc.ci_provider = existing.ci_provider || 'local';
|
|
599
615
|
cc.quality_policy = existing.quality_policy || {
|
|
600
616
|
profile: 'first_party_app',
|
|
601
617
|
coverage: { mode: 'ratchet' },
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
|
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { stdin as input } from "process";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ../topolo-sdk/dist/index.js
|
|
13
13
|
function applyAuthHeaders(headers, credential) {
|
|
14
14
|
if (credential.kind === "api_key") {
|
|
15
15
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -37,19 +37,54 @@ var TopoloSdkError = class extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
var TopoloAuthError = class extends TopoloSdkError {
|
|
40
|
-
|
|
40
|
+
status;
|
|
41
|
+
body;
|
|
42
|
+
service;
|
|
43
|
+
path;
|
|
44
|
+
requestId;
|
|
45
|
+
retryAfter;
|
|
46
|
+
constructor(message, code = "auth_error", options = {}) {
|
|
41
47
|
super(code, message);
|
|
42
48
|
this.name = "TopoloAuthError";
|
|
49
|
+
this.status = options.status ?? 401;
|
|
50
|
+
this.body = options.body;
|
|
51
|
+
this.service = options.service;
|
|
52
|
+
this.path = options.path;
|
|
53
|
+
this.requestId = options.requestId;
|
|
54
|
+
this.retryAfter = options.retryAfter;
|
|
43
55
|
}
|
|
44
56
|
};
|
|
45
57
|
var TopoloPermissionError = class extends TopoloSdkError {
|
|
46
58
|
required;
|
|
47
|
-
|
|
59
|
+
status;
|
|
60
|
+
body;
|
|
61
|
+
service;
|
|
62
|
+
path;
|
|
63
|
+
requestId;
|
|
64
|
+
retryAfter;
|
|
65
|
+
constructor(message, required, options = {}) {
|
|
48
66
|
super("permission_denied", message);
|
|
49
67
|
this.name = "TopoloPermissionError";
|
|
50
68
|
this.required = required;
|
|
69
|
+
this.status = options.status ?? 403;
|
|
70
|
+
this.body = options.body;
|
|
71
|
+
this.service = options.service;
|
|
72
|
+
this.path = options.path;
|
|
73
|
+
this.requestId = options.requestId;
|
|
74
|
+
this.retryAfter = options.retryAfter;
|
|
51
75
|
}
|
|
52
76
|
};
|
|
77
|
+
function extractHttpErrorCode(body) {
|
|
78
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
|
|
79
|
+
const record4 = body;
|
|
80
|
+
if (typeof record4.code === "string" && record4.code.trim()) return record4.code.trim();
|
|
81
|
+
const nested = record4.error;
|
|
82
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
83
|
+
const code = nested.code;
|
|
84
|
+
if (typeof code === "string" && code.trim()) return code.trim();
|
|
85
|
+
}
|
|
86
|
+
return "http_error";
|
|
87
|
+
}
|
|
53
88
|
var TopoloConfirmationRequiredError = class extends TopoloSdkError {
|
|
54
89
|
constructor(message) {
|
|
55
90
|
super("confirmation_required", message);
|
|
@@ -61,13 +96,17 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
61
96
|
body;
|
|
62
97
|
service;
|
|
63
98
|
path;
|
|
64
|
-
|
|
65
|
-
|
|
99
|
+
requestId;
|
|
100
|
+
retryAfter;
|
|
101
|
+
constructor(service, path8, status, body, message, options = {}) {
|
|
102
|
+
super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path8}`);
|
|
66
103
|
this.name = "TopoloHttpError";
|
|
67
104
|
this.status = status;
|
|
68
105
|
this.body = body;
|
|
69
106
|
this.service = service;
|
|
70
107
|
this.path = path8;
|
|
108
|
+
this.requestId = options.requestId;
|
|
109
|
+
this.retryAfter = options.retryAfter;
|
|
71
110
|
}
|
|
72
111
|
};
|
|
73
112
|
var APP_API_URLS = {
|
|
@@ -137,7 +176,9 @@ var APPLICATIONS = {
|
|
|
137
176
|
"domains": [
|
|
138
177
|
"admin.topolo.app"
|
|
139
178
|
],
|
|
140
|
-
"routes": [
|
|
179
|
+
"routes": [
|
|
180
|
+
"admin.topolo.app"
|
|
181
|
+
],
|
|
141
182
|
"appId": null
|
|
142
183
|
},
|
|
143
184
|
{
|
|
@@ -185,7 +226,7 @@ var APPLICATIONS = {
|
|
|
185
226
|
"appDir": "TopoloAuth",
|
|
186
227
|
"name": "topolo-auth",
|
|
187
228
|
"packageName": "topolo-auth",
|
|
188
|
-
"productionUrl": "https://auth.topolo.app
|
|
229
|
+
"productionUrl": "https://auth.topolo.app",
|
|
189
230
|
"services": [
|
|
190
231
|
"auth"
|
|
191
232
|
],
|
|
@@ -221,7 +262,9 @@ var APPLICATIONS = {
|
|
|
221
262
|
"domains": [
|
|
222
263
|
"backup.topolo.app"
|
|
223
264
|
],
|
|
224
|
-
"routes": [
|
|
265
|
+
"routes": [
|
|
266
|
+
"backup.topolo.app"
|
|
267
|
+
],
|
|
225
268
|
"appId": "backup"
|
|
226
269
|
}
|
|
227
270
|
]
|
|
@@ -305,7 +348,9 @@ var APPLICATIONS = {
|
|
|
305
348
|
"domains": [
|
|
306
349
|
"bugfix.topolo.app"
|
|
307
350
|
],
|
|
308
|
-
"routes": [
|
|
351
|
+
"routes": [
|
|
352
|
+
"bugfix.topolo.app"
|
|
353
|
+
],
|
|
309
354
|
"appId": "bugfix"
|
|
310
355
|
}
|
|
311
356
|
]
|
|
@@ -327,7 +372,9 @@ var APPLICATIONS = {
|
|
|
327
372
|
"domains": [
|
|
328
373
|
"bytes.topolo.app"
|
|
329
374
|
],
|
|
330
|
-
"routes": [
|
|
375
|
+
"routes": [
|
|
376
|
+
"bytes.topolo.app"
|
|
377
|
+
],
|
|
331
378
|
"appId": "bytes"
|
|
332
379
|
},
|
|
333
380
|
{
|
|
@@ -357,7 +404,9 @@ var APPLICATIONS = {
|
|
|
357
404
|
"domains": [
|
|
358
405
|
"calendar.topolo.app"
|
|
359
406
|
],
|
|
360
|
-
"routes": [
|
|
407
|
+
"routes": [
|
|
408
|
+
"calendar.topolo.app"
|
|
409
|
+
],
|
|
361
410
|
"appId": "calendar"
|
|
362
411
|
},
|
|
363
412
|
{
|
|
@@ -387,9 +436,7 @@ var APPLICATIONS = {
|
|
|
387
436
|
"domains": [
|
|
388
437
|
"campaigns.topolo.app"
|
|
389
438
|
],
|
|
390
|
-
"routes": [
|
|
391
|
-
"campaigns.topolo.app"
|
|
392
|
-
],
|
|
439
|
+
"routes": [],
|
|
393
440
|
"appId": "campaigns"
|
|
394
441
|
},
|
|
395
442
|
{
|
|
@@ -419,7 +466,9 @@ var APPLICATIONS = {
|
|
|
419
466
|
"domains": [
|
|
420
467
|
"capacity.topolo.app"
|
|
421
468
|
],
|
|
422
|
-
"routes": [
|
|
469
|
+
"routes": [
|
|
470
|
+
"capacity.topolo.app"
|
|
471
|
+
],
|
|
423
472
|
"appId": "capacity"
|
|
424
473
|
}
|
|
425
474
|
]
|
|
@@ -441,7 +490,9 @@ var APPLICATIONS = {
|
|
|
441
490
|
"domains": [
|
|
442
491
|
"chat.topolo.app"
|
|
443
492
|
],
|
|
444
|
-
"routes": [
|
|
493
|
+
"routes": [
|
|
494
|
+
"chat.topolo.app"
|
|
495
|
+
],
|
|
445
496
|
"appId": "chat"
|
|
446
497
|
},
|
|
447
498
|
{
|
|
@@ -479,7 +530,9 @@ var APPLICATIONS = {
|
|
|
479
530
|
"domains": [
|
|
480
531
|
"guest.topolo.app"
|
|
481
532
|
],
|
|
482
|
-
"routes": [
|
|
533
|
+
"routes": [
|
|
534
|
+
"guest.topolo.app"
|
|
535
|
+
],
|
|
483
536
|
"appId": null
|
|
484
537
|
},
|
|
485
538
|
{
|
|
@@ -489,7 +542,9 @@ var APPLICATIONS = {
|
|
|
489
542
|
"domains": [
|
|
490
543
|
"commerce.topolo.app"
|
|
491
544
|
],
|
|
492
|
-
"routes": [
|
|
545
|
+
"routes": [
|
|
546
|
+
"commerce.topolo.app"
|
|
547
|
+
],
|
|
493
548
|
"appId": "commerce"
|
|
494
549
|
},
|
|
495
550
|
{
|
|
@@ -499,7 +554,9 @@ var APPLICATIONS = {
|
|
|
499
554
|
"domains": [
|
|
500
555
|
"station-commerce.topolo.app"
|
|
501
556
|
],
|
|
502
|
-
"routes": [
|
|
557
|
+
"routes": [
|
|
558
|
+
"station-commerce.topolo.app"
|
|
559
|
+
],
|
|
503
560
|
"appId": null
|
|
504
561
|
}
|
|
505
562
|
]
|
|
@@ -677,7 +734,9 @@ var APPLICATIONS = {
|
|
|
677
734
|
"domains": [
|
|
678
735
|
"docs.topolo.app"
|
|
679
736
|
],
|
|
680
|
-
"routes": [
|
|
737
|
+
"routes": [
|
|
738
|
+
"docs.topolo.app"
|
|
739
|
+
],
|
|
681
740
|
"appId": null
|
|
682
741
|
},
|
|
683
742
|
{
|
|
@@ -707,7 +766,9 @@ var APPLICATIONS = {
|
|
|
707
766
|
"domains": [
|
|
708
767
|
"feed-analytics-api.topolo.app"
|
|
709
768
|
],
|
|
710
|
-
"routes": [
|
|
769
|
+
"routes": [
|
|
770
|
+
"feed-analytics-api.topolo.app"
|
|
771
|
+
],
|
|
711
772
|
"appId": null
|
|
712
773
|
},
|
|
713
774
|
{
|
|
@@ -717,7 +778,9 @@ var APPLICATIONS = {
|
|
|
717
778
|
"domains": [
|
|
718
779
|
"feed-api.topolo.app"
|
|
719
780
|
],
|
|
720
|
-
"routes": [
|
|
781
|
+
"routes": [
|
|
782
|
+
"feed-api.topolo.app"
|
|
783
|
+
],
|
|
721
784
|
"appId": null
|
|
722
785
|
},
|
|
723
786
|
{
|
|
@@ -727,7 +790,9 @@ var APPLICATIONS = {
|
|
|
727
790
|
"domains": [
|
|
728
791
|
"feed.topolo.app"
|
|
729
792
|
],
|
|
730
|
-
"routes": [
|
|
793
|
+
"routes": [
|
|
794
|
+
"feed.topolo.app"
|
|
795
|
+
],
|
|
731
796
|
"appId": "feed"
|
|
732
797
|
}
|
|
733
798
|
]
|
|
@@ -809,7 +874,9 @@ var APPLICATIONS = {
|
|
|
809
874
|
"domains": [
|
|
810
875
|
"forms.topolo.app"
|
|
811
876
|
],
|
|
812
|
-
"routes": [
|
|
877
|
+
"routes": [
|
|
878
|
+
"forms.topolo.app"
|
|
879
|
+
],
|
|
813
880
|
"appId": "forms"
|
|
814
881
|
},
|
|
815
882
|
{
|
|
@@ -839,7 +906,9 @@ var APPLICATIONS = {
|
|
|
839
906
|
"domains": [
|
|
840
907
|
"home.topolo.app"
|
|
841
908
|
],
|
|
842
|
-
"routes": [
|
|
909
|
+
"routes": [
|
|
910
|
+
"home.topolo.app"
|
|
911
|
+
],
|
|
843
912
|
"appId": "home"
|
|
844
913
|
},
|
|
845
914
|
{
|
|
@@ -929,7 +998,9 @@ var APPLICATIONS = {
|
|
|
929
998
|
"domains": [
|
|
930
999
|
"learn.topolo.app"
|
|
931
1000
|
],
|
|
932
|
-
"routes": [
|
|
1001
|
+
"routes": [
|
|
1002
|
+
"learn.topolo.app"
|
|
1003
|
+
],
|
|
933
1004
|
"appId": "learn"
|
|
934
1005
|
},
|
|
935
1006
|
{
|
|
@@ -983,7 +1054,9 @@ var APPLICATIONS = {
|
|
|
983
1054
|
"domains": [
|
|
984
1055
|
"mail.topolo.app"
|
|
985
1056
|
],
|
|
986
|
-
"routes": [
|
|
1057
|
+
"routes": [
|
|
1058
|
+
"mail.topolo.app"
|
|
1059
|
+
],
|
|
987
1060
|
"appId": "mail"
|
|
988
1061
|
},
|
|
989
1062
|
{
|
|
@@ -1021,7 +1094,9 @@ var APPLICATIONS = {
|
|
|
1021
1094
|
"domains": [
|
|
1022
1095
|
"mdm.topolo.app"
|
|
1023
1096
|
],
|
|
1024
|
-
"routes": [
|
|
1097
|
+
"routes": [
|
|
1098
|
+
"mdm.topolo.app"
|
|
1099
|
+
],
|
|
1025
1100
|
"appId": "mdm"
|
|
1026
1101
|
}
|
|
1027
1102
|
]
|
|
@@ -1043,7 +1118,9 @@ var APPLICATIONS = {
|
|
|
1043
1118
|
"domains": [
|
|
1044
1119
|
"messages.topolo.app"
|
|
1045
1120
|
],
|
|
1046
|
-
"routes": [
|
|
1121
|
+
"routes": [
|
|
1122
|
+
"messages.topolo.app"
|
|
1123
|
+
],
|
|
1047
1124
|
"appId": "messages"
|
|
1048
1125
|
},
|
|
1049
1126
|
{
|
|
@@ -1077,11 +1154,13 @@ var APPLICATIONS = {
|
|
|
1077
1154
|
{
|
|
1078
1155
|
"name": "topolo-nexus-dashboard",
|
|
1079
1156
|
"kind": "worker",
|
|
1080
|
-
"apiUrl":
|
|
1157
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1081
1158
|
"domains": [
|
|
1082
1159
|
"nexus.topolo.app"
|
|
1083
1160
|
],
|
|
1084
|
-
"routes": [
|
|
1161
|
+
"routes": [
|
|
1162
|
+
"nexus.topolo.app"
|
|
1163
|
+
],
|
|
1085
1164
|
"appId": null
|
|
1086
1165
|
},
|
|
1087
1166
|
{
|
|
@@ -1137,7 +1216,9 @@ var APPLICATIONS = {
|
|
|
1137
1216
|
"domains": [
|
|
1138
1217
|
"observability-watch.topolo.app"
|
|
1139
1218
|
],
|
|
1140
|
-
"routes": [
|
|
1219
|
+
"routes": [
|
|
1220
|
+
"observability-watch.topolo.app"
|
|
1221
|
+
],
|
|
1141
1222
|
"appId": "observability_watch"
|
|
1142
1223
|
}
|
|
1143
1224
|
]
|
|
@@ -1209,7 +1290,9 @@ var APPLICATIONS = {
|
|
|
1209
1290
|
"domains": [
|
|
1210
1291
|
"p2p.topolo.app"
|
|
1211
1292
|
],
|
|
1212
|
-
"routes": [
|
|
1293
|
+
"routes": [
|
|
1294
|
+
"p2p.topolo.app"
|
|
1295
|
+
],
|
|
1213
1296
|
"appId": "p2p"
|
|
1214
1297
|
},
|
|
1215
1298
|
{
|
|
@@ -1324,7 +1407,9 @@ var APPLICATIONS = {
|
|
|
1324
1407
|
"domains": [
|
|
1325
1408
|
"quro.topolo.app"
|
|
1326
1409
|
],
|
|
1327
|
-
"routes": [
|
|
1410
|
+
"routes": [
|
|
1411
|
+
"quro.topolo.app"
|
|
1412
|
+
],
|
|
1328
1413
|
"appId": null
|
|
1329
1414
|
}
|
|
1330
1415
|
]
|
|
@@ -1406,7 +1491,9 @@ var APPLICATIONS = {
|
|
|
1406
1491
|
"domains": [
|
|
1407
1492
|
"studio.topolo.app"
|
|
1408
1493
|
],
|
|
1409
|
-
"routes": [
|
|
1494
|
+
"routes": [
|
|
1495
|
+
"studio.topolo.app"
|
|
1496
|
+
],
|
|
1410
1497
|
"appId": "social_studio"
|
|
1411
1498
|
},
|
|
1412
1499
|
{
|
|
@@ -1444,7 +1531,9 @@ var APPLICATIONS = {
|
|
|
1444
1531
|
"domains": [
|
|
1445
1532
|
"socialize.topolo.app"
|
|
1446
1533
|
],
|
|
1447
|
-
"routes": [
|
|
1534
|
+
"routes": [
|
|
1535
|
+
"socialize.topolo.app"
|
|
1536
|
+
],
|
|
1448
1537
|
"appId": "socialize"
|
|
1449
1538
|
},
|
|
1450
1539
|
{
|
|
@@ -1454,7 +1543,9 @@ var APPLICATIONS = {
|
|
|
1454
1543
|
"domains": [
|
|
1455
1544
|
"socialize-api.topolo.app"
|
|
1456
1545
|
],
|
|
1457
|
-
"routes": [
|
|
1546
|
+
"routes": [
|
|
1547
|
+
"socialize-api.topolo.app"
|
|
1548
|
+
],
|
|
1458
1549
|
"appId": null
|
|
1459
1550
|
},
|
|
1460
1551
|
{
|
|
@@ -1514,7 +1605,9 @@ var APPLICATIONS = {
|
|
|
1514
1605
|
"domains": [
|
|
1515
1606
|
"status.topolo.app"
|
|
1516
1607
|
],
|
|
1517
|
-
"routes": [
|
|
1608
|
+
"routes": [
|
|
1609
|
+
"status.topolo.app"
|
|
1610
|
+
],
|
|
1518
1611
|
"appId": "status"
|
|
1519
1612
|
}
|
|
1520
1613
|
]
|
|
@@ -1600,7 +1693,9 @@ var APPLICATIONS = {
|
|
|
1600
1693
|
"domains": [
|
|
1601
1694
|
"survey.topolo.app"
|
|
1602
1695
|
],
|
|
1603
|
-
"routes": [
|
|
1696
|
+
"routes": [
|
|
1697
|
+
"survey.topolo.app"
|
|
1698
|
+
],
|
|
1604
1699
|
"appId": "survey"
|
|
1605
1700
|
},
|
|
1606
1701
|
{
|
|
@@ -1630,7 +1725,9 @@ var APPLICATIONS = {
|
|
|
1630
1725
|
"domains": [
|
|
1631
1726
|
"voice.topolo.app"
|
|
1632
1727
|
],
|
|
1633
|
-
"routes": [
|
|
1728
|
+
"routes": [
|
|
1729
|
+
"voice.topolo.app"
|
|
1730
|
+
],
|
|
1634
1731
|
"appId": "voice"
|
|
1635
1732
|
},
|
|
1636
1733
|
{
|
|
@@ -1681,7 +1778,9 @@ var APPLICATIONS = {
|
|
|
1681
1778
|
"domains": [
|
|
1682
1779
|
"web.topolo.app"
|
|
1683
1780
|
],
|
|
1684
|
-
"routes": [
|
|
1781
|
+
"routes": [
|
|
1782
|
+
"web.topolo.app"
|
|
1783
|
+
],
|
|
1685
1784
|
"appId": "web"
|
|
1686
1785
|
}
|
|
1687
1786
|
]
|
|
@@ -1758,7 +1857,7 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1758
1857
|
"Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
|
|
1759
1858
|
"Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
|
|
1760
1859
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1761
|
-
"Constrain app
|
|
1860
|
+
"Constrain the app workspace-view layout wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
|
|
1762
1861
|
]
|
|
1763
1862
|
},
|
|
1764
1863
|
{
|
|
@@ -2387,6 +2486,166 @@ function isMultipleOf(value, divisor) {
|
|
|
2387
2486
|
const quotient = value / divisor;
|
|
2388
2487
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
2389
2488
|
}
|
|
2489
|
+
function parseTopoloActionUploadContract(value) {
|
|
2490
|
+
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
2491
|
+
throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
|
|
2492
|
+
}
|
|
2493
|
+
const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
|
|
2494
|
+
const fileNameField = requiredField(value.fileNameField, "fileNameField");
|
|
2495
|
+
const maxBytes = Number(value.maxBytes);
|
|
2496
|
+
const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
|
|
2497
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
|
|
2498
|
+
throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
|
|
2499
|
+
}
|
|
2500
|
+
if (value.profile === "inline-base64") {
|
|
2501
|
+
return {
|
|
2502
|
+
profile: "inline-base64",
|
|
2503
|
+
contentField: requiredField(value.contentField, "contentField"),
|
|
2504
|
+
contentTypeField,
|
|
2505
|
+
fileNameField,
|
|
2506
|
+
maxBytes,
|
|
2507
|
+
acceptedContentTypes
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
|
|
2511
|
+
const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
|
|
2512
|
+
const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
|
|
2513
|
+
if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
|
|
2514
|
+
return {
|
|
2515
|
+
profile: "url-session",
|
|
2516
|
+
fileNameField,
|
|
2517
|
+
contentTypeField,
|
|
2518
|
+
sizeField: requiredField(value.sizeField, "sizeField"),
|
|
2519
|
+
maxBytes,
|
|
2520
|
+
acceptedContentTypes,
|
|
2521
|
+
uploadUrlPath,
|
|
2522
|
+
uploadMethod,
|
|
2523
|
+
...complete ? { complete } : {}
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
function prepareTopoloActionUploadInput(contractValue, content, input3, options) {
|
|
2527
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2528
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2529
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
2530
|
+
if (bytes.byteLength === 0) {
|
|
2531
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2532
|
+
}
|
|
2533
|
+
if (bytes.byteLength > contract.maxBytes) {
|
|
2534
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2535
|
+
}
|
|
2536
|
+
return mapUploadInput(contract, bytesToBase64(bytes), input3, options);
|
|
2537
|
+
}
|
|
2538
|
+
function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input3, options) {
|
|
2539
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2540
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2541
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2542
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2543
|
+
}
|
|
2544
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2545
|
+
const byteLength = contentBase64.length / 4 * 3 - padding;
|
|
2546
|
+
if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2547
|
+
if (byteLength > contract.maxBytes) {
|
|
2548
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2549
|
+
}
|
|
2550
|
+
return mapUploadInput(contract, contentBase64, input3, options);
|
|
2551
|
+
}
|
|
2552
|
+
function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input3, options) {
|
|
2553
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2554
|
+
if (contract.profile !== "url-session") {
|
|
2555
|
+
throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
|
|
2556
|
+
}
|
|
2557
|
+
validateUploadMetadata(contract, byteLength, options);
|
|
2558
|
+
return {
|
|
2559
|
+
contract,
|
|
2560
|
+
input: {
|
|
2561
|
+
...input3,
|
|
2562
|
+
[contract.fileNameField]: options.fileName.trim(),
|
|
2563
|
+
[contract.contentTypeField]: options.contentType.trim().toLowerCase(),
|
|
2564
|
+
[contract.sizeField]: byteLength
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
function topoloUploadResponseValue(response, path8) {
|
|
2569
|
+
return path8.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
|
|
2570
|
+
}
|
|
2571
|
+
function decodeTopoloActionBase64(contentBase64) {
|
|
2572
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2573
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2574
|
+
}
|
|
2575
|
+
const binary = atob(contentBase64);
|
|
2576
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
2577
|
+
}
|
|
2578
|
+
function mapUploadInput(contract, contentBase64, input3, options) {
|
|
2579
|
+
validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
|
|
2580
|
+
const fileName = options.fileName.trim();
|
|
2581
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2582
|
+
return {
|
|
2583
|
+
...input3,
|
|
2584
|
+
[contract.contentField]: contentBase64,
|
|
2585
|
+
[contract.contentTypeField]: contentType,
|
|
2586
|
+
[contract.fileNameField]: fileName
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function validateUploadMetadata(contract, byteLength, options) {
|
|
2590
|
+
const fileName = options.fileName.trim();
|
|
2591
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2592
|
+
if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
|
|
2593
|
+
if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
|
|
2594
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2595
|
+
}
|
|
2596
|
+
if (byteLength > contract.maxBytes) {
|
|
2597
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2598
|
+
}
|
|
2599
|
+
if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
|
|
2600
|
+
throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function decodedBase64Size(contentBase64) {
|
|
2604
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2605
|
+
return contentBase64.length / 4 * 3 - padding;
|
|
2606
|
+
}
|
|
2607
|
+
function parseCompleteContract(value) {
|
|
2608
|
+
if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
|
|
2609
|
+
return {
|
|
2610
|
+
actionId: requiredPath(value.actionId, "complete.actionId"),
|
|
2611
|
+
inputField: requiredField(value.inputField, "complete.inputField"),
|
|
2612
|
+
responsePath: requiredPath(value.responsePath, "complete.responsePath")
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
function requiredPath(value, name) {
|
|
2616
|
+
const path8 = typeof value === "string" ? value.trim() : "";
|
|
2617
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path8)) {
|
|
2618
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2619
|
+
}
|
|
2620
|
+
return path8;
|
|
2621
|
+
}
|
|
2622
|
+
function unsupportedInlineProfile() {
|
|
2623
|
+
return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
|
|
2624
|
+
}
|
|
2625
|
+
function isCanonicalBase64(value) {
|
|
2626
|
+
return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
2627
|
+
}
|
|
2628
|
+
function requiredField(value, name) {
|
|
2629
|
+
const field = typeof value === "string" ? value.trim() : "";
|
|
2630
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
|
|
2631
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2632
|
+
}
|
|
2633
|
+
return field;
|
|
2634
|
+
}
|
|
2635
|
+
function matchesContentType(contentType, accepted) {
|
|
2636
|
+
return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
|
|
2637
|
+
}
|
|
2638
|
+
function bytesToBase64(bytes) {
|
|
2639
|
+
let binary = "";
|
|
2640
|
+
const chunkSize = 32768;
|
|
2641
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
2642
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
2643
|
+
}
|
|
2644
|
+
return btoa(binary);
|
|
2645
|
+
}
|
|
2646
|
+
function isRecord2(value) {
|
|
2647
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2648
|
+
}
|
|
2390
2649
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2391
2650
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
2392
2651
|
var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
|
|
@@ -2463,6 +2722,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2463
2722
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
2464
2723
|
}
|
|
2465
2724
|
}
|
|
2725
|
+
if (opts.body !== void 0 && opts.rawBody !== void 0) {
|
|
2726
|
+
throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
|
|
2727
|
+
}
|
|
2466
2728
|
const headers = new Headers();
|
|
2467
2729
|
headers.set("Accept", "application/json");
|
|
2468
2730
|
if (opts.body !== void 0) headers.set("Content-Type", "application/json");
|
|
@@ -2497,7 +2759,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2497
2759
|
return await this.fetchImpl(urlStr, {
|
|
2498
2760
|
method,
|
|
2499
2761
|
headers: new Headers(headers),
|
|
2500
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2762
|
+
body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
|
|
2501
2763
|
signal
|
|
2502
2764
|
});
|
|
2503
2765
|
} finally {
|
|
@@ -2545,14 +2807,32 @@ var TopoloClient = class _TopoloClient {
|
|
|
2545
2807
|
error: message,
|
|
2546
2808
|
status: res.status
|
|
2547
2809
|
});
|
|
2548
|
-
|
|
2810
|
+
const errorContext = {
|
|
2811
|
+
status: res.status,
|
|
2812
|
+
body: parsed,
|
|
2813
|
+
service: resolvedService.serviceKey,
|
|
2814
|
+
path: opts.path,
|
|
2815
|
+
requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
|
|
2816
|
+
retryAfter: res.headers.get("Retry-After") || void 0
|
|
2817
|
+
};
|
|
2818
|
+
if (res.status === 401) {
|
|
2819
|
+
throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
|
|
2820
|
+
}
|
|
2549
2821
|
if (res.status === 403) {
|
|
2550
2822
|
throw new TopoloPermissionError(
|
|
2551
2823
|
describeError(parsed, "Permission denied"),
|
|
2552
|
-
extractRequired(parsed)
|
|
2824
|
+
extractRequired(parsed),
|
|
2825
|
+
errorContext
|
|
2553
2826
|
);
|
|
2554
2827
|
}
|
|
2555
|
-
throw new TopoloHttpError(
|
|
2828
|
+
throw new TopoloHttpError(
|
|
2829
|
+
resolvedService.serviceKey,
|
|
2830
|
+
opts.path,
|
|
2831
|
+
res.status,
|
|
2832
|
+
parsed,
|
|
2833
|
+
message,
|
|
2834
|
+
errorContext
|
|
2835
|
+
);
|
|
2556
2836
|
}
|
|
2557
2837
|
this.emit({
|
|
2558
2838
|
phase: "response",
|
|
@@ -2711,7 +2991,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2711
2991
|
}
|
|
2712
2992
|
const entries = [];
|
|
2713
2993
|
for (const item of raw) {
|
|
2714
|
-
if (!
|
|
2994
|
+
if (!isRecord3(item)) continue;
|
|
2715
2995
|
const s = item;
|
|
2716
2996
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2717
2997
|
if (!appId) continue;
|
|
@@ -2786,8 +3066,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2786
3066
|
"app_topolo_auth.service_api_key_resources.list",
|
|
2787
3067
|
{ appId: service.appId }
|
|
2788
3068
|
);
|
|
2789
|
-
const payload =
|
|
2790
|
-
if (!
|
|
3069
|
+
const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
|
|
3070
|
+
if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
|
|
2791
3071
|
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
2792
3072
|
}
|
|
2793
3073
|
return {
|
|
@@ -2893,6 +3173,71 @@ var TopoloClient = class _TopoloClient {
|
|
|
2893
3173
|
const entry = await this.getAction(action);
|
|
2894
3174
|
return this.executeResolvedAction(entry, input3, options);
|
|
2895
3175
|
}
|
|
3176
|
+
async uploadAction(action, content, input3, options) {
|
|
3177
|
+
const entry = await this.getAction(action);
|
|
3178
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3179
|
+
if (contract.profile === "url-session") {
|
|
3180
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
3181
|
+
return this.executeUrlSessionUpload(entry, contract, bytes, input3, options);
|
|
3182
|
+
}
|
|
3183
|
+
const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input3, options);
|
|
3184
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3185
|
+
}
|
|
3186
|
+
async uploadActionBase64(action, contentBase64, input3, options) {
|
|
3187
|
+
const entry = await this.getAction(action);
|
|
3188
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3189
|
+
if (contract.profile === "url-session") {
|
|
3190
|
+
return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input3, options);
|
|
3191
|
+
}
|
|
3192
|
+
const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input3, options);
|
|
3193
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3194
|
+
}
|
|
3195
|
+
async executeUrlSessionUpload(entry, contract, bytes, input3, options) {
|
|
3196
|
+
const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input3, options);
|
|
3197
|
+
const initiation = await this.executeResolvedAction(entry, prepared.input, options);
|
|
3198
|
+
const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
|
|
3199
|
+
if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
|
|
3200
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
|
|
3201
|
+
}
|
|
3202
|
+
await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
|
|
3203
|
+
if (!contract.complete) return initiation;
|
|
3204
|
+
const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
|
|
3205
|
+
if (typeof completionValue !== "string" || !completionValue.trim()) {
|
|
3206
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
|
|
3207
|
+
}
|
|
3208
|
+
const completion = await this.getAction(contract.complete.actionId);
|
|
3209
|
+
return this.executeResolvedAction(completion, {
|
|
3210
|
+
...options.completionInput ?? {},
|
|
3211
|
+
[contract.complete.inputField]: completionValue
|
|
3212
|
+
}, options);
|
|
3213
|
+
}
|
|
3214
|
+
async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
|
|
3215
|
+
const service = entry.appSlug || entry.appId;
|
|
3216
|
+
const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
|
|
3217
|
+
const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
|
|
3218
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3219
|
+
if (target.origin === new URL(resolved.baseUrl).origin) {
|
|
3220
|
+
await this.request({
|
|
3221
|
+
service,
|
|
3222
|
+
baseUrl: resolved.baseUrl,
|
|
3223
|
+
path: target.toString(),
|
|
3224
|
+
method,
|
|
3225
|
+
rawBody: body,
|
|
3226
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3227
|
+
resource: options.resource,
|
|
3228
|
+
confirm: options.confirm
|
|
3229
|
+
});
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
const response = await this.fetchImpl(target, {
|
|
3233
|
+
method,
|
|
3234
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3235
|
+
body
|
|
3236
|
+
});
|
|
3237
|
+
if (!response.ok) {
|
|
3238
|
+
throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
2896
3241
|
async validateActionInput(action, input3 = {}) {
|
|
2897
3242
|
const entry = await this.getAction(action);
|
|
2898
3243
|
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input3) };
|
|
@@ -3051,7 +3396,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
3051
3396
|
return null;
|
|
3052
3397
|
}
|
|
3053
3398
|
function normalizeApiKeyResourceMetadata(input3) {
|
|
3054
|
-
if (!
|
|
3399
|
+
if (!isRecord3(input3)) return null;
|
|
3055
3400
|
const resourceType = readString(input3.resourceType ?? input3.resource_type);
|
|
3056
3401
|
const label = readString(input3.label);
|
|
3057
3402
|
const emptyLabel = readString(input3.emptyLabel ?? input3.empty_label);
|
|
@@ -3085,7 +3430,7 @@ function normalizeApiKeyResourceMetadataList(input3) {
|
|
|
3085
3430
|
return resources;
|
|
3086
3431
|
}
|
|
3087
3432
|
function normalizeBindableResource(input3) {
|
|
3088
|
-
if (!
|
|
3433
|
+
if (!isRecord3(input3)) return null;
|
|
3089
3434
|
const resourceType = readString(input3.resourceType ?? input3.resource_type);
|
|
3090
3435
|
const resourceId = readString(input3.resourceId ?? input3.resource_id);
|
|
3091
3436
|
if (!resourceType || !resourceId) return null;
|
|
@@ -3096,13 +3441,13 @@ function normalizeBindableResource(input3) {
|
|
|
3096
3441
|
slug: readOptionalString(input3.slug),
|
|
3097
3442
|
status: readOptionalString(input3.status),
|
|
3098
3443
|
organizationId: readOptionalString(input3.organizationId ?? input3.organization_id),
|
|
3099
|
-
metadata:
|
|
3444
|
+
metadata: isRecord3(input3.metadata) ? input3.metadata : {}
|
|
3100
3445
|
};
|
|
3101
3446
|
}
|
|
3102
3447
|
function normalizeResourceBindings(input3) {
|
|
3103
3448
|
if (!Array.isArray(input3)) return [];
|
|
3104
3449
|
return input3.flatMap((binding) => {
|
|
3105
|
-
if (!
|
|
3450
|
+
if (!isRecord3(binding)) return [];
|
|
3106
3451
|
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
3107
3452
|
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
3108
3453
|
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
@@ -3115,7 +3460,7 @@ function readOptionalString(value) {
|
|
|
3115
3460
|
const normalized = readString(value);
|
|
3116
3461
|
return normalized || null;
|
|
3117
3462
|
}
|
|
3118
|
-
function
|
|
3463
|
+
function isRecord3(value) {
|
|
3119
3464
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3120
3465
|
}
|
|
3121
3466
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -3187,14 +3532,14 @@ function catalogSearchQuery(options) {
|
|
|
3187
3532
|
return query;
|
|
3188
3533
|
}
|
|
3189
3534
|
function normalizeCatalogPage(value) {
|
|
3190
|
-
if (!
|
|
3535
|
+
if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
|
|
3191
3536
|
return {
|
|
3192
3537
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
3193
3538
|
hasMore: value.hasMore === true
|
|
3194
3539
|
};
|
|
3195
3540
|
}
|
|
3196
3541
|
function normalizeServiceCatalogEntry(item) {
|
|
3197
|
-
if (!
|
|
3542
|
+
if (!isRecord3(item)) return null;
|
|
3198
3543
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
3199
3544
|
if (!appId) return null;
|
|
3200
3545
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -3504,7 +3849,7 @@ function createTopolo(options) {
|
|
|
3504
3849
|
};
|
|
3505
3850
|
}
|
|
3506
3851
|
|
|
3507
|
-
//
|
|
3852
|
+
// ../topolo-sdk/dist/node/credential-store.js
|
|
3508
3853
|
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
3509
3854
|
import { existsSync } from "fs";
|
|
3510
3855
|
import { homedir, platform } from "os";
|
|
@@ -3518,23 +3863,50 @@ var TopoloSdkError2 = class extends Error {
|
|
|
3518
3863
|
}
|
|
3519
3864
|
};
|
|
3520
3865
|
var TopoloAuthError2 = class extends TopoloSdkError2 {
|
|
3521
|
-
|
|
3866
|
+
status;
|
|
3867
|
+
body;
|
|
3868
|
+
service;
|
|
3869
|
+
path;
|
|
3870
|
+
requestId;
|
|
3871
|
+
retryAfter;
|
|
3872
|
+
constructor(message, code = "auth_error", options = {}) {
|
|
3522
3873
|
super(code, message);
|
|
3523
3874
|
this.name = "TopoloAuthError";
|
|
3875
|
+
this.status = options.status ?? 401;
|
|
3876
|
+
this.body = options.body;
|
|
3877
|
+
this.service = options.service;
|
|
3878
|
+
this.path = options.path;
|
|
3879
|
+
this.requestId = options.requestId;
|
|
3880
|
+
this.retryAfter = options.retryAfter;
|
|
3524
3881
|
}
|
|
3525
3882
|
};
|
|
3883
|
+
function extractHttpErrorCode2(body) {
|
|
3884
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
|
|
3885
|
+
const record4 = body;
|
|
3886
|
+
if (typeof record4.code === "string" && record4.code.trim()) return record4.code.trim();
|
|
3887
|
+
const nested = record4.error;
|
|
3888
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
3889
|
+
const code = nested.code;
|
|
3890
|
+
if (typeof code === "string" && code.trim()) return code.trim();
|
|
3891
|
+
}
|
|
3892
|
+
return "http_error";
|
|
3893
|
+
}
|
|
3526
3894
|
var TopoloHttpError2 = class extends TopoloSdkError2 {
|
|
3527
3895
|
status;
|
|
3528
3896
|
body;
|
|
3529
3897
|
service;
|
|
3530
3898
|
path;
|
|
3531
|
-
|
|
3532
|
-
|
|
3899
|
+
requestId;
|
|
3900
|
+
retryAfter;
|
|
3901
|
+
constructor(service, path8, status, body, message, options = {}) {
|
|
3902
|
+
super(extractHttpErrorCode2(body), message ?? `HTTP ${status} from ${service}${path8}`);
|
|
3533
3903
|
this.name = "TopoloHttpError";
|
|
3534
3904
|
this.status = status;
|
|
3535
3905
|
this.body = body;
|
|
3536
3906
|
this.service = service;
|
|
3537
3907
|
this.path = path8;
|
|
3908
|
+
this.requestId = options.requestId;
|
|
3909
|
+
this.retryAfter = options.retryAfter;
|
|
3538
3910
|
}
|
|
3539
3911
|
};
|
|
3540
3912
|
var APP_API_URLS2 = {
|
|
@@ -4011,6 +4383,10 @@ function fail(err, mode) {
|
|
|
4011
4383
|
process.stderr.write(`Error: ${payload.message}
|
|
4012
4384
|
`);
|
|
4013
4385
|
if (payload.code) process.stderr.write(`Code: ${payload.code}
|
|
4386
|
+
`);
|
|
4387
|
+
if (payload.status) process.stderr.write(`HTTP: ${payload.status}
|
|
4388
|
+
`);
|
|
4389
|
+
if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
|
|
4014
4390
|
`);
|
|
4015
4391
|
if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
|
|
4016
4392
|
`);
|
|
@@ -4029,6 +4405,12 @@ function normalizeError(err) {
|
|
|
4029
4405
|
return {
|
|
4030
4406
|
message: err.message,
|
|
4031
4407
|
code,
|
|
4408
|
+
status: err.status,
|
|
4409
|
+
service: err.service,
|
|
4410
|
+
path: err.path,
|
|
4411
|
+
requestId: err.requestId,
|
|
4412
|
+
retryAfter: err.retryAfter,
|
|
4413
|
+
details: err.body,
|
|
4032
4414
|
hint: code ? hintMap[code] : void 0,
|
|
4033
4415
|
exitCode: code === "auth_error" || code === "confirmation_required" ? 2 : code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
|
|
4034
4416
|
};
|
|
@@ -4397,6 +4779,7 @@ function formatPretty(info) {
|
|
|
4397
4779
|
|
|
4398
4780
|
// src/commands/actions.ts
|
|
4399
4781
|
import { readFileSync as readFileSync2 } from "fs";
|
|
4782
|
+
import { basename, extname } from "path";
|
|
4400
4783
|
|
|
4401
4784
|
// src/commands/api.ts
|
|
4402
4785
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
@@ -4551,6 +4934,30 @@ function registerActionsCommand(program2) {
|
|
|
4551
4934
|
fail(err, mode);
|
|
4552
4935
|
}
|
|
4553
4936
|
});
|
|
4937
|
+
actions.command("upload <action>").description("Upload one local file through an action-declared upload contract").requiredOption("--file <path>", "Local file to upload").option("--content-type <type>", "MIME type; inferred for common file extensions").option("-d, --data <json-or-@file>", "Additional JSON action input or @path to a JSON file", "{}").option("--complete-data <json-or-@file>", "Additional input for a declared completion action", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Acknowledge a mutating upload action").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
4938
|
+
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
4939
|
+
try {
|
|
4940
|
+
const topolo = await requiredClient();
|
|
4941
|
+
const contentType = opts.contentType?.trim().toLowerCase() || inferContentType(opts.file);
|
|
4942
|
+
if (!contentType) throw new Error("--content-type is required for this file extension");
|
|
4943
|
+
const resource = parseResourceOption(opts.resourceType, opts.resourceId);
|
|
4944
|
+
const result = await topolo.client.uploadAction(
|
|
4945
|
+
action,
|
|
4946
|
+
readFileSync2(opts.file),
|
|
4947
|
+
parseJsonObject(opts.data || "{}"),
|
|
4948
|
+
{
|
|
4949
|
+
fileName: basename(opts.file),
|
|
4950
|
+
contentType,
|
|
4951
|
+
completionInput: parseJsonObject(opts.completeData || "{}"),
|
|
4952
|
+
confirm: opts.confirm === true,
|
|
4953
|
+
...resource ? { resource } : {}
|
|
4954
|
+
}
|
|
4955
|
+
);
|
|
4956
|
+
emit(result, mode, () => JSON.stringify(result, null, 2));
|
|
4957
|
+
} catch (err) {
|
|
4958
|
+
fail(err, mode);
|
|
4959
|
+
}
|
|
4960
|
+
});
|
|
4554
4961
|
actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
4555
4962
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
4556
4963
|
try {
|
|
@@ -4631,6 +5038,21 @@ function parseJsonObject(value) {
|
|
|
4631
5038
|
}
|
|
4632
5039
|
return parsed;
|
|
4633
5040
|
}
|
|
5041
|
+
function inferContentType(path8) {
|
|
5042
|
+
return {
|
|
5043
|
+
".avif": "image/avif",
|
|
5044
|
+
".gif": "image/gif",
|
|
5045
|
+
".heic": "image/heic",
|
|
5046
|
+
".jpeg": "image/jpeg",
|
|
5047
|
+
".jpg": "image/jpeg",
|
|
5048
|
+
".mov": "video/quicktime",
|
|
5049
|
+
".mp4": "video/mp4",
|
|
5050
|
+
".pdf": "application/pdf",
|
|
5051
|
+
".png": "image/png",
|
|
5052
|
+
".svg": "image/svg+xml",
|
|
5053
|
+
".webp": "image/webp"
|
|
5054
|
+
}[extname(path8).toLowerCase()] ?? null;
|
|
5055
|
+
}
|
|
4634
5056
|
function formatActionRows(rows, nextCursor = null) {
|
|
4635
5057
|
if (rows.length === 0) return "No actions available for this credential.";
|
|
4636
5058
|
const serviceWidth = Math.max(7, ...rows.map((row) => (row.appSlug || row.appId).length));
|
|
@@ -11692,16 +12114,16 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
|
|
|
11692
12114
|
const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
|
|
11693
12115
|
const violations = [];
|
|
11694
12116
|
function shouldSkipFile(filePath) {
|
|
11695
|
-
const
|
|
12117
|
+
const basename2 = path7.basename(filePath);
|
|
11696
12118
|
const relativePath2 = path7.relative(root, filePath);
|
|
11697
|
-
if (
|
|
11698
|
-
if (
|
|
11699
|
-
if (
|
|
11700
|
-
if (
|
|
11701
|
-
if (/^cloudcontrol-deployment-report-.+\.json$/.test(
|
|
11702
|
-
if (
|
|
11703
|
-
if (
|
|
11704
|
-
if (
|
|
12119
|
+
if (basename2 === "package-lock.json" || basename2 === "pnpm-lock.yaml" || basename2 === "yarn.lock") return true;
|
|
12120
|
+
if (basename2 === "topolo.cloudcontrol.json") return true;
|
|
12121
|
+
if (basename2 === "wrangler.toml" || basename2 === "wrangler.jsonc") return true;
|
|
12122
|
+
if (basename2 === "consumer-graph.json") return true;
|
|
12123
|
+
if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename2)) return true;
|
|
12124
|
+
if (basename2.endsWith(".generated.ts")) return true;
|
|
12125
|
+
if (basename2.endsWith(".test.ts") || basename2.endsWith(".test.tsx") || basename2.endsWith(".spec.ts")) return true;
|
|
12126
|
+
if (basename2 === ".env.production") return true;
|
|
11705
12127
|
if (relativePath2 === path7.join("src", "environment.ts") || relativePath2 === path7.join("src", "environmentUrls.ts") || relativePath2.endsWith(path7.join("src", "environment.ts")) || relativePath2.endsWith(path7.join("src", "lib", "environment.ts")) || relativePath2 === path7.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
|
|
11706
12128
|
if (relativePath2.includes(`${path7.sep}test${path7.sep}`) || relativePath2.includes(`${path7.sep}tests${path7.sep}`) || relativePath2.includes(`${path7.sep}e2e${path7.sep}`)) return true;
|
|
11707
12129
|
if (relativePath2.startsWith(`scripts${path7.sep}`) || relativePath2.includes(`${path7.sep}scripts${path7.sep}`)) return true;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"topoloPublishedVersion": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
|
+
"topoloPublishedVersion": "0.6.5",
|
|
5
5
|
"description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"skills"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build:sdk": "pnpm --dir ../topolo-sdk build",
|
|
15
|
+
"build:sdk": "python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- pnpm --dir ../topolo-sdk build",
|
|
16
16
|
"prebuild": "pnpm run build:sdk",
|
|
17
17
|
"build": "tsup && node scripts/vendor-generator.mjs",
|
|
18
18
|
"dev": "tsup --watch",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"commander": "^12.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@topolo/sdk": "
|
|
32
|
+
"@topolo/sdk": "file:../topolo-sdk",
|
|
33
33
|
"@types/node": "^20.12.0",
|
|
34
34
|
"@vitest/coverage-v8": "3.2.6",
|
|
35
35
|
"tsup": "^8.0.0",
|