diff --git a/.changeset/kind-bats-occur.md b/.changeset/kind-bats-occur.md new file mode 100644 index 00000000..fe27b21a --- /dev/null +++ b/.changeset/kind-bats-occur.md @@ -0,0 +1,5 @@ +--- +'@rocket/engine': patch +--- + +Add `engine.getVersion()` method diff --git a/.changeset/thirty-scissors-jam.md b/.changeset/thirty-scissors-jam.md new file mode 100644 index 00000000..b2b5f0c8 --- /dev/null +++ b/.changeset/thirty-scissors-jam.md @@ -0,0 +1,12 @@ +--- +'@rocket/cli': patch +--- + +Add start message for `rocket start` + +``` +🚀 Rocket Engine v0.2.5 + + 🚧 Local: http://localhost:8000/ + 🌐 Network: http://xxx.xxx.xxx.xxx:8000/ +``` diff --git a/package.json b/package.json index 432b50e9..db849208 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "start:experimental": "NODE_DEBUG=engine:rendering node --no-warnings --experimental-loader ./packages/engine/src/litCssLoader.js packages/cli/src/cli.js start --open", "start": "NODE_DEBUG=engine:rendering node --trace-warnings packages/cli/src/cli.js start --open", "test": "yarn test:node && yarn test:web", - "test:integration": "playwright test packages/*/test-node/*.spec.js", + "test:integration": "playwright test packages/*/test-node/*.spec.js --retries=3", "test:node": "yarn test:unit && yarn test:integration", "test:unit": "node --trace-warnings ./node_modules/.bin/mocha --require ./scripts/testMochaGlobalHooks.js \"packages/*/test-node/**/*.test.{ts,js,mjs,cjs}\" -- --timeout 8000 --reporter dot --exit", "test:web": "web-test-runner", diff --git a/packages/cli/package.json b/packages/cli/package.json index d932aeec..429fc58c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,10 +59,12 @@ "commander": "^9.0.0", "fs-extra": "^9.0.1", "gray-matter": "^4.0.3", + "ip": "^1.1.5", "plugins-manager": "^0.3.0", "puppeteer": "^13.0.0" }, "devDependencies": { + "@types/ip": "^1.1.0", "koa-proxy": "^1.0.0-alpha.3" }, "types": "./dist-types/src/index.d.ts", diff --git a/packages/cli/src/RocketStart.js b/packages/cli/src/RocketStart.js index 47958780..c21720b4 100755 --- a/packages/cli/src/RocketStart.js +++ b/packages/cli/src/RocketStart.js @@ -1,6 +1,7 @@ import { fromRollup } from '@web/dev-server-rollup'; import { Engine } from '@rocket/engine/server'; +import { logStartMessage } from './start/logStartMessage.js'; export class RocketStart { /** @type {Engine | undefined} */ @@ -60,7 +61,14 @@ export class RocketStart { setupDevServerPlugins: [...this.cli.options.setupDevServerPlugins, ...withWrap], }); try { - console.log('🚀 Engines online'); + this.engine.events.on('devServerStarted', () => { + if (this.engine?.devServer) { + logStartMessage( + { devServerOptions: this.engine.devServer?.config, engine: this.engine }, + console, + ); + } + }); await this.engine.start(); } catch (e) { console.log('Engine start errored'); @@ -71,7 +79,6 @@ export class RocketStart { async stop({ hard = true } = {}) { if (this.engine) { await this.engine.stop({ hard }); - console.log('🚀 Engines offline'); } } } diff --git a/packages/cli/src/start/logStartMessage.js b/packages/cli/src/start/logStartMessage.js new file mode 100644 index 00000000..77d9957a --- /dev/null +++ b/packages/cli/src/start/logStartMessage.js @@ -0,0 +1,54 @@ +import ip from 'ip'; +import { white, bold, cyan, gray } from 'colorette'; + +/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */ + +/** + * + * @param {DevServerConfig} devServerOptions + * @param {string} host + * @param {string} path + * @returns {string} + */ +function createAddress(devServerOptions, host, path) { + return `http${devServerOptions.http2 ? 's' : ''}://${host}:${devServerOptions.port}${path}`; +} + +/** + * + * @param {DevServerConfig} devServerOptions + * @param {console} logger + * @param {string} openPath + */ +function logNetworkAddress(devServerOptions, logger, openPath) { + try { + const address = ip.address(); + if (typeof address === 'string') { + logger.log( + `${white(' 🌐 Network:')} ${cyan(createAddress(devServerOptions, address, openPath))}`, + ); + } + } catch (_a) { + // + } +} + +/** + * @param {{ devServerOptions: DevServerConfig, engine: import('@rocket/engine/server').Engine}} options + * @param {console} logger + */ +export function logStartMessage({ devServerOptions, engine }, logger) { + const prettyHost = devServerOptions.hostname ?? 'localhost'; + let openPath = typeof devServerOptions.open === 'string' ? devServerOptions.open : '/'; + if (!openPath.startsWith('/')) { + openPath = `/${openPath}`; + } + + logger.log(`${bold(`🚀 Rocket Engine`)} ${gray(`v${engine.getVersion()}`)}`); + logger.log(''); + logger.log( + `${white(' 🚧 Local:')} ${cyan(createAddress(devServerOptions, prettyHost, openPath))}`, + ); + logNetworkAddress(devServerOptions, logger, openPath); + logger.log(''); +} diff --git a/packages/cli/test-helpers/index.js b/packages/cli/test-helpers/index.js index e6977dbd..6a2856aa 100644 --- a/packages/cli/test-helpers/index.js +++ b/packages/cli/test-helpers/index.js @@ -5,7 +5,7 @@ import { setupTestCli } from './test-helpers.js'; export function prepareTestCli(importMetaUrl) { const dir = path.dirname(fileURLToPath(importMetaUrl)); - return (cwd, cliOptions = ['build'], options = {}) => setupTestCli(cwd, cliOptions, options, dir); + return fullOptions => setupTestCli({ dir, ...fullOptions }); } const { expect } = chai; diff --git a/packages/cli/test-helpers/test-helpers.js b/packages/cli/test-helpers/test-helpers.js index 56df67da..c2dafd7c 100644 --- a/packages/cli/test-helpers/test-helpers.js +++ b/packages/cli/test-helpers/test-helpers.js @@ -60,7 +60,13 @@ function cleanupLitMarkersFn(text) { return newText; } -export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, dir) { +export async function setupTestCli({ + cwd, + cliOptions = ['build'], + options = {}, + testOptions = {}, + dir, +}) { const resolvedCwd = path.join(dir, cwd.split('/').join(path.sep)); const useOptions = { buildOptimize: false, buildAutoStop: false, ...options, cwd: resolvedCwd }; if (useOptions.inputDir) { @@ -69,6 +75,18 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di useOptions.outputDir = path.join(resolvedCwd, '__output'); useOptions.outputDevDir = path.join(resolvedCwd, '__output-dev'); + const capturedLogs = []; + const origLog = console.log; + const origError = console.error; + if (testOptions.captureLogs) { + console.log = msg => { + capturedLogs.push(msg); + }; + console.error = msg => { + capturedLogs.push(msg); + }; + } + const cli = new RocketCli({ argv: [process.argv[0], new URL('../src/cli.js', import.meta.url).pathname, ...cliOptions], }); @@ -184,6 +202,10 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di async function cleanup() { await cli.stop({ hard: false }); + if (testOptions.captureLogs) { + console.log = origLog; + console.error = origError; + } } async function build() { @@ -243,5 +265,6 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di renameSource, backupOrRestoreSource, restoreSource, + capturedLogs, }; } diff --git a/packages/cli/test-node/01-config.test.js b/packages/cli/test-node/01-config.test.js index c616c493..32fafb81 100644 --- a/packages/cli/test-node/01-config.test.js +++ b/packages/cli/test-node/01-config.test.js @@ -8,11 +8,11 @@ const { expect } = chai; describe('Config', () => { it('01: no config file', async () => { - const { build, readOutput, readDevOutput } = await setupTestCli( - 'fixtures/01-config/01-no-config/', - undefined, - { buildOptimize: true }, - ); + const { build, readOutput, readDevOutput } = await setupTestCli({ + cwd: 'fixtures/01-config/01-no-config/', + options: { buildOptimize: true }, + testOptions: { captureLogs: true }, + }); await build(); expect(readOutput('index.html')).to.equal( @@ -31,14 +31,21 @@ describe('Config', () => { }); it('02: change input dir', async () => { - const { build, readDevOutput } = await setupTestCli('fixtures/01-config/02-change-input-dir/'); + const { build, readDevOutput } = await setupTestCli({ + cwd: 'fixtures/01-config/02-change-input-dir/', + testOptions: { captureLogs: true }, + }); await build(); expect(readDevOutput('index.html')).to.equal(['Hello World!'].join('\n')); }); it('03: can add a middleware (api proxy) to the dev server', async () => { - const { cleanup, cli } = await setupTestCli('fixtures/01-config/03-add-middleware/', ['start']); + const { cleanup, cli } = await setupTestCli({ + cwd: 'fixtures/01-config/03-add-middleware/', + cliOptions: ['start'], + testOptions: { captureLogs: true }, + }); const apiServer = http.createServer((request, response) => { if (request.url === '/api/message') { response.writeHead(200); @@ -61,20 +68,22 @@ describe('Config', () => { }); it('04: can add a rollup plugin via setupDevServerAndBuildPlugins to build', async () => { - const { build, readOutput } = await setupTestCli( - 'fixtures/01-config/04-add-rollup-plugin/', - undefined, - { buildOptimize: true }, - ); + const { build, readOutput } = await setupTestCli({ + cwd: 'fixtures/01-config/04-add-rollup-plugin/', + options: { buildOptimize: true }, + testOptions: { captureLogs: true }, + }); await build(); const inlineModule = await readOutput('e97af63d.js', { format: false }); expect(inlineModule).to.equal('var a={test:"data"};console.log(a);\n'); }); it('04a: can add a rollup plugin via setupDevServerAndBuildPlugins to start', async () => { - const { cli, cleanup } = await setupTestCli('fixtures/01-config/04-add-rollup-plugin/', [ - 'start', - ]); + const { cli, cleanup } = await setupTestCli({ + cwd: 'fixtures/01-config/04-add-rollup-plugin/', + cliOptions: ['start'], + testOptions: { captureLogs: true }, + }); await cli.start(); const { port } = cli?.activePlugin?.engine.devServer.config; @@ -88,9 +97,10 @@ describe('Config', () => { }); it('05: long file header comments', async () => { - const { build, readSource } = await setupTestCli( - 'fixtures/01-config/05-long-file-header-comment/', - ); + const { build, readSource } = await setupTestCli({ + cwd: 'fixtures/01-config/05-long-file-header-comment/', + testOptions: { captureLogs: true }, + }); await build(); expect(readSource('index.rocket.js', { format: false })).to.equal( diff --git a/packages/cli/test-node/02-build.test.js b/packages/cli/test-node/02-build.test.js index 3cf48481..aa4b36cf 100644 --- a/packages/cli/test-node/02-build.test.js +++ b/packages/cli/test-node/02-build.test.js @@ -5,13 +5,13 @@ const { expect } = chai; describe('Build', () => { it('01: copy public files', async () => { - const { build, readOutput, outputExists, readDevOutput } = await setupTestCli( - 'fixtures/02-build/01-copy-public-files/', - undefined, - { + const { build, readOutput, outputExists, readDevOutput } = await setupTestCli({ + cwd: 'fixtures/02-build/01-copy-public-files/', + options: { buildOptimize: true, }, - ); + testOptions: { captureLogs: true }, + }); await build(); expect(readOutput('index.html')).to.equal( diff --git a/packages/cli/test-node/03-upgrade.test.js b/packages/cli/test-node/03-upgrade.test.js index c28ab4bf..f02baa60 100644 --- a/packages/cli/test-node/03-upgrade.test.js +++ b/packages/cli/test-node/03-upgrade.test.js @@ -6,7 +6,11 @@ const { expect } = chai; describe('Upgrade System', () => { it('2021-09-menu', async () => { const { build, sourceExists, readSource, backupOrRestoreSource, restoreSource } = - await setupTestCli('fixtures/03-upgrade/2022-03-menu', ['upgrade']); + await setupTestCli({ + cwd: 'fixtures/03-upgrade/2022-03-menu', + cliOptions: ['upgrade'], + testOptions: { captureLogs: true }, + }); await backupOrRestoreSource(); await build(); diff --git a/packages/cli/test-node/04-open-graph.test.js b/packages/cli/test-node/04-open-graph.test.js index e718d2b4..4b254da7 100644 --- a/packages/cli/test-node/04-open-graph.test.js +++ b/packages/cli/test-node/04-open-graph.test.js @@ -5,13 +5,13 @@ const { expect } = chai; describe('Open Graph', () => { it('generates the image and adds the meta tags', async () => { - const { build, readOutput, outputExists } = await setupTestCli( - 'fixtures/04-open-graph/01-generate-image-and-inject-meta', - undefined, - { + const { build, readOutput, outputExists } = await setupTestCli({ + cwd: 'fixtures/04-open-graph/01-generate-image-and-inject-meta', + options: { buildOptimize: true, }, - ); + testOptions: { captureLogs: true }, + }); await build(); expect(readOutput('index.html', { replaceImageHashes: true })).to.equal( @@ -35,13 +35,13 @@ describe('Open Graph', () => { }); it('handles multiple pages', async () => { - const { build, readOutput } = await setupTestCli( - 'fixtures/04-open-graph/02-multiple-pages', - undefined, - { + const { build, readOutput } = await setupTestCli({ + cwd: 'fixtures/04-open-graph/02-multiple-pages', + options: { buildOptimize: true, }, - ); + testOptions: { captureLogs: true }, + }); await build(); expect(readOutput('index.html', { replaceImageHashes: true })).to.equal( diff --git a/packages/cli/test-node/05-start.test.js b/packages/cli/test-node/05-start.test.js new file mode 100644 index 00000000..a88c8f64 --- /dev/null +++ b/packages/cli/test-node/05-start.test.js @@ -0,0 +1,25 @@ +import chai from 'chai'; +import { white, bold } from 'colorette'; + +import { setupTestCli } from './test-helpers.js'; + +const { expect } = chai; + +describe('Start', () => { + it('Start Message', async () => { + const { cli, capturedLogs, cleanup } = await setupTestCli({ + cwd: 'fixtures/05-start/01-start-message', + cliOptions: ['start'], + testOptions: { captureLogs: true }, + }); + + await cli.start(); + await cleanup(); + + expect(capturedLogs[0].startsWith(`${bold(`🚀 Rocket Engine`)} `)).to.be.true; + expect(capturedLogs[1]).to.equal(''); + expect(capturedLogs[2].startsWith(`${white(' 🚧 Local:')}`)).to.be.true; + expect(capturedLogs[3].startsWith(`${white(' 🌐 Network:')}`)).to.be.true; + expect(capturedLogs[4]).to.equal(''); + }); +}); diff --git a/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/index.rocket.js b/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/index.rocket.js new file mode 100644 index 00000000..ea00a5e1 --- /dev/null +++ b/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/index.rocket.js @@ -0,0 +1,5 @@ +/* START - Rocket auto generated - do not touch */ +export const sourceRelativeFilePath = 'index.rocket.js'; +/* END - Rocket auto generated - do not touch */ + +export default () => 'Hello World!'; diff --git a/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/pageTreeData.rocketGenerated.json b/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/pageTreeData.rocketGenerated.json new file mode 100644 index 00000000..929b8fca --- /dev/null +++ b/packages/cli/test-node/fixtures/05-start/01-start-message/site/pages/pageTreeData.rocketGenerated.json @@ -0,0 +1,8 @@ +{ + "name": "index.rocket.js", + "menuLinkText": "index.rocket.js", + "url": "/", + "outputRelativeFilePath": "index.html", + "sourceRelativeFilePath": "index.rocket.js", + "level": 0 +} \ No newline at end of file diff --git a/packages/engine/package.json b/packages/engine/package.json index 0f081821..bc72134f 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -36,7 +36,7 @@ "debug": "DEBUG=engine:rendering yarn test", "debug:integration": "PWDEBUG=1 yarn test:integration", "test": "mocha --require ../../scripts/testMochaGlobalHooks.js --timeout 8000 test-node/**/*.test.js test-node/*.test.js", - "test:integration": "playwright test test-node/*.spec.js", + "test:integration": "playwright test test-node/*.spec.js --retries=3", "test:watch": "onchange 'src/**/*.js' 'test-node/**/*.js' -- npm test", "types:copy": "copyfiles \"./types/**/*.d.ts\" dist-types/" }, diff --git a/packages/engine/src/Engine.js b/packages/engine/src/Engine.js index dc4f950b..43e1d42b 100644 --- a/packages/engine/src/Engine.js +++ b/packages/engine/src/Engine.js @@ -7,7 +7,7 @@ import { existsSync } from 'fs'; // TODO: implement copy without extra dependency => node 16.7.0 copy has recursive import fse from 'fs-extra'; -import { mkdir, rm } from 'fs/promises'; +import { mkdir, readFile, rm } from 'fs/promises'; import path from 'path'; import { EventEmitter } from 'events'; import { startDevServer } from '@web/dev-server'; @@ -32,6 +32,8 @@ import { RocketHeader } from './file-header/RocketHeader.js'; const logRendering = debuglog('engine:rendering'); +const pkgJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); + export class Engine { /** @type {EngineOptions} */ options = { @@ -267,6 +269,7 @@ export class Engine { readFileConfig: false, // argv: this.__argv, }); + this.events.emit('devServerStarted'); this.devServer.webSockets.on( 'message', @@ -469,4 +472,8 @@ export class Engine { } return result; } + + getVersion() { + return pkgJson.version; + } } diff --git a/packages/search/test-node/rocket.test.js b/packages/search/test-node/rocket.test.js index 51cd80da..029e6b07 100644 --- a/packages/search/test-node/rocket.test.js +++ b/packages/search/test-node/rocket.test.js @@ -7,13 +7,13 @@ const { expect } = chai; describe('Search', () => { it('01: writes the search index', async () => { - const { build, readOutput, readPublic } = await setupTestCli( - 'fixtures/01-single-page/', - undefined, - { + const { build, readOutput, readPublic } = await setupTestCli({ + cwd: 'fixtures/01-single-page/', + options: { buildOptimize: true, }, - ); + testOptions: { captureLogs: true }, + }); await build(); const indexString = diff --git a/yarn.lock b/yarn.lock index 76f983a7..48f95ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1656,6 +1656,13 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.2.tgz#7315b4c4c54f82d13fa61c228ec5c2ea5cc9e0e1" integrity sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w== +"@types/ip@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/ip/-/ip-1.1.0.tgz#aec4f5bfd49e4a4c53b590d88c36eb078827a7c0" + integrity sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ== + dependencies: + "@types/node" "*" + "@types/is-ci@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/is-ci/-/is-ci-3.0.0.tgz#7e8910af6857601315592436f030aaa3ed9783c3"