-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move ensure-next-ahead from a bash script to a tested node script
- Loading branch information
Showing
4 changed files
with
189 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* eslint-disable global-require */ | ||
/* eslint-disable no-underscore-dangle */ | ||
import path from 'path'; | ||
import { run as ensureNextAhead } from '../ensure-next-ahead'; | ||
import * as gitClient_ from '../utils/git-client'; | ||
import * as bumpVersion_ from '../version'; | ||
|
||
jest.mock('../utils/git-client', () => jest.requireActual('jest-mock-extended').mockDeep()); | ||
const gitClient = jest.mocked(gitClient_); | ||
|
||
// eslint-disable-next-line jest/no-mocks-import | ||
jest.mock('fs-extra', () => require('../../../code/__mocks__/fs-extra')); | ||
const fsExtra = require('fs-extra'); | ||
|
||
jest.mock('../version', () => jest.requireActual('jest-mock-extended').mockDeep()); | ||
const bumpVersion = jest.mocked(bumpVersion_); | ||
|
||
jest.spyOn(console, 'log').mockImplementation(() => {}); | ||
jest.spyOn(console, 'warn').mockImplementation(() => {}); | ||
jest.spyOn(console, 'error').mockImplementation(() => {}); | ||
|
||
const CODE_PACKAGE_JSON_PATH = path.join(__dirname, '..', '..', '..', 'code', 'package.json'); | ||
|
||
describe('Ensure next ahead', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
gitClient.git.status.mockResolvedValue({ current: 'next' } as any); | ||
fsExtra.__setMockFiles({ | ||
[CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '2.0.0' }), | ||
}); | ||
}); | ||
|
||
it('should throw when main-version is missing', async () => { | ||
await expect(ensureNextAhead({})).rejects.toThrowErrorMatchingInlineSnapshot(` | ||
"[ | ||
{ | ||
"code": "invalid_type", | ||
"expected": "string", | ||
"received": "undefined", | ||
"path": [ | ||
"mainVersion" | ||
], | ||
"message": "Required" | ||
} | ||
]" | ||
`); | ||
}); | ||
|
||
it('should throw when main-version is not a semver string', async () => { | ||
await expect(ensureNextAhead({ mainVersion: '200' })).rejects | ||
.toThrowErrorMatchingInlineSnapshot(` | ||
"[ | ||
{ | ||
"code": "custom", | ||
"message": "main-version must be a valid semver version string like '7.4.2'.", | ||
"path": [] | ||
} | ||
]" | ||
`); | ||
}); | ||
|
||
it('should not bump version when next is already ahead of main', async () => { | ||
await expect(ensureNextAhead({ mainVersion: '1.0.0' })).resolves.toBeUndefined(); | ||
expect(bumpVersion.run).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should bump version to 3.1.0-alpha.0 when main is 3.0.0 and next is 2.0.0', async () => { | ||
await expect(ensureNextAhead({ mainVersion: '3.0.0' })).resolves.toBeUndefined(); | ||
expect(bumpVersion.run).toHaveBeenCalledWith({ exact: '3.1.0-alpha.0' }); | ||
}); | ||
|
||
it('should bump version to 2.1.0-alpha.0 when main and next are both 2.0.0', async () => { | ||
await expect(ensureNextAhead({ mainVersion: '2.0.0' })).resolves.toBeUndefined(); | ||
expect(bumpVersion.run).toHaveBeenCalledWith({ exact: '2.1.0-alpha.0' }); | ||
}); | ||
|
||
it('should bump version to 2.1.0-alpha.0 when main is 2.0.0 and and next is 2.0.0-rc.10', async () => { | ||
fsExtra.__setMockFiles({ | ||
[CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '2.0.0-rc.10' }), | ||
}); | ||
|
||
await expect(ensureNextAhead({ mainVersion: '2.0.0' })).resolves.toBeUndefined(); | ||
expect(bumpVersion.run).toHaveBeenCalledWith({ exact: '2.1.0-alpha.0' }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* This script ensures that next is always one minor ahead of main. | ||
* This is needed when releasing a stable from next. | ||
* Next will be at eg. 7.4.0-alpha.4, and main will be at 7.3.0. | ||
* Then we release 7.4.0 by merging next to latest-release to main. | ||
* We then ensure here that next is bumped to 7.5.0-alpha.0 - without releasing it. | ||
* If this is a patch release bumping main to 7.3.1, next will not be touched because it's already ahead. | ||
*/ | ||
|
||
/* eslint-disable no-console */ | ||
import chalk from 'chalk'; | ||
import path from 'path'; | ||
import program from 'commander'; | ||
import semver from 'semver'; | ||
import { z } from 'zod'; | ||
import { readJson } from 'fs-extra'; | ||
import dedent from 'ts-dedent'; | ||
import { run as bumpVersion } from './version'; | ||
import { git } from './utils/git-client'; | ||
|
||
program | ||
.name('ensure-next-ahead') | ||
.description('ensure the "next" branch is always a minor version ahead of "main"') | ||
.requiredOption('-M, --main-version <mainVersion>', 'The version currently on the "main" branch'); | ||
|
||
const optionsSchema = z | ||
.object({ | ||
mainVersion: z.string(), | ||
}) | ||
.refine((schema) => semver.valid(schema.mainVersion), { | ||
message: "main-version must be a valid semver version string like '7.4.2'.", | ||
}); | ||
|
||
type Options = { | ||
mainVersion: string; | ||
}; | ||
|
||
const CODE_DIR_PATH = path.join(__dirname, '..', '..', 'code'); | ||
const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); | ||
|
||
const validateOptions = (options: { [key: string]: any }): options is Options => { | ||
optionsSchema.parse(options); | ||
return true; | ||
}; | ||
|
||
const getCurrentVersion = async () => { | ||
const { version } = await readJson(CODE_PACKAGE_JSON_PATH); | ||
console.log(`📐 Current version of Storybook is ${chalk.green(version)}`); | ||
return version; | ||
}; | ||
|
||
export const run = async (options: unknown) => { | ||
if (!validateOptions(options)) { | ||
return; | ||
} | ||
const { mainVersion } = options; | ||
|
||
const { current: currentGitBranch } = await git.status(); | ||
|
||
if (currentGitBranch !== 'next') { | ||
console.warn( | ||
`🚧 The current branch is not "next" but "${currentGitBranch}", this only really makes sense to run on the "next" branch.` | ||
); | ||
} | ||
|
||
// Get the current version from code/package.json | ||
const currentNextVersion = await getCurrentVersion(); | ||
if (semver.gt(currentNextVersion, mainVersion)) { | ||
console.log( | ||
`✅ The version on next (${chalk.green( | ||
currentNextVersion | ||
)}) is already ahead of the version on main (${chalk.green(mainVersion)}), no action needed.` | ||
); | ||
return; | ||
} | ||
|
||
const nextNextVersion = `${semver.inc(mainVersion, 'minor')}-alpha.0`; | ||
|
||
console.log( | ||
`🤜 The version on next (${chalk.green( | ||
currentNextVersion | ||
)}) is behind the version on main (${chalk.green(mainVersion)}), bumping to ${chalk.blue( | ||
nextNextVersion | ||
)}...` | ||
); | ||
|
||
await bumpVersion({ exact: nextNextVersion }); | ||
|
||
console.log( | ||
`✅ bumped all versions to ${chalk.green( | ||
nextNextVersion | ||
)}, remember to commit and push to next.` | ||
); | ||
}; | ||
|
||
if (require.main === module) { | ||
const parsed = program.parse(); | ||
run(parsed.opts()).catch((err) => { | ||
console.error(err); | ||
process.exit(1); | ||
}); | ||
} |