diff --git a/.deployment.config.json b/.deployment.config.json index ff87d26f6d5..eacb1fca148 100644 --- a/.deployment.config.json +++ b/.deployment.config.json @@ -234,7 +234,8 @@ "no_endpoint": true }, "package_rollout": { - "only_consider_changesets_after": "b244fe702d8e96d016a52715e92c8131acfde3ba" + "only_consider_changesets_after": "b244fe702d8e96d016a52715e92c8131acfde3ba", + "disabled": $[STOP_AT_DEV] }, "deployment_config_version": 7 } diff --git a/.github/workflows/masterbot.yml b/.github/workflows/masterbot.yml index 63ea20a1555..e41bb8fd1da 100644 --- a/.github/workflows/masterbot.yml +++ b/.github/workflows/masterbot.yml @@ -3,6 +3,7 @@ on: push: branches: - master + - 'prerelease/**' jobs: build: name: 'Build' diff --git a/Jenkinsfile b/Jenkinsfile index 47c15776e84..01d575545ae 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,10 +1,17 @@ +def parseSemanticVersion(String version) { + def semanticVersionRegex = /^(((([0-9]+)\.[0-9]+)\.[0-9]+)(?:\-.+)?)$/ + def (_, prerelease, patch, minor, major) = (version =~ semanticVersionRegex)[0] + return [major, minor, patch, prerelease] +} + node('heavy && linux && docker') { checkout scm def tag = sh(script: "git tag --contains", returnStdout: true).trim() def isBump = !!tag - def isMaster = env.BRANCH_NAME == 'master' + def isOnReleaseBranch = env.BRANCH_NAME == 'master' + def isOnPrereleaseBranch = env.BRANCH_NAME.startsWith('prerelease/') - if (!isMaster) { + if (!isOnReleaseBranch && !isOnPrereleaseBranch) { return } @@ -37,7 +44,11 @@ node('heavy && linux && docker') { withCredentials([ string(credentialsId: 'NPM_TOKEN', variable: 'NPM_TOKEN')]) { sh "echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc" - sh 'npm run npm:publish:alpha || true' + if (isOnReleaseBranch) { + sh 'npm run npm:publish:alpha || true' + } else { + sh 'npm run npm:publish || true' + } } } } @@ -48,23 +59,22 @@ node('heavy && linux && docker') { headless = readJSON file: 'packages/headless/package.json' atomic = readJSON file: 'packages/atomic/package.json' atomicReact = readJSON file: 'packages/atomic-react/package.json' - semanticVersionRegex = /^([^\.]*)\.[^\.]*/ - - (headlessMinor, headlessMajor) = (headless.version =~ semanticVersionRegex)[0] - (atomicMinor, atomicMajor) = (atomic.version =~ semanticVersionRegex)[0] - (atomicReactMinor, atomicReactMajor) = (atomicReact.version =~ semanticVersionRegex)[0] + (headlessMajor, headlessMinor, headlessPatch) = parseSemanticVersion(headless.version) + (atomicMajor, atomicMinor, atomicPatch) = parseSemanticVersion(atomic.version) + (atomicReactMajor, atomicReactMinor, atomicReactPatch) = parseSemanticVersion(atomicReact.version) sh "deployment-package package create --with-deploy \ --resolve HEADLESS_MAJOR_VERSION=${headlessMajor} \ --resolve HEADLESS_MINOR_VERSION=${headlessMinor} \ - --resolve HEADLESS_PATCH_VERSION=${headless.version} \ + --resolve HEADLESS_PATCH_VERSION=${headlessPatch} \ --resolve ATOMIC_MAJOR_VERSION=${atomicMajor} \ --resolve ATOMIC_MINOR_VERSION=${atomicMinor} \ - --resolve ATOMIC_PATCH_VERSION=${atomic.version} \ + --resolve ATOMIC_PATCH_VERSION=${atomicPatch} \ --resolve ATOMIC_REACT_MAJOR_VERSION=${atomicReactMajor} \ --resolve ATOMIC_REACT_MINOR_VERSION=${atomicReactMinor} \ - --resolve ATOMIC_REACT_PATCH_VERSION=${atomicReact.version} \ + --resolve ATOMIC_REACT_PATCH_VERSION=${atomicReactPatch} \ + --resolve STOP_AT_DEV=${!isOnReleaseBranch} \ || true" } } diff --git a/README.md b/README.md index 18dce9e5aac..e86a97f1446 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,29 @@ The following Visual Studio Code extensions are recommended: - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + +## Prerelease +### To start a major prerelease (e.g., `1.2.3` → `2.0.0-pre.0`) +1. Make sure that `HEAD` refers to a version bump on `master`. +2. Create a new branch prefixed with `prerelease/` (e.g., `prerelease/headless_atomic_v2`). +3. Run `npm run bump:version:major-prerelease -- @coveo/package1 @coveo/package2 @coveo/package3`. +4. Push the new commit and its tags to the branch. + +### Adding a new package to an already started prerelease +1. Checkout the prerelease branch. +2. Run `npm run bump:version:major-prerelease -- @coveo/package1` where `@coveo/package1` is the new package. +3. Push the new commit and its tags to the branch. + +### Updating a prerelease branch +1. Wait for `master` to reference a version bump. +2. Pull changes from `master` into the prerelease branch. + +### Officially releasing (e.g., `2.0.0-pre.15` → `2.0.0`) +1. Create a pull request from the prerelease branch to “master”. + * Associate with a Jira issue which QA will use to validate. + * Update the changelogs manually, adding all the changes from the last release to the new release. + * Update all dependents to use the prerelease version (include the `-pre.` suffix). +2. Wait for `master` to reference a version bump. +3. Squash as a version bump. + * You can copy the title & description of the last version bump commit on the prerelease branch. + * You must add the link to the Jira issue at the beginning of the description. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4b3198726ca..abcbe15427d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@commitlint/config-conventional": "17.0.3", "@commitlint/config-lerna-scopes": "17.0.2", "@commitlint/lint": "9.1.2", + "@coveo/semantic-monorepo-tools": "1.5.5", "@octokit/rest": "18.12.0", "@rollup/plugin-typescript": "8.3.4", "@trivago/prettier-plugin-sort-imports": "3.4.0", @@ -37,6 +38,7 @@ "concurrently": "6.5.1", "cross-fetch": "3.1.5", "cz-conventional-changelog": "3.3.0", + "detect-indent": "7.0.1", "esbuild": "0.14.2", "esbuild-plugin-alias": "0.2.1", "eslint": "8.22.0", @@ -3963,6 +3965,20 @@ "resolved": "packages/quantic", "link": true }, + "node_modules/@coveo/semantic-monorepo-tools": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@coveo/semantic-monorepo-tools/-/semantic-monorepo-tools-1.5.5.tgz", + "integrity": "sha512-zKd7ENzBoW9wPpKDleSa+w463HR0FfnqZO9vt27pbUjFuNAeQqwydI3nb9YGOJjen7USpfOA9D5zuJrjWTyVJg==", + "dev": true, + "dependencies": { + "conventional-changelog-writer": "^5.0.1", + "conventional-commits-parser": "^3.2.4", + "debug": "^4.3.3", + "git-raw-commits": "^2.0.11", + "semver": "^7.3.7", + "tempfile": "^4.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -24522,6 +24538,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/commitizen/node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/commitizen/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -27356,12 +27381,12 @@ } }, "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz", + "integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12.20" } }, "node_modules/detect-newline": { @@ -62198,6 +62223,31 @@ "node": ">=4" } }, + "node_modules/tempfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-4.0.0.tgz", + "integrity": "sha512-dNH6UWyI8kijDmLVb0IJvCG4JZ5uOmy40CLoi/dVySL49v0f0Y+jIN2rE6Hj85y8mIIya1vwpKZlL9jASs5ktg==", + "dev": true, + "dependencies": { + "temp-dir": "^2.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempfile/node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/tempy": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", @@ -66319,6 +66369,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/write-json-file/node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/write-json-file/node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -84899,6 +84958,20 @@ } } }, + "@coveo/semantic-monorepo-tools": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@coveo/semantic-monorepo-tools/-/semantic-monorepo-tools-1.5.5.tgz", + "integrity": "sha512-zKd7ENzBoW9wPpKDleSa+w463HR0FfnqZO9vt27pbUjFuNAeQqwydI3nb9YGOJjen7USpfOA9D5zuJrjWTyVJg==", + "dev": true, + "requires": { + "conventional-changelog-writer": "^5.0.1", + "conventional-commits-parser": "^3.2.4", + "debug": "^4.3.3", + "git-raw-commits": "^2.0.11", + "semver": "^7.3.7", + "tempfile": "^4.0.0" + } + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -102896,6 +102969,12 @@ "supports-color": "^7.1.0" } }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -105083,9 +105162,9 @@ "dev": true }, "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz", + "integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==", "dev": true }, "detect-newline": { @@ -132692,6 +132771,24 @@ "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", "dev": true }, + "tempfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-4.0.0.tgz", + "integrity": "sha512-dNH6UWyI8kijDmLVb0IJvCG4JZ5uOmy40CLoi/dVySL49v0f0Y+jIN2rE6Hj85y8mIIya1vwpKZlL9jASs5ktg==", + "dev": true, + "requires": { + "temp-dir": "^2.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true + } + } + }, "tempy": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", @@ -135763,6 +135860,12 @@ "write-file-atomic": "^3.0.0" }, "dependencies": { + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", diff --git a/package.json b/package.json index fcd7cdabe2d..785ecc9f8c9 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "commit": "git-cz", "setup:snyk": "node ./scripts/snyk/remove-samples-workspace.mjs", "bump:version": "node ./scripts/deploy/bump-version.mjs", + "bump:version:major-prerelease": "node ./scripts/deploy/manual-major-prerelease.mjs", + "npm:publish": "lerna run --stream npm:publish", "npm:publish:alpha": "lerna run --stream npm:publish:alpha", "npm:tag": "node ./scripts/deploy/update-npm-tag.mjs", "npm:tag:beta": "npm run npm:tag -- beta", @@ -58,7 +60,9 @@ "rimraf": "3.0.2", "semver": "7.3.7", "ts-jest": "27.1.5", - "typescript": "4.6.4" + "typescript": "4.6.4", + "@coveo/semantic-monorepo-tools": "1.5.5", + "detect-indent": "7.0.1" }, "workspaces": [ "packages/bueno", diff --git a/packages/atomic-angular/modify-package-json.js b/packages/atomic-angular/modify-package-json.js index 4f037218362..3cd6465a2f1 100644 --- a/packages/atomic-angular/modify-package-json.js +++ b/packages/atomic-angular/modify-package-json.js @@ -7,6 +7,8 @@ const packageJSON = JSON.parse(readFileSync(packageJSONPath)); if (!packageJSON.scripts) { packageJSON.scripts = {}; } +packageJSON.scripts['npm:publish'] = + 'node ../../../../../scripts/deploy/publish.mjs'; packageJSON.scripts['npm:publish:alpha'] = 'node ../../../../../scripts/deploy/publish.mjs alpha'; diff --git a/packages/atomic-angular/package.json b/packages/atomic-angular/package.json index eaa6bf54307..3da766b830f 100644 --- a/packages/atomic-angular/package.json +++ b/packages/atomic-angular/package.json @@ -6,6 +6,7 @@ "start": "ng serve", "build": "ng build && npm run copy:assets && npm run npm:modify:dist", "copy:assets": "ncp ../atomic/dist/atomic/assets projects/atomic-angular/dist/assets && ncp ../atomic/dist/atomic/lang projects/atomic-angular/dist/lang", + "npm:publish": "npm --prefix projects/atomic-angular/dist run npm:publish", "npm:publish:alpha": "npm --prefix projects/atomic-angular/dist run npm:publish:alpha", "npm:modify:dist": "node modify-package-json.js" }, diff --git a/packages/atomic-react/package.json b/packages/atomic-react/package.json index d7e6712c023..aefd99c8d95 100644 --- a/packages/atomic-react/package.json +++ b/packages/atomic-react/package.json @@ -15,6 +15,7 @@ "compile:cjs": "tsc -p tsconfig.cjs.json", "compile:iife": "rollup --config rollup.config.js", "compile": "concurrently \"npm run compile:esm\" \"npm run compile:cjs\" \"npm run compile:iife\"", + "npm:publish": "node ../../scripts/deploy/publish.mjs", "npm:publish:alpha": "node ../../scripts/deploy/publish.mjs alpha", "copy:assets": "ncp ../atomic/dist/atomic/assets dist/assets && ncp ../atomic/dist/atomic/lang dist/lang " }, diff --git a/packages/atomic/scripts/copy-dayjs-locales.mjs b/packages/atomic/scripts/copy-dayjs-locales.mjs index a810c8a870a..35fb777bbb2 100644 --- a/packages/atomic/scripts/copy-dayjs-locales.mjs +++ b/packages/atomic/scripts/copy-dayjs-locales.mjs @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import {promisify} from 'util'; const readFile = promisify(fs.readFile); diff --git a/packages/atomic/scripts/copy-headless.mjs b/packages/atomic/scripts/copy-headless.mjs index ffae12ed4c2..a7dafefa7f3 100644 --- a/packages/atomic/scripts/copy-headless.mjs +++ b/packages/atomic/scripts/copy-headless.mjs @@ -1,5 +1,5 @@ -import fs from 'fs'; import _ncp from 'ncp'; +import fs from 'node:fs'; import {promisify} from 'util'; const ncp = promisify(_ncp); diff --git a/packages/atomic/scripts/create-generated-folder.mjs b/packages/atomic/scripts/create-generated-folder.mjs index f1081becd2b..faf5a1920ae 100644 --- a/packages/atomic/scripts/create-generated-folder.mjs +++ b/packages/atomic/scripts/create-generated-folder.mjs @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import {promisify} from 'util'; const rm = promisify(fs.rm); diff --git a/packages/atomic/scripts/list-assets.mjs b/packages/atomic/scripts/list-assets.mjs index 59447a7601c..f770c972227 100644 --- a/packages/atomic/scripts/list-assets.mjs +++ b/packages/atomic/scripts/list-assets.mjs @@ -1,4 +1,4 @@ -import {readdirSync, writeFileSync} from 'fs'; +import {readdirSync, writeFileSync} from 'node:fs'; const files = readdirSync('dist/atomic/assets'); writeFileSync('docs/assets.json', JSON.stringify({assets: files})); diff --git a/packages/atomic/scripts/split-locales.mjs b/packages/atomic/scripts/split-locales.mjs index b48d2112640..e305d22b71f 100644 --- a/packages/atomic/scripts/split-locales.mjs +++ b/packages/atomic/scripts/split-locales.mjs @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import {promisify} from 'util'; const mkdir = promisify(fs.mkdir); diff --git a/packages/auth/package.json b/packages/auth/package.json index d895c90d45f..bdc7307c166 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -17,6 +17,7 @@ "test:watch": "jest --watch --colors --no-cache --silent=false", "typedefinitions": "tsc -p src/tsconfig.build.json -d --emitDeclarationOnly --declarationDir dist/definitions", "clean": "rimraf -rf dist/*", + "npm:publish": "node ../../scripts/deploy/publish.mjs", "npm:publish:alpha": "node ../../scripts/deploy/publish.mjs alpha", "e2e:saml": "vite manual-e2e/saml/" }, diff --git a/packages/headless/esbuild.mjs b/packages/headless/esbuild.mjs index 2a84bfbc855..3458432c724 100644 --- a/packages/headless/esbuild.mjs +++ b/packages/headless/esbuild.mjs @@ -1,7 +1,7 @@ import {build} from 'esbuild'; import alias from 'esbuild-plugin-alias'; -import {readFileSync, promises} from 'fs'; -import {resolve} from 'path'; +import {readFileSync, promises} from 'node:fs'; +import {resolve} from 'node:path'; import {umdWrapper} from '../../scripts/bundle/umd.mjs'; import {apacheLicense} from '../../scripts/license/apache.mjs'; diff --git a/packages/headless/scripts/extract-documentation.mjs b/packages/headless/scripts/extract-documentation.mjs index a0e78cfaac8..032da5c70b9 100644 --- a/packages/headless/scripts/extract-documentation.mjs +++ b/packages/headless/scripts/extract-documentation.mjs @@ -1,4 +1,4 @@ -import {readdirSync} from 'fs'; +import {readdirSync} from 'node:fs'; import {promisify} from 'util'; import {execute} from '../../../scripts/exec.mjs'; diff --git a/packages/quantic/package.json b/packages/quantic/package.json index bc2e6634e91..dc996a3f442 100644 --- a/packages/quantic/package.json +++ b/packages/quantic/package.json @@ -32,6 +32,7 @@ "doc:generate": "mkdir -p docs/out; jsdoc -c jsdoc-config.json > docs/out/quantic-docs.json", "package:create": "../../node_modules/.bin/ts-node scripts/build/create-package.ts --remove-translations", "package:create:publish": "../../node_modules/.bin/ts-node scripts/build/create-package.ts --remove-translations --promote", + "npm:publish": "node ../../scripts/deploy/publish.mjs", "npm:publish:alpha": "node ../../scripts/deploy/publish.mjs alpha", "preinstall": "node scripts/npm/check-sfdx-project.js", "postinstall": "node scripts/npm/setup-quantic.js" diff --git a/packages/samples/angular/scripts/link-hoisted-dependencies.mjs b/packages/samples/angular/scripts/link-hoisted-dependencies.mjs index 2a52ea218a4..8e464e6fa56 100644 --- a/packages/samples/angular/scripts/link-hoisted-dependencies.mjs +++ b/packages/samples/angular/scripts/link-hoisted-dependencies.mjs @@ -1,7 +1,7 @@ // TODO: remove this script when nohoist (https://github.com/npm/rfcs/issues/287) is introduced. import {execSync} from 'child_process'; -import {existsSync, symlinkSync, mkdirSync} from 'fs'; -import {resolve, parse} from 'path'; +import {existsSync, symlinkSync, mkdirSync} from 'node:fs'; +import {resolve, parse} from 'node:path'; import {workspacesRoot} from '../../../../scripts/packages.mjs'; /** @param {string} dependency */ diff --git a/scripts/deploy/bump-version.mjs b/scripts/deploy/bump-version.mjs index 34be4d8d61d..f45156afb94 100644 --- a/scripts/deploy/bump-version.mjs +++ b/scripts/deploy/bump-version.mjs @@ -1,30 +1,16 @@ -import {execute} from '../exec.mjs'; - -async function getHeadCommitHash() { - return execute('git', ['rev-parse', 'HEAD']); -} - -async function getHeadCommitTag() { - return execute('git', ['tag', '--points-at', 'HEAD']); -} - -async function checkoutLatestMaster() { - await execute('git', ['checkout', 'master']); - await execute('git', ['pull', 'origin', 'master']); -} +import { + isOnReleaseBranch, + getHowManyCommitsBehindUpstream, + getHeadCommitTag, +} from '../git.mjs'; +import {bumpPrereleaseVersionAndPush} from '../prerelease.mjs'; +import {bumpReleaseVersionAndPush} from '../release.mjs'; async function bumpVersionAndPush() { try { - await execute('npx', [ - '--no-install', - 'lerna', - 'version', - '--conventional-commits', - '--conventional-graduate', - '--no-private', - '--yes', - '--exact', - ]); + (await isOnReleaseBranch()) + ? await bumpReleaseVersionAndPush() + : await bumpPrereleaseVersionAndPush(); } catch (e) { console.error( 'Failed to bump version. Exiting to not publish local changes.', @@ -36,13 +22,9 @@ async function bumpVersionAndPush() { async function main() { try { - const buildCommitHash = await getHeadCommitHash(); - await checkoutLatestMaster(); - const masterCommitHash = await getHeadCommitHash(); - - if (buildCommitHash !== masterCommitHash) { + if ((await getHowManyCommitsBehindUpstream()) !== 0) { console.log( - 'Build commit does not match latest master commit. Skipping version bump.' + 'Build commit does not match latest commit. Skipping version bump.' ); return; } diff --git a/scripts/deploy/invalidate-cloudfront.mjs b/scripts/deploy/invalidate-cloudfront.mjs index d9f21e1f68e..084c02d7b97 100644 --- a/scripts/deploy/invalidate-cloudfront.mjs +++ b/scripts/deploy/invalidate-cloudfront.mjs @@ -1,22 +1,23 @@ import awsSDK from 'aws-sdk'; -import {resolve} from 'path'; +import {resolve} from 'node:path'; import {getPackageFromPath, workspacesRoot} from '../packages.mjs'; const cloudfront = new awsSDK.CloudFront(); -async function getMajorVersion(dir) { - const {version} = getPackageFromPath( - resolve(workspacesRoot, 'packages', dir, 'package.json') - ); +/** + * @param {import('../packages.mjs').PackageDir} dir + */ +function getMajorVersion(dir) { + const {version} = getPackageDefinitionFromPackageDir(resolve(dir)); return version.split('.')[0]; } async function main() { const pathsToInvalidate = [ '/atomic/latest/*', - `/atomic/v${await getMajorVersion('atomic')}/*`, + `/atomic/v${getMajorVersion('atomic')}/*`, '/headless/latest/*', - `/headless/v${await getMajorVersion('headless')}/*`, + `/headless/v${getMajorVersion('headless')}/*`, ]; const invalidationRequest = cloudfront.createInvalidation({ diff --git a/scripts/deploy/manual-major-prerelease.mjs b/scripts/deploy/manual-major-prerelease.mjs new file mode 100644 index 00000000000..77ebf91443e --- /dev/null +++ b/scripts/deploy/manual-major-prerelease.mjs @@ -0,0 +1,74 @@ +import {getCurrentVersion, gitAdd} from '@coveo/semantic-monorepo-tools'; +import {resolve} from 'node:path'; +import {execute} from '../exec.mjs'; +import {commitVersionBump, tagExists, tagPackages} from '../git.mjs'; +import { + getPackageDefinitionFromPackageName, + getPackagePathFromPackageDir, + updatePackageVersion, +} from '../packages.mjs'; +import {prereleaseSuffix} from '../prerelease.mjs'; + +/** + * @typedef {import('../packages.mjs').PackageDefinition} PackageDefinition + */ + +/** + * @param {number} packageName + * @param {number} major + * @param {number} prerelease + * @returns {Promise} + */ +async function getNewMajorPrerelease(packageName, major, prerelease = 0) { + const version = `${major}.0.0-${prereleaseSuffix}.${prerelease}`; + if (await tagExists(`${packageName}@${version}`)) { + return getNewMajorPrerelease(packageName, major, prerelease + 1); + } + return version; +} + +/** + * @param {PackageDefinition} packageDef + */ +async function getNewVersion(packageDef) { + const version = getCurrentVersion( + getPackagePathFromPackageDir(packageDef.packageDir) + ); + return getNewMajorPrerelease(packageDef.name, version.major + 1); +} + +/** + * @param {PackageDefinition[]} packages + */ +async function locallyBumpVersions(packages) { + for (const packageDef of packages) { + const newVersion = await getNewVersion(packageDef); + updatePackageVersion( + packageDef.name, + newVersion, + packages.map(({packageDir}) => packageDir) + ); + } + await execute('npm', ['install', '--package-lock-only']); +} + +/** + * @param {string[]} args + */ +export async function main(args) { + const packageNamesToBump = args.slice(2); + if (!packageNamesToBump.length) { + console.error('You must specify which packages to bump.'); + return; + } + const packages = packageNamesToBump.map(getPackageDefinitionFromPackageName); + await locallyBumpVersions(packages); + const updatedPackages = packageNamesToBump.map( + getPackageDefinitionFromPackageName + ); + await gitAdd('.'); + await commitVersionBump(updatedPackages); + await tagPackages(updatedPackages); +} + +main(process.argv); diff --git a/scripts/deploy/publish.mjs b/scripts/deploy/publish.mjs index 8a6a974cdf5..1489449afa6 100644 --- a/scripts/deploy/publish.mjs +++ b/scripts/deploy/publish.mjs @@ -1,42 +1,55 @@ -import {execSync, spawnSync} from 'child_process'; -import {readFileSync} from 'fs'; -import {resolve} from 'path'; -import {getPackageFromPath} from '../packages.mjs'; +import {resolve} from 'node:path'; +import {execute} from '../exec.mjs'; +import {isOnReleaseBranch} from '../git.mjs'; +import {getPackageDefinitionFromPath} from '../packages.mjs'; +import {isPrereleaseVersion} from '../prerelease.mjs'; const [tag] = process.argv.slice(2); -const pathToPackageJSON = resolve(process.cwd(), 'package.json'); -const pkg = getPackageFromPath(pathToPackageJSON); +const pathToPackageJSON = resolve(process.cwd(), './package.json'); +const pkg = getPackageDefinitionFromPath(pathToPackageJSON); const packageRef = `${pkg.name}@${pkg.version}`; -function shouldPublish() { +async function isAlreadyPublished() { try { - const packageVersionNotPublished = !execSync( - `npm view ${packageRef}` - ).toString().length; - return packageVersionNotPublished; + const isPublished = !!execute('npm', ['view', packageRef]); + return isPublished; } catch (e) { - const isFirstPublish = e.toString().includes('code E404'); - return isFirstPublish; + const isFirstPublish = e.includes('code E404'); + return !isFirstPublish; } } -function publish() { +async function shouldPublish() { + if (isAlreadyPublished()) { + return false; + } + if (await isOnReleaseBranch()) { + return true; + } + // On a prerelease branch, we may not want to prerelease some packages. + // We only want to prerelease packages that were already bumped to a prerelease version. + return isPrereleaseVersion(pkg.version); +} + +async function publish() { const params = ['publish', '--verbose', '--access', 'public']; if (tag) { params.push('--tag', tag); } - spawnSync('npm', params, { - stdio: 'inherit', - }); + await execute('npm', params); } -if (shouldPublish()) { - publish(); -} else { - console.info( - `Skipped publishing ${packageRef} (${ - tag || 'latest' - }) since it's already published.` - ); +async function main() { + if (await shouldPublish()) { + await publish(); + } else { + console.info( + `Skipped publishing ${packageRef} (${ + tag || 'latest' + }) since it's already published.` + ); + } } + +main(); diff --git a/scripts/deploy/update-npm-tag.mjs b/scripts/deploy/update-npm-tag.mjs index d7755711975..abada3a22b2 100644 --- a/scripts/deploy/update-npm-tag.mjs +++ b/scripts/deploy/update-npm-tag.mjs @@ -1,18 +1,14 @@ -import {resolve} from 'path'; +import {resolve} from 'node:path'; +import {promisify} from 'node:util'; import {execute} from '../exec.mjs'; import { packageDirsNpmTag, - getPackageFromPath, - workspacesRoot, + getPackageDefinitionFromPackageDir, } from '../packages.mjs'; async function main() { const requests = packageDirsNpmTag - .map((dir) => - getPackageFromPath( - resolve(workspacesRoot, 'packages', dir, 'package.json') - ) - ) + .map((dir) => getPackageDefinitionFromPackageDir(dir)) .map(({name, version}) => updateNpmTag(name, version)); await Promise.all(requests); diff --git a/scripts/exec.mjs b/scripts/exec.mjs index b046a1a55bb..cb8f56ce781 100644 --- a/scripts/exec.mjs +++ b/scripts/exec.mjs @@ -1,4 +1,5 @@ -import {spawn} from 'child_process'; +import {Buffer} from 'node:buffer'; +import {spawn} from 'node:child_process'; /** * @param {string} str @@ -15,8 +16,10 @@ function trimNewline(str) { export function execute(command, args = []) { return new Promise((resolve, reject) => { const proc = spawn(command, args); - let data = ''; - let error = ''; + /** @type {Buffer} */ + let dataBuffer = Buffer.alloc(0); + /** @type {Buffer} */ + let errorBuffer = Buffer.alloc(0); console.log( '\x1b[35m>\x1b[0m\xa0', @@ -28,7 +31,7 @@ export function execute(command, args = []) { proc.stdout.on('data', (chunk) => { console.log(trimNewline(chunk.toString())); - data += chunk.toString(); + dataBuffer = Buffer.concat([dataBuffer, chunk]); }); proc.stderr.on('data', (chunk) => { const exclamation = '\x1b[31m!\x1b[0m\xa0'; @@ -36,12 +39,17 @@ export function execute(command, args = []) { exclamation, trimNewline(chunk.toString()).replace('\n', '\n' + exclamation) ); - error += chunk.toString(); + errorBuffer = Buffer.concat([errorBuffer, chunk]); }); proc.on('exit', (code) => code === 0 - ? resolve(trimNewline(data)) - : reject(JSON.stringify({code, error: trimNewline(error)})) + ? resolve(trimNewline(dataBuffer.toString('utf8'))) + : reject( + JSON.stringify({ + code, + error: trimNewline(errorBuffer.toString('utf8')), + }) + ) ); }); } diff --git a/scripts/git.mjs b/scripts/git.mjs new file mode 100644 index 00000000000..c1f2c2c6294 --- /dev/null +++ b/scripts/git.mjs @@ -0,0 +1,61 @@ +import {gitTag} from '@coveo/semantic-monorepo-tools'; +import {readFileSync} from 'node:fs'; +import {resolve} from 'node:path'; +import {execute} from './exec.mjs'; + +/** + * @typedef {import('./packages.mjs').PackageDefinition} PackageDefinition + */ + +const releaseBranch = 'master'; + +async function getBranchName() { + return await execute('git', ['branch', '--show-current']); +} + +export async function isOnReleaseBranch() { + return (await getBranchName()) === releaseBranch; +} + +export async function getHowManyCommitsBehindUpstream() { + return parseInt(await execute('git', ['rev-list', '--count', 'HEAD..@{u}'])); +} + +export async function getHeadCommitTag() { + return await execute('git', ['tag', '--points-at', 'HEAD']); +} + +/** + * @param {string} tag + */ +export async function tagExists(tag) { + return !!(await execute('git', ['tag', '-l', tag])); +} + +/** + * @param {PackageDefinition[]} updatedPackages + */ +export async function commitVersionBump(updatedPackages) { + /** @type {{command: {version: {message: string}}}} */ + const lernaConfig = JSON.parse( + readFileSync(resolve('.', 'lerna.json')).toString() + ); + const lernaMessageSection = lernaConfig.command.version.message; + const updatedPackagesMessageSection = updatedPackages + .map( + (packageDef) => `\u0020-\u0020${packageDef.name}@${packageDef.version}` + ) + .join('\n'); + const message = `${lernaMessageSection}\n\n${updatedPackagesMessageSection}`; + await execute('git', ['commit', '--no-verify', '-m', message]); +} + +/** + * @param {PackageDefinition[]} packages + */ +export async function tagPackages(packages) { + for (const {name, version} of packages) { + const tag = `${name}@${version}`; + await gitTag(tag); + } +} diff --git a/scripts/notify-docs/published-ui-kit.mjs b/scripts/notify-docs/published-ui-kit.mjs index 3002d0ba5a2..fa3cefdb84e 100644 --- a/scripts/notify-docs/published-ui-kit.mjs +++ b/scripts/notify-docs/published-ui-kit.mjs @@ -1,16 +1,6 @@ import {Octokit} from '@octokit/rest'; -import {resolve} from 'path'; -import {getPackageFromPath, workspacesRoot} from '../packages.mjs'; - -const headlessPackageJson = getPackageFromPath( - resolve(workspacesRoot, 'packages', 'headless', 'package.json') -); -const atomicPackageJson = getPackageFromPath( - resolve(workspacesRoot, 'packages', 'atomic', 'package.json') -); -const quanticPackageJson = getPackageFromPath( - resolve(workspacesRoot, 'packages', 'quantic', 'package.json') -); +import {resolve} from 'node:path'; +import {getPackageDefinitionFromPackageDir} from '../packages.mjs'; const token = process.env.GITHUB_TOKEN || ''; const github = new Octokit({auth: token}); @@ -20,9 +10,12 @@ const repo = 'doc_jekyll-public-site'; const event_type = 'published_ui-kit_to_npm'; async function notify() { - const headless_version = headlessPackageJson.version; - const atomic_version = atomicPackageJson.version; - const quantic_version = quanticPackageJson.version; + const {version: headless_version} = + getPackageDefinitionFromPackageDir('headless'); + const {version: atomic_version} = + getPackageDefinitionFromPackageDir('atomic'); + const {version: quantic_version} = + getPackageDefinitionFromPackageDir('quantic'); const client_payload = {headless_version, atomic_version, quantic_version}; return github.repos.createDispatchEvent({ diff --git a/scripts/packages.mjs b/scripts/packages.mjs index c31de9d4951..7c2d9d1d4e5 100644 --- a/scripts/packages.mjs +++ b/scripts/packages.mjs @@ -1,8 +1,9 @@ -import {readFileSync} from 'fs'; -import {resolve} from 'path'; -import {fileURLToPath} from 'url'; +import detectIndent from 'detect-indent'; +import {existsSync, writeFileSync, readFileSync} from 'node:fs'; +import {resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; -export const packageDirsNpmTag = [ +export const packageDirsNpmTag = /** @type {const} */ ([ 'atomic', 'auth', 'bueno', @@ -10,8 +11,13 @@ export const packageDirsNpmTag = [ 'atomic-react', 'atomic-angular/projects/atomic-angular', 'quantic', -]; +]); +/** + * @typedef {(typeof packageDirsNpmTag)[number]} PackageDir + */ + +/** @type {PackageDir[]} */ export const packageDirsSnyk = ['headless', 'atomic']; export const workspacesRoot = resolve( @@ -20,11 +26,86 @@ export const workspacesRoot = resolve( '..' ); +/** + * @typedef PackageDefinition + * @property {string} name + * @property {string} version + * @property {PackageDir} packageDir + */ + +/** + * @param {PackageDir} packageDir + */ +export function getPackagePathFromPackageDir(packageDir) { + return resolve(workspacesRoot, 'packages', packageDir); +} + /** * @param {string} fullPath - * @returns {{name: string, version: string}} + * @returns {import('@lerna/package').RawManifest} */ -export function getPackageFromPath(fullPath) { - const {name, version} = JSON.parse(readFileSync(fullPath).toString()); - return {name, version}; +function getPackageManifestFromPackagePath(fullPath) { + return resolve(JSON.parse(readFileSync(fullPath).toString()), 'package.json'); +} + +/** + * @param {PackageDir} packageDir E.g.: `samples/some-package` + * @returns {PackageDefinition} + */ +export function getPackageDefinitionFromPackageDir(packageDir) { + const fullPath = getPackagePathFromPackageDir(packageDir); + if (!existsSync(fullPath)) { + throw `Could not find package at ${fullPath}.`; + } + const {name, version} = getPackageManifestFromPackagePath(fullPath); + return {name, version, packageDir}; +} + +/** + * @param {string} name + */ +export function getPackageDefinitionFromPackageName(name) { + return packageDirsNpmTag + .map((packageDir) => getPackageDefinitionFromPackageDir(packageDir)) + .find((packageDef) => packageDef.name === name); +} + +/** + * @param {string} packageName + * @param {string} newVersion + * @param {PackageDir[]} depdendenciesPackageDirs E.g.: [`samples/some-package`] + */ +export function updatePackageVersion( + packageName, + newVersion, + depdendenciesPackageDirs +) { + depdendenciesPackageDirs.forEach((packageDir) => { + const manifestPath = resolve( + getPackagePathFromPackageDir(packageDir), + 'package.json' + ); + const originalContentAsText = readFileSync(manifestPath).toString(); + const {indent} = detectIndent(originalContentAsText); + /** @type {import('@lerna/package').RawManifest} */ + const manifest = JSON.parse(originalContentAsText); + + if (manifest.name === packageName) { + manifest.version = newVersion; + } else { + if (packageName in (manifest.dependencies || {})) { + manifest.dependencies[packageName] = newVersion; + } + if (packageName in (manifest.devDependencies || {})) { + manifest.devDependencies[packageName] = newVersion; + } + if (packageName in (manifest.peerDependencies || {})) { + manifest.peerDependencies[packageName] = newVersion; + } + } + writeFileSync( + manifestPath, + JSON.stringify(manifest, undefined, indent || ' ') + ); + }); } diff --git a/scripts/prerelease.mjs b/scripts/prerelease.mjs new file mode 100644 index 00000000000..c8fd553b7f1 --- /dev/null +++ b/scripts/prerelease.mjs @@ -0,0 +1,78 @@ +import { + getNextVersion, + getCurrentVersion, + gitPush, + gitPushTags, + gitAdd, +} from '@coveo/semantic-monorepo-tools'; +import {resolve} from 'node:path'; +import semver from 'semver'; +import {execute} from './exec.mjs'; +import {commitVersionBump, tagPackages} from './git.mjs'; +import { + packageDirsNpmTag, + getPackageDefinitionFromPackageDir, + updatePackageVersion, + getPackagePathFromPackageDir, +} from './packages.mjs'; + +/** + * @typedef {import('./packages.mjs').PackageDefinition} PackageDefinition + */ + +export const prereleaseSuffix = 'pre'; + +/** + * @param {string} version + */ +export function isPrereleaseVersion(version) { + return !!semver.prerelease(version); +} + +function getPackagesToPrereleaseBump() { + return packageDirsNpmTag + .map(getPackageDefinitionFromPackageDir) + .filter(({version}) => isPrereleaseVersion(version)); +} + +/** + * @param {PackageDefinition} packageDef + */ +async function getNewVersion(packageDef) { + const version = getCurrentVersion( + getPackagePathFromPackageDir(packageDef.packageDir) + ); + return getNextVersion(version, { + type: 'prerelease', + preid: prereleaseSuffix, + }); +} + +/** + * @param {PackageDefinition[]} packages + */ +async function locallyBumpVersions(packages) { + for (const packageDef of packages) { + const newVersion = await getNewVersion(packageDef); + updatePackageVersion( + packageDef.name, + newVersion, + packages.map(({packageDir}) => packageDir) + ); + } + await execute('npm', ['install', '--package-lock-only']); +} + +export async function bumpPrereleaseVersionAndPush() { + console.info('Doing a prerelease version bump.'); + const packages = getPackagesToPrereleaseBump(); + await locallyBumpVersions(packages); + const updatedPackages = packages.map(({packageDir}) => + getPackageDefinitionFromPackageDir(packageDir) + ); + await gitAdd('.'); + await commitVersionBump(updatedPackages); + await tagPackages(updatedPackages); + await gitPush(); + await gitPushTags(); +} diff --git a/scripts/release.mjs b/scripts/release.mjs new file mode 100644 index 00000000000..e1cd5dc167a --- /dev/null +++ b/scripts/release.mjs @@ -0,0 +1,15 @@ +import {execute} from './exec.mjs'; + +export async function bumpReleaseVersionAndPush() { + console.info('Doing a release version bump.'); + await execute('npx', [ + '--no-install', + 'lerna', + 'version', + '--conventional-commits', + '--conventional-graduate', + '--no-private', + '--yes', + '--exact', + ]); +} diff --git a/scripts/reports/bundle-size/command.mjs b/scripts/reports/bundle-size/command.mjs index a7ad90683f3..c9c092e3443 100644 --- a/scripts/reports/bundle-size/command.mjs +++ b/scripts/reports/bundle-size/command.mjs @@ -1,5 +1,5 @@ -import {statSync, readdirSync} from 'fs'; -import {resolve} from 'path'; +import {statSync, readdirSync} from 'node:fs'; +import {resolve} from 'node:path'; import {execute} from '../../exec.mjs'; async function setup() { diff --git a/scripts/reports/bundle-size/time-series.mjs b/scripts/reports/bundle-size/time-series.mjs index 32a6cff5e89..57493eecd22 100644 --- a/scripts/reports/bundle-size/time-series.mjs +++ b/scripts/reports/bundle-size/time-series.mjs @@ -1,4 +1,4 @@ -import {existsSync, writeFileSync, appendFileSync} from 'fs'; +import {existsSync, writeFileSync, appendFileSync} from 'node:fs'; import {computeFileSizes} from './command.mjs'; const branch = process.env.GIT_BRANCH; diff --git a/scripts/snyk/remove-samples-workspace.mjs b/scripts/snyk/remove-samples-workspace.mjs index b87b81e7ece..af945f20624 100644 --- a/scripts/snyk/remove-samples-workspace.mjs +++ b/scripts/snyk/remove-samples-workspace.mjs @@ -1,6 +1,6 @@ -import {readFileSync, writeFileSync} from 'fs'; -import {resolve} from 'path'; -import {cwd} from 'process'; +import {readFileSync, writeFileSync} from 'node:fs'; +import {resolve} from 'node:path'; +import {cwd} from 'node:process'; import {packageDirsSnyk} from '../packages.mjs'; const packageJsonPath = resolve(cwd(), 'package.json');