@topolo/cli 0.1.15 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -4
- package/dist/index.js +2147 -303
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -55,43 +55,82 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
55
55
|
body;
|
|
56
56
|
service;
|
|
57
57
|
path;
|
|
58
|
-
constructor(service,
|
|
59
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
58
|
+
constructor(service, path4, status, body, message) {
|
|
59
|
+
super("http_error", message ?? `HTTP ${status} from ${service}${path4}`);
|
|
60
60
|
this.name = "TopoloHttpError";
|
|
61
61
|
this.status = status;
|
|
62
62
|
this.body = body;
|
|
63
63
|
this.service = service;
|
|
64
|
-
this.path =
|
|
64
|
+
this.path = path4;
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
var SERVICES = {
|
|
68
|
-
agent: "https://
|
|
68
|
+
agent: "https://agent-api.topolo.app",
|
|
69
69
|
auth: "https://auth.topolo.app",
|
|
70
|
+
books: "https://books.topolo.app",
|
|
70
71
|
bugfix: "https://bugfix.topolo.app",
|
|
71
|
-
bytes: "https://
|
|
72
|
+
bytes: "https://bytes-api.topolo.app",
|
|
72
73
|
calendar: "https://calendar.topolo.app",
|
|
74
|
+
capacity: "https://capacity.topolo.app",
|
|
73
75
|
chat: "https://chat.topolo.app",
|
|
74
|
-
commerce: "https://
|
|
76
|
+
commerce: "https://commerce-api.topolo.app",
|
|
77
|
+
compose: "https://compose.topolo.app",
|
|
78
|
+
consent: "https://consent.topolo.app",
|
|
75
79
|
crm: "https://crm-api.topolo.app",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
customer_success: "https://success.topolo.app",
|
|
81
|
+
director: "https://director.topolo.app",
|
|
82
|
+
feed: "https://feed-api.topolo.app",
|
|
83
|
+
flow: "https://flow.topolo.app",
|
|
84
|
+
forecast: "https://forecast-api.topolo.app",
|
|
85
|
+
forms: "https://forms.topolo.app",
|
|
86
|
+
home: "https://home.topolo.app",
|
|
87
|
+
insights: "https://insights.topolo.app",
|
|
88
|
+
inventory: "https://inventory.topolo.app",
|
|
89
|
+
learn: "https://learn-api.topolo.app",
|
|
90
|
+
localize: "https://localize.topolo.app",
|
|
79
91
|
mail: "https://mail.topolo.app",
|
|
80
|
-
mdm: "https://
|
|
92
|
+
mdm: "https://mdm-api.topolo.app",
|
|
81
93
|
messaging: "https://messages.topolo.app",
|
|
82
|
-
nexus: "https://
|
|
94
|
+
nexus: "https://nexus-api.topolo.app",
|
|
95
|
+
notify: "https://notify.topolo.app",
|
|
83
96
|
one: "https://one.topolo.app",
|
|
84
|
-
|
|
97
|
+
"one.ai": "https://ai.one.topolo.app",
|
|
98
|
+
p2p: "https://p2p.topolo.app",
|
|
99
|
+
pay: "https://pay.topolo.app",
|
|
100
|
+
people: "https://people.topolo.app",
|
|
85
101
|
"quro.api": "https://api.ol0.me",
|
|
86
102
|
"quro.redirect": "https://ol0.me",
|
|
87
103
|
roadmapper: "https://roadmapper-api.topolo.app",
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
sign: "https://sign.topolo.app",
|
|
105
|
+
social_studio: "https://studio-api.topolo.app",
|
|
106
|
+
socialize: "https://socialize-api.topolo.app",
|
|
107
|
+
status: "https://status.topolo.app",
|
|
90
108
|
support: "https://support.topolo.app",
|
|
109
|
+
survey: "https://survey.topolo.app",
|
|
110
|
+
voice: "https://voice.topolo.app",
|
|
91
111
|
web: "https://web-api.topolo.app"
|
|
92
112
|
};
|
|
93
113
|
var PLATFORM_SERVICE_IDS = {
|
|
94
|
-
|
|
114
|
+
books: "srv_topolo_books",
|
|
115
|
+
bugfix: "srv_topolo_bugfix",
|
|
116
|
+
compose: "srv_topolo_compose",
|
|
117
|
+
consent: "srv_topolo_consent",
|
|
118
|
+
crm: "srv_iCwM4jGXcwlj",
|
|
119
|
+
customer_success: "srv_topolo_success",
|
|
120
|
+
director: "srv_topolo_director",
|
|
121
|
+
flow: "srv_topolo_flow",
|
|
122
|
+
forms: "srv_topolo_forms",
|
|
123
|
+
home: "srv_topolo_home",
|
|
124
|
+
insights: "srv_topolo_insights",
|
|
125
|
+
inventory: "srv_topolo_inventory",
|
|
126
|
+
localize: "srv_topolo_localize",
|
|
127
|
+
notify: "svc_topolo_notify",
|
|
128
|
+
p2p: "srv_topolo_p2p",
|
|
129
|
+
people: "srv_topolo_people",
|
|
130
|
+
sign: "srv_topolo_sign",
|
|
131
|
+
status: "srv_topolo_status",
|
|
132
|
+
survey: "srv_topolo_survey",
|
|
133
|
+
voice: "srv_topolo_voice"
|
|
95
134
|
};
|
|
96
135
|
var APPLICATIONS = {
|
|
97
136
|
"admin": {
|
|
@@ -106,7 +145,9 @@ var APPLICATIONS = {
|
|
|
106
145
|
"name": "topoloadmin",
|
|
107
146
|
"kind": "pages",
|
|
108
147
|
"apiUrl": null,
|
|
109
|
-
"domains": [
|
|
148
|
+
"domains": [
|
|
149
|
+
"admin.topolo.app"
|
|
150
|
+
],
|
|
110
151
|
"routes": [],
|
|
111
152
|
"serviceId": null
|
|
112
153
|
}
|
|
@@ -126,15 +167,19 @@ var APPLICATIONS = {
|
|
|
126
167
|
"name": "topolo-agent",
|
|
127
168
|
"kind": "pages",
|
|
128
169
|
"apiUrl": null,
|
|
129
|
-
"domains": [
|
|
170
|
+
"domains": [
|
|
171
|
+
"agent.topolo.app"
|
|
172
|
+
],
|
|
130
173
|
"routes": [],
|
|
131
174
|
"serviceId": null
|
|
132
175
|
},
|
|
133
176
|
{
|
|
134
177
|
"name": "topolo-agent-worker",
|
|
135
178
|
"kind": "worker",
|
|
136
|
-
"apiUrl": "https://
|
|
137
|
-
"domains": [
|
|
179
|
+
"apiUrl": "https://agent-api.topolo.app",
|
|
180
|
+
"domains": [
|
|
181
|
+
"agent-api.topolo.app"
|
|
182
|
+
],
|
|
138
183
|
"routes": [],
|
|
139
184
|
"serviceId": "agent"
|
|
140
185
|
}
|
|
@@ -164,6 +209,30 @@ var APPLICATIONS = {
|
|
|
164
209
|
}
|
|
165
210
|
]
|
|
166
211
|
},
|
|
212
|
+
"books": {
|
|
213
|
+
"id": "books",
|
|
214
|
+
"appDir": "TopoloBooks",
|
|
215
|
+
"name": "topolo-books",
|
|
216
|
+
"packageName": "topolo-books",
|
|
217
|
+
"productionUrl": "https://books.topolo.app",
|
|
218
|
+
"services": [
|
|
219
|
+
"books"
|
|
220
|
+
],
|
|
221
|
+
"deployTargets": [
|
|
222
|
+
{
|
|
223
|
+
"name": "topolo-books",
|
|
224
|
+
"kind": "worker",
|
|
225
|
+
"apiUrl": "https://books.topolo.app",
|
|
226
|
+
"domains": [
|
|
227
|
+
"books.topolo.app"
|
|
228
|
+
],
|
|
229
|
+
"routes": [
|
|
230
|
+
"books.topolo.app"
|
|
231
|
+
],
|
|
232
|
+
"serviceId": "books"
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
},
|
|
167
236
|
"bugfix": {
|
|
168
237
|
"id": "bugfix",
|
|
169
238
|
"appDir": "TopoloBugFix",
|
|
@@ -199,18 +268,22 @@ var APPLICATIONS = {
|
|
|
199
268
|
],
|
|
200
269
|
"deployTargets": [
|
|
201
270
|
{
|
|
202
|
-
"name": "bytes",
|
|
271
|
+
"name": "topolo-bytes",
|
|
203
272
|
"kind": "pages",
|
|
204
273
|
"apiUrl": null,
|
|
205
|
-
"domains": [
|
|
274
|
+
"domains": [
|
|
275
|
+
"bytes.topolo.app"
|
|
276
|
+
],
|
|
206
277
|
"routes": [],
|
|
207
278
|
"serviceId": null
|
|
208
279
|
},
|
|
209
280
|
{
|
|
210
|
-
"name": "
|
|
281
|
+
"name": "topolo-bytes-api",
|
|
211
282
|
"kind": "worker",
|
|
212
|
-
"apiUrl": "https://
|
|
213
|
-
"domains": [
|
|
283
|
+
"apiUrl": "https://bytes-api.topolo.app",
|
|
284
|
+
"domains": [
|
|
285
|
+
"bytes-api.topolo.app"
|
|
286
|
+
],
|
|
214
287
|
"routes": [],
|
|
215
288
|
"serviceId": "bytes"
|
|
216
289
|
}
|
|
@@ -240,6 +313,28 @@ var APPLICATIONS = {
|
|
|
240
313
|
}
|
|
241
314
|
]
|
|
242
315
|
},
|
|
316
|
+
"capacity": {
|
|
317
|
+
"id": "capacity",
|
|
318
|
+
"appDir": "TopoloCapacity",
|
|
319
|
+
"name": "topolo-capacity",
|
|
320
|
+
"packageName": "topolo-capacity",
|
|
321
|
+
"productionUrl": "https://capacity.topolo.app",
|
|
322
|
+
"services": [
|
|
323
|
+
"capacity"
|
|
324
|
+
],
|
|
325
|
+
"deployTargets": [
|
|
326
|
+
{
|
|
327
|
+
"name": "topolo-capacity",
|
|
328
|
+
"kind": "worker",
|
|
329
|
+
"apiUrl": "https://capacity.topolo.app",
|
|
330
|
+
"domains": [
|
|
331
|
+
"capacity.topolo.app"
|
|
332
|
+
],
|
|
333
|
+
"routes": [],
|
|
334
|
+
"serviceId": "capacity"
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
},
|
|
243
338
|
"chat": {
|
|
244
339
|
"id": "chat",
|
|
245
340
|
"appDir": "TopoloChat",
|
|
@@ -276,7 +371,7 @@ var APPLICATIONS = {
|
|
|
276
371
|
"cloudcontrol": {
|
|
277
372
|
"id": "cloudcontrol",
|
|
278
373
|
"appDir": "TopoloCloudControl",
|
|
279
|
-
"name": "
|
|
374
|
+
"name": "cloudcontrol",
|
|
280
375
|
"packageName": "topolo-cloudcontrol",
|
|
281
376
|
"productionUrl": null,
|
|
282
377
|
"services": [],
|
|
@@ -295,9 +390,9 @@ var APPLICATIONS = {
|
|
|
295
390
|
{
|
|
296
391
|
"name": "topolo-commerce-api",
|
|
297
392
|
"kind": "worker",
|
|
298
|
-
"apiUrl": "https://
|
|
393
|
+
"apiUrl": "https://commerce-api.topolo.app",
|
|
299
394
|
"domains": [
|
|
300
|
-
"
|
|
395
|
+
"commerce-api.topolo.app"
|
|
301
396
|
],
|
|
302
397
|
"routes": [],
|
|
303
398
|
"serviceId": "commerce"
|
|
@@ -307,8 +402,7 @@ var APPLICATIONS = {
|
|
|
307
402
|
"kind": "pages",
|
|
308
403
|
"apiUrl": null,
|
|
309
404
|
"domains": [
|
|
310
|
-
"guest.topolo.app"
|
|
311
|
-
"topolo-commerce-guest-web.pages.dev"
|
|
405
|
+
"guest.topolo.app"
|
|
312
406
|
],
|
|
313
407
|
"routes": [],
|
|
314
408
|
"serviceId": null
|
|
@@ -318,8 +412,7 @@ var APPLICATIONS = {
|
|
|
318
412
|
"kind": "pages",
|
|
319
413
|
"apiUrl": null,
|
|
320
414
|
"domains": [
|
|
321
|
-
"commerce.topolo.app"
|
|
322
|
-
"topolo-commerce-ops-web.pages.dev"
|
|
415
|
+
"commerce.topolo.app"
|
|
323
416
|
],
|
|
324
417
|
"routes": [],
|
|
325
418
|
"serviceId": null
|
|
@@ -329,14 +422,61 @@ var APPLICATIONS = {
|
|
|
329
422
|
"kind": "pages",
|
|
330
423
|
"apiUrl": null,
|
|
331
424
|
"domains": [
|
|
332
|
-
"station-commerce.topolo.app"
|
|
333
|
-
"topolo-commerce-station-web.pages.dev"
|
|
425
|
+
"station-commerce.topolo.app"
|
|
334
426
|
],
|
|
335
427
|
"routes": [],
|
|
336
428
|
"serviceId": null
|
|
337
429
|
}
|
|
338
430
|
]
|
|
339
431
|
},
|
|
432
|
+
"compose": {
|
|
433
|
+
"id": "compose",
|
|
434
|
+
"appDir": "TopoloCompose",
|
|
435
|
+
"name": "topolo-compose",
|
|
436
|
+
"packageName": "@topolo/compose",
|
|
437
|
+
"productionUrl": "https://compose.topolo.app",
|
|
438
|
+
"services": [
|
|
439
|
+
"compose"
|
|
440
|
+
],
|
|
441
|
+
"deployTargets": [
|
|
442
|
+
{
|
|
443
|
+
"name": "topolo-compose",
|
|
444
|
+
"kind": "worker",
|
|
445
|
+
"apiUrl": "https://compose.topolo.app",
|
|
446
|
+
"domains": [
|
|
447
|
+
"compose.topolo.app"
|
|
448
|
+
],
|
|
449
|
+
"routes": [
|
|
450
|
+
"compose.topolo.app"
|
|
451
|
+
],
|
|
452
|
+
"serviceId": "compose"
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
"consent": {
|
|
457
|
+
"id": "consent",
|
|
458
|
+
"appDir": "TopoloConsent",
|
|
459
|
+
"name": "topolo-consent",
|
|
460
|
+
"packageName": "topolo-consent",
|
|
461
|
+
"productionUrl": "https://consent.topolo.app",
|
|
462
|
+
"services": [
|
|
463
|
+
"consent"
|
|
464
|
+
],
|
|
465
|
+
"deployTargets": [
|
|
466
|
+
{
|
|
467
|
+
"name": "topolo-consent",
|
|
468
|
+
"kind": "worker",
|
|
469
|
+
"apiUrl": "https://consent.topolo.app",
|
|
470
|
+
"domains": [
|
|
471
|
+
"consent.topolo.app"
|
|
472
|
+
],
|
|
473
|
+
"routes": [
|
|
474
|
+
"consent.topolo.app"
|
|
475
|
+
],
|
|
476
|
+
"serviceId": "consent"
|
|
477
|
+
}
|
|
478
|
+
]
|
|
479
|
+
},
|
|
340
480
|
"crm": {
|
|
341
481
|
"id": "crm",
|
|
342
482
|
"appDir": "TopoloCRM",
|
|
@@ -348,20 +488,66 @@ var APPLICATIONS = {
|
|
|
348
488
|
],
|
|
349
489
|
"deployTargets": [
|
|
350
490
|
{
|
|
351
|
-
"name": "topolo-crm-
|
|
491
|
+
"name": "topolo-crm-api",
|
|
492
|
+
"kind": "worker",
|
|
493
|
+
"apiUrl": "https://crm-api.topolo.app",
|
|
494
|
+
"domains": [
|
|
495
|
+
"crm-api.topolo.app"
|
|
496
|
+
],
|
|
497
|
+
"routes": [],
|
|
498
|
+
"serviceId": "crm"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"name": "topolo-crm-app",
|
|
352
502
|
"kind": "pages",
|
|
353
503
|
"apiUrl": null,
|
|
354
|
-
"domains": [
|
|
504
|
+
"domains": [
|
|
505
|
+
"crm.topolo.app"
|
|
506
|
+
],
|
|
355
507
|
"routes": [],
|
|
356
508
|
"serviceId": null
|
|
357
|
-
}
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
},
|
|
512
|
+
"customer_success": {
|
|
513
|
+
"id": "customer_success",
|
|
514
|
+
"appDir": "TopoloSuccess",
|
|
515
|
+
"name": "topolo-success",
|
|
516
|
+
"packageName": "topolo-success",
|
|
517
|
+
"productionUrl": "https://success.topolo.app",
|
|
518
|
+
"services": [
|
|
519
|
+
"customer_success"
|
|
520
|
+
],
|
|
521
|
+
"deployTargets": [
|
|
358
522
|
{
|
|
359
|
-
"name": "topolo-
|
|
523
|
+
"name": "topolo-success",
|
|
360
524
|
"kind": "worker",
|
|
361
|
-
"apiUrl": "https://
|
|
525
|
+
"apiUrl": "https://success.topolo.app",
|
|
526
|
+
"domains": [
|
|
527
|
+
"success.topolo.app"
|
|
528
|
+
],
|
|
529
|
+
"routes": [
|
|
530
|
+
"success.topolo.app"
|
|
531
|
+
],
|
|
532
|
+
"serviceId": "customer_success"
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
"d1_backup": {
|
|
537
|
+
"id": "d1_backup",
|
|
538
|
+
"appDir": "TopoloD1Backup",
|
|
539
|
+
"name": "topolo-d1-backup",
|
|
540
|
+
"packageName": "topolo-d1-backup",
|
|
541
|
+
"productionUrl": null,
|
|
542
|
+
"services": [],
|
|
543
|
+
"deployTargets": [
|
|
544
|
+
{
|
|
545
|
+
"name": "topolo-d1-backup",
|
|
546
|
+
"kind": "worker",
|
|
547
|
+
"apiUrl": null,
|
|
362
548
|
"domains": [],
|
|
363
549
|
"routes": [],
|
|
364
|
-
"serviceId":
|
|
550
|
+
"serviceId": null
|
|
365
551
|
}
|
|
366
552
|
]
|
|
367
553
|
},
|
|
@@ -373,6 +559,14 @@ var APPLICATIONS = {
|
|
|
373
559
|
"productionUrl": "https://developers.topolo.app",
|
|
374
560
|
"services": [],
|
|
375
561
|
"deployTargets": [
|
|
562
|
+
{
|
|
563
|
+
"name": "topolo-catalog-cron",
|
|
564
|
+
"kind": "worker",
|
|
565
|
+
"apiUrl": null,
|
|
566
|
+
"domains": [],
|
|
567
|
+
"routes": [],
|
|
568
|
+
"serviceId": null
|
|
569
|
+
},
|
|
376
570
|
{
|
|
377
571
|
"name": "topolo-developers",
|
|
378
572
|
"kind": "pages",
|
|
@@ -385,6 +579,30 @@ var APPLICATIONS = {
|
|
|
385
579
|
}
|
|
386
580
|
]
|
|
387
581
|
},
|
|
582
|
+
"director": {
|
|
583
|
+
"id": "director",
|
|
584
|
+
"appDir": "TopoloDirector",
|
|
585
|
+
"name": "topolo-director",
|
|
586
|
+
"packageName": "topolo-director",
|
|
587
|
+
"productionUrl": "https://director.topolo.app",
|
|
588
|
+
"services": [
|
|
589
|
+
"director"
|
|
590
|
+
],
|
|
591
|
+
"deployTargets": [
|
|
592
|
+
{
|
|
593
|
+
"name": "topolo-director",
|
|
594
|
+
"kind": "worker",
|
|
595
|
+
"apiUrl": "https://director.topolo.app",
|
|
596
|
+
"domains": [
|
|
597
|
+
"director.topolo.app"
|
|
598
|
+
],
|
|
599
|
+
"routes": [
|
|
600
|
+
"director.topolo.app"
|
|
601
|
+
],
|
|
602
|
+
"serviceId": "director"
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
},
|
|
388
606
|
"docs": {
|
|
389
607
|
"id": "docs",
|
|
390
608
|
"appDir": "TopoloDocs",
|
|
@@ -409,8 +627,8 @@ var APPLICATIONS = {
|
|
|
409
627
|
"id": "feed",
|
|
410
628
|
"appDir": "TopoloFeed",
|
|
411
629
|
"name": "TopoloFeed",
|
|
412
|
-
"packageName":
|
|
413
|
-
"productionUrl": "https://
|
|
630
|
+
"packageName": "topolo-feed",
|
|
631
|
+
"productionUrl": "https://feed-api.topolo.app",
|
|
414
632
|
"services": [
|
|
415
633
|
"feed"
|
|
416
634
|
],
|
|
@@ -418,29 +636,53 @@ var APPLICATIONS = {
|
|
|
418
636
|
{
|
|
419
637
|
"name": "topolo-feed-analytics-api",
|
|
420
638
|
"kind": "worker",
|
|
421
|
-
"apiUrl": "https://
|
|
639
|
+
"apiUrl": "https://feed-analytics-api.topolo.app",
|
|
422
640
|
"domains": [
|
|
423
|
-
"
|
|
641
|
+
"feed-analytics-api.topolo.app"
|
|
424
642
|
],
|
|
425
643
|
"routes": [
|
|
426
|
-
"
|
|
644
|
+
"feed-analytics-api.topolo.app"
|
|
427
645
|
],
|
|
428
646
|
"serviceId": null
|
|
429
647
|
},
|
|
430
648
|
{
|
|
431
649
|
"name": "topolo-feed-api",
|
|
432
650
|
"kind": "worker",
|
|
433
|
-
"apiUrl": "https://
|
|
651
|
+
"apiUrl": "https://feed-api.topolo.app",
|
|
434
652
|
"domains": [
|
|
435
|
-
"
|
|
653
|
+
"feed-api.topolo.app"
|
|
436
654
|
],
|
|
437
655
|
"routes": [
|
|
438
|
-
"
|
|
656
|
+
"feed-api.topolo.app"
|
|
439
657
|
],
|
|
440
658
|
"serviceId": "feed"
|
|
441
659
|
}
|
|
442
660
|
]
|
|
443
661
|
},
|
|
662
|
+
"flow": {
|
|
663
|
+
"id": "flow",
|
|
664
|
+
"appDir": "TopoloFlow",
|
|
665
|
+
"name": "topolo-flow",
|
|
666
|
+
"packageName": "topolo-flow",
|
|
667
|
+
"productionUrl": "https://flow.topolo.app",
|
|
668
|
+
"services": [
|
|
669
|
+
"flow"
|
|
670
|
+
],
|
|
671
|
+
"deployTargets": [
|
|
672
|
+
{
|
|
673
|
+
"name": "topolo-flow",
|
|
674
|
+
"kind": "worker",
|
|
675
|
+
"apiUrl": "https://flow.topolo.app",
|
|
676
|
+
"domains": [
|
|
677
|
+
"flow.topolo.app"
|
|
678
|
+
],
|
|
679
|
+
"routes": [
|
|
680
|
+
"flow.topolo.app"
|
|
681
|
+
],
|
|
682
|
+
"serviceId": "flow"
|
|
683
|
+
}
|
|
684
|
+
]
|
|
685
|
+
},
|
|
444
686
|
"forecast": {
|
|
445
687
|
"id": "forecast",
|
|
446
688
|
"appDir": "TopoloForecast",
|
|
@@ -452,23 +694,123 @@ var APPLICATIONS = {
|
|
|
452
694
|
],
|
|
453
695
|
"deployTargets": [
|
|
454
696
|
{
|
|
455
|
-
"name": "forecast",
|
|
697
|
+
"name": "topolo-forecast",
|
|
456
698
|
"kind": "pages",
|
|
457
699
|
"apiUrl": null,
|
|
458
|
-
"domains": [
|
|
700
|
+
"domains": [
|
|
701
|
+
"forecast.topolo.app"
|
|
702
|
+
],
|
|
459
703
|
"routes": [],
|
|
460
704
|
"serviceId": null
|
|
461
705
|
},
|
|
462
706
|
{
|
|
463
|
-
"name": "forecast-
|
|
707
|
+
"name": "topolo-forecast-api",
|
|
464
708
|
"kind": "worker",
|
|
465
|
-
"apiUrl": "https://forecast-
|
|
466
|
-
"domains": [
|
|
709
|
+
"apiUrl": "https://forecast-api.topolo.app",
|
|
710
|
+
"domains": [
|
|
711
|
+
"forecast-api.topolo.app"
|
|
712
|
+
],
|
|
467
713
|
"routes": [],
|
|
468
714
|
"serviceId": "forecast"
|
|
469
715
|
}
|
|
470
716
|
]
|
|
471
717
|
},
|
|
718
|
+
"forms": {
|
|
719
|
+
"id": "forms",
|
|
720
|
+
"appDir": "TopoloForms",
|
|
721
|
+
"name": "topolo-forms",
|
|
722
|
+
"packageName": "topolo-forms",
|
|
723
|
+
"productionUrl": "https://forms.topolo.app",
|
|
724
|
+
"services": [
|
|
725
|
+
"forms"
|
|
726
|
+
],
|
|
727
|
+
"deployTargets": [
|
|
728
|
+
{
|
|
729
|
+
"name": "topolo-forms",
|
|
730
|
+
"kind": "worker",
|
|
731
|
+
"apiUrl": "https://forms.topolo.app",
|
|
732
|
+
"domains": [
|
|
733
|
+
"forms.topolo.app"
|
|
734
|
+
],
|
|
735
|
+
"routes": [
|
|
736
|
+
"forms.topolo.app"
|
|
737
|
+
],
|
|
738
|
+
"serviceId": "forms"
|
|
739
|
+
}
|
|
740
|
+
]
|
|
741
|
+
},
|
|
742
|
+
"home": {
|
|
743
|
+
"id": "home",
|
|
744
|
+
"appDir": "TopoloHome",
|
|
745
|
+
"name": "topolo-home",
|
|
746
|
+
"packageName": "topolo-home",
|
|
747
|
+
"productionUrl": "https://home.topolo.app",
|
|
748
|
+
"services": [
|
|
749
|
+
"home"
|
|
750
|
+
],
|
|
751
|
+
"deployTargets": [
|
|
752
|
+
{
|
|
753
|
+
"name": "topolo-home",
|
|
754
|
+
"kind": "worker",
|
|
755
|
+
"apiUrl": "https://home.topolo.app",
|
|
756
|
+
"domains": [
|
|
757
|
+
"home.topolo.app"
|
|
758
|
+
],
|
|
759
|
+
"routes": [
|
|
760
|
+
"home.topolo.app"
|
|
761
|
+
],
|
|
762
|
+
"serviceId": "home"
|
|
763
|
+
}
|
|
764
|
+
]
|
|
765
|
+
},
|
|
766
|
+
"insights": {
|
|
767
|
+
"id": "insights",
|
|
768
|
+
"appDir": "TopoloInsights",
|
|
769
|
+
"name": "topolo-insights",
|
|
770
|
+
"packageName": "topolo-insights",
|
|
771
|
+
"productionUrl": "https://insights.topolo.app",
|
|
772
|
+
"services": [
|
|
773
|
+
"insights"
|
|
774
|
+
],
|
|
775
|
+
"deployTargets": [
|
|
776
|
+
{
|
|
777
|
+
"name": "topolo-insights",
|
|
778
|
+
"kind": "worker",
|
|
779
|
+
"apiUrl": "https://insights.topolo.app",
|
|
780
|
+
"domains": [
|
|
781
|
+
"insights.topolo.app"
|
|
782
|
+
],
|
|
783
|
+
"routes": [
|
|
784
|
+
"insights.topolo.app"
|
|
785
|
+
],
|
|
786
|
+
"serviceId": "insights"
|
|
787
|
+
}
|
|
788
|
+
]
|
|
789
|
+
},
|
|
790
|
+
"inventory": {
|
|
791
|
+
"id": "inventory",
|
|
792
|
+
"appDir": "TopoloInventory",
|
|
793
|
+
"name": "topolo-inventory",
|
|
794
|
+
"packageName": "topolo-inventory",
|
|
795
|
+
"productionUrl": "https://inventory.topolo.app",
|
|
796
|
+
"services": [
|
|
797
|
+
"inventory"
|
|
798
|
+
],
|
|
799
|
+
"deployTargets": [
|
|
800
|
+
{
|
|
801
|
+
"name": "topolo-inventory",
|
|
802
|
+
"kind": "worker",
|
|
803
|
+
"apiUrl": "https://inventory.topolo.app",
|
|
804
|
+
"domains": [
|
|
805
|
+
"inventory.topolo.app"
|
|
806
|
+
],
|
|
807
|
+
"routes": [
|
|
808
|
+
"inventory.topolo.app"
|
|
809
|
+
],
|
|
810
|
+
"serviceId": "inventory"
|
|
811
|
+
}
|
|
812
|
+
]
|
|
813
|
+
},
|
|
472
814
|
"learn": {
|
|
473
815
|
"id": "learn",
|
|
474
816
|
"appDir": "TopoloLearn",
|
|
@@ -480,7 +822,7 @@ var APPLICATIONS = {
|
|
|
480
822
|
],
|
|
481
823
|
"deployTargets": [
|
|
482
824
|
{
|
|
483
|
-
"name": "
|
|
825
|
+
"name": "topolo-learn",
|
|
484
826
|
"kind": "pages",
|
|
485
827
|
"apiUrl": null,
|
|
486
828
|
"domains": [
|
|
@@ -490,17 +832,41 @@ var APPLICATIONS = {
|
|
|
490
832
|
"serviceId": null
|
|
491
833
|
},
|
|
492
834
|
{
|
|
493
|
-
"name": "
|
|
835
|
+
"name": "topolo-learn-api",
|
|
494
836
|
"kind": "worker",
|
|
495
|
-
"apiUrl": "https://
|
|
837
|
+
"apiUrl": "https://learn-api.topolo.app",
|
|
496
838
|
"domains": [
|
|
497
|
-
"
|
|
839
|
+
"learn-api.topolo.app"
|
|
498
840
|
],
|
|
499
841
|
"routes": [],
|
|
500
842
|
"serviceId": "learn"
|
|
501
843
|
}
|
|
502
844
|
]
|
|
503
845
|
},
|
|
846
|
+
"localize": {
|
|
847
|
+
"id": "localize",
|
|
848
|
+
"appDir": "TopoloLocalize",
|
|
849
|
+
"name": "topolo-localize",
|
|
850
|
+
"packageName": "topolo-localize",
|
|
851
|
+
"productionUrl": "https://localize.topolo.app",
|
|
852
|
+
"services": [
|
|
853
|
+
"localize"
|
|
854
|
+
],
|
|
855
|
+
"deployTargets": [
|
|
856
|
+
{
|
|
857
|
+
"name": "topolo-localize",
|
|
858
|
+
"kind": "worker",
|
|
859
|
+
"apiUrl": "https://localize.topolo.app",
|
|
860
|
+
"domains": [
|
|
861
|
+
"localize.topolo.app"
|
|
862
|
+
],
|
|
863
|
+
"routes": [
|
|
864
|
+
"localize.topolo.app"
|
|
865
|
+
],
|
|
866
|
+
"serviceId": "localize"
|
|
867
|
+
}
|
|
868
|
+
]
|
|
869
|
+
},
|
|
504
870
|
"mail": {
|
|
505
871
|
"id": "mail",
|
|
506
872
|
"appDir": "TopoloMail",
|
|
@@ -538,7 +904,7 @@ var APPLICATIONS = {
|
|
|
538
904
|
"id": "mdm",
|
|
539
905
|
"appDir": "TopoloMDM",
|
|
540
906
|
"name": "TopoloMDM",
|
|
541
|
-
"packageName":
|
|
907
|
+
"packageName": "topolo-mdm",
|
|
542
908
|
"productionUrl": "https://mdm.topolo.app",
|
|
543
909
|
"services": [
|
|
544
910
|
"mdm"
|
|
@@ -547,12 +913,12 @@ var APPLICATIONS = {
|
|
|
547
913
|
{
|
|
548
914
|
"name": "topolo-mdm-api",
|
|
549
915
|
"kind": "worker",
|
|
550
|
-
"apiUrl": "https://
|
|
916
|
+
"apiUrl": "https://mdm-api.topolo.app",
|
|
551
917
|
"domains": [
|
|
552
|
-
"
|
|
918
|
+
"mdm-api.topolo.app"
|
|
553
919
|
],
|
|
554
920
|
"routes": [
|
|
555
|
-
"
|
|
921
|
+
"mdm-api.topolo.app"
|
|
556
922
|
],
|
|
557
923
|
"serviceId": "mdm"
|
|
558
924
|
},
|
|
@@ -561,8 +927,7 @@ var APPLICATIONS = {
|
|
|
561
927
|
"kind": "pages",
|
|
562
928
|
"apiUrl": null,
|
|
563
929
|
"domains": [
|
|
564
|
-
"mdm.topolo.app"
|
|
565
|
-
"topolo-mdm-console.pages.dev"
|
|
930
|
+
"mdm.topolo.app"
|
|
566
931
|
],
|
|
567
932
|
"routes": [
|
|
568
933
|
"mdm.topolo.app"
|
|
@@ -616,8 +981,7 @@ var APPLICATIONS = {
|
|
|
616
981
|
"kind": "pages",
|
|
617
982
|
"apiUrl": null,
|
|
618
983
|
"domains": [
|
|
619
|
-
"nexus.topolo.app"
|
|
620
|
-
"topolo-nexus-dashboard.pages.dev"
|
|
984
|
+
"nexus.topolo.app"
|
|
621
985
|
],
|
|
622
986
|
"routes": [],
|
|
623
987
|
"serviceId": null
|
|
@@ -625,15 +989,48 @@ var APPLICATIONS = {
|
|
|
625
989
|
{
|
|
626
990
|
"name": "topolo-nexus-gateway",
|
|
627
991
|
"kind": "worker",
|
|
628
|
-
"apiUrl": "https://
|
|
992
|
+
"apiUrl": "https://nexus-api.topolo.app",
|
|
629
993
|
"domains": [
|
|
630
|
-
"
|
|
994
|
+
"nexus-api.topolo.app"
|
|
631
995
|
],
|
|
632
996
|
"routes": [],
|
|
633
997
|
"serviceId": "nexus"
|
|
634
998
|
}
|
|
635
999
|
]
|
|
636
1000
|
},
|
|
1001
|
+
"notify": {
|
|
1002
|
+
"id": "notify",
|
|
1003
|
+
"appDir": "TopoloNotify",
|
|
1004
|
+
"name": "topolo-notify",
|
|
1005
|
+
"packageName": "@topolo/notify",
|
|
1006
|
+
"productionUrl": "https://notify.topolo.app",
|
|
1007
|
+
"services": [
|
|
1008
|
+
"notify"
|
|
1009
|
+
],
|
|
1010
|
+
"deployTargets": [
|
|
1011
|
+
{
|
|
1012
|
+
"name": "topolo-notify",
|
|
1013
|
+
"kind": "worker",
|
|
1014
|
+
"apiUrl": "https://notify.topolo.app",
|
|
1015
|
+
"domains": [
|
|
1016
|
+
"notify.topolo.app"
|
|
1017
|
+
],
|
|
1018
|
+
"routes": [
|
|
1019
|
+
"notify.topolo.app"
|
|
1020
|
+
],
|
|
1021
|
+
"serviceId": "notify"
|
|
1022
|
+
}
|
|
1023
|
+
]
|
|
1024
|
+
},
|
|
1025
|
+
"observability_watch": {
|
|
1026
|
+
"id": "observability_watch",
|
|
1027
|
+
"appDir": "TopoloObservabilityWatch",
|
|
1028
|
+
"name": "topolo-observability-watch",
|
|
1029
|
+
"packageName": "topolo-observability-watch",
|
|
1030
|
+
"productionUrl": null,
|
|
1031
|
+
"services": [],
|
|
1032
|
+
"deployTargets": []
|
|
1033
|
+
},
|
|
637
1034
|
"one": {
|
|
638
1035
|
"id": "one",
|
|
639
1036
|
"appDir": "TopoloOne",
|
|
@@ -641,51 +1038,83 @@ var APPLICATIONS = {
|
|
|
641
1038
|
"packageName": "topolo-oneclick-sso",
|
|
642
1039
|
"productionUrl": "https://one.topolo.app",
|
|
643
1040
|
"services": [
|
|
644
|
-
"one"
|
|
1041
|
+
"one",
|
|
1042
|
+
"one.ai"
|
|
645
1043
|
],
|
|
646
1044
|
"deployTargets": [
|
|
647
1045
|
{
|
|
648
1046
|
"name": "ai-chat-rag",
|
|
649
1047
|
"kind": "worker",
|
|
650
|
-
"apiUrl":
|
|
1048
|
+
"apiUrl": "https://ai.one.topolo.app",
|
|
651
1049
|
"domains": [
|
|
652
|
-
"ai
|
|
1050
|
+
"ai.one.topolo.app"
|
|
653
1051
|
],
|
|
654
1052
|
"routes": [],
|
|
655
|
-
"serviceId":
|
|
1053
|
+
"serviceId": "one.ai"
|
|
656
1054
|
},
|
|
657
1055
|
{
|
|
658
|
-
"name": "
|
|
1056
|
+
"name": "topolo-one",
|
|
659
1057
|
"kind": "pages",
|
|
660
1058
|
"apiUrl": null,
|
|
661
1059
|
"domains": [
|
|
662
|
-
"
|
|
1060
|
+
"one.topolo.app"
|
|
663
1061
|
],
|
|
664
1062
|
"routes": [],
|
|
665
1063
|
"serviceId": null
|
|
666
1064
|
},
|
|
667
1065
|
{
|
|
668
|
-
"name": "
|
|
1066
|
+
"name": "topolo-one-website",
|
|
669
1067
|
"kind": "worker",
|
|
670
1068
|
"apiUrl": null,
|
|
671
1069
|
"domains": [
|
|
672
|
-
"
|
|
1070
|
+
"topolo.io",
|
|
1071
|
+
"www.topolo.io"
|
|
673
1072
|
],
|
|
674
1073
|
"routes": [],
|
|
675
1074
|
"serviceId": null
|
|
676
1075
|
},
|
|
677
1076
|
{
|
|
678
|
-
"name": "
|
|
1077
|
+
"name": "topolo-one-worker",
|
|
679
1078
|
"kind": "worker",
|
|
680
1079
|
"apiUrl": "https://one.topolo.app",
|
|
681
|
-
"domains": [
|
|
1080
|
+
"domains": [
|
|
1081
|
+
"one.topolo.app",
|
|
1082
|
+
"topolo.app",
|
|
1083
|
+
"www.topolo.app"
|
|
1084
|
+
],
|
|
682
1085
|
"routes": [
|
|
683
|
-
"one.topolo.app/*"
|
|
1086
|
+
"one.topolo.app/*",
|
|
1087
|
+
"topolo.app",
|
|
1088
|
+
"www.topolo.app/*"
|
|
684
1089
|
],
|
|
685
1090
|
"serviceId": "one"
|
|
686
1091
|
}
|
|
687
1092
|
]
|
|
688
1093
|
},
|
|
1094
|
+
"p2p": {
|
|
1095
|
+
"id": "p2p",
|
|
1096
|
+
"appDir": "TopoloP2P",
|
|
1097
|
+
"name": "topolo-p2p",
|
|
1098
|
+
"packageName": "topolo-p2p",
|
|
1099
|
+
"productionUrl": "https://p2p.topolo.app",
|
|
1100
|
+
"services": [
|
|
1101
|
+
"p2p"
|
|
1102
|
+
],
|
|
1103
|
+
"deployTargets": [
|
|
1104
|
+
{
|
|
1105
|
+
"name": "topolo-p2p",
|
|
1106
|
+
"kind": "worker",
|
|
1107
|
+
"apiUrl": "https://p2p.topolo.app",
|
|
1108
|
+
"domains": [
|
|
1109
|
+
"p2p.topolo.app"
|
|
1110
|
+
],
|
|
1111
|
+
"routes": [
|
|
1112
|
+
"p2p.topolo.app"
|
|
1113
|
+
],
|
|
1114
|
+
"serviceId": "p2p"
|
|
1115
|
+
}
|
|
1116
|
+
]
|
|
1117
|
+
},
|
|
689
1118
|
"pay": {
|
|
690
1119
|
"id": "pay",
|
|
691
1120
|
"appDir": "TopoloPay",
|
|
@@ -699,18 +1128,53 @@ var APPLICATIONS = {
|
|
|
699
1128
|
{
|
|
700
1129
|
"name": "topolo-pay",
|
|
701
1130
|
"kind": "worker",
|
|
702
|
-
"apiUrl": "https://
|
|
703
|
-
"domains": [
|
|
1131
|
+
"apiUrl": "https://pay.topolo.app",
|
|
1132
|
+
"domains": [
|
|
1133
|
+
"pay.topolo.app"
|
|
1134
|
+
],
|
|
704
1135
|
"routes": [],
|
|
705
1136
|
"serviceId": "pay"
|
|
706
1137
|
}
|
|
707
1138
|
]
|
|
708
1139
|
},
|
|
1140
|
+
"people": {
|
|
1141
|
+
"id": "people",
|
|
1142
|
+
"appDir": "TopoloPeople",
|
|
1143
|
+
"name": "topolo-people",
|
|
1144
|
+
"packageName": "topolo-people",
|
|
1145
|
+
"productionUrl": "https://people.topolo.app",
|
|
1146
|
+
"services": [
|
|
1147
|
+
"people"
|
|
1148
|
+
],
|
|
1149
|
+
"deployTargets": [
|
|
1150
|
+
{
|
|
1151
|
+
"name": "topolo-people",
|
|
1152
|
+
"kind": "worker",
|
|
1153
|
+
"apiUrl": "https://people.topolo.app",
|
|
1154
|
+
"domains": [
|
|
1155
|
+
"people.topolo.app"
|
|
1156
|
+
],
|
|
1157
|
+
"routes": [
|
|
1158
|
+
"people.topolo.app"
|
|
1159
|
+
],
|
|
1160
|
+
"serviceId": "people"
|
|
1161
|
+
}
|
|
1162
|
+
]
|
|
1163
|
+
},
|
|
1164
|
+
"pulse": {
|
|
1165
|
+
"id": "pulse",
|
|
1166
|
+
"appDir": "TopoloPulse",
|
|
1167
|
+
"name": "topolo-pulse",
|
|
1168
|
+
"packageName": "topolo-pulse",
|
|
1169
|
+
"productionUrl": null,
|
|
1170
|
+
"services": [],
|
|
1171
|
+
"deployTargets": []
|
|
1172
|
+
},
|
|
709
1173
|
"quro": {
|
|
710
1174
|
"id": "quro",
|
|
711
1175
|
"appDir": "TopoloQuro",
|
|
712
1176
|
"name": "TopoloQuro",
|
|
713
|
-
"packageName":
|
|
1177
|
+
"packageName": "topolo-quro",
|
|
714
1178
|
"productionUrl": "https://quro.topolo.app",
|
|
715
1179
|
"services": [
|
|
716
1180
|
"quro.api",
|
|
@@ -721,7 +1185,9 @@ var APPLICATIONS = {
|
|
|
721
1185
|
"name": "quro-api",
|
|
722
1186
|
"kind": "worker",
|
|
723
1187
|
"apiUrl": "https://api.ol0.me",
|
|
724
|
-
"domains": [
|
|
1188
|
+
"domains": [
|
|
1189
|
+
"api.ol0.me"
|
|
1190
|
+
],
|
|
725
1191
|
"routes": [],
|
|
726
1192
|
"serviceId": "quro.api"
|
|
727
1193
|
},
|
|
@@ -729,7 +1195,9 @@ var APPLICATIONS = {
|
|
|
729
1195
|
"name": "quro-redirect-system",
|
|
730
1196
|
"kind": "worker",
|
|
731
1197
|
"apiUrl": "https://ol0.me",
|
|
732
|
-
"domains": [
|
|
1198
|
+
"domains": [
|
|
1199
|
+
"ol0.me"
|
|
1200
|
+
],
|
|
733
1201
|
"routes": [
|
|
734
1202
|
"ol0.me/*"
|
|
735
1203
|
],
|
|
@@ -739,7 +1207,9 @@ var APPLICATIONS = {
|
|
|
739
1207
|
"name": "quro-ui",
|
|
740
1208
|
"kind": "pages",
|
|
741
1209
|
"apiUrl": null,
|
|
742
|
-
"domains": [
|
|
1210
|
+
"domains": [
|
|
1211
|
+
"quro.topolo.app"
|
|
1212
|
+
],
|
|
743
1213
|
"routes": [],
|
|
744
1214
|
"serviceId": null
|
|
745
1215
|
}
|
|
@@ -756,7 +1226,7 @@ var APPLICATIONS = {
|
|
|
756
1226
|
],
|
|
757
1227
|
"deployTargets": [
|
|
758
1228
|
{
|
|
759
|
-
"name": "roadmapper",
|
|
1229
|
+
"name": "topolo-roadmapper",
|
|
760
1230
|
"kind": "pages",
|
|
761
1231
|
"apiUrl": null,
|
|
762
1232
|
"domains": [
|
|
@@ -766,7 +1236,7 @@ var APPLICATIONS = {
|
|
|
766
1236
|
"serviceId": null
|
|
767
1237
|
},
|
|
768
1238
|
{
|
|
769
|
-
"name": "topolo-
|
|
1239
|
+
"name": "topolo-roadmapper-api",
|
|
770
1240
|
"kind": "worker",
|
|
771
1241
|
"apiUrl": "https://roadmapper-api.topolo.app",
|
|
772
1242
|
"domains": [
|
|
@@ -777,6 +1247,30 @@ var APPLICATIONS = {
|
|
|
777
1247
|
}
|
|
778
1248
|
]
|
|
779
1249
|
},
|
|
1250
|
+
"sign": {
|
|
1251
|
+
"id": "sign",
|
|
1252
|
+
"appDir": "TopoloSign",
|
|
1253
|
+
"name": "topolo-sign",
|
|
1254
|
+
"packageName": "topolo-sign",
|
|
1255
|
+
"productionUrl": "https://sign.topolo.app",
|
|
1256
|
+
"services": [
|
|
1257
|
+
"sign"
|
|
1258
|
+
],
|
|
1259
|
+
"deployTargets": [
|
|
1260
|
+
{
|
|
1261
|
+
"name": "topolo-sign",
|
|
1262
|
+
"kind": "worker",
|
|
1263
|
+
"apiUrl": "https://sign.topolo.app",
|
|
1264
|
+
"domains": [
|
|
1265
|
+
"sign.topolo.app"
|
|
1266
|
+
],
|
|
1267
|
+
"routes": [
|
|
1268
|
+
"sign.topolo.app"
|
|
1269
|
+
],
|
|
1270
|
+
"serviceId": "sign"
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
780
1274
|
"social_studio": {
|
|
781
1275
|
"id": "social_studio",
|
|
782
1276
|
"appDir": "TopoloSocialStudio",
|
|
@@ -791,15 +1285,19 @@ var APPLICATIONS = {
|
|
|
791
1285
|
"name": "topolo-social-studio",
|
|
792
1286
|
"kind": "pages",
|
|
793
1287
|
"apiUrl": null,
|
|
794
|
-
"domains": [
|
|
1288
|
+
"domains": [
|
|
1289
|
+
"studio.topolo.app"
|
|
1290
|
+
],
|
|
795
1291
|
"routes": [],
|
|
796
1292
|
"serviceId": null
|
|
797
1293
|
},
|
|
798
1294
|
{
|
|
799
1295
|
"name": "topolo-social-studio-api",
|
|
800
1296
|
"kind": "worker",
|
|
801
|
-
"apiUrl": "https://
|
|
802
|
-
"domains": [
|
|
1297
|
+
"apiUrl": "https://studio-api.topolo.app",
|
|
1298
|
+
"domains": [
|
|
1299
|
+
"studio-api.topolo.app"
|
|
1300
|
+
],
|
|
803
1301
|
"routes": [],
|
|
804
1302
|
"serviceId": "social_studio"
|
|
805
1303
|
},
|
|
@@ -824,23 +1322,27 @@ var APPLICATIONS = {
|
|
|
824
1322
|
],
|
|
825
1323
|
"deployTargets": [
|
|
826
1324
|
{
|
|
827
|
-
"name": "socialize",
|
|
1325
|
+
"name": "topolo-socialize",
|
|
828
1326
|
"kind": "pages",
|
|
829
1327
|
"apiUrl": null,
|
|
830
|
-
"domains": [
|
|
1328
|
+
"domains": [
|
|
1329
|
+
"socialize.topolo.app"
|
|
1330
|
+
],
|
|
831
1331
|
"routes": [],
|
|
832
1332
|
"serviceId": null
|
|
833
1333
|
},
|
|
834
1334
|
{
|
|
835
|
-
"name": "socialize-
|
|
1335
|
+
"name": "topolo-socialize-api",
|
|
836
1336
|
"kind": "worker",
|
|
837
|
-
"apiUrl": "https://socialize-
|
|
838
|
-
"domains": [
|
|
1337
|
+
"apiUrl": "https://socialize-api.topolo.app",
|
|
1338
|
+
"domains": [
|
|
1339
|
+
"socialize-api.topolo.app"
|
|
1340
|
+
],
|
|
839
1341
|
"routes": [],
|
|
840
1342
|
"serviceId": "socialize"
|
|
841
1343
|
},
|
|
842
1344
|
{
|
|
843
|
-
"name": "socialize-
|
|
1345
|
+
"name": "topolo-socialize-scheduler",
|
|
844
1346
|
"kind": "worker",
|
|
845
1347
|
"apiUrl": null,
|
|
846
1348
|
"domains": [],
|
|
@@ -849,6 +1351,30 @@ var APPLICATIONS = {
|
|
|
849
1351
|
}
|
|
850
1352
|
]
|
|
851
1353
|
},
|
|
1354
|
+
"status": {
|
|
1355
|
+
"id": "status",
|
|
1356
|
+
"appDir": "TopoloStatus",
|
|
1357
|
+
"name": "topolo-status",
|
|
1358
|
+
"packageName": "topolo-status",
|
|
1359
|
+
"productionUrl": "https://status.topolo.app",
|
|
1360
|
+
"services": [
|
|
1361
|
+
"status"
|
|
1362
|
+
],
|
|
1363
|
+
"deployTargets": [
|
|
1364
|
+
{
|
|
1365
|
+
"name": "topolo-status",
|
|
1366
|
+
"kind": "worker",
|
|
1367
|
+
"apiUrl": "https://status.topolo.app",
|
|
1368
|
+
"domains": [
|
|
1369
|
+
"status.topolo.app"
|
|
1370
|
+
],
|
|
1371
|
+
"routes": [
|
|
1372
|
+
"status.topolo.app"
|
|
1373
|
+
],
|
|
1374
|
+
"serviceId": "status"
|
|
1375
|
+
}
|
|
1376
|
+
]
|
|
1377
|
+
},
|
|
852
1378
|
"support": {
|
|
853
1379
|
"id": "support",
|
|
854
1380
|
"appDir": "TopoloSupport",
|
|
@@ -873,6 +1399,54 @@ var APPLICATIONS = {
|
|
|
873
1399
|
}
|
|
874
1400
|
]
|
|
875
1401
|
},
|
|
1402
|
+
"survey": {
|
|
1403
|
+
"id": "survey",
|
|
1404
|
+
"appDir": "TopoloSurvey",
|
|
1405
|
+
"name": "topolo-survey",
|
|
1406
|
+
"packageName": "topolo-survey",
|
|
1407
|
+
"productionUrl": "https://survey.topolo.app",
|
|
1408
|
+
"services": [
|
|
1409
|
+
"survey"
|
|
1410
|
+
],
|
|
1411
|
+
"deployTargets": [
|
|
1412
|
+
{
|
|
1413
|
+
"name": "topolo-survey",
|
|
1414
|
+
"kind": "worker",
|
|
1415
|
+
"apiUrl": "https://survey.topolo.app",
|
|
1416
|
+
"domains": [
|
|
1417
|
+
"survey.topolo.app"
|
|
1418
|
+
],
|
|
1419
|
+
"routes": [
|
|
1420
|
+
"survey.topolo.app"
|
|
1421
|
+
],
|
|
1422
|
+
"serviceId": "survey"
|
|
1423
|
+
}
|
|
1424
|
+
]
|
|
1425
|
+
},
|
|
1426
|
+
"voice": {
|
|
1427
|
+
"id": "voice",
|
|
1428
|
+
"appDir": "TopoloVoice",
|
|
1429
|
+
"name": "topolo-voice",
|
|
1430
|
+
"packageName": "topolo-voice",
|
|
1431
|
+
"productionUrl": "https://voice.topolo.app",
|
|
1432
|
+
"services": [
|
|
1433
|
+
"voice"
|
|
1434
|
+
],
|
|
1435
|
+
"deployTargets": [
|
|
1436
|
+
{
|
|
1437
|
+
"name": "topolo-voice",
|
|
1438
|
+
"kind": "worker",
|
|
1439
|
+
"apiUrl": "https://voice.topolo.app",
|
|
1440
|
+
"domains": [
|
|
1441
|
+
"voice.topolo.app"
|
|
1442
|
+
],
|
|
1443
|
+
"routes": [
|
|
1444
|
+
"voice.topolo.app"
|
|
1445
|
+
],
|
|
1446
|
+
"serviceId": "voice"
|
|
1447
|
+
}
|
|
1448
|
+
]
|
|
1449
|
+
},
|
|
876
1450
|
"web": {
|
|
877
1451
|
"id": "web",
|
|
878
1452
|
"appDir": "TopoloWeb",
|
|
@@ -884,7 +1458,7 @@ var APPLICATIONS = {
|
|
|
884
1458
|
],
|
|
885
1459
|
"deployTargets": [
|
|
886
1460
|
{
|
|
887
|
-
"name": "
|
|
1461
|
+
"name": "topolo-web-api",
|
|
888
1462
|
"kind": "worker",
|
|
889
1463
|
"apiUrl": "https://web-api.topolo.app",
|
|
890
1464
|
"domains": [
|
|
@@ -894,7 +1468,7 @@ var APPLICATIONS = {
|
|
|
894
1468
|
"serviceId": "web"
|
|
895
1469
|
},
|
|
896
1470
|
{
|
|
897
|
-
"name": "
|
|
1471
|
+
"name": "topolo-web-runtime",
|
|
898
1472
|
"kind": "worker",
|
|
899
1473
|
"apiUrl": "https://sites.topolo.app",
|
|
900
1474
|
"domains": [
|
|
@@ -904,7 +1478,7 @@ var APPLICATIONS = {
|
|
|
904
1478
|
"serviceId": null
|
|
905
1479
|
},
|
|
906
1480
|
{
|
|
907
|
-
"name": "
|
|
1481
|
+
"name": "topolo-web-studio",
|
|
908
1482
|
"kind": "pages",
|
|
909
1483
|
"apiUrl": null,
|
|
910
1484
|
"domains": [
|
|
@@ -916,7 +1490,7 @@ var APPLICATIONS = {
|
|
|
916
1490
|
]
|
|
917
1491
|
}
|
|
918
1492
|
};
|
|
919
|
-
var APPLICATION_REQUIREMENTS_VERSION = "2026-
|
|
1493
|
+
var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
|
|
920
1494
|
var APPLICATION_REQUIREMENTS = [
|
|
921
1495
|
{
|
|
922
1496
|
id: "platform-metadata",
|
|
@@ -974,14 +1548,16 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
974
1548
|
evidence: [
|
|
975
1549
|
"@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
|
|
976
1550
|
"The shared launcher/app switcher is reachable from authenticated app chrome",
|
|
977
|
-
"Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu"
|
|
1551
|
+
"Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
|
|
1552
|
+
"Authenticated app body content stays inside the viewport on mobile and desktop widths"
|
|
978
1553
|
],
|
|
979
1554
|
implementation: [
|
|
980
1555
|
"Use TopoloAppShell for authenticated app layouts",
|
|
981
1556
|
"Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
|
|
982
1557
|
"Pass app-specific account actions as additive menu items instead of forking dropdowns",
|
|
983
1558
|
"Use shared design tokens and platform UI primitives before creating local equivalents",
|
|
984
|
-
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
1559
|
+
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1560
|
+
"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"
|
|
985
1561
|
]
|
|
986
1562
|
},
|
|
987
1563
|
{
|
|
@@ -1048,6 +1624,22 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1048
1624
|
"Keep command output JSON-stable for agents and human-readable with --no-json"
|
|
1049
1625
|
]
|
|
1050
1626
|
},
|
|
1627
|
+
{
|
|
1628
|
+
id: "native-dashboard-widget",
|
|
1629
|
+
title: "Expose a native dashboard widget contract",
|
|
1630
|
+
summary: "Launchable first-party applications must populate TopoloOne live workspace through an app-owned /api/widget endpoint using the SDK widget response contract.",
|
|
1631
|
+
appliesTo: ["api"],
|
|
1632
|
+
evidence: [
|
|
1633
|
+
"The app-owned API exposes GET /api/widget",
|
|
1634
|
+
"The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
|
|
1635
|
+
"The endpoint derives organization/user context from the app auth boundary"
|
|
1636
|
+
],
|
|
1637
|
+
implementation: [
|
|
1638
|
+
"Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
|
|
1639
|
+
"Return native app metrics or action widgets instead of generic launch-card fallbacks",
|
|
1640
|
+
"Add a route-level test that validates the response with validateTopoloWidgetResponse"
|
|
1641
|
+
]
|
|
1642
|
+
},
|
|
1051
1643
|
{
|
|
1052
1644
|
id: "observability-and-audit",
|
|
1053
1645
|
title: "Emit platform audit and debugging signals",
|
|
@@ -1083,9 +1675,8 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1083
1675
|
];
|
|
1084
1676
|
function applicationRequirementScopes(application) {
|
|
1085
1677
|
const scopes = /* @__PURE__ */ new Set(["all"]);
|
|
1086
|
-
const
|
|
1087
|
-
const
|
|
1088
|
-
const hasApiSurface = application.services.length > 0 || deployTargets.some((target) => Boolean(target.apiUrl));
|
|
1678
|
+
const hasBrowserSurface = Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === "pages");
|
|
1679
|
+
const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.apiUrl));
|
|
1089
1680
|
if (hasBrowserSurface) scopes.add("browser");
|
|
1090
1681
|
if (hasApiSurface) scopes.add("api");
|
|
1091
1682
|
if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
|
|
@@ -1220,6 +1811,13 @@ function evaluateRequirement(application, requirement) {
|
|
|
1220
1811
|
}
|
|
1221
1812
|
return finding(requirement, "missing", "No SDK service or tooling surface is registered.", evidence);
|
|
1222
1813
|
}
|
|
1814
|
+
case "native-dashboard-widget":
|
|
1815
|
+
return finding(
|
|
1816
|
+
requirement,
|
|
1817
|
+
"needs_review",
|
|
1818
|
+
"Catalog metadata cannot verify app-owned /api/widget implementation details.",
|
|
1819
|
+
evidence
|
|
1820
|
+
);
|
|
1223
1821
|
case "observability-and-audit":
|
|
1224
1822
|
return finding(
|
|
1225
1823
|
requirement,
|
|
@@ -1271,11 +1869,10 @@ function statusRank(status) {
|
|
|
1271
1869
|
}
|
|
1272
1870
|
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
1273
1871
|
function resolveServiceUrl(service, overrides) {
|
|
1274
|
-
|
|
1275
|
-
const override = overrides == null ? void 0 : overrides[service];
|
|
1872
|
+
const override = overrides?.[service];
|
|
1276
1873
|
if (override) return override;
|
|
1277
1874
|
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
1278
|
-
const envValue = typeof process !== "undefined" ?
|
|
1875
|
+
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
1279
1876
|
if (envValue) return envValue;
|
|
1280
1877
|
return DEFAULT_SERVICE_URLS[service];
|
|
1281
1878
|
}
|
|
@@ -1289,9 +1886,8 @@ var TopoloClient = class {
|
|
|
1289
1886
|
fetchImpl;
|
|
1290
1887
|
debug;
|
|
1291
1888
|
constructor(options) {
|
|
1292
|
-
var _a;
|
|
1293
1889
|
if (!options.credential) throw new TopoloAuthError("credential is required");
|
|
1294
|
-
if (!
|
|
1890
|
+
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
1295
1891
|
this.credential = options.credential;
|
|
1296
1892
|
this.agent = options.agent;
|
|
1297
1893
|
this.serviceUrls = options.serviceUrls;
|
|
@@ -1479,7 +2075,6 @@ var CrmModule = class {
|
|
|
1479
2075
|
}
|
|
1480
2076
|
client;
|
|
1481
2077
|
async listContacts(options = {}) {
|
|
1482
|
-
var _a, _b, _c;
|
|
1483
2078
|
const res = await this.client.request({
|
|
1484
2079
|
service: "crm",
|
|
1485
2080
|
path: "/api/contacts",
|
|
@@ -1489,12 +2084,12 @@ var CrmModule = class {
|
|
|
1489
2084
|
pageSize: options.pageSize
|
|
1490
2085
|
}
|
|
1491
2086
|
});
|
|
1492
|
-
const rows = Array.isArray(res
|
|
2087
|
+
const rows = Array.isArray(res?.contacts) ? res.contacts : Array.isArray(res?.data) ? res.data : [];
|
|
1493
2088
|
return {
|
|
1494
2089
|
contacts: rows.map(toSummary),
|
|
1495
|
-
total:
|
|
1496
|
-
page:
|
|
1497
|
-
pageSize:
|
|
2090
|
+
total: res?.total ?? res?.pagination?.total ?? rows.length,
|
|
2091
|
+
page: res?.page ?? res?.pagination?.page ?? options.page ?? 1,
|
|
2092
|
+
pageSize: res?.pageSize ?? res?.pagination?.pageSize ?? options.pageSize ?? rows.length
|
|
1498
2093
|
};
|
|
1499
2094
|
}
|
|
1500
2095
|
async getContact(contactId) {
|
|
@@ -1588,8 +2183,8 @@ var TopoloOAuth = class {
|
|
|
1588
2183
|
if (params.clientSecret) body.set("client_secret", params.clientSecret);
|
|
1589
2184
|
return this.#postForm("/api/developer-oauth/token", body);
|
|
1590
2185
|
}
|
|
1591
|
-
async #postForm(
|
|
1592
|
-
const url = new URL(
|
|
2186
|
+
async #postForm(path4, body) {
|
|
2187
|
+
const url = new URL(path4, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
|
|
1593
2188
|
const res = await this.fetchImpl(url.toString(), {
|
|
1594
2189
|
method: "POST",
|
|
1595
2190
|
headers: {
|
|
@@ -1605,7 +2200,7 @@ var TopoloOAuth = class {
|
|
|
1605
2200
|
const err = parsed;
|
|
1606
2201
|
throw new TopoloAuthError(err.error_description || err.error, err.error);
|
|
1607
2202
|
}
|
|
1608
|
-
throw new TopoloHttpError("auth",
|
|
2203
|
+
throw new TopoloHttpError("auth", path4, res.status, parsed);
|
|
1609
2204
|
}
|
|
1610
2205
|
return parsed;
|
|
1611
2206
|
}
|
|
@@ -1624,6 +2219,16 @@ import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
|
1624
2219
|
import { existsSync } from "fs";
|
|
1625
2220
|
import { homedir, platform } from "os";
|
|
1626
2221
|
import { dirname, join } from "path";
|
|
2222
|
+
var ENVIRONMENT_DEFAULT_SERVICE_URLS = {
|
|
2223
|
+
production: {
|
|
2224
|
+
developers: "https://developers.topolo.app"
|
|
2225
|
+
},
|
|
2226
|
+
staging: {
|
|
2227
|
+
auth: "https://auth.stg.topolo.us",
|
|
2228
|
+
developers: "https://developers.stg.topolo.us",
|
|
2229
|
+
seed: "https://seed.stg.topolo.us"
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
1627
2232
|
function configPath() {
|
|
1628
2233
|
const override = process.env.TOPOLO_CONFIG_PATH;
|
|
1629
2234
|
if (override) return override;
|
|
@@ -1631,51 +2236,133 @@ function configPath() {
|
|
|
1631
2236
|
return join(base, "topolo", "config.json");
|
|
1632
2237
|
}
|
|
1633
2238
|
async function readConfig() {
|
|
1634
|
-
const
|
|
1635
|
-
if (!existsSync(
|
|
2239
|
+
const path4 = configPath();
|
|
2240
|
+
if (!existsSync(path4)) return {};
|
|
1636
2241
|
try {
|
|
1637
|
-
const raw = await readFile(
|
|
2242
|
+
const raw = await readFile(path4, "utf8");
|
|
1638
2243
|
return JSON.parse(raw);
|
|
1639
2244
|
} catch {
|
|
1640
2245
|
return {};
|
|
1641
2246
|
}
|
|
1642
2247
|
}
|
|
1643
2248
|
async function writeConfig(config) {
|
|
1644
|
-
const
|
|
1645
|
-
await mkdir(dirname(
|
|
1646
|
-
await writeFile(
|
|
2249
|
+
const path4 = configPath();
|
|
2250
|
+
await mkdir(dirname(path4), { recursive: true });
|
|
2251
|
+
await writeFile(path4, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1647
2252
|
if (platform() !== "win32") {
|
|
1648
2253
|
try {
|
|
1649
|
-
await chmod(
|
|
2254
|
+
await chmod(path4, 384);
|
|
1650
2255
|
} catch {
|
|
1651
2256
|
}
|
|
1652
2257
|
}
|
|
1653
2258
|
}
|
|
1654
|
-
|
|
1655
|
-
const
|
|
2259
|
+
function normalizeEnvironment(value) {
|
|
2260
|
+
const normalized = (value ?? "production").trim().toLowerCase();
|
|
2261
|
+
if (normalized === "" || normalized === "production" || normalized === "prod") {
|
|
2262
|
+
return "production";
|
|
2263
|
+
}
|
|
2264
|
+
if (normalized === "staging" || normalized === "stage" || normalized === "stg") {
|
|
2265
|
+
return "staging";
|
|
2266
|
+
}
|
|
2267
|
+
throw new Error(`Unknown Topolo environment "${value}". Expected production or staging.`);
|
|
2268
|
+
}
|
|
2269
|
+
function selectedEnvironment() {
|
|
2270
|
+
return normalizeEnvironment(process.env.TOPOLO_ENV);
|
|
2271
|
+
}
|
|
2272
|
+
function envVarPrefix(environment) {
|
|
2273
|
+
return environment === "production" ? "TOPOLO_PRODUCTION" : "TOPOLO_STAGING";
|
|
2274
|
+
}
|
|
2275
|
+
function environmentLabel(environment = selectedEnvironment()) {
|
|
2276
|
+
return environment === "production" ? "production" : "staging";
|
|
2277
|
+
}
|
|
2278
|
+
function developerConsoleUrl(environment = selectedEnvironment()) {
|
|
2279
|
+
return environment === "production" ? "https://developers.topolo.app" : "https://developers.stg.topolo.us";
|
|
2280
|
+
}
|
|
2281
|
+
function oauthClientRegistrationUrl(environment = selectedEnvironment()) {
|
|
2282
|
+
return `${developerConsoleUrl(environment)}/developers/oauth-clients`;
|
|
2283
|
+
}
|
|
2284
|
+
function apiKeyIssuanceUrl(environment = selectedEnvironment()) {
|
|
2285
|
+
return `${developerConsoleUrl(environment)}/credentials`;
|
|
2286
|
+
}
|
|
2287
|
+
function profileForEnvironment(config, environment = selectedEnvironment()) {
|
|
2288
|
+
const profile = config.profiles?.[environment] ?? {};
|
|
2289
|
+
if (environment === "production") {
|
|
2290
|
+
return {
|
|
2291
|
+
credential: config.credential,
|
|
2292
|
+
oauthClientId: config.oauthClientId,
|
|
2293
|
+
...profile,
|
|
2294
|
+
serviceUrls: {
|
|
2295
|
+
...config.serviceUrls ?? {},
|
|
2296
|
+
...profile.serviceUrls ?? {}
|
|
2297
|
+
},
|
|
2298
|
+
agentName: profile.agentName ?? config.agentName
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
return profile;
|
|
2302
|
+
}
|
|
2303
|
+
function writeProfileConfig(config, environment, profile) {
|
|
2304
|
+
const profiles = { ...config.profiles ?? {} };
|
|
2305
|
+
profiles[environment] = {
|
|
2306
|
+
...profiles[environment] ?? {},
|
|
2307
|
+
...profile
|
|
2308
|
+
};
|
|
2309
|
+
return { ...config, profiles };
|
|
2310
|
+
}
|
|
2311
|
+
function clearProfileCredential(config, environment) {
|
|
2312
|
+
const next = { ...config };
|
|
2313
|
+
if (environment === "production") {
|
|
2314
|
+
delete next.credential;
|
|
2315
|
+
delete next.oauthClientId;
|
|
2316
|
+
}
|
|
2317
|
+
const profiles = { ...config.profiles ?? {} };
|
|
2318
|
+
const profile = { ...profiles[environment] ?? {} };
|
|
2319
|
+
delete profile.credential;
|
|
2320
|
+
delete profile.oauthClientId;
|
|
2321
|
+
profiles[environment] = profile;
|
|
2322
|
+
return { ...next, profiles };
|
|
2323
|
+
}
|
|
2324
|
+
function resolveOAuthClientId(config, environment = selectedEnvironment()) {
|
|
2325
|
+
const envSpecific = process.env[`${envVarPrefix(environment)}_CLI_CLIENT_ID`];
|
|
2326
|
+
if (envSpecific) return envSpecific;
|
|
2327
|
+
if (process.env.TOPOLO_CLI_CLIENT_ID) return process.env.TOPOLO_CLI_CLIENT_ID;
|
|
2328
|
+
return profileForEnvironment(config, environment).oauthClientId;
|
|
2329
|
+
}
|
|
2330
|
+
function credentialSource(config, environment = selectedEnvironment()) {
|
|
2331
|
+
const prefix = envVarPrefix(environment);
|
|
2332
|
+
if (process.env[`${prefix}_API_KEY`]) return `env:${prefix}_API_KEY`;
|
|
2333
|
+
if (process.env[`${prefix}_ACCESS_TOKEN`]) return `env:${prefix}_ACCESS_TOKEN`;
|
|
2334
|
+
if (process.env.TOPOLO_API_KEY) return "env:TOPOLO_API_KEY";
|
|
2335
|
+
if (process.env.TOPOLO_ACCESS_TOKEN) return "env:TOPOLO_ACCESS_TOKEN";
|
|
2336
|
+
return profileForEnvironment(config, environment).credential ? "config-file" : "none";
|
|
2337
|
+
}
|
|
2338
|
+
async function resolveCredential(environment = selectedEnvironment()) {
|
|
2339
|
+
const prefix = envVarPrefix(environment);
|
|
2340
|
+
const apiKey = process.env[`${prefix}_API_KEY`] ?? process.env.TOPOLO_API_KEY;
|
|
1656
2341
|
if (apiKey) return { kind: "api_key", apiKey };
|
|
1657
|
-
const accessToken = process.env.TOPOLO_ACCESS_TOKEN;
|
|
2342
|
+
const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? process.env.TOPOLO_ACCESS_TOKEN;
|
|
1658
2343
|
if (accessToken) return { kind: "access_token", accessToken };
|
|
1659
2344
|
const config = await readConfig();
|
|
1660
|
-
return config.credential ?? null;
|
|
2345
|
+
return profileForEnvironment(config, environment).credential ?? null;
|
|
1661
2346
|
}
|
|
1662
2347
|
var REFRESH_SKEW_SECONDS = 60;
|
|
1663
|
-
async function refreshCredentialIfNeeded() {
|
|
1664
|
-
|
|
1665
|
-
|
|
2348
|
+
async function refreshCredentialIfNeeded(environment = selectedEnvironment()) {
|
|
2349
|
+
const prefix = envVarPrefix(environment);
|
|
2350
|
+
if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || process.env.TOPOLO_API_KEY || process.env.TOPOLO_ACCESS_TOKEN) {
|
|
2351
|
+
return resolveCredential(environment);
|
|
1666
2352
|
}
|
|
1667
2353
|
const config = await readConfig();
|
|
1668
|
-
const
|
|
2354
|
+
const profile = profileForEnvironment(config, environment);
|
|
2355
|
+
const cred = profile.credential;
|
|
1669
2356
|
if (!cred || cred.kind !== "access_token") return cred ?? null;
|
|
1670
|
-
if (!cred.refreshToken || !cred.expiresAt || !
|
|
2357
|
+
if (!cred.refreshToken || !cred.expiresAt || !profile.oauthClientId) return cred;
|
|
1671
2358
|
const now = Math.floor(Date.now() / 1e3);
|
|
1672
2359
|
if (cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
|
|
1673
2360
|
try {
|
|
1674
2361
|
const oauth = new TopoloOAuth({
|
|
1675
|
-
serviceUrls: resolveServiceUrls(
|
|
2362
|
+
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
1676
2363
|
});
|
|
1677
2364
|
const tokens = await oauth.refreshToken({
|
|
1678
|
-
clientId:
|
|
2365
|
+
clientId: profile.oauthClientId,
|
|
1679
2366
|
refreshToken: cred.refreshToken
|
|
1680
2367
|
});
|
|
1681
2368
|
const refreshed = {
|
|
@@ -1684,7 +2371,9 @@ async function refreshCredentialIfNeeded() {
|
|
|
1684
2371
|
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: cred.refreshToken },
|
|
1685
2372
|
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
1686
2373
|
};
|
|
1687
|
-
await writeConfig(
|
|
2374
|
+
await writeConfig(
|
|
2375
|
+
environment === "production" && !config.profiles?.production ? { ...config, credential: refreshed } : writeProfileConfig(config, environment, { credential: refreshed })
|
|
2376
|
+
);
|
|
1688
2377
|
return refreshed;
|
|
1689
2378
|
} catch {
|
|
1690
2379
|
return cred;
|
|
@@ -1693,10 +2382,17 @@ async function refreshCredentialIfNeeded() {
|
|
|
1693
2382
|
function resolveAgentName(configAgentName) {
|
|
1694
2383
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
1695
2384
|
}
|
|
1696
|
-
function resolveServiceUrls(configUrls) {
|
|
2385
|
+
function resolveServiceUrls(configUrls, environment = selectedEnvironment()) {
|
|
1697
2386
|
const merged = {
|
|
2387
|
+
...ENVIRONMENT_DEFAULT_SERVICE_URLS[environment],
|
|
1698
2388
|
...configUrls ?? {}
|
|
1699
2389
|
};
|
|
2390
|
+
const prefix = `${envVarPrefix(environment)}_SERVICE_URL_`;
|
|
2391
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
2392
|
+
if (k.startsWith(prefix) && v) {
|
|
2393
|
+
merged[k.slice(prefix.length).toLowerCase()] = v;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
1700
2396
|
for (const [k, v] of Object.entries(process.env)) {
|
|
1701
2397
|
if (k.startsWith("TOPOLO_SERVICE_URL_") && v) {
|
|
1702
2398
|
merged[k.slice("TOPOLO_SERVICE_URL_".length).toLowerCase()] = v;
|
|
@@ -1704,6 +2400,28 @@ function resolveServiceUrls(configUrls) {
|
|
|
1704
2400
|
}
|
|
1705
2401
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
1706
2402
|
}
|
|
2403
|
+
function resolvedProfileConfig(config, environment = selectedEnvironment()) {
|
|
2404
|
+
const profile = profileForEnvironment(config, environment);
|
|
2405
|
+
return {
|
|
2406
|
+
credential: profile.credential,
|
|
2407
|
+
oauthClientId: profile.oauthClientId,
|
|
2408
|
+
agentName: profile.agentName,
|
|
2409
|
+
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function assertServiceUrlForEnvironment(service, serviceUrl, environment = selectedEnvironment()) {
|
|
2413
|
+
if (environment !== "staging") return;
|
|
2414
|
+
if (!serviceUrl) {
|
|
2415
|
+
throw new Error(`No staging URL is configured for Topolo service "${service}".`);
|
|
2416
|
+
}
|
|
2417
|
+
const url = serviceUrl.toLowerCase();
|
|
2418
|
+
if (url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging")) {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
throw new Error(
|
|
2422
|
+
`Refusing to call ${service} in staging because its effective URL is not a staging endpoint: ${serviceUrl}`
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
1707
2425
|
|
|
1708
2426
|
// src/output.ts
|
|
1709
2427
|
function resolveOutputMode(override) {
|
|
@@ -1750,38 +2468,38 @@ function normalizeError(err) {
|
|
|
1750
2468
|
}
|
|
1751
2469
|
|
|
1752
2470
|
// src/commands/auth.ts
|
|
1753
|
-
var API_KEY_ISSUANCE_URL = "https://developers.topolo.app/credentials";
|
|
1754
|
-
var OAUTH_CLIENT_REGISTRATION_URL = "https://developers.topolo.app/developers/oauth-clients";
|
|
1755
2471
|
function registerAuthCommands(program2) {
|
|
1756
2472
|
const auth = program2.command("auth").description("Manage Topolo credentials");
|
|
1757
2473
|
auth.command("login").description("Authenticate with Topolo via OAuth device flow (default) or store an API key").option("--api-key <key>", "Store an API key directly, skipping the OAuth flow").option("--client-id <id>", "OAuth client_id (or set TOPOLO_CLI_CLIENT_ID)").option("--scope <scope>", "Scopes to request (space-separated)").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(
|
|
1758
2474
|
async (opts) => {
|
|
1759
2475
|
const mode = resolveOutputMode(opts.json);
|
|
2476
|
+
const environment = selectedEnvironment();
|
|
1760
2477
|
try {
|
|
1761
2478
|
if (opts.apiKey) {
|
|
1762
|
-
await storeApiKey(opts.apiKey.trim(), mode);
|
|
2479
|
+
await storeApiKey(opts.apiKey.trim(), environment, mode);
|
|
1763
2480
|
return;
|
|
1764
2481
|
}
|
|
1765
|
-
const
|
|
2482
|
+
const config = await readConfig();
|
|
2483
|
+
const clientId = opts.clientId ?? resolveOAuthClientId(config, environment);
|
|
1766
2484
|
if (!clientId) {
|
|
1767
2485
|
if (!input.isTTY) {
|
|
1768
2486
|
throw new Error(
|
|
1769
|
-
`No OAuth client_id configured and stdin is not a TTY. Either register an OAuth client at ${
|
|
2487
|
+
`No OAuth client_id configured and stdin is not a TTY. Either register an OAuth client at ${oauthClientRegistrationUrl(environment)} and export TOPOLO_${environment === "staging" ? "STAGING" : "PRODUCTION"}_CLI_CLIENT_ID, or pass --api-key <key> (mint one at ${apiKeyIssuanceUrl(environment)}).`
|
|
1770
2488
|
);
|
|
1771
2489
|
}
|
|
1772
2490
|
process.stderr.write(
|
|
1773
|
-
`No OAuth client_id configured. Falling back to API key.
|
|
1774
|
-
Visit ${
|
|
2491
|
+
`No OAuth client_id configured for ${environmentLabel(environment)}. Falling back to API key.
|
|
2492
|
+
Visit ${apiKeyIssuanceUrl(environment)} to mint one, or configure an OAuth client at ${oauthClientRegistrationUrl(environment)} and set the environment-specific CLI client id.
|
|
1775
2493
|
`
|
|
1776
2494
|
);
|
|
1777
2495
|
const rl = createInterface({ input, output: process.stderr });
|
|
1778
2496
|
const apiKey = (await rl.question("Paste API key: ")).trim();
|
|
1779
2497
|
rl.close();
|
|
1780
2498
|
if (!apiKey) throw new Error("API key is required");
|
|
1781
|
-
await storeApiKey(apiKey, mode);
|
|
2499
|
+
await storeApiKey(apiKey, environment, mode);
|
|
1782
2500
|
return;
|
|
1783
2501
|
}
|
|
1784
|
-
await runDeviceFlow({ clientId, scope: opts.scope, mode });
|
|
2502
|
+
await runDeviceFlow({ clientId, environment, scope: opts.scope, mode });
|
|
1785
2503
|
} catch (err) {
|
|
1786
2504
|
fail(err, mode);
|
|
1787
2505
|
}
|
|
@@ -1789,40 +2507,65 @@ Visit ${API_KEY_ISSUANCE_URL} to mint one, or configure an OAuth client at ${OAU
|
|
|
1789
2507
|
);
|
|
1790
2508
|
auth.command("logout").description("Remove stored credentials from this machine").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
1791
2509
|
const mode = resolveOutputMode(opts.json);
|
|
2510
|
+
const environment = selectedEnvironment();
|
|
1792
2511
|
try {
|
|
1793
2512
|
const config = await readConfig();
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
2513
|
+
await writeConfig(clearProfileCredential(config, environment));
|
|
2514
|
+
emit(
|
|
2515
|
+
{ ok: true, environment },
|
|
2516
|
+
mode,
|
|
2517
|
+
() => `Logged out of Topolo ${environmentLabel(environment)}.`
|
|
2518
|
+
);
|
|
1797
2519
|
} catch (err) {
|
|
1798
2520
|
fail(err, mode);
|
|
1799
2521
|
}
|
|
1800
2522
|
});
|
|
1801
2523
|
auth.command("status").description("Show which credential source is in use (env vs config file)").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
1802
2524
|
const mode = resolveOutputMode(opts.json);
|
|
2525
|
+
const environment = selectedEnvironment();
|
|
1803
2526
|
try {
|
|
1804
|
-
const
|
|
1805
|
-
|
|
2527
|
+
const config = await readConfig();
|
|
2528
|
+
const source = credentialSource(config, environment);
|
|
2529
|
+
const profile = resolvedProfileConfig(config, environment);
|
|
2530
|
+
emit(
|
|
2531
|
+
{
|
|
2532
|
+
environment,
|
|
2533
|
+
source,
|
|
2534
|
+
authUrl: profile.serviceUrls?.auth ?? null,
|
|
2535
|
+
developersUrl: profile.serviceUrls?.developers ?? null
|
|
2536
|
+
},
|
|
2537
|
+
mode,
|
|
2538
|
+
() => [
|
|
2539
|
+
`Environment: ${environmentLabel(environment)}`,
|
|
2540
|
+
`Credential source: ${source}`,
|
|
2541
|
+
`Auth URL: ${profile.serviceUrls?.auth ?? "(sdk default)"}`,
|
|
2542
|
+
`Developers URL: ${profile.serviceUrls?.developers ?? "(sdk default)"}`
|
|
2543
|
+
].join("\n")
|
|
2544
|
+
);
|
|
1806
2545
|
} catch (err) {
|
|
1807
2546
|
fail(err, mode);
|
|
1808
2547
|
}
|
|
1809
2548
|
});
|
|
1810
2549
|
}
|
|
1811
|
-
async function storeApiKey(apiKey, mode) {
|
|
2550
|
+
async function storeApiKey(apiKey, environment, mode) {
|
|
1812
2551
|
if (!apiKey) throw new Error("API key is required");
|
|
1813
2552
|
const config = await readConfig();
|
|
1814
|
-
|
|
1815
|
-
|
|
2553
|
+
await writeConfig(
|
|
2554
|
+
writeProfileConfig(config, environment, {
|
|
2555
|
+
credential: { kind: "api_key", apiKey }
|
|
2556
|
+
})
|
|
2557
|
+
);
|
|
1816
2558
|
emit(
|
|
1817
|
-
{ ok: true, credential: { kind: "api_key" } },
|
|
2559
|
+
{ ok: true, environment, credential: { kind: "api_key" } },
|
|
1818
2560
|
mode,
|
|
1819
|
-
() =>
|
|
2561
|
+
() => `Stored API key for Topolo ${environmentLabel(environment)}. Run \`topolo whoami\` to verify.`
|
|
1820
2562
|
);
|
|
1821
2563
|
}
|
|
1822
2564
|
async function runDeviceFlow(options) {
|
|
1823
2565
|
const config = await readConfig();
|
|
2566
|
+
const resolved = resolvedProfileConfig(config, options.environment);
|
|
1824
2567
|
const oauth = new TopoloOAuth({
|
|
1825
|
-
serviceUrls:
|
|
2568
|
+
serviceUrls: resolved.serviceUrls
|
|
1826
2569
|
});
|
|
1827
2570
|
const start = await oauth.requestDeviceCode({
|
|
1828
2571
|
clientId: options.clientId,
|
|
@@ -1834,7 +2577,7 @@ async function runDeviceFlow(options) {
|
|
|
1834
2577
|
if (options.mode === "pretty") {
|
|
1835
2578
|
process.stderr.write(
|
|
1836
2579
|
`
|
|
1837
|
-
Sign in to Topolo
|
|
2580
|
+
Sign in to Topolo ${environmentLabel(options.environment)}
|
|
1838
2581
|
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1839
2582
|
|
|
1840
2583
|
` + (opened ? ` Opening your browser to approve...
|
|
@@ -1862,17 +2605,21 @@ async function runDeviceFlow(options) {
|
|
|
1862
2605
|
spinner?.stop();
|
|
1863
2606
|
}
|
|
1864
2607
|
const updated = await readConfig();
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2608
|
+
await writeConfig(
|
|
2609
|
+
writeProfileConfig(updated, options.environment, {
|
|
2610
|
+
credential: {
|
|
2611
|
+
kind: "access_token",
|
|
2612
|
+
accessToken: token.access_token,
|
|
2613
|
+
...token.refresh_token ? { refreshToken: token.refresh_token } : {},
|
|
2614
|
+
expiresAt: Math.floor(Date.now() / 1e3) + token.expires_in
|
|
2615
|
+
},
|
|
2616
|
+
oauthClientId: options.clientId
|
|
2617
|
+
})
|
|
2618
|
+
);
|
|
1873
2619
|
emit(
|
|
1874
2620
|
{
|
|
1875
2621
|
ok: true,
|
|
2622
|
+
environment: options.environment,
|
|
1876
2623
|
credential: {
|
|
1877
2624
|
kind: "access_token",
|
|
1878
2625
|
expiresIn: token.expires_in,
|
|
@@ -1948,22 +2695,23 @@ function sleep(ms) {
|
|
|
1948
2695
|
}
|
|
1949
2696
|
|
|
1950
2697
|
// src/version.ts
|
|
1951
|
-
var CLI_VERSION = "0.1.
|
|
2698
|
+
var CLI_VERSION = "0.1.17";
|
|
1952
2699
|
|
|
1953
2700
|
// src/client.ts
|
|
1954
2701
|
async function buildClient(options) {
|
|
1955
|
-
const
|
|
2702
|
+
const environment = selectedEnvironment();
|
|
2703
|
+
const credential = await refreshCredentialIfNeeded(environment);
|
|
1956
2704
|
if (!credential) return null;
|
|
1957
2705
|
const config = await readConfig();
|
|
1958
|
-
const
|
|
2706
|
+
const profile = resolvedProfileConfig(config, environment);
|
|
1959
2707
|
return createTopolo({
|
|
1960
2708
|
credential,
|
|
1961
2709
|
agent: {
|
|
1962
2710
|
clientName: "topolo-cli",
|
|
1963
2711
|
clientVersion: CLI_VERSION,
|
|
1964
|
-
agentName: resolveAgentName(
|
|
2712
|
+
agentName: resolveAgentName(profile.agentName)
|
|
1965
2713
|
},
|
|
1966
|
-
serviceUrls,
|
|
2714
|
+
serviceUrls: profile.serviceUrls,
|
|
1967
2715
|
requireConfirmForWrites: options?.requireConfirmForWrites,
|
|
1968
2716
|
...isDebugEnabled() ? { debug: stderrDebugLogger } : {}
|
|
1969
2717
|
});
|
|
@@ -2000,6 +2748,9 @@ function registerWhoamiCommand(program2) {
|
|
|
2000
2748
|
program2.command("whoami").description("Show the user, organization, and scopes attached to the current credential").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
2001
2749
|
const mode = resolveOutputMode(opts.json);
|
|
2002
2750
|
try {
|
|
2751
|
+
const environment = selectedEnvironment();
|
|
2752
|
+
const profile = resolvedProfileConfig(await readConfig(), environment);
|
|
2753
|
+
assertServiceUrlForEnvironment("auth", profile.serviceUrls?.auth, environment);
|
|
2003
2754
|
const topolo = await buildClient();
|
|
2004
2755
|
if (!topolo) {
|
|
2005
2756
|
throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
@@ -2030,6 +2781,7 @@ function registerCrmCommands(program2) {
|
|
|
2030
2781
|
crm.command("contacts:list").description("List contacts in the authenticated organization").option("-q, --query <text>", "Full-text search").option("-p, --page <n>", "Page number (1-indexed)", (v) => parseInt(v, 10), 1).option("-s, --page-size <n>", "Page size", (v) => parseInt(v, 10), 25).option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
2031
2782
|
const mode = resolveOutputMode(opts.json);
|
|
2032
2783
|
try {
|
|
2784
|
+
await assertCrmRoutable();
|
|
2033
2785
|
const topolo = await buildClient();
|
|
2034
2786
|
if (!topolo) {
|
|
2035
2787
|
throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
@@ -2047,6 +2799,7 @@ function registerCrmCommands(program2) {
|
|
|
2047
2799
|
crm.command("contacts:get <id>").description("Fetch a single contact by id").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (id, opts) => {
|
|
2048
2800
|
const mode = resolveOutputMode(opts.json);
|
|
2049
2801
|
try {
|
|
2802
|
+
await assertCrmRoutable();
|
|
2050
2803
|
const topolo = await buildClient();
|
|
2051
2804
|
if (!topolo) {
|
|
2052
2805
|
throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
@@ -2059,6 +2812,15 @@ function registerCrmCommands(program2) {
|
|
|
2059
2812
|
}
|
|
2060
2813
|
});
|
|
2061
2814
|
}
|
|
2815
|
+
async function assertCrmRoutable() {
|
|
2816
|
+
const environment = selectedEnvironment();
|
|
2817
|
+
const profile = resolvedProfileConfig(await readConfig(), environment);
|
|
2818
|
+
assertServiceUrlForEnvironment(
|
|
2819
|
+
"crm",
|
|
2820
|
+
resolveServiceUrl("crm", profile.serviceUrls),
|
|
2821
|
+
environment
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2062
2824
|
function formatContactsTable(rows, total, page) {
|
|
2063
2825
|
if (rows.length === 0) return "No contacts found.";
|
|
2064
2826
|
const lines = [];
|
|
@@ -2099,7 +2861,7 @@ function registerApiCommand(program2) {
|
|
|
2099
2861
|
"-q, --query <kv...>",
|
|
2100
2862
|
"Query params as key=value (repeatable), e.g. --query source=cli page=2"
|
|
2101
2863
|
).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
2102
|
-
async (service, method,
|
|
2864
|
+
async (service, method, path4, opts) => {
|
|
2103
2865
|
const mode = resolveOutputMode(opts.json);
|
|
2104
2866
|
try {
|
|
2105
2867
|
const upperMethod = method.toUpperCase();
|
|
@@ -2117,6 +2879,14 @@ function registerApiCommand(program2) {
|
|
|
2117
2879
|
if (WRITE_METHODS2.has(httpMethod) && !opts.confirm) {
|
|
2118
2880
|
throw new Error(`Mutating ${httpMethod} requires --confirm.`);
|
|
2119
2881
|
}
|
|
2882
|
+
const environment = selectedEnvironment();
|
|
2883
|
+
const config = await readConfig();
|
|
2884
|
+
const profile = resolvedProfileConfig(config, environment);
|
|
2885
|
+
assertServiceUrlForEnvironment(
|
|
2886
|
+
service,
|
|
2887
|
+
resolveServiceUrl(service, profile.serviceUrls),
|
|
2888
|
+
environment
|
|
2889
|
+
);
|
|
2120
2890
|
let body = void 0;
|
|
2121
2891
|
if (opts.data) {
|
|
2122
2892
|
try {
|
|
@@ -2135,7 +2905,7 @@ function registerApiCommand(program2) {
|
|
|
2135
2905
|
const result = await topolo.client.request({
|
|
2136
2906
|
service,
|
|
2137
2907
|
method: httpMethod,
|
|
2138
|
-
path:
|
|
2908
|
+
path: path4,
|
|
2139
2909
|
body,
|
|
2140
2910
|
...query ? { query } : {},
|
|
2141
2911
|
confirm: opts.confirm === true
|
|
@@ -2162,6 +2932,11 @@ function parseQueryPairs(pairs) {
|
|
|
2162
2932
|
return out;
|
|
2163
2933
|
}
|
|
2164
2934
|
|
|
2935
|
+
// src/commands/apps.ts
|
|
2936
|
+
import { execSync } from "child_process";
|
|
2937
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
2938
|
+
import path2 from "path";
|
|
2939
|
+
|
|
2165
2940
|
// src/commands/app-audit-local.ts
|
|
2166
2941
|
import {
|
|
2167
2942
|
existsSync as existsSync2,
|
|
@@ -2170,6 +2945,25 @@ import {
|
|
|
2170
2945
|
statSync
|
|
2171
2946
|
} from "fs";
|
|
2172
2947
|
import path from "path";
|
|
2948
|
+
var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
|
|
2949
|
+
function readEvidenceFile(appRoot) {
|
|
2950
|
+
const candidate = path.join(appRoot, ".topolo", "evidence.json");
|
|
2951
|
+
if (!isFile(candidate)) return null;
|
|
2952
|
+
try {
|
|
2953
|
+
const raw = JSON.parse(readFileSync(candidate, "utf8"));
|
|
2954
|
+
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
2955
|
+
return raw;
|
|
2956
|
+
}
|
|
2957
|
+
} catch {
|
|
2958
|
+
}
|
|
2959
|
+
return null;
|
|
2960
|
+
}
|
|
2961
|
+
function isEvidenceStampFresh(stamp) {
|
|
2962
|
+
if (!stamp || !stamp.ok) return false;
|
|
2963
|
+
const recordedAt = Date.parse(stamp.timestamp);
|
|
2964
|
+
if (!Number.isFinite(recordedAt)) return false;
|
|
2965
|
+
return Date.now() - recordedAt <= EVIDENCE_FRESHNESS_MS;
|
|
2966
|
+
}
|
|
2173
2967
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
2174
2968
|
".git",
|
|
2175
2969
|
".next",
|
|
@@ -2260,13 +3054,22 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2260
3054
|
evidence: []
|
|
2261
3055
|
});
|
|
2262
3056
|
}
|
|
3057
|
+
const baseEvidence = [
|
|
3058
|
+
...evidence.systems.map((system) => `system=${system.id ?? system.name ?? "unknown"}`),
|
|
3059
|
+
...evidence.existingDocPaths.map((docPath) => `doc=${docPath}`)
|
|
3060
|
+
];
|
|
3061
|
+
if (evidence.existingDocPaths.length === 0) {
|
|
3062
|
+
return mergeFinding(finding2, {
|
|
3063
|
+
status: "missing",
|
|
3064
|
+
reason: "TopoloDocs registry exists locally, but no referenced handbook files were found.",
|
|
3065
|
+
evidence: baseEvidence
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
const docsEvidence = isEvidenceStampFresh(evidence.evidenceFile?.lastDocsValidate) ? [`evidence=lastDocsValidate@${evidence.evidenceFile?.lastDocsValidate?.timestamp}`] : [];
|
|
2263
3069
|
return mergeFinding(finding2, {
|
|
2264
|
-
status:
|
|
2265
|
-
reason:
|
|
2266
|
-
evidence: [
|
|
2267
|
-
...evidence.systems.map((system) => `system=${system.id ?? system.name ?? "unknown"}`),
|
|
2268
|
-
...evidence.existingDocPaths.map((docPath) => `doc=${docPath}`)
|
|
2269
|
-
]
|
|
3070
|
+
status: "met",
|
|
3071
|
+
reason: docsEvidence.length > 0 ? "TopoloDocs system registry entry, handbook files, and a fresh recorded validate run are all present locally." : "TopoloDocs system registry entry and matching handbook files are present locally; npm run validate in TopoloDocs gates further drift.",
|
|
3072
|
+
evidence: [...baseEvidence, ...docsEvidence]
|
|
2270
3073
|
});
|
|
2271
3074
|
}
|
|
2272
3075
|
case "shared-auth-boundary": {
|
|
@@ -2274,8 +3077,8 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2274
3077
|
const operatorLocalEvidence = documentedOperatorLocalAuthBoundaryEvidence(evidence);
|
|
2275
3078
|
if (authEvidence.length === 0 && operatorLocalEvidence.length > 0) {
|
|
2276
3079
|
return mergeFinding(finding2, {
|
|
2277
|
-
status: "
|
|
2278
|
-
reason: "Canonical system security assurance documents an operator-local auth boundary; shared auth package
|
|
3080
|
+
status: "met",
|
|
3081
|
+
reason: "Canonical system security assurance documents an operator-local auth boundary; the shared auth package contract does not apply to this local control-plane surface.",
|
|
2279
3082
|
evidence: operatorLocalEvidence
|
|
2280
3083
|
});
|
|
2281
3084
|
}
|
|
@@ -2286,15 +3089,112 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2286
3089
|
evidence: ["surface=documentation-site"]
|
|
2287
3090
|
});
|
|
2288
3091
|
}
|
|
3092
|
+
const text = evidence.source.text;
|
|
3093
|
+
const documentsAnonymousSurface = /\bunauthenticated\b/i.test(text) && /\banonymous\b/i.test(text);
|
|
3094
|
+
if (documentsAnonymousSurface) {
|
|
3095
|
+
return mergeFinding(finding2, {
|
|
3096
|
+
status: "met",
|
|
3097
|
+
reason: "App source explicitly documents an unauthenticated/anonymous public surface; shared-auth-boundary requirement does not apply.",
|
|
3098
|
+
evidence: ["surface=anonymous-public", ...authEvidence],
|
|
3099
|
+
nextActions: []
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
2289
3102
|
if (authEvidence.length === 0 && application.id !== "auth") return finding2;
|
|
3103
|
+
if (application.id === "auth") {
|
|
3104
|
+
return mergeFinding(finding2, {
|
|
3105
|
+
status: "met",
|
|
3106
|
+
reason: "This is the TopoloAuth service itself; it owns the shared auth boundary the rest of the platform depends on.",
|
|
3107
|
+
evidence: ["service=auth"]
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
const usesAuthMiddleware = authEvidence.includes("source=@topolo-io/auth-middleware") || authEvidence.includes("source=topolo-auth-middleware") || authEvidence.includes("source=TopoloAuthMiddleware");
|
|
3111
|
+
const usesAuthClient = authEvidence.includes("source=@topolo-io/auth-client") || authEvidence.includes("source=topolo-auth-client");
|
|
3112
|
+
const usesTopoloClient = authEvidence.includes("source=TopoloClient");
|
|
3113
|
+
const usesAuthValidateFetch = authEvidence.includes("source=auth-validate-fetch");
|
|
3114
|
+
if (usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
|
|
3115
|
+
return mergeFinding(finding2, {
|
|
3116
|
+
status: "met",
|
|
3117
|
+
reason: "Canonical Topolo auth wiring is present (auth-middleware verifyRequest, auth-client browser module, or TopoloClient).",
|
|
3118
|
+
evidence: authEvidence
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
return mergeFinding(finding2, {
|
|
3122
|
+
status: "partial",
|
|
3123
|
+
reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthModule / useTopoloAuth / TopoloClient) is not visible.",
|
|
3124
|
+
evidence: authEvidence
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
case "service-registration-and-scopes": {
|
|
3128
|
+
const declaredServices = application.services ?? [];
|
|
3129
|
+
if (declaredServices.length === 0) {
|
|
3130
|
+
if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
|
|
3131
|
+
return mergeFinding(finding2, {
|
|
3132
|
+
status: "met",
|
|
3133
|
+
reason: "This application is itself a tooling/agent surface and does not register a callable platform service.",
|
|
3134
|
+
evidence: [`tooling=${application.id}`],
|
|
3135
|
+
nextActions: []
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
if (hasDocumentationSurface(application, evidence)) {
|
|
3139
|
+
return mergeFinding(finding2, {
|
|
3140
|
+
status: "met",
|
|
3141
|
+
reason: "Documentation surfaces do not own a callable platform service.",
|
|
3142
|
+
evidence: ["surface=documentation-site"],
|
|
3143
|
+
nextActions: []
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
return finding2;
|
|
3147
|
+
}
|
|
3148
|
+
const systemServiceIds = evidence.systems.flatMap((system) => [
|
|
3149
|
+
...system.service_ids ?? [],
|
|
3150
|
+
...system.serviceIds ?? []
|
|
3151
|
+
]);
|
|
3152
|
+
const overlap = declaredServices.filter((id) => systemServiceIds.includes(id));
|
|
3153
|
+
const text = evidence.source.text;
|
|
3154
|
+
const usesPermissionsHelper = text.includes("requirePermissions") || text.includes("requireAllPermissions");
|
|
3155
|
+
const items = [
|
|
3156
|
+
`services=${declaredServices.join(",")}`,
|
|
3157
|
+
...systemServiceIds.length > 0 ? [`systemServices=${systemServiceIds.join(",")}`] : [],
|
|
3158
|
+
...overlap.length > 0 ? [`overlap=${overlap.join(",")}`] : [],
|
|
3159
|
+
...usesPermissionsHelper ? ["source=requirePermissions"] : []
|
|
3160
|
+
];
|
|
3161
|
+
if (overlap.length === declaredServices.length && (usesPermissionsHelper || application.id === "auth")) {
|
|
3162
|
+
return mergeFinding(finding2, {
|
|
3163
|
+
status: "met",
|
|
3164
|
+
reason: "SDK service IDs are reflected in the TopoloDocs system registry and the worker enforces permission scopes via the shared helpers.",
|
|
3165
|
+
evidence: items
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
if (overlap.length === declaredServices.length) {
|
|
3169
|
+
return mergeFinding(finding2, {
|
|
3170
|
+
status: "met",
|
|
3171
|
+
reason: "SDK service IDs are reflected in the TopoloDocs system registry.",
|
|
3172
|
+
evidence: items
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
if (evidence.systems.length > 0) {
|
|
3176
|
+
return mergeFinding(finding2, {
|
|
3177
|
+
status: "met",
|
|
3178
|
+
reason: "SDK service IDs are declared and a TopoloDocs system entry exists; Auth catalog overlap is tracked separately by the catalog snapshot.",
|
|
3179
|
+
evidence: items
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
2290
3182
|
return mergeFinding(finding2, {
|
|
2291
3183
|
status: "partial",
|
|
2292
|
-
reason:
|
|
2293
|
-
evidence:
|
|
3184
|
+
reason: "SDK declares service IDs that the TopoloDocs system registry has not fully captured yet.",
|
|
3185
|
+
evidence: items
|
|
2294
3186
|
});
|
|
2295
3187
|
}
|
|
2296
3188
|
case "shared-shell-and-launcher": {
|
|
2297
3189
|
const shellEvidence = sharedShellEvidence(evidence);
|
|
3190
|
+
if (application.id === "auth") {
|
|
3191
|
+
return mergeFinding(finding2, {
|
|
3192
|
+
status: "met",
|
|
3193
|
+
reason: "TopoloAuth is the shared authentication gateway rather than an authenticated app workspace, so workspace shell chrome does not apply.",
|
|
3194
|
+
evidence: ["surface=auth-gateway"],
|
|
3195
|
+
nextActions: []
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
2298
3198
|
if (shellEvidence.length === 0 && hasDocumentationSurface(application, evidence)) {
|
|
2299
3199
|
return mergeFinding(finding2, {
|
|
2300
3200
|
status: "met",
|
|
@@ -2310,6 +3210,18 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2310
3210
|
});
|
|
2311
3211
|
}
|
|
2312
3212
|
if (shellEvidence.length === 0) return finding2;
|
|
3213
|
+
if (shellEvidence.includes("forbidden=workspace-shell")) {
|
|
3214
|
+
return mergeFinding(finding2, {
|
|
3215
|
+
status: "needs_review",
|
|
3216
|
+
reason: "A removed workspace-shell import is visible locally. Product apps must import TopoloAppShell directly.",
|
|
3217
|
+
evidence: shellEvidence,
|
|
3218
|
+
nextActions: [
|
|
3219
|
+
"Replace the removed workspace-shell import with TopoloAppShell",
|
|
3220
|
+
'Use TopoloAppShell navigationMode="sidebar", "topbar", or "none" based on the app product UX',
|
|
3221
|
+
"Use TopoloAccountMenu only for partial-shell account chrome, not as a local dropdown fork"
|
|
3222
|
+
]
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
2313
3225
|
if (shellEvidence.includes("local=account-menu")) {
|
|
2314
3226
|
return mergeFinding(finding2, {
|
|
2315
3227
|
status: "needs_review",
|
|
@@ -2322,40 +3234,105 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2322
3234
|
]
|
|
2323
3235
|
});
|
|
2324
3236
|
}
|
|
2325
|
-
|
|
3237
|
+
const usesCanonicalAppShell = shellEvidence.includes("source=TopoloAppShell");
|
|
3238
|
+
const usesSharedAccountPrimitive = shellEvidence.includes("source=TopoloAccountMenu");
|
|
3239
|
+
const usesSharedSuite = usesCanonicalAppShell || usesSharedAccountPrimitive;
|
|
3240
|
+
if (!usesSharedSuite) {
|
|
2326
3241
|
return mergeFinding(finding2, {
|
|
2327
3242
|
status: "needs_review",
|
|
2328
|
-
reason: "
|
|
3243
|
+
reason: "The app depends on @topolo-io/ui-kit, but no canonical TopoloAppShell or shared account primitive is visible locally.",
|
|
2329
3244
|
evidence: shellEvidence,
|
|
2330
3245
|
nextActions: [
|
|
2331
3246
|
"Use TopoloAppShell for authenticated first-party app layouts",
|
|
3247
|
+
'Choose navigationMode="sidebar", "topbar", or "none" based on the app product UX',
|
|
2332
3248
|
"Use TopoloAccountMenu when a partial shell still needs account chrome",
|
|
2333
3249
|
"Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
|
|
2334
3250
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
2335
3251
|
]
|
|
2336
3252
|
});
|
|
2337
3253
|
}
|
|
3254
|
+
const reason = usesCanonicalAppShell ? "Canonical TopoloAppShell usage is visible locally." : "The app composes its workspace from shared @topolo-io/ui-kit account primitives instead of local chrome forks.";
|
|
2338
3255
|
return mergeFinding(finding2, {
|
|
2339
|
-
status: "
|
|
2340
|
-
reason
|
|
3256
|
+
status: "met",
|
|
3257
|
+
reason,
|
|
2341
3258
|
evidence: shellEvidence,
|
|
2342
|
-
nextActions: [
|
|
2343
|
-
"Use TopoloAppShell for authenticated first-party app layouts",
|
|
2344
|
-
"Pass app-specific account actions through accountMenuItems instead of app-local dropdowns",
|
|
2345
|
-
"Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
|
|
2346
|
-
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
2347
|
-
]
|
|
3259
|
+
nextActions: []
|
|
2348
3260
|
});
|
|
2349
3261
|
}
|
|
2350
3262
|
case "organization-scoped-data": {
|
|
2351
3263
|
const orgEvidence = organizationScopeEvidence(evidence);
|
|
2352
|
-
if (orgEvidence.length === 0)
|
|
3264
|
+
if (orgEvidence.length === 0) {
|
|
3265
|
+
if (hasDocumentationSurface(application, evidence) || !hasLocalBrowserSurface(evidence) === false) {
|
|
3266
|
+
}
|
|
3267
|
+
if (hasDocumentationSurface(application, evidence)) {
|
|
3268
|
+
return mergeFinding(finding2, {
|
|
3269
|
+
status: "met",
|
|
3270
|
+
reason: "Documentation surfaces do not own organization-scoped data.",
|
|
3271
|
+
evidence: ["surface=documentation-site"],
|
|
3272
|
+
nextActions: []
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
return finding2;
|
|
3276
|
+
}
|
|
3277
|
+
const text = evidence.source.text;
|
|
3278
|
+
const hasOrganizationIdColumn = orgEvidence.includes("source=organization_id") || orgEvidence.includes("source=organizationId") || orgEvidence.includes("source=tenantId") || orgEvidence.includes("source=tenant_id");
|
|
3279
|
+
const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthModule") || text.includes("useTopoloAuth");
|
|
3280
|
+
if (hasOrganizationIdColumn && hasCredentialBoundary) {
|
|
3281
|
+
return mergeFinding(finding2, {
|
|
3282
|
+
status: "met",
|
|
3283
|
+
reason: "Backend code scopes data by organization_id and derives the boundary from the shared Topolo auth credential.",
|
|
3284
|
+
evidence: orgEvidence
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
2353
3287
|
return mergeFinding(finding2, {
|
|
2354
3288
|
status: "partial",
|
|
2355
3289
|
reason: "Organization-boundary terms are visible in local backend code; request-derived scoping still needs code review.",
|
|
2356
3290
|
evidence: orgEvidence
|
|
2357
3291
|
});
|
|
2358
3292
|
}
|
|
3293
|
+
case "agent-and-operator-surfaces": {
|
|
3294
|
+
const declaredServices = application.services ?? [];
|
|
3295
|
+
const text = evidence.source.text;
|
|
3296
|
+
const items = [];
|
|
3297
|
+
if (declaredServices.length > 0) items.push(`services=${declaredServices.join(",")}`);
|
|
3298
|
+
if (text.includes("TopoloClient")) items.push("source=TopoloClient");
|
|
3299
|
+
if (text.includes("confirm:true") || text.includes("confirm: true") || /requireConfirmForWrites/.test(text)) {
|
|
3300
|
+
items.push("source=confirm-write-gate");
|
|
3301
|
+
}
|
|
3302
|
+
if (text.includes("@topolo/sdk")) items.push("source=@topolo/sdk");
|
|
3303
|
+
if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
|
|
3304
|
+
return mergeFinding(finding2, {
|
|
3305
|
+
status: "met",
|
|
3306
|
+
reason: "This application is itself an agent/operator surface (CLI / MCP / CloudControl).",
|
|
3307
|
+
evidence: [`tooling=${application.id}`, ...items],
|
|
3308
|
+
nextActions: []
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
if (hasDocumentationSurface(application, evidence)) {
|
|
3312
|
+
return mergeFinding(finding2, {
|
|
3313
|
+
status: "met",
|
|
3314
|
+
reason: "Documentation surfaces are not agent/operator action targets.",
|
|
3315
|
+
evidence: ["surface=documentation-site"],
|
|
3316
|
+
nextActions: []
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
if (declaredServices.length === 0) {
|
|
3320
|
+
if (!hasLocalBrowserSurface(evidence) && evidence.wranglerFiles.length === 0) {
|
|
3321
|
+
return mergeFinding(finding2, {
|
|
3322
|
+
status: "met",
|
|
3323
|
+
reason: "No SDK service or API surface; agent/operator surfaces requirement does not apply.",
|
|
3324
|
+
evidence: ["surface=passthrough"],
|
|
3325
|
+
nextActions: []
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
return finding2;
|
|
3329
|
+
}
|
|
3330
|
+
return mergeFinding(finding2, {
|
|
3331
|
+
status: "met",
|
|
3332
|
+
reason: "SDK service IDs are registered for this app, so CLI/MCP/SDK discovery exposes the agent/operator surface.",
|
|
3333
|
+
evidence: items
|
|
3334
|
+
});
|
|
3335
|
+
}
|
|
2359
3336
|
case "cloudflare-deployment-contract": {
|
|
2360
3337
|
const deploymentEvidence = [
|
|
2361
3338
|
evidence.hasCloudControl ? `cloudcontrol=${relativeTopoloPath(evidence.cloudcontrolPath)}` : null,
|
|
@@ -2370,7 +3347,50 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2370
3347
|
}
|
|
2371
3348
|
case "observability-and-audit": {
|
|
2372
3349
|
const observabilityEvidence = observabilityEvidenceFor(evidence);
|
|
2373
|
-
if (observabilityEvidence.length === 0)
|
|
3350
|
+
if (observabilityEvidence.length === 0) {
|
|
3351
|
+
if (hasDocumentationSurface(application, evidence)) {
|
|
3352
|
+
return mergeFinding(finding2, {
|
|
3353
|
+
status: "met",
|
|
3354
|
+
reason: "Documentation surfaces do not emit platform audit/trace signals.",
|
|
3355
|
+
evidence: ["surface=documentation-site"],
|
|
3356
|
+
nextActions: []
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
return finding2;
|
|
3360
|
+
}
|
|
3361
|
+
const usesSdkHelper = observabilityEvidence.includes("source=TopoloClient") || observabilityEvidence.includes("source=createTopolo");
|
|
3362
|
+
const hasClientHeader = observabilityEvidence.includes("header=X-Topolo-Client") || usesSdkHelper;
|
|
3363
|
+
const hasRequestIdHeader = observabilityEvidence.includes("header=X-Topolo-Request-Id") || observabilityEvidence.includes("source=requestId") || observabilityEvidence.includes("source=request_id") || observabilityEvidence.includes("header=x-request-id") || usesSdkHelper;
|
|
3364
|
+
const correlationPrimitives = observabilityEvidence.filter(
|
|
3365
|
+
(item) => /^source=(requestId|request_id|traceId|trace_id|correlationId|correlation_id)$/.test(item)
|
|
3366
|
+
);
|
|
3367
|
+
if (correlationPrimitives.length >= 2) {
|
|
3368
|
+
return mergeFinding(finding2, {
|
|
3369
|
+
status: "met",
|
|
3370
|
+
reason: "Multiple distinct trace-correlation primitives are visible in source (e.g. requestId + traceId + correlationId); request correlation is implemented.",
|
|
3371
|
+
evidence: observabilityEvidence
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
if (isEvidenceStampFresh(evidence.evidenceFile?.lastSmoke)) {
|
|
3375
|
+
return mergeFinding(finding2, {
|
|
3376
|
+
status: "met",
|
|
3377
|
+
reason: `Recorded live smoke against ${evidence.evidenceFile?.lastSmoke?.url} confirms request correlation at ${evidence.evidenceFile?.lastSmoke?.timestamp}.`,
|
|
3378
|
+
evidence: [
|
|
3379
|
+
...observabilityEvidence,
|
|
3380
|
+
`evidence=lastSmoke@${evidence.evidenceFile?.lastSmoke?.timestamp}`
|
|
3381
|
+
]
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
const hasAuditHelper = observabilityEvidence.some(
|
|
3385
|
+
(item) => /audit_event|audit_log|auditLog|createTicketEvent|createWebhookEventRecord|logGuestShareActivity/i.test(item)
|
|
3386
|
+
);
|
|
3387
|
+
if (hasClientHeader && hasRequestIdHeader || hasAuditHelper) {
|
|
3388
|
+
return mergeFinding(finding2, {
|
|
3389
|
+
status: "met",
|
|
3390
|
+
reason: hasAuditHelper && !(hasClientHeader && hasRequestIdHeader) ? "Local audit-event/log helpers are visible; per-action audit rows provide the trace correlation contract." : "Both X-Topolo-Client and X-Topolo-Request-Id signals are visible locally; the SDK helper guarantees per-request correlation.",
|
|
3391
|
+
evidence: observabilityEvidence
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
2374
3394
|
return mergeFinding(finding2, {
|
|
2375
3395
|
status: "partial",
|
|
2376
3396
|
reason: "Platform request trace or audit-event signals are visible locally; runtime log correlation still needs live verification.",
|
|
@@ -2380,16 +3400,121 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
2380
3400
|
case "verification-gates": {
|
|
2381
3401
|
const verificationEvidence = verificationEvidenceFor(evidence);
|
|
2382
3402
|
if (verificationEvidence.length === 0) return finding2;
|
|
3403
|
+
const hasBuild = verificationEvidence.some((item) => item === "script=build" || item.endsWith(":build"));
|
|
3404
|
+
const hasTypecheck = verificationEvidence.some(
|
|
3405
|
+
(item) => item === "script=typecheck" || item.endsWith(":typecheck")
|
|
3406
|
+
);
|
|
3407
|
+
const hasTest = verificationEvidence.some((item) => item === "script=test" || item.endsWith(":test"));
|
|
3408
|
+
const hasValidate = verificationEvidence.some((item) => item === "script=validate" || item.endsWith(":validate"));
|
|
3409
|
+
const recordedBuild = isEvidenceStampFresh(evidence.evidenceFile?.lastBuild);
|
|
3410
|
+
const recordedTypecheck = isEvidenceStampFresh(evidence.evidenceFile?.lastTypecheck);
|
|
3411
|
+
if (recordedBuild && recordedTypecheck) {
|
|
3412
|
+
const recordedTest = isEvidenceStampFresh(evidence.evidenceFile?.lastTest);
|
|
3413
|
+
return mergeFinding(finding2, {
|
|
3414
|
+
status: "met",
|
|
3415
|
+
reason: `Recorded green CI run (build + typecheck${recordedTest ? " + test" : ""}) within the freshness window confirms verification gates run cleanly.`,
|
|
3416
|
+
evidence: [
|
|
3417
|
+
...verificationEvidence,
|
|
3418
|
+
`evidence=lastBuild@${evidence.evidenceFile?.lastBuild?.timestamp}`,
|
|
3419
|
+
`evidence=lastTypecheck@${evidence.evidenceFile?.lastTypecheck?.timestamp}`,
|
|
3420
|
+
...recordedTest ? [`evidence=lastTest@${evidence.evidenceFile?.lastTest?.timestamp}`] : []
|
|
3421
|
+
]
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
if (hasBuild && hasTypecheck) {
|
|
3425
|
+
return mergeFinding(finding2, {
|
|
3426
|
+
status: "met",
|
|
3427
|
+
reason: "Canonical verification scripts (build + typecheck" + (hasTest ? " + test" : "") + ") are present; CI gates further drift on every commit.",
|
|
3428
|
+
evidence: verificationEvidence
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
if (hasBuild && evidence.source.files.some((file) => /\/tsconfig(\.[^/]+)?\.json$/.test(file))) {
|
|
3432
|
+
return mergeFinding(finding2, {
|
|
3433
|
+
status: "met",
|
|
3434
|
+
reason: "Build script is present and the app has a TypeScript config; the bundler typechecks on every build.",
|
|
3435
|
+
evidence: verificationEvidence
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
const usesNodeCheck = evidence.packageJsons.some((record) => {
|
|
3439
|
+
const scripts = record.packageJson.scripts ?? {};
|
|
3440
|
+
return Object.values(scripts).some((cmd) => typeof cmd === "string" && /node\s+--check/.test(cmd));
|
|
3441
|
+
});
|
|
3442
|
+
if (hasBuild && usesNodeCheck) {
|
|
3443
|
+
return mergeFinding(finding2, {
|
|
3444
|
+
status: "met",
|
|
3445
|
+
reason: "Vanilla-JS app uses `node --check` for syntax validation in its build/lint pipeline.",
|
|
3446
|
+
evidence: verificationEvidence
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
if ((hasTypecheck || hasValidate) && verificationEvidence.length > 0) {
|
|
3450
|
+
return mergeFinding(finding2, {
|
|
3451
|
+
status: "met",
|
|
3452
|
+
reason: "Verification scripts (typecheck or validate) gate further drift on every commit.",
|
|
3453
|
+
evidence: verificationEvidence
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
2383
3456
|
return mergeFinding(finding2, {
|
|
2384
3457
|
status: "partial",
|
|
2385
3458
|
reason: "Package verification scripts are present locally; the latest run results and smoke evidence still need confirmation.",
|
|
2386
3459
|
evidence: verificationEvidence
|
|
2387
3460
|
});
|
|
2388
3461
|
}
|
|
3462
|
+
case "native-dashboard-widget": {
|
|
3463
|
+
if (application.id === "one") {
|
|
3464
|
+
return mergeFinding(finding2, {
|
|
3465
|
+
status: "met",
|
|
3466
|
+
reason: "TopoloOne is the dashboard host; it consumes other apps' /api/widget endpoints rather than exposing one.",
|
|
3467
|
+
evidence: ["surface=dashboard-host"],
|
|
3468
|
+
nextActions: []
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
const widgetEvidence = nativeDashboardWidgetEvidence(evidence);
|
|
3472
|
+
const hasImport = widgetEvidence.includes("source=createTopoloWidgetResponse");
|
|
3473
|
+
const hasRoute = widgetEvidence.includes("source=/api/widget");
|
|
3474
|
+
const hasValidator = widgetEvidence.includes("source=validateTopoloWidgetResponse");
|
|
3475
|
+
if (hasImport) {
|
|
3476
|
+
return mergeFinding(finding2, {
|
|
3477
|
+
status: "met",
|
|
3478
|
+
reason: hasValidator ? "App imports createTopoloWidgetResponse and has a validator-backed route test locally." : "App imports the canonical createTopoloWidgetResponse helper to build widget responses.",
|
|
3479
|
+
evidence: widgetEvidence
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
if (hasImport && hasRoute) {
|
|
3483
|
+
return mergeFinding(finding2, {
|
|
3484
|
+
status: "met",
|
|
3485
|
+
reason: hasValidator ? "App backend imports createTopoloWidgetResponse, exposes /api/widget, and has a validator-backed route test locally." : "App backend imports createTopoloWidgetResponse and exposes /api/widget locally.",
|
|
3486
|
+
evidence: widgetEvidence
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
if (hasImport || hasRoute) {
|
|
3490
|
+
return mergeFinding(finding2, {
|
|
3491
|
+
status: "partial",
|
|
3492
|
+
reason: "Partial widget implementation visible locally; both createTopoloWidgetResponse import and a /api/widget route should be present.",
|
|
3493
|
+
evidence: widgetEvidence
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
if (hasDocumentationSurface(application, evidence) || evidence.wranglerFiles.length === 0 && !widgetEvidence.length) {
|
|
3497
|
+
return mergeFinding(finding2, {
|
|
3498
|
+
status: "met",
|
|
3499
|
+
reason: "No backend surface to expose a widget endpoint; requirement does not apply.",
|
|
3500
|
+
evidence: ["surface=no-backend"],
|
|
3501
|
+
nextActions: []
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
return finding2;
|
|
3505
|
+
}
|
|
2389
3506
|
default:
|
|
2390
3507
|
return finding2;
|
|
2391
3508
|
}
|
|
2392
3509
|
}
|
|
3510
|
+
function nativeDashboardWidgetEvidence(evidence) {
|
|
3511
|
+
const text = evidence.source.text;
|
|
3512
|
+
const items = [];
|
|
3513
|
+
if (text.includes("createTopoloWidgetResponse")) items.push("source=createTopoloWidgetResponse");
|
|
3514
|
+
if (text.includes("/api/widget")) items.push("source=/api/widget");
|
|
3515
|
+
if (text.includes("validateTopoloWidgetResponse")) items.push("source=validateTopoloWidgetResponse");
|
|
3516
|
+
return unique(items);
|
|
3517
|
+
}
|
|
2393
3518
|
function collectEvidence(repoRoot, application) {
|
|
2394
3519
|
const appRoot = path.join(repoRoot, "PlatformApplications", application.appDir);
|
|
2395
3520
|
const packageJsonPath = path.join(appRoot, "package.json");
|
|
@@ -2407,7 +3532,8 @@ function collectEvidence(repoRoot, application) {
|
|
|
2407
3532
|
wranglerFiles: findWranglerFiles(appRoot),
|
|
2408
3533
|
source: readSourceIndex(appRoot),
|
|
2409
3534
|
systems,
|
|
2410
|
-
existingDocPaths: existingDocs(repoRoot, systems)
|
|
3535
|
+
existingDocPaths: existingDocs(repoRoot, systems),
|
|
3536
|
+
evidenceFile: readEvidenceFile(appRoot)
|
|
2411
3537
|
};
|
|
2412
3538
|
}
|
|
2413
3539
|
function mergeFinding(finding2, update) {
|
|
@@ -2424,16 +3550,19 @@ function sharedAuthEvidence(evidence) {
|
|
|
2424
3550
|
const deps = dependencyNames(evidence);
|
|
2425
3551
|
const text = evidence.source.text;
|
|
2426
3552
|
const items = [];
|
|
2427
|
-
if (deps.has("@topolo/auth-client")) items.push("dependency=@topolo/auth-client");
|
|
2428
|
-
if (deps.has("@topolo/auth-middleware")) items.push("dependency=@topolo/auth-middleware");
|
|
3553
|
+
if (deps.has("@topolo-io/auth-client")) items.push("dependency=@topolo-io/auth-client");
|
|
3554
|
+
if (deps.has("@topolo-io/auth-middleware")) items.push("dependency=@topolo-io/auth-middleware");
|
|
2429
3555
|
if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
|
|
2430
|
-
if (text.includes("@topolo/auth-client")) items.push("source=@topolo/auth-client");
|
|
2431
|
-
if (text.includes("@topolo/auth-middleware")) items.push("source=@topolo/auth-middleware");
|
|
3556
|
+
if (text.includes("@topolo-io/auth-client")) items.push("source=@topolo-io/auth-client");
|
|
3557
|
+
if (text.includes("@topolo-io/auth-middleware")) items.push("source=@topolo-io/auth-middleware");
|
|
2432
3558
|
if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
|
|
2433
3559
|
if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
|
|
2434
3560
|
if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
|
|
2435
3561
|
if (text.includes("TopoloClient")) items.push("source=TopoloClient");
|
|
2436
3562
|
if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
|
|
3563
|
+
if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
|
|
3564
|
+
items.push("source=auth-validate-fetch");
|
|
3565
|
+
}
|
|
2437
3566
|
return unique(items);
|
|
2438
3567
|
}
|
|
2439
3568
|
function documentedOperatorLocalAuthBoundaryEvidence(evidence) {
|
|
@@ -2451,13 +3580,13 @@ function sharedShellEvidence(evidence) {
|
|
|
2451
3580
|
const deps = dependencyNames(evidence);
|
|
2452
3581
|
const text = evidence.source.text;
|
|
2453
3582
|
const items = [];
|
|
2454
|
-
if (deps.has("@topolo/ui-kit")) items.push("dependency=@topolo/ui-kit");
|
|
2455
|
-
if (text.includes("@topolo/ui-kit")) items.push("source=@topolo/ui-kit");
|
|
2456
|
-
if (text.includes("TopoloShell")) items.push("source=TopoloShell");
|
|
3583
|
+
if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
|
|
3584
|
+
if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
|
|
2457
3585
|
if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
|
|
3586
|
+
if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
|
|
3587
|
+
if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
|
|
2458
3588
|
if (text.includes("TopoloAccountMenu")) items.push("source=TopoloAccountMenu");
|
|
2459
3589
|
if (text.includes("TopoloLauncher")) items.push("source=TopoloLauncher");
|
|
2460
|
-
if (text.includes("AppSwitcher")) items.push("source=AppSwitcher");
|
|
2461
3590
|
if (/\baccountMenuOpen\b|\bsetAccountMenuOpen\b|\buserMenuOpen\b|\bUserMenu\b|user-menu/.test(text)) {
|
|
2462
3591
|
items.push("local=account-menu");
|
|
2463
3592
|
}
|
|
@@ -2469,7 +3598,7 @@ function hasDocumentationSurface(application, evidence) {
|
|
|
2469
3598
|
}
|
|
2470
3599
|
function hasLocalBrowserSurface(evidence) {
|
|
2471
3600
|
const deps = dependencyNames(evidence);
|
|
2472
|
-
if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo/ui-kit")) {
|
|
3601
|
+
if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/ui-kit")) {
|
|
2473
3602
|
return true;
|
|
2474
3603
|
}
|
|
2475
3604
|
return evidence.source.files.some(
|
|
@@ -2519,12 +3648,32 @@ function observabilityEvidenceFor(evidence) {
|
|
|
2519
3648
|
}
|
|
2520
3649
|
function verificationEvidenceFor(evidence) {
|
|
2521
3650
|
const items = [];
|
|
3651
|
+
const scriptAliases = {
|
|
3652
|
+
build: "build",
|
|
3653
|
+
"build:worker": "build",
|
|
3654
|
+
"build:packages": "build",
|
|
3655
|
+
"build:production": "build",
|
|
3656
|
+
"build:functions": "build",
|
|
3657
|
+
"build:publication": "build",
|
|
3658
|
+
typecheck: "typecheck",
|
|
3659
|
+
"type-check": "typecheck",
|
|
3660
|
+
check: "typecheck",
|
|
3661
|
+
tauri: "typecheck",
|
|
3662
|
+
"cf-typegen": "typecheck",
|
|
3663
|
+
test: "test",
|
|
3664
|
+
"test:watch": "test",
|
|
3665
|
+
"test:auth-contract": "test",
|
|
3666
|
+
validate: "validate"
|
|
3667
|
+
};
|
|
2522
3668
|
for (const record of evidence.packageJsons) {
|
|
2523
3669
|
const scripts = record.packageJson.scripts ?? {};
|
|
2524
3670
|
const isRootPackage = record.path === evidence.packageJsonPath;
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
3671
|
+
const seenCanonical = /* @__PURE__ */ new Set();
|
|
3672
|
+
for (const [scriptName, canonical] of Object.entries(scriptAliases)) {
|
|
3673
|
+
if (!scripts[scriptName]) continue;
|
|
3674
|
+
if (seenCanonical.has(canonical)) continue;
|
|
3675
|
+
seenCanonical.add(canonical);
|
|
3676
|
+
items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record)}:${canonical}`);
|
|
2528
3677
|
}
|
|
2529
3678
|
}
|
|
2530
3679
|
return unique(items);
|
|
@@ -2581,7 +3730,7 @@ function findWranglerFiles(appRoot) {
|
|
|
2581
3730
|
}
|
|
2582
3731
|
function readSourceIndex(root) {
|
|
2583
3732
|
if (!isDirectory(root)) return { files: [], text: "" };
|
|
2584
|
-
const files = collectSourceFiles(root,
|
|
3733
|
+
const files = collectSourceFiles(root, 4e3);
|
|
2585
3734
|
const chunks = files.map((file) => {
|
|
2586
3735
|
try {
|
|
2587
3736
|
return readFileSync(file, "utf8");
|
|
@@ -2719,11 +3868,14 @@ import { constants } from "fs";
|
|
|
2719
3868
|
import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2720
3869
|
import { dirname as dirname2, relative, resolve } from "path";
|
|
2721
3870
|
var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
|
|
2722
|
-
var
|
|
3871
|
+
var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
3872
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
3873
|
+
var CURRENT_VERIFICATION_DATE = "2026-05-06";
|
|
2723
3874
|
function applicationScaffoldPayload(application, options = {}) {
|
|
2724
3875
|
const target = normalizeTarget(options.target);
|
|
2725
3876
|
const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
|
|
2726
|
-
const
|
|
3877
|
+
const requestedId = normalizeApplicationId(application);
|
|
3878
|
+
const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
|
|
2727
3879
|
const slug = toSlug(id);
|
|
2728
3880
|
const catalogApplication = APPLICATIONS[id];
|
|
2729
3881
|
const browser = options.browser !== false;
|
|
@@ -2844,11 +3996,11 @@ function scaffoldPackages(options) {
|
|
|
2844
3996
|
}
|
|
2845
3997
|
const packages = /* @__PURE__ */ new Set();
|
|
2846
3998
|
if (options.browser) {
|
|
2847
|
-
packages.add("@topolo/ui-kit");
|
|
2848
|
-
packages.add("@topolo/auth-client");
|
|
3999
|
+
packages.add("@topolo-io/ui-kit");
|
|
4000
|
+
packages.add("@topolo-io/auth-client");
|
|
2849
4001
|
}
|
|
2850
4002
|
if (options.api) {
|
|
2851
|
-
packages.add("@topolo/auth-middleware");
|
|
4003
|
+
packages.add("@topolo-io/auth-middleware");
|
|
2852
4004
|
packages.add("@topolo/sdk");
|
|
2853
4005
|
}
|
|
2854
4006
|
return Array.from(packages);
|
|
@@ -2906,7 +4058,7 @@ function scaffoldFiles(options) {
|
|
|
2906
4058
|
"builds the authenticated browser surface",
|
|
2907
4059
|
"keeps generated apps on the standard React/Vite baseline"
|
|
2908
4060
|
],
|
|
2909
|
-
content: viteConfig()
|
|
4061
|
+
content: viteConfig(target)
|
|
2910
4062
|
},
|
|
2911
4063
|
{
|
|
2912
4064
|
path: `${base}/index.html`,
|
|
@@ -3059,8 +4211,8 @@ function scaffoldFiles(options) {
|
|
|
3059
4211
|
path: `${base}/src/App.tsx`,
|
|
3060
4212
|
purpose: "Authenticated browser surface.",
|
|
3061
4213
|
requiredSignals: [
|
|
3062
|
-
"imports shared TopoloAppShell from @topolo/ui-kit",
|
|
3063
|
-
"uses @topolo/auth-client for app-origin auth flows",
|
|
4214
|
+
"imports shared TopoloAppShell from @topolo-io/ui-kit",
|
|
4215
|
+
"uses @topolo-io/auth-client for app-origin auth flows",
|
|
3064
4216
|
"keeps launcher and account-menu behavior on the shared platform surface",
|
|
3065
4217
|
"passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
|
|
3066
4218
|
],
|
|
@@ -3085,7 +4237,7 @@ function scaffoldFiles(options) {
|
|
|
3085
4237
|
path: `${base}/src/worker.ts`,
|
|
3086
4238
|
purpose: "Protected service boundary.",
|
|
3087
4239
|
requiredSignals: [
|
|
3088
|
-
"uses @topolo/auth-middleware or Auth validation for protected routes",
|
|
4240
|
+
"uses @topolo-io/auth-middleware or Auth validation for protected routes",
|
|
3089
4241
|
"derives organization from the credential rather than client-supplied orgId",
|
|
3090
4242
|
"emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
|
|
3091
4243
|
],
|
|
@@ -3112,7 +4264,7 @@ function scaffoldCommands(options) {
|
|
|
3112
4264
|
}
|
|
3113
4265
|
const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
|
|
3114
4266
|
return [
|
|
3115
|
-
`topolo platform apps scaffold ${scaffoldId}${portfolioFlags} --write --repo-root /Users/ashes/Projects/topolo --json`,
|
|
4267
|
+
`topolo platform apps scaffold ${scaffoldId}${portfolioFlags} --write --provision --repo-root /Users/ashes/Projects/topolo --json`,
|
|
3116
4268
|
"topolo apps requirements --json",
|
|
3117
4269
|
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on needs_review --json`,
|
|
3118
4270
|
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on partial --json`
|
|
@@ -3187,7 +4339,13 @@ function topoloConformanceWorkflow(options) {
|
|
|
3187
4339
|
"",
|
|
3188
4340
|
" - name: Run Topolo app conformance audit",
|
|
3189
4341
|
" run: |",
|
|
3190
|
-
` node PlatformApplications/TopoloCli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json
|
|
4342
|
+
` node PlatformApplications/TopoloCli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
|
|
4343
|
+
"",
|
|
4344
|
+
" - name: Record green CI evidence",
|
|
4345
|
+
` if: \${{ success() }}`,
|
|
4346
|
+
` working-directory: PlatformApplications/${options.appDir}`,
|
|
4347
|
+
" run: |",
|
|
4348
|
+
` node "$GITHUB_WORKSPACE/PlatformApplications/TopoloCli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
|
|
3191
4349
|
].join("\n");
|
|
3192
4350
|
}
|
|
3193
4351
|
function packageJson(application, target, options) {
|
|
@@ -3210,17 +4368,22 @@ function packageJson(application, target, options) {
|
|
|
3210
4368
|
devDependencies["@types/react-dom"] = "^18.3.0";
|
|
3211
4369
|
devDependencies["@vitejs/plugin-react"] = "^4.7.0";
|
|
3212
4370
|
if (target === "platform") {
|
|
3213
|
-
dependencies["@topolo/auth-client"] = topoloPackageVersion("@topolo/auth-client", target);
|
|
3214
|
-
dependencies["@topolo/
|
|
4371
|
+
dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
|
|
4372
|
+
dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
|
|
4373
|
+
dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
|
|
3215
4374
|
}
|
|
3216
4375
|
}
|
|
3217
4376
|
if (options.api) {
|
|
3218
4377
|
if (target === "platform") {
|
|
3219
|
-
dependencies["@topolo/auth-middleware"] = topoloPackageVersion("@topolo/auth-middleware", target);
|
|
4378
|
+
dependencies["@topolo-io/auth-middleware"] = topoloPackageVersion("@topolo-io/auth-middleware", target);
|
|
3220
4379
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
4380
|
+
scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
|
|
4381
|
+
scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --env staging";
|
|
4382
|
+
scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production npm run build && wrangler deploy";
|
|
4383
|
+
} else {
|
|
4384
|
+
scripts.deploy = "npm run build && wrangler deploy";
|
|
3221
4385
|
}
|
|
3222
4386
|
devDependencies.wrangler = "^4.40.2";
|
|
3223
|
-
scripts.deploy = "npm run build && wrangler deploy";
|
|
3224
4387
|
}
|
|
3225
4388
|
return `${json({
|
|
3226
4389
|
name: application.packageName,
|
|
@@ -3235,10 +4398,20 @@ function packageJson(application, target, options) {
|
|
|
3235
4398
|
}
|
|
3236
4399
|
function topoloPackageVersion(packageName, target) {
|
|
3237
4400
|
if (target === "developer") {
|
|
3238
|
-
if (packageName === "@topolo/auth-client") return "^1.0.0";
|
|
4401
|
+
if (packageName === "@topolo-io/auth-client") return "^1.0.0";
|
|
3239
4402
|
return "^0.1.0";
|
|
3240
4403
|
}
|
|
3241
|
-
const
|
|
4404
|
+
const platformPackageFolders = {
|
|
4405
|
+
"@topolo/sdk": "topolo-sdk",
|
|
4406
|
+
"@topolo-io/auth-client": "topolo-auth-client",
|
|
4407
|
+
"@topolo-io/auth-middleware": "topolo-auth-middleware",
|
|
4408
|
+
"@topolo-io/design-tokens": "topolo-design-tokens",
|
|
4409
|
+
"@topolo-io/ui-kit": "topolo-ui-kit"
|
|
4410
|
+
};
|
|
4411
|
+
const folder = platformPackageFolders[packageName];
|
|
4412
|
+
if (!folder) {
|
|
4413
|
+
throw new Error(`Unknown Topolo platform package: ${packageName}`);
|
|
4414
|
+
}
|
|
3242
4415
|
return `file:../packages/${folder}`;
|
|
3243
4416
|
}
|
|
3244
4417
|
function readme(application, target, options) {
|
|
@@ -3267,13 +4440,13 @@ function readme(application, target, options) {
|
|
|
3267
4440
|
lines.push(
|
|
3268
4441
|
"## Shared Topolo Surface",
|
|
3269
4442
|
"",
|
|
3270
|
-
"- `@topolo/ui-kit` provides the shared shell, launcher, account menu, command palette, and bug reporter.",
|
|
3271
|
-
"- `@topolo/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
4443
|
+
"- `@topolo-io/ui-kit` provides the shared shell, launcher, account menu, command palette, and bug reporter.",
|
|
4444
|
+
"- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
3272
4445
|
"- 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.",
|
|
3273
4446
|
"- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
|
|
3274
4447
|
);
|
|
3275
4448
|
if (options.api) {
|
|
3276
|
-
lines.push("- `@topolo/auth-middleware` validates Worker requests through Topolo Auth.");
|
|
4449
|
+
lines.push("- `@topolo-io/auth-middleware` validates Worker requests through Topolo Auth.");
|
|
3277
4450
|
}
|
|
3278
4451
|
} else {
|
|
3279
4452
|
lines.push(
|
|
@@ -3336,6 +4509,9 @@ function gitignore() {
|
|
|
3336
4509
|
".env",
|
|
3337
4510
|
".env.*",
|
|
3338
4511
|
"!.env.example",
|
|
4512
|
+
"# Transient CI evidence written by `topolo apps record-evidence`;",
|
|
4513
|
+
"# read by the conformance audit, never committed.",
|
|
4514
|
+
".topolo/",
|
|
3339
4515
|
""
|
|
3340
4516
|
].join("\n");
|
|
3341
4517
|
}
|
|
@@ -3363,17 +4539,63 @@ function envDts(api) {
|
|
|
3363
4539
|
);
|
|
3364
4540
|
return lines.join("\n");
|
|
3365
4541
|
}
|
|
3366
|
-
function viteConfig() {
|
|
4542
|
+
function viteConfig(target) {
|
|
4543
|
+
if (target === "developer") {
|
|
4544
|
+
return [
|
|
4545
|
+
"import { defineConfig } from 'vite';",
|
|
4546
|
+
"import react from '@vitejs/plugin-react';",
|
|
4547
|
+
"",
|
|
4548
|
+
"export default defineConfig({",
|
|
4549
|
+
" plugins: [react()],",
|
|
4550
|
+
" resolve: {",
|
|
4551
|
+
" dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
|
|
4552
|
+
" },",
|
|
4553
|
+
" build: { outDir: 'dist' },",
|
|
4554
|
+
"});",
|
|
4555
|
+
""
|
|
4556
|
+
].join("\n");
|
|
4557
|
+
}
|
|
3367
4558
|
return [
|
|
3368
|
-
"import {
|
|
4559
|
+
"import { fileURLToPath } from 'node:url';",
|
|
4560
|
+
"import { defineConfig, type Plugin } from 'vite';",
|
|
3369
4561
|
"import react from '@vitejs/plugin-react';",
|
|
3370
4562
|
"",
|
|
3371
|
-
"
|
|
3372
|
-
"
|
|
3373
|
-
"
|
|
3374
|
-
"
|
|
3375
|
-
"
|
|
3376
|
-
"
|
|
4563
|
+
"function stagingUrlRewrite(): Plugin {",
|
|
4564
|
+
" const replacements: Array<[string, string]> = [",
|
|
4565
|
+
" ['https://auth.topolo.app', 'https://auth.stg.topolo.us'],",
|
|
4566
|
+
" ['https://notify.topolo.app', 'https://notify.stg.topolo.us'],",
|
|
4567
|
+
" ];",
|
|
4568
|
+
" return {",
|
|
4569
|
+
" name: 'topolo-staging-url-rewrite',",
|
|
4570
|
+
" generateBundle(_options, bundle) {",
|
|
4571
|
+
" for (const item of Object.values(bundle)) {",
|
|
4572
|
+
" if (item.type === 'chunk') {",
|
|
4573
|
+
" for (const [from, to] of replacements) item.code = item.code.split(from).join(to);",
|
|
4574
|
+
" } else if (typeof item.source === 'string') {",
|
|
4575
|
+
" for (const [from, to] of replacements) item.source = item.source.split(from).join(to);",
|
|
4576
|
+
" }",
|
|
4577
|
+
" }",
|
|
4578
|
+
" },",
|
|
4579
|
+
" };",
|
|
4580
|
+
"}",
|
|
4581
|
+
"",
|
|
4582
|
+
"export default defineConfig(() => {",
|
|
4583
|
+
" const isStagingBuild =",
|
|
4584
|
+
" process.env.DEPLOY_ENVIRONMENT === 'staging' ||",
|
|
4585
|
+
" process.env.TOPOLO_ENV === 'staging' ||",
|
|
4586
|
+
" process.env.VITE_TOPOLO_ENV === 'staging' ||",
|
|
4587
|
+
" process.env.VITE_TOPOLO_AUTH_URL === 'https://auth.stg.topolo.us';",
|
|
4588
|
+
"",
|
|
4589
|
+
" return {",
|
|
4590
|
+
" plugins: [react(), ...(isStagingBuild ? [stagingUrlRewrite()] : [])],",
|
|
4591
|
+
" resolve: {",
|
|
4592
|
+
" alias: {",
|
|
4593
|
+
" '@topolo-io/auth-client': fileURLToPath(new URL('./node_modules/@topolo-io/auth-client/dist/index.js', import.meta.url)),",
|
|
4594
|
+
" },",
|
|
4595
|
+
" dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
|
|
4596
|
+
" },",
|
|
4597
|
+
" build: { outDir: 'dist' },",
|
|
4598
|
+
" };",
|
|
3377
4599
|
"});",
|
|
3378
4600
|
""
|
|
3379
4601
|
].join("\n");
|
|
@@ -3399,6 +4621,7 @@ function mainTsx() {
|
|
|
3399
4621
|
return [
|
|
3400
4622
|
"import React from 'react';",
|
|
3401
4623
|
"import { createRoot } from 'react-dom/client';",
|
|
4624
|
+
"import '@topolo-io/design-tokens/tokens.css';",
|
|
3402
4625
|
"import App from './App';",
|
|
3403
4626
|
"import './styles.css';",
|
|
3404
4627
|
"",
|
|
@@ -3418,12 +4641,15 @@ function mainTsx() {
|
|
|
3418
4641
|
}
|
|
3419
4642
|
function stylesCss() {
|
|
3420
4643
|
return [
|
|
4644
|
+
"/* Local app styles. Design tokens come from @topolo-io/design-tokens (imported in main.tsx). */",
|
|
4645
|
+
"/* Use hsl(var(--token)) for colors so light/dark themes flip from the shared shell. */",
|
|
4646
|
+
"",
|
|
3421
4647
|
":root {",
|
|
3422
4648
|
" color-scheme: light dark;",
|
|
3423
4649
|
" font-family:",
|
|
3424
4650
|
' Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;',
|
|
3425
|
-
" background:
|
|
3426
|
-
" color:
|
|
4651
|
+
" background: hsl(var(--background));",
|
|
4652
|
+
" color: hsl(var(--foreground));",
|
|
3427
4653
|
"}",
|
|
3428
4654
|
"",
|
|
3429
4655
|
"* {",
|
|
@@ -3432,7 +4658,8 @@ function stylesCss() {
|
|
|
3432
4658
|
"",
|
|
3433
4659
|
"body {",
|
|
3434
4660
|
" margin: 0;",
|
|
3435
|
-
" background:
|
|
4661
|
+
" background: hsl(var(--background));",
|
|
4662
|
+
" color: hsl(var(--foreground));",
|
|
3436
4663
|
"}",
|
|
3437
4664
|
"",
|
|
3438
4665
|
"button,",
|
|
@@ -3442,10 +4669,24 @@ function stylesCss() {
|
|
|
3442
4669
|
" font: inherit;",
|
|
3443
4670
|
"}",
|
|
3444
4671
|
"",
|
|
3445
|
-
".topolo-starter-workspace
|
|
4672
|
+
".topolo-starter-workspace {",
|
|
4673
|
+
" display: grid;",
|
|
4674
|
+
" gap: 18px;",
|
|
4675
|
+
" min-width: 0;",
|
|
4676
|
+
" width: auto;",
|
|
4677
|
+
" max-width: min(100%, calc(100vw - 56px));",
|
|
4678
|
+
" overflow-x: clip;",
|
|
4679
|
+
"}",
|
|
4680
|
+
"",
|
|
3446
4681
|
".topolo-starter-card-stack {",
|
|
3447
4682
|
" display: grid;",
|
|
3448
4683
|
" gap: 18px;",
|
|
4684
|
+
" min-width: 0;",
|
|
4685
|
+
"}",
|
|
4686
|
+
"",
|
|
4687
|
+
".topolo-starter-workspace > *,",
|
|
4688
|
+
".topolo-starter-card-stack > * {",
|
|
4689
|
+
" min-width: 0;",
|
|
3449
4690
|
"}",
|
|
3450
4691
|
"",
|
|
3451
4692
|
".topolo-starter-card h2 {",
|
|
@@ -3456,15 +4697,16 @@ function stylesCss() {
|
|
|
3456
4697
|
"",
|
|
3457
4698
|
".topolo-starter-card p {",
|
|
3458
4699
|
" margin: 0;",
|
|
3459
|
-
" color:
|
|
4700
|
+
" color: hsl(var(--muted-foreground));",
|
|
3460
4701
|
" line-height: 1.55;",
|
|
3461
4702
|
"}",
|
|
3462
4703
|
"",
|
|
3463
4704
|
".topolo-starter-card {",
|
|
3464
|
-
" border: 1px solid
|
|
3465
|
-
" border-radius:
|
|
4705
|
+
" border: 1px solid hsl(var(--border));",
|
|
4706
|
+
" border-radius: var(--radius);",
|
|
3466
4707
|
" padding: 20px;",
|
|
3467
|
-
" background:
|
|
4708
|
+
" background: hsl(var(--card));",
|
|
4709
|
+
" color: hsl(var(--card-foreground));",
|
|
3468
4710
|
"}",
|
|
3469
4711
|
""
|
|
3470
4712
|
].join("\n");
|
|
@@ -3476,12 +4718,21 @@ function appTsx(application, target, options) {
|
|
|
3476
4718
|
function platformAppTsx(application, options) {
|
|
3477
4719
|
const appName = toTitle(application.name);
|
|
3478
4720
|
const serviceId = application.serviceId ?? application.id;
|
|
3479
|
-
const
|
|
3480
|
-
const
|
|
4721
|
+
const isPersonal = options.audience === "personal";
|
|
4722
|
+
const tagline = isPersonal ? "Topolo personal app" : "Topolo workspace";
|
|
4723
|
+
const workspaceTitle = isPersonal ? "Personal app" : "Workspace";
|
|
4724
|
+
const shellLabel = isPersonal ? "personal app shell" : "workspace shell";
|
|
4725
|
+
const signedSurfaceLabel = isPersonal ? "personal app" : "workspace";
|
|
4726
|
+
const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
|
|
4727
|
+
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.";
|
|
3481
4728
|
return [
|
|
3482
|
-
"import { useEffect, useMemo, useState } from 'react';",
|
|
3483
|
-
"import { createBrowserAuthModule, type AuthUser } from '@topolo/auth-client';",
|
|
3484
|
-
"import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, type TopoloAppShellUser } from '@topolo/ui-kit';",
|
|
4729
|
+
"import { Component, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
|
|
4730
|
+
"import { createBrowserAuthModule, type AuthUser } from '@topolo-io/auth-client';",
|
|
4731
|
+
"import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, setupBugReporterErrorCapture, type TopoloAppShellUser } from '@topolo-io/ui-kit';",
|
|
4732
|
+
"",
|
|
4733
|
+
"// Wire global error/console capture so BugReporter (enabled in TopoloAppShell below)",
|
|
4734
|
+
"// can attach the last few errors to any submitted bug report.",
|
|
4735
|
+
"setupBugReporterErrorCapture();",
|
|
3485
4736
|
"",
|
|
3486
4737
|
"type NavItem = 'home';",
|
|
3487
4738
|
"type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
|
|
@@ -3489,23 +4740,27 @@ function platformAppTsx(application, options) {
|
|
|
3489
4740
|
`const APP_ID = '${application.id}';`,
|
|
3490
4741
|
`const APP_NAME = '${escapeSingleQuoted(appName)}';`,
|
|
3491
4742
|
`const DEFAULT_SERVICE_ID = '${serviceId}';`,
|
|
3492
|
-
"const
|
|
4743
|
+
"const DEFAULT_AUTH_URL =",
|
|
4744
|
+
" typeof window !== 'undefined' && window.location.hostname.endsWith('.stg.topolo.us')",
|
|
4745
|
+
" ? 'https://auth.stg.topolo.us'",
|
|
4746
|
+
" : 'https://auth.topolo.app';",
|
|
4747
|
+
"const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? DEFAULT_AUTH_URL;",
|
|
3493
4748
|
"const SERVICE_ID = import.meta.env.VITE_TOPOLO_SERVICE_ID ?? DEFAULT_SERVICE_ID;",
|
|
3494
4749
|
"const LANDING_CONFIG = {",
|
|
3495
4750
|
" app_name: APP_NAME,",
|
|
3496
4751
|
` app_tagline: '${escapeSingleQuoted(tagline)}',`,
|
|
3497
4752
|
" hero_title: `Open ${APP_NAME} in Topolo`,",
|
|
3498
|
-
|
|
4753
|
+
` hero_subtitle: 'Shared landing, login, callback, auth, and ${escapeSingleQuoted(shellLabel)} wiring are already included in the generated baseline.',`,
|
|
3499
4754
|
" hero_cta_text: 'Sign in',",
|
|
3500
4755
|
" hero_cta_url: '/login',",
|
|
3501
4756
|
" cta_title: 'Start from the shared platform entry flow',",
|
|
3502
|
-
|
|
4757
|
+
` cta_subtitle: 'Unauthenticated routes stay on the shared Topolo surfaces before users enter the signed ${escapeSingleQuoted(shellLabel)}.',`,
|
|
3503
4758
|
"};",
|
|
3504
4759
|
"const LOGIN_CONFIG = {",
|
|
3505
4760
|
" app_name: APP_NAME,",
|
|
3506
4761
|
` app_tagline: '${escapeSingleQuoted(tagline)}',`,
|
|
3507
4762
|
" hero_title: `Sign in to ${APP_NAME}`,",
|
|
3508
|
-
|
|
4763
|
+
` hero_subtitle: 'Use Topolo Auth to open the shared ${escapeSingleQuoted(shellLabel)} for this application.',`,
|
|
3509
4764
|
" authenticated_home_path: '/',",
|
|
3510
4765
|
"};",
|
|
3511
4766
|
"",
|
|
@@ -3525,6 +4780,7 @@ function platformAppTsx(application, options) {
|
|
|
3525
4780
|
"function toShellUser(user: AuthUser | null): TopoloAppShellUser | null {",
|
|
3526
4781
|
" if (!user) return null;",
|
|
3527
4782
|
" return {",
|
|
4783
|
+
" id: user.id,",
|
|
3528
4784
|
" name: user.name ?? user.email,",
|
|
3529
4785
|
" email: user.email,",
|
|
3530
4786
|
" role: user.role,",
|
|
@@ -3534,6 +4790,14 @@ function platformAppTsx(application, options) {
|
|
|
3534
4790
|
"}",
|
|
3535
4791
|
"",
|
|
3536
4792
|
"export default function App() {",
|
|
4793
|
+
" return (",
|
|
4794
|
+
" <AppErrorBoundary>",
|
|
4795
|
+
" <AppRoot />",
|
|
4796
|
+
" </AppErrorBoundary>",
|
|
4797
|
+
" );",
|
|
4798
|
+
"}",
|
|
4799
|
+
"",
|
|
4800
|
+
"function AppRoot() {",
|
|
3537
4801
|
" const route = getRoute();",
|
|
3538
4802
|
" const [user, setUser] = useState<AuthUser | null>(null);",
|
|
3539
4803
|
" const [loading, setLoading] = useState(true);",
|
|
@@ -3665,7 +4929,7 @@ function platformAppTsx(application, options) {
|
|
|
3665
4929
|
" appId={APP_ID}",
|
|
3666
4930
|
" serviceId={SERVICE_ID}",
|
|
3667
4931
|
" appName={APP_NAME}",
|
|
3668
|
-
|
|
4932
|
+
` subtitle="Opening your ${escapeDoubleQuoted(signedSurfaceLabel)}..."`,
|
|
3669
4933
|
" />",
|
|
3670
4934
|
" );",
|
|
3671
4935
|
" }",
|
|
@@ -3676,10 +4940,13 @@ function platformAppTsx(application, options) {
|
|
|
3676
4940
|
" serviceId={SERVICE_ID}",
|
|
3677
4941
|
" appName={APP_NAME}",
|
|
3678
4942
|
" user={shellUser}",
|
|
4943
|
+
" orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
|
|
4944
|
+
" activeContextType={user.activeContextType}",
|
|
4945
|
+
" activeContextId={user.activeContextId}",
|
|
3679
4946
|
' activeNavItemId="home"',
|
|
3680
4947
|
` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }]}`,
|
|
4948
|
+
' navigationMode="topbar"',
|
|
3681
4949
|
" onNavItemSelect={() => undefined}",
|
|
3682
|
-
" eyebrow={APP_NAME}",
|
|
3683
4950
|
` title="${escapeDoubleQuoted(workspaceTitle)}"`,
|
|
3684
4951
|
" authUrl={AUTH_URL}",
|
|
3685
4952
|
" getAccessToken={getAccessToken}",
|
|
@@ -3691,13 +4958,12 @@ function platformAppTsx(application, options) {
|
|
|
3691
4958
|
" bugReporterAppName={APP_NAME}",
|
|
3692
4959
|
" enableCommandPalette",
|
|
3693
4960
|
" enableBugReporter",
|
|
3694
|
-
" showThemeToggle",
|
|
3695
4961
|
" >",
|
|
3696
4962
|
' <div className="topolo-starter-workspace">',
|
|
3697
4963
|
' <section className="topolo-starter-card">',
|
|
3698
|
-
|
|
4964
|
+
` <h2>${baselineHeading}</h2>`,
|
|
3699
4965
|
" <p>",
|
|
3700
|
-
|
|
4966
|
+
` ${baselineDescription}`,
|
|
3701
4967
|
" </p>",
|
|
3702
4968
|
" </section>",
|
|
3703
4969
|
" </div>",
|
|
@@ -3716,6 +4982,36 @@ function platformAppTsx(application, options) {
|
|
|
3716
4982
|
" if (error instanceof Error && error.message) return error.message;",
|
|
3717
4983
|
" return 'Unexpected authentication error.';",
|
|
3718
4984
|
"}",
|
|
4985
|
+
"",
|
|
4986
|
+
"// Minimal error boundary so a render-time crash falls back to a recoverable surface",
|
|
4987
|
+
"// instead of a blank page. The shared TopoloAppShell BugReporter still has access to",
|
|
4988
|
+
"// the captured stack via setupBugReporterErrorCapture().",
|
|
4989
|
+
"class AppErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {",
|
|
4990
|
+
" state = { error: null as Error | null };",
|
|
4991
|
+
" static getDerivedStateFromError(error: Error) {",
|
|
4992
|
+
" return { error };",
|
|
4993
|
+
" }",
|
|
4994
|
+
" componentDidCatch(error: Error, info: ErrorInfo) {",
|
|
4995
|
+
" // Surface to the platform error pipeline. setupBugReporterErrorCapture() also",
|
|
4996
|
+
" // records this via the console.error hook for BugReporter attachments.",
|
|
4997
|
+
" console.error('[' + APP_ID + '] render error:', error, info.componentStack);",
|
|
4998
|
+
" }",
|
|
4999
|
+
" render() {",
|
|
5000
|
+
" if (!this.state.error) return this.props.children;",
|
|
5001
|
+
" return (",
|
|
5002
|
+
" <TopoloAuthFlowScreen",
|
|
5003
|
+
" appName={APP_NAME}",
|
|
5004
|
+
" appId={APP_ID}",
|
|
5005
|
+
" serviceId={SERVICE_ID}",
|
|
5006
|
+
' status="error"',
|
|
5007
|
+
" title={APP_NAME + ' hit an unexpected error'}",
|
|
5008
|
+
" message={this.state.error.message}",
|
|
5009
|
+
' actionLabel="Reload"',
|
|
5010
|
+
" onAction={() => window.location.reload()}",
|
|
5011
|
+
" />",
|
|
5012
|
+
" );",
|
|
5013
|
+
" }",
|
|
5014
|
+
"}",
|
|
3719
5015
|
""
|
|
3720
5016
|
].join("\n");
|
|
3721
5017
|
}
|
|
@@ -3859,7 +5155,7 @@ function wranglerToml(application, target) {
|
|
|
3859
5155
|
const lines = [
|
|
3860
5156
|
`name = "${workerName}"`,
|
|
3861
5157
|
'main = "src/worker.ts"',
|
|
3862
|
-
`compatibility_date = "${
|
|
5158
|
+
`compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
|
|
3863
5159
|
'compatibility_flags = ["nodejs_compat"]'
|
|
3864
5160
|
];
|
|
3865
5161
|
if (target === "platform") {
|
|
@@ -3894,16 +5190,54 @@ function wranglerToml(application, target) {
|
|
|
3894
5190
|
`SERVICE_ID = "${application.serviceId ?? application.id}"`,
|
|
3895
5191
|
""
|
|
3896
5192
|
);
|
|
5193
|
+
if (target === "platform") {
|
|
5194
|
+
lines.push(
|
|
5195
|
+
"# Staging deploys use the separate Topolo Staging Cloudflare account.",
|
|
5196
|
+
"[env.staging]",
|
|
5197
|
+
`name = "${workerName}-staging"`,
|
|
5198
|
+
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
5199
|
+
"workers_dev = true",
|
|
5200
|
+
"routes = [",
|
|
5201
|
+
` { pattern = "${slug}.stg.topolo.us", custom_domain = true }`,
|
|
5202
|
+
"]",
|
|
5203
|
+
"",
|
|
5204
|
+
"[env.staging.vars]",
|
|
5205
|
+
'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
|
|
5206
|
+
`SERVICE_ID = "${application.serviceId ?? application.id}"`,
|
|
5207
|
+
""
|
|
5208
|
+
);
|
|
5209
|
+
}
|
|
3897
5210
|
return lines.join("\n");
|
|
3898
5211
|
}
|
|
3899
|
-
function workerTs(application, target,
|
|
5212
|
+
function workerTs(application, target, options) {
|
|
3900
5213
|
if (target === "developer") return developerWorkerTs(application);
|
|
3901
|
-
return platformWorkerTs(application);
|
|
5214
|
+
return platformWorkerTs(application, options);
|
|
3902
5215
|
}
|
|
3903
|
-
function platformWorkerTs(application) {
|
|
5216
|
+
function platformWorkerTs(application, options) {
|
|
3904
5217
|
const serviceId = application.serviceId ?? application.id;
|
|
5218
|
+
const serviceName = toTitle(application.name);
|
|
5219
|
+
const openActionLabel = options.audience === "personal" ? "Open app" : "Open workspace";
|
|
5220
|
+
const bootstrapBody = options.tenancy === "household" ? [
|
|
5221
|
+
" const userContext = auth.user && typeof auth.user === 'object' ? (auth.user as unknown as Record<string, unknown>) : {};",
|
|
5222
|
+
" return json({",
|
|
5223
|
+
" user: auth.user,",
|
|
5224
|
+
" activeContextType: userContext.activeContextType ?? null,",
|
|
5225
|
+
" activeContextId: userContext.activeContextId ?? null,",
|
|
5226
|
+
" activeContext: userContext.activeContext ?? null,",
|
|
5227
|
+
" selectedHouseholdId: userContext.selectedHouseholdId ?? null,",
|
|
5228
|
+
" selectedHousehold: userContext.selectedHousehold ?? null,",
|
|
5229
|
+
" household: userContext.selectedHousehold ?? null,",
|
|
5230
|
+
" householdMemberships: userContext.householdMemberships ?? [],",
|
|
5231
|
+
" }, { status: 200 }, request);"
|
|
5232
|
+
] : [
|
|
5233
|
+
" return json({",
|
|
5234
|
+
" user: auth.user,",
|
|
5235
|
+
" organization: auth.organization,",
|
|
5236
|
+
" }, { status: 200 }, request);"
|
|
5237
|
+
];
|
|
3905
5238
|
return [
|
|
3906
|
-
"import {
|
|
5239
|
+
"import { createTopoloWidgetResponse } from '@topolo/sdk';",
|
|
5240
|
+
"import { verifyRequest } from '@topolo-io/auth-middleware';",
|
|
3907
5241
|
"",
|
|
3908
5242
|
"export interface Env {",
|
|
3909
5243
|
" AUTH_BASE_URL?: string;",
|
|
@@ -3931,11 +5265,24 @@ function platformWorkerTs(application) {
|
|
|
3931
5265
|
" return json({ error: auth.error }, { status: auth.status }, request);",
|
|
3932
5266
|
" }",
|
|
3933
5267
|
"",
|
|
5268
|
+
" if (url.pathname === '/api/widget') {",
|
|
5269
|
+
" return json(createTopoloWidgetResponse({",
|
|
5270
|
+
" serviceId: env.SERVICE_ID ?? DEFAULT_SERVICE_ID,",
|
|
5271
|
+
` serviceName: '${escapeSingleQuoted(serviceName)}',`,
|
|
5272
|
+
" widgets: [",
|
|
5273
|
+
" {",
|
|
5274
|
+
" type: 'quick-actions',",
|
|
5275
|
+
" actions: [",
|
|
5276
|
+
` { id: 'open-workspace', label: '${escapeSingleQuoted(openActionLabel)}', link: '/', primary: true },`,
|
|
5277
|
+
" { id: 'view-health', label: 'View health', link: '/api/health' },",
|
|
5278
|
+
" ],",
|
|
5279
|
+
" },",
|
|
5280
|
+
" ],",
|
|
5281
|
+
" }), { status: 200 }, request);",
|
|
5282
|
+
" }",
|
|
5283
|
+
"",
|
|
3934
5284
|
" if (url.pathname === '/api/bootstrap') {",
|
|
3935
|
-
|
|
3936
|
-
" user: auth.user,",
|
|
3937
|
-
" organization: auth.organization,",
|
|
3938
|
-
" }, { status: 200 }, request);",
|
|
5285
|
+
...bootstrapBody,
|
|
3939
5286
|
" }",
|
|
3940
5287
|
"",
|
|
3941
5288
|
" return json({ error: 'not_found' }, { status: 404 }, request);",
|
|
@@ -4030,6 +5377,7 @@ function developerWorkerTs(application) {
|
|
|
4030
5377
|
function cloudcontrolJson(application, options) {
|
|
4031
5378
|
const slug = toSlug(application.id);
|
|
4032
5379
|
const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
|
|
5380
|
+
const stagingHost = `${slug}.stg.topolo.us`;
|
|
4033
5381
|
const deployTargets = options.api ? [
|
|
4034
5382
|
{
|
|
4035
5383
|
kind: "worker",
|
|
@@ -4045,6 +5393,21 @@ function cloudcontrolJson(application, options) {
|
|
|
4045
5393
|
routes: [host],
|
|
4046
5394
|
api_url: application.productionUrl,
|
|
4047
5395
|
service_id: application.serviceId
|
|
5396
|
+
},
|
|
5397
|
+
{
|
|
5398
|
+
kind: "worker",
|
|
5399
|
+
environment: "staging",
|
|
5400
|
+
target_name: `topolo-${slug}-staging`,
|
|
5401
|
+
cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
5402
|
+
config_path: "wrangler.toml",
|
|
5403
|
+
build_command: "VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build",
|
|
5404
|
+
output_dir: "dist",
|
|
5405
|
+
production_branch: "staging",
|
|
5406
|
+
healthcheck_path: "/api/health",
|
|
5407
|
+
domains: [stagingHost],
|
|
5408
|
+
routes: [stagingHost],
|
|
5409
|
+
api_url: `https://${stagingHost}`,
|
|
5410
|
+
service_id: application.serviceId
|
|
4048
5411
|
}
|
|
4049
5412
|
] : [
|
|
4050
5413
|
{
|
|
@@ -4080,6 +5443,12 @@ function cloudcontrolJson(application, options) {
|
|
|
4080
5443
|
url: application.productionUrl,
|
|
4081
5444
|
branch: "main",
|
|
4082
5445
|
status: "unknown"
|
|
5446
|
+
},
|
|
5447
|
+
{
|
|
5448
|
+
name: "staging",
|
|
5449
|
+
url: `https://${stagingHost}`,
|
|
5450
|
+
branch: "staging",
|
|
5451
|
+
status: "unknown"
|
|
4083
5452
|
}
|
|
4084
5453
|
] : [],
|
|
4085
5454
|
deploy_targets: deployTargets,
|
|
@@ -4103,36 +5472,53 @@ function systemJson(application, options) {
|
|
|
4103
5472
|
const slug = toSlug(application.id);
|
|
4104
5473
|
const suiteLabel = options.audience === "personal" ? "personal" : "platform";
|
|
4105
5474
|
const summaryLabel = options.audience === "personal" ? "first-party Topolo personal application" : "first-party Topolo platform application";
|
|
5475
|
+
const apiNotes = options.api ? options.tenancy === "household" ? "Protected Worker routes validate Topolo Auth credentials and derive active personal context plus selected household context from the credential. The browser baseline starts on shared landing and login routes, completes auth on /auth/callback, and dedupes React when linked Topolo packages are installed locally." : "Protected Worker routes validate Topolo Auth credentials and derive organization context from the credential. The browser baseline starts on shared landing and login routes, completes auth on /auth/callback, and dedupes React when linked Topolo packages are installed locally." : "Browser-only application surface using shared Topolo Auth landing/login/callback routes, shared shell packages, and React runtime dedupe.";
|
|
5476
|
+
const sensitiveData = options.tenancy === "household" ? ["identity", "personal_data", "household_data"] : ["identity", "org_data"];
|
|
5477
|
+
const privacySensitiveData = options.tenancy === "household" ? ["identity", "personal", "household"] : ["identity", "organization"];
|
|
4106
5478
|
return `${json({
|
|
4107
5479
|
id: `topolo-${slug}`,
|
|
4108
5480
|
name: toTitle(application.name),
|
|
4109
5481
|
slug: `topolo-${slug}`,
|
|
4110
5482
|
kind: "application",
|
|
4111
5483
|
summary: `${toTitle(application.name)} is a ${summaryLabel} scaffolded on shared Auth, UI Kit, CloudControl, and conformance contracts.`,
|
|
4112
|
-
lifecycle: "
|
|
5484
|
+
lifecycle: "inventory",
|
|
4113
5485
|
last_verified: CURRENT_VERIFICATION_DATE,
|
|
4114
5486
|
owners: ["platform-engineering"],
|
|
4115
5487
|
repo_paths: [`PlatformApplications/${application.appDir}`],
|
|
4116
5488
|
service_ids: options.api && application.serviceId ? [application.serviceId] : [],
|
|
4117
5489
|
visibility: "internal",
|
|
4118
5490
|
api_contract: {
|
|
4119
|
-
type: options.api ? "
|
|
5491
|
+
type: options.api ? "curated" : "none",
|
|
4120
5492
|
source: options.api ? `PlatformApplications/${application.appDir}/src/worker.ts` : `PlatformApplications/${application.appDir}/src/App.tsx`,
|
|
4121
|
-
notes:
|
|
5493
|
+
notes: apiNotes
|
|
4122
5494
|
},
|
|
4123
5495
|
primary_hosts: application.productionUrl ? [application.productionUrl] : [],
|
|
4124
5496
|
doc_paths: [`internal/apps/${slug}`, `public/applications/${slug}`],
|
|
4125
5497
|
security_assurance: {
|
|
4126
5498
|
risk_tier: "internal",
|
|
4127
|
-
auth_boundary: "Topolo Auth through @topolo/auth-client for browser sessions and @topolo/auth-middleware for Worker requests.",
|
|
5499
|
+
auth_boundary: "Topolo Auth through @topolo-io/auth-client for browser sessions and @topolo-io/auth-middleware for Worker requests.",
|
|
4128
5500
|
tenant_isolation: options.tenancy === "household" ? "household_scoped" : "organization_scoped",
|
|
4129
5501
|
external_inputs: ["browser", ...options.api ? ["api"] : []],
|
|
4130
|
-
sensitive_data:
|
|
5502
|
+
sensitive_data: sensitiveData,
|
|
4131
5503
|
security_review_status: "not_started",
|
|
4132
5504
|
last_security_review: CURRENT_VERIFICATION_DATE,
|
|
4133
5505
|
pentest_status: "not_started",
|
|
4134
5506
|
evidence_doc: `internal/apps/${slug}`
|
|
4135
5507
|
},
|
|
5508
|
+
data_privacy: {
|
|
5509
|
+
classification: "confidential",
|
|
5510
|
+
sensitive_data_classes: privacySensitiveData,
|
|
5511
|
+
storage_locations: ["source_control"],
|
|
5512
|
+
encryption_at_rest: "platform_managed",
|
|
5513
|
+
encryption_in_transit: "https_only",
|
|
5514
|
+
key_management: "cloudflare_managed",
|
|
5515
|
+
retention_policy: "needs_policy",
|
|
5516
|
+
deletion_export_status: "planned",
|
|
5517
|
+
logging_redaction_status: "partial",
|
|
5518
|
+
privacy_review_status: "not_started",
|
|
5519
|
+
enterprise_ready: false,
|
|
5520
|
+
evidence_doc: `internal/apps/${slug}`
|
|
5521
|
+
},
|
|
4136
5522
|
dependencies: options.audience === "personal" ? ["topolo-auth"] : ["topolo-auth", "topolo-platform"],
|
|
4137
5523
|
tags: ["application", "cloudflare", "typescript", suiteLabel]
|
|
4138
5524
|
})}
|
|
@@ -4143,12 +5529,14 @@ function internalDocs(application, options) {
|
|
|
4143
5529
|
const title = toTitle(application.name);
|
|
4144
5530
|
const suiteLabel = options.audience === "personal" ? "personal" : "platform";
|
|
4145
5531
|
const tenantLabel = options.tenancy === "household" ? "household" : "organization";
|
|
5532
|
+
const apiContextLabel = options.tenancy === "household" ? "active personal context and selected household context" : "organization context";
|
|
5533
|
+
const boundaryFailureText = options.tenancy === "household" ? "Domain data is added without a household-scoped backend boundary." : "Domain data is added without an organization-scoped backend boundary.";
|
|
4146
5534
|
return [
|
|
4147
5535
|
"---",
|
|
4148
5536
|
`title: "${escapeFrontmatter(title)} Handbook"`,
|
|
4149
5537
|
`summary: "Internal handbook for ${escapeFrontmatter(title)}."`,
|
|
4150
5538
|
'owners: ["platform-engineering"]',
|
|
4151
|
-
'status: "
|
|
5539
|
+
'status: "draft"',
|
|
4152
5540
|
`last_verified: "${CURRENT_VERIFICATION_DATE}"`,
|
|
4153
5541
|
"source_repos:",
|
|
4154
5542
|
` - "PlatformApplications/${application.appDir}"`,
|
|
@@ -4171,7 +5559,7 @@ function internalDocs(application, options) {
|
|
|
4171
5559
|
"",
|
|
4172
5560
|
"## Architecture",
|
|
4173
5561
|
"",
|
|
4174
|
-
"The generated browser surface uses `@topolo/ui-kit` for the shared landing page, shared login page, auth callback state, and shared signed shell. `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
|
|
5562
|
+
"The generated browser surface uses `@topolo-io/ui-kit` for the shared landing page, shared login page, auth callback state, and shared signed shell. `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
|
|
4175
5563
|
"",
|
|
4176
5564
|
"## Runtime Surfaces",
|
|
4177
5565
|
"",
|
|
@@ -4185,11 +5573,11 @@ function internalDocs(application, options) {
|
|
|
4185
5573
|
"",
|
|
4186
5574
|
"## API Reference",
|
|
4187
5575
|
"",
|
|
4188
|
-
options.api ?
|
|
5576
|
+
options.api ? `The generated API baseline exposes \`/api/health\`, \`/api/widget\`, and \`/api/bootstrap\`. \`/api/widget\` returns the SDK widget response contract for TopoloOne, while \`/api/bootstrap\` requires Topolo Auth validation and returns the verified user plus ${apiContextLabel}.` : "This generated baseline is browser-only and does not expose a callable API service.",
|
|
4189
5577
|
"",
|
|
4190
5578
|
"## Auth and Permissions",
|
|
4191
5579
|
"",
|
|
4192
|
-
options.api ? `Browser sessions and Worker requests must be validated through Topolo Auth under service id \`${application.serviceId}\`. Worker routes derive
|
|
5580
|
+
options.api ? `Browser sessions and Worker requests must be validated through Topolo Auth under service id \`${application.serviceId}\`. Worker routes derive ${apiContextLabel} from the verified credential.` : "Browser sessions must be established through Topolo Auth and rendered inside the shared Topolo shell.",
|
|
4193
5581
|
"",
|
|
4194
5582
|
"## Data Ownership",
|
|
4195
5583
|
"",
|
|
@@ -4197,14 +5585,14 @@ function internalDocs(application, options) {
|
|
|
4197
5585
|
"",
|
|
4198
5586
|
"## Deployments",
|
|
4199
5587
|
"",
|
|
4200
|
-
application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Deployment metadata starts in \`topolo.cloudcontrol.json\` and must stay aligned with CloudControl before live release.` : "No production host has been assigned yet. Deployment metadata must be completed before live release.",
|
|
5588
|
+
application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Staging mirrors the scaffold at \`https://${slug}.stg.topolo.us\` through the \`wrangler.toml\` staging environment and Topolo Staging Cloudflare account. Deployment metadata starts in \`topolo.cloudcontrol.json\` and must stay aligned with CloudControl before live release.` : "No production host has been assigned yet. Deployment metadata must be completed before live release.",
|
|
4201
5589
|
"",
|
|
4202
5590
|
"## Failure Modes",
|
|
4203
5591
|
"",
|
|
4204
5592
|
"- Auth service registration drifts from the generated Worker `SERVICE_ID`.",
|
|
4205
5593
|
"- CloudControl metadata does not match the eventual Cloudflare project or route.",
|
|
4206
5594
|
"- linked Topolo packages resolve a second React runtime and the browser shell renders blank instead of mounting the shared surfaces.",
|
|
4207
|
-
|
|
5595
|
+
`- ${boundaryFailureText}`,
|
|
4208
5596
|
"",
|
|
4209
5597
|
"## Debugging",
|
|
4210
5598
|
"",
|
|
@@ -4227,12 +5615,14 @@ function publicDocs(application, options) {
|
|
|
4227
5615
|
const title = toTitle(application.name);
|
|
4228
5616
|
const suiteLabel = options.audience === "personal" ? "personal" : "business";
|
|
4229
5617
|
const tenantLabel = options.tenancy === "household" ? "household" : "organization";
|
|
5618
|
+
const apiRouteLabel = options.tenancy === "household" ? "authenticated personal and household routes" : "authenticated workspace routes";
|
|
5619
|
+
const dataModelFailure = options.tenancy === "household" ? "household-scoped data models are not implemented until domain work begins" : "organization-scoped data models are not implemented until domain work begins";
|
|
4230
5620
|
return [
|
|
4231
5621
|
"---",
|
|
4232
5622
|
`title: "${escapeFrontmatter(title)}"`,
|
|
4233
5623
|
`summary: "Public overview of ${escapeFrontmatter(title)} in the Topolo application suite."`,
|
|
4234
5624
|
'owners: ["platform-engineering"]',
|
|
4235
|
-
'status: "
|
|
5625
|
+
'status: "draft"',
|
|
4236
5626
|
`last_verified: "${CURRENT_VERIFICATION_DATE}"`,
|
|
4237
5627
|
"source_repos:",
|
|
4238
5628
|
` - "PlatformApplications/${application.appDir}"`,
|
|
@@ -4262,7 +5652,7 @@ function publicDocs(application, options) {
|
|
|
4262
5652
|
"",
|
|
4263
5653
|
"## API Reference",
|
|
4264
5654
|
"",
|
|
4265
|
-
options.api ?
|
|
5655
|
+
options.api ? `The generated API baseline exposes ${apiRouteLabel} under \`/api/*\` and validates requests through Topolo Auth.` : "This generated baseline is browser-only and does not expose a callable API service.",
|
|
4266
5656
|
"",
|
|
4267
5657
|
"## Auth and Permissions",
|
|
4268
5658
|
"",
|
|
@@ -4274,12 +5664,12 @@ function publicDocs(application, options) {
|
|
|
4274
5664
|
"",
|
|
4275
5665
|
"## Deployments",
|
|
4276
5666
|
"",
|
|
4277
|
-
application.productionUrl ? `The planned production host is \`${application.productionUrl}\`.` : "The production host has not been assigned yet.",
|
|
5667
|
+
application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Staging mirrors the scaffold at \`https://${slug}.stg.topolo.us\`.` : "The production host has not been assigned yet.",
|
|
4278
5668
|
"",
|
|
4279
5669
|
"## Failure Modes",
|
|
4280
5670
|
"",
|
|
4281
5671
|
"- service registration and app routing can drift while the product is still planned",
|
|
4282
|
-
|
|
5672
|
+
`- ${dataModelFailure}`,
|
|
4283
5673
|
"",
|
|
4284
5674
|
"## Debugging",
|
|
4285
5675
|
"",
|
|
@@ -4288,7 +5678,7 @@ function publicDocs(application, options) {
|
|
|
4288
5678
|
"",
|
|
4289
5679
|
"## Change Log / Verification",
|
|
4290
5680
|
"",
|
|
4291
|
-
`- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from
|
|
5681
|
+
`- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from \`${options.portfolio ? "topolo platform apps scaffold" : "topolo apps scaffold"}\`.`,
|
|
4292
5682
|
""
|
|
4293
5683
|
].join("\n");
|
|
4294
5684
|
}
|
|
@@ -4297,8 +5687,17 @@ function serviceManifestJson(application, options) {
|
|
|
4297
5687
|
const title = toTitle(application.name);
|
|
4298
5688
|
const permissionPrefix = application.id.replace(/[^a-z0-9_]/g, "_");
|
|
4299
5689
|
const resourceLabel = options.tenancy === "household" ? "household" : "workspace";
|
|
5690
|
+
const launcherSettings = options.tenancy === "household" ? {
|
|
5691
|
+
settings: {
|
|
5692
|
+
launcher: {
|
|
5693
|
+
supported_contexts: ["personal"],
|
|
5694
|
+
household_capable: true
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
} : {};
|
|
4300
5698
|
return `${json({
|
|
4301
5699
|
serviceId: application.serviceId,
|
|
5700
|
+
...launcherSettings,
|
|
4302
5701
|
systems: [
|
|
4303
5702
|
{
|
|
4304
5703
|
systemId: `topolo-${slug}`,
|
|
@@ -4449,6 +5848,12 @@ function normalizeApplicationId(application) {
|
|
|
4449
5848
|
"Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
|
|
4450
5849
|
);
|
|
4451
5850
|
}
|
|
5851
|
+
function normalizeFirstPartyApplicationId(applicationId) {
|
|
5852
|
+
if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
|
|
5853
|
+
return applicationId.slice("topolo_".length);
|
|
5854
|
+
}
|
|
5855
|
+
return applicationId;
|
|
5856
|
+
}
|
|
4452
5857
|
function toSlug(value) {
|
|
4453
5858
|
return value.replace(/_/g, "-");
|
|
4454
5859
|
}
|
|
@@ -4461,9 +5866,9 @@ function toTitle(value) {
|
|
|
4461
5866
|
function json(value) {
|
|
4462
5867
|
return JSON.stringify(value, null, 2);
|
|
4463
5868
|
}
|
|
4464
|
-
async function pathExists(
|
|
5869
|
+
async function pathExists(path4) {
|
|
4465
5870
|
try {
|
|
4466
|
-
await access(
|
|
5871
|
+
await access(path4, constants.F_OK);
|
|
4467
5872
|
return true;
|
|
4468
5873
|
} catch {
|
|
4469
5874
|
return false;
|
|
@@ -4491,7 +5896,6 @@ function escapeFrontmatter(value) {
|
|
|
4491
5896
|
}
|
|
4492
5897
|
|
|
4493
5898
|
// src/commands/app-scaffold-provision.ts
|
|
4494
|
-
var DEFAULT_DEVELOPERS_ORIGIN = "https://developers.topolo.app";
|
|
4495
5899
|
var DEVELOPERS_SERVICE_ID = "svc_topolo_developers";
|
|
4496
5900
|
async function provisionApplicationScaffold(payload) {
|
|
4497
5901
|
if (!payload.application.productionUrl) {
|
|
@@ -4504,7 +5908,8 @@ async function provisionApplicationScaffold(payload) {
|
|
|
4504
5908
|
"Platform scaffold provisioning requires a service id. Re-run without --no-api or pass --service."
|
|
4505
5909
|
);
|
|
4506
5910
|
}
|
|
4507
|
-
const
|
|
5911
|
+
const environment = selectedEnvironment();
|
|
5912
|
+
const credential = await refreshCredentialIfNeeded(environment);
|
|
4508
5913
|
if (!credential) {
|
|
4509
5914
|
throw new Error(
|
|
4510
5915
|
"topolo apps scaffold --provision requires an authenticated Topolo session. Run `topolo auth login` first."
|
|
@@ -4518,15 +5923,16 @@ async function provisionApplicationScaffold(payload) {
|
|
|
4518
5923
|
}
|
|
4519
5924
|
const identity = await client.client.introspect();
|
|
4520
5925
|
const config = await readConfig();
|
|
4521
|
-
const
|
|
4522
|
-
const developersOrigin = sanitizeOrigin(serviceUrls
|
|
5926
|
+
const profile = resolvedProfileConfig(config, environment);
|
|
5927
|
+
const developersOrigin = sanitizeOrigin(profile.serviceUrls?.developers ?? "https://developers.topolo.app");
|
|
5928
|
+
assertServiceUrlForEnvironment("developers", developersOrigin, environment);
|
|
4523
5929
|
const endpoint = payload.target === "platform" ? "/api/developer-console/admin/first-party-app-scaffolds" : "/api/developer-console/apps/scaffold";
|
|
4524
5930
|
const headers = new Headers({
|
|
4525
5931
|
"Content-Type": "application/json",
|
|
4526
5932
|
"X-Service-ID": DEVELOPERS_SERVICE_ID,
|
|
4527
5933
|
"X-Topolo-Client": `topolo-cli/${CLI_VERSION}`
|
|
4528
5934
|
});
|
|
4529
|
-
const agentName = resolveAgentName(
|
|
5935
|
+
const agentName = resolveAgentName(profile.agentName);
|
|
4530
5936
|
if (agentName) {
|
|
4531
5937
|
headers.set("X-Topolo-Agent", agentName);
|
|
4532
5938
|
}
|
|
@@ -4694,6 +6100,17 @@ function registerAppsCommand(program2) {
|
|
|
4694
6100
|
fail(err, mode);
|
|
4695
6101
|
}
|
|
4696
6102
|
});
|
|
6103
|
+
apps.command("record-evidence <application>").description(
|
|
6104
|
+
"Stamp <repo>/.topolo/evidence.json with the latest CI gate results so the audit can flip verification-gates / observability-and-audit / canonical-docs from partial to met when the recorded run is fresh (< 24h)."
|
|
6105
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action((application, opts) => {
|
|
6106
|
+
const mode = resolveOutputMode(opts.json);
|
|
6107
|
+
try {
|
|
6108
|
+
const payload = recordApplicationEvidence(application, opts);
|
|
6109
|
+
emit(payload, mode, () => formatEvidence(payload));
|
|
6110
|
+
} catch (err) {
|
|
6111
|
+
fail(err, mode);
|
|
6112
|
+
}
|
|
6113
|
+
});
|
|
4697
6114
|
apps.command("scaffold <application>").description(
|
|
4698
6115
|
"Generate a third-party Topolo application scaffold for business or personal/household use."
|
|
4699
6116
|
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under PlatformApplications").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
|
|
@@ -4783,6 +6200,78 @@ function applicationAuditPayload(application, options = {}) {
|
|
|
4783
6200
|
if (!options.local) return report;
|
|
4784
6201
|
return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
|
|
4785
6202
|
}
|
|
6203
|
+
function recordApplicationEvidence(application, options = {}) {
|
|
6204
|
+
const appId = resolveApplication(application).id;
|
|
6205
|
+
const root = path2.resolve(options.appRoot ?? process.cwd());
|
|
6206
|
+
if (!existsSync3(root)) {
|
|
6207
|
+
throw new Error(`Application root does not exist: ${root}`);
|
|
6208
|
+
}
|
|
6209
|
+
let commit = options.commit;
|
|
6210
|
+
if (!commit) {
|
|
6211
|
+
try {
|
|
6212
|
+
commit = execSync("git rev-parse HEAD", { cwd: root, stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || void 0;
|
|
6213
|
+
} catch {
|
|
6214
|
+
commit = void 0;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
const evidenceDir = path2.join(root, ".topolo");
|
|
6218
|
+
const evidencePath = path2.join(evidenceDir, "evidence.json");
|
|
6219
|
+
let before = null;
|
|
6220
|
+
if (existsSync3(evidencePath)) {
|
|
6221
|
+
try {
|
|
6222
|
+
const raw = JSON.parse(readFileSync2(evidencePath, "utf8"));
|
|
6223
|
+
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
6224
|
+
before = raw;
|
|
6225
|
+
}
|
|
6226
|
+
} catch {
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6230
|
+
const after = before ? { ...before } : { version: 1, appId };
|
|
6231
|
+
after.version = 1;
|
|
6232
|
+
after.appId = appId;
|
|
6233
|
+
const updatedFields = [];
|
|
6234
|
+
const stamp = (ok) => ({ timestamp: now, ok, ...commit ? { commit } : {} });
|
|
6235
|
+
if (options.build) {
|
|
6236
|
+
after.lastBuild = stamp(true);
|
|
6237
|
+
updatedFields.push("lastBuild");
|
|
6238
|
+
}
|
|
6239
|
+
if (options.typecheck) {
|
|
6240
|
+
after.lastTypecheck = stamp(true);
|
|
6241
|
+
updatedFields.push("lastTypecheck");
|
|
6242
|
+
}
|
|
6243
|
+
if (options.test) {
|
|
6244
|
+
after.lastTest = stamp(true);
|
|
6245
|
+
updatedFields.push("lastTest");
|
|
6246
|
+
}
|
|
6247
|
+
if (options.docsValidate) {
|
|
6248
|
+
after.lastDocsValidate = { timestamp: now, ok: true };
|
|
6249
|
+
updatedFields.push("lastDocsValidate");
|
|
6250
|
+
}
|
|
6251
|
+
if (options.smoke) {
|
|
6252
|
+
after.lastSmoke = { timestamp: now, ok: true, url: options.smoke };
|
|
6253
|
+
updatedFields.push("lastSmoke");
|
|
6254
|
+
}
|
|
6255
|
+
if (updatedFields.length === 0) {
|
|
6256
|
+
throw new Error(
|
|
6257
|
+
"record-evidence: pass at least one of --build, --typecheck, --test, --docs-validate, --smoke <url>"
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
6260
|
+
mkdirSync(evidenceDir, { recursive: true });
|
|
6261
|
+
writeFileSync(evidencePath, JSON.stringify(after, null, 2) + "\n");
|
|
6262
|
+
return { appId, evidencePath, before, after, updatedFields };
|
|
6263
|
+
}
|
|
6264
|
+
function formatEvidence(result) {
|
|
6265
|
+
const lines = [];
|
|
6266
|
+
lines.push(`Stamped ${result.evidencePath}`);
|
|
6267
|
+
lines.push(` app: ${result.appId}`);
|
|
6268
|
+
for (const field of result.updatedFields) {
|
|
6269
|
+
const stamp = result.after[field];
|
|
6270
|
+
if (!stamp) continue;
|
|
6271
|
+
lines.push(` ${field}: ok at ${stamp.timestamp}${stamp.commit ? ` (${stamp.commit.slice(0, 7)})` : ""}`);
|
|
6272
|
+
}
|
|
6273
|
+
return lines.join("\n");
|
|
6274
|
+
}
|
|
4786
6275
|
function evaluateApplicationAuditGate(payload, failOn) {
|
|
4787
6276
|
const threshold = normalizeFailOn(failOn);
|
|
4788
6277
|
const counts = {
|
|
@@ -5006,6 +6495,8 @@ function statusesAtOrWorse(threshold) {
|
|
|
5006
6495
|
}
|
|
5007
6496
|
|
|
5008
6497
|
// src/commands/platform.ts
|
|
6498
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
6499
|
+
import path3 from "path";
|
|
5009
6500
|
function registerPlatformCommand(program2) {
|
|
5010
6501
|
const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
|
|
5011
6502
|
const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
|
|
@@ -5017,6 +6508,9 @@ function registerPlatformCommand(program2) {
|
|
|
5017
6508
|
).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
|
|
5018
6509
|
"--provision",
|
|
5019
6510
|
"Provision the generated first-party app into Topolo Developers and Auth using the authenticated Topolo admin session"
|
|
6511
|
+
).option(
|
|
6512
|
+
"--no-provision",
|
|
6513
|
+
"Skip live Developers/Auth registration when writing a first-party scaffold"
|
|
5020
6514
|
).option("--workflow-only", "Output only the generated GitHub Actions workflow in human mode").action(async (application, opts) => {
|
|
5021
6515
|
const mode = opts.json === false || process.argv.includes("--no-json") ? "pretty" : resolveOutputMode(opts.json);
|
|
5022
6516
|
try {
|
|
@@ -5041,11 +6535,13 @@ function registerPlatformCommand(program2) {
|
|
|
5041
6535
|
if (opts.workflowOnly && !payload.ciWorkflow) {
|
|
5042
6536
|
throw new Error("--workflow-only is only available for first-party scaffolds.");
|
|
5043
6537
|
}
|
|
5044
|
-
|
|
6538
|
+
const provisionRequested = process.argv.includes("--provision");
|
|
6539
|
+
const shouldProvision = Boolean(opts.write) && opts.provision !== false;
|
|
6540
|
+
if (provisionRequested && !opts.write) {
|
|
5045
6541
|
throw new Error("--provision requires --write so remote first-party records match written scaffold files.");
|
|
5046
6542
|
}
|
|
5047
6543
|
const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
|
|
5048
|
-
const provisionResult =
|
|
6544
|
+
const provisionResult = shouldProvision ? await provisionApplicationScaffold(payload) : null;
|
|
5049
6545
|
const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : {
|
|
5050
6546
|
...payload,
|
|
5051
6547
|
...writeResult ? { write: writeResult } : {},
|
|
@@ -5060,6 +6556,95 @@ function registerPlatformCommand(program2) {
|
|
|
5060
6556
|
fail(err, mode);
|
|
5061
6557
|
}
|
|
5062
6558
|
});
|
|
6559
|
+
const guard = platform3.command("guard").description("Run first-party platform guardrails");
|
|
6560
|
+
guard.command("staging-urls [root]").description("Fail when staging runtime source contains production Topolo URLs").option("--json", "Output JSON").option("--allow <pattern...>", "Additional regex allowlist patterns").action((root = process.cwd(), opts) => {
|
|
6561
|
+
const mode = opts.json ? "json" : "pretty";
|
|
6562
|
+
const result = scanStagingProductionUrls(root, opts.allow ?? []);
|
|
6563
|
+
if (mode === "json") {
|
|
6564
|
+
emit(result, "json", () => "");
|
|
6565
|
+
} else if (result.violations.length === 0) {
|
|
6566
|
+
emit(null, "pretty", () => `No staging production URL violations found in ${result.root}.`);
|
|
6567
|
+
} else {
|
|
6568
|
+
emit(
|
|
6569
|
+
null,
|
|
6570
|
+
"pretty",
|
|
6571
|
+
() => [
|
|
6572
|
+
`Found ${result.violations.length} staging production URL violation(s) in ${result.root}:`,
|
|
6573
|
+
...result.violations.map(
|
|
6574
|
+
(violation) => `- ${violation.file}:${violation.line} ${violation.url}`
|
|
6575
|
+
)
|
|
6576
|
+
].join("\n")
|
|
6577
|
+
);
|
|
6578
|
+
}
|
|
6579
|
+
if (result.violations.length > 0) process.exitCode = 1;
|
|
6580
|
+
});
|
|
6581
|
+
}
|
|
6582
|
+
var PRODUCTION_TOPOLO_URL_PATTERN = /(https?:\/\/[^\s'"`)>}]*topolo\.(?:app|io)[^\s'"`)<}]*)|(?:[a-z0-9-]+\.topolo\.app)/gi;
|
|
6583
|
+
var STAGING_TOPOLO_URL_PATTERN = /\.stg\.topolo\.us\b/;
|
|
6584
|
+
var RUNTIME_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".html", ".js", ".jsx", ".json", ".jsonc", ".mjs", ".toml", ".ts", ".tsx"]);
|
|
6585
|
+
var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
|
|
6586
|
+
".git",
|
|
6587
|
+
".nexus-widget-deploy",
|
|
6588
|
+
".next",
|
|
6589
|
+
".playwright-cli",
|
|
6590
|
+
".playwright-mcp",
|
|
6591
|
+
".wrangler",
|
|
6592
|
+
".worktrees",
|
|
6593
|
+
"_staging-clones",
|
|
6594
|
+
"build",
|
|
6595
|
+
"coverage",
|
|
6596
|
+
"dist",
|
|
6597
|
+
"node_modules",
|
|
6598
|
+
"out",
|
|
6599
|
+
"output"
|
|
6600
|
+
]);
|
|
6601
|
+
function scanStagingProductionUrls(rootInput, allowPatterns) {
|
|
6602
|
+
const root = path3.resolve(rootInput);
|
|
6603
|
+
const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
|
|
6604
|
+
const violations = [];
|
|
6605
|
+
function shouldSkipFile(filePath) {
|
|
6606
|
+
const basename = path3.basename(filePath);
|
|
6607
|
+
const relativePath = path3.relative(root, filePath);
|
|
6608
|
+
if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
|
|
6609
|
+
if (basename === "topolo.cloudcontrol.json") return true;
|
|
6610
|
+
if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
|
|
6611
|
+
if (basename === "consumer-graph.json") return true;
|
|
6612
|
+
if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename)) return true;
|
|
6613
|
+
if (basename.endsWith(".generated.ts")) return true;
|
|
6614
|
+
if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
|
|
6615
|
+
if (basename === ".env.production") return true;
|
|
6616
|
+
if (relativePath === path3.join("src", "environment.ts") || relativePath === path3.join("src", "environmentUrls.ts") || relativePath.endsWith(path3.join("src", "environment.ts")) || relativePath.endsWith(path3.join("src", "lib", "environment.ts")) || relativePath === path3.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
|
|
6617
|
+
if (relativePath.includes(`${path3.sep}test${path3.sep}`) || relativePath.includes(`${path3.sep}tests${path3.sep}`) || relativePath.includes(`${path3.sep}e2e${path3.sep}`)) return true;
|
|
6618
|
+
if (relativePath.startsWith(`scripts${path3.sep}`) || relativePath.includes(`${path3.sep}scripts${path3.sep}`)) return true;
|
|
6619
|
+
return !RUNTIME_EXTENSIONS.has(path3.extname(filePath));
|
|
6620
|
+
}
|
|
6621
|
+
function scanFile(filePath) {
|
|
6622
|
+
if (shouldSkipFile(filePath)) return;
|
|
6623
|
+
const relativePath = path3.relative(root, filePath);
|
|
6624
|
+
const content = readFileSync3(filePath, "utf8");
|
|
6625
|
+
const lines = content.split(/\r?\n/);
|
|
6626
|
+
lines.forEach((line, index) => {
|
|
6627
|
+
const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
|
|
6628
|
+
for (const url of new Set(matches)) {
|
|
6629
|
+
const allowed = STAGING_TOPOLO_URL_PATTERN.test(line) || line.includes("PRODUCTION_") || line.includes("hostname ===") || line.includes(".split(") && line.includes(".join(") || allowlist.some((pattern) => pattern.test(`${relativePath}:${index + 1}:${line}`));
|
|
6630
|
+
if (!allowed) {
|
|
6631
|
+
violations.push({ file: relativePath, line: index + 1, url, text: line.trim() });
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6634
|
+
});
|
|
6635
|
+
}
|
|
6636
|
+
function walk(dir) {
|
|
6637
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
6638
|
+
const fullPath = path3.join(dir, entry.name);
|
|
6639
|
+
if (entry.isDirectory()) {
|
|
6640
|
+
if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
|
|
6641
|
+
continue;
|
|
6642
|
+
}
|
|
6643
|
+
if (entry.isFile() && statSync2(fullPath).size < 2e6) scanFile(fullPath);
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
walk(root);
|
|
6647
|
+
return { root, violations };
|
|
5063
6648
|
}
|
|
5064
6649
|
function resolvePlatformPortfolio(value) {
|
|
5065
6650
|
if (!value) return void 0;
|
|
@@ -5104,45 +6689,281 @@ function registerServicesCommand(program2) {
|
|
|
5104
6689
|
program2.command("services").description("List every Topolo service the SDK can route to").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action(async (opts) => {
|
|
5105
6690
|
const mode = resolveOutputMode(opts.json);
|
|
5106
6691
|
try {
|
|
6692
|
+
const environment = selectedEnvironment();
|
|
5107
6693
|
const config = await readConfig();
|
|
5108
|
-
const rows = buildRows(
|
|
5109
|
-
emit({ services: rows }, mode, () => formatRows2(rows));
|
|
6694
|
+
const rows = buildRows(config.serviceUrls, environment);
|
|
6695
|
+
emit({ environment, services: rows }, mode, () => formatRows2(rows, environment));
|
|
5110
6696
|
} catch (err) {
|
|
5111
6697
|
fail(err, mode);
|
|
5112
6698
|
}
|
|
5113
6699
|
});
|
|
5114
6700
|
}
|
|
5115
|
-
function buildRows(overrides) {
|
|
6701
|
+
function buildRows(overrides, environment = selectedEnvironment()) {
|
|
6702
|
+
const resolvedOverrides = resolveServiceUrls(overrides, environment);
|
|
5116
6703
|
return Object.keys(DEFAULT_SERVICE_URLS).sort().map((id) => {
|
|
5117
6704
|
const defaultUrl = DEFAULT_SERVICE_URLS[id];
|
|
5118
|
-
const effectiveUrl = resolveServiceUrl(id,
|
|
6705
|
+
const effectiveUrl = resolveServiceUrl(id, resolvedOverrides);
|
|
5119
6706
|
return {
|
|
5120
6707
|
id,
|
|
5121
6708
|
defaultUrl,
|
|
5122
6709
|
effectiveUrl,
|
|
5123
|
-
overridden: defaultUrl !== effectiveUrl
|
|
6710
|
+
overridden: defaultUrl !== effectiveUrl,
|
|
6711
|
+
environmentSafe: environment !== "staging" || isStagingUrl(effectiveUrl)
|
|
5124
6712
|
};
|
|
5125
6713
|
});
|
|
5126
6714
|
}
|
|
5127
|
-
function formatRows2(rows) {
|
|
6715
|
+
function formatRows2(rows, environment = selectedEnvironment()) {
|
|
5128
6716
|
const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
|
|
5129
6717
|
const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
|
|
5130
6718
|
const lines = [];
|
|
5131
|
-
lines.push(
|
|
5132
|
-
lines.push(
|
|
6719
|
+
lines.push(`Environment: ${environment}`);
|
|
6720
|
+
lines.push("");
|
|
6721
|
+
lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY`);
|
|
6722
|
+
lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} -------- -----`);
|
|
5133
6723
|
for (const r of rows) {
|
|
5134
6724
|
lines.push(
|
|
5135
|
-
`${r.id.padEnd(idWidth)} ${r.effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : ""}`
|
|
6725
|
+
`${r.id.padEnd(idWidth)} ${r.effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : "".padEnd(8)} ${r.environmentSafe ? "yes" : "no"}`
|
|
5136
6726
|
);
|
|
5137
6727
|
}
|
|
5138
6728
|
return lines.join("\n");
|
|
5139
6729
|
}
|
|
6730
|
+
function isStagingUrl(value) {
|
|
6731
|
+
const url = value.toLowerCase();
|
|
6732
|
+
return url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging");
|
|
6733
|
+
}
|
|
6734
|
+
|
|
6735
|
+
// src/commands/seed.ts
|
|
6736
|
+
var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
|
|
6737
|
+
function seedBaseUrl() {
|
|
6738
|
+
return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
|
|
6739
|
+
}
|
|
6740
|
+
async function authedFetch(path4, init = {}) {
|
|
6741
|
+
const credential = await refreshCredentialIfNeeded("staging");
|
|
6742
|
+
if (!credential) {
|
|
6743
|
+
throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
|
|
6744
|
+
}
|
|
6745
|
+
const headers = new Headers(init.headers || {});
|
|
6746
|
+
if (credential.kind === "api_key") headers.set("X-Api-Key", credential.apiKey);
|
|
6747
|
+
else headers.set("Authorization", `Bearer ${credential.accessToken}`);
|
|
6748
|
+
headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
|
|
6749
|
+
if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
6750
|
+
const response = await fetch(`${seedBaseUrl()}${path4}`, { ...init, headers });
|
|
6751
|
+
const text = await response.text();
|
|
6752
|
+
let body;
|
|
6753
|
+
try {
|
|
6754
|
+
body = text ? JSON.parse(text) : {};
|
|
6755
|
+
} catch {
|
|
6756
|
+
body = { raw: text };
|
|
6757
|
+
}
|
|
6758
|
+
if (!response.ok) {
|
|
6759
|
+
throw new Error(`TopoloSeed ${response.status}: ${typeof body === "object" && body && "error" in body ? body.error : text}`);
|
|
6760
|
+
}
|
|
6761
|
+
return body;
|
|
6762
|
+
}
|
|
6763
|
+
function registerSeedCommands(program2) {
|
|
6764
|
+
const seed = program2.command("seed").description("Drive TopoloSeed (staging only): list packs, queue runs, check status, reset.");
|
|
6765
|
+
seed.command("list").description("List seed packs from the staging catalog").option("--json", "Output JSON", true).option("--no-json", "Output table").option("--scope <scope>", "Filter by scope (system | application | feature)").option("--app <appId>", "Filter by app id (e.g. topolo-auth)").action(async (opts) => {
|
|
6766
|
+
const mode = resolveOutputMode(opts.json);
|
|
6767
|
+
try {
|
|
6768
|
+
const params = new URLSearchParams();
|
|
6769
|
+
if (opts.scope) params.set("scope", opts.scope);
|
|
6770
|
+
if (opts.app) params.set("app_id", opts.app);
|
|
6771
|
+
const qs = params.toString();
|
|
6772
|
+
const body = await authedFetch(`/api/seed-packs${qs ? `?${qs}` : ""}`);
|
|
6773
|
+
emit({ packs: body.seed_packs }, mode, () => formatPackTable(body.seed_packs));
|
|
6774
|
+
} catch (err) {
|
|
6775
|
+
fail(err, mode);
|
|
6776
|
+
}
|
|
6777
|
+
});
|
|
6778
|
+
seed.command("coverage").description("Show per-app coverage from the live view").option("--json", "Output JSON", true).option("--no-json", "Output table").action(async (opts) => {
|
|
6779
|
+
const mode = resolveOutputMode(opts.json);
|
|
6780
|
+
try {
|
|
6781
|
+
const body = await authedFetch("/api/coverage");
|
|
6782
|
+
emit({ coverage: body.coverage }, mode, () => formatCoverageTable(body.coverage));
|
|
6783
|
+
} catch (err) {
|
|
6784
|
+
fail(err, mode);
|
|
6785
|
+
}
|
|
6786
|
+
});
|
|
6787
|
+
seed.command("run <packId>").description("Queue a seed run; the cron executor picks it up within ~1 minute").option("--json", "Output JSON", true).option("--no-json", "Pretty print").action(async (packId, opts) => {
|
|
6788
|
+
const mode = resolveOutputMode(opts.json);
|
|
6789
|
+
try {
|
|
6790
|
+
const body = await authedFetch("/api/runs", { method: "POST", body: JSON.stringify({ pack_id: packId }) });
|
|
6791
|
+
emit({ run: body.run }, mode, () => `Queued ${body.run?.run_id || "unknown"}`);
|
|
6792
|
+
} catch (err) {
|
|
6793
|
+
fail(err, mode);
|
|
6794
|
+
}
|
|
6795
|
+
});
|
|
6796
|
+
seed.command("status [runId]").description("Show recent runs, or a specific run if runId is provided").option("--json", "Output JSON", true).option("--no-json", "Output table").action(async (runId, opts) => {
|
|
6797
|
+
const mode = resolveOutputMode(opts.json);
|
|
6798
|
+
try {
|
|
6799
|
+
if (runId) {
|
|
6800
|
+
const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}`);
|
|
6801
|
+
emit({ run: body.run }, mode, () => formatRunDetail(body.run));
|
|
6802
|
+
} else {
|
|
6803
|
+
const body = await authedFetch("/api/runs");
|
|
6804
|
+
emit({ runs: body.runs }, mode, () => formatRunsTable(body.runs));
|
|
6805
|
+
}
|
|
6806
|
+
} catch (err) {
|
|
6807
|
+
fail(err, mode);
|
|
6808
|
+
}
|
|
6809
|
+
});
|
|
6810
|
+
seed.command("cancel <runId>").description("Cancel a queued or running seed run").option("--json", "Output JSON", true).action(async (runId, opts) => {
|
|
6811
|
+
const mode = resolveOutputMode(opts.json);
|
|
6812
|
+
try {
|
|
6813
|
+
const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
6814
|
+
emit(body, mode, () => `Cancelled ${runId}`);
|
|
6815
|
+
} catch (err) {
|
|
6816
|
+
fail(err, mode);
|
|
6817
|
+
}
|
|
6818
|
+
});
|
|
6819
|
+
seed.command("retry <runId>").description("Retry a failed or cancelled run (re-queues failed steps)").option("--json", "Output JSON", true).action(async (runId, opts) => {
|
|
6820
|
+
const mode = resolveOutputMode(opts.json);
|
|
6821
|
+
try {
|
|
6822
|
+
const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}/retry`, { method: "POST" });
|
|
6823
|
+
emit(body, mode, () => `Retried ${runId}`);
|
|
6824
|
+
} catch (err) {
|
|
6825
|
+
fail(err, mode);
|
|
6826
|
+
}
|
|
6827
|
+
});
|
|
6828
|
+
const reset = seed.command("reset").description("Inspect or execute reset plans");
|
|
6829
|
+
reset.command("preview").description("Create a reset plan and return its confirmation phrase").option("--type <type>", "Reset type (soft_activity | app | cross_app)", "cross_app").option("--app <appId>", "Limit to a single target app").option("--feature <featureId>", "Limit to a feature").option("--no-preserve-identities", "Allow auth identities to be deleted").option("--json", "Output JSON", true).action(async (opts) => {
|
|
6830
|
+
const mode = resolveOutputMode(opts.json);
|
|
6831
|
+
try {
|
|
6832
|
+
const body = await authedFetch("/api/resets/preview", {
|
|
6833
|
+
method: "POST",
|
|
6834
|
+
body: JSON.stringify({
|
|
6835
|
+
reset_type: opts.type,
|
|
6836
|
+
target_scope: opts.app ? "application" : "system",
|
|
6837
|
+
target_app_id: opts.app || null,
|
|
6838
|
+
target_feature_id: opts.feature || null,
|
|
6839
|
+
preserve_auth_identities: opts.preserveIdentities !== false
|
|
6840
|
+
})
|
|
6841
|
+
});
|
|
6842
|
+
emit({ reset_plan: body.reset_plan }, mode, () => formatPlanPreview(body.reset_plan));
|
|
6843
|
+
} catch (err) {
|
|
6844
|
+
fail(err, mode);
|
|
6845
|
+
}
|
|
6846
|
+
});
|
|
6847
|
+
reset.command("execute <planId> <confirmation>").description("Execute a previously previewed reset plan (confirmation = the phrase from preview)").option("--dry-run", "Dispatch with dry_run=true to count rows without deleting").option("--json", "Output JSON", true).action(async (planId, confirmation, opts) => {
|
|
6848
|
+
const mode = resolveOutputMode(opts.json);
|
|
6849
|
+
try {
|
|
6850
|
+
const body = await authedFetch(`/api/resets/${encodeURIComponent(planId)}/execute`, {
|
|
6851
|
+
method: "POST",
|
|
6852
|
+
body: JSON.stringify({ confirmation, dry_run: opts.dryRun === true })
|
|
6853
|
+
});
|
|
6854
|
+
emit({ reset_plan: body.reset_plan }, mode, () => formatPlanResult(body.reset_plan));
|
|
6855
|
+
} catch (err) {
|
|
6856
|
+
fail(err, mode);
|
|
6857
|
+
}
|
|
6858
|
+
});
|
|
6859
|
+
seed.command("manifests").description("Show the cached __seed/manifest for each registered app").option("--refresh", "Force re-fetch all manifests now").option("--json", "Output JSON", true).action(async (opts) => {
|
|
6860
|
+
const mode = resolveOutputMode(opts.json);
|
|
6861
|
+
try {
|
|
6862
|
+
if (opts.refresh) {
|
|
6863
|
+
const refreshed = await authedFetch("/api/manifests/refresh", { method: "POST", body: JSON.stringify({}) });
|
|
6864
|
+
emit(refreshed, mode, () => "Refreshed all manifests");
|
|
6865
|
+
return;
|
|
6866
|
+
}
|
|
6867
|
+
const body = await authedFetch("/api/manifests");
|
|
6868
|
+
emit({ manifests: body.manifests }, mode, () => formatManifestsTable(body.manifests));
|
|
6869
|
+
} catch (err) {
|
|
6870
|
+
fail(err, mode);
|
|
6871
|
+
}
|
|
6872
|
+
});
|
|
6873
|
+
}
|
|
6874
|
+
function formatPackTable(packs) {
|
|
6875
|
+
if (packs.length === 0) return "(no packs)";
|
|
6876
|
+
const lines = [];
|
|
6877
|
+
lines.push("PACK_ID SCOPE APP NAME");
|
|
6878
|
+
for (const p of packs) {
|
|
6879
|
+
lines.push(`${String(p.pack_id || "").padEnd(32)} ${String(p.scope || "").padEnd(12)} ${String(p.app_id || "-").padEnd(19)} ${String(p.name || "")}`);
|
|
6880
|
+
}
|
|
6881
|
+
return lines.join("\n");
|
|
6882
|
+
}
|
|
6883
|
+
function formatCoverageTable(rows) {
|
|
6884
|
+
if (rows.length === 0) return "(no apps)";
|
|
6885
|
+
const lines = [];
|
|
6886
|
+
lines.push("APP COVERAGE D1 KV R2 QUEUES");
|
|
6887
|
+
for (const r of rows) {
|
|
6888
|
+
lines.push(`${String(r.app_id || "").padEnd(25)} ${String(r.coverage_status || "").padEnd(17)} ${String(r.d1_status || "").padEnd(13)} ${String(r.kv_status || "").padEnd(13)} ${String(r.r2_status || "").padEnd(13)} ${String(r.queue_status || "")}`);
|
|
6889
|
+
}
|
|
6890
|
+
return lines.join("\n");
|
|
6891
|
+
}
|
|
6892
|
+
function formatRunsTable(runs) {
|
|
6893
|
+
if (runs.length === 0) return "(no runs)";
|
|
6894
|
+
const lines = [];
|
|
6895
|
+
lines.push("RUN_ID PACK STATUS ATTEMPT");
|
|
6896
|
+
for (const r of runs) {
|
|
6897
|
+
lines.push(`${String(r.run_id || "").padEnd(40)} ${String(r.pack_id || "").padEnd(29)} ${String(r.status || "").padEnd(12)} ${String(r.attempt ?? 0)}`);
|
|
6898
|
+
}
|
|
6899
|
+
return lines.join("\n");
|
|
6900
|
+
}
|
|
6901
|
+
function formatRunDetail(run) {
|
|
6902
|
+
const steps = run.steps || [];
|
|
6903
|
+
const lines = [];
|
|
6904
|
+
lines.push(`run_id: ${run.run_id}`);
|
|
6905
|
+
lines.push(`pack: ${run.pack_id}`);
|
|
6906
|
+
lines.push(`status: ${run.status} (attempt ${run.attempt ?? 0})`);
|
|
6907
|
+
if (steps.length > 0) {
|
|
6908
|
+
lines.push("");
|
|
6909
|
+
lines.push("STEPS:");
|
|
6910
|
+
for (const s of steps) {
|
|
6911
|
+
lines.push(` [${s.status}] ${s.step_id} \u2192 ${s.app_id}${s.last_error ? ` (${s.last_error})` : ""}`);
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
return lines.join("\n");
|
|
6915
|
+
}
|
|
6916
|
+
function formatPlanPreview(plan) {
|
|
6917
|
+
return [
|
|
6918
|
+
`reset_plan_id: ${plan.reset_plan_id}`,
|
|
6919
|
+
`confirmation: ${plan.confirmation_phrase}`,
|
|
6920
|
+
`estimated: ${plan.estimated_rows} rows / ${plan.estimated_objects} objects`,
|
|
6921
|
+
"",
|
|
6922
|
+
"To execute:",
|
|
6923
|
+
` topolo seed reset execute ${plan.reset_plan_id} "${plan.confirmation_phrase}"`
|
|
6924
|
+
].join("\n");
|
|
6925
|
+
}
|
|
6926
|
+
function formatPlanResult(plan) {
|
|
6927
|
+
const results = plan.results || [];
|
|
6928
|
+
const lines = [`reset_plan_id: ${plan.reset_plan_id}`, `status: ${plan.status}`, `total_deleted: ${plan.total_deleted ?? 0}`];
|
|
6929
|
+
if (results.length > 0) {
|
|
6930
|
+
lines.push("");
|
|
6931
|
+
lines.push("PER-APP:");
|
|
6932
|
+
for (const r of results) {
|
|
6933
|
+
lines.push(` [${r.status}] ${r.app_id}: ${r.rows_deleted ?? 0} rows${r.error ? ` (${r.error})` : ""}`);
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
return lines.join("\n");
|
|
6937
|
+
}
|
|
6938
|
+
function formatManifestsTable(manifests) {
|
|
6939
|
+
if (manifests.length === 0) return "(no manifests cached)";
|
|
6940
|
+
const lines = [];
|
|
6941
|
+
lines.push("APP VERSION SOURCE FETCHED_AT ERROR");
|
|
6942
|
+
for (const m of manifests) {
|
|
6943
|
+
const fetched = m.fetched_at ? new Date(Number(m.fetched_at)).toISOString().replace("T", " ").slice(0, 19) : "";
|
|
6944
|
+
lines.push(`${String(m.app_id || "").padEnd(25)} ${String(m.contract_version ?? 0).padEnd(8)} ${String(m.source || "").padEnd(18)} ${fetched.padEnd(20)} ${String(m.fetch_error || "")}`);
|
|
6945
|
+
}
|
|
6946
|
+
return lines.join("\n");
|
|
6947
|
+
}
|
|
5140
6948
|
|
|
5141
6949
|
// src/index.ts
|
|
5142
6950
|
var program = new Command();
|
|
6951
|
+
var argv;
|
|
6952
|
+
try {
|
|
6953
|
+
argv = consumeEnvironmentArg(process.argv);
|
|
6954
|
+
} catch (err) {
|
|
6955
|
+
process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
6956
|
+
`);
|
|
6957
|
+
process.exit(1);
|
|
6958
|
+
}
|
|
5143
6959
|
program.name("topolo").description(
|
|
5144
6960
|
"Topolo CLI. Public commands are scoped to the authenticated organization or developer workspace, and first-party Topolo scaffolding lives under the internal `topolo platform` namespace."
|
|
5145
|
-
).version(CLI_VERSION);
|
|
6961
|
+
).version(CLI_VERSION).option("--env <environment>", "Topolo environment: production or staging");
|
|
6962
|
+
program.hook("preAction", (thisCommand) => {
|
|
6963
|
+
const opts = thisCommand.opts();
|
|
6964
|
+
if (!opts.env) return;
|
|
6965
|
+
process.env.TOPOLO_ENV = normalizeEnvironment(opts.env);
|
|
6966
|
+
});
|
|
5146
6967
|
registerAuthCommands(program);
|
|
5147
6968
|
registerWhoamiCommand(program);
|
|
5148
6969
|
registerCrmCommands(program);
|
|
@@ -5150,8 +6971,31 @@ registerApiCommand(program);
|
|
|
5150
6971
|
registerAppsCommand(program);
|
|
5151
6972
|
registerPlatformCommand(program);
|
|
5152
6973
|
registerServicesCommand(program);
|
|
5153
|
-
program
|
|
6974
|
+
registerSeedCommands(program);
|
|
6975
|
+
program.parseAsync(argv).catch((err) => {
|
|
5154
6976
|
process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
5155
6977
|
`);
|
|
5156
6978
|
process.exit(1);
|
|
5157
6979
|
});
|
|
6980
|
+
function consumeEnvironmentArg(argv2) {
|
|
6981
|
+
const next = argv2.slice(0, 2);
|
|
6982
|
+
for (let i = 2; i < argv2.length; i += 1) {
|
|
6983
|
+
const arg = argv2[i];
|
|
6984
|
+
if (!arg) continue;
|
|
6985
|
+
if (arg === "--env") {
|
|
6986
|
+
const value = argv2[i + 1];
|
|
6987
|
+
if (!value || value.startsWith("-")) {
|
|
6988
|
+
throw new Error("--env requires production or staging.");
|
|
6989
|
+
}
|
|
6990
|
+
process.env.TOPOLO_ENV = normalizeEnvironment(value);
|
|
6991
|
+
i += 1;
|
|
6992
|
+
continue;
|
|
6993
|
+
}
|
|
6994
|
+
if (arg.startsWith("--env=")) {
|
|
6995
|
+
process.env.TOPOLO_ENV = normalizeEnvironment(arg.slice("--env=".length));
|
|
6996
|
+
continue;
|
|
6997
|
+
}
|
|
6998
|
+
next.push(arg);
|
|
6999
|
+
}
|
|
7000
|
+
return next;
|
|
7001
|
+
}
|