Skip to content

Commit

Permalink
move dep-installer stuff to own dir
Browse files Browse the repository at this point in the history
  • Loading branch information
flotwig committed Mar 17, 2022
1 parent b0fe8bf commit 6e64fc0
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 275 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@
// Volar is the main extension that powers Vue's language features.
// "volar.autoCompleteRefs": false,
"volar.takeOverMode.enabled": true,

"editor.tabSize": 2,
}
3 changes: 2 additions & 1 deletion packages/server/test/integration/plugins_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ require('../spec_helper')

const plugins = require('../../lib/plugins')
const Fixtures = require('@tooling/system-tests/lib/fixtures')
const { scaffoldCommonNodeModules } = require('@tooling/system-tests/lib/dep-installer')

const pluginsFile = Fixtures.projectPath('plugin-before-browser-launch-deprecation/cypress/plugins/index.js')

describe('lib/plugins', () => {
beforeEach(async () => {
Fixtures.scaffoldProject('plugin-before-browser-launch-deprecation')
await Fixtures.scaffoldCommonNodeModules()
await scaffoldCommonNodeModules()
})

afterEach(() => {
Expand Down
3 changes: 2 additions & 1 deletion scripts/binary/smoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Promise = require('bluebird')
const os = require('os')
const verify = require('../../cli/lib/tasks/verify')
const Fixtures = require('@tooling/system-tests/lib/fixtures')
const { scaffoldCommonNodeModules } = require('@tooling/system-tests/lib/dep-installer')

const fs = Promise.promisifyAll(fse)

Expand Down Expand Up @@ -160,7 +161,7 @@ const runFailingProjectTest = function (buildAppExecutable, e2e) {
}

const test = async function (buildAppExecutable) {
await Fixtures.scaffoldCommonNodeModules()
await scaffoldCommonNodeModules()
Fixtures.scaffoldProject('e2e')
const e2e = Fixtures.projectPath('e2e')

Expand Down
267 changes: 267 additions & 0 deletions system-tests/lib/dep-installer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import fs from 'fs-extra'
import path from 'path'
import cachedir from 'cachedir'
import execa from 'execa'
import { cyTmpDir, projectPath, projects, root } from '../fixtures'
import tempDir from 'temp-dir'

/**
* Symlink the cached `node_modules` directory to the temp project directory's `node_modules`.
*/
async function symlinkNodeModulesFromCache (project: string, cacheDir: string): Promise<void> {
const from = path.join(projectPath(project), 'node_modules')

try {
await fs.stat(cacheDir)
} catch (err) {
console.log(`📦 Creating a new node_modules cache dir at ${cacheDir}`)
await fs.mkdirp(cacheDir)
}

try {
await fs.symlink(cacheDir, from, 'junction')
} catch (err) {
if (err.code !== 'EEXIST') return
}
console.log(`📦 node_modules symlink created at ${from}`)
}

/**
* Given a package name, returns the path to the module directory on disk.
*/
function pathToPackage (pkg: string): string {
return path.dirname(require.resolve(`${pkg}/package.json`))
}


/**
* Given a path to a `package.json`, convert any references to development
* versions of packages to absolute paths, so `yarn` will not reach out to
* the Internet to obtain these packages once it runs in the temp dir.
* @returns a list of dependency names that were updated
*/
async function makeWorkspacePackagesAbsolute (pathToPkgJson: string): Promise<string[]> {
const pkgJson = await fs.readJson(pathToPkgJson)
const updatedDeps: string[] = []

for (const deps of [pkgJson.dependencies, pkgJson.devDependencies, pkgJson.optionalDependencies]) {
for (const dep in deps) {
const version = deps[dep]

if (version.startsWith('file:')) {
const absPath = pathToPackage(dep)

console.log(`📦 Setting absolute path in package.json for ${dep}: ${absPath}.`)

deps[dep] = `file:${absPath}`
updatedDeps.push(dep)
}
}
}

await fs.writeJson(pathToPkgJson, pkgJson)

return updatedDeps
}

function getYarnCommand (opts: {
yarnV311: boolean
updateYarnLock: boolean
isCI: boolean
runScripts: boolean
}): string {
let cmd = `yarn install`

if (opts.yarnV311) {
// @see https://yarnpkg.com/cli/install
if (!opts.runScripts) cmd += ' --mode=skip-build'

if (!opts.updateYarnLock) cmd += ' --immutable'

return cmd
}

cmd += ' --prefer-offline'

if (!opts.runScripts) cmd += ' --ignore-scripts'

if (!opts.updateYarnLock) cmd += ' --frozen-lockfile'

// yarn v1 has a bug with integrity checking and local cache/dependencies
// @see https://github.com/yarnpkg/yarn/issues/6407
cmd += ' --update-checksums'

// in CircleCI, this offline cache can be used
if (opts.isCI) cmd += ` --cache-folder=~/.yarn-${process.platform} `
else cmd += ` --cache-folder=${path.join(tempDir, 'cy-system-tests-yarn-cache', String(Date.now()))}`

return cmd
}

type Dependencies = Record<string, string>

/**
* Type for package.json files for system-tests example projects.
*/
type SystemTestPkgJson = {
/**
* By default, scaffolding will run `yarn install` if there is a `package.json`.
* This option, if set, disables that.
*/
_cySkipYarnInstall?: boolean
/**
* Run the yarn v2-style install command instead of yarn v1-style.
*/
_cyYarnV311?: 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.
*/
_cyRunScripts?: boolean
dependencies?: Dependencies
devDependencies?: Dependencies
optionalDependencies?: Dependencies
}

/**
* Given a `system-tests` project name, detect and install the `node_modules`
* specified in the project's `package.json`. No-op if no `package.json` is found.
*/
export async function scaffoldProjectNodeModules (project: string, updateYarnLock: boolean = !!process.env.UPDATE_YARN_LOCK): Promise<void> {
const projectDir = projectPath(project)
const relativePathToMonorepoRoot = path.relative(
path.join(projects, project),
path.join(root, '..'),
)
const projectPkgJsonPath = path.join(projectDir, 'package.json')

const runCmd = async (cmd) => {
console.log(`📦 Running "${cmd}" in ${projectDir}`)
await execa(cmd, { cwd: projectDir, stdio: 'inherit', shell: true })
}

const cacheDir = path.join(cachedir('cy-system-tests-node-modules'), project, 'node_modules')

async function removeWorkspacePackages (packages: string[]): Promise<void> {
for (const dep of packages) {
const depDir = path.join(cacheDir, dep)

await fs.remove(depDir)
}
}

try {
// this will throw and exit early if the package.json does not exist
const pkgJson: SystemTestPkgJson = require(projectPkgJsonPath)

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

if (pkgJson._cySkipYarnInstall) {
return console.log(`📦 cySkipYarnInstall set in package.json, skipping yarn steps`)
}

if (!pkgJson.dependencies && !pkgJson.devDependencies && !pkgJson.optionalDependencies) {
return console.log(`📦 No dependencies found, skipping yarn steps`)
}

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

// 2. Before running `yarn`, resolve workspace deps to absolute paths.
// This is required to fix `yarn install` for workspace-only packages.
const workspaceDeps = await makeWorkspacePackagesAbsolute(projectPkgJsonPath)

await removeWorkspacePackages(workspaceDeps)

// 3. Fix relative paths in temp dir's `yarn.lock`.
const relativePathToProjectDir = path.relative(projectDir, path.join(root, '..'))
const yarnLockPath = path.join(projectDir, 'yarn.lock')

console.log('📦 Writing yarn.lock with fixed relative paths to temp dir')
try {
const yarnLock = (await fs.readFile(yarnLockPath, 'utf8'))
.replaceAll(relativePathToMonorepoRoot, relativePathToProjectDir)

await fs.writeFile(yarnLockPath, yarnLock)
} catch (err) {
if (err.code !== 'ENOENT' || !updateYarnLock) throw err

console.log('📦 No yarn.lock found, continuing')
}

// 4. Run `yarn install`.
const cmd = getYarnCommand({
updateYarnLock,
yarnV311: pkgJson._cyYarnV311,
isCI: !!process.env.CI,
runScripts: pkgJson._cyRunScripts,
})

await runCmd(cmd)

console.log(`📦 Copying yarn.lock and fixing relative paths for ${project}`)

// Replace workspace dependency paths in `yarn.lock` with tokens so it can be the same
// for all developers
const yarnLock = (await fs.readFile(yarnLockPath, 'utf8'))
.replaceAll(relativePathToProjectDir, relativePathToMonorepoRoot)

await fs.writeFile(path.join(projects, project, 'yarn.lock'), yarnLock)

// 5. After `yarn install`, we must now symlink *over* all workspace dependencies, or else
// `require` calls from `yarn install`'d workspace deps to peer deps will fail.
await removeWorkspacePackages(workspaceDeps)
for (const dep of workspaceDeps) {
console.log(`📦 Symlinking workspace dependency: ${dep}`)
const depDir = path.join(cacheDir, dep)

await fs.symlink(pathToPackage(dep), depDir, 'junction')
}
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') return

console.error(`⚠ An error occurred while installing the node_modules for ${project}.`)
console.error([err.message, err.stack].join('\n'))
throw err
}
}

export async function scaffoldCommonNodeModules () {
await Promise.all([
'@cypress/code-coverage',
'@cypress/webpack-dev-server',
'@packages/socket',
'@packages/ts',
'@tooling/system-tests',
'bluebird',
'chai',
'dayjs',
'debug',
'execa',
'fs-extra',
'https-proxy-agent',
'jimp',
'lazy-ass',
'lodash',
'proxyquire',
'react',
'semver',
'systeminformation',
'tslib',
'typescript',
].map(symlinkNodeModule))
}

export async function symlinkNodeModule (pkg) {
const from = path.join(cyTmpDir, 'node_modules', pkg)
const to = pathToPackage(pkg)

await fs.ensureDir(path.dirname(from))
try {
await fs.symlink(to, from, 'junction')
} catch (err) {
if (err.code === 'EEXIST') return

throw err
}
}
Loading

0 comments on commit 6e64fc0

Please sign in to comment.