@topolo/cli 0.1.28 → 0.1.30
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 +401 -455
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -65,54 +65,53 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
var SERVICES = {
|
|
68
|
-
agent: "https://agent
|
|
68
|
+
agent: "https://agent.topolo.app",
|
|
69
69
|
auth: "https://auth.topolo.app",
|
|
70
70
|
backup: "https://backup.topolo.app",
|
|
71
71
|
books: "https://books.topolo.app",
|
|
72
72
|
bugfix: "https://bugfix.topolo.app",
|
|
73
|
-
bytes: "https://bytes
|
|
73
|
+
bytes: "https://bytes.topolo.app",
|
|
74
74
|
calendar: "https://calendar.topolo.app",
|
|
75
75
|
capacity: "https://capacity.topolo.app",
|
|
76
76
|
chat: "https://chat.topolo.app",
|
|
77
|
-
commerce: "https://commerce
|
|
77
|
+
commerce: "https://commerce.topolo.app",
|
|
78
78
|
compose: "https://compose.topolo.app",
|
|
79
79
|
consent: "https://consent.topolo.app",
|
|
80
|
-
crm: "https://crm
|
|
80
|
+
crm: "https://crm.topolo.app",
|
|
81
81
|
developers: "https://developers.topolo.app",
|
|
82
82
|
director: "https://director.topolo.app",
|
|
83
|
-
feed: "https://feed
|
|
83
|
+
feed: "https://feed.topolo.app",
|
|
84
84
|
flow: "https://flow.topolo.app",
|
|
85
|
-
forecast: "https://forecast
|
|
85
|
+
forecast: "https://forecast.topolo.app",
|
|
86
86
|
forms: "https://forms.topolo.app",
|
|
87
87
|
home: "https://home.topolo.app",
|
|
88
88
|
insights: "https://insights.topolo.app",
|
|
89
89
|
inventory: "https://inventory.topolo.app",
|
|
90
|
-
learn: "https://learn
|
|
90
|
+
learn: "https://learn.topolo.app",
|
|
91
91
|
localize: "https://localize.topolo.app",
|
|
92
92
|
mail: "https://mail.topolo.app",
|
|
93
|
-
mdm: "https://mdm
|
|
93
|
+
mdm: "https://mdm.topolo.app",
|
|
94
94
|
messages: "https://messages.topolo.app",
|
|
95
95
|
nexus: "https://nexus-api.topolo.app",
|
|
96
96
|
notify: "https://notify.topolo.app",
|
|
97
97
|
observability_watch: "https://observability-watch.topolo.app",
|
|
98
|
-
one: "https://
|
|
98
|
+
one: "https://www.topolo.app",
|
|
99
99
|
p2p: "https://p2p.topolo.app",
|
|
100
100
|
pay: "https://pay.topolo.app",
|
|
101
101
|
people: "https://people.topolo.app",
|
|
102
|
-
pulse: "https://pulse.topolo.app",
|
|
103
102
|
"quro.api": "https://api.ol0.me",
|
|
104
103
|
"quro.redirect": "https://ol0.me",
|
|
105
|
-
roadmapper: "https://roadmapper
|
|
104
|
+
roadmapper: "https://roadmapper.topolo.app",
|
|
106
105
|
sign: "https://sign.topolo.app",
|
|
107
|
-
social_studio: "https://studio
|
|
108
|
-
socialize: "https://socialize
|
|
106
|
+
social_studio: "https://studio.topolo.app",
|
|
107
|
+
socialize: "https://socialize.topolo.app",
|
|
109
108
|
spaces: "https://spaces.topolo.app",
|
|
110
109
|
status: "https://status.topolo.app",
|
|
111
110
|
success: "https://success.topolo.app",
|
|
112
111
|
support: "https://support.topolo.app",
|
|
113
112
|
survey: "https://survey.topolo.app",
|
|
114
113
|
voice: "https://voice.topolo.app",
|
|
115
|
-
web: "https://web
|
|
114
|
+
web: "https://web.topolo.app"
|
|
116
115
|
};
|
|
117
116
|
var APPLICATIONS = {
|
|
118
117
|
"admin": {
|
|
@@ -161,17 +160,15 @@ var APPLICATIONS = {
|
|
|
161
160
|
"agent.topolo.app"
|
|
162
161
|
],
|
|
163
162
|
"routes": [],
|
|
164
|
-
"serviceId":
|
|
163
|
+
"serviceId": "agent"
|
|
165
164
|
},
|
|
166
165
|
{
|
|
167
166
|
"name": "topolo-agent-api",
|
|
168
167
|
"kind": "worker",
|
|
169
|
-
"apiUrl":
|
|
170
|
-
"domains": [
|
|
171
|
-
"agent-api.topolo.app"
|
|
172
|
-
],
|
|
168
|
+
"apiUrl": null,
|
|
169
|
+
"domains": [],
|
|
173
170
|
"routes": [],
|
|
174
|
-
"serviceId":
|
|
171
|
+
"serviceId": null
|
|
175
172
|
}
|
|
176
173
|
]
|
|
177
174
|
},
|
|
@@ -291,17 +288,15 @@ var APPLICATIONS = {
|
|
|
291
288
|
"bytes.topolo.app"
|
|
292
289
|
],
|
|
293
290
|
"routes": [],
|
|
294
|
-
"serviceId":
|
|
291
|
+
"serviceId": "bytes"
|
|
295
292
|
},
|
|
296
293
|
{
|
|
297
294
|
"name": "topolo-bytes-api",
|
|
298
295
|
"kind": "worker",
|
|
299
|
-
"apiUrl":
|
|
300
|
-
"domains": [
|
|
301
|
-
"bytes-api.topolo.app"
|
|
302
|
-
],
|
|
296
|
+
"apiUrl": null,
|
|
297
|
+
"domains": [],
|
|
303
298
|
"routes": [],
|
|
304
|
-
"serviceId":
|
|
299
|
+
"serviceId": null
|
|
305
300
|
}
|
|
306
301
|
]
|
|
307
302
|
},
|
|
@@ -400,12 +395,10 @@ var APPLICATIONS = {
|
|
|
400
395
|
{
|
|
401
396
|
"name": "topolo-commerce-api",
|
|
402
397
|
"kind": "worker",
|
|
403
|
-
"apiUrl":
|
|
404
|
-
"domains": [
|
|
405
|
-
"commerce-api.topolo.app"
|
|
406
|
-
],
|
|
398
|
+
"apiUrl": null,
|
|
399
|
+
"domains": [],
|
|
407
400
|
"routes": [],
|
|
408
|
-
"serviceId":
|
|
401
|
+
"serviceId": null
|
|
409
402
|
},
|
|
410
403
|
{
|
|
411
404
|
"name": "topolo-commerce-guest-web",
|
|
@@ -425,7 +418,7 @@ var APPLICATIONS = {
|
|
|
425
418
|
"commerce.topolo.app"
|
|
426
419
|
],
|
|
427
420
|
"routes": [],
|
|
428
|
-
"serviceId":
|
|
421
|
+
"serviceId": "commerce"
|
|
429
422
|
},
|
|
430
423
|
{
|
|
431
424
|
"name": "topolo-commerce-station-web",
|
|
@@ -512,12 +505,10 @@ var APPLICATIONS = {
|
|
|
512
505
|
{
|
|
513
506
|
"name": "topolo-crm-api",
|
|
514
507
|
"kind": "worker",
|
|
515
|
-
"apiUrl":
|
|
516
|
-
"domains": [
|
|
517
|
-
"crm-api.topolo.app"
|
|
518
|
-
],
|
|
508
|
+
"apiUrl": null,
|
|
509
|
+
"domains": [],
|
|
519
510
|
"routes": [],
|
|
520
|
-
"serviceId":
|
|
511
|
+
"serviceId": null
|
|
521
512
|
},
|
|
522
513
|
{
|
|
523
514
|
"name": "topolo-crm-app",
|
|
@@ -527,7 +518,7 @@ var APPLICATIONS = {
|
|
|
527
518
|
"crm.topolo.app"
|
|
528
519
|
],
|
|
529
520
|
"routes": [],
|
|
530
|
-
"serviceId":
|
|
521
|
+
"serviceId": "crm"
|
|
531
522
|
}
|
|
532
523
|
]
|
|
533
524
|
},
|
|
@@ -650,12 +641,10 @@ var APPLICATIONS = {
|
|
|
650
641
|
{
|
|
651
642
|
"name": "topolo-feed-api",
|
|
652
643
|
"kind": "worker",
|
|
653
|
-
"apiUrl":
|
|
654
|
-
"domains": [
|
|
655
|
-
"feed-api.topolo.app"
|
|
656
|
-
],
|
|
644
|
+
"apiUrl": null,
|
|
645
|
+
"domains": [],
|
|
657
646
|
"routes": [],
|
|
658
|
-
"serviceId":
|
|
647
|
+
"serviceId": null
|
|
659
648
|
},
|
|
660
649
|
{
|
|
661
650
|
"name": "topolo-feed-web",
|
|
@@ -665,7 +654,7 @@ var APPLICATIONS = {
|
|
|
665
654
|
"feed.topolo.app"
|
|
666
655
|
],
|
|
667
656
|
"routes": [],
|
|
668
|
-
"serviceId":
|
|
657
|
+
"serviceId": "feed"
|
|
669
658
|
}
|
|
670
659
|
]
|
|
671
660
|
},
|
|
@@ -717,17 +706,15 @@ var APPLICATIONS = {
|
|
|
717
706
|
"forecast.topolo.app"
|
|
718
707
|
],
|
|
719
708
|
"routes": [],
|
|
720
|
-
"serviceId":
|
|
709
|
+
"serviceId": "forecast"
|
|
721
710
|
},
|
|
722
711
|
{
|
|
723
712
|
"name": "topolo-forecast-api",
|
|
724
713
|
"kind": "worker",
|
|
725
|
-
"apiUrl":
|
|
726
|
-
"domains": [
|
|
727
|
-
"forecast-api.topolo.app"
|
|
728
|
-
],
|
|
714
|
+
"apiUrl": null,
|
|
715
|
+
"domains": [],
|
|
729
716
|
"routes": [],
|
|
730
|
-
"serviceId":
|
|
717
|
+
"serviceId": null
|
|
731
718
|
}
|
|
732
719
|
]
|
|
733
720
|
},
|
|
@@ -869,17 +856,15 @@ var APPLICATIONS = {
|
|
|
869
856
|
"learn.topolo.app"
|
|
870
857
|
],
|
|
871
858
|
"routes": [],
|
|
872
|
-
"serviceId":
|
|
859
|
+
"serviceId": "learn"
|
|
873
860
|
},
|
|
874
861
|
{
|
|
875
862
|
"name": "topolo-learn-api",
|
|
876
863
|
"kind": "worker",
|
|
877
|
-
"apiUrl":
|
|
878
|
-
"domains": [
|
|
879
|
-
"learn-api.topolo.app"
|
|
880
|
-
],
|
|
864
|
+
"apiUrl": null,
|
|
865
|
+
"domains": [],
|
|
881
866
|
"routes": [],
|
|
882
|
-
"serviceId":
|
|
867
|
+
"serviceId": null
|
|
883
868
|
}
|
|
884
869
|
]
|
|
885
870
|
},
|
|
@@ -950,12 +935,10 @@ var APPLICATIONS = {
|
|
|
950
935
|
{
|
|
951
936
|
"name": "topolo-mdm-api",
|
|
952
937
|
"kind": "worker",
|
|
953
|
-
"apiUrl":
|
|
954
|
-
"domains": [
|
|
955
|
-
"mdm-api.topolo.app"
|
|
956
|
-
],
|
|
938
|
+
"apiUrl": null,
|
|
939
|
+
"domains": [],
|
|
957
940
|
"routes": [],
|
|
958
|
-
"serviceId":
|
|
941
|
+
"serviceId": null
|
|
959
942
|
},
|
|
960
943
|
{
|
|
961
944
|
"name": "topolo-mdm-console",
|
|
@@ -965,7 +948,7 @@ var APPLICATIONS = {
|
|
|
965
948
|
"mdm.topolo.app"
|
|
966
949
|
],
|
|
967
950
|
"routes": [],
|
|
968
|
-
"serviceId":
|
|
951
|
+
"serviceId": "mdm"
|
|
969
952
|
}
|
|
970
953
|
]
|
|
971
954
|
},
|
|
@@ -1098,20 +1081,23 @@ var APPLICATIONS = {
|
|
|
1098
1081
|
{
|
|
1099
1082
|
"name": "topolo-one",
|
|
1100
1083
|
"kind": "worker",
|
|
1101
|
-
"apiUrl":
|
|
1102
|
-
"domains": [
|
|
1103
|
-
|
|
1084
|
+
"apiUrl": "https://one.topolo.app",
|
|
1085
|
+
"domains": [
|
|
1086
|
+
"one.topolo.app"
|
|
1087
|
+
],
|
|
1088
|
+
"routes": [
|
|
1089
|
+
"one.topolo.app/*"
|
|
1090
|
+
],
|
|
1104
1091
|
"serviceId": null
|
|
1105
1092
|
},
|
|
1106
1093
|
{
|
|
1107
1094
|
"name": "topolo-one-api",
|
|
1108
1095
|
"kind": "worker",
|
|
1109
|
-
"apiUrl": "https://
|
|
1096
|
+
"apiUrl": "https://www.topolo.app",
|
|
1110
1097
|
"domains": [
|
|
1111
|
-
"
|
|
1098
|
+
"www.topolo.app"
|
|
1112
1099
|
],
|
|
1113
1100
|
"routes": [
|
|
1114
|
-
"one.topolo.app/*",
|
|
1115
1101
|
"www.topolo.app/*"
|
|
1116
1102
|
],
|
|
1117
1103
|
"serviceId": "one"
|
|
@@ -1221,36 +1207,6 @@ var APPLICATIONS = {
|
|
|
1221
1207
|
}
|
|
1222
1208
|
]
|
|
1223
1209
|
},
|
|
1224
|
-
"pulse": {
|
|
1225
|
-
"id": "pulse",
|
|
1226
|
-
"appDir": "TopoloPulse",
|
|
1227
|
-
"name": "topolo-pulse",
|
|
1228
|
-
"packageName": "topolo-pulse",
|
|
1229
|
-
"productionUrl": "https://pulse.topolo.app",
|
|
1230
|
-
"services": [
|
|
1231
|
-
"pulse"
|
|
1232
|
-
],
|
|
1233
|
-
"deployTargets": [
|
|
1234
|
-
{
|
|
1235
|
-
"name": "topolo-pulse",
|
|
1236
|
-
"kind": "worker",
|
|
1237
|
-
"apiUrl": "https://pulse.topolo.app",
|
|
1238
|
-
"domains": [
|
|
1239
|
-
"pulse.topolo.app"
|
|
1240
|
-
],
|
|
1241
|
-
"routes": [],
|
|
1242
|
-
"serviceId": "pulse"
|
|
1243
|
-
},
|
|
1244
|
-
{
|
|
1245
|
-
"name": "topolo-pulse-api",
|
|
1246
|
-
"kind": "worker",
|
|
1247
|
-
"apiUrl": null,
|
|
1248
|
-
"domains": [],
|
|
1249
|
-
"routes": [],
|
|
1250
|
-
"serviceId": null
|
|
1251
|
-
}
|
|
1252
|
-
]
|
|
1253
|
-
},
|
|
1254
1210
|
"quro": {
|
|
1255
1211
|
"id": "quro",
|
|
1256
1212
|
"appDir": "TopoloQuro",
|
|
@@ -1316,17 +1272,15 @@ var APPLICATIONS = {
|
|
|
1316
1272
|
"roadmapper.topolo.app"
|
|
1317
1273
|
],
|
|
1318
1274
|
"routes": [],
|
|
1319
|
-
"serviceId":
|
|
1275
|
+
"serviceId": "roadmapper"
|
|
1320
1276
|
},
|
|
1321
1277
|
{
|
|
1322
1278
|
"name": "topolo-roadmapper-api",
|
|
1323
1279
|
"kind": "worker",
|
|
1324
|
-
"apiUrl":
|
|
1325
|
-
"domains": [
|
|
1326
|
-
"roadmapper-api.topolo.app"
|
|
1327
|
-
],
|
|
1280
|
+
"apiUrl": null,
|
|
1281
|
+
"domains": [],
|
|
1328
1282
|
"routes": [],
|
|
1329
|
-
"serviceId":
|
|
1283
|
+
"serviceId": null
|
|
1330
1284
|
}
|
|
1331
1285
|
]
|
|
1332
1286
|
},
|
|
@@ -1378,17 +1332,15 @@ var APPLICATIONS = {
|
|
|
1378
1332
|
"studio.topolo.app"
|
|
1379
1333
|
],
|
|
1380
1334
|
"routes": [],
|
|
1381
|
-
"serviceId":
|
|
1335
|
+
"serviceId": "social_studio"
|
|
1382
1336
|
},
|
|
1383
1337
|
{
|
|
1384
1338
|
"name": "topolo-social-studio-api",
|
|
1385
1339
|
"kind": "worker",
|
|
1386
|
-
"apiUrl":
|
|
1387
|
-
"domains": [
|
|
1388
|
-
"studio-api.topolo.app"
|
|
1389
|
-
],
|
|
1340
|
+
"apiUrl": null,
|
|
1341
|
+
"domains": [],
|
|
1390
1342
|
"routes": [],
|
|
1391
|
-
"serviceId":
|
|
1343
|
+
"serviceId": null
|
|
1392
1344
|
},
|
|
1393
1345
|
{
|
|
1394
1346
|
"name": "topolo-social-studio-queue",
|
|
@@ -1418,17 +1370,15 @@ var APPLICATIONS = {
|
|
|
1418
1370
|
"socialize.topolo.app"
|
|
1419
1371
|
],
|
|
1420
1372
|
"routes": [],
|
|
1421
|
-
"serviceId":
|
|
1373
|
+
"serviceId": "socialize"
|
|
1422
1374
|
},
|
|
1423
1375
|
{
|
|
1424
1376
|
"name": "topolo-socialize-api",
|
|
1425
1377
|
"kind": "worker",
|
|
1426
|
-
"apiUrl":
|
|
1427
|
-
"domains": [
|
|
1428
|
-
"socialize-api.topolo.app"
|
|
1429
|
-
],
|
|
1378
|
+
"apiUrl": null,
|
|
1379
|
+
"domains": [],
|
|
1430
1380
|
"routes": [],
|
|
1431
|
-
"serviceId":
|
|
1381
|
+
"serviceId": null
|
|
1432
1382
|
},
|
|
1433
1383
|
{
|
|
1434
1384
|
"name": "topolo-socialize-scheduler",
|
|
@@ -1629,12 +1579,10 @@ var APPLICATIONS = {
|
|
|
1629
1579
|
{
|
|
1630
1580
|
"name": "topolo-web-api",
|
|
1631
1581
|
"kind": "worker",
|
|
1632
|
-
"apiUrl":
|
|
1633
|
-
"domains": [
|
|
1634
|
-
"web-api.topolo.app"
|
|
1635
|
-
],
|
|
1582
|
+
"apiUrl": null,
|
|
1583
|
+
"domains": [],
|
|
1636
1584
|
"routes": [],
|
|
1637
|
-
"serviceId":
|
|
1585
|
+
"serviceId": null
|
|
1638
1586
|
},
|
|
1639
1587
|
{
|
|
1640
1588
|
"name": "topolo-web-runtime",
|
|
@@ -1657,12 +1605,12 @@ var APPLICATIONS = {
|
|
|
1657
1605
|
"web.topolo.app"
|
|
1658
1606
|
],
|
|
1659
1607
|
"routes": [],
|
|
1660
|
-
"serviceId":
|
|
1608
|
+
"serviceId": "web"
|
|
1661
1609
|
}
|
|
1662
1610
|
]
|
|
1663
1611
|
}
|
|
1664
1612
|
};
|
|
1665
|
-
var APPLICATION_REQUIREMENTS_VERSION = "2026-
|
|
1613
|
+
var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
|
|
1666
1614
|
var APPLICATION_REQUIREMENTS = [
|
|
1667
1615
|
{
|
|
1668
1616
|
id: "platform-metadata",
|
|
@@ -1718,16 +1666,20 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1718
1666
|
summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
|
|
1719
1667
|
appliesTo: ["browser"],
|
|
1720
1668
|
evidence: [
|
|
1721
|
-
"@topolo/
|
|
1722
|
-
"
|
|
1723
|
-
"Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
|
|
1669
|
+
"@topolo-io/app-shell is used for the platform shell where the app has authenticated UI",
|
|
1670
|
+
"@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome",
|
|
1671
|
+
"Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive",
|
|
1672
|
+
"Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges",
|
|
1673
|
+
"Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states",
|
|
1724
1674
|
"Authenticated app body content stays inside the viewport on mobile and desktop widths"
|
|
1725
1675
|
],
|
|
1726
1676
|
implementation: [
|
|
1727
|
-
"Use TopoloAppShell for authenticated app layouts",
|
|
1728
|
-
"Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
|
|
1677
|
+
"Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts",
|
|
1678
|
+
"Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome",
|
|
1729
1679
|
"Pass app-specific account actions as additive menu items instead of forking dropdowns",
|
|
1730
1680
|
"Use shared design tokens and platform UI primitives before creating local equivalents",
|
|
1681
|
+
"Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
|
|
1682
|
+
"Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
|
|
1731
1683
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1732
1684
|
"Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
|
|
1733
1685
|
]
|
|
@@ -1806,11 +1758,13 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1806
1758
|
evidence: [
|
|
1807
1759
|
"The app-owned API exposes GET /api/widget",
|
|
1808
1760
|
"The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
|
|
1761
|
+
"Every returned widget includes snapshot.summary with app-owned operational prose",
|
|
1809
1762
|
"The endpoint derives organization/user context from the app auth boundary"
|
|
1810
1763
|
],
|
|
1811
1764
|
implementation: [
|
|
1812
1765
|
"Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
|
|
1813
1766
|
"Return native app metrics or action widgets instead of generic launch-card fallbacks",
|
|
1767
|
+
"Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne",
|
|
1814
1768
|
"Add a route-level test that validates the response with validateTopoloWidgetResponse"
|
|
1815
1769
|
]
|
|
1816
1770
|
},
|
|
@@ -2052,10 +2006,7 @@ function resolveCatalogServiceUrl(entry, options = {}) {
|
|
|
2052
2006
|
if (override) return override;
|
|
2053
2007
|
const apiBaseUrl = firstString(entry.apiBaseUrl, entry.api_base_url);
|
|
2054
2008
|
if (apiBaseUrl) return apiBaseUrl;
|
|
2055
|
-
|
|
2056
|
-
const liveUrl = firstString(entry.baseUrl);
|
|
2057
|
-
if (bundledUrl && (!liveUrl || sameUrlEnvironment(bundledUrl, liveUrl))) return bundledUrl;
|
|
2058
|
-
return liveUrl ?? bundledUrl ?? null;
|
|
2009
|
+
return null;
|
|
2059
2010
|
}
|
|
2060
2011
|
function resolveServiceUrl(service, overrides) {
|
|
2061
2012
|
const override = resolveServiceUrlOverride([service], overrides);
|
|
@@ -2097,37 +2048,17 @@ function resolveServiceUrlOverride(keys, overrides) {
|
|
|
2097
2048
|
}
|
|
2098
2049
|
return null;
|
|
2099
2050
|
}
|
|
2100
|
-
function firstBundledServiceUrl(keys) {
|
|
2101
|
-
for (const key of keys) {
|
|
2102
|
-
const url = DEFAULT_SERVICE_URLS[key];
|
|
2103
|
-
if (url) return url;
|
|
2104
|
-
}
|
|
2105
|
-
return null;
|
|
2106
|
-
}
|
|
2107
2051
|
function firstString(...values) {
|
|
2108
2052
|
for (const value of values) {
|
|
2109
2053
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2110
2054
|
}
|
|
2111
2055
|
return null;
|
|
2112
2056
|
}
|
|
2113
|
-
function sameUrlEnvironment(left, right) {
|
|
2114
|
-
return urlEnvironment(left) === urlEnvironment(right);
|
|
2115
|
-
}
|
|
2116
|
-
function urlEnvironment(value) {
|
|
2117
|
-
try {
|
|
2118
|
-
const url = new URL(value);
|
|
2119
|
-
const host = url.hostname.toLowerCase();
|
|
2120
|
-
if (host === "localhost" || host === "127.0.0.1") return "local";
|
|
2121
|
-
if (host.endsWith(".stg.topolo.us") || host.includes("topolo-staging")) return "staging";
|
|
2122
|
-
if (host.endsWith(".topolo.app") || host.endsWith(".topolo.us")) return "production";
|
|
2123
|
-
return "external";
|
|
2124
|
-
} catch {
|
|
2125
|
-
return "external";
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
2057
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2129
2058
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
2130
2059
|
var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
|
|
2060
|
+
var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
|
|
2061
|
+
var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
|
|
2131
2062
|
var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2132
2063
|
var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
|
|
2133
2064
|
var CATALOG_CACHE_TTL_MS = 60 * 1e3;
|
|
@@ -2206,6 +2137,18 @@ var TopoloClient = class _TopoloClient {
|
|
|
2206
2137
|
if (opts.headers) {
|
|
2207
2138
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
2208
2139
|
}
|
|
2140
|
+
if (opts.resource) {
|
|
2141
|
+
const resourceType = opts.resource.resourceType.trim();
|
|
2142
|
+
const resourceId = opts.resource.resourceId.trim();
|
|
2143
|
+
if (!resourceType || !resourceId) {
|
|
2144
|
+
throw new TopoloSdkError(
|
|
2145
|
+
"invalid_resource_context",
|
|
2146
|
+
"Both resource.resourceType and resource.resourceId are required when resource context is provided."
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
|
|
2150
|
+
headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
|
|
2151
|
+
}
|
|
2209
2152
|
const controller = new AbortController();
|
|
2210
2153
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
2211
2154
|
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
@@ -2295,7 +2238,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2295
2238
|
async resolveLiveCallableService(serviceKey) {
|
|
2296
2239
|
const catalog = await this.listServices();
|
|
2297
2240
|
const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
|
|
2298
|
-
if (!match
|
|
2241
|
+
if (!match) return null;
|
|
2299
2242
|
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
2300
2243
|
serviceKey,
|
|
2301
2244
|
overrides: this.serviceUrls
|
|
@@ -2388,18 +2331,24 @@ var TopoloClient = class _TopoloClient {
|
|
|
2388
2331
|
const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
|
|
2389
2332
|
if (!serviceId) continue;
|
|
2390
2333
|
const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
|
|
2334
|
+
const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
|
|
2335
|
+
s.api_key_resources ?? s.apiKeyResources
|
|
2336
|
+
);
|
|
2337
|
+
const apiKeyResource = normalizeApiKeyResourceMetadata(s.api_key_resource ?? s.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
|
|
2338
|
+
const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
|
|
2391
2339
|
entries.push({
|
|
2392
2340
|
serviceId,
|
|
2393
2341
|
slug: typeof s.app_slug === "string" ? s.app_slug : null,
|
|
2394
2342
|
name: typeof s.name === "string" ? s.name : null,
|
|
2395
2343
|
description: typeof s.description === "string" ? s.description : null,
|
|
2396
|
-
baseUrl: typeof s.base_url === "string" ? s.base_url : null,
|
|
2397
2344
|
apiBaseUrl: typeof s.api_base_url === "string" ? s.api_base_url : typeof s.apiBaseUrl === "string" ? s.apiBaseUrl : null,
|
|
2398
2345
|
launchUrl: typeof s.launch_url === "string" ? s.launch_url : typeof s.launchUrl === "string" ? s.launchUrl : null,
|
|
2399
2346
|
status: typeof s.status === "string" ? s.status : null,
|
|
2400
2347
|
surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
|
|
2401
2348
|
launchable: s.launchable === true,
|
|
2402
|
-
permissions
|
|
2349
|
+
permissions,
|
|
2350
|
+
apiKeyResource,
|
|
2351
|
+
apiKeyResources
|
|
2403
2352
|
});
|
|
2404
2353
|
}
|
|
2405
2354
|
this.serviceCatalogCache = {
|
|
@@ -2480,13 +2429,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2480
2429
|
slug: app.id,
|
|
2481
2430
|
name: app.name ?? app.id,
|
|
2482
2431
|
description: null,
|
|
2483
|
-
baseUrl: app.productionUrl ?? null,
|
|
2484
2432
|
apiBaseUrl: null,
|
|
2485
2433
|
launchUrl: app.productionUrl ?? null,
|
|
2486
2434
|
status: "bundled",
|
|
2487
2435
|
surfaceKind: "application",
|
|
2488
2436
|
launchable: Boolean(app.productionUrl),
|
|
2489
|
-
permissions: []
|
|
2437
|
+
permissions: [],
|
|
2438
|
+
apiKeyResource: null,
|
|
2439
|
+
apiKeyResources: []
|
|
2490
2440
|
}));
|
|
2491
2441
|
}
|
|
2492
2442
|
/**
|
|
@@ -2535,6 +2485,46 @@ function resolveConfiguredServiceId(slug, serviceIds) {
|
|
|
2535
2485
|
}
|
|
2536
2486
|
return null;
|
|
2537
2487
|
}
|
|
2488
|
+
function normalizeApiKeyResourceMetadata(input2) {
|
|
2489
|
+
if (!isRecord(input2)) return null;
|
|
2490
|
+
const resourceType = readString(input2.resourceType ?? input2.resource_type);
|
|
2491
|
+
const label = readString(input2.label);
|
|
2492
|
+
const emptyLabel = readString(input2.emptyLabel ?? input2.empty_label);
|
|
2493
|
+
const resourceConcept = readString(input2.resourceConcept ?? input2.resource_concept);
|
|
2494
|
+
const canonicalResourceType = readString(input2.canonicalResourceType ?? input2.canonical_resource_type);
|
|
2495
|
+
const defaultEntitlement = readString(input2.defaultEntitlement ?? input2.default_entitlement);
|
|
2496
|
+
const exceptionPolicy = readString(input2.exceptionPolicy ?? input2.exception_policy);
|
|
2497
|
+
if (!resourceType || !label || !emptyLabel || resourceConcept !== "workspace" || canonicalResourceType !== "workspace" || defaultEntitlement !== "enterprise" || exceptionPolicy !== "app_or_organization") {
|
|
2498
|
+
return null;
|
|
2499
|
+
}
|
|
2500
|
+
return {
|
|
2501
|
+
resourceType,
|
|
2502
|
+
label,
|
|
2503
|
+
emptyLabel,
|
|
2504
|
+
resourceConcept,
|
|
2505
|
+
canonicalResourceType,
|
|
2506
|
+
defaultEntitlement,
|
|
2507
|
+
exceptionPolicy
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
function normalizeApiKeyResourceMetadataList(input2) {
|
|
2511
|
+
if (!Array.isArray(input2)) return [];
|
|
2512
|
+
const resources = [];
|
|
2513
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
2514
|
+
for (const item of input2) {
|
|
2515
|
+
const resource = normalizeApiKeyResourceMetadata(item);
|
|
2516
|
+
if (!resource || seenTypes.has(resource.resourceType)) continue;
|
|
2517
|
+
seenTypes.add(resource.resourceType);
|
|
2518
|
+
resources.push(resource);
|
|
2519
|
+
}
|
|
2520
|
+
return resources;
|
|
2521
|
+
}
|
|
2522
|
+
function readString(value) {
|
|
2523
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2524
|
+
}
|
|
2525
|
+
function isRecord(value) {
|
|
2526
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2527
|
+
}
|
|
2538
2528
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
2539
2529
|
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2540
2530
|
const configured = serviceUrls?.[serviceKey];
|
|
@@ -2573,13 +2563,6 @@ function catalogEntryLookupKeys(entry) {
|
|
|
2573
2563
|
}
|
|
2574
2564
|
return [...keys];
|
|
2575
2565
|
}
|
|
2576
|
-
function isCallableCatalogEntry(entry) {
|
|
2577
|
-
const kind = normalizeLookupKey(entry.surfaceKind || "");
|
|
2578
|
-
if (kind === "api" || kind === "technical_service" || kind === "service") return true;
|
|
2579
|
-
return Boolean(
|
|
2580
|
-
(entry.baseUrl || entry.apiBaseUrl) && catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key))
|
|
2581
|
-
);
|
|
2582
|
-
}
|
|
2583
2566
|
function normalizeActionCatalogEntry(item) {
|
|
2584
2567
|
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
2585
2568
|
const a = item;
|
|
@@ -3530,7 +3513,7 @@ function registerApiCommand(program2) {
|
|
|
3530
3513
|
).option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option(
|
|
3531
3514
|
"-q, --query <kv...>",
|
|
3532
3515
|
"Query params as key=value (repeatable), e.g. --query source=cli page=2"
|
|
3533
|
-
).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
3516
|
+
).option("--resource-type <type>", "Cataloged app-local workspace resource alias for resource-scoped API keys").option("--resource-id <id>", "Workspace resource id for resource-scoped API keys").option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
3534
3517
|
async (service, method, path4, opts) => {
|
|
3535
3518
|
const mode = resolveOutputMode(opts.json);
|
|
3536
3519
|
try {
|
|
@@ -3561,6 +3544,7 @@ function registerApiCommand(program2) {
|
|
|
3561
3544
|
}
|
|
3562
3545
|
}
|
|
3563
3546
|
const query = parseQueryPairs(opts.query);
|
|
3547
|
+
const resource = parseResourceOption(opts.resourceType, opts.resourceId);
|
|
3564
3548
|
const topolo = await buildClient();
|
|
3565
3549
|
if (!topolo) {
|
|
3566
3550
|
throw new Error(
|
|
@@ -3573,6 +3557,7 @@ function registerApiCommand(program2) {
|
|
|
3573
3557
|
path: path4,
|
|
3574
3558
|
body,
|
|
3575
3559
|
...query ? { query } : {},
|
|
3560
|
+
...resource ? { resource } : {},
|
|
3576
3561
|
confirm: opts.confirm === true
|
|
3577
3562
|
});
|
|
3578
3563
|
emit(result, mode, () => JSON.stringify(result, null, 2));
|
|
@@ -3582,6 +3567,15 @@ function registerApiCommand(program2) {
|
|
|
3582
3567
|
}
|
|
3583
3568
|
);
|
|
3584
3569
|
}
|
|
3570
|
+
function parseResourceOption(resourceType, resourceId) {
|
|
3571
|
+
const normalizedType = resourceType?.trim() || "";
|
|
3572
|
+
const normalizedId = resourceId?.trim() || "";
|
|
3573
|
+
if (!normalizedType && !normalizedId) return void 0;
|
|
3574
|
+
if (!normalizedType || !normalizedId) {
|
|
3575
|
+
throw new Error("--resource-type and --resource-id must be provided together.");
|
|
3576
|
+
}
|
|
3577
|
+
return { resourceType: normalizedType, resourceId: normalizedId };
|
|
3578
|
+
}
|
|
3585
3579
|
function resolveConfiguredServiceUrl2(service, serviceUrls) {
|
|
3586
3580
|
const normalized = service.trim().toLowerCase();
|
|
3587
3581
|
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
@@ -3627,7 +3621,7 @@ import {
|
|
|
3627
3621
|
import path from "path";
|
|
3628
3622
|
var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
|
|
3629
3623
|
var REQUIRED_AUTH_CLIENT_VERSION = "1.1.9";
|
|
3630
|
-
var REQUIRED_UI_KIT_VERSION = "0.4.
|
|
3624
|
+
var REQUIRED_UI_KIT_VERSION = "0.4.196";
|
|
3631
3625
|
function readEvidenceFile(appRoot) {
|
|
3632
3626
|
const candidate = path.join(appRoot, ".topolo", "evidence.json");
|
|
3633
3627
|
if (!isFile(candidate)) return null;
|
|
@@ -3903,7 +3897,7 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3903
3897
|
nextActions: []
|
|
3904
3898
|
});
|
|
3905
3899
|
}
|
|
3906
|
-
if (
|
|
3900
|
+
if (hasDocumentationSurface(application, evidence)) {
|
|
3907
3901
|
return mergeFinding(finding2, {
|
|
3908
3902
|
status: "met",
|
|
3909
3903
|
reason: "This is a documentation surface rather than an authenticated app workspace, so the shared application shell requirement does not apply.",
|
|
@@ -3921,7 +3915,7 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3921
3915
|
if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
|
|
3922
3916
|
return mergeFinding(finding2, {
|
|
3923
3917
|
status: "needs_review",
|
|
3924
|
-
reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION};
|
|
3918
|
+
reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; shared primitive fixes require the current kit.`,
|
|
3925
3919
|
evidence: shellEvidence,
|
|
3926
3920
|
nextActions: [
|
|
3927
3921
|
`Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
|
|
@@ -3954,13 +3948,61 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3954
3948
|
]
|
|
3955
3949
|
});
|
|
3956
3950
|
}
|
|
3951
|
+
if (shellEvidence.includes("local=raw-symbol-icons")) {
|
|
3952
|
+
return mergeFinding(finding2, {
|
|
3953
|
+
status: "needs_review",
|
|
3954
|
+
reason: "Raw symbolic UI icons are visible locally. Product apps must use TopoloPlatformIcon, TopoloAppIcon, or the shared @topolo-io/ui-kit/icons entrypoint so nav, status, and provider icons stay consistent.",
|
|
3955
|
+
evidence: shellEvidence,
|
|
3956
|
+
nextActions: [
|
|
3957
|
+
"Replace raw nav/status/provider glyphs with TopoloPlatformIcon or TopoloAppIcon",
|
|
3958
|
+
"Use @topolo-io/ui-kit/icons for lucide icons instead of text symbols",
|
|
3959
|
+
"Keep app page content on TopoloAppLayout primitives where available"
|
|
3960
|
+
]
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
if (shellEvidence.includes("local=direct-lucide-react")) {
|
|
3964
|
+
return mergeFinding(finding2, {
|
|
3965
|
+
status: "needs_review",
|
|
3966
|
+
reason: "The app imports lucide-react directly. First-party browser apps should import icons through @topolo-io/ui-kit/icons so the fleet shares one icon library contract.",
|
|
3967
|
+
evidence: shellEvidence,
|
|
3968
|
+
nextActions: [
|
|
3969
|
+
"Change direct lucide-react imports to @topolo-io/ui-kit/icons",
|
|
3970
|
+
"Use TopoloPlatformIcon for recurring platform, provider, capability, and status icons",
|
|
3971
|
+
"Remove the app-local lucide-react dependency when nothing else needs it"
|
|
3972
|
+
]
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3975
|
+
if (shellEvidence.includes("local=missing-shared-page-primitives")) {
|
|
3976
|
+
return mergeFinding(finding2, {
|
|
3977
|
+
status: "needs_review",
|
|
3978
|
+
reason: "The app uses shared shell chrome, but app-owned page content still appears to rely on local layout primitives. Use TopoloAppLayout primitives for metrics, empty states, status badges, grids, and page sections so app bodies stay visually aligned.",
|
|
3979
|
+
evidence: shellEvidence,
|
|
3980
|
+
nextActions: [
|
|
3981
|
+
"Replace repeated local stat cards, empty states, status pills, and page section wrappers with TopoloAppLayout primitives",
|
|
3982
|
+
"Keep app-owned workspace wrappers constrained with min-width: 0 and shared shell spacing tokens",
|
|
3983
|
+
"Rerun visual QA at desktop and mobile widths after moving the page body primitives"
|
|
3984
|
+
]
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
if (shellEvidence.includes("local=missing-visual-qa-fixtures")) {
|
|
3988
|
+
return mergeFinding(finding2, {
|
|
3989
|
+
status: "needs_review",
|
|
3990
|
+
reason: "The app has a browser workspace but no deterministic visual QA mode was found. Staging visual checks need fixture-backed pages so layout, icons, empty states, and loading states can be reviewed without live API/CORS noise.",
|
|
3991
|
+
evidence: shellEvidence,
|
|
3992
|
+
nextActions: [
|
|
3993
|
+
"Add a visualQa=1 or __devpreview=1 mode for local preview builds",
|
|
3994
|
+
"Provide fixture responses for overview, list/table, detail, settings, and empty/loading surfaces",
|
|
3995
|
+
"Use Playwright screenshots against the fixture mode before staging rollout"
|
|
3996
|
+
]
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3957
3999
|
const usesCanonicalAppShell = shellEvidence.includes("source=TopoloAppShell");
|
|
3958
4000
|
const usesSharedAccountPrimitive = shellEvidence.includes("source=TopoloAccountMenu");
|
|
3959
4001
|
const usesSharedSuite = usesCanonicalAppShell || usesSharedAccountPrimitive;
|
|
3960
4002
|
if (!usesSharedSuite) {
|
|
3961
4003
|
return mergeFinding(finding2, {
|
|
3962
4004
|
status: "needs_review",
|
|
3963
|
-
reason: "The app depends on
|
|
4005
|
+
reason: "The app depends on shared Topolo browser packages, but no canonical TopoloAppShell or shared account primitive is visible locally.",
|
|
3964
4006
|
evidence: shellEvidence,
|
|
3965
4007
|
nextActions: [
|
|
3966
4008
|
"Use TopoloAppShell for authenticated first-party app layouts",
|
|
@@ -4310,12 +4352,30 @@ function sharedShellEvidence(evidence) {
|
|
|
4310
4352
|
const deps = dependencyNames(evidence);
|
|
4311
4353
|
const text = evidence.source.text;
|
|
4312
4354
|
const items = [];
|
|
4355
|
+
if (deps.has("@topolo-io/app-shell")) items.push("dependency=@topolo-io/app-shell");
|
|
4313
4356
|
if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
|
|
4314
4357
|
if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
|
|
4315
4358
|
items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
|
|
4316
4359
|
}
|
|
4317
4360
|
if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
|
|
4361
|
+
if (text.includes("@topolo-io/ui-kit/icons")) items.push("source=@topolo-io/ui-kit/icons");
|
|
4362
|
+
if (text.includes("TopoloPlatformIcon")) items.push("source=TopoloPlatformIcon");
|
|
4363
|
+
const usesSharedPagePrimitives = /\bTopoloApp(?:MetricCard|Toolbar|EmptyState|StatusBadge|Page|PageHeader|Section|Grid)\b/.test(text);
|
|
4364
|
+
if (/\bTopoloApp(?:MetricCard|Toolbar|EmptyState|StatusBadge|Page|PageHeader|Section|Grid)\b/.test(text)) {
|
|
4365
|
+
items.push("source=TopoloAppLayout");
|
|
4366
|
+
}
|
|
4367
|
+
if (text.includes("@topolo-io/app-shell")) items.push("source=@topolo-io/app-shell");
|
|
4318
4368
|
if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
|
|
4369
|
+
const hasVisualQaMode = /visualQa|__devpreview|visual qa/i.test(text);
|
|
4370
|
+
const hasVisualQaFixtures = hasVisualQaMode && /fixture|visualQaResponse|visualQaApi|mock[A-Z]\w*|devpreview/i.test(text) && /fetch|useQuery|api/i.test(text);
|
|
4371
|
+
if (hasVisualQaFixtures) items.push("source=visual-qa-fixtures");
|
|
4372
|
+
if (/from\s+['"]lucide-react['"]/.test(text)) items.push("local=direct-lucide-react");
|
|
4373
|
+
if (/(?:icon|Icon)\s*:\s*['"][◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
|
|
4374
|
+
items.push("local=raw-symbol-icons");
|
|
4375
|
+
}
|
|
4376
|
+
if (/(?:<span[^>]*>|<div[^>]*>)[^<]{0,16}[◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
|
|
4377
|
+
items.push("local=raw-symbol-icons");
|
|
4378
|
+
}
|
|
4319
4379
|
if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
|
|
4320
4380
|
if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
|
|
4321
4381
|
if (text.includes("TopoloAccountMenu")) items.push("source=TopoloAccountMenu");
|
|
@@ -4323,6 +4383,12 @@ function sharedShellEvidence(evidence) {
|
|
|
4323
4383
|
if (/\baccountMenuOpen\b|\bsetAccountMenuOpen\b|\buserMenuOpen\b|\bUserMenu\b|user-menu/.test(text)) {
|
|
4324
4384
|
items.push("local=account-menu");
|
|
4325
4385
|
}
|
|
4386
|
+
if (deps.has("@topolo-io/app-shell") && !usesSharedPagePrimitives) {
|
|
4387
|
+
items.push("local=missing-shared-page-primitives");
|
|
4388
|
+
}
|
|
4389
|
+
if (hasLocalBrowserSurface(evidence) && !hasVisualQaFixtures) {
|
|
4390
|
+
items.push("local=missing-visual-qa-fixtures");
|
|
4391
|
+
}
|
|
4326
4392
|
return unique(items);
|
|
4327
4393
|
}
|
|
4328
4394
|
function hasDocumentationSurface(application, evidence) {
|
|
@@ -4331,7 +4397,7 @@ function hasDocumentationSurface(application, evidence) {
|
|
|
4331
4397
|
}
|
|
4332
4398
|
function hasLocalBrowserSurface(evidence) {
|
|
4333
4399
|
const deps = dependencyNames(evidence);
|
|
4334
|
-
if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/ui-kit")) {
|
|
4400
|
+
if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/app-shell") || deps.has("@topolo-io/ui-kit")) {
|
|
4335
4401
|
return true;
|
|
4336
4402
|
}
|
|
4337
4403
|
return evidence.source.files.some(
|
|
@@ -4775,8 +4841,11 @@ function scaffoldPackages(options) {
|
|
|
4775
4841
|
}
|
|
4776
4842
|
const packages = /* @__PURE__ */ new Set();
|
|
4777
4843
|
if (options.browser) {
|
|
4778
|
-
packages.add("@topolo-io/
|
|
4844
|
+
packages.add("@topolo-io/app-funnel");
|
|
4845
|
+
packages.add("@topolo-io/app-launcher");
|
|
4846
|
+
packages.add("@topolo-io/app-shell");
|
|
4779
4847
|
packages.add("@topolo-io/auth-client");
|
|
4848
|
+
packages.add("@topolo-io/ui-kit");
|
|
4780
4849
|
}
|
|
4781
4850
|
if (options.api) {
|
|
4782
4851
|
packages.add("@topolo-io/worker-runtime");
|
|
@@ -5063,8 +5132,8 @@ function scaffoldFiles(options) {
|
|
|
5063
5132
|
path: `${browserBase}/src/App.tsx`,
|
|
5064
5133
|
purpose: "Authenticated browser surface.",
|
|
5065
5134
|
requiredSignals: [
|
|
5066
|
-
"
|
|
5067
|
-
"uses @topolo-io/auth-client for app-origin auth flows",
|
|
5135
|
+
"bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
|
|
5136
|
+
"uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
|
|
5068
5137
|
"keeps launcher and account-menu behavior on the shared platform surface",
|
|
5069
5138
|
"passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
|
|
5070
5139
|
],
|
|
@@ -5280,10 +5349,14 @@ function packageJson(application, target, options) {
|
|
|
5280
5349
|
devDependencies["@vitejs/plugin-react"] = "^4.7.0";
|
|
5281
5350
|
if (target === "platform") {
|
|
5282
5351
|
dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
|
|
5352
|
+
dependencies["@topolo-io/app-funnel"] = topoloPackageVersion("@topolo-io/app-funnel", target);
|
|
5353
|
+
dependencies["@topolo-io/app-launcher"] = topoloPackageVersion("@topolo-io/app-launcher", target);
|
|
5354
|
+
dependencies["@topolo-io/app-shell"] = topoloPackageVersion("@topolo-io/app-shell", target);
|
|
5283
5355
|
dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
|
|
5284
5356
|
dependencies["@topolo-io/onboarding"] = topoloPackageVersion("@topolo-io/onboarding", target);
|
|
5285
5357
|
dependencies["@topolo-io/tour"] = topoloPackageVersion("@topolo-io/tour", target);
|
|
5286
5358
|
dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
|
|
5359
|
+
dependencies["react-router-dom"] = "^7.6.2";
|
|
5287
5360
|
} else {
|
|
5288
5361
|
dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
|
|
5289
5362
|
}
|
|
@@ -5327,6 +5400,9 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5327
5400
|
const platformPackageFolders = {
|
|
5328
5401
|
"@topolo/sdk": "topolo-sdk",
|
|
5329
5402
|
"@topolo/login": "topolo-login",
|
|
5403
|
+
"@topolo-io/app-funnel": "topolo-app-funnel",
|
|
5404
|
+
"@topolo-io/app-launcher": "topolo-app-launcher",
|
|
5405
|
+
"@topolo-io/app-shell": "topolo-app-shell",
|
|
5330
5406
|
"@topolo-io/auth-client": "topolo-auth-client",
|
|
5331
5407
|
"@topolo-io/auth-middleware": "topolo-auth-middleware",
|
|
5332
5408
|
"@topolo-io/worker-runtime": "topolo-worker-runtime",
|
|
@@ -5412,7 +5488,10 @@ function readme(application, target, options) {
|
|
|
5412
5488
|
lines.push(
|
|
5413
5489
|
"## Shared Topolo Surface",
|
|
5414
5490
|
"",
|
|
5415
|
-
"- `@topolo-io/
|
|
5491
|
+
"- `@topolo-io/app-funnel` provides the generated public application funnel/landing surface.",
|
|
5492
|
+
"- `@topolo-io/app-shell` provides the generated route/auth bootstrap, product shell, login, callback, and loading surfaces.",
|
|
5493
|
+
"- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
|
|
5494
|
+
"- `@topolo-io/ui-kit` provides primitive account, command palette, icon, transition, and bug reporter UI.",
|
|
5416
5495
|
"- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
5417
5496
|
"- Generated first-party browser apps start on the shared landing page at `/`, use the shared login page at `/login`, and complete sign-in on `/auth/callback` before entering the workspace shell.",
|
|
5418
5497
|
"- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
|
|
@@ -5834,13 +5913,13 @@ function platformAppTsx(application, options) {
|
|
|
5834
5913
|
const tagline = isPersonal ? "Topolo personal app" : "Topolo workspace";
|
|
5835
5914
|
const workspaceTitle = isPersonal ? "Personal app" : "Workspace";
|
|
5836
5915
|
const shellLabel = isPersonal ? "personal app shell" : "workspace shell";
|
|
5837
|
-
const signedSurfaceLabel = isPersonal ? "personal app" : "workspace";
|
|
5838
5916
|
const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
|
|
5839
5917
|
const baselineDescription = isPersonal ? "This scaffold starts on the shared landing and login surfaces, completes sign-in on the shared callback path, and renders the signed personal app inside the shared Topolo shell." : "This scaffold starts on the shared landing and login surfaces, completes sign-in on the shared callback path, and renders the signed workspace inside the shared Topolo shell.";
|
|
5840
5918
|
return [
|
|
5841
5919
|
"import { Component, useCallback, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
|
|
5842
|
-
"import {
|
|
5843
|
-
"import {
|
|
5920
|
+
"import { LandingPage } from '@topolo-io/app-funnel';",
|
|
5921
|
+
"import { LoginPage, TopoloAppLoadingScaffold, TopoloAppRoot, TopoloAuthFlowScreen, createTopoloAuth, useAuth, type AppRoute as TopoloAppRoute } from '@topolo-io/app-shell';",
|
|
5922
|
+
"import { setupBugReporterErrorCapture } from '@topolo-io/ui-kit';",
|
|
5844
5923
|
"import { GettingStarted, type OnboardingGuideStep } from '@topolo-io/onboarding';",
|
|
5845
5924
|
"import { IntroTour } from '@topolo-io/tour';",
|
|
5846
5925
|
"import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, getBrowserAuthBaseUrl, resolveAppServiceId } from './service-identity';",
|
|
@@ -5849,11 +5928,6 @@ function platformAppTsx(application, options) {
|
|
|
5849
5928
|
"// can attach the last few errors to any submitted bug report.",
|
|
5850
5929
|
"setupBugReporterErrorCapture();",
|
|
5851
5930
|
"",
|
|
5852
|
-
"type NavItem = 'home' | 'getting-started';",
|
|
5853
|
-
"type WorkspaceView = 'workspace' | 'getting-started';",
|
|
5854
|
-
"type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
|
|
5855
|
-
"type BrowserAuthModule = ReturnType<typeof createBrowserAuthSessionController>;",
|
|
5856
|
-
"",
|
|
5857
5931
|
"const APP_ID = SCAFFOLD_APP_ID;",
|
|
5858
5932
|
"const APP_NAME = SCAFFOLD_APP_NAME;",
|
|
5859
5933
|
"const AUTH_URL = getBrowserAuthBaseUrl();",
|
|
@@ -5875,69 +5949,17 @@ function platformAppTsx(application, options) {
|
|
|
5875
5949
|
" authenticated_home_path: '/',",
|
|
5876
5950
|
"};",
|
|
5877
5951
|
"",
|
|
5878
|
-
"let activeAuthServiceId = '';",
|
|
5879
|
-
"let activeAuthModule: BrowserAuthModule | null = null;",
|
|
5880
|
-
"",
|
|
5881
|
-
"function getAuthModule(serviceId: string): BrowserAuthModule {",
|
|
5882
|
-
" const normalizedServiceId = serviceId.trim();",
|
|
5883
|
-
" if (!normalizedServiceId) throw new Error(`Missing Auth service id for ${SCAFFOLD_SERVICE_SLUG}`);",
|
|
5884
|
-
" if (!activeAuthModule || activeAuthServiceId !== normalizedServiceId) {",
|
|
5885
|
-
" activeAuthServiceId = normalizedServiceId;",
|
|
5886
|
-
" activeAuthModule = createBrowserAuthSessionController({",
|
|
5887
|
-
" authServiceUrl: AUTH_URL,",
|
|
5888
|
-
" serviceId: normalizedServiceId,",
|
|
5889
|
-
" devSessionKey: `${normalizedServiceId}_session`,",
|
|
5890
|
-
" devSessionStorageEnabled: import.meta.env.DEV,",
|
|
5891
|
-
" callbackPath: '/auth/callback',",
|
|
5892
|
-
" debug: import.meta.env.DEV,",
|
|
5893
|
-
" });",
|
|
5894
|
-
" }",
|
|
5895
|
-
" return activeAuthModule;",
|
|
5896
|
-
"}",
|
|
5897
|
-
"",
|
|
5898
|
-
"function getAccessToken(): string | null {",
|
|
5899
|
-
" return activeAuthModule?.accessTokenMemory.get() ?? activeAuthModule?.accessTokenSession.get() ?? null;",
|
|
5900
|
-
"}",
|
|
5901
|
-
"",
|
|
5902
|
-
"function toShellUser(user: AuthUser | null): TopoloAppShellUser | null {",
|
|
5903
|
-
" if (!user) return null;",
|
|
5904
|
-
" return {",
|
|
5905
|
-
" id: user.id,",
|
|
5906
|
-
" name: user.name ?? user.email,",
|
|
5907
|
-
" email: user.email,",
|
|
5908
|
-
" role: user.role,",
|
|
5909
|
-
" organizationName: user.organization?.name ?? user.orgSlug ?? null,",
|
|
5910
|
-
" organizationId: user.organization?.id ?? user.orgId ?? null,",
|
|
5911
|
-
" };",
|
|
5912
|
-
"}",
|
|
5913
|
-
"",
|
|
5914
5952
|
"export default function App() {",
|
|
5915
5953
|
" return (",
|
|
5916
5954
|
" <AppErrorBoundary>",
|
|
5917
|
-
" <
|
|
5955
|
+
" <CanonicalAppRoot />",
|
|
5918
5956
|
" </AppErrorBoundary>",
|
|
5919
5957
|
" );",
|
|
5920
5958
|
"}",
|
|
5921
5959
|
"",
|
|
5922
|
-
"function
|
|
5923
|
-
" const route = getRoute();",
|
|
5924
|
-
" const [user, setUser] = useState<AuthUser | null>(null);",
|
|
5960
|
+
"function CanonicalAppRoot() {",
|
|
5925
5961
|
" const [serviceId, setServiceId] = useState('');",
|
|
5926
5962
|
" const [serviceIdentityError, setServiceIdentityError] = useState('');",
|
|
5927
|
-
" const [loading, setLoading] = useState(true);",
|
|
5928
|
-
" const [authError, setAuthError] = useState('');",
|
|
5929
|
-
" const [loginAttempted, setLoginAttempted] = useState(false);",
|
|
5930
|
-
" const [view, setView] = useState<WorkspaceView>('workspace');",
|
|
5931
|
-
" const auth = useMemo(() => serviceId ? getAuthModule(serviceId) : null, [serviceId]);",
|
|
5932
|
-
" const openWorkspace = useCallback(() => setView('workspace'), []);",
|
|
5933
|
-
" const openGettingStarted = useCallback(() => {",
|
|
5934
|
-
" setView('getting-started');",
|
|
5935
|
-
" if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });",
|
|
5936
|
-
" }, []);",
|
|
5937
|
-
" const handleNavSelect = useCallback((id: NavItem) => {",
|
|
5938
|
-
" if (id === 'getting-started') openGettingStarted();",
|
|
5939
|
-
" else openWorkspace();",
|
|
5940
|
-
" }, [openGettingStarted, openWorkspace]);",
|
|
5941
5963
|
"",
|
|
5942
5964
|
" useEffect(() => {",
|
|
5943
5965
|
" let active = true;",
|
|
@@ -5949,229 +5971,139 @@ function platformAppTsx(application, options) {
|
|
|
5949
5971
|
" .catch((error) => {",
|
|
5950
5972
|
" if (!active) return;",
|
|
5951
5973
|
" setServiceIdentityError(errorMessage(error));",
|
|
5952
|
-
" setLoading(false);",
|
|
5953
5974
|
" });",
|
|
5954
5975
|
" return () => {",
|
|
5955
5976
|
" active = false;",
|
|
5956
5977
|
" };",
|
|
5957
5978
|
" }, []);",
|
|
5958
5979
|
"",
|
|
5959
|
-
"
|
|
5960
|
-
"
|
|
5961
|
-
"
|
|
5962
|
-
"
|
|
5963
|
-
"
|
|
5964
|
-
"
|
|
5965
|
-
"
|
|
5966
|
-
"
|
|
5967
|
-
"",
|
|
5968
|
-
" if (route.kind === 'callback') {",
|
|
5969
|
-
" const result = await auth.authClient.handleAuthCallbackAsync(window.location.href);",
|
|
5970
|
-
" if (!active) return;",
|
|
5971
|
-
" if (!result.authenticated) {",
|
|
5972
|
-
" setAuthError(result.error || 'Authentication failed.');",
|
|
5973
|
-
" setLoading(false);",
|
|
5974
|
-
" return;",
|
|
5975
|
-
" }",
|
|
5976
|
-
" setUser(result.user ?? null);",
|
|
5977
|
-
" window.location.replace('/');",
|
|
5978
|
-
" return;",
|
|
5979
|
-
" }",
|
|
5980
|
-
"",
|
|
5981
|
-
" const nextUser = await auth.authClient.initialize({",
|
|
5982
|
-
" cookieRefresh: publicRoute ? 'background' : 'blocking',",
|
|
5983
|
-
" });",
|
|
5984
|
-
" if (!active) return;",
|
|
5985
|
-
" setUser(nextUser);",
|
|
5986
|
-
" setLoading(false);",
|
|
5987
|
-
" };",
|
|
5988
|
-
"",
|
|
5989
|
-
" void syncAuth().catch((error) => {",
|
|
5990
|
-
" if (!active) return;",
|
|
5991
|
-
" setAuthError(errorMessage(error));",
|
|
5992
|
-
" setLoading(false);",
|
|
5993
|
-
" });",
|
|
5994
|
-
" return () => {",
|
|
5995
|
-
" active = false;",
|
|
5996
|
-
" };",
|
|
5997
|
-
" }, [auth, route.kind]);",
|
|
5998
|
-
"",
|
|
5999
|
-
" useEffect(() => {",
|
|
6000
|
-
" if (!auth) return;",
|
|
6001
|
-
" const handleSessionUpdated = () => {",
|
|
6002
|
-
" const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
|
|
6003
|
-
" if (nextUser) setUser(nextUser);",
|
|
6004
|
-
" };",
|
|
6005
|
-
" window.addEventListener('auth:session-updated', handleSessionUpdated);",
|
|
6006
|
-
" return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
|
|
6007
|
-
" }, [auth]);",
|
|
6008
|
-
"",
|
|
6009
|
-
" useEffect(() => {",
|
|
6010
|
-
" if (!loading && user && route.kind === 'login') {",
|
|
6011
|
-
" window.location.replace('/');",
|
|
6012
|
-
" }",
|
|
6013
|
-
" }, [loading, route.kind, user]);",
|
|
6014
|
-
"",
|
|
6015
|
-
" const shellUser = useMemo(() => toShellUser(user), [user]);",
|
|
6016
|
-
" const activeServiceId = serviceId || SCAFFOLD_SERVICE_SLUG;",
|
|
5980
|
+
" const auth = useMemo(() => serviceId ? createTopoloAuth({",
|
|
5981
|
+
" authServiceUrl: AUTH_URL,",
|
|
5982
|
+
" serviceId,",
|
|
5983
|
+
" devSessionKey: `${serviceId}_session`,",
|
|
5984
|
+
" callbackPath: '/auth/callback',",
|
|
5985
|
+
" sessionStorageRestoreEnabled: import.meta.env.DEV,",
|
|
5986
|
+
" debug: import.meta.env.DEV,",
|
|
5987
|
+
" }) : null, [serviceId]);",
|
|
6017
5988
|
"",
|
|
6018
|
-
"
|
|
6019
|
-
"
|
|
6020
|
-
"
|
|
6021
|
-
"
|
|
6022
|
-
"
|
|
6023
|
-
"
|
|
6024
|
-
"
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
"
|
|
6028
|
-
|
|
6029
|
-
"
|
|
6030
|
-
"
|
|
6031
|
-
"
|
|
6032
|
-
" }",
|
|
6033
|
-
" return (",
|
|
6034
|
-
" <TopoloLoadingScreen",
|
|
6035
|
-
" appId={APP_ID}",
|
|
6036
|
-
" serviceId={SCAFFOLD_SERVICE_SLUG}",
|
|
6037
|
-
" appName={APP_NAME}",
|
|
6038
|
-
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6039
|
-
" />",
|
|
6040
|
-
" );",
|
|
6041
|
-
" }",
|
|
6042
|
-
"",
|
|
6043
|
-
" if (route.kind === 'callback') {",
|
|
6044
|
-
" if (loading) {",
|
|
6045
|
-
" return (",
|
|
6046
|
-
" <TopoloLoadingScreen",
|
|
6047
|
-
" appId={APP_ID}",
|
|
6048
|
-
" serviceId={activeServiceId}",
|
|
6049
|
-
" appName={APP_NAME}",
|
|
6050
|
-
' subtitle="Completing secure sign-in..."',
|
|
6051
|
-
" />",
|
|
6052
|
-
" );",
|
|
6053
|
-
" }",
|
|
5989
|
+
" const routes = useMemo<TopoloAppRoute[]>(() => serviceId ? [",
|
|
5990
|
+
" {",
|
|
5991
|
+
" path: '/workspace',",
|
|
5992
|
+
" nav: { id: 'home', label: '" + escapeSingleQuoted(workspaceTitle) + "' },",
|
|
5993
|
+
" loading: { shell: 'topbar', content: 'workspace', title: '" + escapeSingleQuoted(workspaceTitle) + "' },",
|
|
5994
|
+
" element: <ScaffoldWorkspace />,",
|
|
5995
|
+
" },",
|
|
5996
|
+
" {",
|
|
5997
|
+
" path: '/getting-started',",
|
|
5998
|
+
" nav: { id: 'getting-started', label: 'Getting started' },",
|
|
5999
|
+
" loading: { shell: 'topbar', content: 'card-grid', title: 'Getting started' },",
|
|
6000
|
+
" element: <ScaffoldGettingStartedRoute serviceId={serviceId} />,",
|
|
6001
|
+
" },",
|
|
6002
|
+
" ] : [], [serviceId]);",
|
|
6054
6003
|
"",
|
|
6004
|
+
" if (serviceIdentityError) {",
|
|
6055
6005
|
" return (",
|
|
6056
6006
|
" <TopoloAuthFlowScreen",
|
|
6057
6007
|
" appName={APP_NAME}",
|
|
6058
6008
|
" appId={APP_ID}",
|
|
6059
|
-
" serviceId={
|
|
6009
|
+
" serviceId={SCAFFOLD_SERVICE_SLUG}",
|
|
6060
6010
|
' status="error"',
|
|
6061
|
-
' title="
|
|
6062
|
-
" message={
|
|
6063
|
-
' actionLabel="
|
|
6064
|
-
" onAction={() => window.location.
|
|
6011
|
+
' title="Service identity unavailable"',
|
|
6012
|
+
" message={serviceIdentityError}",
|
|
6013
|
+
' actionLabel="Retry"',
|
|
6014
|
+
" onAction={() => window.location.reload()}",
|
|
6065
6015
|
" />",
|
|
6066
6016
|
" );",
|
|
6067
6017
|
" }",
|
|
6068
6018
|
"",
|
|
6069
|
-
" if (
|
|
6019
|
+
" if (!auth) {",
|
|
6070
6020
|
" return (",
|
|
6071
|
-
" <
|
|
6021
|
+
" <TopoloAppLoadingScaffold",
|
|
6072
6022
|
" appId={APP_ID}",
|
|
6073
|
-
" serviceId={
|
|
6023
|
+
" serviceId={SCAFFOLD_SERVICE_SLUG}",
|
|
6074
6024
|
" appName={APP_NAME}",
|
|
6025
|
+
" layout={{ shell: 'topbar', content: 'workspace' }}",
|
|
6075
6026
|
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6076
6027
|
" />",
|
|
6077
6028
|
" );",
|
|
6078
6029
|
" }",
|
|
6079
6030
|
"",
|
|
6080
|
-
"
|
|
6081
|
-
"
|
|
6082
|
-
"
|
|
6031
|
+
" return (",
|
|
6032
|
+
" <TopoloAppRoot",
|
|
6033
|
+
" appName={APP_NAME}",
|
|
6034
|
+
" serviceId={serviceId}",
|
|
6035
|
+
" auth={auth}",
|
|
6036
|
+
" routes={routes}",
|
|
6037
|
+
' home="/workspace"',
|
|
6038
|
+
" publicLanding={",
|
|
6039
|
+
" <LandingPage",
|
|
6040
|
+
" serviceId={serviceId}",
|
|
6041
|
+
" authApiUrl={AUTH_URL}",
|
|
6042
|
+
" config={LANDING_CONFIG}",
|
|
6043
|
+
" onCTAClick={() => window.location.assign('/login')}",
|
|
6044
|
+
" />",
|
|
6045
|
+
" }",
|
|
6046
|
+
" loginElement={",
|
|
6083
6047
|
" <LoginPage",
|
|
6084
|
-
" serviceId={
|
|
6048
|
+
" serviceId={serviceId}",
|
|
6085
6049
|
" authApiUrl={AUTH_URL}",
|
|
6086
6050
|
" config={LOGIN_CONFIG}",
|
|
6087
|
-
" isLoading={loginAttempted}",
|
|
6088
6051
|
" onLogin={async (provider) => {",
|
|
6089
|
-
"
|
|
6090
|
-
" try {",
|
|
6091
|
-
" await auth.authClient.login(provider || 'topolo');",
|
|
6092
|
-
" } catch {",
|
|
6093
|
-
" setLoginAttempted(false);",
|
|
6094
|
-
" }",
|
|
6052
|
+
" await auth.login(provider || 'topolo');",
|
|
6095
6053
|
" }}",
|
|
6096
6054
|
" onPasswordLogin={async (request) => {",
|
|
6097
|
-
" const result = await auth.
|
|
6098
|
-
" if (!result.authenticated)
|
|
6099
|
-
"
|
|
6100
|
-
" }",
|
|
6101
|
-
" setUser(result.user ?? null);",
|
|
6102
|
-
" window.location.replace('/');",
|
|
6055
|
+
" const result = await auth.loginWithPassword(request);",
|
|
6056
|
+
" if (!result.authenticated) return result;",
|
|
6057
|
+
" window.dispatchEvent(new CustomEvent('auth:session-updated', { detail: { tokens: result.tokens, user: result.user } }));",
|
|
6103
6058
|
" return undefined;",
|
|
6104
6059
|
" }}",
|
|
6105
6060
|
" />",
|
|
6106
|
-
"
|
|
6107
|
-
"
|
|
6108
|
-
"",
|
|
6109
|
-
"
|
|
6110
|
-
"
|
|
6111
|
-
"
|
|
6112
|
-
"
|
|
6113
|
-
"
|
|
6114
|
-
"
|
|
6115
|
-
" />",
|
|
6116
|
-
" );",
|
|
6117
|
-
" }",
|
|
6118
|
-
"",
|
|
6119
|
-
" if (route.kind === 'login') {",
|
|
6120
|
-
" return (",
|
|
6121
|
-
" <TopoloLoadingScreen",
|
|
6122
|
-
" appId={APP_ID}",
|
|
6123
|
-
" serviceId={activeServiceId}",
|
|
6124
|
-
" appName={APP_NAME}",
|
|
6125
|
-
` subtitle="Opening your ${escapeDoubleQuoted(signedSurfaceLabel)}..."`,
|
|
6126
|
-
" />",
|
|
6127
|
-
" );",
|
|
6128
|
-
" }",
|
|
6129
|
-
"",
|
|
6130
|
-
" return (",
|
|
6131
|
-
" <TopoloAppShell<NavItem>",
|
|
6132
|
-
" appId={APP_ID}",
|
|
6133
|
-
" serviceId={activeServiceId}",
|
|
6134
|
-
" appName={APP_NAME}",
|
|
6135
|
-
" user={shellUser}",
|
|
6136
|
-
" orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
|
|
6137
|
-
" activeContextType={user.activeContextType}",
|
|
6138
|
-
" activeContextId={user.activeContextId}",
|
|
6139
|
-
" activeNavItemId={view === 'getting-started' ? 'getting-started' : 'home'}",
|
|
6140
|
-
` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }, { id: 'getting-started', label: 'Getting started' }]}`,
|
|
6141
|
-
' navigationMode="topbar"',
|
|
6142
|
-
" onNavItemSelect={handleNavSelect}",
|
|
6143
|
-
` title={view === 'getting-started' ? 'Getting started' : '${escapeSingleQuoted(workspaceTitle)}'}`,
|
|
6144
|
-
" authUrl={AUTH_URL}",
|
|
6145
|
-
" getAccessToken={getAccessToken}",
|
|
6146
|
-
" onSignOut={async () => {",
|
|
6147
|
-
" await auth.authClient.logout();",
|
|
6148
|
-
" window.location.assign('/login');",
|
|
6149
|
-
" }}",
|
|
6150
|
-
" commandPlaceholder={`Search ${APP_NAME}`}",
|
|
6151
|
-
" bugReporterAppName={APP_NAME}",
|
|
6152
|
-
" enableCommandPalette",
|
|
6153
|
-
" enableBugReporter",
|
|
6154
|
-
" >",
|
|
6155
|
-
" {view === 'getting-started' ? (",
|
|
6156
|
-
" <ScaffoldGettingStarted",
|
|
6157
|
-
" serviceId={activeServiceId}",
|
|
6158
|
-
" organizationId={user.organization?.id ?? user.orgId ?? ''}",
|
|
6159
|
-
" role={user.role}",
|
|
6160
|
-
" onOpenWorkspace={openWorkspace}",
|
|
6061
|
+
" }",
|
|
6062
|
+
" loadingFallback={({ loading }) => (",
|
|
6063
|
+
" <TopoloAppLoadingScaffold",
|
|
6064
|
+
" appId={APP_ID}",
|
|
6065
|
+
" serviceId={serviceId}",
|
|
6066
|
+
" appName={APP_NAME}",
|
|
6067
|
+
" layout={{ shell: loading?.shell || 'topbar', content: loading?.content || 'workspace' }}",
|
|
6068
|
+
" title={loading?.title}",
|
|
6069
|
+
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6161
6070
|
" />",
|
|
6162
|
-
" ) : (",
|
|
6163
|
-
' <div className="topolo-starter-workspace">',
|
|
6164
|
-
' <section className="topolo-starter-card" data-tour="workspace-card">',
|
|
6165
|
-
` <h2>${baselineHeading}</h2>`,
|
|
6166
|
-
" <p>",
|
|
6167
|
-
` ${baselineDescription}`,
|
|
6168
|
-
" </p>",
|
|
6169
|
-
" </section>",
|
|
6170
|
-
" </div>",
|
|
6171
6071
|
" )}",
|
|
6072
|
+
" shell={{",
|
|
6073
|
+
" appId: APP_ID,",
|
|
6074
|
+
" authUrl: AUTH_URL,",
|
|
6075
|
+
" navigationMode: 'topbar',",
|
|
6076
|
+
" title: APP_NAME,",
|
|
6077
|
+
" commandPlaceholder: `Search ${APP_NAME}`,",
|
|
6078
|
+
" bugReporterAppName: APP_NAME,",
|
|
6079
|
+
" enableCommandPalette: true,",
|
|
6080
|
+
" enableBugReporter: true,",
|
|
6081
|
+
" }}",
|
|
6082
|
+
" />",
|
|
6083
|
+
" );",
|
|
6084
|
+
"}",
|
|
6085
|
+
"",
|
|
6086
|
+
"function ScaffoldWorkspace() {",
|
|
6087
|
+
" const { user } = useAuth();",
|
|
6088
|
+
" const openWorkspace = useCallback(() => {",
|
|
6089
|
+
" window.history.pushState({}, '', '/workspace');",
|
|
6090
|
+
" window.dispatchEvent(new PopStateEvent('popstate'));",
|
|
6091
|
+
" }, []);",
|
|
6092
|
+
" const openGettingStarted = useCallback(() => {",
|
|
6093
|
+
" window.history.pushState({}, '', '/getting-started');",
|
|
6094
|
+
" window.dispatchEvent(new PopStateEvent('popstate'));",
|
|
6095
|
+
" }, []);",
|
|
6096
|
+
" return (",
|
|
6097
|
+
' <div className="topolo-starter-workspace">',
|
|
6098
|
+
' <section className="topolo-starter-card" data-tour="workspace-card">',
|
|
6099
|
+
` <h2>${baselineHeading}</h2>`,
|
|
6100
|
+
" <p>",
|
|
6101
|
+
` ${baselineDescription}`,
|
|
6102
|
+
" </p>",
|
|
6103
|
+
" </section>",
|
|
6172
6104
|
" <IntroTour",
|
|
6173
6105
|
" appName={APP_NAME}",
|
|
6174
|
-
" role={user
|
|
6106
|
+
" role={user?.role}",
|
|
6175
6107
|
" onOpenGettingStarted={openGettingStarted}",
|
|
6176
6108
|
" extraSteps={[",
|
|
6177
6109
|
" {",
|
|
@@ -6184,7 +6116,20 @@ function platformAppTsx(application, options) {
|
|
|
6184
6116
|
" },",
|
|
6185
6117
|
" ]}",
|
|
6186
6118
|
" />",
|
|
6187
|
-
" </
|
|
6119
|
+
" </div>",
|
|
6120
|
+
" );",
|
|
6121
|
+
"}",
|
|
6122
|
+
"",
|
|
6123
|
+
"function ScaffoldGettingStartedRoute({ serviceId }: { serviceId: string }) {",
|
|
6124
|
+
" const { user, tokens } = useAuth();",
|
|
6125
|
+
" return (",
|
|
6126
|
+
" <ScaffoldGettingStarted",
|
|
6127
|
+
" serviceId={serviceId}",
|
|
6128
|
+
" organizationId={String((user as any)?.organization?.id ?? user?.orgId ?? '')}",
|
|
6129
|
+
" role={user?.role}",
|
|
6130
|
+
" getAccessToken={() => tokens?.accessToken ?? null}",
|
|
6131
|
+
" onOpenWorkspace={() => window.location.assign('/workspace')}",
|
|
6132
|
+
" />",
|
|
6188
6133
|
" );",
|
|
6189
6134
|
"}",
|
|
6190
6135
|
"",
|
|
@@ -6192,15 +6137,16 @@ function platformAppTsx(application, options) {
|
|
|
6192
6137
|
" serviceId: string;",
|
|
6193
6138
|
" organizationId: string;",
|
|
6194
6139
|
" role?: string;",
|
|
6140
|
+
" getAccessToken: () => string | null;",
|
|
6195
6141
|
" onOpenWorkspace: () => void;",
|
|
6196
6142
|
"}) {",
|
|
6197
|
-
" const { serviceId, organizationId, role, onOpenWorkspace } = props;",
|
|
6143
|
+
" const { serviceId, organizationId, role, getAccessToken, onOpenWorkspace } = props;",
|
|
6198
6144
|
" const steps: OnboardingGuideStep[] = [",
|
|
6199
6145
|
" {",
|
|
6200
6146
|
" id: 'explore-workspace',",
|
|
6201
6147
|
" title: 'Explore your workspace',",
|
|
6202
6148
|
" description:",
|
|
6203
|
-
" 'Your
|
|
6149
|
+
" 'Your workspace lives in the Topolo shell \u2014 the launcher, command palette, and account menu are all one click away.',",
|
|
6204
6150
|
" actionLabel: 'Open workspace',",
|
|
6205
6151
|
" onAction: onOpenWorkspace,",
|
|
6206
6152
|
" },",
|
|
@@ -6213,9 +6159,9 @@ function platformAppTsx(application, options) {
|
|
|
6213
6159
|
" },",
|
|
6214
6160
|
" {",
|
|
6215
6161
|
" id: 'build-feature',",
|
|
6216
|
-
" title: '
|
|
6162
|
+
" title: 'Make it your own',",
|
|
6217
6163
|
" description:",
|
|
6218
|
-
" '
|
|
6164
|
+
" 'Open the account menu to adjust your settings and theme \u2014 your preferences stay in sync across every Topolo app.',",
|
|
6219
6165
|
" },",
|
|
6220
6166
|
" ];",
|
|
6221
6167
|
" return (",
|
|
@@ -6224,7 +6170,7 @@ function platformAppTsx(application, options) {
|
|
|
6224
6170
|
" authApiBaseUrl={`${AUTH_URL}/api`}",
|
|
6225
6171
|
" organizationId={organizationId}",
|
|
6226
6172
|
" serviceId={serviceId}",
|
|
6227
|
-
" getAccessToken={
|
|
6173
|
+
" getAccessToken={getAccessToken}",
|
|
6228
6174
|
" role={role || undefined}",
|
|
6229
6175
|
" steps={steps}",
|
|
6230
6176
|
" eyebrow={`Welcome to ${APP_NAME}`}",
|
|
@@ -6240,13 +6186,6 @@ function platformAppTsx(application, options) {
|
|
|
6240
6186
|
" );",
|
|
6241
6187
|
"}",
|
|
6242
6188
|
"",
|
|
6243
|
-
"function getRoute(): AppRoute {",
|
|
6244
|
-
" const pathname = window.location.pathname.replace(/\\/+$/, '') || '/';",
|
|
6245
|
-
" if (pathname === '/login') return { kind: 'login' };",
|
|
6246
|
-
" if (pathname === '/auth/callback') return { kind: 'callback' };",
|
|
6247
|
-
" return { kind: 'root' };",
|
|
6248
|
-
"}",
|
|
6249
|
-
"",
|
|
6250
6189
|
"function errorMessage(error: unknown): string {",
|
|
6251
6190
|
" if (error instanceof Error && error.message) return error.message;",
|
|
6252
6191
|
" return 'Unexpected authentication error.';",
|
|
@@ -6793,6 +6732,9 @@ function platformWorkerTs(application, options) {
|
|
|
6793
6732
|
" return [",
|
|
6794
6733
|
" {",
|
|
6795
6734
|
" type: 'quick-actions',",
|
|
6735
|
+
" snapshot: {",
|
|
6736
|
+
" summary: `${SCAFFOLD_APP_NAME} is ready for workspace operations and exposes health checks for operators.`,",
|
|
6737
|
+
" },",
|
|
6796
6738
|
" actions: [",
|
|
6797
6739
|
` { id: 'open-workspace', label: '${escapeSingleQuoted(openActionLabel)}', link: '/', primary: true },`,
|
|
6798
6740
|
" { id: 'view-health', label: 'View health', link: '/api/health' },",
|
|
@@ -6904,6 +6846,9 @@ function developerWorkerTs(application) {
|
|
|
6904
6846
|
" return [",
|
|
6905
6847
|
" {",
|
|
6906
6848
|
" type: 'quick-actions',",
|
|
6849
|
+
" snapshot: {",
|
|
6850
|
+
" summary: `${SERVICE_NAME} is ready for workspace operations and exposes health checks for operators.`,",
|
|
6851
|
+
" },",
|
|
6907
6852
|
" actions: [",
|
|
6908
6853
|
" { id: 'open-app', label: `Open ${SERVICE_NAME}`, link: '/', primary: true },",
|
|
6909
6854
|
" { id: 'view-health', label: 'View health', link: '/api/health' },",
|
|
@@ -7470,7 +7415,7 @@ function internalDocs(application, options) {
|
|
|
7470
7415
|
"",
|
|
7471
7416
|
"## Architecture",
|
|
7472
7417
|
"",
|
|
7473
|
-
"The generated browser surface in `apps/web` uses `@topolo-io/
|
|
7418
|
+
"The generated browser surface in `apps/web` uses `@topolo-io/app-funnel` for the shared public landing page, `@topolo-io/app-shell` for shared login, auth callback state, and signed shell, `@topolo-io/app-launcher` for app switching, and `@topolo-io/ui-kit` for shared primitives. `apps/web/vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
|
|
7474
7419
|
options.api ? `The generated app keeps browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and API runtime service identity in \`${appPath}/apps/api/src/service-identity.ts\`. Runtime code reads the provisioned Auth service id from the API Worker \`SERVICE_ID\` binding and exposes it to the browser through the same-origin service-identity route forwarded by the web Worker.` : `The generated app centralizes browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and reads Auth identity from the app runtime instead of calling Auth for slug lookups.`,
|
|
7475
7420
|
"",
|
|
7476
7421
|
"## Runtime Surfaces",
|
|
@@ -7875,9 +7820,6 @@ function escapeHtml(value) {
|
|
|
7875
7820
|
function escapeSingleQuoted(value) {
|
|
7876
7821
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
7877
7822
|
}
|
|
7878
|
-
function escapeDoubleQuoted(value) {
|
|
7879
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7880
|
-
}
|
|
7881
7823
|
function escapeFrontmatter(value) {
|
|
7882
7824
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7883
7825
|
}
|
|
@@ -7991,17 +7933,17 @@ async function provisionApplicationScaffold(payload) {
|
|
|
7991
7933
|
developersOrigin,
|
|
7992
7934
|
developersApp: {
|
|
7993
7935
|
id: appId,
|
|
7994
|
-
slug:
|
|
7995
|
-
listingStatus:
|
|
7996
|
-
organizationId:
|
|
7936
|
+
slug: readString2(app.slug),
|
|
7937
|
+
listingStatus: readString2(app.listingStatus),
|
|
7938
|
+
organizationId: readString2(app.organizationId)
|
|
7997
7939
|
},
|
|
7998
7940
|
authService: service ? {
|
|
7999
7941
|
id: readRequiredString(
|
|
8000
7942
|
service.id,
|
|
8001
7943
|
"Auth registration did not return a service id."
|
|
8002
7944
|
),
|
|
8003
|
-
status:
|
|
8004
|
-
baseUrl:
|
|
7945
|
+
status: readString2(service.status),
|
|
7946
|
+
baseUrl: readString2(service.base_url) ?? readString2(service.baseUrl)
|
|
8005
7947
|
} : null,
|
|
8006
7948
|
authConfig: authConfig ? {
|
|
8007
7949
|
login: authConfig.login === true,
|
|
@@ -8067,7 +8009,7 @@ async function resolveServiceIdBySlug(authOrigin, slug) {
|
|
|
8067
8009
|
function pickRecord(value) {
|
|
8068
8010
|
return value && typeof value === "object" ? value : null;
|
|
8069
8011
|
}
|
|
8070
|
-
function
|
|
8012
|
+
function readString2(value) {
|
|
8071
8013
|
if (typeof value !== "string") {
|
|
8072
8014
|
return null;
|
|
8073
8015
|
}
|
|
@@ -8075,7 +8017,7 @@ function readString(value) {
|
|
|
8075
8017
|
return trimmed ? trimmed : null;
|
|
8076
8018
|
}
|
|
8077
8019
|
function readRequiredString(value, message) {
|
|
8078
|
-
const resolved =
|
|
8020
|
+
const resolved = readString2(value);
|
|
8079
8021
|
if (!resolved) {
|
|
8080
8022
|
throw new Error(message);
|
|
8081
8023
|
}
|
|
@@ -8252,7 +8194,7 @@ function applicationRowsFromCatalog(catalog) {
|
|
|
8252
8194
|
name: entry.name ?? entry.slug ?? entry.serviceId,
|
|
8253
8195
|
appDir: "-",
|
|
8254
8196
|
packageName: null,
|
|
8255
|
-
productionUrl: entry.
|
|
8197
|
+
productionUrl: entry.launchUrl ?? null,
|
|
8256
8198
|
services: [displayCatalogId(entry)],
|
|
8257
8199
|
deployTargets: 0
|
|
8258
8200
|
})).sort((left, right) => left.id.localeCompare(right.id));
|
|
@@ -8849,22 +8791,26 @@ function buildRows(catalog, overrides, environment = selectedEnvironment()) {
|
|
|
8849
8791
|
defaultUrl,
|
|
8850
8792
|
effectiveUrl,
|
|
8851
8793
|
overridden: Boolean(configuredUrl && configuredUrl !== defaultUrl),
|
|
8852
|
-
environmentSafe: environment !== "staging" || isStagingUrl(effectiveUrl),
|
|
8853
|
-
permissions: [...entry.permissions]
|
|
8794
|
+
environmentSafe: environment !== "staging" || !effectiveUrl || isStagingUrl(effectiveUrl),
|
|
8795
|
+
permissions: [...entry.permissions],
|
|
8796
|
+
apiKeyResource: entry.apiKeyResource,
|
|
8797
|
+
apiKeyResources: [...entry.apiKeyResources]
|
|
8854
8798
|
};
|
|
8855
8799
|
}).sort((left, right) => left.id.localeCompare(right.id));
|
|
8856
8800
|
}
|
|
8857
8801
|
function formatRows2(rows, environment = selectedEnvironment()) {
|
|
8858
8802
|
const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
|
|
8859
|
-
const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
|
|
8803
|
+
const urlWidth = Math.max(7, ...rows.map((r) => (r.effectiveUrl ?? "-").length));
|
|
8860
8804
|
const lines = [];
|
|
8861
8805
|
lines.push(`Environment: ${environment}`);
|
|
8862
8806
|
lines.push("");
|
|
8863
|
-
lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY`);
|
|
8864
|
-
lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} --------
|
|
8807
|
+
lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY API KEY RESOURCE`);
|
|
8808
|
+
lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} -------- ----- ----------------`);
|
|
8865
8809
|
for (const r of rows) {
|
|
8810
|
+
const effectiveUrl = r.effectiveUrl ?? "-";
|
|
8811
|
+
const apiKeyResource = r.apiKeyResources.length > 0 ? r.apiKeyResources.map((resource) => `${resource.resourceType}:${resource.canonicalResourceType}`).join(",") : "-";
|
|
8866
8812
|
lines.push(
|
|
8867
|
-
`${r.id.padEnd(idWidth)} ${
|
|
8813
|
+
`${r.id.padEnd(idWidth)} ${effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : "".padEnd(8)} ${r.environmentSafe ? "yes" : "no"} ${apiKeyResource}`
|
|
8868
8814
|
);
|
|
8869
8815
|
}
|
|
8870
8816
|
return lines.join("\n");
|
|
@@ -8885,7 +8831,7 @@ function displayServiceId(entry) {
|
|
|
8885
8831
|
return serviceIdCandidates(entry.slug || entry.serviceId)[0] || entry.serviceId;
|
|
8886
8832
|
}
|
|
8887
8833
|
function defaultServiceUrl(id, entry) {
|
|
8888
|
-
return resolveCatalogServiceUrl(entry, { serviceKey: id })
|
|
8834
|
+
return resolveCatalogServiceUrl(entry, { serviceKey: id });
|
|
8889
8835
|
}
|
|
8890
8836
|
function configuredServiceUrl(id, overrides) {
|
|
8891
8837
|
if (!overrides) return null;
|