Skip to content

Commit

Permalink
Merge pull request #826 from chromaui/track-node-version
Browse files Browse the repository at this point in the history
Pass runtime metadata in `announceBuild`
  • Loading branch information
ghengeveld authored Oct 10, 2023
2 parents 2fed8db + 28d33af commit d6c989a
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 d6c989a

Please sign in to comment.