@vertile-ai/iac 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -37
- package/docs/README.md +3 -2
- package/docs/index.html +2 -2
- package/docs/manifest.md +38 -5
- package/examples/bun-hono-api/README.md +9 -0
- package/examples/bun-hono-api/infrastructure/iac/iac.do.json +87 -0
- package/examples/bun-hono-api/infrastructure/iac/iac.json +107 -0
- package/examples/bun-hono-api/package.json +15 -0
- package/examples/bun-hono-api/src/index.ts +7 -0
- package/examples/go-api/README.md +8 -0
- package/examples/go-api/cmd/server/main.go +16 -0
- package/examples/go-api/go.mod +3 -0
- package/examples/go-api/infrastructure/iac/iac.do.json +90 -0
- package/examples/go-api/infrastructure/iac/iac.json +106 -0
- package/examples/next-monorepo/README.md +22 -0
- package/examples/next-monorepo/apps/admin/package.json +7 -0
- package/examples/next-monorepo/apps/web/package.json +7 -0
- package/examples/next-monorepo/infrastructure/iac/iac.aws.json +172 -0
- package/examples/next-monorepo/infrastructure/iac/iac.do.json +129 -0
- package/examples/{dynomic → next-monorepo}/infrastructure/iac/iac.json +102 -23
- package/examples/next-monorepo/infrastructure/iac/iac.vercel.json +109 -0
- package/examples/{dynomic → next-monorepo}/package.json +2 -2
- package/examples/node-api/README.md +8 -0
- package/examples/node-api/infrastructure/iac/iac.aws.json +132 -0
- package/examples/node-api/infrastructure/iac/iac.json +111 -0
- package/examples/node-api/package.json +12 -0
- package/examples/node-api/src/server.js +8 -0
- package/examples/python-fastapi-api/README.md +8 -0
- package/examples/python-fastapi-api/app/main.py +8 -0
- package/examples/python-fastapi-api/infrastructure/iac/iac.aws.json +129 -0
- package/examples/python-fastapi-api/infrastructure/iac/iac.json +113 -0
- package/examples/python-fastapi-api/pyproject.toml +10 -0
- package/examples/react-spa/README.md +8 -0
- package/examples/react-spa/index.html +2 -0
- package/examples/react-spa/infrastructure/iac/iac.aws.json +115 -0
- package/examples/react-spa/infrastructure/iac/iac.json +111 -0
- package/examples/react-spa/infrastructure/iac/iac.vercel.json +76 -0
- package/examples/react-spa/package.json +19 -0
- package/examples/react-spa/src/main.jsx +8 -0
- package/examples/sveltekit-web/README.md +9 -0
- package/examples/sveltekit-web/infrastructure/iac/iac.json +76 -0
- package/examples/sveltekit-web/infrastructure/iac/iac.vercel.json +74 -0
- package/examples/sveltekit-web/package.json +19 -0
- package/examples/sveltekit-web/src/routes/+page.svelte +3 -0
- package/examples/sveltekit-web/svelte.config.js +7 -0
- package/package.json +1 -1
- package/schema/iac.schema.json +83 -2
- package/src/apply.mjs +3 -4
- package/src/cli.mjs +1 -0
- package/src/core/context.mjs +4 -2
- package/src/core/deployments.mjs +39 -0
- package/src/core/env-files.mjs +38 -0
- package/src/core/env-source.mjs +5 -0
- package/src/core/hcl.mjs +2 -1
- package/src/core/manifest.mjs +15 -1
- package/src/core/render.mjs +19 -8
- package/src/core/vercel-manifests.mjs +5 -11
- package/src/plan.mjs +3 -4
- package/src/providers/aws/index.mjs +43 -24
- package/src/provision-env.mjs +72 -24
- package/src/render.mjs +3 -4
- package/src/shared.mjs +0 -4
- package/src/sync-env.mjs +33 -38
- package/examples/dynomic/README.md +0 -22
- package/examples/dynomic/apps/admin/package.json +0 -7
- package/examples/dynomic/apps/web/package.json +0 -7
- /package/examples/{dynomic → next-monorepo}/apps/admin/vercel.json +0 -0
- /package/examples/{dynomic → next-monorepo}/apps/web/vercel.json +0 -0
package/src/sync-env.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import process from 'node:process'
|
|
6
6
|
import { resolvePlatformContext } from './core/context.mjs'
|
|
7
|
+
import { envSourceDir } from './core/env-source.mjs'
|
|
8
|
+
import { environmentFiles, environmentOutputFile } from './core/env-files.mjs'
|
|
7
9
|
import { readManifest } from './core/manifest.mjs'
|
|
8
10
|
import { readOption } from './shared.mjs'
|
|
9
11
|
|
|
@@ -26,30 +28,36 @@ function splitList(value) {
|
|
|
26
28
|
.filter(Boolean)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
function
|
|
31
|
+
function configuredVariants(manifest) {
|
|
32
|
+
const variants = { ...defaultVariants }
|
|
33
|
+
const configured = manifest.env?.environments || {}
|
|
34
|
+
for (const [name, config] of Object.entries(configured)) {
|
|
35
|
+
const strict = config && typeof config === 'object' && !Array.isArray(config)
|
|
36
|
+
? config.strict ?? true
|
|
37
|
+
: true
|
|
38
|
+
variants[name] = {
|
|
39
|
+
output: environmentOutputFile(manifest, name),
|
|
40
|
+
sources: environmentFiles(manifest, name),
|
|
41
|
+
strict,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return variants
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function selectedVariantNames(argv, variants) {
|
|
30
48
|
const value = readOption(argv, '--variants')
|
|
31
49
|
if (!value) return ['local', 'production', 'staging', 'test']
|
|
32
50
|
|
|
33
51
|
const names = splitList(value)
|
|
34
|
-
const invalid = names.filter((name) => !
|
|
52
|
+
const invalid = names.filter((name) => !variants[name])
|
|
35
53
|
if (invalid.length > 0) {
|
|
36
54
|
throw new Error(
|
|
37
|
-
`Invalid --variants values: ${invalid.join(', ')}. Supported: ${Object.keys(
|
|
55
|
+
`Invalid --variants values: ${invalid.join(', ')}. Supported: ${Object.keys(variants).join(',')}`,
|
|
38
56
|
)
|
|
39
57
|
}
|
|
40
58
|
return names
|
|
41
59
|
}
|
|
42
60
|
|
|
43
|
-
function envSourceDir(manifest) {
|
|
44
|
-
return (
|
|
45
|
-
manifest.env.sourceDir ||
|
|
46
|
-
manifest.env.dir ||
|
|
47
|
-
manifest.env.infraDir ||
|
|
48
|
-
manifest.infraDir ||
|
|
49
|
-
'infrastructure'
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
61
|
function parseEnvLine(line) {
|
|
54
62
|
if (!line) return null
|
|
55
63
|
const trimmed = line.trim()
|
|
@@ -89,23 +97,24 @@ function entriesToLines(entries) {
|
|
|
89
97
|
|
|
90
98
|
function resolveLayer({ rootDir, baseDir, variant }) {
|
|
91
99
|
const examplePath = path.join(baseDir, '.env.example')
|
|
92
|
-
const
|
|
100
|
+
const sourcePaths = variant.sources.map((source) => path.join(baseDir, source))
|
|
101
|
+
const existingSourcePaths = sourcePaths.filter((sourcePath) => fs.existsSync(sourcePath))
|
|
93
102
|
|
|
94
103
|
if (variant.strict) {
|
|
95
|
-
if (
|
|
104
|
+
if (existingSourcePaths.length === 0) {
|
|
96
105
|
throw new Error(
|
|
97
|
-
`Missing required
|
|
106
|
+
`Missing required env source file: ${sourcePaths.map((sourcePath) => path.relative(rootDir, sourcePath)).join(' or ')}`,
|
|
98
107
|
)
|
|
99
108
|
}
|
|
100
109
|
return {
|
|
101
|
-
entries: readEnvFile
|
|
102
|
-
sourceLabel: path.relative(rootDir, sourcePath),
|
|
110
|
+
entries: mergeLayers(existingSourcePaths.map(readEnvFile)),
|
|
111
|
+
sourceLabel: existingSourcePaths.map((sourcePath) => path.relative(rootDir, sourcePath)).join(' + '),
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
if (!fs.existsSync(examplePath) &&
|
|
115
|
+
if (!fs.existsSync(examplePath) && existingSourcePaths.length === 0) {
|
|
107
116
|
throw new Error(
|
|
108
|
-
`Missing required
|
|
117
|
+
`Missing required env source files: ${sourcePaths.map((sourcePath) => path.relative(rootDir, sourcePath)).join(' or ')} or ${path.relative(rootDir, examplePath)}`,
|
|
109
118
|
)
|
|
110
119
|
}
|
|
111
120
|
|
|
@@ -115,7 +124,7 @@ function resolveLayer({ rootDir, baseDir, variant }) {
|
|
|
115
124
|
layers.push(readEnvFile(examplePath))
|
|
116
125
|
labels.push(path.relative(rootDir, examplePath))
|
|
117
126
|
}
|
|
118
|
-
|
|
127
|
+
for (const sourcePath of existingSourcePaths) {
|
|
119
128
|
layers.push(readEnvFile(sourcePath))
|
|
120
129
|
labels.push(path.relative(rootDir, sourcePath))
|
|
121
130
|
}
|
|
@@ -144,19 +153,6 @@ function projectSharedLayer(sharedLayer, app, sharedPrefixes = []) {
|
|
|
144
153
|
.filter(({ key }) => key)
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
function assertNoSharedOverrides(sharedLayer, packageLayer, appKey) {
|
|
148
|
-
const sharedKeys = new Set(sharedLayer.map(({ key }) => key))
|
|
149
|
-
const overlaps = packageLayer
|
|
150
|
-
.map(({ key }) => key)
|
|
151
|
-
.filter((key, index, keys) => sharedKeys.has(key) && keys.indexOf(key) === index)
|
|
152
|
-
|
|
153
|
-
if (overlaps.length === 0) return
|
|
154
|
-
|
|
155
|
-
throw new Error(
|
|
156
|
-
`Detected shared env key overrides for app "${appKey}": ${overlaps.join(', ')}`,
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
156
|
function linesToEnvMap(lines) {
|
|
161
157
|
const values = new Map()
|
|
162
158
|
for (const line of lines) {
|
|
@@ -219,9 +215,10 @@ async function main() {
|
|
|
219
215
|
const context = resolvePlatformContext(argv)
|
|
220
216
|
const manifest = readManifest(context.manifestPath)
|
|
221
217
|
const dryRun = hasFlag(argv, '--dry-run')
|
|
222
|
-
const
|
|
218
|
+
const availableVariants = configuredVariants(manifest)
|
|
219
|
+
const variants = selectedVariantNames(argv, availableVariants).map((name) => ({
|
|
223
220
|
name,
|
|
224
|
-
...
|
|
221
|
+
...availableVariants[name],
|
|
225
222
|
}))
|
|
226
223
|
|
|
227
224
|
if (shouldSkipSync(manifest)) {
|
|
@@ -248,8 +245,6 @@ async function main() {
|
|
|
248
245
|
})
|
|
249
246
|
|
|
250
247
|
const sharedEntries = projectSharedLayer(shared.entries, app, sharedPrefixes)
|
|
251
|
-
assertNoSharedOverrides(sharedEntries, scoped.entries, app.key)
|
|
252
|
-
|
|
253
248
|
const merged = mergeLayers([sharedEntries, scoped.entries])
|
|
254
249
|
const mergedLines = entriesToLines(merged)
|
|
255
250
|
const outputPath = path.join(appOutputDir(context.repoRoot, app), variant.output)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Dynomic
|
|
2
|
-
|
|
3
|
-
Dynomic is a fixture project used to exercise Vertile AI IaC from the shape a
|
|
4
|
-
product repo would keep. It intentionally uses the full current manifest surface:
|
|
5
|
-
multiple Vercel apps, domains, preview/production env provisioning, local env
|
|
6
|
-
sync, portable storage/database/queue/compute concepts, provider overrides, and
|
|
7
|
-
provider-specific escape hatch resources.
|
|
8
|
-
|
|
9
|
-
It intentionally contains no real secrets. The env files use placeholder values
|
|
10
|
-
so package consumers can inspect and run dry-runs without extra setup.
|
|
11
|
-
|
|
12
|
-
## Commands
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
vertile-iac sync-env --repo-root examples/dynomic --variants=staging,production
|
|
16
|
-
vertile-iac env --repo-root examples/dynomic --targets=preview,production
|
|
17
|
-
vertile-iac projects --repo-root examples/dynomic --projects=web
|
|
18
|
-
vertile-iac domains --repo-root examples/dynomic --projects=web
|
|
19
|
-
vertile-iac render --repo-root examples/dynomic --target=all --env=production
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Apply commands require `VERCEL_TOKEN` and a real Vercel team/project.
|
|
File without changes
|
|
File without changes
|