• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/// <reference types="node"/>
2import { normalize, relative } from "path";
3import assert = require("assert");
4import { readFileSync, writeFileSync } from "fs";
5
6/**
7 * A minimal description for a parsed package.json object.
8 */
9interface PackageJson {
10    name: string;
11    version: string;
12    keywords: string[];
13}
14
15function main(): void {
16    const args = process.argv.slice(2);
17    if (args.length < 3) {
18        const thisProgramName = relative(process.cwd(), __filename);
19        console.log("Usage:");
20        console.log(`\tnode ${thisProgramName} <dev|insiders> <package.json location> <file containing version>`);
21        return;
22    }
23
24    const tag = args[0];
25    if (tag !== "dev" && tag !== "insiders" && tag !== "experimental") {
26        throw new Error(`Unexpected tag name '${tag}'.`);
27    }
28
29    // Acquire the version from the package.json file and modify it appropriately.
30    const packageJsonFilePath = normalize(args[1]);
31    const packageJsonValue: PackageJson = JSON.parse(readFileSync(packageJsonFilePath).toString());
32
33    const { majorMinor, patch } = parsePackageJsonVersion(packageJsonValue.version);
34    const prereleasePatch = getPrereleasePatch(tag, patch);
35
36    // Acquire and modify the source file that exposes the version string.
37    const tsFilePath = normalize(args[2]);
38    const tsFileContents = readFileSync(tsFilePath).toString();
39    const modifiedTsFileContents = updateTsFile(tsFilePath, tsFileContents, majorMinor, patch, prereleasePatch);
40
41    // Ensure we are actually changing something - the user probably wants to know that the update failed.
42    if (tsFileContents === modifiedTsFileContents) {
43        let err = `\n  '${tsFilePath}' was not updated while configuring for a prerelease publish for '${tag}'.\n    `;
44        err += `Ensure that you have not already run this script; otherwise, erase your changes using 'git checkout -- "${tsFilePath}"'.`;
45        throw new Error(err + "\n");
46    }
47
48    // Finally write the changes to disk.
49    // Modify the package.json structure
50    packageJsonValue.version = `${majorMinor}.${prereleasePatch}`;
51    writeFileSync(packageJsonFilePath, JSON.stringify(packageJsonValue, /*replacer:*/ undefined, /*space:*/ 4));
52    writeFileSync(tsFilePath, modifiedTsFileContents);
53}
54
55/* eslint-disable no-null/no-null */
56function updateTsFile(tsFilePath: string, tsFileContents: string, majorMinor: string, patch: string, nightlyPatch: string): string {
57    const majorMinorRgx = /export const versionMajorMinor = "(\d+\.\d+)"/;
58    const majorMinorMatch = majorMinorRgx.exec(tsFileContents);
59    assert(majorMinorMatch !== null, `The file '${tsFilePath}' seems to no longer have a string matching '${majorMinorRgx}'.`);
60    const parsedMajorMinor = majorMinorMatch![1];
61    assert(parsedMajorMinor === majorMinor, `versionMajorMinor does not match. ${tsFilePath}: '${parsedMajorMinor}'; package.json: '${majorMinor}'`);
62
63    const versionRgx = /export const version(?:: string)? = `\$\{versionMajorMinor\}\.(\d)(-\w+)?`;/;
64    const patchMatch = versionRgx.exec(tsFileContents);
65    assert(patchMatch !== null, `The file '${tsFilePath}' seems to no longer have a string matching '${versionRgx.toString()}'.`);
66    const parsedPatch = patchMatch![1];
67    if (parsedPatch !== patch) {
68        throw new Error(`patch does not match. ${tsFilePath}: '${parsedPatch}; package.json: '${patch}'`);
69    }
70
71    return tsFileContents.replace(versionRgx, `export const version: string = \`\${versionMajorMinor}.${nightlyPatch}\`;`);
72}
73
74function parsePackageJsonVersion(versionString: string): { majorMinor: string, patch: string } {
75    const versionRgx = /(\d+\.\d+)\.(\d+)($|\-)/;
76    const match = versionString.match(versionRgx);
77    assert(match !== null, "package.json 'version' should match " + versionRgx.toString());
78    return { majorMinor: match![1], patch: match![2] };
79}
80/* eslint-enable no-null/no-null */
81
82/** e.g. 0-dev.20170707 */
83function getPrereleasePatch(tag: string, plainPatch: string): string {
84    // We're going to append a representation of the current time at the end of the current version.
85    // String.prototype.toISOString() returns a 24-character string formatted as 'YYYY-MM-DDTHH:mm:ss.sssZ',
86    // but we'd prefer to just remove separators and limit ourselves to YYYYMMDD.
87    // UTC time will always be implicit here.
88    const now = new Date();
89    const timeStr = now.toISOString().replace(/:|T|\.|-/g, "").slice(0, 8);
90
91    return `${plainPatch}-${tag}.${timeStr}`;
92}
93
94main();
95