mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-04 05:50:47 -05:00
feat: improved DX for unified-desktop-gui (#18099)
- Moves graphql-codegen config to the root, which will serve all packages needing it - Adds gulpfile for coordinating scripts related to dev environment in launchpad app - yarn dev from the root runs yarn gulp dev, which: Runs autobarrel for rolling up the @packages/graphql files Cleans the dist & cache for .vite Starts the a codegen watcher for Nexus Starts the graphql-codegen --watch & highlights output Starts vite servers for launchpad & app Starts electron watch.js
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../.eslintrc.json",
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import getenv from 'getenv'
|
||||
|
||||
// Where to fetch the remote "federated" schema TODO: add w/ stitching PR
|
||||
// export const CYPRESS_INTERNAL_CLOUD_ENV = 'production'
|
||||
|
||||
export const CYPRESS_INTERNAL_DEBUG_PORT_STARTUP = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_STARTUP', 7200)
|
||||
|
||||
export const CYPRESS_INTERNAL_DEBUG_PORT_ELECTRON = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_ELECTRON', 7201)
|
||||
|
||||
export const CYPRESS_INTERNAL_DEBUG_PORT_CODEGEN = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_CODEGEN', 7202)
|
||||
@@ -0,0 +1,69 @@
|
||||
import gulp from 'gulp'
|
||||
import { autobarrelWatcher } from './tasks/gulpAutobarrel'
|
||||
import { startCypressWatch } from './tasks/gulpCypress'
|
||||
import { graphqlCodegen, graphqlCodegenWatch, nexusCodegen, nexusCodegenWatch } from './tasks/gulpGraphql'
|
||||
import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad } from './tasks/gulpVite'
|
||||
import { makePathMap } from './utils/makePathMap'
|
||||
|
||||
gulp.task(
|
||||
'dev',
|
||||
gulp.series(
|
||||
// Autobarrel watcher
|
||||
autobarrelWatcher,
|
||||
|
||||
// Fetch the latest "remote" schema from the Cypress cloud
|
||||
// TODO: with stitching bracnh
|
||||
// fetchCloudSchema,
|
||||
|
||||
gulp.parallel(
|
||||
// Clean the vite apps
|
||||
viteCleanApp,
|
||||
viteCleanLaunchpad,
|
||||
),
|
||||
// Codegen for our GraphQL Server so we have the latest schema to build the frontend codegen correctly
|
||||
nexusCodegenWatch,
|
||||
|
||||
// ... and generate the correct GraphQL types for the frontend
|
||||
graphqlCodegenWatch,
|
||||
|
||||
// Now that we have the codegen, we can start the frontend(s)
|
||||
gulp.parallel(
|
||||
viteApp,
|
||||
viteLaunchpad,
|
||||
),
|
||||
|
||||
// And we're finally ready for electron, watching for changes in /graphql to auto-restart the server
|
||||
startCypressWatch,
|
||||
),
|
||||
)
|
||||
|
||||
gulp.task('buildProd', gulp.series(
|
||||
nexusCodegen,
|
||||
graphqlCodegen,
|
||||
))
|
||||
|
||||
gulp.task(
|
||||
'postinstall',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
// Clean the vite apps
|
||||
viteCleanApp,
|
||||
viteCleanLaunchpad,
|
||||
),
|
||||
'buildProd',
|
||||
),
|
||||
)
|
||||
|
||||
// gulp.task(
|
||||
// 'devLegacy', // Tim: TODO
|
||||
// )
|
||||
|
||||
// gulp.task(
|
||||
// 'debug', // Tim: TODO
|
||||
// )
|
||||
|
||||
gulp.task(makePathMap)
|
||||
gulp.task(nexusCodegen)
|
||||
gulp.task(nexusCodegenWatch)
|
||||
gulp.task(graphqlCodegen)
|
||||
gulp.task(graphqlCodegenWatch)
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable */
|
||||
// Auto-generated by makePathMap.ts
|
||||
import path from 'path'
|
||||
export const monorepoPaths = {
|
||||
root: path.join(__dirname, '../..'),
|
||||
pkgDir: path.join(__dirname, '../../packages'),
|
||||
pkgApp: path.join(__dirname, '../../packages/app'),
|
||||
pkgDesktopGui: path.join(__dirname, '../../packages/desktop-gui'),
|
||||
pkgDriver: path.join(__dirname, '../../packages/driver'),
|
||||
pkgElectron: path.join(__dirname, '../../packages/electron'),
|
||||
pkgExample: path.join(__dirname, '../../packages/example'),
|
||||
pkgExtension: path.join(__dirname, '../../packages/extension'),
|
||||
pkgFrontendShared: path.join(__dirname, '../../packages/frontend-shared'),
|
||||
pkgGraphql: path.join(__dirname, '../../packages/graphql'),
|
||||
pkgHttpsProxy: path.join(__dirname, '../../packages/https-proxy'),
|
||||
pkgLauncher: path.join(__dirname, '../../packages/launcher'),
|
||||
pkgLaunchpad: path.join(__dirname, '../../packages/launchpad'),
|
||||
pkgNetStubbing: path.join(__dirname, '../../packages/net-stubbing'),
|
||||
pkgNetwork: path.join(__dirname, '../../packages/network'),
|
||||
pkgProxy: path.join(__dirname, '../../packages/proxy'),
|
||||
pkgReporter: path.join(__dirname, '../../packages/reporter'),
|
||||
pkgResolveDist: path.join(__dirname, '../../packages/resolve-dist'),
|
||||
pkgRewriter: path.join(__dirname, '../../packages/rewriter'),
|
||||
pkgRoot: path.join(__dirname, '../../packages/root'),
|
||||
pkgRunner: path.join(__dirname, '../../packages/runner'),
|
||||
pkgRunnerCt: path.join(__dirname, '../../packages/runner-ct'),
|
||||
pkgRunnerShared: path.join(__dirname, '../../packages/runner-shared'),
|
||||
pkgServer: path.join(__dirname, '../../packages/server'),
|
||||
pkgSocket: path.join(__dirname, '../../packages/socket'),
|
||||
pkgTs: path.join(__dirname, '../../packages/ts'),
|
||||
pkgTypes: path.join(__dirname, '../../packages/types'),
|
||||
pkgUiComponents: path.join(__dirname, '../../packages/ui-components'),
|
||||
pkgWebConfig: path.join(__dirname, '../../packages/web-config')
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { resolveAutobarrelConfig, autobarrelWatch } from 'autobarrel'
|
||||
import path from 'path'
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
|
||||
/**
|
||||
* Creates "barrel" files according to config in autobarrel.json:
|
||||
* https://github.com/tgriesser/autobarrel
|
||||
* Particularly useful in @packages/graphql because we want to import all
|
||||
* types into the root schema
|
||||
*/
|
||||
export async function autobarrelWatcher () {
|
||||
await autobarrelWatch(
|
||||
await resolveAutobarrelConfig({
|
||||
path: path.join(monorepoPaths.root, 'autobarrel.json'),
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import chokidar from 'chokidar'
|
||||
import path from 'path'
|
||||
import childProcess, { ChildProcess } from 'child_process'
|
||||
import pDefer from 'p-defer'
|
||||
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
|
||||
/**
|
||||
* Starts cypress, but watches the GraphQL files & restarts the server
|
||||
* when any of those change
|
||||
*/
|
||||
export function startCypressWatch () {
|
||||
const watcher = chokidar.watch('src/**/*.{js,ts}', {
|
||||
cwd: monorepoPaths.pkgGraphql,
|
||||
ignored: /\.gen\.ts/,
|
||||
ignoreInitial: true,
|
||||
})
|
||||
let child: ChildProcess | null = null
|
||||
|
||||
let isClosing = false
|
||||
let isRestarting = false
|
||||
|
||||
const argv = process.argv.slice(3)
|
||||
const pathToCli = path.resolve(monorepoPaths.root, 'cli', 'bin', 'cypress')
|
||||
|
||||
function openServer () {
|
||||
if (child) {
|
||||
child.removeAllListeners()
|
||||
}
|
||||
|
||||
if (!argv.includes('--project') && !argv.includes('--global')) {
|
||||
argv.push('--global')
|
||||
}
|
||||
|
||||
if (!argv.includes('--dev')) {
|
||||
argv.push('--dev')
|
||||
}
|
||||
|
||||
child = childProcess.fork(pathToCli, ['open', ...argv], {
|
||||
stdio: 'inherit',
|
||||
execArgv: [],
|
||||
env: {
|
||||
...process.env,
|
||||
LAUNCHPAD: '1',
|
||||
CYPRESS_INTERNAL_DEV_WATCH: 'true',
|
||||
},
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
if (isClosing) {
|
||||
process.exit(code ?? 0)
|
||||
}
|
||||
})
|
||||
|
||||
child.on('disconnect', () => {
|
||||
child = null
|
||||
})
|
||||
}
|
||||
|
||||
async function restartServer () {
|
||||
if (isRestarting) {
|
||||
return
|
||||
}
|
||||
|
||||
const dfd = pDefer()
|
||||
|
||||
if (child) {
|
||||
child.on('exit', dfd.resolve)
|
||||
isRestarting = true
|
||||
child.send('close')
|
||||
} else {
|
||||
dfd.resolve()
|
||||
}
|
||||
|
||||
await dfd.promise
|
||||
isRestarting = false
|
||||
openServer()
|
||||
}
|
||||
|
||||
watcher.on('add', restartServer)
|
||||
watcher.on('change', restartServer)
|
||||
|
||||
openServer()
|
||||
|
||||
process.on('beforeExit', () => {
|
||||
isClosing = true
|
||||
child?.send('close')
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import path from 'path'
|
||||
import pDefer from 'p-defer'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { nexusTypegen, watchNexusTypegen } from '../utils/nexusTypegenUtil'
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
import { spawned } from '../utils/childProcessUtils'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
export async function nexusCodegen () {
|
||||
return nexusTypegen({
|
||||
cwd: monorepoPaths.pkgGraphql,
|
||||
filePath: path.join(monorepoPaths.pkgGraphql, 'src/schema.ts'),
|
||||
outputPath: path.join(monorepoPaths.pkgGraphql, 'src/gen/nxs.gen.ts'),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches & regenerates the
|
||||
*/
|
||||
export async function nexusCodegenWatch () {
|
||||
return watchNexusTypegen({
|
||||
cwd: monorepoPaths.pkgGraphql,
|
||||
watchPaths: [
|
||||
'src/**/*.ts',
|
||||
],
|
||||
filePath: path.join(monorepoPaths.pkgGraphql, 'src/schema.ts'),
|
||||
outputPath: path.join(monorepoPaths.pkgGraphql, 'src/gen/nxs.gen.ts'),
|
||||
})
|
||||
}
|
||||
|
||||
export async function graphqlCodegen () {
|
||||
return spawned('gql-codegen', 'yarn graphql-codegen --config graphql-codegen.yml', {
|
||||
cwd: monorepoPaths.root,
|
||||
waitForExit: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function graphqlCodegenWatch () {
|
||||
const spawned = spawn('graphql-codegen', ['--watch', '--config', 'graphql-codegen.yml'], {
|
||||
cwd: monorepoPaths.root,
|
||||
})
|
||||
const dfd = pDefer()
|
||||
let hasResolved = false
|
||||
|
||||
spawned.stdout.on('data', (chunk) => {
|
||||
const strs = `${chunk}`.split('\n').filter((f) => f)
|
||||
const timestampRegex = /\[\d{2}:\d{2}:\d{2}\]/
|
||||
const isFailureBlock = strs.some((s) => s.includes('[failed]'))
|
||||
|
||||
strs.forEach((str) => {
|
||||
let codegenMsg = timestampRegex.test(str) ? str.slice(10) : str
|
||||
|
||||
if (codegenMsg.includes('Watching for changes') && !hasResolved) {
|
||||
dfd.resolve({})
|
||||
}
|
||||
|
||||
if (codegenMsg === str) {
|
||||
process.stdout.write(
|
||||
`${chalk.cyan('graphqlCodegen')}: ${chalk.gray(str)}\n`,
|
||||
)
|
||||
} else if (codegenMsg.startsWith(' Generate .') || isFailureBlock) {
|
||||
codegenMsg = codegenMsg.includes('[failed]')
|
||||
? chalk.red(codegenMsg)
|
||||
: chalk.yellow(codegenMsg)
|
||||
|
||||
process.stdout.write(`${chalk.cyan('graphqlCodegen')}: ${codegenMsg}\n`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
spawned.stderr.on('data', (data) => {
|
||||
console.error(chalk.red(String(data)))
|
||||
})
|
||||
|
||||
return dfd.promise
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import glob from 'glob'
|
||||
import childProcess from 'child_process'
|
||||
import util from 'util'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
|
||||
const execAsync = util.promisify(childProcess.exec)
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error(reason)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
export function getTsPackages (packagesPath: string = ''): Promise<Set<string>> {
|
||||
const dfd = {} as Record<string, any>
|
||||
|
||||
dfd.promise = new Promise((resolve, reject) => {
|
||||
dfd.resolve = resolve
|
||||
dfd.reject = reject
|
||||
})
|
||||
|
||||
glob(
|
||||
path.join(monorepoPaths.root, packagesPath, '/packages/*/package.json'),
|
||||
(err, packageJsons) => {
|
||||
if (err) {
|
||||
return dfd.reject(err)
|
||||
}
|
||||
|
||||
type PackageJsonStructure = {
|
||||
name: string
|
||||
dependencies: Record<string, string>
|
||||
devDependencies: Record<string, string>
|
||||
scripts: Record<string, string>
|
||||
}
|
||||
const required = packageJsons.map((path) => {
|
||||
return require(path)
|
||||
}) as PackageJsonStructure[]
|
||||
|
||||
const packages = new Set<string>()
|
||||
|
||||
required.forEach((r) => {
|
||||
// Select only packages that have build-ts
|
||||
if (r.scripts && r.scripts['build-ts']) {
|
||||
packages.add(r.name)
|
||||
}
|
||||
})
|
||||
|
||||
dfd.resolve(packages)
|
||||
},
|
||||
)
|
||||
|
||||
return dfd.promise
|
||||
}
|
||||
|
||||
export async function buildPackageTsc ({
|
||||
packagesPath = '',
|
||||
tsPackages = new Set(),
|
||||
onlyPackages,
|
||||
}: {
|
||||
packagesPath?: string
|
||||
tsPackages?: Set<string>
|
||||
onlyPackages?: string[]
|
||||
}) {
|
||||
console.log(
|
||||
chalk.blue(`TSC: Building deps for ${onlyPackages || 'All Packages'}`),
|
||||
)
|
||||
|
||||
const errors = []
|
||||
|
||||
let built = 0
|
||||
|
||||
const packages = onlyPackages || [...Array.from(tsPackages)]
|
||||
|
||||
for (const pkg of packages) {
|
||||
try {
|
||||
await execAsync('tsc', {
|
||||
cwd: path.join(
|
||||
__dirname,
|
||||
'../../',
|
||||
packagesPath,
|
||||
`/packages/${pkg.replace(/\@(packages|cypress|frontend)\//, '')}`,
|
||||
),
|
||||
})
|
||||
|
||||
built++
|
||||
console.log(
|
||||
`${chalk.green(`Built`)} ${pkg} ${chalk.magenta(
|
||||
`${built} / ${packages.length}`,
|
||||
)}`,
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`${chalk.red(`Failed built`)} ${pkg} ${chalk.magenta(
|
||||
`${built} / ${packages.length}`,
|
||||
)}`,
|
||||
)
|
||||
|
||||
errors.push({ package: pkg, stdout: e.stdout })
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
errors.forEach((e) => {
|
||||
console.log(`Error building ${e.package}`)
|
||||
console.error(chalk.red(e.stdout))
|
||||
})
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import detectPort from 'detect-port'
|
||||
import { exit } from '../utils/exitUtil'
|
||||
|
||||
export async function friendlyStartupWarnings () {
|
||||
const ALL_SERVER_PORTS_USED = [3000, 4000, 8484, 1234]
|
||||
const unavaiablePorts: number[] = []
|
||||
|
||||
await Promise.all(
|
||||
ALL_SERVER_PORTS_USED.map(async (port) => {
|
||||
if (port !== (await detectPort(port))) {
|
||||
unavaiablePorts.push(port)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
if (unavaiablePorts.length > 0) {
|
||||
exit(
|
||||
`The following ports needed by Cypress are already in use: ${unavaiablePorts.join(', ')}.\nCheck that you don't have another web-server running.\nThe command "pkill node -9" can be used to kill all node processes.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { SpawnOptions } from 'child_process'
|
||||
import getenv from 'getenv'
|
||||
import pDefer from 'p-defer'
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
import { AllSpawnableApps, spawned } from '../utils/childProcessUtils'
|
||||
|
||||
const CYPRESS_VITE_APP_PORT = getenv.int('CYPRESS_VITE_APP_PORT', 3333)
|
||||
const CYPRESS_VITE_LAUNCHPAD_PORT = getenv.int('CYPRESS_VITE_LAUNCHPAD_PORT', 3001)
|
||||
|
||||
export function viteApp () {
|
||||
return viteDev('vite-app', `yarn vite --port ${CYPRESS_VITE_APP_PORT} --base /__vite__/`, {
|
||||
cwd: monorepoPaths.pkgApp,
|
||||
})
|
||||
}
|
||||
|
||||
export function viteLaunchpad () {
|
||||
return viteDev('vite-launchpad', `yarn vite --port ${CYPRESS_VITE_LAUNCHPAD_PORT}`, {
|
||||
cwd: monorepoPaths.pkgLaunchpad,
|
||||
})
|
||||
}
|
||||
|
||||
export function viteCleanApp () {
|
||||
return spawned('vite-clean', `yarn clean`, {
|
||||
cwd: monorepoPaths.pkgApp,
|
||||
})
|
||||
}
|
||||
|
||||
export function viteCleanLaunchpad () {
|
||||
return spawned('vite-clean', `yarn clean`, {
|
||||
cwd: monorepoPaths.pkgLaunchpad,
|
||||
})
|
||||
}
|
||||
|
||||
function viteDev (
|
||||
prefix: AllSpawnableApps,
|
||||
command: string,
|
||||
opts: SpawnOptions = {},
|
||||
|
||||
) {
|
||||
const dfd = pDefer()
|
||||
let ready = false
|
||||
|
||||
spawned(prefix, command, opts, {
|
||||
tapOut (chunk, enc, cb) {
|
||||
if (!ready && String(chunk).includes('dev server running at')) {
|
||||
ready = true
|
||||
setTimeout(() => dfd.resolve(), 20) // flush the rest of the chunks
|
||||
}
|
||||
|
||||
cb(null, chunk)
|
||||
},
|
||||
})
|
||||
|
||||
return dfd.promise
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"types": []
|
||||
},
|
||||
"include": ["**/*"]
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { exec, ExecOptions, spawn, SpawnOptions } from 'child_process'
|
||||
import through2 from 'through2'
|
||||
import util from 'util'
|
||||
|
||||
// import psTree from 'ps-tree'
|
||||
// import psNode from 'ps-node'
|
||||
// import util from 'util'
|
||||
|
||||
import { prefixStream } from './prefixStream'
|
||||
|
||||
const spawningApps = new Set()
|
||||
// const killAsync = util.promisify(psNode.kill)
|
||||
// const psTreeAsync = util.promisify(psTree)
|
||||
// const runningApps = new Map<
|
||||
// AllSpawnableApps,
|
||||
// [ChildProcess, ArgsFor<typeof spawned>, Function]
|
||||
// >()
|
||||
|
||||
export async function allReady () {
|
||||
while (spawningApps.size > 0) {
|
||||
await new Promise((ready) => setTimeout(ready, 100))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export type AllSpawnableApps =
|
||||
| `vite-${string}`
|
||||
| `vite:build-${string}`
|
||||
| 'gql-codegen'
|
||||
|
||||
interface SpawnedOptions extends SpawnOptions {
|
||||
waitForExit?: boolean
|
||||
}
|
||||
|
||||
export async function spawned (
|
||||
prefix: AllSpawnableApps,
|
||||
command: string,
|
||||
opts: SpawnedOptions = {},
|
||||
tapThrough: {
|
||||
tapOut?: through2.TransformFunction
|
||||
tapErr?: through2.TransformFunction
|
||||
} = {},
|
||||
) {
|
||||
const { waitForExit, ...spawnOpts } = opts
|
||||
|
||||
spawningApps.add(prefix)
|
||||
const [executable, ...rest] = command.split(' ')
|
||||
const cp = spawn(executable, rest, {
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
FORCE_COLOR: '1',
|
||||
NODE_ENV: 'development',
|
||||
...process.env,
|
||||
},
|
||||
...spawnOpts,
|
||||
})
|
||||
const tapOut = tapThrough.tapOut || null
|
||||
const tapErr = tapThrough.tapErr || null
|
||||
const prefixedStdout = cp.stdout?.pipe(
|
||||
through2(function (chunk, enc, cb) {
|
||||
if (tapOut) {
|
||||
tapOut.call(this, chunk, enc, cb)
|
||||
} else {
|
||||
cb(null, chunk)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.pipe(prefixStream(`${prefix}:${cp.pid}`))
|
||||
|
||||
const prefixedStderr = cp.stderr?.pipe(
|
||||
through2(function (chunk, enc, cb) {
|
||||
if (tapErr) {
|
||||
tapErr.call(this, chunk, enc, cb)
|
||||
} else {
|
||||
cb(null, chunk)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.pipe(prefixStream(`${prefix}:${cp.pid}`))
|
||||
|
||||
prefixedStdout?.pipe(process.stdout)
|
||||
prefixedStderr?.pipe(process.stderr)
|
||||
|
||||
// const cleanup = () => {
|
||||
// prefixedStdout?.unpipe(process.stdout)
|
||||
// prefixedStderr?.unpipe(process.stderr)
|
||||
// }
|
||||
|
||||
// runningApps.set(prefix, [cp, [prefix, command, opts], cleanup])
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (waitForExit) {
|
||||
cp.once('exit', () => {
|
||||
resolve(cp)
|
||||
})
|
||||
|
||||
cp.once('error', reject)
|
||||
} else {
|
||||
cp.stdout?.once('data', () => {
|
||||
spawningApps.delete(prefix)
|
||||
resolve(cp)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const execAsyncLocal = util.promisify(exec)
|
||||
|
||||
interface ExecAsyncOptions extends ExecOptions {
|
||||
encoding?: string | null
|
||||
silent?: boolean
|
||||
}
|
||||
|
||||
export const execAsync = async (
|
||||
command: string,
|
||||
options: ExecAsyncOptions = {},
|
||||
) => {
|
||||
const { silent } = options
|
||||
|
||||
if (!silent) {
|
||||
console.log(command)
|
||||
}
|
||||
|
||||
const result = await execAsyncLocal(command, options)
|
||||
|
||||
if (!silent && typeof result.stdout === 'string' && result.stdout.length) {
|
||||
console.log(result.stdout)
|
||||
}
|
||||
|
||||
if (!silent && typeof result.stderr === 'string' && result.stderr.length) {
|
||||
console.error(result.stderr)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import chalk from 'chalk'
|
||||
import dedent from 'dedent'
|
||||
|
||||
export function exit (msg: string | Error): void {
|
||||
if (msg instanceof Error) {
|
||||
console.log(msg.stack)
|
||||
|
||||
return exit(msg.message)
|
||||
}
|
||||
|
||||
console.error(`\n${chalk.red(dedent(msg))}\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, '../../..')
|
||||
|
||||
/**
|
||||
* Builds
|
||||
*/
|
||||
export async function makePathMap () {
|
||||
const packages = await fs.readdir(path.join(ROOT_DIR, 'packages'))
|
||||
const dirs = await Promise.all(
|
||||
packages.map(async (p) => {
|
||||
try {
|
||||
await fs.stat(path.join(ROOT_DIR, `packages/${p}/package.json`))
|
||||
|
||||
return p
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(__dirname, '../monorepoPaths.ts'),
|
||||
`/* eslint-disable */
|
||||
// Auto-generated by makePathMap.ts
|
||||
import path from 'path'
|
||||
export const monorepoPaths = {
|
||||
root: path.join(__dirname, '../..'),
|
||||
pkgDir: path.join(__dirname, '../../packages'),
|
||||
${dirs
|
||||
.filter((f) => f)
|
||||
.map((dir) => {
|
||||
return ` ${_.camelCase(
|
||||
`pkg-${dir}`,
|
||||
)}: path.join(__dirname, '../../packages/${dir}')`
|
||||
}).join(',\n')}
|
||||
}
|
||||
`,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { spawn, execSync } from 'child_process'
|
||||
import chalk from 'chalk'
|
||||
import pDefer from 'p-defer'
|
||||
import chokidar from 'chokidar'
|
||||
import _ from 'lodash'
|
||||
|
||||
interface NexusTypegenCfg {
|
||||
cwd: string
|
||||
/**
|
||||
* Path to the file we need to execute to generate the schema
|
||||
*/
|
||||
filePath: string
|
||||
outputPath?: string
|
||||
}
|
||||
|
||||
function prefixTypegen (s: string) {
|
||||
return `${chalk.cyan('nexusTypegen')}: ${s}`
|
||||
}
|
||||
|
||||
export async function nexusTypegen (cfg: NexusTypegenCfg) {
|
||||
const dfd = pDefer()
|
||||
|
||||
if (cfg.outputPath) {
|
||||
execSync(`touch ${cfg.outputPath}`)
|
||||
}
|
||||
|
||||
const out = spawn('node', ['-r', '@packages/ts/register', cfg.filePath], {
|
||||
cwd: cfg.cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
CYPRESS_INTERNAL_NEXUS_CODEGEN: 'true',
|
||||
},
|
||||
})
|
||||
|
||||
out.stderr.on('data', (data) => {
|
||||
process.stdout.write(prefixTypegen(chalk.red(String(data))))
|
||||
dfd.resolve({})
|
||||
})
|
||||
|
||||
out.stdout.on('data', (data) => {
|
||||
const outString = String(data)
|
||||
.split('\n')
|
||||
.map((s) => prefixTypegen(chalk.magentaBright(s)))
|
||||
.join('\n')
|
||||
|
||||
process.stdout.write('\n')
|
||||
process.stdout.write(outString)
|
||||
process.stdout.write('\n')
|
||||
dfd.resolve({})
|
||||
})
|
||||
|
||||
out.on('error', dfd.reject)
|
||||
|
||||
return dfd.promise
|
||||
}
|
||||
|
||||
let debounced: Record<string, Function> = {}
|
||||
|
||||
const nexusTypegenDebounced = (cfg: NexusTypegenCfg) => {
|
||||
debounced[cfg.filePath] =
|
||||
debounced[cfg.filePath] ?? _.debounce(nexusTypegen, 500)
|
||||
|
||||
debounced[cfg.filePath](cfg)
|
||||
}
|
||||
|
||||
interface NexusTypegenWatchCfg extends NexusTypegenCfg {
|
||||
watchPaths: string[]
|
||||
}
|
||||
|
||||
export async function watchNexusTypegen (cfg: NexusTypegenWatchCfg) {
|
||||
const dfd = pDefer()
|
||||
|
||||
const watcher = chokidar.watch(cfg.watchPaths, {
|
||||
cwd: cfg.cwd,
|
||||
ignored: /\.gen\.ts/,
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
watcher.on('all', (evt, path) => {
|
||||
console.log(prefixTypegen(`${evt} ${path}`))
|
||||
nexusTypegenDebounced(cfg)
|
||||
})
|
||||
|
||||
watcher.on('ready', () => {
|
||||
console.log(prefixTypegen(`Codegen Watcher Ready for ${cfg.filePath}`))
|
||||
nexusTypegen(cfg).then(dfd.resolve, dfd.reject)
|
||||
})
|
||||
|
||||
return dfd.promise
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import chalk from 'chalk'
|
||||
import { Transform } from 'stream'
|
||||
|
||||
/**
|
||||
* Takes a stream and prefixes with a given string
|
||||
* @param prefixStr
|
||||
* @returns
|
||||
*/
|
||||
export function prefixStream (prefixStr: string | (() => string)) {
|
||||
const prefix =
|
||||
typeof prefixStr === 'string'
|
||||
? Buffer.from(`[${chalk.gray(prefixStr)}]: `)
|
||||
: () => Buffer.from(`[${chalk.gray(prefixStr())}]: `)
|
||||
|
||||
// https://stackoverflow.com/a/45126242
|
||||
class PrefixStream extends Transform {
|
||||
_rest?: Buffer
|
||||
|
||||
_transform (chunk: Buffer, encoding: string, cb: CallableFunction) {
|
||||
this._rest =
|
||||
this._rest && this._rest.length
|
||||
? Buffer.concat([this._rest, chunk])
|
||||
: chunk
|
||||
|
||||
let index
|
||||
|
||||
while (this._rest && (index = this._rest.indexOf('\n')) !== -1) {
|
||||
const line = this._rest.slice(0, ++index)
|
||||
|
||||
this._rest = this._rest.slice(index)
|
||||
this.push(
|
||||
Buffer.concat([
|
||||
typeof prefix === 'function' ? prefix() : prefix,
|
||||
line,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
cb()
|
||||
}
|
||||
|
||||
_flush (cb: CallableFunction) {
|
||||
if (this._rest && this._rest.length) {
|
||||
cb(
|
||||
null,
|
||||
Buffer.concat([
|
||||
typeof prefix === 'function' ? prefix() : prefix,
|
||||
this._rest,
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PrefixStream()
|
||||
}
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
process.env.GRAPHQL_CODEGEN = 'true'
|
||||
require('@packages/server')
|
||||
|
||||
if (process.argv.includes('--devWatch')) {
|
||||
if (process.env.CYPRESS_INTERNAL_DEV_WATCH) {
|
||||
process.on('message', (msg) => {
|
||||
if (msg === 'close') {
|
||||
process.exit(0)
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
const chokidar = require('chokidar')
|
||||
const childProcess = require('child_process')
|
||||
const path = require('path')
|
||||
const pDefer = require('p-defer')
|
||||
|
||||
const watcher = chokidar.watch('packages/graphql/src/**/*.{js,ts}', {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
ignored: '**/nxs.gen.ts',
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* @type {childProcess.ChildProcess}
|
||||
*/
|
||||
let child
|
||||
|
||||
let isClosing = false
|
||||
let isRestarting = false
|
||||
|
||||
function runServer () {
|
||||
if (child) {
|
||||
child.removeAllListeners()
|
||||
}
|
||||
|
||||
child = childProcess.fork(path.join(__dirname, 'start.js'), [...process.argv, '--devWatch'], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
if (isClosing) {
|
||||
process.exit(code)
|
||||
}
|
||||
})
|
||||
|
||||
child.on('disconnect', () => {
|
||||
child = null
|
||||
})
|
||||
}
|
||||
|
||||
async function restartServer () {
|
||||
if (isRestarting) {
|
||||
return
|
||||
}
|
||||
|
||||
const dfd = pDefer()
|
||||
|
||||
if (child) {
|
||||
child.on('exit', dfd.resolve)
|
||||
isRestarting = true
|
||||
child.send('close')
|
||||
} else {
|
||||
dfd.resolve()
|
||||
}
|
||||
|
||||
await dfd.promise
|
||||
isRestarting = false
|
||||
runServer()
|
||||
}
|
||||
|
||||
watcher.on('add', restartServer)
|
||||
watcher.on('change', restartServer)
|
||||
|
||||
runServer()
|
||||
|
||||
process.on('beforeExit', () => {
|
||||
isClosing = true
|
||||
child?.send('close')
|
||||
})
|
||||
Reference in New Issue
Block a user