diff --git a/.github/actions/publish_to_npm/action.yml b/.github/actions/publish_to_npm/action.yml index 0f9267ea5c..d02cd69d08 100644 --- a/.github/actions/publish_to_npm/action.yml +++ b/.github/actions/publish_to_npm/action.yml @@ -5,16 +5,12 @@ inputs: runs: using: composite steps: - # TODO: Figure out a way to specify whether to publish to "latest" or "next" tag - name: Publish to NPM run: |- # Add NPM token to allow publishing echo "//registry.npmjs.org/:_authToken=${{ inputs.NODE_AUTH_TOKEN }}" > ~/.npmrc - # Publish all changed packages. The "from-package" arg means "look - # at the version numbers in each package.json file and if that doesn't - # exist on NPM, publish" - npm run lerna -- publish from-package --yes --no-verify-access + npm run publish-to-npm # Cleanup rm ~/.npmrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5739002e4b..a225e504eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -272,7 +272,7 @@ jobs: - name: Generate ${{ matrix.template }} project run: |- - node packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js --outputDir generated-${{ matrix.template }} + node packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js --outputDir ${{ env.PROJECT_DIR }} env: GENERATOR_PRESET: ${{ matrix.template }} timeout-minutes: 7 diff --git a/package.json b/package.json index de368ceec3..a9ee0b7721 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,10 @@ "name": "pwa-kit", "version": "3.0.0-dev", "scripts": { - "bump-version": "node ./scripts/bump-version.js", + "bump-version": "node ./scripts/bump-version/index.js", + "bump-version:retail-react-app": "node ./scripts/bump-version/index.js --package=retail-react-app", + "bump-version:commerce-sdk-react": "node ./scripts/bump-version/index.js --package=commerce-sdk-react-preview", + "publish-to-npm": "node ./scripts/publish-to-npm.js", "format": "lerna run --stream format", "preinstall": "node ./scripts/check-version.js", "postinstall": "node ./scripts/bootstrap.js", diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json index 7c6af23973..2ee1309888 100644 --- a/packages/template-retail-react-app/package.json +++ b/packages/template-retail-react-app/package.json @@ -2,6 +2,7 @@ "name": "retail-react-app", "version": "3.0.0-dev", "license": "See license in LICENSE", + "author": "cc-pwa-kit@salesforce.com", "ccExtensibility": { "extendable": ["retail-react-app"] }, diff --git a/scripts/.eslintrc.js b/scripts/.eslintrc.js new file mode 100644 index 0000000000..f873c90af5 --- /dev/null +++ b/scripts/.eslintrc.js @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +module.exports = { + extends: [require.resolve('../packages/pwa-kit-dev/dist/configs/eslint/no-react')] +} diff --git a/scripts/.prettierrc.yaml b/scripts/.prettierrc.yaml new file mode 100644 index 0000000000..33069bf2b2 --- /dev/null +++ b/scripts/.prettierrc.yaml @@ -0,0 +1,7 @@ +printWidth: 100 +singleQuote: true +semi: false +bracketSpacing: false +tabWidth: 4 +arrowParens: 'always' +trailingComma: 'none' diff --git a/scripts/bump-version.js b/scripts/bump-version.js deleted file mode 100644 index e886598650..0000000000 --- a/scripts/bump-version.js +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env node -/* - * Copyright (c) 2023, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -const sh = require('shelljs') -const path = require('path') - -sh.set('-e') -const lernaConfigPath = path.join(__dirname, '..', 'lerna.json') -const rootPkgPath = path.join(__dirname, '..', 'package.json') -const rootPkgLockPath = path.join(__dirname, '..', 'package-lock.json') - -const main = () => { - sh.exec(`lerna version --no-push --no-git-tag-version --yes ${process.argv.slice(2).join(' ')}`) - sh.exec(`npm install`) - const lernaConfig = JSON.parse(sh.cat(lernaConfigPath)) - const rootPkg = JSON.parse(sh.cat(rootPkgPath)) - const rootLockPkg = JSON.parse(sh.cat(rootPkgLockPath)) - - // find all monorepo packages, look inside each package json, find peerDependency that is a monorepo package - // and update it with a new version - const {stdout} = sh.exec('lerna list --all --json', {silent: true}) - const packages = JSON.parse(stdout.toString()) - const lernaPackageNames = packages.map((pkg) => pkg.name) - packages.forEach(({location}) => { - const pkgFilePath = path.join(location, 'package.json') - const pkg = JSON.parse(sh.cat(pkgFilePath)) - const peerDependencies = pkg.peerDependencies - if (!peerDependencies) return - Object.keys(peerDependencies).forEach((dep) => { - if (lernaPackageNames.includes(dep)) { - console.log(`Found lerna local package ${dep} as a peer dependency of ${pkg.name}.`) - peerDependencies[dep] = `^${lernaConfig.version}` - new sh.ShellString(JSON.stringify(pkg, null, 2)).to(pkgFilePath) - } - }) - }) - - // update versions for root package and root package lock - rootPkg.version = lernaConfig.version - rootLockPkg.version = lernaConfig.version - new sh.ShellString(JSON.stringify(rootPkg, null, 2)).to(rootPkgPath) - new sh.ShellString(JSON.stringify(rootLockPkg, null, 2)).to(rootPkgLockPath) -} - -main() diff --git a/scripts/bump-version/independent-pkg-version.js b/scripts/bump-version/independent-pkg-version.js new file mode 100644 index 0000000000..e194e94c29 --- /dev/null +++ b/scripts/bump-version/independent-pkg-version.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable @typescript-eslint/no-var-requires */ +const sh = require('shelljs') +const path = require('path') +const {saveJSONToFile, setPackageVersion} = require('../utils') + +// Exit upon error +sh.set('-e') + +const monorepoPackages = JSON.parse(sh.exec('lerna list --all --json', {silent: true})) + +const pathToPackage = (packageName) => { + const pkg = monorepoPackages.find((pkg) => pkg.name === packageName) + return pkg?.location +} + +// Meant for setting the version of a package that has its own independent version +const main = () => { + const version = process.argv[2] + const pkgName = process.argv[3] + const otherPackages = monorepoPackages.filter((pkg) => pkg.name !== pkgName) + + setPackageVersion(version, {cwd: pathToPackage(pkgName)}) + + // Update other packages who depend on the current package + otherPackages.forEach(({location}) => { + const pathToPkgJson = path.join(location, 'package.json') + const pkgJson = JSON.parse(sh.cat(pathToPkgJson)) + + if (pkgJson.dependencies?.[pkgName]) { + pkgJson.dependencies[pkgName] = version + } else if (pkgJson.devDependencies?.[pkgName]) { + pkgJson.devDependencies[pkgName] = version + } + + saveJSONToFile(pkgJson, pathToPkgJson) + }) +} + +main() diff --git a/scripts/bump-version/index.js b/scripts/bump-version/index.js new file mode 100644 index 0000000000..efe58340e4 --- /dev/null +++ b/scripts/bump-version/index.js @@ -0,0 +1,129 @@ +#!/usr/bin/env node +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable @typescript-eslint/no-var-requires */ +const sh = require('shelljs') +const path = require('path') +const program = require('commander') +const {saveJSONToFile, setPackageVersion} = require('../utils') + +// Exit upon error +sh.set('-e') + +const rootPath = path.join(__dirname, '..', '..') +const lernaConfigPath = path.join(rootPath, 'lerna.json') + +const monorepoPackages = JSON.parse(sh.exec('lerna list --all --json', {silent: true})) +const monorepoPackageNames = monorepoPackages.map((pkg) => pkg.name) + +const INDEPENDENT_PACKAGES = ['retail-react-app', 'commerce-sdk-react-preview'] +const independentPackages = INDEPENDENT_PACKAGES.map((pkgName) => + monorepoPackages.find((pkg) => pkg.name === pkgName) +) + +/** + * @param {import('commander').CommanderStatic} program + */ +const main = (program) => { + const targetVersion = program.args[0] + if (!targetVersion) { + program.help() + } + + const opts = program.opts() + if (opts.package !== 'sdk') { + // Assume that we're bumping the version of package that has its own independent version + + const script1 = path.join(__dirname, 'independent-pkg-version.js') + sh.exec(`node ${script1} ${targetVersion} ${opts.package}`) + + const script2 = path.join(__dirname, 'pwa-kit-deps-version.js') + const updateDepsBehaviour = /-dev\b/.test(targetVersion) ? 'sync' : 'latest' + sh.exec(`node ${script2} ${updateDepsBehaviour} ${opts.package}`) + + // After updating the dependencies, let's update the package lock files + sh.exec('npm install') + + process.exit(0) + } + + sh.exec(`lerna version --exact --no-push --no-git-tag-version --yes ${targetVersion}`) + // `--exact` above is for pinning the version of the pwa-kit dependencies + // https://github.com/lerna/lerna/tree/main/libs/commands/version#--exact + + const lernaConfig = JSON.parse(sh.cat(lernaConfigPath)) + const newMonorepoVersion = lernaConfig.version + + // update versions for root package and root package lock + setPackageVersion(newMonorepoVersion, {cwd: rootPath}) + + independentPackages.forEach((pkg) => { + const {location, version: oldVersion} = pkg + // Restore and then increment to the next pre-release version + // TODO: is it possible to _not_ trigger the lifecycle scripts? See CHANGELOG.md + setPackageVersion(oldVersion, {cwd: location}) + setPackageVersion('prerelease', {cwd: location}) + + const newVersion = JSON.parse(sh.exec('npm pkg get version', {cwd: location, silent: true})) + pkg.version = newVersion + }) + + // Now that all of the package version updates are done, + // let's make sure some dependencies' versions are updated accordingly + monorepoPackages.forEach(({location}) => { + const pathToPkgJson = path.join(location, 'package.json') + const pkgJson = JSON.parse(sh.cat(pathToPkgJson)) + + updatePeerDeps(pkgJson, newMonorepoVersion) + updateDeps(pkgJson) + + saveJSONToFile(pkgJson, pathToPkgJson) + }) + + // After updating the dependencies, let's update the package lock files + sh.exec('npm install') + + sh.echo('\nVersions of packages in the monorepo:\n') + sh.exec('lerna list --all --long') +} + +const updatePeerDeps = (pkgJson, newMonorepoVersion) => { + const peerDependencies = pkgJson.peerDependencies + if (!peerDependencies) return + + Object.keys(peerDependencies).forEach((dep) => { + if (monorepoPackageNames.includes(dep)) { + console.log(`Found lerna local package ${dep} as a peer dependency of ${pkgJson.name}.`) + peerDependencies[dep] = `^${newMonorepoVersion}` + } + }) +} + +const updateDeps = (pkgJson) => { + independentPackages.forEach((independentPkg) => { + const newVersion = independentPkg.version + + if (pkgJson.dependencies?.[independentPkg.name]) { + pkgJson.dependencies[independentPkg.name] = newVersion + } else if (pkgJson.devDependencies?.[independentPkg.name]) { + pkgJson.devDependencies[independentPkg.name] = newVersion + } + }) +} + +program.description('Bump the version of a package in our monorepo') +program.arguments('') + +program.option( + '-p, --package ', + 'the package name or an alias to a group of packages', + 'sdk' +) + +program.parse(process.argv) +main(program) diff --git a/scripts/bump-version/pwa-kit-deps-version.js b/scripts/bump-version/pwa-kit-deps-version.js new file mode 100644 index 0000000000..8ea56c2eb5 --- /dev/null +++ b/scripts/bump-version/pwa-kit-deps-version.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable @typescript-eslint/no-var-requires */ +const sh = require('shelljs') +const path = require('path') +const {saveJSONToFile} = require('../utils') + +// Exit upon error +sh.set('-e') + +const publicPackages = JSON.parse(sh.exec('lerna list --json', {silent: true})) + +const pathToPackage = (packageName) => { + const pkg = publicPackages.find((pkg) => pkg.name === packageName) + return pkg?.location +} + +// Assuming that this is run within a specific package, +// the script would update its pwa-kit/sdk dependencies. +const main = () => { + const updateBehaviour = process.argv[2] + const packageName = process.argv[3] + + const pathToPackageJson = path.join(pathToPackage(packageName), 'package.json') + const pkgJson = JSON.parse(sh.cat(pathToPackageJson)) + + if (updateBehaviour === 'latest') { + publicPackages.forEach(({name}) => { + if (pkgJson.dependencies?.[name]) { + pkgJson.dependencies[name] = getLatestVersion(name) + } else if (pkgJson.devDependencies?.[name]) { + pkgJson.devDependencies[name] = getLatestVersion(name) + } + }) + } else if (updateBehaviour === 'sync') { + // Sync version with what's in the monorepo + publicPackages.forEach(({version, name}) => { + if (pkgJson.dependencies?.[name]) { + pkgJson.dependencies[name] = version + } else if (pkgJson.devDependencies?.[name]) { + pkgJson.devDependencies[name] = version + } + }) + } + + saveJSONToFile(pkgJson, pathToPackageJson) +} + +const getLatestVersion = (pkgName) => { + return sh.exec(`npm info ${pkgName}@latest version`, {silent: true}).trim() +} + +main() diff --git a/scripts/publish-to-npm.js b/scripts/publish-to-npm.js new file mode 100644 index 0000000000..3566167bce --- /dev/null +++ b/scripts/publish-to-npm.js @@ -0,0 +1,96 @@ +#!/usr/bin/env node +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +const sh = require('shelljs') + +// The branch naming convention for releasing a particular package is: release--1.1.x +// For example: 'release-retail-react-app-1.1.x' +const RELEASE_ONE_PACKAGE = /release-([-a-z]+)-\d+\./i + +const main = () => { + // Exiting early if working tree is not clean + verifyCleanWorkingTree() + + const branchName = sh.exec('git branch --show-current', {silent: true}).trim() + // DEBUG + // const branchName = 'release-3.0.x' + // const branchName = 'release-retail-react-app-1.0.x' + + console.log('--- Given the current branch:', branchName) + + const matched = branchName.match(RELEASE_ONE_PACKAGE) + const packageName = matched && matched[1] + + if (packageName) { + console.log(`--- Releasing ${packageName}...`) + publishPackages([packageName]) + } else { + console.log('--- Releasing all packages...') + publishPackages() + } +} + +const publishPackages = (packages = []) => { + verifyCleanWorkingTree() + + const publicPackages = JSON.parse(sh.exec('lerna list --json', {silent: true})) + const packagesToIgnore = publicPackages.filter((pkg) => !packages.includes(pkg.name)) + + const cleanUp = () => { + // Undo the temporary commit + sh.exec('git reset HEAD~1', {silent: true}) + + packagesToIgnore.forEach((pkg) => { + sh.exec('npm pkg delete private', {cwd: pkg.location}) + }) + } + + const publishSomePackagesOnly = packages.length > 0 + if (publishSomePackagesOnly) { + packagesToIgnore.forEach((pkg) => { + sh.exec('npm pkg set private=true', {cwd: pkg.location}) + }) + + sh.exec('git add .', {silent: true}) + sh.exec('git commit -m "temporary commit to have clean working tree"', {silent: true}) + } + + // Why do we still want `lerna publish`? It turns out that we do need it. Sometimes we wanted some behaviour that's unique to Lerna. + // For example: we have `publishConfig.directory` in some package.json files that only Lerna knows what to do with it. + // https://github.com/lerna/lerna/tree/main/libs/commands/publish#publishconfigdirectory + + const {stderr, code} = sh.exec( + `npm run lerna -- publish from-package --yes --no-verify-access --pre-dist-tag next ${ + process.env.CI ? '' : '--registry http://localhost:4873/' + }` + ) + // DEBUG + // console.log('--- Would publish these public packages to npm:') + // sh.exec('lerna list --long') + + // Make sure to clean up, no matter if there's an error or not + if (publishSomePackagesOnly) { + cleanUp() + } + + if (stderr) { + process.exit(code) + } +} + +const verifyCleanWorkingTree = () => { + const isWorkingTreeClean = sh.exec('git status --porcelain', {silent: true}).trim() === '' + if (!isWorkingTreeClean) { + console.error( + 'There are some uncommitted changes. `lerna publish` expects a clean working tree.' + ) + process.exit(1) + } +} + +main() diff --git a/scripts/smoke-test-npm-scripts.js b/scripts/smoke-test-npm-scripts.js index 096ad8e786..4bc95090d1 100644 --- a/scripts/smoke-test-npm-scripts.js +++ b/scripts/smoke-test-npm-scripts.js @@ -40,7 +40,8 @@ const main = (opts) => { /^build$/, /^start.*$/, /^compile-translations.*$/, - /^extract-default-translations.*$/ + /^extract-default-translations.*$/, + /^bump-version.*$/ ] const scripts = Object.keys(pkg.scripts).filter( diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 0000000000..c58afd90ae --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +const sh = require('shelljs') + +const saveJSONToFile = (json, filePath) => { + const jsonString = JSON.stringify(json, null, 2) + // Make sure there's a newline at end of file + new sh.ShellString(`${jsonString}\n`).to(filePath) +} + +const setPackageVersion = (version, shellOptions = {}) => { + sh.exec(`npm version --no-git-tag ${version}`, {silent: true, ...shellOptions}) +} + +module.exports = { + saveJSONToFile, + setPackageVersion +}