diff --git a/src/align-versions.js b/src/align-versions.js index 24ffb8be4..c01a937dc 100644 --- a/src/align-versions.js +++ b/src/align-versions.js @@ -21,6 +21,11 @@ const tasks = new Listr([ * @param {GlobalOptions & ReleaseOptions} ctx */ task: async (ctx) => { + if (!process.env.CI) { + console.info('⚠ This run was not triggered in a known CI environment, running in dry-run mode.') // eslint-disable-line no-console + return + } + const rootDir = process.cwd() const workspaces = pkg.workspaces @@ -46,6 +51,8 @@ const tasks = new Listr([ const manifestPath = path.join(packageDir, 'package.json') const manifest = fs.readJSONSync(path.join(packageDir, 'package.json')) + console.info('check project', manifest.name) + for (const type of dependencyTypes) { for (const [dep, version] of Object.entries(siblingVersions)) { if (manifest[type] != null && manifest[type][dep] != null && manifest[type][dep] !== version) { @@ -71,7 +78,7 @@ const tasks = new Listr([ } if (!process.env.CI) { - console.info('CI env var is not set, not pushing to git') // eslint-disable-line no-console + // do not push to remote repo if in dry-run mode return } diff --git a/src/check-project/check-monorepo-readme.js b/src/check-project/check-monorepo-readme.js index e71deaedd..a6e29240c 100644 --- a/src/check-project/check-monorepo-readme.js +++ b/src/check-project/check-monorepo-readme.js @@ -159,7 +159,7 @@ export async function checkMonorepoReadme (projectDir, repoUrl, webRoot, default apiDocs = parseMarkdown(APIDOCS(pkg)) } - const structure = parseMarkdown(STRUCTURE(projectDir, projectDirs)) + const structure = parseMarkdown(STRUCTURE(projectDir, projectDirs, webRoot)) readme.children = [ ...header, diff --git a/src/check-project/index.js b/src/check-project/index.js index 56870aac4..5f2ee3304 100755 --- a/src/check-project/index.js +++ b/src/check-project/index.js @@ -115,6 +115,23 @@ async function processMonorepo (projectDir, manifest, branchName, repoUrl, ciFil const projectDirs = [] const webRoot = `${repoUrl}/tree/${branchName}` + const { releaseType } = await prompt.get({ + properties: { + releaseType: { + description: 'Monorepo release type: semantic-release | release-please', + required: true, + conform: (value) => { + return ['semantic-release', 'release-please'].includes(value) + }, + default: usesReleasePlease() ? 'release-please' : 'semantic-release' + } + } + }) + + if (releaseType !== 'release-please' && releaseType !== 'semantic-release') { + throw new Error('Invalid release type specified') + } + for (const subProjectDir of await getSubprojectDirectories(projectDir, workspaces)) { const stat = await fs.stat(subProjectDir) @@ -133,7 +150,16 @@ async function processMonorepo (projectDir, manifest, branchName, repoUrl, ciFil console.info('Found monorepo project', pkg.name) - await processModule(subProjectDir, pkg, branchName, repoUrl, homePage, ciFile, manifest) + await processModule({ + projectDir: subProjectDir, + manifest: pkg, + branchName, + repoUrl, + homePage, + ciFile, + rootManifest: manifest, + releaseType + }) projectDirs.push(subProjectDir) } @@ -141,7 +167,13 @@ async function processMonorepo (projectDir, manifest, branchName, repoUrl, ciFil await alignMonorepoProjectDependencies(projectDirs) await configureMonorepoProjectReferences(projectDirs) - let proposedManifest = await monorepoManifest(manifest, repoUrl, repoUrl, branchName) + let proposedManifest = await monorepoManifest({ + manifest, + repoUrl, + homePage: repoUrl, + branchName, + releaseType + }) proposedManifest = sortManifest(proposedManifest) await ensureFileHasContents(projectDir, 'package.json', JSON.stringify(proposedManifest, null, 2)) @@ -324,7 +356,9 @@ function addReferences (deps, references, refs) { * @param {string} ciFile */ async function processProject (projectDir, manifest, branchName, repoUrl, ciFile) { - await processModule(projectDir, manifest, branchName, repoUrl, repoUrl, ciFile) + const releaseType = 'semantic-release' + + await processModule({ projectDir, manifest, branchName, repoUrl, homePage: repoUrl, ciFile, releaseType }) await checkBuildFiles(projectDir, branchName, repoUrl) } @@ -336,16 +370,32 @@ function isAegirProject (manifest) { } /** - * - * @param {string} projectDir - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} homePage - * @param {string} ciFile - * @param {any} [rootManifest] + * @typedef {object} ProcessModuleContext + * @property {string} projectDir + * @property {any} manifest + * @property {string} branchName + * @property {string} repoUrl + * @property {string} homePage + * @property {string} ciFile + * @property {any} [rootManifest] + * @property {"semantic-release" | "release-please"} releaseType + */ + +/** + * @typedef {object} ProcessManifestContext + * @property {any} manifest + * @property {string} branchName + * @property {string} repoUrl + * @property {string} homePage + * @property {"semantic-release" | "release-please"} releaseType */ -async function processModule (projectDir, manifest, branchName, repoUrl, homePage = repoUrl, ciFile, rootManifest) { + +/** + * @param {ProcessModuleContext} context + */ +async function processModule (context) { + const { projectDir, manifest, branchName, repoUrl, homePage = repoUrl, ciFile, rootManifest, releaseType } = context + if (!isAegirProject(manifest) && manifest.name !== 'aegir') { throw new Error(`"${projectDir}" is not an aegir project`) } @@ -412,29 +462,23 @@ async function processModule (projectDir, manifest, branchName, repoUrl, homePag if (typescript) { console.info('TypeScript project detected') - proposedManifest = await typescriptManifest(manifest, branchName, repoUrl, homePage) + proposedManifest = await typescriptManifest({ manifest, branchName, repoUrl, homePage, releaseType }) } else if (typedESM) { console.info('Typed ESM project detected') - proposedManifest = await typedESMManifest(manifest, branchName, repoUrl, homePage) + proposedManifest = await typedESMManifest({ manifest, branchName, repoUrl, homePage, releaseType }) } else if (typedCJS) { console.info('Typed CJS project detected') - proposedManifest = await typedCJSManifest(manifest, branchName, repoUrl, homePage) + proposedManifest = await typedCJSManifest({ manifest, branchName, repoUrl, homePage, releaseType }) } else if (untypedESM) { console.info('Untyped ESM project detected') - proposedManifest = await untypedESMManifest(manifest, branchName, repoUrl, homePage) + proposedManifest = await untypedESMManifest({ manifest, branchName, repoUrl, homePage, releaseType }) } else if (untypedCJS) { console.info('Untyped CJS project detected') - proposedManifest = await untypedCJSManifest(manifest, branchName, repoUrl, homePage) + proposedManifest = await untypedCJSManifest({ manifest, branchName, repoUrl, homePage, releaseType }) } else { throw new Error('Cannot determine project type') } - // remove release config from monorepo projects as multi-semantic-release - // wants it defined in the root manifest - if (rootManifest != null) { - proposedManifest.release = undefined - } - proposedManifest = sortManifest(proposedManifest) await ensureFileHasContents(projectDir, 'package.json', JSON.stringify(proposedManifest, null, 2)) diff --git a/src/check-project/manifests/monorepo.js b/src/check-project/manifests/monorepo.js index 8cebd6d0f..7a29b3321 100644 --- a/src/check-project/manifests/monorepo.js +++ b/src/check-project/manifests/monorepo.js @@ -1,24 +1,41 @@ -import { semanticReleaseConfig } from '../semantic-release-config.js' import { sortFields, constructManifest } from '../utils.js' /** - * @param {any} manifest - * @param {string} repoUrl - * @param {string} homePage - * @param {string} branchName + * @param {import('../index.js').ProcessManifestContext} context */ -export async function monorepoManifest (manifest, repoUrl, homePage, branchName) { +export async function monorepoManifest (context) { + const { manifest, repoUrl, homePage } = context + + const scripts = { + ...manifest.scripts + } + + const devDependencies = manifest.devDependencies ?? {} + + if (context.releaseType === 'semantic-release') { + scripts.release = 'run-s build npm:release docs' + scripts['npm:release'] = 'aegir run release' + scripts.docs = 'aegir docs' + + delete manifest.release + devDependencies['npm-run-all'] = '^4.1.5' + } + + if (context.releaseType === 'release-please') { + scripts.release = 'run-s build npm:release docs' + scripts['npm:release'] = 'aegir exec --bail false npm -- publish' + scripts['release:rc'] = 'aegir release-rc' + scripts.docs = 'aegir docs' + + devDependencies['npm-run-all'] = '^4.1.5' + } + let proposedManifest = constructManifest(manifest, { private: true, - release: ( - Object.values(manifest.scripts ?? {}) - .some(script => script.includes('semantic-release') || script.includes('aegir release')) - ) - ? semanticReleaseConfig(branchName) - : undefined + scripts }, repoUrl, homePage) const rest = { diff --git a/src/check-project/manifests/typed-cjs.js b/src/check-project/manifests/typed-cjs.js index 5a90ace9e..0d408ab44 100644 --- a/src/check-project/manifests/typed-cjs.js +++ b/src/check-project/manifests/typed-cjs.js @@ -8,12 +8,24 @@ import { const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} [homePage] + * @param {import('../index.js').ProcessManifestContext} context */ -export async function typedCJSManifest (manifest, branchName, repoUrl, homePage = repoUrl) { +export async function typedCJSManifest (context) { + const { manifest, branchName, repoUrl, homePage } = context + let release + const scripts = { + ...manifest.scripts + } + + if (context.releaseType === 'semantic-release') { + scripts.release = 'aegir release' + release = semanticReleaseConfig(branchName) + } + + if (context.releaseType === 'release-please') { + delete scripts.release + } + let proposedManifest = constructManifest(manifest, { main: 'src/index.js', types: 'dist/src/index.d.ts', @@ -41,7 +53,8 @@ export async function typedCJSManifest (manifest, branchName, repoUrl, homePage project: true } }, manifest.eslintConfig), - release: (manifest.scripts?.release?.includes('semantic-release') || manifest.scripts?.release?.includes('aegir release')) ? semanticReleaseConfig(branchName) : undefined + scripts, + release }, repoUrl, homePage) const rest = { diff --git a/src/check-project/manifests/typed-esm.js b/src/check-project/manifests/typed-esm.js index d8aed8423..d5312b0a4 100644 --- a/src/check-project/manifests/typed-esm.js +++ b/src/check-project/manifests/typed-esm.js @@ -9,12 +9,24 @@ import { const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} [homePage] + * @param {import('../index.js').ProcessManifestContext} context */ -export async function typedESMManifest (manifest, branchName, repoUrl, homePage = repoUrl) { +export async function typedESMManifest (context) { + const { manifest, branchName, repoUrl, homePage } = context + let release + const scripts = { + ...manifest.scripts + } + + if (context.releaseType === 'semantic-release') { + scripts.release = 'aegir release' + release = semanticReleaseConfig(branchName) + } + + if (context.releaseType === 'release-please') { + delete scripts.release + } + let proposedManifest = constructManifest(manifest, { type: 'module', types: './dist/src/index.d.ts', @@ -55,7 +67,8 @@ export async function typedESMManifest (manifest, branchName, repoUrl, homePage sourceType: 'module' } }, manifest.eslintConfig), - release: (manifest.scripts?.release?.includes('semantic-release') || manifest.scripts?.release?.includes('aegir release')) ? semanticReleaseConfig(branchName) : undefined + release, + scripts }, repoUrl, homePage) const rest = { diff --git a/src/check-project/manifests/typescript.js b/src/check-project/manifests/typescript.js index 671a8e9fc..9b002ffe7 100644 --- a/src/check-project/manifests/typescript.js +++ b/src/check-project/manifests/typescript.js @@ -11,12 +11,21 @@ import { const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} [homePage] + * @param {import('../index.js').ProcessManifestContext} context */ -export async function typescriptManifest (manifest, branchName, repoUrl, homePage = repoUrl) { +export async function typescriptManifest (context) { + const { manifest, branchName, repoUrl, homePage } = context + let release + + if (context.releaseType === 'semantic-release') { + manifest.scripts.release = 'aegir release' + release = semanticReleaseConfig(branchName) + } + + if (context.releaseType === 'release-please') { + delete manifest.scripts.release + } + let proposedManifest = constructManifest(manifest, { type: 'module', types: './dist/src/index.d.ts', @@ -42,7 +51,7 @@ export async function typescriptManifest (manifest, branchName, repoUrl, homePag sourceType: 'module' } }, manifest.eslintConfig), - release: (manifest.scripts?.release?.includes('semantic-release') || manifest.scripts?.release?.includes('aegir release')) ? semanticReleaseConfig(branchName) : undefined + release }, repoUrl, homePage) if (proposedManifest.exports != null && Object.keys(proposedManifest.exports).length > 1) { diff --git a/src/check-project/manifests/untyped-cjs.js b/src/check-project/manifests/untyped-cjs.js index 84b3daf3a..f8f3fef12 100644 --- a/src/check-project/manifests/untyped-cjs.js +++ b/src/check-project/manifests/untyped-cjs.js @@ -8,12 +8,24 @@ import { const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} [homePage] + * @param {import('../index.js').ProcessManifestContext} context */ -export async function untypedCJSManifest (manifest, branchName, repoUrl, homePage = repoUrl) { +export async function untypedCJSManifest (context) { + const { manifest, branchName, repoUrl, homePage } = context + let release + const scripts = { + ...manifest.scripts + } + + if (context.releaseType === 'semantic-release') { + scripts.release = 'aegir release' + release = semanticReleaseConfig(branchName) + } + + if (context.releaseType === 'release-please') { + delete scripts.release + } + let proposedManifest = constructManifest(manifest, { main: 'src/index.js', files: [ @@ -26,7 +38,8 @@ export async function untypedCJSManifest (manifest, branchName, repoUrl, homePag project: true } }, manifest.eslintConfig), - release: (manifest.scripts?.release?.includes('semantic-release') || manifest.scripts?.release?.includes('aegir release')) ? semanticReleaseConfig(branchName) : undefined + release, + scripts }, repoUrl, homePage) const rest = { diff --git a/src/check-project/manifests/untyped-esm.js b/src/check-project/manifests/untyped-esm.js index 4a9dd4693..29e8b00ba 100644 --- a/src/check-project/manifests/untyped-esm.js +++ b/src/check-project/manifests/untyped-esm.js @@ -8,12 +8,24 @@ import { const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @param {any} manifest - * @param {string} branchName - * @param {string} repoUrl - * @param {string} [homePage] + * @param {import('../index.js').ProcessManifestContext} context */ -export async function untypedESMManifest (manifest, branchName, repoUrl, homePage = repoUrl) { +export async function untypedESMManifest (context) { + const { manifest, branchName, repoUrl, homePage } = context + let release + const scripts = { + ...manifest.scripts + } + + if (context.releaseType === 'semantic-release') { + scripts.release = 'aegir release' + release = semanticReleaseConfig(branchName) + } + + if (context.releaseType === 'release-please') { + delete scripts.release + } + let proposedManifest = constructManifest(manifest, { type: 'module', files: [ @@ -35,7 +47,8 @@ export async function untypedESMManifest (manifest, branchName, repoUrl, homePag sourceType: 'module' } }, manifest.eslintConfig), - release: (manifest.scripts?.release?.includes('semantic-release') || manifest.scripts?.release?.includes('aegir release')) ? semanticReleaseConfig(branchName) : undefined + release, + scripts }, repoUrl, homePage) const rest = { diff --git a/src/check-project/readme/structure.js b/src/check-project/readme/structure.js index 48522335e..7cc532361 100644 --- a/src/check-project/readme/structure.js +++ b/src/check-project/readme/structure.js @@ -4,8 +4,9 @@ import fs from 'fs-extra' /** * @param {string} monorepoDir * @param {string[]} projectDirs + * @param {string} webRoot */ -export const STRUCTURE = (monorepoDir, projectDirs) => { +export const STRUCTURE = (monorepoDir, projectDirs, webRoot) => { /** @type {Record} */ const packages = {} @@ -21,7 +22,7 @@ export const STRUCTURE = (monorepoDir, projectDirs) => { # Packages ${Object.entries(packages).map(([key, value]) => { - return `* [\`${key}\`](.${key}) ${value}` + return `* [\`${key}\`](${webRoot}/${key}) ${value}` }).join('\n')} ` } diff --git a/src/utils.js b/src/utils.js index a6ee1642c..6289d78cd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -301,6 +301,24 @@ export const usesReleasePlease = (dir = process.cwd()) => { } } +export const usesSemanticReleaseMonorepo = (dir = process.cwd()) => { + try { + if (pkg.private) { + return false + } + + const cwd = path.resolve(dir, '..') + const rootManifest = readPackageUpSync({ + cwd + }) + + return Object.values(rootManifest?.packageJson.scripts ?? {}) + .some(script => script.includes('aegir run release')) + } catch { + return false + } +} + /** * Binaries we need are normally in `node_modules/.bin` of the root project * unless a sibling dependency has caused a different version to be hoisted