diff --git a/README.md b/README.md index 96ae9f4..908300b 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,8 @@ If you've cloned this repo and want to work on the tool, you have to install its running `pnpm install`. Once they're installed, you can run the following commands: -* `pnpm run release-build` - Builds the package in the `dist` directory for publishing to npm. +* `pnpm run release-build` - Creates a release build of the package in `dist` directory. +* `pnpm run npm-publish` - Builds the package and publishes it to npm. * `pnpm run eval` - Runs an eval from source. * `pnpm run report` - Runs the report app from source. * `pnpm run init` - Runs the init script from source. diff --git a/package.json b/package.json index 6718d29..08ce91a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "0.0.5", "scripts": { "build-runner": "tsc", - "release-build": "tsx release-build.ts", + "release-build": "tsx ./scripts/release-build.ts", + "npm-publish": "tsx ./scripts/npm-publish.ts", "web-codegen-scorer": "tsx ./runner/bin/cli.ts", "wcs": "pnpm -s web-codegen-scorer", "eval": "pnpm web-codegen-scorer eval", @@ -20,7 +21,7 @@ "evaluation", "web", "web development", - "code quaility", + "code quality", "prompt engineering" ], "author": "angular", diff --git a/report-app/src/styles.scss b/report-app/src/styles.scss index d74ad2d..ed830e4 100644 --- a/report-app/src/styles.scss +++ b/report-app/src/styles.scss @@ -67,6 +67,13 @@ html.dark-mode { box-sizing: border-box; } +// These can show up in generated markdown. +hr { + border: none; + background: none; + border-top: solid 1px var(--border-color); +} + @include buttons.button-styles; @include inputs.input-styles; @include callouts.callout-styles; diff --git a/runner/report-cli.ts b/runner/report-cli.ts index 3da1323..bd777d2 100644 --- a/runner/report-cli.ts +++ b/runner/report-cli.ts @@ -1,5 +1,5 @@ import { Arguments, Argv, CommandModule } from 'yargs'; -import { join } from 'path'; +import { join, relative } from 'path'; import { executeCommand } from './utils/exec.js'; import { REPORTS_ROOT_DIR } from './configuration/constants.js'; import { toProcessAbsolutePath } from './file-system-utils.js'; @@ -63,9 +63,8 @@ async function handler(cliArgs: Arguments): Promise { formatTitleCard( [ `View your reports at http://localhost:${cliArgs.port}`, - `Reports are served from ${reportsDir}`, - ].join('\n'), - 120 // Use a wider box since file paths can be long. + `Reports are served from ${relative(process.cwd(), reportsDir)}`, + ].join('\n') ) ); diff --git a/runner/reporting/format.ts b/runner/reporting/format.ts index 0bfae12..28ec7cf 100644 --- a/runner/reporting/format.ts +++ b/runner/reporting/format.ts @@ -14,7 +14,7 @@ export function formatTitleCard(text: string, width = 80): string { borderStyle: 'double', borderColor: 'cyan', padding: 1, - width, + width: Math.min(width, process.stdout.columns), }); } diff --git a/runner/reporting/report-logging.ts b/runner/reporting/report-logging.ts index cb36940..b532050 100644 --- a/runner/reporting/report-logging.ts +++ b/runner/reporting/report-logging.ts @@ -289,7 +289,7 @@ export function logReportToConsole(runInfo: RunInfo): void { boxen(summaryLines.join('\n'), { padding: 1, margin: { top: 2 }, - width: 80, + width: Math.min(80, process.stdout.columns), borderColor: 'cyan', borderStyle: 'double', title: 'Assessment Summary', diff --git a/scripts/npm-publish.ts b/scripts/npm-publish.ts new file mode 100644 index 0000000..c8e69a1 --- /dev/null +++ b/scripts/npm-publish.ts @@ -0,0 +1,73 @@ +import { join } from 'path'; +import { spawn } from 'child_process'; +import { input } from '@inquirer/prompts'; +import { executeCommand } from '../runner/utils/exec.js'; +import { readFile, writeFile } from 'fs/promises'; + +const root = join(import.meta.dirname, '..'); +const distDirectory = join(root, 'dist'); +const packageJsonPath = join(root, 'package.json'); +const registry = 'https://wombat-dressing-room.appspot.com'; + +(async () => { + try { + const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as { + version: string; + }; + + const version = await input({ + message: `Which version would you like to publish? Current version is ${packageJson.version}`, + required: true, + }); + + // Build the project. + await executeCommand( + `pnpm release-build --version=${version}`, + root, + undefined, + { + forwardStdoutToParent: true, + forwardStderrToParent: true, + } + ); + + // Log into our registry. + await spawnInteractive('npm', ['login', '--registry', registry]); + + // Publish to npm. + await executeCommand( + `npm --registry ${registry} publish --access public --tag latest`, + distDirectory, + undefined, + { + forwardStderrToParent: true, + forwardStdoutToParent: true, + } + ); + + // Write the package.json back to disk so the version is in sync. + packageJson.version = version; + await writeFile(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); + + console.log('Done! 🎉'); + console.log('Remember to push the changed package.json!'); + } catch (e: unknown) { + // If the user presses ctrl + c, Inquirer will throw `ExitPromptError`. Ignore it. + if (!(e instanceof Error) || e.name !== 'ExitPromptError') { + throw e; + } + } +})(); + +function spawnInteractive(command: string, args: string[]) { + return new Promise((resolve, reject) => { + const childProcess = spawn(command, args, { + shell: true, + stdio: 'inherit', + }); + + childProcess.on('close', (status) => + status === 0 ? resolve() : reject(status) + ); + }); +} diff --git a/release-build.ts b/scripts/release-build.ts similarity index 82% rename from release-build.ts rename to scripts/release-build.ts index d1d720f..82c6a9a 100644 --- a/release-build.ts +++ b/scripts/release-build.ts @@ -1,12 +1,11 @@ -/// import { join } from 'path'; import { rm, cp, readFile, writeFile } from 'fs/promises'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { globSync as glob } from 'tinyglobby'; -import { executeCommand } from './runner/utils/exec.js'; +import { executeCommand } from '../runner/utils/exec.js'; -const root = import.meta.dirname; +const root = join(import.meta.dirname, '..'); const runnerSource = join(root, 'runner'); const targetDirectory = join(root, 'dist'); const reportAppSource = join(root, 'report-app'); @@ -14,10 +13,15 @@ const reportAppDist = join(reportAppSource, 'dist'); const browserAgentRelativePath = 'runner/testing/browser-agent'; const args = yargs(hideBin(process.argv)) + .version(false) .option('runner-only', { type: 'boolean', default: false, }) + .option('version', { + type: 'string', + default: null, + }) .parseSync(); (async () => { @@ -34,7 +38,7 @@ const args = yargs(hideBin(process.argv)) // Generate the package.json. await writeFile( join(targetDirectory, 'package.json'), - await getPackageJson(join(root, 'package.json')) + await getPackageJson(join(root, 'package.json'), args.version) ); // Copy the readme and license. @@ -86,17 +90,29 @@ const args = yargs(hideBin(process.argv)) } console.log(`Release output has been built in ${targetDirectory}`); - - // TODO: also have `npm publish` here? })(); -async function getPackageJson(path: string): Promise { +async function getPackageJson( + path: string, + version: string | null +): Promise { const content = await readFile(path, 'utf8'); const parsed = JSON.parse(content) as { + version: string; scripts?: unknown; devDependencies?: unknown; }; + if (version) { + if (version === parsed.version) { + throw new Error( + `Specified version is the same version as the current one.` + ); + } else { + parsed.version = version; + } + } + // Delete some fields that aren't relevant for end users. delete parsed.scripts; delete parsed.devDependencies; diff --git a/tsconfig.json b/tsconfig.json index a57d57e..5e5d2e8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,5 @@ "types": ["node"] }, "include": ["runner/**/*.ts"], - "exclude": ["node_modules", "examples"] + "exclude": ["node_modules", "examples", "scripts"] }