Skip to content

Commit

Permalink
Merge pull request #1122 from chromaui/revert-1121-revert-1112-tom/ca…
Browse files Browse the repository at this point in the history
…p-2327-track-hasrouter-and-haspagecomponents-on-build-events

Send project metadata to the index
  • Loading branch information
tmeasday authored Dec 2, 2024
2 parents 0375949 + e744798 commit 485d250
Show file tree
Hide file tree
Showing 23 changed files with 768 additions and 158 deletions.
4 changes: 4 additions & 0 deletions bin-src/trace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import meow from 'meow';

import { getRepositoryRoot } from '../node-src/git/git';
import { getDependentStoryFiles } from '../node-src/lib/getDependentStoryFiles';
import { isPackageManifestFile } from '../node-src/lib/utils';
import { readStatsFile } from '../node-src/tasks/readStatsFile';
Expand Down Expand Up @@ -91,6 +92,9 @@ export async function main(argv: string[]) {
untraced: flags.untraced,
traceChanged: flags.mode || true,
},
git: {
rootPath: await getRepositoryRoot(),
},
} as any;
const stats = await readStatsFile(flags.statsFile);
const changedFiles = input.map((f) => f.replace(/^\.\//, ''));
Expand Down
167 changes: 167 additions & 0 deletions node-src/git/execGit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { PassThrough, Transform } from 'node:stream';
import { beforeEach } from 'node:test';

import { execaCommand as rawExecaCommand } from 'execa';
import { describe, expect, it, vitest } from 'vitest';

import gitNoCommits from '../ui/messages/errors/gitNoCommits';
import gitNotInitialized from '../ui/messages/errors/gitNotInitialized';
import gitNotInstalled from '../ui/messages/errors/gitNotInstalled';
import { execGitCommand, execGitCommandCountLines, execGitCommandOneLine } from './execGit';

vitest.mock('execa');

const execaCommand = vitest.mocked(rawExecaCommand);
beforeEach(() => {
execaCommand.mockReset();
});

describe('execGitCommand', () => {
it('returns execa output if it works', async () => {
execaCommand.mockResolvedValue({
all: Buffer.from('some output'),
} as any);

expect(await execGitCommand('some command')).toEqual('some output');
});

it('errors if there is no output', async () => {
execaCommand.mockResolvedValue({
all: undefined,
} as any);

await expect(execGitCommand('some command')).rejects.toThrow(/Unexpected missing git/);
});

it('handles missing git error', async () => {
execaCommand.mockRejectedValue(new Error('not a git repository'));

await expect(execGitCommand('some command')).rejects.toThrow(
gitNotInitialized({ command: 'some command' })
);
});

it('handles git not found error', async () => {
execaCommand.mockRejectedValue(new Error('git not found'));

await expect(execGitCommand('some command')).rejects.toThrow(
gitNotInstalled({ command: 'some command' })
);
});

it('handles no commits yet', async () => {
execaCommand.mockRejectedValue(new Error('does not have any commits yet'));

await expect(execGitCommand('some command')).rejects.toThrow(
gitNoCommits({ command: 'some command' })
);
});

it('rethrows arbitrary errors', async () => {
execaCommand.mockRejectedValue(new Error('something random'));
await expect(execGitCommand('some command')).rejects.toThrow('something random');
});
});

function createExecaStreamer() {
let resolver;
let rejecter;
const promiseLike = new Promise((aResolver, aRejecter) => {
resolver = aResolver;
rejecter = aRejecter;
}) as Promise<unknown> & {
stdout: Transform;
kill: () => void;
_rejecter: (err: Error) => void;
};
promiseLike.stdout = new PassThrough();
promiseLike.kill = resolver;
promiseLike._rejecter = rejecter;
return promiseLike;
}

describe('execGitCommandOneLine', () => {
it('returns the first line if the command works', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandOneLine('some command');

streamer.stdout.write('First line\n');
streamer.stdout.write('Second line\n');

expect(await promise).toEqual('First line');
});

it('returns the output if the command only has one line', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandOneLine('some command');

streamer.stdout.write('First line\n');
streamer.stdout.end();

expect(await promise).toEqual('First line');
});

it('Return an error if the command has no ouput', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandOneLine('some command');

streamer.kill();

await expect(promise).rejects.toThrow(/missing git command output/);
});

it('rethrows arbitrary errors', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandOneLine('some command');

streamer._rejecter(new Error('some error'));

await expect(promise).rejects.toThrow(/some error/);
});
});

describe('execGitCommandCountLines', () => {
it('counts lines, many', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandCountLines('some command');

streamer.stdout.write('First line\n');
streamer.stdout.write('Second line\n');
streamer.kill();

expect(await promise).toEqual(2);
});

it('counts lines, one', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandCountLines('some command');

streamer.stdout.write('First line\n');
streamer.kill();

expect(await promise).toEqual(1);
});

it('counts lines, none', async () => {
const streamer = createExecaStreamer();
execaCommand.mockReturnValue(streamer as any);

const promise = execGitCommandCountLines('some command');

streamer.kill();

expect(await promise).toEqual(0);
});
});
120 changes: 120 additions & 0 deletions node-src/git/execGit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { createInterface } from 'node:readline';

import { execaCommand } from 'execa';

import gitNoCommits from '../ui/messages/errors/gitNoCommits';
import gitNotInitialized from '../ui/messages/errors/gitNotInitialized';
import gitNotInstalled from '../ui/messages/errors/gitNotInstalled';

const defaultOptions: Parameters<typeof execaCommand>[1] = {
env: { LANG: 'C', LC_ALL: 'C' }, // make sure we're speaking English
timeout: 20_000, // 20 seconds
all: true, // interleave stdout and stderr
shell: true, // we'll deal with escaping ourselves (for now)
};

/**
* Execute a Git command in the local terminal.
*
* @param command The command to execute.
* @param options Execa options
*
* @returns The result of the command from the terminal.
*/
export async function execGitCommand(
command: string,
options?: Parameters<typeof execaCommand>[1]
) {
try {
const { all } = await execaCommand(command, { ...defaultOptions, ...options });

if (all === undefined) {
throw new Error(`Unexpected missing git command output for command: '${command}'`);
}

return all.toString();
} catch (error) {
const { message } = error;

if (message.includes('not a git repository')) {
throw new Error(gitNotInitialized({ command }));
}

if (message.includes('git not found')) {
throw new Error(gitNotInstalled({ command }));
}

if (message.includes('does not have any commits yet')) {
throw new Error(gitNoCommits({ command }));
}

throw error;
}
}

/**
* Execute a Git command in the local terminal and just get the first line.
*
* @param command The command to execute.
* @param options Execa options
*
* @returns The first line of the command from the terminal.
*/
export async function execGitCommandOneLine(
command: string,
options?: Parameters<typeof execaCommand>[1]
) {
const process = execaCommand(command, { ...defaultOptions, buffer: false, ...options });

return Promise.race([
// This promise will resolve only if there is an error or it times out
(async () => {
await process;

throw new Error(`Unexpected missing git command output for command: '${command}'`);
})(),
// We expect this promise to resolve first
new Promise<string>((resolve, reject) => {
if (!process.stdout) {
return reject(new Error('Unexpected missing stdout'));
}

const rl = createInterface(process.stdout);
rl.once('line', (line) => {
rl.close();
process.kill();

resolve(line);
});
}),
]);
}

/**
* Execute a Git command in the local terminal and count the lines in the result
*
* @param command The command to execute.
* @param options Execa options
*
* @returns The number of lines the command returned
*/
export async function execGitCommandCountLines(
command: string,
options?: Parameters<typeof execaCommand>[1]
) {
const process = execaCommand(command, { ...defaultOptions, buffer: false, ...options });
if (!process.stdout) {
throw new Error('Unexpected missing stdout');
}

let lineCount = 0;
const rl = createInterface(process.stdout);
rl.on('line', () => {
lineCount += 1;
});

// If the process errors, this will throw
await process;

return lineCount;
}
3 changes: 2 additions & 1 deletion node-src/git/getParentCommits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import gql from 'fake-tag';

import { localBuildsSpecifier } from '../lib/localBuildsSpecifier';
import { Context } from '../types';
import { commitExists, execGitCommand } from './git';
import { execGitCommand } from './execGit';
import { commitExists } from './git';

export const FETCH_N_INITIAL_BUILD_COMMITS = 20;

Expand Down
Loading

0 comments on commit 485d250

Please sign in to comment.