diff --git a/src/cli.ts b/src/cli.ts index 94165e1a09..6b77ea6568 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -26,6 +26,7 @@ import { fetch, fs, path, + stdin, VERSION, } from './index.js' import { installDeps, parseDeps } from './deps.js' @@ -113,100 +114,95 @@ export async function main() { await startRepl() return } - if (argv.eval) { - await runScript(argv.eval, argv.ext) - return - } - const [firstArg] = argv._ - updateArgv(argv._.slice(firstArg === undefined ? 0 : 1)) - if (!firstArg || firstArg === '-') { - const success = await scriptFromStdin(argv.ext) - if (!success) { - printUsage() - process.exitCode = 1 - } - return - } - if (/^https?:/.test(firstArg)) { - await scriptFromHttp(firstArg, argv.ext) - return - } - const filepath = firstArg.startsWith('file:') - ? url.fileURLToPath(firstArg) - : path.resolve(firstArg) - await importPath(filepath) -} -export async function runScript(script: string, ext?: string) { - const filepath = getFilepath($.cwd, 'zx', ext) - await writeAndImport(script, filepath) + const { script, scriptPath, tempPath } = await readScript() + await runScript(script, scriptPath, tempPath) } -export async function scriptFromStdin(ext?: string): Promise { - let script = '' - if (!process.stdin.isTTY) { - process.stdin.setEncoding('utf8') - for await (const chunk of process.stdin) { - script += chunk +async function runScript( + script: string, + scriptPath: string, + tempPath: string +): Promise { + const rmTemp = () => fs.rmSync(tempPath, { force: true }) + try { + if (tempPath) { + scriptPath = tempPath + await fs.writeFile(tempPath, script) } - if (script.length > 0) { - await runScript(script, ext) - return true + if (argv.install) { + await installDeps( + parseDeps(script), + path.dirname(scriptPath), + argv.registry + ) } - } - return false -} -export async function scriptFromHttp(remote: string, _ext?: string) { - const res = await fetch(remote) - if (!res.ok) { - console.error(`Error: Can't get ${remote}`) - process.exit(1) - } - const script = await res.text() - const pathname = new URL(remote).pathname - const { name, ext } = path.parse(pathname) - const filepath = getFilepath($.cwd, name, _ext || ext) - await writeAndImport(script, filepath) -} + injectGlobalRequire(scriptPath) + process.once('exit', rmTemp) -export async function writeAndImport( - script: string | Buffer, - filepath: string, - origin = filepath -) { - await fs.writeFile(filepath, script) - try { - process.once('exit', () => fs.rmSync(filepath, { force: true })) - await importPath(filepath, origin) + // TODO: fix unanalyzable-dynamic-import to work correctly with jsr.io + await import(url.pathToFileURL(scriptPath).toString()) } finally { - await fs.rm(filepath) + rmTemp() } } -export async function importPath( - filepath: string, - origin = filepath -): Promise { - const contents = await fs.readFile(filepath, 'utf8') - const { ext, base, dir } = path.parse(filepath) - const tempFilename = getFilepath(dir, base) +async function readScript() { + const [firstArg] = argv._ + let script = '' + let scriptPath = '' + let tempPath = '' + let argSlice = 1 + + if (argv.eval) { + argSlice = 0 + script = argv.eval + tempPath = getFilepath($.cwd, 'zx', argv.ext) + } else if (!firstArg || firstArg === '-') { + script = await readScriptFromStdin() + tempPath = getFilepath($.cwd, 'zx', argv.ext) + if (script.length === 0) { + printUsage() + process.exitCode = 1 + throw new Error('No script provided') + } + } else if (/^https?:/.test(firstArg)) { + const { name, ext = argv.ext } = path.parse(new URL(firstArg).pathname) + script = await readScriptFromHttp(firstArg) + tempPath = getFilepath($.cwd, name, ext) + } else { + script = await fs.readFile(firstArg, 'utf8') + scriptPath = firstArg.startsWith('file:') + ? url.fileURLToPath(firstArg) + : path.resolve(firstArg) + } + const { ext, base, dir } = path.parse(tempPath || scriptPath) if (ext === '') { - return writeAndImport(contents, tempFilename, origin) + tempPath = getFilepath(dir, base) } if (ext === '.md') { - return writeAndImport(transformMarkdown(contents), tempFilename, origin) - } - if (argv.install) { - const deps = parseDeps(contents) - await installDeps(deps, dir, argv.registry) + script = transformMarkdown(script) + tempPath = getFilepath(dir, base) } + if (argSlice) updateArgv(argv._.slice(argSlice)) + + return { script, scriptPath, tempPath } +} + +async function readScriptFromStdin(): Promise { + return !process.stdin.isTTY ? stdin() : '' +} - injectGlobalRequire(origin) - // TODO: fix unanalyzable-dynamic-import to work correctly with jsr.io - await import(url.pathToFileURL(filepath).toString()) +async function readScriptFromHttp(remote: string): Promise { + const res = await fetch(remote) + if (!res.ok) { + console.error(`Error: Can't get ${remote}`) + process.exit(1) + } + return res.text() } export function injectGlobalRequire(origin: string) { diff --git a/test/cli.test.js b/test/cli.test.js index a63275d007..8a095ec7e2 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -191,7 +191,7 @@ describe('cli', () => { const port = await getPort() const server = await getServer([resp]).start(port) const out = - await $`node build/cli.js --verbose http://127.0.0.1:${port}/echo.mjs` + await $`node build/cli.js --verbose http://127.0.0.1:${port}/script.mjs` assert.match(out.stderr, /test/) await server.stop() }) @@ -204,6 +204,16 @@ describe('cli', () => { await server.stop() }) + test('scripts (md) from https', async () => { + const resp = await fs.readFile(path.resolve('test/fixtures/md.http')) + const port = await getPort() + const server = await getServer([resp]).start(port) + const out = + await $`node build/cli.js --verbose http://127.0.0.1:${port}/script.md` + assert.match(out.stderr, /md/) + await server.stop() + }) + test('scripts with no extension', async () => { await $`node build/cli.js test/fixtures/no-extension` assert.ok( @@ -237,10 +247,6 @@ describe('cli', () => { await $`node build/cli.js test/fixtures/markdown.md` }) - test('markdown scripts are working', async () => { - await $`node build/cli.js test/fixtures/markdown.md` - }) - test('markdown scripts are working for CRLF', async () => { const p = await $`node build/cli.js test/fixtures/markdown-crlf.md` assert.ok(p.stdout.includes('Hello, world!')) diff --git a/test/export.test.js b/test/export.test.js index 83ef319ca0..6e07584e51 100644 --- a/test/export.test.js +++ b/test/export.test.js @@ -75,17 +75,12 @@ describe('cli', () => { assert.equal(typeof cli.argv.v, 'boolean', 'cli.argv.v') assert.equal(typeof cli.argv.verbose, 'boolean', 'cli.argv.verbose') assert.equal(typeof cli.argv.version, 'boolean', 'cli.argv.version') - assert.equal(typeof cli.importPath, 'function', 'cli.importPath') assert.equal(typeof cli.injectGlobalRequire, 'function', 'cli.injectGlobalRequire') assert.equal(typeof cli.isMain, 'function', 'cli.isMain') assert.equal(typeof cli.main, 'function', 'cli.main') assert.equal(typeof cli.normalizeExt, 'function', 'cli.normalizeExt') assert.equal(typeof cli.printUsage, 'function', 'cli.printUsage') - assert.equal(typeof cli.runScript, 'function', 'cli.runScript') - assert.equal(typeof cli.scriptFromHttp, 'function', 'cli.scriptFromHttp') - assert.equal(typeof cli.scriptFromStdin, 'function', 'cli.scriptFromStdin') assert.equal(typeof cli.transformMarkdown, 'function', 'cli.transformMarkdown') - assert.equal(typeof cli.writeAndImport, 'function', 'cli.writeAndImport') }) }) diff --git a/test/fixtures/md.http b/test/fixtures/md.http new file mode 100644 index 0000000000..c6f67d7742 --- /dev/null +++ b/test/fixtures/md.http @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Content-Type: plain/text; charset=UTF-8 +Content-Length: 15 +Server: netcat! + +# Title +```js +$`echo 'md'` +```