1*fe5cfb17STomasz Sapeta/**
2*fe5cfb17STomasz Sapeta * Copyright (c) Meta Platforms, Inc. and affiliates.
3*fe5cfb17STomasz Sapeta *
4*fe5cfb17STomasz Sapeta * This source code is licensed under the MIT license found in the
5*fe5cfb17STomasz Sapeta * LICENSE file in the root directory of this source tree.
6*fe5cfb17STomasz Sapeta *
7*fe5cfb17STomasz Sapeta * @format
8*fe5cfb17STomasz Sapeta */
9*fe5cfb17STomasz Sapeta
10*fe5cfb17STomasz Sapetaconst VERSION_REGEX = /^v?((\d+)\.(\d+)\.(\d+)(?:-(.+))?)$/;
11*fe5cfb17STomasz Sapeta
12*fe5cfb17STomasz Sapeta/**
13*fe5cfb17STomasz Sapeta * Parses a version string and performs some checks to verify its validity.
14*fe5cfb17STomasz Sapeta * A valid version is in the format vX.Y.Z[-KKK] where X, Y, Z are numbers and KKK can be something else.
15*fe5cfb17STomasz Sapeta * The `builtType` is used to enforce that the major version can assume only specific
16*fe5cfb17STomasz Sapeta * values.
17*fe5cfb17STomasz Sapeta *
18*fe5cfb17STomasz Sapeta * Some examples of valid versions are:
19*fe5cfb17STomasz Sapeta * - stable: 0.68.1
20*fe5cfb17STomasz Sapeta * - stable prerelease: 0.70.0-rc.0
21*fe5cfb17STomasz Sapeta * - e2e-test: X.Y.Z-20221116-2018
22*fe5cfb17STomasz Sapeta * - nightly: 0.0.0-20221116-2018-0bc4547fc
23*fe5cfb17STomasz Sapeta * - dryrun: 1000.0.0
24*fe5cfb17STomasz Sapeta *
25*fe5cfb17STomasz Sapeta * Parameters:
26*fe5cfb17STomasz Sapeta * - @versionStr the string representing a version
27*fe5cfb17STomasz Sapeta * - @buildType the build type. It can be of values: `dry-run`, `release`, `nightly`
28*fe5cfb17STomasz Sapeta *
29*fe5cfb17STomasz Sapeta * Returns: an object with the shape:
30*fe5cfb17STomasz Sapeta * ```
31*fe5cfb17STomasz Sapeta * {
32*fe5cfb17STomasz Sapeta *   version: string,
33*fe5cfb17STomasz Sapeta *   major: number,
34*fe5cfb17STomasz Sapeta *   minor: number,
35*fe5cfb17STomasz Sapeta *   patch: number,
36*fe5cfb17STomasz Sapeta *   prerelease: string
37*fe5cfb17STomasz Sapeta * }
38*fe5cfb17STomasz Sapeta * ```
39*fe5cfb17STomasz Sapeta *
40*fe5cfb17STomasz Sapeta */
41*fe5cfb17STomasz Sapetafunction parseVersion(versionStr, buildType) {
42*fe5cfb17STomasz Sapeta  try {
43*fe5cfb17STomasz Sapeta    validateBuildType(buildType);
44*fe5cfb17STomasz Sapeta  } catch (e) {
45*fe5cfb17STomasz Sapeta    throw e;
46*fe5cfb17STomasz Sapeta  }
47*fe5cfb17STomasz Sapeta
48*fe5cfb17STomasz Sapeta  const match = extractMatchIfValid(versionStr);
49*fe5cfb17STomasz Sapeta  const [, version, major, minor, patch, prerelease] = match;
50*fe5cfb17STomasz Sapeta
51*fe5cfb17STomasz Sapeta  const versionObject = {
52*fe5cfb17STomasz Sapeta    version,
53*fe5cfb17STomasz Sapeta    major,
54*fe5cfb17STomasz Sapeta    minor,
55*fe5cfb17STomasz Sapeta    patch,
56*fe5cfb17STomasz Sapeta    prerelease,
57*fe5cfb17STomasz Sapeta  };
58*fe5cfb17STomasz Sapeta
59*fe5cfb17STomasz Sapeta  try {
60*fe5cfb17STomasz Sapeta    validateVersion(versionObject, buildType);
61*fe5cfb17STomasz Sapeta  } catch (e) {
62*fe5cfb17STomasz Sapeta    throw e;
63*fe5cfb17STomasz Sapeta  }
64*fe5cfb17STomasz Sapeta
65*fe5cfb17STomasz Sapeta  return versionObject;
66*fe5cfb17STomasz Sapeta}
67*fe5cfb17STomasz Sapeta
68*fe5cfb17STomasz Sapetafunction validateBuildType(buildType) {
69*fe5cfb17STomasz Sapeta  const validBuildTypes = new Set(['release', 'dry-run', 'nightly']);
70*fe5cfb17STomasz Sapeta  if (!validBuildTypes.has(buildType)) {
71*fe5cfb17STomasz Sapeta    throw new Error(`Unsupported build type: ${buildType}`);
72*fe5cfb17STomasz Sapeta  }
73*fe5cfb17STomasz Sapeta}
74*fe5cfb17STomasz Sapeta
75*fe5cfb17STomasz Sapetafunction extractMatchIfValid(versionStr) {
76*fe5cfb17STomasz Sapeta  const match = versionStr.match(VERSION_REGEX);
77*fe5cfb17STomasz Sapeta  if (!match) {
78*fe5cfb17STomasz Sapeta    throw new Error(
79*fe5cfb17STomasz Sapeta      `You must pass a correctly formatted version; couldn't parse ${versionStr}`,
80*fe5cfb17STomasz Sapeta    );
81*fe5cfb17STomasz Sapeta  }
82*fe5cfb17STomasz Sapeta  return match;
83*fe5cfb17STomasz Sapeta}
84*fe5cfb17STomasz Sapeta
85*fe5cfb17STomasz Sapetafunction validateVersion(versionObject, buildType) {
86*fe5cfb17STomasz Sapeta  const map = {
87*fe5cfb17STomasz Sapeta    release: validateRelease,
88*fe5cfb17STomasz Sapeta    'dry-run': validateDryRun,
89*fe5cfb17STomasz Sapeta    nightly: validateNightly,
90*fe5cfb17STomasz Sapeta  };
91*fe5cfb17STomasz Sapeta
92*fe5cfb17STomasz Sapeta  const validationFunction = map[buildType];
93*fe5cfb17STomasz Sapeta  validationFunction(versionObject);
94*fe5cfb17STomasz Sapeta}
95*fe5cfb17STomasz Sapeta
96*fe5cfb17STomasz Sapeta/**
97*fe5cfb17STomasz Sapeta * Releases are in the form of 0.Y.Z[-RC.0]
98*fe5cfb17STomasz Sapeta */
99*fe5cfb17STomasz Sapetafunction validateRelease(version) {
100*fe5cfb17STomasz Sapeta  const validRelease = isStableRelease(version) || isStablePrerelease(version);
101*fe5cfb17STomasz Sapeta  if (!validRelease) {
102*fe5cfb17STomasz Sapeta    throw new Error(`Version ${version.version} is not valid for Release`);
103*fe5cfb17STomasz Sapeta  }
104*fe5cfb17STomasz Sapeta}
105*fe5cfb17STomasz Sapeta
106*fe5cfb17STomasz Sapetafunction validateDryRun(version) {
107*fe5cfb17STomasz Sapeta  const isNightly = isNightlyBuild(version) && version.prerelease != null;
108*fe5cfb17STomasz Sapeta
109*fe5cfb17STomasz Sapeta  if (
110*fe5cfb17STomasz Sapeta    !isMain(version) &&
111*fe5cfb17STomasz Sapeta    !isNightly &&
112*fe5cfb17STomasz Sapeta    !isStableRelease(version) &&
113*fe5cfb17STomasz Sapeta    !isStablePrerelease(version)
114*fe5cfb17STomasz Sapeta  ) {
115*fe5cfb17STomasz Sapeta    throw new Error(`Version ${version.version} is not valid for dry-runs`);
116*fe5cfb17STomasz Sapeta  }
117*fe5cfb17STomasz Sapeta}
118*fe5cfb17STomasz Sapeta
119*fe5cfb17STomasz Sapetafunction validateNightly(version) {
120*fe5cfb17STomasz Sapeta  // a valid nightly is a prerelease
121*fe5cfb17STomasz Sapeta  const isPrerelease = version.prerelease != null;
122*fe5cfb17STomasz Sapeta  const isValidNightly = isNightlyBuild(version) && isPrerelease;
123*fe5cfb17STomasz Sapeta  if (!isValidNightly) {
124*fe5cfb17STomasz Sapeta    throw new Error(`Version ${version.version} is not valid for nightlies`);
125*fe5cfb17STomasz Sapeta  }
126*fe5cfb17STomasz Sapeta}
127*fe5cfb17STomasz Sapeta
128*fe5cfb17STomasz Sapetafunction isStableRelease(version) {
129*fe5cfb17STomasz Sapeta  return (
130*fe5cfb17STomasz Sapeta    version.major === '0' && version.minor !== '0' && version.prerelease == null
131*fe5cfb17STomasz Sapeta  );
132*fe5cfb17STomasz Sapeta}
133*fe5cfb17STomasz Sapeta
134*fe5cfb17STomasz Sapetafunction isStablePrerelease(version) {
135*fe5cfb17STomasz Sapeta  return (
136*fe5cfb17STomasz Sapeta    version.major === '0' &&
137*fe5cfb17STomasz Sapeta    version.minor !== '0' &&
138*fe5cfb17STomasz Sapeta    version.patch.match(/^\d+$/) &&
139*fe5cfb17STomasz Sapeta    version.prerelease != null &&
140*fe5cfb17STomasz Sapeta    (version.prerelease.startsWith('rc.') ||
141*fe5cfb17STomasz Sapeta      version.prerelease.startsWith('rc-') ||
142*fe5cfb17STomasz Sapeta      version.prerelease.match(/^(\d{8})-(\d{4})$/))
143*fe5cfb17STomasz Sapeta  );
144*fe5cfb17STomasz Sapeta}
145*fe5cfb17STomasz Sapeta
146*fe5cfb17STomasz Sapetafunction isNightlyBuild(version) {
147*fe5cfb17STomasz Sapeta  return (
148*fe5cfb17STomasz Sapeta    version.major === '0' && version.minor === '0' && version.patch === '0'
149*fe5cfb17STomasz Sapeta  );
150*fe5cfb17STomasz Sapeta}
151*fe5cfb17STomasz Sapeta
152*fe5cfb17STomasz Sapetafunction isMain(version) {
153*fe5cfb17STomasz Sapeta  return (
154*fe5cfb17STomasz Sapeta    version.major === '1000' && version.minor === '0' && version.patch === '0'
155*fe5cfb17STomasz Sapeta  );
156*fe5cfb17STomasz Sapeta}
157*fe5cfb17STomasz Sapeta
158*fe5cfb17STomasz Sapetafunction isReleaseBranch(branch) {
159*fe5cfb17STomasz Sapeta  return branch.endsWith('-stable');
160*fe5cfb17STomasz Sapeta}
161*fe5cfb17STomasz Sapeta
162*fe5cfb17STomasz Sapetamodule.exports = {
163*fe5cfb17STomasz Sapeta  validateBuildType,
164*fe5cfb17STomasz Sapeta  parseVersion,
165*fe5cfb17STomasz Sapeta  isReleaseBranch,
166*fe5cfb17STomasz Sapeta  isMain,
167*fe5cfb17STomasz Sapeta  isStableRelease,
168*fe5cfb17STomasz Sapeta  isStablePrerelease,
169*fe5cfb17STomasz Sapeta};
170