diff --git a/bin/cloudworker.js b/bin/cloudworker.js index 2abb9c4..3f430cf 100755 --- a/bin/cloudworker.js +++ b/bin/cloudworker.js @@ -20,6 +20,8 @@ program .option('-d, --debug', 'Debug', false) .option('-s, --kv-set [variable.key=value]', 'Binds variable to a local implementation of Workers KV and sets key to value', collect, []) .option('-f, --kv-file [variable=path]', 'Set the filepath for value peristence for the local implementation of Workers KV', collect, []) + .option('-b, --bind [variable=value]', 'Binds variable to the value provided', collect, []) + .option('-a, --bind-file [variable=path]', 'Binds variable to the contents of the given file', collect, []) .option('-w, --wasm [variable=path]', 'Binds variable to wasm located at path', collect, []) .option('-c, --enable-cache', 'Enables cache ', false) .option('-r, --watch', 'Watch the worker script and restart the worker when changes are detected', false) @@ -44,8 +46,9 @@ function run (file, wasmBindings) { console.log('Starting up...') const fullpath = path.resolve(process.cwd(), file) const script = utils.read(fullpath) - const bindings = utils.extractKVBindings(program.kvSet.concat(program.set), program.kvFile) - Object.assign(bindings, wasmBindings) + const kvBindings = utils.extractKVBindings(program.kvSet.concat(program.set), program.kvFile) + const bindings = utils.extractBindings(program.bind, program.bindFile) + Object.assign(bindings, kvBindings, wasmBindings) // Add a warning log for deprecation if (program.set.length > 0) console.warn('Warning: Flag --set is now deprecated, please use --kv-set instead') diff --git a/lib/__tests__/utils.test.js b/lib/__tests__/utils.test.js index 25cbb26..ca596dc 100644 --- a/lib/__tests__/utils.test.js +++ b/lib/__tests__/utils.test.js @@ -35,4 +35,72 @@ describe('utils', () => { cb() }) + + test('extractKVBindings throws on invalid filepath format', async () => { + expect(() => { utils.extractKVBindings([], ['invalid format']) }).toThrow() + }) + + test('extractKVBindings handles files and sets', async (cb) => { + const kv = new KeyValueStore(path.resolve('test-kv.json')) + kv.put('hello', 'world') + + const bindings = utils.extractKVBindings(['test.this=great'], ['test=test-kv.json']) + const {test} = bindings + + fs.unlinkSync(kv.path) + expect(await test.get('hello')).toEqual('world') + expect(await test.get('this')).toEqual('great') + + cb() + }) + + test('extractBindings throws on invalid format', async () => { + expect(() => { utils.extractBindings(['invalid format'], []) }).toThrow() + }) + + test('extractBindings parses properly', async () => { + const bindings = utils.extractBindings(['foo=bar', 'baz=qux'], []) + expect(bindings.foo).toEqual('bar') + expect(bindings.baz).toEqual('qux') + }) + + test('extractBindings allows = in value', async () => { + const bindings = utils.extractBindings(['foo="const bar=\'abc\';"'], []) + expect(bindings.foo).toEqual('"const bar=\'abc\';"') + }) + + test('extractBindings handles file as binding value', async () => { + const content = JSON.stringify({ + foo: 'abc', + bar: 12345, + baz: { + qux: ['a', 'b', 'c', 'd'], + plugh: [ { id: 987 }, { id: 876 } ], + }, + }) + const path = '/tmp/__cloudworker_test.json' + await fs.writeFileSync(path, content) + + const bindings = utils.extractBindings([], [`__DATA=${path}`]) + expect(bindings.__DATA).toEqual(content) + + await fs.unlinkSync(path) + }) + + test('extractBindings thows on invalid file format', async () => { + expect(() => { utils.extractBindings([], ['invalid file format']) }).toThrow() + }) + + test('extractBindings throw on nonexistent path', async () => { + expect(() => { utils.extractBindings([], ['foo=/tmp/__cloudworker_fake_file.json']) }).toThrow() + }) + + test('parseWasmFlags throws on invalid format', async () => { + expect(() => { utils.parseWasmFlags(['invalid format']) }).toThrow() + }) + + test('parseWasmFlags parses properly', async () => { + const bindings = utils.parseWasmFlags(['foo=bar']) + expect(bindings.foo).toEqual('bar') + }) }) diff --git a/lib/utils.js b/lib/utils.js index 57a811c..b44f64b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,61 +4,82 @@ module.exports.read = f => fs.readFileSync(f).toString('utf-8') module.exports.extractKVBindings = (kvSetFlags, kvFileFlags) => { const bindings = {} - const filepaths = extractKVPaths(kvFileFlags) + const filepaths = parseFlags('kv-file', kvFileFlags) for (const [variable, path] of Object.entries(filepaths)) { bindings[variable] = new KeyValueStore(path) } - for (const flag of kvSetFlags) { - const comps = flag.split('.') - if (comps.length < 2 || comps[1].split('=').length < 2) { - throw new Error('Invalid kv-set flag format. Expected format of [variable].[key]=[value]') - } - - const variable = comps[0] - const kvFragment = comps.slice(1).join('.') - const kvComponents = kvFragment.split('=') - const key = kvComponents[0] - const value = kvComponents.slice(1).join('=') + const kvStores = parseFlags('kv-set', kvSetFlags, true) + for (const [variable, obj] of Object.entries(kvStores)) { if (!bindings[variable]) { bindings[variable] = new KeyValueStore(filepaths[variable]) } - bindings[variable].put(key, value) + for (const [key, value] of Object.entries(obj)) { + bindings[variable].put(key, value) + } } return bindings } -const extractKVPaths = (kvFileFlags) => { - const paths = {} +module.exports.extractBindings = (bindingFlags, bindingFileFlags) => { + return Object.assign( + {}, + parseFlags('bind', bindingFlags), + parseFlags('bind-file', bindingFileFlags, false, (variable, filepath) => { + if (!fs.existsSync(filepath)) { + throw new Error(`Invalid bind-file path "${filepath}"`) + } - if (!kvFileFlags) return paths + return fs.readFileSync(filepath).toString('utf-8') + }) + ) +} - for (const flag of kvFileFlags) { - const components = flag.split('=') +module.exports.parseWasmFlags = (wasmFlags) => parseFlags('wasm', wasmFlags) - if (flag.length < 2) { - throw new Error('Invalid kv-file flag format. Expected format of [variable]=[value]') - } +/** + * Parse flags into bindings. + * + * @param {string} type Type of binding being parsed. + * @param {Array} flags Command line flags to parse. + * @param {boolean} objectVariable Whether the variable represents an object name and key + * @param {Function} handleVariable Function to call for custom variable/value handling. + * + * @returns {Object} bindings The variable bindings parsed from the flags. + */ +function parseFlags (type, flags = [], objectVariable = false, handleVariable = null) { + const bindings = {} - paths[components[0]] = components[1] - } + for (const flag of flags) { + if (objectVariable) { + const comps = flag.split('.') + if (comps.length < 2 || comps[1].split('=').length < 2) { + throw new Error(`Invalid ${type} flag format. Expected format of [variable].[key]=[value]`) + } - return paths -} + const variable = comps[0] + const kvFragment = comps.slice(1).join('.') + const kvComponents = kvFragment.split('=') + const key = kvComponents[0] + const value = kvComponents.slice(1).join('=') -module.exports.parseWasmFlags = (wasmFlags) => { - const bindings = {} - for (const flag of wasmFlags) { - const comps = flag.split('=') - if (comps.length !== 2) { - throw new Error('Invalid wasm flag format. Expected format of [variable=path]') + bindings[variable] = Object.assign({}, bindings[variable], { [key]: value }) + } else { + const comps = flag.split('=') + if (comps.length < 2) { + throw new Error(`Invalid ${type} flag format. Expected format of [variable]=[value]`) + } + + const variable = comps[0] + const value = comps.slice(1).join('=') + if (handleVariable) bindings[variable] = handleVariable(variable, value) + else bindings[variable] = value } - const [variable, path] = comps - bindings[variable] = path } + return bindings }