tova 0.5.0 → 0.7.0

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 (43) hide show
  1. package/bin/tova.js +109 -57
  2. package/package.json +7 -2
  3. package/src/analyzer/analyzer.js +315 -79
  4. package/src/analyzer/{client-analyzer.js → browser-analyzer.js} +20 -17
  5. package/src/analyzer/form-analyzer.js +113 -0
  6. package/src/analyzer/scope.js +2 -2
  7. package/src/codegen/base-codegen.js +1 -0
  8. package/src/codegen/{client-codegen.js → browser-codegen.js} +444 -5
  9. package/src/codegen/cli-codegen.js +386 -0
  10. package/src/codegen/codegen.js +163 -45
  11. package/src/codegen/edge-codegen.js +1351 -0
  12. package/src/codegen/form-codegen.js +553 -0
  13. package/src/codegen/security-codegen.js +5 -5
  14. package/src/codegen/server-codegen.js +88 -7
  15. package/src/diagnostics/error-codes.js +1 -1
  16. package/src/docs/generator.js +1 -1
  17. package/src/formatter/formatter.js +4 -4
  18. package/src/lexer/tokens.js +12 -2
  19. package/src/lsp/server.js +1 -1
  20. package/src/parser/ast.js +45 -5
  21. package/src/parser/{client-ast.js → browser-ast.js} +3 -3
  22. package/src/parser/{client-parser.js → browser-parser.js} +42 -15
  23. package/src/parser/cli-ast.js +35 -0
  24. package/src/parser/cli-parser.js +140 -0
  25. package/src/parser/edge-ast.js +83 -0
  26. package/src/parser/edge-parser.js +262 -0
  27. package/src/parser/form-ast.js +80 -0
  28. package/src/parser/form-parser.js +206 -0
  29. package/src/parser/parser.js +86 -53
  30. package/src/registry/block-registry.js +56 -0
  31. package/src/registry/plugins/bench-plugin.js +23 -0
  32. package/src/registry/plugins/browser-plugin.js +30 -0
  33. package/src/registry/plugins/cli-plugin.js +24 -0
  34. package/src/registry/plugins/data-plugin.js +21 -0
  35. package/src/registry/plugins/edge-plugin.js +32 -0
  36. package/src/registry/plugins/security-plugin.js +27 -0
  37. package/src/registry/plugins/server-plugin.js +46 -0
  38. package/src/registry/plugins/shared-plugin.js +17 -0
  39. package/src/registry/plugins/test-plugin.js +23 -0
  40. package/src/registry/register-all.js +25 -0
  41. package/src/runtime/ssr.js +2 -2
  42. package/src/stdlib/inline.js +178 -3
  43. package/src/version.js +1 -1
package/bin/tova.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import { resolve, basename, dirname, join, relative } from 'path';
4
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
- // Bun.hash used instead of crypto.createHash for faster hashing
6
+ import { createHash as _cryptoHash } from 'crypto';
7
7
  import { Lexer } from '../src/lexer/lexer.js';
8
8
  import { Parser } from '../src/parser/parser.js';
9
9
  import { Analyzer } from '../src/analyzer/analyzer.js';
@@ -36,9 +36,9 @@ const color = {
36
36
  };
37
37
 
38
38
  const HELP = `
39
- ╦ ╦═╗ ╦
40
- ║ ╠╣
41
- ╩═╝╚═╝╩═╝╩ v${VERSION}
39
+ ╔╦╗╔═╗╦ ╦╔═╗
40
+ ║ ║ ║╚╗╔╝╠═╣
41
+ ╩ ╚═╝ ╚╝ ╩ ╩ v${VERSION}
42
42
 
43
43
  Created by Enoch Kujem Abassey
44
44
  A modern full-stack language that transpiles to JavaScript
@@ -601,6 +601,18 @@ async function runFile(filePath, options = {}) {
601
601
  const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
602
602
  const stdlib = getRunStdlib();
603
603
 
604
+ // CLI mode: execute the cli code directly
605
+ if (output.isCli) {
606
+ let code = stdlib + '\n' + output.cli;
607
+ code = code.replace(/^export /gm, '');
608
+ // Override process.argv for cli dispatch
609
+ const scriptArgs = options.scriptArgs || [];
610
+ code = `process.argv = ["node", ${JSON.stringify(resolved)}, ...${JSON.stringify(scriptArgs)}];\n` + code;
611
+ const fn = new AsyncFunction('__tova_args', '__tova_filename', '__tova_dirname', code);
612
+ await fn(scriptArgs, resolved, dirname(resolved));
613
+ return;
614
+ }
615
+
604
616
  // Compile .tova dependencies and inline them
605
617
  let depCode = '';
606
618
  if (hasTovaImports) {
@@ -616,7 +628,7 @@ async function runFile(filePath, options = {}) {
616
628
  }
617
629
  }
618
630
 
619
- let code = stdlib + '\n' + depCode + (output.shared || '') + '\n' + (output.server || output.client || '');
631
+ let code = stdlib + '\n' + depCode + (output.shared || '') + '\n' + (output.server || output.browser || '');
620
632
  // Strip 'export ' keywords — not valid inside AsyncFunction (used in tova build only)
621
633
  code = code.replace(/^export /gm, '');
622
634
  // Strip import lines for local modules (already inlined above)
@@ -766,8 +778,29 @@ async function buildProject(args) {
766
778
  const outSubDir = dirname(join(outDir, outBaseName));
767
779
  if (outSubDir !== outDir) mkdirSync(outSubDir, { recursive: true });
768
780
 
781
+ // CLI files: write single executable <name>.js with shebang
782
+ if (output.isCli) {
783
+ if (output.cli && output.cli.trim()) {
784
+ const cliPath = join(outDir, `${outBaseName}.js`);
785
+ const shebang = '#!/usr/bin/env node\n';
786
+ writeFileSync(cliPath, shebang + output.cli);
787
+ try { chmodSync(cliPath, 0o755); } catch (e) { /* ignore on Windows */ }
788
+ if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', cliPath)} [cli]${timing}`);
789
+ }
790
+ if (!noCache) {
791
+ const outputPaths = {};
792
+ if (output.cli && output.cli.trim()) outputPaths.cli = join(outDir, `${outBaseName}.js`);
793
+ if (single) {
794
+ const absFile = files[0];
795
+ const sourceContent = readFileSync(absFile, 'utf-8');
796
+ buildCache.set(absFile, sourceContent, outputPaths);
797
+ } else {
798
+ buildCache.setGroup(`dir:${dir}`, files, outputPaths);
799
+ }
800
+ }
801
+ }
769
802
  // Module files: write single <name>.js (not .shared.js)
770
- if (output.isModule) {
803
+ else if (output.isModule) {
771
804
  if (output.shared && output.shared.trim()) {
772
805
  const modulePath = join(outDir, `${outBaseName}.js`);
773
806
  writeFileSync(modulePath, generateSourceMap(output.shared, modulePath));
@@ -800,11 +833,18 @@ async function buildProject(args) {
800
833
  if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', serverPath)}${timing}`);
801
834
  }
802
835
 
803
- // Write default client
804
- if (output.client) {
805
- const clientPath = join(outDir, `${outBaseName}.client.js`);
806
- writeFileSync(clientPath, generateSourceMap(output.client, clientPath));
807
- if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', clientPath)}${timing}`);
836
+ // Write default browser
837
+ if (output.browser) {
838
+ const browserPath = join(outDir, `${outBaseName}.browser.js`);
839
+ writeFileSync(browserPath, generateSourceMap(output.browser, browserPath));
840
+ if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', browserPath)}${timing}`);
841
+ }
842
+
843
+ // Write default edge
844
+ if (output.edge) {
845
+ const edgePath = join(outDir, `${outBaseName}.edge.js`);
846
+ writeFileSync(edgePath, generateSourceMap(output.edge, edgePath));
847
+ if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', edgePath)} [edge]${timing}`);
808
848
  }
809
849
 
810
850
  // Write named server blocks (multi-block)
@@ -817,13 +857,23 @@ async function buildProject(args) {
817
857
  }
818
858
  }
819
859
 
820
- // Write named client blocks (multi-block)
821
- if (output.multiBlock && output.clients) {
822
- for (const [name, code] of Object.entries(output.clients)) {
860
+ // Write named edge blocks (multi-block)
861
+ if (output.multiBlock && output.edges) {
862
+ for (const [name, code] of Object.entries(output.edges)) {
863
+ if (name === 'default') continue;
864
+ const path = join(outDir, `${outBaseName}.edge.${name}.js`);
865
+ writeFileSync(path, code);
866
+ if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', path)} [edge:${name}]${timing}`);
867
+ }
868
+ }
869
+
870
+ // Write named browser blocks (multi-block)
871
+ if (output.multiBlock && output.browsers) {
872
+ for (const [name, code] of Object.entries(output.browsers)) {
823
873
  if (name === 'default') continue;
824
- const path = join(outDir, `${outBaseName}.client.${name}.js`);
874
+ const path = join(outDir, `${outBaseName}.browser.${name}.js`);
825
875
  writeFileSync(path, code);
826
- if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', path)} [client:${name}]${timing}`);
876
+ if (!isQuiet) console.log(` ✓ ${relLabel} → ${relative('.', path)} [browser:${name}]${timing}`);
827
877
  }
828
878
  }
829
879
 
@@ -832,7 +882,7 @@ async function buildProject(args) {
832
882
  const outputPaths = {};
833
883
  if (output.shared && output.shared.trim()) outputPaths.shared = join(outDir, `${outBaseName}.shared.js`);
834
884
  if (output.server) outputPaths.server = join(outDir, `${outBaseName}.server.js`);
835
- if (output.client) outputPaths.client = join(outDir, `${outBaseName}.client.js`);
885
+ if (output.browser) outputPaths.browser = join(outDir, `${outBaseName}.browser.js`);
836
886
  if (single) {
837
887
  const absFile = files[0];
838
888
  const sourceContent = readFileSync(absFile, 'utf-8');
@@ -1083,10 +1133,10 @@ async function devServer(args) {
1083
1133
  writeFileSync(join(outDir, `${outBaseName}.shared.js`), output.shared);
1084
1134
  }
1085
1135
 
1086
- if (output.client) {
1087
- const p = join(outDir, `${outBaseName}.client.js`);
1088
- writeFileSync(p, output.client);
1089
- clientHTML = await generateDevHTML(output.client, srcDir, actualReloadPort);
1136
+ if (output.browser) {
1137
+ const p = join(outDir, `${outBaseName}.browser.js`);
1138
+ writeFileSync(p, output.browser);
1139
+ clientHTML = await generateDevHTML(output.browser, srcDir, actualReloadPort);
1090
1140
  writeFileSync(join(outDir, 'index.html'), clientHTML);
1091
1141
  hasClient = true;
1092
1142
  }
@@ -1117,10 +1167,10 @@ async function devServer(args) {
1117
1167
  }
1118
1168
  }
1119
1169
 
1120
- if (output.multiBlock && output.clients) {
1121
- for (const [name, code] of Object.entries(output.clients)) {
1170
+ if (output.multiBlock && output.browsers) {
1171
+ for (const [name, code] of Object.entries(output.browsers)) {
1122
1172
  if (name === 'default') continue;
1123
- const p = join(outDir, `${outBaseName}.client.${name}.js`);
1173
+ const p = join(outDir, `${outBaseName}.browser.${name}.js`);
1124
1174
  writeFileSync(p, code);
1125
1175
  }
1126
1176
  }
@@ -1231,9 +1281,9 @@ async function devServer(args) {
1231
1281
  if (output.shared && output.shared.trim()) {
1232
1282
  writeFileSync(join(outDir, `${outBaseName}.shared.js`), output.shared);
1233
1283
  }
1234
- if (output.client) {
1235
- writeFileSync(join(outDir, `${outBaseName}.client.js`), output.client);
1236
- rebuildClientHTML = await generateDevHTML(output.client, srcDir, actualReloadPort);
1284
+ if (output.browser) {
1285
+ writeFileSync(join(outDir, `${outBaseName}.browser.js`), output.browser);
1286
+ rebuildClientHTML = await generateDevHTML(output.browser, srcDir, actualReloadPort);
1237
1287
  writeFileSync(join(outDir, 'index.html'), rebuildClientHTML);
1238
1288
  }
1239
1289
  if (output.server) {
@@ -2507,7 +2557,7 @@ async function startRepl() {
2507
2557
  'in', 'return', 'match', 'type', 'import', 'from', 'and', 'or', 'not',
2508
2558
  'try', 'catch', 'finally', 'break', 'continue', 'async', 'await',
2509
2559
  'guard', 'interface', 'derive', 'pub', 'impl', 'trait', 'defer',
2510
- 'yield', 'extern', 'is', 'with', 'as', 'export', 'server', 'client', 'shared',
2560
+ 'yield', 'extern', 'is', 'with', 'as', 'export', 'server', 'client', 'browser', 'shared',
2511
2561
  ]);
2512
2562
 
2513
2563
  const TYPE_NAMES = new Set([
@@ -3007,7 +3057,7 @@ async function binaryBuild(srcDir, outputName, outDir) {
3007
3057
  // Step 1: Compile all .tova files to JS
3008
3058
  const sharedParts = [];
3009
3059
  const serverParts = [];
3010
- const clientParts = [];
3060
+ const browserParts = [];
3011
3061
 
3012
3062
  for (const file of tovaFiles) {
3013
3063
  try {
@@ -3015,7 +3065,7 @@ async function binaryBuild(srcDir, outputName, outDir) {
3015
3065
  const output = compileTova(source, file);
3016
3066
  if (output.shared) sharedParts.push(output.shared);
3017
3067
  if (output.server) serverParts.push(output.server);
3018
- if (output.client) clientParts.push(output.client);
3068
+ if (output.browser) browserParts.push(output.browser);
3019
3069
  } catch (err) {
3020
3070
  console.error(` Error in ${relative(srcDir, file)}: ${err.message}`);
3021
3071
  process.exit(1);
@@ -3085,7 +3135,7 @@ async function productionBuild(srcDir, outDir) {
3085
3135
 
3086
3136
  console.log(`\n Production build...\n`);
3087
3137
 
3088
- const clientParts = [];
3138
+ const browserParts = [];
3089
3139
  const serverParts = [];
3090
3140
  const sharedParts = [];
3091
3141
  let cssContent = '';
@@ -3097,14 +3147,14 @@ async function productionBuild(srcDir, outDir) {
3097
3147
 
3098
3148
  if (output.shared) sharedParts.push(output.shared);
3099
3149
  if (output.server) serverParts.push(output.server);
3100
- if (output.client) clientParts.push(output.client);
3150
+ if (output.browser) browserParts.push(output.browser);
3101
3151
  } catch (err) {
3102
3152
  console.error(` Error in ${relative(srcDir, file)}: ${err.message}`);
3103
3153
  process.exit(1);
3104
3154
  }
3105
3155
  }
3106
3156
 
3107
- const allClientCode = clientParts.join('\n');
3157
+ const allClientCode = browserParts.join('\n');
3108
3158
  const allServerCode = serverParts.join('\n');
3109
3159
  const allSharedCode = sharedParts.join('\n');
3110
3160
 
@@ -3452,7 +3502,8 @@ class BuildCache {
3452
3502
  }
3453
3503
 
3454
3504
  _hashContent(content) {
3455
- return Bun.hash(content).toString(16);
3505
+ if (typeof Bun !== 'undefined' && Bun.hash) return Bun.hash(content).toString(16);
3506
+ return _cryptoHash('md5').update(content).digest('hex');
3456
3507
  }
3457
3508
 
3458
3509
  load() {
@@ -3493,7 +3544,8 @@ class BuildCache {
3493
3544
  for (const f of files.slice().sort()) {
3494
3545
  combined += f + readFileSync(f, 'utf-8');
3495
3546
  }
3496
- return Bun.hash(combined).toString(16);
3547
+ if (typeof Bun !== 'undefined' && Bun.hash) return Bun.hash(combined).toString(16);
3548
+ return _cryptoHash('md5').update(combined).digest('hex');
3497
3549
  }
3498
3550
 
3499
3551
  // Store compiled output for a multi-file group
@@ -3565,7 +3617,7 @@ function getCompiledExtension(tovaPath) {
3565
3617
  const lexer = new Lexer(src, tovaPath);
3566
3618
  const tokens = lexer.tokenize();
3567
3619
  // Check if any top-level token is a block keyword (shared/server/client/test/bench/data)
3568
- const BLOCK_KEYWORDS = new Set(['shared', 'server', 'client', 'test', 'bench', 'data']);
3620
+ const BLOCK_KEYWORDS = new Set(['shared', 'server', 'client', 'browser', 'test', 'bench', 'data']);
3569
3621
  let depth = 0;
3570
3622
  for (const tok of tokens) {
3571
3623
  if (tok.type === 'LBRACE') depth++;
@@ -3692,7 +3744,7 @@ function collectExports(ast, filename) {
3692
3744
 
3693
3745
  for (const node of ast.body) {
3694
3746
  // Also collect exports from inside shared/server/client blocks
3695
- if (node.type === 'SharedBlock' || node.type === 'ServerBlock' || node.type === 'ClientBlock') {
3747
+ if (node.type === 'SharedBlock' || node.type === 'ServerBlock' || node.type === 'BrowserBlock') {
3696
3748
  if (node.body) {
3697
3749
  for (const inner of node.body) {
3698
3750
  collectFromNode(inner);
@@ -3722,7 +3774,7 @@ function compileWithImports(source, filename, srcDir) {
3722
3774
  const ast = parser.parse();
3723
3775
 
3724
3776
  // Cache module type from AST (avoids regex heuristic on subsequent lookups)
3725
- const hasBlocks = ast.body.some(n => n.type === 'SharedBlock' || n.type === 'ServerBlock' || n.type === 'ClientBlock' || n.type === 'TestBlock' || n.type === 'BenchBlock' || n.type === 'DataBlock');
3777
+ const hasBlocks = ast.body.some(n => n.type === 'SharedBlock' || n.type === 'ServerBlock' || n.type === 'BrowserBlock' || n.type === 'TestBlock' || n.type === 'BenchBlock' || n.type === 'DataBlock');
3726
3778
  moduleTypeCache.set(filename, hasBlocks ? '.shared.js' : '.js');
3727
3779
 
3728
3780
  // Collect this module's exports for validation
@@ -3820,32 +3872,32 @@ function validateMergedAST(mergedBlocks, sourceFiles) {
3820
3872
  );
3821
3873
  }
3822
3874
 
3823
- // Check client blocks — top-level declarations only
3824
- const clientDecls = { component: new Map(), state: new Map(), computed: new Map(), store: new Map(), fn: new Map() };
3825
- for (const block of mergedBlocks.clientBlocks) {
3875
+ // Check browser blocks — top-level declarations only
3876
+ const browserDecls = { component: new Map(), state: new Map(), computed: new Map(), store: new Map(), fn: new Map() };
3877
+ for (const block of mergedBlocks.browserBlocks) {
3826
3878
  for (const stmt of block.body) {
3827
3879
  const loc = stmt.loc || block.loc;
3828
3880
  if (stmt.type === 'ComponentDeclaration') {
3829
- if (clientDecls.component.has(stmt.name)) addDup('component', stmt.name, clientDecls.component.get(stmt.name), loc);
3830
- else clientDecls.component.set(stmt.name, loc);
3881
+ if (browserDecls.component.has(stmt.name)) addDup('component', stmt.name, browserDecls.component.get(stmt.name), loc);
3882
+ else browserDecls.component.set(stmt.name, loc);
3831
3883
  } else if (stmt.type === 'StateDeclaration') {
3832
3884
  const name = stmt.name || (stmt.targets && stmt.targets[0]);
3833
3885
  if (name) {
3834
- if (clientDecls.state.has(name)) addDup('state', name, clientDecls.state.get(name), loc);
3835
- else clientDecls.state.set(name, loc);
3886
+ if (browserDecls.state.has(name)) addDup('state', name, browserDecls.state.get(name), loc);
3887
+ else browserDecls.state.set(name, loc);
3836
3888
  }
3837
3889
  } else if (stmt.type === 'ComputedDeclaration') {
3838
3890
  const name = stmt.name;
3839
3891
  if (name) {
3840
- if (clientDecls.computed.has(name)) addDup('computed', name, clientDecls.computed.get(name), loc);
3841
- else clientDecls.computed.set(name, loc);
3892
+ if (browserDecls.computed.has(name)) addDup('computed', name, browserDecls.computed.get(name), loc);
3893
+ else browserDecls.computed.set(name, loc);
3842
3894
  }
3843
3895
  } else if (stmt.type === 'StoreDeclaration') {
3844
- if (clientDecls.store.has(stmt.name)) addDup('store', stmt.name, clientDecls.store.get(stmt.name), loc);
3845
- else clientDecls.store.set(stmt.name, loc);
3896
+ if (browserDecls.store.has(stmt.name)) addDup('store', stmt.name, browserDecls.store.get(stmt.name), loc);
3897
+ else browserDecls.store.set(stmt.name, loc);
3846
3898
  } else if (stmt.type === 'FunctionDeclaration') {
3847
- if (clientDecls.fn.has(stmt.name)) addDup('function', stmt.name, clientDecls.fn.get(stmt.name), loc);
3848
- else clientDecls.fn.set(stmt.name, loc);
3899
+ if (browserDecls.fn.has(stmt.name)) addDup('function', stmt.name, browserDecls.fn.get(stmt.name), loc);
3900
+ else browserDecls.fn.set(stmt.name, loc);
3849
3901
  }
3850
3902
  }
3851
3903
  }
@@ -3989,7 +4041,7 @@ function mergeDirectory(dir, srcDir, options = {}) {
3989
4041
  const mergedBody = [];
3990
4042
  const sharedBlocks = [];
3991
4043
  const serverBlocks = [];
3992
- const clientBlocks = [];
4044
+ const browserBlocks = [];
3993
4045
 
3994
4046
  for (const { file, ast } of parsedFiles) {
3995
4047
  for (const node of ast.body) {
@@ -4010,14 +4062,14 @@ function mergeDirectory(dir, srcDir, options = {}) {
4010
4062
 
4011
4063
  if (node.type === 'SharedBlock') sharedBlocks.push(node);
4012
4064
  else if (node.type === 'ServerBlock') serverBlocks.push(node);
4013
- else if (node.type === 'ClientBlock') clientBlocks.push(node);
4065
+ else if (node.type === 'BrowserBlock') browserBlocks.push(node);
4014
4066
 
4015
4067
  mergedBody.push(node);
4016
4068
  }
4017
4069
  }
4018
4070
 
4019
4071
  // Validate for duplicate declarations across files
4020
- validateMergedAST({ sharedBlocks, serverBlocks, clientBlocks }, tovaFiles);
4072
+ validateMergedAST({ sharedBlocks, serverBlocks, browserBlocks }, tovaFiles);
4021
4073
 
4022
4074
  // Build merged Program AST
4023
4075
  const mergedAST = new Program(mergedBody);
@@ -4655,9 +4707,9 @@ async function infoCommand() {
4655
4707
  const config = resolveConfig(process.cwd());
4656
4708
  const hasTOML = config._source === 'tova.toml';
4657
4709
 
4658
- console.log(`\n ╦ ╦═╗ ╦`);
4659
- console.log(` ║ ╠╣`);
4660
- console.log(` ╩═╝╚═╝╩═╝╩ v${VERSION}\n`);
4710
+ console.log(`\n ╔╦╗╔═╗╦ ╦╔═╗`);
4711
+ console.log(` ║ ║ ║╚╗╔╝╠═╣`);
4712
+ console.log(` ╩ ╚═╝ ╚╝ ╩ ╩ v${VERSION}\n`);
4661
4713
 
4662
4714
  // Bun version
4663
4715
  let bunVersion = 'not found';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
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",
@@ -36,7 +36,12 @@
36
36
  "url": "https://github.com/tova-lang/tova-lang/issues"
37
37
  },
38
38
  "author": "Enoch Kujem Abassey",
39
- "keywords": ["language", "transpiler", "fullstack", "javascript"],
39
+ "keywords": [
40
+ "language",
41
+ "transpiler",
42
+ "fullstack",
43
+ "javascript"
44
+ ],
40
45
  "license": "MIT",
41
46
  "devDependencies": {
42
47
  "@codemirror/autocomplete": "^6.20.0",