@ankhorage/infra 0.4.0 → 0.4.1

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/src/index.test.ts CHANGED
@@ -45,7 +45,7 @@ describe('generateInfrastructure', () => {
45
45
  plugins: [],
46
46
  };
47
47
 
48
- const result = generateInfrastructure(manifest);
48
+ const result = generateInfrastructure(manifest, { appManifest: createAppManifest('alpha') });
49
49
  const paths = result.files.map((f) => f.path);
50
50
 
51
51
  expect(result.meta.target).toBe('minikube');
@@ -147,8 +147,8 @@ describe('generateInfrastructure', () => {
147
147
  expect(envExample?.content).toContain('APP_SOURCE_DIR=');
148
148
  expect(envExample?.content).toContain('APP_WEB_EXPORT_DIR=.ankh/web-export');
149
149
  expect(envExample?.content).toContain('SUPABASE_PROJECT_DIR=');
150
- expect(envExample?.content).toContain('SUPABASE_LOCAL_PORT_BASE=59120');
151
- expect(envExample?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT=59125');
150
+ expect(envExample?.content).toContain('SUPABASE_LOCAL_PORT_BASE=55540');
151
+ expect(envExample?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT=55545');
152
152
  expect(envExample?.content).toContain('APP_IMAGE=ankh/alpha-local:dev');
153
153
  expect(envExample?.content).toContain('APP_IMAGE_SYNC_STRATEGY=docker-load');
154
154
  expect(envExample?.content).toContain('AUTH_RUNTIME_MODE=local');
@@ -168,7 +168,8 @@ describe('generateInfrastructure', () => {
168
168
 
169
169
  const minikubeReadme = result.files.find((f) => f.path === 'infra/minikube/README.md');
170
170
  expect(minikubeReadme?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT');
171
- expect(minikubeReadme?.content).toContain('59125');
171
+ expect(minikubeReadme?.content).toContain('55545');
172
+ expect(minikubeReadme?.content).toContain('Supabase local project identity: `alpha`');
172
173
  expect(minikubeReadme?.content).toContain('$APP_SOURCE_DIR/.env.local');
173
174
  expect(minikubeReadme?.content).toContain('apps/card/.env.local');
174
175
 
@@ -333,7 +334,10 @@ describe('generateInfrastructure', () => {
333
334
  plugins: [],
334
335
  };
335
336
 
336
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
337
+ const result = generateInfrastructure(manifest, {
338
+ namespaceHint: 'shop',
339
+ appManifest: createAppManifest('shop'),
340
+ });
337
341
 
338
342
  expect(result.warnings).toContain(
339
343
  'Storage buckets are configured but not created automatically yet. Ensure buckets exist in Supabase Storage: assets,uploads.',
@@ -380,7 +384,10 @@ describe('generateInfrastructure', () => {
380
384
  plugins: [],
381
385
  };
382
386
 
383
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
387
+ const result = generateInfrastructure(manifest, {
388
+ namespaceHint: 'shop',
389
+ appManifest: createAppManifest('shop'),
390
+ });
384
391
 
385
392
  expect(result.files.map((f) => f.path)).toContain(
386
393
  'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
@@ -425,7 +432,10 @@ describe('generateInfrastructure', () => {
425
432
  plugins: [],
426
433
  };
427
434
 
428
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
435
+ const result = generateInfrastructure(manifest, {
436
+ namespaceHint: 'shop',
437
+ appManifest: createAppManifest('shop'),
438
+ });
429
439
  expect(result.warnings).toContain(
430
440
  'Storage buckets are empty after normalization; no storage artifacts generated.',
431
441
  );
@@ -451,7 +461,10 @@ describe('generateInfrastructure', () => {
451
461
  plugins: [],
452
462
  };
453
463
 
454
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
464
+ const result = generateInfrastructure(manifest, {
465
+ namespaceHint: 'shop',
466
+ appManifest: createAppManifest('shop'),
467
+ });
455
468
  expect(result.warnings).toContain(
456
469
  'Storage buckets are empty after normalization; no storage artifacts generated.',
457
470
  );
@@ -473,7 +486,10 @@ describe('generateInfrastructure', () => {
473
486
  plugins: [],
474
487
  };
475
488
 
476
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
489
+ const result = generateInfrastructure(manifest, {
490
+ namespaceHint: 'shop',
491
+ appManifest: createAppManifest('shop'),
492
+ });
477
493
  expect(result.warnings).toContain(
478
494
  'Storage provider "auto" could not be resolved for minikube; no storage artifacts generated.',
479
495
  );
@@ -645,9 +661,98 @@ describe('generateInfrastructure', () => {
645
661
  plugins: [],
646
662
  };
647
663
 
648
- expect(() => generateInfrastructure(manifest)).toThrow(
649
- 'Unsupported authorization engine for minikube adapter: opa',
664
+ expect(() =>
665
+ generateInfrastructure(manifest, { appManifest: createAppManifest('shop') }),
666
+ ).toThrow('Unsupported authorization engine for minikube adapter: opa');
667
+ });
668
+
669
+ test('requires canonical app slug for local Supabase infrastructure', () => {
670
+ const manifest: InfraManifestInput = {
671
+ deployment: {
672
+ target: 'minikube',
673
+ monitoring: false,
674
+ },
675
+ database: {
676
+ provider: 'supabase',
677
+ tier: 'dev',
678
+ },
679
+ plugins: [],
680
+ };
681
+
682
+ expect(() => generateInfrastructure(manifest, { namespaceHint: 'shop' })).toThrow(
683
+ 'Cannot generate local Supabase infrastructure: appManifest.metadata.slug is required.',
684
+ );
685
+ expect(() =>
686
+ generateInfrastructure(manifest, {
687
+ namespaceHint: 'shop',
688
+ appManifest: createAppManifest('Scanner App'),
689
+ }),
690
+ ).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
691
+
692
+ const fortyCharacterSlug = 'a'.repeat(40);
693
+ const fortyCharacterResult = generateInfrastructure(manifest, {
694
+ namespaceHint: 'shop',
695
+ appManifest: createAppManifest(fortyCharacterSlug),
696
+ });
697
+ const fortyCharacterScript = fortyCharacterResult.files.find(
698
+ (f) => f.path === 'infra/minikube/scripts/supabase-local-env.sh',
650
699
  );
700
+ expect(fortyCharacterScript?.content).toContain(
701
+ `EXPECTED_SUPABASE_PROJECT_ID="${fortyCharacterSlug}"`,
702
+ );
703
+
704
+ expect(() =>
705
+ generateInfrastructure(manifest, {
706
+ namespaceHint: 'shop',
707
+ appManifest: createAppManifest('a'.repeat(41)),
708
+ }),
709
+ ).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
710
+
711
+ expect(() =>
712
+ generateInfrastructure(manifest, {
713
+ namespaceHint: 'shop',
714
+ appManifest: createAppManifest('very-long-project-name-with-identical-prefix-alpha'),
715
+ }),
716
+ ).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
717
+ expect(() =>
718
+ generateInfrastructure(manifest, {
719
+ namespaceHint: 'shop',
720
+ appManifest: createAppManifest('very-long-project-name-with-identical-prefix-beta'),
721
+ }),
722
+ ).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
723
+ });
724
+
725
+ test('keeps Supabase project identity independent from domain-derived namespace', () => {
726
+ const manifest: InfraManifestInput = {
727
+ deployment: {
728
+ target: 'minikube',
729
+ monitoring: false,
730
+ },
731
+ database: {
732
+ provider: 'supabase',
733
+ tier: 'dev',
734
+ },
735
+ networking: {
736
+ domain: 'local.example.test',
737
+ cdn: false,
738
+ },
739
+ plugins: [],
740
+ };
741
+
742
+ const result = generateInfrastructure(manifest, {
743
+ namespaceHint: 'ignored',
744
+ appManifest: createAppManifest('scanner'),
745
+ });
746
+ const namespaceFile = result.files.find((f) => f.path === 'infra/minikube/k8s/namespace.yaml');
747
+ const envExample = result.files.find((f) => f.path === 'infra/minikube/.env.example');
748
+ const script = result.files.find(
749
+ (f) => f.path === 'infra/minikube/scripts/supabase-local-env.sh',
750
+ );
751
+
752
+ expect(namespaceFile?.content).toContain('name: local-example-test');
753
+ expect(envExample?.content).toContain('SUPABASE_LOCAL_PORT_BASE=64020');
754
+ expect(envExample?.content).not.toContain('SUPABASE_LOCAL_PORT_BASE=61550');
755
+ expect(script?.content).toContain('EXPECTED_SUPABASE_PROJECT_ID="scanner"');
651
756
  });
652
757
 
653
758
  test('uses namespace hint when domain is not provided', () => {
@@ -671,7 +776,10 @@ describe('generateInfrastructure', () => {
671
776
  plugins: [],
672
777
  };
673
778
 
674
- const result = generateInfrastructure(manifest, { namespaceHint: 'shop' });
779
+ const result = generateInfrastructure(manifest, {
780
+ namespaceHint: 'shop',
781
+ appManifest: createAppManifest('shop'),
782
+ });
675
783
  const namespaceFile = result.files.find((f) => f.path === 'infra/minikube/k8s/namespace.yaml');
676
784
  const upScript = result.files.find((f) => f.path === 'infra/minikube/scripts/up.sh');
677
785
  const statusScript = result.files.find((f) => f.path === 'infra/minikube/scripts/status.sh');
@@ -688,3 +796,34 @@ describe('generateInfrastructure', () => {
688
796
  expect(statusScript?.content).toContain('- secret/supabase-auth-secrets: present');
689
797
  });
690
798
  });
799
+
800
+ function createAppManifest(
801
+ slug: string,
802
+ ): Pick<AppManifest, 'metadata' | 'navigator' | 'screens' | 'settings'> {
803
+ return {
804
+ metadata: {
805
+ name: slug,
806
+ slug,
807
+ version: '1.0.0',
808
+ themeId: 'default',
809
+ },
810
+ navigator: {
811
+ type: 'stack',
812
+ routes: [],
813
+ },
814
+ screens: {},
815
+ settings: {
816
+ localization: {
817
+ defaultLocale: 'en',
818
+ locales: ['en'],
819
+ },
820
+ authFlow: {
821
+ signInRoute: '/sign-in',
822
+ signUpRoute: '/sign-up',
823
+ signOutRoute: '/sign-out',
824
+ unauthorizedRoute: '/sign-in',
825
+ postSignInRoute: '/',
826
+ },
827
+ },
828
+ };
829
+ }