Skip to content

Commit

Permalink
Merge branch 'main' into tweak-auto-release
Browse files Browse the repository at this point in the history
  • Loading branch information
ghengeveld authored Oct 10, 2023
2 parents aa764cb + c92d977 commit bb76c06
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 113 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# v7.3.0 (Tue Oct 10 2023)

#### 🚀 Enhancement

- Pass runtime metadata in `announceBuild` [#826](https://github.com/chromaui/chromatic-cli/pull/826) ([@ghengeveld](https://github.com/ghengeveld))

#### 🐛 Bug Fix

- Gracefully handle gpg signature info in `git log` output [#833](https://github.com/chromaui/chromatic-cli/pull/833) ([@ghengeveld](https://github.com/ghengeveld))

#### Authors: 1

- Gert Hengeveld ([@ghengeveld](https://github.com/ghengeveld))

---

# v7.2.3 (Fri Oct 06 2023)

#### 🐛 Bug Fix
Expand Down
68 changes: 67 additions & 1 deletion node-src/git/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
import { execaCommand } from 'execa';
import { describe, expect, it, vi } from 'vitest';

import { getSlug } from './git';
import { getCommit, getSlug, hasPreviousCommit } from './git';

vi.mock('execa');

const command = vi.mocked(execaCommand);

describe('getCommit', () => {
it('parses log output', async () => {
command.mockImplementation(
() =>
Promise.resolve({
all: `19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a ## 1696588814 ## [email protected] ## Gert Hengeveld`,
}) as any
);
expect(await getCommit()).toEqual({
commit: '19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a',
committedAt: 1696588814 * 1000,
committerEmail: '[email protected]',
committerName: 'Gert Hengeveld',
});
});

it('ignores gpg signature information', async () => {
command.mockImplementation(
() =>
Promise.resolve({
all: `
gpg: Signature made Fri Oct 6 12:40:14 2023 CEST
gpg: using RSA key 4AEE18F83AFDEB23
gpg: Can't check signature: No public key
19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a ## 1696588814 ## [email protected] ## Gert Hengeveld
`.trim(),
}) as any
);
expect(await getCommit()).toEqual({
commit: '19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a',
committedAt: 1696588814 * 1000,
committerEmail: '[email protected]',
committerName: 'Gert Hengeveld',
});
});
});

describe('getSlug', () => {
it('returns the slug portion of the git url', async () => {
command.mockImplementation(
Expand All @@ -25,3 +62,32 @@ describe('getSlug', () => {
expect(await getSlug()).toBe('foo/bar.baz');
});
});

describe('hasPreviousCommit', () => {
it('returns true if a commit is found', async () => {
command.mockImplementation(
() => Promise.resolve({ all: `19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a` }) as any
);
expect(await hasPreviousCommit()).toEqual(true);
});

it('returns false if no commit is found', async () => {
command.mockImplementation(() => Promise.resolve({ all: `` }) as any);
expect(await hasPreviousCommit()).toEqual(false);
});

it('ignores gpg signature information', async () => {
command.mockImplementation(
() =>
Promise.resolve({
all: `
gpg: Signature made Fri Oct 6 12:40:14 2023 CEST
gpg: using RSA key 4AEE18F83AFDEB23
gpg: Can't check signature: No public key
19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a
`.trim(),
}) as any
);
expect(await hasPreviousCommit()).toEqual(true);
});
});
12 changes: 10 additions & 2 deletions node-src/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ export async function getCommit(revision = '') {
// Technically this yields the author info, not committer info
`git --no-pager log -n 1 --format="%H ## %ct ## %ae ## %an" ${revision}`
);
const [commit, committedAtSeconds, committerEmail, committerName] = result.split(' ## ');

// Ignore lines that don't match the expected format (e.g. gpg signature info)
const format = new RegExp('^[a-f0-9]+ ## ');
const data = result.split('\n').find((line: string) => format.test(line));

const [commit, committedAtSeconds, committerEmail, committerName] = data.split(' ## ');
const committedAt = Number(committedAtSeconds) * 1000;
return { commit, committedAt, committerEmail, committerName };
}
Expand Down Expand Up @@ -119,7 +124,10 @@ export async function getUncommittedHash() {

export async function hasPreviousCommit() {
const result = await execGitCommand(`git --no-pager log -n 1 --skip=1 --format="%H"`);
return !!result.trim();

// Ignore lines that don't match the expected format (e.g. gpg signature info)
const allhex = new RegExp('^[a-f0-9]+$');
return result.split('\n').some((line: string) => allhex.test(line));
}

// Check if a commit exists in the repository
Expand Down
10 changes: 9 additions & 1 deletion node-src/lib/getPackageManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { parseNr, getCliCommand, parseNa } from '@antfu/ni';
import { execa } from 'execa';

// 'npm' | 'pnpm' | 'yarn' | 'bun'
export const getPackageManagerName = async () => {
return getCliCommand(parseNa, [], { programmatic: true }) as any;
return getCliCommand(parseNa, [], { programmatic: true });
};

// e.g. `npm run build-storybook`
export const getPackageManagerRunCommand = async (args: string[]) => {
return getCliCommand(parseNr, args, { programmatic: true });
};

// e.g. `8.19.2`
export const getPackageManagerVersion = async (packageManager: string) => {
const { stdout } = await execa(packageManager || (await getPackageManagerName()), ['--version']);
const [output] = (stdout.toString() as string).trim().split('\n', 1);
return output.trim().replace(/^v/, '');
};
69 changes: 20 additions & 49 deletions node-src/tasks/build.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { execa as execaDefault, execaCommand } from 'execa';
import { execaCommand } from 'execa';
import mockfs from 'mock-fs';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { buildStorybook, setSourceDir, setSpawnParams } from './build';
import { buildStorybook, setSourceDir, setBuildCommand } from './build';

vi.mock('execa');

const execa = vi.mocked(execaDefault);
const command = vi.mocked(execaCommand);

afterEach(() => {
Expand Down Expand Up @@ -39,16 +38,8 @@ describe('setSourceDir', () => {
});
});

describe('setSpawnParams', () => {
const npmExecPath = process.env.npm_execpath;

beforeEach(() => {
process.env.npm_execpath = npmExecPath;
execa.mockReturnValue(Promise.resolve({ stdout: '1.2.3' }) as any);
command.mockReturnValue(Promise.resolve({ stdout: '1.2.3' }) as any);
});

it('sets the spawn params on the context', async () => {
describe('setBuildCommand', () => {
it('sets the build command on the context', async () => {
mockfs({ './package.json': JSON.stringify({ packageManager: 'npm' }) });

const ctx = {
Expand All @@ -57,16 +48,11 @@ describe('setSpawnParams', () => {
storybook: { version: '6.2.0' },
git: { changedFiles: ['./index.js'] },
} as any;
await setSpawnParams(ctx);

expect(ctx.spawnParams).toEqual({
client: 'npm',
clientVersion: '1.2.3',
nodeVersion: '1.2.3',
platform: expect.stringMatching(/darwin|linux|win32/),
command:
'npm run build:storybook -- --output-dir ./source-dir/ --webpack-stats-json ./source-dir/',
});
await setBuildCommand(ctx);

expect(ctx.buildCommand).toEqual(
'npm run build:storybook -- --output-dir ./source-dir/ --webpack-stats-json ./source-dir/'
);
});

it('supports yarn', async () => {
Expand All @@ -78,15 +64,9 @@ describe('setSpawnParams', () => {
storybook: { version: '6.1.0' },
git: {},
} as any;
await setSpawnParams(ctx);

expect(ctx.spawnParams).toEqual({
client: 'yarn',
clientVersion: '1.2.3',
nodeVersion: '1.2.3',
platform: expect.stringMatching(/darwin|linux|win32/),
command: 'yarn run build:storybook --output-dir ./source-dir/',
});
await setBuildCommand(ctx);

expect(ctx.buildCommand).toEqual('yarn run build:storybook --output-dir ./source-dir/');
});

it('supports pnpm', async () => {
Expand All @@ -98,15 +78,9 @@ describe('setSpawnParams', () => {
storybook: { version: '6.1.0' },
git: {},
} as any;
await setSpawnParams(ctx);

expect(ctx.spawnParams).toEqual({
client: 'pnpm',
clientVersion: '1.2.3',
nodeVersion: '1.2.3',
platform: expect.stringMatching(/darwin|linux|win32/),
command: 'pnpm run build:storybook --output-dir ./source-dir/',
});
await setBuildCommand(ctx);

expect(ctx.buildCommand).toEqual('pnpm run build:storybook --output-dir ./source-dir/');
});

it('warns if --only-changes is not supported', async () => {
Expand All @@ -117,7 +91,7 @@ describe('setSpawnParams', () => {
git: { changedFiles: ['./index.js'] },
log: { warn: vi.fn() },
} as any;
await setSpawnParams(ctx);
await setBuildCommand(ctx);
expect(ctx.log.warn).toHaveBeenCalledWith(
'Storybook version 6.2.0 or later is required to use the --only-changed flag'
);
Expand All @@ -127,7 +101,7 @@ describe('setSpawnParams', () => {
describe('buildStorybook', () => {
it('runs the build command', async () => {
const ctx = {
spawnParams: { command: 'npm run build:storybook --script-args' },
buildCommand: 'npm run build:storybook --script-args',
env: { STORYBOOK_BUILD_TIMEOUT: 1000 },
log: { debug: vi.fn() },
options: {},
Expand All @@ -138,15 +112,12 @@ describe('buildStorybook', () => {
'npm run build:storybook --script-args',
expect.objectContaining({ stdio: expect.any(Array) })
);
expect(ctx.log.debug).toHaveBeenCalledWith(
'Using spawnParams:',
JSON.stringify(ctx.spawnParams, null, 2)
);
expect(ctx.log.debug).toHaveBeenCalledWith('Running build command:', ctx.buildCommand);
});

it('fails when build times out', async () => {
const ctx = {
spawnParams: { command: 'npm run build:storybook --script-args' },
buildCommand: 'npm run build:storybook --script-args',
options: { buildScriptName: '' },
env: { STORYBOOK_BUILD_TIMEOUT: 0 },
log: { debug: vi.fn(), error: vi.fn() },
Expand Down
31 changes: 9 additions & 22 deletions node-src/tasks/build.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execa, execaCommand } from 'execa';
import { execaCommand } from 'execa';
import { createWriteStream, readFileSync } from 'fs';
import path from 'path';
import semver from 'semver';
Expand All @@ -10,9 +10,7 @@ import { Context } from '../types';
import { endActivity, startActivity } from '../ui/components/activity';
import buildFailed from '../ui/messages/errors/buildFailed';
import { failed, initial, pending, skipped, success } from '../ui/tasks/build';
import { getPackageManagerName, getPackageManagerRunCommand } from '../lib/getPackageManager';

const trimOutput = ({ stdout }) => stdout && stdout.toString().trim();
import { getPackageManagerRunCommand } from '../lib/getPackageManager';

export const setSourceDir = async (ctx: Context) => {
if (ctx.options.outputDir) {
Expand All @@ -26,20 +24,17 @@ export const setSourceDir = async (ctx: Context) => {
}
};

export const setSpawnParams = async (ctx) => {
export const setBuildCommand = async (ctx: Context) => {
const webpackStatsSupported =
ctx.storybook && ctx.storybook.version
? semver.gte(semver.coerce(ctx.storybook.version), '6.2.0')
: true;

if (ctx.git.changedFiles && !webpackStatsSupported) {
ctx.log.warn('Storybook version 6.2.0 or later is required to use the --only-changed flag');
}

const client = await getPackageManagerName();
const clientVersion = await execa(client, ['--version']).then(trimOutput);
const nodeVersion = await execa('node', ['--version']).then(trimOutput);

const command = await getPackageManagerRunCommand(
ctx.buildCommand = await getPackageManagerRunCommand(
[
ctx.options.buildScriptName,
'--output-dir',
Expand All @@ -48,14 +43,6 @@ export const setSpawnParams = async (ctx) => {
ctx.git.changedFiles && webpackStatsSupported && ctx.sourceDir,
].filter(Boolean)
);

ctx.spawnParams = {
client,
clientVersion,
nodeVersion,
platform: process.platform,
command,
};
};

const timeoutAfter = (ms) =>
Expand All @@ -71,10 +58,10 @@ export const buildStorybook = async (ctx: Context) => {

const { experimental_abortSignal: signal } = ctx.options;
try {
const { command } = ctx.spawnParams;
ctx.log.debug('Using spawnParams:', JSON.stringify(ctx.spawnParams, null, 2));
ctx.log.debug('Running build command:', ctx.buildCommand);
ctx.log.debug('Runtime metadata:', JSON.stringify(ctx.runtimeMetadata, null, 2));

const subprocess = execaCommand(command, { stdio: [null, logFile, logFile], signal });
const subprocess = execaCommand(ctx.buildCommand, { stdio: [null, logFile, logFile], signal });
await Promise.race([subprocess, timeoutAfter(ctx.env.STORYBOOK_BUILD_TIMEOUT)]);
} catch (e) {
signal?.throwIfAborted();
Expand All @@ -101,7 +88,7 @@ export default createTask({
},
steps: [
setSourceDir,
setSpawnParams,
setBuildCommand,
transitionTo(pending),
startActivity,
buildStorybook,
Expand Down
Loading

0 comments on commit bb76c06

Please sign in to comment.