UNPKG

@underpostnet/underpost

Version:

Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc

592 lines (554 loc) 26 kB
/** * Release orchestrator module for managing version builds and deployments of the Underpost CLI. * * Provides automated workflows for building new versions (bumping version numbers across * all package files, manifests, and configurations) and deploying releases (committing, * pushing, and syncing secrets to remote repositories). * * @module src/cli/release.js * @namespace UnderpostRelease */ import fs from 'fs-extra'; import path from 'path'; import dotenv from 'dotenv'; import { pbcopy, shellCd, shellExec } from '../server/process.js'; import { Dns } from '../server/dns.js'; import { loggerFactory } from '../server/logger.js'; import { timer } from '../client/components/core/CommonJs.js'; import Underpost from '../index.js'; const logger = loggerFactory(import.meta); /** * Files that must never be touched by a version bump, even if they happen to contain a * literal version string. CHANGELOG is historical; logs/ and node_modules/ are runtime * outputs; engine-private/.env.* and secrets must not be rewritten by regex. */ const VERSION_BUMP_SKIP = [ /(^|\/)CHANGELOG\.md$/i, /(^|\/)logs\//, /(^|\/)node_modules\//, /(^|\/)\.git\//, /(^|\/)engine-private\/.*\.env(\..*)?$/, ]; const isSkipped = (filePath) => VERSION_BUMP_SKIP.some((re) => re.test(filePath)); /** * Declarative regex-based version bump targets for files bumpp cannot handle natively * (image tags, doc strings, README badges, build-args, etc.). * * Each target is one of: * - { file: 'path/to/file', patterns: RegExp | RegExp[] } * - { dir: 'dir', match: RegExp, patterns: RegExp | RegExp[], recursive?: boolean } * * Every pattern MUST have exactly one capture group: the prefix to preserve. The replacement * is `$1<newVersion>`. Use a lookahead `(?=...)` to anchor a trailing context (closing quote, * backtick, etc.) without consuming it. This keeps the callback signature trivial. * * The walker iterates targets, applies patterns to each matching file, and reports the * number of substitutions per file. Files that match nothing are silently skipped. */ const buildVersionBumpTargets = () => [ // ── src/index.js — anchored to the static class field, never a bare replaceAll. ── { file: 'src/index.js', patterns: /(static version = ['"]v)\d+\.\d+\.\d+(?=['"])/g, }, // ── README + CLI-HELP fallbacks. CLI-HELP is regenerated by `deploy cli-docs`, but bump // it too so the file is consistent if the regeneration step is skipped. ── { file: 'README.md', patterns: [ /(socket\.dev\/api\/badge\/npm\/package\/underpost\/)\d+\.\d+\.\d+/g, /(socket\.dev\/npm\/package\/underpost\/overview\/)\d+\.\d+\.\d+/g, /(ci\/cd cli v)\d+\.\d+\.\d+/gi, ], }, { file: 'CLI-HELP.md', patterns: /(ci\/cd cli v)\d+\.\d+\.\d+/gi, optional: true, }, // ── Cyberia + nexodev docs. ── { dir: 'src/client/public/cyberia-docs', match: /\.md$/, patterns: [/(\*\*(?:Current )?[Vv]ersion:\*\* )\d+\.\d+\.\d+/g, /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g], recursive: true, }, { dir: 'src/client/public/nexodev/docs/references', match: /\.md$/, patterns: [ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, /(UNDERPOST_VERSION=)\d+\.\d+\.\d+/g, /(ci\/cd cli v)\d+\.\d+\.\d+/gi, // version tag bumps inside markdown narrative, e.g. `v3.2.9`, `v3.2.10`, … /(`v)\d+\.\d+\.\d+(?=`)/g, ], recursive: true, }, // ── Kubernetes manifests. Covers deployment.yaml + cronjob yamls under dd-cron. ── { dir: 'manifests/deployment', match: /deployment\.yaml$/, patterns: [/(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, /(engine\.version: )\d+\.\d+\.\d+/g], recursive: true, }, { dir: 'manifests/cronjobs', match: /\.yaml$/, patterns: /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, recursive: true, }, // ── GitHub Actions workflows. Single sweep across docker-image.*.ci.yml, engine-*.cd.yml, // and the root docker-image.ci.yml — replaces previous three separate loops. ── { dir: '.github/workflows', match: /\.(yml|yaml)$/, patterns: [ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, /(underpost-engine:v)\d+\.\d+\.\d+/g, /(type=raw,value=v)\d+\.\d+\.\d+/g, /(UNDERPOST_VERSION=)\d+\.\d+\.\d+/g, /(UNDERPOST_VERSION:\s*['"]?)\d+\.\d+\.\d+/g, ], }, // ── Docker-compose image-tag defaults. Root compose + generator + cyberia runtime compose. // Tags live in `${VAR:-vX.Y.Z}` / `VAR=vX.Y.Z` shapes bumpp cannot detect. ── { file: 'docker-compose.yml', patterns: /(_TAG:-v)\d+\.\d+\.\d+/g, }, { file: 'src/cli/docker-compose.js', patterns: /(_TAG=v)\d+\.\d+\.\d+/g, }, { file: 'src/runtime/engine-cyberia/docker-compose.yml', patterns: /(_TAG:-v)\d+\.\d+\.\d+/g, }, // ── Cyberia CLI dev image tar/name defaults (bin/cyberia.js). ── { file: 'bin/cyberia.js', patterns: [/(-dev_v)\d+\.\d+\.\d+(?=\.tar)/g, /(-dev:v)\d+\.\d+\.\d+/g], }, // ── Runtime Dockerfiles: `ARG UNDERPOST_VERSION=X.Y.Z` build-arg defaults. ── { dir: 'src/runtime', match: /^Dockerfile(\.\w+)?$/, patterns: /(ARG UNDERPOST_VERSION=)\d+\.\d+\.\d+/g, recursive: true, }, // ── Runtime compose.env shipped tag defaults ── { dir: 'src/runtime', match: /^compose\.env$/, patterns: /(_TAG=v)\d+\.\d+\.\d+/g, recursive: true, }, // ── Deploy-monitor smoke-test image default (scripts/test-monitor.sh). ── { file: 'scripts/test-monitor.sh', patterns: /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, }, // ── engine-private confs (gitignored — bumped only if present). ── { dir: 'engine-private/conf', match: /(?:deployment\.yaml|conf\.instances\.json)$/, patterns: [ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, /(underpost-engine:v)\d+\.\d+\.\d+/g, /(engine\.version: )\d+\.\d+\.\d+/g, /(v)\d+\.\d+\.\d+/g, ], recursive: true, optional: true, }, ]; /** * Walks a directory (optionally recursively) and returns paths matching `match`. */ const walkDir = (dir, match, recursive) => { if (!fs.existsSync(dir)) return []; const out = []; const stack = [dir]; while (stack.length) { const cur = stack.pop(); let entries; try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; } for (const ent of entries) { const full = path.join(cur, ent.name); if (ent.isDirectory()) { if (recursive) stack.push(full); } else if (ent.isFile() && match.test(ent.name) && !isSkipped(full)) { out.push(full); } } } return out; }; /** * Applies an array of regex patterns to a single file in place. Substitutes only when the * matched version literally equals `oldVersion` — narrative references to other versions * (e.g. example tags `(v3.2.9, v3.2.10, …)`) are left untouched. * * Returns the substitution count (0 if nothing matched). */ const bumpFile = (filePath, patterns, oldVersion, newVersion, { dryRun }) => { if (isSkipped(filePath)) return 0; if (!fs.existsSync(filePath)) return 0; const original = fs.readFileSync(filePath, 'utf8'); const regexes = Array.isArray(patterns) ? patterns : [patterns]; let updated = original; let count = 0; for (const re of regexes) { updated = updated.replace(re, (m, prefix) => { const matchedVersion = m.match(/\d+\.\d+\.\d+/)?.[0]; if (matchedVersion !== oldVersion) return m; count += 1; return `${prefix}${newVersion}`; }); } if (count > 0 && updated !== original && !dryRun) { fs.writeFileSync(filePath, updated, 'utf8'); } return count; }; /** * Bumps every non-canonical file declared in VERSION_BUMP_TARGETS. * Returns a per-file report so callers can print a summary. * * @param {string} oldVersion - The version currently in the files (pre-bump). * @param {string} newVersion - The version to write. * @param {{dryRun?: boolean}} [opts] * @returns {Array<{file: string, count: number}>} */ const bumpAuxiliaryFiles = (oldVersion, newVersion, { dryRun = false } = {}) => { const report = []; if (oldVersion === newVersion) return report; for (const target of buildVersionBumpTargets()) { const files = target.file ? [target.file] : walkDir(target.dir, target.match, target.recursive ?? false); for (const file of files) { if (target.optional && !fs.existsSync(file)) continue; const count = bumpFile(file, target.patterns, oldVersion, newVersion, { dryRun }); if (count > 0) report.push({ file, count }); } } return report; }; /** * Prints a per-file change summary table. */ const printBumpReport = (report, { dryRun }) => { const header = dryRun ? 'Version bump (dry-run) — would change:' : 'Version bump — files updated:'; logger.info(header); if (!report.length) { console.log(' (no files matched)'); return; } const maxLen = Math.max(...report.map((r) => r.file.length)); for (const { file, count } of report) { console.log(` ${file.padEnd(maxLen + 2)} ${count} match${count === 1 ? '' : 'es'}`); } console.log(` Total: ${report.length} file(s), ${report.reduce((s, r) => s + r.count, 0)} substitution(s)`); }; /** * Kills any Node.js dev-server or nodemon processes that may hold file locks * (e.g. overwriting package.json). Skips VSCode internals and the current process. */ function killDevServers() { for (const port of [4001, 4002, 4003, 3000]) shellExec(`node bin run kill ${port}`); } const TEMPLATE_PATH = '../pwa-microservices-template'; /** * Runs a command in an ISOLATED environment. `release build` loads the engine's * `dd-cron/.env.production` (DEPLOY_ID=dd-cron, DB creds, secrets, …) into its own `process.env`, * which `shellExec` children would otherwise inherit — and the template's `dotenv.config()` runs * without override, so it could never reclaim those keys. `env -i` starts the child with an empty * environment; only PATH/HOME-class essentials are re-added so node/npm/git still resolve. The * template then reads only its own `.env`, resolving `dd-default` exactly like a fresh clone. */ const ISOLATED_ENV = 'env -i HOME="$HOME" PATH="$PATH" USER="$USER" LOGNAME="$LOGNAME" TERM="$TERM" LANG="$LANG"'; /** * Builds the pwa-microservices-template from scratch and smoke-tests its default workflow. * * 1. Cleans the engine, pulls latest, and rebuilds the template via `bin/build.template`. * 2. Derives `.env` + `.env.example` from the template `.env.example` (DHCP host IP + file * logs), both written from the same `dd-default` content so the default startup workflow * bootstraps `engine-private` from scratch out of them. `.env` is rewritten because it is * gitignored (git clean never removes it) and may otherwise hold a stale `dd-cron`. * 3. Installs deps, then builds + runs the template `dev` server in an ISOLATED env * (see `ISOLATED_ENV`) so it resolves `dd-default` like a fresh clone, and asserts the * startup log is error-free. * * @returns {boolean} true when the template started cleanly, false otherwise. */ async function buildAndTestTemplate(opts = {}) { killDevServers(); Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] }); shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`); fs.removeSync(TEMPLATE_PATH); shellExec(`npm run build:template`); shellExec(`node bin run shared-dir ${TEMPLATE_PATH}`); const upsertEnvVar = (content, key, value) => { const re = new RegExp(`^(${key}=).*`, 'm'); if (re.test(content)) return content.replace(re, `$1${value}`); return `${content.trimEnd()}\n${key}=${value}\n`; }; const dhcpHostIp = Dns.getLocalIPv4Address(); logger.info(`DHCP host IP for template test: ${dhcpHostIp}`); let envContent = fs.readFileSync(`${TEMPLATE_PATH}/.env.example`, 'utf8'); if (dhcpHostIp) envContent = envContent.replace(/127\.0\.0\.1/g, dhcpHostIp); envContent = upsertEnvVar(envContent, 'ENABLE_FILE_LOGS', 'true'); if (opts.mongoHost) envContent = upsertEnvVar(envContent, 'DB_HOST', opts.mongoHost); if (opts.mongoUser) envContent = upsertEnvVar(envContent, 'DB_USER', opts.mongoUser); if (opts.mongoPassword) envContent = upsertEnvVar(envContent, 'DB_PASSWORD', opts.mongoPassword); if (opts.valkeyHost) envContent = upsertEnvVar(envContent, 'VALKEY_HOST', opts.valkeyHost); // fs.writeFileSync(`${TEMPLATE_PATH}/.env`, envContent, 'utf8'); fs.writeFileSync(`${TEMPLATE_PATH}/.env.example`, envContent, 'utf8'); shellExec(`cd ${TEMPLATE_PATH} && npm install`); shellExec(`cd ${TEMPLATE_PATH} && node bin env clean`); // Build + run in an isolated env so the template resolves dd-default from its own .env and // never inherits the engine's dd-cron deploy selection. See ISOLATED_ENV above. // // ENABLE_FILE_LOGS must be passed inline: src/server.js builds start.js's logger at import // time, before Config.build() loads the template .env, so the var has to be present in the // process env from the start for logs/start.js/all.log (the success probe below) to be written. shellExec(`cd ${TEMPLATE_PATH} && ${ISOLATED_ENV} npm run build`); shellExec(`cd ${TEMPLATE_PATH} && ${ISOLATED_ENV} ENABLE_FILE_LOGS=true timeout 5s npm run dev`, { async: true }); await timer(5500); const templateLogPath = `${TEMPLATE_PATH}/logs/start.js/all.log`; const runnerResult = fs.existsSync(templateLogPath) ? fs.readFileSync(templateLogPath, 'utf8') : ''; logger.info('Test template runner result'); killDevServers(); shellCd(`/home/dd/engine`); Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] }); if (!runnerResult || runnerResult.toLowerCase().match('error')) { logger.error('Test template runner result failed'); return false; } return true; } /** * @class UnderpostRelease * @description Orchestrates version builds and release deployments for the Underpost CLI. * This class provides static methods to automate the full release lifecycle: * building a new version (testing, bumping versions, rebuilding manifests) * and deploying a release (syncing secrets, committing, and pushing to remotes). * @memberof UnderpostRelease */ class UnderpostRelease { static API = { /** * Builds a new version of the Underpost engine. * * Performs the full version build pipeline: * 1. Loads production env and pulls latest code; kills dev servers on ports 4001-4003. * 2. Builds and smoke-tests the pwa-microservices-template (aborts on error). * 3. Canonical version files: delegates to `bumpp` (driven by `bump.config.js`). Bumps * package.json, package-lock.json (root + packages['']), and every * engine-private/conf/**\/package.json. * 4. Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS — covers * src/index.js, README, cyberia-docs, nexodev docs, manifests (deployment + cronjobs), * .github/workflows (image tags, type=raw, UNDERPOST_VERSION build-args), and * engine-private confs (deployment.yaml + conf.instances.json). Files in * VERSION_BUMP_SKIP (CHANGELOG, logs/, .env.*, node_modules/) are never touched. * The walker only rewrites occurrences whose version literally equals the pre-bump * version — narrative references like `(v3.2.9, v3.2.10, …)` are preserved. * 5. Rebuilds CLI docs, deps, client bundle, deploy manifests, default confs. * 6. Syncs cron setup-start scripts and builds the changelog. * * With `options.dryRun: true`, steps 3–4 print a per-file substitution report and exit * without writing files or running steps 5–6. * * @method build * @param {string} [newVersion] - The new version string to set. Defaults to current version if not provided. * @param {{dryRun?: boolean, mongoHost?: string, mongoUser?: string, mongoPassword?: string, valkeyHost?: string}} [options] - Commander options. * `--dry-run` previews changes without writing files. * `--mongo-host` overrides `DB_HOST` in the template `.env.example` smoke test. * `--mongo-user` overrides `DB_USER` in the template `.env.example` smoke test. * `--mongo-password` overrides `DB_PASSWORD` in the template `.env.example` smoke test. * `--valkey-host` overrides `VALKEY_HOST` in the template `.env.example` smoke test. * @memberof UnderpostRelease */ async build(newVersion, options = {}) { const dryRun = !!options.dryRun; dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true }); shellCd(`/home/dd/engine`); // ── Resolve from/to versions up front. Used by every bump step + downstream commands. ── const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8')); const { version } = originPackageJson; if (!newVersion) newVersion = version; logger.info(`Release build — bumping ${version}${newVersion}${dryRun ? ' (dry-run)' : ''}`); if (!dryRun) { const templateOk = await buildAndTestTemplate(options); if (!templateOk) return; } // ── Canonical version files: delegate to bumpp (package.json, package-lock.json, // engine-private/conf/**/package.json). bumpp owns lockfile semantics (root version // + packages[''].version) so we don't reimplement them. ── const { versionBump } = await import('bumpp'); const bumppResult = await versionBump({ release: newVersion, files: [ 'package.json', 'package-lock.json', ...(fs.existsSync('./engine-private/conf') ? ['engine-private/conf/**/package.json'] : []), ], commit: false, tag: false, push: false, confirm: false, recursive: false, ignoreScripts: true, dry: dryRun, }); logger.info(`bumpp updated ${bumppResult?.files?.length ?? 0} canonical file(s).`); // ── Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS. ── const report = bumpAuxiliaryFiles(version, newVersion, { dryRun }); printBumpReport(report, { dryRun }); if (dryRun) { logger.info('Dry-run complete. No files modified. Re-run without --dry-run to apply.'); return { from: version, to: newVersion, files: report.map((r) => r.file), dryRun: true }; } // ── Downstream regenerations and manifest sync (unchanged from previous flow). ── shellExec(`node bin/deploy cli-docs ${version} ${newVersion}`); shellExec(`node bin/deploy update-dependencies`); shellExec(`node bin/build dd`); shellExec(`node bin run build-cluster-deployment-manifests`); shellExec(`node bin new --default-conf --conf-workflow-id template`); shellExec(`sudo rm -rf ./engine-private/conf/dd-default`); shellExec(`node bin new --deploy-id dd-default`); console.log(fs.existsSync(`./engine-private/conf/dd-default`)); shellExec(`sudo rm -rf ./engine-private/conf/dd-default`); shellExec(`node bin cron --kubeadm --setup-start --git`); // --apply shellExec(`node bin cmt --changelog-build`); return { from: version, to: newVersion, files: report.map((r) => r.file), dryRun: false }; }, /** * Runs the local equivalent of an engine-*.ci.yml GitHub Actions workflow. * * Mirrors the CI pipeline locally: * 1. Loads production environment (for GITHUB_TOKEN / GITHUB_USERNAME). * 2. Clones pwa-microservices-template and engine-{suffix} (bare) into the parent dir (/home/dd). * 3. Builds dd-{suffix} development from the engine directory. * 4. Replaces .git in pwa-microservices-template with the bare-cloned git, then commits and pushes * to the underpostnet/engine-{suffix} remote repository. * * @method ci * @param {string} deployId - The deploy-id suffix (e.g., "cyberia", "core", "lampp", "test"). * Accepts "cyberia", "dd-cyberia", or "engine-cyberia" — the prefix is stripped automatically. * @param {string} [message] - Optional commit message. Defaults to the last commit message of pwa-microservices-template. * @param {object} [options] - Commander options object (unused, reserved for future flags). * @memberof UnderpostRelease */ async ci(deployId, message, options) { dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true }); const suffix = deployId.replace(/^(dd-|engine-)/, ''); const repoName = `engine-${suffix}`; const buildTarget = `dd-${suffix}`; const githubOrg = process.env.GITHUB_USERNAME || 'underpostnet'; shellCd('/home/dd'); shellExec(`sudo rm -rf /home/dd/pwa-microservices-template`); shellExec(`node engine/bin clone ${githubOrg}/pwa-microservices-template`); // Use the message passed from the caller (engine repo changelog); // fall back to the engine repo's last commit if not provided. let commitMsg = message; if (!commitMsg) { shellCd('/home/dd/engine'); commitMsg = shellExec(`node bin cmt --changelog-msg --changelog-no-hash`, { stdout: true, silent: true, }).trim(); shellCd('/home/dd'); } commitMsg = (commitMsg || '').trim() || `Update ${repoName} repository`; logger.info(`CI push commit message: ${commitMsg}`); shellExec(`node engine/bin clone --bare ${githubOrg}/${repoName}`); shellCd('/home/dd/engine'); shellExec(`node bin/build ${buildTarget}`); shellCd('/home/dd/pwa-microservices-template'); shellExec(`rm -rf ./.git`); shellExec(`mv ../${repoName}.git ./.git`); shellExec(`git config --local core.bare false`); shellExec(`git reset`); Underpost.repo.initLocalRepo({ path: '/home/dd/pwa-microservices-template' }); return { triggerCmd: `cd /home/dd/pwa-microservices-template && git add . && git commit -m "${commitMsg}" && node ../engine/bin push . ${githubOrg}/${repoName}`, }; }, /** * Runs the pwa-microservices-template update and push flow locally. * * Always removes and re-clones pwa-microservices-template, then: * 1. Runs build:template (node bin/build.template) to sync engine sources. * 2. Installs dependencies and builds the template. * 3. Commits and pushes to the pwa-microservices-template remote repository. * * @method pwa * @param {string} [message] - Optional commit message. Defaults to last commit message of pwa-microservices-template. * @param {object} [options] - Commander options object (unused, reserved for future flags). * @memberof UnderpostRelease */ async pwa(message, options) { dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true }); const githubOrg = process.env.GITHUB_USERNAME || 'underpostnet'; // Use the message passed from the caller (engine repo changelog); // fall back to the engine repo's last commit if not provided. let commitMsg = message; if (!commitMsg) { shellCd('/home/dd/engine'); commitMsg = shellExec(`node bin cmt --changelog-msg --changelog-no-hash`, { stdout: true, silent: true, }).trim(); } commitMsg = (commitMsg || '').trim() || `Update pwa-microservices-template repository`; shellCd('/home/dd'); shellExec(`sudo rm -rf /home/dd/pwa-microservices-template`); shellExec(`node engine/bin clone ${githubOrg}/pwa-microservices-template`); shellCd('/home/dd/engine'); shellExec(`npm run build:template`); shellExec(`cd ../pwa-microservices-template && npm install && npm run build`); shellCd('/home/dd/pwa-microservices-template'); shellExec(`git add .`); // shellExec(`git commit -m "${commitMsg}"`); return { triggerCmd: `node bin push . ${githubOrg}/engine && cd /home/dd/pwa-microservices-template && git commit -m "${commitMsg}" && node ../engine/bin push . ${githubOrg}/pwa-microservices-template`, }; }, /** * Deploys a new version release to remote repositories. * * Performs the release deployment pipeline: * 1. Loads production environment from dd-cron. * 2. Syncs Underpost secrets from the production env file. * 3. Builds the dd configuration. * 4. Stages all changes in both engine and engine-private repositories. * 5. Commits with a release message including the version tag. * 6. Pushes both repositories to their respective GitHub remotes. * * @method deploy * @param {string} [version] - The version string for the release commit message (e.g., "3.1.4"). * @param {object} [options] - Commander options object (unused, reserved for future flags). * @memberof UnderpostRelease */ async deploy(version, options) { dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true }); killDevServers(); shellExec( `node bin secret underpost --create-from-file /home/dd/engine/engine-private/conf/dd-cron/.env.production`, ); shellExec(`node bin/build dd --conf`); shellExec(`git add . && cd ./engine-private && git add .`); shellExec(`node bin cmt . ci package-pwa-microservices-template 'New release v:${version}'`); shellExec(`node bin cmt ./engine-private ci package-pwa-microservices-template`); shellExec(`node bin push . ${process.env.GITHUB_USERNAME}/engine`); shellExec(`cd ./engine-private && node ../bin push . ${process.env.GITHUB_USERNAME}/engine-private`); }, }; } export { UnderpostRelease }; export default UnderpostRelease;