tova 0.10.3 → 0.11.12
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 +93 -6604
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +3 -812
- package/src/analyzer/auth-analyzer.js +123 -0
- package/src/analyzer/cli-analyzer.js +91 -0
- package/src/analyzer/concurrency-analyzer.js +126 -0
- package/src/analyzer/edge-analyzer.js +215 -0
- package/src/analyzer/security-analyzer.js +297 -0
- package/src/cli/build.js +1044 -0
- package/src/cli/check.js +154 -0
- package/src/cli/compile.js +815 -0
- package/src/cli/completions.js +192 -0
- package/src/cli/deploy.js +15 -0
- package/src/cli/dev.js +612 -0
- package/src/cli/doctor.js +130 -0
- package/src/cli/format.js +52 -0
- package/src/cli/info.js +72 -0
- package/src/cli/migrate.js +386 -0
- package/src/cli/new.js +1435 -0
- package/src/cli/package.js +452 -0
- package/src/cli/repl.js +437 -0
- package/src/cli/run.js +137 -0
- package/src/cli/test.js +255 -0
- package/src/cli/upgrade.js +261 -0
- package/src/cli/utils.js +190 -0
- package/src/codegen/codegen.js +51 -17
- package/src/registry/plugins/auth-plugin.js +13 -2
- package/src/registry/plugins/cli-plugin.js +13 -2
- package/src/registry/plugins/concurrency-plugin.js +4 -0
- package/src/registry/plugins/edge-plugin.js +13 -2
- package/src/registry/plugins/security-plugin.js +13 -2
- package/src/version.js +1 -1
package/src/cli/check.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/cli/check.js — Type-checking command
|
|
2
|
+
import { resolve, dirname, relative } from 'path';
|
|
3
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
4
|
+
import { Lexer } from '../lexer/lexer.js';
|
|
5
|
+
import { Parser } from '../parser/parser.js';
|
|
6
|
+
import { Analyzer } from '../analyzer/analyzer.js';
|
|
7
|
+
import { richError, DiagnosticFormatter, formatSummary } from '../diagnostics/formatter.js';
|
|
8
|
+
import { getExplanation, lookupCode } from '../diagnostics/error-codes.js';
|
|
9
|
+
import { generateSecurityScorecard } from '../diagnostics/security-scorecard.js';
|
|
10
|
+
import { findFiles } from './utils.js';
|
|
11
|
+
|
|
12
|
+
async function checkProject(args) {
|
|
13
|
+
const checkStrict = args.includes('--strict');
|
|
14
|
+
const checkStrictSecurity = args.includes('--strict-security');
|
|
15
|
+
const isVerbose = args.includes('--verbose');
|
|
16
|
+
const isQuiet = args.includes('--quiet');
|
|
17
|
+
|
|
18
|
+
// --explain <code>: show explanation for a specific error code inline with check output
|
|
19
|
+
const explainIdx = args.indexOf('--explain');
|
|
20
|
+
const explainCode = explainIdx >= 0 ? args[explainIdx + 1] : null;
|
|
21
|
+
if (explainCode) {
|
|
22
|
+
// If --explain is used standalone, just show the explanation
|
|
23
|
+
const info = lookupCode(explainCode);
|
|
24
|
+
if (!info) {
|
|
25
|
+
console.error(`Unknown error code: ${explainCode}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const explanation = getExplanation(explainCode);
|
|
29
|
+
console.log(`\n ${explainCode}: ${info.title} [${info.category}]\n`);
|
|
30
|
+
if (explanation) {
|
|
31
|
+
console.log(explanation);
|
|
32
|
+
} else {
|
|
33
|
+
console.log(` No detailed explanation available yet for ${explainCode}.\n`);
|
|
34
|
+
}
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const explicitSrc = args.filter(a => !a.startsWith('--'))[0];
|
|
39
|
+
const srcPath = resolve(explicitSrc || '.');
|
|
40
|
+
|
|
41
|
+
// Support both single file and directory arguments
|
|
42
|
+
let tovaFiles;
|
|
43
|
+
if (existsSync(srcPath) && statSync(srcPath).isFile()) {
|
|
44
|
+
tovaFiles = srcPath.endsWith('.tova') ? [srcPath] : [];
|
|
45
|
+
} else {
|
|
46
|
+
tovaFiles = findFiles(srcPath, '.tova');
|
|
47
|
+
}
|
|
48
|
+
const srcDir = existsSync(srcPath) && statSync(srcPath).isFile() ? dirname(srcPath) : srcPath;
|
|
49
|
+
if (tovaFiles.length === 0) {
|
|
50
|
+
console.error('No .tova files found');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let totalErrors = 0;
|
|
55
|
+
let totalWarnings = 0;
|
|
56
|
+
const seenCodes = new Set();
|
|
57
|
+
let _checkScorecardData = null;
|
|
58
|
+
const _allCheckWarnings = [];
|
|
59
|
+
|
|
60
|
+
for (const file of tovaFiles) {
|
|
61
|
+
const relPath = relative(srcDir, file);
|
|
62
|
+
const start = Date.now();
|
|
63
|
+
try {
|
|
64
|
+
const source = readFileSync(file, 'utf-8');
|
|
65
|
+
const lexer = new Lexer(source, file);
|
|
66
|
+
const tokens = lexer.tokenize();
|
|
67
|
+
const parser = new Parser(tokens, file);
|
|
68
|
+
const ast = parser.parse();
|
|
69
|
+
const analyzer = new Analyzer(ast, file, { strict: checkStrict, strictSecurity: checkStrictSecurity, tolerant: true });
|
|
70
|
+
const result = analyzer.analyze();
|
|
71
|
+
|
|
72
|
+
const errors = result.errors || [];
|
|
73
|
+
const warnings = result.warnings || [];
|
|
74
|
+
totalErrors += errors.length;
|
|
75
|
+
totalWarnings += warnings.length;
|
|
76
|
+
_allCheckWarnings.push(...warnings);
|
|
77
|
+
|
|
78
|
+
// Collect security info for scorecard
|
|
79
|
+
if (!_checkScorecardData) {
|
|
80
|
+
const hasServer = ast.body.some(n => n.type === 'ServerBlock');
|
|
81
|
+
const hasEdge = ast.body.some(n => n.type === 'EdgeBlock');
|
|
82
|
+
if (hasServer || hasEdge) {
|
|
83
|
+
const secNode = ast.body.find(n => n.type === 'SecurityBlock');
|
|
84
|
+
let secCfg = null;
|
|
85
|
+
if (secNode) {
|
|
86
|
+
secCfg = {};
|
|
87
|
+
for (const child of secNode.body || []) {
|
|
88
|
+
if (child.type === 'AuthDeclaration') secCfg.auth = { authType: child.authType || 'jwt', storage: child.config?.storage?.value };
|
|
89
|
+
else if (child.type === 'CsrfDeclaration') secCfg.csrf = { enabled: child.config?.enabled?.value !== false };
|
|
90
|
+
else if (child.type === 'RateLimitDeclaration') secCfg.rateLimit = { max: child.config?.max?.value };
|
|
91
|
+
else if (child.type === 'CspDeclaration') secCfg.csp = { default_src: true };
|
|
92
|
+
else if (child.type === 'CorsDeclaration') {
|
|
93
|
+
const origins = child.config?.origins;
|
|
94
|
+
secCfg.cors = { origins: origins ? (origins.elements || []).map(e => e.value) : [] };
|
|
95
|
+
}
|
|
96
|
+
else if (child.type === 'AuditDeclaration') secCfg.audit = { events: ['auth'] };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
_checkScorecardData = { securityConfig: secCfg, hasServer, hasEdge };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (errors.length > 0 || warnings.length > 0) {
|
|
104
|
+
const formatter = new DiagnosticFormatter(source, file);
|
|
105
|
+
for (const e of errors) {
|
|
106
|
+
console.error(formatter.formatError(e.message, { line: e.line, column: e.column }, { hint: e.hint, code: e.code, length: e.length, fix: e.fix }));
|
|
107
|
+
if (e.code) seenCodes.add(e.code);
|
|
108
|
+
}
|
|
109
|
+
for (const w of warnings) {
|
|
110
|
+
console.warn(formatter.formatWarning(w.message, { line: w.line, column: w.column }, { hint: w.hint, code: w.code, length: w.length, fix: w.fix }));
|
|
111
|
+
if (w.code) seenCodes.add(w.code);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (isVerbose) {
|
|
116
|
+
const elapsed = Date.now() - start;
|
|
117
|
+
console.log(` ✓ ${relPath} (${elapsed}ms)`);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
totalErrors++;
|
|
121
|
+
if (err.errors) {
|
|
122
|
+
const source = readFileSync(file, 'utf-8');
|
|
123
|
+
console.error(richError(source, err, file));
|
|
124
|
+
} else {
|
|
125
|
+
console.error(` ✗ ${relPath}: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Security scorecard (shown with --verbose or --strict-security, suppressed with --quiet)
|
|
131
|
+
if ((isVerbose || checkStrictSecurity) && !isQuiet && _checkScorecardData) {
|
|
132
|
+
const scorecard = generateSecurityScorecard(
|
|
133
|
+
_checkScorecardData.securityConfig,
|
|
134
|
+
_allCheckWarnings,
|
|
135
|
+
_checkScorecardData.hasServer,
|
|
136
|
+
_checkScorecardData.hasEdge
|
|
137
|
+
);
|
|
138
|
+
if (scorecard) console.log(scorecard.format());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!isQuiet) {
|
|
142
|
+
console.log(`\n ${tovaFiles.length} file${tovaFiles.length === 1 ? '' : 's'} checked, ${formatSummary(totalErrors, totalWarnings)}`);
|
|
143
|
+
// Show explain hint for encountered error codes
|
|
144
|
+
if (seenCodes.size > 0 && (totalErrors > 0 || totalWarnings > 0)) {
|
|
145
|
+
const codes = [...seenCodes].sort().slice(0, 5).join(', ');
|
|
146
|
+
const more = seenCodes.size > 5 ? ` and ${seenCodes.size - 5} more` : '';
|
|
147
|
+
console.log(`\n Run \`tova explain <code>\` for details on: ${codes}${more}`);
|
|
148
|
+
}
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
if (totalErrors > 0) process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { checkProject };
|