vmlive 1.0.8 → 1.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +195 -192
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vmlive",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Local development VM for custom Serverless PaaS",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -22,7 +22,7 @@ const CONFIG_PATH = path.resolve('vm.json');
22
22
 
23
23
  const buildResourcesConfig = () => {
24
24
  return {
25
- d1Databases: ["DB"],
25
+ d1Databases: ["DB"],
26
26
  kvNamespaces: { "__RAW_KV": "local_store" },
27
27
  r2Buckets: ["R2"]
28
28
  };
@@ -62,7 +62,10 @@ const buildProxyDispatcher = (functions, workspaceId, projectId, includeDashboar
62
62
  const status = res.status;
63
63
  const color = status >= 500 ? "\\x1b[31m" : status >= 400 ? "\\x1b[33m" : "\\x1b[32m";
64
64
 
65
+ const isMuted = path.startsWith('/.internal/') || path.includes('devtools');
66
+ if (!isMuted) {
65
67
  console.log(\`\\x1b[90m[\${targetName}]\\x1b[0m \${method} \${path} \${color}\${status}\\x1b[0m\`);
68
+ }
66
69
  return res;
67
70
  }
68
71
  }
@@ -86,35 +89,35 @@ const runInit = async () => {
86
89
  const configPath = path.join(os.homedir(), '.vm-config.json');
87
90
  let jwtToken = process.env.VM_API_TOKEN;
88
91
  if (!jwtToken && fs.existsSync(configPath)) {
89
- jwtToken = JSON.parse(fs.readFileSync(configPath, 'utf-8')).token;
92
+ jwtToken = JSON.parse(fs.readFileSync(configPath, 'utf-8')).token;
90
93
  }
91
94
 
92
95
  if (!jwtToken) {
93
- console.error('\x1b[31m❌ Unauthorized.\x1b[0m Please run \x1b[36mnpx vmlive login\x1b[0m first to securely pair this CLI.');
94
- process.exit(1);
96
+ console.error('\x1b[31m❌ Unauthorized.\x1b[0m Please run \x1b[36mnpx vmlive login\x1b[0m first to securely pair this CLI.');
97
+ process.exit(1);
95
98
  }
96
99
 
97
- const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'http://localhost:8787';
100
+ const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
98
101
 
99
102
  // 2. Extract Workspace Architecture
100
103
  console.log('\x1b[36mFetching workspaces...\x1b[0m');
101
104
  const wsRes = await fetch(`${GATEKEEPER_URL}/api/workspaces`, {
102
- headers: { 'Authorization': `Bearer ${jwtToken}` }
105
+ headers: { 'Authorization': `Bearer ${jwtToken}` }
103
106
  });
104
107
 
105
108
  if (!wsRes.ok) {
106
- if (wsRes.status === 401) {
107
- console.error('\x1b[31m❌ Session Expired:\x1b[0m Your authentication token has expired. Please run \x1b[36mnpx vmlive login\x1b[0m to securely re-authenticate.');
108
- } else {
109
- console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to securely retrieve workspace architecture from Production. HTTP ' + wsRes.status);
110
- }
111
- process.exit(1);
109
+ if (wsRes.status === 401) {
110
+ console.error('\x1b[31m❌ Session Expired:\x1b[0m Your authentication token has expired. Please run \x1b[36mnpx vmlive login\x1b[0m to securely re-authenticate.');
111
+ } else {
112
+ console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to securely retrieve workspace architecture from Production. HTTP ' + wsRes.status);
113
+ }
114
+ process.exit(1);
112
115
  }
113
116
 
114
117
  let workspaces = await wsRes.json();
115
118
  if (!Array.isArray(workspaces)) {
116
- console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to retrieve workspaces.');
117
- process.exit(1);
119
+ console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to retrieve workspaces.');
120
+ process.exit(1);
118
121
  }
119
122
 
120
123
  // 3. Interactive Target Lock
@@ -122,104 +125,104 @@ const runInit = async () => {
122
125
  workspaceChoices.push({ name: '+ Create New Workspace', value: '__CREATE_WORKSPACE__' });
123
126
 
124
127
  let workspaceSlug = await select({
125
- message: 'Which workspace would you like to use?',
126
- choices: workspaceChoices
128
+ message: 'Which workspace would you like to use?',
129
+ choices: workspaceChoices
127
130
  });
128
131
 
129
132
  if (workspaceSlug === '__CREATE_WORKSPACE__') {
130
- const wsName = await input({ message: 'What do you want to name your new workspace?' });
131
- console.log('\x1b[36mCreating workspace...\x1b[0m');
132
-
133
- const wsCreateRes = await fetch(`${GATEKEEPER_URL}/api/workspaces`, {
134
- method: 'POST',
135
- headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
136
- body: JSON.stringify({ name: wsName })
137
- });
138
-
139
- if (!wsCreateRes.ok) {
140
- console.error('\x1b[31m❌ Creation Failed:\x1b[0m', await wsCreateRes.text());
141
- process.exit(1);
142
- }
143
- const wsData = await wsCreateRes.json();
144
- workspaceSlug = wsData.workspace.slug;
145
- console.log(`\x1b[32m✔ Workspace created.\x1b[0m\n`);
146
-
147
- const wantsVerify = await confirm({
148
- message: 'Anti-abuse: Would you like to securely verify your identity with Stripe? (You will not be charged)',
149
- default: true
150
- });
151
-
152
- if (wantsVerify) {
153
- console.log('\x1b[36mGenerating verification link...\x1b[0m');
154
- const stripeRes = await fetch(`${GATEKEEPER_URL}/api/${workspaceSlug}/stripe/setup`, {
155
- method: 'POST',
156
- headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
157
- body: JSON.stringify({ return_url: 'https://vm.live/dashboard' })
158
- });
159
-
160
- if (stripeRes.ok) {
161
- const stripeData = await stripeRes.json();
162
- console.log(`\n\x1b]8;;${stripeData.url}\x1b\\\x1b[34m${stripeData.url}\x1b[0m\x1b]8;;\x1b\\\n`);
163
- const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
164
- exec(`${openCmd} "${stripeData.url}"`);
165
- await input({ message: 'Press Enter once verified to continue:' });
166
- } else {
167
- console.error('\x1b[31m❌ Failed to generate Stripe link:\x1b[0m', await stripeRes.text());
168
- }
169
- }
133
+ const wsName = await input({ message: 'What do you want to name your new workspace?' });
134
+ console.log('\x1b[36mCreating workspace...\x1b[0m');
135
+
136
+ const wsCreateRes = await fetch(`${GATEKEEPER_URL}/api/workspaces`, {
137
+ method: 'POST',
138
+ headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ name: wsName })
140
+ });
141
+
142
+ if (!wsCreateRes.ok) {
143
+ console.error('\x1b[31m❌ Creation Failed:\x1b[0m', await wsCreateRes.text());
144
+ process.exit(1);
145
+ }
146
+ const wsData = await wsCreateRes.json();
147
+ workspaceSlug = wsData.workspace.slug;
148
+ console.log(`\x1b[32m✔ Workspace created.\x1b[0m\n`);
149
+
150
+ const wantsVerify = await confirm({
151
+ message: 'Anti-abuse: Would you like to securely verify your identity with Stripe? (You will not be charged)',
152
+ default: true
153
+ });
154
+
155
+ if (wantsVerify) {
156
+ console.log('\x1b[36mGenerating verification link...\x1b[0m');
157
+ const stripeRes = await fetch(`${GATEKEEPER_URL}/api/${workspaceSlug}/stripe/setup`, {
158
+ method: 'POST',
159
+ headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({ return_url: 'https://vm.live/dashboard' })
161
+ });
162
+
163
+ if (stripeRes.ok) {
164
+ const stripeData = await stripeRes.json();
165
+ console.log(`\n\x1b]8;;${stripeData.url}\x1b\\\x1b[34m${stripeData.url}\x1b[0m\x1b]8;;\x1b\\\n`);
166
+ const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
167
+ exec(`${openCmd} "${stripeData.url}"`);
168
+ await input({ message: 'Press Enter once verified to continue:' });
169
+ } else {
170
+ console.error('\x1b[31m❌ Failed to generate Stripe link:\x1b[0m', await stripeRes.text());
171
+ }
172
+ }
170
173
  }
171
174
 
172
175
  // 4. Extract Project Containers
173
176
  const projRes = await fetch(`${GATEKEEPER_URL}/api/${workspaceSlug}/projects`, {
174
- headers: { 'Authorization': `Bearer ${jwtToken}` }
177
+ headers: { 'Authorization': `Bearer ${jwtToken}` }
175
178
  });
176
179
 
177
180
  if (!projRes.ok) {
178
- console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to logically retrieve project constraints from Production.');
179
- process.exit(1);
181
+ console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to logically retrieve project constraints from Production.');
182
+ process.exit(1);
180
183
  }
181
184
 
182
185
  let projects = await projRes.json();
183
186
  if (!Array.isArray(projects)) {
184
- console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to retrieve projects.');
185
- process.exit(1);
187
+ console.error('\x1b[31m❌ Sandbox Error:\x1b[0m Failed to retrieve projects.');
188
+ process.exit(1);
186
189
  }
187
190
 
188
191
  const projectChoices = projects.map(p => ({ name: p.name, value: p.slug }));
189
192
  projectChoices.push({ name: '+ Create New Project', value: '__CREATE_PROJECT__' });
190
193
 
191
194
  let projectSlug = await select({
192
- message: 'Which project would you like to use?',
193
- choices: projectChoices
195
+ message: 'Which project would you like to use?',
196
+ choices: projectChoices
194
197
  });
195
198
 
196
199
  if (projectSlug === '__CREATE_PROJECT__') {
197
- const projName = await input({ message: 'What do you want to name your new project?' });
198
- console.log('\x1b[36mCreating project...\x1b[0m');
199
-
200
- const projCreateRes = await fetch(`${GATEKEEPER_URL}/api/${workspaceSlug}/projects`, {
201
- method: 'POST',
202
- headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
203
- body: JSON.stringify({ name: projName })
204
- });
205
-
206
- if (!projCreateRes.ok) {
207
- console.error('\x1b[31m❌ Creation Failed:\x1b[0m', await projCreateRes.text());
208
- process.exit(1);
209
- }
210
- const projData = await projCreateRes.json();
211
- projectSlug = projData.project.slug;
212
- console.log(`\x1b[32m✔ Project created.\x1b[0m\n`);
200
+ const projName = await input({ message: 'What do you want to name your new project?' });
201
+ console.log('\x1b[36mCreating project...\x1b[0m');
202
+
203
+ const projCreateRes = await fetch(`${GATEKEEPER_URL}/api/${workspaceSlug}/projects`, {
204
+ method: 'POST',
205
+ headers: { 'Authorization': `Bearer ${jwtToken}`, 'Content-Type': 'application/json' },
206
+ body: JSON.stringify({ name: projName })
207
+ });
208
+
209
+ if (!projCreateRes.ok) {
210
+ console.error('\x1b[31m❌ Creation Failed:\x1b[0m', await projCreateRes.text());
211
+ process.exit(1);
212
+ }
213
+ const projData = await projCreateRes.json();
214
+ projectSlug = projData.project.slug;
215
+ console.log(`\x1b[32m✔ Project created.\x1b[0m\n`);
213
216
  }
214
217
 
215
218
  const functionNameRaw = await input({
216
- message: 'What do you want to name this function?',
217
- default: 'api'
219
+ message: 'What do you want to name this function?',
220
+ default: 'api'
218
221
  });
219
222
  const functionName = functionNameRaw.trim().toLowerCase().replace(/[^a-z0-9-]/g, '') || 'api';
220
223
 
221
224
  if (!fs.existsSync(path.resolve('src'))) {
222
- fs.mkdirSync(path.resolve('src'), { recursive: true });
225
+ fs.mkdirSync(path.resolve('src'), { recursive: true });
223
226
  }
224
227
  const funcFile = path.resolve('src', `${functionName}.ts`);
225
228
 
@@ -278,14 +281,14 @@ export default {
278
281
  fs.writeFileSync(pkgPath, defaultPackageJson);
279
282
  } else {
280
283
  try {
281
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
282
- pkg.devDependencies = pkg.devDependencies || {};
283
- if (!pkg.devDependencies['@vmlive/types']) {
284
- pkg.devDependencies['@vmlive/types'] = '*';
285
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
286
- }
287
- } catch(e) {
288
- // gracefully ignore malformed package.json
284
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
285
+ pkg.devDependencies = pkg.devDependencies || {};
286
+ if (!pkg.devDependencies['@vmlive/types']) {
287
+ pkg.devDependencies['@vmlive/types'] = '*';
288
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
289
+ }
290
+ } catch (e) {
291
+ // gracefully ignore malformed package.json
289
292
  }
290
293
  }
291
294
 
@@ -307,22 +310,22 @@ export default {
307
310
  fs.writeFileSync(tsConfigPath, defaultTsConfig);
308
311
  } else {
309
312
  try {
310
- const tsconfig = JSON.parse(fs.readFileSync(tsConfigPath, 'utf8'));
311
- tsconfig.compilerOptions = tsconfig.compilerOptions || {};
312
- tsconfig.compilerOptions.types = tsconfig.compilerOptions.types || [];
313
- if (!tsconfig.compilerOptions.types.includes('@vmlive/types')) {
314
- tsconfig.compilerOptions.types.push('@vmlive/types');
315
- fs.writeFileSync(tsConfigPath, JSON.stringify(tsconfig, null, 2));
316
- }
317
- } catch(e) {}
313
+ const tsconfig = JSON.parse(fs.readFileSync(tsConfigPath, 'utf8'));
314
+ tsconfig.compilerOptions = tsconfig.compilerOptions || {};
315
+ tsconfig.compilerOptions.types = tsconfig.compilerOptions.types || [];
316
+ if (!tsconfig.compilerOptions.types.includes('@vmlive/types')) {
317
+ tsconfig.compilerOptions.types.push('@vmlive/types');
318
+ fs.writeFileSync(tsConfigPath, JSON.stringify(tsconfig, null, 2));
319
+ }
320
+ } catch (e) { }
318
321
  }
319
322
 
320
323
  console.log('\x1b[36mInstalling dependencies...\x1b[0m');
321
324
  await new Promise((resolve) => {
322
- exec('npm install', (err) => {
323
- if (err) console.error('\x1b[33m⚠️ Warning: npm install failed. You may need to run it manually to resolve TS errors.\x1b[0m');
324
- resolve();
325
- });
325
+ exec('npm install', (err) => {
326
+ if (err) console.error('\x1b[33m⚠️ Warning: npm install failed. You may need to run it manually to resolve TS errors.\x1b[0m');
327
+ resolve();
328
+ });
326
329
  });
327
330
 
328
331
  console.log('\x1b[32mProject created successfully.\x1b[0m');
@@ -331,45 +334,45 @@ export default {
331
334
 
332
335
  const runAdd = async () => {
333
336
  if (!fs.existsSync(CONFIG_PATH)) {
334
- console.error('\x1b[31m❌ Error:\x1b[0m Cannot add function. No vm.json found in this directory. Run `npx vmlive init` first.');
335
- process.exit(1);
337
+ console.error('\x1b[31m❌ Error:\x1b[0m Cannot add function. No vm.json found in this directory. Run `npx vmlive init` first.');
338
+ process.exit(1);
336
339
  }
337
-
340
+
338
341
  const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
339
-
342
+
340
343
  const functionNameRaw = await input({
341
- message: 'What do you want to name this new function?',
342
- default: 'webhooks'
344
+ message: 'What do you want to name this new function?',
345
+ default: 'webhooks'
343
346
  });
344
347
  const functionName = functionNameRaw.trim().toLowerCase().replace(/[^a-z0-9-]/g, '');
345
348
  if (!functionName) process.exit(1);
346
-
349
+
347
350
  if (config.functions.some(f => f.name === functionName)) {
348
- console.error(`\x1b[31m❌ Error:\x1b[0m Function '${functionName}' already exists in this project.`);
349
- process.exit(1);
351
+ console.error(`\x1b[31m❌ Error:\x1b[0m Function '${functionName}' already exists in this project.`);
352
+ process.exit(1);
350
353
  }
351
-
354
+
352
355
  if (!fs.existsSync(path.resolve('src'))) {
353
- fs.mkdirSync(path.resolve('src'), { recursive: true });
356
+ fs.mkdirSync(path.resolve('src'), { recursive: true });
354
357
  }
355
-
358
+
356
359
  const targetFile = path.resolve('src', `${functionName}.ts`);
357
360
  if (!fs.existsSync(targetFile)) {
358
- const defaultApiTs = `import type { Env, Context } from '@vmlive/types';
361
+ const defaultApiTs = `import type { Env, Context } from '@vmlive/types';
359
362
  export default {
360
363
  async fetch(request: Request, env: Env, ctx: Context) {
361
364
  return new Response("Hello from ${functionName}!", { status: 200 });
362
365
  }
363
366
  };
364
367
  `;
365
- fs.writeFileSync(targetFile, defaultApiTs);
368
+ fs.writeFileSync(targetFile, defaultApiTs);
366
369
  }
367
-
370
+
368
371
  config.functions.push({
369
- name: functionName,
370
- entry: `src/${functionName}.ts`
372
+ name: functionName,
373
+ entry: `src/${functionName}.ts`
371
374
  });
372
-
375
+
373
376
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
374
377
  console.log(`\x1b[32m✔ Function '${functionName}' securely scaffolded successfully!\x1b[0m`);
375
378
  };
@@ -380,14 +383,14 @@ const runDev = async () => {
380
383
  }
381
384
 
382
385
  const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
383
-
386
+
384
387
  if (!fs.existsSync(WORK_DIR)) fs.mkdirSync(WORK_DIR, { recursive: true });
385
388
  if (!fs.existsSync(DATA_DIR)) {
386
389
  fs.mkdirSync(DATA_DIR, { recursive: true });
387
390
  }
388
391
 
389
- const sharedBindings = fs.existsSync(path.resolve('.env'))
390
- ? dotenv.parse(fs.readFileSync(path.resolve('.env')))
392
+ const sharedBindings = fs.existsSync(path.resolve('.env'))
393
+ ? dotenv.parse(fs.readFileSync(path.resolve('.env')))
391
394
  : {};
392
395
 
393
396
  const resourcesConfig = buildResourcesConfig();
@@ -411,7 +414,7 @@ const runDev = async () => {
411
414
  format: 'esm',
412
415
  outdir: WORK_DIR,
413
416
  outExtension: { '.js': '.mjs' },
414
- external: ['cloudflare:*'],
417
+ external: ['cloudflare:*'],
415
418
  logLevel: 'warning'
416
419
  });
417
420
 
@@ -459,9 +462,9 @@ const runDev = async () => {
459
462
  }
460
463
  }))
461
464
  ];
462
- const mf = new Emulator({
463
- workers: miniflareWorkers,
464
- port: mfPort,
465
+ const mf = new Emulator({
466
+ workers: miniflareWorkers,
467
+ port: mfPort,
465
468
  cachePersist: path.join(WORK_DIR, 'cache'),
466
469
  d1Persist: path.join(DATA_DIR, 'db'),
467
470
  kvPersist: path.join(DATA_DIR, 'kv'),
@@ -469,10 +472,10 @@ const runDev = async () => {
469
472
  });
470
473
  await mf.ready;
471
474
  console.log('\nLocal endpoints available:');
472
-
475
+
473
476
  const dashboardUrl = `http://dashboard.localhost:${mfPort}`;
474
477
  console.log(` \x1b]8;;${dashboardUrl}\x1b\\\x1b[32m[dashboard]\x1b[0m\x1b]8;;\x1b\\ ${dashboardUrl}`);
475
-
478
+
476
479
  config.functions.forEach(fn => {
477
480
  const endpointUrl = `http://${workspaceId}-${projectId}-${fn.name}.localhost:${mfPort}`;
478
481
  const padName = `[${fn.name}]`.padEnd(13, ' ');
@@ -480,12 +483,12 @@ const runDev = async () => {
480
483
  });
481
484
 
482
485
  setInterval(() => {
483
- const memoryData = process.memoryUsage();
484
- const heapMb = Math.round(memoryData.heapUsed / 1024 / 1024);
485
- if (heapMb > 110) {
486
- console.warn(`\n\x1b[33m⚠️ [VM-CLI] CAUTION: HIGH MEMORY USAGE (${heapMb}MB)\x1b[0m`);
487
- console.warn(`\x1b[33mYour worker environment is approaching the 128MB production limit. Local memory profiling is bloated due to dev-tooling overhead, but ensure your production build avoids massive synchronous allocations.\x1b[0m`);
488
- }
486
+ const memoryData = process.memoryUsage();
487
+ const heapMb = Math.round(memoryData.heapUsed / 1024 / 1024);
488
+ if (heapMb > 110) {
489
+ console.warn(`\n\x1b[33m⚠️ [VM-CLI] CAUTION: HIGH MEMORY USAGE (${heapMb}MB)\x1b[0m`);
490
+ console.warn(`\x1b[33mYour worker environment is approaching the 128MB production limit. Local memory profiling is bloated due to dev-tooling overhead, but ensure your production build avoids massive synchronous allocations.\x1b[0m`);
491
+ }
489
492
  }, 10000);
490
493
 
491
494
  let debounceTimeout = null;
@@ -496,15 +499,15 @@ const runDev = async () => {
496
499
  try {
497
500
  console.log(`\nReloading ${filename}...`);
498
501
  await builder.rebuild();
499
-
502
+
500
503
  config.functions.forEach(fn => {
501
504
  const relativeTarget = `./${fn.name}-out.mjs`;
502
505
  fs.writeFileSync(path.join(WORK_DIR, `${fn.name}-shim.mjs`), generateShim(relativeTarget, workspaceId, projectId, fn.name));
503
506
  });
504
507
 
505
- await mf.setOptions({
506
- workers: miniflareWorkers,
507
- port: mfPort,
508
+ await mf.setOptions({
509
+ workers: miniflareWorkers,
510
+ port: mfPort,
508
511
  cachePersist: path.join(WORK_DIR, 'cache'),
509
512
  d1Persist: path.join(DATA_DIR, 'db'),
510
513
  kvPersist: path.join(DATA_DIR, 'kv'),
@@ -529,16 +532,16 @@ const runDev = async () => {
529
532
 
530
533
  const runDeploy = async () => {
531
534
  console.log('\x1b[36mDeploying...\x1b[0m');
532
-
535
+
533
536
  const configPath = path.join(os.homedir(), '.vm-config.json');
534
537
  let token = process.env.VM_API_TOKEN;
535
538
  if (!token && fs.existsSync(configPath)) {
536
- token = JSON.parse(fs.readFileSync(configPath, 'utf-8')).token;
539
+ token = JSON.parse(fs.readFileSync(configPath, 'utf-8')).token;
537
540
  }
538
-
541
+
539
542
  if (!token) {
540
- console.error('\x1b[31m❌ Unauthorized. Please run `vm login` first.\x1b[0m');
541
- process.exit(1);
543
+ console.error('\x1b[31m❌ Unauthorized. Please run `vm login` first.\x1b[0m');
544
+ process.exit(1);
542
545
  }
543
546
 
544
547
  if (!fs.existsSync(CONFIG_PATH)) {
@@ -552,52 +555,52 @@ const runDeploy = async () => {
552
555
  // Dotenv Cascading: Extract explicitly configured variables mapping them tightly
553
556
  let userEnv = {};
554
557
  if (fs.existsSync('.env')) {
555
- const baseEnv = dotenv.parse(fs.readFileSync('.env'));
556
- userEnv = { ...userEnv, ...baseEnv };
558
+ const baseEnv = dotenv.parse(fs.readFileSync('.env'));
559
+ userEnv = { ...userEnv, ...baseEnv };
557
560
  }
558
561
  if (fs.existsSync('.env.production')) {
559
- const prodEnv = dotenv.parse(fs.readFileSync('.env.production'));
560
- userEnv = { ...userEnv, ...prodEnv }; // Strict Override True
562
+ const prodEnv = dotenv.parse(fs.readFileSync('.env.production'));
563
+ userEnv = { ...userEnv, ...prodEnv }; // Strict Override True
561
564
  }
562
-
565
+
563
566
  console.log(`\x1b[35m[Config]\x1b[0m Successfully extracted ${Object.keys(userEnv).length} Environment Variables.`);
564
567
 
565
568
  for (const fn of functions) {
566
- console.log(`\x1b[36m[esbuild]\x1b[0m Bundling ${fn.name}...`);
567
- const outPath = path.join(WORK_DIR, `${fn.name}-out.mjs`);
568
-
569
- await esbuild.build({
570
- entryPoints: [path.resolve(fn.entry)],
571
- bundle: true,
572
- format: 'esm',
573
- outfile: outPath,
574
- external: ['cloudflare:*', 'node:*'],
575
- });
576
-
577
- const code = fs.readFileSync(outPath, 'utf8');
578
-
579
- const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'http://localhost:8787';
580
- const target = `${GATEKEEPER_URL}/api/${workspaceId}/projects/${projectId}/functions/${fn.name}/deploy`;
581
-
582
- console.log(`\x1b[36m[POST]\x1b[0m Uploading...`);
583
-
584
- const res = await fetch(target, {
585
- method: 'POST',
586
- headers: {
587
- 'Authorization': `Bearer ${token}`,
588
- 'Content-Type': 'application/json'
589
- },
590
- body: JSON.stringify({ code, envVars: userEnv })
591
- });
592
-
593
- if (!res.ok) {
594
- console.error(`\x1b[31m❌ Deployment failed: ${res.status}\x1b[0m`);
595
- console.error(await res.text());
596
- process.exit(1);
597
- }
598
-
599
- const jsonRes = await res.json();
600
- console.log(`\x1b[32mDeployed to: ${jsonRes.public_url || 'Success!'}\x1b[0m`);
569
+ console.log(`\x1b[36m[esbuild]\x1b[0m Bundling ${fn.name}...`);
570
+ const outPath = path.join(WORK_DIR, `${fn.name}-out.mjs`);
571
+
572
+ await esbuild.build({
573
+ entryPoints: [path.resolve(fn.entry)],
574
+ bundle: true,
575
+ format: 'esm',
576
+ outfile: outPath,
577
+ external: ['cloudflare:*', 'node:*'],
578
+ });
579
+
580
+ const code = fs.readFileSync(outPath, 'utf8');
581
+
582
+ const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
583
+ const target = `${GATEKEEPER_URL}/api/${workspaceId}/projects/${projectId}/functions/${fn.name}/deploy`;
584
+
585
+ console.log(`\x1b[36m[POST]\x1b[0m Uploading...`);
586
+
587
+ const res = await fetch(target, {
588
+ method: 'POST',
589
+ headers: {
590
+ 'Authorization': `Bearer ${token}`,
591
+ 'Content-Type': 'application/json'
592
+ },
593
+ body: JSON.stringify({ code, envVars: userEnv })
594
+ });
595
+
596
+ if (!res.ok) {
597
+ console.error(`\x1b[31m❌ Deployment failed: ${res.status}\x1b[0m`);
598
+ console.error(await res.text());
599
+ process.exit(1);
600
+ }
601
+
602
+ const jsonRes = await res.json();
603
+ console.log(`\x1b[32mDeployed to: ${jsonRes.public_url || 'Success!'}\x1b[0m`);
601
604
  }
602
605
  };
603
606
 
@@ -607,8 +610,8 @@ const runLogin = async () => {
607
610
  const codeVerifier = crypto.randomBytes(32).toString('base64url');
608
611
  const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
609
612
 
610
- const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'http://localhost:8787';
611
-
613
+ const GATEKEEPER_URL = process.env.GATEKEEPER_URL || 'https://api.vm.live';
614
+
612
615
  const server = http.createServer(async (req, res) => {
613
616
  const url = new URL(req.url, `http://127.0.0.1:${server.address().port}`);
614
617
  const authCode = url.searchParams.get('code');
@@ -648,18 +651,18 @@ const runLogin = async () => {
648
651
  </body></html>
649
652
  `);
650
653
  console.log('\x1b[32m✔ Successfully authenticated.\x1b[0m');
651
-
654
+
652
655
  setTimeout(() => {
653
656
  server.close();
654
657
  process.exit(0);
655
658
  }, 1500); // 1.5s flush buffer TCP protection
656
-
657
-
659
+
660
+
658
661
  } catch (e) {
659
662
  console.error('\x1b[31m❌ Failed to exchange code:\x1b[0m', e.message);
660
663
  res.writeHead(500, { 'Content-Type': 'text/html' });
661
664
  res.end('<html><body><h1>Authentication Failed</h1></body></html>');
662
-
665
+
663
666
  setTimeout(() => {
664
667
  server.close();
665
668
  process.exit(1);
@@ -669,7 +672,7 @@ const runLogin = async () => {
669
672
 
670
673
  server.listen(0, '127.0.0.1', () => {
671
674
  const PORT = server.address().port;
672
- const EDITOR_URL = process.env.EDITOR_URL || 'http://localhost:5174';
675
+ const EDITOR_URL = process.env.EDITOR_URL || 'https://dash.vm.live';
673
676
  const authUrl = `${EDITOR_URL}/?challenge=${codeChallenge}&port=${PORT}`;
674
677
  console.log(`\x1b[34mOpening browser to: \x1b]8;;${authUrl}\x1b\\${authUrl}\x1b]8;;\x1b\\\x1b[0m`);
675
678
  const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
@@ -694,7 +697,7 @@ const main = async () => {
694
697
  console.log(`vmlive CLI v${pkg.version}`);
695
698
  } else {
696
699
  console.error(`\x1b[31m❌ Unknown command: ${command || 'missing'}\x1b[0m`);
697
- console.log('Usage: vm init | vm add | vm dev | vm login | vm deploy | vm which');
700
+ console.log('Usage: vmlive init | vmlive add | vmlive dev | vmlive login | vmlive deploy | vmlive which');
698
701
  process.exit(1);
699
702
  }
700
703
  };