diff --git a/package.json b/package.json index 200ee2c47838..94f750219261 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "lint:fix": "yarn lint --fix", "project:copy": "node ./tasks/framework-tools/frameworkFilesToProject.mjs", "project:deps": "node ./tasks/framework-tools/frameworkDepsToProject.mjs", + "project:deps:yarn3": "node ./tasks/framework-tools/frameworkDepsToYarn3Project.mjs", "project:sync": "node ./tasks/framework-tools/frameworkSyncToProject.mjs", "publish:canary": "lerna publish --force-publish --canary --include-merged-tags --preid canary --dist-tag canary --yes --loglevel verbose", "release": "node ./tasks/release/cli.mjs", diff --git a/packages/api/package.json b/packages/api/package.json index ad4c98c58b88..0740165d1eb6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,6 +9,12 @@ "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", + "bin": { + "redwood": "./dist/bins/redwood.js", + "rw": "./dist/bins/redwood.js", + "rwfw": "./dist/bins/rwfw.js", + "tsc": "./dist/bins/tsc.js" + }, "files": [ "dist", "logger", diff --git a/packages/api/src/bins/redwood.ts b/packages/api/src/bins/redwood.ts new file mode 100644 index 000000000000..85ae67315e48 --- /dev/null +++ b/packages/api/src/bins/redwood.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['redwood']) diff --git a/packages/api/src/bins/rwfw.ts b/packages/api/src/bins/rwfw.ts new file mode 100644 index 000000000000..65f65a6fb863 --- /dev/null +++ b/packages/api/src/bins/rwfw.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['rwfw']) diff --git a/packages/api/src/bins/tsc.ts b/packages/api/src/bins/tsc.ts new file mode 100644 index 000000000000..22c9992cec31 --- /dev/null +++ b/packages/api/src/bins/tsc.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromTypeScript = createRequire( + require.resolve('typescript/package.json') +) + +const bins = requireFromTypeScript('./package.json')['bin'] + +requireFromTypeScript(bins['tsc']) diff --git a/packages/cli/src/commands/__tests__/type-check.test.js b/packages/cli/src/commands/__tests__/type-check.test.js index 9b9cfc3aee70..af84efc837d3 100644 --- a/packages/cli/src/commands/__tests__/type-check.test.js +++ b/packages/cli/src/commands/__tests__/type-check.test.js @@ -44,6 +44,12 @@ jest.mock('../../lib', () => { } }) +jest.mock('../../commands/upgrade', () => { + return { + getCmdMajorVersion: () => 3, + } +}) + import path from 'path' import concurrently from 'concurrently' @@ -70,12 +76,12 @@ test('Should run tsc commands correctly, in order', async () => { // Ensure tsc command run correctly for web side expect(concurrentlyArgs.commands).toContainEqual({ cwd: path.join('myBasePath', 'web'), - command: 'yarn -s tsc --noEmit --skipLibCheck', + command: 'yarn tsc --noEmit --skipLibCheck', }) // Ensure tsc command run correctly for web side expect(concurrentlyArgs.commands).toContainEqual({ cwd: path.join('myBasePath', 'api'), - command: 'yarn -s tsc --noEmit --skipLibCheck', + command: 'yarn tsc --noEmit --skipLibCheck', }) // Ensure we have raw sequential output from tsc expect(concurrentlyArgs.options).toEqual({ group: true, raw: true }) @@ -95,9 +101,9 @@ test('Should generate prisma client', async () => { // Ensure tsc command run correctly for web side expect(concurrentlyArgs.commands).toContainEqual({ cwd: path.join('myBasePath', 'api'), - command: 'yarn -s tsc --noEmit --skipLibCheck', + command: 'yarn tsc --noEmit --skipLibCheck', }) - expect(runCommandTask.mock.results[0].value[0]).toEqual( - 'yarn prisma generate --schema="../../__fixtures__/example-todo-main/api/prisma"' + expect(runCommandTask.mock.results[0].value[0]).toMatch( + /.+(\\|\/)prisma(\\|\/)build(\\|\/)index.js.+/ ) }) diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 162357881481..1b5df7619435 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -98,7 +98,7 @@ export const handler = async ({ `yarn cross-env NODE_ENV=production webpack --config ${require.resolve( '@redwoodjs/core/config/webpack.perf.js' )}`, - { stdio: 'inherit', shell: true } + { stdio: 'inherit', shell: true, cwd: rwjsPaths.web.base } ) // We do not want to continue building... return @@ -110,7 +110,7 @@ export const handler = async ({ `yarn cross-env NODE_ENV=production webpack --config ${require.resolve( '@redwoodjs/core/config/webpack.stats.js' )}`, - { stdio: 'inherit', shell: true } + { stdio: 'inherit', shell: true, cwd: rwjsPaths.web.base } ) // We do not want to continue building... return diff --git a/packages/cli/src/commands/setup/deploy/helpers/index.js b/packages/cli/src/commands/setup/deploy/helpers/index.js index e67aa52509b6..e7b6e1d05901 100644 --- a/packages/cli/src/commands/setup/deploy/helpers/index.js +++ b/packages/cli/src/commands/setup/deploy/helpers/index.js @@ -1,3 +1,4 @@ +import { spawnSync } from 'child_process' import fs from 'fs' import path from 'path' @@ -114,10 +115,14 @@ export const addPackagesTask = ({ ].filter(Boolean), ] } else { + const { stdout } = spawnSync('yarn', ['--version']) + + const yarnVersion = stdout.toString() + installCommand = [ 'yarn', [ - '-W', + yarnVersion.startsWith('1') && '-W', 'add', devDependency && '--dev', ...packagesWithSameRWVersion, diff --git a/packages/cli/src/commands/setup/deploy/providers/serverless.js b/packages/cli/src/commands/setup/deploy/providers/serverless.js index c75a94d816f4..397d9f1d681d 100644 --- a/packages/cli/src/commands/setup/deploy/providers/serverless.js +++ b/packages/cli/src/commands/setup/deploy/providers/serverless.js @@ -91,10 +91,22 @@ const updateRedwoodTomlTask = () => { } export const handler = async ({ force }) => { + const [serverless, serverlessLift, ...rest] = projectDevPackages + const tasks = new Listr( [ addPackagesTask({ - packages: projectDevPackages, + packages: [serverless, ...rest], + devDependency: true, + }), + addPackagesTask({ + packages: [serverless, serverlessLift], + side: 'web', + devDependency: true, + }), + addPackagesTask({ + packages: [serverless], + side: 'api', devDependency: true, }), addFilesTask({ diff --git a/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js b/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js index edd46bea77ec..d74ec2849611 100644 --- a/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js +++ b/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js @@ -124,7 +124,9 @@ export const handler = async ({ force, install }) => { } } - await execa('yarn', ['tailwindcss', 'init', tailwindConfigPath]) + await execa('yarn', ['tailwindcss', 'init', tailwindConfigPath], { + cwd: rwPaths.web.base, + }) // Replace `content`. const tailwindConfig = fs.readFileSync(tailwindConfigPath, 'utf-8') diff --git a/packages/cli/src/commands/type-check.js b/packages/cli/src/commands/type-check.js index 66b24691f539..ec7598e62f40 100644 --- a/packages/cli/src/commands/type-check.js +++ b/packages/cli/src/commands/type-check.js @@ -8,6 +8,7 @@ import terminalLink from 'terminal-link' import { getProject } from '@redwoodjs/structure' import { errorTelemetry } from '@redwoodjs/telemetry' +import { getCmdMajorVersion } from '../commands/upgrade' import { getPaths } from '../lib' import c from '../lib/colors' import { generatePrismaClient } from '../lib/generatePrismaClient' @@ -55,13 +56,17 @@ export const handler = async ({ sides, verbose, prisma, generate }) => { const typeCheck = async () => { let conclusiveExitCode = 0 + const yarnVersion = await getCmdMajorVersion('yarn') + const tscForAllSides = sides.map((side) => { const projectDir = path.join(getPaths().base, side) // -s flag to suppress error output from yarn. For example yarn doc link on non-zero status. // Since it'll be printed anyways after the whole execution. return { cwd: projectDir, - command: `yarn -s tsc --noEmit --skipLibCheck`, + command: `yarn ${ + yarnVersion > 1 ? '' : '-s' + } tsc --noEmit --skipLibCheck`, } }) diff --git a/packages/cli/src/commands/upgrade.js b/packages/cli/src/commands/upgrade.js index 4d013fdbb3d5..20cfa2f4e817 100644 --- a/packages/cli/src/commands/upgrade.js +++ b/packages/cli/src/commands/upgrade.js @@ -143,13 +143,19 @@ export const handler = async ({ dryRun, tag, verbose, dedupe }) => { } } async function yarnInstall({ verbose }) { + const yarnVersion = await getCmdMajorVersion('yarn') + try { - await execa('yarn install', ['--force', '--non-interactive'], { - shell: true, - stdio: verbose ? 'inherit' : 'pipe', + await execa( + 'yarn install', + yarnVersion > 1 ? [] : ['--force', '--non-interactive'], + { + shell: true, + stdio: verbose ? 'inherit' : 'pipe', - cwd: getPaths().base, - }) + cwd: getPaths().base, + } + ) } catch (e) { throw new Error( 'Could not finish installation. Please run `yarn install --force`, before continuing' @@ -253,11 +259,12 @@ async function refreshPrismaClient(task, { verbose }) { } } -const getCmdMajorVersion = async (command) => { +export const getCmdMajorVersion = async (command) => { // Get current version const { stdout } = await execa(command, ['--version'], { cwd: getPaths().base, }) + if (!SEMVER_REGEX.test(stdout)) { throw new Error(`Unable to verify ${command} version.`) } diff --git a/packages/cli/src/lib/generatePrismaClient.js b/packages/cli/src/lib/generatePrismaClient.js index dbc22a8494b2..43ce4c74d01b 100644 --- a/packages/cli/src/lib/generatePrismaClient.js +++ b/packages/cli/src/lib/generatePrismaClient.js @@ -21,7 +21,7 @@ export const generatePrismaCommand = (schema) => { } return { - cmd: 'yarn prisma', + cmd: `node "${require.resolve('prisma/build/index.js')}"`, args: ['generate', schema && `--schema="${schema}"`], } } diff --git a/packages/cli/src/rwfw.js b/packages/cli/src/rwfw.js index 8ea88fe32e73..a6de655c8d3e 100644 --- a/packages/cli/src/rwfw.js +++ b/packages/cli/src/rwfw.js @@ -47,9 +47,10 @@ if (!command.length || command.some((cmd) => helpCommands.includes(cmd))) { } try { - execa.sync('yarn', ['--cwd', absRwFwPath, ...command], { + execa.sync('yarn', [...command], { stdio: 'inherit', shell: true, + cwd: absRwFwPath, env: { RWJS_CWD: projectPath, }, diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/README.md b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/README.md new file mode 100644 index 000000000000..517e526780a8 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/README.md @@ -0,0 +1,21 @@ +# Upgrade Yarn + +This codemod upgrades Redwood projects from yarn 1 to yarn 3. + +The first thing it does is enable corepack: + +``` +corepack enable +``` + +Corepack is a new thing in the Node.js coreā€”it essentially packages yarn with node so that it doesn't have to be installed separately. +Its only available on node >= `v14.9.0`, so we check for that first. + +After enabling corepack, we set the yarn version: + +``` +yarn set version stable +``` + +Finally, before installing, we have to update `.yarnrc.yml` and `.gitignore`. +We have to update the `.yarnc.yml` because we only support the `node_modules` install strategy. diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/.gitignore b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/.gitignore new file mode 100644 index 000000000000..750eb28bae6d --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/.gitignore @@ -0,0 +1,13 @@ +.idea +.DS_Store +.env +.netlify +.redwood +dev.db* +dist +dist-babel +node_modules +yarn-error.log +web/public/mockServiceWorker.js +web/types/graphql.d.ts +api/types/graphql.d.ts diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/.keep b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/package.json b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/package.json new file mode 100644 index 000000000000..73f2e6ba0517 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/input/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "workspaces": { + "packages": [ + "api", + "web", + "packages/*" + ] + }, + "devDependencies": { + "@redwoodjs/core": "0.47.0" + }, + "eslintConfig": { + "extends": "@redwoodjs/eslint-config", + "root": true + }, + "engines": { + "node": ">=14.17 <=16.x", + "yarn": ">=1.15 <2" + }, + "prisma": { + "seed": "yarn rw exec seed" + } +} diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.gitignore b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.gitignore new file mode 100644 index 000000000000..f30b0a00ec20 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.gitignore @@ -0,0 +1,20 @@ +.idea +.DS_Store +.env +.netlify +.redwood +dev.db* +dist +dist-babel +node_modules +yarn-error.log +web/public/mockServiceWorker.js +web/types/graphql.d.ts +api/types/graphql.d.ts +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.keep b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.yarnrc.yml b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.yarnrc.yml new file mode 100644 index 000000000000..169534762c54 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/.yarnrc.yml @@ -0,0 +1,9 @@ +compressionLevel: 0 + +enableGlobalCache: true + +nmMode: hardlinks-local + +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-3.2.0.cjs diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/package.json b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/package.json new file mode 100644 index 000000000000..80a3c8efd745 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/__testfixtures__/default/output/package.json @@ -0,0 +1,25 @@ +{ + "private": true, + "workspaces": { + "packages": [ + "api", + "web", + "packages/*" + ] + }, + "devDependencies": { + "@redwoodjs/core": "0.47.0" + }, + "eslintConfig": { + "extends": "@redwoodjs/eslint-config", + "root": true + }, + "engines": { + "node": ">=14.17 <=16.x", + "yarn": ">=1.15 <2" + }, + "prisma": { + "seed": "yarn rw exec seed" + }, + "packageManager": "yarn@3.2.0" +} diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.ts b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.ts new file mode 100644 index 000000000000..0c2919e7c6c4 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.ts @@ -0,0 +1,88 @@ +import { spawnSync } from 'child_process' +import fs from 'fs' +import path from 'path' + +import getRWPaths from '../../../lib/getRWPaths' + +async function upgradeYarn() { + const rwPaths = getRWPaths() + + // Projects need to be on node v14.9.0 or greater for corepack to work. + const [major, minor] = process.version.replace('v', '').split('.').map(Number) + + if (major < 14) { + console.log('You have to be on node v14.9.0 or greater') + process.exit(1) + } + + if (major === 14 && minor < 9) { + console.log('You have to be on node v14.9.0 or greater') + process.exit(1) + } + + console.log('Enabling corepack...') + spawnSync('corepack enable', { + shell: true, + cwd: rwPaths.base, + }) + + console.log('Setting yarn version to 3...') + + spawnSync('yarn set version stable', { + shell: true, + cwd: rwPaths.base, + }) + + const { stdout } = spawnSync('yarn --version', { + shell: true, + cwd: rwPaths.base, + }) + + const yarnVersion = stdout.toString().trim() + + console.log() + console.log('Writing .yarnrc.yml and appending to .gitignore...') + + fs.writeFileSync( + path.join(rwPaths.base, '.yarnrc.yml'), + + [ + 'compressionLevel: 0', + '', + 'enableGlobalCache: true', + '', + 'nmMode: hardlinks-local', + '', + 'nodeLinker: node-modules', + '', + `yarnPath: .yarn/releases/yarn-${yarnVersion}.cjs`, + ].join('\n') + ) + + const gitignorePath = path.join(rwPaths.base, '.gitignore') + const gitignore = fs.readFileSync(gitignorePath) + fs.writeFileSync( + path.join(gitignorePath), + `${gitignore}${[ + '.pnp.*', + '.yarn/*', + '!.yarn/patches', + '!.yarn/plugins', + '!.yarn/releases', + '!.yarn/sdks', + '!.yarn/versions', + ].join('\n')}` + ) + + console.log('Installing...') + spawnSync('yarn install', { + shell: true, + cwd: rwPaths.base, + stdio: 'inherit', + }) + + console.log() + console.log('Done! Be sure to commit the changes') +} + +export default upgradeYarn diff --git a/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.yargs.ts b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.yargs.ts new file mode 100644 index 000000000000..9585b88b0b31 --- /dev/null +++ b/packages/codemods/src/codemods/v0.48.x/upgradeYarn/upgradeYarn.yargs.ts @@ -0,0 +1,17 @@ +import task from 'tasuku' + +import upgradeYarn from './upgradeYarn' + +export const command = 'upgrade-yarn' +export const description = + '(v0.48.x->v0.48.x) Changes the structure of your Redwood Project' + +export const handler = () => { + task('Upgrade Yarn', async ({ setError }: task.TaskInnerApi) => { + try { + await upgradeYarn() + } catch (e: any) { + setError('Failed to codemod your project \n' + e?.message) + } + }) +} diff --git a/packages/core/.babelrc.js b/packages/core/.babelrc.js new file mode 100644 index 000000000000..3b2c815712d9 --- /dev/null +++ b/packages/core/.babelrc.js @@ -0,0 +1 @@ +module.exports = { extends: '../../babel.config.js' } diff --git a/packages/core/package.json b/packages/core/package.json index 30bb518683e3..a2030ee1cbc8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,9 +8,28 @@ "directory": "packages/core" }, "license": "MIT", + "bin": { + "cross-env": "./dist/bins/cross-env.js", + "eslint": "./dist/bins/eslint.js", + "jest": "./dist/bins/jest.js", + "nodemon": "./dist/bins/nodemon.js", + "redwood": "./dist/bins/redwood.js", + "rimraf": "./dist/bins/rimraf.js", + "rw": "./dist/bins/redwood.js", + "rw-api-server-watch": "./dist/bins/rw-api-server-watch.js", + "rw-gen": "./dist/bins/rw-gen.js", + "rw-gen-watch": "./dist/bins/rw-gen-watch.js", + "rwfw": "./dist/bins/rwfw.js" + }, "files": [ - "config" + "config", + "dist" ], + "scripts": { + "build": "yarn build:js", + "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\"", + "prepublishOnly": "NODE_ENV=production yarn build" + }, "dependencies": { "@babel/cli": "7.16.7", "@babel/core": "7.16.7", @@ -53,6 +72,7 @@ "nodemon": "2.0.15", "null-loader": "4.0.1", "react-refresh": "0.11.0", + "rimraf": "3.0.2", "style-loader": "3.3.1", "svg-react-loader": "0.4.6", "typescript": "4.6.2", @@ -65,5 +85,8 @@ "webpack-merge": "5.8.0", "webpack-retry-chunk-load-plugin": "3.0.0" }, + "devDependencies": { + "@types/rimraf": "3.0.2" + }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/core/src/bins/cross-env.ts b/packages/core/src/bins/cross-env.ts new file mode 100644 index 000000000000..206e21e1eca8 --- /dev/null +++ b/packages/core/src/bins/cross-env.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCrossEnv = createRequire( + require.resolve('cross-env/package.json') +) + +const bins = requireFromCrossEnv('./package.json')['bin'] + +requireFromCrossEnv(`./${bins['cross-env']}`) diff --git a/packages/core/src/bins/eslint.ts b/packages/core/src/bins/eslint.ts new file mode 100644 index 000000000000..a4904d38e678 --- /dev/null +++ b/packages/core/src/bins/eslint.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromESLint = createRequire(require.resolve('eslint/package.json')) + +const bins = requireFromESLint('./package.json')['bin'] + +requireFromESLint(bins['eslint']) diff --git a/packages/core/src/bins/jest.ts b/packages/core/src/bins/jest.ts new file mode 100644 index 000000000000..7269056f094a --- /dev/null +++ b/packages/core/src/bins/jest.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromJest = createRequire(require.resolve('jest/package.json')) + +const bin = requireFromJest('./package.json')['bin'] + +requireFromJest(bin) diff --git a/packages/core/src/bins/nodemon.ts b/packages/core/src/bins/nodemon.ts new file mode 100644 index 000000000000..82c33c06c015 --- /dev/null +++ b/packages/core/src/bins/nodemon.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromNodemon = createRequire( + require.resolve('nodemon/package.json') +) + +const bins = requireFromNodemon('./package.json')['bin'] + +requireFromNodemon(bins['nodemon']) diff --git a/packages/core/src/bins/redwood.ts b/packages/core/src/bins/redwood.ts new file mode 100644 index 000000000000..6f57d1d25a11 --- /dev/null +++ b/packages/core/src/bins/redwood.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env node +/** + * A proxy for running the `redwood` @redwoodjs/cli bin (`yarn redwood`, or `yarn rw`) from @redwoodjs/core. + * + * @remarks + * `createRequire` is for ES6 modules. + * Some modules can only be imported via require and not via ES6 import/export syntax. + * But require doesn't exist in ES6, so you have to create it. + * + * But that's not the reason we're using it here. + * We're using it here to require files from other packages for yarn 3 reasons-- + * something kinda along these lines: + * + * > If your package is something that automatically loads plugins (for example eslint), + * > peer dependencies obviously aren't an option as you can't reasonably list all plugins. + * > Instead, you should use the createRequire function to load plugins + * > on behalf of the configuration file that lists the plugins to load + * > be it the package.json or a custom one like the .eslintrc.js file. + * + * @see {@link https://yarnpkg.com/advanced/rulebook#packages-should-only-ever-require-what-they-formally-list-in-their-dependencies} + * @see {@link https://yarnpkg.com/advanced/rulebook#modules-shouldnt-hardcode-node_modules-paths-to-access-other-modules} + */ +import { createRequire } from 'module' + +// You can think about the argument we're passing to createRequire as being kinda like setting the `cwd`. +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['redwood']) diff --git a/packages/core/src/bins/rimraf.ts b/packages/core/src/bins/rimraf.ts new file mode 100644 index 000000000000..605ebc629e66 --- /dev/null +++ b/packages/core/src/bins/rimraf.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromRimraf = createRequire(require.resolve('rimraf/package.json')) + +const bin = requireFromRimraf('./package.json')['bin'] + +requireFromRimraf(bin) diff --git a/packages/core/src/bins/rw-api-server-watch.ts b/packages/core/src/bins/rw-api-server-watch.ts new file mode 100644 index 000000000000..c55b59a3ceeb --- /dev/null +++ b/packages/core/src/bins/rw-api-server-watch.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromApiServer = createRequire( + require.resolve('@redwoodjs/api-server/package.json') +) + +const bins = requireFromApiServer('./package.json')['bin'] + +requireFromApiServer(bins['rw-api-server-watch']) diff --git a/packages/core/src/bins/rw-gen-watch.ts b/packages/core/src/bins/rw-gen-watch.ts new file mode 100644 index 000000000000..1f6854e2f73d --- /dev/null +++ b/packages/core/src/bins/rw-gen-watch.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromInternal = createRequire( + require.resolve('@redwoodjs/internal/package.json') +) + +const bins = requireFromInternal('./package.json')['bin'] + +requireFromInternal(bins['rw-gen-watch']) diff --git a/packages/core/src/bins/rw-gen.ts b/packages/core/src/bins/rw-gen.ts new file mode 100644 index 000000000000..557d81daec35 --- /dev/null +++ b/packages/core/src/bins/rw-gen.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromInternal = createRequire( + require.resolve('@redwoodjs/internal/package.json') +) + +const bins = requireFromInternal('./package.json')['bin'] + +const { run } = requireFromInternal(bins['rw-gen']) + +run() diff --git a/packages/core/src/bins/rwfw.ts b/packages/core/src/bins/rwfw.ts new file mode 100644 index 000000000000..65f65a6fb863 --- /dev/null +++ b/packages/core/src/bins/rwfw.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['rwfw']) diff --git a/packages/internal/src/generate/generate.ts b/packages/internal/src/generate/generate.ts index 7a3bce228805..077900192f93 100644 --- a/packages/internal/src/generate/generate.ts +++ b/packages/internal/src/generate/generate.ts @@ -13,7 +13,7 @@ export const generate = async () => { ) as string[] } -const run = async () => { +export const run = async () => { console.log() console.log('Generating...') console.log() diff --git a/packages/web/package.json b/packages/web/package.json index 5da535209620..7925647ce5e9 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -9,6 +9,17 @@ "license": "MIT", "main": "./dist/index.js", "types": "dist/index.d.ts", + "bin": { + "build-storybook": "./dist/bins/build-storybook.js", + "cross-env": "./dist/bins/cross-env.js", + "msw": "./dist/bins/msw.js", + "redwood": "./dist/bins/redwood.js", + "rw": "./dist/bins/redwood.js", + "rwfw": "./dist/bins/rwfw.js", + "start-storybook": "./dist/bins/start-storybook.js", + "tsc": "./dist/bins/tsc.js", + "webpack": "./dist/bins/webpack.js" + }, "files": [ "dist", "apollo", diff --git a/packages/web/src/bins/build-storybook.ts b/packages/web/src/bins/build-storybook.ts new file mode 100644 index 000000000000..50a306b58cf3 --- /dev/null +++ b/packages/web/src/bins/build-storybook.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromStorybook = createRequire( + require.resolve('@storybook/react/package.json') +) + +const bins = requireFromStorybook('./package.json')['bin'] + +requireFromStorybook(bins['build-storybook']) diff --git a/packages/web/src/bins/cross-env.ts b/packages/web/src/bins/cross-env.ts new file mode 100644 index 000000000000..206e21e1eca8 --- /dev/null +++ b/packages/web/src/bins/cross-env.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCrossEnv = createRequire( + require.resolve('cross-env/package.json') +) + +const bins = requireFromCrossEnv('./package.json')['bin'] + +requireFromCrossEnv(`./${bins['cross-env']}`) diff --git a/packages/web/src/bins/msw.ts b/packages/web/src/bins/msw.ts new file mode 100644 index 000000000000..83a83ab2c00b --- /dev/null +++ b/packages/web/src/bins/msw.ts @@ -0,0 +1,9 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromMSW = createRequire(require.resolve('msw/package.json')) + +const bins = requireFromMSW('./package.json')['bin'] + +// Most `package.json`s list their bins as relative paths, but not MSW. +requireFromMSW(`./${bins['msw']}`) diff --git a/packages/web/src/bins/redwood.ts b/packages/web/src/bins/redwood.ts new file mode 100644 index 000000000000..85ae67315e48 --- /dev/null +++ b/packages/web/src/bins/redwood.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['redwood']) diff --git a/packages/web/src/bins/rwfw.ts b/packages/web/src/bins/rwfw.ts new file mode 100644 index 000000000000..65f65a6fb863 --- /dev/null +++ b/packages/web/src/bins/rwfw.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromCli = createRequire( + require.resolve('@redwoodjs/cli/package.json') +) + +const bins = requireFromCli('./package.json')['bin'] + +// Ensure we run all commands from the base of the RW project +// even if you invoke from ./web or ./api +const rwProjectRoot = requireFromCli('./dist/lib/index.js').getPaths().base +process.chdir(rwProjectRoot) + +requireFromCli(bins['rwfw']) diff --git a/packages/web/src/bins/start-storybook.ts b/packages/web/src/bins/start-storybook.ts new file mode 100644 index 000000000000..484a85c76bd8 --- /dev/null +++ b/packages/web/src/bins/start-storybook.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromStorybook = createRequire( + require.resolve('@storybook/react/package.json') +) + +const bins = requireFromStorybook('./package.json')['bin'] + +requireFromStorybook(bins['start-storybook']) diff --git a/packages/web/src/bins/tsc.ts b/packages/web/src/bins/tsc.ts new file mode 100644 index 000000000000..22c9992cec31 --- /dev/null +++ b/packages/web/src/bins/tsc.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromTypeScript = createRequire( + require.resolve('typescript/package.json') +) + +const bins = requireFromTypeScript('./package.json')['bin'] + +requireFromTypeScript(bins['tsc']) diff --git a/packages/web/src/bins/webpack.ts b/packages/web/src/bins/webpack.ts new file mode 100644 index 000000000000..7fdbc047dabb --- /dev/null +++ b/packages/web/src/bins/webpack.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { createRequire } from 'module' + +const requireFromWebpack = createRequire( + require.resolve('webpack/package.json') +) + +const bins = requireFromWebpack('./package.json')['bin'] + +requireFromWebpack(`./${bins['webpack']}`) diff --git a/tasks/framework-tools/frameworkDepsToYarn3Project.mjs b/tasks/framework-tools/frameworkDepsToYarn3Project.mjs new file mode 100644 index 000000000000..24caa582b195 --- /dev/null +++ b/tasks/framework-tools/frameworkDepsToYarn3Project.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node +/* eslint-env node, es2021 */ +import path from 'node:path' +import { $, cd } from 'zx' + +process.env.FORCE_COLOR = '1' + +const frameworkPath = path.resolve('../../', process.cwd()) +const frameworkCorePath = path.join(frameworkPath, 'packages', 'core') +const frameworkWebPath = path.join(frameworkPath, 'packages', 'web') +const frameworkApiPath = path.join(frameworkPath, 'packages', 'api') + +const projectPath = process.argv[2] ?? process.env.RWJS_CWD +const projectWebPath = path.join(projectPath, 'web') +const projectApiPath = path.join(projectPath, 'api') + +// Core +console.log('-'.repeat(80)) +cd(frameworkCorePath) +await $`rm -rf dist` +await $`yarn build` +await $`yarn pack` +await $`tar -xzf ./package.tgz` +cd(projectPath) +await $`yarn add -D ${path.join(frameworkCorePath, 'package')}` +await $`yarn bin` + +// Web +console.log('-'.repeat(80)) +cd(frameworkWebPath) +await $`rm -rf dist` +await $`yarn build` +await $`yarn pack` +await $`tar -xzf ./package.tgz` +cd(projectWebPath) +await $`yarn add ${path.join(frameworkWebPath, 'package')}` +await $`yarn bin` + +// Api +console.log('-'.repeat(80)) +cd(frameworkApiPath) +await $`rm -rf dist` +await $`yarn build` +await $`yarn pack` +await $`tar -xzf ./package.tgz` +cd(projectApiPath) +await $`yarn add ${path.join(frameworkApiPath, 'package')}` +await $`yarn bin` + +// Dist files +console.log('-'.repeat(80)) +cd(frameworkPath) +// So that rwfw works +const rwfw = path.join('cli', 'dist', 'rwfw.js') +await $`cp ${path.join(frameworkPath, 'packages', rwfw)} ${path.join( + projectPath, + 'node_modules', + '@redwoodjs', + rwfw +)}` +// So that building prisma works +const generatePrismaClientPath = path.join( + 'cli', + 'dist', + 'lib', + 'generatePrismaClient.js' +) +await $`cp ${path.join( + frameworkPath, + 'packages', + generatePrismaClientPath +)} ${path.join( + projectPath, + 'node_modules', + '@redwoodjs', + generatePrismaClientPath +)}` diff --git a/tasks/framework-tools/lib/project.mjs b/tasks/framework-tools/lib/project.mjs index 7d3e596f702e..204a5a8fd237 100644 --- a/tasks/framework-tools/lib/project.mjs +++ b/tasks/framework-tools/lib/project.mjs @@ -31,6 +31,7 @@ export function fixProjectBinaries(projectPath) { fs.symlinkSync(binPath, binSymlink) } console.log('chmod +x', terminalLink(binName, binPath)) + fs.chmodSync(binSymlink, '755') fs.chmodSync(binPath, '755') } } diff --git a/tasks/run-e2e b/tasks/run-e2e index 8281c6287837..360737d14d9d 100755 --- a/tasks/run-e2e +++ b/tasks/run-e2e @@ -70,8 +70,38 @@ const createRedwoodJSApp = () => { } } -const addFrameworkDepsToProject = () => { +const upgradeToYarn3 = () => { try { + execa.sync( + `RWJS_CWD=${REDWOOD_PROJECT_DIRECTORY} yarn node ./packages/codemods/dist/codemods.js upgrade-yarn`, + { + cwd: REDWOODJS_FRAMEWORK_PATH, + env: { REDWOOD_CI: '1' }, + shell: true, + stdio: 'inherit', + } + ) + } catch (e) { + console.error('Error: Could not upgrade Redwood Project to yarn 3') + console.error(e) + process.exit(1) + } +} + +const addFrameworkDepsToProject = (yarn3) => { + try { + if (yarn3) { + execa.sync('yarn project:deps:yarn3', { + cwd: REDWOODJS_FRAMEWORK_PATH, + shell: true, + stdio: 'inherit', + env: { + RWFW_PATH: REDWOODJS_FRAMEWORK_PATH, + RWJS_CWD: REDWOOD_PROJECT_DIRECTORY, + }, + }) + } + execa.sync('yarn project:deps', { cwd: REDWOODJS_FRAMEWORK_PATH, shell: true, @@ -107,38 +137,10 @@ const copyFrameworkPackages = () => { process.exit(1) } } -const checkYarnVersion = () => { - try { - const { stdout } = execa.sync('yarn --version', { - cwd: REDWOOD_PROJECT_DIRECTORY, - shell: true, - }) - console.log('Yarn version is', stdout) - if (!stdout.startsWith('1')) { - setYarnClassic() - } - } catch (e) { - console.error(e) - console.error('Error: We could not find yarn') - process.exit(1) - } -} -const setYarnClassic = () => { - try { - execa.sync('yarn set version classic', { - cwd: REDWOOD_PROJECT_DIRECTORY, - shell: true, - stdio: 'inherit', - }) - } catch (e) { - console.error(e) - console.error('Error: We could not set yarn classic') - process.exit(1) - } -} -const runYarnInstall = () => { + +const runYarnInstall = (yarn3) => { try { - execa.sync('yarn install', { + execa.sync(`yarn install ${yarn3 ? '--no-immutable' : ''}`, { cwd: REDWOOD_PROJECT_DIRECTORY, shell: true, stdio: 'inherit', @@ -212,6 +214,7 @@ const args = yargs .option('copy-framework', { default: true, type: 'boolean', alias: 'copy' }) .option('typescript', { default: false, type: 'boolean', alias: 'ts' }) .option('auto-start', { default: true, type: 'boolean', alias: 'start' }) + .option('yarn3', { default: true, type: 'boolean' }) .scriptName('run-e2e') .example('run-e2e') .example('run-e2e /tmp/redwood-app --ts') @@ -235,14 +238,20 @@ makeDirectory(REDWOOD_PROJECT_DIRECTORY) console.log() console.log('-'.repeat(80)) -let { buildFramework, copyFramework, createProject, typescript, autoStart } = - args +let { + buildFramework, + copyFramework, + createProject, + typescript, + autoStart, + yarn3, +} = args const tasks = [ buildFramework && buildRedwoodFramework, createProject && createRedwoodJSApp, - copyFramework && addFrameworkDepsToProject, - copyFramework && checkYarnVersion, - copyFramework && runYarnInstall, + yarn3 && upgradeToYarn3, + copyFramework && (() => addFrameworkDepsToProject(yarn3)), + copyFramework && (() => runYarnInstall(yarn3)), copyFramework && copyFrameworkPackages, !typescript && convertProjectToJavaScript, autoStart && runDevServerInBackground, diff --git a/yarn.lock b/yarn.lock index c2238f2b90bb..928c66301ab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5747,6 +5747,11 @@ __metadata: optional: true firebase-admin: optional: true + bin: + redwood: ./dist/bins/redwood.js + rw: ./dist/bins/redwood.js + rwfw: ./dist/bins/rwfw.js + tsc: ./dist/bins/tsc.js languageName: unknown linkType: soft @@ -5881,6 +5886,7 @@ __metadata: "@redwoodjs/eslint-config": 0.47.1 "@redwoodjs/internal": 0.47.1 "@redwoodjs/testing": 0.47.1 + "@types/rimraf": 3.0.2 babel-loader: 8.2.3 babel-plugin-auto-import: 1.1.0 babel-plugin-graphql-tag: 3.3.0 @@ -5904,6 +5910,7 @@ __metadata: nodemon: 2.0.15 null-loader: 4.0.1 react-refresh: 0.11.0 + rimraf: 3.0.2 style-loader: 3.3.1 svg-react-loader: 0.4.6 typescript: 4.6.2 @@ -5915,6 +5922,18 @@ __metadata: webpack-manifest-plugin: 4.1.1 webpack-merge: 5.8.0 webpack-retry-chunk-load-plugin: 3.0.0 + bin: + cross-env: ./dist/bins/cross-env.js + eslint: ./dist/bins/eslint.js + jest: ./dist/bins/jest.js + nodemon: ./dist/bins/nodemon.js + redwood: ./dist/bins/redwood.js + rimraf: ./dist/bins/rimraf.js + rw: ./dist/bins/redwood.js + rw-api-server-watch: ./dist/bins/rw-api-server-watch.js + rw-gen: ./dist/bins/rw-gen.js + rw-gen-watch: ./dist/bins/rw-gen-watch.js + rwfw: ./dist/bins/rwfw.js languageName: unknown linkType: soft @@ -6245,6 +6264,16 @@ __metadata: prop-types: 15.8.1 react: 17.0.2 react-dom: 17.0.2 + bin: + build-storybook: ./dist/bins/build-storybook.js + cross-env: ./dist/bins/cross-env.js + msw: ./dist/bins/msw.js + redwood: ./dist/bins/redwood.js + rw: ./dist/bins/redwood.js + rwfw: ./dist/bins/rwfw.js + start-storybook: ./dist/bins/start-storybook.js + tsc: ./dist/bins/tsc.js + webpack: ./dist/bins/webpack.js languageName: unknown linkType: soft