astro-ghostcms/.pnpm-store/v3/files/30/674a6267077c6e528e762835223...

371 lines
11 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
import {
ReleasePlan,
Config,
NewChangeset,
PreState,
PackageGroup,
} from "@changesets/types";
import determineDependents from "./determine-dependents";
import flattenReleases from "./flatten-releases";
import matchFixedConstraint from "./match-fixed-constraint";
import applyLinks from "./apply-links";
import { incrementVersion } from "./increment";
import semverParse from "semver/functions/parse";
import { InternalError } from "@changesets/errors";
import { Packages, Package } from "@manypkg/get-packages";
import { getDependentsGraph } from "@changesets/get-dependents-graph";
import { PreInfo, InternalRelease } from "./types";
type SnapshotReleaseParameters = {
tag?: string | undefined;
commit?: string | undefined;
};
function getPreVersion(version: string) {
let parsed = semverParse(version)!;
let preVersion =
parsed.prerelease[1] === undefined ? -1 : parsed.prerelease[1];
if (typeof preVersion !== "number") {
throw new InternalError("preVersion is not a number");
}
preVersion++;
return preVersion;
}
function getSnapshotSuffix(
template: Config["snapshot"]["prereleaseTemplate"],
snapshotParameters: SnapshotReleaseParameters
): string {
let snapshotRefDate = new Date();
const placeholderValues = {
commit: snapshotParameters.commit,
tag: snapshotParameters.tag,
timestamp: snapshotRefDate.getTime().toString(),
datetime: snapshotRefDate
.toISOString()
.replace(/\.\d{3}Z$/, "")
.replace(/[^\d]/g, ""),
};
// We need a special handling because we need to handle a case where `--snapshot` is used without any template,
// and the resulting version needs to be composed without a tag.
if (!template) {
return [placeholderValues.tag, placeholderValues.datetime]
.filter(Boolean)
.join("-");
}
const placeholders = Object.keys(placeholderValues) as Array<
keyof typeof placeholderValues
>;
if (!template.includes(`{tag}`) && placeholderValues.tag !== undefined) {
throw new Error(
`Failed to compose snapshot version: "{tag}" placeholder is missing, but the snapshot parameter is defined (value: '${placeholderValues.tag}')`
);
}
return placeholders.reduce((prev, key) => {
return prev.replace(new RegExp(`\\{${key}\\}`, "g"), () => {
const value = placeholderValues[key];
if (value === undefined) {
throw new Error(
`Failed to compose snapshot version: "{${key}}" placeholder is used without having a value defined!`
);
}
return value;
});
}, template);
}
function getSnapshotVersion(
release: InternalRelease,
preInfo: PreInfo | undefined,
useCalculatedVersion: boolean,
snapshotSuffix: string
): string {
if (release.type === "none") {
return release.oldVersion;
}
/**
* Using version as 0.0.0 so that it does not hinder with other version release
* For example;
* if user has a regular pre-release at 1.0.0-beta.0 and then you had a snapshot pre-release at 1.0.0-canary-git-hash
* and a consumer is using the range ^1.0.0-beta, most people would expect that range to resolve to 1.0.0-beta.0
* but it'll actually resolve to 1.0.0-canary-hash. Using 0.0.0 solves this problem because it won't conflict with other versions.
*
* You can set `snapshot.useCalculatedVersion` flag to true to use calculated versions if you don't care about the above problem.
*/
const baseVersion = useCalculatedVersion
? incrementVersion(release, preInfo)
: `0.0.0`;
return `${baseVersion}-${snapshotSuffix}`;
}
function getNewVersion(
release: InternalRelease,
preInfo: PreInfo | undefined
): string {
if (release.type === "none") {
return release.oldVersion;
}
return incrementVersion(release, preInfo);
}
type OptionalProp<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
function assembleReleasePlan(
changesets: NewChangeset[],
packages: Packages,
config: OptionalProp<Config, "snapshot">,
// intentionally not using an optional parameter here so the result of `readPreState` has to be passed in here
preState: PreState | undefined,
// snapshot: undefined -> not using snaphot
// snapshot: { tag: undefined } -> --snapshot (empty tag)
// snapshot: { tag: "canary" } -> --snapshot canary
snapshot?: SnapshotReleaseParameters | string | boolean
): ReleasePlan {
// TODO: remove `refined*` in the next major version of this package
// just use `config` and `snapshot` parameters directly, typed as: `config: Config, snapshot?: SnapshotReleaseParameters`
const refinedConfig: Config = config.snapshot
? (config as Config)
: {
...config,
snapshot: {
prereleaseTemplate: null,
useCalculatedVersion: (
config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH as any
).useCalculatedVersionForSnapshots,
},
};
const refinedSnapshot: SnapshotReleaseParameters | undefined =
typeof snapshot === "string"
? { tag: snapshot }
: typeof snapshot === "boolean"
? { tag: undefined }
: snapshot;
let packagesByName = new Map(
packages.packages.map((x) => [x.packageJson.name, x])
);
const relevantChangesets = getRelevantChangesets(
changesets,
refinedConfig.ignore,
preState
);
const preInfo = getPreInfo(
changesets,
packagesByName,
refinedConfig,
preState
);
// releases is, at this point a list of all packages we are going to releases,
// flattened down to one release per package, having a reference back to their
// changesets, and with a calculated new versions
let releases = flattenReleases(
relevantChangesets,
packagesByName,
refinedConfig.ignore
);
let dependencyGraph = getDependentsGraph(packages, {
bumpVersionsWithWorkspaceProtocolOnly:
refinedConfig.bumpVersionsWithWorkspaceProtocolOnly,
});
let releasesValidated = false;
while (releasesValidated === false) {
// The map passed in to determineDependents will be mutated
let dependentAdded = determineDependents({
releases,
packagesByName,
dependencyGraph,
preInfo,
config: refinedConfig,
});
// `releases` might get mutated here
let fixedConstraintUpdated = matchFixedConstraint(
releases,
packagesByName,
refinedConfig
);
let linksUpdated = applyLinks(
releases,
packagesByName,
refinedConfig.linked
);
releasesValidated =
!linksUpdated && !dependentAdded && !fixedConstraintUpdated;
}
if (preInfo?.state.mode === "exit") {
for (let pkg of packages.packages) {
// If a package had a prerelease, but didn't trigger a version bump in the regular release,
// we want to give it a patch release.
// Detailed explanation at https://github.com/changesets/changesets/pull/382#discussion_r434434182
if (preInfo.preVersions.get(pkg.packageJson.name) !== 0) {
const existingRelease = releases.get(pkg.packageJson.name);
if (!existingRelease) {
releases.set(pkg.packageJson.name, {
name: pkg.packageJson.name,
type: "patch",
oldVersion: pkg.packageJson.version,
changesets: [],
});
} else if (
existingRelease.type === "none" &&
!refinedConfig.ignore.includes(pkg.packageJson.name)
) {
existingRelease.type = "patch";
}
}
}
}
// Caching the snapshot version here and use this if it is snapshot release
const snapshotSuffix =
refinedSnapshot &&
getSnapshotSuffix(
refinedConfig.snapshot.prereleaseTemplate,
refinedSnapshot
);
return {
changesets: relevantChangesets,
releases: [...releases.values()].map((incompleteRelease) => {
return {
...incompleteRelease,
newVersion: snapshotSuffix
? getSnapshotVersion(
incompleteRelease,
preInfo,
refinedConfig.snapshot.useCalculatedVersion,
snapshotSuffix
)
: getNewVersion(incompleteRelease, preInfo),
};
}),
preState: preInfo?.state,
};
}
function getRelevantChangesets(
changesets: NewChangeset[],
ignored: Readonly<string[]>,
preState: PreState | undefined
): NewChangeset[] {
for (const changeset of changesets) {
// Using the following 2 arrays to decide whether a changeset
// contains both ignored and not ignored packages
const ignoredPackages = [];
const notIgnoredPackages = [];
for (const release of changeset.releases) {
if (
ignored.find(
(ignoredPackageName) => ignoredPackageName === release.name
)
) {
ignoredPackages.push(release.name);
} else {
notIgnoredPackages.push(release.name);
}
}
if (ignoredPackages.length > 0 && notIgnoredPackages.length > 0) {
throw new Error(
`Found mixed changeset ${changeset.id}\n` +
`Found ignored packages: ${ignoredPackages.join(" ")}\n` +
`Found not ignored packages: ${notIgnoredPackages.join(" ")}\n` +
"Mixed changesets that contain both ignored and not ignored packages are not allowed"
);
}
}
if (preState && preState.mode !== "exit") {
let usedChangesetIds = new Set(preState.changesets);
return changesets.filter(
(changeset) => !usedChangesetIds.has(changeset.id)
);
}
return changesets;
}
function getHighestPreVersion(
packageGroup: PackageGroup,
packagesByName: Map<string, Package>
): number {
let highestPreVersion = 0;
for (let pkg of packageGroup) {
highestPreVersion = Math.max(
getPreVersion(packagesByName.get(pkg)!.packageJson.version),
highestPreVersion
);
}
return highestPreVersion;
}
function getPreInfo(
changesets: NewChangeset[],
packagesByName: Map<string, Package>,
config: Config,
preState: PreState | undefined
): PreInfo | undefined {
if (preState === undefined) {
return;
}
let updatedPreState = {
...preState,
changesets: changesets.map((changeset) => changeset.id),
initialVersions: {
...preState.initialVersions,
},
};
for (const [, pkg] of packagesByName) {
if (updatedPreState.initialVersions[pkg.packageJson.name] === undefined) {
updatedPreState.initialVersions[pkg.packageJson.name] =
pkg.packageJson.version;
}
}
// Populate preVersion
// preVersion is the map between package name and its next pre version number.
let preVersions = new Map<string, number>();
for (const [, pkg] of packagesByName) {
preVersions.set(
pkg.packageJson.name,
getPreVersion(pkg.packageJson.version)
);
}
for (let fixedGroup of config.fixed) {
let highestPreVersion = getHighestPreVersion(fixedGroup, packagesByName);
for (let fixedPackage of fixedGroup) {
preVersions.set(fixedPackage, highestPreVersion);
}
}
for (let linkedGroup of config.linked) {
let highestPreVersion = getHighestPreVersion(linkedGroup, packagesByName);
for (let linkedPackage of linkedGroup) {
preVersions.set(linkedPackage, highestPreVersion);
}
}
return {
state: updatedPreState,
preVersions,
};
}
export default assembleReleasePlan;