create-feltdb 0.4.1 → 0.4.3
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/dist/application-identity.js +132 -0
- package/dist/cli-scripts-generator.js +211 -0
- package/dist/cli.js +2 -2
- package/dist/create.js +1 -1
- package/dist/docker-compose-generator.js +258 -0
- package/dist/package-versions.js +1 -1
- package/dist/runtime-templates.js +272 -0
- package/package.json +1 -1
|
@@ -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
|
+
/**
|
|
12
|
+
* Generate a stable UUID from application name
|
|
13
|
+
* Ensures same name always produces same ID
|
|
14
|
+
*/
|
|
15
|
+
export function generateAppId(appName) {
|
|
16
|
+
const hash = crypto
|
|
17
|
+
.createHash('sha256')
|
|
18
|
+
.update(appName)
|
|
19
|
+
.digest('hex');
|
|
20
|
+
// Convert to UUID v5-like format
|
|
21
|
+
const uuid = [
|
|
22
|
+
hash.substring(0, 8),
|
|
23
|
+
hash.substring(8, 12),
|
|
24
|
+
'5' + hash.substring(13, 16),
|
|
25
|
+
((parseInt(hash.substring(16, 18), 16) & 0x3f) | 0x80).toString(16).padStart(2, '0') +
|
|
26
|
+
hash.substring(18, 20),
|
|
27
|
+
hash.substring(20, 32),
|
|
28
|
+
].join('-');
|
|
29
|
+
return uuid;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize application name for use in identifiers
|
|
33
|
+
*/
|
|
34
|
+
export function sanitizeAppName(name) {
|
|
35
|
+
return (name
|
|
36
|
+
// Replace spaces with hyphens
|
|
37
|
+
.replace(/\s+/g, '-')
|
|
38
|
+
// Remove non-alphanumeric except hyphens
|
|
39
|
+
.replace(/[^a-z0-9-]/gi, '')
|
|
40
|
+
// Remove leading/trailing hyphens
|
|
41
|
+
.replace(/^-+|-+$/g, '')
|
|
42
|
+
// Limit to 50 chars
|
|
43
|
+
.substring(0, 50)
|
|
44
|
+
.toLowerCase());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get current FeltDB version from package.json
|
|
48
|
+
*/
|
|
49
|
+
export function getCurrentFeltDBVersion() {
|
|
50
|
+
// This would be replaced at build time with the actual version
|
|
51
|
+
return '0.4.2';
|
|
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
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI lifecycle scripts for FeltDB applications
|
|
3
|
+
*
|
|
4
|
+
* Generates scripts for common operations:
|
|
5
|
+
* - feltdb:up - Start services
|
|
6
|
+
* - feltdb:down - Stop services
|
|
7
|
+
* - feltdb:logs - View logs
|
|
8
|
+
* - feltdb:status - Check health
|
|
9
|
+
* - feltdb:reset - Reset data
|
|
10
|
+
* - feltdb:ps - List running services
|
|
11
|
+
*/
|
|
12
|
+
export function generateUpScript() {
|
|
13
|
+
return `#!/bin/bash
|
|
14
|
+
|
|
15
|
+
# Start FeltDB application stack
|
|
16
|
+
# Usage: npm run feltdb:up
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
echo "🚀 Starting FeltDB application..."
|
|
21
|
+
|
|
22
|
+
# Load environment
|
|
23
|
+
if [ -f .env.local ]; then
|
|
24
|
+
export \$(cat .env.local | grep -v '^#' | xargs)
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Build and start containers
|
|
28
|
+
docker-compose up -d
|
|
29
|
+
|
|
30
|
+
echo "⏳ Waiting for services to be healthy..."
|
|
31
|
+
|
|
32
|
+
# Wait for FeltDB
|
|
33
|
+
TIMEOUT=60
|
|
34
|
+
ELAPSED=0
|
|
35
|
+
while [ \$ELAPSED -lt \$TIMEOUT ]; do
|
|
36
|
+
if curl -s -f http://localhost:7700/health > /dev/null 2>&1; then
|
|
37
|
+
STATE=\$(curl -s http://localhost:7700/health | grep -o '"state":"[^"]*' | cut -d'"' -f4)
|
|
38
|
+
if [ "\$STATE" = "ready" ] || [ "\$STATE" = "recovering" ]; then
|
|
39
|
+
echo "✅ FeltDB is \$STATE"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
sleep 2
|
|
44
|
+
ELAPSED=\$((ELAPSED + 2))
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
# Wait for application
|
|
48
|
+
ELAPSED=0
|
|
49
|
+
while [ \$ELAPSED -lt \$TIMEOUT ]; do
|
|
50
|
+
if curl -s -f http://localhost:3000/health > /dev/null 2>&1; then
|
|
51
|
+
echo "✅ Application is ready"
|
|
52
|
+
break
|
|
53
|
+
fi
|
|
54
|
+
sleep 2
|
|
55
|
+
ELAPSED=\$((ELAPSED + 2))
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "✨ FeltDB stack is running!"
|
|
60
|
+
echo ""
|
|
61
|
+
echo "Services:"
|
|
62
|
+
echo " FeltDB: http://localhost:7700"
|
|
63
|
+
echo " App: http://localhost:3000"
|
|
64
|
+
echo " Studio: http://localhost:8000"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Next: npm run feltdb:logs"
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
export function generateDownScript() {
|
|
70
|
+
return `#!/bin/bash
|
|
71
|
+
|
|
72
|
+
# Stop FeltDB application stack
|
|
73
|
+
# Usage: npm run feltdb:down
|
|
74
|
+
|
|
75
|
+
echo "🛑 Stopping FeltDB application..."
|
|
76
|
+
|
|
77
|
+
docker-compose down
|
|
78
|
+
|
|
79
|
+
echo "✅ Services stopped"
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
export function generateLogsScript() {
|
|
83
|
+
return `#!/bin/bash
|
|
84
|
+
|
|
85
|
+
# View logs from FeltDB stack
|
|
86
|
+
# Usage: npm run feltdb:logs [service]
|
|
87
|
+
# npm run feltdb:logs feltdb
|
|
88
|
+
# npm run feltdb:logs app
|
|
89
|
+
# npm run feltdb:logs studio
|
|
90
|
+
|
|
91
|
+
SERVICE=\$1
|
|
92
|
+
|
|
93
|
+
if [ -z "\$SERVICE" ]; then
|
|
94
|
+
echo "📋 Logs from all services (Ctrl+C to exit):"
|
|
95
|
+
docker-compose logs -f
|
|
96
|
+
else
|
|
97
|
+
echo "📋 Logs from \$SERVICE (Ctrl+C to exit):"
|
|
98
|
+
docker-compose logs -f \$SERVICE
|
|
99
|
+
fi
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
export function generateStatusScript() {
|
|
103
|
+
return `#!/bin/bash
|
|
104
|
+
|
|
105
|
+
# Check health and status of FeltDB stack
|
|
106
|
+
# Usage: npm run feltdb:status
|
|
107
|
+
|
|
108
|
+
echo "🔍 FeltDB Application Status"
|
|
109
|
+
echo ""
|
|
110
|
+
|
|
111
|
+
# Docker Compose status
|
|
112
|
+
echo "Containers:"
|
|
113
|
+
docker-compose ps
|
|
114
|
+
|
|
115
|
+
echo ""
|
|
116
|
+
|
|
117
|
+
# FeltDB health
|
|
118
|
+
echo "FeltDB Health:"
|
|
119
|
+
if RESPONSE=\$(curl -s -m 5 http://localhost:7700/health 2>/dev/null); then
|
|
120
|
+
STATE=\$(echo \$RESPONSE | grep -o '"state":"[^"]*' | cut -d'"' -f4)
|
|
121
|
+
VERSION=\$(echo \$RESPONSE | grep -o '"version":"[^"]*' | cut -d'"' -f4)
|
|
122
|
+
echo " State: \$STATE"
|
|
123
|
+
echo " Version: \$VERSION"
|
|
124
|
+
else
|
|
125
|
+
echo " ❌ No response"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Application health
|
|
129
|
+
echo ""
|
|
130
|
+
echo "Application Health:"
|
|
131
|
+
if RESPONSE=\$(curl -s -m 5 http://localhost:3000/health 2>/dev/null); then
|
|
132
|
+
echo " ✅ Responding"
|
|
133
|
+
else
|
|
134
|
+
echo " ❌ No response"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
echo ""
|
|
138
|
+
echo "Log tail: npm run feltdb:logs"
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
export function generateResetScript() {
|
|
142
|
+
return `#!/bin/bash
|
|
143
|
+
|
|
144
|
+
# Reset FeltDB data (WARNING: destructive)
|
|
145
|
+
# Usage: npm run feltdb:reset
|
|
146
|
+
|
|
147
|
+
echo "⚠️ This will delete all FeltDB data!"
|
|
148
|
+
read -p "Are you sure? (type 'yes' to confirm): " CONFIRM
|
|
149
|
+
|
|
150
|
+
if [ "\$CONFIRM" != "yes" ]; then
|
|
151
|
+
echo "Cancelled."
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo "🧹 Resetting FeltDB data..."
|
|
156
|
+
|
|
157
|
+
# Stop containers
|
|
158
|
+
docker-compose down
|
|
159
|
+
|
|
160
|
+
# Remove volumes
|
|
161
|
+
docker volume rm \$(docker volume ls -q | grep feltdb_data) 2>/dev/null || true
|
|
162
|
+
|
|
163
|
+
# Restart
|
|
164
|
+
echo "🚀 Restarting services..."
|
|
165
|
+
docker-compose up -d
|
|
166
|
+
|
|
167
|
+
echo "✅ Data reset complete"
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
export function generatePsScript() {
|
|
171
|
+
return `#!/bin/bash
|
|
172
|
+
|
|
173
|
+
# List running FeltDB services
|
|
174
|
+
# Usage: npm run feltdb:ps
|
|
175
|
+
|
|
176
|
+
docker-compose ps
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
export function generatePackageJsonScripts(runtime) {
|
|
180
|
+
const scripts = {
|
|
181
|
+
'feltdb:up': 'bash scripts/feltdb-up.sh',
|
|
182
|
+
'feltdb:down': 'bash scripts/feltdb-down.sh',
|
|
183
|
+
'feltdb:logs': 'bash scripts/feltdb-logs.sh',
|
|
184
|
+
'feltdb:status': 'bash scripts/feltdb-status.sh',
|
|
185
|
+
'feltdb:reset': 'bash scripts/feltdb-reset.sh',
|
|
186
|
+
'feltdb:ps': 'bash scripts/feltdb-ps.sh',
|
|
187
|
+
};
|
|
188
|
+
if (runtime === 'browser') {
|
|
189
|
+
// Browser doesn't need container scripts
|
|
190
|
+
return {
|
|
191
|
+
dev: 'vite',
|
|
192
|
+
build: 'tsc && vite build',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (runtime === 'node') {
|
|
196
|
+
return {
|
|
197
|
+
dev: 'tsx src/index.ts',
|
|
198
|
+
build: 'tsc',
|
|
199
|
+
start: 'node dist/index.js',
|
|
200
|
+
...scripts,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (runtime === 'self-hosted') {
|
|
204
|
+
return {
|
|
205
|
+
dev: 'docker-compose up -d --build',
|
|
206
|
+
build: 'docker-compose build',
|
|
207
|
+
...scripts,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return scripts;
|
|
211
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -124,11 +124,11 @@ async function promptForOptions(defaults) {
|
|
|
124
124
|
async function main() {
|
|
125
125
|
const args = process.argv.slice(2);
|
|
126
126
|
if (args.includes('--help') || args.includes('-h')) {
|
|
127
|
-
console.log(`create-feltdb 0.4.
|
|
127
|
+
console.log(`create-feltdb 0.4.2\n\nUsage: create-feltdb [project-name] [options]\n\nOptions:\n --runtime <browser|node|self-hosted>\n --framework <react|vanilla>\n --no-distributed\n --no-agents\n --capabilities <list>\n --no-install\n --no-start\n -y, --yes\n -h, --help\n --version`);
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
if (args.includes('--version')) {
|
|
131
|
-
console.log('0.4.
|
|
131
|
+
console.log('0.4.2');
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
134
|
// Find project name (first non-flag argument)
|
package/dist/create.js
CHANGED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Compose generator for self-hosted FeltDB deployments
|
|
3
|
+
*
|
|
4
|
+
* Generates:
|
|
5
|
+
* - docker-compose.yml with three services (FeltDB, App, Studio)
|
|
6
|
+
* - Dockerfile for application container
|
|
7
|
+
* - .dockerignore for optimized builds
|
|
8
|
+
* - Environment configuration
|
|
9
|
+
*/
|
|
10
|
+
export function generateDockerCompose(config) {
|
|
11
|
+
return `version: '3.9'
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
feltdb:
|
|
15
|
+
image: rkendel1/feltdb:latest
|
|
16
|
+
container_name: \${COMPOSE_PROJECT_NAME}-feltdb
|
|
17
|
+
environment:
|
|
18
|
+
FELTDB_APP_NAME: \${FELTDB_APP_NAME:-${config.appName}}
|
|
19
|
+
FELTDB_APP_ID: \${FELTDB_APP_ID:-${config.appId}}
|
|
20
|
+
FELTDB_VERSION: \${FELTDB_VERSION:-${config.version}}
|
|
21
|
+
FELTDB_PEERS: \${FELTDB_PEERS:-}
|
|
22
|
+
FELTDB_REPLICATION_PORT: ${config.replicationPort}
|
|
23
|
+
NODE_ENV: \${NODE_ENV:-production}
|
|
24
|
+
ports:
|
|
25
|
+
- "\${FELTDB_API_PORT:-${config.port}}:7700"
|
|
26
|
+
- "\${FELTDB_REPLICATION_PORT:-${config.replicationPort}}:${config.replicationPort}"
|
|
27
|
+
volumes:
|
|
28
|
+
- feltdb_data:/data
|
|
29
|
+
- ./feltdb.flow:/app/feltdb.flow:ro
|
|
30
|
+
healthcheck:
|
|
31
|
+
test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
|
|
32
|
+
interval: 10s
|
|
33
|
+
timeout: 5s
|
|
34
|
+
retries: 3
|
|
35
|
+
start_period: 30s
|
|
36
|
+
restart: unless-stopped
|
|
37
|
+
networks:
|
|
38
|
+
- feltdb_network
|
|
39
|
+
labels:
|
|
40
|
+
app.feltdb/component: "database"
|
|
41
|
+
app.feltdb/version: "${config.version}"
|
|
42
|
+
|
|
43
|
+
app:
|
|
44
|
+
build:
|
|
45
|
+
context: .
|
|
46
|
+
dockerfile: Dockerfile
|
|
47
|
+
args:
|
|
48
|
+
NODE_ENV: production
|
|
49
|
+
container_name: \${COMPOSE_PROJECT_NAME}-app
|
|
50
|
+
environment:
|
|
51
|
+
FELTDB_API_URL: http://feltdb:7700
|
|
52
|
+
FELTDB_APP_NAME: \${FELTDB_APP_NAME:-${config.appName}}
|
|
53
|
+
NODE_ENV: \${NODE_ENV:-production}
|
|
54
|
+
ports:
|
|
55
|
+
- "\${APP_PORT:-3000}:3000"
|
|
56
|
+
depends_on:
|
|
57
|
+
feltdb:
|
|
58
|
+
condition: service_healthy
|
|
59
|
+
volumes:
|
|
60
|
+
- ./src:/app/src
|
|
61
|
+
- app_node_modules:/app/node_modules
|
|
62
|
+
healthcheck:
|
|
63
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
64
|
+
interval: 10s
|
|
65
|
+
timeout: 5s
|
|
66
|
+
retries: 3
|
|
67
|
+
start_period: 30s
|
|
68
|
+
restart: unless-stopped
|
|
69
|
+
networks:
|
|
70
|
+
- feltdb_network
|
|
71
|
+
labels:
|
|
72
|
+
app.feltdb/component: "application"
|
|
73
|
+
app.feltdb/version: "${config.version}"
|
|
74
|
+
|
|
75
|
+
studio:
|
|
76
|
+
image: rkendel1/feltdb-studio:latest
|
|
77
|
+
container_name: \${COMPOSE_PROJECT_NAME}-studio
|
|
78
|
+
environment:
|
|
79
|
+
FELTDB_API_URL: http://feltdb:7700
|
|
80
|
+
APP_URL: http://app:3000
|
|
81
|
+
ports:
|
|
82
|
+
- "\${STUDIO_PORT:-${config.studioPort}}:8000"
|
|
83
|
+
depends_on:
|
|
84
|
+
- feltdb
|
|
85
|
+
- app
|
|
86
|
+
networks:
|
|
87
|
+
- feltdb_network
|
|
88
|
+
labels:
|
|
89
|
+
app.feltdb/component: "studio"
|
|
90
|
+
|
|
91
|
+
networks:
|
|
92
|
+
feltdb_network:
|
|
93
|
+
driver: bridge
|
|
94
|
+
labels:
|
|
95
|
+
app.feltdb/network: "internal"
|
|
96
|
+
|
|
97
|
+
volumes:
|
|
98
|
+
feltdb_data:
|
|
99
|
+
driver: local
|
|
100
|
+
labels:
|
|
101
|
+
app.feltdb/data: "durable-operations-log"
|
|
102
|
+
app_node_modules:
|
|
103
|
+
driver: local
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
export function generateDockerfile(config, framework) {
|
|
107
|
+
const buildCommand = framework === 'react' ? 'npm run build' : 'npm run build';
|
|
108
|
+
return `# FeltDB Application Container
|
|
109
|
+
# Multi-stage build for optimized production image
|
|
110
|
+
|
|
111
|
+
FROM node:18-alpine AS builder
|
|
112
|
+
|
|
113
|
+
WORKDIR /app
|
|
114
|
+
|
|
115
|
+
# Install dependencies
|
|
116
|
+
COPY package.json package-lock.json ./
|
|
117
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
118
|
+
|
|
119
|
+
# Copy source
|
|
120
|
+
COPY . .
|
|
121
|
+
|
|
122
|
+
# Build application
|
|
123
|
+
RUN ${buildCommand}
|
|
124
|
+
|
|
125
|
+
# Final stage
|
|
126
|
+
FROM node:18-alpine
|
|
127
|
+
|
|
128
|
+
WORKDIR /app
|
|
129
|
+
|
|
130
|
+
# Install health check utility
|
|
131
|
+
RUN apk add --no-cache curl
|
|
132
|
+
|
|
133
|
+
# Copy built application
|
|
134
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
135
|
+
COPY --from=builder /app/dist ./dist
|
|
136
|
+
COPY --from=builder /app/package.json ./package.json
|
|
137
|
+
COPY --from=builder /app/feltdb.flow ./feltdb.flow
|
|
138
|
+
|
|
139
|
+
# Create data directory for Node runtime
|
|
140
|
+
RUN mkdir -p /data && chmod 755 /data
|
|
141
|
+
|
|
142
|
+
# Non-root user for security
|
|
143
|
+
RUN addgroup -g 1001 -S nodejs
|
|
144
|
+
RUN adduser -S nodejs -u 1001
|
|
145
|
+
USER nodejs
|
|
146
|
+
|
|
147
|
+
# Health check
|
|
148
|
+
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \\
|
|
149
|
+
CMD curl -f http://localhost:3000/health || exit 1
|
|
150
|
+
|
|
151
|
+
# Startup
|
|
152
|
+
ENV NODE_ENV=production
|
|
153
|
+
EXPOSE 3000
|
|
154
|
+
|
|
155
|
+
CMD ["node", "dist/index.js"]
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
export function generateDockerIgnore() {
|
|
159
|
+
return `node_modules
|
|
160
|
+
npm-debug.log
|
|
161
|
+
dist
|
|
162
|
+
build
|
|
163
|
+
.git
|
|
164
|
+
.gitignore
|
|
165
|
+
.env
|
|
166
|
+
.env.local
|
|
167
|
+
.env.*.local
|
|
168
|
+
.DS_Store
|
|
169
|
+
*.log
|
|
170
|
+
.feltdb/keys.json
|
|
171
|
+
.feltdb/connection.json
|
|
172
|
+
.nextc
|
|
173
|
+
.next
|
|
174
|
+
coverage
|
|
175
|
+
.nyc_output
|
|
176
|
+
.cache
|
|
177
|
+
.turbo
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
export function generateDotEnvLocal(config) {
|
|
181
|
+
return `# Docker Compose Environment
|
|
182
|
+
# Generated by create-feltdb for \${config.appName}
|
|
183
|
+
|
|
184
|
+
COMPOSE_PROJECT_NAME=${config.appName.toLowerCase().replace(/[^a-z0-9]/g, '-')}
|
|
185
|
+
|
|
186
|
+
# FeltDB Configuration
|
|
187
|
+
FELTDB_APP_NAME=${config.appName}
|
|
188
|
+
FELTDB_APP_ID=${config.appId}
|
|
189
|
+
FELTDB_VERSION=${config.version}
|
|
190
|
+
FELTDB_API_PORT=${config.port}
|
|
191
|
+
FELTDB_REPLICATION_PORT=${config.replicationPort}
|
|
192
|
+
FELTDB_PEERS=
|
|
193
|
+
|
|
194
|
+
# Application Configuration
|
|
195
|
+
APP_PORT=3000
|
|
196
|
+
FELTDB_API_URL=http://feltdb:7700
|
|
197
|
+
|
|
198
|
+
# Studio Configuration
|
|
199
|
+
STUDIO_PORT=${config.studioPort}
|
|
200
|
+
|
|
201
|
+
# Runtime
|
|
202
|
+
NODE_ENV=production
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
export function generateHealthCheckScript() {
|
|
206
|
+
return `#!/bin/bash
|
|
207
|
+
|
|
208
|
+
# Multi-service health check orchestration
|
|
209
|
+
# Ensures all services are ready before marking deployment as healthy
|
|
210
|
+
|
|
211
|
+
set -e
|
|
212
|
+
|
|
213
|
+
TIMEOUT=60
|
|
214
|
+
ELAPSED=0
|
|
215
|
+
INTERVAL=2
|
|
216
|
+
|
|
217
|
+
echo "[Health Check] Starting orchestration check (timeout: \${TIMEOUT}s)"
|
|
218
|
+
|
|
219
|
+
# Wait for FeltDB
|
|
220
|
+
echo "[Health Check] Checking FeltDB service..."
|
|
221
|
+
while [ \$ELAPSED -lt \$TIMEOUT ]; do
|
|
222
|
+
if curl -s -f http://feltdb:7700/health > /dev/null 2>&1; then
|
|
223
|
+
STATE=\$(curl -s http://feltdb:7700/health | grep -o '"state":"[^"]*' | cut -d'"' -f4)
|
|
224
|
+
if [ "\$STATE" = "ready" ] || [ "\$STATE" = "recovering" ]; then
|
|
225
|
+
echo "[Health Check] FeltDB is \$STATE"
|
|
226
|
+
break
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
sleep \$INTERVAL
|
|
230
|
+
ELAPSED=\$((ELAPSED + INTERVAL))
|
|
231
|
+
done
|
|
232
|
+
|
|
233
|
+
if [ \$ELAPSED -ge \$TIMEOUT ]; then
|
|
234
|
+
echo "[Health Check] FAILED: FeltDB did not respond within timeout"
|
|
235
|
+
exit 1
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Wait for application
|
|
239
|
+
echo "[Health Check] Checking application service..."
|
|
240
|
+
ELAPSED=0
|
|
241
|
+
while [ \$ELAPSED -lt \$TIMEOUT ]; do
|
|
242
|
+
if curl -s -f http://app:3000/health > /dev/null 2>&1; then
|
|
243
|
+
echo "[Health Check] Application is ready"
|
|
244
|
+
break
|
|
245
|
+
fi
|
|
246
|
+
sleep \$INTERVAL
|
|
247
|
+
ELAPSED=\$((ELAPSED + INTERVAL))
|
|
248
|
+
done
|
|
249
|
+
|
|
250
|
+
if [ \$ELAPSED -ge \$TIMEOUT ]; then
|
|
251
|
+
echo "[Health Check] FAILED: Application did not respond within timeout"
|
|
252
|
+
exit 1
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
echo "[Health Check] All services healthy - deployment ready"
|
|
256
|
+
exit 0
|
|
257
|
+
`;
|
|
258
|
+
}
|
package/dist/package-versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// One release train keeps generated applications installable. The repository
|
|
2
2
|
// validation script checks these values against every workspace manifest.
|
|
3
|
-
export const FELTDB_PACKAGE_VERSION = '0.4.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.4.3';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-specific templates and configuration
|
|
3
|
+
*
|
|
4
|
+
* Generates runtime-appropriate file structure and configuration
|
|
5
|
+
* for Browser, Node, and Self-hosted deployment modes.
|
|
6
|
+
*/
|
|
7
|
+
export function generateBrowserRuntime(config) {
|
|
8
|
+
return {
|
|
9
|
+
feltdbInit: `import { createFeltDB } from '@feltdb/core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Browser Runtime
|
|
13
|
+
*
|
|
14
|
+
* Local-first FeltDB with durable IndexedDB storage.
|
|
15
|
+
* No server required. Full offline capability.
|
|
16
|
+
*
|
|
17
|
+
* AppID: ${config.appId}
|
|
18
|
+
*/
|
|
19
|
+
export const db = createFeltDB({
|
|
20
|
+
namespace: '${config.appName}',
|
|
21
|
+
runtime: 'browser',
|
|
22
|
+
storage: {
|
|
23
|
+
type: 'indexeddb',
|
|
24
|
+
durable: true,
|
|
25
|
+
},
|
|
26
|
+
${config.distributed ? `
|
|
27
|
+
replication: {
|
|
28
|
+
enabled: false, // Browser cannot initiate replication
|
|
29
|
+
},` : ''}
|
|
30
|
+
health: {
|
|
31
|
+
onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
|
|
32
|
+
console.log(\`[FeltDB Health] Browser state changed: \${state}\`);
|
|
33
|
+
window.dispatchEvent(new CustomEvent('feltdb:health', { detail: { state } }));
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Collections
|
|
39
|
+
export const documents = db.collection('documents');
|
|
40
|
+
export const reports = db.collection('reports');
|
|
41
|
+
`,
|
|
42
|
+
envExample: `# Browser Runtime
|
|
43
|
+
# No environment configuration needed.
|
|
44
|
+
# Data persists locally in IndexedDB.
|
|
45
|
+
NODE_ENV=development
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function generateNodeRuntime(config) {
|
|
50
|
+
return {
|
|
51
|
+
feltdbInit: `import { createFeltDB } from '@feltdb/core';
|
|
52
|
+
import path from 'path';
|
|
53
|
+
import { fileURLToPath } from 'url';
|
|
54
|
+
import fs from 'fs';
|
|
55
|
+
|
|
56
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Node Runtime
|
|
60
|
+
*
|
|
61
|
+
* Server-side FeltDB with durable file-based storage.
|
|
62
|
+
* Can participate in distributed replication.
|
|
63
|
+
* Persistent operation log to /data directory.
|
|
64
|
+
*
|
|
65
|
+
* AppID: ${config.appId}
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
const dataDir = process.env.FELTDB_DATA_DIR || path.join(__dirname, '..', 'data');
|
|
69
|
+
if (!fs.existsSync(dataDir)) {
|
|
70
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const db = createFeltDB({
|
|
74
|
+
namespace: '${config.appName}',
|
|
75
|
+
runtime: 'node',
|
|
76
|
+
storage: {
|
|
77
|
+
type: 'file',
|
|
78
|
+
path: dataDir,
|
|
79
|
+
durable: true,
|
|
80
|
+
fsync: true,
|
|
81
|
+
},
|
|
82
|
+
${config.distributed ? `
|
|
83
|
+
replication: {
|
|
84
|
+
enabled: true,
|
|
85
|
+
port: parseInt(process.env.FELTDB_REPLICATION_PORT || '9000'),
|
|
86
|
+
},` : ''}
|
|
87
|
+
health: {
|
|
88
|
+
onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
|
|
89
|
+
console.log(\`[FeltDB Health] Node state changed: \${state}\`);
|
|
90
|
+
// Emit to monitoring system
|
|
91
|
+
process.emit('feltdb:health', { state, timestamp: Date.now() });
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Collections
|
|
97
|
+
export const documents = db.collection('documents');
|
|
98
|
+
export const reports = db.collection('reports');
|
|
99
|
+
|
|
100
|
+
// Startup hook
|
|
101
|
+
export async function startup() {
|
|
102
|
+
console.log(\`[FeltDB] Starting Node runtime\`);
|
|
103
|
+
console.log(\`[FeltDB] App: ${config.appName} (${config.appId})\`);
|
|
104
|
+
console.log(\`[FeltDB] Version: ${config.version}\`);
|
|
105
|
+
console.log(\`[FeltDB] Data directory: \${dataDir}\`);
|
|
106
|
+
${config.distributed ? `console.log(\`[FeltDB] Replication enabled on port \${process.env.FELTDB_REPLICATION_PORT || 9000}\`);` : ''}
|
|
107
|
+
await db.ready();
|
|
108
|
+
console.log(\`[FeltDB] Ready\`);
|
|
109
|
+
}
|
|
110
|
+
`,
|
|
111
|
+
envExample: `# Node Runtime
|
|
112
|
+
FELTDB_DATA_DIR=./data
|
|
113
|
+
FELTDB_REPLICATION_PORT=9000
|
|
114
|
+
NODE_ENV=development
|
|
115
|
+
`,
|
|
116
|
+
startupScript: `#!/usr/bin/env node
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Node runtime startup orchestration
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
import { db, startup } from './feltdb.js';
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
try {
|
|
126
|
+
await startup();
|
|
127
|
+
|
|
128
|
+
// Start HTTP server for API
|
|
129
|
+
const { createServer } = await import('./server.js');
|
|
130
|
+
const server = createServer(db);
|
|
131
|
+
const port = process.env.FELTDB_API_PORT || 7700;
|
|
132
|
+
server.listen(port, () => {
|
|
133
|
+
console.log(\`[HTTP] API server listening on port \${port}\`);
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('[FeltDB] Startup failed:', err);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main();
|
|
142
|
+
`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function generateSelfHostedRuntime(config) {
|
|
146
|
+
return {
|
|
147
|
+
feltdbInit: `import { createFeltDB } from '@feltdb/core';
|
|
148
|
+
import path from 'path';
|
|
149
|
+
import { fileURLToPath } from 'url';
|
|
150
|
+
import fs from 'fs';
|
|
151
|
+
|
|
152
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Self-hosted Runtime
|
|
156
|
+
*
|
|
157
|
+
* Containerized FeltDB with Docker Compose orchestration.
|
|
158
|
+
* Durable file-based storage with persistent volumes.
|
|
159
|
+
* Full replication topology with multi-node support.
|
|
160
|
+
*
|
|
161
|
+
* AppID: ${config.appId}
|
|
162
|
+
* Version: ${config.version}
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
const dataDir = process.env.FELTDB_DATA_DIR || '/data';
|
|
166
|
+
if (!fs.existsSync(dataDir)) {
|
|
167
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const db = createFeltDB({
|
|
171
|
+
namespace: '${config.appName}',
|
|
172
|
+
runtime: 'self-hosted',
|
|
173
|
+
storage: {
|
|
174
|
+
type: 'file',
|
|
175
|
+
path: dataDir,
|
|
176
|
+
durable: true,
|
|
177
|
+
fsync: true,
|
|
178
|
+
},
|
|
179
|
+
replication: {
|
|
180
|
+
enabled: true,
|
|
181
|
+
port: 9000,
|
|
182
|
+
peers: (process.env.FELTDB_PEERS || '').split(',').filter(Boolean),
|
|
183
|
+
},
|
|
184
|
+
health: {
|
|
185
|
+
onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
|
|
186
|
+
console.log(\`[\${new Date().toISOString()}] [FeltDB] \${state.toUpperCase()}\`);
|
|
187
|
+
// Structured logging for container orchestration
|
|
188
|
+
process.emit('feltdb:health', { state, timestamp: Date.now() });
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Collections
|
|
194
|
+
export const documents = db.collection('documents');
|
|
195
|
+
export const reports = db.collection('reports');
|
|
196
|
+
|
|
197
|
+
// Startup with health check loop
|
|
198
|
+
export async function startup() {
|
|
199
|
+
console.log(\`[\${new Date().toISOString()}] [FeltDB] Container starting\`);
|
|
200
|
+
console.log(\`[FeltDB] App: ${config.appName} (${config.appId})\`);
|
|
201
|
+
console.log(\`[FeltDB] Version: ${config.version}\`);
|
|
202
|
+
console.log(\`[FeltDB] Runtime: self-hosted\`);
|
|
203
|
+
console.log(\`[FeltDB] Data directory: \${dataDir}\`);
|
|
204
|
+
|
|
205
|
+
await db.ready();
|
|
206
|
+
console.log(\`[\${new Date().toISOString()}] [FeltDB] READY\`);
|
|
207
|
+
}
|
|
208
|
+
`,
|
|
209
|
+
envExample: `# Self-hosted Docker Runtime
|
|
210
|
+
FELTDB_DATA_DIR=/data
|
|
211
|
+
FELTDB_APP_NAME=${config.appName}
|
|
212
|
+
FELTDB_APP_ID=${config.appId}
|
|
213
|
+
FELTDB_VERSION=${config.version}
|
|
214
|
+
FELTDB_REPLICATION_PORT=9000
|
|
215
|
+
FELTDB_PEERS=
|
|
216
|
+
NODE_ENV=production
|
|
217
|
+
`,
|
|
218
|
+
healthCheck: `#!/bin/sh
|
|
219
|
+
|
|
220
|
+
# Health check for Docker container
|
|
221
|
+
# Verifies FeltDB is responding and not in RECOVERING state
|
|
222
|
+
|
|
223
|
+
HEALTH_URL="http://localhost:7700/health"
|
|
224
|
+
TIMEOUT=5
|
|
225
|
+
|
|
226
|
+
# Query health endpoint
|
|
227
|
+
RESPONSE=\$(curl -s -m \${TIMEOUT} \${HEALTH_URL} 2>/dev/null)
|
|
228
|
+
|
|
229
|
+
if [ -z "\$RESPONSE" ]; then
|
|
230
|
+
echo "UNHEALTHY: No response from health endpoint"
|
|
231
|
+
exit 1
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
STATE=\$(echo \$RESPONSE | grep -o '"state":"[^"]*' | cut -d'"' -f4)
|
|
235
|
+
|
|
236
|
+
case "\$STATE" in
|
|
237
|
+
ready)
|
|
238
|
+
echo "HEALTHY: State is ready"
|
|
239
|
+
exit 0
|
|
240
|
+
;;
|
|
241
|
+
recovering)
|
|
242
|
+
echo "STARTING: Recovering from disk"
|
|
243
|
+
exit 0
|
|
244
|
+
;;
|
|
245
|
+
starting)
|
|
246
|
+
echo "STARTING: Initializing"
|
|
247
|
+
exit 0
|
|
248
|
+
;;
|
|
249
|
+
failed)
|
|
250
|
+
echo "UNHEALTHY: State is failed"
|
|
251
|
+
exit 1
|
|
252
|
+
;;
|
|
253
|
+
*)
|
|
254
|
+
echo "UNKNOWN: State is \$STATE"
|
|
255
|
+
exit 2
|
|
256
|
+
;;
|
|
257
|
+
esac
|
|
258
|
+
`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
export function getTemplate(config) {
|
|
262
|
+
switch (config.runtime) {
|
|
263
|
+
case 'browser':
|
|
264
|
+
return generateBrowserRuntime(config);
|
|
265
|
+
case 'node':
|
|
266
|
+
return generateNodeRuntime(config);
|
|
267
|
+
case 'self-hosted':
|
|
268
|
+
return generateSelfHostedRuntime(config);
|
|
269
|
+
default:
|
|
270
|
+
throw new Error(`Unknown runtime: ${config.runtime}`);
|
|
271
|
+
}
|
|
272
|
+
}
|