tova 0.9.8 → 0.9.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/bin/tova.js +119 -26
- package/package.json +1 -1
- package/src/codegen/codegen.js +2 -0
- package/src/codegen/server-codegen.js +38 -2
- package/src/parser/auth-parser.js +2 -2
- package/src/version.js +1 -1
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
|
-
|
|
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
|
|
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 =
|
|
1520
|
+
reloadServer = await _compatServe({
|
|
1442
1521
|
port: actualReloadPort,
|
|
1443
1522
|
fetch(req) {
|
|
1444
1523
|
return handleReloadFetch(req);
|
|
@@ -1489,7 +1568,7 @@ async function devServer(args) {
|
|
|
1489
1568
|
for (const [dir, files] of dirGroups) {
|
|
1490
1569
|
const dirName = basename(dir) === '.' ? 'app' : basename(dir);
|
|
1491
1570
|
try {
|
|
1492
|
-
const result = mergeDirectory(dir, srcDir, { strict: buildStrict, strictSecurity: buildStrictSecurity });
|
|
1571
|
+
const result = mergeDirectory(dir, srcDir, { strict: buildStrict, strictSecurity: buildStrictSecurity, isDev: true });
|
|
1493
1572
|
if (!result) continue;
|
|
1494
1573
|
|
|
1495
1574
|
const { output, single } = result;
|
|
@@ -1584,7 +1663,7 @@ async function devServer(args) {
|
|
|
1584
1663
|
|
|
1585
1664
|
const child = spawn('bun', ['run', sf.path], {
|
|
1586
1665
|
stdio: 'inherit',
|
|
1587
|
-
env: { ...process.env, [envKey]: String(port), PORT: String(port) },
|
|
1666
|
+
env: { ...process.env, [envKey]: String(port), PORT: String(port), __TOVA_HMR_STATE_PATH: join(outDir, '.hmr-state.json') },
|
|
1588
1667
|
});
|
|
1589
1668
|
|
|
1590
1669
|
child.on('error', (err) => {
|
|
@@ -1624,7 +1703,7 @@ async function devServer(args) {
|
|
|
1624
1703
|
'.map': 'application/json',
|
|
1625
1704
|
};
|
|
1626
1705
|
|
|
1627
|
-
const staticServer =
|
|
1706
|
+
const staticServer = await _compatServe({
|
|
1628
1707
|
port: basePort,
|
|
1629
1708
|
async fetch(req) {
|
|
1630
1709
|
const url = new URL(req.url);
|
|
@@ -1734,7 +1813,7 @@ async function devServer(args) {
|
|
|
1734
1813
|
|
|
1735
1814
|
for (const [dir, files] of rebuildDirGroups) {
|
|
1736
1815
|
const dirName = basename(dir) === '.' ? 'app' : basename(dir);
|
|
1737
|
-
const result = mergeDirectory(dir, srcDir, { strict: buildStrict, strictSecurity: buildStrictSecurity });
|
|
1816
|
+
const result = mergeDirectory(dir, srcDir, { strict: buildStrict, strictSecurity: buildStrictSecurity, isDev: true });
|
|
1738
1817
|
if (!result) continue;
|
|
1739
1818
|
|
|
1740
1819
|
const { output, single } = result;
|
|
@@ -1811,7 +1890,7 @@ async function devServer(args) {
|
|
|
1811
1890
|
const port = basePort + rebuildPortOffset;
|
|
1812
1891
|
const child = spawn('bun', ['run', serverPath], {
|
|
1813
1892
|
stdio: 'inherit',
|
|
1814
|
-
env: { ...process.env, PORT: String(port) },
|
|
1893
|
+
env: { ...process.env, PORT: String(port), __TOVA_HMR_STATE_PATH: join(outDir, '.hmr-state.json') },
|
|
1815
1894
|
});
|
|
1816
1895
|
processes.push({ child, label: 'server', port });
|
|
1817
1896
|
rebuildPortOffset++;
|
|
@@ -1842,6 +1921,9 @@ async function devServer(args) {
|
|
|
1842
1921
|
for (const p of processes) {
|
|
1843
1922
|
try { p.child.kill('SIGKILL'); } catch {}
|
|
1844
1923
|
}
|
|
1924
|
+
// Clean up HMR state file for fresh start next time
|
|
1925
|
+
const hmrPath = join(outDir, '.hmr-state.json');
|
|
1926
|
+
try { if (existsSync(hmrPath)) rmSync(hmrPath); } catch {}
|
|
1845
1927
|
process.exit(0);
|
|
1846
1928
|
});
|
|
1847
1929
|
|
|
@@ -3373,8 +3455,8 @@ tova add github.com/yourname/${projectName}
|
|
|
3373
3455
|
|
|
3374
3456
|
// git init (silent, only if git is available)
|
|
3375
3457
|
try {
|
|
3376
|
-
const gitProc =
|
|
3377
|
-
if (gitProc.exitCode === 0) {
|
|
3458
|
+
const gitProc = _compatSpawnSync('git', ['init'], { cwd: projectDir, stdout: 'pipe', stderr: 'pipe' });
|
|
3459
|
+
if ((gitProc.exitCode ?? gitProc.status) === 0) {
|
|
3378
3460
|
console.log(` ${color.green('✓')} Initialized git repository`);
|
|
3379
3461
|
}
|
|
3380
3462
|
} catch {}
|
|
@@ -4162,6 +4244,9 @@ function hasNpmImports(code) {
|
|
|
4162
4244
|
}
|
|
4163
4245
|
|
|
4164
4246
|
async function bundleClientCode(clientCode, srcDir) {
|
|
4247
|
+
if (!_hasBun) {
|
|
4248
|
+
throw new Error('Client bundling with npm imports requires Bun. Install from https://bun.sh and run with: bun tova build --production');
|
|
4249
|
+
}
|
|
4165
4250
|
const tmpDir = join(srcDir, '.tova-out', '.tmp-bundle');
|
|
4166
4251
|
try {
|
|
4167
4252
|
mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4897,7 +4982,10 @@ async function productionBuild(srcDir, outDir, isStatic = false) {
|
|
|
4897
4982
|
const allSharedCode = sharedParts.join('\n');
|
|
4898
4983
|
|
|
4899
4984
|
// Generate content hash for cache busting
|
|
4900
|
-
const hashCode = (s) =>
|
|
4985
|
+
const hashCode = (s) => {
|
|
4986
|
+
if (_hasBun) return Bun.hash(s).toString(16).slice(0, 12);
|
|
4987
|
+
return _cryptoHash('md5').update(s).digest('hex').slice(0, 12);
|
|
4988
|
+
};
|
|
4901
4989
|
|
|
4902
4990
|
// Write server bundle
|
|
4903
4991
|
if (allServerCode.trim()) {
|
|
@@ -5578,7 +5666,7 @@ function collectExports(ast, filename) {
|
|
|
5578
5666
|
return { publicExports, allNames };
|
|
5579
5667
|
}
|
|
5580
5668
|
|
|
5581
|
-
function compileWithImports(source, filename, srcDir) {
|
|
5669
|
+
function compileWithImports(source, filename, srcDir, options = {}) {
|
|
5582
5670
|
if (compilationCache.has(filename)) {
|
|
5583
5671
|
return compilationCache.get(filename);
|
|
5584
5672
|
}
|
|
@@ -5683,7 +5771,7 @@ function compileWithImports(source, filename, srcDir) {
|
|
|
5683
5771
|
}
|
|
5684
5772
|
}
|
|
5685
5773
|
|
|
5686
|
-
const codegen = new CodeGenerator(ast, filename);
|
|
5774
|
+
const codegen = new CodeGenerator(ast, filename, { isDev: options.isDev });
|
|
5687
5775
|
const output = codegen.generate();
|
|
5688
5776
|
compilationCache.set(filename, output);
|
|
5689
5777
|
return output;
|
|
@@ -5819,7 +5907,7 @@ function mergeDirectory(dir, srcDir, options = {}) {
|
|
|
5819
5907
|
// Single file — use existing per-file compilation
|
|
5820
5908
|
const file = tovaFiles[0];
|
|
5821
5909
|
const source = readFileSync(file, 'utf-8');
|
|
5822
|
-
return { output: compileWithImports(source, file, srcDir), files: [file], single: true };
|
|
5910
|
+
return { output: compileWithImports(source, file, srcDir, { isDev: options.isDev }), files: [file], single: true };
|
|
5823
5911
|
}
|
|
5824
5912
|
|
|
5825
5913
|
// Parse all files in the directory
|
|
@@ -5935,7 +6023,7 @@ function mergeDirectory(dir, srcDir, options = {}) {
|
|
|
5935
6023
|
}
|
|
5936
6024
|
|
|
5937
6025
|
// Run codegen on merged AST
|
|
5938
|
-
const codegen = new CodeGenerator(mergedAST, dir);
|
|
6026
|
+
const codegen = new CodeGenerator(mergedAST, dir, { isDev: options.isDev });
|
|
5939
6027
|
const output = codegen.generate();
|
|
5940
6028
|
|
|
5941
6029
|
// Collect source content from all files for source maps
|
|
@@ -6024,12 +6112,13 @@ async function doctorCommand() {
|
|
|
6024
6112
|
|
|
6025
6113
|
// 2. Bun availability
|
|
6026
6114
|
try {
|
|
6027
|
-
const bunProc =
|
|
6028
|
-
const bunVer = bunProc.stdout.toString().trim();
|
|
6029
|
-
if (bunProc.exitCode === 0 && bunVer) {
|
|
6115
|
+
const bunProc = _compatSpawnSync('bun', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
6116
|
+
const bunVer = (bunProc.stdout || '').toString().trim();
|
|
6117
|
+
if ((bunProc.exitCode ?? bunProc.status) === 0 && bunVer) {
|
|
6030
6118
|
const major = parseInt(bunVer.split('.')[0], 10);
|
|
6031
6119
|
if (major >= 1) {
|
|
6032
|
-
|
|
6120
|
+
const whichProc = _compatSpawnSync('which', ['bun'], { stdout: 'pipe', stderr: 'pipe' });
|
|
6121
|
+
pass(`Bun v${bunVer}`, (whichProc.stdout || '').toString().trim());
|
|
6033
6122
|
} else {
|
|
6034
6123
|
warn(`Bun v${bunVer}`, 'Bun >= 1.0 recommended');
|
|
6035
6124
|
}
|
|
@@ -6077,9 +6166,9 @@ async function doctorCommand() {
|
|
|
6077
6166
|
|
|
6078
6167
|
// 5. git
|
|
6079
6168
|
try {
|
|
6080
|
-
const gitProc =
|
|
6081
|
-
if (gitProc.exitCode === 0) {
|
|
6082
|
-
const gitVer = gitProc.stdout.toString().trim();
|
|
6169
|
+
const gitProc = _compatSpawnSync('git', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
6170
|
+
if ((gitProc.exitCode ?? gitProc.status) === 0) {
|
|
6171
|
+
const gitVer = (gitProc.stdout || '').toString().trim();
|
|
6083
6172
|
pass('git available', gitVer);
|
|
6084
6173
|
} else {
|
|
6085
6174
|
warn('git', 'not found');
|
|
@@ -6323,6 +6412,10 @@ function detectInstallMethod() {
|
|
|
6323
6412
|
const execPath = process.execPath || process.argv[0];
|
|
6324
6413
|
const scriptPath = process.argv[1] || '';
|
|
6325
6414
|
if (execPath.includes('.tova/bin') || scriptPath.includes('.tova/')) return 'binary';
|
|
6415
|
+
// Check if ~/.tova/bin/tova exists — indicates binary/wrapper install even if
|
|
6416
|
+
// the wrapper points to a local repo checkout
|
|
6417
|
+
const wrapperPath = join(process.env.HOME || '', '.tova', 'bin', 'tova');
|
|
6418
|
+
if (existsSync(wrapperPath)) return 'binary';
|
|
6326
6419
|
return 'npm';
|
|
6327
6420
|
}
|
|
6328
6421
|
|
|
@@ -6584,8 +6677,8 @@ async function infoCommand() {
|
|
|
6584
6677
|
// Bun version
|
|
6585
6678
|
let bunVersion = 'not found';
|
|
6586
6679
|
try {
|
|
6587
|
-
const proc =
|
|
6588
|
-
bunVersion = proc.stdout.toString().trim();
|
|
6680
|
+
const proc = _compatSpawnSync('bun', ['--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
6681
|
+
bunVersion = (proc.stdout || '').toString().trim();
|
|
6589
6682
|
} catch {}
|
|
6590
6683
|
console.log(` Runtime: Bun v${bunVersion}`);
|
|
6591
6684
|
console.log(` Platform: ${process.platform} ${process.arch}`);
|
package/package.json
CHANGED
package/src/codegen/codegen.js
CHANGED
|
@@ -59,6 +59,7 @@ export class CodeGenerator {
|
|
|
59
59
|
this.ast = ast;
|
|
60
60
|
this.filename = filename;
|
|
61
61
|
this._sourceMaps = options.sourceMaps !== false; // default true; pass false for REPL/check
|
|
62
|
+
this._isDev = options.isDev || false;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// Group blocks by name (null name = "default")
|
|
@@ -250,6 +251,7 @@ export class CodeGenerator {
|
|
|
250
251
|
for (const [name, blocks] of serverGroups) {
|
|
251
252
|
const gen = new (getServerCodegen())();
|
|
252
253
|
gen._sourceMapsEnabled = this._sourceMaps;
|
|
254
|
+
gen._isDev = this._isDev;
|
|
253
255
|
const key = name || 'default';
|
|
254
256
|
// Build peer blocks map (all named blocks except self)
|
|
255
257
|
let peerBlocks = null;
|
|
@@ -240,6 +240,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
240
240
|
const functions = [];
|
|
241
241
|
const middlewares = [];
|
|
242
242
|
const otherStatements = [];
|
|
243
|
+
const hmrVarNames = [];
|
|
243
244
|
let healthPath = null;
|
|
244
245
|
let healthChecks = null;
|
|
245
246
|
let corsConfig = null;
|
|
@@ -2142,8 +2143,29 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2142
2143
|
// ════════════════════════════════════════════════════════════
|
|
2143
2144
|
// 13. Other statements + Server Functions
|
|
2144
2145
|
// ════════════════════════════════════════════════════════════
|
|
2146
|
+
if (this._isDev) {
|
|
2147
|
+
lines.push('// ── HMR State ──');
|
|
2148
|
+
lines.push('const __hmrStatePath = process.env.__TOVA_HMR_STATE_PATH || "";');
|
|
2149
|
+
lines.push('let __hmrState = {};');
|
|
2150
|
+
lines.push('if (__hmrStatePath) { try { __hmrState = JSON.parse(require("fs").readFileSync(__hmrStatePath, "utf-8")); } catch {} }');
|
|
2151
|
+
lines.push('');
|
|
2152
|
+
}
|
|
2145
2153
|
for (const stmt of otherStatements) {
|
|
2146
|
-
|
|
2154
|
+
// HMR wrapping: new variable declarations get restored from __hmrState
|
|
2155
|
+
const isHmrEligible = this._isDev && stmt.targets && stmt.targets.length === 1 &&
|
|
2156
|
+
stmt.values && stmt.values.length === 1 && typeof stmt.targets[0] === 'string' &&
|
|
2157
|
+
(stmt.type === 'Assignment' ? !this.isDeclared(stmt.targets[0]) : stmt.type === 'VarDeclaration');
|
|
2158
|
+
if (isHmrEligible) {
|
|
2159
|
+
const name = stmt.targets[0];
|
|
2160
|
+
hmrVarNames.push(name);
|
|
2161
|
+
this.declareVar(name);
|
|
2162
|
+
if (stmt.isPublic) this._userDefinedNames.add(name);
|
|
2163
|
+
const initExpr = this.genExpression(stmt.values[0]);
|
|
2164
|
+
const keyword = stmt.type === 'VarDeclaration' ? 'let' : 'const';
|
|
2165
|
+
lines.push(`${keyword} ${name} = (${JSON.stringify(name)} in __hmrState) ? __hmrState[${JSON.stringify(name)}] : ${initExpr};`);
|
|
2166
|
+
} else {
|
|
2167
|
+
lines.push(this.generateStatement(stmt));
|
|
2168
|
+
}
|
|
2147
2169
|
}
|
|
2148
2170
|
|
|
2149
2171
|
if (functions.length > 0) {
|
|
@@ -3688,10 +3710,24 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
3688
3710
|
// 24. Graceful Shutdown — on_stop hooks (F3) + clearInterval (F8)
|
|
3689
3711
|
// ════════════════════════════════════════════════════════════
|
|
3690
3712
|
lines.push('// ── Graceful Shutdown ──');
|
|
3713
|
+
// HMR state-save helper (emitted as first action in __shutdown)
|
|
3714
|
+
const hmrSaveLines = [];
|
|
3715
|
+
if (this._isDev && hmrVarNames.length > 0) {
|
|
3716
|
+
const entries = hmrVarNames.map(n => `${JSON.stringify(n)}: ${n}`).join(', ');
|
|
3717
|
+
hmrSaveLines.push(` try { require("fs").writeFileSync(__hmrStatePath, JSON.stringify({ ${entries} })); } catch {}`);
|
|
3718
|
+
}
|
|
3691
3719
|
if (isFastMode) {
|
|
3692
|
-
|
|
3720
|
+
if (hmrSaveLines.length > 0) {
|
|
3721
|
+
lines.push('function __shutdown() {');
|
|
3722
|
+
for (const l of hmrSaveLines) lines.push(l);
|
|
3723
|
+
lines.push(' __server.stop(); process.exit(0);');
|
|
3724
|
+
lines.push('}');
|
|
3725
|
+
} else {
|
|
3726
|
+
lines.push('function __shutdown() { __server.stop(); process.exit(0); }');
|
|
3727
|
+
}
|
|
3693
3728
|
} else {
|
|
3694
3729
|
lines.push('async function __shutdown() {');
|
|
3730
|
+
for (const l of hmrSaveLines) lines.push(l);
|
|
3695
3731
|
lines.push(` console.log(\`Tova server${label} shutting down...\`);`);
|
|
3696
3732
|
lines.push(' __shuttingDown = true;');
|
|
3697
3733
|
lines.push(' __server.stop();');
|
|
@@ -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
|
|
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 (
|
|
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.
|
|
2
|
+
export const VERSION = "0.9.11";
|