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
12 changes: 5 additions & 7 deletions tools/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ if [[ $GITHUB_REF =~ $TAG_TEST ]]; then
NPM_TAG=next
fi;
else
FULL_VERSION=$(node -e "console.log(require('./package.json').version)")
# Name the canary after the release itself, never after a prerelease of it. A canary built on
# `21.0.0-rc.1` sorts above it, so the caret range `ng add` writes into a user's package.json
# resolves to the canary rather than to the release candidate they asked for.
BASE_VERSION=${FULL_VERSION%%-*}
if [[ $BASE_VERSION != "$FULL_VERSION" ]]; then
echo "package.json version is $FULL_VERSION. Naming this canary after $BASE_VERSION instead, so it does not outrank $FULL_VERSION on npm. Prereleases are published from their own git tag, so this field is meant to hold a plain release number." >&2
PACKAGE_VERSION=$(node -e "console.log(require('./package.json').version)")
if ! PUBLISHED_VERSIONS=$(npm view @angular/fire versions --json); then
echo "Could not read the published @angular/fire versions from npm, so the canary has no version to build on." >&2
exit 1
fi
BASE_VERSION=$(node ./tools/canary-version.js "$PACKAGE_VERSION" "$PUBLISHED_VERSIONS") || exit 1
# `sha-` stops npm dropping an all-digit sha's leading zero.
CANARY_ID=$(TZ=UTC git show -s --date=format-local:%Y%m%d%H%M%S --format=%cd.sha-%h $GITHUB_SHA)
OVERRIDE_VERSION=$BASE_VERSION-canary.$CANARY_ID
Expand Down
74 changes: 74 additions & 0 deletions tools/canary-version.jasmine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { maxSatisfying as semverMaxSatisfying } from 'semver';
import { canaryBaseVersion } from './canary-version.js';
import 'jasmine';

/* What's on npm today: a placeholder, v20 releases, rc.1, canaries named after the rc series
* (deprecated), and canaries named after 21.0.0. */
const publishedToday = [
'0.0.0',
'20.0.0',
'20.1.0',
'21.0.0-rc.0',
'21.0.0-rc.0-canary.fb6796b',
'21.0.0-rc.1',
'21.0.0-rc.1-canary.95b3de1',
'21.0.0-canary.a2662fe',
'21.0.0-canary.20260930011755.sha-59d44e7',
];

const canaryNamedAfter = (base: string) => `${base}-canary.20261001000000.sha-abcdef0`;

describe('canaryBaseVersion', () => {

it('names the canary one patch above the release candidate', () => {
expect(canaryBaseVersion('21.0.0', publishedToday)).toBe('21.0.1');
});

it('names the canary one patch above the release once it ships', () => {
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0'])).toBe('21.0.1');
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0', '21.0.1'])).toBe('21.0.2');
});

it('names the canary above a minor or patch release candidate', () => {
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0', '21.1.0-rc.0'])).toBe('21.1.1');
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0', '21.0.1-rc.0'])).toBe('21.0.2');
});

it('does not count earlier canaries', () => {
const published = [...publishedToday, canaryNamedAfter('21.0.1'), canaryNamedAfter('21.0.1')];
expect(canaryBaseVersion('21.0.0', published)).toBe('21.0.1');
});

it('ignores releases from other majors', () => {
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0', '20.0.4'])).toBe('21.0.1');
expect(canaryBaseVersion('21.0.0', [...publishedToday, '21.0.0', '22.0.0-rc.0'])).toBe('21.0.1');
});

it('uses the package.json version when nothing in its major is published yet', () => {
expect(canaryBaseVersion('22.0.0-rc.0', publishedToday)).toBe('22.0.0');
expect(canaryBaseVersion('22.0.0-rc.0', [...publishedToday, '22.0.0-rc.0'])).toBe('22.0.1');
});

it('throws when the package.json version has no version number in it', () => {
expect(() => canaryBaseVersion('abc', publishedToday)).toThrowError(TypeError);
});

it('gives a canary that its own caret range selects, and that release ranges never select', () => {
const states = [
publishedToday,
[...publishedToday, '21.0.0'],
[...publishedToday, '21.0.0', '21.0.1'],
[...publishedToday, '21.0.0', '21.1.0-rc.0'],
[...publishedToday, '21.0.0', '21.0.1-rc.0'],
];
for (const published of states) {
const canary = canaryNamedAfter(canaryBaseVersion('21.0.0', published));
const versions = [...published, canary];
expect(semverMaxSatisfying(versions, `^${canary}`)).toBe(canary);
for (const range of ['^21.0.0', '~21.0.0', '^21.0.0-rc.1', '^20.0.0']) {
expect(semverMaxSatisfying(versions, range)).not.toBe(canary);
}
}
});

});
29 changes: 29 additions & 0 deletions tools/canary-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const semver = require('semver');

/**
* Picks the version a canary build is named after, as in `<base>-canary.<time>.sha-<sha>`.
*
* `ng add @angular/fire@canary` saves `^<canary>`, and npm installs the highest version in that
* range, so the canary must rank above every release and release candidate already published
* in its major.
*
* @param {string} packageVersion The `version` field of the repository's package.json.
* @param {string[]} publishedVersions Every @angular/fire version on npm.
* @returns {string} One patch above the highest published version of the same major, or the
* package.json version without its prerelease part if that is higher.
*/
function canaryBaseVersion(packageVersion, publishedVersions) {
const packageBase = semver.coerce(packageVersion);
// Published canaries don't count, or every canary would be named one patch above the one before it.
const releases = publishedVersions.filter(
version => !version.includes('canary') && semver.major(version) === packageBase.major,
);
const nextPatch = releases.length ? semver.coerce(semver.rsort(releases)[0]).inc('patch') : packageBase;
return semver.gt(packageBase, nextPatch) ? packageBase.version : nextPatch.version;
}

module.exports = { canaryBaseVersion };

if (require.main === module) {
console.log(canaryBaseVersion(process.argv[2], JSON.parse(process.argv[3])));
}
1 change: 1 addition & 0 deletions tsconfig.jasmine.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"include": [
"tools/jasmine.ts",
"tools/**/*.jasmine.ts",
"src/**/*.jasmine.ts",
// Not sure what is wrong here, but since upgrading karma it's fallen apart
// "src/**/*.spec.ts",
Expand Down
Loading