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) +})