Skip to content

Commit 360d8b7

Browse files
committed
clean up dep-installer logic and split yarn out
1 parent 15faef4 commit 360d8b7

File tree

4 files changed

+119
-91
lines changed

4 files changed

+119
-91
lines changed

system-tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ You can also set special properties in a test project's `package.json` to influe
113113

114114
`package.json` Property Name | Type | Description
115115
--- | --- | ---
116-
`_cySkipYarnInstall` | `boolean` | If `true`, skip the automatic `yarn install` for this package, even though it has a `package.json`.
116+
`_cySkipDepInstall` | `boolean` | If `true`, skip the automatic `yarn install` for this package, even though it has a `package.json`.
117117
`_cyYarnV311` | `boolean` | Run the yarn v3.1.1-style install command instead of yarn v1-style.
118118
`_cyRunScripts` | `boolean` | By default, the automatic `yarn install` will not run postinstall scripts. This option, if set, will cause postinstall scripts to run for this project.
119119

system-tests/lib/dep-installer/index.ts

Lines changed: 78 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import path from 'path'
33
import cachedir from 'cachedir'
44
import execa from 'execa'
55
import { cyTmpDir, projectPath, projects, root } from '../fixtures'
6-
import tempDir from 'temp-dir'
6+
import { getYarnCommand } from './yarn'
7+
8+
/**
9+
* Given a package name, returns the path to the module directory on disk.
10+
*/
11+
export function pathToPackage (pkg: string): string {
12+
return path.dirname(require.resolve(`${pkg}/package.json`))
13+
}
714

815
/**
916
* Symlink the cached `node_modules` directory to the temp project directory's `node_modules`.
@@ -26,13 +33,62 @@ async function symlinkNodeModulesFromCache (project: string, cacheDir: string):
2633
console.log(`📦 node_modules symlink created at ${from}`)
2734
}
2835

36+
type Dependencies = Record<string, string>
37+
2938
/**
30-
* Given a package name, returns the path to the module directory on disk.
39+
* Type for package.json files for system-tests example projects.
3140
*/
32-
function pathToPackage (pkg: string): string {
33-
return path.dirname(require.resolve(`${pkg}/package.json`))
41+
type SystemTestPkgJson = {
42+
/**
43+
* By default, scaffolding will run `yarn install` if there is a `package.json`.
44+
* This option, if set, disables that.
45+
*/
46+
_cySkipDepInstall?: boolean
47+
/**
48+
* Run the yarn v2-style install command instead of yarn v1-style.
49+
*/
50+
_cyYarnV311?: boolean
51+
/**
52+
* By default, the automatic `yarn install` will not run postinstall scripts. This
53+
* option, if set, will cause postinstall scripts to run for this project.
54+
*/
55+
_cyRunScripts?: boolean
56+
dependencies?: Dependencies
57+
devDependencies?: Dependencies
58+
optionalDependencies?: Dependencies
3459
}
3560

61+
async function getLockFilename (dir: string) {
62+
const hasYarnLock = !!await fs.stat(path.join(dir, 'yarn.lock')).catch(() => false)
63+
const hasNpmLock = !!await fs.stat(path.join(dir, 'package-lock.json')).catch(() => false)
64+
65+
if (hasYarnLock && hasNpmLock) throw new Error(`The example project at '${dir}' has conflicting lockfiles. Only use one package manager's lockfile per project.`)
66+
67+
if (hasYarnLock) return 'yarn.lock'
68+
69+
if (hasNpmLock) return 'package-lock.json'
70+
}
71+
72+
function getRelativePathToProjectDir (projectDir: string) {
73+
return path.relative(projectDir, path.join(root, '..'))
74+
}
75+
76+
async function restoreLockFileRelativePaths (opts: { projectDir: string, lockFilePath: string, relativePathToMonorepoRoot: string }) {
77+
const relativePathToProjectDir = getRelativePathToProjectDir(opts.projectDir)
78+
const lockFileContents = (await fs.readFile(opts.lockFilePath, 'utf8'))
79+
.replaceAll(opts.relativePathToMonorepoRoot, relativePathToProjectDir)
80+
81+
await fs.writeFile(opts.lockFilePath, lockFileContents)
82+
}
83+
84+
async function normalizeLockFileRelativePaths (opts: { project: string, projectDir: string, lockFilePath: string, lockFilename: string, relativePathToMonorepoRoot: string }) {
85+
const relativePathToProjectDir = getRelativePathToProjectDir(opts.projectDir)
86+
const lockFileContents = (await fs.readFile(opts.lockFilePath, 'utf8'))
87+
.replaceAll(relativePathToProjectDir, opts.relativePathToMonorepoRoot)
88+
89+
// write back to the original project dir, not the tmp copy
90+
await fs.writeFile(path.join(projects, opts.project, 'yarn.lock'), lockFileContents)
91+
}
3692

3793
/**
3894
* Given a path to a `package.json`, convert any references to development
@@ -64,68 +120,10 @@ async function makeWorkspacePackagesAbsolute (pathToPkgJson: string): Promise<st
64120
return updatedDeps
65121
}
66122

67-
function getYarnCommand (opts: {
68-
yarnV311: boolean
69-
updateYarnLock: boolean
70-
isCI: boolean
71-
runScripts: boolean
72-
}): string {
73-
let cmd = `yarn install`
74-
75-
if (opts.yarnV311) {
76-
// @see https://yarnpkg.com/cli/install
77-
if (!opts.runScripts) cmd += ' --mode=skip-build'
78-
79-
if (!opts.updateYarnLock) cmd += ' --immutable'
80-
81-
return cmd
82-
}
83-
84-
cmd += ' --prefer-offline'
85-
86-
if (!opts.runScripts) cmd += ' --ignore-scripts'
87-
88-
if (!opts.updateYarnLock) cmd += ' --frozen-lockfile'
89-
90-
// yarn v1 has a bug with integrity checking and local cache/dependencies
91-
// @see https://github.com/yarnpkg/yarn/issues/6407
92-
cmd += ' --update-checksums'
93-
94-
// in CircleCI, this offline cache can be used
95-
if (opts.isCI) cmd += ` --cache-folder=~/.yarn-${process.platform} `
96-
else cmd += ` --cache-folder=${path.join(tempDir, 'cy-system-tests-yarn-cache', String(Date.now()))}`
97-
98-
return cmd
99-
}
100-
101-
type Dependencies = Record<string, string>
102-
103-
/**
104-
* Type for package.json files for system-tests example projects.
105-
*/
106-
type SystemTestPkgJson = {
107-
/**
108-
* By default, scaffolding will run `yarn install` if there is a `package.json`.
109-
* This option, if set, disables that.
110-
*/
111-
_cySkipYarnInstall?: boolean
112-
/**
113-
* Run the yarn v2-style install command instead of yarn v1-style.
114-
*/
115-
_cyYarnV311?: boolean
116-
/**
117-
* By default, the automatic `yarn install` will not run postinstall scripts. This
118-
* option, if set, will cause postinstall scripts to run for this project.
119-
*/
120-
_cyRunScripts?: boolean
121-
dependencies?: Dependencies
122-
devDependencies?: Dependencies
123-
optionalDependencies?: Dependencies
124-
}
125-
126123
/**
127124
* Given a `system-tests` project name, detect and install the `node_modules`
128125
* specified in the project's `package.json`. No-op if no `package.json` is found.
126+
* Will use `yarn` or `npm` based on the lockfile present.
129127
*/
130128
export async function scaffoldProjectNodeModules (project: string, updateYarnLock: boolean = !!process.env.UPDATE_YARN_LOCK): Promise<void> {
131129
const projectDir = projectPath(project)
@@ -156,40 +154,34 @@ export async function scaffoldProjectNodeModules (project: string, updateYarnLoc
156154

157155
console.log(`📦 Found package.json for project ${project}.`)
158156

159-
if (pkgJson._cySkipYarnInstall) {
160-
return console.log(`📦 cySkipYarnInstall set in package.json, skipping yarn steps`)
157+
if (pkgJson._cySkipDepInstall) {
158+
return console.log(`📦 _cySkipDepInstall set in package.json, skipping dep-installer steps`)
161159
}
162160

163161
if (!pkgJson.dependencies && !pkgJson.devDependencies && !pkgJson.optionalDependencies) {
164-
return console.log(`📦 No dependencies found, skipping yarn steps`)
162+
return console.log(`📦 No dependencies found, skipping dep-installer steps`)
165163
}
166164

167165
// 1. Ensure there is a cache directory set up for this test project's `node_modules`.
168166
await symlinkNodeModulesFromCache(project, cacheDir)
169167

170-
// 2. Before running `yarn`, resolve workspace deps to absolute paths.
168+
// 2. Before running the package installer, resolve workspace deps to absolute paths.
171169
// This is required to fix `yarn install` for workspace-only packages.
172170
const workspaceDeps = await makeWorkspacePackagesAbsolute(projectPkgJsonPath)
173171

174172
await removeWorkspacePackages(workspaceDeps)
175173

176-
// 3. Fix relative paths in temp dir's `yarn.lock`.
177-
const relativePathToProjectDir = path.relative(projectDir, path.join(root, '..'))
178-
const yarnLockPath = path.join(projectDir, 'yarn.lock')
174+
const lockFilename = await getLockFilename(projectDir)
179175

180-
console.log('📦 Writing yarn.lock with fixed relative paths to temp dir')
181-
try {
182-
const yarnLock = (await fs.readFile(yarnLockPath, 'utf8'))
183-
.replaceAll(relativePathToMonorepoRoot, relativePathToProjectDir)
176+
if (!lockFilename) throw new Error(`package.json exists, but missing a lockfile for example project in '${projectDir}'`)
184177

185-
await fs.writeFile(yarnLockPath, yarnLock)
186-
} catch (err) {
187-
if (err.code !== 'ENOENT' || !updateYarnLock) throw err
178+
// 3. Fix relative paths in temp dir's lockfile.
179+
const lockFilePath = path.join(projectDir, lockFilename)
188180

189-
console.log('📦 No yarn.lock found, continuing')
190-
}
181+
console.log(`📦 Writing ${lockFilename} with fixed relative paths to temp dir`)
182+
await restoreLockFileRelativePaths({ projectDir, lockFilePath, relativePathToMonorepoRoot })
191183

192-
// 4. Run `yarn install`.
184+
// 4. Run `yarn/npm install`.
193185
const cmd = getYarnCommand({
194186
updateYarnLock,
195187
yarnV311: pkgJson._cyYarnV311,
@@ -199,17 +191,13 @@ export async function scaffoldProjectNodeModules (project: string, updateYarnLoc
199191

200192
await runCmd(cmd)
201193

202-
console.log(`📦 Copying yarn.lock and fixing relative paths for ${project}`)
203-
204-
// Replace workspace dependency paths in `yarn.lock` with tokens so it can be the same
205-
// for all developers
206-
const yarnLock = (await fs.readFile(yarnLockPath, 'utf8'))
207-
.replaceAll(relativePathToProjectDir, relativePathToMonorepoRoot)
208-
209-
await fs.writeFile(path.join(projects, project, 'yarn.lock'), yarnLock)
194+
// 5. Now that the lockfile is up to date, update workspace dependency paths in the lockfile with monorepo
195+
// relative paths so it can be the same for all developers
196+
console.log(`📦 Copying ${lockFilename} and fixing relative paths for ${project}`)
197+
await normalizeLockFileRelativePaths({ project, projectDir, lockFilePath, lockFilename, relativePathToMonorepoRoot })
210198

211-
// 5. After `yarn install`, we must now symlink *over* all workspace dependencies, or else
212-
// `require` calls from `yarn install`'d workspace deps to peer deps will fail.
199+
// 6. After install, we must now symlink *over* all workspace dependencies, or else
200+
// `require` calls from installed workspace deps to peer deps will fail.
213201
await removeWorkspacePackages(workspaceDeps)
214202
for (const dep of workspaceDeps) {
215203
console.log(`📦 Symlinking workspace dependency: ${dep}`)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import path from 'path'
2+
import tempDir from 'temp-dir'
3+
4+
export function getYarnCommand (opts: {
5+
yarnV311: boolean
6+
updateYarnLock: boolean
7+
isCI: boolean
8+
runScripts: boolean
9+
}): string {
10+
let cmd = `yarn install`
11+
12+
if (opts.yarnV311) {
13+
// @see https://yarnpkg.com/cli/install
14+
if (!opts.runScripts) cmd += ' --mode=skip-build'
15+
16+
if (!opts.updateYarnLock) cmd += ' --immutable'
17+
18+
return cmd
19+
}
20+
21+
cmd += ' --prefer-offline'
22+
23+
if (!opts.runScripts) cmd += ' --ignore-scripts'
24+
25+
if (!opts.updateYarnLock) cmd += ' --frozen-lockfile'
26+
27+
// yarn v1 has a bug with integrity checking and local cache/dependencies
28+
// @see https://github.com/yarnpkg/yarn/issues/6407
29+
cmd += ' --update-checksums'
30+
31+
// in CircleCI, this offline cache can be used
32+
if (opts.isCI) cmd += ` --cache-folder=~/.yarn-${process.platform} `
33+
else cmd += ` --cache-folder=${path.join(tempDir, 'cy-system-tests-yarn-cache', String(Date.now()))}`
34+
35+
return cmd
36+
}

system-tests/lib/system-tests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,8 @@ const systemTests = {
950950
await settings.write(e2ePath, ctx.settings)
951951
}
952952

953+
if (options.onAfterScaffold) await options.onAfterScaffold()
954+
953955
let stdout = ''
954956
let stderr = ''
955957

@@ -1090,6 +1092,8 @@ const systemTests = {
10901092
sp.on('exit', resolve)
10911093
})
10921094

1095+
if (options.onAfterExec) await options.onAfterExec()
1096+
10931097
await copy()
10941098

10951099
return exit(exitCode)

0 commit comments

Comments
 (0)