diff --git a/src/index.ts b/src/index.ts index 6df6c6e..196f7e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { Cache, CommandContext, Configuration, + Hooks, Plugin, Project, Report, @@ -18,7 +19,7 @@ import binTmpl from "./bin-wrapper.sh.in"; import defaultExprTmpl from "./default.nix.in"; import projectExprTmpl from "./yarn-project.nix.in"; -// Actual body of NixifyCommand. +// Generator function that runs after `yarn install`. const generate = async (project: Project, report: Report) => { const { configuration, cwd } = project; const yarnPathAbs = configuration.get(`yarnPath`); @@ -27,33 +28,20 @@ const generate = async (project: Project, report: Report) => { // TODO: Should try to remove this. Our binary wrappers currently do // `node -r .pnp.js `, but not even sure if that's supported. if (configuration.get(`nodeLinker`) !== `pnp`) { - report.reportError( + report.reportWarning( 0, - `Only 'pnp' is currently supported for the 'nodeLinker' setting.` + `Currently, yarn-plugin-nixify only supports 'pnp' for the 'nodeLinker' setting.` ); return; } - const yarnPath = ppath.relative(cwd, yarnPathAbs); - if (yarnPath.startsWith(".")) { - report.reportError( + let yarnPath = ppath.relative(cwd, yarnPathAbs); + if (yarnPath.startsWith(`../`)) { + yarnPath = yarnPathAbs; + report.reportWarning( 0, `The Yarn path ${yarnPathAbs} is outside the project directory - it cannot be reached by the Nix build` ); - return; - } - - // Simple check to see if the project has been setup, so we know the Yarn - // cache is populated. TODO: Can we also see if it is up-to-date? - if ( - !xfs.existsSync(ppath.join(cwd, lockfileFilename)) || - !xfs.existsSync(getPnpPath(project).main) - ) { - report.reportError( - 0, - `The project in ${cwd}/package.json doesn't seem to have been installed - running an install there might help` - ); - return; } // List files the derivation will depend on. @@ -82,11 +70,13 @@ const generate = async (project: Project, report: Report) => { // just user global configuration files, but the warnings can help highlight // dependencies on private registries.) const relativePath = ppath.relative(cwd, inputPath); - if (relativePath.startsWith(`..`)) { - report.reportWarning( - 0, - `The path ${inputPath} was ignored, because it cannot be reached by the Nix build` - ); + if (relativePath.startsWith(`../`)) { + if (inputPath !== yarnPath) { + report.reportWarning( + 0, + `The path ${inputPath} is outside the project directory and was ignored - it may not be reachable in the Nix build` + ); + } continue; } // Add the file itself. @@ -102,16 +92,28 @@ const generate = async (project: Project, report: Report) => { // Build the Nix output-hash by hashing the Yarn cache folder. The // derivation should build the exact same. const cacheFolder = configuration.get(`cacheFolder`); - const hasherResult = await execUtils.execvp( - `nix-hash`, - [`--type`, `sha256`, `--base32`, cacheFolder], - { cwd, encoding: `utf8`, strict: true } - ); - const cacheHash = hasherResult.stdout.trim(); + let cacheHash = ``; + try { + const hasherResult = await execUtils.execvp( + `nix-hash`, + [`--type`, `sha256`, `--base32`, cacheFolder], + { cwd, encoding: `utf8`, strict: true } + ); + cacheHash = hasherResult.stdout.trim(); + } catch (err) { + if (err.code === `ENOENT`) { + report.reportWarning( + 0, + `No Nix installation found - yarn-project.nix will not be updated` + ); + } else { + throw err; + } + } // Render the Nix expression. const ident = project.topLevelWorkspace.manifest.name; - const projectName = ident ? structUtils.stringifyIdent(ident) : "workspace"; + const projectName = ident ? structUtils.stringifyIdent(ident) : `workspace`; const projectExpr = projectExprTmpl .replace(`@@PROJECT_NAME@@`, JSON.stringify(projectName)) .replace(`@@OFFLINE_CACHE_HASH@@`, JSON.stringify(cacheHash)) @@ -126,37 +128,18 @@ const generate = async (project: Project, report: Report) => { ); const projectExprPath = ppath.join(cwd, `yarn-project.nix` as Filename); xfs.writeFileSync(projectExprPath, projectExpr); - report.reportInfo(0, `Wrote: ${projectExprPath}`); // 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, `Wrote: ${defaultExprPath}`); + report.reportInfo( + 0, + `A minimal default.nix was created. You may want to customize it.` + ); } }; -// Command that generates Nix expressions to setup the project. -// TODO: Automate by running on every install instead? There's a hook in Yarn, -// but it doesn't tell us if the persist flag is set. -class NixifyCommand extends Command { - @Command.Path(`nixify`) - async execute() { - const configuration = await Configuration.find( - this.context.cwd, - this.context.plugins - ); - const { project } = await Project.find(configuration, this.context.cwd); - - const report = await StreamReport.start( - { configuration, stdout: this.context.stdout }, - async (report) => generate(project, report) - ); - - return report.exitCode(); - } -} - // Internal command that does just the fetch part of `yarn install`. // Used inside the Nix offline-cache derivation to build the cache. class BuildCacheCommand extends Command { @@ -234,8 +217,15 @@ class InstallBinCommand extends Command { } } -const plugin: Plugin = { - commands: [NixifyCommand, BuildCacheCommand, InstallBinCommand], +const plugin: Plugin = { + commands: [BuildCacheCommand, FetchLocatorCommand, InstallBinCommand], + hooks: { + afterAllInstalled: async (project, opts) => { + if (opts.persistProject !== false) { + await generate(project, opts.report); + } + }, + }, }; export default plugin; diff --git a/src/yarn-project.nix.in b/src/yarn-project.nix.in index f388b30..cca2bcc 100644 --- a/src/yarn-project.nix.in +++ b/src/yarn-project.nix.in @@ -1,7 +1,7 @@ # This file is generated by running "yarn install" inside your project. # Manual changes might be lost - proceed with caution! -{ lib, bash, nodejs, perl, stdenv, writeText }: +{ lib, bash, coreutils, nodejs, perl, stdenv, writeText }: with lib; @@ -43,7 +43,7 @@ let export HOME="$TEMP" # Setup to environment so we can run Yarn. - export NIX_YARN_PATH="$PWD/${yarn-path}" + export NIX_YARN_PATH="$(readlink -f '${yarn-path}')" ${yarn-alias} # Invoke a plugin internal command to build the cache. @@ -64,7 +64,7 @@ in stdenv.mkDerivation { npm_config_build_from_source = "true"; # Make sure the build uses the right Node.js version everywhere. - buildInputs = [ nodejs ]; + buildInputs = [ coreutils nodejs ]; # Define the Yarn alias in the build environment. postHook = yarn-alias; @@ -80,7 +80,7 @@ in stdenv.mkDerivation { cd "$out/libexec/$sourceRoot" # Store the absolute path to Yarn for the 'yarn' alias. - export NIX_YARN_PATH="$PWD/${yarn-path}" + export NIX_YARN_PATH="$(readlink -f ${yarn-path})" # Point Yarn to the offline cache built separately. yarn config set cacheFolder '${offline-cache}'