diff --git a/tasks/framework-tools/frameworkDepsToProject.mjs b/tasks/framework-tools/frameworkDepsToProject.mjs index fa399ca324c1..5f8702755a8c 100755 --- a/tasks/framework-tools/frameworkDepsToProject.mjs +++ b/tasks/framework-tools/frameworkDepsToProject.mjs @@ -1,23 +1,31 @@ #!/usr/bin/env node /* eslint-env node */ +// @ts-check import path from 'node:path' import { addDependenciesToPackageJson } from './lib/project.mjs' -const projectPath = process.argv?.[2] ?? process.env.RWJS_CWD +function main() { + const projectPath = process.argv?.[2] ?? process.env.RWJS_CWD -if (!projectPath) { - console.log('Error: Please specify the path to your Redwood Project') - console.log(`Usage: ${process.argv?.[1]} /path/to/rwjs/proect`) - process.exit(1) -} + if (!projectPath) { + process.exitCode = 1 + console.error([ + 'Error: Please specify the path to your Redwood project', + `Usage: ${process.argv?.[1]} ./path/to/rw/project`, + ]) + return + } -try { - const packageJsonPath = path.join(projectPath, 'package.json') - addDependenciesToPackageJson(packageJsonPath) - console.log('Done. Now run `yarn install`.') -} catch (e) { - console.log('Error:', e.message) - process.exit(1) + try { + const packageJsonPath = path.join(projectPath, 'package.json') + addDependenciesToPackageJson(packageJsonPath) + console.log('Done. Now run `yarn install`.') + } catch (e) { + console.log('Error:', e.message) + process.exitCode = 1 + } } + +main() diff --git a/tasks/framework-tools/frameworkFilesToProject.mjs b/tasks/framework-tools/frameworkFilesToProject.mjs index 5f0c98ee441c..35ae70bfb875 100755 --- a/tasks/framework-tools/frameworkFilesToProject.mjs +++ b/tasks/framework-tools/frameworkFilesToProject.mjs @@ -1,19 +1,28 @@ #!/usr/bin/env node /* eslint-env node */ +// @ts-check import { copyFrameworkFilesToProject } from './lib/project.mjs' -const projectPath = process.argv?.[2] ?? process.env.RWJS_CWD +async function main() { + const redwoodProjectPath = process.argv?.[2] ?? process.env.RWJS_CWD -if (!projectPath) { - console.log('Error: Please specify the path to your Redwood Project') - console.log(`Usage: ${process.argv?.[1]} /path/to/rwjs/proect`) - process.exit(1) -} + // Mostly just making TS happy with the second condition. + if (!redwoodProjectPath || typeof redwoodProjectPath !== 'string') { + process.exitCode = 1 + console.error([ + 'Error: Please specify the path to your Redwood project', + `Usage: ${process.argv?.[1]} ./path/to/rw/project`, + ]) + return + } -try { - await copyFrameworkFilesToProject(projectPath) -} catch (e) { - console.error('Error:', e.message) - process.exit(1) + try { + await copyFrameworkFilesToProject(redwoodProjectPath) + } catch (e) { + console.error('Error:', e.message) + process.exitCode = 1 + } } + +main() diff --git a/tasks/framework-tools/frameworkSyncToProject.mjs b/tasks/framework-tools/frameworkSyncToProject.mjs index 324e4629e818..a1f2b50a51d8 100644 --- a/tasks/framework-tools/frameworkSyncToProject.mjs +++ b/tasks/framework-tools/frameworkSyncToProject.mjs @@ -1,129 +1,299 @@ #!/usr/bin/env node /* eslint-env node */ +// @ts-check -import fs from 'node:fs' +import { execSync } from 'node:child_process' import path from 'node:path' import c from 'ansi-colors' import chokidar from 'chokidar' +import fs from 'fs-extra' +import { rimraf } from 'rimraf' +import { hideBin } from 'yargs/helpers' +import yargs from 'yargs/yargs' import { + getPackageName, + resolvePackageJsonPathFromFilePath, + REDWOOD_FRAMEWORK_PATH, REDWOOD_PACKAGES_PATH, - packageJsonName, - resolvePackageJsonPath, - buildPackages, } from './lib/framework.mjs' -import { cleanPackages } from './lib/framework.mjs' import { - installProjectPackages, addDependenciesToPackageJson, copyFrameworkFilesToProject, + fixProjectBinaries, } from './lib/project.mjs' -const projectPath = process.argv?.[2] ?? process.env.RWJS_CWD +const IGNORE_EXTENSIONS = ['.DS_Store'] -if (!projectPath) { - console.log('Error: Please specify the path to your Redwood Project') - console.log(`Usage: ${process.argv?.[1]} /path/to/rwjs/project`) - process.exit(1) -} +// Add to this array of strings, RegExps, or functions (whichever makes the most sense) +// to ignore files that we don't want triggering package rebuilds. +const ignored = [ + /node_modules/, -// Cache the original package.json and restore it when this process exits. -const projectPackageJsonPath = path.join(projectPath, 'package.json') + /packages\/codemods/, + /packages\/create-redwood-app/, -const projectPackageJson = fs.readFileSync(projectPackageJsonPath, 'utf-8') -process.on('SIGINT', () => { - console.log() - console.log(`Removing framework packages from 'package.json'...`) - fs.writeFileSync(projectPackageJsonPath, projectPackageJson) - // TODO: Delete `node_modules/@redwoodjs` - console.log("...Done. Run 'yarn install'") - process.exit(0) -}) + /dist/, -function logStatus(m) { - console.log(c.bgYellow(c.black('rwfw ')), c.yellow(m)) -} + /__fixtures__/, + /__mocks__/, + /__tests__/, + /.test./, + /jest.config.{js,ts}/, -function logError(m) { - console.log(c.bgRed(c.black('rwfw ')), c.red(m)) -} + /README.md/, -chokidar - .watch(REDWOOD_PACKAGES_PATH, { - ignoreInitial: true, - persistent: true, - awaitWriteFinish: true, - ignored: (file) => - file.includes('/node_modules/') || - file.includes('/dist/') || - file.includes('/dist') || - file.includes('/__tests__/') || - file.includes('/__fixtures__/') || - file.includes('/.test./') || - ['.DS_Store'].some((ext) => file.endsWith(ext)), - }) - .on('ready', async () => { - logStatus('Cleaning Framework...') - cleanPackages() + // esbuild emits meta.json files that we sometimes suffix. + /meta.(\w*\.?)json/, - logStatus('Building Framework...') - buildPackages() + (filePath) => IGNORE_EXTENSIONS.some((ext) => filePath.endsWith(ext)), +] +const separator = '-'.repeat(process.stdout.columns) + +async function main() { + const { _: positionals, ...options } = yargs(hideBin(process.argv)) + .option('setUpForWatch', { + description: 'Set up the project for watching for framework changes', + type: 'boolean', + default: true, + }) + .option('watch', { + description: 'Watch for changes to the framework packages', + type: 'boolean', + default: true, + }) + .option('cleanUp', { + description: 'Clean up the Redwood project on SIGINT or process exit', + type: 'boolean', + default: true, + }) + .option('verbose', { + description: 'Print more', + type: 'boolean', + default: true, + }) + .parseSync() + + const redwoodProjectPath = positionals[0] ?? process.env.RWJS_CWD + + // Mostly just making TS happy with the second condition. + if (!redwoodProjectPath || typeof redwoodProjectPath !== 'string') { + process.exitCode = 1 + console.error([ + 'Error: Please specify the path to your Redwood project', + `Usage: ${process.argv?.[1]} ./path/to/rw/project`, + ]) + return + } + + if (options.setUpForWatch) { + logStatus('Cleaning the Redwood framework...') + execSync('yarn build:clean', { + stdio: options.verbose ? 'inherit' : 'pipe', + cwd: REDWOOD_FRAMEWORK_PATH, + }) + } + + if (options.setUpForWatch) { + try { + logStatus('Building the Redwood framework...') + execSync('yarn build', { + stdio: options.verbose ? 'inherit' : 'pipe', + cwd: REDWOOD_FRAMEWORK_PATH, + }) + console.log() + } catch (e) { + // Temporary error handling for. + // > Lerna (powered by Nx) ENOENT: no such file or directory, open '/Users/dom/projects/redwood/redwood/node_modules/lerna/node_modules/nx/package.json' + process.exitCode = 1 + console.error( + [ + c.bgYellow(c.black('Heads up ')), + '', + "If this failed because Nx couldn't find its package.json file in node_modules, it's a known issue. The workaround is just trying again.", + ].join('\n') + ) + return + } + } + + // Settig up here first before we add the first SIGINT handler + // just for visual output. + process.on('SIGINT', () => { console.log() - logStatus('Adding dependencies...') - addDependenciesToPackageJson(projectPackageJsonPath) - installProjectPackages(projectPath) + }) + + if (options.setUpForWatch) { + // Save the project's package.json so that we can restore it when this process exits. + const redwoodProjectPackageJsonPath = path.join( + redwoodProjectPath, + 'package.json' + ) + const redwoodProjectPackageJson = fs.readFileSync( + redwoodProjectPackageJsonPath, + 'utf-8' + ) + + if (options.cleanUp) { + logStatus('Setting up clean up on SIGINT or process exit...') + + const cleanUp = createCleanUp({ + redwoodProjectPackageJsonPath, + redwoodProjectPackageJson, + }) + + process.on('SIGINT', cleanUp) + process.on('exit', cleanUp) + } + logStatus("Adding the Redwood framework's dependencies...") + addDependenciesToPackageJson(redwoodProjectPackageJsonPath) + + try { + execSync('yarn install', { + cwd: redwoodProjectPath, + stdio: options.verbose ? 'inherit' : 'pipe', + }) + console.log() + } catch (e) { + process.exitCode = 1 + console.error(e) + return + } + } + + if (options.setUpForWatch) { + logStatus('Copying the Redwood framework files...') + await copyFrameworkFilesToProject(redwoodProjectPath) console.log() - logStatus('Copying files...') - await copyFrameworkFilesToProject(projectPath) + } + if (options.setUpForWatch) { + logStatus("Fixing the project's binaries...") + fixProjectBinaries(redwoodProjectPath) console.log() - logStatus('Done, and waiting for changes...') - console.log('-'.repeat(80)) + } + + if (!options.watch) { + return + } + + logStatus('Waiting for changes') + console.log(separator) + + const watcher = chokidar.watch(REDWOOD_PACKAGES_PATH, { + ignored, + // We don't want chokidar to emit events as it discovers paths, only as they change. + ignoreInitial: true, + // Debounce the events. + awaitWriteFinish: true, }) - .on('all', async (_event, filePath) => { - logStatus(filePath) + + let closedWatcher = false + + async function closeWatcher() { + if (closedWatcher) { + return + } + + logStatus('Closing the watcher...') + await watcher.close() + closedWatcher = true + } + + process.on('SIGINT', closeWatcher) + process.on('exit', closeWatcher) + + watcher.on('all', async (event, filePath) => { + logStatus(`${event}: ${filePath}`) if (filePath.endsWith('package.json')) { logStatus( - `${c.red( - 'Warning:' - )} You modified a package.json file. If you've modified the ${c.underline( - 'dependencies' - )}, then you must run ${c.underline('yarn rwfw project:sync')} again.` + [ + `${c.red('Warning:')} You modified a package.json file.`, + `If you've modified the ${c.underline('dependencies')}`, + `then you must run ${c.underline('yarn rwfw project:sync')} again.`, + ].join(' ') ) } - const packageJsonPath = resolvePackageJsonPath(filePath) - const packageName = packageJsonName(packageJsonPath) - logStatus(c.magenta(packageName)) + const packageJsonPath = resolvePackageJsonPathFromFilePath(filePath) + const packageName = getPackageName(packageJsonPath) - let hasHadError = false + let errored = false try { - console.log() - logStatus(`Cleaning ${packageName}...`) - cleanPackages([packageJsonPath]) + logStatus(`Cleaning ${c.magenta(packageName)}...`) + await rimraf(path.join(path.dirname(packageJsonPath), 'dist')) - console.log() - logStatus(`Building ${packageName}...`) - buildPackages([packageJsonPath]) + logStatus(`Building ${c.magenta(packageName)}...`) + execSync('yarn build', { + stdio: options.verbose ? 'inherit' : 'pipe', + cwd: path.dirname(packageJsonPath), + }) - console.log() logStatus(`Copying ${packageName}...`) - await copyFrameworkFilesToProject(projectPath, [packageJsonPath]) - } catch (error) { - hasHadError = true - console.log(error) + await copyFrameworkFilesToProject(redwoodProjectPath, [packageJsonPath]) console.log() - logError(`Error building ${packageName}...`) + } catch (error) { + errored = true } - if (!hasHadError) { - console.log() - logStatus(`Done, and waiting for changes...`) - console.log('-'.repeat(80)) + if (errored) { + logError(`Error building ${packageName}`) } + + logStatus(`Done, and waiting for changes...`) + console.log(separator) }) +} + +/** + * @param {string} m + */ +function logStatus(m) { + console.log(c.bgYellow(c.black('rwfw ')), c.yellow(m)) +} + +/** + * @param {string} m + */ +function logError(m) { + console.error(c.bgRed(c.black('rwfw ')), c.red(m)) +} + +function createCleanUp({ + redwoodProjectPackageJsonPath, + redwoodProjectPackageJson, +}) { + let cleanedUp = false + + return function () { + if (cleanedUp) { + return + } + + logStatus("Restoring the Redwood project's package.json...") + + fs.writeFileSync(redwoodProjectPackageJsonPath, redwoodProjectPackageJson) + + console.log( + [ + '', + 'To get your project back to its original state...', + "- undo the changes to project's your yarn.lock file", + "- remove your project's node_modules directory", + "- run 'yarn install'", + '', + ].join('\n') + ) + + cleanedUp = true + } +} + +// ------------------------ + +main() diff --git a/tasks/framework-tools/lib/framework.mjs b/tasks/framework-tools/lib/framework.mjs index cec877983cec..fcf60e708940 100644 --- a/tasks/framework-tools/lib/framework.mjs +++ b/tasks/framework-tools/lib/framework.mjs @@ -1,122 +1,130 @@ /* eslint-env node */ +// @ts-check -import fs from 'node:fs' +import { execSync } from 'node:child_process' import path from 'node:path' import url from 'node:url' import Arborist from '@npmcli/arborist' -import c from 'ansi-colors' -import execa from 'execa' -import fg from 'fast-glob' +import fs from 'fs-extra' import packlist from 'npm-packlist' const __dirname = path.dirname(url.fileURLToPath(import.meta.url)) -export const REDWOOD_PACKAGES_PATH = path.resolve( - __dirname, - '../../../packages' +export const REDWOOD_FRAMEWORK_PATH = path.resolve(__dirname, '../../../') + +export const REDWOOD_PACKAGES_PATH = path.join( + REDWOOD_FRAMEWORK_PATH, + 'packages' ) +const IGNORE_PACKAGES = ['@redwoodjs/codemods', 'create-redwood-app'] + /** - * A list of the `@redwoodjs` package.json files that are published to npm - * and installed into a Redwood Project. + * Get the names, locations, and absolute package.json file paths of all the packages we publish to NPM. * - * The reason there's more logic here than seems necessary is because we have package.json files - * like packages/web/toast/package.json that aren't real packages, but just entry points. + * @returns {{ location: string, name: string, packageJsonPath: string }[]} */ -export function frameworkPkgJsonFiles() { - let pkgJsonFiles = fg.sync('**/package.json', { - cwd: REDWOOD_PACKAGES_PATH, - ignore: [ - '**/node_modules/**', - '**/create-redwood-app/**', - '**/codemods/**', - ], - absolute: true, +function getFrameworkPackagesData() { + const output = execSync('yarn workspaces list --json', { + encoding: 'utf-8', }) - for (const pkgJsonFile of pkgJsonFiles) { - try { - JSON.parse(fs.readFileSync(pkgJsonFile)) - } catch (e) { - throw new Error(pkgJsonFile + ' is not a valid package.json file.') - } + const frameworkPackagesData = output + .trim() + .split('\n') + .map(JSON.parse) + // Fliter out the root package. + .filter(({ location }) => location !== '.') + // Some packages we won't bother copying into Redwood projects. + .filter(({ name }) => !IGNORE_PACKAGES.includes(name)) + + for (const frameworkPackage of frameworkPackagesData) { + frameworkPackage.packageJsonPath = path.join( + REDWOOD_FRAMEWORK_PATH, + frameworkPackage.location, + 'package.json' + ) } - pkgJsonFiles = pkgJsonFiles.filter((pkgJsonFile) => { - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile)) - return !!pkgJson.name - }) + return frameworkPackagesData +} - return pkgJsonFiles +/** + * @returns {string[]} A list of absolute package.json file paths. + */ +export function getFrameworkPackageJsonPaths() { + return getFrameworkPackagesData().map( + ({ packageJsonPath }) => packageJsonPath + ) } /** * The dependencies used by `@redwoodjs` packages. + * + * @returns {{ [key: string]: string }?} A map of package names to versions. */ -export function frameworkDependencies(packages = frameworkPkgJsonFiles()) { +export function getFrameworkDependencies( + packageJsonPaths = getFrameworkPackageJsonPaths() +) { const dependencies = {} - for (const packageJsonPath of packages) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)) + for (const packageJsonPath of packageJsonPaths) { + const packageJson = fs.readJSONSync(packageJsonPath) for (const [name, version] of Object.entries( packageJson?.dependencies ?? {} )) { - // Skip `@redwoodjs/*` packages, since these are processed - // by the workspace. - if (!name.startsWith('@redwoodjs/')) { - dependencies[name] = version - - // Warn if the packages are duplicated and are not the same version. - if (dependencies[name] && dependencies[name] !== version) { - console.warn( - c.yellow('Warning:'), - name, - 'dependency version mismatched, please make sure the versions are the same!' - ) - } + // Skip `@redwoodjs` packages, since these are processed by the workspace. + if (name.startsWith('@redwoodjs/')) { + continue + } + + dependencies[name] = version + + // Throw if there's duplicate dependencies that aren't same version. + if (dependencies[name] && dependencies[name] !== version) { + throw new Error( + `${name} dependency version mismatched, please make sure the versions are the same` + ) } } } - return sortObjectKeys(dependencies) + + return dependencies } /** - * The files included in `@redwoodjs` packages. - * Note: The packages must be built. + * The files included in all the `@redwoodjs` packages. + * The packages must be built for this to work. + * + * @returns {Promise<{ [key: string]: string[] }>} A map of package names to files. */ -export async function frameworkPackagesFiles( - packages = frameworkPkgJsonFiles() +export async function getFrameworkPackagesFiles( + packageJsonPaths = getFrameworkPackageJsonPaths() ) { - const fileList = {} - for (const packageFile of packages) { - const packageJson = JSON.parse(fs.readFileSync(packageFile)) + const frameworkPackageFiles = {} - const arborist = new Arborist({ path: path.dirname(packageFile) }) + for (const packageJsonPath of packageJsonPaths) { + const packageJson = fs.readJSONSync(packageJsonPath) + const arborist = new Arborist({ path: path.dirname(packageJsonPath) }) const tree = await arborist.loadActual() - fileList[packageJson.name] = await packlist(tree) + frameworkPackageFiles[packageJson.name] = await packlist(tree) } - return fileList + + return frameworkPackageFiles } /** * Returns execute files for `@redwoodjs` packages. **/ -export function frameworkPackagesBins(packages = frameworkPkgJsonFiles()) { +export function getFrameworkPackagesBins( + packageJsonPaths = getFrameworkPackageJsonPaths() +) { let bins = {} - for (const packageFile of packages) { - let packageJson - - try { - packageJson = JSON.parse(fs.readFileSync(packageFile)) - } catch (e) { - throw new Error(packageFile + ' is not a valid package.json file.') - } - if (!packageJson.name) { - continue - } + for (const packageJsonPath of packageJsonPaths) { + const packageJson = fs.readJSONSync(packageJsonPath) if (!packageJson.bin) { continue @@ -128,62 +136,58 @@ export function frameworkPackagesBins(packages = frameworkPkgJsonFiles()) { bins[binName] = path.join(packageJson.name, binPath) } } + return bins } /** * Determine base package directory for any filename in it's path. + * + * @param {string} filePath + * @returns {string} The package.json path **/ -export function resolvePackageJsonPath(filePath) { - // Do we want the package name? - const packageName = filePath - .replace(REDWOOD_PACKAGES_PATH, '') - .split(path.sep)[1] - return path.join(REDWOOD_PACKAGES_PATH, packageName, 'package.json') -} +export function resolvePackageJsonPathFromFilePath(filePath) { + const packageJsonPath = findUp('package.json', path.dirname(filePath)) -export function packageJsonName(packageJsonPath) { - return JSON.parse(fs.readFileSync(packageJsonPath), 'utf-8').name + const frameworkPackageJsonPaths = getFrameworkPackageJsonPaths() + + if (frameworkPackageJsonPaths.includes(packageJsonPath)) { + return packageJsonPath + } + + // There's some directories that have their own package.json, but aren't published to npm, + // like @redwoodjs/web/apollo. We want the path to @redwoodjs/web's package.json, not @redwoodjs/web/apollo's. + return findUp('package.json', path.resolve(filePath, '../../')) } /** - * Clean Redwood packages. + * @param {string} packageJsonPath + * @returns {string} The package name if it has one */ -export function cleanPackages(packages = frameworkPkgJsonFiles()) { - const packageNames = packages.map(packageJsonName) - - execa.sync( - 'yarn lerna run build:clean', - ['--parallel', `--scope={${packageNames.join(',') + ','}}`], - { - shell: true, - stdio: 'inherit', - cwd: path.resolve(__dirname, '../../../'), - } - ) +export function getPackageName(packageJsonPath) { + return fs.readJSONSync(packageJsonPath).name } /** - * Build Redwood packages. + * Find a file by walking up parent directories. Taken from @redwoodjs/project-config. + * + * @param {string} file The file to find. + * @param {string} startingDirectory The directory to start searching from. + * @returns {string | null} The path to the file, or null if it can't be found. */ -export function buildPackages(packages = frameworkPkgJsonFiles()) { - const packageNames = packages.map(packageJsonName) - execa.sync( - 'yarn lerna run build', - ['--parallel', `--scope={${packageNames.join(',') + ','}}`], - { - shell: true, - stdio: 'inherit', - cwd: path.resolve(__dirname, '../../../'), - } - ) -} +export function findUp(file, startingDirectory = process.cwd()) { + const possibleFilepath = path.join(startingDirectory, file) + + if (fs.existsSync(possibleFilepath)) { + return possibleFilepath + } + + const parentDirectory = path.dirname(startingDirectory) + + // If we've reached the root directory, there's no file to be found. + if (parentDirectory === startingDirectory) { + return null + } -function sortObjectKeys(obj) { - return Object.keys(obj) - .sort() - .reduce((acc, key) => { - acc[key] = obj[key] - return acc - }, {}) + return findUp(file, parentDirectory) } diff --git a/tasks/framework-tools/lib/project.mjs b/tasks/framework-tools/lib/project.mjs index ad10d6fc41de..32ea43514b21 100644 --- a/tasks/framework-tools/lib/project.mjs +++ b/tasks/framework-tools/lib/project.mjs @@ -1,36 +1,51 @@ /* eslint-env node */ -import fs from 'node:fs' import path from 'node:path' import execa from 'execa' +import fs from 'fs-extra' import ora from 'ora' import { rimraf } from 'rimraf' import terminalLink from 'terminal-link' import { - frameworkDependencies, - frameworkPkgJsonFiles, - frameworkPackagesFiles, - frameworkPackagesBins, - packageJsonName, + getFrameworkDependencies, + getFrameworkPackageJsonPaths, + getFrameworkPackagesFiles, + getFrameworkPackagesBins, + getPackageName, } from './framework.mjs' /** * Sets binaries as executable and creates symlinks to `node_modules/.bin` if they do not exist. */ export function fixProjectBinaries(projectPath) { - const bins = frameworkPackagesBins() + const bins = getFrameworkPackagesBins() + for (let [binName, binPath] of Object.entries(bins)) { // if the binPath doesn't exist, create it. const binSymlink = path.join(projectPath, 'node_modules/.bin', binName) binPath = path.join(projectPath, 'node_modules', binPath) + if (!fs.existsSync(binSymlink)) { fs.mkdirSync(path.dirname(binSymlink), { recursive: true, }) + + // `fs.existsSync` checks if the target of the symlink exists, not the symlink. If the symlink exists, + // we have to remove it before we can fix it. As far as I can tell, there's no way to check if a symlink exists but not its target, + // so we try-catch removing it so that we cover both cases. + try { + fs.unlinkSync(binSymlink) + } catch (e) { + if (e.code !== 'ENOENT') { + throw new Error(e) + } + } + fs.symlinkSync(binPath, binSymlink) } + console.log('chmod +x', terminalLink(binName, binPath)) fs.chmodSync(binSymlink, '755') fs.chmodSync(binPath, '755') @@ -38,46 +53,49 @@ export function fixProjectBinaries(projectPath) { } /** - * Append all the `@redwoodjs` dependencies to the root `package.json` in a Redwood Project. + * Add all the `@redwoodjs` packages' dependencies to the root `package.json` in a Redwood Project. + * + * @param {string} packageJsonPath - The path to the root `package.json` in a Redwood Project. + * @param {{ [key: string]: string }?} dependencies - A map of package names to versions. + * + * @returns {void} */ export function addDependenciesToPackageJson( packageJsonPath, - dependencies = frameworkDependencies() + dependencies = getFrameworkDependencies() ) { - if (!fs.existsSync(packageJsonPath)) { - console.log( - `Error: The package.json path: ${packageJsonPath} does not exist.` - ) - process.exit(1) - } - const packageJsonLink = terminalLink( 'package.json', 'file://' + packageJsonPath ) - const numOfDeps = Object.keys(dependencies).length + const numberOfDependencies = Object.keys(dependencies).length const spinner = ora( - `Adding ${numOfDeps} framework dependencies to ${packageJsonLink}...` + `Adding ${numberOfDependencies} framework dependencies to ${packageJsonLink}...` ).start() - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) + const packageJson = fs.readJSONSync(packageJsonPath) + packageJson.dependencies = { - ...(packageJson.dependencies || {}), + ...packageJson.dependencies, ...dependencies, } - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)) + + fs.writeJSONSync(packageJsonPath, packageJson, { spaces: 2 }) + spinner.succeed( - `Added ${numOfDeps} framework dependencies to ${packageJsonLink}` + `Added ${numberOfDependencies} framework dependencies to ${packageJsonLink}` ) } export function installProjectPackages(projectPath) { const spinner = ora("Running 'yarn install'...") + spinner.start() + try { - execa.sync('yarn install', { + execa.commandSync('yarn install', { cwd: projectPath, shell: true, }) @@ -89,21 +107,22 @@ export function installProjectPackages(projectPath) { 'file://' + path.join(projectPath, 'yarn-error.log') )} for more information.` ) + console.log('-'.repeat(80)) } } export async function copyFrameworkFilesToProject( projectPath, - packages = frameworkPkgJsonFiles() + packageJsonPaths = getFrameworkPackageJsonPaths() ) { // Loop over every package, delete all existing files, copy over the new files, // and fix binaries. - const packagesFiles = await frameworkPackagesFiles(packages) + const packagesFiles = await getFrameworkPackagesFiles(packageJsonPaths) - const packageNamesToPaths = packages.reduce( + const packageNamesToPaths = packageJsonPaths.reduce( (packageNamesToPaths, packagePath) => { - packageNamesToPaths[packageJsonName(packagePath)] = + packageNamesToPaths[getPackageName(packagePath)] = path.dirname(packagePath) return packageNamesToPaths }, @@ -111,22 +130,21 @@ export async function copyFrameworkFilesToProject( ) for (const [packageName, files] of Object.entries(packagesFiles)) { - const packageDstPath = path.join(projectPath, 'node_modules', packageName) + const packageDistPath = path.join(projectPath, 'node_modules', packageName) + console.log( - terminalLink(packageName, 'file://' + packageDstPath), + terminalLink(packageName, 'file://' + packageDistPath), files.length, 'files' ) - await rimraf(packageDstPath) + + await rimraf(packageDistPath) for (const file of files) { const src = path.join(packageNamesToPaths[packageName], file) - const dst = path.join(packageDstPath, file) - fs.mkdirSync(path.dirname(dst), { recursive: true }) - fs.copyFileSync(src, dst) + const dest = path.join(packageDistPath, file) + fs.mkdirSync(path.dirname(dest), { recursive: true }) + fs.copyFileSync(src, dest) } } - - console.log() - fixProjectBinaries(projectPath) }