@ankhorage/infra 3.3.0 → 4.1.0

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 (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +6 -7
  3. package/dist/adapters/minikube/base/index.js +26 -10
  4. package/dist/adapters/minikube/base/index.js.map +1 -1
  5. package/dist/adapters/minikube/index.d.ts.map +1 -1
  6. package/dist/adapters/minikube/index.js +2 -16
  7. package/dist/adapters/minikube/index.js.map +1 -1
  8. package/dist/index.d.ts +0 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/infraValidation.d.ts +1 -0
  13. package/dist/infraValidation.d.ts.map +1 -1
  14. package/dist/infraValidation.js +6 -1
  15. package/dist/infraValidation.js.map +1 -1
  16. package/dist/project/index.d.ts +1 -1
  17. package/dist/project/index.d.ts.map +1 -1
  18. package/dist/project/index.js +1 -1
  19. package/dist/project/index.js.map +1 -1
  20. package/dist/readme-usage.js +5 -0
  21. package/dist/readme-usage.js.map +1 -1
  22. package/dist/runtime.d.ts +6 -0
  23. package/dist/runtime.d.ts.map +1 -1
  24. package/dist/runtime.js +24 -0
  25. package/dist/runtime.js.map +1 -1
  26. package/dist/types.d.ts +1 -1
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/types.js.map +1 -1
  29. package/package.json +7 -7
  30. package/src/adapters/minikube/README.md +9 -1
  31. package/src/adapters/minikube/base/index.test.ts +33 -0
  32. package/src/adapters/minikube/base/index.ts +28 -10
  33. package/src/adapters/minikube/base/portForwardRuntime.test.ts +288 -0
  34. package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +272 -9
  35. package/src/adapters/minikube/index.ts +2 -21
  36. package/src/apiInfrastructure.test.ts +69 -0
  37. package/src/generatedApiLedgerCleanup.test.ts +72 -0
  38. package/src/index.ts +0 -6
  39. package/src/infraApiValidation.test.ts +42 -0
  40. package/src/infraValidation.ts +10 -1
  41. package/src/project/index.test.ts +46 -0
  42. package/src/project/index.ts +1 -0
  43. package/src/readme-usage.ts +5 -0
  44. package/src/runtime.ts +31 -0
  45. package/src/types.ts +1 -1
  46. package/dist/generatedApiDatabase.d.ts +0 -20
  47. package/dist/generatedApiDatabase.d.ts.map +0 -1
  48. package/dist/generatedApiDatabase.js +0 -343
  49. package/dist/generatedApiDatabase.js.map +0 -1
  50. package/src/generatedApiDatabase.test.ts +0 -355
  51. package/src/generatedApiDatabase.ts +0 -558
@@ -0,0 +1,69 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { generateInfrastructure } from './index';
4
+ import { createAppManifest } from './testSupport';
5
+ import type { InfraManifestInput } from './types';
6
+
7
+ const DEPLOYMENT = { target: 'minikube', monitoring: false } as const;
8
+
9
+ describe('canonical API infrastructure', () => {
10
+ test('treats external APIs as infrastructure no-ops', () => {
11
+ const manifest: InfraManifestInput = {
12
+ deployment: DEPLOYMENT,
13
+ modules: [],
14
+ apis: [
15
+ {
16
+ id: 'nutrition',
17
+ origin: 'external',
18
+ protocol: 'rest',
19
+ baseUrl: 'https://api.ankhorage.com/v1/nutrition',
20
+ endpoints: {},
21
+ },
22
+ ],
23
+ };
24
+
25
+ const result = generateInfrastructure(manifest, {
26
+ appManifest: createAppManifest('nutrition', manifest),
27
+ });
28
+
29
+ expect(result.warnings).not.toContainEqual(expect.stringContaining('infra.apis.nutrition'));
30
+ expect(listApiArtifactPaths(result.files)).toEqual([]);
31
+ });
32
+
33
+ test('warns for internal APIs without generating partial API infrastructure', () => {
34
+ const manifest: InfraManifestInput = {
35
+ deployment: DEPLOYMENT,
36
+ modules: [],
37
+ apis: [
38
+ {
39
+ id: 'orders',
40
+ origin: 'internal',
41
+ protocol: 'rest',
42
+ basePath: '/api/orders',
43
+ endpoints: {},
44
+ },
45
+ ],
46
+ };
47
+
48
+ const result = generateInfrastructure(manifest, {
49
+ appManifest: createAppManifest('orders', manifest),
50
+ });
51
+
52
+ expect(result.warnings).toContain(
53
+ 'infra.apis.orders has origin "internal", but internal API provisioning is not supported in Phase 1; no API infrastructure will be generated.',
54
+ );
55
+ expect(listApiArtifactPaths(result.files)).toEqual([]);
56
+ });
57
+ });
58
+
59
+ function listApiArtifactPaths(files: readonly { readonly path: string }[]): readonly string[] {
60
+ return files
61
+ .map((file) => file.path)
62
+ .filter(
63
+ (path) =>
64
+ path.includes('generated-api') ||
65
+ path.includes('generated_api') ||
66
+ path.includes('/api/deployment') ||
67
+ path.includes('/api/service'),
68
+ );
69
+ }
@@ -0,0 +1,72 @@
1
+ import { expect, test } from 'bun:test';
2
+ import { promises as fs } from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+
6
+ import { syncProjectInfrastructure } from './projectInfrastructure';
7
+ import { createAppManifest } from './testSupport';
8
+
9
+ const OBSOLETE_FILES = [
10
+ 'infra/minikube/db/generated-api-resources.json',
11
+ 'infra/minikube/db/migrations/001_generated_api_resources.sql',
12
+ ] as const;
13
+
14
+ test('removes obsolete generated API database artifacts tracked by the infra ledger', async () => {
15
+ const projectPath = await fs.mkdtemp(path.join(os.tmpdir(), 'infra-api-cleanup-'));
16
+
17
+ try {
18
+ await writeObsoleteArtifacts(projectPath);
19
+ const manifest = createAppManifest('shop', {
20
+ deployment: { target: 'minikube', monitoring: false },
21
+ modules: [],
22
+ });
23
+ const result = await syncProjectInfrastructure({
24
+ projectId: 'shop',
25
+ projectPath,
26
+ manifest,
27
+ generateInfrastructureImpl: () => ({
28
+ files: [],
29
+ warnings: [],
30
+ meta: { target: 'minikube', providers: [] },
31
+ dependencies: [],
32
+ }),
33
+ });
34
+
35
+ expect(result.removed).toBe(OBSOLETE_FILES.length);
36
+ for (const filePath of OBSOLETE_FILES) {
37
+ expect(await pathExists(path.join(projectPath, filePath))).toBe(false);
38
+ }
39
+ } finally {
40
+ await fs.rm(projectPath, { force: true, recursive: true });
41
+ }
42
+ });
43
+
44
+ async function writeObsoleteArtifacts(projectPath: string): Promise<void> {
45
+ for (const filePath of OBSOLETE_FILES) {
46
+ const absolutePath = path.join(projectPath, filePath);
47
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
48
+ await fs.writeFile(absolutePath, 'obsolete', 'utf8');
49
+ }
50
+
51
+ await fs.mkdir(path.join(projectPath, '.ankh'), { recursive: true });
52
+ await fs.writeFile(
53
+ path.join(projectPath, '.ankh/infra-ledger.json'),
54
+ JSON.stringify({
55
+ schemaVersion: 1,
56
+ generatedAt: new Date().toISOString(),
57
+ target: 'minikube',
58
+ files: OBSOLETE_FILES,
59
+ warnings: [],
60
+ }),
61
+ 'utf8',
62
+ );
63
+ }
64
+
65
+ async function pathExists(filePath: string): Promise<boolean> {
66
+ try {
67
+ await fs.access(filePath);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
package/src/index.ts CHANGED
@@ -14,12 +14,6 @@ export {
14
14
  resolveAuthRedirectConfiguration,
15
15
  type ResolveAuthRedirectConfigurationInput,
16
16
  } from './authRedirects';
17
- export {
18
- type GeneratedApiDatabaseArtifacts,
19
- type GeneratedApiDatabaseDiagnostic,
20
- type GeneratedApiDatabaseDiagnosticCode,
21
- generateGeneratedApiDatabaseArtifacts,
22
- } from './generatedApiDatabase';
23
17
  export {
24
18
  createInfraSecretStoreAdapter,
25
19
  type CreateInfraSecretStoreAdapterInput,
@@ -0,0 +1,42 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { validateInfraSupport } from './infraValidation';
4
+ import type { InfraManifestInput } from './types';
5
+
6
+ describe('canonical API support validation', () => {
7
+ test('does not warn for external APIs', () => {
8
+ const manifest: InfraManifestInput = {
9
+ modules: [],
10
+ apis: [
11
+ {
12
+ id: 'nutrition',
13
+ origin: 'external',
14
+ protocol: 'rest',
15
+ baseUrl: 'https://api.ankhorage.com/v1/nutrition',
16
+ endpoints: {},
17
+ },
18
+ ],
19
+ };
20
+
21
+ expect(validateInfraSupport(manifest)).toEqual([]);
22
+ });
23
+
24
+ test('reports every internal API as unsupported without treating it as invalid', () => {
25
+ const manifest: InfraManifestInput = {
26
+ modules: [],
27
+ apis: [
28
+ {
29
+ id: 'orders',
30
+ origin: 'internal',
31
+ protocol: 'rest',
32
+ basePath: '/api/orders',
33
+ endpoints: {},
34
+ },
35
+ ],
36
+ };
37
+
38
+ expect(validateInfraSupport(manifest)).toEqual([
39
+ 'infra.apis.orders has origin "internal", but internal API provisioning is not supported in Phase 1; no API infrastructure will be generated.',
40
+ ]);
41
+ });
42
+ });
@@ -9,7 +9,7 @@ import {
9
9
  import type { InfraManifestInput } from './types.js';
10
10
 
11
11
  export function validateInfraSupport(manifest: InfraManifestInput): readonly string[] {
12
- const warnings: string[] = [];
12
+ const warnings: string[] = [...validateInfraApiSupport(manifest)];
13
13
 
14
14
  const deploymentTarget = manifest.deployment?.target;
15
15
  if (deploymentTarget !== undefined && !isSupported(deploymentTarget, DEPLOYMENT_TARGETS)) {
@@ -69,6 +69,15 @@ export function validateInfraSupport(manifest: InfraManifestInput): readonly str
69
69
  return warnings;
70
70
  }
71
71
 
72
+ export function validateInfraApiSupport(manifest: InfraManifestInput): readonly string[] {
73
+ return (manifest.apis ?? [])
74
+ .filter((api) => api.origin === 'internal')
75
+ .map(
76
+ (api) =>
77
+ `infra.apis.${api.id} has origin "internal", but internal API provisioning is not supported in Phase 1; no API infrastructure will be generated.`,
78
+ );
79
+ }
80
+
72
81
  function warningForUnsupportedValue(args: {
73
82
  readonly path: string;
74
83
  readonly value: string;
@@ -5,6 +5,7 @@ import path from 'path';
5
5
 
6
6
  import { createAppManifest } from '../testSupport.js';
7
7
  import {
8
+ ensureProjectInfrastructureRuntime,
8
9
  inspectProjectInfrastructure,
9
10
  readProjectInfrastructureEnvironment,
10
11
  resolveProjectInfrastructureDatabaseUrl,
@@ -78,6 +79,39 @@ describe('@ankhorage/infra/project', () => {
78
79
  });
79
80
  });
80
81
 
82
+ test('ensures the generated runtime port-forward group through the owner API', async () => {
83
+ const projectPath = await createProjectFixture();
84
+ const scriptPath = path.join(projectPath, 'infra', 'minikube', 'scripts', 'port-forward.sh');
85
+ await fs.writeFile(scriptPath, '#!/usr/bin/env bash\nprintf \'%s\' "$*"\n', 'utf8');
86
+
87
+ const result = await ensureProjectInfrastructureRuntime({
88
+ projectId: 'example',
89
+ projectPath,
90
+ target: 'minikube',
91
+ });
92
+
93
+ expect(result).toEqual({ stdout: 'start runtime', stderr: '' });
94
+ });
95
+
96
+ test('reports actionable runtime-forward failures without reconciling infrastructure', async () => {
97
+ const projectPath = await createProjectFixture();
98
+ const scriptPath = path.join(projectPath, 'infra', 'minikube', 'scripts', 'port-forward.sh');
99
+ await fs.writeFile(
100
+ scriptPath,
101
+ '#!/usr/bin/env bash\necho "app: target app/service/app-runtime not found"\nexit 1\n',
102
+ 'utf8',
103
+ );
104
+
105
+ await expectRejectMessage(
106
+ ensureProjectInfrastructureRuntime({
107
+ projectId: 'example',
108
+ projectPath,
109
+ target: 'minikube',
110
+ }),
111
+ "Failed to ensure infrastructure runtime for project 'example': app: target app/service/app-runtime not found",
112
+ );
113
+ });
114
+
81
115
  test('reads only requested runtime environment values from the generated fallback', async () => {
82
116
  const projectPath = await createProjectFixture();
83
117
  const result = await readProjectInfrastructureEnvironment({
@@ -147,3 +181,15 @@ describe('@ankhorage/infra/project', () => {
147
181
  });
148
182
  });
149
183
  });
184
+
185
+ async function expectRejectMessage(
186
+ promise: Promise<unknown>,
187
+ expectedMessage: string,
188
+ ): Promise<void> {
189
+ try {
190
+ await promise;
191
+ throw new Error('Expected operation to reject.');
192
+ } catch (error) {
193
+ expect(error instanceof Error ? error.message : String(error)).toContain(expectedMessage);
194
+ }
195
+ }
@@ -12,6 +12,7 @@ export {
12
12
  syncProjectInfrastructure,
13
13
  } from '../projectInfrastructure.js';
14
14
  export {
15
+ ensureProjectInfrastructureRuntime,
15
16
  type InfraLifecycleScript,
16
17
  type InfraPortForwardInfo,
17
18
  InfraScriptExecutionError,
@@ -29,6 +29,11 @@ import { runCli } from './cli/bin.js';
29
29
  * operations accept an explicit project path and do not require Studio or a
30
30
  * particular workspace layout.
31
31
  *
32
+ * Running applications can repair their provider-aware host endpoints through
33
+ * `ensureProjectInfrastructureRuntime()` from `@ankhorage/infra/project`. This
34
+ * operation only ensures the generated runtime port-forward group; it does not
35
+ * deploy, bootstrap, or reconcile infrastructure.
36
+ *
32
37
  * @usage
33
38
  */
34
39
  await runCli(['--help']);
package/src/runtime.ts CHANGED
@@ -85,6 +85,37 @@ export async function resolveProjectInfrastructurePortForward(args: {
85
85
  };
86
86
  }
87
87
 
88
+ /** Ensures the generated host forwards required by a running application are active. */
89
+ export async function ensureProjectInfrastructureRuntime(args: {
90
+ readonly projectId: string;
91
+ readonly projectPath: string;
92
+ readonly target: string;
93
+ }): Promise<InfraScriptOutput> {
94
+ try {
95
+ return await runProjectInfrastructureLifecycle({
96
+ args: ['start', 'runtime'],
97
+ projectId: args.projectId,
98
+ projectPath: args.projectPath,
99
+ script: 'port-forward',
100
+ target: args.target,
101
+ });
102
+ } catch (error) {
103
+ if (!(error instanceof InfraScriptExecutionError)) {
104
+ throw error;
105
+ }
106
+
107
+ const detail = error.stderr.trim() || error.stdout.trim();
108
+ throw new InfraScriptExecutionError({
109
+ exitCode: error.exitCode,
110
+ message: `Failed to ensure infrastructure runtime for project '${args.projectId}'${
111
+ detail.length > 0 ? `: ${detail}` : '.'
112
+ }`,
113
+ stderr: error.stderr,
114
+ stdout: error.stdout,
115
+ });
116
+ }
117
+ }
118
+
88
119
  export async function runProjectInfraScript(args: {
89
120
  readonly context: InfraCommandContext;
90
121
  readonly projectId: string;
package/src/types.ts CHANGED
@@ -37,7 +37,7 @@ export interface InfrastructureGenerationOptions {
37
37
  */
38
38
  readonly appManifest?: Pick<
39
39
  AppManifest,
40
- 'deploy' | 'generatedApis' | 'infra' | 'metadata' | 'navigator' | 'screens' | 'settings'
40
+ 'deploy' | 'infra' | 'metadata' | 'navigator' | 'screens' | 'settings'
41
41
  >;
42
42
  }
43
43
 
@@ -1,20 +0,0 @@
1
- import type { GeneratedApiRegistry } from '@ankhorage/contracts';
2
- import type { GeneratedInfrastructureFile } from './types';
3
- export type GeneratedApiDatabaseDiagnosticCode = 'duplicate-field' | 'duplicate-resource-id' | 'duplicate-target' | 'invalid-default' | 'invalid-identifier' | 'invalid-primary-key' | 'invalid-seed' | 'registry-id-mismatch' | 'unsupported-policy' | 'unsupported-provider';
4
- export interface GeneratedApiDatabaseDiagnostic {
5
- readonly code: GeneratedApiDatabaseDiagnosticCode;
6
- readonly severity: 'error' | 'warning';
7
- readonly apiId: string;
8
- readonly resourceId?: string;
9
- readonly path?: string;
10
- readonly message: string;
11
- }
12
- export interface GeneratedApiDatabaseArtifacts {
13
- readonly files: readonly GeneratedInfrastructureFile[];
14
- readonly diagnostics: readonly GeneratedApiDatabaseDiagnostic[];
15
- }
16
- export declare function generateGeneratedApiDatabaseArtifacts(args: {
17
- readonly generatedApis: GeneratedApiRegistry | undefined;
18
- readonly databaseProvider: string | undefined;
19
- }): GeneratedApiDatabaseArtifacts;
20
- //# sourceMappingURL=generatedApiDatabase.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generatedApiDatabase.d.ts","sourceRoot":"","sources":["../src/generatedApiDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,oBAAoB,EAGrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,kCAAkC,GAC1C,iBAAiB,GACjB,uBAAuB,GACvB,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,GACpB,qBAAqB,GACrB,cAAc,GACd,sBAAsB,GACtB,oBAAoB,GACpB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,IAAI,EAAE,kCAAkC,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,2BAA2B,EAAE,CAAC;IACvD,QAAQ,CAAC,WAAW,EAAE,SAAS,8BAA8B,EAAE,CAAC;CACjE;AAmBD,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC1D,QAAQ,CAAC,aAAa,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,GAAG,6BAA6B,CA2ChC"}