tova 0.7.0 → 0.9.4
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/bin/tova.js +1312 -139
- package/package.json +8 -1
- package/src/analyzer/analyzer.js +539 -11
- package/src/analyzer/browser-analyzer.js +56 -8
- package/src/analyzer/deploy-analyzer.js +44 -0
- package/src/analyzer/scope.js +7 -0
- package/src/analyzer/server-analyzer.js +33 -1
- package/src/codegen/base-codegen.js +1296 -23
- package/src/codegen/browser-codegen.js +725 -20
- package/src/codegen/codegen.js +87 -5
- package/src/codegen/deploy-codegen.js +49 -0
- package/src/codegen/server-codegen.js +54 -6
- package/src/codegen/shared-codegen.js +5 -0
- package/src/codegen/theme-codegen.js +69 -0
- package/src/codegen/wasm-codegen.js +6 -0
- package/src/config/edit-toml.js +6 -2
- package/src/config/git-resolver.js +128 -0
- package/src/config/lock-file.js +57 -0
- package/src/config/module-cache.js +58 -0
- package/src/config/module-entry.js +37 -0
- package/src/config/module-path.js +63 -0
- package/src/config/pkg-errors.js +62 -0
- package/src/config/resolve.js +26 -0
- package/src/config/resolver.js +139 -0
- package/src/config/search.js +28 -0
- package/src/config/semver.js +72 -0
- package/src/config/toml.js +61 -6
- package/src/deploy/deploy.js +217 -0
- package/src/deploy/infer.js +218 -0
- package/src/deploy/provision.js +315 -0
- package/src/diagnostics/security-scorecard.js +111 -0
- package/src/lexer/lexer.js +18 -3
- package/src/lsp/server.js +482 -0
- package/src/parser/animate-ast.js +45 -0
- package/src/parser/ast.js +39 -0
- package/src/parser/browser-ast.js +19 -1
- package/src/parser/browser-parser.js +221 -4
- package/src/parser/concurrency-ast.js +15 -0
- package/src/parser/concurrency-parser.js +236 -0
- package/src/parser/deploy-ast.js +37 -0
- package/src/parser/deploy-parser.js +132 -0
- package/src/parser/parser.js +42 -5
- package/src/parser/select-ast.js +39 -0
- package/src/parser/theme-ast.js +29 -0
- package/src/parser/theme-parser.js +70 -0
- package/src/registry/plugins/concurrency-plugin.js +32 -0
- package/src/registry/plugins/deploy-plugin.js +33 -0
- package/src/registry/plugins/theme-plugin.js +20 -0
- package/src/registry/register-all.js +6 -0
- package/src/runtime/charts.js +547 -0
- package/src/runtime/embedded.js +6 -2
- package/src/runtime/reactivity.js +60 -0
- package/src/runtime/router.js +703 -295
- package/src/runtime/table.js +606 -33
- package/src/stdlib/inline.js +365 -10
- package/src/stdlib/runtime-bridge.js +152 -0
- package/src/stdlib/string.js +84 -2
- package/src/stdlib/validation.js +1 -1
- package/src/version.js +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// CLI deploy command entry point for the Tova language
|
|
2
|
+
// Provides argument parsing and deploy orchestration.
|
|
3
|
+
// SSH execution is stubbed for now — will be wired in integration.
|
|
4
|
+
|
|
5
|
+
import { inferInfrastructure } from './infer.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse CLI deploy arguments into a config object.
|
|
9
|
+
*
|
|
10
|
+
* tova deploy prod --plan
|
|
11
|
+
* tova deploy prod --rollback
|
|
12
|
+
* tova deploy prod --logs --since "1 hour ago"
|
|
13
|
+
* tova deploy prod --status
|
|
14
|
+
* tova deploy prod --ssh
|
|
15
|
+
* tova deploy prod --setup-git
|
|
16
|
+
* tova deploy --list --server root@example.com
|
|
17
|
+
* tova deploy prod --remove
|
|
18
|
+
* tova deploy prod --logs --instance 1
|
|
19
|
+
*/
|
|
20
|
+
export function parseDeployArgs(args) {
|
|
21
|
+
const result = {
|
|
22
|
+
envName: null,
|
|
23
|
+
plan: false,
|
|
24
|
+
rollback: false,
|
|
25
|
+
logs: false,
|
|
26
|
+
status: false,
|
|
27
|
+
ssh: false,
|
|
28
|
+
setupGit: false,
|
|
29
|
+
remove: false,
|
|
30
|
+
list: false,
|
|
31
|
+
server: null,
|
|
32
|
+
since: null,
|
|
33
|
+
instance: null,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i];
|
|
38
|
+
switch (arg) {
|
|
39
|
+
case '--plan': result.plan = true; break;
|
|
40
|
+
case '--rollback': result.rollback = true; break;
|
|
41
|
+
case '--logs': result.logs = true; break;
|
|
42
|
+
case '--status': result.status = true; break;
|
|
43
|
+
case '--ssh': result.ssh = true; break;
|
|
44
|
+
case '--setup-git': result.setupGit = true; break;
|
|
45
|
+
case '--remove': result.remove = true; break;
|
|
46
|
+
case '--list': result.list = true; break;
|
|
47
|
+
case '--server': result.server = args[++i]; break;
|
|
48
|
+
case '--since': result.since = args[++i]; break;
|
|
49
|
+
case '--instance': result.instance = parseInt(args[++i], 10); break;
|
|
50
|
+
default:
|
|
51
|
+
if (!arg.startsWith('--') && !result.envName) {
|
|
52
|
+
result.envName = arg;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Print a deploy plan to the console.
|
|
62
|
+
* Shows the infrastructure that would be provisioned.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} infra - Infrastructure manifest from inferInfrastructure()
|
|
65
|
+
*/
|
|
66
|
+
export function printPlan(infra) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(' Deploy Plan');
|
|
70
|
+
lines.push(' ──────────────────────────────────────');
|
|
71
|
+
lines.push('');
|
|
72
|
+
|
|
73
|
+
if (infra.name) lines.push(` Environment: ${infra.name}`);
|
|
74
|
+
if (infra.server) lines.push(` Server: ${infra.server}`);
|
|
75
|
+
if (infra.domain) lines.push(` Domain: ${infra.domain}`);
|
|
76
|
+
lines.push(` Instances: ${infra.instances}`);
|
|
77
|
+
lines.push(` Memory: ${infra.memory}`);
|
|
78
|
+
lines.push(` Branch: ${infra.branch}`);
|
|
79
|
+
lines.push(` Health: ${infra.health} (every ${infra.health_interval}s)`);
|
|
80
|
+
lines.push(` Keep: ${infra.keep_releases} releases`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
|
|
83
|
+
// Required services
|
|
84
|
+
const services = [];
|
|
85
|
+
if (infra.requires.bun) services.push('Bun');
|
|
86
|
+
if (infra.requires.caddy) services.push('Caddy');
|
|
87
|
+
if (infra.requires.ufw) services.push('UFW');
|
|
88
|
+
if (services.length > 0) {
|
|
89
|
+
lines.push(` Services: ${services.join(', ')}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Databases
|
|
93
|
+
if (infra.databases.length > 0) {
|
|
94
|
+
const dbNames = infra.databases.map(d => d.engine);
|
|
95
|
+
lines.push(` Databases: ${dbNames.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Features
|
|
99
|
+
const features = [];
|
|
100
|
+
if (infra.hasWebSocket) features.push('WebSocket');
|
|
101
|
+
if (infra.hasSSE) features.push('SSE');
|
|
102
|
+
if (infra.hasBrowser) features.push('Static assets');
|
|
103
|
+
if (features.length > 0) {
|
|
104
|
+
lines.push(` Features: ${features.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Required secrets
|
|
108
|
+
if (infra.requiredSecrets.length > 0) {
|
|
109
|
+
lines.push(` Secrets: ${infra.requiredSecrets.join(', ')}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Env variables
|
|
113
|
+
const envKeys = Object.keys(infra.env || {});
|
|
114
|
+
if (envKeys.length > 0) {
|
|
115
|
+
lines.push(` Env vars: ${envKeys.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lines.push('');
|
|
119
|
+
lines.push(' ──────────────────────────────────────');
|
|
120
|
+
lines.push('');
|
|
121
|
+
|
|
122
|
+
console.log(lines.join('\n'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Main deploy orchestrator.
|
|
127
|
+
*
|
|
128
|
+
* Compiles the project, infers infrastructure, and (eventually) executes
|
|
129
|
+
* SSH deployment. For now the SSH parts are stubbed.
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} ast - Parsed program AST
|
|
132
|
+
* @param {Object} buildResult - Codegen output
|
|
133
|
+
* @param {Object} deployArgs - Parsed CLI args from parseDeployArgs()
|
|
134
|
+
* @param {string} projectDir - Absolute path to the project directory
|
|
135
|
+
* @returns {Object} result with plan, infra, and status
|
|
136
|
+
*/
|
|
137
|
+
export async function deploy(ast, buildResult, deployArgs, projectDir) {
|
|
138
|
+
// Infer full infrastructure manifest from AST
|
|
139
|
+
const infra = inferInfrastructure(ast);
|
|
140
|
+
|
|
141
|
+
// Override environment name from CLI args
|
|
142
|
+
if (deployArgs.envName) {
|
|
143
|
+
infra.name = deployArgs.envName;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If deploy config exists in build result, merge it
|
|
147
|
+
if (buildResult.deploy && buildResult.deploy[deployArgs.envName]) {
|
|
148
|
+
const envConfig = buildResult.deploy[deployArgs.envName];
|
|
149
|
+
if (envConfig.server) infra.server = envConfig.server;
|
|
150
|
+
if (envConfig.domain) infra.domain = envConfig.domain;
|
|
151
|
+
if (envConfig.instances) infra.instances = envConfig.instances;
|
|
152
|
+
if (envConfig.memory) infra.memory = envConfig.memory;
|
|
153
|
+
if (envConfig.branch) infra.branch = envConfig.branch;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Plan mode — just show what would be deployed
|
|
157
|
+
if (deployArgs.plan) {
|
|
158
|
+
printPlan(infra);
|
|
159
|
+
return { action: 'plan', infra };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Rollback mode — stub
|
|
163
|
+
if (deployArgs.rollback) {
|
|
164
|
+
console.log(` Rolling back ${deployArgs.envName}...`);
|
|
165
|
+
// TODO: SSH into server, symlink previous release
|
|
166
|
+
return { action: 'rollback', infra };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Logs mode — stub
|
|
170
|
+
if (deployArgs.logs) {
|
|
171
|
+
const since = deployArgs.since || '1 hour ago';
|
|
172
|
+
const instance = deployArgs.instance !== null ? ` (instance ${deployArgs.instance})` : '';
|
|
173
|
+
console.log(` Fetching logs for ${deployArgs.envName}${instance} since ${since}...`);
|
|
174
|
+
// TODO: SSH into server, journalctl/tail logs
|
|
175
|
+
return { action: 'logs', infra };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Status mode — stub
|
|
179
|
+
if (deployArgs.status) {
|
|
180
|
+
console.log(` Checking status of ${deployArgs.envName}...`);
|
|
181
|
+
// TODO: SSH into server, check systemd service status
|
|
182
|
+
return { action: 'status', infra };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// SSH mode — stub
|
|
186
|
+
if (deployArgs.ssh) {
|
|
187
|
+
console.log(` Opening SSH session to ${deployArgs.envName}...`);
|
|
188
|
+
// TODO: spawn interactive SSH session
|
|
189
|
+
return { action: 'ssh', infra };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Setup git push-to-deploy — stub
|
|
193
|
+
if (deployArgs.setupGit) {
|
|
194
|
+
console.log(` Setting up git push-to-deploy for ${deployArgs.envName}...`);
|
|
195
|
+
// TODO: SSH into server, configure bare repo + post-receive hook
|
|
196
|
+
return { action: 'setup-git', infra };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Remove deployment — stub
|
|
200
|
+
if (deployArgs.remove) {
|
|
201
|
+
console.log(` Removing deployment ${deployArgs.envName}...`);
|
|
202
|
+
// TODO: SSH into server, stop services, remove files
|
|
203
|
+
return { action: 'remove', infra };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// List deployments — stub
|
|
207
|
+
if (deployArgs.list) {
|
|
208
|
+
console.log(' Listing deployments...');
|
|
209
|
+
// TODO: SSH into server, list ~/apps/
|
|
210
|
+
return { action: 'list', infra };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Default: full deploy — stub
|
|
214
|
+
console.log(` Deploying to ${deployArgs.envName}...`);
|
|
215
|
+
// TODO: rsync build, run provision script, restart services
|
|
216
|
+
return { action: 'deploy', infra };
|
|
217
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Infrastructure Inference Engine for the Tova language
|
|
2
|
+
// Walks the entire program AST and produces a complete infrastructure manifest
|
|
3
|
+
// by combining explicit deploy block config with inferred requirements from
|
|
4
|
+
// server, browser, and security blocks.
|
|
5
|
+
|
|
6
|
+
import { DeployCodegen } from '../codegen/deploy-codegen.js';
|
|
7
|
+
|
|
8
|
+
const MANIFEST_DEFAULTS = {
|
|
9
|
+
name: null,
|
|
10
|
+
server: null,
|
|
11
|
+
domain: null,
|
|
12
|
+
instances: 1,
|
|
13
|
+
memory: '512mb',
|
|
14
|
+
branch: 'main',
|
|
15
|
+
health: '/healthz',
|
|
16
|
+
health_interval: 30,
|
|
17
|
+
health_timeout: 5,
|
|
18
|
+
restart_on_failure: true,
|
|
19
|
+
keep_releases: 5,
|
|
20
|
+
env: {},
|
|
21
|
+
databases: [],
|
|
22
|
+
requires: { bun: false, caddy: false, ufw: false },
|
|
23
|
+
hasWebSocket: false,
|
|
24
|
+
hasSSE: false,
|
|
25
|
+
hasBrowser: false,
|
|
26
|
+
requiredSecrets: [],
|
|
27
|
+
blockTypes: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Walk an AST node tree recursively and invoke a visitor callback on each node.
|
|
32
|
+
*/
|
|
33
|
+
function walkNode(node, visitor) {
|
|
34
|
+
if (!node || typeof node !== 'object') return;
|
|
35
|
+
if (node.type) visitor(node);
|
|
36
|
+
|
|
37
|
+
// Walk arrays (e.g., body, entries, arguments)
|
|
38
|
+
for (const key of Object.keys(node)) {
|
|
39
|
+
const val = node[key];
|
|
40
|
+
if (Array.isArray(val)) {
|
|
41
|
+
for (const item of val) {
|
|
42
|
+
walkNode(item, visitor);
|
|
43
|
+
}
|
|
44
|
+
} else if (val && typeof val === 'object' && val.type) {
|
|
45
|
+
walkNode(val, visitor);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Collect all env() call arguments from a node tree.
|
|
52
|
+
* env("JWT_SECRET") => CallExpression with callee.name === "env" and args[0].value
|
|
53
|
+
*/
|
|
54
|
+
function collectEnvCalls(node) {
|
|
55
|
+
const secrets = [];
|
|
56
|
+
walkNode(node, (n) => {
|
|
57
|
+
if (
|
|
58
|
+
n.type === 'CallExpression' &&
|
|
59
|
+
n.callee &&
|
|
60
|
+
n.callee.type === 'Identifier' &&
|
|
61
|
+
n.callee.name === 'env' &&
|
|
62
|
+
n.arguments &&
|
|
63
|
+
n.arguments.length > 0
|
|
64
|
+
) {
|
|
65
|
+
const arg = n.arguments[0];
|
|
66
|
+
if (arg && (arg.value !== undefined)) {
|
|
67
|
+
secrets.push(typeof arg.value === 'string' ? arg.value : String(arg.value));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return secrets;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Infer infrastructure requirements from the full program AST.
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} ast - Program AST with ast.body array of top-level blocks
|
|
78
|
+
* @returns {Object} Complete infrastructure manifest
|
|
79
|
+
*/
|
|
80
|
+
export function inferInfrastructure(ast) {
|
|
81
|
+
const manifest = JSON.parse(JSON.stringify(MANIFEST_DEFAULTS));
|
|
82
|
+
const blockTypes = new Set();
|
|
83
|
+
const deployBlocks = [];
|
|
84
|
+
const inferredDatabases = [];
|
|
85
|
+
const secretsSet = new Set();
|
|
86
|
+
|
|
87
|
+
if (!ast || !ast.body) return manifest;
|
|
88
|
+
|
|
89
|
+
for (const node of ast.body) {
|
|
90
|
+
switch (node.type) {
|
|
91
|
+
case 'DeployBlock': {
|
|
92
|
+
blockTypes.add('deploy');
|
|
93
|
+
deployBlocks.push(node);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case 'ServerBlock': {
|
|
98
|
+
blockTypes.add('server');
|
|
99
|
+
manifest.requires.bun = true;
|
|
100
|
+
manifest.requires.caddy = true;
|
|
101
|
+
manifest.requires.ufw = true;
|
|
102
|
+
|
|
103
|
+
// Scan server block body for specific declarations
|
|
104
|
+
if (node.body && Array.isArray(node.body)) {
|
|
105
|
+
for (const stmt of node.body) {
|
|
106
|
+
if (stmt.type === 'DbDeclaration') {
|
|
107
|
+
// Server-block db is always SQLite (bun:sqlite)
|
|
108
|
+
const dbConfig = {};
|
|
109
|
+
if (stmt.config && typeof stmt.config === 'object') {
|
|
110
|
+
for (const [k, v] of Object.entries(stmt.config)) {
|
|
111
|
+
dbConfig[k] = v && v.value !== undefined ? v.value : v;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
inferredDatabases.push({ engine: 'sqlite', config: dbConfig });
|
|
115
|
+
}
|
|
116
|
+
if (stmt.type === 'WebSocketDeclaration') {
|
|
117
|
+
manifest.hasWebSocket = true;
|
|
118
|
+
}
|
|
119
|
+
if (stmt.type === 'SseDeclaration') {
|
|
120
|
+
manifest.hasSSE = true;
|
|
121
|
+
}
|
|
122
|
+
// Also check inside route groups
|
|
123
|
+
if (stmt.type === 'RouteGroupDeclaration' && stmt.body) {
|
|
124
|
+
for (const inner of stmt.body) {
|
|
125
|
+
if (inner.type === 'WebSocketDeclaration') {
|
|
126
|
+
manifest.hasWebSocket = true;
|
|
127
|
+
}
|
|
128
|
+
if (inner.type === 'SseDeclaration') {
|
|
129
|
+
manifest.hasSSE = true;
|
|
130
|
+
}
|
|
131
|
+
if (inner.type === 'DbDeclaration') {
|
|
132
|
+
const dbConfig = {};
|
|
133
|
+
if (inner.config && typeof inner.config === 'object') {
|
|
134
|
+
for (const [k, v] of Object.entries(inner.config)) {
|
|
135
|
+
dbConfig[k] = v && v.value !== undefined ? v.value : v;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
inferredDatabases.push({ engine: 'sqlite', config: dbConfig });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case 'BrowserBlock': {
|
|
148
|
+
blockTypes.add('browser');
|
|
149
|
+
manifest.hasBrowser = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'SecurityBlock': {
|
|
154
|
+
blockTypes.add('security');
|
|
155
|
+
// Scan for env() calls to find required secrets
|
|
156
|
+
if (node.body && Array.isArray(node.body)) {
|
|
157
|
+
for (const stmt of node.body) {
|
|
158
|
+
if (stmt.type === 'SecurityAuthDeclaration') {
|
|
159
|
+
// Walk the config looking for env() calls
|
|
160
|
+
if (stmt.config && typeof stmt.config === 'object') {
|
|
161
|
+
for (const [, value] of Object.entries(stmt.config)) {
|
|
162
|
+
const secrets = collectEnvCalls(value);
|
|
163
|
+
for (const s of secrets) secretsSet.add(s);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Merge explicit deploy config via DeployCodegen
|
|
175
|
+
if (deployBlocks.length > 0) {
|
|
176
|
+
const deployConfig = DeployCodegen.mergeDeployBlocks(deployBlocks);
|
|
177
|
+
// Apply deploy config fields to manifest
|
|
178
|
+
if (deployConfig.name) manifest.name = deployConfig.name;
|
|
179
|
+
if (deployConfig.server) manifest.server = deployConfig.server;
|
|
180
|
+
if (deployConfig.domain) manifest.domain = deployConfig.domain;
|
|
181
|
+
if (deployConfig.instances !== undefined) manifest.instances = deployConfig.instances;
|
|
182
|
+
if (deployConfig.memory) manifest.memory = deployConfig.memory;
|
|
183
|
+
if (deployConfig.branch) manifest.branch = deployConfig.branch;
|
|
184
|
+
if (deployConfig.health) manifest.health = deployConfig.health;
|
|
185
|
+
if (deployConfig.health_interval !== undefined) manifest.health_interval = deployConfig.health_interval;
|
|
186
|
+
if (deployConfig.health_timeout !== undefined) manifest.health_timeout = deployConfig.health_timeout;
|
|
187
|
+
if (deployConfig.restart_on_failure !== undefined) manifest.restart_on_failure = deployConfig.restart_on_failure;
|
|
188
|
+
if (deployConfig.keep_releases !== undefined) manifest.keep_releases = deployConfig.keep_releases;
|
|
189
|
+
if (deployConfig.env && Object.keys(deployConfig.env).length > 0) {
|
|
190
|
+
manifest.env = { ...manifest.env, ...deployConfig.env };
|
|
191
|
+
}
|
|
192
|
+
// Declared databases from deploy block
|
|
193
|
+
if (deployConfig.databases && deployConfig.databases.length > 0) {
|
|
194
|
+
manifest.databases = [...deployConfig.databases];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Merge inferred databases with declared ones (avoid duplicates by engine name)
|
|
199
|
+
const declaredEngines = new Set(manifest.databases.map(d => d.engine));
|
|
200
|
+
for (const inferred of inferredDatabases) {
|
|
201
|
+
if (!declaredEngines.has(inferred.engine)) {
|
|
202
|
+
manifest.databases.push(inferred);
|
|
203
|
+
declaredEngines.add(inferred.engine);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// If server block is present, ensure bun/caddy/ufw are required
|
|
208
|
+
if (blockTypes.has('server')) {
|
|
209
|
+
manifest.requires.bun = true;
|
|
210
|
+
manifest.requires.caddy = true;
|
|
211
|
+
manifest.requires.ufw = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
manifest.requiredSecrets = [...secretsSet].sort();
|
|
215
|
+
manifest.blockTypes = [...blockTypes].sort();
|
|
216
|
+
|
|
217
|
+
return manifest;
|
|
218
|
+
}
|