@ankhorage/infra 5.1.3 → 5.1.5
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 +12 -0
- package/README.md +1 -1
- package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.d.ts.map +1 -1
- package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.js +11 -3
- package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.js.map +1 -1
- package/package.json +5 -5
- package/src/features/environment-lifecycle/composition/createProjectInfraLifecycle.ts +11 -2
- package/src/hetznerK3sProduction.test.ts +14 -1
- package/src/projectLifecycle.test.ts +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ankhorage/infra
|
|
2
2
|
|
|
3
|
+
## 5.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7da18cd: Make project Infra destroy idempotent when no stored ownership state exists, avoiding provider resolution for never-provisioned or already-destroyed environments.
|
|
8
|
+
|
|
9
|
+
## 5.1.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3a4d8a3: Consume the current Infra networking TLS contract while proving the released k3s production HTTPS integration in the Hetzner composition acceptance baseline.
|
|
14
|
+
|
|
3
15
|
## 5.1.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# INFRA
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Executable infra provider and standalone CLI for Ankhorage project workflows.
|
|
9
9
|
|
package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,CAyFvB"}
|
|
@@ -58,7 +58,15 @@ export function createProjectInfraLifecycle(options = {}) {
|
|
|
58
58
|
return result;
|
|
59
59
|
},
|
|
60
60
|
async destroyAsync(request) {
|
|
61
|
-
const
|
|
61
|
+
const state = await services.readState(request.projectPath, request.environment);
|
|
62
|
+
if (state === null) {
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
value: { environment: request.environment, ledger: null },
|
|
66
|
+
diagnostics: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const prepared = await prepareProjectOperationAsync(request, services, true, state);
|
|
62
70
|
const result = await services.operations.destroy({
|
|
63
71
|
...prepared.operation,
|
|
64
72
|
confirmation: request.confirmation,
|
|
@@ -81,8 +89,8 @@ export function createProjectInfraLifecycle(options = {}) {
|
|
|
81
89
|
};
|
|
82
90
|
}
|
|
83
91
|
/*** 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);
|
|
92
|
+
async function prepareProjectOperationAsync(request, services, useStoredDesired = false, knownState) {
|
|
93
|
+
const state = knownState ?? (await services.readState(request.projectPath, request.environment));
|
|
86
94
|
const currentDesired = readEnvironment(request.manifest, request.environment);
|
|
87
95
|
const desired = useStoredDesired && state !== null ? state.desired : currentDesired;
|
|
88
96
|
if (desired === undefined) {
|
|
@@ -1 +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"]}
|
|
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 state = await services.readState(request.projectPath, request.environment);\n if (state === null) {\n return {\n ok: true,\n value: { environment: request.environment, ledger: null },\n diagnostics: [],\n };\n }\n const prepared = await prepareProjectOperationAsync(request, services, true, state);\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 knownState?: InfraStoredState,\n): Promise<PreparedProjectOperation> {\n const state = knownState ?? (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"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/infra",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.1.
|
|
4
|
+
"version": "5.1.5",
|
|
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.
|
|
38
|
+
"@ankhorage/contracts": "^19.4.0"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
@@ -114,12 +114,12 @@
|
|
|
114
114
|
"@ankhorage/devtools": "^1.14.0",
|
|
115
115
|
"@ankhorage/docker-compose": "^0.5.3",
|
|
116
116
|
"@ankhorage/hetzner": "^0.3.0",
|
|
117
|
-
"@ankhorage/k3s": "^0.
|
|
117
|
+
"@ankhorage/k3s": "^0.4.1",
|
|
118
118
|
"@ankhorage/local": "^0.3.0",
|
|
119
|
-
"@ankhorage/minikube": "^0.4.
|
|
119
|
+
"@ankhorage/minikube": "^0.4.2",
|
|
120
120
|
"@ankhorage/paradox": "^0.1.24",
|
|
121
121
|
"@ankhorage/r2": "^0.2.0",
|
|
122
|
-
"@ankhorage/supabase": "^0.4.
|
|
122
|
+
"@ankhorage/supabase": "^0.4.9",
|
|
123
123
|
"@ankhorage/supabase-vault": "^0.3.1",
|
|
124
124
|
"@types/bun": "^1.4.1",
|
|
125
125
|
"@types/node": "^25.6.0",
|
|
@@ -70,7 +70,15 @@ export function createProjectInfraLifecycle(
|
|
|
70
70
|
return result;
|
|
71
71
|
},
|
|
72
72
|
async destroyAsync(request) {
|
|
73
|
-
const
|
|
73
|
+
const state = await services.readState(request.projectPath, request.environment);
|
|
74
|
+
if (state === null) {
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
value: { environment: request.environment, ledger: null },
|
|
78
|
+
diagnostics: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const prepared = await prepareProjectOperationAsync(request, services, true, state);
|
|
74
82
|
const result = await services.operations.destroy(
|
|
75
83
|
{
|
|
76
84
|
...prepared.operation,
|
|
@@ -109,8 +117,9 @@ async function prepareProjectOperationAsync(
|
|
|
109
117
|
request: ProjectInfraOperationRequest,
|
|
110
118
|
services: ReturnType<typeof createInfraCommandServices>,
|
|
111
119
|
useStoredDesired = false,
|
|
120
|
+
knownState?: InfraStoredState,
|
|
112
121
|
): Promise<PreparedProjectOperation> {
|
|
113
|
-
const state = await services.readState(request.projectPath, request.environment);
|
|
122
|
+
const state = knownState ?? (await services.readState(request.projectPath, request.environment));
|
|
114
123
|
const currentDesired = readEnvironment(request.manifest, request.environment);
|
|
115
124
|
const desired = useStoredDesired && state !== null ? state.desired : currentDesired;
|
|
116
125
|
if (desired === undefined) {
|
|
@@ -56,7 +56,11 @@ const manifest = {
|
|
|
56
56
|
},
|
|
57
57
|
database: { provider: 'supabase', tier: 'prod' },
|
|
58
58
|
auth: { provider: 'supabase' },
|
|
59
|
-
networking: {
|
|
59
|
+
networking: {
|
|
60
|
+
domain,
|
|
61
|
+
publicBaseUrl,
|
|
62
|
+
tls: { mode: 'acme-http-01', contactEmail: 'infra@example.test' },
|
|
63
|
+
},
|
|
60
64
|
},
|
|
61
65
|
},
|
|
62
66
|
modules: [],
|
|
@@ -92,6 +96,11 @@ test('composes Hetzner, k3s, Kubernetes and Supabase while retaining compute for
|
|
|
92
96
|
},
|
|
93
97
|
]);
|
|
94
98
|
expect(fixture.k3s.lastAccess).toHaveLength(1);
|
|
99
|
+
expect(fixture.k3s.lastSpec?.networking).toEqual({
|
|
100
|
+
domain,
|
|
101
|
+
publicBaseUrl,
|
|
102
|
+
tls: { mode: 'acme-http-01', contactEmail: 'infra@example.test' },
|
|
103
|
+
});
|
|
95
104
|
const [access] = fixture.k3s.lastAccess;
|
|
96
105
|
expect(access?.transport.kind).toBe('ssh');
|
|
97
106
|
expect(access?.transport.kind === 'ssh' && access.transport.host).toBe('203.0.113.10');
|
|
@@ -99,6 +108,8 @@ test('composes Hetzner, k3s, Kubernetes and Supabase while retaining compute for
|
|
|
99
108
|
expect(JSON.stringify(firstUp)).not.toContain('phase10-service-role-key');
|
|
100
109
|
expect(fixture.kubernetes.serializedResources()).toContain(`"host":"${domain}"`);
|
|
101
110
|
expect(fixture.kubernetes.serializedResources()).toContain('PersistentVolumeClaim');
|
|
111
|
+
expect(fixture.kubernetes.serializedResources()).toContain('/etc/postgresql-custom');
|
|
112
|
+
expect(fixture.kubernetes.serializedResources()).toContain('.ankhorage-image-seeded');
|
|
102
113
|
expect(firstUp.resources.some(({ persistent }) => persistent)).toBe(true);
|
|
103
114
|
const urlOutput = firstUp.outputs.find(({ name }) => name === 'url');
|
|
104
115
|
expect(urlOutput?.value).toBe(publicBaseUrl);
|
|
@@ -308,6 +319,7 @@ class FakeK3sControlPlane implements K3sControlPlane {
|
|
|
308
319
|
readonly api = new FakeKubernetesApi();
|
|
309
320
|
readonly calls: string[] = [];
|
|
310
321
|
lastAccess: readonly K3sNodeAccess[] = [];
|
|
322
|
+
lastSpec?: K3sClusterSpec;
|
|
311
323
|
state: K3sClusterObservation['state'] = 'absent';
|
|
312
324
|
nodes: K3sClusterObservation['nodes'] = [];
|
|
313
325
|
|
|
@@ -331,6 +343,7 @@ class FakeK3sControlPlane implements K3sControlPlane {
|
|
|
331
343
|
): Promise<InfraResult<K3sClusterObservation>> {
|
|
332
344
|
this.calls.push('ensure');
|
|
333
345
|
this.lastAccess = access;
|
|
346
|
+
this.lastSpec = spec;
|
|
334
347
|
this.state = 'ready';
|
|
335
348
|
this.nodes = spec.nodes.map(({ id }) => ({
|
|
336
349
|
id,
|
|
@@ -18,6 +18,35 @@ afterEach(async () => {
|
|
|
18
18
|
temporaryPaths.clear();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
test('destroy is idempotent without stored ownership state or provider resolution', 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
|
+
throw new Error('Provider dependencies must not resolve without stored ownership state.');
|
|
29
|
+
},
|
|
30
|
+
operations: createOperations(destroyRequests),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
const request = {
|
|
34
|
+
projectId: 'sample',
|
|
35
|
+
projectPath,
|
|
36
|
+
manifest: { environments: { local: desired }, modules: [] },
|
|
37
|
+
environment: 'local' as const,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const destroyed = await lifecycle.destroyAsync({
|
|
41
|
+
...request,
|
|
42
|
+
confirmation: { projectId: 'sample', environment: 'local' },
|
|
43
|
+
persistence: { policy: 'retain' },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(destroyed).toEqual(success({ environment: 'local', ledger: null }));
|
|
47
|
+
expect(destroyRequests).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
|
|
21
50
|
test('runs typed project lifecycle operations with canonical state and artifact ownership', async () => {
|
|
22
51
|
const projectPath = await fs.mkdtemp(path.join(os.tmpdir(), 'infra-project-lifecycle-'));
|
|
23
52
|
temporaryPaths.add(projectPath);
|