diff --git a/package.json b/package.json index d732218..9d7a949 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start-worker": "node --import ./register.js --no-warnings ./src/workers/newsroom.ts", "worker-dev": "nodemon ./src/workers/newsroom.ts", "speechgen": "node --import ./register.js --no-warnings ./src/speechgen.ts", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "release": "node --import ./register.js --no-warnings ./src/release.ts" }, "keywords": [ "news", @@ -63,6 +64,7 @@ "pug": "^3.0.3", "randomstring": "^1.3.1", "rotating-file-stream": "^3.2.5", + "semver": "^7.7.0", "sharp": "^0.33.5", "shoetest": "^1.2.2", "socket.io": "^4.8.1", @@ -95,6 +97,7 @@ "@types/passport-local": "^1.0.38", "@types/pug": "^2.0.10", "@types/randomstring": "^1.3.0", + "@types/semver": "^7.5.8", "@types/stats-lite": "^2.2.2", "@types/user-agents": "^1.0.4", "@types/uuid": "^10.0.0", @@ -104,6 +107,7 @@ "globals": "^15.14.0", "less": "^4.2.2", "nodemon": "^3.1.9", + "simple-git": "^3.27.0", "ts-node": "^10.9.2", "tsc-watch": "^6.2.1", "tslib": "^2.8.1", @@ -111,4 +115,4 @@ "typescript-eslint": "^8.21.0" }, "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b04ab09..3b1fe66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: rotating-file-stream: specifier: ^3.2.5 version: 3.2.5 + semver: + specifier: ^7.7.0 + version: 7.7.0 sharp: specifier: ^0.33.5 version: 0.33.5 @@ -216,6 +219,9 @@ importers: '@types/randomstring': specifier: ^1.3.0 version: 1.3.0 + '@types/semver': + specifier: ^7.5.8 + version: 7.5.8 '@types/stats-lite': specifier: ^2.2.2 version: 2.2.2 @@ -243,6 +249,9 @@ importers: nodemon: specifier: ^3.1.9 version: 3.1.9 + simple-git: + specifier: ^3.27.0 + version: 3.27.0 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.12.0)(typescript@5.7.3) @@ -646,6 +655,12 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -827,6 +842,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -2482,6 +2500,9 @@ packages: sift@17.1.3: resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} + simple-git@3.27.0: + resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -3177,6 +3198,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.0(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + '@mongodb-js/saslprep@1.1.9': dependencies: sparse-bitfield: 3.0.3 @@ -3372,6 +3401,8 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/semver@7.5.8': {} + '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 @@ -5264,6 +5295,14 @@ snapshots: sift@17.1.3: {} + simple-git@3.27.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.4.0(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 diff --git a/src/config/env.ts b/src/config/env.ts index 55edecb..770edaf 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -3,21 +3,29 @@ // All Rights Reserved import "dotenv/config"; -// import type PackageJson from "../../package.json"; +import type PackageJson from "../../package.json"; -import path, { dirname } from "path"; -import { fileURLToPath } from "url"; +import path, { dirname } from "node:path"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line export const ROOT_DIR = path.resolve(__dirname, "..", ".."); export const SRC_DIR = path.resolve(__dirname, ".."); +async function readJsonFile(path: string): Promise { + const file = await fs.promises.readFile(path); + return JSON.parse(file.toString("utf-8")) as T; +} + + /* eslint-disable no-process-env */ export default { NODE_ENV: process.env.NODE_ENV, timezone: process.env.DTP_TIMEZONE || "America/New_York", root: ROOT_DIR, src: SRC_DIR, + pkg: await readJsonFile(path.join(ROOT_DIR, "package.json")), site: { company: process.env.DTP_SITE_COMPANY || "DTP Technologies, LLC", companyShort: process.env.DTP_SITE_COMPANY_SHORT || "DTP", diff --git a/src/release.ts b/src/release.ts new file mode 100755 index 0000000..4cc0436 --- /dev/null +++ b/src/release.ts @@ -0,0 +1,80 @@ +// release.ts +// Copyright (C) 2025 DTP Technologies, LLC +// All Rights Reserved + +import env from "./config/env.js"; +import assert from "node:assert"; + +import path from "node:path"; +import fs from "node:fs"; + +import semver from "semver"; +import SimpleGit from "simple-git"; + +const release = process.argv[2] as semver.ReleaseType; +assert (release && semver.RELEASE_TYPES.includes(release), "Must specify the release type: major, minor, patch, build"); + +const pkgVersion = new semver.SemVer(env.pkg.version); +assert(pkgVersion, "A valid version is required in package.json"); + +const newVersion = new semver.SemVer(env.pkg.version); +newVersion.inc(release); +env.pkg.version = newVersion.format(); + +const releaseTag = `v${newVersion.format()}`; +console.log('tagging release:', pkgVersion.format(), release, newVersion.format(), releaseTag); + +(async ( ) => { + try { + + const simpleGit = SimpleGit(env.root); + + /* + * Make sure we're on the develop branch. Don't switch to it, we've already + * read package.json and other configs. Must be on develop when running a + * release. + */ + const branch = await simpleGit.branch(); + assert(branch.current === "develop", "Not on develop branch, please fix."); + + /* + * Pull origin develop to make sure we're in sync. + */ + await simpleGit.pull("origin", "develop"); + + /* + * Make sure the repo is of clean status + */ + const status = await simpleGit.status(); + assert(!status.detached, "git repo is detached, please fix."); + assert(status.not_added.length === 0, "You have untracked files, please fix."); + assert(status.modified.length === 0, "You have modified files, please fix."); + + /* + * Write package.json to disk + */ + const pkgFilename = path.join(env.root, "package.json"); + await fs.promises.writeFile(pkgFilename, JSON.stringify(env.pkg, null, 2), "utf-8"); + + /* + * Commit package.json, tag the repo for release, and push to develop on + * origin. + */ + await simpleGit.commit(`releasing ${releaseTag} to master`); + await simpleGit.addTag(releaseTag); + await simpleGit.push("origin", "develop"); + + /* + * Switch to master, integrate changes from develop, and push. + */ + await simpleGit.checkout("master"); + await simpleGit.pull("origin", "master"); + await simpleGit.pull(".", "develop"); + await simpleGit.push("origin","master"); + + simpleGit.checkout("develop"); + } catch (error) { + console.error(`Failed to process release: ${(error as Error).message}`, { error }); + } + +})(); \ No newline at end of file