@ankhorage/infra 5.0.0 → 5.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ankhorage/infra
2
2
 
3
+ ## 5.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0771f29: Add reproducible local Minikube + Supabase Phase 8 parity acceptance against the released provider-neutral adapters and runtime fixes.
8
+
9
+ ## 5.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 51a60f2: Expose a typed project lifecycle that applies the canonical Infra use cases while preserving environment-scoped state and generated artifact ownership.
14
+
3
15
  ## 5.0.0
4
16
 
5
17
  ### Major Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # INFRA
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v4.1.12](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v5.0.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Executable infra provider and standalone CLI for Ankhorage project workflows.
9
9
 
@@ -0,0 +1,4 @@
1
+ import type { CreateProjectInfraLifecycleOptions, ProjectInfraLifecycle } from '../../../types/infraProject.js';
2
+ /*** Create the typed project-facing Infra lifecycle backed by canonical use cases and ownership state. */
3
+ export declare function createProjectInfraLifecycle(options?: CreateProjectInfraLifecycleOptions): ProjectInfraLifecycle;
4
+ //# sourceMappingURL=createProjectInfraLifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createProjectInfraLifecycle.d.ts","sourceRoot":"","sources":["../../../../src/features/environment-lifecycle/composition/createProjectInfraLifecycle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,kCAAkC,EAClC,qBAAqB,EAEtB,MAAM,gCAAgC,CAAC;AAGxC,0GAA0G;AAC1G,wBAAgB,2BAA2B,CACzC,OAAO,GAAE,kCAAuC,GAC/C,qBAAqB,CAiFvB"}
@@ -0,0 +1,123 @@
1
+ import { INFRA_PACKAGE_VERSION } from '../../../constants.js';
2
+ import { createInfraCommandServices } from './createInfraCommandServices.js';
3
+ /*** Create the typed project-facing Infra lifecycle backed by canonical use cases and ownership state. */
4
+ export function createProjectInfraLifecycle(options = {}) {
5
+ const services = createInfraCommandServices(options.services);
6
+ return {
7
+ async validateAsync(request) {
8
+ const prepared = await prepareProjectOperationAsync(request, services);
9
+ return services.operations.validate(prepared.operation, prepared.dependencies);
10
+ },
11
+ async planAsync(request) {
12
+ const prepared = await prepareProjectOperationAsync(request, services);
13
+ return services.operations.plan(prepared.operation, prepared.dependencies);
14
+ },
15
+ async generateAsync(request) {
16
+ const prepared = await prepareProjectOperationAsync(request, services);
17
+ const result = await services.operations.generate(prepared.operation, prepared.dependencies);
18
+ if (!result.ok)
19
+ return result;
20
+ await services.writeArtifacts(request.projectPath, result.value.artifacts, prepared.state?.ledger);
21
+ await services.writeState(request.projectPath, {
22
+ schemaVersion: 1,
23
+ desired: prepared.desired,
24
+ ledger: result.value.ledger,
25
+ });
26
+ return result;
27
+ },
28
+ async upAsync(request) {
29
+ const prepared = await prepareProjectOperationAsync(request, services);
30
+ const result = await services.operations.up(prepared.operation, prepared.dependencies);
31
+ if (!result.ok)
32
+ return result;
33
+ await services.writeState(request.projectPath, {
34
+ schemaVersion: 1,
35
+ desired: prepared.desired,
36
+ ledger: result.value.ledger,
37
+ });
38
+ return result;
39
+ },
40
+ async statusAsync(request) {
41
+ const prepared = await prepareProjectOperationAsync(request, services);
42
+ return services.operations.status(prepared.operation, prepared.dependencies);
43
+ },
44
+ async outputsAsync(request) {
45
+ const prepared = await prepareProjectOperationAsync(request, services);
46
+ return services.operations.outputs(prepared.operation);
47
+ },
48
+ async downAsync(request) {
49
+ const prepared = await prepareProjectOperationAsync(request, services);
50
+ const result = await services.operations.down(prepared.operation, prepared.dependencies);
51
+ if (!result.ok)
52
+ return result;
53
+ await services.writeState(request.projectPath, {
54
+ schemaVersion: 1,
55
+ desired: prepared.desired,
56
+ ledger: result.value.ledger,
57
+ });
58
+ return result;
59
+ },
60
+ async destroyAsync(request) {
61
+ const prepared = await prepareProjectOperationAsync(request, services, true);
62
+ const result = await services.operations.destroy({
63
+ ...prepared.operation,
64
+ confirmation: request.confirmation,
65
+ persistence: request.persistence,
66
+ }, prepared.dependencies);
67
+ if (!result.ok)
68
+ return result;
69
+ await services.writeArtifacts(request.projectPath, [], prepared.state?.ledger);
70
+ if (result.value.ledger === null)
71
+ await services.removeState(request.projectPath, request.environment);
72
+ else {
73
+ await services.writeState(request.projectPath, {
74
+ schemaVersion: 1,
75
+ desired: prepared.desired,
76
+ ledger: result.value.ledger,
77
+ });
78
+ }
79
+ return result;
80
+ },
81
+ };
82
+ }
83
+ /*** Resolve one explicit project/environment request with its prior ownership state and dependencies. */
84
+ async function prepareProjectOperationAsync(request, services, useStoredDesired = false) {
85
+ const state = await services.readState(request.projectPath, request.environment);
86
+ const currentDesired = readEnvironment(request.manifest, request.environment);
87
+ const desired = useStoredDesired && state !== null ? state.desired : currentDesired;
88
+ if (desired === undefined) {
89
+ throw new Error(`Infra environment "${request.environment}" is not configured.`);
90
+ }
91
+ const context = {
92
+ cwd: request.projectPath,
93
+ env: request.executionEnvironment ?? process.env,
94
+ version: INFRA_PACKAGE_VERSION,
95
+ writeStdout: () => undefined,
96
+ writeStderr: () => undefined,
97
+ };
98
+ return {
99
+ state,
100
+ desired,
101
+ operation: {
102
+ projectId: request.projectId,
103
+ manifest: replaceEnvironment(request.manifest, request.environment, desired),
104
+ environment: request.environment,
105
+ ...(state === null ? {} : { previous: state.ledger, previousDesired: state.desired }),
106
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
107
+ },
108
+ dependencies: services.createDependencies(context),
109
+ };
110
+ }
111
+ /*** Read one exact environment without inventing a fallback. */
112
+ function readEnvironment(manifest, environment) {
113
+ if (environment === 'local')
114
+ return manifest.environments.local;
115
+ if (environment === 'preview')
116
+ return manifest.environments.preview;
117
+ return manifest.environments.production;
118
+ }
119
+ /*** Replace one selected environment while preserving all other manifest state. */
120
+ function replaceEnvironment(manifest, environment, desired) {
121
+ return { ...manifest, environments: { ...manifest.environments, [environment]: desired } };
122
+ }
123
+ //# sourceMappingURL=createProjectInfraLifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createProjectInfraLifecycle.js","sourceRoot":"","sources":["../../../../src/features/environment-lifecycle/composition/createProjectInfraLifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InfraEnvironmentSpec, InfraManifest } from '@ankhorage/contracts/infra';\n\nimport { INFRA_PACKAGE_VERSION } from '../../../constants.js';\nimport type { InfraOperationRequest, InfraStoredState } from '../../../types/infraOrchestration.js';\nimport type {\n CreateProjectInfraLifecycleOptions,\n ProjectInfraLifecycle,\n ProjectInfraOperationRequest,\n} from '../../../types/infraProject.js';\nimport { createInfraCommandServices } from './createInfraCommandServices.js';\n\n/*** Create the typed project-facing Infra lifecycle backed by canonical use cases and ownership state. */\nexport function createProjectInfraLifecycle(\n options: CreateProjectInfraLifecycleOptions = {},\n): ProjectInfraLifecycle {\n const services = createInfraCommandServices(options.services);\n return {\n async validateAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n return services.operations.validate(prepared.operation, prepared.dependencies);\n },\n async planAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n return services.operations.plan(prepared.operation, prepared.dependencies);\n },\n async generateAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n const result = await services.operations.generate(prepared.operation, prepared.dependencies);\n if (!result.ok) return result;\n await services.writeArtifacts(\n request.projectPath,\n result.value.artifacts,\n prepared.state?.ledger,\n );\n await services.writeState(request.projectPath, {\n schemaVersion: 1,\n desired: prepared.desired,\n ledger: result.value.ledger,\n });\n return result;\n },\n async upAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n const result = await services.operations.up(prepared.operation, prepared.dependencies);\n if (!result.ok) return result;\n await services.writeState(request.projectPath, {\n schemaVersion: 1,\n desired: prepared.desired,\n ledger: result.value.ledger,\n });\n return result;\n },\n async statusAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n return services.operations.status(prepared.operation, prepared.dependencies);\n },\n async outputsAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n return services.operations.outputs(prepared.operation);\n },\n async downAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services);\n const result = await services.operations.down(prepared.operation, prepared.dependencies);\n if (!result.ok) return result;\n await services.writeState(request.projectPath, {\n schemaVersion: 1,\n desired: prepared.desired,\n ledger: result.value.ledger,\n });\n return result;\n },\n async destroyAsync(request) {\n const prepared = await prepareProjectOperationAsync(request, services, true);\n const result = await services.operations.destroy(\n {\n ...prepared.operation,\n confirmation: request.confirmation,\n persistence: request.persistence,\n },\n prepared.dependencies,\n );\n if (!result.ok) return result;\n await services.writeArtifacts(request.projectPath, [], prepared.state?.ledger);\n if (result.value.ledger === null)\n await services.removeState(request.projectPath, request.environment);\n else {\n await services.writeState(request.projectPath, {\n schemaVersion: 1,\n desired: prepared.desired,\n ledger: result.value.ledger,\n });\n }\n return result;\n },\n };\n}\n\ninterface PreparedProjectOperation {\n readonly operation: InfraOperationRequest;\n readonly desired: InfraEnvironmentSpec;\n readonly state: InfraStoredState | null;\n readonly dependencies: ReturnType<\n ReturnType<typeof createInfraCommandServices>['createDependencies']\n >;\n}\n\n/*** Resolve one explicit project/environment request with its prior ownership state and dependencies. */\nasync function prepareProjectOperationAsync(\n request: ProjectInfraOperationRequest,\n services: ReturnType<typeof createInfraCommandServices>,\n useStoredDesired = false,\n): Promise<PreparedProjectOperation> {\n const state = await services.readState(request.projectPath, request.environment);\n const currentDesired = readEnvironment(request.manifest, request.environment);\n const desired = useStoredDesired && state !== null ? state.desired : currentDesired;\n if (desired === undefined) {\n throw new Error(`Infra environment \"${request.environment}\" is not configured.`);\n }\n const context = {\n cwd: request.projectPath,\n env: request.executionEnvironment ?? process.env,\n version: INFRA_PACKAGE_VERSION,\n writeStdout: () => undefined,\n writeStderr: () => undefined,\n };\n return {\n state,\n desired,\n operation: {\n projectId: request.projectId,\n manifest: replaceEnvironment(request.manifest, request.environment, desired),\n environment: request.environment,\n ...(state === null ? {} : { previous: state.ledger, previousDesired: state.desired }),\n ...(request.signal === undefined ? {} : { signal: request.signal }),\n },\n dependencies: services.createDependencies(context),\n };\n}\n\n/*** Read one exact environment without inventing a fallback. */\nfunction readEnvironment(\n manifest: InfraManifest,\n environment: ProjectInfraOperationRequest['environment'],\n): InfraEnvironmentSpec | undefined {\n if (environment === 'local') return manifest.environments.local;\n if (environment === 'preview') return manifest.environments.preview;\n return manifest.environments.production;\n}\n\n/*** Replace one selected environment while preserving all other manifest state. */\nfunction replaceEnvironment(\n manifest: InfraManifest,\n environment: ProjectInfraOperationRequest['environment'],\n desired: InfraEnvironmentSpec,\n): InfraManifest {\n return { ...manifest, environments: { ...manifest.environments, [environment]: desired } };\n}\n"]}
@@ -3,6 +3,7 @@ export { removeStoredInfraStateAsync } from '../features/environment-lifecycle/a
3
3
  export { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';
4
4
  export { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';
5
5
  export { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';
6
+ export { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';
6
7
  export { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';
7
- export type { InfraArtifactWriteResult, ResolvedInfraProject } from '../types/infraProject.js';
8
+ export type { CreateProjectInfraLifecycleOptions, InfraArtifactWriteResult, ProjectInfraDestroyRequest, ProjectInfraLifecycle, ProjectInfraOperationRequest, ResolvedInfraProject, } from '../types/infraProject.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC;AACnG,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC;AACnG,YAAY,EACV,kCAAkC,EAClC,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,0BAA0B,CAAC"}
@@ -3,5 +3,6 @@ export { removeStoredInfraStateAsync } from '../features/environment-lifecycle/a
3
3
  export { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';
4
4
  export { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';
5
5
  export { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';
6
+ export { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';
6
7
  export { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC","sourcesContent":["export { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nexport { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nexport { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';\nexport { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nexport { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nexport { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';\nexport type { InfraArtifactWriteResult, ResolvedInfraProject } from '../types/infraProject.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC","sourcesContent":["export { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nexport { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nexport { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';\nexport { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nexport { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nexport { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';\nexport { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';\nexport type {\n CreateProjectInfraLifecycleOptions,\n InfraArtifactWriteResult,\n ProjectInfraDestroyRequest,\n ProjectInfraLifecycle,\n ProjectInfraOperationRequest,\n ResolvedInfraProject,\n} from '../types/infraProject.js';\n"]}
@@ -1,4 +1,8 @@
1
1
  import type { AppManifest } from '@ankhorage/contracts';
2
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
3
+ import type { InfraDestroyRequest, InfraManifest } from '@ankhorage/contracts/infra';
4
+ import type { InfraCommandServices } from './infraCli.js';
5
+ import type { InfraDestroyOperationResult, InfraDownOperationResult, InfraGenerateOperationResult, InfraOutputsOperationResult, InfraPlanOperationResult, InfraStatusOperationResult, InfraUpOperationResult, InfraValidateOperationResult } from './infraOrchestration.js';
2
6
  export interface InfraArtifactWriteResult {
3
7
  readonly written: number;
4
8
  readonly removed: number;
@@ -11,4 +15,29 @@ export interface ResolvedInfraProject {
11
15
  readonly projectPath: string;
12
16
  readonly workspaceRoot: string;
13
17
  }
18
+ export interface ProjectInfraOperationRequest {
19
+ readonly projectId: string;
20
+ readonly projectPath: string;
21
+ readonly manifest: InfraManifest;
22
+ readonly environment: AppEnvironmentId;
23
+ readonly executionEnvironment?: Readonly<Record<string, string | undefined>>;
24
+ readonly signal?: AbortSignal;
25
+ }
26
+ export interface ProjectInfraDestroyRequest extends ProjectInfraOperationRequest {
27
+ readonly confirmation: InfraDestroyRequest['confirmation'];
28
+ readonly persistence: InfraDestroyRequest['persistence'];
29
+ }
30
+ export interface ProjectInfraLifecycle {
31
+ validateAsync(request: ProjectInfraOperationRequest): Promise<InfraValidateOperationResult>;
32
+ planAsync(request: ProjectInfraOperationRequest): Promise<InfraPlanOperationResult>;
33
+ generateAsync(request: ProjectInfraOperationRequest): Promise<InfraGenerateOperationResult>;
34
+ upAsync(request: ProjectInfraOperationRequest): Promise<InfraUpOperationResult>;
35
+ statusAsync(request: ProjectInfraOperationRequest): Promise<InfraStatusOperationResult>;
36
+ outputsAsync(request: ProjectInfraOperationRequest): Promise<InfraOutputsOperationResult>;
37
+ downAsync(request: ProjectInfraOperationRequest): Promise<InfraDownOperationResult>;
38
+ destroyAsync(request: ProjectInfraDestroyRequest): Promise<InfraDestroyOperationResult>;
39
+ }
40
+ export interface CreateProjectInfraLifecycleOptions {
41
+ readonly services?: Partial<InfraCommandServices>;
42
+ }
14
43
  //# sourceMappingURL=infraProject.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"infraProject.d.ts","sourceRoot":"","sources":["../../src/types/infraProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC"}
1
+ {"version":3,"file":"infraProject.d.ts","sourceRoot":"","sources":["../../src/types/infraProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAErF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EACV,2BAA2B,EAC3B,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC7B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA2B,SAAQ,4BAA4B;IAC9E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAC;IAC3D,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC5F,SAAS,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACpF,aAAa,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC5F,OAAO,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAChF,WAAW,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACxF,YAAY,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC1F,SAAS,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACpF,YAAY,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzF;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"infraProject.js","sourceRoot":"","sources":["../../src/types/infraProject.ts"],"names":[],"mappings":"","sourcesContent":["import type { AppManifest } from '@ankhorage/contracts';\n\nexport interface InfraArtifactWriteResult {\n readonly written: number;\n readonly removed: number;\n}\n\nexport interface ResolvedInfraProject {\n readonly appsRoot: string;\n readonly manifest: AppManifest;\n readonly manifestPath: string;\n readonly projectId: string;\n readonly projectPath: string;\n readonly workspaceRoot: string;\n}\n"]}
1
+ {"version":3,"file":"infraProject.js","sourceRoot":"","sources":["../../src/types/infraProject.ts"],"names":[],"mappings":"","sourcesContent":["import type { AppManifest } from '@ankhorage/contracts';\nimport type { AppEnvironmentId } from '@ankhorage/contracts/environments';\nimport type { InfraDestroyRequest, InfraManifest } from '@ankhorage/contracts/infra';\n\nimport type { InfraCommandServices } from './infraCli.js';\nimport type {\n InfraDestroyOperationResult,\n InfraDownOperationResult,\n InfraGenerateOperationResult,\n InfraOutputsOperationResult,\n InfraPlanOperationResult,\n InfraStatusOperationResult,\n InfraUpOperationResult,\n InfraValidateOperationResult,\n} from './infraOrchestration.js';\n\nexport interface InfraArtifactWriteResult {\n readonly written: number;\n readonly removed: number;\n}\n\nexport interface ResolvedInfraProject {\n readonly appsRoot: string;\n readonly manifest: AppManifest;\n readonly manifestPath: string;\n readonly projectId: string;\n readonly projectPath: string;\n readonly workspaceRoot: string;\n}\n\nexport interface ProjectInfraOperationRequest {\n readonly projectId: string;\n readonly projectPath: string;\n readonly manifest: InfraManifest;\n readonly environment: AppEnvironmentId;\n readonly executionEnvironment?: Readonly<Record<string, string | undefined>>;\n readonly signal?: AbortSignal;\n}\n\nexport interface ProjectInfraDestroyRequest extends ProjectInfraOperationRequest {\n readonly confirmation: InfraDestroyRequest['confirmation'];\n readonly persistence: InfraDestroyRequest['persistence'];\n}\n\nexport interface ProjectInfraLifecycle {\n validateAsync(request: ProjectInfraOperationRequest): Promise<InfraValidateOperationResult>;\n planAsync(request: ProjectInfraOperationRequest): Promise<InfraPlanOperationResult>;\n generateAsync(request: ProjectInfraOperationRequest): Promise<InfraGenerateOperationResult>;\n upAsync(request: ProjectInfraOperationRequest): Promise<InfraUpOperationResult>;\n statusAsync(request: ProjectInfraOperationRequest): Promise<InfraStatusOperationResult>;\n outputsAsync(request: ProjectInfraOperationRequest): Promise<InfraOutputsOperationResult>;\n downAsync(request: ProjectInfraOperationRequest): Promise<InfraDownOperationResult>;\n destroyAsync(request: ProjectInfraDestroyRequest): Promise<InfraDestroyOperationResult>;\n}\n\nexport interface CreateProjectInfraLifecycleOptions {\n readonly services?: Partial<InfraCommandServices>;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "5.0.0",
4
+ "version": "5.1.1",
5
5
  "description": "Project-aware infrastructure generation library and executable Ankh provider for Ankhorage.",
6
6
  "homepage": "https://github.com/ankhorage/infra#readme",
7
7
  "bugs": {
@@ -35,7 +35,7 @@
35
35
  "openapi"
36
36
  ],
37
37
  "dependencies": {
38
- "@ankhorage/contracts": "^19.0.0"
38
+ "@ankhorage/contracts": "^19.1.0"
39
39
  },
40
40
  "files": [
41
41
  "dist",
@@ -100,6 +100,7 @@
100
100
  "prepack": "bun run build",
101
101
  "test": "bun test src",
102
102
  "test:e2e": "ANKH_INFRA_COMPOSITION_E2E=1 bun test src/infraComposition.integration.test.ts",
103
+ "test:e2e:minikube-supabase": "ANKH_INFRA_MINIKUBE_SUPABASE_E2E=1 bun test src/localMinikubeSupabase.integration.test.ts",
103
104
  "typecheck": "bun x tsc --noEmit -p tsconfig.json",
104
105
  "check-types": "bun run typecheck",
105
106
  "version-packages": "ankhorage-changeset version",
@@ -114,10 +115,10 @@
114
115
  "@ankhorage/hetzner": "^0.3.0",
115
116
  "@ankhorage/k3s": "^0.3.0",
116
117
  "@ankhorage/local": "^0.3.0",
117
- "@ankhorage/minikube": "^0.3.0",
118
+ "@ankhorage/minikube": "^0.4.1",
118
119
  "@ankhorage/paradox": "^0.1.24",
119
120
  "@ankhorage/r2": "^0.2.0",
120
- "@ankhorage/supabase": "^0.2.0",
121
+ "@ankhorage/supabase": "^0.4.5",
121
122
  "@ankhorage/supabase-vault": "^0.3.1",
122
123
  "@types/bun": "^1.4.1",
123
124
  "@types/node": "^25.6.0",
@@ -0,0 +1,157 @@
1
+ import type { InfraEnvironmentSpec, InfraManifest } from '@ankhorage/contracts/infra';
2
+
3
+ import { INFRA_PACKAGE_VERSION } from '../../../constants.js';
4
+ import type { InfraOperationRequest, InfraStoredState } from '../../../types/infraOrchestration.js';
5
+ import type {
6
+ CreateProjectInfraLifecycleOptions,
7
+ ProjectInfraLifecycle,
8
+ ProjectInfraOperationRequest,
9
+ } from '../../../types/infraProject.js';
10
+ import { createInfraCommandServices } from './createInfraCommandServices.js';
11
+
12
+ /*** Create the typed project-facing Infra lifecycle backed by canonical use cases and ownership state. */
13
+ export function createProjectInfraLifecycle(
14
+ options: CreateProjectInfraLifecycleOptions = {},
15
+ ): ProjectInfraLifecycle {
16
+ const services = createInfraCommandServices(options.services);
17
+ return {
18
+ async validateAsync(request) {
19
+ const prepared = await prepareProjectOperationAsync(request, services);
20
+ return services.operations.validate(prepared.operation, prepared.dependencies);
21
+ },
22
+ async planAsync(request) {
23
+ const prepared = await prepareProjectOperationAsync(request, services);
24
+ return services.operations.plan(prepared.operation, prepared.dependencies);
25
+ },
26
+ async generateAsync(request) {
27
+ const prepared = await prepareProjectOperationAsync(request, services);
28
+ const result = await services.operations.generate(prepared.operation, prepared.dependencies);
29
+ if (!result.ok) return result;
30
+ await services.writeArtifacts(
31
+ request.projectPath,
32
+ result.value.artifacts,
33
+ prepared.state?.ledger,
34
+ );
35
+ await services.writeState(request.projectPath, {
36
+ schemaVersion: 1,
37
+ desired: prepared.desired,
38
+ ledger: result.value.ledger,
39
+ });
40
+ return result;
41
+ },
42
+ async upAsync(request) {
43
+ const prepared = await prepareProjectOperationAsync(request, services);
44
+ const result = await services.operations.up(prepared.operation, prepared.dependencies);
45
+ if (!result.ok) return result;
46
+ await services.writeState(request.projectPath, {
47
+ schemaVersion: 1,
48
+ desired: prepared.desired,
49
+ ledger: result.value.ledger,
50
+ });
51
+ return result;
52
+ },
53
+ async statusAsync(request) {
54
+ const prepared = await prepareProjectOperationAsync(request, services);
55
+ return services.operations.status(prepared.operation, prepared.dependencies);
56
+ },
57
+ async outputsAsync(request) {
58
+ const prepared = await prepareProjectOperationAsync(request, services);
59
+ return services.operations.outputs(prepared.operation);
60
+ },
61
+ async downAsync(request) {
62
+ const prepared = await prepareProjectOperationAsync(request, services);
63
+ const result = await services.operations.down(prepared.operation, prepared.dependencies);
64
+ if (!result.ok) return result;
65
+ await services.writeState(request.projectPath, {
66
+ schemaVersion: 1,
67
+ desired: prepared.desired,
68
+ ledger: result.value.ledger,
69
+ });
70
+ return result;
71
+ },
72
+ async destroyAsync(request) {
73
+ const prepared = await prepareProjectOperationAsync(request, services, true);
74
+ const result = await services.operations.destroy(
75
+ {
76
+ ...prepared.operation,
77
+ confirmation: request.confirmation,
78
+ persistence: request.persistence,
79
+ },
80
+ prepared.dependencies,
81
+ );
82
+ if (!result.ok) return result;
83
+ await services.writeArtifacts(request.projectPath, [], prepared.state?.ledger);
84
+ if (result.value.ledger === null)
85
+ await services.removeState(request.projectPath, request.environment);
86
+ else {
87
+ await services.writeState(request.projectPath, {
88
+ schemaVersion: 1,
89
+ desired: prepared.desired,
90
+ ledger: result.value.ledger,
91
+ });
92
+ }
93
+ return result;
94
+ },
95
+ };
96
+ }
97
+
98
+ interface PreparedProjectOperation {
99
+ readonly operation: InfraOperationRequest;
100
+ readonly desired: InfraEnvironmentSpec;
101
+ readonly state: InfraStoredState | null;
102
+ readonly dependencies: ReturnType<
103
+ ReturnType<typeof createInfraCommandServices>['createDependencies']
104
+ >;
105
+ }
106
+
107
+ /*** Resolve one explicit project/environment request with its prior ownership state and dependencies. */
108
+ async function prepareProjectOperationAsync(
109
+ request: ProjectInfraOperationRequest,
110
+ services: ReturnType<typeof createInfraCommandServices>,
111
+ useStoredDesired = false,
112
+ ): Promise<PreparedProjectOperation> {
113
+ const state = await services.readState(request.projectPath, request.environment);
114
+ const currentDesired = readEnvironment(request.manifest, request.environment);
115
+ const desired = useStoredDesired && state !== null ? state.desired : currentDesired;
116
+ if (desired === undefined) {
117
+ throw new Error(`Infra environment "${request.environment}" is not configured.`);
118
+ }
119
+ const context = {
120
+ cwd: request.projectPath,
121
+ env: request.executionEnvironment ?? process.env,
122
+ version: INFRA_PACKAGE_VERSION,
123
+ writeStdout: () => undefined,
124
+ writeStderr: () => undefined,
125
+ };
126
+ return {
127
+ state,
128
+ desired,
129
+ operation: {
130
+ projectId: request.projectId,
131
+ manifest: replaceEnvironment(request.manifest, request.environment, desired),
132
+ environment: request.environment,
133
+ ...(state === null ? {} : { previous: state.ledger, previousDesired: state.desired }),
134
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
135
+ },
136
+ dependencies: services.createDependencies(context),
137
+ };
138
+ }
139
+
140
+ /*** Read one exact environment without inventing a fallback. */
141
+ function readEnvironment(
142
+ manifest: InfraManifest,
143
+ environment: ProjectInfraOperationRequest['environment'],
144
+ ): InfraEnvironmentSpec | undefined {
145
+ if (environment === 'local') return manifest.environments.local;
146
+ if (environment === 'preview') return manifest.environments.preview;
147
+ return manifest.environments.production;
148
+ }
149
+
150
+ /*** Replace one selected environment while preserving all other manifest state. */
151
+ function replaceEnvironment(
152
+ manifest: InfraManifest,
153
+ environment: ProjectInfraOperationRequest['environment'],
154
+ desired: InfraEnvironmentSpec,
155
+ ): InfraManifest {
156
+ return { ...manifest, environments: { ...manifest.environments, [environment]: desired } };
157
+ }
@@ -0,0 +1,219 @@
1
+ import { createHmac } from 'node:crypto';
2
+
3
+ import type {
4
+ InfraLedger,
5
+ InfraManifest,
6
+ InfraResourceIdentity,
7
+ InfraResult,
8
+ } from '@ankhorage/contracts/infra';
9
+ import { expect, test } from 'bun:test';
10
+
11
+ import { createNodeInfraAdapterPackageResolver } from './features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.js';
12
+ import { destroyInfraEnvironmentAsync } from './features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.js';
13
+ import { downInfraEnvironmentAsync } from './features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.js';
14
+ import { planInfraEnvironmentAsync } from './features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.js';
15
+ import { statusInfraEnvironmentAsync } from './features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.js';
16
+ import { upInfraEnvironmentAsync } from './features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js';
17
+ import type { InfraOrchestrationDependencies } from './types/infraOrchestration.js';
18
+
19
+ const projectId = 'infra145-minikube-supabase';
20
+ const baseUrl = 'http://127.0.0.1:54321';
21
+ const bucket = 'phase8-acceptance';
22
+ const jwtSecret = 'phase8-jwt-secret-that-is-at-least-thirty-two-characters';
23
+ const credentials = {
24
+ postgresPassword: 'phase8-postgres-password',
25
+ jwtSecret,
26
+ anonKey: createJwt('anon'),
27
+ serviceRoleKey: createJwt('service_role'),
28
+ realtimeSecretKeyBase: 'r'.repeat(64),
29
+ realtimeDatabaseEncryptionKey: '0123456789abcdef',
30
+ pgMetaCryptoKey: 'phase8-meta-crypto-key-that-is-at-least-32-characters',
31
+ } as const;
32
+ const manifest = {
33
+ environments: {
34
+ local: {
35
+ deployment: {
36
+ compute: { provider: 'local' },
37
+ runtime: {
38
+ provider: 'minikube',
39
+ profile: projectId,
40
+ driver: 'docker',
41
+ cpus: 2,
42
+ memoryMiB: 5_120,
43
+ },
44
+ },
45
+ database: { provider: 'supabase', tier: 'dev' },
46
+ auth: { provider: 'supabase' },
47
+ objectStorage: { provider: 'supabase', buckets: [bucket] },
48
+ networking: { publicBaseUrl: baseUrl },
49
+ },
50
+ },
51
+ modules: [],
52
+ } as const satisfies InfraManifest;
53
+
54
+ test.skipIf(process.env.ANKH_INFRA_MINIKUBE_SUPABASE_E2E !== '1')(
55
+ 'runs local Minikube and Supabase through the provider-neutral lifecycle',
56
+ async () => {
57
+ const dependencies = createDependencies();
58
+ let ledger: InfraLedger | undefined;
59
+
60
+ try {
61
+ const initialPlan = requireSuccess(
62
+ await planInfraEnvironmentAsync({ projectId, manifest }, dependencies),
63
+ );
64
+ expect(initialPlan.actions.some(({ operation }) => operation === 'create')).toBe(true);
65
+
66
+ const { ledger: firstLedger, outputs: firstOutputs } = requireSuccess(
67
+ await upInfraEnvironmentAsync({ projectId, manifest }, dependencies),
68
+ );
69
+ ledger = firstLedger;
70
+ assertSafePublicOutputs(firstOutputs);
71
+ expect(
72
+ firstOutputs.some(
73
+ ({ owner, name, value }) =>
74
+ owner.adapter === 'minikube' && name === 'localUrl' && value === baseUrl,
75
+ ),
76
+ ).toBe(true);
77
+ expect(
78
+ firstOutputs.some(
79
+ ({ environmentVariable, value }) =>
80
+ environmentVariable === 'EXPO_PUBLIC_SUPABASE_URL' && value === baseUrl,
81
+ ),
82
+ ).toBe(true);
83
+ expect(firstOutputs.some(({ name, value }) => name === 'bucket' && value === bucket)).toBe(
84
+ true,
85
+ );
86
+
87
+ const status = requireSuccess(
88
+ await statusInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
89
+ );
90
+ expect(status.state).toBe('ready');
91
+
92
+ const convergedPlan = requireSuccess(
93
+ await planInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
94
+ );
95
+ expect(convergedPlan.actions.every(({ operation }) => operation === 'noop')).toBe(true);
96
+
97
+ const { ledger: downLedger } = requireSuccess(
98
+ await downInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
99
+ );
100
+ ledger = downLedger;
101
+
102
+ const { ledger: resumedLedger, outputs: resumedOutputs } = requireSuccess(
103
+ await upInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
104
+ );
105
+ ledger = resumedLedger;
106
+ assertSafePublicOutputs(resumedOutputs);
107
+
108
+ const health = await fetch(`${baseUrl}/auth/v1/health`);
109
+ expect(health.ok).toBe(true);
110
+
111
+ const destroyed = requireSuccess(
112
+ await destroyInfraEnvironmentAsync(
113
+ createDestroyRequest(ledger, persistentIdentities(ledger)),
114
+ dependencies,
115
+ ),
116
+ );
117
+ ledger = destroyed.ledger ?? undefined;
118
+ expect(destroyed.ledger).toBeNull();
119
+ } finally {
120
+ if (ledger !== undefined) {
121
+ await destroyInfraEnvironmentAsync(
122
+ createDestroyRequest(ledger, persistentIdentities(ledger)),
123
+ dependencies,
124
+ );
125
+ }
126
+ }
127
+ },
128
+ 900_000,
129
+ );
130
+
131
+ function createDependencies(): InfraOrchestrationDependencies {
132
+ return {
133
+ adapterResolver: createNodeInfraAdapterPackageResolver(),
134
+ credentials: {
135
+ resolveAsync: (reference) =>
136
+ Promise.resolve(
137
+ reference.name === 'SUPABASE_BOOTSTRAP'
138
+ ? success(credentials)
139
+ : failure(
140
+ 'unexpected-control-plane-credential',
141
+ `Unexpected control-plane credential ${reference.name}.`,
142
+ ),
143
+ ),
144
+ },
145
+ secrets: {
146
+ resolveAsync: (reference) =>
147
+ Promise.resolve(
148
+ failure(
149
+ 'unexpected-managed-secret',
150
+ `Unexpected managed secret ${reference.ref}/${reference.key}.`,
151
+ ),
152
+ ),
153
+ },
154
+ };
155
+ }
156
+
157
+ function createDestroyRequest(
158
+ previous: InfraLedger,
159
+ confirmedResources: readonly InfraResourceIdentity[],
160
+ ) {
161
+ return {
162
+ projectId,
163
+ manifest,
164
+ environment: 'local' as const,
165
+ previous,
166
+ confirmation: { projectId, environment: 'local' as const },
167
+ persistence: { policy: 'delete' as const, confirmedResources },
168
+ };
169
+ }
170
+
171
+ function persistentIdentities(ledger: InfraLedger): readonly InfraResourceIdentity[] {
172
+ return ledger.resources.filter(({ persistent }) => persistent).map(({ identity }) => identity);
173
+ }
174
+
175
+ function assertSafePublicOutputs(outputs: InfraLedger['outputs']): void {
176
+ const serialized = JSON.stringify(outputs);
177
+ expect(serialized).not.toContain(credentials.serviceRoleKey);
178
+ expect(serialized).not.toContain(credentials.postgresPassword);
179
+ expect(serialized).not.toContain(credentials.jwtSecret);
180
+ expect(serialized).not.toContain(credentials.realtimeSecretKeyBase);
181
+ expect(serialized).not.toContain(credentials.realtimeDatabaseEncryptionKey);
182
+ expect(serialized).not.toContain(credentials.pgMetaCryptoKey);
183
+ expect(
184
+ outputs.some(
185
+ ({ environmentVariable, value }) =>
186
+ environmentVariable === 'EXPO_PUBLIC_SUPABASE_ANON_KEY' && value === credentials.anonKey,
187
+ ),
188
+ ).toBe(true);
189
+ }
190
+
191
+ function createJwt(role: 'anon' | 'service_role'): string {
192
+ const encodedHeader = encodeJwtPart({ alg: 'HS256', typ: 'JWT' });
193
+ const encodedPayload = encodeJwtPart({
194
+ role,
195
+ iss: 'supabase',
196
+ iat: 1_700_000_000,
197
+ exp: 4_102_444_800,
198
+ });
199
+ const unsigned = `${encodedHeader}.${encodedPayload}`;
200
+ const signature = createHmac('sha256', jwtSecret).update(unsigned).digest('base64url');
201
+ return `${unsigned}.${signature}`;
202
+ }
203
+
204
+ function encodeJwtPart(value: Readonly<Record<string, string | number>>): string {
205
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
206
+ }
207
+
208
+ function requireSuccess<T>(result: InfraResult<T>): T {
209
+ if (!result.ok) throw new Error(result.diagnostics.map(({ message }) => message).join(' '));
210
+ return result.value;
211
+ }
212
+
213
+ function success<T>(value: T): InfraResult<T> {
214
+ return { ok: true, value, diagnostics: [] };
215
+ }
216
+
217
+ function failure(code: string, message: string): InfraResult<never> {
218
+ return { ok: false, diagnostics: [{ severity: 'error', code, message }] };
219
+ }
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
 
3
3
  import {
4
+ createProjectInfraLifecycle,
4
5
  readStoredInfraStateAsync,
5
6
  resolveInfraProjectAsync,
6
7
  writeInfraGeneratedArtifactsAsync,
@@ -8,6 +9,7 @@ import {
8
9
 
9
10
  describe('@ankhorage/infra/project', () => {
10
11
  test('exports project resolution, safe state and artifact owner APIs', () => {
12
+ expect(createProjectInfraLifecycle).toBeFunction();
11
13
  expect(resolveInfraProjectAsync).toBeFunction();
12
14
  expect(readStoredInfraStateAsync).toBeFunction();
13
15
  expect(writeInfraGeneratedArtifactsAsync).toBeFunction();
@@ -3,5 +3,13 @@ export { removeStoredInfraStateAsync } from '../features/environment-lifecycle/a
3
3
  export { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';
4
4
  export { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';
5
5
  export { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';
6
+ export { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';
6
7
  export { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';
7
- export type { InfraArtifactWriteResult, ResolvedInfraProject } from '../types/infraProject.js';
8
+ export type {
9
+ CreateProjectInfraLifecycleOptions,
10
+ InfraArtifactWriteResult,
11
+ ProjectInfraDestroyRequest,
12
+ ProjectInfraLifecycle,
13
+ ProjectInfraOperationRequest,
14
+ ResolvedInfraProject,
15
+ } from '../types/infraProject.js';
@@ -0,0 +1,136 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import type { InfraEnvironmentSpec, InfraLedger, InfraResult } from '@ankhorage/contracts/infra';
6
+ import { afterEach, expect, test } from 'bun:test';
7
+
8
+ import { createProjectInfraLifecycle, readStoredInfraStateAsync } from './project/index.js';
9
+ import type { InfraLifecycleOperations } from './types/infraCli.js';
10
+ import type { InfraDestroyOperationRequest } from './types/infraOrchestration.js';
11
+
12
+ const temporaryPaths = new Set<string>();
13
+
14
+ afterEach(async () => {
15
+ await Promise.all(
16
+ [...temporaryPaths].map((entry) => fs.rm(entry, { force: true, recursive: true })),
17
+ );
18
+ temporaryPaths.clear();
19
+ });
20
+
21
+ test('runs typed project lifecycle operations with canonical state and artifact ownership', async () => {
22
+ const projectPath = await fs.mkdtemp(path.join(os.tmpdir(), 'infra-project-lifecycle-'));
23
+ temporaryPaths.add(projectPath);
24
+ const destroyRequests: InfraDestroyOperationRequest[] = [];
25
+ const lifecycle = createProjectInfraLifecycle({
26
+ services: {
27
+ createDependencies: () => ({
28
+ adapterResolver: { loadAsync: () => Promise.resolve({}) },
29
+ credentials: { resolveAsync: () => Promise.resolve(success({})) },
30
+ secrets: { resolveAsync: () => Promise.resolve(success('secret')) },
31
+ }),
32
+ operations: createOperations(destroyRequests),
33
+ },
34
+ });
35
+ const request = {
36
+ projectId: 'sample',
37
+ projectPath,
38
+ manifest: { environments: { local: desired }, modules: [] },
39
+ environment: 'local' as const,
40
+ };
41
+
42
+ const generated = await lifecycle.generateAsync(request);
43
+ expect(generated.ok).toBe(true);
44
+ expect(await fs.readFile(path.join(projectPath, 'infra/example.txt'), 'utf8')).toBe('generated');
45
+ expect((await readStoredInfraStateAsync(projectPath, 'local'))?.ledger).toEqual(ledger);
46
+
47
+ const outputs = await lifecycle.outputsAsync(request);
48
+ expect(outputs.ok && outputs.value.outputs).toEqual(ledger.outputs);
49
+
50
+ const destroyed = await lifecycle.destroyAsync({
51
+ ...request,
52
+ confirmation: { projectId: 'sample', environment: 'local' },
53
+ persistence: { policy: 'retain' },
54
+ });
55
+ expect(destroyed.ok).toBe(true);
56
+ expect(destroyRequests[0]?.confirmation).toEqual({ projectId: 'sample', environment: 'local' });
57
+ expect(await readStoredInfraStateAsync(projectPath, 'local')).toBeNull();
58
+ expect(await fileExists(path.join(projectPath, 'infra/example.txt'))).toBe(false);
59
+ });
60
+
61
+ const desired = {
62
+ deployment: {
63
+ compute: { provider: 'local' },
64
+ runtime: { provider: 'docker-compose' },
65
+ },
66
+ } as const satisfies InfraEnvironmentSpec;
67
+
68
+ const owner = {
69
+ projectId: 'sample',
70
+ environment: 'local',
71
+ adapter: 'docker-compose',
72
+ resourceId: 'runtime',
73
+ } as const;
74
+
75
+ const ledger = {
76
+ schemaVersion: 1,
77
+ projectId: 'sample',
78
+ environment: 'local',
79
+ targets: [],
80
+ resources: [],
81
+ outputs: [{ owner, name: 'url', visibility: 'public', value: 'https://example.test' }],
82
+ artifacts: [{ owner, path: 'infra/example.txt' }],
83
+ } as const satisfies InfraLedger;
84
+
85
+ /*** Create deterministic operation fakes while recording destructive authorization. */
86
+ function createOperations(
87
+ destroyRequests: InfraDestroyOperationRequest[],
88
+ ): InfraLifecycleOperations {
89
+ return {
90
+ validate: () => Promise.resolve(success({ environment: 'local' })),
91
+ plan: (request) =>
92
+ Promise.resolve(success({ projectId: request.projectId, environment: 'local', actions: [] })),
93
+ generate: () =>
94
+ Promise.resolve(
95
+ success({
96
+ environment: 'local',
97
+ artifacts: [{ owner, path: 'infra/example.txt', content: 'generated' }],
98
+ ledger,
99
+ }),
100
+ ),
101
+ up: () =>
102
+ Promise.resolve(
103
+ success({ environment: 'local', targets: [], resources: [], outputs: [], ledger }),
104
+ ),
105
+ status: (request) =>
106
+ Promise.resolve(
107
+ success({
108
+ projectId: request.projectId,
109
+ environment: 'local',
110
+ state: 'ready',
111
+ resources: [],
112
+ }),
113
+ ),
114
+ outputs: () => success({ environment: 'local', outputs: ledger.outputs }),
115
+ down: () => Promise.resolve(success({ environment: 'local', ledger })),
116
+ destroy: (request) => {
117
+ destroyRequests.push(request);
118
+ return Promise.resolve(success({ environment: 'local', ledger: null }));
119
+ },
120
+ };
121
+ }
122
+
123
+ /*** Create one successful Infra result for operation fakes. */
124
+ function success<T>(value: T): InfraResult<T> {
125
+ return { ok: true, value, diagnostics: [] };
126
+ }
127
+
128
+ /*** Report whether a generated artifact still exists. */
129
+ async function fileExists(filePath: string): Promise<boolean> {
130
+ try {
131
+ await fs.access(filePath);
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
@@ -1,4 +1,18 @@
1
1
  import type { AppManifest } from '@ankhorage/contracts';
2
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
3
+ import type { InfraDestroyRequest, InfraManifest } from '@ankhorage/contracts/infra';
4
+
5
+ import type { InfraCommandServices } from './infraCli.js';
6
+ import type {
7
+ InfraDestroyOperationResult,
8
+ InfraDownOperationResult,
9
+ InfraGenerateOperationResult,
10
+ InfraOutputsOperationResult,
11
+ InfraPlanOperationResult,
12
+ InfraStatusOperationResult,
13
+ InfraUpOperationResult,
14
+ InfraValidateOperationResult,
15
+ } from './infraOrchestration.js';
2
16
 
3
17
  export interface InfraArtifactWriteResult {
4
18
  readonly written: number;
@@ -13,3 +27,32 @@ export interface ResolvedInfraProject {
13
27
  readonly projectPath: string;
14
28
  readonly workspaceRoot: string;
15
29
  }
30
+
31
+ export interface ProjectInfraOperationRequest {
32
+ readonly projectId: string;
33
+ readonly projectPath: string;
34
+ readonly manifest: InfraManifest;
35
+ readonly environment: AppEnvironmentId;
36
+ readonly executionEnvironment?: Readonly<Record<string, string | undefined>>;
37
+ readonly signal?: AbortSignal;
38
+ }
39
+
40
+ export interface ProjectInfraDestroyRequest extends ProjectInfraOperationRequest {
41
+ readonly confirmation: InfraDestroyRequest['confirmation'];
42
+ readonly persistence: InfraDestroyRequest['persistence'];
43
+ }
44
+
45
+ export interface ProjectInfraLifecycle {
46
+ validateAsync(request: ProjectInfraOperationRequest): Promise<InfraValidateOperationResult>;
47
+ planAsync(request: ProjectInfraOperationRequest): Promise<InfraPlanOperationResult>;
48
+ generateAsync(request: ProjectInfraOperationRequest): Promise<InfraGenerateOperationResult>;
49
+ upAsync(request: ProjectInfraOperationRequest): Promise<InfraUpOperationResult>;
50
+ statusAsync(request: ProjectInfraOperationRequest): Promise<InfraStatusOperationResult>;
51
+ outputsAsync(request: ProjectInfraOperationRequest): Promise<InfraOutputsOperationResult>;
52
+ downAsync(request: ProjectInfraOperationRequest): Promise<InfraDownOperationResult>;
53
+ destroyAsync(request: ProjectInfraDestroyRequest): Promise<InfraDestroyOperationResult>;
54
+ }
55
+
56
+ export interface CreateProjectInfraLifecycleOptions {
57
+ readonly services?: Partial<InfraCommandServices>;
58
+ }