From 5d3ed3c1190cd8055643899e33eebd6cd5ac1eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Kochen?= Date: Tue, 25 Aug 2020 18:55:38 +0200 Subject: [PATCH] Split into modules and reorganize --- package.json | 1 + src/FetchOneCommand.ts | 52 ++++ src/InstallBinCommand.ts | 84 +++++++ src/generate.ts | 97 ++++++++ src/index.ts | 228 +----------------- src/{ => tmpl}/bin-wrapper-node-modules.sh.in | 0 src/{ => tmpl}/bin-wrapper-pnp.sh.in | 0 src/{ => tmpl}/default.nix.in | 0 src/{ => tmpl}/yarn-project.nix.in | 0 tsconfig.json | 4 +- 10 files changed, 241 insertions(+), 225 deletions(-) create mode 100644 src/FetchOneCommand.ts create mode 100644 src/InstallBinCommand.ts create mode 100644 src/generate.ts rename src/{ => tmpl}/bin-wrapper-node-modules.sh.in (100%) rename src/{ => tmpl}/bin-wrapper-pnp.sh.in (100%) rename src/{ => tmpl}/default.nix.in (100%) rename src/{ => tmpl}/yarn-project.nix.in (100%) diff --git a/package.json b/package.json index 92e9ca8..6d0cc39 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "webpack-sources": "^1.4.3" }, "scripts": { + "check": "tsc", "build": "./build.js -p", "build-dev": "./build.js", "fmt": "prettier --write src" diff --git a/src/FetchOneCommand.ts b/src/FetchOneCommand.ts new file mode 100644 index 0000000..43faafb --- /dev/null +++ b/src/FetchOneCommand.ts @@ -0,0 +1,52 @@ +import { Command } from "clipanion"; + +import { + Cache, + CommandContext, + Configuration, + LocatorHash, + Project, + StreamReport, +} from "@yarnpkg/core"; + +// Internal command that fetches a single locator. +// Used from within Nix to build the cache for the project. +export default class FetchOneCommand extends Command { + @Command.String() + locatorHash: string = ``; + + @Command.Path(`nixify`, `fetch-one`) + async execute() { + const configuration = await Configuration.find( + this.context.cwd, + this.context.plugins + ); + const { project } = await Project.find(configuration, this.context.cwd); + const cache = await Cache.find(configuration); + + const fetcher = configuration.makeFetcher(); + + const report = await StreamReport.start( + { configuration, stdout: this.context.stdout }, + async (report) => { + const pkg = project.originalPackages.get( + this.locatorHash as LocatorHash + ); + if (!pkg) { + report.reportError(0, `Invalid locator hash`); + return; + } + + await fetcher.fetch(pkg, { + checksums: project.storedChecksums, + project, + cache, + fetcher, + report, + }); + } + ); + + return report.exitCode(); + } +} diff --git a/src/InstallBinCommand.ts b/src/InstallBinCommand.ts new file mode 100644 index 0000000..8ff4db1 --- /dev/null +++ b/src/InstallBinCommand.ts @@ -0,0 +1,84 @@ +import { Command } from "clipanion"; +import { Filename, npath, ppath, xfs } from "@yarnpkg/fslib"; +import { getPnpPath } from "@yarnpkg/plugin-pnp"; + +import { + CommandContext, + Configuration, + Project, + StreamReport, +} from "@yarnpkg/core"; + +import binWrapperNodeModulesTmpl from "./tmpl/bin-wrapper-node-modules.sh.in"; +import binWrapperPnpTmpl from "./tmpl/bin-wrapper-pnp.sh.in"; + +const supportedLinkers = [`pnp`, `node-modules`]; + +// Internal command that creates wrappers for binaries. +// Used inside the Nix install phase. +export default class InstallBinCommand extends Command { + @Command.String() + binDir: string = ``; + + @Command.Path(`nixify`, `install-bin`) + async execute() { + const configuration = await Configuration.find( + this.context.cwd, + this.context.plugins + ); + const { project, workspace } = await Project.find( + configuration, + this.context.cwd + ); + + const report = await StreamReport.start( + { configuration, stdout: this.context.stdout }, + async (report) => { + if (!workspace || workspace.manifest.bin.size === 0) { + return; + } + + let nodeLinker = configuration.get(`nodeLinker`); + if (!supportedLinkers.includes(nodeLinker)) { + nodeLinker = `node-modules`; + report.reportWarning( + 0, + `The nodeLinker ${nodeLinker} is not supported - executables may have trouble finding dependencies` + ); + } + + const binDir = npath.toPortablePath(this.binDir); + const pnpPath = getPnpPath(project).main; + for (const [name, scriptInput] of workspace.manifest.bin) { + const binPath = ppath.join(binDir, name as Filename); + const scriptPath = ppath.join( + project.cwd, + npath.toPortablePath(scriptInput) + ); + + let script; + switch (nodeLinker) { + case `pnp`: + script = binWrapperPnpTmpl + .replace(`@@PNP_PATH@@`, pnpPath) + .replace(`@@SCRIPT_PATH@@`, scriptPath); + break; + case `node-modules`: + script = binWrapperNodeModulesTmpl.replace( + `@@SCRIPT_PATH@@`, + scriptPath + ); + break; + default: + throw Error(`Invalid nodeLinker ${nodeLinker}`); + } + + xfs.writeFileSync(binPath, script); + xfs.chmodSync(binPath, 0o755); + } + } + ); + + return report.exitCode(); + } +} diff --git a/src/generate.ts b/src/generate.ts new file mode 100644 index 0000000..548fa6e --- /dev/null +++ b/src/generate.ts @@ -0,0 +1,97 @@ +import { Cache, Project, Report, structUtils } from "@yarnpkg/core"; +import { Filename, PortablePath, ppath, xfs } from "@yarnpkg/fslib"; + +import defaultExprTmpl from "./tmpl/default.nix.in"; +import projectExprTmpl from "./tmpl/yarn-project.nix.in"; + +// Generator function that runs after `yarn install`. +export default async (project: Project, cache: Cache, report: Report) => { + const { configuration, cwd } = project; + const yarnPathAbs = configuration.get(`yarnPath`); + const cacheFolderAbs = configuration.get(`cacheFolder`); + + let yarnPath = ppath.relative(cwd, yarnPathAbs); + if (yarnPath.startsWith(`../`)) { + yarnPath = yarnPathAbs; + report.reportWarning( + 0, + `The Yarn path ${yarnPathAbs} is outside the project - it may not be reachable by the Nix build` + ); + } + + let cacheFolder = ppath.relative(cwd, cacheFolderAbs); + if (cacheFolder.startsWith(`../`)) { + cacheFolder = cacheFolderAbs; + report.reportWarning( + 0, + `The cache folder ${cacheFolderAbs} is outside the project - it may not be reachable by the Nix build` + ); + } + + for (const source of configuration.sources.values()) { + if (!source.startsWith(`<`)) { + const relativeSource = ppath.relative(cwd, source as PortablePath); + if (relativeSource.startsWith(`../`)) { + report.reportWarning( + 0, + `The config file ${source} is outside the project - it may not be reachable by the Nix build` + ); + } + } + } + + // Build a list of cache entries so Nix can fetch them. + let cacheEntries = []; + const cacheFiles = new Set(xfs.readdirSync(cache.cwd)); + for (const pkg of project.storedPackages.values()) { + const checksum = project.storedChecksums.get(pkg.locatorHash); + if (!checksum) continue; + + const cachePath = cache.getLocatorPath(pkg, checksum); + if (!cachePath) continue; + + const filename = ppath.basename(cachePath); + if (!cacheFiles.has(filename)) continue; + + let name = structUtils.slugifyIdent(pkg).replace(/^@/, "_at_"); + if (pkg.version) { + name += `-${pkg.version}`; + } + + const sha512 = checksum.split(`/`).pop(); + cacheEntries.push([ + `name = ${JSON.stringify(name)};`, + `filename = ${JSON.stringify(filename)};`, + `sha512 = ${JSON.stringify(sha512)};`, + `locatorHash = ${JSON.stringify(pkg.locatorHash)};`, + ]); + } + + // Render the Nix expression. + const ident = project.topLevelWorkspace.manifest.name; + const projectName = ident ? structUtils.stringifyIdent(ident) : `workspace`; + const projectExpr = projectExprTmpl + .replace(`@@PROJECT_NAME@@`, JSON.stringify(projectName)) + .replace(`@@YARN_PATH@@`, JSON.stringify(yarnPath)) + .replace(`@@CACHE_FOLDER@@`, JSON.stringify(cacheFolder)) + .replace( + `@@CACHE_ENTRIES@@`, + `[\n` + + [...cacheEntries] + .map((entry) => ` { ${entry.join(` `)} }\n`) + .join(``) + + ` ]` + ); + const projectExprPath = ppath.join(cwd, `yarn-project.nix` as Filename); + xfs.writeFileSync(projectExprPath, projectExpr); + + // Create a wrapper if it does not exist yet. + const defaultExprPath = ppath.join(cwd, `default.nix` as Filename); + if (!xfs.existsSync(defaultExprPath)) { + xfs.writeFileSync(defaultExprPath, defaultExprTmpl); + report.reportInfo( + 0, + `A minimal default.nix was created. You may want to customize it.` + ); + } +}; diff --git a/src/index.ts b/src/index.ts index 148c535..44bb1bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,227 +1,7 @@ -import { Command } from "clipanion"; -import { Filename, npath, ppath, xfs } from "@yarnpkg/fslib"; -import { getPnpPath } from "@yarnpkg/plugin-pnp"; - -import { - Cache, - CommandContext, - Configuration, - Hooks, - LocatorHash, - Plugin, - Project, - Report, - StreamReport, - structUtils, -} from "@yarnpkg/core"; - -import binWrapperPnpTmpl from "./bin-wrapper-pnp.sh.in"; -import binWrapperNodeModulesTmpl from "./bin-wrapper-node-modules.sh.in"; -import defaultExprTmpl from "./default.nix.in"; -import projectExprTmpl from "./yarn-project.nix.in"; - -const supportedLinkers = [`pnp`, `node-modules`]; - -// Generator function that runs after `yarn install`. -const generate = async (project: Project, cache: Cache, report: Report) => { - const { configuration, cwd } = project; - const yarnPathAbs = configuration.get(`yarnPath`); - const cacheFolderAbs = configuration.get(`cacheFolder`); - - let yarnPath = ppath.relative(cwd, yarnPathAbs); - if (yarnPath.startsWith(`../`)) { - yarnPath = yarnPathAbs; - report.reportWarning( - 0, - `The Yarn path ${yarnPathAbs} is outside the project - it may not be reachable by the Nix build` - ); - } - - let cacheFolder = ppath.relative(cwd, cacheFolderAbs); - if (cacheFolder.startsWith(`../`)) { - cacheFolder = cacheFolderAbs; - report.reportWarning( - 0, - `The cache folder ${cacheFolderAbs} is outside the project - it may not be reachable by the Nix build` - ); - } - - for (const source of configuration.sources.values()) { - if (!source.startsWith(`<`)) { - const relativeSource = ppath.relative(cwd, source); - if (relativeSource.startsWith(`../`)) { - report.reportWarning( - 0, - `The config file ${source} is outside the project - it may not be reachable by the Nix build` - ); - } - } - } - - // Build a list of cache entries so Nix can fetch them. - let cacheEntries = []; - const cacheFiles = new Set(xfs.readdirSync(cache.cwd)); - for (const pkg of project.storedPackages.values()) { - const checksum = project.storedChecksums.get(pkg.locatorHash); - if (!checksum) continue; - - const cachePath = cache.getLocatorPath(pkg, checksum); - if (!cachePath) continue; - - const filename = ppath.basename(cachePath); - if (!cacheFiles.has(filename)) continue; - - let name = structUtils.slugifyIdent(pkg).replace(/^@/, "_at_"); - if (pkg.version) { - name += `-${pkg.version}`; - } - - const sha512 = checksum.split(`/`).pop(); - cacheEntries.push([ - `name = ${JSON.stringify(name)};`, - `filename = ${JSON.stringify(filename)};`, - `sha512 = ${JSON.stringify(sha512)};`, - `locatorHash = ${JSON.stringify(pkg.locatorHash)};`, - ]); - } - - // Render the Nix expression. - const ident = project.topLevelWorkspace.manifest.name; - const projectName = ident ? structUtils.stringifyIdent(ident) : `workspace`; - const projectExpr = projectExprTmpl - .replace(`@@PROJECT_NAME@@`, JSON.stringify(projectName)) - .replace(`@@YARN_PATH@@`, JSON.stringify(yarnPath)) - .replace(`@@CACHE_FOLDER@@`, JSON.stringify(cacheFolder)) - .replace( - `@@CACHE_ENTRIES@@`, - `[\n` + - [...cacheEntries] - .map((entry) => ` { ${entry.join(` `)} }\n`) - .join(``) + - ` ]` - ); - const projectExprPath = ppath.join(cwd, `yarn-project.nix` as Filename); - xfs.writeFileSync(projectExprPath, projectExpr); - - // Create a wrapper if it does not exist yet. - const defaultExprPath = ppath.join(cwd, `default.nix` as Filename); - if (!xfs.existsSync(defaultExprPath)) { - xfs.writeFileSync(defaultExprPath, defaultExprTmpl); - report.reportInfo( - 0, - `A minimal default.nix was created. You may want to customize it.` - ); - } -}; - -// Internal command that fetches a single locator. -// Used from within Nix to build the cache for the project. -class FetchOneCommand extends Command { - @Command.String() - locatorHash: string = ``; - - @Command.Path(`nixify`, `fetch-one`) - async execute() { - const configuration = await Configuration.find( - this.context.cwd, - this.context.plugins - ); - const { project } = await Project.find(configuration, this.context.cwd); - const cache = await Cache.find(configuration); - - const fetcher = configuration.makeFetcher(); - - const report = await StreamReport.start( - { configuration, stdout: this.context.stdout }, - async (report) => { - const pkg = project.originalPackages.get( - this.locatorHash as LocatorHash - ); - if (!pkg) { - report.reportError(0, `Invalid locator hash`); - return; - } - - await fetcher.fetch(pkg, { - checksums: project.storedChecksums, - project, - cache, - fetcher, - report, - }); - } - ); - - return report.exitCode(); - } -} - -// Internal command that creates wrappers for binaries. -// Used inside the Nix install phase. -class InstallBinCommand extends Command { - @Command.String() - binDir: string = ``; - - @Command.Path(`nixify`, `install-bin`) - async execute() { - const configuration = await Configuration.find( - this.context.cwd, - this.context.plugins - ); - const { project, workspace } = await Project.find( - configuration, - this.context.cwd - ); - - const report = await StreamReport.start( - { configuration, stdout: this.context.stdout }, - async (report) => { - if (!workspace || workspace.manifest.bin.size === 0) { - return; - } - - let nodeLinker = configuration.get(`nodeLinker`); - if (!supportedLinkers.includes(nodeLinker)) { - nodeLinker = `node-modules`; - report.reportWarning( - 0, - `The nodeLinker ${nodeLinker} is not supported - executables may have trouble finding dependencies` - ); - } - - const binDir = npath.toPortablePath(this.binDir); - const pnpPath = getPnpPath(project).main; - for (const [name, scriptInput] of workspace.manifest.bin) { - const binPath = ppath.join(binDir, name as Filename); - const scriptPath = ppath.join( - project.cwd, - npath.toPortablePath(scriptInput) - ); - - let script; - switch (nodeLinker) { - case `pnp`: - script = binWrapperPnpTmpl - .replace(`@@PNP_PATH@@`, pnpPath) - .replace(`@@SCRIPT_PATH@@`, scriptPath); - break; - case `node-modules`: - script = binWrapperNodeModulesTmpl.replace( - `@@SCRIPT_PATH@@`, - scriptPath - ); - break; - default: - throw Error(`Invalid nodeLinker ${nodeLinker}`); - } - - xfs.writeFileSync(binPath, script); - xfs.chmodSync(binPath, 0o755); - } - } - ); - } -} +import FetchOneCommand from "./FetchOneCommand"; +import InstallBinCommand from "./InstallBinCommand"; +import generate from "./generate"; +import { Hooks, Plugin } from "@yarnpkg/core"; const plugin: Plugin = { commands: [FetchOneCommand, InstallBinCommand], diff --git a/src/bin-wrapper-node-modules.sh.in b/src/tmpl/bin-wrapper-node-modules.sh.in similarity index 100% rename from src/bin-wrapper-node-modules.sh.in rename to src/tmpl/bin-wrapper-node-modules.sh.in diff --git a/src/bin-wrapper-pnp.sh.in b/src/tmpl/bin-wrapper-pnp.sh.in similarity index 100% rename from src/bin-wrapper-pnp.sh.in rename to src/tmpl/bin-wrapper-pnp.sh.in diff --git a/src/default.nix.in b/src/tmpl/default.nix.in similarity index 100% rename from src/default.nix.in rename to src/tmpl/default.nix.in diff --git a/src/yarn-project.nix.in b/src/tmpl/yarn-project.nix.in similarity index 100% rename from src/yarn-project.nix.in rename to src/tmpl/yarn-project.nix.in diff --git a/tsconfig.json b/tsconfig.json index d5f1f54..ca8d4b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,9 @@ "module": "commonjs", "target": "es2018", "lib": ["es2018"], - "strict": true + "types": ["node"], + "strict": true, + "noEmit": true }, "include": ["src/**/*.ts"] }