tova 0.9.8 → 0.9.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/bin/tova.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { resolve, basename, dirname, join, relative, sep, extname } from 'path';
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync, rmSync, chmodSync, renameSync, watch as fsWatch } from 'fs';
5
- import { spawn } from 'child_process';
5
+ import { spawn, spawnSync as _spawnSync } from 'child_process';
6
6
  import { createHash as _cryptoHash } from 'crypto';
7
7
  import { createRequire as _createRequire } from 'module';
8
8
  import { Lexer } from '../src/lexer/lexer.js';
@@ -25,6 +25,78 @@ import { addToSection, removeFromSection } from '../src/config/edit-toml.js';
25
25
  import { stringifyTOML } from '../src/config/toml.js';
26
26
 
27
27
  import { VERSION } from '../src/version.js';
28
+ import { createServer as _createHttpServer } from 'http';
29
+
30
+ const _hasBun = typeof Bun !== 'undefined';
31
+
32
+ // ─── Compat: Bun.serve() fallback to Node http.createServer ─
33
+ function _compatServe({ port, fetch: fetchHandler }) {
34
+ if (_hasBun) {
35
+ return Bun.serve({ port, fetch: fetchHandler });
36
+ }
37
+ // Node.js fallback using http.createServer
38
+ return new Promise((resolve, reject) => {
39
+ const server = _createHttpServer(async (req, res) => {
40
+ try {
41
+ const url = `http://localhost:${port}${req.url}`;
42
+ const headers = new Headers();
43
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
44
+ headers.append(req.rawHeaders[i], req.rawHeaders[i + 1]);
45
+ }
46
+ const request = new Request(url, {
47
+ method: req.method,
48
+ headers,
49
+ ...(req.method !== 'GET' && req.method !== 'HEAD' ? { body: req, duplex: 'half' } : {}),
50
+ });
51
+ const response = await fetchHandler(request);
52
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
53
+ if (response.body) {
54
+ const reader = response.body.getReader();
55
+ const pump = async () => {
56
+ while (true) {
57
+ const { done, value } = await reader.read();
58
+ if (done) { res.end(); return; }
59
+ res.write(value);
60
+ }
61
+ };
62
+ pump().catch(() => res.end());
63
+ } else {
64
+ const buf = Buffer.from(await response.arrayBuffer());
65
+ res.end(buf);
66
+ }
67
+ } catch (err) {
68
+ res.writeHead(500);
69
+ res.end('Internal Server Error');
70
+ }
71
+ });
72
+ server.listen(port, () => resolve(server));
73
+ server.on('error', reject);
74
+ });
75
+ }
76
+
77
+ // ─── Compat: Bun.spawnSync fallback to child_process.spawnSync ─
78
+ // Accepts Bun-style opts: { stdout: 'pipe', stderr: 'pipe', cwd, timeout }
79
+ function _compatSpawnSync(cmd, args, opts) {
80
+ if (_hasBun) return Bun.spawnSync([cmd, ...args], opts);
81
+ // Translate Bun-style stdout/stderr to Node-style stdio
82
+ const nodeOpts = { ...opts };
83
+ if (!nodeOpts.stdio) {
84
+ nodeOpts.stdio = [
85
+ 'pipe',
86
+ nodeOpts.stdout === 'pipe' ? 'pipe' : (nodeOpts.stdout || 'pipe'),
87
+ nodeOpts.stderr === 'pipe' ? 'pipe' : (nodeOpts.stderr || 'pipe'),
88
+ ];
89
+ }
90
+ delete nodeOpts.stdout;
91
+ delete nodeOpts.stderr;
92
+ const result = _spawnSync(cmd, args, nodeOpts);
93
+ return {
94
+ ...result,
95
+ exitCode: result.status,
96
+ stdout: result.stdout ? (typeof result.stdout === 'string' ? result.stdout : result.stdout.toString()) : '',
97
+ stderr: result.stderr ? (typeof result.stderr === 'string' ? result.stderr : result.stderr.toString()) : '',
98
+ };
99
+ }
28
100
 
29
101
  // ─── CLI Color Helpers ──────────────────────────────────────
30
102
  const isTTY = process.stdout?.isTTY;
@@ -1417,10 +1489,17 @@ function cleanBuild(args) {
1417
1489
 
1418
1490
  async function devServer(args) {
1419
1491
  const config = resolveConfig(process.cwd());
1420
- const explicitSrc = args.filter(a => !a.startsWith('--'))[0];
1421
- const srcDir = resolve(explicitSrc || config.project.entry || '.');
1492
+ // Parse --port value first, then filter positional args (skip flag values)
1422
1493
  const explicitPort = args.find((_, i, a) => a[i - 1] === '--port');
1423
- const basePort = parseInt(explicitPort || config.dev.port || '3000');
1494
+ const basePort = parseInt(explicitPort || config.dev?.port || '3000');
1495
+ const flagsWithValues = new Set(['--port']);
1496
+ const positional = [];
1497
+ for (let i = 0; i < args.length; i++) {
1498
+ if (args[i].startsWith('--')) { if (flagsWithValues.has(args[i])) i++; continue; }
1499
+ positional.push(args[i]);
1500
+ }
1501
+ const explicitSrc = positional[0];
1502
+ const srcDir = resolve(explicitSrc || config.project?.entry || '.');
1424
1503
  const buildStrict = args.includes('--strict');
1425
1504
  const buildStrictSecurity = args.includes('--strict-security');
1426
1505
 
@@ -1438,7 +1517,7 @@ async function devServer(args) {
1438
1517
  let actualReloadPort = reloadPort;
1439
1518
  for (let attempt = 0; attempt < 10; attempt++) {
1440
1519
  try {
1441
- reloadServer = Bun.serve({
1520
+ reloadServer = await _compatServe({
1442
1521
  port: actualReloadPort,
1443
1522
  fetch(req) {
1444
1523
  return handleReloadFetch(req);
@@ -1624,7 +1703,7 @@ async function devServer(args) {
1624
1703
  '.map': 'application/json',
1625
1704
  };
1626
1705
 
1627
- const staticServer = Bun.serve({
1706
+ const staticServer = await _compatServe({
1628
1707
  port: basePort,
1629
1708
  async fetch(req) {
1630
1709
  const url = new URL(req.url);
@@ -3373,8 +3452,8 @@ tova add github.com/yourname/${projectName}
3373
3452
 
3374
3453
  // git init (silent, only if git is available)
3375
3454
  try {
3376
- const gitProc = Bun.spawnSync(['git', 'init'], { cwd: projectDir, stdout: 'pipe', stderr: 'pipe' });
3377
- if (gitProc.exitCode === 0) {
3455
+ const gitProc = _compatSpawnSync('git', ['init'], { cwd: projectDir, stdout: 'pipe', stderr: 'pipe' });
3456
+ if ((gitProc.exitCode ?? gitProc.status) === 0) {
3378
3457
  console.log(` ${color.green('✓')} Initialized git repository`);
3379
3458
  }
3380
3459
  } catch {}
@@ -4162,6 +4241,9 @@ function hasNpmImports(code) {
4162
4241
  }
4163
4242
 
4164
4243
  async function bundleClientCode(clientCode, srcDir) {
4244
+ if (!_hasBun) {
4245
+ throw new Error('Client bundling with npm imports requires Bun. Install from https://bun.sh and run with: bun tova build --production');
4246
+ }
4165
4247
  const tmpDir = join(srcDir, '.tova-out', '.tmp-bundle');
4166
4248
  try {
4167
4249
  mkdirSync(tmpDir, { recursive: true });
@@ -4897,7 +4979,10 @@ async function productionBuild(srcDir, outDir, isStatic = false) {
4897
4979
  const allSharedCode = sharedParts.join('\n');
4898
4980
 
4899
4981
  // Generate content hash for cache busting
4900
- const hashCode = (s) => Bun.hash(s).toString(16).slice(0, 12);
4982
+ const hashCode = (s) => {
4983
+ if (_hasBun) return Bun.hash(s).toString(16).slice(0, 12);
4984
+ return _cryptoHash('md5').update(s).digest('hex').slice(0, 12);
4985
+ };
4901
4986
 
4902
4987
  // Write server bundle
4903
4988
  if (allServerCode.trim()) {
@@ -6024,12 +6109,13 @@ async function doctorCommand() {
6024
6109
 
6025
6110
  // 2. Bun availability
6026
6111
  try {
6027
- const bunProc = Bun.spawnSync(['bun', '--version']);
6028
- const bunVer = bunProc.stdout.toString().trim();
6029
- if (bunProc.exitCode === 0 && bunVer) {
6112
+ const bunProc = _compatSpawnSync('bun', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
6113
+ const bunVer = (bunProc.stdout || '').toString().trim();
6114
+ if ((bunProc.exitCode ?? bunProc.status) === 0 && bunVer) {
6030
6115
  const major = parseInt(bunVer.split('.')[0], 10);
6031
6116
  if (major >= 1) {
6032
- pass(`Bun v${bunVer}`, Bun.spawnSync(['which', 'bun']).stdout.toString().trim());
6117
+ const whichProc = _compatSpawnSync('which', ['bun'], { stdout: 'pipe', stderr: 'pipe' });
6118
+ pass(`Bun v${bunVer}`, (whichProc.stdout || '').toString().trim());
6033
6119
  } else {
6034
6120
  warn(`Bun v${bunVer}`, 'Bun >= 1.0 recommended');
6035
6121
  }
@@ -6077,9 +6163,9 @@ async function doctorCommand() {
6077
6163
 
6078
6164
  // 5. git
6079
6165
  try {
6080
- const gitProc = Bun.spawnSync(['git', '--version']);
6081
- if (gitProc.exitCode === 0) {
6082
- const gitVer = gitProc.stdout.toString().trim();
6166
+ const gitProc = _compatSpawnSync('git', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
6167
+ if ((gitProc.exitCode ?? gitProc.status) === 0) {
6168
+ const gitVer = (gitProc.stdout || '').toString().trim();
6083
6169
  pass('git available', gitVer);
6084
6170
  } else {
6085
6171
  warn('git', 'not found');
@@ -6323,6 +6409,10 @@ function detectInstallMethod() {
6323
6409
  const execPath = process.execPath || process.argv[0];
6324
6410
  const scriptPath = process.argv[1] || '';
6325
6411
  if (execPath.includes('.tova/bin') || scriptPath.includes('.tova/')) return 'binary';
6412
+ // Check if ~/.tova/bin/tova exists — indicates binary/wrapper install even if
6413
+ // the wrapper points to a local repo checkout
6414
+ const wrapperPath = join(process.env.HOME || '', '.tova', 'bin', 'tova');
6415
+ if (existsSync(wrapperPath)) return 'binary';
6326
6416
  return 'npm';
6327
6417
  }
6328
6418
 
@@ -6584,8 +6674,8 @@ async function infoCommand() {
6584
6674
  // Bun version
6585
6675
  let bunVersion = 'not found';
6586
6676
  try {
6587
- const proc = Bun.spawnSync(['bun', '--version']);
6588
- bunVersion = proc.stdout.toString().trim();
6677
+ const proc = _compatSpawnSync('bun', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
6678
+ bunVersion = (proc.stdout || '').toString().trim();
6589
6679
  } catch {}
6590
6680
  console.log(` Runtime: Bun v${bunVersion}`);
6591
6681
  console.log(` Platform: ${process.platform} ${process.arch}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
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",
@@ -5,7 +5,7 @@ import { TokenType } from '../lexer/tokens.js';
5
5
  import * as AST from './ast.js';
6
6
  import { AuthConfigField, AuthProviderDeclaration, AuthHookDeclaration, AuthProtectedRoute } from './auth-ast.js';
7
7
 
8
- const CONFIG_KEY_TOKENS = new Set([
8
+ const AUTH_CONFIG_KEY_TOKENS = new Set([
9
9
  TokenType.IDENTIFIER, TokenType.TYPE, TokenType.STORE,
10
10
  TokenType.FN, TokenType.MATCH, TokenType.IF,
11
11
  ]);
@@ -15,7 +15,7 @@ export function installAuthParser(ParserClass) {
15
15
  ParserClass.prototype._authParserInstalled = true;
16
16
 
17
17
  ParserClass.prototype._expectAuthConfigKey = function(context) {
18
- if (CONFIG_KEY_TOKENS.has(this.current().type)) {
18
+ if (AUTH_CONFIG_KEY_TOKENS.has(this.current().type)) {
19
19
  return this.advance().value;
20
20
  }
21
21
  this.error(`Expected ${context} config key`);
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.9.8";
2
+ export const VERSION = "0.9.10";