diff --git a/.github/workflows/publish-unstable.yml b/.github/workflows/publish-unstable.yml new file mode 100644 index 000000000..157439ed0 --- /dev/null +++ b/.github/workflows/publish-unstable.yml @@ -0,0 +1,51 @@ +# For every push to the master branch, this publishes an NPM package to the +# "unstable" NPM tag. + +name: Publish Unstable + +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: publish-unstable-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + publish: + name: "NPM Publish" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable + registry-url: 'https://registry.npmjs.org' + - name: Locate Yarn Cache + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install Dependencies + run: yarn install --frozen-lockfile + # - name: Lint + # run: yarn lint + - name: Build + run: yarn build + + + - name: set versions + run: node ./test-packages/unstable-release/version.js + + - name: npm publish + run: node ./test-packages/unstable-release/publish.js + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/test-packages/unstable-release/.eslintrc.cjs b/test-packages/unstable-release/.eslintrc.cjs new file mode 100644 index 000000000..79667c4c4 --- /dev/null +++ b/test-packages/unstable-release/.eslintrc.cjs @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = { + root: true, + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, + }, + extends: ['eslint:recommended', 'plugin:prettier/recommended'], + env: { + browser: false, + node: true, + }, + overrides: [ + // node files + { + files: ['./.eslintrc.cjs'], + parserOptions: { + sourceType: 'script', + }, + env: { + browser: false, + node: true, + }, + plugins: ['node'], + extends: ['plugin:node/recommended'], + }, + ], +}; diff --git a/test-packages/unstable-release/README.md b/test-packages/unstable-release/README.md new file mode 100644 index 000000000..2140161f1 --- /dev/null +++ b/test-packages/unstable-release/README.md @@ -0,0 +1,9 @@ +# @glint/unstable-release + +Tools for supporting unstable releases off `main`. + +_The changes this package makes to the monorepo should not be committed_. + +```bash +node ./test-packages/unstable-release/version.js +``` diff --git a/test-packages/unstable-release/package.json b/test-packages/unstable-release/package.json new file mode 100644 index 000000000..747c53061 --- /dev/null +++ b/test-packages/unstable-release/package.json @@ -0,0 +1,20 @@ +{ + "name": "@glint/unstable-release", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "lint:js": "eslint . --cache --config ./.eslintrc.cjs" + }, + "dependencies": { + "execa": "^7.0.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.3" + }, + "devDependencies": { + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-node": "^11.1.0", + "prettier": "^2.5.1" + } +} diff --git a/test-packages/unstable-release/publish.js b/test-packages/unstable-release/publish.js new file mode 100644 index 000000000..0a35c9b88 --- /dev/null +++ b/test-packages/unstable-release/publish.js @@ -0,0 +1,31 @@ + +import { execaCommand } from 'execa'; +import { listPublicWorkspaces } from './workspaces.js'; +import { dirname } from 'path'; + +async function publish() { + let publicWorkspaces = await listPublicWorkspaces(); + + const errors = []; + + for (let workspace of publicWorkspaces) { + console.info(`Publishing ${workspace}`); + try { + await execaCommand('npm publish --tag=unstable --verbose --access=public', { cwd: dirname(workspace) }); + } catch (err) { + console.info(`Publishing ${workspace} has failed. A full list of errors will be printed at the end of this run`); + errors.push(err); + continue; + } + + console.info(`Publishing ${workspace} completed successfully!`); + } + + if (errors.length) { + console.error('Errors were encountered while publishing these packages'); + errors.forEach(error => console.log(error.stderr)); + process.exit(1); + } +} + +publish(); diff --git a/test-packages/unstable-release/version.js b/test-packages/unstable-release/version.js new file mode 100644 index 000000000..5a876f3a6 --- /dev/null +++ b/test-packages/unstable-release/version.js @@ -0,0 +1,66 @@ +import fse from 'fs-extra'; +import { listPublicWorkspaces, currentSHA } from './workspaces.js'; + +/** + * This is an I/O heavy way to do this, but hopefully it reads easy + * + * these functions change the CWD as they go, returnning to he previous + * CWD via finally blocks upon finish. + */ +async function updateVersions() { + let sha = await currentSHA(); + + let publicWorkspaces = await listPublicWorkspaces(); + + // Pick new versions for each package + for (let workspace of publicWorkspaces) { + console.info(`Setting version of ${workspace}`); + await setVersion(sha, workspace); + } + + // Update each dependency to use the new versions + for (let workspace of publicWorkspaces) { + console.info(`Updating dependencies of ${workspace}`); + await updateDependencies(workspace); + } +} + +updateVersions(); + +//////////////////////////////////////////// + +const NEW_VERSIONS = {}; + +async function setVersion(sha, filePath) { + let json = await fse.readJSON(filePath); + + // we need to at the very least bump the patch version of the unstable packages so + // that ^ dependenies won't pick up the stable versions + const [major, minor, patch] = json.version.split('.'); + + json.version = `${major}.${minor}.${parseInt(patch) + 1}-unstable.${sha}`; + + NEW_VERSIONS[json.name] = json.version; + + await fse.writeJSON(filePath, json, { spaces: 2 }); +} + +async function updateDependencies(filePath) { + let json = await fse.readJSON(filePath); + + for (let [dep, version] of Object.entries(NEW_VERSIONS)) { + if ((json.dependencies || {})[dep]) { + json.dependencies[dep] = version; + } + + if ((json.devDependencies || {})[dep]) { + json.devDependencies[dep] = version; + } + + if ((json.peerDependencies || {})[dep]) { + json.peerDependencies[dep] = version; + } + } + + await fse.writeJSON(filePath, json, { spaces: 2 }); +} diff --git a/test-packages/unstable-release/workspaces.js b/test-packages/unstable-release/workspaces.js new file mode 100644 index 000000000..d52294c8a --- /dev/null +++ b/test-packages/unstable-release/workspaces.js @@ -0,0 +1,30 @@ +import globby from 'globby'; +import { execaCommand } from 'execa'; +import fse from 'fs-extra'; + +/** + * All publishable packages are in packages/* + * + * We could read package.json#workspaces, but then we'd have more to filter out. + */ +export async function listPublicWorkspaces() { + let filePaths = await globby(['packages/*/package.json']); + + let result = []; + + for (let filePath of filePaths) { + let packageJson = await fse.readJSON(filePath); + + if (packageJson.private) continue; + + result.push(filePath); + } + + return result; +} + +export async function currentSHA() { + let { stdout } = await execaCommand(`git rev-parse --short HEAD`); + + return stdout.trim(); +} diff --git a/yarn.lock b/yarn.lock index b4fcd663b..2f1205780 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6660,7 +6660,7 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^7.1.1: +execa@^7.0.0, execa@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==