@ankhorage/infra 0.4.2 → 1.0.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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/adapters/minikube/auth/supabase/index.d.ts.map +1 -1
- package/dist/adapters/minikube/auth/supabase/index.js +9 -39
- package/dist/adapters/minikube/auth/supabase/index.js.map +1 -1
- package/dist/adapters/minikube/auth/supabase/profile.d.ts +0 -1
- package/dist/adapters/minikube/auth/supabase/profile.d.ts.map +1 -1
- package/dist/adapters/minikube/auth/supabase/profile.js +1 -1
- package/dist/adapters/minikube/auth/supabase/profile.js.map +1 -1
- package/dist/adapters/minikube/authz/cerbos/index.d.ts.map +1 -1
- package/dist/adapters/minikube/authz/cerbos/index.js +22 -2
- package/dist/adapters/minikube/authz/cerbos/index.js.map +1 -1
- package/dist/adapters/minikube/base/index.d.ts +5 -2
- package/dist/adapters/minikube/base/index.d.ts.map +1 -1
- package/dist/adapters/minikube/base/index.js +2728 -1843
- package/dist/adapters/minikube/base/index.js.map +1 -1
- package/dist/adapters/minikube/contracts.d.ts +25 -0
- package/dist/adapters/minikube/contracts.d.ts.map +1 -1
- package/dist/adapters/minikube/contracts.js +1 -0
- package/dist/adapters/minikube/contracts.js.map +1 -1
- package/dist/adapters/minikube/index.d.ts.map +1 -1
- package/dist/adapters/minikube/index.js +18 -33
- package/dist/adapters/minikube/index.js.map +1 -1
- package/dist/adapters/minikube/secrets/supabase-vault/index.d.ts.map +1 -1
- package/dist/adapters/minikube/secrets/supabase-vault/index.js +49 -6
- package/dist/adapters/minikube/secrets/supabase-vault/index.js.map +1 -1
- package/dist/adapters/minikube/storage/supabase/index.d.ts.map +1 -1
- package/dist/adapters/minikube/storage/supabase/index.js +2 -1
- package/dist/adapters/minikube/storage/supabase/index.js.map +1 -1
- package/package.json +4 -1
- package/src/adapters/minikube/README.md +41 -38
- package/src/adapters/minikube/auth/supabase/index.ts +9 -40
- package/src/adapters/minikube/auth/supabase/profile.ts +1 -1
- package/src/adapters/minikube/authz/cerbos/index.ts +25 -3
- package/src/adapters/minikube/base/index.test.ts +380 -675
- package/src/adapters/minikube/base/index.ts +2935 -1878
- package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +636 -0
- package/src/adapters/minikube/contracts.ts +29 -0
- package/src/adapters/minikube/index.ts +18 -44
- package/src/adapters/minikube/secrets/index.test.ts +15 -7
- package/src/adapters/minikube/secrets/supabase-vault/index.ts +53 -6
- package/src/adapters/minikube/storage/supabase/index.ts +1 -5
- package/src/apis.test.ts +2 -1
- package/src/index.test.ts +63 -577
- package/src/minikubeImageLifecycle.test.ts +4 -3
package/src/index.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { generateInfrastructure } from './index';
|
|
|
5
5
|
import type { InfraManifestInput } from './types';
|
|
6
6
|
|
|
7
7
|
describe('generateInfrastructure', () => {
|
|
8
|
-
test('generates minikube artifacts
|
|
8
|
+
test('generates minikube artifacts for app-owned Kubernetes infrastructure', () => {
|
|
9
9
|
const manifest: InfraManifestInput = {
|
|
10
10
|
deployment: {
|
|
11
11
|
target: 'minikube',
|
|
@@ -46,267 +46,61 @@ describe('generateInfrastructure', () => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const result = generateInfrastructure(manifest, { appManifest: createAppManifest('alpha') });
|
|
49
|
-
const paths = result.files.map((
|
|
49
|
+
const paths = result.files.map((file) => file.path);
|
|
50
50
|
|
|
51
51
|
expect(result.meta.target).toBe('minikube');
|
|
52
52
|
expect(result.meta.providers).toEqual(['auto', 'cerbos', 'supabase']);
|
|
53
|
-
expect(paths).toContain('infra/minikube/k8s/
|
|
53
|
+
expect(paths).toContain('infra/minikube/k8s/namespaces/app.yaml');
|
|
54
|
+
expect(paths).toContain('infra/minikube/k8s/namespaces/supabase.yaml');
|
|
54
55
|
expect(paths).toContain('infra/minikube/k8s/app/deployment.yaml');
|
|
55
56
|
expect(paths).toContain('infra/minikube/k8s/app/service.yaml');
|
|
57
|
+
expect(paths).toContain('infra/minikube/k8s/supabase/postgres.yaml');
|
|
58
|
+
expect(paths).toContain('infra/minikube/k8s/supabase/gateway.yaml');
|
|
56
59
|
expect(paths).toContain('infra/minikube/k8s/auth/supabase/supabase-auth.configmap.yaml');
|
|
57
60
|
expect(paths).toContain('infra/minikube/k8s/auth/supabase/app-runtime-auth.env.configmap.yaml');
|
|
58
61
|
expect(paths).toContain('infra/minikube/k8s/authz/cerbos/cerbos.policy.configmap.yaml');
|
|
59
62
|
expect(paths).toContain('infra/minikube/k8s/authz/cerbos/cerbos.deployment.yaml');
|
|
60
|
-
expect(paths).toContain('infra/minikube/auth/supabase-runtime-wiring.md');
|
|
61
63
|
expect(paths).toContain('infra/minikube/k8s/storage/supabase/supabase-storage.configmap.yaml');
|
|
62
64
|
expect(paths).toContain(
|
|
63
65
|
'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
64
66
|
);
|
|
65
67
|
expect(paths).toContain('infra/minikube/scripts/up.sh');
|
|
66
|
-
expect(paths).toContain('infra/minikube/scripts/
|
|
68
|
+
expect(paths).toContain('infra/minikube/scripts/down.sh');
|
|
69
|
+
expect(paths).toContain('infra/minikube/scripts/reset.sh');
|
|
70
|
+
expect(paths).toContain('infra/minikube/scripts/destroy.sh');
|
|
71
|
+
expect(paths).toContain('infra/minikube/scripts/status.sh');
|
|
67
72
|
expect(paths).toContain('infra/minikube/scripts/port-forward.sh');
|
|
68
|
-
expect(paths).toContain('infra/minikube/scripts/supabase-local-env.sh');
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expect(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
expect(
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(upScript
|
|
86
|
-
expect(upScript
|
|
87
|
-
expect(upScript
|
|
88
|
-
expect(upScript
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
expect(
|
|
98
|
-
|
|
99
|
-
);
|
|
100
|
-
expect(upScript?.content).toContain('AUTH_RUNTIME_MODE="${AUTH_RUNTIME_MODE:-local}"');
|
|
101
|
-
expect(upScript?.content).toContain(
|
|
102
|
-
'minikube -p "${PROFILE}" image build -t "${APP_IMAGE}" -f "${ROOT_DIR}/app-image/Dockerfile" "${EXPORT_DIR}"',
|
|
103
|
-
);
|
|
104
|
-
expect(upScript?.content).toContain('APP_IMAGE_PULL_SECRET_NAME');
|
|
105
|
-
expect(upScript?.content).toContain('create secret docker-registry');
|
|
106
|
-
expect(upScript?.content).toContain('PULL_SECRET_PATCH=');
|
|
107
|
-
expect(upScript?.content).toContain('--patch "${PULL_SECRET_PATCH}"');
|
|
108
|
-
expect(upScript?.content).toContain(
|
|
109
|
-
'SUPABASE_SECRET_SYNC_ENABLED="${SUPABASE_SECRET_SYNC_ENABLED:-true}"',
|
|
110
|
-
);
|
|
111
|
-
expect(upScript?.content).toContain(
|
|
112
|
-
'SUPABASE_LOCAL_ENV_SCRIPT="${SCRIPT_DIR}/supabase-local-env.sh"',
|
|
113
|
-
);
|
|
114
|
-
expect(upScript?.content).toContain(
|
|
115
|
-
'Supabase keys missing; running ${SUPABASE_LOCAL_ENV_SCRIPT}.',
|
|
116
|
-
);
|
|
117
|
-
expect(upScript?.content).toContain('create secret generic supabase-auth-secrets');
|
|
118
|
-
expect(upScript?.content).toContain(
|
|
119
|
-
'Synchronized supabase-auth-secrets from environment values.',
|
|
120
|
-
);
|
|
121
|
-
expect(upScript?.content).toContain('imagePullSecrets');
|
|
122
|
-
expect(upScript?.content).toContain('set image deployment/app-runtime');
|
|
123
|
-
expect(upScript?.content).toContain('scale deployment/app-runtime');
|
|
124
|
-
expect(upScript?.content).toContain('rollout restart deployment/app-runtime');
|
|
125
|
-
expect(upScript?.content).toContain('rollout status deployment/app-runtime --timeout=180s');
|
|
126
|
-
|
|
127
|
-
const kustomization = result.files.find(
|
|
128
|
-
(f) => f.path === 'infra/minikube/k8s/kustomization.yaml',
|
|
129
|
-
);
|
|
130
|
-
expect(kustomization?.content).toContain('auth/supabase/supabase-auth.configmap.yaml');
|
|
131
|
-
expect(kustomization?.content).toContain('auth/supabase/app-runtime-auth.env.configmap.yaml');
|
|
132
|
-
expect(kustomization?.content).toContain('authz/cerbos/cerbos.policy.configmap.yaml');
|
|
133
|
-
expect(kustomization?.content).toContain('authz/cerbos/cerbos.deployment.yaml');
|
|
134
|
-
expect(kustomization?.content).toContain('storage/supabase/supabase-storage.configmap.yaml');
|
|
135
|
-
expect(kustomization?.content).toContain(
|
|
136
|
-
'storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
137
|
-
);
|
|
138
|
-
expect(kustomization?.content).toContain('app/deployment.yaml');
|
|
139
|
-
expect(kustomization?.content).toContain('app/service.yaml');
|
|
140
|
-
|
|
141
|
-
const envExample = result.files.find((f) => f.path === 'infra/minikube/.env.example');
|
|
142
|
-
expect(envExample?.content).toContain('SUPABASE_JWT_SECRET=');
|
|
143
|
-
expect(envExample?.content).toContain('SUPABASE_SECRET_SYNC_ENABLED=true');
|
|
144
|
-
expect(envExample?.content).toContain('CERBOS_URL=http://cerbos:3592');
|
|
145
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_SUPABASE_URL=');
|
|
146
|
-
expect(envExample?.content).toContain('APP_BUILD_ENABLED=true');
|
|
147
|
-
expect(envExample?.content).toContain('APP_SOURCE_DIR=');
|
|
148
|
-
expect(envExample?.content).toContain('APP_WEB_EXPORT_DIR=.ankh/web-export');
|
|
149
|
-
expect(envExample?.content).toContain('SUPABASE_PROJECT_DIR=');
|
|
150
|
-
expect(envExample?.content).toContain('SUPABASE_LOCAL_PORT_BASE=55540');
|
|
151
|
-
expect(envExample?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT=55545');
|
|
152
|
-
expect(envExample?.content).toContain('APP_IMAGE=ankh/alpha-local:dev');
|
|
153
|
-
expect(envExample?.content).toContain('APP_IMAGE_SYNC_STRATEGY=docker-load');
|
|
154
|
-
expect(envExample?.content).toContain('AUTH_RUNTIME_MODE=local');
|
|
155
|
-
expect(envExample?.content).toContain('APP_PORT_FORWARD_LOCAL_PORT=18080');
|
|
156
|
-
expect(envExample?.content).toContain('APP_PORT_FORWARD_REMOTE_PORT=80');
|
|
157
|
-
expect(envExample?.content).toContain('APP_REPLICAS=');
|
|
158
|
-
expect(envExample?.content).toContain('APP_FORCE_ROLLOUT_RESTART=true');
|
|
159
|
-
expect(envExample?.content).toContain('APP_IMAGE_PULL_SECRET_NAME=');
|
|
160
|
-
expect(envExample?.content).toContain('APP_IMAGE_PULL_SECRET_SERVER=');
|
|
161
|
-
expect(envExample?.content).toContain('STORAGE_PROVIDER=supabase');
|
|
162
|
-
expect(envExample?.content).toContain('STORAGE_RUNTIME_MODE=local');
|
|
163
|
-
expect(envExample?.content).toContain('STORAGE_BUCKETS=assets');
|
|
164
|
-
expect(envExample?.content).toContain('STORAGE_DEFAULT_BUCKET=assets');
|
|
165
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_STORAGE_PROVIDER=supabase');
|
|
166
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_STORAGE_BUCKETS=assets');
|
|
167
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_STORAGE_DEFAULT_BUCKET=assets');
|
|
168
|
-
|
|
169
|
-
const minikubeReadme = result.files.find((f) => f.path === 'infra/minikube/README.md');
|
|
170
|
-
expect(minikubeReadme?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT');
|
|
171
|
-
expect(minikubeReadme?.content).toContain('55545');
|
|
172
|
-
expect(minikubeReadme?.content).toContain('Supabase local project identity: `alpha`');
|
|
173
|
-
expect(minikubeReadme?.content).toContain('$APP_SOURCE_DIR/.env.local');
|
|
174
|
-
expect(minikubeReadme?.content).toContain('apps/card/.env.local');
|
|
175
|
-
|
|
176
|
-
const runtimeGuide = result.files.find(
|
|
177
|
-
(f) => f.path === 'infra/minikube/auth/supabase-runtime-wiring.md',
|
|
178
|
-
);
|
|
179
|
-
expect(runtimeGuide?.content).toContain('envFrom:');
|
|
180
|
-
expect(runtimeGuide?.content).toContain('supabase-auth-secrets');
|
|
181
|
-
expect(runtimeGuide?.content).toContain('AUTH_SIGN_IN_IDENTIFIERS');
|
|
182
|
-
expect(runtimeGuide?.content).toContain('AUTH_SIGN_UP_REQUIRED_FIELDS');
|
|
183
|
-
expect(runtimeGuide?.content).toContain('AUTH_SIGN_UP_OPTIONAL_FIELDS');
|
|
184
|
-
expect(runtimeGuide?.content).toContain('AUTH_SIGN_UP_POLICY');
|
|
185
|
-
expect(runtimeGuide?.content).toContain('AUTH_PROFILE_FIELDS');
|
|
186
|
-
|
|
187
|
-
const supabaseConfigMap = result.files.find(
|
|
188
|
-
(f) => f.path === 'infra/minikube/k8s/auth/supabase/supabase-auth.configmap.yaml',
|
|
189
|
-
);
|
|
190
|
-
expect(supabaseConfigMap?.content).toContain('AUTH_SIGN_IN_IDENTIFIERS: "email,username"');
|
|
191
|
-
expect(supabaseConfigMap?.content).toContain('AUTH_SIGN_UP_REQUIRED_FIELDS: "email,password"');
|
|
192
|
-
expect(supabaseConfigMap?.content).toContain(
|
|
193
|
-
'AUTH_SIGN_UP_OPTIONAL_FIELDS: "firstName,lastName"',
|
|
194
|
-
);
|
|
195
|
-
expect(supabaseConfigMap?.content).toContain('AUTH_SIGN_UP_POLICY: "requireVerification"');
|
|
196
|
-
expect(supabaseConfigMap?.content).toContain(
|
|
197
|
-
'AUTH_PROFILE_FIELDS: "email,firstName,lastName,avatarUrl"',
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
const runtimeAuthConfigMap = result.files.find(
|
|
201
|
-
(f) => f.path === 'infra/minikube/k8s/auth/supabase/app-runtime-auth.env.configmap.yaml',
|
|
202
|
-
);
|
|
203
|
-
expect(runtimeAuthConfigMap?.content).toContain('AUTH_SIGN_IN_IDENTIFIERS: "email,username"');
|
|
204
|
-
expect(runtimeAuthConfigMap?.content).toContain(
|
|
205
|
-
'AUTH_SIGN_UP_REQUIRED_FIELDS: "email,password"',
|
|
206
|
-
);
|
|
207
|
-
expect(runtimeAuthConfigMap?.content).toContain(
|
|
208
|
-
'AUTH_SIGN_UP_OPTIONAL_FIELDS: "firstName,lastName"',
|
|
209
|
-
);
|
|
210
|
-
expect(runtimeAuthConfigMap?.content).toContain('AUTH_SIGN_UP_POLICY: "requireVerification"');
|
|
211
|
-
expect(runtimeAuthConfigMap?.content).toContain(
|
|
212
|
-
'AUTH_PROFILE_FIELDS: "email,firstName,lastName,avatarUrl"',
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const appDeployment = result.files.find(
|
|
216
|
-
(f) => f.path === 'infra/minikube/k8s/app/deployment.yaml',
|
|
217
|
-
);
|
|
218
|
-
expect(appDeployment?.content).toContain('image: ankh/alpha-local:dev');
|
|
219
|
-
expect(appDeployment?.content).toContain('name: app-infra-config');
|
|
220
|
-
expect(appDeployment?.content).toContain('name: app-runtime-auth-env');
|
|
221
|
-
expect(appDeployment?.content).toContain('name: app-runtime-storage-env');
|
|
222
|
-
expect(appDeployment?.content).toContain('name: supabase-auth-secrets');
|
|
223
|
-
|
|
224
|
-
const appDeploymentContent = appDeployment?.content ?? '';
|
|
225
|
-
const appInfraIndex = appDeploymentContent.indexOf('name: app-infra-config');
|
|
226
|
-
const authIndex = appDeploymentContent.indexOf('name: app-runtime-auth-env');
|
|
227
|
-
const storageIndex = appDeploymentContent.indexOf('name: app-runtime-storage-env');
|
|
228
|
-
const supabaseSecretsIndex = appDeploymentContent.indexOf('name: supabase-auth-secrets');
|
|
229
|
-
|
|
230
|
-
// envFrom precedence: later sources win on duplicate keys; explicit `env` entries win over `envFrom`.
|
|
231
|
-
// Keep storage discovery (`app-runtime-storage-env`) before provider secrets to allow future overlap.
|
|
232
|
-
expect(appInfraIndex).toBeGreaterThanOrEqual(0);
|
|
233
|
-
expect(authIndex).toBeGreaterThan(appInfraIndex);
|
|
234
|
-
expect(storageIndex).toBeGreaterThan(authIndex);
|
|
235
|
-
expect(supabaseSecretsIndex).toBeGreaterThan(storageIndex);
|
|
236
|
-
|
|
237
|
-
const appBuildScript = result.files.find(
|
|
238
|
-
(f) => f.path === 'infra/minikube/scripts/build-app-image.sh',
|
|
239
|
-
);
|
|
240
|
-
expect(appBuildScript?.executable).toBe(true);
|
|
241
|
-
expect(appBuildScript?.content).toContain('bunx expo export --platform web --output-dir');
|
|
242
|
-
expect(appBuildScript?.content).toContain('docker build -t "${APP_IMAGE}"');
|
|
243
|
-
expect(appBuildScript?.content).toContain('APP_SOURCE_DIR=');
|
|
244
|
-
|
|
245
|
-
const portForwardScript = result.files.find(
|
|
246
|
-
(f) => f.path === 'infra/minikube/scripts/port-forward.sh',
|
|
247
|
-
);
|
|
248
|
-
expect(portForwardScript?.executable).toBe(true);
|
|
249
|
-
expect(portForwardScript?.content).toContain(
|
|
250
|
-
'kubectl -n "${NAMESPACE}" port-forward service/app-runtime "${LOCAL_PORT}:${REMOTE_PORT}"',
|
|
251
|
-
);
|
|
252
|
-
expect(portForwardScript?.content).toContain(
|
|
253
|
-
'LOCAL_PORT="${APP_PORT_FORWARD_LOCAL_PORT:-18080}"',
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
const supabaseLocalEnvScript = result.files.find(
|
|
257
|
-
(f) => f.path === 'infra/minikube/scripts/supabase-local-env.sh',
|
|
258
|
-
);
|
|
259
|
-
expect(supabaseLocalEnvScript?.executable).toBe(true);
|
|
260
|
-
expect(supabaseLocalEnvScript?.content).toContain('supabase status -o env');
|
|
261
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
262
|
-
'supabase --workdir "${SUPABASE_PROJECT_DIR}" init --yes',
|
|
263
|
-
);
|
|
264
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
265
|
-
'supabase --workdir "${SUPABASE_PROJECT_DIR}" start',
|
|
266
|
-
);
|
|
267
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
268
|
-
'supabase --workdir "${SUPABASE_PROJECT_DIR}" migration up --local',
|
|
269
|
-
);
|
|
270
|
-
expect(supabaseLocalEnvScript?.content).toContain('SUPABASE_PROJECT_DIR');
|
|
271
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
272
|
-
'SUPABASE_PROJECT_DIR="${SUPABASE_PROJECT_DIR:-${ROOT_DIR}}"',
|
|
273
|
-
);
|
|
274
|
-
expect(supabaseLocalEnvScript?.content).toContain('supabase CLI >= 2.106.0 is required');
|
|
275
|
-
expect(supabaseLocalEnvScript?.content).toContain('SUPABASE_LOCAL_ANALYTICS_PORT');
|
|
276
|
-
expect(supabaseLocalEnvScript?.content).toContain('export SUPABASE_LOCAL_ANALYTICS_PORT');
|
|
277
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
278
|
-
'Patch("analytics", "port", _env_required("SUPABASE_LOCAL_ANALYTICS_PORT"))',
|
|
279
|
-
);
|
|
280
|
-
expect(supabaseLocalEnvScript?.content).toContain(
|
|
281
|
-
'upsert_env "${ENV_FILE}" "SUPABASE_LOCAL_ANALYTICS_PORT" "${SUPABASE_LOCAL_ANALYTICS_PORT}"',
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const appImageDockerfile = result.files.find(
|
|
285
|
-
(f) => f.path === 'infra/minikube/app-image/Dockerfile',
|
|
286
|
-
);
|
|
287
|
-
expect(appImageDockerfile?.content).toContain('FROM nginx:1.27-alpine');
|
|
288
|
-
expect(appImageDockerfile?.content).toContain('listen 8080;');
|
|
289
|
-
expect(appImageDockerfile?.content).toContain('COPY . /usr/share/nginx/html');
|
|
290
|
-
|
|
291
|
-
const cerbosConfig = result.files.find(
|
|
292
|
-
(f) => f.path === 'infra/minikube/k8s/authz/cerbos/cerbos.configmap.yaml',
|
|
293
|
-
);
|
|
294
|
-
const cerbosPolicy = result.files.find(
|
|
295
|
-
(f) => f.path === 'infra/minikube/k8s/authz/cerbos/cerbos.policy.configmap.yaml',
|
|
296
|
-
);
|
|
297
|
-
const cerbosDeployment = result.files.find(
|
|
298
|
-
(f) => f.path === 'infra/minikube/k8s/authz/cerbos/cerbos.deployment.yaml',
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
expect(cerbosConfig?.content).toContain('name: cerbos-config');
|
|
302
|
-
expect(cerbosConfig?.content).toContain('config.yaml: |');
|
|
303
|
-
expect(cerbosConfig?.content).not.toContain('resource_policy.yaml');
|
|
304
|
-
expect(cerbosPolicy?.content).toContain('name: cerbos-policy');
|
|
305
|
-
expect(cerbosPolicy?.content).toContain('app.resource_policy.yaml: |');
|
|
306
|
-
expect(cerbosPolicy?.content).not.toContain('route.resource_policy.yaml: |');
|
|
307
|
-
expect(cerbosPolicy?.content).not.toContain('screen.resource_policy.yaml: |');
|
|
308
|
-
expect(cerbosPolicy?.content).not.toContain('metadata.yaml');
|
|
309
|
-
expect(cerbosDeployment?.content).toContain('name: cerbos-policy');
|
|
73
|
+
expect(paths).not.toContain('infra/minikube/scripts/supabase-local-env.sh');
|
|
74
|
+
|
|
75
|
+
const envExample = getFile(result.files, 'infra/minikube/.env.example');
|
|
76
|
+
expect(envExample).toContain('ANKH_APP_SLUG=alpha');
|
|
77
|
+
expect(envExample).not.toContain('SUPABASE_RUNTIME_ENABLED=');
|
|
78
|
+
expect(envExample).toContain('EXPO_PUBLIC_SUPABASE_URL=');
|
|
79
|
+
expect(envExample).toContain('APP_IMAGE=ankh/alpha:dev');
|
|
80
|
+
expect(envExample).not.toContain('MINIKUBE_PROFILE=');
|
|
81
|
+
expect(envExample).not.toContain('SUPABASE_PROJECT_DIR=');
|
|
82
|
+
|
|
83
|
+
const upScript = getFile(result.files, 'infra/minikube/scripts/up.sh');
|
|
84
|
+
expect(upScript).toContain('SUPABASE_RUNTIME_ENABLED="true"');
|
|
85
|
+
expect(upScript).not.toContain('SUPABASE_RUNTIME_ENABLED="${SUPABASE_RUNTIME_ENABLED');
|
|
86
|
+
expect(upScript).toContain('minikube start -p "${PROFILE}"');
|
|
87
|
+
expect(upScript).toContain('kubectl --context "${PROFILE}" apply -k "${K8S_DIR}"');
|
|
88
|
+
expect(upScript).toContain('supabase migration up --db-url "${SUPABASE_DB_URL}"');
|
|
89
|
+
expect(upScript).toContain('create secret generic supabase-runtime-secrets');
|
|
90
|
+
expect(upScript).toContain('create secret generic supabase-public-runtime');
|
|
91
|
+
expect(upScript).toContain('rollout status deployment/app-runtime --timeout=180s');
|
|
92
|
+
expect(upScript).not.toContain('supabase start');
|
|
93
|
+
expect(upScript).not.toContain('supabase status');
|
|
94
|
+
|
|
95
|
+
const appDeployment = getFile(result.files, 'infra/minikube/k8s/app/deployment.yaml');
|
|
96
|
+
expect(appDeployment).toContain('namespace: app');
|
|
97
|
+
expect(appDeployment).toContain('image: ankh/alpha:dev');
|
|
98
|
+
expect(appDeployment).toContain('name: supabase-public-runtime');
|
|
99
|
+
expect(appDeployment).not.toContain('supabase-auth-secrets');
|
|
100
|
+
|
|
101
|
+
const runtimeGuide = getFile(result.files, 'infra/minikube/auth/supabase-runtime-wiring.md');
|
|
102
|
+
expect(runtimeGuide).toContain('supabase-public-runtime');
|
|
103
|
+
expect(runtimeGuide).not.toContain('SUPABASE_SERVICE_ROLE_KEY');
|
|
310
104
|
});
|
|
311
105
|
|
|
312
106
|
test('generates minikube storage artifacts for explicit supabase provider with normalized buckets', () => {
|
|
@@ -318,10 +112,6 @@ describe('generateInfrastructure', () => {
|
|
|
318
112
|
auth: {
|
|
319
113
|
scope: 'global',
|
|
320
114
|
provider: 'supabase',
|
|
321
|
-
authorization: {
|
|
322
|
-
kind: 'ABAC',
|
|
323
|
-
engine: 'cerbos',
|
|
324
|
-
},
|
|
325
115
|
},
|
|
326
116
|
database: {
|
|
327
117
|
provider: 'supabase',
|
|
@@ -335,7 +125,6 @@ describe('generateInfrastructure', () => {
|
|
|
335
125
|
};
|
|
336
126
|
|
|
337
127
|
const result = generateInfrastructure(manifest, {
|
|
338
|
-
namespaceHint: 'shop',
|
|
339
128
|
appManifest: createAppManifest('shop'),
|
|
340
129
|
});
|
|
341
130
|
|
|
@@ -343,159 +132,13 @@ describe('generateInfrastructure', () => {
|
|
|
343
132
|
'Storage buckets are configured but not created automatically yet. Ensure buckets exist in Supabase Storage: assets,uploads.',
|
|
344
133
|
);
|
|
345
134
|
|
|
346
|
-
const storageConfig =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
expect(storageConfig?.content).toContain('STORAGE_PROVIDER: "supabase"');
|
|
350
|
-
expect(storageConfig?.content).toContain('STORAGE_BUCKETS: "assets,uploads"');
|
|
351
|
-
expect(storageConfig?.content).toContain('STORAGE_DEFAULT_BUCKET: "assets"');
|
|
352
|
-
|
|
353
|
-
const runtimeStorageEnv = result.files.find(
|
|
354
|
-
(f) =>
|
|
355
|
-
f.path === 'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
356
|
-
);
|
|
357
|
-
expect(runtimeStorageEnv?.content).toContain('STORAGE_RUNTIME_MODE: "local"');
|
|
358
|
-
expect(runtimeStorageEnv?.content).toContain('STORAGE_BUCKETS: "assets,uploads"');
|
|
359
|
-
expect(runtimeStorageEnv?.content).toContain('EXPO_PUBLIC_STORAGE_BUCKETS: "assets,uploads"');
|
|
360
|
-
expect(runtimeStorageEnv?.content).toContain('EXPO_PUBLIC_STORAGE_DEFAULT_BUCKET: "assets"');
|
|
361
|
-
|
|
362
|
-
const appInfraConfigMap = result.files.find(
|
|
363
|
-
(f) => f.path === 'infra/minikube/k8s/app.configmap.yaml',
|
|
364
|
-
);
|
|
365
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_PROVIDER: "supabase"');
|
|
366
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_BUCKETS: "assets,uploads"');
|
|
367
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_DEFAULT_BUCKET: "assets"');
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test('resolves storage.provider="auto" to supabase when database provider is supabase', () => {
|
|
371
|
-
const manifest: InfraManifestInput = {
|
|
372
|
-
deployment: {
|
|
373
|
-
target: 'minikube',
|
|
374
|
-
monitoring: false,
|
|
375
|
-
},
|
|
376
|
-
database: {
|
|
377
|
-
provider: 'supabase',
|
|
378
|
-
tier: 'dev',
|
|
379
|
-
},
|
|
380
|
-
storage: {
|
|
381
|
-
provider: 'auto',
|
|
382
|
-
buckets: ['assets'],
|
|
383
|
-
},
|
|
384
|
-
plugins: [],
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
const result = generateInfrastructure(manifest, {
|
|
388
|
-
namespaceHint: 'shop',
|
|
389
|
-
appManifest: createAppManifest('shop'),
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
expect(result.files.map((f) => f.path)).toContain(
|
|
393
|
-
'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
const envExample = result.files.find((f) => f.path === 'infra/minikube/.env.example');
|
|
397
|
-
expect(envExample?.content).toContain('STORAGE_PROVIDER=supabase');
|
|
398
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_STORAGE_PROVIDER=supabase');
|
|
399
|
-
expect(envExample?.content).toContain('SUPABASE_URL=');
|
|
400
|
-
expect(envExample?.content).toContain('EXPO_PUBLIC_SUPABASE_URL=');
|
|
401
|
-
|
|
402
|
-
const appInfraConfigMap = result.files.find(
|
|
403
|
-
(f) => f.path === 'infra/minikube/k8s/app.configmap.yaml',
|
|
404
|
-
);
|
|
405
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_PROVIDER: "supabase"');
|
|
406
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_BUCKETS: "assets"');
|
|
407
|
-
expect(appInfraConfigMap?.content).toContain('STORAGE_DEFAULT_BUCKET: "assets"');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test('warns when explicit supabase provider buckets are empty after normalization', () => {
|
|
411
|
-
const manifest: InfraManifestInput = {
|
|
412
|
-
deployment: {
|
|
413
|
-
target: 'minikube',
|
|
414
|
-
monitoring: false,
|
|
415
|
-
},
|
|
416
|
-
auth: {
|
|
417
|
-
scope: 'global',
|
|
418
|
-
provider: 'supabase',
|
|
419
|
-
authorization: {
|
|
420
|
-
kind: 'ABAC',
|
|
421
|
-
engine: 'cerbos',
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
database: {
|
|
425
|
-
provider: 'supabase',
|
|
426
|
-
tier: 'dev',
|
|
427
|
-
},
|
|
428
|
-
storage: {
|
|
429
|
-
provider: 'supabase',
|
|
430
|
-
buckets: ['', ' ', ' '],
|
|
431
|
-
},
|
|
432
|
-
plugins: [],
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const result = generateInfrastructure(manifest, {
|
|
436
|
-
namespaceHint: 'shop',
|
|
437
|
-
appManifest: createAppManifest('shop'),
|
|
438
|
-
});
|
|
439
|
-
expect(result.warnings).toContain(
|
|
440
|
-
'Storage buckets are empty after normalization; no storage artifacts generated.',
|
|
441
|
-
);
|
|
442
|
-
expect(result.files.map((f) => f.path)).not.toContain(
|
|
443
|
-
'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
444
|
-
);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
test('warns when auto provider resolves to supabase but buckets are empty after normalization', () => {
|
|
448
|
-
const manifest: InfraManifestInput = {
|
|
449
|
-
deployment: {
|
|
450
|
-
target: 'minikube',
|
|
451
|
-
monitoring: false,
|
|
452
|
-
},
|
|
453
|
-
database: {
|
|
454
|
-
provider: 'supabase',
|
|
455
|
-
tier: 'dev',
|
|
456
|
-
},
|
|
457
|
-
storage: {
|
|
458
|
-
provider: 'auto',
|
|
459
|
-
buckets: [' ', ''],
|
|
460
|
-
},
|
|
461
|
-
plugins: [],
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
const result = generateInfrastructure(manifest, {
|
|
465
|
-
namespaceHint: 'shop',
|
|
466
|
-
appManifest: createAppManifest('shop'),
|
|
467
|
-
});
|
|
468
|
-
expect(result.warnings).toContain(
|
|
469
|
-
'Storage buckets are empty after normalization; no storage artifacts generated.',
|
|
470
|
-
);
|
|
471
|
-
expect(result.files.map((f) => f.path)).not.toContain(
|
|
472
|
-
'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
473
|
-
);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
test('does not resolve storage.provider="auto" when neither auth nor database is supabase', () => {
|
|
477
|
-
const manifest: InfraManifestInput = {
|
|
478
|
-
deployment: {
|
|
479
|
-
target: 'minikube',
|
|
480
|
-
monitoring: false,
|
|
481
|
-
},
|
|
482
|
-
storage: {
|
|
483
|
-
provider: 'auto',
|
|
484
|
-
buckets: ['assets'],
|
|
485
|
-
},
|
|
486
|
-
plugins: [],
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
const result = generateInfrastructure(manifest, {
|
|
490
|
-
namespaceHint: 'shop',
|
|
491
|
-
appManifest: createAppManifest('shop'),
|
|
492
|
-
});
|
|
493
|
-
expect(result.warnings).toContain(
|
|
494
|
-
'Storage provider "auto" could not be resolved for minikube; no storage artifacts generated.',
|
|
495
|
-
);
|
|
496
|
-
expect(result.files.map((f) => f.path)).not.toContain(
|
|
497
|
-
'infra/minikube/k8s/storage/supabase/app-runtime-storage.env.configmap.yaml',
|
|
135
|
+
const storageConfig = getFile(
|
|
136
|
+
result.files,
|
|
137
|
+
'infra/minikube/k8s/storage/supabase/supabase-storage.configmap.yaml',
|
|
498
138
|
);
|
|
139
|
+
expect(storageConfig).toContain('namespace: app');
|
|
140
|
+
expect(storageConfig).toContain('STORAGE_BUCKETS: "assets,uploads"');
|
|
141
|
+
expect(storageConfig).toContain('STORAGE_DEFAULT_BUCKET: "assets"');
|
|
499
142
|
});
|
|
500
143
|
|
|
501
144
|
test('generates route and screen cerbos policies when app manifest context is provided', () => {
|
|
@@ -572,28 +215,20 @@ describe('generateInfrastructure', () => {
|
|
|
572
215
|
},
|
|
573
216
|
};
|
|
574
217
|
|
|
575
|
-
const result = generateInfrastructure(manifest, {
|
|
576
|
-
const cerbosPolicy =
|
|
577
|
-
|
|
218
|
+
const result = generateInfrastructure(manifest, { appManifest });
|
|
219
|
+
const cerbosPolicy = getFile(
|
|
220
|
+
result.files,
|
|
221
|
+
'infra/minikube/k8s/authz/cerbos/cerbos.policy.configmap.yaml',
|
|
578
222
|
);
|
|
579
223
|
|
|
580
|
-
expect(cerbosPolicy
|
|
581
|
-
expect(cerbosPolicy
|
|
582
|
-
expect(cerbosPolicy
|
|
583
|
-
expect(cerbosPolicy?.content).toContain(
|
|
224
|
+
expect(cerbosPolicy).toContain('route.resource_policy.yaml: |');
|
|
225
|
+
expect(cerbosPolicy).toContain('screen.resource_policy.yaml: |');
|
|
226
|
+
expect(cerbosPolicy).toContain(
|
|
584
227
|
'expr: "request.resource.attr.route in [\\"sign-in\\",\\"sign-up\\"]"',
|
|
585
228
|
);
|
|
586
|
-
expect(cerbosPolicy
|
|
229
|
+
expect(cerbosPolicy).toContain(
|
|
587
230
|
'expr: "request.resource.attr.route in [\\"cart\\",\\"index\\"]"',
|
|
588
231
|
);
|
|
589
|
-
expect(cerbosPolicy?.content).toContain(
|
|
590
|
-
'expr: "request.resource.attr.screen_id in [\\"screen-sign-in\\",\\"screen-sign-up\\"]"',
|
|
591
|
-
);
|
|
592
|
-
expect(cerbosPolicy?.content).toContain(
|
|
593
|
-
'expr: "request.resource.attr.screen_id in [\\"screen-cart\\",\\"screen-home\\"]"',
|
|
594
|
-
);
|
|
595
|
-
expect(cerbosPolicy?.content).toContain('roles: ["anonymous","authenticated"]');
|
|
596
|
-
expect(cerbosPolicy?.content).toContain('roles: ["authenticated"]');
|
|
597
232
|
});
|
|
598
233
|
|
|
599
234
|
test('throws for missing deployment', () => {
|
|
@@ -618,55 +253,7 @@ describe('generateInfrastructure', () => {
|
|
|
618
253
|
);
|
|
619
254
|
});
|
|
620
255
|
|
|
621
|
-
test('
|
|
622
|
-
const manifest: InfraManifestInput = {
|
|
623
|
-
deployment: {
|
|
624
|
-
target: 'minikube',
|
|
625
|
-
monitoring: false,
|
|
626
|
-
},
|
|
627
|
-
auth: {
|
|
628
|
-
scope: 'global',
|
|
629
|
-
provider: 'cognito',
|
|
630
|
-
authorization: {
|
|
631
|
-
kind: 'ABAC',
|
|
632
|
-
engine: 'cerbos',
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
plugins: [],
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
expect(() => generateInfrastructure(manifest)).toThrow(
|
|
639
|
-
'Unsupported auth provider for minikube adapter: "cognito". Only "supabase" is currently supported.',
|
|
640
|
-
);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test('throws for unsupported opa authorization engine', () => {
|
|
644
|
-
const manifest: InfraManifestInput = {
|
|
645
|
-
deployment: {
|
|
646
|
-
target: 'minikube',
|
|
647
|
-
monitoring: false,
|
|
648
|
-
},
|
|
649
|
-
auth: {
|
|
650
|
-
scope: 'global',
|
|
651
|
-
provider: 'supabase',
|
|
652
|
-
authorization: {
|
|
653
|
-
kind: 'ABAC',
|
|
654
|
-
engine: 'opa' as never,
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
database: {
|
|
658
|
-
provider: 'supabase',
|
|
659
|
-
tier: 'dev',
|
|
660
|
-
},
|
|
661
|
-
plugins: [],
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
expect(() =>
|
|
665
|
-
generateInfrastructure(manifest, { appManifest: createAppManifest('shop') }),
|
|
666
|
-
).toThrow('Unsupported authorization engine for minikube adapter: opa');
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
test('requires canonical app slug for local Supabase infrastructure', () => {
|
|
256
|
+
test('requires canonical app slug for minikube infrastructure', () => {
|
|
670
257
|
const manifest: InfraManifestInput = {
|
|
671
258
|
deployment: {
|
|
672
259
|
target: 'minikube',
|
|
@@ -680,123 +267,22 @@ describe('generateInfrastructure', () => {
|
|
|
680
267
|
};
|
|
681
268
|
|
|
682
269
|
expect(() => generateInfrastructure(manifest, { namespaceHint: 'shop' })).toThrow(
|
|
683
|
-
'Cannot generate
|
|
270
|
+
'Cannot generate Minikube infrastructure: appManifest.metadata.slug is required.',
|
|
684
271
|
);
|
|
685
272
|
expect(() =>
|
|
686
273
|
generateInfrastructure(manifest, {
|
|
687
|
-
namespaceHint: 'shop',
|
|
688
274
|
appManifest: createAppManifest('Scanner App'),
|
|
689
275
|
}),
|
|
690
276
|
).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
|
|
691
|
-
|
|
692
|
-
const fortyCharacterSlug = 'a'.repeat(40);
|
|
693
|
-
const fortyCharacterResult = generateInfrastructure(manifest, {
|
|
694
|
-
namespaceHint: 'shop',
|
|
695
|
-
appManifest: createAppManifest(fortyCharacterSlug),
|
|
696
|
-
});
|
|
697
|
-
const fortyCharacterScript = fortyCharacterResult.files.find(
|
|
698
|
-
(f) => f.path === 'infra/minikube/scripts/supabase-local-env.sh',
|
|
699
|
-
);
|
|
700
|
-
expect(fortyCharacterScript?.content).toContain(
|
|
701
|
-
`EXPECTED_SUPABASE_PROJECT_ID="${fortyCharacterSlug}"`,
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
expect(() =>
|
|
705
|
-
generateInfrastructure(manifest, {
|
|
706
|
-
namespaceHint: 'shop',
|
|
707
|
-
appManifest: createAppManifest('a'.repeat(41)),
|
|
708
|
-
}),
|
|
709
|
-
).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
|
|
710
|
-
|
|
711
|
-
expect(() =>
|
|
712
|
-
generateInfrastructure(manifest, {
|
|
713
|
-
namespaceHint: 'shop',
|
|
714
|
-
appManifest: createAppManifest('very-long-project-name-with-identical-prefix-alpha'),
|
|
715
|
-
}),
|
|
716
|
-
).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
|
|
717
|
-
expect(() =>
|
|
718
|
-
generateInfrastructure(manifest, {
|
|
719
|
-
namespaceHint: 'shop',
|
|
720
|
-
appManifest: createAppManifest('very-long-project-name-with-identical-prefix-beta'),
|
|
721
|
-
}),
|
|
722
|
-
).toThrow('appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters');
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
test('keeps Supabase project identity independent from domain-derived namespace', () => {
|
|
726
|
-
const manifest: InfraManifestInput = {
|
|
727
|
-
deployment: {
|
|
728
|
-
target: 'minikube',
|
|
729
|
-
monitoring: false,
|
|
730
|
-
},
|
|
731
|
-
database: {
|
|
732
|
-
provider: 'supabase',
|
|
733
|
-
tier: 'dev',
|
|
734
|
-
},
|
|
735
|
-
networking: {
|
|
736
|
-
domain: 'local.example.test',
|
|
737
|
-
cdn: false,
|
|
738
|
-
},
|
|
739
|
-
plugins: [],
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
const result = generateInfrastructure(manifest, {
|
|
743
|
-
namespaceHint: 'ignored',
|
|
744
|
-
appManifest: createAppManifest('scanner'),
|
|
745
|
-
});
|
|
746
|
-
const namespaceFile = result.files.find((f) => f.path === 'infra/minikube/k8s/namespace.yaml');
|
|
747
|
-
const envExample = result.files.find((f) => f.path === 'infra/minikube/.env.example');
|
|
748
|
-
const script = result.files.find(
|
|
749
|
-
(f) => f.path === 'infra/minikube/scripts/supabase-local-env.sh',
|
|
750
|
-
);
|
|
751
|
-
|
|
752
|
-
expect(namespaceFile?.content).toContain('name: local-example-test');
|
|
753
|
-
expect(envExample?.content).toContain('SUPABASE_LOCAL_PORT_BASE=64020');
|
|
754
|
-
expect(envExample?.content).not.toContain('SUPABASE_LOCAL_PORT_BASE=61550');
|
|
755
|
-
expect(script?.content).toContain('EXPECTED_SUPABASE_PROJECT_ID="scanner"');
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
test('uses namespace hint when domain is not provided', () => {
|
|
759
|
-
const manifest: InfraManifestInput = {
|
|
760
|
-
deployment: {
|
|
761
|
-
target: 'minikube',
|
|
762
|
-
monitoring: false,
|
|
763
|
-
},
|
|
764
|
-
auth: {
|
|
765
|
-
scope: 'global',
|
|
766
|
-
provider: 'supabase',
|
|
767
|
-
authorization: {
|
|
768
|
-
kind: 'ABAC',
|
|
769
|
-
engine: 'cerbos',
|
|
770
|
-
},
|
|
771
|
-
},
|
|
772
|
-
database: {
|
|
773
|
-
provider: 'supabase',
|
|
774
|
-
tier: 'dev',
|
|
775
|
-
},
|
|
776
|
-
plugins: [],
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
const result = generateInfrastructure(manifest, {
|
|
780
|
-
namespaceHint: 'shop',
|
|
781
|
-
appManifest: createAppManifest('shop'),
|
|
782
|
-
});
|
|
783
|
-
const namespaceFile = result.files.find((f) => f.path === 'infra/minikube/k8s/namespace.yaml');
|
|
784
|
-
const upScript = result.files.find((f) => f.path === 'infra/minikube/scripts/up.sh');
|
|
785
|
-
const statusScript = result.files.find((f) => f.path === 'infra/minikube/scripts/status.sh');
|
|
786
|
-
|
|
787
|
-
expect(namespaceFile?.content).toContain('name: shop');
|
|
788
|
-
expect(upScript?.content).toContain('NAMESPACE="${ANKH_NAMESPACE:-shop}"');
|
|
789
|
-
expect(upScript?.content).toContain('APP_IMAGE="${APP_IMAGE:-ankh/shop:dev}"');
|
|
790
|
-
expect(statusScript?.content).toContain('NAMESPACE="${ANKH_NAMESPACE:-shop}"');
|
|
791
|
-
expect(statusScript?.content).toContain('kubectl config current-context');
|
|
792
|
-
expect(statusScript?.content).toContain('kubectl cluster-info');
|
|
793
|
-
expect(statusScript?.content).toContain("Namespace '${NAMESPACE}' was not found");
|
|
794
|
-
expect(statusScript?.content).toContain('Runtime auth checks:');
|
|
795
|
-
expect(statusScript?.content).toContain('- AUTH_RUNTIME_MODE=${AUTH_RUNTIME_MODE}');
|
|
796
|
-
expect(statusScript?.content).toContain('- secret/supabase-auth-secrets: present');
|
|
797
277
|
});
|
|
798
278
|
});
|
|
799
279
|
|
|
280
|
+
function getFile(files: readonly { path: string; content: string }[], path: string): string {
|
|
281
|
+
const file = files.find((candidate) => candidate.path === path);
|
|
282
|
+
if (!file) throw new Error(`Missing generated file: ${path}`);
|
|
283
|
+
return file.content;
|
|
284
|
+
}
|
|
285
|
+
|
|
800
286
|
function createAppManifest(
|
|
801
287
|
slug: string,
|
|
802
288
|
): Pick<AppManifest, 'metadata' | 'navigator' | 'screens' | 'settings'> {
|