@feltdb/core 0.6.9 → 0.6.11
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-lifecycle.js +112 -0
- package/dist/cli/application.js +10 -0
- package/dist/cli/commands.js +407 -119
- package/dist/cli/development-session.js +54 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/workspace-integration.js +146 -20
- package/dist/collection.d.ts +110 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +228 -3
- package/dist/create/cli.js +4 -2
- package/dist/create/create.js +42 -6
- package/dist/create/frameworks.js +18 -0
- 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/create/workspace-initialization.js +4 -5
- package/dist/db.d.ts +63 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +145 -4
- package/dist/development-runtime-bridge.d.ts +60 -0
- package/dist/development-runtime-bridge.d.ts.map +1 -0
- package/dist/development-runtime-bridge.js +171 -0
- 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 +80 -0
- package/dist/index-core.d.ts.map +1 -0
- package/dist/index-core.js +74 -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.d.ts +2 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/StateExplorer.d.ts +2 -1
- package/dist/studio/components/StateExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-9kDSWiGL.js → components-C5p2TfIU.js} +25 -14
- package/dist/studio/index.js +234 -160
- 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-DospFFYE.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/development-node.d.ts +16 -0
- package/dist/workspace/development-node.d.ts.map +1 -1
- package/dist/workspace/development-node.js +85 -1
- 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 +12 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +7 -1
- 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 +88 -0
- package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
- package/dist/workspace/investigation-lifecycle-manager.js +259 -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 +238 -0
- package/dist/workspace/runtime-investigation.d.ts +5 -1
- package/dist/workspace/runtime-investigation.d.ts.map +1 -1
- package/dist/workspace/runtime-investigation.js +50 -21
- package/dist/workspace/runtime-observation.d.ts +82 -0
- package/dist/workspace/runtime-observation.d.ts.map +1 -0
- package/dist/workspace/runtime-observation.js +170 -0
- package/dist/workspace/runtime-observer.d.ts +57 -0
- package/dist/workspace/runtime-observer.d.ts.map +1 -0
- package/dist/workspace/runtime-observer.js +272 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -5
- package/dist/workspace/workspace-identity.d.ts +1 -1
- package/dist/workspace/workspace-identity.d.ts.map +1 -1
- package/dist/workspace/workspace-identity.js +4 -4
- package/dist/workspace/workspace-types.d.ts +208 -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/commands.js
CHANGED
|
@@ -7,9 +7,12 @@ 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 {
|
|
10
|
+
import { randomBytes } from 'crypto';
|
|
11
|
+
import { createFeltDB, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, parseFlowSpec, planFlowSpecMigration, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
|
|
11
12
|
import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
|
|
12
|
-
import { detectApplication, discoverApplicationUrl } from './application.js';
|
|
13
|
+
import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
|
|
14
|
+
import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
|
|
15
|
+
import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
|
|
13
16
|
function loadProjectEnvironment(file = path.resolve('.env.local')) {
|
|
14
17
|
if (!fs.existsSync(file))
|
|
15
18
|
return;
|
|
@@ -55,6 +58,8 @@ export async function handleCommand(command, args) {
|
|
|
55
58
|
return handleWorkspace(args);
|
|
56
59
|
case 'dev':
|
|
57
60
|
return handleDev(args);
|
|
61
|
+
case 'investigations':
|
|
62
|
+
return handleInvestigations(args);
|
|
58
63
|
case 'build':
|
|
59
64
|
return handleBuild(args);
|
|
60
65
|
case 'check':
|
|
@@ -76,6 +81,92 @@ export async function handleCommand(command, args) {
|
|
|
76
81
|
return handleHelp();
|
|
77
82
|
}
|
|
78
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Inspect the durable investigation store.
|
|
86
|
+
*
|
|
87
|
+
* The evidence chain is only useful if a developer can get back to it after
|
|
88
|
+
* the browser is closed, so it is queryable from the command line.
|
|
89
|
+
*/
|
|
90
|
+
function handleInvestigations(args) {
|
|
91
|
+
if (args.includes('--help')) {
|
|
92
|
+
console.log('Usage: feltdb investigations [<investigation-id>] [--json]\n');
|
|
93
|
+
console.log('List durable runtime investigations, or show one in full.\n');
|
|
94
|
+
console.log('Options:');
|
|
95
|
+
console.log(' --json Emit machine-readable JSON');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const store = new InvestigationLifecycleManager(process.cwd());
|
|
99
|
+
const json = args.includes('--json');
|
|
100
|
+
const id = args.find(value => !value.startsWith('--'));
|
|
101
|
+
if (id) {
|
|
102
|
+
const investigation = store.loadInvestigation(id);
|
|
103
|
+
if (!investigation)
|
|
104
|
+
throw new Error(`Investigation not found: ${id}`);
|
|
105
|
+
if (json) {
|
|
106
|
+
console.log(JSON.stringify(investigation, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const observation = investigation.originalObservation;
|
|
110
|
+
console.log(`\nInvestigation ${investigation.id}`);
|
|
111
|
+
console.log(` Workspace: ${investigation.workspaceId}`);
|
|
112
|
+
console.log(` Investigation ${investigation.investigationState}`);
|
|
113
|
+
console.log(` Remediation: ${investigation.remediationState}`);
|
|
114
|
+
console.log(` Verification: ${investigation.verificationState}\n`);
|
|
115
|
+
if (observation) {
|
|
116
|
+
console.log('Observation');
|
|
117
|
+
console.log(` ${observation.method} ${observation.url} -> HTTP ${observation.status}`);
|
|
118
|
+
console.log(` ${observation.browser || 'unknown'} / ${observation.runtime || 'unknown'}`
|
|
119
|
+
+ `${observation.durationMs !== undefined ? ` / ${observation.durationMs}ms` : ''}\n`);
|
|
120
|
+
}
|
|
121
|
+
if (investigation.evidence?.length) {
|
|
122
|
+
console.log('Evidence (observed by the runtime)');
|
|
123
|
+
for (const item of investigation.evidence)
|
|
124
|
+
console.log(` [${item.kind}] ${item.statement}`);
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
if (investigation.finding) {
|
|
128
|
+
console.log('Finding (FeltDB analysis, not an observation)');
|
|
129
|
+
console.log(` ${investigation.finding.statement}`);
|
|
130
|
+
console.log(` confidence: ${investigation.finding.confidence}\n`);
|
|
131
|
+
}
|
|
132
|
+
if (investigation.remediation?.changeIdentity) {
|
|
133
|
+
console.log('Remediation');
|
|
134
|
+
console.log(` ${investigation.remediation.changeKind}: ${investigation.remediation.changeIdentity}`);
|
|
135
|
+
if (investigation.remediation.changedPaths.length) {
|
|
136
|
+
console.log(` changed: ${investigation.remediation.changedPaths.join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
console.log();
|
|
139
|
+
}
|
|
140
|
+
if (investigation.verificationAttempt) {
|
|
141
|
+
const attempt = investigation.verificationAttempt;
|
|
142
|
+
console.log('Verification');
|
|
143
|
+
console.log(` ${attempt.result}: ${attempt.summary}`);
|
|
144
|
+
console.log(` ${attempt.originalStatus} -> ${attempt.observedStatus ?? 'no matching observation'}\n`);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const investigations = store.listInvestigations();
|
|
149
|
+
if (json) {
|
|
150
|
+
console.log(JSON.stringify(investigations, null, 2));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (investigations.length === 0) {
|
|
154
|
+
console.log('\nNo investigations recorded.');
|
|
155
|
+
console.log('Run "feltdb dev", exercise the application, and a runtime defect will open one.\n');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
console.log(`\n${investigations.length} investigation${investigations.length === 1 ? '' : 's'}\n`);
|
|
159
|
+
for (const investigation of investigations) {
|
|
160
|
+
const observation = investigation.originalObservation;
|
|
161
|
+
const summary = observation
|
|
162
|
+
? `${observation.method} ${observation.url} -> ${observation.status}`
|
|
163
|
+
: investigation.observationId;
|
|
164
|
+
console.log(` ${investigation.id} ${investigation.verificationState.padEnd(12)} ${summary}`);
|
|
165
|
+
}
|
|
166
|
+
const metrics = store.metrics();
|
|
167
|
+
console.log(`\n ${metrics.observations} observations, ${metrics.evidenceRecords} evidence records, `
|
|
168
|
+
+ `${metrics.serializedBytes} bytes, loaded in ${metrics.loadMilliseconds}ms\n`);
|
|
169
|
+
}
|
|
79
170
|
function flowFile(args) {
|
|
80
171
|
const candidate = args.find(value => !value.startsWith('--')) || 'feltdb.flow';
|
|
81
172
|
return path.resolve(candidate);
|
|
@@ -414,37 +505,30 @@ async function handleWorkspaceConnect(args) {
|
|
|
414
505
|
console.log(' This feature will be implemented in the next phase');
|
|
415
506
|
console.log(' (when @feltdb/client workspace APIs are available)\n');
|
|
416
507
|
}
|
|
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
508
|
async function handleDev(args) {
|
|
432
509
|
var _a, _b, _c;
|
|
510
|
+
const projectDir = process.cwd();
|
|
433
511
|
if (args.includes('--help')) {
|
|
434
|
-
console.log('Usage: feltdb dev [--app-url URL
|
|
512
|
+
console.log('Usage: feltdb dev [--app-url URL] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
|
|
513
|
+
console.log('\nOptions:');
|
|
514
|
+
console.log(' --app-url <url> Application URL (e.g., http://127.0.0.1:3000)');
|
|
515
|
+
console.log(' --studio-port <port> FeltDB Studio port (default: 7701)');
|
|
516
|
+
console.log(' --authority-port <port> Authority port (default: 7700)');
|
|
517
|
+
console.log(' --discovery-port <port> Pairing discovery port (default: 7799)');
|
|
518
|
+
console.log(' --application-timeout <ms> Managed application readiness timeout (default: 15000)');
|
|
519
|
+
console.log(' --no-open Do not open Studio in browser');
|
|
435
520
|
return;
|
|
436
521
|
}
|
|
437
522
|
loadProjectEnvironment();
|
|
438
523
|
console.log('🚀 Starting FeltDB development server...\n');
|
|
439
|
-
const projectDir = process.cwd();
|
|
440
|
-
const configPath = path.join(projectDir, 'feltdb.config.json');
|
|
441
524
|
// .feltdb/ is reserved local runtime state. Protect it before discovery,
|
|
442
525
|
// initialization, the authority, or pairing can persist anything there.
|
|
526
|
+
// This runs after `--help` returns, so asking for help never writes anything.
|
|
443
527
|
ensureWorkspaceGitIgnored(projectDir);
|
|
528
|
+
const configPath = path.join(projectDir, 'feltdb.config.json');
|
|
444
529
|
// Initialize workspace if needed
|
|
445
530
|
let workspace = discoverWorkspace(projectDir);
|
|
446
531
|
if (!workspace) {
|
|
447
|
-
// Get project name from package.json
|
|
448
532
|
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
449
533
|
let projectId = path.basename(projectDir);
|
|
450
534
|
if (fs.existsSync(packageJsonPath)) {
|
|
@@ -460,31 +544,20 @@ async function handleDev(args) {
|
|
|
460
544
|
console.log(`✨ Initialized Development Workspace`);
|
|
461
545
|
console.log(` Workspace ID: ${workspace.workspaceId}\n`);
|
|
462
546
|
}
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
namespace,
|
|
478
|
-
runtime: 'browser',
|
|
479
|
-
storage: 'indexeddb',
|
|
480
|
-
distributed: true,
|
|
481
|
-
agents: { enabled: false },
|
|
482
|
-
capabilities: { search: true },
|
|
483
|
-
};
|
|
484
|
-
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n');
|
|
485
|
-
console.log(`✨ Created feltdb.config.json with default configuration\n`);
|
|
486
|
-
}
|
|
487
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
547
|
+
// Investigation lifecycle manager will be initialized when needed
|
|
548
|
+
// const investigationManager = new InvestigationLifecycleManager(projectDir);
|
|
549
|
+
// Existing applications may attach without adopting generated FeltDB source.
|
|
550
|
+
// Missing developer-owned config is resolved in memory and never written.
|
|
551
|
+
const configDefaults = {
|
|
552
|
+
namespace: workspace.projectId,
|
|
553
|
+
runtime: 'browser',
|
|
554
|
+
storage: 'indexeddb',
|
|
555
|
+
distributed: true,
|
|
556
|
+
applicationUrl: null,
|
|
557
|
+
};
|
|
558
|
+
const config = fs.existsSync(configPath)
|
|
559
|
+
? { ...configDefaults, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) }
|
|
560
|
+
: configDefaults;
|
|
488
561
|
if (config.runtime === 'managed') {
|
|
489
562
|
(_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = process.env.VITE_FELTDB_MANAGED_URL);
|
|
490
563
|
(_b = process.env).VITE_FELTDB_API_KEY || (_b.VITE_FELTDB_API_KEY = process.env.VITE_FELTDB_MANAGED_API_KEY);
|
|
@@ -495,17 +568,16 @@ async function handleDev(args) {
|
|
|
495
568
|
const runtimeNamespace = config.runtime === 'managed'
|
|
496
569
|
? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
|
|
497
570
|
: config.namespace;
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
571
|
+
const applicationLifecycle = resolveApplicationLifecycle(config);
|
|
572
|
+
// Application URL precedence:
|
|
573
|
+
// 1. --app-url (highest priority)
|
|
574
|
+
// 2. feltdb.config.json applicationUrl
|
|
575
|
+
// 3. Deterministically discovered running application
|
|
576
|
+
// 4. No application (lowest priority - allowed)
|
|
577
|
+
const hasAppUrlArg = args.includes('--app-url');
|
|
578
|
+
const appUrlArgument = hasAppUrlArg ? args[args.indexOf('--app-url') + 1] : undefined;
|
|
579
|
+
if (hasAppUrlArg && !appUrlArgument)
|
|
501
580
|
throw new Error('--app-url requires a URL');
|
|
502
|
-
const hasAppPort = args.includes('--app-port') || args.includes('--port');
|
|
503
|
-
const appPortValue = args.includes('--app-port')
|
|
504
|
-
? args[args.indexOf('--app-port') + 1]
|
|
505
|
-
: args.includes('--port') ? args[args.indexOf('--port') + 1] : undefined;
|
|
506
|
-
if (!hasAppUrl && hasAppPort && !appPortValue)
|
|
507
|
-
throw new Error('--app-port requires a port');
|
|
508
|
-
const appPort = appPortValue ? Number(appPortValue) : undefined;
|
|
509
581
|
if (appUrlArgument) {
|
|
510
582
|
try {
|
|
511
583
|
const parsed = new URL(appUrlArgument);
|
|
@@ -516,31 +588,54 @@ async function handleDev(args) {
|
|
|
516
588
|
throw new Error(`Invalid --app-url: ${appUrlArgument}`);
|
|
517
589
|
}
|
|
518
590
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
591
|
+
let appUrl;
|
|
592
|
+
let detectedApplication;
|
|
593
|
+
// Priority 1: --app-url takes highest priority
|
|
594
|
+
if (appUrlArgument) {
|
|
595
|
+
appUrl = appUrlArgument;
|
|
526
596
|
try {
|
|
527
|
-
|
|
597
|
+
detectedApplication = detectApplication(projectDir);
|
|
528
598
|
}
|
|
529
599
|
catch {
|
|
530
|
-
|
|
600
|
+
detectedApplication = { packageManager: 'npm', framework: 'External', feltDBEnabled: false, feltDBSignals: [] };
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// Priority 2: Check feltdb.config.json applicationUrl
|
|
605
|
+
if (config.applicationUrl) {
|
|
606
|
+
appUrl = config.applicationUrl;
|
|
607
|
+
try {
|
|
608
|
+
detectedApplication = detectApplication(projectDir);
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
detectedApplication = { packageManager: 'npm', framework: 'External', feltDBEnabled: false, feltDBSignals: [] };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
// Priority 3: Try to discover an existing running application
|
|
616
|
+
if (applicationLifecycle === 'managed') {
|
|
617
|
+
detectedApplication = detectApplication(projectDir);
|
|
618
|
+
appUrl = applicationUrlCandidate(detectedApplication);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const discovered = await discoverApplicationUrl(projectDir);
|
|
622
|
+
detectedApplication = discovered.project;
|
|
623
|
+
appUrl = discovered.url;
|
|
624
|
+
}
|
|
531
625
|
}
|
|
532
626
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
throw new Error('No application URL was detected.\nStart your application and run:\n feltdb dev --app-url http://127.0.0.1:<port>\nOr configure applicationUrl in feltdb.config.json.');
|
|
627
|
+
if (detectedApplication?.feltDBEnabled) {
|
|
628
|
+
console.log(`Attaching existing FeltDB application (${detectedApplication.feltDBSignals.join(', ')})\n`);
|
|
536
629
|
}
|
|
537
630
|
const requestedStudioPort = Number(args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '7701' : '7701');
|
|
538
631
|
const studioPort = String(await availablePort(requestedStudioPort));
|
|
539
632
|
if (studioPort !== String(requestedStudioPort))
|
|
540
|
-
console.log(
|
|
633
|
+
console.log(`⚠ Port ${requestedStudioPort} is busy; using ${studioPort} for Studio.\n`);
|
|
541
634
|
process.env.STUDIO_PORT = studioPort;
|
|
542
635
|
let selfHostedStarted = false;
|
|
543
636
|
let localAuthority = null;
|
|
637
|
+
let managedApplication = null;
|
|
638
|
+
let developmentSession = null;
|
|
544
639
|
const stopSelfHosted = () => {
|
|
545
640
|
if (selfHostedStarted) {
|
|
546
641
|
spawnSync('docker', ['compose', 'stop', 'feltdb'], { cwd: process.cwd(), stdio: 'ignore' });
|
|
@@ -570,62 +665,184 @@ async function handleDev(args) {
|
|
|
570
665
|
});
|
|
571
666
|
process.env.VITE_FELTDB_URL = localAuthority.endpoint;
|
|
572
667
|
}
|
|
668
|
+
const authorityUrl = process.env.VITE_FELTDB_URL;
|
|
669
|
+
if (!authorityUrl)
|
|
670
|
+
throw new Error('Development session authority URL was not resolved');
|
|
671
|
+
const discoveryPort = Number(args.includes('--discovery-port') ? args[args.indexOf('--discovery-port') + 1] || '7799' : '7799');
|
|
672
|
+
developmentSession = createDevelopmentSession({
|
|
673
|
+
sessionId: `dev_${randomBytes(16).toString('hex')}`,
|
|
674
|
+
workspaceId: workspace.workspaceId,
|
|
675
|
+
projectId: workspace.projectId,
|
|
676
|
+
namespace: runtimeNamespace || config.namespace,
|
|
677
|
+
runtime: config.runtime,
|
|
678
|
+
storage: config.storage,
|
|
679
|
+
lifecycle: applicationLifecycle,
|
|
680
|
+
authorityUrl,
|
|
681
|
+
studioUrl: `http://127.0.0.1:${studioPort}`,
|
|
682
|
+
pairingUrl: `http://127.0.0.1:${discoveryPort}`,
|
|
683
|
+
applicationUrl: appUrl,
|
|
684
|
+
});
|
|
685
|
+
transitionDevelopmentSession(developmentSession, 'STARTING');
|
|
573
686
|
// Generate pairing token for the workspace
|
|
574
687
|
// This enables browsers and IDEs to discover and connect to the workspace
|
|
575
688
|
let pairingToken = null;
|
|
576
689
|
let pairingDiscoveryServer = null;
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
690
|
+
try {
|
|
691
|
+
if (workspace) {
|
|
692
|
+
const token = generatePairingToken();
|
|
693
|
+
token.workspaceId = developmentSession.workspaceId;
|
|
694
|
+
token.endpoint = developmentSession.authorityUrl;
|
|
695
|
+
token.authorityEndpoint = token.endpoint;
|
|
696
|
+
token.namespace = developmentSession.namespace;
|
|
697
|
+
if (!token.endpoint) {
|
|
698
|
+
throw new Error('Development Workspace pairing requires a FeltDB authority endpoint');
|
|
699
|
+
}
|
|
700
|
+
persistPairingToken(process.cwd(), token);
|
|
701
|
+
pairingDiscoveryServer = await startPairingDiscoveryServer(token, discoveryPort, '127.0.0.1', {
|
|
702
|
+
sessionId: developmentSession.sessionId,
|
|
703
|
+
workspaceId: developmentSession.workspaceId,
|
|
704
|
+
namespace: developmentSession.namespace,
|
|
705
|
+
runtime: developmentSession.runtime,
|
|
706
|
+
authorityUrl: developmentSession.authorityUrl,
|
|
707
|
+
applicationUrl: developmentSession.applicationUrl,
|
|
708
|
+
});
|
|
709
|
+
const pairingAddress = pairingDiscoveryServer.address();
|
|
710
|
+
developmentSession.pairingUrl = `http://127.0.0.1:${pairingAddress.port}`;
|
|
711
|
+
pairingToken = token.token;
|
|
585
712
|
}
|
|
586
|
-
persistPairingToken(process.cwd(), token);
|
|
587
|
-
const discoveryPort = Number(args.includes('--discovery-port') ? args[args.indexOf('--discovery-port') + 1] || '7799' : '7799');
|
|
588
|
-
pairingDiscoveryServer = await startPairingDiscoveryServer(token, discoveryPort);
|
|
589
|
-
pairingToken = token.token;
|
|
590
713
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
console.log(` Storage: ${config.storage}`);
|
|
602
|
-
console.log(` Distributed: ${config.distributed}`);
|
|
603
|
-
if (workspace) {
|
|
604
|
-
console.log(` Workspace: ${workspace.workspaceId}\n`);
|
|
605
|
-
console.log(`🔗 Development Workspace`);
|
|
606
|
-
if (pairingToken) {
|
|
607
|
-
console.log(` Pairing Code: ${pairingToken}`);
|
|
608
|
-
console.log(` Pairing discovery: http://127.0.0.1:${(pairingDiscoveryServer?.address()).port}`);
|
|
609
|
-
console.log(` Clients can use this code to auto-discover the workspace`);
|
|
714
|
+
catch (error) {
|
|
715
|
+
transitionDevelopmentSession(developmentSession, 'FAILED');
|
|
716
|
+
await localAuthority?.close();
|
|
717
|
+
stopSelfHosted();
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
let stopping = null;
|
|
721
|
+
const stopAll = () => stopping ?? (stopping = (async () => {
|
|
722
|
+
if (developmentSession && developmentSession.state !== 'STOPPING' && developmentSession.state !== 'STOPPED') {
|
|
723
|
+
transitionDevelopmentSession(developmentSession, 'STOPPING');
|
|
610
724
|
}
|
|
725
|
+
await managedApplication?.stop();
|
|
726
|
+
if (pairingDiscoveryServer?.listening) {
|
|
727
|
+
await new Promise(resolve => pairingDiscoveryServer.close(() => resolve()));
|
|
728
|
+
}
|
|
729
|
+
await localAuthority?.close();
|
|
730
|
+
stopSelfHosted();
|
|
731
|
+
if (developmentSession?.state === 'STOPPING')
|
|
732
|
+
transitionDevelopmentSession(developmentSession, 'STOPPED');
|
|
733
|
+
})());
|
|
734
|
+
process.once('exit', () => { void stopAll(); });
|
|
735
|
+
process.once('SIGINT', async () => { await stopAll(); process.exit(130); });
|
|
736
|
+
process.once('SIGTERM', async () => { await stopAll(); process.exit(143); });
|
|
737
|
+
if (applicationLifecycle === 'managed') {
|
|
738
|
+
try {
|
|
739
|
+
if (!detectedApplication)
|
|
740
|
+
detectedApplication = detectApplication(projectDir);
|
|
741
|
+
if (!appUrl) {
|
|
742
|
+
throw new Error(`Managed application lifecycle requires an expected application URL from --app-url, feltdb.config.json applicationUrl, FELTDB_APP_URL, or an explicit port in package.json.scripts.dev (workspace: ${workspace.workspaceId}; project: ${projectDir})`);
|
|
743
|
+
}
|
|
744
|
+
const timeoutArgument = args.includes('--application-timeout')
|
|
745
|
+
? args[args.indexOf('--application-timeout') + 1]
|
|
746
|
+
: undefined;
|
|
747
|
+
const applicationTimeoutMs = timeoutArgument === undefined ? 15000 : Number(timeoutArgument);
|
|
748
|
+
if (!Number.isFinite(applicationTimeoutMs) || applicationTimeoutMs <= 0)
|
|
749
|
+
throw new Error('--application-timeout must be a positive number of milliseconds');
|
|
750
|
+
managedApplication = startManagedApplication({
|
|
751
|
+
projectDir,
|
|
752
|
+
project: detectedApplication,
|
|
753
|
+
environment: { ...process.env, ...developmentSessionEnvironment(developmentSession) },
|
|
754
|
+
});
|
|
755
|
+
console.log(`Starting managed application: ${managedApplication.command}`);
|
|
756
|
+
await waitForManagedApplication({ application: managedApplication, expectedUrl: appUrl, timeoutMs: applicationTimeoutMs, workspaceId: workspace.workspaceId, projectDir });
|
|
757
|
+
console.log(`Managed application ready: ${appUrl}\n`);
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
transitionDevelopmentSession(developmentSession, 'FAILED');
|
|
761
|
+
await stopAll();
|
|
762
|
+
throw error;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// Output startup information
|
|
766
|
+
console.log('✨ Development Workspace');
|
|
767
|
+
if (workspace) {
|
|
768
|
+
console.log(` Workspace ID: ${workspace.workspaceId}`);
|
|
611
769
|
}
|
|
612
770
|
console.log();
|
|
771
|
+
console.log('🔗 Development Workspace');
|
|
772
|
+
if (pairingToken) {
|
|
773
|
+
console.log(` Pairing Code: ${pairingToken}`);
|
|
774
|
+
console.log(` Pairing discovery: ${developmentSession.pairingUrl}`);
|
|
775
|
+
}
|
|
776
|
+
console.log();
|
|
777
|
+
console.log('FeltDB Dev Server');
|
|
778
|
+
console.log(` Namespace: ${developmentSession.namespace}`);
|
|
779
|
+
console.log(` Runtime: ${developmentSession.runtime}`);
|
|
780
|
+
console.log(` Storage: ${developmentSession.storage}`);
|
|
781
|
+
console.log(` Distributed: ${config.distributed}`);
|
|
782
|
+
console.log(` Application: ${applicationLifecycle}`);
|
|
783
|
+
console.log();
|
|
784
|
+
if (appUrl) {
|
|
785
|
+
console.log(`Application: ${appUrl}`);
|
|
786
|
+
console.log(`Authority: ${developmentSession.authorityUrl}`);
|
|
787
|
+
console.log();
|
|
788
|
+
console.log(`Using existing application at ${appUrl}`);
|
|
789
|
+
console.log(`app=${encodeURIComponent(appUrl)}\n`);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
console.log('❌ No application URL is configured.\n');
|
|
793
|
+
console.log('Start your application, then run:\n');
|
|
794
|
+
console.log(' feltdb dev --app-url http://127.0.0.1:<port>\n');
|
|
795
|
+
console.log('Or set applicationUrl in feltdb.config.json\n');
|
|
796
|
+
console.log('FeltDB workspace is ready at:');
|
|
797
|
+
console.log('Authority: ' + developmentSession.authorityUrl);
|
|
798
|
+
console.log('Studio: ' + developmentSession.studioUrl);
|
|
799
|
+
console.log();
|
|
800
|
+
}
|
|
613
801
|
const open = !args.includes('--no-open');
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
802
|
+
try {
|
|
803
|
+
const studio = handleStudio([
|
|
804
|
+
'--port', studioPort,
|
|
805
|
+
'--namespace', developmentSession.namespace,
|
|
806
|
+
'--runtime', developmentSession.runtime,
|
|
807
|
+
'--workspace-id', developmentSession.workspaceId,
|
|
808
|
+
'--session-id', developmentSession.sessionId,
|
|
809
|
+
'--project-id', developmentSession.projectId,
|
|
810
|
+
'--authority-url', developmentSession.authorityUrl,
|
|
811
|
+
'--pairing-url', developmentSession.pairingUrl,
|
|
812
|
+
'--lifecycle', developmentSession.lifecycle,
|
|
813
|
+
...(developmentSession.applicationUrl ? ['--app-url', developmentSession.applicationUrl] : []),
|
|
814
|
+
...((developmentSession.runtime === 'self-hosted' || developmentSession.runtime === 'managed') ? ['--connect', developmentSession.authorityUrl] : []),
|
|
815
|
+
...(open ? [] : ['--no-open']),
|
|
816
|
+
], () => {
|
|
817
|
+
transitionDevelopmentSession(developmentSession, 'READY');
|
|
818
|
+
console.log(`\n${developmentSessionSummary(developmentSession)}\n`);
|
|
819
|
+
transitionDevelopmentSession(developmentSession, 'RUNNING');
|
|
820
|
+
});
|
|
821
|
+
if (managedApplication) {
|
|
822
|
+
await Promise.race([studio, managedApplication.exited.then(exit => {
|
|
823
|
+
throw new Error(`Managed application stopped during the development session (command: ${managedApplication.command}; exit: ${exit.code ?? exit.signal})`);
|
|
824
|
+
})]);
|
|
825
|
+
}
|
|
826
|
+
else
|
|
827
|
+
await studio;
|
|
828
|
+
}
|
|
829
|
+
finally {
|
|
830
|
+
await stopAll();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function runLocalVite(args, waitForExit) {
|
|
834
|
+
const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
835
|
+
const child = spawn(executable, ['exec', '--', 'vite', ...args], {
|
|
836
|
+
cwd: process.cwd(),
|
|
837
|
+
stdio: 'inherit',
|
|
838
|
+
env: process.env,
|
|
839
|
+
});
|
|
840
|
+
if (!waitForExit)
|
|
841
|
+
return child;
|
|
842
|
+
return new Promise((resolve, reject) => {
|
|
843
|
+
child.once('error', reject);
|
|
844
|
+
child.once('exit', code => code === 0 ? resolve() : reject(new Error(`Vite exited with status ${code ?? 'unknown'}`)));
|
|
845
|
+
});
|
|
629
846
|
}
|
|
630
847
|
async function handleBuild(args) {
|
|
631
848
|
console.log('🔨 Building FeltDB application...\n');
|
|
@@ -867,7 +1084,7 @@ async function handleDoctor() {
|
|
|
867
1084
|
}
|
|
868
1085
|
console.log('\n✅ System healthy\n');
|
|
869
1086
|
}
|
|
870
|
-
async function handleStudio(args) {
|
|
1087
|
+
async function handleStudio(args, onReady) {
|
|
871
1088
|
const connectUrl = args.includes('--connect')
|
|
872
1089
|
? args[args.indexOf('--connect') + 1]
|
|
873
1090
|
: undefined;
|
|
@@ -887,6 +1104,19 @@ async function handleStudio(args) {
|
|
|
887
1104
|
const appUrl = args.includes('--app-url')
|
|
888
1105
|
? args[args.indexOf('--app-url') + 1]
|
|
889
1106
|
: undefined;
|
|
1107
|
+
const sessionValue = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
|
|
1108
|
+
const session = {
|
|
1109
|
+
sessionId: sessionValue('--session-id'),
|
|
1110
|
+
workspaceId: sessionValue('--workspace-id'),
|
|
1111
|
+
projectId: sessionValue('--project-id'),
|
|
1112
|
+
authorityUrl: sessionValue('--authority-url'),
|
|
1113
|
+
pairingUrl: sessionValue('--pairing-url'),
|
|
1114
|
+
lifecycle: sessionValue('--lifecycle'),
|
|
1115
|
+
namespace,
|
|
1116
|
+
runtime,
|
|
1117
|
+
applicationUrl: appUrl,
|
|
1118
|
+
bridgeUrl: sessionValue('--pairing-url'),
|
|
1119
|
+
};
|
|
890
1120
|
if (connectUrl) {
|
|
891
1121
|
console.log(`Connecting to: ${connectUrl}`);
|
|
892
1122
|
console.log(`Remote Studio: http://localhost:${port}\n`);
|
|
@@ -897,19 +1127,21 @@ async function handleStudio(args) {
|
|
|
897
1127
|
const require = createRequire(import.meta.url);
|
|
898
1128
|
const packageFile = require.resolve('@feltdb/core/package.json');
|
|
899
1129
|
const root = path.join(path.dirname(packageFile), 'dist', 'studio-app');
|
|
1130
|
+
if (!fs.existsSync(path.join(root, 'index.html'))) {
|
|
1131
|
+
console.log('⚠️ Studio application artifacts not found.');
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
900
1134
|
const projectFlow = ['feltdb.flow', ...fs.readdirSync(process.cwd()).filter(value => value.endsWith('.flow'))]
|
|
901
1135
|
.map(value => path.resolve(process.cwd(), value))
|
|
902
1136
|
.find((value, index, values) => values.indexOf(value) === index && fs.existsSync(value));
|
|
903
|
-
if (!fs.existsSync(path.join(root, 'index.html')))
|
|
904
|
-
throw new Error('Studio application artifact is missing; reinstall @feltdb/core');
|
|
905
1137
|
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' };
|
|
906
|
-
const server = http.createServer((request, response) => {
|
|
1138
|
+
const server = http.createServer(async (request, response) => {
|
|
907
1139
|
const pathname = decodeURIComponent(new URL(request.url || '/', 'http://localhost').pathname);
|
|
908
1140
|
if (pathname === '/_feltdb/config') {
|
|
909
1141
|
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
910
1142
|
response.setHeader('Cache-Control', 'no-store');
|
|
911
1143
|
response.setHeader('Pragma', 'no-cache');
|
|
912
|
-
response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || '' : '' }));
|
|
1144
|
+
response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || '' : '', session }));
|
|
913
1145
|
return;
|
|
914
1146
|
}
|
|
915
1147
|
if (pathname === '/_feltdb/project') {
|
|
@@ -923,6 +1155,52 @@ async function handleStudio(args) {
|
|
|
923
1155
|
response.end(JSON.stringify({ namespace, filename: path.basename(projectFlow), source: fs.readFileSync(projectFlow, 'utf8') }));
|
|
924
1156
|
return;
|
|
925
1157
|
}
|
|
1158
|
+
if (pathname === '/_feltdb/runtime') {
|
|
1159
|
+
if (!session.bridgeUrl || !session.sessionId) {
|
|
1160
|
+
response.statusCode = 404;
|
|
1161
|
+
response.end('No development session');
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
const upstream = await fetch(`${session.bridgeUrl}/api/v1/development/runtimes?sessionId=${encodeURIComponent(session.sessionId)}`);
|
|
1166
|
+
response.statusCode = upstream.status;
|
|
1167
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1168
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1169
|
+
response.end(await upstream.text());
|
|
1170
|
+
}
|
|
1171
|
+
catch (error) {
|
|
1172
|
+
response.statusCode = 503;
|
|
1173
|
+
response.end(JSON.stringify({ error: String(error) }));
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (pathname === '/_feltdb/observations') {
|
|
1178
|
+
if (!session.bridgeUrl || !session.sessionId) {
|
|
1179
|
+
response.statusCode = 404;
|
|
1180
|
+
response.end('No development session');
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const query = new URL(request.url || '/', 'http://localhost').searchParams;
|
|
1184
|
+
const runtimeInstanceId = query.get('runtimeInstanceId');
|
|
1185
|
+
if (!runtimeInstanceId) {
|
|
1186
|
+
response.statusCode = 400;
|
|
1187
|
+
response.end('runtimeInstanceId is required');
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const parameters = new URLSearchParams({ sessionId: session.sessionId, limit: query.get('limit') || '100', cursor: query.get('cursor') || '0' });
|
|
1191
|
+
try {
|
|
1192
|
+
const upstream = await fetch(`${session.bridgeUrl}/api/v1/development/runtime/${encodeURIComponent(runtimeInstanceId)}/observations?${parameters}`);
|
|
1193
|
+
response.statusCode = upstream.status;
|
|
1194
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1195
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1196
|
+
response.end(await upstream.text());
|
|
1197
|
+
}
|
|
1198
|
+
catch (error) {
|
|
1199
|
+
response.statusCode = 503;
|
|
1200
|
+
response.end(JSON.stringify({ error: String(error) }));
|
|
1201
|
+
}
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
926
1204
|
const requested = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
|
|
927
1205
|
const candidate = path.resolve(root, requested);
|
|
928
1206
|
const file = candidate.startsWith(`${path.resolve(root)}${path.sep}`) && fs.existsSync(candidate) && fs.statSync(candidate).isFile() ? candidate : path.join(root, 'index.html');
|
|
@@ -936,9 +1214,18 @@ async function handleStudio(args) {
|
|
|
936
1214
|
parameters.set('connect', connectUrl);
|
|
937
1215
|
if (appUrl)
|
|
938
1216
|
parameters.set('app', appUrl);
|
|
1217
|
+
if (session.workspaceId)
|
|
1218
|
+
parameters.set('workspace', session.workspaceId);
|
|
1219
|
+
if (session.sessionId)
|
|
1220
|
+
parameters.set('session', session.sessionId);
|
|
1221
|
+
if (session.authorityUrl)
|
|
1222
|
+
parameters.set('authority', session.authorityUrl);
|
|
1223
|
+
if (session.pairingUrl)
|
|
1224
|
+
parameters.set('pairing', session.pairingUrl);
|
|
939
1225
|
const query = `?${parameters.toString()}`;
|
|
940
1226
|
const studioUrl = `http://127.0.0.1:${port}/${query}`;
|
|
941
1227
|
console.log(`FeltDB Studio ready at ${studioUrl}`);
|
|
1228
|
+
onReady?.();
|
|
942
1229
|
console.log('Use Ctrl+C to stop the server.');
|
|
943
1230
|
if (open) {
|
|
944
1231
|
const command = process.platform === 'darwin' ? ['open', studioUrl] : process.platform === 'win32' ? ['cmd', '/c', 'start', '', studioUrl] : ['xdg-open', studioUrl];
|
|
@@ -964,6 +1251,7 @@ Commands:
|
|
|
964
1251
|
connect Connect to remote FeltDB
|
|
965
1252
|
status Show connection status
|
|
966
1253
|
dev Start development server
|
|
1254
|
+
investigations List or show durable runtime investigations
|
|
967
1255
|
build Build FeltDB application
|
|
968
1256
|
check Validate configuration
|
|
969
1257
|
inspect Inspect runtime state
|