diff --git a/js/bsc.js b/js/bsc.js index 46c72e1..efc0902 100644 --- a/js/bsc.js +++ b/js/bsc.js @@ -1 +1 @@ -module.exports = require('./config.js').loadConfig('./bsc.json', '.bys') +module.exports = require('../dist/modules/config.js').loadConfig('./bsc.json', '.bys') diff --git a/js/bsconfig.js b/js/bsconfig.js index 765c9af..737dbd7 100644 --- a/js/bsconfig.js +++ b/js/bsconfig.js @@ -1 +1 @@ -module.exports = require('./config.js').loadConfig('./bsconfig.json', '.bs') +module.exports = require('../dist/modules/config.js').loadConfig('./bsconfig.json', '.bs') diff --git a/js/config.js b/js/config.js deleted file mode 100644 index b5017e5..0000000 --- a/js/config.js +++ /dev/null @@ -1,31 +0,0 @@ -const fs = require('node:fs'); - -function loadConfig(filename, primary) { - const defaultConfig = { - extension: { primary, secondary: '.bynixscript', module: '.mbs' }, - readFolder: './', toFolder: './', watch: false, - allowJs: false, strict: false, translate: 'javascript' - } - var config - try { - config = JSON.parse(fs.readFileSync(filename, 'utf8')); - } catch (error) { - 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 - } - 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 - } -} - -module.exports = { loadConfig } diff --git a/js/dirProcess.js b/js/dirProcess.js deleted file mode 100644 index 46ae5e8..0000000 --- a/js/dirProcess.js +++ /dev/null @@ -1,29 +0,0 @@ -const fs = require('node:fs'); -const path = require('node:path'); -const vm = require('node:vm'); -const { compileSource } = require('../scripts/compile.cjs'); - -function 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; - } - try { - new vm.Script(compileSource(data), { filename: filePath }).runInThisContext() - } catch (executionError) { - console.error(`Error executing file ${file}:`, executionError) - } - }) - }) - }) -} - -module.exports = { readAndProcessFilesInDirectory } diff --git a/js/download.js b/js/download.js deleted file mode 100644 index 2a008e9..0000000 --- a/js/download.js +++ /dev/null @@ -1,15 +0,0 @@ -const https = require('node:https'); - -function 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}`) }); -} - -module.exports = { https, getDownloads } diff --git a/js/error.js b/js/error.js deleted file mode 100644 index a0df077..0000000 --- a/js/error.js +++ /dev/null @@ -1,34 +0,0 @@ -function stackParsing(filename, errorMsg, errorStack, code, type, modulLocation) { - if (code === undefined || code === null) { - return errorMsg - } - 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` - } else if (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; - } - 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; - } - 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}` - } -} - -module.exports = { stackParsing } diff --git a/js/watcher.js b/js/watcher.js deleted file mode 100644 index 98f338e..0000000 --- a/js/watcher.js +++ /dev/null @@ -1,43 +0,0 @@ -const chokidar = require('chokidar'); -const fs = require('node:fs'); -const path = require('node:path'); -const { compileSource } = require('../scripts/compile.cjs'); - -function watching(directory, directory2) { - const watcher = chokidar.watch(directory, { ignored: /(^|[\/\\])\../, persistent: true }); - const extensions = ['.bs', '.bys', '.bynixscript', '.mbs']; - function outputPath(filePath) { - const extension = path.extname(filePath); - if (!extensions.includes(extension)) { - return null - } - return path.join(directory2, path.basename(filePath, extension) + (extension === '.mbs' ? '.mjs' : '.js')); - } - function compileFile(filePath) { - const output = outputPath(filePath); - if (!output) { - return; - } - try { - const source = fs.readFileSync(filePath, 'utf8'); - const code = compileSource(source); - fs.writeFileSync(output, code); - console.log(`File ${output} has been created or changed.`) - } catch (error) { - console.error(`Error compiling ${filePath}:`, error) - } - } - function removeFile(filePath) { - const output = outputPath(filePath); - if (output && fs.existsSync(output)) { - fs.unlinkSync(output); - console.log(`File ${output} has been deleted.`) - } - } - 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 -} - -module.exports = { watching } diff --git a/scripts/build.cjs b/scripts/build.cjs index 203dbba..b46d3d0 100644 --- a/scripts/build.cjs +++ b/scripts/build.cjs @@ -7,15 +7,11 @@ 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 modules) { - const code = compileSource(fs.readFileSync(path.join(source, `${name}.bs`), 'utf8')) - fs.writeFileSync(path.join(root, 'js', `${name}.js`), - code.endsWith('\n') ? code : code + '\n') - } 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')), @@ -23,6 +19,24 @@ async function main() { } } + 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, @@ -30,7 +44,18 @@ async function main() { format: 'cjs', bundle: true, minify: true, - target: 'node20' + 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({ diff --git a/src/translate.bs b/src/translate.bs index 20e6090..4644bc2 100644 --- a/src/translate.bs +++ b/src/translate.bs @@ -1,6 +1,6 @@ const fs = require('node:fs') const path = require('node:path') -const { extensions } = require('../js/config.js').loadConfig('./bsc.json', '.bys') +const { extensions } = require('./config.js').loadConfig('./bsc.json', '.bys') const { compileSource } = require('../scripts/compile.cjs') func resolveFile(file): diff --git a/tests/cli.test.cjs b/tests/cli.test.cjs index 30d6fcd..b1f8b56 100644 --- a/tests/cli.test.cjs +++ b/tests/cli.test.cjs @@ -9,6 +9,7 @@ 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' }) @@ -26,6 +27,10 @@ test('build produces the packaged executable', () => { }) 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) }) @@ -231,18 +236,34 @@ test('conflicting legacy commands fail without changing files', context => { assert.ok(fs.existsSync(path.join(directory, 'safe.bys'))) }) -test('generated modules match their BynixScript sources', () => { +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']) { - const source = fs.readFileSync(path.join(root, 'src', `${name}.bs`), 'utf8') - const code = compileSource(source) - assert.equal(fs.readFileSync(path.join(root, 'js', `${name}.js`), 'utf8'), - code.endsWith('\n') ? code : code + '\n') + 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('../js/config.js') + 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') @@ -265,7 +286,7 @@ test('configuration wrappers preserve separate defaults and public exports', con }) test('generated error formatter preserves its public behavior', () => { - const { stackParsing } = require('../js/error.js') + 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') @@ -275,7 +296,7 @@ test('generated error formatter preserves its public behavior', () => { test('generated download utility keeps its https export and reports counts', () => { const { EventEmitter } = require('node:events') - const { https, getDownloads } = require('../js/download.js') + const { https, getDownloads } = require('../dist/modules/download.js') const originalGet = https.get const originalLog = console.log const messages = [] @@ -300,7 +321,7 @@ test('generated download utility keeps its https export and reports counts', () 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(root, 'js', 'dirProcess.js') + const modulePath = path.join(generated, 'dirProcess.js') const importOnly = spawnSync(process.execPath, ['-e', `require(${JSON.stringify(modulePath)})`], { cwd: directory, encoding: 'utf8' }) @@ -319,7 +340,7 @@ test('directory processor does not run on import and compiles file contents', as test('watcher compiles with the shared compiler and removes generated output', async context => { const source = fixture(context) const output = fixture(context) - const { watching } = require('../js/watcher.js') + const { watching } = require('../dist/modules/watcher.js') const watcher = watching(source, output) context.after(() => watcher.close()) await new Promise((resolve, reject) => {