@ankhorage/infra 0.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/adapters/minikube/auth/index.d.ts +7 -0
- package/dist/adapters/minikube/auth/index.d.ts.map +1 -0
- package/dist/adapters/minikube/auth/index.js +14 -0
- package/dist/adapters/minikube/auth/index.js.map +1 -0
- package/dist/adapters/minikube/auth/supabase/index.d.ts +7 -0
- package/dist/adapters/minikube/auth/supabase/index.d.ts.map +1 -0
- package/dist/adapters/minikube/auth/supabase/index.js +213 -0
- package/dist/adapters/minikube/auth/supabase/index.js.map +1 -0
- package/dist/adapters/minikube/authz/cerbos/index.d.ts +9 -0
- package/dist/adapters/minikube/authz/cerbos/index.d.ts.map +1 -0
- package/dist/adapters/minikube/authz/cerbos/index.js +360 -0
- package/dist/adapters/minikube/authz/cerbos/index.js.map +1 -0
- package/dist/adapters/minikube/authz/index.d.ts +9 -0
- package/dist/adapters/minikube/authz/index.d.ts.map +1 -0
- package/dist/adapters/minikube/authz/index.js +23 -0
- package/dist/adapters/minikube/authz/index.js.map +1 -0
- package/dist/adapters/minikube/base/index.d.ts +9 -0
- package/dist/adapters/minikube/base/index.d.ts.map +1 -0
- package/dist/adapters/minikube/base/index.js +1371 -0
- package/dist/adapters/minikube/base/index.js.map +1 -0
- package/dist/adapters/minikube/contracts.d.ts +9 -0
- package/dist/adapters/minikube/contracts.d.ts.map +1 -0
- package/dist/adapters/minikube/contracts.js +9 -0
- package/dist/adapters/minikube/contracts.js.map +1 -0
- package/dist/adapters/minikube/index.d.ts +3 -0
- package/dist/adapters/minikube/index.d.ts.map +1 -0
- package/dist/adapters/minikube/index.js +114 -0
- package/dist/adapters/minikube/index.js.map +1 -0
- package/dist/adapters/minikube/storage/index.d.ts +7 -0
- package/dist/adapters/minikube/storage/index.d.ts.map +1 -0
- package/dist/adapters/minikube/storage/index.js +77 -0
- package/dist/adapters/minikube/storage/index.js.map +1 -0
- package/dist/adapters/minikube/storage/supabase/index.d.ts +7 -0
- package/dist/adapters/minikube/storage/supabase/index.d.ts.map +1 -0
- package/dist/adapters/minikube/storage/supabase/index.js +81 -0
- package/dist/adapters/minikube/storage/supabase/index.js.map +1 -0
- package/dist/datasets.d.ts +11 -0
- package/dist/datasets.d.ts.map +1 -0
- package/dist/datasets.js +397 -0
- package/dist/datasets.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +86 -0
- package/src/adapters/minikube/README.md +73 -0
- package/src/adapters/minikube/auth/index.ts +23 -0
- package/src/adapters/minikube/auth/supabase/index.ts +269 -0
- package/src/adapters/minikube/authz/cerbos/index.ts +458 -0
- package/src/adapters/minikube/authz/index.ts +32 -0
- package/src/adapters/minikube/base/index.test.ts +103 -0
- package/src/adapters/minikube/base/index.ts +1478 -0
- package/src/adapters/minikube/contracts.ts +17 -0
- package/src/adapters/minikube/index.ts +149 -0
- package/src/adapters/minikube/storage/index.ts +91 -0
- package/src/adapters/minikube/storage/supabase/index.ts +112 -0
- package/src/datasets.test.ts +178 -0
- package/src/datasets.ts +562 -0
- package/src/index.test.ts +679 -0
- package/src/index.ts +43 -0
- package/src/minikubeImageLifecycle.test.ts +34 -0
- package/src/types.ts +82 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GeneratedInfrastructureFile } from '../../types';
|
|
2
|
+
|
|
3
|
+
export interface MinikubeAdapterArtifacts {
|
|
4
|
+
files: GeneratedInfrastructureFile[];
|
|
5
|
+
resources: string[];
|
|
6
|
+
envEntries: string[];
|
|
7
|
+
warnings: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function emptyMinikubeArtifacts(): MinikubeAdapterArtifacts {
|
|
11
|
+
return {
|
|
12
|
+
files: [],
|
|
13
|
+
resources: [],
|
|
14
|
+
envEntries: [],
|
|
15
|
+
warnings: [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { generateDatasetInfrastructureArtifacts } from '../../datasets';
|
|
2
|
+
import type {
|
|
3
|
+
GeneratedPackageDependency,
|
|
4
|
+
InfraManifestInput,
|
|
5
|
+
InfrastructureGenerationOptions,
|
|
6
|
+
InfrastructureGenerationResult,
|
|
7
|
+
} from '../../types';
|
|
8
|
+
import { generateAuthProviderArtifacts } from './auth';
|
|
9
|
+
import { generateAuthorizationArtifacts } from './authz';
|
|
10
|
+
import { generateMinikubeBaseArtifacts } from './base';
|
|
11
|
+
import { generateStorageArtifacts } from './storage';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_NAMESPACE = 'ankh-app';
|
|
14
|
+
|
|
15
|
+
export function generateMinikubeInfra(
|
|
16
|
+
manifest: InfraManifestInput,
|
|
17
|
+
options: InfrastructureGenerationOptions = {},
|
|
18
|
+
): InfrastructureGenerationResult {
|
|
19
|
+
if (manifest.deployment?.target !== 'minikube') {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Minikube adapter only supports deployment target "minikube" (received "${manifest.deployment?.target ?? 'undefined'}").`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (manifest.database?.provider && manifest.database.provider !== 'supabase') {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Unsupported database provider for minikube adapter: "${manifest.database.provider}". Only "supabase" is implemented.`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const namespace = resolveNamespace(manifest, options);
|
|
32
|
+
|
|
33
|
+
const authArtifacts = generateAuthProviderArtifacts({ manifest, namespace });
|
|
34
|
+
const authzArtifacts = generateAuthorizationArtifacts({
|
|
35
|
+
manifest,
|
|
36
|
+
namespace,
|
|
37
|
+
appManifest: options.appManifest,
|
|
38
|
+
});
|
|
39
|
+
const storageArtifacts = generateStorageArtifacts({ manifest, namespace });
|
|
40
|
+
const datasetArtifacts = generateDatasetInfrastructureArtifacts({
|
|
41
|
+
data: options.appManifest?.data,
|
|
42
|
+
databaseProvider: manifest.database?.provider,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const extraResources = unique([
|
|
46
|
+
...authArtifacts.resources,
|
|
47
|
+
...authzArtifacts.resources,
|
|
48
|
+
...storageArtifacts.resources,
|
|
49
|
+
]);
|
|
50
|
+
const extraEnvEntries = unique([
|
|
51
|
+
...authArtifacts.envEntries,
|
|
52
|
+
...authzArtifacts.envEntries,
|
|
53
|
+
...storageArtifacts.envEntries,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const baseFiles = generateMinikubeBaseArtifacts({
|
|
57
|
+
manifest,
|
|
58
|
+
namespace,
|
|
59
|
+
extraResources,
|
|
60
|
+
extraEnvEntries,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const warnings = unique([
|
|
64
|
+
...collectWarnings(manifest),
|
|
65
|
+
...authArtifacts.warnings,
|
|
66
|
+
...authzArtifacts.warnings,
|
|
67
|
+
...storageArtifacts.warnings,
|
|
68
|
+
...datasetArtifacts.warnings,
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
files: [
|
|
73
|
+
...baseFiles,
|
|
74
|
+
...authArtifacts.files,
|
|
75
|
+
...authzArtifacts.files,
|
|
76
|
+
...storageArtifacts.files,
|
|
77
|
+
...datasetArtifacts.files,
|
|
78
|
+
],
|
|
79
|
+
warnings,
|
|
80
|
+
meta: {
|
|
81
|
+
target: 'minikube',
|
|
82
|
+
providers: collectProviders(manifest),
|
|
83
|
+
},
|
|
84
|
+
dependencies: collectDependencies(manifest),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function collectProviders(manifest: InfraManifestInput): string[] {
|
|
89
|
+
const providers = new Set<string>();
|
|
90
|
+
|
|
91
|
+
if (manifest.auth?.provider) providers.add(manifest.auth.provider);
|
|
92
|
+
if (manifest.auth?.authorization.engine) providers.add(manifest.auth.authorization.engine);
|
|
93
|
+
if (manifest.database?.provider) providers.add(manifest.database.provider);
|
|
94
|
+
if (manifest.storage?.provider) providers.add(manifest.storage.provider);
|
|
95
|
+
if (manifest.state?.provider) providers.add(manifest.state.provider);
|
|
96
|
+
|
|
97
|
+
return [...providers].sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function collectDependencies(manifest: InfraManifestInput): readonly GeneratedPackageDependency[] {
|
|
101
|
+
if (manifest.state?.provider !== 'legend') {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
name: '@ankhorage/state-legend',
|
|
108
|
+
version: '^0.1.0',
|
|
109
|
+
reason: 'Selected by infra.state.provider=legend.',
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function collectWarnings(manifest: InfraManifestInput): string[] {
|
|
115
|
+
const warnings: string[] = [];
|
|
116
|
+
|
|
117
|
+
if (manifest.deployment?.monitoring) {
|
|
118
|
+
warnings.push(
|
|
119
|
+
'Monitoring is enabled in manifest, but monitoring stack resources are not generated yet.',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (manifest.networking?.cdn) {
|
|
124
|
+
warnings.push('CDN is enabled in manifest, but CDN resources are not generated for minikube.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return warnings;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveNamespace(
|
|
131
|
+
manifest: InfraManifestInput,
|
|
132
|
+
options: InfrastructureGenerationOptions,
|
|
133
|
+
): string {
|
|
134
|
+
const source =
|
|
135
|
+
manifest.networking?.domain?.trim() ?? options.namespaceHint?.trim() ?? DEFAULT_NAMESPACE;
|
|
136
|
+
|
|
137
|
+
const normalized = source
|
|
138
|
+
.toLowerCase()
|
|
139
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
140
|
+
.replace(/-+/g, '-')
|
|
141
|
+
.replace(/^-|-$/g, '');
|
|
142
|
+
|
|
143
|
+
const safe = normalized.slice(0, 63).replace(/-+$/g, '');
|
|
144
|
+
return safe || DEFAULT_NAMESPACE;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function unique(values: string[]): string[] {
|
|
148
|
+
return [...new Set(values)];
|
|
149
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { InfraManifestInput } from '../../../types';
|
|
2
|
+
import { emptyMinikubeArtifacts, type MinikubeAdapterArtifacts } from '../contracts';
|
|
3
|
+
import { generateSupabaseStorageArtifacts } from './supabase';
|
|
4
|
+
|
|
5
|
+
export function generateStorageArtifacts(args: {
|
|
6
|
+
manifest: InfraManifestInput;
|
|
7
|
+
namespace: string;
|
|
8
|
+
}): MinikubeAdapterArtifacts {
|
|
9
|
+
const { manifest, namespace } = args;
|
|
10
|
+
const spec = manifest.storage;
|
|
11
|
+
|
|
12
|
+
if (!spec) {
|
|
13
|
+
return emptyMinikubeArtifacts();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const buckets = normalizeBuckets(spec.buckets);
|
|
17
|
+
const { provider } = spec;
|
|
18
|
+
const includeSupabaseConnectionEnvEntries = manifest.auth?.provider !== 'supabase';
|
|
19
|
+
const emptyBucketsWarning =
|
|
20
|
+
'Storage buckets are empty after normalization; no storage artifacts generated.';
|
|
21
|
+
|
|
22
|
+
switch (provider) {
|
|
23
|
+
case 'auto': {
|
|
24
|
+
const shouldResolveToSupabase =
|
|
25
|
+
manifest.database?.provider === 'supabase' || manifest.auth?.provider === 'supabase';
|
|
26
|
+
|
|
27
|
+
if (!shouldResolveToSupabase) {
|
|
28
|
+
return {
|
|
29
|
+
...emptyMinikubeArtifacts(),
|
|
30
|
+
warnings: [
|
|
31
|
+
'Storage provider "auto" could not be resolved for minikube; no storage artifacts generated.',
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (buckets.length === 0) {
|
|
37
|
+
return {
|
|
38
|
+
...emptyMinikubeArtifacts(),
|
|
39
|
+
warnings: [emptyBucketsWarning],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return generateSupabaseStorageArtifacts({
|
|
44
|
+
namespace,
|
|
45
|
+
buckets,
|
|
46
|
+
includeSupabaseConnectionEnvEntries,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
case 'supabase': {
|
|
50
|
+
if (buckets.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
...emptyMinikubeArtifacts(),
|
|
53
|
+
warnings: [emptyBucketsWarning],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return generateSupabaseStorageArtifacts({
|
|
58
|
+
namespace,
|
|
59
|
+
buckets,
|
|
60
|
+
includeSupabaseConnectionEnvEntries,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
case 's3':
|
|
64
|
+
case 'r2':
|
|
65
|
+
return {
|
|
66
|
+
...emptyMinikubeArtifacts(),
|
|
67
|
+
warnings: [
|
|
68
|
+
`Storage provider "${provider}" is not implemented for minikube yet; no storage artifacts generated.`,
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
default: {
|
|
72
|
+
const exhaustiveCheck: never = provider;
|
|
73
|
+
return exhaustiveCheck;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeBuckets(rawBuckets: readonly string[]): string[] {
|
|
79
|
+
const normalized: string[] = [];
|
|
80
|
+
const seen = new Set<string>();
|
|
81
|
+
|
|
82
|
+
for (const raw of rawBuckets) {
|
|
83
|
+
const bucket = raw.trim();
|
|
84
|
+
if (!bucket) continue;
|
|
85
|
+
if (seen.has(bucket)) continue;
|
|
86
|
+
seen.add(bucket);
|
|
87
|
+
normalized.push(bucket);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { MinikubeAdapterArtifacts } from '../../contracts';
|
|
2
|
+
|
|
3
|
+
export function generateSupabaseStorageArtifacts(args: {
|
|
4
|
+
namespace: string;
|
|
5
|
+
buckets: string[];
|
|
6
|
+
includeSupabaseConnectionEnvEntries: boolean;
|
|
7
|
+
}): MinikubeAdapterArtifacts {
|
|
8
|
+
const { namespace, buckets, includeSupabaseConnectionEnvEntries } = args;
|
|
9
|
+
const root = 'infra/minikube/k8s/storage/supabase';
|
|
10
|
+
const resourceRoot = 'storage/supabase';
|
|
11
|
+
const bucketsCsv = buckets.join(',');
|
|
12
|
+
const defaultBucket = buckets[0] ?? '';
|
|
13
|
+
|
|
14
|
+
const envEntries = [
|
|
15
|
+
'STORAGE_PROVIDER=supabase',
|
|
16
|
+
'STORAGE_RUNTIME_MODE=local',
|
|
17
|
+
`STORAGE_BUCKETS=${bucketsCsv}`,
|
|
18
|
+
`STORAGE_DEFAULT_BUCKET=${defaultBucket}`,
|
|
19
|
+
'EXPO_PUBLIC_STORAGE_PROVIDER=supabase',
|
|
20
|
+
`EXPO_PUBLIC_STORAGE_BUCKETS=${bucketsCsv}`,
|
|
21
|
+
`EXPO_PUBLIC_STORAGE_DEFAULT_BUCKET=${defaultBucket}`,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
if (includeSupabaseConnectionEnvEntries) {
|
|
25
|
+
envEntries.push(
|
|
26
|
+
'',
|
|
27
|
+
'# Supabase provider connection details',
|
|
28
|
+
'SUPABASE_SECRET_SYNC_ENABLED=true',
|
|
29
|
+
'SUPABASE_URL=',
|
|
30
|
+
'SUPABASE_ANON_KEY=',
|
|
31
|
+
'SUPABASE_SERVICE_ROLE_KEY=',
|
|
32
|
+
'SUPABASE_JWT_SECRET=',
|
|
33
|
+
'EXPO_PUBLIC_SUPABASE_URL=',
|
|
34
|
+
'EXPO_PUBLIC_SUPABASE_ANON_KEY=',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
files: [
|
|
40
|
+
{
|
|
41
|
+
path: `${root}/supabase-storage.configmap.yaml`,
|
|
42
|
+
content: getSupabaseStorageConfigMap({
|
|
43
|
+
namespace,
|
|
44
|
+
bucketsCsv,
|
|
45
|
+
defaultBucket,
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: `${root}/app-runtime-storage.env.configmap.yaml`,
|
|
50
|
+
content: getRuntimeStorageEnvConfigMap({
|
|
51
|
+
namespace,
|
|
52
|
+
bucketsCsv,
|
|
53
|
+
defaultBucket,
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
resources: [
|
|
58
|
+
`${resourceRoot}/supabase-storage.configmap.yaml`,
|
|
59
|
+
`${resourceRoot}/app-runtime-storage.env.configmap.yaml`,
|
|
60
|
+
],
|
|
61
|
+
envEntries,
|
|
62
|
+
warnings: [
|
|
63
|
+
`Storage buckets are configured but not created automatically yet. Ensure buckets exist in Supabase Storage: ${bucketsCsv}.`,
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getSupabaseStorageConfigMap(args: {
|
|
69
|
+
namespace: string;
|
|
70
|
+
bucketsCsv: string;
|
|
71
|
+
defaultBucket: string;
|
|
72
|
+
}) {
|
|
73
|
+
const { namespace, bucketsCsv, defaultBucket } = args;
|
|
74
|
+
|
|
75
|
+
return `apiVersion: v1
|
|
76
|
+
kind: ConfigMap
|
|
77
|
+
metadata:
|
|
78
|
+
name: supabase-storage-config
|
|
79
|
+
namespace: ${namespace}
|
|
80
|
+
data:
|
|
81
|
+
STORAGE_PROVIDER: "supabase"
|
|
82
|
+
STORAGE_BUCKETS: "${escapeYamlDoubleQuoted(bucketsCsv)}"
|
|
83
|
+
STORAGE_DEFAULT_BUCKET: "${escapeYamlDoubleQuoted(defaultBucket)}"
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getRuntimeStorageEnvConfigMap(args: {
|
|
88
|
+
namespace: string;
|
|
89
|
+
bucketsCsv: string;
|
|
90
|
+
defaultBucket: string;
|
|
91
|
+
}) {
|
|
92
|
+
const { namespace, bucketsCsv, defaultBucket } = args;
|
|
93
|
+
|
|
94
|
+
return `apiVersion: v1
|
|
95
|
+
kind: ConfigMap
|
|
96
|
+
metadata:
|
|
97
|
+
name: app-runtime-storage-env
|
|
98
|
+
namespace: ${namespace}
|
|
99
|
+
data:
|
|
100
|
+
STORAGE_PROVIDER: "supabase"
|
|
101
|
+
STORAGE_RUNTIME_MODE: "local"
|
|
102
|
+
STORAGE_BUCKETS: "${escapeYamlDoubleQuoted(bucketsCsv)}"
|
|
103
|
+
STORAGE_DEFAULT_BUCKET: "${escapeYamlDoubleQuoted(defaultBucket)}"
|
|
104
|
+
EXPO_PUBLIC_STORAGE_PROVIDER: "supabase"
|
|
105
|
+
EXPO_PUBLIC_STORAGE_BUCKETS: "${escapeYamlDoubleQuoted(bucketsCsv)}"
|
|
106
|
+
EXPO_PUBLIC_STORAGE_DEFAULT_BUCKET: "${escapeYamlDoubleQuoted(defaultBucket)}"
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function escapeYamlDoubleQuoted(value: string): string {
|
|
111
|
+
return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
112
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { AppManifest } from '@ankhorage/contracts';
|
|
2
|
+
import { describe, expect, test } from 'bun:test';
|
|
3
|
+
|
|
4
|
+
import { generateInfrastructure } from './index';
|
|
5
|
+
import type { InfraManifestInput } from './types';
|
|
6
|
+
|
|
7
|
+
describe('dataset infrastructure generation', () => {
|
|
8
|
+
test('generates provider-neutral dataset, seed, API, and OpenAPI artifacts', () => {
|
|
9
|
+
const manifest: InfraManifestInput = {
|
|
10
|
+
deployment: {
|
|
11
|
+
target: 'minikube',
|
|
12
|
+
monitoring: false,
|
|
13
|
+
},
|
|
14
|
+
database: {
|
|
15
|
+
provider: 'supabase',
|
|
16
|
+
tier: 'dev',
|
|
17
|
+
},
|
|
18
|
+
plugins: [],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const appManifest: Pick<
|
|
22
|
+
AppManifest,
|
|
23
|
+
'data' | 'metadata' | 'navigator' | 'screens' | 'settings'
|
|
24
|
+
> = {
|
|
25
|
+
metadata: {
|
|
26
|
+
name: 'Cards',
|
|
27
|
+
slug: 'cards',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
themeId: 'default',
|
|
30
|
+
},
|
|
31
|
+
data: {
|
|
32
|
+
datasets: {
|
|
33
|
+
poker_situations: {
|
|
34
|
+
id: 'poker_situations',
|
|
35
|
+
label: 'Poker situations',
|
|
36
|
+
description: 'App-owned poker trainer situations.',
|
|
37
|
+
collection: {
|
|
38
|
+
name: 'poker_situations',
|
|
39
|
+
primaryKey: 'id',
|
|
40
|
+
fields: [
|
|
41
|
+
{ name: 'id', type: 'uuid', required: true, unique: true },
|
|
42
|
+
{ name: 'title', type: 'text', required: true },
|
|
43
|
+
{ name: 'description', type: 'text', required: true },
|
|
44
|
+
{ name: 'availableActions', type: 'json' },
|
|
45
|
+
{ name: 'correctAction', type: 'text' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
operations: ['list', 'read', 'create'],
|
|
49
|
+
seed: [
|
|
50
|
+
{
|
|
51
|
+
title: 'Button faces a raise',
|
|
52
|
+
description: 'Choose the best action with position and stack depth in mind.',
|
|
53
|
+
availableActions: [
|
|
54
|
+
{ label: 'Fold', value: 'fold' },
|
|
55
|
+
{ label: 'Call', value: 'call' },
|
|
56
|
+
{ label: 'Raise', value: 'raise' },
|
|
57
|
+
],
|
|
58
|
+
correctAction: 'raise',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
navigator: {
|
|
65
|
+
type: 'stack',
|
|
66
|
+
routes: [],
|
|
67
|
+
},
|
|
68
|
+
screens: {},
|
|
69
|
+
settings: {
|
|
70
|
+
localization: {
|
|
71
|
+
defaultLocale: 'en',
|
|
72
|
+
locales: ['en'],
|
|
73
|
+
},
|
|
74
|
+
authFlow: {
|
|
75
|
+
signInRoute: '/sign-in',
|
|
76
|
+
signUpRoute: '/sign-up',
|
|
77
|
+
signOutRoute: '/sign-out',
|
|
78
|
+
unauthorizedRoute: '/sign-in',
|
|
79
|
+
postSignInRoute: '/',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = generateInfrastructure(manifest, { namespaceHint: 'cards', appManifest });
|
|
85
|
+
const rerun = generateInfrastructure(manifest, { namespaceHint: 'cards', appManifest });
|
|
86
|
+
const paths = result.files.map((file) => file.path);
|
|
87
|
+
|
|
88
|
+
expect(result.files).toEqual(rerun.files);
|
|
89
|
+
expect(paths).toContain('infra/minikube/db/datasets.json');
|
|
90
|
+
expect(paths).toContain('infra/minikube/db/dataset-seed.json');
|
|
91
|
+
expect(paths).toContain('infra/minikube/db/migrations/001_datasets.sql');
|
|
92
|
+
expect(paths).toContain('infra/minikube/db/seeds/001_dataset_seed.sql');
|
|
93
|
+
expect(paths).toContain('infra/minikube/db/README.md');
|
|
94
|
+
expect(paths).toContain('src/generated/datasets/collections.ts');
|
|
95
|
+
expect(paths).toContain('src/generated/datasets/appDatasetApi.ts');
|
|
96
|
+
expect(paths).toContain('src/generated/datasets/datasetApiHandlers.ts');
|
|
97
|
+
expect(paths).toContain('src/generated/datasets/openapi.json');
|
|
98
|
+
|
|
99
|
+
const collectionsFile = result.files.find(
|
|
100
|
+
(file) => file.path === 'src/generated/datasets/collections.ts',
|
|
101
|
+
);
|
|
102
|
+
expect(collectionsFile?.content).toContain('GENERATED_DATASETS');
|
|
103
|
+
expect(collectionsFile?.content).toContain('poker_situations');
|
|
104
|
+
expect(collectionsFile?.content).toContain('availableActions');
|
|
105
|
+
|
|
106
|
+
const apiFile = result.files.find(
|
|
107
|
+
(file) => file.path === 'src/generated/datasets/appDatasetApi.ts',
|
|
108
|
+
);
|
|
109
|
+
expect(apiFile?.content).toContain('poker_situations.list');
|
|
110
|
+
expect(apiFile?.content).toContain('/api/datasets/poker_situations');
|
|
111
|
+
expect(apiFile?.content).toContain('poker_situations.read');
|
|
112
|
+
expect(apiFile?.content).toContain('/api/datasets/poker_situations/{id}');
|
|
113
|
+
|
|
114
|
+
const handlersFile = result.files.find(
|
|
115
|
+
(file) => file.path === 'src/generated/datasets/datasetApiHandlers.ts',
|
|
116
|
+
);
|
|
117
|
+
expect(handlersFile?.content).toContain('handleGeneratedDatasetList');
|
|
118
|
+
expect(handlersFile?.content).toContain('handleGeneratedDatasetCreate');
|
|
119
|
+
expect(handlersFile?.content).toContain('createGeneratedDatasetMemoryStore');
|
|
120
|
+
expect(handlersFile?.content).toContain('poker_situations');
|
|
121
|
+
|
|
122
|
+
const openApiFile = result.files.find(
|
|
123
|
+
(file) => file.path === 'src/generated/datasets/openapi.json',
|
|
124
|
+
);
|
|
125
|
+
expect(openApiFile?.content).toContain('Generated app dataset API');
|
|
126
|
+
expect(openApiFile?.content).toContain('poker_situations.create');
|
|
127
|
+
expect(openApiFile?.content).toContain('x-ankh-dataset');
|
|
128
|
+
|
|
129
|
+
const migrationFile = result.files.find(
|
|
130
|
+
(file) => file.path === 'infra/minikube/db/migrations/001_datasets.sql',
|
|
131
|
+
);
|
|
132
|
+
expect(migrationFile?.content).toContain('create extension if not exists pgcrypto');
|
|
133
|
+
expect(migrationFile?.content).toContain(
|
|
134
|
+
'create table if not exists "public"."poker_situations"',
|
|
135
|
+
);
|
|
136
|
+
expect(migrationFile?.content).toContain('"id" uuid default gen_random_uuid() primary key');
|
|
137
|
+
expect(migrationFile?.content).toContain('"availableActions" jsonb');
|
|
138
|
+
|
|
139
|
+
const seedFile = result.files.find(
|
|
140
|
+
(file) => file.path === 'infra/minikube/db/dataset-seed.json',
|
|
141
|
+
);
|
|
142
|
+
expect(seedFile?.content).toContain('Button faces a raise');
|
|
143
|
+
expect(seedFile?.content).toContain('correctAction');
|
|
144
|
+
expect(seedFile?.content).toContain('"id"');
|
|
145
|
+
|
|
146
|
+
const seedSqlFile = result.files.find(
|
|
147
|
+
(file) => file.path === 'infra/minikube/db/seeds/001_dataset_seed.sql',
|
|
148
|
+
);
|
|
149
|
+
expect(seedSqlFile?.content).toContain('insert into "public"."poker_situations"');
|
|
150
|
+
expect(seedSqlFile?.content).toContain('Button faces a raise');
|
|
151
|
+
expect(seedSqlFile?.content).toContain('on conflict ("id") do nothing');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('reports selected state adapter dependencies', () => {
|
|
155
|
+
const manifest: InfraManifestInput = {
|
|
156
|
+
deployment: {
|
|
157
|
+
target: 'minikube',
|
|
158
|
+
monitoring: false,
|
|
159
|
+
},
|
|
160
|
+
state: {
|
|
161
|
+
provider: 'legend',
|
|
162
|
+
persistence: 'none',
|
|
163
|
+
},
|
|
164
|
+
plugins: [],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = generateInfrastructure(manifest, { namespaceHint: 'cards' });
|
|
168
|
+
|
|
169
|
+
expect(result.meta.providers).toContain('legend');
|
|
170
|
+
expect(result.dependencies).toEqual([
|
|
171
|
+
{
|
|
172
|
+
name: '@ankhorage/state-legend',
|
|
173
|
+
version: '^0.1.0',
|
|
174
|
+
reason: 'Selected by infra.state.provider=legend.',
|
|
175
|
+
},
|
|
176
|
+
]);
|
|
177
|
+
});
|
|
178
|
+
});
|