import { readFileSync, writeFileSync } from 'fs'; import { resolve } from 'path'; import semver from 'semver'; const CONSTANTS_DIR = resolve( __dirname, '../src/engine/core-modules/upgrade/constants', ); const AUTO_GENERATED_HEADER = `/* * _____ _ *|_ _|_ _____ _ __ | |_ _ _ * | | \\ \\ /\\ / / _ \\ '_ \\| __| | | | Auto-generated file * | | \\ V V / __/ | | | |_| |_| | Any edits to this will be overridden * |_| \\_/\\_/ \\___|_| |_|\\__|\\__, | Generated by: npx nx version:bump twenty-server * |___/ */ `; const incrementMinorVersion = (version: string): string => { const incremented = semver.inc(version, 'minor'); if (!incremented) { throw new Error(`Failed to increment version: ${version}`); } return incremented; }; const getCurrentVersion = (): string => { const filePath = `${CONSTANTS_DIR}/twenty-current-version.constant.ts`; const content = readFileSync(filePath, 'utf-8'); const match = content.match(/'([0-9.]+)'/); if (!match) { throw new Error('Could not extract current version'); } return match[1]; }; const getPreviousVersions = (): string[] => { const filePath = `${CONSTANTS_DIR}/twenty-previous-versions.constant.ts`; const content = readFileSync(filePath, 'utf-8'); const matches = content.match(/'[0-9.]+'/g); if (!matches) { return []; } return matches.map((m) => m.replace(/'/g, '')); }; const getNextVersions = (): string[] => { const filePath = `${CONSTANTS_DIR}/twenty-next-versions.constant.ts`; const content = readFileSync(filePath, 'utf-8'); const matches = content.match(/'[0-9.]+'/g); if (!matches) { return []; } return matches.map((m) => m.replace(/'/g, '')); }; const updateCurrentVersion = (newVersion: string): void => { const filePath = `${CONSTANTS_DIR}/twenty-current-version.constant.ts`; writeFileSync( filePath, `${AUTO_GENERATED_HEADER}export const TWENTY_CURRENT_VERSION = '${newVersion}' as const;\n`, ); console.log(`Updated TWENTY_CURRENT_VERSION to '${newVersion}'`); }; const updatePreviousVersions = (versions: string[]): void => { const filePath = `${CONSTANTS_DIR}/twenty-previous-versions.constant.ts`; const formattedVersions = versions.map((v) => ` '${v}',`).join('\n'); writeFileSync( filePath, `${AUTO_GENERATED_HEADER}export const TWENTY_PREVIOUS_VERSIONS = [\n${formattedVersions}\n] as const;\n`, ); console.log( `Updated TWENTY_PREVIOUS_VERSIONS with ${versions.length} versions`, ); }; const updateNextVersions = (versions: string[]): void => { const filePath = `${CONSTANTS_DIR}/twenty-next-versions.constant.ts`; const formattedVersions = versions.map((v) => ` '${v}',`).join('\n'); writeFileSync( filePath, `${AUTO_GENERATED_HEADER}export const TWENTY_NEXT_VERSIONS = [\n${formattedVersions}\n] as const;\n`, ); console.log(`Updated TWENTY_NEXT_VERSIONS with ${versions.length} versions`); }; const resolveNewVersion = ({ newVersionArg, currentVersion, nextVersions, }: { newVersionArg: string | undefined; currentVersion: string; nextVersions: string[]; }): string => { if (newVersionArg) { if (!semver.valid(newVersionArg)) { throw new Error(`Invalid version format: ${newVersionArg}`); } if (!semver.gt(newVersionArg, currentVersion)) { throw new Error( `New version '${newVersionArg}' must be greater than current version '${currentVersion}'`, ); } if (nextVersions.length > 0) { const highestNextVersion = nextVersions[nextVersions.length - 1]; if (semver.gt(newVersionArg, highestNextVersion)) { throw new Error( `New version '${newVersionArg}' cannot be greater than highest planned next version '${highestNextVersion}'`, ); } } return newVersionArg; } if (nextVersions.length === 0) { const computed = incrementMinorVersion(currentVersion); console.log( `\nNo version provided and TWENTY_NEXT_VERSIONS is empty, computed from current: '${computed}'`, ); return computed; } console.log( `\nNo version provided, using TWENTY_NEXT_VERSIONS[0]: '${nextVersions[0]}'`, ); return nextVersions[0]; }; const partitionNextVersions = ({ nextVersions, newVersion, }: { nextVersions: string[]; newVersion: string; }): { skippedNextVersions: string[]; remainingNextVersions: string[]; } => nextVersions.reduce<{ skippedNextVersions: string[]; remainingNextVersions: string[]; }>( (acc, version) => { if (semver.lt(version, newVersion)) { return { ...acc, skippedNextVersions: [...acc.skippedNextVersions, version], }; } if (semver.gt(version, newVersion)) { return { ...acc, remainingNextVersions: [...acc.remainingNextVersions, version], }; } return acc; }, { skippedNextVersions: [], remainingNextVersions: [] }, ); const bumpVersion = (newVersionArg?: string): void => { const currentVersion = getCurrentVersion(); const previousVersions = getPreviousVersions(); const nextVersions = getNextVersions(); console.log('\nCurrent state:'); console.log(` TWENTY_CURRENT_VERSION: '${currentVersion}'`); console.log( ` TWENTY_PREVIOUS_VERSIONS: [${previousVersions.map((v) => `'${v}'`).join(', ')}]`, ); console.log( ` TWENTY_NEXT_VERSIONS: [${nextVersions.map((v) => `'${v}'`).join(', ')}]`, ); const newVersion = resolveNewVersion({ newVersionArg, currentVersion, nextVersions, }); const { skippedNextVersions, remainingNextVersions } = partitionNextVersions({ nextVersions, newVersion, }); const updatedPreviousVersions = [ ...previousVersions, currentVersion, ...skippedNextVersions, ]; const updatedNextVersions = remainingNextVersions.length > 0 ? remainingNextVersions : [incrementMinorVersion(newVersion)]; console.log('\nApplying changes:'); updatePreviousVersions(updatedPreviousVersions); updateCurrentVersion(newVersion); updateNextVersions(updatedNextVersions); console.log('\nNew state:'); console.log(` TWENTY_CURRENT_VERSION: '${newVersion}'`); console.log( ` TWENTY_PREVIOUS_VERSIONS: [${updatedPreviousVersions.map((v) => `'${v}'`).join(', ')}]`, ); console.log( ` TWENTY_NEXT_VERSIONS: [${updatedNextVersions.map((v) => `'${v}'`).join(', ')}]`, ); console.log('\nVersion bump complete!'); }; const newVersion = process.argv[2]; bumpVersion(newVersion);