@authrim/setup 0.1.140 → 0.1.142

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.
Files changed (122) hide show
  1. package/dist/__tests__/keys.test.js +73 -2
  2. package/dist/__tests__/keys.test.js.map +1 -1
  3. package/dist/__tests__/migrate.test.js +4 -4
  4. package/dist/__tests__/migrate.test.js.map +1 -1
  5. package/dist/__tests__/paths.test.js +163 -1
  6. package/dist/__tests__/paths.test.js.map +1 -1
  7. package/dist/__tests__/source-context.test.d.ts +2 -0
  8. package/dist/__tests__/source-context.test.d.ts.map +1 -0
  9. package/dist/__tests__/source-context.test.js +72 -0
  10. package/dist/__tests__/source-context.test.js.map +1 -0
  11. package/dist/cli/commands/deploy.d.ts.map +1 -1
  12. package/dist/cli/commands/deploy.js +65 -37
  13. package/dist/cli/commands/deploy.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +277 -198
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/core/admin.d.ts +6 -1
  18. package/dist/core/admin.d.ts.map +1 -1
  19. package/dist/core/admin.js +45 -20
  20. package/dist/core/admin.js.map +1 -1
  21. package/dist/core/cloudflare.d.ts +38 -1
  22. package/dist/core/cloudflare.d.ts.map +1 -1
  23. package/dist/core/cloudflare.js +729 -115
  24. package/dist/core/cloudflare.js.map +1 -1
  25. package/dist/core/config.d.ts +164 -34
  26. package/dist/core/config.d.ts.map +1 -1
  27. package/dist/core/config.js +72 -18
  28. package/dist/core/config.js.map +1 -1
  29. package/dist/core/deploy.d.ts +18 -0
  30. package/dist/core/deploy.d.ts.map +1 -1
  31. package/dist/core/deploy.js +126 -25
  32. package/dist/core/deploy.js.map +1 -1
  33. package/dist/core/keys.d.ts +20 -4
  34. package/dist/core/keys.d.ts.map +1 -1
  35. package/dist/core/keys.js +77 -17
  36. package/dist/core/keys.js.map +1 -1
  37. package/dist/core/login-ui-client.d.ts +42 -0
  38. package/dist/core/login-ui-client.d.ts.map +1 -0
  39. package/dist/core/login-ui-client.js +173 -0
  40. package/dist/core/login-ui-client.js.map +1 -0
  41. package/dist/core/migrate.d.ts +37 -0
  42. package/dist/core/migrate.d.ts.map +1 -1
  43. package/dist/core/migrate.js +92 -2
  44. package/dist/core/migrate.js.map +1 -1
  45. package/dist/core/paths.d.ts +78 -13
  46. package/dist/core/paths.d.ts.map +1 -1
  47. package/dist/core/paths.js +135 -17
  48. package/dist/core/paths.js.map +1 -1
  49. package/dist/core/source-context.d.ts +22 -0
  50. package/dist/core/source-context.d.ts.map +1 -0
  51. package/dist/core/source-context.js +46 -0
  52. package/dist/core/source-context.js.map +1 -0
  53. package/dist/core/tenant-mode.d.ts +4 -0
  54. package/dist/core/tenant-mode.d.ts.map +1 -0
  55. package/dist/core/tenant-mode.js +17 -0
  56. package/dist/core/tenant-mode.js.map +1 -0
  57. package/dist/core/ui-deployment.d.ts +21 -0
  58. package/dist/core/ui-deployment.d.ts.map +1 -0
  59. package/dist/core/ui-deployment.js +90 -0
  60. package/dist/core/ui-deployment.js.map +1 -0
  61. package/dist/core/ui-env.d.ts +28 -0
  62. package/dist/core/ui-env.d.ts.map +1 -1
  63. package/dist/core/ui-env.js +16 -0
  64. package/dist/core/ui-env.js.map +1 -1
  65. package/dist/core/url-config.d.ts +16 -0
  66. package/dist/core/url-config.d.ts.map +1 -0
  67. package/dist/core/url-config.js +46 -0
  68. package/dist/core/url-config.js.map +1 -0
  69. package/dist/core/wrangler.d.ts +50 -1
  70. package/dist/core/wrangler.d.ts.map +1 -1
  71. package/dist/core/wrangler.js +171 -57
  72. package/dist/core/wrangler.js.map +1 -1
  73. package/dist/i18n/locales/de.d.ts.map +1 -1
  74. package/dist/i18n/locales/de.js +38 -1
  75. package/dist/i18n/locales/de.js.map +1 -1
  76. package/dist/i18n/locales/en.d.ts.map +1 -1
  77. package/dist/i18n/locales/en.js +38 -1
  78. package/dist/i18n/locales/en.js.map +1 -1
  79. package/dist/i18n/locales/es.d.ts.map +1 -1
  80. package/dist/i18n/locales/es.js +38 -1
  81. package/dist/i18n/locales/es.js.map +1 -1
  82. package/dist/i18n/locales/fr.d.ts.map +1 -1
  83. package/dist/i18n/locales/fr.js +38 -1
  84. package/dist/i18n/locales/fr.js.map +1 -1
  85. package/dist/i18n/locales/id.d.ts.map +1 -1
  86. package/dist/i18n/locales/id.js +38 -1
  87. package/dist/i18n/locales/id.js.map +1 -1
  88. package/dist/i18n/locales/ja.d.ts.map +1 -1
  89. package/dist/i18n/locales/ja.js +38 -1
  90. package/dist/i18n/locales/ja.js.map +1 -1
  91. package/dist/i18n/locales/ko.d.ts.map +1 -1
  92. package/dist/i18n/locales/ko.js +38 -1
  93. package/dist/i18n/locales/ko.js.map +1 -1
  94. package/dist/i18n/locales/pt.d.ts.map +1 -1
  95. package/dist/i18n/locales/pt.js +38 -1
  96. package/dist/i18n/locales/pt.js.map +1 -1
  97. package/dist/i18n/locales/ru.d.ts.map +1 -1
  98. package/dist/i18n/locales/ru.js +38 -1
  99. package/dist/i18n/locales/ru.js.map +1 -1
  100. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  101. package/dist/i18n/locales/zh-CN.js +38 -1
  102. package/dist/i18n/locales/zh-CN.js.map +1 -1
  103. package/dist/i18n/locales/zh-TW.d.ts.map +1 -1
  104. package/dist/i18n/locales/zh-TW.js +38 -1
  105. package/dist/i18n/locales/zh-TW.js.map +1 -1
  106. package/dist/i18n/types.d.ts +8 -0
  107. package/dist/i18n/types.d.ts.map +1 -1
  108. package/dist/index.d.ts +8 -1
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +46 -30
  111. package/dist/index.js.map +1 -1
  112. package/dist/web/api.d.ts.map +1 -1
  113. package/dist/web/api.js +243 -116
  114. package/dist/web/api.js.map +1 -1
  115. package/dist/web/ui.d.ts.map +1 -1
  116. package/dist/web/ui.js +513 -115
  117. package/dist/web/ui.js.map +1 -1
  118. package/migrations/000_fresh_schema.sql +229 -10
  119. package/migrations/admin/007_admin_role_inheritance.sql +32 -0
  120. package/migrations/admin/008_admin_rebac_definitions.sql +117 -0
  121. package/migrations/admin/009_optimize_admin_audit_indexes.sql +15 -0
  122. package/package.json +5 -5
@@ -109,15 +109,11 @@ export async function getAccountId() {
109
109
  }
110
110
  }
111
111
  /**
112
- * Get the workers.dev subdomain for the account
113
- * This is needed because workers.dev URLs are: {worker}.{subdomain}.workers.dev
112
+ * Get Cloudflare API token from wrangler config or environment variable.
113
+ * Searches platform-specific paths for OAuth token, falls back to CLOUDFLARE_API_TOKEN env var.
114
114
  */
115
- export async function getWorkersSubdomain() {
115
+ export async function getCloudflareApiToken() {
116
116
  try {
117
- const accountId = await getAccountId();
118
- if (!accountId)
119
- return null;
120
- // Read OAuth token from wrangler config
121
117
  const { readFile } = await import('node:fs/promises');
122
118
  const { homedir, platform } = await import('node:os');
123
119
  const { join } = await import('node:path');
@@ -126,23 +122,16 @@ export async function getWorkersSubdomain() {
126
122
  const home = homedir();
127
123
  const configPaths = [];
128
124
  if (platform() === 'darwin') {
129
- // macOS: ~/Library/Preferences/.wrangler/config/default.toml (XDG-compliant)
130
125
  configPaths.push(join(home, 'Library/Preferences/.wrangler/config/default.toml'));
131
- // macOS legacy fallback
132
126
  configPaths.push(join(home, '.wrangler/config/default.toml'));
133
127
  }
134
128
  else if (platform() === 'win32') {
135
- // Windows: Multiple possible locations
136
129
  const appData = process.env.APPDATA;
137
130
  if (appData) {
138
- // 1. XDG-compliant: %APPDATA%\xdg.config\.wrangler\config\default.toml (CORRECT PATH)
139
131
  configPaths.push(join(appData, 'xdg.config/.wrangler/config/default.toml'));
140
- // Also try without xdg.config prefix
141
132
  configPaths.push(join(appData, '.wrangler/config/default.toml'));
142
133
  }
143
- // 2. Legacy: %USERPROFILE%\.wrangler\config\default.toml
144
134
  configPaths.push(join(home, '.wrangler/config/default.toml'));
145
- // 3. %LOCALAPPDATA%
146
135
  const localAppData = process.env.LOCALAPPDATA;
147
136
  if (localAppData) {
148
137
  configPaths.push(join(localAppData, 'xdg.config/.wrangler/config/default.toml'));
@@ -150,13 +139,10 @@ export async function getWorkersSubdomain() {
150
139
  }
151
140
  }
152
141
  else {
153
- // Linux: XDG-compliant path
154
142
  const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(home, '.config');
155
143
  configPaths.push(join(xdgConfigHome, '.wrangler/config/default.toml'));
156
- // Linux legacy fallback
157
144
  configPaths.push(join(home, '.wrangler/config/default.toml'));
158
145
  }
159
- let oauthToken = null;
160
146
  for (const configPath of configPaths) {
161
147
  if (!existsSync(configPath))
162
148
  continue;
@@ -164,28 +150,39 @@ export async function getWorkersSubdomain() {
164
150
  const configContent = await readFile(configPath, 'utf-8');
165
151
  const tokenMatch = configContent.match(/oauth_token\s*=\s*"([^"]+)"/);
166
152
  if (tokenMatch?.[1]) {
167
- oauthToken = tokenMatch[1];
168
- break;
153
+ return { token: tokenMatch[1], source: 'oauth' };
169
154
  }
170
155
  }
171
156
  catch {
172
157
  // Continue to next path
173
158
  }
174
159
  }
175
- if (!oauthToken) {
176
- // Try using CLOUDFLARE_API_TOKEN environment variable as fallback
177
- const apiToken = process.env.CLOUDFLARE_API_TOKEN;
178
- if (apiToken) {
179
- oauthToken = apiToken;
180
- }
181
- else {
182
- return null;
183
- }
160
+ // Fallback to CLOUDFLARE_API_TOKEN environment variable
161
+ const apiToken = process.env.CLOUDFLARE_API_TOKEN;
162
+ if (apiToken) {
163
+ return { token: apiToken, source: 'env' };
184
164
  }
185
- // Call Cloudflare API to get subdomain
165
+ return null;
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
171
+ /**
172
+ * Get the workers.dev subdomain for the account
173
+ * This is needed because workers.dev URLs are: {worker}.{subdomain}.workers.dev
174
+ */
175
+ export async function getWorkersSubdomain() {
176
+ try {
177
+ const accountId = await getAccountId();
178
+ if (!accountId)
179
+ return null;
180
+ const tokenInfo = await getCloudflareApiToken();
181
+ if (!tokenInfo)
182
+ return null;
186
183
  const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/subdomain`, {
187
184
  headers: {
188
- Authorization: `Bearer ${oauthToken}`,
185
+ Authorization: `Bearer ${tokenInfo.token}`,
189
186
  },
190
187
  });
191
188
  if (!response.ok)
@@ -197,6 +194,488 @@ export async function getWorkersSubdomain() {
197
194
  return null;
198
195
  }
199
196
  }
197
+ /**
198
+ * Extract zone name (registrable domain) from a hostname.
199
+ * e.g., "auth.example.com" → "example.com"
200
+ * "example.co.jp" → "example.co.jp"
201
+ */
202
+ export function extractZoneName(hostname) {
203
+ const parts = hostname.split('.');
204
+ // Comprehensive two-part TLD list based on the Public Suffix List (PSL).
205
+ // Only includes patterns commonly used for web hosting on Cloudflare.
206
+ // Sorted alphabetically by country code.
207
+ const twoPartTlds = new Set([
208
+ // ae - United Arab Emirates
209
+ 'ac.ae',
210
+ 'co.ae',
211
+ 'net.ae',
212
+ 'org.ae',
213
+ // ar - Argentina
214
+ 'com.ar',
215
+ 'net.ar',
216
+ 'org.ar',
217
+ // at - Austria
218
+ 'co.at',
219
+ 'or.at',
220
+ // au - Australia
221
+ 'com.au',
222
+ 'net.au',
223
+ 'org.au',
224
+ // bd - Bangladesh
225
+ 'com.bd',
226
+ 'net.bd',
227
+ 'org.bd',
228
+ // bh - Bahrain
229
+ 'com.bh',
230
+ 'net.bh',
231
+ 'org.bh',
232
+ // bn - Brunei
233
+ 'com.bn',
234
+ 'net.bn',
235
+ 'org.bn',
236
+ // bo - Bolivia
237
+ 'com.bo',
238
+ 'net.bo',
239
+ 'org.bo',
240
+ // br - Brazil
241
+ 'com.br',
242
+ 'net.br',
243
+ 'org.br',
244
+ // bw - Botswana
245
+ 'co.bw',
246
+ 'org.bw',
247
+ // bz - Belize
248
+ 'co.bz',
249
+ 'com.bz',
250
+ 'net.bz',
251
+ 'org.bz',
252
+ // cn - China
253
+ 'com.cn',
254
+ 'net.cn',
255
+ 'org.cn',
256
+ // co - Colombia
257
+ 'com.co',
258
+ 'net.co',
259
+ 'org.co',
260
+ // cr - Costa Rica
261
+ 'co.cr',
262
+ 'or.cr',
263
+ // cu - Cuba
264
+ 'com.cu',
265
+ 'net.cu',
266
+ 'org.cu',
267
+ // cy - Cyprus
268
+ 'com.cy',
269
+ 'net.cy',
270
+ 'org.cy',
271
+ // do - Dominican Republic
272
+ 'com.do',
273
+ 'net.do',
274
+ 'org.do',
275
+ // dz - Algeria
276
+ 'com.dz',
277
+ 'net.dz',
278
+ 'org.dz',
279
+ // ec - Ecuador
280
+ 'com.ec',
281
+ 'net.ec',
282
+ 'org.ec',
283
+ // eg - Egypt
284
+ 'com.eg',
285
+ 'net.eg',
286
+ 'org.eg',
287
+ // et - Ethiopia
288
+ 'com.et',
289
+ 'net.et',
290
+ 'org.et',
291
+ // fj - Fiji
292
+ 'com.fj',
293
+ 'net.fj',
294
+ 'org.fj',
295
+ // ge - Georgia
296
+ 'com.ge',
297
+ 'net.ge',
298
+ 'org.ge',
299
+ // gh - Ghana
300
+ 'com.gh',
301
+ 'net.gh',
302
+ 'org.gh',
303
+ // gr - Greece
304
+ 'com.gr',
305
+ 'net.gr',
306
+ 'org.gr',
307
+ // gt - Guatemala
308
+ 'com.gt',
309
+ 'net.gt',
310
+ 'org.gt',
311
+ // gy - Guyana
312
+ 'co.gy',
313
+ 'com.gy',
314
+ 'net.gy',
315
+ 'org.gy',
316
+ // hk - Hong Kong
317
+ 'com.hk',
318
+ 'net.hk',
319
+ 'org.hk',
320
+ // hn - Honduras
321
+ 'com.hn',
322
+ 'net.hn',
323
+ 'org.hn',
324
+ // hr - Croatia
325
+ 'com.hr',
326
+ // id - Indonesia
327
+ 'co.id',
328
+ 'or.id',
329
+ 'web.id',
330
+ 'net.id',
331
+ // il - Israel
332
+ 'co.il',
333
+ 'net.il',
334
+ 'org.il',
335
+ // im - Isle of Man
336
+ 'co.im',
337
+ 'com.im',
338
+ 'net.im',
339
+ 'org.im',
340
+ // in - India
341
+ 'co.in',
342
+ 'net.in',
343
+ 'org.in',
344
+ // io - British Indian Ocean Territory
345
+ 'com.io',
346
+ 'net.io',
347
+ 'org.io',
348
+ // iq - Iraq
349
+ 'com.iq',
350
+ 'net.iq',
351
+ 'org.iq',
352
+ // ir - Iran
353
+ 'co.ir',
354
+ 'net.ir',
355
+ 'org.ir',
356
+ // je - Jersey
357
+ 'co.je',
358
+ 'net.je',
359
+ 'org.je',
360
+ // jo - Jordan
361
+ 'com.jo',
362
+ 'net.jo',
363
+ 'org.jo',
364
+ // jp - Japan
365
+ 'co.jp',
366
+ 'ne.jp',
367
+ 'or.jp',
368
+ 'ac.jp',
369
+ 'go.jp',
370
+ 'gr.jp',
371
+ 'ed.jp',
372
+ 'ad.jp',
373
+ 'lg.jp',
374
+ // ke - Kenya
375
+ 'co.ke',
376
+ 'or.ke',
377
+ 'ne.ke',
378
+ // kh - Cambodia (uses .com.kh etc.)
379
+ 'com.kh',
380
+ 'net.kh',
381
+ 'org.kh',
382
+ // kr - South Korea
383
+ 'co.kr',
384
+ 'or.kr',
385
+ 'ne.kr',
386
+ // kw - Kuwait
387
+ 'com.kw',
388
+ 'net.kw',
389
+ 'org.kw',
390
+ // kz - Kazakhstan
391
+ 'com.kz',
392
+ 'net.kz',
393
+ 'org.kz',
394
+ // lb - Lebanon
395
+ 'com.lb',
396
+ 'net.lb',
397
+ 'org.lb',
398
+ // lc - Saint Lucia
399
+ 'co.lc',
400
+ 'com.lc',
401
+ 'net.lc',
402
+ 'org.lc',
403
+ // lk - Sri Lanka
404
+ 'com.lk',
405
+ 'net.lk',
406
+ 'org.lk',
407
+ // ly - Libya
408
+ 'com.ly',
409
+ 'net.ly',
410
+ 'org.ly',
411
+ // ma - Morocco
412
+ 'co.ma',
413
+ 'net.ma',
414
+ 'org.ma',
415
+ // mm - Myanmar
416
+ 'com.mm',
417
+ 'net.mm',
418
+ 'org.mm',
419
+ // mo - Macau
420
+ 'com.mo',
421
+ 'net.mo',
422
+ 'org.mo',
423
+ // mt - Malta
424
+ 'com.mt',
425
+ 'net.mt',
426
+ 'org.mt',
427
+ // mu - Mauritius
428
+ 'co.mu',
429
+ 'com.mu',
430
+ 'net.mu',
431
+ 'org.mu',
432
+ // mv - Maldives
433
+ 'com.mv',
434
+ 'net.mv',
435
+ 'org.mv',
436
+ // mx - Mexico
437
+ 'com.mx',
438
+ 'net.mx',
439
+ 'org.mx',
440
+ // my - Malaysia
441
+ 'com.my',
442
+ 'net.my',
443
+ 'org.my',
444
+ // mz - Mozambique
445
+ 'co.mz',
446
+ 'net.mz',
447
+ 'org.mz',
448
+ // na - Namibia
449
+ 'co.na',
450
+ 'com.na',
451
+ 'net.na',
452
+ 'org.na',
453
+ // ng - Nigeria
454
+ 'com.ng',
455
+ 'net.ng',
456
+ 'org.ng',
457
+ // ni - Nicaragua
458
+ 'com.ni',
459
+ 'net.ni',
460
+ 'org.ni',
461
+ // np - Nepal
462
+ 'com.np',
463
+ 'net.np',
464
+ 'org.np',
465
+ // nz - New Zealand
466
+ 'co.nz',
467
+ 'net.nz',
468
+ 'org.nz',
469
+ // om - Oman
470
+ 'com.om',
471
+ 'net.om',
472
+ 'org.om',
473
+ // pa - Panama
474
+ 'com.pa',
475
+ 'net.pa',
476
+ 'org.pa',
477
+ // pe - Peru
478
+ 'com.pe',
479
+ 'net.pe',
480
+ 'org.pe',
481
+ // pg - Papua New Guinea
482
+ 'com.pg',
483
+ 'net.pg',
484
+ 'org.pg',
485
+ // ph - Philippines
486
+ 'com.ph',
487
+ 'net.ph',
488
+ 'org.ph',
489
+ // pk - Pakistan
490
+ 'com.pk',
491
+ 'net.pk',
492
+ 'org.pk',
493
+ // pr - Puerto Rico
494
+ 'com.pr',
495
+ 'net.pr',
496
+ 'org.pr',
497
+ // ps - Palestine
498
+ 'com.ps',
499
+ 'net.ps',
500
+ 'org.ps',
501
+ // pt - Portugal
502
+ 'com.pt',
503
+ 'net.pt',
504
+ 'org.pt',
505
+ // py - Paraguay
506
+ 'com.py',
507
+ 'net.py',
508
+ 'org.py',
509
+ // qa - Qatar
510
+ 'com.qa',
511
+ 'net.qa',
512
+ 'org.qa',
513
+ // ro - Romania
514
+ 'com.ro',
515
+ 'net.ro',
516
+ 'org.ro',
517
+ // rs - Serbia
518
+ 'co.rs',
519
+ 'org.rs',
520
+ // ru - Russia (ru uses direct TLD, but also has some patterns)
521
+ 'com.ru',
522
+ 'net.ru',
523
+ 'org.ru',
524
+ // rw - Rwanda
525
+ 'co.rw',
526
+ 'net.rw',
527
+ 'org.rw',
528
+ // sa - Saudi Arabia
529
+ 'com.sa',
530
+ 'net.sa',
531
+ 'org.sa',
532
+ // sb - Solomon Islands
533
+ 'com.sb',
534
+ 'net.sb',
535
+ 'org.sb',
536
+ // sc - Seychelles
537
+ 'com.sc',
538
+ 'net.sc',
539
+ 'org.sc',
540
+ // sd - Sudan
541
+ 'com.sd',
542
+ 'net.sd',
543
+ 'org.sd',
544
+ // sg - Singapore
545
+ 'com.sg',
546
+ 'net.sg',
547
+ 'org.sg',
548
+ // sl - Sierra Leone
549
+ 'com.sl',
550
+ 'net.sl',
551
+ 'org.sl',
552
+ // sv - El Salvador
553
+ 'com.sv',
554
+ 'org.sv',
555
+ // sy - Syria
556
+ 'com.sy',
557
+ 'net.sy',
558
+ 'org.sy',
559
+ // th - Thailand
560
+ 'co.th',
561
+ 'in.th',
562
+ 'ac.th',
563
+ 'or.th',
564
+ 'net.th',
565
+ // tn - Tunisia
566
+ 'com.tn',
567
+ 'net.tn',
568
+ 'org.tn',
569
+ // tr - Turkey
570
+ 'com.tr',
571
+ 'net.tr',
572
+ 'org.tr',
573
+ // tt - Trinidad and Tobago
574
+ 'co.tt',
575
+ 'com.tt',
576
+ 'net.tt',
577
+ 'org.tt',
578
+ // tw - Taiwan
579
+ 'com.tw',
580
+ 'net.tw',
581
+ 'org.tw',
582
+ // tz - Tanzania
583
+ 'co.tz',
584
+ 'or.tz',
585
+ 'ne.tz',
586
+ // ua - Ukraine
587
+ 'com.ua',
588
+ 'net.ua',
589
+ 'org.ua',
590
+ // ug - Uganda
591
+ 'co.ug',
592
+ 'or.ug',
593
+ 'ne.ug',
594
+ // uk - United Kingdom
595
+ 'co.uk',
596
+ 'org.uk',
597
+ 'me.uk',
598
+ 'net.uk',
599
+ // uy - Uruguay
600
+ 'com.uy',
601
+ 'net.uy',
602
+ 'org.uy',
603
+ // uz - Uzbekistan
604
+ 'co.uz',
605
+ 'com.uz',
606
+ 'net.uz',
607
+ 'org.uz',
608
+ // vc - Saint Vincent and the Grenadines
609
+ 'com.vc',
610
+ 'net.vc',
611
+ 'org.vc',
612
+ // ve - Venezuela
613
+ 'co.ve',
614
+ 'com.ve',
615
+ 'net.ve',
616
+ 'org.ve',
617
+ // vn - Vietnam
618
+ 'com.vn',
619
+ 'net.vn',
620
+ 'org.vn',
621
+ // za - South Africa
622
+ 'co.za',
623
+ 'net.za',
624
+ 'org.za',
625
+ // zm - Zambia
626
+ 'co.zm',
627
+ 'com.zm',
628
+ 'net.zm',
629
+ 'org.zm',
630
+ // zw - Zimbabwe
631
+ 'co.zw',
632
+ 'org.zw',
633
+ ]);
634
+ const lastTwo = parts.slice(-2).join('.');
635
+ if (twoPartTlds.has(lastTwo) && parts.length >= 3) {
636
+ return parts.slice(-3).join('.');
637
+ }
638
+ return parts.length >= 2 ? parts.slice(-2).join('.') : hostname;
639
+ }
640
+ /**
641
+ * Check if a Cloudflare zone exists for the given domain.
642
+ * Gracefully handles authentication failures and network errors.
643
+ */
644
+ export async function checkZoneExists(domain) {
645
+ try {
646
+ const tokenInfo = await getCloudflareApiToken();
647
+ if (!tokenInfo) {
648
+ return { found: false, error: 'Not logged in to Cloudflare (run: wrangler login)' };
649
+ }
650
+ const zoneName = extractZoneName(domain);
651
+ const response = await fetch(`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(zoneName)}`, {
652
+ headers: {
653
+ Authorization: `Bearer ${tokenInfo.token}`,
654
+ },
655
+ });
656
+ if (!response.ok) {
657
+ if (response.status === 403) {
658
+ return { found: false, error: 'Token lacks zone:read permission' };
659
+ }
660
+ return { found: false, error: `Cloudflare API returned ${response.status}` };
661
+ }
662
+ const data = (await response.json());
663
+ if (!data.success || !data.result || data.result.length === 0) {
664
+ return { found: false };
665
+ }
666
+ const zone = data.result[0];
667
+ return {
668
+ found: true,
669
+ zone: { id: zone.id, name: zone.name, status: zone.status },
670
+ };
671
+ }
672
+ catch (error) {
673
+ return {
674
+ found: false,
675
+ error: error instanceof Error ? error.message : 'Unknown error',
676
+ };
677
+ }
678
+ }
200
679
  // =============================================================================
201
680
  // D1 Database Operations
202
681
  // =============================================================================
@@ -344,8 +823,76 @@ export async function executeD1Migration(dbName, sqlFilePath, onProgress) {
344
823
  return { success: false, error: message };
345
824
  }
346
825
  }
826
+ /** SQL to create the migration tracking table (idempotent) */
827
+ const CREATE_MIGRATIONS_TABLE_SQL = `
828
+ CREATE TABLE IF NOT EXISTS authrim_migrations (
829
+ filename TEXT PRIMARY KEY,
830
+ applied_at INTEGER NOT NULL
831
+ );
832
+ `.trim();
347
833
  /**
348
- * Run all D1 migrations for a database
834
+ * Ensure the authrim_migrations tracking table exists in the target database.
835
+ * Returns true on success, false on failure.
836
+ */
837
+ async function ensureMigrationsTable(dbName, onProgress) {
838
+ try {
839
+ await wrangler([
840
+ 'd1',
841
+ 'execute',
842
+ dbName,
843
+ '--remote',
844
+ '--yes',
845
+ '--command',
846
+ CREATE_MIGRATIONS_TABLE_SQL,
847
+ ]);
848
+ return true;
849
+ }
850
+ catch (error) {
851
+ onProgress?.(` ⚠️ Could not create migrations table: ${error instanceof Error ? error.message : String(error)}`);
852
+ return false;
853
+ }
854
+ }
855
+ /**
856
+ * Return the set of migration filenames already recorded in authrim_migrations.
857
+ * Falls back to an empty set on error so we never skip migrations when unsure.
858
+ */
859
+ async function getAppliedMigrations(dbName) {
860
+ try {
861
+ const { stdout } = await wrangler([
862
+ 'd1',
863
+ 'execute',
864
+ dbName,
865
+ '--remote',
866
+ '--yes',
867
+ '--command',
868
+ 'SELECT filename FROM authrim_migrations;',
869
+ '--json',
870
+ ]);
871
+ const rows = JSON.parse(stdout);
872
+ const results = rows?.[0]?.results ?? [];
873
+ return new Set(results.map((r) => r.filename));
874
+ }
875
+ catch {
876
+ return new Set();
877
+ }
878
+ }
879
+ /**
880
+ * Record a migration filename as applied.
881
+ */
882
+ async function recordMigration(dbName, filename) {
883
+ const sql = `INSERT OR IGNORE INTO authrim_migrations (filename, applied_at) VALUES ('${filename.replace(/'/g, "''")}', ${Date.now()});`;
884
+ try {
885
+ await wrangler(['d1', 'execute', dbName, '--remote', '--yes', '--command', sql]);
886
+ }
887
+ catch {
888
+ // Non-fatal: tracking failure should not abort the migration run
889
+ }
890
+ }
891
+ /**
892
+ * Run all D1 migrations for a database.
893
+ *
894
+ * Uses an `authrim_migrations` tracking table inside the D1 database to skip
895
+ * files that have already been applied, making repeated runs idempotent.
349
896
  */
350
897
  export async function runD1Migrations(dbName, migrationsDir, onProgress) {
351
898
  const { existsSync, readdirSync } = await import('node:fs');
@@ -354,6 +901,7 @@ export async function runD1Migrations(dbName, migrationsDir, onProgress) {
354
901
  return {
355
902
  success: false,
356
903
  appliedCount: 0,
904
+ skippedCount: 0,
357
905
  error: `Migrations directory not found: ${migrationsDir}`,
358
906
  };
359
907
  }
@@ -363,18 +911,34 @@ export async function runD1Migrations(dbName, migrationsDir, onProgress) {
363
911
  .sort();
364
912
  if (sqlFiles.length === 0) {
365
913
  onProgress?.(` No migration files found in ${migrationsDir}`);
366
- return { success: true, appliedCount: 0 };
914
+ return { success: true, appliedCount: 0, skippedCount: 0 };
367
915
  }
368
916
  onProgress?.(` Found ${sqlFiles.length} migration files`);
917
+ // Ensure tracking table exists; if it fails we continue without tracking
918
+ await ensureMigrationsTable(dbName, onProgress);
919
+ const applied = await getAppliedMigrations(dbName);
920
+ onProgress?.(` ${applied.size} migration(s) already recorded as applied`);
369
921
  let appliedCount = 0;
922
+ let skippedCount = 0;
370
923
  for (const sqlFile of sqlFiles) {
924
+ if (applied.has(sqlFile)) {
925
+ onProgress?.(` ⏭ Skipping (already applied): ${sqlFile}`);
926
+ skippedCount++;
927
+ continue;
928
+ }
371
929
  const result = await executeD1Migration(dbName, join(migrationsDir, sqlFile), onProgress);
372
930
  if (!result.success) {
373
- return { success: false, appliedCount, error: `Failed on ${sqlFile}: ${result.error}` };
931
+ return {
932
+ success: false,
933
+ appliedCount,
934
+ skippedCount,
935
+ error: `Failed on ${sqlFile}: ${result.error}`,
936
+ };
374
937
  }
938
+ await recordMigration(dbName, sqlFile);
375
939
  appliedCount++;
376
940
  }
377
- return { success: true, appliedCount };
941
+ return { success: true, appliedCount, skippedCount };
378
942
  }
379
943
  /**
380
944
  * Run migrations for an Authrim environment
@@ -419,9 +983,9 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
419
983
  onProgress?.(` ❌ ${errorMsg}`);
420
984
  return {
421
985
  success: false,
422
- core: { success: false, appliedCount: 0, error: errorMsg },
423
- pii: { success: false, appliedCount: 0, error: errorMsg },
424
- admin: { success: false, appliedCount: 0, error: errorMsg },
986
+ core: { success: false, appliedCount: 0, skippedCount: 0, error: errorMsg },
987
+ pii: { success: false, appliedCount: 0, skippedCount: 0, error: errorMsg },
988
+ admin: { success: false, appliedCount: 0, skippedCount: 0, error: errorMsg },
425
989
  };
426
990
  }
427
991
  // Run core database migrations
@@ -431,7 +995,7 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
431
995
  onProgress?.(` ❌ Core migration failed: ${coreResult.error}`);
432
996
  }
433
997
  else {
434
- onProgress?.(` ✅ Applied ${coreResult.appliedCount} core migrations`);
998
+ onProgress?.(` ✅ Applied ${coreResult.appliedCount} core migrations (${coreResult.skippedCount} skipped)`);
435
999
  }
436
1000
  // Run PII database migrations
437
1001
  const piiMigrationsDir = join(migrationsRoot, 'pii');
@@ -439,7 +1003,7 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
439
1003
  let piiResult;
440
1004
  if (!existsSync(piiMigrationsDir)) {
441
1005
  onProgress?.(` ⚠️ PII migrations directory not found: ${piiMigrationsDir}`);
442
- piiResult = { success: true, appliedCount: 0 };
1006
+ piiResult = { success: true, appliedCount: 0, skippedCount: 0 };
443
1007
  }
444
1008
  else {
445
1009
  piiResult = await runD1Migrations(piiDbName, piiMigrationsDir, onProgress);
@@ -447,7 +1011,7 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
447
1011
  onProgress?.(` ❌ PII migration failed: ${piiResult.error}`);
448
1012
  }
449
1013
  else {
450
- onProgress?.(` ✅ Applied ${piiResult.appliedCount} PII migrations`);
1014
+ onProgress?.(` ✅ Applied ${piiResult.appliedCount} PII migrations (${piiResult.skippedCount} skipped)`);
451
1015
  }
452
1016
  }
453
1017
  // Run Admin database migrations
@@ -456,7 +1020,7 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
456
1020
  let adminResult;
457
1021
  if (!existsSync(adminMigrationsDir)) {
458
1022
  onProgress?.(` ⚠️ Admin migrations directory not found: ${adminMigrationsDir}`);
459
- adminResult = { success: true, appliedCount: 0 };
1023
+ adminResult = { success: true, appliedCount: 0, skippedCount: 0 };
460
1024
  }
461
1025
  else {
462
1026
  adminResult = await runD1Migrations(adminDbName, adminMigrationsDir, onProgress);
@@ -464,7 +1028,7 @@ export async function runMigrationsForEnvironment(env, rootDir, onProgress) {
464
1028
  onProgress?.(` ❌ Admin migration failed: ${adminResult.error}`);
465
1029
  }
466
1030
  else {
467
- onProgress?.(` ✅ Applied ${adminResult.appliedCount} admin migrations`);
1031
+ onProgress?.(` ✅ Applied ${adminResult.appliedCount} admin migrations (${adminResult.skippedCount} skipped)`);
468
1032
  }
469
1033
  }
470
1034
  return {
@@ -604,7 +1168,14 @@ export async function createKVNamespace(name, preview = false) {
604
1168
  */
605
1169
  export async function deleteKVNamespace(namespaceId) {
606
1170
  try {
607
- await wrangler(['kv', 'namespace', 'delete', '--namespace-id', namespaceId]);
1171
+ await wrangler([
1172
+ 'kv',
1173
+ 'namespace',
1174
+ 'delete',
1175
+ '--namespace-id',
1176
+ namespaceId,
1177
+ '--skip-confirmation',
1178
+ ]);
608
1179
  return true;
609
1180
  }
610
1181
  catch {
@@ -837,18 +1408,31 @@ export async function provisionResources(options) {
837
1408
  // Provision R2 buckets (optional)
838
1409
  if (options.createR2) {
839
1410
  onProgress('📁 R2 Buckets');
840
- const bucketName = `${env}-authrim-avatars`;
841
- onProgress(` ⏳ Creating: ${bucketName}...`);
1411
+ const avatarBucketName = `${env}-authrim-avatars`;
1412
+ onProgress(` ⏳ Creating: ${avatarBucketName}...`);
842
1413
  try {
843
- const result = await createR2Bucket(bucketName);
1414
+ const result = await createR2Bucket(avatarBucketName);
844
1415
  resources.r2.push({
845
1416
  binding: 'AVATARS',
846
1417
  name: result.name,
847
1418
  });
848
- onProgress(` ✅ ${bucketName} created`);
1419
+ onProgress(` ✅ ${avatarBucketName} created`);
849
1420
  }
850
1421
  catch (error) {
851
- onProgress(` ⚠️ Skipped: ${bucketName} - ${sanitizeError(error)}`);
1422
+ onProgress(` ⚠️ Skipped: ${avatarBucketName} - ${sanitizeError(error)}`);
1423
+ }
1424
+ const diagnosticBucketName = `${env}-diagnostic-logs`;
1425
+ onProgress(` ⏳ Creating: ${diagnosticBucketName}...`);
1426
+ try {
1427
+ const result = await createR2Bucket(diagnosticBucketName);
1428
+ resources.r2.push({
1429
+ binding: 'DIAGNOSTIC_LOGS',
1430
+ name: result.name,
1431
+ });
1432
+ onProgress(` ✅ ${diagnosticBucketName} created`);
1433
+ }
1434
+ catch (error) {
1435
+ onProgress(` ⚠️ Skipped: ${diagnosticBucketName} - ${sanitizeError(error)}`);
852
1436
  }
853
1437
  onProgress('');
854
1438
  }
@@ -898,7 +1482,7 @@ const AUTHRIM_PATTERNS = {
898
1482
  // KV can have either lowercase or uppercase env prefix (e.g., conformance-CLIENTS_CACHE or TESTENV-CLIENTS_CACHE)
899
1483
  kv: /^([a-zA-Z][a-zA-Z0-9-]*)-(?:CLIENTS_CACHE|INITIAL_ACCESS_TOKENS|SETTINGS|REBAC_CACHE|USER_CACHE|AUTHRIM_CONFIG|STATE_STORE|CONSENT_CACHE)(?:_preview)?$/i,
900
1484
  queue: /^([a-z][a-z0-9-]*)-audit-queue$/,
901
- r2: /^([a-z][a-z0-9-]*)-authrim-avatars$/,
1485
+ r2: /^([a-z][a-z0-9-]*)-(authrim-avatars|diagnostic-logs)$/,
902
1486
  // Pages projects: {env}-ar-admin-ui, {env}-ar-login-ui
903
1487
  pages: /^([a-z][a-z0-9-]*)-(ar-admin-ui|ar-login-ui)$/,
904
1488
  };
@@ -907,11 +1491,17 @@ const AUTHRIM_PATTERNS = {
907
1491
  */
908
1492
  export async function listWorkers() {
909
1493
  try {
910
- const { stdout: _stdout } = await wrangler(['deployments', 'list', '--json']);
911
- // Note: wrangler deployments list doesn't give us what we need
912
- // We'll use wrangler whoami to get account and then list workers differently
913
- // For now, return empty - we'll detect workers from D1/KV patterns
914
- return [];
1494
+ const accountId = await getAccountId();
1495
+ if (!accountId)
1496
+ return [];
1497
+ const tokenInfo = await getCloudflareApiToken();
1498
+ if (!tokenInfo)
1499
+ return [];
1500
+ const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts`, { headers: { Authorization: `Bearer ${tokenInfo.token}` } });
1501
+ if (!response.ok)
1502
+ return [];
1503
+ const data = (await response.json());
1504
+ return (data.result || []).map((w) => ({ name: w.id }));
915
1505
  }
916
1506
  catch {
917
1507
  return [];
@@ -996,6 +1586,46 @@ export async function listPagesProjects() {
996
1586
  */
997
1587
  export async function deletePagesProject(name) {
998
1588
  try {
1589
+ // First, remove all custom domains from the Pages project
1590
+ // This is required before the project can be deleted
1591
+ const token = await getCloudflareApiToken();
1592
+ if (token) {
1593
+ try {
1594
+ const accountId = await getAccountId();
1595
+ // Get project details to list custom domains
1596
+ const projectResponse = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${name}`, {
1597
+ headers: {
1598
+ Authorization: `Bearer ${token.token}`,
1599
+ },
1600
+ });
1601
+ if (projectResponse.ok) {
1602
+ const projectData = (await projectResponse.json());
1603
+ const domains = projectData.result?.domains || [];
1604
+ // Remove each custom domain (skip *.pages.dev domains)
1605
+ for (const domain of domains) {
1606
+ if (!domain.endsWith('.pages.dev')) {
1607
+ try {
1608
+ await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${name}/domains/${domain}`, {
1609
+ method: 'DELETE',
1610
+ headers: {
1611
+ Authorization: `Bearer ${token.token}`,
1612
+ },
1613
+ });
1614
+ // Small delay to let Cloudflare process the deletion
1615
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1616
+ }
1617
+ catch {
1618
+ // Continue even if domain deletion fails
1619
+ }
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ catch {
1625
+ // Continue even if custom domain cleanup fails
1626
+ }
1627
+ }
1628
+ // Now delete the Pages project
999
1629
  await wrangler(['pages', 'project', 'delete', name, '--yes']);
1000
1630
  return true;
1001
1631
  }
@@ -1009,13 +1639,16 @@ export async function deletePagesProject(name) {
1009
1639
  export async function detectEnvironments(onProgress) {
1010
1640
  const environments = new Map();
1011
1641
  const progress = onProgress || (() => { });
1012
- progress('Scanning D1 databases...');
1642
+ // Scan Workers first — environments are only valid if Workers or D1 exist
1643
+ progress('Scanning Workers...');
1644
+ const workerEnvs = new Set();
1013
1645
  try {
1014
- const databases = await listD1Databases();
1015
- for (const db of databases) {
1016
- const match = db.name.match(AUTHRIM_PATTERNS.d1);
1646
+ const workers = await listWorkers();
1647
+ for (const w of workers) {
1648
+ const match = w.name.match(AUTHRIM_PATTERNS.worker);
1017
1649
  if (match) {
1018
1650
  const env = match[1].toLowerCase();
1651
+ workerEnvs.add(env);
1019
1652
  if (!environments.has(env)) {
1020
1653
  environments.set(env, {
1021
1654
  env,
@@ -1027,20 +1660,22 @@ export async function detectEnvironments(onProgress) {
1027
1660
  pages: [],
1028
1661
  });
1029
1662
  }
1030
- environments.get(env).d1.push({ name: db.name, id: db.uuid });
1663
+ environments.get(env).workers.push({ name: w.name });
1031
1664
  }
1032
1665
  }
1033
1666
  }
1034
1667
  catch (error) {
1035
- progress(` ⚠️ Could not scan D1: ${error instanceof Error ? error.message : error}`);
1668
+ progress(` ⚠️ Could not scan Workers: ${error instanceof Error ? error.message : error}`);
1036
1669
  }
1037
- progress('Scanning KV namespaces...');
1670
+ progress('Scanning D1 databases...');
1671
+ const d1Envs = new Set();
1038
1672
  try {
1039
- const namespaces = await listKVNamespaces();
1040
- for (const ns of namespaces) {
1041
- const match = ns.title.match(AUTHRIM_PATTERNS.kv);
1673
+ const databases = await listD1Databases();
1674
+ for (const db of databases) {
1675
+ const match = db.name.match(AUTHRIM_PATTERNS.d1);
1042
1676
  if (match) {
1043
1677
  const env = match[1].toLowerCase();
1678
+ d1Envs.add(env);
1044
1679
  if (!environments.has(env)) {
1045
1680
  environments.set(env, {
1046
1681
  env,
@@ -1052,7 +1687,24 @@ export async function detectEnvironments(onProgress) {
1052
1687
  pages: [],
1053
1688
  });
1054
1689
  }
1055
- environments.get(env).kv.push({ name: ns.title, id: ns.id });
1690
+ environments.get(env).d1.push({ name: db.name, id: db.uuid });
1691
+ }
1692
+ }
1693
+ }
1694
+ catch (error) {
1695
+ progress(` ⚠️ Could not scan D1: ${error instanceof Error ? error.message : error}`);
1696
+ }
1697
+ progress('Scanning KV namespaces...');
1698
+ try {
1699
+ const namespaces = await listKVNamespaces();
1700
+ for (const ns of namespaces) {
1701
+ const match = ns.title.match(AUTHRIM_PATTERNS.kv);
1702
+ if (match) {
1703
+ const env = match[1].toLowerCase();
1704
+ // Only attach KV to environments that already have Workers or D1
1705
+ if (environments.has(env)) {
1706
+ environments.get(env).kv.push({ name: ns.title, id: ns.id });
1707
+ }
1056
1708
  }
1057
1709
  }
1058
1710
  }
@@ -1066,18 +1718,10 @@ export async function detectEnvironments(onProgress) {
1066
1718
  const match = q.name.match(AUTHRIM_PATTERNS.queue);
1067
1719
  if (match) {
1068
1720
  const env = match[1].toLowerCase();
1069
- if (!environments.has(env)) {
1070
- environments.set(env, {
1071
- env,
1072
- workers: [],
1073
- d1: [],
1074
- kv: [],
1075
- queues: [],
1076
- r2: [],
1077
- pages: [],
1078
- });
1721
+ // Only attach Queues to environments that already have Workers or D1
1722
+ if (environments.has(env)) {
1723
+ environments.get(env).queues.push({ name: q.name, id: q.id });
1079
1724
  }
1080
- environments.get(env).queues.push({ name: q.name, id: q.id });
1081
1725
  }
1082
1726
  }
1083
1727
  }
@@ -1091,18 +1735,10 @@ export async function detectEnvironments(onProgress) {
1091
1735
  const match = bucket.name.match(AUTHRIM_PATTERNS.r2);
1092
1736
  if (match) {
1093
1737
  const env = match[1].toLowerCase();
1094
- if (!environments.has(env)) {
1095
- environments.set(env, {
1096
- env,
1097
- workers: [],
1098
- d1: [],
1099
- kv: [],
1100
- queues: [],
1101
- r2: [],
1102
- pages: [],
1103
- });
1738
+ // Only attach R2 to environments that already have Workers or D1
1739
+ if (environments.has(env)) {
1740
+ environments.get(env).r2.push({ name: bucket.name });
1104
1741
  }
1105
- environments.get(env).r2.push({ name: bucket.name });
1106
1742
  }
1107
1743
  }
1108
1744
  }
@@ -1116,42 +1752,20 @@ export async function detectEnvironments(onProgress) {
1116
1752
  const match = project.name.match(AUTHRIM_PATTERNS.pages);
1117
1753
  if (match) {
1118
1754
  const env = match[1].toLowerCase();
1119
- if (!environments.has(env)) {
1120
- environments.set(env, {
1121
- env,
1122
- workers: [],
1123
- d1: [],
1124
- kv: [],
1125
- queues: [],
1126
- r2: [],
1127
- pages: [],
1128
- });
1755
+ // Only attach Pages to environments that already have Workers or D1
1756
+ if (environments.has(env)) {
1757
+ environments.get(env).pages.push({ name: project.name });
1129
1758
  }
1130
- environments.get(env).pages.push({ name: project.name });
1131
1759
  }
1132
1760
  }
1133
1761
  }
1134
1762
  catch (error) {
1135
1763
  progress(` ⚠️ Could not scan Pages: ${error instanceof Error ? error.message : error}`);
1136
1764
  }
1137
- // Infer workers from detected environments
1138
- const workerComponents = [
1139
- 'ar-lib-core',
1140
- 'ar-auth',
1141
- 'ar-token',
1142
- 'ar-userinfo',
1143
- 'ar-discovery',
1144
- 'ar-management',
1145
- 'ar-router',
1146
- 'ar-async',
1147
- 'ar-saml',
1148
- 'ar-bridge',
1149
- 'ar-vc',
1150
- 'ar-policy',
1151
- ];
1765
+ // Filter: only keep environments that have actual Workers or D1 databases
1152
1766
  for (const [env, info] of environments) {
1153
- for (const comp of workerComponents) {
1154
- info.workers.push({ name: `${env}-${comp}` });
1767
+ if (info.workers.length === 0 && info.d1.length === 0) {
1768
+ environments.delete(env);
1155
1769
  }
1156
1770
  }
1157
1771
  progress(`Found ${environments.size} environment(s)`);