Skip to content

Commit

Permalink
Split setSpawnParams into separate setRuntimeMetadata and setBuildCom…
Browse files Browse the repository at this point in the history
…mand steps and pass runtimeMetadata to AnnounceBuild mutation
  • Loading branch information
ghengeveld committed Oct 2, 2023
1 parent ceb8500 commit a864a21
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 109 deletions.
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
86 changes: 84 additions & 2 deletions node-src/tasks/initialize.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { describe, expect, it, vi } from 'vitest';
import { execa as execaDefault, execaCommand } from 'execa';
import mockfs from 'mock-fs';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { announceBuild, setEnvironment } from './initialize';
import { announceBuild, setEnvironment, setRuntimeMetadata } from './initialize';

vi.mock('execa');

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

afterEach(() => {
mockfs.restore();
});

process.env.GERRIT_BRANCH = 'foo/bar';
process.env.TRAVIS_EVENT_TYPE = 'pull_request';
Expand All @@ -19,6 +30,70 @@ describe('setEnvironment', () => {
});
});

describe('setRuntimeMetadata', () => {
beforeEach(() => {
execa.mockReturnValue(Promise.resolve({ stdout: '1.2.3' }) as any);
command.mockReturnValue(Promise.resolve({ stdout: '1.2.3' }) as any);
});

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

const ctx = {
sourceDir: './source-dir/',
options: { buildScriptName: 'build:storybook' },
storybook: { version: '6.2.0' },
git: { changedFiles: ['./index.js'] },
} as any;
await setRuntimeMetadata(ctx);

expect(ctx.runtimeMetadata).toEqual({
nodePlatform: expect.stringMatching(/darwin|linux|win32/),
nodeVersion: process.versions.node,
packageManager: 'npm',
packageManagerVersion: '1.2.3',
});
});

it('supports yarn', async () => {
mockfs({ './package.json': JSON.stringify({ packageManager: 'yarn' }) });

const ctx = {
sourceDir: './source-dir/',
options: { buildScriptName: 'build:storybook' },
storybook: { version: '6.1.0' },
git: {},
} as any;
await setRuntimeMetadata(ctx);

expect(ctx.runtimeMetadata).toEqual({
nodePlatform: expect.stringMatching(/darwin|linux|win32/),
nodeVersion: process.versions.node,
packageManager: 'yarn',
packageManagerVersion: '1.2.3',
});
});

it('supports pnpm', async () => {
mockfs({ './package.json': JSON.stringify({ packageManager: 'pnpm' }) });

const ctx = {
sourceDir: './source-dir/',
options: { buildScriptName: 'build:storybook' },
storybook: { version: '6.1.0' },
git: {},
} as any;
await setRuntimeMetadata(ctx);

expect(ctx.runtimeMetadata).toEqual({
nodePlatform: expect.stringMatching(/darwin|linux|win32/),
nodeVersion: process.versions.node,
packageManager: 'pnpm',
packageManagerVersion: '1.2.3',
});
});
});

describe('announceBuild', () => {
const defaultContext = {
env,
Expand All @@ -28,6 +103,12 @@ describe('announceBuild', () => {
git: { version: 'whatever', matchesBranch: () => false, committedAt: 0 },
pkg: { version: '1.0.0' },
storybook: { version: '2.0.0', viewLayer: 'react', addons: [] },
runtimeMetadata: {
nodePlatform: 'darwin',
nodeVersion: '18.12.1',
packageManager: 'npm',
pacakgeManagerVersion: '8.19.2',
},
};

it('creates a build on the index and puts it on context', async () => {
Expand All @@ -54,6 +135,7 @@ describe('announceBuild', () => {
storybookAddons: ctx.storybook.addons,
storybookVersion: ctx.storybook.version,
storybookViewLayer: ctx.storybook.viewLayer,
...defaultContext.runtimeMetadata,
},
},
{ retries: 3 }
Expand Down
26 changes: 25 additions & 1 deletion node-src/tasks/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { emailHash } from '../lib/emailHash';
import { getPackageManagerName, getPackageManagerVersion } from '../lib/getPackageManager';
import { createTask, transitionTo } from '../lib/tasks';
import { Context } from '../types';
import noAncestorBuild from '../ui/messages/warnings/noAncestorBuild';
Expand Down Expand Up @@ -39,6 +40,22 @@ export const setEnvironment = async (ctx: Context) => {
ctx.log.debug(`Got environment:\n${JSON.stringify(ctx.environment, null, 2)}`);
};

export const setRuntimeMetadata = async (ctx: Context) => {
ctx.runtimeMetadata = {
nodePlatform: process.platform,
nodeVersion: process.versions.node,
};

try {
const packageManager = await getPackageManagerName();
ctx.runtimeMetadata.packageManager = packageManager as any;
const packageManagerVersion = await getPackageManagerVersion(packageManager);
ctx.runtimeMetadata.packageManagerVersion = packageManagerVersion;
} catch (e) {
ctx.log.debug(`Failed to set runtime metadata: ${e.message}`);
}
};

export const announceBuild = async (ctx: Context) => {
const { patchBaseRef, patchHeadRef, preserveMissingSpecs, isLocalBuild } = ctx.options;
const {
Expand Down Expand Up @@ -71,6 +88,7 @@ export const announceBuild = async (ctx: Context) => {
isLocalBuild,
needsBaselines: !!turboSnap && !turboSnap.bailReason,
packageVersion: ctx.pkg.version,
...ctx.runtimeMetadata,
rebuildForBuildId,
storybookAddons: ctx.storybook.addons,
storybookVersion: ctx.storybook.version,
Expand All @@ -97,5 +115,11 @@ export default createTask({
name: 'initialize',
title: initial.title,
skip: (ctx: Context) => ctx.skip,
steps: [transitionTo(pending), setEnvironment, announceBuild, transitionTo(success, true)],
steps: [
transitionTo(pending),
setEnvironment,
setRuntimeMetadata,
announceBuild,
transitionTo(success, true),
],
});
Loading

0 comments on commit a864a21

Please sign in to comment.