@authrim/setup 0.1.141 → 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 (103) hide show
  1. package/dist/__tests__/keys.test.js.map +1 -1
  2. package/dist/__tests__/migrate.test.js +4 -4
  3. package/dist/__tests__/migrate.test.js.map +1 -1
  4. package/dist/__tests__/paths.test.js.map +1 -1
  5. package/dist/cli/commands/deploy.d.ts.map +1 -1
  6. package/dist/cli/commands/deploy.js +57 -63
  7. package/dist/cli/commands/deploy.js.map +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +231 -171
  10. package/dist/cli/commands/init.js.map +1 -1
  11. package/dist/core/admin.d.ts.map +1 -1
  12. package/dist/core/admin.js +13 -3
  13. package/dist/core/admin.js.map +1 -1
  14. package/dist/core/cloudflare.d.ts +38 -1
  15. package/dist/core/cloudflare.d.ts.map +1 -1
  16. package/dist/core/cloudflare.js +729 -115
  17. package/dist/core/cloudflare.js.map +1 -1
  18. package/dist/core/config.d.ts +136 -28
  19. package/dist/core/config.d.ts.map +1 -1
  20. package/dist/core/config.js +58 -11
  21. package/dist/core/config.js.map +1 -1
  22. package/dist/core/deploy.d.ts +18 -0
  23. package/dist/core/deploy.d.ts.map +1 -1
  24. package/dist/core/deploy.js +126 -25
  25. package/dist/core/deploy.js.map +1 -1
  26. package/dist/core/keys.d.ts.map +1 -1
  27. package/dist/core/keys.js +2 -0
  28. package/dist/core/keys.js.map +1 -1
  29. package/dist/core/login-ui-client.d.ts.map +1 -1
  30. package/dist/core/login-ui-client.js +43 -7
  31. package/dist/core/login-ui-client.js.map +1 -1
  32. package/dist/core/paths.d.ts.map +1 -1
  33. package/dist/core/paths.js +5 -5
  34. package/dist/core/paths.js.map +1 -1
  35. package/dist/core/tenant-mode.d.ts +4 -0
  36. package/dist/core/tenant-mode.d.ts.map +1 -0
  37. package/dist/core/tenant-mode.js +17 -0
  38. package/dist/core/tenant-mode.js.map +1 -0
  39. package/dist/core/ui-deployment.d.ts +21 -0
  40. package/dist/core/ui-deployment.d.ts.map +1 -0
  41. package/dist/core/ui-deployment.js +90 -0
  42. package/dist/core/ui-deployment.js.map +1 -0
  43. package/dist/core/ui-env.d.ts +17 -0
  44. package/dist/core/ui-env.d.ts.map +1 -1
  45. package/dist/core/ui-env.js +16 -0
  46. package/dist/core/ui-env.js.map +1 -1
  47. package/dist/core/url-config.d.ts +16 -0
  48. package/dist/core/url-config.d.ts.map +1 -0
  49. package/dist/core/url-config.js +46 -0
  50. package/dist/core/url-config.js.map +1 -0
  51. package/dist/core/wrangler.d.ts +50 -1
  52. package/dist/core/wrangler.d.ts.map +1 -1
  53. package/dist/core/wrangler.js +169 -55
  54. package/dist/core/wrangler.js.map +1 -1
  55. package/dist/i18n/locales/de.d.ts.map +1 -1
  56. package/dist/i18n/locales/de.js +37 -0
  57. package/dist/i18n/locales/de.js.map +1 -1
  58. package/dist/i18n/locales/en.d.ts.map +1 -1
  59. package/dist/i18n/locales/en.js +37 -0
  60. package/dist/i18n/locales/en.js.map +1 -1
  61. package/dist/i18n/locales/es.d.ts.map +1 -1
  62. package/dist/i18n/locales/es.js +37 -0
  63. package/dist/i18n/locales/es.js.map +1 -1
  64. package/dist/i18n/locales/fr.d.ts.map +1 -1
  65. package/dist/i18n/locales/fr.js +37 -0
  66. package/dist/i18n/locales/fr.js.map +1 -1
  67. package/dist/i18n/locales/id.d.ts.map +1 -1
  68. package/dist/i18n/locales/id.js +37 -0
  69. package/dist/i18n/locales/id.js.map +1 -1
  70. package/dist/i18n/locales/ja.d.ts.map +1 -1
  71. package/dist/i18n/locales/ja.js +37 -0
  72. package/dist/i18n/locales/ja.js.map +1 -1
  73. package/dist/i18n/locales/ko.d.ts.map +1 -1
  74. package/dist/i18n/locales/ko.js +37 -0
  75. package/dist/i18n/locales/ko.js.map +1 -1
  76. package/dist/i18n/locales/pt.d.ts.map +1 -1
  77. package/dist/i18n/locales/pt.js +37 -0
  78. package/dist/i18n/locales/pt.js.map +1 -1
  79. package/dist/i18n/locales/ru.d.ts.map +1 -1
  80. package/dist/i18n/locales/ru.js +37 -0
  81. package/dist/i18n/locales/ru.js.map +1 -1
  82. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  83. package/dist/i18n/locales/zh-CN.js +37 -0
  84. package/dist/i18n/locales/zh-CN.js.map +1 -1
  85. package/dist/i18n/locales/zh-TW.d.ts.map +1 -1
  86. package/dist/i18n/locales/zh-TW.js +37 -0
  87. package/dist/i18n/locales/zh-TW.js.map +1 -1
  88. package/dist/i18n/types.d.ts +8 -0
  89. package/dist/i18n/types.d.ts.map +1 -1
  90. package/dist/index.js +38 -29
  91. package/dist/index.js.map +1 -1
  92. package/dist/web/api.d.ts.map +1 -1
  93. package/dist/web/api.js +207 -95
  94. package/dist/web/api.js.map +1 -1
  95. package/dist/web/ui.d.ts.map +1 -1
  96. package/dist/web/ui.js +506 -109
  97. package/dist/web/ui.js.map +1 -1
  98. package/migrations/000_fresh_schema.sql +227 -9
  99. package/migrations/admin/006_admin_setup_tokens.sql +91 -91
  100. package/migrations/admin/007_admin_role_inheritance.sql +32 -0
  101. package/migrations/admin/008_admin_rebac_definitions.sql +117 -0
  102. package/migrations/admin/009_optimize_admin_audit_indexes.sql +15 -0
  103. package/package.json +5 -5
package/dist/web/api.js CHANGED
@@ -21,12 +21,16 @@ import { createLockFile, saveLockFile } from '../core/lock.js';
21
21
  import { getEnvironmentPaths, getExternalKeysDir, findKeysDirectory, resolvePaths, listEnvironments, findAuthrimBaseDir, } from '../core/paths.js';
22
22
  import { generateWranglerConfig, toToml } from '../core/wrangler.js';
23
23
  import { syncWranglerConfigs } from '../core/wrangler-sync.js';
24
+ import { buildUrlsConfig } from '../core/url-config.js';
25
+ import { normalizeTenantConfigForApiDomain } from '../core/tenant-mode.js';
24
26
  import { deployAll, uploadSecrets, buildApiPackages, deployAllPages, deployPagesComponent, deployWorker, PAGES_COMPONENTS, } from '../core/deploy.js';
25
27
  import { getEnabledComponents, WORKER_COMPONENTS } from '../core/naming.js';
26
28
  import { getLocalPackageVersions, compareVersions, getComponentsToUpdate, } from '../core/version.js';
27
29
  import { completeInitialSetup } from '../core/admin.js';
28
- import { writeFile, chmod } from 'node:fs/promises';
29
- import { join } from 'node:path';
30
+ import { resolveUiDeploymentSettings } from '../core/ui-deployment.js';
31
+ import { saveUiEnv, buildInitialUiEnvConfig } from '../core/ui-env.js';
32
+ import { writeFile, chmod, mkdir } from 'node:fs/promises';
33
+ import { join, dirname } from 'node:path';
30
34
  // =============================================================================
31
35
  // Session & Security
32
36
  // =============================================================================
@@ -117,6 +121,29 @@ export function createApiRoutes() {
117
121
  api.use('/deploy', validateSession);
118
122
  api.use('/reset', validateSession);
119
123
  api.use('/admin/*', validateSession);
124
+ api.use('/cloudflare/*', validateSession);
125
+ // ==========================================================================
126
+ // Cloudflare Zone Check
127
+ // ==========================================================================
128
+ api.post('/cloudflare/check-zone', async (c) => {
129
+ try {
130
+ const body = (await c.req.json());
131
+ const { domain } = body;
132
+ if (!domain || typeof domain !== 'string') {
133
+ return c.json({ found: false, error: 'domain is required' }, 400);
134
+ }
135
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/.test(domain)) {
136
+ return c.json({ found: false, error: 'Invalid domain format' }, 400);
137
+ }
138
+ const { checkZoneExists } = await import('../core/cloudflare.js');
139
+ const result = await checkZoneExists(domain);
140
+ return c.json(result);
141
+ }
142
+ catch (error) {
143
+ const message = error instanceof Error ? error.message : 'Unknown error';
144
+ return c.json({ found: false, error: message }, 500);
145
+ }
146
+ });
120
147
  // Get current state (no auth required - read-only)
121
148
  api.get('/state', (c) => {
122
149
  return c.json(state);
@@ -248,10 +275,13 @@ export function createApiRoutes() {
248
275
  // Use new structure for saving
249
276
  const envPaths = getEnvironmentPaths({ baseDir, env });
250
277
  // Ensure directory exists
251
- const { mkdir } = await import('node:fs/promises');
252
278
  await mkdir(envPaths.root, { recursive: true });
253
279
  // Save config
254
280
  await writeFile(envPaths.config, JSON.stringify(config, null, 2));
281
+ const initialUiEnv = buildInitialUiEnvConfig(config);
282
+ if (initialUiEnv) {
283
+ await saveUiEnv(envPaths.uiEnv, initialUiEnv);
284
+ }
255
285
  state.config = config;
256
286
  return c.json({ success: true, configPath: envPaths.config, structure: 'new' });
257
287
  }
@@ -268,34 +298,21 @@ export function createApiRoutes() {
268
298
  return withLock(async () => {
269
299
  try {
270
300
  const body = await c.req.json();
271
- const { env = 'prod', apiDomain, loginUiDomain, adminUiDomain, tenant, components } = body;
301
+ const { env = 'prod', apiDomain, loginUiDomain, adminUiDomain, tenant, components, zoneId, customDomainBinding, } = body;
272
302
  const config = createDefaultConfig(env);
273
303
  // Update tenant configuration
274
304
  if (tenant) {
275
- config.tenant = {
276
- name: tenant.name || 'default',
277
- displayName: tenant.displayName || 'Default Tenant',
278
- multiTenant: tenant.multiTenant || false,
279
- baseDomain: tenant.baseDomain,
280
- };
305
+ config.tenant = normalizeTenantConfigForApiDomain(tenant);
281
306
  }
282
307
  // Update URLs with domain configuration
283
- config.urls = {
284
- api: {
285
- custom: apiDomain || null,
286
- auto: `https://${env}-ar-router.workers.dev`,
287
- },
288
- loginUi: {
289
- custom: loginUiDomain || null,
290
- auto: `https://${env}-ar-login-ui.pages.dev`,
291
- sameAsApi: false,
292
- },
293
- adminUi: {
294
- custom: adminUiDomain || null,
295
- auto: `https://${env}-ar-admin-ui.pages.dev`,
296
- sameAsApi: false,
297
- },
298
- };
308
+ config.urls = buildUrlsConfig({
309
+ env,
310
+ apiDomain,
311
+ loginUiDomain,
312
+ adminUiDomain,
313
+ zoneId: zoneId || null,
314
+ customDomainBinding: customDomainBinding ?? false,
315
+ });
299
316
  // Update components if provided
300
317
  if (components) {
301
318
  config.components = {
@@ -385,7 +402,6 @@ export function createApiRoutes() {
385
402
  const baseDir = findAuthrimBaseDir(keysBaseDir);
386
403
  const envPaths = getEnvironmentPaths({ baseDir, env, keysBaseDir });
387
404
  // Ensure directory exists with restrictive permissions
388
- const { mkdir } = await import('node:fs/promises');
389
405
  await mkdir(keysDir, { recursive: true, mode: 0o700 });
390
406
  // Save API key with restrictive permissions
391
407
  await writeFile(envPaths.keyFiles.resendApiKey, apiKey.trim());
@@ -475,6 +491,8 @@ export function createApiRoutes() {
475
491
  addProgress('Creating lock file...');
476
492
  const lock = createLockFile(env, resources);
477
493
  const rootDir = findAuthrimBaseDir(process.cwd());
494
+ const envPaths = getEnvironmentPaths({ baseDir: rootDir, env });
495
+ addProgress(`Saving lock.json to ${envPaths.lock} ...`);
478
496
  await saveLockFile(lock, { env, baseDir: rootDir });
479
497
  // Save config.json
480
498
  addProgress('Saving config.json...');
@@ -497,16 +515,32 @@ export function createApiRoutes() {
497
515
  const apiUrl = workersSubdomain
498
516
  ? `https://${env}-ar-router.${workersSubdomain}.workers.dev`
499
517
  : `https://${env}-ar-router.workers.dev`;
500
- const loginUiUrl = `https://${env}-ar-login-ui.pages.dev`;
501
- const adminUiUrl = `https://${env}-ar-admin-ui.pages.dev`;
502
- config.urls = {
503
- api: { auto: apiUrl },
504
- loginUi: { sameAsApi: false, auto: loginUiUrl },
505
- adminUi: { sameAsApi: false, auto: adminUiUrl },
506
- };
518
+ config.urls = buildUrlsConfig({
519
+ env,
520
+ apiDomain: config.urls?.api?.custom || null,
521
+ loginUiDomain: config.urls?.loginUi?.custom || null,
522
+ adminUiDomain: config.urls?.adminUi?.custom || null,
523
+ zoneId: config.urls?.api?.zoneId ?? null,
524
+ customDomainBinding: config.urls?.api?.customDomainBinding ?? false,
525
+ workersSubdomain,
526
+ existingUrls: {
527
+ api: {
528
+ ...config.urls?.api,
529
+ auto: apiUrl,
530
+ },
531
+ loginUi: config.urls?.loginUi,
532
+ adminUi: config.urls?.adminUi,
533
+ },
534
+ });
507
535
  addProgress(`Configured URLs: API=${apiUrl}`);
508
- const envPaths = getEnvironmentPaths({ baseDir: rootDir, env });
536
+ // Explicitly ensure directory exists (defense in depth; saveLockFile also creates it)
537
+ await mkdir(dirname(envPaths.config), { recursive: true });
538
+ addProgress(`Saving config.json to ${envPaths.config} ...`);
509
539
  await writeFile(envPaths.config, JSON.stringify(config, null, 2), 'utf-8');
540
+ const initialUiEnv = buildInitialUiEnvConfig(config);
541
+ if (initialUiEnv) {
542
+ await saveUiEnv(envPaths.uiEnv, initialUiEnv);
543
+ }
510
544
  state.config = config;
511
545
  // Generate wrangler.toml files
512
546
  addProgress('Generating wrangler.toml files...');
@@ -535,11 +569,18 @@ export function createApiRoutes() {
535
569
  }
536
570
  state.status = 'configuring';
537
571
  addProgress('Provisioning complete!');
572
+ addProgress(`📁 Config saved: ${envPaths.config}`);
573
+ addProgress(`📁 Lock saved: ${envPaths.lock}`);
538
574
  return c.json({
539
575
  success: true,
540
576
  resources,
541
577
  lock,
542
578
  config,
579
+ savedPaths: {
580
+ config: envPaths.config,
581
+ lock: envPaths.lock,
582
+ root: envPaths.root,
583
+ },
543
584
  });
544
585
  }
545
586
  catch (error) {
@@ -644,7 +685,11 @@ export function createApiRoutes() {
644
685
  const baseDir = findAuthrimBaseDir(process.cwd());
645
686
  const resolved = resolvePaths({ baseDir, env });
646
687
  let keysDir;
647
- const foundKeys = findKeysDirectory({ env, sourceDir: baseDir, keysBaseDir: process.cwd() });
688
+ const foundKeys = findKeysDirectory({
689
+ env,
690
+ sourceDir: baseDir,
691
+ keysBaseDir: process.cwd(),
692
+ });
648
693
  if (foundKeys) {
649
694
  keysDir = foundKeys.path;
650
695
  }
@@ -659,6 +704,7 @@ export function createApiRoutes() {
659
704
  const secrets = {};
660
705
  const secretFiles = [
661
706
  { file: join(keysDir, 'private.pem'), name: 'PRIVATE_KEY_PEM' },
707
+ { file: join(keysDir, 'public.jwk.json'), name: 'PUBLIC_JWK_JSON' },
662
708
  { file: join(keysDir, 'rp_token_encryption_key.txt'), name: 'RP_TOKEN_ENCRYPTION_KEY' },
663
709
  { file: join(keysDir, 'admin_api_secret.txt'), name: 'ADMIN_API_SECRET' },
664
710
  { file: join(keysDir, 'key_manager_secret.txt'), name: 'KEY_MANAGER_SECRET' },
@@ -802,45 +848,70 @@ export function createApiRoutes() {
802
848
  const apiBaseUrl = cfg?.urls?.api?.custom ||
803
849
  cfg?.urls?.api?.auto ||
804
850
  `https://${env}-ar-router.workers.dev`;
805
- // Get ui.env path for Vite builds (new structure only)
806
- // Always regenerate ui.env from config to ensure sync
807
- let uiEnvPath;
808
- if (resolved.type === 'new') {
809
- uiEnvPath = resolved.paths.uiEnv;
810
- // Regenerate ui.env from config.json to ensure they are in sync
811
- // Detect if custom domains are used (same registrable domain = no need for proxy)
812
- const apiHasCustomDomain = !!cfg?.urls?.api?.custom;
813
- const adminUiHasCustomDomain = !!cfg?.urls?.adminUi?.custom;
814
- const useDirectMode = apiHasCustomDomain && adminUiHasCustomDomain;
815
- const { saveUiEnv } = await import('../core/ui-env.js');
816
- try {
817
- if (useDirectMode) {
818
- await saveUiEnv(uiEnvPath, {
819
- PUBLIC_API_BASE_URL: apiBaseUrl, // Frontend sends directly to backend
820
- API_BACKEND_URL: '', // Proxy disabled
821
- });
822
- addProgress(`ui.env synced (direct mode: ${apiBaseUrl})`);
823
- addProgress(`Custom domains detected - Safari ITP proxy disabled`);
851
+ let loginUiClientId;
852
+ if (cfg?.components?.loginUi && !dryRun) {
853
+ const loginUiUrl = cfg?.urls?.loginUi?.custom ||
854
+ cfg?.urls?.loginUi?.auto ||
855
+ `https://${env}-ar-login-ui.pages.dev`;
856
+ const foundKeys = findKeysDirectory({
857
+ env,
858
+ sourceDir: rootDir,
859
+ keysBaseDir: process.cwd(),
860
+ });
861
+ const adminApiSecretPath = foundKeys
862
+ ? join(foundKeys.path, 'admin_api_secret.txt')
863
+ : resolved.paths.keyFiles.adminApiSecret;
864
+ const { ensureLoginUiClient } = await import('../core/login-ui-client.js');
865
+ const clientResult = await ensureLoginUiClient({
866
+ apiBaseUrl,
867
+ loginUiUrl,
868
+ adminApiSecretPath,
869
+ onProgress: addProgress,
870
+ });
871
+ if (clientResult.success && clientResult.clientId) {
872
+ loginUiClientId = clientResult.clientId;
873
+ if (clientResult.alreadyExists) {
874
+ addProgress(` ✓ Login UI client exists: ${loginUiClientId}`);
824
875
  }
825
876
  else {
826
- await saveUiEnv(uiEnvPath, {
827
- PUBLIC_API_BASE_URL: '', // Empty for proxy mode (same-origin)
828
- API_BACKEND_URL: apiBaseUrl, // Server-side proxy target
829
- });
830
- addProgress(`ui.env synced (proxy mode: ${apiBaseUrl})`);
877
+ addProgress(` ✓ Login UI client created: ${loginUiClientId}`);
831
878
  }
832
879
  }
833
- catch (syncError) {
834
- addProgress(`⚠️ Could not sync ui.env: ${syncError}`);
880
+ else {
881
+ addProgress(` ⚠️ Login UI client creation skipped: ${clientResult.error}`);
835
882
  }
836
883
  }
884
+ const loginUiSettings = resolveUiDeploymentSettings({
885
+ component: 'ar-login-ui',
886
+ config: cfg,
887
+ apiBaseUrl,
888
+ loginUiClientId,
889
+ });
890
+ const adminUiSettings = resolveUiDeploymentSettings({
891
+ component: 'ar-admin-ui',
892
+ config: cfg,
893
+ apiBaseUrl,
894
+ });
837
895
  pagesSummary = await deployAllPages({
838
896
  env,
839
897
  rootDir: resolve(rootDir),
840
898
  dryRun,
841
899
  onProgress: addProgress,
842
900
  apiBaseUrl,
843
- uiEnvPath,
901
+ perComponent: {
902
+ 'ar-login-ui': {
903
+ apiBaseUrl: loginUiSettings.apiBaseUrl,
904
+ runtimeApiBackendUrl: loginUiSettings.runtimeApiBackendUrl,
905
+ uiEnvConfig: loginUiSettings.uiEnv,
906
+ serviceBindingName: loginUiSettings.serviceBindingName,
907
+ },
908
+ 'ar-admin-ui': {
909
+ apiBaseUrl: adminUiSettings.apiBaseUrl,
910
+ runtimeApiBackendUrl: adminUiSettings.runtimeApiBackendUrl,
911
+ uiEnvConfig: adminUiSettings.uiEnv,
912
+ serviceBindingName: adminUiSettings.serviceBindingName,
913
+ },
914
+ },
844
915
  }, {
845
916
  loginUi: cfg?.components?.loginUi ?? true,
846
917
  adminUi: cfg?.components?.adminUi ?? true,
@@ -865,10 +936,13 @@ export function createApiRoutes() {
865
936
  // Update lock file with deployed workers information
866
937
  if (workersSuccess && !dryRun && summary.successCount > 0) {
867
938
  try {
868
- const { loadLockFileAuto, saveLockFile: saveLock } = await import('../core/lock.js');
939
+ const { loadLockFileAuto, saveLockFile: saveLock, AuthrimLockSchema, } = await import('../core/lock.js');
869
940
  const { lock: currentLock, path: lockPath } = await loadLockFileAuto(rootDir, env);
870
- if (currentLock && lockPath) {
871
- const workers = { ...currentLock.workers };
941
+ if (lockPath) {
942
+ // Use existing lock or create minimal one (recovery scenario: lock deleted/missing)
943
+ const now = new Date().toISOString();
944
+ const baseLock = currentLock ?? AuthrimLockSchema.parse({ env, createdAt: now });
945
+ const workers = { ...baseLock.workers };
872
946
  for (const result of summary.results) {
873
947
  if (result.success && result.deployedAt) {
874
948
  workers[result.component] = {
@@ -879,9 +953,9 @@ export function createApiRoutes() {
879
953
  }
880
954
  }
881
955
  const updatedLock = {
882
- ...currentLock,
956
+ ...baseLock,
883
957
  workers,
884
- updatedAt: new Date().toISOString(),
958
+ updatedAt: now,
885
959
  };
886
960
  await saveLock(updatedLock, lockPath);
887
961
  addProgress('Lock file updated with deployment info');
@@ -975,7 +1049,11 @@ export function createApiRoutes() {
975
1049
  const resolved = resolvePaths({ baseDir, env });
976
1050
  const isLegacy = resolved.type === 'legacy';
977
1051
  // Detect actual token path using 3-tier fallback (external → internal → legacy)
978
- const foundKeys = findKeysDirectory({ env, sourceDir: baseDir, keysBaseDir: process.cwd() });
1052
+ const foundKeys = findKeysDirectory({
1053
+ env,
1054
+ sourceDir: baseDir,
1055
+ keysBaseDir: process.cwd(),
1056
+ });
979
1057
  const tokenPath = foundKeys
980
1058
  ? join(foundKeys.path, 'setup_token.txt')
981
1059
  : isLegacy
@@ -1047,7 +1125,7 @@ export function createApiRoutes() {
1047
1125
  return withLock(async () => {
1048
1126
  try {
1049
1127
  const body = await c.req.json();
1050
- const { kvNamespaceId, baseUrl } = body;
1128
+ const { kvNamespaceId, baseUrl, env: envName } = body;
1051
1129
  if (!kvNamespaceId || !/^[a-f0-9]{32}$/i.test(kvNamespaceId)) {
1052
1130
  return c.json({ success: false, error: 'Invalid KV namespace ID' }, 400);
1053
1131
  }
@@ -1068,8 +1146,29 @@ export function createApiRoutes() {
1068
1146
  if (!result.success || !result.token) {
1069
1147
  return c.json({ success: false, error: result.error || 'Failed to generate token' }, 500);
1070
1148
  }
1149
+ // Resolve the best base URL: prefer custom API domain from config
1150
+ let resolvedBaseUrl = baseUrl;
1151
+ if (envName) {
1152
+ try {
1153
+ const baseDir = findAuthrimBaseDir(process.cwd());
1154
+ const resolved = resolvePaths({ baseDir, env: envName });
1155
+ const configPath = resolved.type === 'new'
1156
+ ? resolved.paths.config
1157
+ : resolved.paths.config;
1158
+ if (existsSync(configPath)) {
1159
+ const cfg = JSON.parse(await readFile(configPath, 'utf-8'));
1160
+ const configBaseUrl = cfg?.urls?.api?.custom || cfg?.urls?.api?.auto;
1161
+ if (configBaseUrl) {
1162
+ resolvedBaseUrl = configBaseUrl;
1163
+ }
1164
+ }
1165
+ }
1166
+ catch {
1167
+ // Config not available, use the provided baseUrl
1168
+ }
1169
+ }
1071
1170
  // Construct setup URL
1072
- const cleanBaseUrl = baseUrl.replace(/\/+$/, '');
1171
+ const cleanBaseUrl = resolvedBaseUrl.replace(/\/+$/, '');
1073
1172
  const setupUrl = `${cleanBaseUrl}/admin-init-setup?token=${result.token}`;
1074
1173
  return c.json({
1075
1174
  success: true,
@@ -1455,40 +1554,53 @@ export function createApiRoutes() {
1455
1554
  const apiBaseUrl = cfg?.urls?.api?.custom ||
1456
1555
  cfg?.urls?.api?.auto ||
1457
1556
  `https://${env}-ar-router.workers.dev`;
1458
- // Get ui.env path for new structure
1459
- let uiEnvPath;
1460
- if (resolved.type === 'new') {
1461
- uiEnvPath = resolved.paths.uiEnv;
1462
- // Sync ui.env before build
1463
- const apiHasCustomDomain = !!cfg?.urls?.api?.custom;
1464
- const adminUiHasCustomDomain = !!cfg?.urls?.adminUi?.custom;
1465
- const useDirectMode = apiHasCustomDomain && adminUiHasCustomDomain;
1466
- const { saveUiEnv } = await import('../core/ui-env.js');
1467
- try {
1468
- if (useDirectMode) {
1469
- await saveUiEnv(uiEnvPath, {
1470
- PUBLIC_API_BASE_URL: apiBaseUrl,
1471
- API_BACKEND_URL: '',
1472
- });
1557
+ let loginUiClientId;
1558
+ if (componentName === 'ar-login-ui' && !dryRun) {
1559
+ const loginUiUrl = cfg?.urls?.loginUi?.custom ||
1560
+ cfg?.urls?.loginUi?.auto ||
1561
+ `https://${env}-ar-login-ui.pages.dev`;
1562
+ const foundKeys = findKeysDirectory({
1563
+ env,
1564
+ sourceDir: rootDir,
1565
+ keysBaseDir: process.cwd(),
1566
+ });
1567
+ const adminApiSecretPath = foundKeys
1568
+ ? join(foundKeys.path, 'admin_api_secret.txt')
1569
+ : resolved.paths.keyFiles.adminApiSecret;
1570
+ const { ensureLoginUiClient } = await import('../core/login-ui-client.js');
1571
+ const clientResult = await ensureLoginUiClient({
1572
+ apiBaseUrl,
1573
+ loginUiUrl,
1574
+ adminApiSecretPath,
1575
+ onProgress: addProgress,
1576
+ });
1577
+ if (clientResult.success && clientResult.clientId) {
1578
+ loginUiClientId = clientResult.clientId;
1579
+ if (clientResult.alreadyExists) {
1580
+ addProgress(` ✓ Login UI client exists: ${loginUiClientId}`);
1473
1581
  }
1474
1582
  else {
1475
- await saveUiEnv(uiEnvPath, {
1476
- PUBLIC_API_BASE_URL: '',
1477
- API_BACKEND_URL: apiBaseUrl,
1478
- });
1583
+ addProgress(` ✓ Login UI client created: ${loginUiClientId}`);
1479
1584
  }
1480
- addProgress(`ui.env synced for ${componentName}`);
1481
1585
  }
1482
- catch {
1483
- addProgress(`Warning: Could not sync ui.env`);
1586
+ else {
1587
+ addProgress(` ⚠️ Login UI client creation skipped: ${clientResult.error}`);
1484
1588
  }
1485
1589
  }
1590
+ const uiSettings = resolveUiDeploymentSettings({
1591
+ component: componentName,
1592
+ config: cfg,
1593
+ apiBaseUrl,
1594
+ loginUiClientId,
1595
+ });
1486
1596
  const result = await deployPagesComponent(componentName, {
1487
1597
  env,
1488
1598
  rootDir,
1489
1599
  dryRun,
1490
- apiBaseUrl,
1491
- uiEnvPath,
1600
+ apiBaseUrl: uiSettings.apiBaseUrl,
1601
+ runtimeApiBackendUrl: uiSettings.runtimeApiBackendUrl,
1602
+ uiEnvConfig: uiSettings.uiEnv,
1603
+ serviceBindingName: uiSettings.serviceBindingName,
1492
1604
  onProgress: addProgress,
1493
1605
  });
1494
1606
  if (result.success) {