Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ type CliOptionState = Pick<
| 'force'
| 'checkOverrides'
| 'coverage'
| 'directMerge'
>;

type MenuContext = {
Expand Down Expand Up @@ -79,7 +78,6 @@ function readOptions(opts: Record<string, unknown>): CliOptionState {
force: opts.force === true,
checkOverrides: opts.checkOverrides === true,
coverage: opts.coverage === true,
directMerge: opts.directMerge === true,
};
}

Expand All @@ -106,10 +104,6 @@ const serviceDefinitions: ServiceDefinition[] = [
{ flags: '--hard', description: 'overwrite drifted files with upstream version (aggressive realignment)' },
{ flags: '--unpinned', description: 'ignore pinned files (except package.json) to resurface upstream changes' },
{ flags: '--track <mode>', description: 'override upstream tracking for this run: release|branch' },
{
flags: '--direct-merge',
description: 'auto-merge (squash) the sync PR once checks pass — needs auto-merge enabled on the repo',
},
],
includeInMenu: (context) => !context.isUpstreamRepo,
menuDescription: () => 'merge upstream changes + sync package.json',
Expand Down Expand Up @@ -219,7 +213,6 @@ function buildProgram(setSelection: (selection: CliServiceSelection) => void): C
' $ cella sync --hard',
' $ cella sync --unpinned',
' $ cella sync --track branch',
' $ cella sync --direct-merge',
' $ cella audit --check-overrides',
' $ cella contributions --fork raak --json',
].join('\n'),
Expand Down
7 changes: 0 additions & 7 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,6 @@ export interface RuntimeConfig extends CellaCliConfig {

/** Regenerate test coverage before showing the stats summary (stats service) */
coverage?: boolean;

/**
* After opening the sync PR, enable GitHub auto-merge (squash) so it merges itself once
* required checks pass (sync service). Opt-in per run via `--direct-merge`; needs auto-merge
* enabled on the repo. Not the advised default — a sync PR is usually worth a human look.
*/
directMerge?: boolean;
}

/** File status after analysis */
Expand Down
29 changes: 2 additions & 27 deletions src/services/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,22 +358,6 @@ function indentLines(text: string): string {
.join('\n');
}

/**
* Turn on GitHub auto-merge (squash) for the just-opened sync PR: it merges itself once required
* checks pass, so `--direct-merge` needs no babysitting. Returns whether it was enabled; on failure
* (the repo doesn't allow auto-merge, `gh` too old) it prints the manual command and returns false.
*/
function enableAutoMerge(forkPath: string, branch: string): boolean {
console.info(pc.dim('enabling auto-merge (squash) once checks pass...'));
const merge = mergePrSquash(forkPath, branch, { auto: true, deleteBranch: true });
if (merge.ok) return true;

console.info(pc.yellow('could not enable auto-merge (is it enabled for the repo?). merge it manually:'));
if (merge.output) console.info(pc.dim(indentLines(merge.output)));
console.info(pc.dim(` gh pr merge ${branch} --squash --delete-branch`));
return false;
}

/**
* Push the finished sync branch to `origin`, open a PR into the trunk, and switch back to the
* trunk. Runs when `cella sync` is invoked on a sync branch whose merge is already committed —
Expand All @@ -382,9 +366,6 @@ function enableAutoMerge(forkPath: string, branch: string): boolean {
* Before pushing, any merge commits on the branch are flattened away (see `flattenSyncBranch`)
* so the PR never lists the upstream branch's entire history.
*
* With `--direct-merge`, once the PR is open GitHub auto-merge is enabled (see `enableAutoMerge`)
* so it squash-merges itself as soon as required checks pass — no forgotten open PR to trip over.
*
* Every step degrades gracefully: a failed push (no `origin`, auth) prints the manual steps and
* leaves you on the branch; a missing/failed `gh` (or an existing PR) prints the `gh` command but
* still returns you to the trunk since the branch is already pushed.
Expand All @@ -396,7 +377,6 @@ async function shipSyncBranch(config: RuntimeConfig, branch: string): Promise<vo
const flattened = await flattenSyncBranch(forkPath, branch, base);
let prUrl: string | undefined;
let prOpened = false;
let autoMergeEnabled = false;

// The squash commit's subject is the versioned sync message — reuse it as the PR title so the
// PR name carries the upstream version and commit id (release-please only needs the prefix).
Expand Down Expand Up @@ -424,9 +404,7 @@ async function shipSyncBranch(config: RuntimeConfig, branch: string): Promise<vo
});
prUrl = extractFirstUrl(`${pr.stdout ?? ''}\n${pr.stderr ?? ''}`);
prOpened = pr.status === 0;
if (prOpened) {
if (config.directMerge) autoMergeEnabled = enableAutoMerge(forkPath, branch);
} else {
if (!prOpened) {
console.info(pc.yellow('could not open the PR automatically (it may already exist). open it with:'));
if (prUrl) console.info(pc.dim(` ${prUrl}`));
printPrCreateStep(branch, base, prTitle);
Expand All @@ -443,10 +421,7 @@ async function shipSyncBranch(config: RuntimeConfig, branch: string): Promise<vo
console.info();
if (prUrl) {
console.info(`${pc.green('✓')} Sync pull request ${prOpened ? 'opened' : 'ready'}`);
const status = autoMergeEnabled
? `auto-merge on — squashes into '${base}' when checks pass`
: `branch pushed, back on '${base}'`;
console.info(pc.dim(` ${prUrl} · ${status}`));
console.info(pc.dim(` ${prUrl} · branch pushed, back on '${base}'`));
} else {
console.info(`${pc.green('✓')} Sync branch pushed`);
console.info(pc.dim(` '${branch}' is on origin, back on '${base}'`));
Expand Down
12 changes: 3 additions & 9 deletions src/utils/gh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,16 @@ export function listOpenSyncPrs(cwd: string, prefix: string): GhPullRequest[] {
/**
* Build the argv for `gh pr merge`. Pure — unit-tested.
*
* `auto` uses GitHub auto-merge (the PR squashes once required checks pass); without it the
* merge is attempted immediately and fails when the PR is not mergeable.
* The merge is attempted immediately and fails when the PR is not mergeable.
*/
export function buildMergeArgs(ref: string, options: { auto?: boolean; deleteBranch?: boolean } = {}): string[] {
export function buildMergeArgs(ref: string, options: { deleteBranch?: boolean } = {}): string[] {
const args = ['pr', 'merge', ref, '--squash'];
if (options.auto) args.push('--auto');
if (options.deleteBranch) args.push('--delete-branch');
return args;
}

/** Squash-merge a PR (by number, branch, or URL). See {@link buildMergeArgs}. */
export function mergePrSquash(
cwd: string,
ref: string | number,
options: { auto?: boolean; deleteBranch?: boolean } = {},
): GhResult {
export function mergePrSquash(cwd: string, ref: string | number, options: { deleteBranch?: boolean } = {}): GhResult {
return runGh(cwd, buildMergeArgs(String(ref), options));
}

Expand Down
10 changes: 0 additions & 10 deletions tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,5 @@ describe('parseCli', () => {
expect(config.service).toBe('sync');
expect(config.logFile).toBe(true);
expect(config.hard).toBe(true);
expect(config.directMerge).toBe(false);
});

it('parses --direct-merge for sync', async () => {
process.argv = ['node', 'cella', 'sync', '--direct-merge'];

const config = await parseCli(baseConfig, '/tmp/fork');

expect(config.service).toBe('sync');
expect(config.directMerge).toBe(true);
});
});
13 changes: 1 addition & 12 deletions tests/gh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,7 @@ describe('buildMergeArgs', () => {
expect(buildMergeArgs('42')).toEqual(['pr', 'merge', '42', '--squash']);
});

it('adds --auto for auto-merge and --delete-branch when requested', () => {
expect(buildMergeArgs('cella/sync/x', { auto: true, deleteBranch: true })).toEqual([
'pr',
'merge',
'cella/sync/x',
'--squash',
'--auto',
'--delete-branch',
]);
});

it('can delete the branch without enabling auto-merge', () => {
it('deletes the branch when requested', () => {
expect(buildMergeArgs('7', { deleteBranch: true })).toEqual(['pr', 'merge', '7', '--squash', '--delete-branch']);
});
});
Loading