tova 0.4.0 → 0.4.2

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/bin/tova.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { resolve, basename, dirname, join, relative } from 'path';
4
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync, rmSync, watch as fsWatch } from 'fs';
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync, rmSync, chmodSync, renameSync, watch as fsWatch } from 'fs';
5
5
  import { spawn } from 'child_process';
6
6
  // Bun.hash used instead of crypto.createHash for faster hashing
7
7
  import { Lexer } from '../src/lexer/lexer.js';
@@ -4397,6 +4397,66 @@ function detectInstallMethod() {
4397
4397
  return 'npm';
4398
4398
  }
4399
4399
 
4400
+ function compareSemver(a, b) {
4401
+ // Returns: -1 if a < b, 0 if a === b, 1 if a > b
4402
+ const pa = a.split('.').map(Number);
4403
+ const pb = b.split('.').map(Number);
4404
+ for (let i = 0; i < 3; i++) {
4405
+ if ((pa[i] || 0) < (pb[i] || 0)) return -1;
4406
+ if ((pa[i] || 0) > (pb[i] || 0)) return 1;
4407
+ }
4408
+ return 0;
4409
+ }
4410
+
4411
+ function formatBytes(bytes) {
4412
+ if (bytes < 1024) return bytes + ' B';
4413
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
4414
+ return (bytes / 1048576).toFixed(1) + ' MB';
4415
+ }
4416
+
4417
+ async function downloadWithProgress(url, destPath) {
4418
+ const res = await fetch(url);
4419
+ if (!res.ok) return null;
4420
+
4421
+ const contentLength = parseInt(res.headers.get('content-length'), 10) || 0;
4422
+ const reader = res.body.getReader();
4423
+ const chunks = [];
4424
+ let received = 0;
4425
+
4426
+ const barWidth = 20;
4427
+
4428
+ while (true) {
4429
+ const { done, value } = await reader.read();
4430
+ if (done) break;
4431
+ chunks.push(value);
4432
+ received += value.length;
4433
+
4434
+ if (isTTY) {
4435
+ if (contentLength > 0) {
4436
+ const pct = Math.min(100, Math.round((received / contentLength) * 100));
4437
+ const filled = Math.round((pct / 100) * barWidth);
4438
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
4439
+ process.stdout.write(`\r Downloading... [${bar}] ${pct}% (${formatBytes(received)} / ${formatBytes(contentLength)})`);
4440
+ } else {
4441
+ process.stdout.write(`\r Downloading... ${formatBytes(received)}`);
4442
+ }
4443
+ }
4444
+ }
4445
+
4446
+ if (isTTY) process.stdout.write('\n');
4447
+
4448
+ // Combine chunks into a single Uint8Array
4449
+ const result = new Uint8Array(received);
4450
+ let offset = 0;
4451
+ for (const chunk of chunks) {
4452
+ result.set(chunk, offset);
4453
+ offset += chunk.length;
4454
+ }
4455
+
4456
+ writeFileSync(destPath, result);
4457
+ return { compressed: url.endsWith('.gz'), size: received };
4458
+ }
4459
+
4400
4460
  async function upgradeCommand() {
4401
4461
  console.log(`\n Current version: ${color.bold('Tova v' + VERSION)}\n`);
4402
4462
  console.log(' Checking for updates...');
@@ -4404,102 +4464,98 @@ async function upgradeCommand() {
4404
4464
  const installMethod = detectInstallMethod();
4405
4465
 
4406
4466
  try {
4467
+ // Always check npm registry as the source of truth for latest version
4468
+ const res = await fetch('https://registry.npmjs.org/tova/latest');
4469
+ if (!res.ok) {
4470
+ console.error(color.red(' Could not reach the npm registry. Check your network connection.'));
4471
+ process.exit(1);
4472
+ }
4473
+ const data = await res.json();
4474
+ const latestVersion = data.version;
4475
+
4476
+ if (compareSemver(VERSION, latestVersion) >= 0) {
4477
+ console.log(` ${color.green('Already on the latest version')} (v${VERSION}).\n`);
4478
+ return;
4479
+ }
4480
+
4481
+ console.log(` New version available: ${color.green('v' + latestVersion)}\n`);
4482
+
4407
4483
  if (installMethod === 'binary') {
4408
- // Binary install: check GitHub releases
4409
- const res = await fetch('https://api.github.com/repos/tova-lang/tova-lang/releases/latest');
4410
- if (!res.ok) {
4411
- console.error(color.red(' Could not reach GitHub. Check your network connection.'));
4412
- process.exit(1);
4484
+ console.log(' Upgrading via binary...');
4485
+
4486
+ // Check GitHub releases for the matching binary
4487
+ const ghRes = await fetch('https://api.github.com/repos/tova-lang/tova-lang/releases/latest');
4488
+ let ghTag = null;
4489
+ if (ghRes.ok) {
4490
+ const ghData = await ghRes.json();
4491
+ const ghVersion = (ghData.tag_name || '').replace(/^v/, '');
4492
+ if (compareSemver(ghVersion, VERSION) > 0) {
4493
+ ghTag = ghData.tag_name;
4494
+ }
4413
4495
  }
4414
- const data = await res.json();
4415
- const latestVersion = (data.tag_name || '').replace(/^v/, '');
4416
4496
 
4417
- if (latestVersion === VERSION) {
4418
- console.log(` ${color.green('Already on the latest version')} (v${VERSION}).\n`);
4497
+ if (!ghTag) {
4498
+ // No newer binary release available — fall back to npm install
4499
+ console.log(` ${color.dim('No binary release for v' + latestVersion + ' yet. Falling back to npm...')}\n`);
4500
+ await npmUpgrade(latestVersion);
4419
4501
  return;
4420
4502
  }
4421
4503
 
4422
- console.log(` New version available: ${color.green('v' + latestVersion)}\n`);
4423
- console.log(' Upgrading via binary...');
4424
-
4425
4504
  // Detect platform
4426
4505
  const platform = process.platform === 'darwin' ? 'darwin' : process.platform === 'linux' ? 'linux' : 'windows';
4427
4506
  const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
4428
4507
  const assetName = `tova-${platform}-${arch}`;
4429
- const downloadUrl = `https://github.com/tova-lang/tova-lang/releases/download/${data.tag_name}/${assetName}.gz`;
4508
+ const downloadUrl = `https://github.com/tova-lang/tova-lang/releases/download/${ghTag}/${assetName}.gz`;
4430
4509
 
4431
4510
  const installDir = join(process.env.HOME || '', '.tova', 'bin');
4432
4511
  const tmpPath = join(installDir, 'tova.download');
4433
4512
  const binPath = join(installDir, 'tova');
4434
4513
 
4435
- // Download compressed binary
4436
- const dlRes = await fetch(downloadUrl);
4437
- if (!dlRes.ok) {
4514
+ // Ensure install directory exists
4515
+ mkdirSync(installDir, { recursive: true });
4516
+
4517
+ // Download compressed binary with progress
4518
+ let dlResult = await downloadWithProgress(downloadUrl, tmpPath);
4519
+ if (!dlResult) {
4438
4520
  // Fall back to uncompressed
4439
- const dlRes2 = await fetch(downloadUrl.replace('.gz', ''));
4440
- if (!dlRes2.ok) {
4441
- console.error(color.red(` Download failed. URL: ${downloadUrl.replace('.gz', '')}`));
4442
- process.exit(1);
4521
+ dlResult = await downloadWithProgress(downloadUrl.replace('.gz', ''), tmpPath);
4522
+ if (!dlResult) {
4523
+ console.log(` ${color.dim('Binary download failed. Falling back to npm...')}\n`);
4524
+ await npmUpgrade(latestVersion);
4525
+ return;
4443
4526
  }
4444
- writeFileSync(tmpPath, new Uint8Array(await dlRes2.arrayBuffer()));
4445
- } else {
4527
+ }
4528
+
4529
+ if (dlResult.compressed) {
4446
4530
  // Decompress gzip
4447
- const compressed = new Uint8Array(await dlRes.arrayBuffer());
4448
- const decompressed = Bun.gunzipSync(compressed);
4531
+ console.log(' Decompressing...');
4532
+ const compressed = readFileSync(tmpPath);
4533
+ const { gunzipSync } = await import('zlib');
4534
+ const decompressed = gunzipSync(compressed);
4449
4535
  writeFileSync(tmpPath, decompressed);
4450
4536
  }
4451
4537
 
4452
4538
  // Make executable
4453
- const { chmodSync } = await import('fs');
4454
4539
  chmodSync(tmpPath, 0o755);
4455
4540
 
4456
4541
  // Verify the new binary works
4457
- const verifyProc = Bun.spawnSync([tmpPath, '--version'], { stdout: 'pipe', stderr: 'pipe' });
4458
- if (verifyProc.exitCode !== 0) {
4542
+ console.log(' Verifying binary...');
4543
+ const { spawnSync } = await import('child_process');
4544
+ const verifyProc = spawnSync(tmpPath, ['--version'], { timeout: 10000 });
4545
+ if (verifyProc.status !== 0) {
4459
4546
  rmSync(tmpPath, { force: true });
4460
- console.error(color.red(' Downloaded binary verification failed.'));
4461
- process.exit(1);
4547
+ console.error(color.red(' Downloaded binary verification failed. Falling back to npm...'));
4548
+ await npmUpgrade(latestVersion);
4549
+ return;
4462
4550
  }
4463
4551
 
4464
4552
  // Atomic rename
4465
- const { renameSync } = await import('fs');
4466
4553
  renameSync(tmpPath, binPath);
4467
4554
 
4468
4555
  console.log(`\n ${color.green('✓')} Upgraded: v${VERSION} -> ${color.bold('v' + latestVersion)}\n`);
4469
4556
  } else {
4470
- // npm/bun install: check npm registry
4471
- const res = await fetch('https://registry.npmjs.org/tova/latest');
4472
- if (!res.ok) {
4473
- console.error(color.red(' Could not reach the npm registry. Check your network connection.'));
4474
- process.exit(1);
4475
- }
4476
- const data = await res.json();
4477
- const latestVersion = data.version;
4478
-
4479
- if (latestVersion === VERSION) {
4480
- console.log(` ${color.green('Already on the latest version')} (v${VERSION}).\n`);
4481
- return;
4482
- }
4483
-
4484
- console.log(` New version available: ${color.green('v' + latestVersion)}\n`);
4485
4557
  console.log(' Upgrading...');
4486
-
4487
- const pm = detectPackageManager();
4488
- const installCmd = pm === 'bun' ? ['bun', ['add', '-g', 'tova@latest']]
4489
- : pm === 'pnpm' ? ['pnpm', ['add', '-g', 'tova@latest']]
4490
- : pm === 'yarn' ? ['yarn', ['global', 'add', 'tova@latest']]
4491
- : ['npm', ['install', '-g', 'tova@latest']];
4492
-
4493
- const proc = spawn(installCmd[0], installCmd[1], { stdio: 'inherit' });
4494
- const exitCode = await new Promise(res => proc.on('close', res));
4495
-
4496
- if (exitCode === 0) {
4497
- console.log(`\n ${color.green('✓')} Upgraded to Tova v${latestVersion}.\n`);
4498
- } else {
4499
- console.error(color.red(`\n Upgrade failed (exit code ${exitCode}).`));
4500
- console.error(` Try manually: ${installCmd[0]} ${installCmd[1].join(' ')}\n`);
4501
- process.exit(1);
4502
- }
4558
+ await npmUpgrade(latestVersion);
4503
4559
  }
4504
4560
  } catch (err) {
4505
4561
  console.error(color.red(` Upgrade failed: ${err.message}`));
@@ -4512,6 +4568,25 @@ async function upgradeCommand() {
4512
4568
  }
4513
4569
  }
4514
4570
 
4571
+ async function npmUpgrade(latestVersion) {
4572
+ const pm = detectPackageManager();
4573
+ const installCmd = pm === 'bun' ? ['bun', ['add', '-g', 'tova@latest']]
4574
+ : pm === 'pnpm' ? ['pnpm', ['add', '-g', 'tova@latest']]
4575
+ : pm === 'yarn' ? ['yarn', ['global', 'add', 'tova@latest']]
4576
+ : ['npm', ['install', '-g', 'tova@latest']];
4577
+
4578
+ const proc = spawn(installCmd[0], installCmd[1], { stdio: 'inherit' });
4579
+ const exitCode = await new Promise(res => proc.on('close', res));
4580
+
4581
+ if (exitCode === 0) {
4582
+ console.log(`\n ${color.green('✓')} Upgraded to Tova v${latestVersion}.\n`);
4583
+ } else {
4584
+ console.error(color.red(`\n Upgrade failed (exit code ${exitCode}).`));
4585
+ console.error(` Try manually: ${installCmd[0]} ${installCmd[1].join(' ')}\n`);
4586
+ process.exit(1);
4587
+ }
4588
+ }
4589
+
4515
4590
  function detectPackageManager() {
4516
4591
  if (typeof Bun !== 'undefined') return 'bun';
4517
4592
  const ua = process.env.npm_config_user_agent || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Tova — a modern programming language that transpiles to JavaScript, unifying frontend and backend",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/embed-runtime.js — do not edit
2
- export const VERSION = "0.4.0";
2
+ export const VERSION = "0.4.2";