Compare commits

...

3 Commits

Author SHA1 Message Date
github-actions[bot]
8b48fb9760 Version Packages 2022-08-20 20:25:47 +02:00
Thomas Allmer
39206a1738 feat(cli): start writes to _site-dev and only clears that folder 2022-08-20 20:23:22 +02:00
Thomas Allmer
cbfb0f91e2 feat(cli): add "rocket preview" command to verify a production build 2022-08-20 20:23:22 +02:00
14 changed files with 223 additions and 37 deletions

View File

@@ -35,6 +35,7 @@
"setup:ts-configs": "node scripts/generate-ts-configs.mjs", "setup:ts-configs": "node scripts/generate-ts-configs.mjs",
"start:experimental": "NODE_DEBUG=engine:rendering node --no-warnings --experimental-loader ./packages/engine/src/litCssLoader.js packages/cli/src/cli.js start --open", "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", "start": "NODE_DEBUG=engine:rendering node --trace-warnings packages/cli/src/cli.js start --open",
"preview": "node packages/cli/src/cli.js preview --open",
"test": "yarn test:node && yarn test:web", "test": "yarn test:node && yarn test:web",
"test:integration": "playwright test packages/*/test-node/*.spec.js --retries=3", "test:integration": "playwright test packages/*/test-node/*.spec.js --retries=3",
"test:node": "yarn test:unit && yarn test:integration", "test:node": "yarn test:unit && yarn test:integration",

View File

@@ -1,5 +1,13 @@
# @rocket/cli # @rocket/cli
## 0.20.2
### Patch Changes
- 39206a1: `rocket start` now outputs to `_site-dev` instead of `_site`.
- 39206a1: `rocket start` clears only its output folder (defaults to `_site-dev`)
- cbfb0f9: Add `rocket preview` command to enable fast checking of the production build
## 0.20.1 ## 0.20.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/cli", "name": "@rocket/cli",
"version": "0.20.1", "version": "0.20.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View File

@@ -88,6 +88,8 @@ export class RocketBuild {
async build() { async build() {
await this.cli.events.dispatchEventDone('build-start'); await this.cli.events.dispatchEventDone('build-start');
await this.cli.clearOutputDir();
await this.cli.clearOutputDevDir();
this.engine = new Engine(); this.engine = new Engine();
this.engine.setOptions({ this.engine.setOptions({

View File

@@ -4,6 +4,7 @@ import { RocketStart } from './RocketStart.js';
import { RocketBuild } from './RocketBuild.js'; import { RocketBuild } from './RocketBuild.js';
import { RocketInit } from './RocketInit.js'; import { RocketInit } from './RocketInit.js';
import { RocketUpgrade } from './RocketUpgrade.js'; import { RocketUpgrade } from './RocketUpgrade.js';
import { RocketPreview } from './RocketPreview.js';
// import { ignore } from './images/ignore.js'; // import { ignore } from './images/ignore.js';
import path from 'path'; import path from 'path';
@@ -34,7 +35,7 @@ export class RocketCli {
open: false, open: false,
cwd: process.cwd(), cwd: process.cwd(),
inputDir: 'FALLBACK', inputDir: 'FALLBACK',
outputDir: '_site', outputDir: 'FALLBACK',
outputDevDir: '_site-dev', outputDevDir: '_site-dev',
serviceWorkerSourcePath: '', serviceWorkerSourcePath: '',
@@ -93,6 +94,9 @@ export class RocketCli {
if (this.options.inputDir === 'FALLBACK') { if (this.options.inputDir === 'FALLBACK') {
this.options.inputDir = path.join(this.options.cwd, 'site', 'pages'); this.options.inputDir = path.join(this.options.cwd, 'site', 'pages');
} }
if (this.options.outputDir === 'FALLBACK') {
this.options.outputDir = path.join(this.options.cwd, '_site');
}
if (this.options.inputDir instanceof URL) { if (this.options.inputDir instanceof URL) {
this.options.inputDir = this.options.inputDir.pathname; this.options.inputDir = this.options.inputDir.pathname;
} }
@@ -122,7 +126,6 @@ export class RocketCli {
} }
async prepare() { async prepare() {
await this.clearOutputDirs();
if (!this.options.presets) { if (!this.options.presets) {
return; return;
} }
@@ -180,6 +183,7 @@ export class RocketCli {
{ plugin: RocketInit, options: {} }, { plugin: RocketInit, options: {} },
// { plugin: RocketLint }, // { plugin: RocketLint },
{ plugin: RocketUpgrade, options: {} }, { plugin: RocketUpgrade, options: {} },
{ plugin: RocketPreview, options: {} },
]; ];
if (Array.isArray(this.options.setupCliPlugins)) { if (Array.isArray(this.options.setupCliPlugins)) {
@@ -230,10 +234,13 @@ export class RocketCli {
} }
} }
async clearOutputDirs() { async clearOutputDir() {
if (this.options.outputDir && existsSync(this.options.outputDir)) { if (this.options.outputDir && existsSync(this.options.outputDir)) {
await rm(this.options.outputDir, { recursive: true, force: true }); await rm(this.options.outputDir, { recursive: true, force: true });
} }
}
async clearOutputDevDir() {
if (this.options.outputDevDir && existsSync(this.options.outputDevDir)) { if (this.options.outputDevDir && existsSync(this.options.outputDevDir)) {
await rm(this.options.outputDevDir, { recursive: true, force: true }); await rm(this.options.outputDevDir, { recursive: true, force: true });
} }

View File

@@ -0,0 +1,79 @@
import { logPreviewMessage } from './preview/logPreviewMessage.js';
import { startDevServer } from '@web/dev-server';
import path from 'path';
import { existsSync } from 'fs';
import { gray, bold } from 'colorette';
export class RocketPreview {
/**
* @param {import('commander').Command} program
* @param {import('./RocketCli.js').RocketCli} cli
*/
async setupCommand(program, cli) {
this.cli = cli;
this.active = true;
program
.command('preview')
.option('-i, --input-dir <path>', 'path to the folder with the build html files')
.option('-o, --open', 'automatically open the browser')
.action(async cliOptions => {
cli.setOptions(cliOptions);
cli.activePlugin = this;
await this.preview();
});
}
async preview() {
if (!this.cli) {
return;
}
// for typescript as `this.cli.options.outputDir` supports other inputs as well
// but the cli will normalize it to a string before calling plugins
if (
typeof this.cli.options.inputDir !== 'string' ||
typeof this.cli.options.outputDir !== 'string'
) {
return;
}
const rootIndexHtml = path.join(this.cli.options.outputDir, 'index.html');
if (!existsSync(rootIndexHtml)) {
console.log(`${bold(`👀 Previewing Production Build`)}`);
console.log('');
console.log(` 🛑 No index.html found in the build directory ${gray(`${rootIndexHtml}`)}`);
console.log(' 🤔 Did you forget to run `rocket build` before?');
console.log('');
return;
}
/** @type {import('@web/dev-server').DevServerConfig} */
const config = {
open: this.cli.options.open,
rootDir: this.cli.options.outputDir,
clearTerminalOnReload: false,
};
try {
this.devServer = await startDevServer({
config,
logStartMessage: false,
readCliArgs: false,
readFileConfig: false,
// argv: this.__argv,
});
logPreviewMessage({ devServerOptions: this.devServer.config }, console);
} catch (e) {
console.log('🛑 Starting preview server failed');
console.error(e);
}
}
async stop() {
if (this.devServer) {
await this.devServer.stop();
}
}
}

View File

@@ -31,11 +31,12 @@ export class RocketStart {
if (!this.cli) { if (!this.cli) {
return; return;
} }
await this.cli.clearOutputDevDir();
// TODO: enable URL support in the Engine and remove this "workaround" // TODO: enable URL support in the Engine and remove this "workaround"
if ( if (
typeof this.cli.options.inputDir !== 'string' || typeof this.cli.options.inputDir !== 'string' ||
typeof this.cli.options.outputDir !== 'string' typeof this.cli.options.outputDevDir !== 'string'
) { ) {
return; return;
} }
@@ -50,7 +51,7 @@ export class RocketStart {
this.engine = new Engine(); this.engine = new Engine();
this.engine.setOptions({ this.engine.setOptions({
docsDir: this.cli.options.inputDir, docsDir: this.cli.options.inputDir,
outputDir: this.cli.options.outputDir, outputDir: this.cli.options.outputDevDir,
setupPlugins: this.cli.options.setupEnginePlugins, setupPlugins: this.cli.options.setupEnginePlugins,
open: this.cli.options.open, open: this.cli.options.open,
longFileHeaderWidth: this.cli.options.longFileHeaderWidth, longFileHeaderWidth: this.cli.options.longFileHeaderWidth,

View File

@@ -0,0 +1,34 @@
import ip from 'ip';
import { white, cyan } from 'colorette';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
/**
*
* @param {DevServerConfig} devServerOptions
* @param {string} host
* @param {string} path
* @returns {string}
*/
export function createAddress(devServerOptions, host, path) {
return `http${devServerOptions.http2 ? 's' : ''}://${host}:${devServerOptions.port}${path}`;
}
/**
*
* @param {DevServerConfig} devServerOptions
* @param {console} logger
* @param {string} openPath
*/
export 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) {
//
}
}

View File

@@ -0,0 +1,33 @@
import { white, bold, cyan, gray } from 'colorette';
import { createAddress, logNetworkAddress } from '../helpers/infoMessages.js';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
/**
* @param {{ devServerOptions: DevServerConfig}} options
* @param {console} logger
*/
export function logPreviewMessage({ devServerOptions }, logger) {
const prettyHost = devServerOptions.hostname ?? 'localhost';
let openPath = typeof devServerOptions.open === 'string' ? devServerOptions.open : '/';
if (!openPath.startsWith('/')) {
openPath = `/${openPath}`;
}
logger.log(`${bold(`👀 Previewing Production Build`)}`);
logger.log('');
logger.log(
`${white(' 🚧 Local:')} ${cyan(createAddress(devServerOptions, prettyHost, openPath))}`,
);
logNetworkAddress(devServerOptions, logger, openPath);
const sourceDir = devServerOptions.rootDir;
if (sourceDir) {
logger.log(`${white(' 📝 Source:')} ${cyan(sourceDir)}`);
}
logger.log('');
logger.log(
gray(
'If what you see works as expected then you can upload "source" to your production web server.',
),
);
}

View File

@@ -1,38 +1,8 @@
import ip from 'ip';
import { white, bold, cyan, gray } from 'colorette'; import { white, bold, cyan, gray } from 'colorette';
import { createAddress, logNetworkAddress } from '../helpers/infoMessages.js';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */ /** @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 {{ devServerOptions: DevServerConfig, engine: import('@rocket/engine/server').Engine}} options
* @param {console} logger * @param {console} logger

View File

@@ -0,0 +1,49 @@
import chai from 'chai';
import { white, bold, gray } from 'colorette';
import { setupTestCli } from './test-helpers.js';
const { expect } = chai;
describe('Preview', () => {
it('01: Preview Message', async () => {
const { cli, capturedLogs, cleanup } = await setupTestCli({
cwd: 'fixtures/06-preview/01-preview-message',
cliOptions: ['preview'],
testOptions: { captureLogs: true },
});
await cli.start();
await cleanup();
expect(capturedLogs[0]).to.equal(`${bold(`👀 Previewing Production Build`)}`);
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].startsWith(`${white(' 📝 Source:')}`)).to.be.true;
expect(capturedLogs[5]).to.equal('');
expect(capturedLogs[6]).to.equal(
`${gray(
'If what you see works as expected then you can upload "source" to your production web server.',
)}`,
);
});
it('02: Error Message if there is no build output', async () => {
const { cli, capturedLogs, cleanup } = await setupTestCli({
cwd: 'fixtures/06-preview/02-error-no-build',
cliOptions: ['preview'],
testOptions: { captureLogs: true },
});
await cli.start();
await cleanup();
expect(capturedLogs[0]).to.equal(`${bold(`👀 Previewing Production Build`)}`);
expect(capturedLogs[1]).to.equal('');
expect(capturedLogs[2].startsWith(` 🛑 No index.html found in the build directory`)).to.be
.true;
expect(capturedLogs[3]).to.equal(' 🤔 Did you forget to run `rocket build` before?');
expect(capturedLogs[4]).to.equal('');
});
});

View File

@@ -0,0 +1 @@
!__output

View File

@@ -0,0 +1 @@
so preview does not stop