@feltdb/core 0.6.8 → 0.6.10
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/cli/application.js +55 -88
- package/dist/cli/commands.js +206 -95
- package/dist/cli/index.js +1 -1
- package/dist/collection.d.ts +110 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +228 -3
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +408 -178
- package/dist/create/server-source/crates/feltdb/src/lib.rs +49 -5
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +659 -0
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +285 -0
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +582 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +141 -0
- package/dist/db.d.ts +59 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +94 -2
- package/dist/embedded-transaction.d.ts +77 -0
- package/dist/embedded-transaction.d.ts.map +1 -0
- package/dist/embedded-transaction.js +102 -0
- package/dist/feltdb.d.ts +49 -1
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +33 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +128 -4
- package/dist/freshness.d.ts +119 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +115 -0
- package/dist/http-db.d.ts +48 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +77 -1
- package/dist/index-backend.d.ts +8 -0
- package/dist/index-backend.d.ts.map +1 -1
- package/dist/index-backend.js +13 -0
- package/dist/index-core.d.ts +78 -0
- package/dist/index-core.d.ts.map +1 -0
- package/dist/index-core.js +72 -0
- package/dist/index-manager.d.ts +11 -0
- package/dist/index-manager.d.ts.map +1 -1
- package/dist/index-manager.js +30 -13
- package/dist/index-types.d.ts +17 -0
- package/dist/index-types.d.ts.map +1 -1
- package/dist/index.browser.d.ts +14 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +13 -0
- package/dist/index.d.ts +8 -59
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -54
- package/dist/indexeddb-db.d.ts +32 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +151 -1
- package/dist/memory-db.d.ts +26 -0
- package/dist/memory-db.d.ts.map +1 -1
- package/dist/memory-db.js +86 -0
- package/dist/runtime-registry.d.ts +32 -0
- package/dist/runtime-registry.d.ts.map +1 -0
- package/dist/runtime-registry.js +38 -0
- package/dist/studio-app/assets/{feltdb_wasm-B4wq4mqp.js → feltdb_wasm-CJv3wHzi.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
- package/dist/studio-app/assets/index-D3rT0SJi.js +28 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +98 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +86 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/browser.d.ts +22 -0
- package/dist/workspace/browser.d.ts.map +1 -0
- package/dist/workspace/browser.js +17 -0
- package/dist/workspace/git-identity.d.ts +35 -0
- package/dist/workspace/git-identity.d.ts.map +1 -0
- package/dist/workspace/git-identity.js +81 -0
- package/dist/workspace/index.d.ts +11 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +6 -0
- package/dist/workspace/investigation-analysis.d.ts +47 -0
- package/dist/workspace/investigation-analysis.d.ts.map +1 -0
- package/dist/workspace/investigation-analysis.js +181 -0
- package/dist/workspace/investigation-lifecycle-manager.d.ts +86 -0
- package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
- package/dist/workspace/investigation-lifecycle-manager.js +229 -0
- package/dist/workspace/investigation-supervisor.d.ts +100 -0
- package/dist/workspace/investigation-supervisor.d.ts.map +1 -0
- package/dist/workspace/investigation-supervisor.js +236 -0
- package/dist/workspace/runtime-investigation.d.ts +1 -1
- package/dist/workspace/runtime-investigation.d.ts.map +1 -1
- package/dist/workspace/runtime-investigation.js +46 -21
- package/dist/workspace/runtime-observation.d.ts +79 -0
- package/dist/workspace/runtime-observation.d.ts.map +1 -0
- package/dist/workspace/runtime-observation.js +152 -0
- package/dist/workspace/runtime-observer.d.ts +51 -0
- package/dist/workspace/runtime-observer.d.ts.map +1 -0
- package/dist/workspace/runtime-observer.js +268 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -5
- package/dist/workspace/workspace-types.d.ts +113 -0
- package/dist/workspace/workspace-types.d.ts.map +1 -1
- package/package.json +12 -4
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +0 -523
- package/dist/studio-app/assets/feltdb_wasm_bg-Ceyi7l21.wasm +0 -0
- package/dist/studio-app/assets/index-BF45M757.js +0 -28
package/dist/cli/application.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import net from 'net';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
4
|
const FRAMEWORKS = [
|
|
5
5
|
{ name: 'Next.js', packages: ['next'], pattern: /(?:^|\s)next(?:\s+dev)?(?:\s|$)/ },
|
|
6
6
|
{ name: 'Astro', packages: ['astro'], pattern: /(?:^|\s)astro(?:\s+dev)?(?:\s|$)/ },
|
|
@@ -21,107 +21,74 @@ function packageManagerAt(root, declared) {
|
|
|
21
21
|
return 'bun';
|
|
22
22
|
return 'npm';
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function scriptPort(script = '') {
|
|
25
25
|
const match = script.match(/(?:^|\s)(?:PORT=|--port(?:=|\s+)|-p\s+)(\d{1,5})(?:\s|$)/i);
|
|
26
26
|
if (!match)
|
|
27
27
|
return undefined;
|
|
28
28
|
const port = Number(match[1]);
|
|
29
29
|
return port > 0 && port <= 65535 ? port : undefined;
|
|
30
30
|
}
|
|
31
|
+
function readJson(file, malformedMessage) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`${malformedMessage} (${error.message})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Read application metadata only. This function never starts an application. */
|
|
31
40
|
export function detectApplication(root) {
|
|
32
41
|
const packageFile = path.join(root, 'package.json');
|
|
33
|
-
|
|
34
|
-
if (fs.existsSync(packageFile)) {
|
|
35
|
-
try {
|
|
36
|
-
manifest = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
throw new Error(`Cannot detect application: malformed package.json (${error.message})`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const manager = packageManagerAt(root, typeof manifest.packageManager === 'string' ? manifest.packageManager : undefined);
|
|
42
|
+
const manifest = fs.existsSync(packageFile) ? readJson(packageFile, 'Cannot inspect application: malformed package.json') : {};
|
|
43
43
|
const script = typeof manifest.scripts?.dev === 'string' ? manifest.scripts.dev.trim() : undefined;
|
|
44
44
|
const dependencies = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
45
45
|
const framework = FRAMEWORKS.find(candidate => candidate.packages.some(name => name in dependencies) || (script ? candidate.pattern.test(script) : false));
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
// Projects generated by FeltDB historically use `feltdb dev` as their script.
|
|
59
|
-
// Keep their Vite application working without recursively spawning this CLI.
|
|
60
|
-
if (framework?.name === 'Vite' || fs.existsSync(path.join(root, 'index.html'))) {
|
|
61
|
-
const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
62
|
-
return { packageManager: manager, framework: 'Vite', devScript: script, command: executable, args: ['exec', '--', 'vite'] };
|
|
63
|
-
}
|
|
64
|
-
throw new Error('Cannot determine how to start the application. Add a package.json dev script or pass --app-url.');
|
|
46
|
+
const feltConfigFile = path.join(root, 'feltdb.config.json');
|
|
47
|
+
const feltConfig = fs.existsSync(feltConfigFile) ? readJson(feltConfigFile, 'Cannot inspect application: malformed feltdb.config.json') : {};
|
|
48
|
+
const configuredUrl = typeof feltConfig.appUrl === 'string' ? feltConfig.appUrl
|
|
49
|
+
: typeof feltConfig.applicationUrl === 'string' ? feltConfig.applicationUrl : undefined;
|
|
50
|
+
return {
|
|
51
|
+
packageManager: packageManagerAt(root, typeof manifest.packageManager === 'string' ? manifest.packageManager : undefined),
|
|
52
|
+
framework: framework?.name || (script ? 'Custom' : 'Unknown'),
|
|
53
|
+
devScript: script,
|
|
54
|
+
configuredPort: scriptPort(script),
|
|
55
|
+
configuredUrl,
|
|
56
|
+
};
|
|
65
57
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
export function applicationUrlCandidate(project, environment = process.env) {
|
|
59
|
+
if (environment.FELTDB_APP_URL)
|
|
60
|
+
return environment.FELTDB_APP_URL;
|
|
61
|
+
if (project.configuredUrl)
|
|
62
|
+
return project.configuredUrl;
|
|
63
|
+
if (project.configuredPort)
|
|
64
|
+
return `http://127.0.0.1:${project.configuredPort}`;
|
|
65
|
+
return undefined;
|
|
70
66
|
}
|
|
71
|
-
export function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const args = [...project.args];
|
|
76
|
-
// This is only the legacy Vite fallback, never an override of an existing script.
|
|
77
|
-
if (project.framework === 'Vite' && !project.devScript?.replace(/\s+/g, ' ').match(/^(?:npm run )?vite/)) {
|
|
78
|
-
args.push('--host', '127.0.0.1');
|
|
79
|
-
if (options.appPort)
|
|
80
|
-
args.push('--port', String(options.appPort));
|
|
67
|
+
export function applicationIsRunning(url, timeoutMs = 750) {
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = new URL(url);
|
|
81
71
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (['Vite', 'Next.js', 'Astro', 'Nuxt', 'SvelteKit', 'Remix'].includes(project.framework)) {
|
|
85
|
-
if (project.packageManager === 'npm')
|
|
86
|
-
args.push('--');
|
|
87
|
-
args.push(flag, String(options.appPort));
|
|
88
|
-
}
|
|
72
|
+
catch {
|
|
73
|
+
return Promise.resolve(false);
|
|
89
74
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
process.stdout.write(text);
|
|
101
|
-
const found = applicationUrls(text);
|
|
102
|
-
if (found.length)
|
|
103
|
-
finish(found[0]);
|
|
104
|
-
};
|
|
105
|
-
child.stdout?.on('data', inspect);
|
|
106
|
-
child.stderr?.on('data', inspect);
|
|
107
|
-
child.once('error', error => { if (!settled) {
|
|
108
|
-
settled = true;
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
reject(error);
|
|
111
|
-
} });
|
|
112
|
-
child.once('exit', code => { if (!settled) {
|
|
113
|
-
settled = true;
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
reject(new Error(`Application exited before reporting its URL (status ${code ?? 'unknown'})`));
|
|
116
|
-
} });
|
|
117
|
-
const timer = setTimeout(() => {
|
|
118
|
-
if (!settled) {
|
|
119
|
-
settled = true;
|
|
120
|
-
reject(new Error('Unable to determine application URL from dev-server output. Pass --app-url or --app-port.'));
|
|
121
|
-
}
|
|
122
|
-
}, options.startupTimeoutMs ?? 30000);
|
|
123
|
-
if (options.appPort)
|
|
124
|
-
finish(`http://127.0.0.1:${options.appPort}`);
|
|
75
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
76
|
+
return Promise.resolve(false);
|
|
77
|
+
const port = Number(parsed.port || (parsed.protocol === 'https:' ? 443 : 80));
|
|
78
|
+
return new Promise(resolve => {
|
|
79
|
+
const socket = net.createConnection({ host: parsed.hostname, port });
|
|
80
|
+
const done = (running) => { socket.destroy(); resolve(running); };
|
|
81
|
+
socket.setTimeout(timeoutMs);
|
|
82
|
+
socket.once('connect', () => done(true));
|
|
83
|
+
socket.once('timeout', () => done(false));
|
|
84
|
+
socket.once('error', () => done(false));
|
|
125
85
|
});
|
|
126
|
-
|
|
86
|
+
}
|
|
87
|
+
/** Discover only a deterministically configured, already-running application. */
|
|
88
|
+
export async function discoverApplicationUrl(root) {
|
|
89
|
+
const project = detectApplication(root);
|
|
90
|
+
const candidate = applicationUrlCandidate(project);
|
|
91
|
+
if (!candidate)
|
|
92
|
+
return { project };
|
|
93
|
+
return { project, url: await applicationIsRunning(candidate) ? candidate : undefined };
|
|
127
94
|
}
|
package/dist/cli/commands.js
CHANGED
|
@@ -7,9 +7,9 @@ import http from 'http';
|
|
|
7
7
|
import net from 'net';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { spawn, spawnSync } from 'child_process';
|
|
10
|
-
import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
|
|
10
|
+
import { createFeltDB, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, parseFlowSpec, planFlowSpecMigration, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
|
|
11
11
|
import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
|
|
12
|
-
import { detectApplication,
|
|
12
|
+
import { detectApplication, discoverApplicationUrl } from './application.js';
|
|
13
13
|
function loadProjectEnvironment(file = path.resolve('.env.local')) {
|
|
14
14
|
if (!fs.existsSync(file))
|
|
15
15
|
return;
|
|
@@ -55,6 +55,8 @@ export async function handleCommand(command, args) {
|
|
|
55
55
|
return handleWorkspace(args);
|
|
56
56
|
case 'dev':
|
|
57
57
|
return handleDev(args);
|
|
58
|
+
case 'investigations':
|
|
59
|
+
return handleInvestigations(args);
|
|
58
60
|
case 'build':
|
|
59
61
|
return handleBuild(args);
|
|
60
62
|
case 'check':
|
|
@@ -76,6 +78,92 @@ export async function handleCommand(command, args) {
|
|
|
76
78
|
return handleHelp();
|
|
77
79
|
}
|
|
78
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Inspect the durable investigation store.
|
|
83
|
+
*
|
|
84
|
+
* The evidence chain is only useful if a developer can get back to it after
|
|
85
|
+
* the browser is closed, so it is queryable from the command line.
|
|
86
|
+
*/
|
|
87
|
+
function handleInvestigations(args) {
|
|
88
|
+
if (args.includes('--help')) {
|
|
89
|
+
console.log('Usage: feltdb investigations [<investigation-id>] [--json]\n');
|
|
90
|
+
console.log('List durable runtime investigations, or show one in full.\n');
|
|
91
|
+
console.log('Options:');
|
|
92
|
+
console.log(' --json Emit machine-readable JSON');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const store = new InvestigationLifecycleManager(process.cwd());
|
|
96
|
+
const json = args.includes('--json');
|
|
97
|
+
const id = args.find(value => !value.startsWith('--'));
|
|
98
|
+
if (id) {
|
|
99
|
+
const investigation = store.loadInvestigation(id);
|
|
100
|
+
if (!investigation)
|
|
101
|
+
throw new Error(`Investigation not found: ${id}`);
|
|
102
|
+
if (json) {
|
|
103
|
+
console.log(JSON.stringify(investigation, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const observation = investigation.originalObservation;
|
|
107
|
+
console.log(`\nInvestigation ${investigation.id}`);
|
|
108
|
+
console.log(` Workspace: ${investigation.workspaceId}`);
|
|
109
|
+
console.log(` Investigation ${investigation.investigationState}`);
|
|
110
|
+
console.log(` Remediation: ${investigation.remediationState}`);
|
|
111
|
+
console.log(` Verification: ${investigation.verificationState}\n`);
|
|
112
|
+
if (observation) {
|
|
113
|
+
console.log('Observation');
|
|
114
|
+
console.log(` ${observation.method} ${observation.url} -> HTTP ${observation.status}`);
|
|
115
|
+
console.log(` ${observation.browser || 'unknown'} / ${observation.runtime || 'unknown'}`
|
|
116
|
+
+ `${observation.durationMs !== undefined ? ` / ${observation.durationMs}ms` : ''}\n`);
|
|
117
|
+
}
|
|
118
|
+
if (investigation.evidence?.length) {
|
|
119
|
+
console.log('Evidence (observed by the runtime)');
|
|
120
|
+
for (const item of investigation.evidence)
|
|
121
|
+
console.log(` [${item.kind}] ${item.statement}`);
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
|
124
|
+
if (investigation.finding) {
|
|
125
|
+
console.log('Finding (FeltDB analysis, not an observation)');
|
|
126
|
+
console.log(` ${investigation.finding.statement}`);
|
|
127
|
+
console.log(` confidence: ${investigation.finding.confidence}\n`);
|
|
128
|
+
}
|
|
129
|
+
if (investigation.remediation?.changeIdentity) {
|
|
130
|
+
console.log('Remediation');
|
|
131
|
+
console.log(` ${investigation.remediation.changeKind}: ${investigation.remediation.changeIdentity}`);
|
|
132
|
+
if (investigation.remediation.changedPaths.length) {
|
|
133
|
+
console.log(` changed: ${investigation.remediation.changedPaths.join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
console.log();
|
|
136
|
+
}
|
|
137
|
+
if (investigation.verificationAttempt) {
|
|
138
|
+
const attempt = investigation.verificationAttempt;
|
|
139
|
+
console.log('Verification');
|
|
140
|
+
console.log(` ${attempt.result}: ${attempt.summary}`);
|
|
141
|
+
console.log(` ${attempt.originalStatus} -> ${attempt.observedStatus ?? 'no matching observation'}\n`);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const investigations = store.listInvestigations();
|
|
146
|
+
if (json) {
|
|
147
|
+
console.log(JSON.stringify(investigations, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (investigations.length === 0) {
|
|
151
|
+
console.log('\nNo investigations recorded.');
|
|
152
|
+
console.log('Run "feltdb dev", exercise the application, and a runtime defect will open one.\n');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
console.log(`\n${investigations.length} investigation${investigations.length === 1 ? '' : 's'}\n`);
|
|
156
|
+
for (const investigation of investigations) {
|
|
157
|
+
const observation = investigation.originalObservation;
|
|
158
|
+
const summary = observation
|
|
159
|
+
? `${observation.method} ${observation.url} -> ${observation.status}`
|
|
160
|
+
: investigation.observationId;
|
|
161
|
+
console.log(` ${investigation.id} ${investigation.verificationState.padEnd(12)} ${summary}`);
|
|
162
|
+
}
|
|
163
|
+
const metrics = store.metrics();
|
|
164
|
+
console.log(`\n ${metrics.observations} observations, ${metrics.evidenceRecords} evidence records, `
|
|
165
|
+
+ `${metrics.serializedBytes} bytes, loaded in ${metrics.loadMilliseconds}ms\n`);
|
|
166
|
+
}
|
|
79
167
|
function flowFile(args) {
|
|
80
168
|
const candidate = args.find(value => !value.startsWith('--')) || 'feltdb.flow';
|
|
81
169
|
return path.resolve(candidate);
|
|
@@ -414,37 +502,29 @@ async function handleWorkspaceConnect(args) {
|
|
|
414
502
|
console.log(' This feature will be implemented in the next phase');
|
|
415
503
|
console.log(' (when @feltdb/client workspace APIs are available)\n');
|
|
416
504
|
}
|
|
417
|
-
function runLocalVite(args, waitForExit) {
|
|
418
|
-
const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
419
|
-
const child = spawn(executable, ['exec', '--', 'vite', ...args], {
|
|
420
|
-
cwd: process.cwd(),
|
|
421
|
-
stdio: 'inherit',
|
|
422
|
-
env: process.env,
|
|
423
|
-
});
|
|
424
|
-
if (!waitForExit)
|
|
425
|
-
return child;
|
|
426
|
-
return new Promise((resolve, reject) => {
|
|
427
|
-
child.once('error', reject);
|
|
428
|
-
child.once('exit', code => code === 0 ? resolve() : reject(new Error(`Vite exited with status ${code ?? 'unknown'}`)));
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
505
|
async function handleDev(args) {
|
|
432
506
|
var _a, _b, _c;
|
|
507
|
+
const projectDir = process.cwd();
|
|
433
508
|
if (args.includes('--help')) {
|
|
434
|
-
console.log('Usage: feltdb dev [--app-url URL
|
|
509
|
+
console.log('Usage: feltdb dev [--app-url URL] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
|
|
510
|
+
console.log('\nOptions:');
|
|
511
|
+
console.log(' --app-url <url> Application URL (e.g., http://127.0.0.1:3000)');
|
|
512
|
+
console.log(' --studio-port <port> FeltDB Studio port (default: 7701)');
|
|
513
|
+
console.log(' --authority-port <port> Authority port (default: 7700)');
|
|
514
|
+
console.log(' --discovery-port <port> Pairing discovery port (default: 7799)');
|
|
515
|
+
console.log(' --no-open Do not open Studio in browser');
|
|
435
516
|
return;
|
|
436
517
|
}
|
|
437
518
|
loadProjectEnvironment();
|
|
438
519
|
console.log('🚀 Starting FeltDB development server...\n');
|
|
439
|
-
const projectDir = process.cwd();
|
|
440
|
-
const configPath = path.join(projectDir, 'feltdb.config.json');
|
|
441
520
|
// .feltdb/ is reserved local runtime state. Protect it before discovery,
|
|
442
521
|
// initialization, the authority, or pairing can persist anything there.
|
|
522
|
+
// This runs after `--help` returns, so asking for help never writes anything.
|
|
443
523
|
ensureWorkspaceGitIgnored(projectDir);
|
|
524
|
+
const configPath = path.join(projectDir, 'feltdb.config.json');
|
|
444
525
|
// Initialize workspace if needed
|
|
445
526
|
let workspace = discoverWorkspace(projectDir);
|
|
446
527
|
if (!workspace) {
|
|
447
|
-
// Get project name from package.json
|
|
448
528
|
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
449
529
|
let projectId = path.basename(projectDir);
|
|
450
530
|
if (fs.existsSync(packageJsonPath)) {
|
|
@@ -460,6 +540,8 @@ async function handleDev(args) {
|
|
|
460
540
|
console.log(`✨ Initialized Development Workspace`);
|
|
461
541
|
console.log(` Workspace ID: ${workspace.workspaceId}\n`);
|
|
462
542
|
}
|
|
543
|
+
// Investigation lifecycle manager will be initialized when needed
|
|
544
|
+
// const investigationManager = new InvestigationLifecycleManager(projectDir);
|
|
463
545
|
// Create feltdb.config.json if it doesn't exist
|
|
464
546
|
if (!fs.existsSync(configPath)) {
|
|
465
547
|
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
@@ -478,11 +560,10 @@ async function handleDev(args) {
|
|
|
478
560
|
runtime: 'browser',
|
|
479
561
|
storage: 'indexeddb',
|
|
480
562
|
distributed: true,
|
|
481
|
-
|
|
482
|
-
capabilities: { search: true },
|
|
563
|
+
applicationUrl: null,
|
|
483
564
|
};
|
|
484
565
|
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n');
|
|
485
|
-
console.log(`✨ Created feltdb.config.json
|
|
566
|
+
console.log(`✨ Created feltdb.config.json\n`);
|
|
486
567
|
}
|
|
487
568
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
488
569
|
if (config.runtime === 'managed') {
|
|
@@ -495,10 +576,59 @@ async function handleDev(args) {
|
|
|
495
576
|
const runtimeNamespace = config.runtime === 'managed'
|
|
496
577
|
? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
|
|
497
578
|
: config.namespace;
|
|
579
|
+
// Application URL precedence:
|
|
580
|
+
// 1. --app-url (highest priority)
|
|
581
|
+
// 2. feltdb.config.json applicationUrl
|
|
582
|
+
// 3. Deterministically discovered running application
|
|
583
|
+
// 4. No application (lowest priority - allowed)
|
|
584
|
+
const hasAppUrlArg = args.includes('--app-url');
|
|
585
|
+
const appUrlArgument = hasAppUrlArg ? args[args.indexOf('--app-url') + 1] : undefined;
|
|
586
|
+
if (hasAppUrlArg && !appUrlArgument)
|
|
587
|
+
throw new Error('--app-url requires a URL');
|
|
588
|
+
if (appUrlArgument) {
|
|
589
|
+
try {
|
|
590
|
+
const parsed = new URL(appUrlArgument);
|
|
591
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
592
|
+
throw new Error('unsupported protocol');
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
throw new Error(`Invalid --app-url: ${appUrlArgument}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
let appUrl;
|
|
599
|
+
let detectedApplication;
|
|
600
|
+
// Priority 1: --app-url takes highest priority
|
|
601
|
+
if (appUrlArgument) {
|
|
602
|
+
appUrl = appUrlArgument;
|
|
603
|
+
try {
|
|
604
|
+
detectedApplication = detectApplication(projectDir);
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
detectedApplication = { packageManager: 'npm', framework: 'External' };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
// Priority 2: Check feltdb.config.json applicationUrl
|
|
612
|
+
if (config.applicationUrl) {
|
|
613
|
+
appUrl = config.applicationUrl;
|
|
614
|
+
try {
|
|
615
|
+
detectedApplication = detectApplication(projectDir);
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
detectedApplication = { packageManager: 'npm', framework: 'External' };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
// Priority 3: Try to discover an existing running application
|
|
623
|
+
const discovered = await discoverApplicationUrl(projectDir);
|
|
624
|
+
detectedApplication = discovered.project;
|
|
625
|
+
appUrl = discovered.url;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
498
628
|
const requestedStudioPort = Number(args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '7701' : '7701');
|
|
499
629
|
const studioPort = String(await availablePort(requestedStudioPort));
|
|
500
630
|
if (studioPort !== String(requestedStudioPort))
|
|
501
|
-
console.log(
|
|
631
|
+
console.log(`⚠ Port ${requestedStudioPort} is busy; using ${studioPort} for Studio.\n`);
|
|
502
632
|
process.env.STUDIO_PORT = studioPort;
|
|
503
633
|
let selfHostedStarted = false;
|
|
504
634
|
let localAuthority = null;
|
|
@@ -549,91 +679,69 @@ async function handleDev(args) {
|
|
|
549
679
|
pairingDiscoveryServer = await startPairingDiscoveryServer(token, discoveryPort);
|
|
550
680
|
pairingToken = token.token;
|
|
551
681
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
const hasAppPort = args.includes('--app-port') || args.includes('--port');
|
|
557
|
-
const appPortValue = args.includes('--app-port')
|
|
558
|
-
? args[args.indexOf('--app-port') + 1]
|
|
559
|
-
: args.includes('--port') ? args[args.indexOf('--port') + 1] : undefined;
|
|
560
|
-
if (!hasAppUrl && hasAppPort && !appPortValue)
|
|
561
|
-
throw new Error('--app-port requires a port');
|
|
562
|
-
const appPort = appPortValue ? Number(appPortValue) : undefined;
|
|
563
|
-
if (appUrlArgument) {
|
|
564
|
-
try {
|
|
565
|
-
const parsed = new URL(appUrlArgument);
|
|
566
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
567
|
-
throw new Error('unsupported protocol');
|
|
568
|
-
}
|
|
569
|
-
catch {
|
|
570
|
-
throw new Error(`Invalid --app-url: ${appUrlArgument}`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (!hasAppUrl && appPortValue && (!Number.isInteger(appPort) || appPort < 1 || appPort > 65535))
|
|
574
|
-
throw new Error(`Invalid --app-port: ${appPortValue}`);
|
|
575
|
-
const application = appUrlArgument ? undefined : detectApplication(projectDir);
|
|
576
|
-
const runningApplication = application ? startApplication(application, projectDir, { appPort }) : undefined;
|
|
577
|
-
let appUrl;
|
|
578
|
-
try {
|
|
579
|
-
appUrl = appUrlArgument || await runningApplication.url;
|
|
682
|
+
// Output startup information
|
|
683
|
+
console.log('✨ Development Workspace');
|
|
684
|
+
if (workspace) {
|
|
685
|
+
console.log(` Workspace ID: ${workspace.workspaceId}`);
|
|
580
686
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
throw error;
|
|
687
|
+
console.log();
|
|
688
|
+
console.log('🔗 Development Workspace');
|
|
689
|
+
if (pairingToken) {
|
|
690
|
+
console.log(` Pairing Code: ${pairingToken}`);
|
|
691
|
+
console.log(` Pairing discovery: http://127.0.0.1:${(pairingDiscoveryServer?.address()).port}`);
|
|
587
692
|
}
|
|
693
|
+
console.log();
|
|
588
694
|
console.log('FeltDB Dev Server');
|
|
589
|
-
console.log(`
|
|
590
|
-
console.log(`
|
|
591
|
-
console.log(`
|
|
592
|
-
console.log('Application');
|
|
593
|
-
console.log(` Framework: ${application?.framework || 'External'}`);
|
|
594
|
-
console.log(` Dev command: ${application ? [application.command.replace(/\.cmd$/, ''), ...application.args].join(' ') : '(already running)'}`);
|
|
595
|
-
console.log(` URL: ${appUrl}`);
|
|
596
|
-
console.log(` Namespace: ${config.namespace}`);
|
|
597
|
-
console.log(` Runtime: ${config.runtime}`);
|
|
598
|
-
console.log(` Storage: ${config.storage}`);
|
|
695
|
+
console.log(` Namespace: ${config.namespace}`);
|
|
696
|
+
console.log(` Runtime: ${config.runtime}`);
|
|
697
|
+
console.log(` Storage: ${config.storage}`);
|
|
599
698
|
console.log(` Distributed: ${config.distributed}`);
|
|
600
|
-
if (workspace) {
|
|
601
|
-
console.log(` Workspace: ${workspace.workspaceId}\n`);
|
|
602
|
-
console.log(`🔗 Development Workspace`);
|
|
603
|
-
if (pairingToken) {
|
|
604
|
-
console.log(` Pairing Code: ${pairingToken}`);
|
|
605
|
-
console.log(` Pairing discovery: http://127.0.0.1:${(pairingDiscoveryServer?.address()).port}`);
|
|
606
|
-
console.log(` Clients can use this code to auto-discover the workspace`);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
699
|
console.log();
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
700
|
+
if (appUrl) {
|
|
701
|
+
console.log(`Application: ${appUrl}`);
|
|
702
|
+
console.log(`Authority: ${process.env.VITE_FELTDB_URL}`);
|
|
703
|
+
console.log();
|
|
704
|
+
console.log(`Using existing application at ${appUrl}`);
|
|
705
|
+
console.log(`app=${encodeURIComponent(appUrl)}\n`);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
console.log('❌ No application URL is configured.\n');
|
|
709
|
+
console.log('Start your application, then run:\n');
|
|
710
|
+
console.log(' feltdb dev --app-url http://127.0.0.1:<port>\n');
|
|
711
|
+
console.log('Or set applicationUrl in feltdb.config.json\n');
|
|
712
|
+
console.log('FeltDB workspace is ready at:');
|
|
713
|
+
console.log('Authority: ' + process.env.VITE_FELTDB_URL);
|
|
714
|
+
console.log('Studio: http://127.0.0.1:' + studioPort);
|
|
715
|
+
console.log();
|
|
716
|
+
}
|
|
717
|
+
const stopAll = () => { pairingDiscoveryServer?.close(); void localAuthority?.close(); stopSelfHosted(); };
|
|
625
718
|
process.once('exit', stopAll);
|
|
626
719
|
process.once('SIGINT', () => { stopAll(); process.exit(130); });
|
|
627
720
|
process.once('SIGTERM', () => { stopAll(); process.exit(143); });
|
|
721
|
+
const open = !args.includes('--no-open');
|
|
628
722
|
await handleStudio([
|
|
629
723
|
'--port', studioPort,
|
|
630
724
|
'--namespace', runtimeNamespace || 'default',
|
|
631
725
|
'--runtime', config.runtime || 'browser',
|
|
632
|
-
'--app-url', appUrl,
|
|
726
|
+
...(appUrl ? ['--app-url', appUrl] : []),
|
|
633
727
|
...((config.runtime === 'self-hosted' || config.runtime === 'managed') && process.env.VITE_FELTDB_URL ? ['--connect', process.env.VITE_FELTDB_URL] : []),
|
|
634
728
|
...(open ? [] : ['--no-open']),
|
|
635
729
|
]);
|
|
636
730
|
}
|
|
731
|
+
function runLocalVite(args, waitForExit) {
|
|
732
|
+
const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
733
|
+
const child = spawn(executable, ['exec', '--', 'vite', ...args], {
|
|
734
|
+
cwd: process.cwd(),
|
|
735
|
+
stdio: 'inherit',
|
|
736
|
+
env: process.env,
|
|
737
|
+
});
|
|
738
|
+
if (!waitForExit)
|
|
739
|
+
return child;
|
|
740
|
+
return new Promise((resolve, reject) => {
|
|
741
|
+
child.once('error', reject);
|
|
742
|
+
child.once('exit', code => code === 0 ? resolve() : reject(new Error(`Vite exited with status ${code ?? 'unknown'}`)));
|
|
743
|
+
});
|
|
744
|
+
}
|
|
637
745
|
async function handleBuild(args) {
|
|
638
746
|
console.log('🔨 Building FeltDB application...\n');
|
|
639
747
|
const configPath = path.join(process.cwd(), 'feltdb.config.json');
|
|
@@ -904,11 +1012,13 @@ async function handleStudio(args) {
|
|
|
904
1012
|
const require = createRequire(import.meta.url);
|
|
905
1013
|
const packageFile = require.resolve('@feltdb/core/package.json');
|
|
906
1014
|
const root = path.join(path.dirname(packageFile), 'dist', 'studio-app');
|
|
1015
|
+
if (!fs.existsSync(path.join(root, 'index.html'))) {
|
|
1016
|
+
console.log('⚠️ Studio application artifacts not found.');
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
907
1019
|
const projectFlow = ['feltdb.flow', ...fs.readdirSync(process.cwd()).filter(value => value.endsWith('.flow'))]
|
|
908
1020
|
.map(value => path.resolve(process.cwd(), value))
|
|
909
1021
|
.find((value, index, values) => values.indexOf(value) === index && fs.existsSync(value));
|
|
910
|
-
if (!fs.existsSync(path.join(root, 'index.html')))
|
|
911
|
-
throw new Error('Studio application artifact is missing; reinstall @feltdb/core');
|
|
912
1022
|
const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.svg': 'image/svg+xml', '.json': 'application/json' };
|
|
913
1023
|
const server = http.createServer((request, response) => {
|
|
914
1024
|
const pathname = decodeURIComponent(new URL(request.url || '/', 'http://localhost').pathname);
|
|
@@ -971,6 +1081,7 @@ Commands:
|
|
|
971
1081
|
connect Connect to remote FeltDB
|
|
972
1082
|
status Show connection status
|
|
973
1083
|
dev Start development server
|
|
1084
|
+
investigations List or show durable runtime investigations
|
|
974
1085
|
build Build FeltDB application
|
|
975
1086
|
check Validate configuration
|
|
976
1087
|
inspect Inspect runtime state
|
package/dist/cli/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as path from 'path';
|
|
|
23
23
|
import * as readline from 'readline';
|
|
24
24
|
import { getClient } from './api-client.js';
|
|
25
25
|
import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
|
|
26
|
-
const VERSION = '0.6.
|
|
26
|
+
const VERSION = '0.6.10';
|
|
27
27
|
function prompt(question) {
|
|
28
28
|
const rl = readline.createInterface({
|
|
29
29
|
input: process.stdin,
|