diff --git a/.github/.yarnrc.publish.yml b/.github/.yarnrc.publish.yml new file mode 100644 index 0000000..866e203 --- /dev/null +++ b/.github/.yarnrc.publish.yml @@ -0,0 +1,10 @@ +# Auth config for publishing to npm registry. +# It's put in /.github so it's only picked up by +# github actions. + +npmPublishRegistry: 'https://registry.npmjs.org' + +npmRegistries: + //registry.npmjs.org: + npmAlwaysAuth: true + npmAuthToken: ${NODE_AUTH_TOKEN} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0cd8a32..b8b4a00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ on: branches: [main] jobs: - build: + Test: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml new file mode 100644 index 0000000..78e4f58 --- /dev/null +++ b/.github/workflows/publish-prerelease.yml @@ -0,0 +1,34 @@ +name: 'Publish Prerelease' +on: + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '16.x' + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + - uses: actions/cache@v2 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - run: yarn install + - name: Build the theatre packages + run: yarn build + - name: Update .yarnrc.yml with the auth config for the npmPublishRegistry + run: cat .github/.yarnrc.publish.yml >> .yarnrc.yml + - name: Publish the theatre packages + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # LATEST_COMMIT_HASH: ${{ github.event.pull_request.head.sha }} + run: yarn zx scripts/prerelease.mjs diff --git a/.yarnrc.yml b/.yarnrc.yml index 336881c..3502e1c 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -2,8 +2,8 @@ nodeLinker: node-modules plugins: - path: .yarn/plugins/@yarnpkg/plugin-compat.cjs - spec: "@yarnpkg/plugin-compat" + spec: '@yarnpkg/plugin-compat' - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" + spec: '@yarnpkg/plugin-interactive-tools' yarnPath: .yarn/releases/yarn-3.2.0.cjs diff --git a/scripts/prerelease.mjs b/scripts/prerelease.mjs new file mode 100644 index 0000000..4611b7c --- /dev/null +++ b/scripts/prerelease.mjs @@ -0,0 +1,149 @@ +/** + * This script publishes the insider packages from the CI. You can't run it locally unless you have a a valid npm access token and you store its value in the `NPM_TOKEN` environmental variable. + */ + +import os from 'os' +import path from 'path' + +const packagesToPublish = [ + '@theatre/core', + '@theatre/studio', + '@theatre/dataverse', + '@theatre/react', + '@theatre/browser-bundles', + '@theatre/r3f', +] + +/** + * Receives a version number and returns it without the tags, if there are any + * + * @param {string} version - Version number + * @returns Version number without the tags + * + * @example + * ```javascript + * const version_1 = '0.4.8-dev3-ec175817' + * const version_2 = '0.4.8' + * + * stripTag(version_1) === stripTag(version_2) === '0.4.8' // returns `true` + * ``` + */ +function stripTag(version) { + const regExp = /^[0-9]+\.[0-9]+\.[0-9]+/g + const matches = version.match(regExp) + if (!matches) { + throw new Error(`Version number not found in "${version}"`) + } + + return matches[0] +} + +/** + * Creates a version number like `0.4.8-insiders.ec175817` + * + * @param {string} packageName - Name of the package + * @param {string} commitHash - A commit hash + */ +function getNewVersionName(packageName, commitHash) { + // The `r3f` package has its own release schedule, so its version numbers + // are almost always different from the rest of the packages. + const pathToPackageJson = + packageName === '@theatre/r3f' + ? path.resolve(__dirname, '..', 'packages', 'r3f', 'package.json') + : path.resolve(__dirname, '../', './package.json') + + const jsonData = JSON.parse( + fs.readFileSync(pathToPackageJson, {encoding: 'utf-8'}), + ) + const strippedVersion = stripTag(jsonData.version) + + return `${strippedVersion}-insiders.${commitHash}` +} + +/** + * Assigns the new versions to the packages + * + * @param {{name: string, location: string}[]} workspacesListObjects - An Array of objects containing information about the workspaces + * @param {string} latestCommitHash - Hash of the latest commit + */ +async function assignVersions(workspacesListObjects, latestCommitHash) { + for (const workspaceData of workspacesListObjects) { + const pathToPackage = path.resolve( + __dirname, + '../', + workspaceData.location, + './package.json', + ) + + const original = JSON.parse( + fs.readFileSync(pathToPackage, {encoding: 'utf-8'}), + ) + + let {version, dependencies, peerDependencies, devDependencies} = original + // The @theatre/r3f package curently doesn't track the same version number of the other packages like @theatre/core, + // so we need to generate version numbers independently for each package + version = getNewVersionName(workspaceData.name, latestCommitHash) + // Normally we don't have to override the package versions in dependencies because yarn would already convert + // all the "workspace:*" versions to a fixed version before publishing. However, packages like @theatre/studio + // have a peerDependency on @theatre/core set to "*" (meaning they would work with any version of @theatre/core). + // This is not the desired behavior in pre-release versions, so here, we'll fix those "*" versions to the set version. + for (const deps of [dependencies, peerDependencies, devDependencies]) { + if (!deps) continue + for (const wpObject of workspacesListObjects) { + if (deps[wpObject.name]) { + deps[wpObject.name] = getNewVersionName( + wpObject.name, + latestCommitHash, + ) + } + } + } + const newJson = { + ...original, + version, + dependencies, + peerDependencies, + devDependencies, + } + fs.writeFileSync( + path.join(pathToPackage), + JSON.stringify(newJson, undefined, 2), + {encoding: 'utf-8'}, + ) + await $`prettier --write ${workspaceData.location + '/package.json'}` + } +} + +;(async function () { + // @ts-ignore ignore + process.env.THEATRE_IS_PUBLISHING = true + // In the CI `git log -1` points to a fake merge commit, + // so we have to use the value of a special GitHub context variable + // through the `GITHUB_SHA` environmental variable. + + // The length of the abbreviated commit hash can change, that's why we + // need the lenght of the fake merge commit's abbreviated hash. + const fakeMergeCommitHashLength = (await $`git log -1 --pretty=format:%h`) + .stdout.length + + const latestCommitHash = process.env.GITHUB_SHA.slice( + 0, + fakeMergeCommitHashLength, + ) + + const workspacesListString = await $`yarn workspaces list --json` + const workspacesListObjects = workspacesListString.stdout + .split(os.EOL) + // strip out empty lines + .filter(Boolean) + .map((x) => JSON.parse(x)) + + await assignVersions(workspacesListObjects, latestCommitHash) + + await Promise.all( + packagesToPublish.map((workspaceName) => { + const npmTag = 'insiders' + return $`yarn workspace ${workspaceName} npm publish --access public --tag ${npmTag}` + }), + ) +})()