vmlive 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/package.json +1 -1
- package/src/cli.js +9 -101
- package/src/shadow-dos.js +1 -1
package/README.md
CHANGED
|
@@ -33,11 +33,7 @@ Starts the local emulation environment.
|
|
|
33
33
|
- Provides local proxy bindings for your SQL Database (`env.DB`), Global KV (`env.KV`), and Object Storage (`env.BUCKET`) using the `.vmlive/` directory for persistent storage.
|
|
34
34
|
- Supports hot-reloading for `.js` and `.ts` files.
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
Executes your Vitest suites against a strictly ephemeral **in-memory** engine.
|
|
38
|
-
- Spins up `VirtualMachine` in memory-only mode without touching `.vmlive/` disk databases.
|
|
39
|
-
- Runs `npx vitest run` and gracefully destroys the engine upon exit.
|
|
40
|
-
- Allows test suites to deeply mutate state predictably without sandbox bleeding.
|
|
36
|
+
|
|
41
37
|
|
|
42
38
|
### `npx vmlive deploy`
|
|
43
39
|
Uploads the local code to the vm.live edge platform.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -40,8 +40,9 @@ const buildProxyDispatcher = (functions, workspaceId, projectId, includeDashboar
|
|
|
40
40
|
return new Response(null, { status: 204 });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
const explicitTarget = request.headers.get("x-vm-target");
|
|
43
44
|
const hostHeader = request.headers.get("Host") || url.hostname;
|
|
44
|
-
let targetName = hostHeader.split('.')[0];
|
|
45
|
+
let targetName = explicitTarget || hostHeader.split('.')[0];
|
|
45
46
|
|
|
46
47
|
let res;
|
|
47
48
|
|
|
@@ -61,7 +62,10 @@ const buildProxyDispatcher = (functions, workspaceId, projectId, includeDashboar
|
|
|
61
62
|
const status = res.status;
|
|
62
63
|
const color = status >= 500 ? "\\x1b[31m" : status >= 400 ? "\\x1b[33m" : "\\x1b[32m";
|
|
63
64
|
|
|
65
|
+
const isMuted = path.startsWith('/.internal/') || path.includes('devtools');
|
|
66
|
+
if (!isMuted) {
|
|
64
67
|
console.log(\`\\x1b[90m[\${targetName}]\\x1b[0m \${method} \${path} \${color}\${status}\\x1b[0m\`);
|
|
68
|
+
}
|
|
65
69
|
return res;
|
|
66
70
|
}
|
|
67
71
|
}
|
|
@@ -93,7 +97,7 @@ const runInit = async () => {
|
|
|
93
97
|
process.exit(1);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || '
|
|
100
|
+
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
|
|
97
101
|
|
|
98
102
|
// 2. Extract Workspace Architecture
|
|
99
103
|
console.log('\x1b[36mFetching workspaces...\x1b[0m');
|
|
@@ -526,100 +530,6 @@ const runDev = async () => {
|
|
|
526
530
|
});
|
|
527
531
|
};
|
|
528
532
|
|
|
529
|
-
const runTest = async () => {
|
|
530
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
531
|
-
throw new Error("Missing vm.json in project root. Run 'vm init' to scaffold a new project workspace.");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const testArgs = process.argv.slice(3);
|
|
535
|
-
console.log('\x1b[36mInitializing Ephemeral Target Engine for Integration Tests...\x1b[0m');
|
|
536
|
-
|
|
537
|
-
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
538
|
-
if (!fs.existsSync(WORK_DIR)) fs.mkdirSync(WORK_DIR, { recursive: true });
|
|
539
|
-
|
|
540
|
-
const sharedBindings = fs.existsSync(path.resolve('.env'))
|
|
541
|
-
? dotenv.parse(fs.readFileSync(path.resolve('.env')))
|
|
542
|
-
: {};
|
|
543
|
-
|
|
544
|
-
const resourcesConfig = buildResourcesConfig();
|
|
545
|
-
|
|
546
|
-
const entryPoints = config.functions.reduce((acc, fn) => {
|
|
547
|
-
acc[`${fn.name}-out`] = path.resolve(fn.entry);
|
|
548
|
-
return acc;
|
|
549
|
-
}, {});
|
|
550
|
-
|
|
551
|
-
const workspaceId = config.workspaceId || "ws_local";
|
|
552
|
-
const projectId = config.projectId || "prj_local";
|
|
553
|
-
|
|
554
|
-
config.functions.forEach(fn => {
|
|
555
|
-
const relativeTarget = `./${fn.name}-out.mjs`;
|
|
556
|
-
fs.writeFileSync(path.join(WORK_DIR, `${fn.name}-shim.mjs`), generateShim(relativeTarget, workspaceId, projectId, fn.name));
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
const builder = await esbuild.context({
|
|
560
|
-
entryPoints,
|
|
561
|
-
bundle: true,
|
|
562
|
-
format: 'esm',
|
|
563
|
-
outdir: WORK_DIR,
|
|
564
|
-
outExtension: { '.js': '.mjs' },
|
|
565
|
-
external: ['cloudflare:*'],
|
|
566
|
-
logLevel: 'silent'
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
await builder.rebuild();
|
|
570
|
-
|
|
571
|
-
const shadowSource = fs.readFileSync(path.join(__dirname, 'shadow-dos.js'), 'utf-8');
|
|
572
|
-
fs.writeFileSync(path.join(WORK_DIR, 'shadow-dos.mjs'), shadowSource);
|
|
573
|
-
|
|
574
|
-
const mfPort = process.env.PORT ? parseInt(process.env.PORT) : 8787;
|
|
575
|
-
|
|
576
|
-
// Exact parity workers but omitting the Dashboard intentionally
|
|
577
|
-
const miniflareWorkers = [
|
|
578
|
-
buildProxyDispatcher(config.functions, workspaceId, projectId, false),
|
|
579
|
-
{
|
|
580
|
-
name: "vm-shadow-worker",
|
|
581
|
-
modules: true,
|
|
582
|
-
scriptPath: path.join(WORK_DIR, 'shadow-dos.mjs'),
|
|
583
|
-
bindings: { PORT: mfPort },
|
|
584
|
-
durableObjects: { LocalTaskManagerDO: "LocalTaskManagerDO", LocalChannelRoomDO: "LocalChannelRoomDO" }
|
|
585
|
-
},
|
|
586
|
-
...config.functions.map(fn => ({
|
|
587
|
-
name: fn.name,
|
|
588
|
-
modules: true,
|
|
589
|
-
scriptPath: path.join(WORK_DIR, `${fn.name}-shim.mjs`),
|
|
590
|
-
bindings: sharedBindings,
|
|
591
|
-
...resourcesConfig,
|
|
592
|
-
durableObjects: {
|
|
593
|
-
TASK_DO: { className: "LocalTaskManagerDO", scriptName: "vm-shadow-worker" },
|
|
594
|
-
CHANNEL_DO: { className: "LocalChannelRoomDO", scriptName: "vm-shadow-worker" }
|
|
595
|
-
}
|
|
596
|
-
}))
|
|
597
|
-
];
|
|
598
|
-
|
|
599
|
-
// IN-MEMORY EMULATOR (NO PERSIST PATHS)
|
|
600
|
-
const mf = new Emulator({
|
|
601
|
-
workers: miniflareWorkers,
|
|
602
|
-
port: mfPort
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
await mf.ready;
|
|
606
|
-
console.log('\x1b[32m✔ Ephemeral environment completely provisioned and bound.\x1b[0m\\n');
|
|
607
|
-
|
|
608
|
-
const { spawn } = await import('child_process');
|
|
609
|
-
|
|
610
|
-
const testProcess = spawn('npx', ['vitest', 'run', ...testArgs], {
|
|
611
|
-
stdio: 'inherit',
|
|
612
|
-
env: { ...process.env, VMLIVE_TESTING: "true" }
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
testProcess.on('exit', async (code) => {
|
|
616
|
-
console.log('\\n\x1b[36mTerminating ephemeral engine execution context...\x1b[0m');
|
|
617
|
-
await builder.dispose();
|
|
618
|
-
await mf.dispose();
|
|
619
|
-
process.exit(code || 0);
|
|
620
|
-
});
|
|
621
|
-
};
|
|
622
|
-
|
|
623
533
|
const runDeploy = async () => {
|
|
624
534
|
console.log('\x1b[36mDeploying...\x1b[0m');
|
|
625
535
|
|
|
@@ -669,7 +579,7 @@ const runDeploy = async () => {
|
|
|
669
579
|
|
|
670
580
|
const code = fs.readFileSync(outPath, 'utf8');
|
|
671
581
|
|
|
672
|
-
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || '
|
|
582
|
+
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
|
|
673
583
|
const target = `${GATEKEEPER_URL}/api/${workspaceId}/projects/${projectId}/functions/${fn.name}/deploy`;
|
|
674
584
|
|
|
675
585
|
console.log(`\x1b[36m[POST]\x1b[0m Uploading...`);
|
|
@@ -700,7 +610,7 @@ const runLogin = async () => {
|
|
|
700
610
|
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
701
611
|
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
702
612
|
|
|
703
|
-
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || '
|
|
613
|
+
const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
|
|
704
614
|
|
|
705
615
|
const server = http.createServer(async (req, res) => {
|
|
706
616
|
const url = new URL(req.url, `http://127.0.0.1:${server.address().port}`);
|
|
@@ -778,8 +688,6 @@ const main = async () => {
|
|
|
778
688
|
await runAdd();
|
|
779
689
|
} else if (command === 'dev') {
|
|
780
690
|
await runDev();
|
|
781
|
-
} else if (command === 'test') {
|
|
782
|
-
await runTest();
|
|
783
691
|
} else if (command === 'login') {
|
|
784
692
|
await runLogin();
|
|
785
693
|
} else if (command === 'deploy') {
|
|
@@ -789,7 +697,7 @@ const main = async () => {
|
|
|
789
697
|
console.log(`vmlive CLI v${pkg.version}`);
|
|
790
698
|
} else {
|
|
791
699
|
console.error(`\x1b[31m❌ Unknown command: ${command || 'missing'}\x1b[0m`);
|
|
792
|
-
console.log('Usage:
|
|
700
|
+
console.log('Usage: vmlive init | vmlive add | vmlive dev | vmlive login | vmlive deploy | vmlive which');
|
|
793
701
|
process.exit(1);
|
|
794
702
|
}
|
|
795
703
|
};
|
package/src/shadow-dos.js
CHANGED
|
@@ -49,7 +49,7 @@ export class LocalTaskManagerDO extends DurableObject {
|
|
|
49
49
|
const res = await fetch(invokeUrl, {
|
|
50
50
|
method: 'POST',
|
|
51
51
|
headers: {
|
|
52
|
-
'
|
|
52
|
+
'x-vm-target': `${task.workspace_id}-${task.project_id}-${task.function_slug}`,
|
|
53
53
|
'Content-Type': 'application/json',
|
|
54
54
|
'x-vm-system-signature': 'local-bypass'
|
|
55
55
|
},
|