@feltdb/core 0.4.4 → 0.4.6
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 +22 -0
- package/bin/create-feltdb.js +3 -0
- package/bin/feltdb.js +3 -0
- package/dist/analytics-backend.js +1 -1
- package/dist/cli/api-client.js +236 -0
- package/dist/cli/cli.js +18 -0
- package/dist/cli/commands.js +795 -0
- package/dist/cli/config.js +66 -0
- package/dist/cli/index.js +399 -0
- package/dist/create/application-identity.js +132 -0
- package/dist/create/cli-scripts-generator.js +211 -0
- package/dist/create/cli.js +202 -0
- package/dist/create/create.js +638 -0
- package/dist/create/docker-compose-generator.js +258 -0
- package/dist/create/index.js +4 -0
- package/dist/create/package-versions.js +4 -0
- package/dist/create/runtime-templates.js +272 -0
- package/dist/index-backend.js +1 -1
- package/dist/migrate/analyzer/index.d.ts +2 -0
- package/dist/migrate/analyzer/index.js +34 -0
- package/dist/migrate/benchmark/index.d.ts +13 -0
- package/dist/migrate/benchmark/index.js +8 -0
- package/dist/migrate/capability-gate/index.d.ts +3 -0
- package/dist/migrate/capability-gate/index.js +3 -0
- package/dist/migrate/classifier/index.d.ts +3 -0
- package/dist/migrate/classifier/index.js +53 -0
- package/dist/migrate/cli/index.d.ts +2 -0
- package/dist/migrate/cli/index.js +201 -0
- package/dist/migrate/domain-model/index.d.ts +2 -0
- package/dist/migrate/domain-model/index.js +4 -0
- package/dist/migrate/generator/index.d.ts +2 -0
- package/dist/migrate/generator/index.js +21 -0
- package/dist/migrate/index.d.ts +16 -0
- package/dist/migrate/index.js +16 -0
- package/dist/migrate/measurement/index.d.ts +7 -0
- package/dist/migrate/measurement/index.js +1 -0
- package/dist/migrate/migrator/index.d.ts +2 -0
- package/dist/migrate/migrator/index.js +35 -0
- package/dist/migrate/planner/index.d.ts +2 -0
- package/dist/migrate/planner/index.js +12 -0
- package/dist/migrate/prover/index.d.ts +9 -0
- package/dist/migrate/prover/index.js +3 -0
- package/dist/migrate/reporter/index.d.ts +4 -0
- package/dist/migrate/reporter/index.js +1 -0
- package/dist/migrate/runtime-dependencies/index.d.ts +13 -0
- package/dist/migrate/runtime-dependencies/index.js +112 -0
- package/dist/migrate/state-contract/index.d.ts +13 -0
- package/dist/migrate/state-contract/index.js +49 -0
- package/dist/migrate/types/index.d.ts +228 -0
- package/dist/migrate/types/index.js +1 -0
- package/dist/migrate/ui-wiring/index.d.ts +18 -0
- package/dist/migrate/ui-wiring/index.js +2 -0
- package/dist/migrate/util.d.ts +5 -0
- package/dist/migrate/util.js +10 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/useCapability.d.ts +11 -0
- package/dist/react/useCapability.d.ts.map +1 -0
- package/dist/react/useCapability.js +30 -0
- package/dist/react/useCollection.d.ts +17 -0
- package/dist/react/useCollection.d.ts.map +1 -0
- package/dist/react/useCollection.js +69 -0
- package/dist/react/useFeltDB.d.ts +136 -0
- package/dist/react/useFeltDB.d.ts.map +1 -0
- package/dist/react/useFeltDB.js +350 -0
- package/dist/studio/.gitkeep +1 -0
- package/dist/studio/KeyManagementPanel-BOvWTRyH.js +246 -0
- package/dist/studio/app.d.ts +10 -0
- package/dist/studio/app.d.ts.map +1 -0
- package/dist/studio/components/AgentExplorer.d.ts +9 -0
- package/dist/studio/components/AgentExplorer.d.ts.map +1 -0
- package/dist/studio/components/ApplicationDesigner.d.ts +11 -0
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -0
- package/dist/studio/components/CapabilityExplorer.d.ts +9 -0
- package/dist/studio/components/CapabilityExplorer.d.ts.map +1 -0
- package/dist/studio/components/ConflictExplorer.d.ts +9 -0
- package/dist/studio/components/ConflictExplorer.d.ts.map +1 -0
- package/dist/studio/components/ExecutionViewer.d.ts +9 -0
- package/dist/studio/components/ExecutionViewer.d.ts.map +1 -0
- package/dist/studio/components/GlobalSearch.d.ts +10 -0
- package/dist/studio/components/GlobalSearch.d.ts.map +1 -0
- package/dist/studio/components/HealthCenter.d.ts +9 -0
- package/dist/studio/components/HealthCenter.d.ts.map +1 -0
- package/dist/studio/components/KeyManagementPanel.d.ts +9 -0
- package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -0
- package/dist/studio/components/KeyManagementPanel.js +2 -0
- package/dist/studio/components/ManagedInstancePanel.d.ts +4 -0
- package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -0
- package/dist/studio/components/ManagedInstancePanel.js +281 -0
- package/dist/studio/components/OperationsExplorer.d.ts +10 -0
- package/dist/studio/components/OperationsExplorer.d.ts.map +1 -0
- package/dist/studio/components/OverviewDashboard.d.ts +8 -0
- package/dist/studio/components/OverviewDashboard.d.ts.map +1 -0
- package/dist/studio/components/PeerMap.d.ts +8 -0
- package/dist/studio/components/PeerMap.d.ts.map +1 -0
- package/dist/studio/components/ProvenanceViewer.d.ts +11 -0
- package/dist/studio/components/ProvenanceViewer.d.ts.map +1 -0
- package/dist/studio/components/ReferenceExplorer.d.ts +10 -0
- package/dist/studio/components/ReferenceExplorer.d.ts.map +1 -0
- package/dist/studio/components/SettingsPanel.d.ts +7 -0
- package/dist/studio/components/SettingsPanel.d.ts.map +1 -0
- package/dist/studio/components/StateExplorer.d.ts +10 -0
- package/dist/studio/components/StateExplorer.d.ts.map +1 -0
- package/dist/studio/components/WorkflowVisualizer.d.ts +9 -0
- package/dist/studio/components/WorkflowVisualizer.d.ts.map +1 -0
- package/dist/studio/components/index.d.ts +24 -0
- package/dist/studio/components/index.d.ts.map +1 -0
- package/dist/studio/components/index.js +4 -0
- package/dist/studio/components-BRYUceo9.js +1711 -0
- package/dist/studio/hooks/index.d.ts +37 -0
- package/dist/studio/hooks/index.d.ts.map +1 -0
- package/dist/studio/index.d.ts +27 -0
- package/dist/studio/index.d.ts.map +1 -0
- package/dist/studio/index.html +30 -0
- package/dist/studio/index.js +236 -0
- package/dist/studio/main.d.ts +1 -0
- package/dist/studio/main.d.ts.map +1 -0
- package/dist/studio/studio.css +2 -0
- package/dist/studio/types/index.d.ts +133 -0
- package/dist/studio/types/index.d.ts.map +1 -0
- package/dist/studio/utils/api.d.ts +27 -0
- package/dist/studio/utils/api.d.ts.map +1 -0
- package/dist/studio/utils/format.d.ts +24 -0
- package/dist/studio/utils/format.d.ts.map +1 -0
- package/dist/studio/utils/index.d.ts +7 -0
- package/dist/studio/utils/index.d.ts.map +1 -0
- package/dist/studio/utils/index.js +75 -0
- package/dist/studio/utils/managed-instance.d.ts +40 -0
- package/dist/studio/utils/managed-instance.d.ts.map +1 -0
- package/dist/studio/utils/managed-instance.js +51 -0
- package/dist/studio-app/.gitkeep +1 -0
- package/dist/studio-app/assets/feltdb_wasm-BXMn9UxO.js +1 -0
- package/dist/studio-app/assets/feltdb_wasm_bg-bYYbZeRM.wasm +0 -0
- package/dist/studio-app/assets/index-B-lZjdtI.js +28 -0
- package/dist/studio-app/assets/index-CB-Eed9V.css +1 -0
- package/dist/studio-app/index.html +3 -0
- package/dist/wasm/feltdb_wasm.d.ts +461 -0
- package/dist/wasm/feltdb_wasm.js +1690 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/feltdb_wasm_bg.wasm.d.ts +84 -0
- package/dist/wasm/package.json +16 -0
- package/package.json +40 -5
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export function loadFeltDBConfig() {
|
|
4
|
+
const configPath = path.join(process.cwd(), 'feltdb.json');
|
|
5
|
+
if (!fs.existsSync(configPath)) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const data = fs.readFileSync(configPath, 'utf-8');
|
|
10
|
+
return JSON.parse(data);
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
throw new Error(`Failed to load feltdb.json: ${e}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveFeltDBConfig(config) {
|
|
17
|
+
const configPath = path.join(process.cwd(), 'feltdb.json');
|
|
18
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
19
|
+
}
|
|
20
|
+
export function createDefaultConfig(appName) {
|
|
21
|
+
return {
|
|
22
|
+
name: appName,
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
description: 'A FeltDB application',
|
|
25
|
+
account: 'your-account',
|
|
26
|
+
model: {
|
|
27
|
+
collections: [],
|
|
28
|
+
workloads: [],
|
|
29
|
+
workflows: [],
|
|
30
|
+
mesh: {
|
|
31
|
+
scheduling_strategy: 'capability_based',
|
|
32
|
+
failover_strategy: 'automatic',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function validateModel(model) {
|
|
38
|
+
const errors = [];
|
|
39
|
+
if (!Array.isArray(model.collections)) {
|
|
40
|
+
errors.push('collections must be an array');
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(model.workloads)) {
|
|
43
|
+
errors.push('workloads must be an array');
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(model.workflows)) {
|
|
46
|
+
errors.push('workflows must be an array');
|
|
47
|
+
}
|
|
48
|
+
model.collections?.forEach((col, i) => {
|
|
49
|
+
if (!col.name)
|
|
50
|
+
errors.push(`Collection ${i} missing name`);
|
|
51
|
+
if (!col.primaryKey)
|
|
52
|
+
errors.push(`Collection ${i} missing primaryKey`);
|
|
53
|
+
if (!Array.isArray(col.fields))
|
|
54
|
+
errors.push(`Collection ${i} fields must be an array`);
|
|
55
|
+
});
|
|
56
|
+
model.workloads?.forEach((wl, i) => {
|
|
57
|
+
if (!wl.id)
|
|
58
|
+
errors.push(`Workload ${i} missing id`);
|
|
59
|
+
if (!wl.name)
|
|
60
|
+
errors.push(`Workload ${i} missing name`);
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
valid: errors.length === 0,
|
|
64
|
+
errors,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FeltDB CLI - Command-line interface for application management
|
|
4
|
+
*
|
|
5
|
+
* Usage: feltdb <command> [options]
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* login Authenticate with FeltDB
|
|
9
|
+
* init Initialize a new application
|
|
10
|
+
* build Build application model
|
|
11
|
+
* deploy Deploy application to specified topology
|
|
12
|
+
* publish Publish application version
|
|
13
|
+
* status Check deployment status
|
|
14
|
+
* logs Stream application logs
|
|
15
|
+
* execute Run a workload
|
|
16
|
+
* export Export application data
|
|
17
|
+
* backup Create application backup
|
|
18
|
+
* restore Restore from backup
|
|
19
|
+
* list List applications and deployments
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import * as readline from 'readline';
|
|
24
|
+
import { getClient } from './api-client.js';
|
|
25
|
+
import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
|
|
26
|
+
const VERSION = '0.4.2';
|
|
27
|
+
function prompt(question) {
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
});
|
|
32
|
+
return new Promise(resolve => {
|
|
33
|
+
rl.question(question, answer => {
|
|
34
|
+
rl.close();
|
|
35
|
+
resolve(answer);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function parseArgs(args) {
|
|
40
|
+
const flags = {};
|
|
41
|
+
const positional = [];
|
|
42
|
+
for (let i = 0; i < args.length; i++) {
|
|
43
|
+
if (args[i].startsWith('--')) {
|
|
44
|
+
const key = args[i].slice(2);
|
|
45
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
46
|
+
flags[key] = args[++i];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
flags[key] = 'true';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
positional.push(args[i]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { flags, positional };
|
|
57
|
+
}
|
|
58
|
+
const commands = {
|
|
59
|
+
login: {
|
|
60
|
+
name: 'login',
|
|
61
|
+
description: 'Authenticate with FeltDB Studio',
|
|
62
|
+
execute: async (args) => {
|
|
63
|
+
console.log(`FeltDB CLI v${VERSION}`);
|
|
64
|
+
console.log('Authenticating...\n');
|
|
65
|
+
const email = await prompt('Email: ');
|
|
66
|
+
const password = await prompt('Password: ');
|
|
67
|
+
try {
|
|
68
|
+
const client = getClient();
|
|
69
|
+
const result = await client.authenticate(email, password);
|
|
70
|
+
client.saveConfig({
|
|
71
|
+
account: result.account,
|
|
72
|
+
token: result.token,
|
|
73
|
+
});
|
|
74
|
+
console.log(`\n✓ Authenticated as ${email}`);
|
|
75
|
+
console.log(`✓ Account: ${result.account}`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Authentication failed'}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
init: {
|
|
84
|
+
name: 'init',
|
|
85
|
+
description: 'Initialize a new FeltDB application',
|
|
86
|
+
execute: async (args) => {
|
|
87
|
+
const { positional } = parseArgs(args);
|
|
88
|
+
const appName = positional[0] || 'my-app';
|
|
89
|
+
if (fs.existsSync(appName)) {
|
|
90
|
+
console.error(`Error: Directory ${appName} already exists`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
console.log(`✓ Initializing application: ${appName}`);
|
|
94
|
+
const dirs = ['src', 'models', 'workloads', 'workflows'];
|
|
95
|
+
for (const dir of dirs) {
|
|
96
|
+
const dirPath = path.join(appName, dir);
|
|
97
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
98
|
+
console.log(` Created directory: ${dir}/`);
|
|
99
|
+
}
|
|
100
|
+
const config = createDefaultConfig(appName);
|
|
101
|
+
const configPath = path.join(appName, 'feltdb.json');
|
|
102
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
103
|
+
console.log(`✓ Created feltdb.json`);
|
|
104
|
+
console.log(`\nNext steps:`);
|
|
105
|
+
console.log(` 1. cd ${appName}`);
|
|
106
|
+
console.log(` 2. feltdb build`);
|
|
107
|
+
console.log(` 3. feltdb deploy --topology local`);
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
build: {
|
|
111
|
+
name: 'build',
|
|
112
|
+
description: 'Validate and build application model',
|
|
113
|
+
execute: async (args) => {
|
|
114
|
+
console.log('Building application model...');
|
|
115
|
+
const config = loadFeltDBConfig();
|
|
116
|
+
if (!config) {
|
|
117
|
+
console.error('Error: feltdb.json not found. Run `feltdb init` first.');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
console.log(`✓ Loaded configuration: ${config.name}`);
|
|
121
|
+
console.log(` Collections: ${config.model.collections.length}`);
|
|
122
|
+
console.log(` Workloads: ${config.model.workloads.length}`);
|
|
123
|
+
console.log(` Workflows: ${config.model.workflows.length}`);
|
|
124
|
+
const validation = validateModel(config.model);
|
|
125
|
+
if (!validation.valid) {
|
|
126
|
+
console.error('✗ Model validation failed:');
|
|
127
|
+
validation.errors.forEach(err => console.error(` - ${err}`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
console.log(`✓ Model is valid`);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
deploy: {
|
|
134
|
+
name: 'deploy',
|
|
135
|
+
description: 'Deploy application to specified topology',
|
|
136
|
+
execute: async (args) => {
|
|
137
|
+
const { flags, positional } = parseArgs(args);
|
|
138
|
+
const target = positional[0] || 'all';
|
|
139
|
+
const provider = flags.provider || flags.topology || (target === 'all' ? 'split' : 'local');
|
|
140
|
+
const environment = flags.environment || 'production';
|
|
141
|
+
const profiles = { all: 'all-in-one', studio: 'studio-only', 'control-plane': 'control-plane-only', workers: 'worker-expansion', worker: 'worker-expansion', application: 'application-only', app: 'application-only' };
|
|
142
|
+
const profile = flags.profile || profiles[target];
|
|
143
|
+
const config = loadFeltDBConfig();
|
|
144
|
+
if (!config) {
|
|
145
|
+
console.error('Error: feltdb.json not found.');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const allowedTargets = ['all', 'studio', 'control-plane', 'workers', 'worker', 'application', 'app', 'certify'];
|
|
149
|
+
const allowedProviders = ['local', 'docker', 'fly', 'render', 'vercel', 'split'];
|
|
150
|
+
if (!allowedTargets.includes(target))
|
|
151
|
+
throw new Error(`Unknown deployment target: ${target}`);
|
|
152
|
+
if (!allowedProviders.includes(provider))
|
|
153
|
+
throw new Error(`Unknown deployment provider: ${provider}`);
|
|
154
|
+
if (provider === 'vercel' && target !== 'studio' && target !== 'certify') {
|
|
155
|
+
throw new Error('Vercel can deploy the Studio service only');
|
|
156
|
+
}
|
|
157
|
+
const validation = validateModel(config.model);
|
|
158
|
+
if (!validation.valid)
|
|
159
|
+
throw new Error(validation.errors.join('; '));
|
|
160
|
+
const releaseId = flags.release;
|
|
161
|
+
if (!releaseId)
|
|
162
|
+
throw new Error('--release <release_id> is required; deployments execute published releases only');
|
|
163
|
+
const client = getClient();
|
|
164
|
+
try {
|
|
165
|
+
if (target === 'all' && provider === 'split') {
|
|
166
|
+
const deploymentConfig = config.deployment || {};
|
|
167
|
+
const configuration = {
|
|
168
|
+
...(deploymentConfig.configuration || {}),
|
|
169
|
+
'control_plane.url': flags['control-plane-url'] || deploymentConfig.configuration?.['control_plane.url'] || 'https://api.feltdb.com',
|
|
170
|
+
};
|
|
171
|
+
const executeNode = async (node, nodeProvider, nodeProfile) => {
|
|
172
|
+
console.log(`→ ${node}: ${nodeProvider}`);
|
|
173
|
+
const nodePlan = await client.planDeployment({
|
|
174
|
+
release_id: releaseId,
|
|
175
|
+
environment,
|
|
176
|
+
provider: nodeProvider,
|
|
177
|
+
profile: nodeProfile,
|
|
178
|
+
rollback_target: flags.rollback,
|
|
179
|
+
configuration,
|
|
180
|
+
domains: deploymentConfig.domains || [],
|
|
181
|
+
secret_references: deploymentConfig.secret_references || [],
|
|
182
|
+
scaling_policy: { min_instances: deploymentConfig.min_instances || 1, max_instances: deploymentConfig.max_instances || 1 },
|
|
183
|
+
});
|
|
184
|
+
const nodeCertification = await client.certifyDeployment(nodePlan);
|
|
185
|
+
if (!nodeCertification.valid)
|
|
186
|
+
throw new Error(`${node} certification failed: ${nodeCertification.diagnostics.join('; ')}`);
|
|
187
|
+
const nodeDeployment = await client.executeDeployment(nodePlan);
|
|
188
|
+
if (nodeDeployment.state !== 'ACTIVE')
|
|
189
|
+
throw new Error(`${node} did not become ACTIVE: ${nodeDeployment.state}`);
|
|
190
|
+
console.log(`✓ ${node} ACTIVE (${nodeDeployment.deployment_id})`);
|
|
191
|
+
return nodeDeployment;
|
|
192
|
+
};
|
|
193
|
+
const controlPlane = await executeNode('control-plane', flags['control-plane-provider'] || 'fly', 'control-plane-only');
|
|
194
|
+
const ready = await client.certificationRequest('GET', '/ready');
|
|
195
|
+
if (ready.authority !== 'rust' || ready.storage !== 'writable' || ready.authentication !== true) {
|
|
196
|
+
throw new Error('deployed control plane failed Rust authority/storage/authentication verification');
|
|
197
|
+
}
|
|
198
|
+
const studio = await executeNode('studio', flags['studio-provider'] || 'vercel', 'studio-only');
|
|
199
|
+
const studioUrl = flags['studio-url'] || configuration['studio.url'];
|
|
200
|
+
if (studioUrl) {
|
|
201
|
+
const response = await fetch(studioUrl, { redirect: 'follow' });
|
|
202
|
+
if (!response.ok)
|
|
203
|
+
throw new Error(`Studio verification failed: HTTP ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
console.log(`✓ Rust control plane verified: ${controlPlane.deployment_id}`);
|
|
206
|
+
console.log(`✓ Studio deployment verified: ${studio.deployment_id}`);
|
|
207
|
+
console.log('FELTDB PRODUCTION DEPLOYMENT: PASS');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
console.log(`🚀 Deploying ${target} to ${provider}...`);
|
|
211
|
+
console.log(` Application: ${config.name}`);
|
|
212
|
+
console.log(` Environment: ${environment}`);
|
|
213
|
+
console.log(` Profile: ${profile}`);
|
|
214
|
+
const deploymentConfig = config.deployment || {};
|
|
215
|
+
const plan = await client.planDeployment({
|
|
216
|
+
release_id: releaseId,
|
|
217
|
+
environment,
|
|
218
|
+
provider,
|
|
219
|
+
profile: target === 'certify' ? (flags.profile || 'all-in-one') : profile,
|
|
220
|
+
rollback_target: flags.rollback,
|
|
221
|
+
configuration: deploymentConfig.configuration || {},
|
|
222
|
+
domains: deploymentConfig.domains || [],
|
|
223
|
+
secret_references: deploymentConfig.secret_references || [],
|
|
224
|
+
scaling_policy: { min_instances: deploymentConfig.min_instances || 1, max_instances: deploymentConfig.max_instances || 1 },
|
|
225
|
+
});
|
|
226
|
+
const certification = await client.certifyDeployment(plan);
|
|
227
|
+
if (!certification.valid)
|
|
228
|
+
throw new Error(`Deployment certification failed: ${certification.diagnostics.join('; ')}`);
|
|
229
|
+
if (target === 'certify') {
|
|
230
|
+
if (flags.live !== 'true') {
|
|
231
|
+
console.log(`✓ Cloud execution plan certified for ${provider}/${flags.profile || 'all-in-one'}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const journey = deploymentConfig.certification;
|
|
235
|
+
if (!journey?.mutation_path || !journey.read_path)
|
|
236
|
+
throw new Error('Live certification requires deployment.certification.mutation_path and read_path');
|
|
237
|
+
if (!flags.rollback)
|
|
238
|
+
throw new Error('Live certification requires --rollback <previous_release_id>');
|
|
239
|
+
const live = await client.executeDeployment(plan);
|
|
240
|
+
if (live.state !== 'ACTIVE')
|
|
241
|
+
throw new Error(`Deployment did not become ACTIVE: ${live.state}`);
|
|
242
|
+
await client.certificationRequest('POST', journey.mutation_path, journey.mutation_body || {});
|
|
243
|
+
const beforeRestart = await client.certificationRequest('GET', journey.read_path);
|
|
244
|
+
await client.deploymentOperation(live.deployment_id, 'restart');
|
|
245
|
+
const afterRestart = await client.certificationRequest('GET', journey.read_path);
|
|
246
|
+
if (JSON.stringify(beforeRestart) !== JSON.stringify(afterRestart))
|
|
247
|
+
throw new Error('Application state did not survive restart');
|
|
248
|
+
await client.deploymentOperation(live.deployment_id, 'rollback');
|
|
249
|
+
const audit = await client.getDeploymentAudit(live.deployment_id);
|
|
250
|
+
if (!Array.isArray(audit.events) || !audit.events.some((event) => event.operation === 'rollback_health'))
|
|
251
|
+
throw new Error('Deployment audit did not record verified rollback');
|
|
252
|
+
await client.deploymentOperation(live.deployment_id, 'destroy');
|
|
253
|
+
console.log(`✓ Live deployment journey certified and destroyed: ${live.deployment_id}`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const deployment = await client.executeDeployment(plan);
|
|
257
|
+
console.log(`✓ Deployment initiated`);
|
|
258
|
+
console.log(` ID: ${deployment.deployment_id}`);
|
|
259
|
+
console.log(` Status: ${deployment.state}`);
|
|
260
|
+
console.log(` Provider: ${provider}`);
|
|
261
|
+
console.log(` Target: ${target}`);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Deployment failed'}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
deployment: {
|
|
270
|
+
name: 'deployment',
|
|
271
|
+
description: 'Run deployment certification against an executable provider',
|
|
272
|
+
execute: async (args) => {
|
|
273
|
+
const action = args[0] || 'certify';
|
|
274
|
+
if (action !== 'certify')
|
|
275
|
+
throw new Error(`Unknown deployment action: ${action}`);
|
|
276
|
+
await commands.deploy.execute(['certify', '--live', 'true', ...args.slice(1)]);
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
release: {
|
|
280
|
+
name: 'release',
|
|
281
|
+
description: 'Certify or publish a release independently of deployment',
|
|
282
|
+
execute: async (args) => {
|
|
283
|
+
const action = args[0] || 'certify';
|
|
284
|
+
if (!['certify', 'publish'].includes(action))
|
|
285
|
+
throw new Error(`Unknown release action: ${action}`);
|
|
286
|
+
const config = loadFeltDBConfig();
|
|
287
|
+
if (!config)
|
|
288
|
+
throw new Error('feltdb.json not found.');
|
|
289
|
+
const validation = validateModel(config.model);
|
|
290
|
+
if (!validation.valid)
|
|
291
|
+
throw new Error(validation.errors.join('; '));
|
|
292
|
+
const { flags } = parseArgs(args.slice(1));
|
|
293
|
+
if (!flags.release)
|
|
294
|
+
throw new Error('--release <release_id> is required');
|
|
295
|
+
const client = getClient();
|
|
296
|
+
if (action === 'certify') {
|
|
297
|
+
const release = await client.verifyRelease(flags.release);
|
|
298
|
+
console.log(`✓ Release candidate certified: ${release.manifest.release_id}`);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const release = await client.publishRelease(flags.release);
|
|
302
|
+
console.log(`✓ Release published: ${release.manifest.release_id}`);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
publish: {
|
|
307
|
+
name: 'publish',
|
|
308
|
+
description: 'Publish a version of the application',
|
|
309
|
+
execute: async (args) => {
|
|
310
|
+
const { flags } = parseArgs(args);
|
|
311
|
+
const message = flags.message || 'Release update';
|
|
312
|
+
const config = loadFeltDBConfig();
|
|
313
|
+
if (!config) {
|
|
314
|
+
console.error('Error: feltdb.json not found.');
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
const client = getClient();
|
|
318
|
+
try {
|
|
319
|
+
console.log('📦 Publishing version...');
|
|
320
|
+
console.log(` Building model...`);
|
|
321
|
+
const app = await client.getApplication(config.name);
|
|
322
|
+
if (!app) {
|
|
323
|
+
console.error(`Error: Application ${config.name} not found`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
console.log(` Creating version snapshot...`);
|
|
327
|
+
const version = await client.publishVersion(app.id, {
|
|
328
|
+
message,
|
|
329
|
+
model: config.model,
|
|
330
|
+
});
|
|
331
|
+
console.log(`✓ Published version ${version.version}`);
|
|
332
|
+
console.log(` ID: ${version.id}`);
|
|
333
|
+
console.log(` Ready for deployment`);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Publishing failed'}`);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
help: {
|
|
342
|
+
name: 'help',
|
|
343
|
+
description: 'Show help information',
|
|
344
|
+
execute: async (args) => {
|
|
345
|
+
console.log(`FeltDB CLI v${VERSION}`);
|
|
346
|
+
console.log(`\nUsage: feltdb <command> [options]\n`);
|
|
347
|
+
console.log('Commands:');
|
|
348
|
+
for (const [key, cmd] of Object.entries(commands)) {
|
|
349
|
+
console.log(` ${cmd.name.padEnd(15)} ${cmd.description}`);
|
|
350
|
+
}
|
|
351
|
+
console.log('\nOptions:');
|
|
352
|
+
console.log(' --help Show this help message');
|
|
353
|
+
console.log(' --version Show version');
|
|
354
|
+
console.log('\nExamples:');
|
|
355
|
+
console.log(' feltdb login');
|
|
356
|
+
console.log(' feltdb init my-app');
|
|
357
|
+
console.log(' feltdb build');
|
|
358
|
+
console.log(' feltdb release certify');
|
|
359
|
+
console.log(' feltdb deploy certify --provider docker');
|
|
360
|
+
console.log(' feltdb deployment certify --provider docker --release <id> --rollback <id>');
|
|
361
|
+
console.log(' feltdb deploy all --release <id>');
|
|
362
|
+
console.log(' feltdb publish --message "Release v1.0"');
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
version: {
|
|
366
|
+
name: 'version',
|
|
367
|
+
description: 'Show version information',
|
|
368
|
+
execute: async () => {
|
|
369
|
+
console.log(`FeltDB CLI v${VERSION}`);
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
async function main() {
|
|
374
|
+
const args = process.argv.slice(2);
|
|
375
|
+
if (args.length === 0) {
|
|
376
|
+
await commands.help.execute([]);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const commandName = args[0];
|
|
380
|
+
const commandArgs = args.slice(1);
|
|
381
|
+
if (!commands[commandName]) {
|
|
382
|
+
console.error(`Unknown command: ${commandName}`);
|
|
383
|
+
console.log(`Run 'feltdb help' for available commands`);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
await commands[commandName].execute(commandArgs);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error instanceof Error) {
|
|
391
|
+
console.error(`Error: ${error.message}`);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
console.error('Unknown error occurred');
|
|
395
|
+
}
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
main();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application identity management
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - FELTDB_APP_NAME: Human-readable application name
|
|
6
|
+
* - FELTDB_APP_ID: UUID-based stable identifier
|
|
7
|
+
* - FELTDB_VERSION: Semantic versioning
|
|
8
|
+
* - Version matching between components
|
|
9
|
+
*/
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import { FELTDB_PACKAGE_VERSION } from './package-versions.js';
|
|
12
|
+
/**
|
|
13
|
+
* Generate a stable UUID from application name
|
|
14
|
+
* Ensures same name always produces same ID
|
|
15
|
+
*/
|
|
16
|
+
export function generateAppId(appName) {
|
|
17
|
+
const hash = crypto
|
|
18
|
+
.createHash('sha256')
|
|
19
|
+
.update(appName)
|
|
20
|
+
.digest('hex');
|
|
21
|
+
// Convert to UUID v5-like format
|
|
22
|
+
const uuid = [
|
|
23
|
+
hash.substring(0, 8),
|
|
24
|
+
hash.substring(8, 12),
|
|
25
|
+
'5' + hash.substring(13, 16),
|
|
26
|
+
((parseInt(hash.substring(16, 18), 16) & 0x3f) | 0x80).toString(16).padStart(2, '0') +
|
|
27
|
+
hash.substring(18, 20),
|
|
28
|
+
hash.substring(20, 32),
|
|
29
|
+
].join('-');
|
|
30
|
+
return uuid;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sanitize application name for use in identifiers
|
|
34
|
+
*/
|
|
35
|
+
export function sanitizeAppName(name) {
|
|
36
|
+
return (name
|
|
37
|
+
// Replace spaces with hyphens
|
|
38
|
+
.replace(/\s+/g, '-')
|
|
39
|
+
// Remove non-alphanumeric except hyphens
|
|
40
|
+
.replace(/[^a-z0-9-]/gi, '')
|
|
41
|
+
// Remove leading/trailing hyphens
|
|
42
|
+
.replace(/^-+|-+$/g, '')
|
|
43
|
+
// Limit to 50 chars
|
|
44
|
+
.substring(0, 50)
|
|
45
|
+
.toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get current FeltDB version from package.json
|
|
49
|
+
*/
|
|
50
|
+
export function getCurrentFeltDBVersion() {
|
|
51
|
+
return FELTDB_PACKAGE_VERSION;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create application identity manifest
|
|
55
|
+
*/
|
|
56
|
+
export function createApplicationIdentity(appName, feltdbVersion) {
|
|
57
|
+
const version = feltdbVersion || getCurrentFeltDBVersion();
|
|
58
|
+
const sanitized = sanitizeAppName(appName);
|
|
59
|
+
const appId = generateAppId(sanitized);
|
|
60
|
+
return {
|
|
61
|
+
name: sanitized,
|
|
62
|
+
id: appId,
|
|
63
|
+
version,
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
components: {
|
|
66
|
+
app: version,
|
|
67
|
+
feltdb: version,
|
|
68
|
+
studio: version,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generate version manifest for Docker image metadata
|
|
74
|
+
*/
|
|
75
|
+
export function generateVersionLabels(identity) {
|
|
76
|
+
return {
|
|
77
|
+
'app.feltdb/app-name': identity.name,
|
|
78
|
+
'app.feltdb/app-id': identity.id,
|
|
79
|
+
'app.feltdb/version': identity.version,
|
|
80
|
+
'app.feltdb/created': identity.createdAt,
|
|
81
|
+
'app.feltdb/component.app': identity.components.app,
|
|
82
|
+
'app.feltdb/component.feltdb': identity.components.feltdb,
|
|
83
|
+
'app.feltdb/component.studio': identity.components.studio,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Validate version compatibility
|
|
88
|
+
*/
|
|
89
|
+
export function validateVersionCompatibility(appVersion, feltdbVersion, studioVersion) {
|
|
90
|
+
const warnings = [];
|
|
91
|
+
// All components should be same major.minor version for safety
|
|
92
|
+
const extractMajorMinor = (v) => v.split('.').slice(0, 2).join('.');
|
|
93
|
+
const appMajorMinor = extractMajorMinor(appVersion);
|
|
94
|
+
const feltdbMajorMinor = extractMajorMinor(feltdbVersion);
|
|
95
|
+
const studioMajorMinor = extractMajorMinor(studioVersion);
|
|
96
|
+
if (appMajorMinor !== feltdbMajorMinor) {
|
|
97
|
+
warnings.push(`App version ${appVersion} differs from FeltDB ${feltdbVersion}`);
|
|
98
|
+
}
|
|
99
|
+
if (feltdbMajorMinor !== studioMajorMinor) {
|
|
100
|
+
warnings.push(`FeltDB version ${feltdbVersion} differs from Studio ${studioVersion}`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
compatible: warnings.length === 0,
|
|
104
|
+
warnings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Generate .feltdb/identity.json manifest
|
|
109
|
+
*/
|
|
110
|
+
export function generateIdentityManifest(identity) {
|
|
111
|
+
return JSON.stringify(identity, null, 2);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate environment file with version information
|
|
115
|
+
*/
|
|
116
|
+
export function generateVersionEnv(identity) {
|
|
117
|
+
return `# Application Identity
|
|
118
|
+
# Generated at: ${identity.createdAt}
|
|
119
|
+
|
|
120
|
+
FELTDB_APP_NAME=${identity.name}
|
|
121
|
+
FELTDB_APP_ID=${identity.id}
|
|
122
|
+
FELTDB_VERSION=${identity.version}
|
|
123
|
+
|
|
124
|
+
# Component Versions
|
|
125
|
+
FELTDB_APP_VERSION=${identity.components.app}
|
|
126
|
+
FELTDB_CORE_VERSION=${identity.components.feltdb}
|
|
127
|
+
FELTDB_STUDIO_VERSION=${identity.components.studio}
|
|
128
|
+
|
|
129
|
+
# Build Info
|
|
130
|
+
FELTDB_BUILT_AT=${new Date().toISOString()}
|
|
131
|
+
`;
|
|
132
|
+
}
|