From 0f184b40928d27a653d5f2fbeab3021d7175a1c3 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Thu, 24 Sep 2026 05:53:53 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Rewrite=20B?=
=?UTF-8?q?ynix=20CLI=20commands=20and=20add=20executable=20build=20step?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 3 +-
README.md | 21 +++++--
docs/markdown/command.md | 61 +++++--------------
package.json | 4 +-
scripts/build.cjs | 52 ++++++++++++++++
src/bsd.bs | 24 +++-----
src/bsp.bs | 73 ++---------------------
src/bsr.bs | 78 ++++--------------------
src/bst.bs | 97 ++++--------------------------
src/index.bs | 49 ++++++++++-----
src/translate.bs | 46 ++++++++++++++
tests/cli.test.cjs | 126 +++++++++++++++++++++++++++++++++++++++
12 files changed, 332 insertions(+), 302 deletions(-)
create mode 100644 scripts/build.cjs
create mode 100644 src/translate.bs
create mode 100644 tests/cli.test.cjs
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..73b58c7 100644
--- a/README.md
+++ b/README.md
@@ -25,19 +25,28 @@ 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
```
### Using CDN
```html
diff --git a/docs/markdown/command.md b/docs/markdown/command.md
index a799299..9f1e796 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` to produce `dist/index.min.cjs`. `npm test` builds it and runs the CLI tests.
diff --git a/package.json b/package.json
index a58745b..6ca3865 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,9 @@
"browser": "js/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"
diff --git a/scripts/build.cjs b/scripts/build.cjs
new file mode 100644
index 0000000..bfeb90f
--- /dev/null
+++ b/scripts/build.cjs
@@ -0,0 +1,52 @@
+const fs = require('node:fs')
+const path = require('node:path')
+const { build } = require('esbuild')
+const { parseCode, addSemicolons } = require('../js/parser.js')
+const { funcReplace } = require('bynixscript/dist/parser/funcDecl.js')
+
+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')
+
+function translateParser(sourceCode) {
+ return funcReplace(addSemicolons(parseCode(sourceCode))).replace(/\.change\(/g, '.replace(')
+}
+
+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')),
+ fs.readFileSync(path.join(source, name.name), 'utf8'))
+ }
+ }
+
+ const parserOutput = path.join(temporary, 'parser')
+ fs.mkdirSync(parserOutput)
+ for (const name of fs.readdirSync(path.join(source, 'parser'))) {
+ if (name.endsWith('.bs')) {
+ const code = fs.readFileSync(path.join(source, 'parser', name), 'utf8')
+ fs.writeFileSync(path.join(parserOutput, name.replace(/\.bs$/, '.js')), translateParser(code))
+ }
+ }
+
+ await build({
+ entryPoints: [path.join(temporary, 'index.js')],
+ outfile: output,
+ platform: 'node',
+ format: 'cjs',
+ bundle: true,
+ minify: true,
+ target: 'node20'
+ })
+ fs.chmodSync(output, 0o755)
+ } finally {
+ fs.rmSync(temporary, { recursive: true, force: true })
+ }
+}
+
+main().catch(error => {
+ console.error(error)
+ process.exitCode = 1
+})
diff --git a/src/bsd.bs b/src/bsd.bs
index 5a411b6..4fa860c 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
-end
+function bsdCommand(file) {
+ const filePath = resolveFile(file)
+ fs.unlinkSync(filePath)
+ console.log(`Deleted ${file}`)
+}
-module.exports = { bsdCommand }
\ No newline at end of file
+module.exports = { bsdCommand }
diff --git a/src/bsp.bs b/src/bsp.bs
index b78b559..ebd2903 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
-};
+function bspCommand(file) {
+ const { source } = readSource(file)
+ console.log(translate(source))
+}
-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
-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..a367e2d 100644
--- a/src/bsr.bs
+++ b/src/bsr.bs
@@ -1,67 +1,15 @@
-#!/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
-end
+function bsrCommand(file) {
+ const { source, filePath } = readSource(file)
+ const code = translate(source)
+ vm.runInNewContext(code, {
+ console,
+ process,
+ require: createRequire(filePath)
+ }, { filename: filePath })
+}
-module.exports = { bsrCommand };
\ No newline at end of file
+module.exports = { bsrCommand }
diff --git a/src/bst.bs b/src/bst.bs
index f44b3bb..798e0db 100644
--- a/src/bst.bs
+++ b/src/bst.bs
@@ -1,86 +1,15 @@
-#!/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
-};
+function 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')
+ console.log(`Compiled ${file} to ${output}`)
+}
-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
-end
-
-module.exports = { bstCommand }
\ No newline at end of file
+module.exports = { bstCommand }
diff --git a/src/index.bs b/src/index.bs
index 473a79b..0aed073 100644
--- a/src/index.bs
+++ b/src/index.bs
@@ -1,28 +1,45 @@
#!/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')
+program.action(() => {})
-const options = program.opts()
+try {
+ program.parse()
+ const options = program.opts()
+ const selected = [
+ ['run', bsrCommand], ['compile', bstCommand],
+ ['print', bspCommand], ['delete', bsdCommand]
+ ].filter(([name]) => options[name] !== undefined)
-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 Error('Specify only one command at a time.')
+ }
+ if (selected.length === 1) {
+ const [name, command] = selected[0]
+ command(options[name])
+ } else if (process.argv.length === 2) {
+ program.error('A command is required.')
+ }
+} catch (error) {
+ console.error(`Error: ${error.message}`)
+ process.exitCode = 1
+}
diff --git a/src/translate.bs b/src/translate.bs
new file mode 100644
index 0000000..9451d04
--- /dev/null
+++ b/src/translate.bs
@@ -0,0 +1,46 @@
+const fs = require('node:fs')
+const path = require('node:path')
+const { extensions } = require('../js/bsc.js')
+const { parsing, parsingMsg, parseCode, addSemicolons } = require('../js/parser.js')
+const replacements = require('./parser/parsingDecl.js')
+
+function resolveFile(file) {
+ if (typeof file !== 'string' || file.trim() === '') {
+ throw new Error('A file path is required.')
+ }
+ if (![extensions.primary, extensions.secondary, extensions.module].some(extension => file.endsWith(extension))) {
+ throw new Error(`Unsupported file extension: ${file}. Use ${extensions.primary}, ${extensions.secondary}, or ${extensions.module}.`)
+ }
+ const filePath = path.resolve(file)
+ if (!fs.existsSync(filePath)) {
+ throw new Error(`File not found: ${file}`)
+ }
+ if (!fs.statSync(filePath).isFile()) {
+ throw new Error(`Not a file: ${file}`)
+ }
+ return filePath
+}
+
+function readSource(file) {
+ const filePath = resolveFile(file)
+ return { filePath, source: fs.readFileSync(filePath, 'utf8') }
+}
+
+function translate(source) {
+ let code = addSemicolons(parseCode(source))
+ if (parsing(code) === false) {
+ throw new Error(parsingMsg.message)
+ }
+ for (const name of [
+ 'flowReplace', 'utilityReplace', 'funcReplace', 'condReplace',
+ 'forEachReplace', 'reassignReplace', 'assignReplace', 'logReplace',
+ 'interactReplace', 'mathReplace', 'commentReplace', 'asyncReplace',
+ 'forReplace', 'convReplace', 'checkReplace', 'oopReplace',
+ 'excepReplace', 'domReplace'
+ ]) {
+ code = replacements[name](code)
+ }
+ return code
+}
+
+module.exports = { resolveFile, readSource, translate, extensions }
diff --git a/tests/cli.test.cjs b/tests/cli.test.cjs
new file mode 100644
index 0000000..020f7db
--- /dev/null
+++ b/tests/cli.test.cjs
@@ -0,0 +1,126 @@
+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 root = path.resolve(__dirname, '..')
+const binary = path.join(root, 'dist', 'index.min.cjs')
+
+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())
+ assert.match(fs.readFileSync(binary, 'utf8'), /^#!\/usr\/bin\/env node/)
+ assert.equal(invoke(['--version']).status, 0)
+})
+
+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)
+})