diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 692d1aa..7410614 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - cache: 'npm' + cache: "npm" - name: Install dependencies run: npm install --ci @@ -41,5 +41,3 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }} run: npx semantic-release - -... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6d7576..c0ceacc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: "${{ matrix.nodejs }}" - cache: 'npm' + cache: "npm" - name: Install dependencies run: npm install --ci @@ -56,7 +56,7 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: 'npm' + cache: "npm" - name: Install dependencies run: npm install --ci @@ -67,7 +67,6 @@ jobs: - name: Execute tests run: npm test - test-result: runs-on: ubuntu-latest @@ -85,5 +84,3 @@ jobs: - name: Some matrix version failed if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 - -... diff --git a/src/common.js b/lib/common.js similarity index 100% rename from src/common.js rename to lib/common.js diff --git a/lib/prepare.js b/lib/prepare.js new file mode 100644 index 0000000..219109b --- /dev/null +++ b/lib/prepare.js @@ -0,0 +1,121 @@ +import path from "node:path"; +import { rename, readFile, writeFile } from "node:fs/promises"; + +import { execa } from "execa"; +import { versionRegex } from "./common.js"; + +const writeVersion = async ({ versionFile, nextVersion, logger, cwd }) => { + const gemVersion = nextVersion.replace("-", "."); + const fullVersionPath = path.resolve(cwd, versionFile); + const versionContents = await readFile(fullVersionPath, "utf8"); + const newContents = versionContents.replace( + versionRegex, + `$1${gemVersion}$2`, + ); + + logger.log("Writing version %s to `%s`", nextVersion, versionFile); + + await writeFile(fullVersionPath, newContents, "utf8"); + + return { gemVersion }; +}; + +const bundleInstall = async ({ + updateGemfileLock, + cwd, + env, + logger, + stdout, + stderr, +}) => { + const command = + typeof updateGemfileLock === "string" + ? updateGemfileLock + : "bundle install"; + + logger.log("Updating lock file with command `%s`", command); + + const installResult = execa.command(command, { cwd, env }); + + installResult.stdout.pipe(stdout, { end: false }); + + installResult.stderr.pipe(stderr, { end: false }); + + await installResult; +}; + +const buildGem = async ({ + gemspec, + gemName, + version, + cwd, + env, + logger, + stdout, + stderr, +}) => { + const gemFile = `${gemName}-${version}.gem`; + + logger.log("Building gem `%s`", gemFile); + + const buildResult = execa("gem", ["build", gemspec], { cwd, env }); + + buildResult.stdout.pipe(stdout, { end: false }); + + buildResult.stderr.pipe(stderr, { end: false }); + + await buildResult; + + return gemFile; +}; + +export default async function prepare( + { updateGemfileLock = false, gemFileDir = false }, + { nextRelease: { version }, cwd, env, logger, stdout, stderr }, + { versionFile, gemSpec, gemName }, +) { + const { gemVersion } = await writeVersion({ + versionFile, + nextVersion: version, + logger, + cwd, + }); + + if (updateGemfileLock) { + await bundleInstall({ + updateGemfileLock, + cwd, + env, + logger, + stdout, + stderr, + }); + } + + let gemFile = await buildGem({ + gemSpec, + gemName, + version: gemVersion, + cwd, + env, + logger, + stdout, + stderr, + }); + + if (gemFileDir) { + const gemFileSource = path.resolve(cwd, gemFile); + + const gemFileDestination = path.resolve(cwd, gemFileDir.trim(), gemFile); + + if (gemFileSource !== gemFileDestination) { + await rename(gemFileSource, gemFileDestination); + } + + gemFile = path.join(gemFileDir.trim(), gemFile); + } + + return { + gemFile, + }; +} diff --git a/lib/publish.js b/lib/publish.js new file mode 100644 index 0000000..90d4fa0 --- /dev/null +++ b/lib/publish.js @@ -0,0 +1,36 @@ +import { unlink } from "node:fs/promises"; +import { execa } from "execa"; + +export default async function publish( + { gemHost, gemPublish = true, gemFileDir = false }, + { cwd, env, logger, nextRelease: { version }, stdout, stderr }, + { gemFile, gemName, credentialsFile }, +) { + if (gemPublish !== false) { + logger.log(`Publishing version ${version} to rubygems`); + + const args = ["push", gemFile, "--config-file", credentialsFile]; + + if (gemHost) { + args.push("--host", gemHost); + } + + const pushResult = execa("gem", args, { cwd, env }); + + pushResult.stdout.pipe(stdout, { end: false }); + + pushResult.stderr.pipe(stderr, { end: false }); + + await pushResult; + + logger.log(`Published version ${version} of ${gemName} to rubygems`); + } else { + logger.log( + `Skip publishing to rubygems because gemPublish is ${gemPublish !== false}`, + ); + } + + if (gemFileDir === false) { + await unlink(gemFile); + } +} diff --git a/src/verify.js b/lib/verify.js similarity index 72% rename from src/verify.js rename to lib/verify.js index 43d8ac6..b67b1b9 100644 --- a/src/verify.js +++ b/lib/verify.js @@ -1,21 +1,18 @@ -import path from 'node:path'; -import { readFile, writeFile } from 'node:fs/promises'; +import path from "node:path"; +import { readFile, writeFile } from "node:fs/promises"; -import { execa } from 'execa'; +import { execa } from "execa"; import { SemanticReleaseError } from "@semantic-release/error"; -import { glob } from 'glob' -import { versionRegex } from './common.js'; +import { glob } from "glob"; +import { versionRegex } from "./common.js"; const loadGemspec = async ({ cwd }) => { - const gemSpecs = await glob( - '*.gemspec', - { cwd } - ); + const gemSpecs = await glob("*.gemspec", { cwd }); if (gemSpecs.length !== 1) { throw new SemanticReleaseError( "Couldn't find a `.gemspec` file.", - 'ENOGEMSPEC', + "ENOGEMSPEC", `A single [.gemspec](https://guides.rubygems.org/specification-reference/) file in the root of your project is required to release a Rubygem. Please follow the "[Make your own gem guide](https://guides.rubygems.org/make-your-own-gem/)" to create a valid \`.gemspec\` file @@ -23,16 +20,14 @@ Please follow the "[Make your own gem guide](https://guides.rubygems.org/make-yo ); } - const [ - gemSpec - ] = gemSpecs; + const [gemSpec] = gemSpecs; let gemName = null; try { const { stdout } = await execa( - 'ruby', - ['-e', `puts Gem::Specification.load('${gemSpec}').name`], + "ruby", + ["-e", `puts Gem::Specification.load('${gemSpec}').name`], { cwd }, ); @@ -40,7 +35,7 @@ Please follow the "[Make your own gem guide](https://guides.rubygems.org/make-yo } catch (error) { throw new SemanticReleaseError( `Error loading \`${gemSpec}\``, - 'EINVALIDGEMSPEC', + "EINVALIDGEMSPEC", `A valid [.gemspec](https://guides.rubygems.org/specification-reference/) is required to release a Rubygem. Please follow the "[Make your own gem guide](https://guides.rubygems.org/make-your-own-gem/)" to create a valid \`.gemspec\` file @@ -48,10 +43,10 @@ Please follow the "[Make your own gem guide](https://guides.rubygems.org/make-yo ); } - if (gemName === '') { + if (gemName === "") { throw new SemanticReleaseError( `Missing \`name\` attribute in \`${gemSpec}\``, - 'ENOGEMNAME', + "ENOGEMNAME", `The [name](https://guides.rubygems.org/specification-reference/#name) attribute is required in your \`.gemspec\` file in order to publish a Rubygem. Please make sure to add a valid \`name\` for your gem in your \`.gemspec\`. @@ -61,20 +56,17 @@ Please make sure to add a valid \`name\` for your gem in your \`.gemspec\`. return { name: gemName, - gemSpec + gemSpec, }; }; const verifyVersionFile = async ({ cwd, versionGlob }) => { - const versionFiles = await glob( - versionGlob, - { cwd } - ); + const versionFiles = await glob(versionGlob, { cwd }); if (versionFiles.length !== 1) { throw new SemanticReleaseError( "Couldn't find a `version.lib` file.", - 'ENOVERSIONFILE', + "ENOVERSIONFILE", `A \`version.rb\` file in the \`lib/*\` dir of your project is required to release a Ruby gem. Please create a \`version.rb\` file with a defined \`VERSION\` constant in your \`lib\` dir (or subdir). @@ -82,24 +74,16 @@ Please create a \`version.rb\` file with a defined \`VERSION\` constant in your ); } - const [ - versionFile - ] = versionFiles; + const [versionFile] = versionFiles; - const fullVersionPath = path.resolve( - cwd, - versionFile - ); + const fullVersionPath = path.resolve(cwd, versionFile); - const versionContents = await readFile( - fullVersionPath, - 'utf8' - ); + const versionContents = await readFile(fullVersionPath, "utf8"); if (!versionRegex.test(versionContents)) { throw new SemanticReleaseError( `Couldn't find a valid version constant defined in \`${versionFile}\`.`, - 'EINVALIDVERSIONFILE', + "EINVALIDVERSIONFILE", `Your \`version.rb\` file must define a \`VERSION\` constant. Please define your gem's version a string constant named \`VERSION\` inside your \`version.rb\` file. @@ -110,13 +94,11 @@ Please define your gem's version a string constant named \`VERSION\` inside your return versionFile; }; -const verifyApiKey = async ( - { env, credentialsFile } -) => { +const verifyApiKey = async ({ env, credentialsFile }) => { if (!env.GEM_HOST_API_KEY) { throw new SemanticReleaseError( - 'No rubygems API key specified.', - 'ENOGEMAPIKEY', + "No rubygems API key specified.", + "ENOGEMAPIKEY", `A rubygems API key must be created and set in the \`GEM_HOST_API_KEY\` environment variable on you CI environment. You can retrieve an API key either from your \`~/.gem/credentials\` file or in your profile in [RubyGems.org](http://rubygems.org/). @@ -127,36 +109,33 @@ You can retrieve an API key either from your \`~/.gem/credentials\` file or in y await writeFile( credentialsFile, `---\n:rubygems_api_key: ${env.GEM_HOST_API_KEY}`, - 'utf8', + "utf8", ); }; export default async function verify( - { versionGlob = 'lib/**/version.rb' }, + { versionGlob = "lib/**/version.rb" }, { env, cwd }, - { credentialsFile } + { credentialsFile }, ) { - const { - name, - gemSpec - } = await loadGemspec({ - cwd + const { name, gemSpec } = await loadGemspec({ + cwd, }); const versionFile = await verifyVersionFile({ cwd, - versionGlob + versionGlob, }); await verifyApiKey({ env, cwd, - credentialsFile + credentialsFile, }); return { gemName: name, gemSpec, - versionFile + versionFile, }; -}; +} diff --git a/src/prepare.js b/src/prepare.js deleted file mode 100644 index 2068cd6..0000000 --- a/src/prepare.js +++ /dev/null @@ -1,162 +0,0 @@ -import path from 'node:path'; -import { rename, readFile, writeFile } from 'node:fs/promises'; - -import { execa } from 'execa'; -import { versionRegex } from './common.js'; - -const writeVersion = async ({ - versionFile, - nextVersion, - logger, - cwd -}) => { - const gemVersion = nextVersion.replace('-', '.'); - const fullVersionPath = path.resolve(cwd, versionFile); - const versionContents = await readFile(fullVersionPath, 'utf8'); - const newContents = versionContents.replace(versionRegex, `$1${gemVersion}$2`); - - logger.log('Writing version %s to `%s`', nextVersion, versionFile); - - await writeFile( - fullVersionPath, - newContents, - 'utf8' - ); - - return { gemVersion }; -}; - -const bundleInstall = async ( - { updateGemfileLock, cwd, env, logger, stdout, stderr } -) => { - const command = typeof updateGemfileLock === 'string' ? updateGemfileLock : 'bundle install'; - - logger.log('Updating lock file with command `%s`', command); - - const installResult = execa.command( - command, - { cwd, env } - ); - - installResult.stdout.pipe( - stdout, - { end: false } - ); - - installResult.stderr.pipe( - stderr, - { end: false } - ); - - await installResult; -}; - -const buildGem = async ( - { gemspec, gemName, version, cwd, env, logger, stdout, stderr } -) => { - const gemFile = `${gemName}-${version}.gem`; - - logger.log('Building gem `%s`', gemFile); - - const buildResult = execa( - 'gem', - ['build', gemspec], - { cwd, env } - ); - - buildResult.stdout.pipe( - stdout, - { end: false } - ); - - buildResult.stderr.pipe( - stderr, - { end: false } - ); - - await buildResult; - - return gemFile; -}; - - - - - - - - - - - - - - - - - -export default async function prepare( - { updateGemfileLock = false, gemFileDir = false }, - { nextRelease: { version }, cwd, env, logger, stdout, stderr }, - { versionFile, gemSpec, gemName }, -) { - const { - gemVersion - } = await writeVersion({ - versionFile, - nextVersion: version, - logger, - cwd - }); - - if (updateGemfileLock) { - await bundleInstall({ - updateGemfileLock, - cwd, - env, - logger, - stdout, - stderr - }); - } - - let gemFile = await buildGem({ - gemSpec, - gemName, - version: gemVersion, - cwd, - env, - logger, - stdout, - stderr, - }); - - if (gemFileDir) { - const gemFileSource = path.resolve( - cwd, - gemFile - ); - - const gemFileDestination = path.resolve( - cwd, - gemFileDir.trim(), - gemFile - ); - - if (gemFileSource !== gemFileDestination) { - await rename( - gemFileSource, - gemFileDestination - ); - } - - gemFile = path.join( - gemFileDir.trim(), - gemFile - ); - } - - return { - gemFile - }; -}; diff --git a/src/publish.js b/src/publish.js deleted file mode 100644 index b2b15fc..0000000 --- a/src/publish.js +++ /dev/null @@ -1,51 +0,0 @@ -import { unlink } from 'node:fs/promises'; -import { execa } from 'execa'; - -export default async function publish( - { gemHost, gemPublish = true, gemFileDir = false }, - { cwd, env, logger, nextRelease: { version }, stdout, stderr }, - { gemFile, gemName, credentialsFile }, -) { - if (gemPublish !== false) { - logger.log(`Publishing version ${version} to rubygems`); - - const args = [ - 'push', - gemFile, - '--config-file', - credentialsFile - ]; - - if (gemHost) { - args.push('--host', gemHost); - } - - const pushResult = execa( - 'gem', - args, - { cwd, env } - ); - - pushResult.stdout.pipe( - stdout, - { end: false } - ); - - pushResult.stderr.pipe( - stderr, - { end: false } - ); - - await pushResult; - - logger.log(`Published version ${version} of ${gemName} to rubygems`); - } else { - logger.log(`Skip publishing to rubygems because gemPublish is ${gemPublish !== false}`); - } - - if (gemFileDir === false) { - await unlink( - gemFile - ); - } -};