diff --git a/.gitignore b/.gitignore index 0cd3179..c62f6e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ /raw/ /dist/ +/.bynix-build-*/ /node_modules/ *.js !js/*.js /.trash/ -*.tgz \ No newline at end of file +*.tgz diff --git a/README.md b/README.md index 56f8607..b5ddcd0 100644 --- a/README.md +++ b/README.md @@ -25,20 +25,35 @@ npm install -g bynixscript ### Using NPM Run a BynixScript file: ``` -bsr path/to/script.bs +bynix run path/to/script.bys ``` Compile a BynixScript file: ``` -bst path/to/script.bs +bynix compile path/to/script.bys ``` -To print a BynixScript file to console: +The compiled JavaScript is written next to the source file (`script.js`, or `script.mjs` for `.mbs`). +To print translated JavaScript without running it: ``` -bsp path/to/script.bs +bynix print path/to/script.bys ``` -To delete a file +To delete a file (an explicit file path is required): ``` -bsd path/to/script.bs +bynix delete path/to/script.bys ``` +Run `bynix --help` or `bynix run --help` for usage. The default supported extensions are `.bys`, `.bynixscript`, and `.mbs`. + +To build the packaged executable from source while developing: +``` +npm ci +npm run build +npm test +``` +The build bootstraps the `.bs` CLI sources with the BynixScript compiler from the +`bynixscript` development dependency, then bundles the compiled JavaScript with +esbuild into `dist/index.min.cjs`. `npm test` also checks that every CLI source +compiles through the same compiler path. +The build also bundles the shared compiler with the browser integration entry +into `dist/browser.js`; browser configuration uses the default extensions. ### Using CDN ```html diff --git a/docs/markdown/command.md b/docs/markdown/command.md index a799299..2e9ded2 100644 --- a/docs/markdown/command.md +++ b/docs/markdown/command.md @@ -1,47 +1,16 @@ # Command -BynixScript has commands that can help you use BynixScript more easily. -1. **Execution command** - - **bsr command**: - This command is used to run BynixScript without compiling first: - ``` - bsr path/to/script.bs - ``` - - **bst command**: - This command is used to compile the BynixScript file: - ``` - bst path/to/script.bs - ``` - - **bsp command**: - This command is used to print the results of the translation of the BynixScript code to the cli: - ``` - bsp path/to/script.bs - ``` - - **bsd command**: - This command is used to delete files: - ``` - bsd path/to/script.bs - ``` -2. **Help command** - - **Display help**: - If you want to see help, you can write: - ``` - bynixscript --help - - # or shorter - bs -h - ``` - - **Display version**: - If you want to see the current version of BynixScript you are using, you can see it with the command: - ``` - bynixscript --version - - # or shorter - bs -v - ``` - - **Display download**: - ``` - bynixscript --download [--day, --week, --month] - - # or shorter - bs -d [-d, -w, -m] - ``` \ No newline at end of file + +After `npm install -g bynixscript`, use the installed `bynix` executable: + +| Command | Action | +| --- | --- | +| `bynix run path/to/script.bys` | Translate and run a BynixScript file. | +| `bynix compile path/to/script.bys` | Write translated JavaScript beside the source file (`.js`, or `.mjs` for `.mbs`). Does not run the file. | +| `bynix print path/to/script.bys` | Print translated JavaScript without running the file. | +| `bynix delete path/to/script.bys` | Delete the specified file. A path is always required. | + +By default, input files use `.bys`, `.bynixscript`, or `.mbs`. Paths are relative to the current directory, or may be absolute. Invalid paths and unsupported extensions produce an error and a nonzero exit status. + +Use `bynix --help` for all commands, `bynix --help` for command-specific help, and `bynix --version` for the version. The earlier option forms `bynix -r `, `bynix -c `, `bynix -p `, and `bynix -d ` remain available. + +For development, run `npm ci` followed by `npm run build`. The build uses the `bynixscript` development dependency as a bootstrap compiler to translate the `.bs` CLI sources through BynixScript's normal parser and replacement passes, then bundles the resulting JavaScript with esbuild into `dist/index.min.cjs`. `npm test` builds the executable, checks that all CLI sources compile through that same path, and runs the command tests. diff --git a/js/browser.js b/js/browser.js index 88af974..dfb68f1 100644 --- a/js/browser.js +++ b/js/browser.js @@ -1,382 +1,29 @@ -let parsingResults; -let allowJs; -const style = document.createElement("style"); -style.textContent = ` - bynix { - display: none; - } - `; -style.classList.add("bynix-style"); -document.head.appendChild(style); -const milown = document.createElement("script"); -const ajax = document.createElement("script"); -const fontawesome = document.createElement("link"); -const brython = document.createElement("script"); -milown.src = ''; -ajax.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/4.0.0-beta.2/jquery.min.js"'; -fontawesome.rel = "stylesheet"; -fontawesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css'; -brython.type = "type/javascript"; -brython.src = 'https://cdn.jsdelivr.net/npm/brython@3.9.6/brython.min.js'; -document.head.appendChild(ajax); -document.head.appendChild(fontawesome); -document.head.appendChild(milown); -document.head.appendChild(brython); -document.addEventListener("DOMContentLoaded", function() { - const bynixTags = document.querySelectorAll("bynix"); - bynixTags.forEach(tag => { - var code = ''; - const src = tag.getAttribute('src'); - const allowJsTag = tag.getAttribute('allowJs'); - - if (allowJsTag === null) { - allowJs = "false"; - } else { - allowJs = allowJsTag; - } - - // Jika src ada, lakukan fetch - if (src) { - const end = src.substring(src.lastIndexOf('.') + 1); - - if (src.endsWith(".bs") || src.endsWith(".bynixscript") || src.endsWith(".mbs")) { - fetch(src) - .then(response => response.text()) - .then(srcCode => { - code = parseCode(srcCode); - parsingResults = parsing(code); - code = addSemicolons(code); - executeCode(code); - tag.remove(); - }) - .catch(error => console.error('Error fetching BynixScript code:', error)); - } else { - console.error("Invalid extension '." + end + "'"); - } - } else { - // Jika src tidak ada, ambil kode dari textContent - code = parseCode(tag.textContent); - parsingResults = parsing(code); - code = addSemicolons(code); - executeCode(code); - tag.remove(); - } - }); -}); - -function executeCode(code) { - if (parsingResults === "false") { - console.error(`Please check your code and try again.`); - } else { - // function - code = code.replace(/func\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\):/g, (match, p1, p2) => `function ${p1}(${p2}) {`); - - // if, elif, else - code = code.replace(/elif\s+(.+?):/g, (match, p1) => `} else if (${p1}) {`); - code = code.replace(/if\s+(.+?):/g, (match, p1) => `if (${p1}) {`); - code = code.replace(/else:/g, "} else {"); - - // forEach - code = code.replace(/forEach\((.+?)\)\:/g, (match, p1) => `forEach(${p1} => {`); - code = code.replace(/forEach\((.+?)\s=>\s(.+?)\)/g, (match, p1, p2) => `forEach(${p1} => ${p2})`); - - // var, const - code = code.replace(/var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+?);/g, (match, p1, p2) => `var ${p1} = ${p2};`); - code = code.replace(/const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+?);/g, (match, p1, p2) => `const ${p1} = ${p2};`); - - // print, print.err, print.info, print.debug, print.trace, print.warn, print.assert - code = code.replace(/print(\.[a-zA-Z]+)?\((.+?)\)/g, (match, p1, p2) => { - const type = p1 || ''; - - if (type.includes(".info") || type.includes(".debug") || type.includes(".trace") || type.includes(".warn") || type.includes(".assert")) { - return `console${p1}(${p2})` - } else if (type.includes(".err")) { - return `console.error(${p2})` - } else if (match.includes("/")) { - return match; - } else { - return `console.log(${p2})` +const { compileSource } = require('../scripts/compile.cjs') + +const style = document.createElement('style') +style.textContent = 'bynix { display: none; }' +style.classList.add('bynix-style') +document.head.appendChild(style) + +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('bynix').forEach(async tag => { + const sourcePath = tag.getAttribute('src') + try { + if (sourcePath && !['.bs', '.bys', '.bynixscript', '.mbs'].some(extension => sourcePath.endsWith(extension))) { + throw new Error(`Unsupported BynixScript file: ${sourcePath}`) } - }); - - // touch, ask, confirm - code = code.replace(/touch\((.+?)\)/g, (match, p1) => `alert(${p1});`); - code = code.replace(/ask\((.+?)\)/g, (match, p1) => `prompt(${p1});`); - code = code.replace(/confirm\((.+?)\)/g, (match, p1) => `confirm(${p1});`); - - // rand, roundDown, roundUp, maxOf, minOf - code = code.replace(/rand\((.+?)\)/g, (match, p1) => `Math.random(${p1})`); - code = code.replace(/roundDown\((.+?)\)/g, (match, p1) => `Math.floor(${p1})`); - code = code.replace(/roundUp\((.+?)\)/g, (match, p1) => `Math.ceil(${p1})`); - code = code.replace(/maxOf\((.+?)\)/g, (match, p1) => `Math.min(${p1})`); - code = code.replace(/minOf\((.+?)\)/g, (match, p1) => `Math.max(${p1})`); - - // getElement, getAllElement, createElement, addClass, addElement, listener - code = code.replace(/getElement\((.+?)\)/g, (match, p1) => `document.querySelector(${p1});`); - code = code.replace(/getAllElement\((.+?)\)/g, (match, p1) => `document.querySelectorAll(${p1});`); - code = code.replace(/createElement\((.+?)\)/g, (match, p1) => `document.createElement(${p1});`); - code = code.replace(/\.addClass\((.+?)\)/g, (match, p1) => `.classList.add(${p1})`); - code = code.replace(/([^\s].+)\.addElement\((.+?)\)/g, (match, p1, p2) => `document.${p1}.appendChild(${p2});`); - code = code.replace(/(.+?)\.listener\((.+?)\)\:/g, (match, p1, p2) => `${p1}.addEventListener(${p2}, () => {`) - code = code.replace(/listener\((.+?)\)\:/g, (match, p1) => `document.addEventListener(${p1}, function() {`) - - // change - code = code.replace(/(.+?)\.change\((.+?), \((.+?)\)\)\:/g, (match, p1, p2, p3) => `${p1}.replace(${p2}, (${p3}) => {`) - code = code.replace(/(.+?)\.change\((.+)\)/g, (match, p1, p2) => `${p1}.replace(${p2})`); - - // text - code = code.replace(/(.+?)\.text/g, (match, p1) => `${p1}.textContent`) - code = code.replace(/(.+?)\.text = (.+?)/g, (match, p1, p2) => `${p1}.textContent = ${p2}`); - code = code.replace(/(? `//${p1}`); - code = code.replace(/\*\*(.+?)/g, (match, p1) => `/*${p1}`); - code = code.replace(/(.+?)\*\*/g, (match, p1) => `${p1}*/`); - - // condition property - code = code.replace(/is_includes\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1) => `includes(${p1})`); - code = code.replace(/is_start\s===\s([a-zA-Z0-9"'`!?*#@$_&-+|.,:;=×÷%/]+)/g, (match, p1) => `startsWith(${p1})`); - code = code.replace(/is_end\s===\s([a-zA-Z0-9"'`!?*#@$_&-+|.,:;=×÷%/]+)/g, (match, p1) => `endsWith(${p1})`); - code = code.replace(/is_matched\s===\s"(.+?)"/g, (match, p1) => `match(${p1})`); - code = code.replace(/is_matched\s===\s'(.+?)'/g, (match, p1) => `match(${p1})`); - code = code.replace(/is_value\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1) => `value === ${p1}`); - code = code.replace(/is_design.(.+?)\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1, p2) => `style.${p1} === ${p2}`); - code = code.replace(/(.+?)\.design\.(.+?)\s=\s(.+?)/g, (match, p1, p2, p3) => `${p1}.style.${p2} = ${p3}`); - code = code.replace(/(.+?)\.design\s=\s(.+?)/g, (match, p1, p2) => `${p1}.style = ${p2}`); - code = code.replace(/is_action\s([=!><]+)\s(["'])checked(["'])/g, (match) => `checked`); - code = code.replace(/is_action\s([=!><]+)\s(["'])selected(["'])/g, (match) => `selected`); - code = code.replace(/is_action\s([=!><]+)\s(["'])open(["'])/g, (match) => `open`); - code = code.replace(/is_have\s([=!><]+)\s(["'])required(["'])/g, (match) => `required`); - code = code.replace(/is_have\s([=!><]+)\s(["'])readOnly(["'])/g, (match) => `readOnly`); - code = code.replace(/is_have\s([=!><]+)\s(["'])autofocus(["'])/g, (match) => `autofocus`); - code = code.replace(/is_have\s([=!><]+)\s(["'])disabled(["'])/g, (match) => `disabled`); - code = code.replace(/is_have\s([=!><]+)\s(["'])multiple(["'])/g, (match) => `multiple`); - code = code.replace(/is_have\s([=!><]+)\s(["'])hidden(["'])/g, (match) => `hidden`); - code = code.replace(/delay\:\n\s+(.+?)/g, (match, p1) => `setTimeout(function() {\n ${p1}`); - - // loop - code = code.replace(/repeat\:\n\s+(.+?)/g, (match, p1) => `setInterval(function() {\n ${p1}`); - code = code.replace(/for (.+?)=(.+?) to (.+?)\:/g, (match, p1, p2, p3) => `for (let ${p1} = ${p2}; ${p1} < ${p3}; ${p1}++) {`); - code = code.replace(/for (.+?) in (.+?)\:/g, (match, p1, p2) => `for (let ${p1} in ${p2}) {`); - code = code.replace(/for (.+?) of (.+?)\:/g, (match, p1, p2) => `for (let ${p1} of ${p2}) {`); - code = code.replace(/(? `switch ((${p1})) {`) - code = code.replace("switch ((", "switch (") - code = code.replace(")) {", ") {") - code = code.replace(/case (.+?):\n(.+?)/g, (match, p1, p2) => `case ${p1}: \n${p2}`) - code = code.replace(/([^\s].+?)\.image\s*=\s*(['"`][^'"`]*['"``])/g, (match, p1, p2) => `image(${p1}, ${p2})`); - code = code.replace(/process\.argv\[(\d+)\]/g, (match, p1) => { - var a = Number(p1) + 0; - return `process.argv[${a}]`; - }); - code = code.replace(/int\((.+?)\)/g, (match, p1) => `parseInt(${p1})`) - code = code.replace(/num\((.+?)\)/g, (match, p1) => `Number(${p1})`) - code = code.replace(/str\((.+?)\)/g, (match, p1) => `String(${p1})`) - code = code.replace(/float\((.+?)\)/g, (match, p1) => `parseFloat(${p1})`) - code = code.replace(/json\((.+?)\)/g, (match, p1) => `JSON.parse(${p1})`); - code = code.replace(/(.+?)\.map\((.+?)\)\:/g, (match, p1, p2) => `${p1}.map(function(${p2}) {`) - code = code.replace(/\.map\((.+?)\)/g, (match, p1) => `.map(${p1})`); - code = code.replace(/(.+?)\.filter\((.+?)\)\:/g, (match, p1, p2) => `${p1}.filter(function(${p2}) {`); - code = code.replace(/\.filter\((.+?)\)/g, (match, p1) => `.filter(${p1})`); - code = code.replace(/(.+?)\.reduce\((.+?)\)\:/g, (match, p1, p2) => `${p1}.reduce(function(${p2}) {`) - code = code.replace(/\.reduce\((.+?)\)/g, (match, p1) => `.reduce(${p1})`); - code = code.replace(/(? `typeof ${p1}`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_type\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1, p2) => `typeof ${p1} === ${p2}`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])type(["'])/g, (match, p1) => `${p1} instanceof TypeError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])reference(["'])/g, (match, p1) => `${p1} instanceof ReferenceError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])syntax(["'])/g, (match, p1) => `${p1} instanceof SyntaxError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])range(["'])/g, (match, p1) => `${p1} instanceof RangeError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])uri(["'])/g, (match, p1) => `${p1} instanceof URIError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])eval(["'])/g, (match, p1) => `${p1} instanceof EvalError`) - code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])(.+?)(["'])/g, (match, p1, p2, p3) => `${p1} instanceof ${p3}`) - code = code.replace(/constructor\((.+?)\)\:/g, (match, p1) => `constructor(${p1}) {`); - code = code.replace(/(.+?)\(\)\:/g, (match, p1) => `${p1}() {`); - code = code.replace(/class\s(.+?)\:/g, (match, p1) => `class ${p1} {`); - code = code.replace(/class\s(.+?)\sextends\s(.+?)\:/g, (match, p1, p2) => `class ${p1} extends ${p2} {`); - code = code.replace(/static\((.+?)\)\ {/g, (match, p1) => `static(${p1}) {`); - code = code.replace(/super\((.+?)\)\:/g, (match, p1) => `super(${p1})`) - code = code.replace(/throw (.+?)/g, (match, p1) => `throw ${p1}`) - code = code.replace(/new (.+?)/g, (match, p1) => `new ${p1}`) - code = code.replace(/end\:(.+?)/g, (match, p1) => `}, ${p1});`); - code = code.replace(/handle\:/g, 'try {') - code = code.replace(/recovery\s\((.+?)\)\:/g, (match, p1) => `} catch (${p1}) {`) - code = code.replace(/final\:/g, "} finally {") - code = code.replace(/handle\:/g, 'try {') - code = code.replace(/recovery\s*\((.+?)\)\:/g, (match, p1) => `} catch (${p1}) {`) - code = code.replace(/final\:/g, "} finally {") - code = code.replace(/Err\((.+?)\)/g, (match, p1) => `Error(${p1})`) - code = code.replace(/URIErr\((.+?)\)/g, (match, p1) => `URIError(${p1})`) - code = code.replace(/EvalErr\((.+?)\)/g, (match, p1) => `EvalError(${p1})`) - code = code.replace(/TypeErr\((.+?)\)/g, (match, p1) => `TypeError(${p1})`) - code = code.replace(/RangeErr\((.+?)\)/g, (match, p1) => `RangeError(${p1})`) - code = code.replace(/SyntaxErr\((.+?)\)/g, (match, p1) => `Error(${p1})`) - code = code.replace(/InternalErr\((.+?)\)/g, (match, p1) => `InternalError(${p1})`) - code = code.replace(/ReferenceErr\((.+?)\)/g, (match, p1) => `ReferenceError(${p1})`) - - const script = document.createElement("script"); - script.textContent = code; - document.body.appendChild(script); - } -} - -function image(element, imageClass) { - if (!element) { - console.error(`Element tidak ditemukan.`); - return; - } - - const icon = document.createElement("i"); - - // Pisahkan class yang diberikan berdasarkan spasi - const classes = imageClass.split(' '); - classes.forEach(cls => icon.classList.add(cls)); - - element.appendChild(icon); -} - -function parsing(variable) { - const patterns = [ - /let\s*(.+?)=(.+?)/g, - /function\s*(.+?)\((.+?)\)/g, - /if\s*\((.+?)\)\s*{/g, - /else if\s*\((.+?)\)\s*{/g, - /else\s*{\s*/g, - /console(.+?)\((.+?)\)/g, - /alert\((.+?)\)/g, - /prompt\((.+?)\)/g, - /Math\.random\((.+?)\)/g, - /Math\.floor\((.+?)\)/g, - /Math\.ceil\((.+?)\)/g, - /Math\.min\((.+?)\)/g, - /Math\.max\((.+?)\)/g, - /document\.createElement\((.+?)\)/g, - /document\.(.+?)\.appendChild\((.+?)\)/g, - /(.+?).replace\((.+?)\)/g, - /\/\//g, - /(.+?)\.textContent\s*=\s*(.+?)/g, - /\/\*(.+?)\*\//g, - /document\.addEventListener\((.+?), function\(\) \{/g, - /forEach\((.+?) \=\> \{/g, - /(.+?)\.includes\((.+?)\)/g, - /(.+?)\.match\((.+?)\)/g, - /(.+?)\.value ===/g, - /(.+?)\.value ==/g, - /(.+?)\.value =/g, - /(.+?)\.value !===/g, - /(.+?)\.value !==/g, - /(.+?)\.value !=/g, - /(.+?)\.value >/g, - /(.+?)\.value /g, - /(.+?)\.value <==/g, - /(.+?)\.style ===/g, - /(.+?)\.style ==/g, - /(.+?)\.style =/g, - /(.+?)\.style !===/g, - /(.+?)\.style !==/g, - /(.+?)\.style !=/g, - /(.+?)\.style >/g, - /(.+?)\.style /g, - /(.+?)\.style <==/g, - /(.+?)\.style\.(.+?) =/g, - /(.+?)\.style =/g, - /(.+?)\.checked/g, - /(.+?)\.required/g, - /setTimeout\(function\(\) \{/g, - /setInterval\(function\(\) \{/g, - /for \((.+?)\) \{/g, - /classList\.add\((.+?)\)/g, - /switch\s*\((.+?)\)/g, - /image\((.+?), (.+?)\)/g, - /class\s*(.+?)\s*\{/g, - /class\s*(.+?)\s*extends\s*(.+?)\s*\{/g, - /constructor(.+?)\s*\{/g, - /(.+?)\(\)\s*\{/g, - /static(.+?)\s*\{/g, - /typeof\s*(.+?)\s*(.+?)\s*(.+?)/g, - /\.map\(function\((.+?)\)\s*\{/g, - /\.filter\(function\((.+?)\)\s*\{/g, - /\.reduce\(function\((.+?)\)\s*\{/g, - /try\s*{/g, - /}\s*catch\s*(.+?)\s*{/g, - /}\s*finally\s*{/g, - ]; - - if (allowJs === "false") { - const matches = patterns.map(pattern => { - const match = variable.match(pattern); - return match ? match[0] : null; - }).filter(Boolean); - - if (matches.length > 0) { - parsingMsg = "Invalid function '" + matches.join("', '") + "'"; - const error = new Error(parsingMsg); - console.error(error); - return "false"; - } else { - parsingMsg = ""; - return "true"; - } - } -} - -function parseCode(code) { - const lines = code.split('\n'); - const output = []; - const stack = []; - - const regex = { - listener: /(.+?)\.listener/, - classFunc: /(.+?)\(\)\:/, - change: /(.+?)\.change\((.+?), \((.+?)\)\)\:/, - map: /(.+?)\.map\((.+?)\)\:/, - filter: /(.+?)\.filter\((.+?)\)\:/, - forEach: /forEach\((.+?)\)\:/, - func: /func\s*(.+?)\((.+?)\)/, - blockStart: /^(if|elif|else|class|constructor|static|final|recovery|func)/, - }; - - for (let line of lines) { - const trimmedLine = line.trim(); - - if (regex.blockStart.test(trimmedLine)) { - stack.push('block'); - output.push(line); - } else if (regex.listener.test(trimmedLine) || regex.change.test(trimmedLine) || - regex.map.test(trimmedLine) || regex.filter.test(trimmedLine) || regex.forEach.test(trimmedLine)) { - stack.push('listener'); - output.push(line); - } else if (regex.func.test(trimmedLine)) { - stack.push('func'); - output.push(line); - } else if (trimmedLine === 'end') { - const indent = line.match(/^\s*/)[0]; - const lastBlock = stack.pop(); - output.push(lastBlock === 'listener' ? `${indent}});` : `${indent}}`); - } else { - output.push(line); - } - } - - return output.join('\n'); -} - -function addSemicolons(code) { - const lines = code.split('\n'); - const output = []; - - const regex = { - needsSemicolon: /^(var|const|return|throw|break|continue|.*\=.*|[^;{}]+\))$/, - alreadyTerminated: /[:;{}]\s*$/ - }; - - for (let line of lines) { - const trimmedLine = line.trim(); - - if (regex.needsSemicolon.test(trimmedLine) && !regex.alreadyTerminated.test(trimmedLine)) { - output.push(line + ';'); - } else { - output.push(line); - } + const source = sourcePath + ? await fetch(sourcePath).then(response => { + if (!response.ok) throw new Error(`Failed to fetch ${sourcePath}: ${response.status}`) + return response.text() + }) + : tag.textContent + const script = document.createElement('script') + script.textContent = compileSource(source, tag.getAttribute('allowJs') === 'true') + document.body.appendChild(script) + tag.remove() + } catch (error) { + console.error('Error executing BynixScript code:', error) } - - return output.join('\n'); -} + }) +}) diff --git a/js/bsc.js b/js/bsc.js index 87ab6a0..efc0902 100644 --- a/js/bsc.js +++ b/js/bsc.js @@ -1,39 +1 @@ -const fs = require('fs'); - -const defaultConfig = { - extension: { - primary: '.bys', - secondary: '.bynixscript', - module: '.mbs' - }, - readFolder: './', - toFolder: './', - watch: false, - allowJs: false, - strict: false, - translate: 'javascript' -}; - -let config; - -try { - const data = fs.readFileSync('./bsc.json', 'utf8'); - config = JSON.parse(data); -} catch (err) { - config = defaultConfig; -} - -const extensions = { - primary: config.extension?.primary || defaultConfig.extension.primary, - secondary: config.extension?.secondary || defaultConfig.extension.secondary, - module: config.extension?.module || defaultConfig.extension.module -}; - -const readFolder = config.readFolder || defaultConfig.readFolder; -const toFolder = config.toFolder || defaultConfig.toFolder; -const watch = config.watch || defaultConfig.watch; -const allowJs = config.allowJs || defaultConfig.allowJs; -const strict = config.strict || defaultConfig.strict; -const translate = config.translate || defaultConfig.translate; - -module.exports = { defaultConfig, config, extensions, toFolder, readFolder, watch, allowJs, strict, translate }; +module.exports = require('../dist/modules/config.js').loadConfig('./bsc.json', '.bys') diff --git a/js/bsconfig.js b/js/bsconfig.js index ad6211c..737dbd7 100644 --- a/js/bsconfig.js +++ b/js/bsconfig.js @@ -1,39 +1 @@ -const fs = require('fs'); - -const defaultConfig = { - extension: { - primary: '.bs', - secondary: '.bynixscript', - module: '.mbs' - }, - readFolder: './', - toFolder: './', - watch: false, - allowJs: false, - strict: false, - translate: 'javascript' -}; - -let config; - -try { - const data = fs.readFileSync('./bsconfig.json', 'utf8'); - config = JSON.parse(data); -} catch (err) { - config = defaultConfig; -} - -const extensions = { - primary: config.extension?.primary || defaultConfig.extension.primary, - secondary: config.extension?.secondary || defaultConfig.extension.secondary, - module: config.extension?.module || defaultConfig.extension.module -}; - -const readFolder = config.readFolder || defaultConfig.readFolder; -const toFolder = config.toFolder || defaultConfig.toFolder; -const watch = config.watch || defaultConfig.watch; -const allowJs = config.allowJs || defaultConfig.allowJs; -const strict = config.strict || defaultConfig.strict; -const translate = config.translate || defaultConfig.translate; - -module.exports = { defaultConfig, config, extensions, toFolder, readFolder, watch, allowJs, strict, translate }; +module.exports = require('../dist/modules/config.js').loadConfig('./bsconfig.json', '.bs') diff --git a/js/dirProcess.js b/js/dirProcess.js deleted file mode 100644 index a05a4bf..0000000 --- a/js/dirProcess.js +++ /dev/null @@ -1,47 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const vm = require('vm'); -const { translate } = require('./parser.js'); - -function readAndProcessFilesInDirectory(directory) { - // Baca semua file dalam direktori - fs.readdir(directory, (err, files) => { - if (err) { - console.error("Error reading directory:", err); - return; - } - - // Filter hanya file .bs - const bsFiles = files.filter(file => path.extname(file) === '.bs'); - - // Proses setiap file .bs - bsFiles.forEach(file => { - const filePath = path.join(directory, file); - - // Baca isi file - fs.readFile(filePath, 'utf8', (err, data) => { - if (err) { - console.error(`Error reading file ${file}:`, err); - return; - } - - // Konversi "print" menjadi "console.log" - let processedData = translate(code); - - // Eksekusi kode yang telah diproses menggunakan vm - try { - const script = new vm.Script(processedData); - script.runInThisContext(); - } catch (err) { - console.error(`Error executing file ${file}:`, err); - } - }); - }); - }); -} - -// Panggil fungsi ini dengan direktori yang berisi file .bs -readAndProcessFilesInDirectory('./src'); - -// Ekspor fungsi jika dibutuhkan untuk digunakan di file lain -module.exports = { readAndProcessFilesInDirectory }; \ No newline at end of file diff --git a/js/download.js b/js/download.js deleted file mode 100644 index 36749df..0000000 --- a/js/download.js +++ /dev/null @@ -1,22 +0,0 @@ -const https = require('https'); - -function getDownloads(packageName, time) { - const url = `https://api.npmjs.org/downloads/point/last-${time}/${packageName}`; - - https.get(url, (res) => { - let data = ''; - - res.on('data', (chunk) => { - data += chunk; - }); - - res.on('end', () => { - const jsonData = JSON.parse(data); - console.log(`Package ${packageName} downloaded ${jsonData.downloads} times.`); - }); - }).on('error', (e) => { - console.error(`Error: ${e.message}`); - }); -} - -module.exports = { https, getDownloads }; diff --git a/js/error.js b/js/error.js deleted file mode 100644 index 018b5ee..0000000 --- a/js/error.js +++ /dev/null @@ -1,58 +0,0 @@ -function stackParsing(filename, errorMsg, errorStack, code, type, modulLocation) { - if (code !== undefined && code !== null) { - const stackLines = errorStack.split('\n'); - let codeLines = code.split('\n'); - - const errorLine = stackLines[0]; - const parts = errorLine.split(':'); - let line; - let column - let tabs; - let arrow; - let errorCode; - if (type === "SyntaxError") { - const errorLine = stackLines[0]; - const parts = errorLine.split(':'); - line = parseInt(parts[1].trim()); - let l = line - 2; - errorCode = codeLines[l].trim(); - - return `${type}: ${errorMsg}\n at file ${filename}\n\nSnippet:\n${errorCode}\n`; - } else if (type === "TypeError" || type === "ReferenceError" || type === "RangeError" || type === "URIError" || type === "EvalError") { - const errorLine = stackLines[5]; - const parts = errorLine.split(':'); - line = parseInt(parts[1].trim()); - column = parseInt(parts[2].trim()); - - if (column > 6) { - column -= 6; - } - - let l = line - 1; - const codeNoTrim = codeLines[l] - errorCode = codeLines[l].trim(); - const spaces = codeNoTrim.match(/^ +/); - const spacesCount = spaces ? spaces[0].length : 0; - let x = column - spacesCount - 1; - if (x < 0) { - x = Math.abs(x); - x -= 2; - } - tabs = ' '.repeat(x); - arrow = tabs + '^'; - - return `${type}: ${errorMsg}\n at file ${filename}\n at line ${line}, column ${column}\n\nSnippet:\n${errorCode}\n${arrow}`; - } else { - let commonErr = errorMsg - commonErr = commonErr.replace(/\sstack/g, ''); - commonErr = commonErr.replace(/bst\.js/g, filename); - commonErr = commonErr.replace(/bsr\.js/g, filename); - commonErr = commonErr.replace(/bsp\.js/g, filename); - return `${type}: ${commonErr}`; - } - } else { - return errorMsg; - } -} - -module.exports = { stackParsing }; \ No newline at end of file diff --git a/js/watcher.js b/js/watcher.js deleted file mode 100644 index 4f46d89..0000000 --- a/js/watcher.js +++ /dev/null @@ -1,93 +0,0 @@ -const chokidar = require('chokidar'); -const fs = require('fs'); -const path = require('path'); -const { parsing, parsingMsg, parseCode } = require('./parser.js') -const { funcReplace, condReplace, forEachReplace, reassignReplace, assignReplace, logReplace, interactReplace, mathReplace, domReplace, commentReplace, utilityReplace, asyncReplace, flowReplace, forReplace, convReplace, checkReplace, oopReplace, excepReplace, otherReplace } = require('../raw/parser/parsingDecl.js') - -function watching(directory, directory2) { - let code; - - const watcher = chokidar.watch(directory, { - ignored: /(^|[\/\\])\../, - persistent: true - }); - - watcher - .on('add', filePath => { - code = fs.readFileSync(filePath, 'utf8'); - code = parseCode(code); - const parsingResults = parsing(code); - if (parsingResults === false) { - console.log("Please check your code and try again.") - } else { - code = flowReplace(code) - code = utilityReplace(code) - code = funcReplace(code) - code = condReplace(code) - code = forEachReplace(code) - code = reassignReplace(code) - code = assignReplace(code) - code = logReplace(code) - code = interactReplace(code) - code = mathReplace(code) - code = commentReplace(code) - code = asyncReplace(code) - code = forReplace(code) - code = convReplace(code) - code = checkReplace(code) - code = oopReplace(code) - code = excepReplace(code) - code = domReplace(code) - var newFilePath = path.join(directory2, path.basename(filePath)); - newFilePath = newFilePath.replace(".bs", ".js"); - fs.writeFileSync(newFilePath, code); - - console.log(`File ${newFilePath} has been created.`); - } - }) - .on('change', filePath => { - code = fs.readFileSync(filePath, 'utf8'); - code = parseCode(code); - const parsingResults = parsing(code); - if (parsingResults === false) { - console.log("Please check your code and try again.") - } else { - code = flowReplace(code) - code = utilityReplace(code) - code = funcReplace(code) - code = condReplace(code) - code = forEachReplace(code) - code = reassignReplace(code) - code = assignReplace(code) - code = logReplace(code) - code = interactReplace(code) - code = mathReplace(code) - code = commentReplace(code) - code = asyncReplace(code) - code = forReplace(code) - code = convReplace(code) - code = checkReplace(code) - code = oopReplace(code) - code = excepReplace(code) - code = domReplace(code) - var newFilePath = path.join(directory2, path.basename(filePath)); - newFilePath = newFilePath.replace(".bs", ".js"); - fs.writeFileSync(newFilePath, code); - - console.log(`File ${newFilePath} has been changed.`); - } - }) - .on('unlink', filePath => { - var newFilePath = path.join(directory2, path.basename(filePath)); - newFilePath = newFilePath.replace(".bs", ".js"); - fs.unlinkSync(newFilePath); - - console.log(`File ${newFilePath} has been deleted.`); - }); - - watcher - .on('error', error => console.log(`Watcher error: ${error}`)) - .on('ready', () => console.log('Initial scan complete. Ready for changes')); -} - -module.exports = { watching }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index caea6c2..5b2d4cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,570 +1,587 @@ { - "name": "bynixscript", - "version": "0.2.1-next", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "bynixscript", - "version": "0.2.1-next", - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "commander": "^14.0.3", - "fs": "^0.0.1-security", - "path": "^0.4.9", - "vm": "^0.1.0" - }, - "bin": { - "bs": "index.js", - "bsd": "dist/bsd.js", - "bsp": "dist/bsp.js", - "bsr": "dist/bsr.js", - "bst": "dist/bst.js", - "bynixscript": "index.js" - }, - "devDependencies": { - "esbuild": "^0.25.10" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", - "license": "ISC" - }, - "node_modules/path": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/path/-/path-0.4.10.tgz", - "integrity": "sha512-OXGpluyRVdUQP5fOGYMsPQo9eba09SmxaFMpM+h61gp1Mz55vhBVJt/Q3cIxAUQ17sIJn7KSwZ2cbmwRRqS1Rw==", - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/vm": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/vm/-/vm-0.1.0.tgz", - "integrity": "sha512-1aKVjgohVDnVhGrJLhl2k8zIrapH+7HsdnIjGvBp3XX2OCj6XGzsIbDp9rZ3r7t6qgDfXEE1EoEAEOLJm9LKnw==", - "license": "MIT" - } + "name": "bynixscript", + "version": "0.2.2-next", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bynixscript", + "version": "0.2.2-next", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "commander": "^14.0.3", + "fs": "^0.0.1-security", + "path": "^0.4.9", + "vm": "^0.1.0" + }, + "bin": { + "bynix": "dist/index.min.cjs" + }, + "devDependencies": { + "bynixscript": "^0.2.1-next", + "esbuild": "^0.25.10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/bynixscript": { + "version": "0.2.1-next", + "resolved": "https://registry.npmjs.org/bynixscript/-/bynixscript-0.2.1-next.tgz", + "integrity": "sha512-jsWvS2zb/F2smSGFKXd0BPZLmxf4FjBkn/KeaYOAOH5/sRLGwM22D3arYfwlIVt0FS0oBC0RGYx3Eb7KtPdhAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "fs": "^0.0.1-security", + "path": "^0.4.9", + "vm": "^0.1.0" + }, + "bin": { + "bs": "index.js", + "bsd": "dist/bsd.js", + "bsp": "dist/bsp.js", + "bsr": "dist/bsr.js", + "bst": "dist/bst.js", + "bynixscript": "index.js" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", + "license": "ISC" + }, + "node_modules/path": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/path/-/path-0.4.10.tgz", + "integrity": "sha512-OXGpluyRVdUQP5fOGYMsPQo9eba09SmxaFMpM+h61gp1Mz55vhBVJt/Q3cIxAUQ17sIJn7KSwZ2cbmwRRqS1Rw==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vm/-/vm-0.1.0.tgz", + "integrity": "sha512-1aKVjgohVDnVhGrJLhl2k8zIrapH+7HsdnIjGvBp3XX2OCj6XGzsIbDp9rZ3r7t6qgDfXEE1EoEAEOLJm9LKnw==", + "license": "MIT" } + } } diff --git a/package.json b/package.json index b437cb3..e06ab7e 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "version": "0.2.2-next", "description": "This is new Programming Language.", "type": "commonjs", - "browser": "js/browser.js", + "browser": "dist/browser.js", "main": "./index.js", "scripts": { - "test": "node index.js" + "build": "node scripts/build.cjs", + "prepack": "npm run build", + "test": "npm run build && node --test tests/*.test.cjs" }, "alias": { "test": "npm run test" @@ -31,7 +33,8 @@ "Indonesian" ], "devDependencies": { + "bynixscript": "^0.2.1-next", "esbuild": "^0.25.10" }, "repository": "https://github.com/UngGasStudio/BynixScript/" -} \ No newline at end of file +} diff --git a/scripts/build.cjs b/scripts/build.cjs new file mode 100644 index 0000000..b46d3d0 --- /dev/null +++ b/scripts/build.cjs @@ -0,0 +1,86 @@ +const fs = require('node:fs') +const path = require('node:path') +const { build } = require('esbuild') +const { compileSource } = require('./compile.cjs') + +const root = path.resolve(__dirname, '..') +const temporary = fs.mkdtempSync(path.join(root, '.bynix-build-')) +const source = path.join(root, 'src') +const output = path.join(root, 'dist', 'index.min.cjs') +const generated = path.join(root, 'dist', 'modules') +const modules = ['config', 'download', 'error', 'dirProcess', 'watcher'] + +async function main() { + try { + for (const name of fs.readdirSync(source, { withFileTypes: true })) { + if (name.isFile() && name.name.endsWith('.bs')) { + fs.writeFileSync(path.join(temporary, name.name.replace(/\.bs$/, '.js')), + compileSource(fs.readFileSync(path.join(source, name.name), 'utf8'))) + } + } + + fs.rmSync(generated, { recursive: true, force: true }) + await build({ + entryPoints: modules.map(name => path.join(temporary, `${name}.js`)), + outdir: generated, + platform: 'node', + format: 'cjs', + bundle: true, + target: 'node20', + plugins: [{ + name: 'compiler', + setup(bundle) { + bundle.onResolve({ filter: /^\.\.\/scripts\/compile\.cjs$/ }, () => ({ + path: path.join(root, 'scripts', 'compile.cjs') + })) + } + }] + }) + + await build({ + entryPoints: [path.join(temporary, 'index.js')], + outfile: output, + platform: 'node', + format: 'cjs', + bundle: true, + minify: true, + target: 'node20', + plugins: [{ + name: 'build-imports', + setup(bundle) { + bundle.onResolve({ filter: /^\.\.\/scripts\/compile\.cjs$/ }, () => ({ + path: path.join(root, 'scripts', 'compile.cjs') + })) + bundle.onResolve({ filter: /^\.\.\/package\.json$/ }, () => ({ + path: path.join(root, 'package.json') + })) + } + }] + }) + fs.chmodSync(output, 0o755) + await build({ + entryPoints: [path.join(root, 'js', 'browser.js')], + outfile: path.join(root, 'dist', 'browser.js'), + platform: 'browser', + format: 'iife', + bundle: true, + plugins: [{ + name: 'browser-config', + setup(bundle) { + bundle.onResolve({ filter: /^fs$/ }, () => ({ path: 'fs', namespace: 'browser-config' })) + bundle.onLoad({ filter: /.*/, namespace: 'browser-config' }, () => ({ + contents: 'module.exports = { readFileSync() { throw new Error("No config file in browser") } }', + loader: 'js' + })) + } + }] + }) + } finally { + fs.rmSync(temporary, { recursive: true, force: true }) + } +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/compile.cjs b/scripts/compile.cjs new file mode 100644 index 0000000..73810ed --- /dev/null +++ b/scripts/compile.cjs @@ -0,0 +1,107 @@ +const { parsing, parsingMsg, parseCode, addSemicolons } = require('bynixscript/js/parser.js') +const replacements = require('bynixscript/dist/parser/parsingDecl.js') + +const passes = [ + 'flowReplace', 'utilityReplace', 'funcReplace', 'condReplace', + 'forEachReplace', 'reassignReplace', 'assignReplace', 'logReplace', + 'interactReplace', 'mathReplace', 'commentReplace', 'asyncReplace', + 'forReplace', 'convReplace', 'checkReplace', 'oopReplace', + 'excepReplace', 'domReplace' +] + +function protectSource(source) { + let prefix = 'BYNIXPROTECTED' + while (source.includes(prefix)) prefix += 'X' + const protectedText = [] + let position = 0 + + function mask(text) { + if (!text) return '' + const token = `${prefix}${protectedText.length}TOKEN` + protectedText.push(text) + return token + } + + function quoted(quote) { + const start = position++ + while (position < source.length) { + if (source[position] === '\\') { + position += 2 + } else if (source[position++] === quote) { + break + } + } + return mask(source.slice(start, position)) + } + + function template() { + let start = position++ + let output = '' + while (position < source.length) { + if (source[position] === '\\') { + position += 2 + } else if (source.startsWith('${', position)) { + output += mask(source.slice(start, position)) + '${' + position += 2 + output += code('}') + start = position + } else if (source[position++] === '`') { + return output + mask(source.slice(start, position)) + } + } + return output + mask(source.slice(start, position)) + } + + function code(terminator) { + let output = '' + let depth = 0 + while (position < source.length) { + const character = source[position] + if (character === terminator && depth === 0) { + position++ + return output + character + } + if (character === '"' || character === "'") { + output += quoted(character) + } else if (character === '`') { + output += template() + } else if (source.startsWith('//', position) || (character === '#' && !source.startsWith('#!', position))) { + const start = position + while (position < source.length && source[position] !== '\n') position++ + const text = source.slice(start, position) + output += mask(character === '#' ? `//${text.slice(1)}` : text) + } else if (source.startsWith('/*', position) || source.startsWith('**', position)) { + const dialect = character === '*' + const start = position + const end = source.indexOf(dialect ? '**' : '*/', position + 2) + position = end === -1 ? source.length : end + 2 + const text = source.slice(start, position) + output += mask(dialect ? `/*${text.slice(2, -2)}*/` : text) + } else { + if (character === '{') depth++ + if (character === '}') depth-- + output += character + position++ + } + } + return output + } + + const masked = code() + const token = new RegExp(`${prefix}(\\d+)TOKEN`, 'g') + return { masked, restore: text => text.replace(token, (match, index) => protectedText[Number(index)]) } +} + +function compileSource(source, allowJs = false) { + const { masked, restore } = protectSource(source) + let code = addSemicolons(parseCode(masked)) + if (!allowJs && parsing(code) === false) { + throw new Error(parsingMsg.message) + } + for (const pass of passes) { + code = replacements[pass](code) + } + return restore(code) +} + +module.exports = { compileSource } diff --git a/src/bsd.bs b/src/bsd.bs index 5a411b6..3542db6 100644 --- a/src/bsd.bs +++ b/src/bsd.bs @@ -1,18 +1,10 @@ -#!/usr/bin/env node -const fs = require('fs') -const path = require('path') -const { defaultConfig, config, readFolder, extensions } = require('../js/bsc.js') +const fs = require('node:fs') +const { resolveFile } = require('./translate.js') -func bsdCommand(locate): - const directory = str(readFolder) - const fileFrom = path.join(directory, locate) - - handle: - fs.unlinkSync(fileFrom) - print("Successfully deleted " + locate + " file") - recovery (err): - print("Error:", err.message) - end +func bsdCommand(file): + const filePath = resolveFile(file) + fs.unlinkSync(filePath) + print(`Deleted ${file}`) end -module.exports = { bsdCommand } \ No newline at end of file +module.exports = { bsdCommand } diff --git a/src/bsp.bs b/src/bsp.bs index b78b559..d17bcda 100644 --- a/src/bsp.bs +++ b/src/bsp.bs @@ -1,69 +1,8 @@ -#!/usr/bin/env node -const fs = require('fs') -const vm = require('vm') -const path = require('path') -const { extensions, toFolder, readFolder, translate } = require('../js/bsc.js') -const { parsing, parsingMsg, parseCode, addSemicolons } = require('../js/parser.js') -const { funcReplace, condReplace, forEachReplace, reassignReplace, assignReplace, logReplace, interactReplace, mathReplace, domReplace, commentReplace, utilityReplace, asyncReplace, flowReplace, forReplace, convReplace, checkReplace, oopReplace, excepReplace, expReplace, otherReplace } = require('./parser/parsingDecl.js') -const { stackParsing } = require('../js/error.js') +const { readSource, translate } = require('./translate.js') -const sandbox = { - console: { - log: () => {} - }, - require: require, - process: process -}; - -func bspCommand(locate): - const directory = str(readFolder) - var fileFrom = path.join(directory, locate) - - var code - handle: - var box = fs.readFileSync(fileFrom, 'utf-8') - if locate.is_end(extensions.primary) || locate.is_end(extensions.secondary) || locate.is_end(extensions.module): - code = parseCode(box) - code = addSemicolons(code) - const parsingResults = parsing(code) - - if parsingResults === false: - print("Error:", parsingMsg.message) - else: - code = flowReplace(code) - code = utilityReplace(code) - code = funcReplace(code) - code = condReplace(code) - code = forEachReplace(code) - code = reassignReplace(code) - code = assignReplace(code) - code = logReplace(code) - code = interactReplace(code) - code = mathReplace(code) - code = commentReplace(code) - code = asyncReplace(code) - code = forReplace(code) - code = convReplace(code) - code = checkReplace(code) - code = oopReplace(code) - code = excepReplace(code) - code = domReplace(code) - - vm.runInNewContext(code, sandbox); - print(code) - end - else: - print(`Error: Unsupported file extension. Please use a ${extensions.primary}, ${extensions.secondary}, or ${extensions.module} file.`) - end - recovery (err): - const msg = err.message - if msg.is_includes('module'): - print(code) - else: - const stackMsg = err.stack - print(stackParsing(locate, err.message, stackMsg, box, err.name, fileFrom)) - end - end +func bspCommand(file): + const { source } = readSource(file) + print(translate(source)) end -module.exports = { bspCommand } \ No newline at end of file +module.exports = { bspCommand } diff --git a/src/bsr.bs b/src/bsr.bs index 6a4e2e6..2aaae85 100644 --- a/src/bsr.bs +++ b/src/bsr.bs @@ -1,67 +1,12 @@ -#!/usr/bin/env node -const fs = require('fs') -const vm = require('vm') -const path = require('path') -const { defaultConfig, config, readFolder, extensions } = require('../js/bsc.js') -const { parsing, parsingMsg, parseCode, addSemicolons } = require('../js/parser.js') -const { funcReplace, condReplace, forEachReplace, reassignReplace, assignReplace, logReplace, interactReplace, mathReplace, domReplace, commentReplace, utilityReplace, asyncReplace, flowReplace, forReplace, convReplace, checkReplace, oopReplace, excepReplace, expReplace, otherReplace } = require('./parser/parsingDecl.js') -const { stackParsing } = require('../js/error.js') +const vm = require('node:vm') +const { createRequire } = require('node:module') +const { readSource, translate } = require('./translate.js') -func bsrCommand(locate): - print(locate) - const directory = str(readFolder) - var fileFrom = path.join(directory, locate) - - const sandbox = { - require: require, - console: console, - process: process, - } - - var code - handle: - var box = fs.readFileSync(fileFrom, 'utf-8') - if locate.is_end(extensions.primary) || locate.is_end(extensions.secondary) || locate.is_end(extensions.module): - code = parseCode(box) - code = addSemicolons(code) - var parsingResults = parsing(code) - - if parsingResults === false: - print("Error:", parsingMsg.message) - else: - code = flowReplace(code) - code = utilityReplace(code) - code = funcReplace(code) - code = condReplace(code) - code = forEachReplace(code) - code = reassignReplace(code) - code = assignReplace(code) - code = logReplace(code) - code = interactReplace(code) - code = mathReplace(code) - code = commentReplace(code) - code = asyncReplace(code) - code = forReplace(code) - code = convReplace(code) - code = checkReplace(code) - code = oopReplace(code) - code = excepReplace(code) - code = domReplace(code) - - vm.runInNewContext(code, sandbox, { filename: locate}); - end - else: - print(`Error: Unsupported file extension. Please use a ${extensions.primary}, ${extensions.secondary}, or ${extensions.module} file.`) - end - recovery (err): - const msg = err.message - if msg.is_includes('module'): - print("Sorry, you cannot using module right now.") - else: - const stackMsg = err.stack - print(stackParsing(locate, err.message, stackMsg, box, err.name, fileFrom)) - end - end +func bsrCommand(file): + const { source, filePath } = readSource(file) + const code = translate(source) + const sandbox = { console, process, require: createRequire(filePath) } + vm.runInNewContext(code, sandbox, { filename: filePath }) end -module.exports = { bsrCommand }; \ No newline at end of file +module.exports = { bsrCommand } diff --git a/src/bst.bs b/src/bst.bs index f44b3bb..67d01db 100644 --- a/src/bst.bs +++ b/src/bst.bs @@ -1,86 +1,14 @@ -#!/usr/bin/env node -const fs = require('fs') -const vm = require('vm') -const path = require('path') -const { extensions, toFolder, readFolder, watch, strict, translate } = require('../js/bsc.js') -const { parsing, parsingMsg, parseCode, addSemicolons } = require('../js/parser.js') -const { funcReplace, condReplace, forEachReplace, reassignReplace, assignReplace, logReplace, interactReplace, mathReplace, domReplace, commentReplace, utilityReplace, asyncReplace, flowReplace, forReplace, convReplace, checkReplace, oopReplace, excepReplace, expReplace, otherReplace } = require('./parser/parsingDecl.js') -const { watching } = require('../js/watcher.js') -const { stackParsing } = require('../js/error.js') +const fs = require('node:fs') +const path = require('node:path') +const { readSource, translate, extensions } = require('./translate.js') -const sandbox = { - console: { - log: () => {} - }, - require: require, - process: process -}; - -func bstCommand(locate): - const directory = str(toFolder) - const directory2 = str(readFolder) - var filePath = path.join(directory, locate) - var fileFrom = path.join(directory2, locate) - - var code - handle: - var box = fs.readFileSync(fileFrom, 'utf-8') - if watch === true: - watching(directory2, directory) - else: - if locate.is_end(extensions.primary) || locate.is_end(extensions.secondary) || locate.is_end(extensions.module): - code = parseCode(box) - code = addSemicolons(code) - const parsingResults = parsing(code) - - if parsingResults === false: - print("Error:", parsingMsg.message) - else: - code = flowReplace(code) - code = utilityReplace(code) - code = funcReplace(code) - code = condReplace(code) - code = forEachReplace(code) - code = reassignReplace(code) - code = assignReplace(code) - code = logReplace(code) - code = interactReplace(code) - code = mathReplace(code) - code = commentReplace(code) - code = asyncReplace(code) - code = forReplace(code) - code = convReplace(code) - code = checkReplace(code) - code = oopReplace(code) - code = excepReplace(code) - code = domReplace(code) - - vm.runInNewContext(code, sandbox); - filePath = filePath.change(extensions.primary, ".js") - filePath = filePath.change(extensions.secondary, ".js") - filePath = filePath.change(extensions.module, ".mjs") - - fs.writeFileSync(filePath, code, 'utf8') - print("Successfully compiled " + locate + " file") - end - else: - print(`Error: Unsupported file extension. Please use a ${extensions.primary}, ${extensions.secondary}, or ${extensions.module} file.`) - end - end - recovery (err): - const msg = err.message - if msg.is_includes('module'): - filePath = filePath.change(extensions.primary, ".js") - filePath = filePath.change(extensions.secondary, ".js") - filePath = filePath.change(extensions.module, ".mjs") - - fs.writeFileSync(filePath, code, 'utf8') - print("Successfully compiled " + locate + " file") - else: - const stackMsg = err.stack - print(stackParsing(locate, err.message, stackMsg, box, err.name)) - end - end +func bstCommand(file): + const { source, filePath } = readSource(file) + const code = translate(source) + const extension = path.extname(filePath) + const output = filePath.slice(0, -extension.length) + (extension === extensions.module ? '.mjs' : '.js') + fs.writeFileSync(output, code, 'utf8') + print(`Compiled ${file} to ${output}`) end -module.exports = { bstCommand } \ No newline at end of file +module.exports = { bstCommand } diff --git a/src/config.bs b/src/config.bs new file mode 100644 index 0000000..cc869f4 --- /dev/null +++ b/src/config.bs @@ -0,0 +1,31 @@ +const fs = require('node:fs') + +func loadConfig(filename, primary): + const defaultConfig = { + extension: { primary, secondary: '.bynixscript', module: '.mbs' }, + readFolder: './', toFolder: './', watch: false, + allowJs: false, strict: false, translate: 'javascript' + } + var config + handle: + config = JSON.parse(fs.readFileSync(filename, 'utf8')) + recovery (error): + config = defaultConfig + end + const extensions = { + primary: config.extension?.primary || defaultConfig.extension.primary, + secondary: config.extension?.secondary || defaultConfig.extension.secondary, + module: config.extension?.module || defaultConfig.extension.module + } + return { + defaultConfig, config, extensions, + readFolder: config.readFolder || defaultConfig.readFolder, + toFolder: config.toFolder || defaultConfig.toFolder, + watch: config.watch || defaultConfig.watch, + allowJs: config.allowJs || defaultConfig.allowJs, + strict: config.strict || defaultConfig.strict, + translate: config.translate || defaultConfig.translate + } +end + +module.exports = { loadConfig } diff --git a/src/dirProcess.bs b/src/dirProcess.bs new file mode 100644 index 0000000..74ed5cf --- /dev/null +++ b/src/dirProcess.bs @@ -0,0 +1,29 @@ +const fs = require('node:fs') +const path = require('node:path') +const vm = require('node:vm') +const { compileSource } = require('../scripts/compile.cjs') + +func readAndProcessFilesInDirectory(directory): + fs.readdir(directory, (error, files) => { + if (error) { + console.error('Error reading directory:', error) + return + } + files.filter(file => path.extname(file) === '.bs').forEach(file => { + const filePath = path.join(directory, file) + fs.readFile(filePath, 'utf8', (readError, data) => { + if (readError) { + console.error(`Error reading file ${file}:`, readError) + return + } + handle: + new vm.Script(compileSource(data), { filename: filePath }).runInThisContext() + recovery (executionError): + console.error(`Error executing file ${file}:`, executionError) + end + }) + }) + }) +end + +module.exports = { readAndProcessFilesInDirectory } diff --git a/src/download.bs b/src/download.bs new file mode 100644 index 0000000..bd22c06 --- /dev/null +++ b/src/download.bs @@ -0,0 +1,15 @@ +const https = require('node:https') + +func getDownloads(packageName, time): + const url = `https://api.npmjs.org/downloads/point/last-${time}/${packageName}` + https.get(url, response => { + let data = '' + response.on('data', chunk => { data += chunk }) + response.on('end', () => { + const jsonData = JSON.parse(data) + console.log(`Package ${packageName} downloaded ${jsonData.downloads} times.`) + }) + }).on('error', error => { console.error(`Error: ${error.message}`) }) +end + +module.exports = { https, getDownloads } diff --git a/src/error.bs b/src/error.bs new file mode 100644 index 0000000..8f9498f --- /dev/null +++ b/src/error.bs @@ -0,0 +1,34 @@ +func stackParsing(filename, errorMsg, errorStack, code, type, modulLocation): + if code === undefined || code === null: + return errorMsg + end + const stackLines = errorStack.split('\n') + const codeLines = code.split('\n') + if type === 'SyntaxError': + const line = parseInt(stackLines[0].split(':')[1].trim()) + const errorCode = codeLines[line - 2].trim() + return `${type}: ${errorMsg}\n at file ${filename}\n\nSnippet:\n${errorCode}\n` + elif type === 'TypeError' || type === 'ReferenceError' || type === 'RangeError' || type === 'URIError' || type === 'EvalError': + const parts = stackLines[5].split(':') + const line = parseInt(parts[1].trim()) + let column = parseInt(parts[2].trim()) + if column > 6: + column -= 6 + end + const codeNoTrim = codeLines[line - 1] + const errorCode = codeNoTrim.trim() + const spaces = codeNoTrim.match(/^ +/) + const spacesCount = spaces ? spaces[0].length : 0 + let offset = column - spacesCount - 1 + if offset < 0: + offset = Math.abs(offset) - 2 + end + const arrow = ' '.repeat(offset) + '^' + return `${type}: ${errorMsg}\n at file ${filename}\n at line ${line}, column ${column}\n\nSnippet:\n${errorCode}\n${arrow}` + else: + const commonErr = errorMsg.replace(/\sstack/g, '').replace(/bst\.js|bsr\.js|bsp\.js/g, filename) + return `${type}: ${commonErr}` + end +end + +module.exports = { stackParsing } diff --git a/src/index.bs b/src/index.bs index 473a79b..e41a652 100644 --- a/src/index.bs +++ b/src/index.bs @@ -1,28 +1,56 @@ #!/usr/bin/env node const { Command } = require('commander') +const { version } = require('../package.json') const { bsrCommand } = require('./bsr.js') const { bstCommand } = require('./bst.js') const { bspCommand } = require('./bsp.js') const { bsdCommand } = require('./bsd.js') -const program = new Command(); +const program = new Command() -program.name('bynix').description('BynixScript command line interface tools').version('0.2.2-next') +program.name('bynix').description('BynixScript command line interface').version(version) +program.showHelpAfterError() -program.option('-r, --run ', 'running files with extensions .bys').option('-c, --compile ', 'compile .bys files to .js').option('-p, --print', 'print code to cli').option('-d, --delete', 'delete a .bys file') +program.command('run ').description('Run a BynixScript file').action(bsrCommand) +program.command('compile ').description('Compile a BynixScript file to JavaScript').action(bstCommand) +program.command('print ').description('Print the translated JavaScript without running it').action(bspCommand) +program.command('delete ').description('Delete a BynixScript file').action(bsdCommand) -program.parse() +program.option('-r, --run ', 'Run a file') +program.option('-c, --compile ', 'Compile a file') +program.option('-p, --print ', 'Print translated JavaScript') +program.option('-d, --delete ', 'Delete a file') +func noOp(): +end +program.action(noOp) -const options = program.opts() +handle: + program.parse() + const options = program.opts() + const selected = [] + for const name of ['run', 'compile', 'print', 'delete']: + if options[name] !== undefined: + selected.push(name) + end + end -if options.run: - bsrCommand(options.run) -elif options.compile: - bstCommand(options.compile) -elif options.print: - bspCommand(options.print) -elif options.delete: - bsdCommand(options.delete) -else: - console.log('No config provided') -end \ No newline at end of file + if selected.length > 1: + throw new Err('Specify only one command at a time.') + elif selected.length === 1: + const name = selected[0] + if name === 'run': + bsrCommand(options[name]) + elif name === 'compile': + bstCommand(options[name]) + elif name === 'print': + bspCommand(options[name]) + else: + bsdCommand(options[name]) + end + elif process.argv.length === 2: + program.error('A command is required.') + end +recovery (error): + process.stderr.write(`Error: ${error.message}\n`) + process.exitCode = 1 +end diff --git a/src/translate.bs b/src/translate.bs new file mode 100644 index 0000000..4644bc2 --- /dev/null +++ b/src/translate.bs @@ -0,0 +1,32 @@ +const fs = require('node:fs') +const path = require('node:path') +const { extensions } = require('./config.js').loadConfig('./bsc.json', '.bys') +const { compileSource } = require('../scripts/compile.cjs') + +func resolveFile(file): + if typeof file !== 'string' || file.trim() === '': + throw new Err('A file path is required.') + end + if !file.is_end(extensions.primary) && !file.is_end(extensions.secondary) && !file.is_end(extensions.module): + throw new Err(`Unsupported file extension: ${file}. Use ${extensions.primary}, ${extensions.secondary}, or ${extensions.module}.`) + end + const filePath = path.resolve(file) + if !fs.existsSync(filePath): + throw new Err(`File not found: ${file}`) + end + if !fs.statSync(filePath).isFile(): + throw new Err(`Not a file: ${file}`) + end + return filePath +end + +func readSource(file): + const filePath = resolveFile(file) + return { filePath, source: fs.readFileSync(filePath, 'utf8') } +end + +func translate(source): + return compileSource(source) +end + +module.exports = { resolveFile, readSource, translate, extensions } diff --git a/src/watcher.bs b/src/watcher.bs new file mode 100644 index 0000000..724f580 --- /dev/null +++ b/src/watcher.bs @@ -0,0 +1,43 @@ +const chokidar = require('chokidar') +const fs = require('node:fs') +const path = require('node:path') +const { compileSource } = require('../scripts/compile.cjs') + +func watching(directory, directory2): + const watcher = chokidar.watch(directory, { ignored: /(^|[\/\\])\../, persistent: true }) + const extensions = ['.bs', '.bys', '.bynixscript', '.mbs'] + func outputPath(filePath): + const extension = path.extname(filePath) + if !extensions.includes(extension): + return null + end + return path.join(directory2, path.basename(filePath, extension) + (extension === '.mbs' ? '.mjs' : '.js')) + end + func compileFile(filePath): + const output = outputPath(filePath) + if !output: + return + end + handle: + const source = fs.readFileSync(filePath, 'utf8') + const code = compileSource(source) + fs.writeFileSync(output, code) + console.log(`File ${output} has been created or changed.`) + recovery (error): + console.error(`Error compiling ${filePath}:`, error) + end + end + func removeFile(filePath): + const output = outputPath(filePath) + if output && fs.existsSync(output): + fs.unlinkSync(output) + console.log(`File ${output} has been deleted.`) + end + end + watcher.on('add', compileFile).on('change', compileFile).on('unlink', removeFile) + watcher.on('error', error => console.error('Watcher error:', error)) + watcher.on('ready', () => console.log('Initial scan complete. Ready for changes')) + return watcher +end + +module.exports = { watching } diff --git a/tests/cli.test.cjs b/tests/cli.test.cjs new file mode 100644 index 0000000..b1f8b56 --- /dev/null +++ b/tests/cli.test.cjs @@ -0,0 +1,421 @@ +const test = require('node:test') +const assert = require('node:assert/strict') +const fs = require('node:fs') +const os = require('node:os') +const path = require('node:path') +const { spawnSync } = require('node:child_process') +const { Script, runInNewContext } = require('node:vm') +const { compileSource } = require('../scripts/compile.cjs') + +const root = path.resolve(__dirname, '..') +const binary = path.join(root, 'dist', 'index.min.cjs') +const generated = path.join(root, 'dist', 'modules') + +function invoke(args, cwd = root) { + return spawnSync(process.execPath, [binary, ...args], { cwd, encoding: 'utf8' }) +} + +function fixture(context) { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'bynix-cli-')) + context.after(() => fs.rmSync(directory, { recursive: true, force: true })) + return directory +} + +test('build produces the packaged executable', () => { + const result = spawnSync(process.execPath, [path.join(root, 'scripts', 'build.cjs')], { + cwd: root, encoding: 'utf8' + }) + assert.equal(result.status, 0, result.stderr) + assert.ok(fs.statSync(binary).isFile()) + for (const name of ['config', 'download', 'error', 'dirProcess', 'watcher']) { + assert.equal(fs.existsSync(path.join(root, 'js', `${name}.js`)), false) + assert.ok(fs.statSync(path.join(generated, `${name}.js`)).isFile()) + } + assert.match(fs.readFileSync(binary, 'utf8'), /^#!\/usr\/bin\/env node/) + assert.equal(invoke(['--version']).status, 0) +}) + +test('every CLI source compiles through the build compiler', () => { + for (const name of ['index', 'bsr', 'bst', 'bsp', 'bsd', 'translate', + 'config', 'download', 'error', 'dirProcess', 'watcher']) { + const source = fs.readFileSync(path.join(root, 'src', `${name}.bs`), 'utf8') + const code = compileSource(source) + assert.match(source, /\bfunc \w+\(|\bhandle:/) + assert.doesNotMatch(source, /\bfunction\s+\w+\s*\(|\btry\s*\{/) + assert.match(code, /function \w+\(|try \{/) + assert.doesNotThrow(() => new Script(code, { filename: `${name}.js` })) + } + assert.match(compileSource('func valid(value):\n print(value)\nend'), /function valid\(value\) \{/) + assert.throws(() => new Script(compileSource('func invalid():\n print("x")\n')), SyntaxError) +}) + +test('bootstrap compiler preserves quoted and template text while translating code', () => { + const source = [ + "const single = 'func print(1) is_end(\"x\") # \\'quoted\\''", + 'const double = "handle: print(2) rand(3) \\"quoted\\""', + 'const template = `first print(3) # **', + 'end \\`escaped\\` ${single} ${`nested print(4) ${double}`}`', + 'const interpolated = `print(5) ${"needle".is_includes("need")}`', + 'func show():', + ' print(single, double, template, interpolated)', + 'end', + 'show()' + ].join('\n') + const output = compileSource(source) + assert.match(output, /function show\(\) \{/) + for (const literal of [ + "'func print(1) is_end(\"x\") # \\'quoted\\''", + '"handle: print(2) rand(3) \\"quoted\\""', + '`first print(3) # **\nend \\`escaped\\` ${single} ${`nested print(4) ${double}`}`', + '`print(5) ${"needle".includes("need")}`' + ]) { + assert.ok(output.includes(literal), `Missing unchanged literal: ${literal}`) + } + const values = [] + runInNewContext(output, { console: { log: (...args) => values.push(args) } }) + assert.deepEqual(values, [[ + "func print(1) is_end(\"x\") # 'quoted'", + 'handle: print(2) rand(3) "quoted"', + 'first print(3) # **\nend `escaped` func print(1) is_end("x") # \'quoted\' nested print(4) handle: print(2) rand(3) "quoted"', + 'print(5) true' + ]]) +}) + +test('bootstrap compiler preserves line and block comments without executing their contents', () => { + const source = [ + 'const events = []', + '# print("from hash") func hidden():', + '// print("from slash") is_end("x")', + '/* print("from block")', + 'end func hidden(): */', + '** print("from dialect block")', + 'end is_includes("x") **', + 'events.push("safe")', + 'print(events.join(","))' + ].join('\n') + const output = compileSource(source) + for (const comment of [ + '// print("from hash") func hidden():', + '// print("from slash") is_end("x")', + '/* print("from block")\nend func hidden(): */', + '/* print("from dialect block")\nend is_includes("x") */' + ]) { + assert.ok(output.includes(comment), `Missing unchanged comment: ${comment}`) + } + const values = [] + runInNewContext(output, { console: { log: value => values.push(value) } }) + assert.deepEqual(values, ['safe']) +}) + +test('packaged compiler preserves literals and ignores comments', context => { + const directory = fixture(context) + const source = [ + 'const value = `print(1) # \\`quoted\\`', + 'end ${"func is_end(2)"}`', + '# print("unsafe hash")', + '/* print("unsafe block")', + 'end */', + 'print(value)' + ].join('\n') + fs.writeFileSync(path.join(directory, 'protected.bys'), source) + const compilation = invoke(['compile', 'protected.bys'], directory) + assert.equal(compilation.status, 0, compilation.stderr) + const output = fs.readFileSync(path.join(directory, 'protected.js'), 'utf8') + assert.ok(output.includes('`print(1) # \\`quoted\\`\nend ${"func is_end(2)"}`')) + assert.ok(output.includes('// print("unsafe hash")')) + assert.ok(output.includes('/* print("unsafe block")\nend */')) + const result = invoke(['run', 'protected.bys'], directory) + assert.equal(result.status, 0, result.stderr) + assert.equal(result.stdout.trim(), 'print(1) # `quoted`\nend func is_end(2)') + assert.doesNotMatch(result.stdout, /unsafe/) +}) + +test('global and command help succeeds', () => { + assert.match(invoke(['--help']).stdout, /run /) + for (const command of ['run', 'compile', 'print', 'delete']) { + const result = invoke([command, '--help']) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, new RegExp(`bynix ${command} .*`)) + } +}) + +test('run executes a source file without writing JavaScript', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'hello.bys'), + 'func greet():\n print("hello from run")\nend\ngreet()\n') + const result = invoke(['run', 'hello.bys'], directory) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, /hello from run/) + assert.equal(fs.existsSync(path.join(directory, 'hello.js')), false) +}) + +test('run reports runtime failures with a nonzero status', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'broken.bys'), 'missingFunction()\n') + const result = invoke(['run', 'broken.bys'], directory) + assert.notEqual(result.status, 0) + assert.match(result.stderr, /missingFunction is not defined/) +}) + +test('compile writes JavaScript without executing source code', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'hello.bynixscript'), 'print("not executed")\n') + const result = invoke(['compile', 'hello.bynixscript'], directory) + assert.equal(result.status, 0, result.stderr) + assert.doesNotMatch(result.stdout, /not executed/) + assert.match(fs.readFileSync(path.join(directory, 'hello.js'), 'utf8'), /console\.log\("not executed"\)/) + fs.writeFileSync(path.join(directory, 'module.mbs'), 'print("module")\n') + assert.equal(invoke(['compile', 'module.mbs'], directory).status, 0) + assert.ok(fs.existsSync(path.join(directory, 'module.mjs'))) +}) + +test('print translates without executing or writing a file', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'hello.bys'), 'print("print only")\n') + const result = invoke(['print', 'hello.bys'], directory) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, /console\.log\("print only"\)/) + assert.doesNotMatch(result.stdout, /^print only$/m) + assert.equal(fs.existsSync(path.join(directory, 'hello.js')), false) +}) + +test('delete only removes the specified file', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'remove.bys'), 'print("bye")\n') + fs.writeFileSync(path.join(directory, 'keep.bys'), 'print("stay")\n') + const result = invoke(['delete', 'remove.bys'], directory) + assert.equal(result.status, 0, result.stderr) + assert.equal(fs.existsSync(path.join(directory, 'remove.bys')), false) + assert.ok(fs.existsSync(path.join(directory, 'keep.bys'))) +}) + +test('missing arguments never delete a file and return failure', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'undefined.bys'), 'safe') + for (const command of ['run', 'compile', 'print', 'delete']) { + const result = invoke([command], directory) + assert.notEqual(result.status, 0) + assert.match(result.stderr, /missing required argument/) + } + assert.notEqual(invoke([], directory).status, 0) + assert.notEqual(invoke(['-d'], directory).status, 0) + assert.ok(fs.existsSync(path.join(directory, 'undefined.bys'))) +}) + +test('invalid paths and extensions return errors for every command', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'invalid.txt'), 'safe') + for (const command of ['run', 'compile', 'print', 'delete']) { + const missing = invoke([command, 'absent.bys'], directory) + assert.notEqual(missing.status, 0) + assert.match(missing.stderr, /File not found/) + const extension = invoke([command, 'invalid.txt'], directory) + assert.notEqual(extension.status, 0) + assert.match(extension.stderr, /Unsupported file extension/) + } + assert.ok(fs.existsSync(path.join(directory, 'invalid.txt'))) +}) + +test('legacy file options still work', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'legacy.bys'), 'print("legacy")\n') + assert.match(invoke(['-r', 'legacy.bys'], directory).stdout, /legacy/) + assert.match(invoke(['-p', 'legacy.bys'], directory).stdout, /console\.log/) + assert.equal(invoke(['-c', 'legacy.bys'], directory).status, 0) + assert.ok(fs.existsSync(path.join(directory, 'legacy.js'))) + assert.equal(invoke(['-d', 'legacy.bys'], directory).status, 0) + assert.equal(fs.existsSync(path.join(directory, 'legacy.bys')), false) +}) + +test('conflicting legacy commands fail without changing files', context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'safe.bys'), 'print("safe")\n') + const result = invoke(['-p', 'safe.bys', '-d', 'safe.bys'], directory) + assert.notEqual(result.status, 0) + assert.match(result.stderr, /Specify only one command/) + assert.ok(fs.existsSync(path.join(directory, 'safe.bys'))) +}) + +test('generated modules expose their source implementations', () => { + const exports = { + config: ['loadConfig'], download: ['https', 'getDownloads'], error: ['stackParsing'], + dirProcess: ['readAndProcessFilesInDirectory'], watcher: ['watching'] + } + for (const [name, expected] of Object.entries(exports)) { + assert.deepEqual(Object.keys(require(path.join(generated, `${name}.js`))), expected) + } +}) + +test('package contains the CLI, browser, and generated modules without js duplicates', () => { + const result = spawnSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], { + cwd: root, encoding: 'utf8' + }) + assert.equal(result.status, 0, result.stderr) + const files = new Set(JSON.parse(result.stdout)[0].files.map(file => file.path)) + for (const file of ['dist/index.min.cjs', 'dist/browser.js', + ...['config', 'download', 'error', 'dirProcess', 'watcher'].map(name => `dist/modules/${name}.js`)]) { + assert.ok(files.has(file), `${file} missing from package`) + } + for (const name of ['config', 'download', 'error', 'dirProcess', 'watcher']) { + assert.equal(files.has(`js/${name}.js`), false) + } +}) + +test('configuration wrappers preserve separate defaults and public exports', context => { + const directory = fixture(context) + const { loadConfig } = require('../dist/modules/config.js') + const legacy = require('../js/bsconfig.js') + const current = require('../js/bsc.js') + assert.equal(legacy.extensions.primary, '.bs') + assert.equal(current.extensions.primary, '.bys') + assert.equal(legacy.defaultConfig.extension.primary, '.bs') + assert.equal(current.defaultConfig.extension.primary, '.bys') + for (const name of ['defaultConfig', 'config', 'extensions', 'readFolder', 'toFolder', + 'watch', 'allowJs', 'strict', 'translate']) { + assert.ok(Object.hasOwn(current, name)) + assert.ok(Object.hasOwn(legacy, name)) + } + const file = path.join(directory, 'custom.json') + fs.writeFileSync(file, JSON.stringify({ extension: { secondary: '.custom' }, readFolder: 'input', watch: true })) + const loaded = loadConfig(file, '.bys') + assert.equal(loaded.extensions.primary, '.bys') + assert.equal(loaded.extensions.secondary, '.custom') + assert.equal(loaded.readFolder, 'input') + assert.equal(loaded.watch, true) + assert.equal(loadConfig(path.join(directory, 'missing.json'), '.bs').extensions.primary, '.bs') +}) + +test('generated error formatter preserves its public behavior', () => { + const { stackParsing } = require('../dist/modules/error.js') + assert.equal(stackParsing('test.bys', 'missing', '', null, 'Error'), 'missing') + assert.equal(stackParsing('test.bys', 'failed in bst.js', '', 'code', 'Error'), + 'Error: failed in test.bys') + assert.equal(stackParsing('test.bys', 'invalid', 'test.bys:3:1', 'first\nsecond\nthird', 'SyntaxError'), + 'SyntaxError: invalid\n at file test.bys\n\nSnippet:\nsecond\n') +}) + +test('generated download utility keeps its https export and reports counts', () => { + const { EventEmitter } = require('node:events') + const { https, getDownloads } = require('../dist/modules/download.js') + const originalGet = https.get + const originalLog = console.log + const messages = [] + try { + console.log = message => messages.push(message) + https.get = (url, callback) => { + assert.equal(url, 'https://api.npmjs.org/downloads/point/last-week/bynixscript') + const response = new EventEmitter() + callback(response) + response.emit('data', '{"downloads":42}') + response.emit('end') + return new EventEmitter() + } + getDownloads('bynixscript', 'week') + assert.deepEqual(messages, ['Package bynixscript downloaded 42 times.']) + } finally { + https.get = originalGet + console.log = originalLog + } +}) + +test('directory processor does not run on import and compiles file contents', async context => { + const directory = fixture(context) + fs.writeFileSync(path.join(directory, 'example.bs'), 'globalThis.bynixDirectoryTest = "print(1)"\n# print("unsafe")\n') + const modulePath = path.join(generated, 'dirProcess.js') + const importOnly = spawnSync(process.execPath, ['-e', `require(${JSON.stringify(modulePath)})`], { + cwd: directory, encoding: 'utf8' + }) + assert.equal(importOnly.status, 0, importOnly.stderr) + assert.equal(importOnly.stdout, '') + assert.equal(importOnly.stderr, '') + const { readAndProcessFilesInDirectory } = require(modulePath) + context.after(() => { delete globalThis.bynixDirectoryTest }) + readAndProcessFilesInDirectory(directory) + for (let attempt = 0; attempt < 100 && globalThis.bynixDirectoryTest === undefined; attempt++) { + await new Promise(resolve => setTimeout(resolve, 10)) + } + assert.equal(globalThis.bynixDirectoryTest, 'print(1)') +}) + +test('watcher compiles with the shared compiler and removes generated output', async context => { + const source = fixture(context) + const output = fixture(context) + const { watching } = require('../dist/modules/watcher.js') + const watcher = watching(source, output) + context.after(() => watcher.close()) + await new Promise((resolve, reject) => { + watcher.once('ready', resolve) + watcher.once('error', reject) + }) + const file = path.join(source, 'watched.bys') + const generated = path.join(output, 'watched.js') + fs.writeFileSync(file, 'print("watch")\n# print("not executed")\n') + async function waitUntil(predicate) { + for (let attempt = 0; attempt < 100; attempt++) { + if (predicate()) return + await new Promise(resolve => setTimeout(resolve, 20)) + } + assert.fail('watcher did not update the output') + } + await waitUntil(() => fs.existsSync(generated)) + assert.equal(fs.readFileSync(generated, 'utf8'), compileSource(fs.readFileSync(file, 'utf8'))) + fs.unlinkSync(file) + await waitUntil(() => !fs.existsSync(generated)) +}) + +test('browser bundle runs the shared compiler without changing strings or comments', async () => { + const browser = fs.readFileSync(path.join(root, 'dist', 'browser.js'), 'utf8') + const scripts = [] + let onReady + let removed = false + const tag = { + textContent: 'const text = "print(123)"\n# print("unsafe")\nprint(text)', + getAttribute: () => null, + remove: () => { removed = true } + } + const document = { + createElement: () => ({ classList: { add() {} } }), + head: { appendChild() {} }, + body: { appendChild: script => scripts.push(script.textContent) }, + addEventListener: (name, callback) => { onReady = callback }, + querySelectorAll: () => [tag] + } + runInNewContext(browser, { document, console }) + onReady() + await new Promise(resolve => setImmediate(resolve)) + assert.equal(removed, true) + assert.equal(scripts.length, 1) + assert.ok(scripts[0].includes('"print(123)"')) + assert.ok(scripts[0].includes('// print("unsafe")')) + const printed = [] + runInNewContext(scripts[0], { console: { log: value => printed.push(value) } }) + assert.deepEqual(printed, ['print(123)']) +}) + +test('browser bundle fetches sources and honors allowJs', async () => { + const browser = fs.readFileSync(path.join(root, 'dist', 'browser.js'), 'utf8') + const scripts = [] + let onReady + const tag = { + getAttribute: name => ({ src: 'remote.bys', allowJs: 'true' })[name] ?? null, + remove() {} + } + const document = { + createElement: () => ({ classList: { add() {} } }), + head: { appendChild() {} }, + body: { appendChild: script => scripts.push(script.textContent) }, + addEventListener: (name, callback) => { onReady = callback }, + querySelectorAll: () => [tag] + } + runInNewContext(browser, { + document, console, + fetch: async file => { + assert.equal(file, 'remote.bys') + return { ok: true, text: async () => 'const message = "print(1)"\nprint(message)\n' } + } + }) + onReady() + await new Promise(resolve => setImmediate(resolve)) + assert.equal(scripts.length, 1) + assert.ok(scripts[0].includes('"print(1)"')) +})