diff --git a/packages/api-server/dist.test.ts b/packages/api-server/dist.test.ts index 120759bd60bc..5dbc290a5937 100644 --- a/packages/api-server/dist.test.ts +++ b/packages/api-server/dist.test.ts @@ -37,6 +37,7 @@ describe('dist', () => { "apiCliOptions": { "apiRootPath": { "alias": [ + "api-root-path", "rootPath", "root-path", ], @@ -74,7 +75,7 @@ describe('dist', () => { "webCliOptions": { "apiHost": { "alias": "api-host", - "desc": "Forward requests from the apiUrl, defined in redwood.toml to this host", + "desc": "Forward requests from the apiUrl, defined in redwood.toml, to this host", "type": "string", }, "port": { diff --git a/packages/api-server/src/cliHandlers.ts b/packages/api-server/src/cliHandlers.ts index ad04725e46ee..a1fadee69b47 100644 --- a/packages/api-server/src/cliHandlers.ts +++ b/packages/api-server/src/cliHandlers.ts @@ -29,7 +29,7 @@ export const apiCliOptions = { port: { default: getConfig().api?.port || 8911, type: 'number', alias: 'p' }, socket: { type: 'string' }, apiRootPath: { - alias: ['rootPath', 'root-path'], + alias: ['api-root-path', 'rootPath', 'root-path'], default: '/', type: 'string', desc: 'Root path where your api functions are served', @@ -49,7 +49,7 @@ export const webCliOptions = { apiHost: { alias: 'api-host', type: 'string', - desc: 'Forward requests from the apiUrl, defined in redwood.toml to this host', + desc: 'Forward requests from the apiUrl, defined in redwood.toml, to this host', }, } as const @@ -128,9 +128,24 @@ export const bothServerHandler = async (options: BothServerArgs) => { export const webServerHandler = async (options: WebServerArgs) => { const { port, socket, apiHost } = options + const apiUrl = getConfig().web.apiUrl + + if (!apiHost && !isFullyQualifiedUrl(apiUrl)) { + console.error( + `${c.red('Error')}: If you don't provide ${c.magenta( + 'apiHost' + )}, ${c.magenta( + 'apiUrl' + )} needs to be a fully-qualified URL. But ${c.magenta( + 'apiUrl' + )} is ${c.yellow(apiUrl)}.` + ) + process.exitCode = 1 + return + } + const tsServer = Date.now() process.stdout.write(c.dim(c.italic('Starting Web Server...\n'))) - const apiUrl = getConfig().web.apiUrl // Construct the graphql url from apiUrl by default // But if apiGraphQLUrl is specified, use that instead const graphqlEndpoint = coerceRootPath( @@ -172,3 +187,13 @@ function coerceRootPath(path: string) { return `${prefix}${path}${suffix}` } + +function isFullyQualifiedUrl(url: string) { + try { + // eslint-disable-next-line no-new + new URL(url) + return true + } catch (e) { + return false + } +} diff --git a/packages/api-server/src/index.ts b/packages/api-server/src/index.ts index dedf60785c19..df9ab77f35b3 100644 --- a/packages/api-server/src/index.ts +++ b/packages/api-server/src/index.ts @@ -1,4 +1,5 @@ #!/usr/bin/env node + import { hideBin } from 'yargs/helpers' import yargs from 'yargs/yargs' @@ -13,29 +14,38 @@ import { export * from './types' -const positionalArgs = yargs(hideBin(process.argv)).parseSync()._ - -// "bin": { -// "rw-api-server-watch": "./dist/watch.js", -// "rw-log-formatter": "./dist/logFormatter/bin.js", -// "rw-server": "./dist/index.js" -// }, - if (require.main === module) { - if (positionalArgs.includes('api') && !positionalArgs.includes('web')) { - apiServerHandler( - yargs(hideBin(process.argv)).options(apiCliOptions).parseSync() + yargs(hideBin(process.argv)) + .scriptName('rw-server') + .usage('usage: $0 ') + .strict() + + .command( + '$0', + 'Run both api and web servers', + // @ts-expect-error just passing yargs though + (yargs) => { + yargs.options(commonOptions) + }, + bothServerHandler ) - } else if ( - positionalArgs.includes('web') && - !positionalArgs.includes('api') - ) { - webServerHandler( - yargs(hideBin(process.argv)).options(webCliOptions).parseSync() + .command( + 'api', + 'Start server for serving only the api', + // @ts-expect-error just passing yargs though + (yargs) => { + yargs.options(apiCliOptions) + }, + apiServerHandler ) - } else { - bothServerHandler( - yargs(hideBin(process.argv)).options(commonOptions).parseSync() + .command( + 'web', + 'Start server for serving only the web side', + // @ts-expect-error just passing yargs though + (yargs) => { + yargs.options(webCliOptions) + }, + webServerHandler ) - } + .parse() } diff --git a/packages/cli/src/commands/serve.js b/packages/cli/src/commands/serve.js index d06a3e871a00..296ac6715871 100644 --- a/packages/cli/src/commands/serve.js +++ b/packages/cli/src/commands/serve.js @@ -122,7 +122,7 @@ export const builder = async (yargs) => { apiHost: { alias: 'api-host', type: 'string', - desc: 'Forward requests from the apiUrl, defined in redwood.toml to this host', + desc: 'Forward requests from the apiUrl, defined in redwood.toml, to this host', }, }), handler: async (argv) => { diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js index d2a16cb0e867..87553fe11243 100644 --- a/packages/cli/src/index.js +++ b/packages/cli/src/index.js @@ -217,10 +217,20 @@ async function runYargs() { await loadPlugins(yarg) // Run - await yarg.parse(process.argv.slice(2), {}, (_err, _argv, output) => { + await yarg.parse(process.argv.slice(2), {}, (err, _argv, output) => { + // Configuring yargs with `strict` makes it error on unknown args; + // here we're signaling that with an exit code. + if (err) { + process.exitCode = 1 + } + // Show the output that yargs was going to if there was no callback provided if (output) { - console.log(output) + if (err) { + console.error(output) + } else { + console.log(output) + } } }) } diff --git a/packages/web-server/package.json b/packages/web-server/package.json index 152c3a59401c..63dbc2e29abb 100644 --- a/packages/web-server/package.json +++ b/packages/web-server/package.json @@ -34,10 +34,9 @@ "dotenv-defaults": "5.0.2", "fast-glob": "3.3.2", "fastify": "4.24.3", - "yargs-parser": "21.1.1" + "yargs": "17.7.2" }, "devDependencies": { - "@types/yargs-parser": "21.0.3", "esbuild": "0.19.9", "typescript": "5.3.3" }, diff --git a/packages/web-server/src/server.ts b/packages/web-server/src/server.ts index dcc5ea955405..974376a83c20 100644 --- a/packages/web-server/src/server.ts +++ b/packages/web-server/src/server.ts @@ -5,19 +5,14 @@ import path from 'path' import chalk from 'chalk' import { config } from 'dotenv-defaults' import Fastify from 'fastify' -import yargsParser from 'yargs-parser' +import { hideBin } from 'yargs/helpers' +import yargs from 'yargs/yargs' import { getPaths, getConfig } from '@redwoodjs/project-config' import { redwoodFastifyWeb } from './web' import { withApiProxy } from './withApiProxy' -interface Opts { - socket?: string - port?: string - apiHost?: string -} - function isFullyQualifiedUrl(url: string) { try { // eslint-disable-next-line no-new @@ -29,22 +24,29 @@ function isFullyQualifiedUrl(url: string) { } async function serve() { - // Parse server file args - const args = yargsParser(process.argv.slice(2), { - string: ['port', 'socket', 'apiHost'], - alias: { apiHost: ['api-host'], port: ['p'] }, - }) - - const options: Opts = { - socket: args.socket, - port: args.port, - apiHost: args.apiHost, - } + const options = yargs(hideBin(process.argv)) + .scriptName('rw-web-server') + .usage('$0', 'Start server for serving only the web side') + .strict() + + .options({ + port: { + default: getConfig().web?.port || 8910, + type: 'number', + alias: 'p', + }, + socket: { type: 'string' }, + apiHost: { + alias: 'api-host', + type: 'string', + desc: 'Forward requests from the apiUrl, defined in redwood.toml, to this host', + }, + }) + .parseSync() const redwoodProjectPaths = getPaths() const redwoodConfig = getConfig() - const port = options.port ? parseInt(options.port) : redwoodConfig.web.port const apiUrl = redwoodConfig.web.apiUrl if (!options.apiHost && !isFullyQualifiedUrl(apiUrl)) { @@ -110,7 +112,7 @@ async function serve() { listenOptions = { path: options.socket } } else { listenOptions = { - port, + port: options.port, host: process.env.NODE_ENV === 'production' ? '0.0.0.0' : '::', } } @@ -121,7 +123,7 @@ async function serve() { if (options.socket) { console.log(`Web server started on ${options.socket}`) } else { - console.log(`Web server started on http://localhost:${port}`) + console.log(`Web server started on http://localhost:${options.port}`) } }) diff --git a/tasks/server-tests/__snapshots__/server.test.mjs.snap b/tasks/server-tests/__snapshots__/server.test.mjs.snap new file mode 100644 index 000000000000..4db853b513af --- /dev/null +++ b/tasks/server-tests/__snapshots__/server.test.mjs.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`serve web (/Users/dom/projects/redwood/redwood/packages/web-server/dist/server.js) errors out on unknown args 1`] = ` +"rw-web-server + +Start server for serving only the web side + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + +Unknown arguments: foo, bar, baz +" +`; + +exports[`serve web (/Users/dom/projects/redwood/redwood/packages/web-server/dist/server.js) fails if apiHost isn't set and apiUrl isn't fully qualified 1`] = ` +"Error: If you don't provide apiHost, apiUrl needs to be a fully-qualified URL. But apiUrl is /.redwood/functions. +" +`; + +exports[`serve web ([ + '/Users/dom/projects/redwood/redwood/packages/api-server/dist/index.js', + 'web' +]) errors out on unknown args 1`] = ` +"rw-server web + +Start server for serving only the web side + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + +Unknown arguments: foo, bar, baz +" +`; + +exports[`serve web ([ + '/Users/dom/projects/redwood/redwood/packages/api-server/dist/index.js', + 'web' +]) fails if apiHost isn't set and apiUrl isn't fully qualified 1`] = ` +"Error: If you don't provide apiHost, apiUrl needs to be a fully-qualified URL. But apiUrl is /.redwood/functions. +" +`; diff --git a/tasks/server-tests/jest.config.js b/tasks/server-tests/jest.config.js index 6d446413e90f..609bf5d104c9 100644 --- a/tasks/server-tests/jest.config.js +++ b/tasks/server-tests/jest.config.js @@ -1,7 +1,9 @@ /** @type {import('jest').Config} */ const config = { rootDir: '.', + testMatch: ['/*.test.mjs'], testTimeout: 5_000 * 2, + transform: {}, } module.exports = config diff --git a/tasks/server-tests/server.test.ts b/tasks/server-tests/server.test.mjs similarity index 50% rename from tasks/server-tests/server.test.ts rename to tasks/server-tests/server.test.mjs index a7e34f9af9e5..7b32fb434719 100644 --- a/tasks/server-tests/server.test.ts +++ b/tasks/server-tests/server.test.mjs @@ -1,34 +1,43 @@ -const fs = require('fs') -const http = require('http') -const path = require('path') +/* eslint-disable camelcase */ -const execa = require('execa') +import http from 'node:http' +import { fileURLToPath } from 'node:url' +import { fs, path, $ } from 'zx' + +const __dirname = fileURLToPath(new URL('./', import.meta.url)) + +const FIXTURE_PATH = fileURLToPath( + new URL('./fixtures/redwood-app', import.meta.url) +) + +//////////////////////////////////////////////////////////////// // Set up RWJS_CWD. let original_RWJS_CWD beforeAll(() => { original_RWJS_CWD = process.env.RWJS_CWD - process.env.RWJS_CWD = path.join(__dirname, './fixtures/redwood-app') + process.env.RWJS_CWD = FIXTURE_PATH }) afterAll(() => { process.env.RWJS_CWD = original_RWJS_CWD }) +//////////////////////////////////////////////////////////////// // Clean up the child process after each test. -let child +let p afterEach(async () => { - if (!child) { + if (!p) { return } - child.cancel() + p.kill() // Wait for child process to terminate. try { - await child + await p } catch (e) { // Ignore the error. } @@ -37,18 +46,15 @@ afterEach(async () => { const TIMEOUT = 1_000 * 2 const commandStrings = { - '@redwoodjs/cli': `node ${path.resolve( - __dirname, - '../../packages/cli/dist/index.js' - )} serve`, - '@redwoodjs/api-server': `node ${path.resolve( + '@redwoodjs/cli': path.resolve(__dirname, '../../packages/cli/dist/index.js'), + '@redwoodjs/api-server': path.resolve( __dirname, '../../packages/api-server/dist/index.js' - )}`, - '@redwoodjs/web-server': `node ${path.resolve( + ), + '@redwoodjs/web-server': path.resolve( __dirname, '../../packages/web-server/dist/server.js' - )}`, + ), } const redwoodToml = fs.readFileSync( @@ -61,11 +67,11 @@ const { } = redwoodToml.match(/apiUrl = "(?[^"]*)/) describe.each([ - [`${commandStrings['@redwoodjs/cli']}`], - [`${commandStrings['@redwoodjs/api-server']}`], + [[commandStrings['@redwoodjs/cli'], 'serve']], + [commandStrings['@redwoodjs/api-server']], ])('serve both (%s)', (commandString) => { it('serves both sides, using the apiRootPath in redwood.toml', async () => { - child = execa.command(commandString) + p = $`yarn node ${commandString}` await new Promise((r) => setTimeout(r, TIMEOUT)) const webRes = await fetch('http://localhost:8910/about') @@ -89,7 +95,7 @@ describe.each([ it('--port changes the port', async () => { const port = 8920 - child = execa.command(`${commandString} --port ${port}`) + p = $`yarn node ${commandString} --port ${port}` await new Promise((r) => setTimeout(r, TIMEOUT)) const webRes = await fetch(`http://localhost:${port}/about`) @@ -109,14 +115,17 @@ describe.each([ expect(apiRes.status).toEqual(200) expect(apiBody).toEqual({ data: 'hello function' }) }) + + it.todo("doesn't respect api.port in redwood.toml") + it.todo('respects web.port in redwood.toml') }) describe.each([ - [`${commandStrings['@redwoodjs/cli']} api`], - [`${commandStrings['@redwoodjs/api-server']} api`], + [[commandStrings['@redwoodjs/cli'], 'serve', 'api']], + [[commandStrings['@redwoodjs/api-server'], 'api']], ])('serve api (%s)', (commandString) => { it('serves the api side', async () => { - child = execa.command(commandString) + p = $`yarn node ${commandString}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch('http://localhost:8911/hello') @@ -129,7 +138,7 @@ describe.each([ it('--port changes the port', async () => { const port = 3000 - child = execa.command(`${commandString} --port ${port}`) + p = $`yarn node ${commandString} --port ${port}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:${port}/hello`) @@ -142,7 +151,7 @@ describe.each([ it('--apiRootPath changes the prefix', async () => { const apiRootPath = '/api' - child = execa.command(`${commandString} --apiRootPath ${apiRootPath}`) + p = $`yarn node ${commandString} --apiRootPath ${apiRootPath}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:8911${apiRootPath}/hello`) @@ -151,45 +160,25 @@ describe.each([ expect(res.status).toEqual(200) expect(body).toEqual({ data: 'hello function' }) }) + + it.todo('respects api.port in redwood.toml') + it.todo("apiRootPath isn't affected by apiUrl") }) // We can't test @redwoodjs/cli here because it depends on node_modules. describe.each([ - [`${commandStrings['@redwoodjs/api-server']} web`], + [[`${commandStrings['@redwoodjs/api-server']}`, 'web']], [commandStrings['@redwoodjs/web-server']], ])('serve web (%s)', (commandString) => { - it('serves the web side', async () => { - child = execa.command(commandString) - await new Promise((r) => setTimeout(r, TIMEOUT)) - - const res = await fetch('http://localhost:8910/about') - const body = await res.text() - - expect(res.status).toEqual(200) - expect(body).toEqual( - fs.readFileSync( - path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), - 'utf-8' - ) - ) - }) - - it('--port changes the port', async () => { - const port = 8912 - - child = execa.command(`${commandString} --port ${port}`) - await new Promise((r) => setTimeout(r, TIMEOUT)) - - const res = await fetch(`http://localhost:${port}/about`) - const body = await res.text() - - expect(res.status).toEqual(200) - expect(body).toEqual( - fs.readFileSync( - path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), - 'utf-8' - ) - ) + it("fails if apiHost isn't set and apiUrl isn't fully qualified", async () => { + try { + await $`yarn node ${commandString}` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).not.toEqual(0) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchSnapshot() + } }) it('--apiHost changes the upstream api url', async () => { @@ -206,9 +195,7 @@ describe.each([ server.listen(apiPort, apiHost) - child = execa.command( - `${commandString} --apiHost http://${apiHost}:${apiPort}` - ) + p = $`yarn node ${commandString} --apiHost http://${apiHost}:${apiPort}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch('http://localhost:8910/.redwood/functions/hello') @@ -220,31 +207,47 @@ describe.each([ server.close() }) - it("doesn't error out on unknown args", async () => { - child = execa.command(`${commandString} --foo --bar --baz`) + it('--port changes the port', async () => { + const port = 8912 + + p = $`yarn node ${commandString} --apiHost http://localhost:8916 --port ${port}` await new Promise((r) => setTimeout(r, TIMEOUT)) - const res = await fetch('http://localhost:8910/about') + const res = await fetch(`http://localhost:${port}/about`) const body = await res.text() expect(res.status).toEqual(200) expect(body).toEqual( - fs.readFileSync( + await fs.readFile( path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), 'utf-8' ) ) }) + + it('errors out on unknown args', async () => { + try { + await $`yarn node ${commandString} --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchSnapshot() + } + }) + + it.todo('respects web.port in redwood.toml') + it.todo("works if apiHost isn't set and apiUrl is fully qualified") + it.todo('fails if apiHost is set and apiUrl is fully qualified') }) describe('@redwoodjs/cli', () => { describe('both server CLI', () => { - const commandString = commandStrings['@redwoodjs/cli'] - it.todo('handles --socket differently') - it('has help configured', () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it('has help configured', async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve --help` expect(stdout).toMatchInlineSnapshot(` "usage: rw @@ -264,45 +267,51 @@ describe('@redwoodjs/cli', () => { --socket [string] Also see the Redwood CLI Reference - (​https://redwoodjs.com/docs/cli-commands#serve​)" + (​https://redwoodjs.com/docs/cli-commands#serve​) + " `) }) it('errors out on unknown args', async () => { - const { stdout } = execa.commandSync(`${commandString} --foo --bar --baz`) - - expect(stdout).toMatchInlineSnapshot(` - "usage: rw - - Commands: - rw serve Run both api and web servers [default] - rw serve api Start server for serving only the api - rw serve web Start server for serving only the web side - - Options: - --help Show help [boolean] - --version Show version number [boolean] - --cwd Working directory to use (where \`redwood.toml\` is located) - --telemetry Whether to send anonymous usage telemetry to RedwoodJS - [boolean] - -p, --port [number] [default: 8910] - --socket [string] - - Also see the Redwood CLI Reference - (​https://redwoodjs.com/docs/cli-commands#serve​) - - Unknown arguments: foo, bar, baz" - `) + try { + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "usage: rw + + Commands: + rw serve Run both api and web servers [default] + rw serve api Start server for serving only the api + rw serve web Start server for serving only the web side + + Options: + --help Show help [boolean] + --version Show version number [boolean] + --cwd Working directory to use (where \`redwood.toml\` is located) + --telemetry Whether to send anonymous usage telemetry to RedwoodJS + [boolean] + -p, --port [number] [default: 8910] + --socket [string] + + Also see the Redwood CLI Reference + (​https://redwoodjs.com/docs/cli-commands#serve​) + + Unknown arguments: foo, bar, baz + " + `) + } }) }) describe('api server CLI', () => { - const commandString = `${commandStrings['@redwoodjs/cli']} api` - it.todo('handles --socket differently') it('loads dotenv files', async () => { - child = execa.command(`${commandString}`) + p = $`yarn node ${commandStrings['@redwoodjs/cli']} serve api` + await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:8911/env`) @@ -312,8 +321,9 @@ describe('@redwoodjs/cli', () => { expect(body).toEqual({ data: '42' }) }) - it('has help configured', () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it('has help configured', async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve api --help` expect(stdout).toMatchInlineSnapshot(` "rw serve api @@ -330,42 +340,48 @@ describe('@redwoodjs/cli', () => { -p, --port [number] [default: 8911] --socket [string] --apiRootPath, --api-root-path, Root path where your api functions - --rootPath, --root-path are served [string] [default: "/"]" + --rootPath, --root-path are served [string] [default: "/"] + " `) }) it('errors out on unknown args', async () => { - const { stdout } = execa.commandSync(`${commandString} --foo --bar --baz`) - - expect(stdout).toMatchInlineSnapshot(` - "rw serve api - - Start server for serving only the api - - Options: - --help Show help [boolean] - --version Show version number [boolean] - --cwd Working directory to use (where - \`redwood.toml\` is located) - --telemetry Whether to send anonymous usage - telemetry to RedwoodJS [boolean] - -p, --port [number] [default: 8911] - --socket [string] - --apiRootPath, --api-root-path, Root path where your api functions - --rootPath, --root-path are served [string] [default: "/"] - - Unknown arguments: foo, bar, baz" - `) + try { + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve api --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "rw serve api + + Start server for serving only the api + + Options: + --help Show help [boolean] + --version Show version number [boolean] + --cwd Working directory to use (where + \`redwood.toml\` is located) + --telemetry Whether to send anonymous usage + telemetry to RedwoodJS [boolean] + -p, --port [number] [default: 8911] + --socket [string] + --apiRootPath, --api-root-path, Root path where your api functions + --rootPath, --root-path are served [string] [default: "/"] + + Unknown arguments: foo, bar, baz + " + `) + } }) }) describe('web server CLI', () => { - const commandString = `${commandStrings['@redwoodjs/cli']} web` - it.todo('handles --socket differently') - it('has help configured', () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it('has help configured', async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve web --help` expect(stdout).toMatchInlineSnapshot(` "rw serve web @@ -382,44 +398,49 @@ describe('@redwoodjs/cli', () => { -p, --port [number] [default: 8910] --socket [string] --apiHost, --api-host Forward requests from the apiUrl, defined in - redwood.toml to this host [string]" + redwood.toml, to this host [string] + " `) }) it('errors out on unknown args', async () => { - const { stdout } = execa.commandSync(`${commandString} --foo --bar --baz`) - - expect(stdout).toMatchInlineSnapshot(` - "rw serve web - - Start server for serving only the web side - - Options: - --help Show help [boolean] - --version Show version number [boolean] - --cwd Working directory to use (where \`redwood.toml\` is - located) - --telemetry Whether to send anonymous usage telemetry to - RedwoodJS [boolean] - -p, --port [number] [default: 8910] - --socket [string] - --apiHost, --api-host Forward requests from the apiUrl, defined in - redwood.toml to this host [string] - - Unknown arguments: foo, bar, baz" - `) + try { + await $`yarn node ${commandStrings['@redwoodjs/cli']} serve web --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "rw serve web + + Start server for serving only the web side + + Options: + --help Show help [boolean] + --version Show version number [boolean] + --cwd Working directory to use (where \`redwood.toml\` is + located) + --telemetry Whether to send anonymous usage telemetry to + RedwoodJS [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + + Unknown arguments: foo, bar, baz + " + `) + } }) }) }) describe('@redwoodjs/api-server', () => { describe('both server CLI', () => { - const commandString = commandStrings['@redwoodjs/api-server'] - it('--socket changes the port', async () => { const socket = 8921 - child = execa.command(`${commandString} --socket ${socket}`) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} --socket ${socket}` await new Promise((r) => setTimeout(r, TIMEOUT)) const webRes = await fetch(`http://localhost:${socket}/about`) @@ -446,9 +467,7 @@ describe('@redwoodjs/api-server', () => { const socket = 8922 const port = 8923 - child = execa.command( - `${commandString} --socket ${socket} --port ${port}` - ) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} --socket ${socket} --port ${port}` await new Promise((r) => setTimeout(r, TIMEOUT)) const webRes = await fetch(`http://localhost:${socket}/about`) @@ -471,48 +490,60 @@ describe('@redwoodjs/api-server', () => { expect(apiBody).toEqual({ data: 'hello function' }) }) - it("doesn't have help configured", () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it("doesn't have help configured", async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/api-server']} --help` expect(stdout).toMatchInlineSnapshot(` - "Options: - --help Show help [boolean] - --version Show version number [boolean]" - `) - }) - - it("doesn't error out on unknown args", async () => { - child = execa.command(`${commandString} --foo --bar --baz`) - await new Promise((r) => setTimeout(r, TIMEOUT)) - - const webRes = await fetch('http://localhost:8910/about') - const webBody = await webRes.text() + "usage: rw-server - expect(webRes.status).toEqual(200) - expect(webBody).toEqual( - fs.readFileSync( - path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), - 'utf-8' - ) - ) + Commands: + rw-server Run both api and web servers [default] + rw-server api Start server for serving only the api + rw-server web Start server for serving only the web side - const apiRes = await fetch( - 'http://localhost:8910/.redwood/functions/hello' - ) - const apiBody = await apiRes.json() + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + " + `) + }) - expect(apiRes.status).toEqual(200) - expect(apiBody).toEqual({ data: 'hello function' }) + it('errors out on unknown args', async () => { + try { + await $`yarn node ${commandStrings['@redwoodjs/api-server']} --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "usage: rw-server + + Commands: + rw-server Run both api and web servers [default] + rw-server api Start server for serving only the api + rw-server web Start server for serving only the web side + + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + + Unknown arguments: foo, bar, baz + " + `) + } }) }) describe('api server CLI', () => { - const commandString = `${commandStrings['@redwoodjs/api-server']} api` - it('--socket changes the port', async () => { const socket = 3001 - child = execa.command(`${commandString} --socket ${socket}`) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} api --socket ${socket}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:${socket}/hello`) @@ -526,9 +557,7 @@ describe('@redwoodjs/api-server', () => { const socket = 3002 const port = 3003 - child = execa.command( - `${commandString} --socket ${socket} --port ${port}` - ) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} api --socket ${socket} --port ${port}` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:${socket}/hello`) @@ -539,7 +568,7 @@ describe('@redwoodjs/api-server', () => { }) it('--loadEnvFiles loads dotenv files', async () => { - child = execa.command(`${commandString} --loadEnvFiles`) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} api --loadEnvFiles` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:8911/env`) @@ -549,35 +578,63 @@ describe('@redwoodjs/api-server', () => { expect(body).toEqual({ data: '42' }) }) - it("doesn't have help configured", () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it('has help configured', async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/api-server']} api --help` expect(stdout).toMatchInlineSnapshot(` - "Options: - --help Show help [boolean] - --version Show version number [boolean]" - `) - }) + "rw-server api - it("doesn't error out on unknown args", async () => { - child = execa.command(`${commandString} --foo --bar --baz`) - await new Promise((r) => setTimeout(r, TIMEOUT)) + Start server for serving only the api - const res = await fetch('http://localhost:8911/hello') - const body = await res.json() + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8911] + --socket [string] + --apiRootPath, --api-root-path, Root path where your api functions + --rootPath, --root-path are served [string] [default: "/"] + --loadEnvFiles Load .env and .env.defaults files + [boolean] [default: false] + " + `) + }) - expect(res.status).toEqual(200) - expect(body).toEqual({ data: 'hello function' }) + it('errors out on unknown args', async () => { + try { + await $`yarn node ${commandStrings['@redwoodjs/api-server']} api --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "rw-server api + + Start server for serving only the api + + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8911] + --socket [string] + --apiRootPath, --api-root-path, Root path where your api functions + --rootPath, --root-path are served [string] [default: "/"] + --loadEnvFiles Load .env and .env.defaults files + [boolean] [default: false] + + Unknown arguments: foo, bar, baz + " + `) + } }) }) describe('web server CLI', () => { - const commandString = `${commandStrings['@redwoodjs/api-server']} web` - it('--socket changes the port', async () => { const socket = 8913 - child = execa.command(`${commandString} --socket ${socket}`) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} web --socket ${socket} --apiHost="http://localhost:8910"` + await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:${socket}/about`) @@ -596,9 +653,7 @@ describe('@redwoodjs/api-server', () => { const socket = 8914 const port = 8915 - child = execa.command( - `${commandString} --socket ${socket} --port ${port}` - ) + p = $`yarn node ${commandStrings['@redwoodjs/api-server']} web --socket ${socket} --port ${port} --apiHost="http://localhost:8910"` await new Promise((r) => setTimeout(r, TIMEOUT)) const res = await fetch(`http://localhost:${socket}/about`) @@ -613,56 +668,74 @@ describe('@redwoodjs/api-server', () => { ) }) - it("doesn't have help configured", () => { - const { stdout } = execa.commandSync(`${commandString} --help`) + it("doesn't have help configured", async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/api-server']} web --help` expect(stdout).toMatchInlineSnapshot(` - "Options: - --help Show help [boolean] - --version Show version number [boolean]" - `) - }) + "rw-server web - it("doesn't error out on unknown args", async () => { - child = execa.command(`${commandString} --foo --bar --baz`, { - stdio: 'inherit', - }) - await new Promise((r) => setTimeout(r, TIMEOUT)) + Start server for serving only the web side - const res = await fetch('http://localhost:8910/about') - const body = await res.text() + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + " + `) + }) - expect(res.status).toEqual(200) - expect(body).toEqual( - fs.readFileSync( - path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), - 'utf-8' - ) - ) + it('errors out on unknown args', async () => { + try { + await $`yarn node ${commandStrings['@redwoodjs/api-server']} web --foo --bar --baz` + expect(true).toEqual(false) + } catch (p) { + expect(p.exitCode).toEqual(1) + expect(p.stdout).toEqual('') + expect(p.stderr).toMatchInlineSnapshot(` + "rw-server web + + Start server for serving only the web side + + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + + Unknown arguments: foo, bar, baz + " + `) + } }) }) }) describe('@redwoodjs/web-server', () => { - const commandString = commandStrings['@redwoodjs/web-server'] - it.todo('handles --socket differently') - // @redwoodjs/web-server doesn't have help configured in a different way than the others. - // The others output help, it's just empty. This doesn't even do that. It just runs. - it("doesn't have help configured", async () => { - child = execa.command(`${commandString} --help`) - await new Promise((r) => setTimeout(r, TIMEOUT)) + it('has help configured', async () => { + const { stdout } = + await $`yarn node ${commandStrings['@redwoodjs/web-server']} --help` - const res = await fetch('http://localhost:8910/about') - const body = await res.text() + expect(stdout).toMatchInlineSnapshot(` + "rw-web-server - expect(res.status).toEqual(200) - expect(body).toEqual( - fs.readFileSync( - path.join(__dirname, './fixtures/redwood-app/web/dist/about.html'), - 'utf-8' - ) - ) + Start server for serving only the web side + + Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --port [number] [default: 8910] + --socket [string] + --apiHost, --api-host Forward requests from the apiUrl, defined in + redwood.toml, to this host [string] + " + `) }) }) diff --git a/yarn.lock b/yarn.lock index 19c5f8e6d538..365f69fa3c67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8721,14 +8721,13 @@ __metadata: "@fastify/static": "npm:6.12.0" "@fastify/url-data": "npm:5.4.0" "@redwoodjs/project-config": "npm:6.0.7" - "@types/yargs-parser": "npm:21.0.3" chalk: "npm:4.1.2" dotenv-defaults: "npm:5.0.2" esbuild: "npm:0.19.9" fast-glob: "npm:3.3.2" fastify: "npm:4.24.3" typescript: "npm:5.3.3" - yargs-parser: "npm:21.1.1" + yargs: "npm:17.7.2" bin: rw-web-server: ./dist/server.js languageName: unknown