import { execSync } from "child_process"; import { join as pathJoin, relative as pathRelative } from "path"; import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath"; import * as fs from "fs"; import * as os from "os"; const singletonDependencies: string[] = ["react", "@types/react"]; // For example [ "@emotion" ] it's more convenient than // having to list every sub emotion packages (@emotion/css @emotion/utils ...) // in singletonDependencies const namespaceSingletonDependencies: string[] = []; const rootDirPath = getThisCodebaseRootDirPath(); const commonThirdPartyDeps = [ ...namespaceSingletonDependencies .map(namespaceModuleName => fs .readdirSync(pathJoin(rootDirPath, "node_modules", namespaceModuleName)) .map(submoduleName => `${namespaceModuleName}/${submoduleName}`) ) .reduce((prev, curr) => [...prev, ...curr], []), ...singletonDependencies ]; //NOTE: This is only required because of: https://github.com/garronej/ts-ci/blob/c0e207b9677523d4ec97fe672ddd72ccbb3c1cc4/README.md?plain=1#L54-L58 { let modifiedPackageJsonContent = fs .readFileSync(pathJoin(rootDirPath, "package.json")) .toString("utf8"); modifiedPackageJsonContent = (() => { const o = JSON.parse(modifiedPackageJsonContent); delete o.files; return JSON.stringify(o, null, 2); })(); modifiedPackageJsonContent = modifiedPackageJsonContent .replace(/"dist\//g, '"') .replace(/"\.\/dist\//g, '"./') .replace(/"!dist\//g, '"!') .replace(/"!\.\/dist\//g, '"!./'); modifiedPackageJsonContent = JSON.stringify( { ...JSON.parse(modifiedPackageJsonContent), version: `0.0.0-rc.${~~(Math.random() * 1000000)}` }, null, 4 ); fs.writeFileSync( pathJoin(rootDirPath, "dist", "package.json"), Buffer.from(modifiedPackageJsonContent, "utf8") ); } const yarnGlobalDirPath = pathJoin(rootDirPath, ".yarn_home"); fs.rmSync(yarnGlobalDirPath, { recursive: true, force: true }); fs.mkdirSync(yarnGlobalDirPath); const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => { const { targetModuleName, cwd } = params; if (targetModuleName === undefined) { const packageJsonFilePath = pathJoin(cwd, "package.json"); const packageJson = JSON.parse( fs.readFileSync(packageJsonFilePath).toString("utf8") ); delete packageJson["packageManager"]; fs.writeFileSync( packageJsonFilePath, Buffer.from(JSON.stringify(packageJson, null, 2)) ); } const cmd = [ "yarn", "link", ...(targetModuleName !== undefined ? [targetModuleName] : ["--no-bin-links"]) ].join(" "); console.log(`$ cd ${pathRelative(rootDirPath, cwd) || "."} && ${cmd}`); execSync(cmd, { cwd, env: { ...process.env, ...(os.platform() === "win32" ? { USERPROFILE: yarnGlobalDirPath, LOCALAPPDATA: yarnGlobalDirPath } : { HOME: yarnGlobalDirPath }) } }); }; const testAppPaths = (() => { const [, , ...testAppNames] = process.argv; return testAppNames .map(testAppName => { const testAppPath = pathJoin(rootDirPath, "..", testAppName); if (fs.existsSync(testAppPath)) { return testAppPath; } console.warn( `Skipping ${testAppName} since it cant be found here: ${testAppPath}` ); return undefined; }) .filter((path): path is string => path !== undefined); })(); if (testAppPaths.length === 0) { console.error("No test app to link into!"); process.exit(-1); } testAppPaths.forEach(testAppPath => { const packageJsonFilePath = pathJoin(testAppPath, "package.json"); const packageJsonContent = fs.readFileSync(packageJsonFilePath); const parsedPackageJson = JSON.parse(packageJsonContent.toString("utf8")) as { scripts?: Record; }; let hasPostInstallOrPrepareScript = false; if (parsedPackageJson.scripts !== undefined) { for (const scriptName of ["postinstall", "prepare"]) { if (parsedPackageJson.scripts[scriptName] === undefined) { continue; } hasPostInstallOrPrepareScript = true; delete parsedPackageJson.scripts[scriptName]; } } if (hasPostInstallOrPrepareScript) { fs.writeFileSync( packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), "utf8") ); } const restorePackageJson = () => { if (!hasPostInstallOrPrepareScript) { return; } fs.writeFileSync(packageJsonFilePath, packageJsonContent); }; try { execSync("yarn install", { cwd: testAppPath }); } catch (error) { restorePackageJson(); throw error; } restorePackageJson(); }); console.log("=== Linking common dependencies ==="); const total = commonThirdPartyDeps.length; let current = 0; commonThirdPartyDeps.forEach(commonThirdPartyDep => { current++; console.log(`${current}/${total} ${commonThirdPartyDep}`); const localInstallPath = pathJoin( ...[ rootDirPath, "node_modules", ...(commonThirdPartyDep.startsWith("@") ? commonThirdPartyDep.split("/") : [commonThirdPartyDep]) ] ); execYarnLink({ cwd: localInstallPath }); }); commonThirdPartyDeps.forEach(commonThirdPartyDep => testAppPaths.forEach(testAppPath => execYarnLink({ cwd: testAppPath, targetModuleName: commonThirdPartyDep }) ) ); console.log("=== Linking in house dependencies ==="); execYarnLink({ cwd: pathJoin(rootDirPath, "dist") }); testAppPaths.forEach(testAppPath => execYarnLink({ cwd: testAppPath, targetModuleName: JSON.parse( fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8") )["name"] }) ); testAppPaths.forEach(testAppPath => { const { scripts = {} } = JSON.parse( fs.readFileSync(pathJoin(testAppPath, "package.json")).toString("utf8") ) as { scripts?: Record; }; for (const scriptName of ["postinstall", "prepare"]) { if (scripts[scriptName] === undefined) { continue; } execSync(`yarn run ${scriptName}`, { cwd: testAppPath }); } }); export {};