├── .gitignore ├── .hgignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── node2nix.js ├── bootstrap.sh ├── default.nix ├── doc └── config.json ├── flake.lock ├── flake.nix ├── lib ├── DeploymentConfig.js ├── Package.js ├── Registry.js ├── SourcesCache.js ├── expressions │ ├── CollectionExpression.js │ ├── CompositionExpression.js │ ├── OutputExpression.js │ └── PackageExpression.js ├── node2nix.js └── sources │ ├── GitSource.js │ ├── HTTPSource.js │ ├── LocalSource.js │ ├── NPMRegistrySource.js │ └── Source.js ├── nix └── node-env.nix ├── node-packages.nix ├── package.json ├── release.nix └── tests ├── cli ├── default.nix └── testprojects │ ├── complete │ └── package.json │ ├── garbage │ ├── node_modules │ │ └── .gitkeep │ └── package.json │ ├── lock │ ├── package-lock.json │ └── package.json │ └── noname │ └── package.json ├── default-v14.nix ├── default-v16.nix ├── grunt ├── Gruntfile.js ├── default.nix ├── node-packages.nix ├── override.nix ├── package.json ├── src │ └── test.js ├── supplement.json └── supplement.nix ├── lockfile-v2 ├── a.js ├── default.nix ├── node-packages.nix ├── package-lock.json └── package.json ├── lockfile ├── a.js ├── default.nix ├── node-packages.nix ├── package-lock.json └── package.json ├── node-packages-v14.nix ├── node-packages-v16.nix ├── override-v14.nix ├── override-v16.nix ├── scoped ├── default.nix ├── node-packages.nix └── package.json ├── testa ├── a.js └── package.json ├── testb ├── b.js └── package.json ├── tests-notes.md ├── tests.json └── versionless ├── default.nix ├── node-packages.nix └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | result 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | (? 2 | Copyright (c) 2012-2014 Shea Levy 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node2nix 2 | ======== 3 | Deploy [NPM Package Manager](http://www.npmjs.org) (NPM) packages with the 4 | [Nix package manager](http://www.nixos.org/nix)! 5 | 6 | Using `node2nix` instead of the "vanilla" NPM is useful for a variety of reasons: 7 | * To deploy NPM packages on [NixOS](https://nixos.org) and to manage complex 8 | software installations (that include non-NPM managed dependencies) by using a 9 | universal deployment solution (Nix). 10 | * To integrate with other tools in the Nix-ecosystem: NixOS to manage an entire 11 | system from a single declarative specification, 12 | [NixOps](https://nixos.org/nixops) to deploy networks of machines (bare metal 13 | and in the cloud), and [Disnix](https://nixos.org/disnix) to manage 14 | service-oriented systems. 15 | 16 | Table of Contents 17 | ================ 18 | 19 | **Table of Contents** 20 | 21 | - [node2nix](#node2nix) 22 | - [Table of Contents](#table-of-contents) 23 | - [Installation](#installation) 24 | - [Building a development version](#building-a-development-version) 25 | - [Usage](#usage) 26 | - [Deploying a Node.js development project](#deploying-a-nodejs-development-project) 27 | - [Generating a tarball from a Node.js development project](#generating-a-tarball-from-a-nodejs-development-project) 28 | - [Deploying a development environment of a Node.js development project](#deploying-a-development-environment-of-a-nodejs-development-project) 29 | - [Using the Node.js environment in other Nix derivations](#using-the-nodejs-environment-in-other-nix-derivations) 30 | - [Deploying a collection of NPM packages from the NPM registry](#deploying-a-collection-of-npm-packages-from-the-npm-registry) 31 | - [Using NPM lock files](#using-npm-lock-files) 32 | - [Generating packages for specific Node.js versions](#generating-packages-for-specific-nodejs-versions) 33 | - [Advanced options](#advanced-options) 34 | - [Development mode](#development-mode) 35 | - [Specifying paths](#specifying-paths) 36 | - [Using alternative NPM registries](#using-alternative-npm-registries) 37 | - [Adding unspecified dependencies](#adding-unspecified-dependencies) 38 | - [Wrapping or patching the code or any of its dependencies](#wrapping-or-patching-the-code-or-any-of-its-dependencies) 39 | - [Adding additional/global NPM packages to a packaging process](#adding-additionalglobal-npm-packages-to-a-packaging-process) 40 | - [Using private Git repositories](#using-private-git-repositories) 41 | - [Disable cache bypassing](#disable-cache-bypassing) 42 | - [Troubleshooting](#troubleshooting) 43 | - [Deploying peer dependencies](#deploying-peer-dependencies) 44 | - [Stripping optional dependencies](#striping-optional-dependencies) 45 | - [Updating the package lock file](#updating-the-package-lock-file) 46 | - [Creating a symlink to the node_modules folder in a shell session](#creating-a-symlink-to-the-node_modules-folder-in-a-shell-session) 47 | - [Disabling running NPM install](#disabling-running-npm-install) 48 | - [API documentation](#api-documentation) 49 | - [License](#license) 50 | - [Acknowledgements](#acknowledgements) 51 | 52 | 53 | 54 | Prerequisites 55 | ============= 56 | To be able to convert Git dependencies, the presence of the `nix-hash` 57 | command-line utility (that is included with the 58 | [Nix package manager](http://nixos/nix)) is required. 59 | 60 | Installation 61 | ============ 62 | There are two ways this package can installed. 63 | 64 | To install this package through the Nix package manager, obtain a copy of 65 | [Nixpkgs](http://nixos.org/nixpkgs) and run: 66 | 67 | ```bash 68 | $ nix-env -f '' -iA nodePackages.node2nix 69 | ``` 70 | 71 | Alternatively, this package can also be installed through NPM by running: 72 | 73 | ```bash 74 | $ npm install -g node2nix 75 | ``` 76 | 77 | Building a development version 78 | ============================== 79 | A development version can be deployed by checking out the Git repository and 80 | running: 81 | 82 | ```bash 83 | $ nix-env -f release.nix -iA package.x86_64-linux 84 | ``` 85 | 86 | The above command installs the development `node2nix` executable into the Nix 87 | profile of the user. 88 | 89 | Alternatively, you can the use NPM to install the project dependencies: 90 | 91 | ``` 92 | $ npm install 93 | ``` 94 | 95 | The project only uses JavaScript dependencies and, as such, will also work on 96 | NixOS. 97 | 98 | Usage 99 | ===== 100 | `node2nix` can be used for a variety of use cases. 101 | 102 | Deploying a Node.js development project 103 | --------------------------------------- 104 | The primary use case of `node2nix` is to deploy a development project as a NPM 105 | package. 106 | 107 | What Node.js developers typically do in a development setting is opening the 108 | source code folder and running: 109 | 110 | ```bash 111 | $ npm install 112 | ``` 113 | 114 | The above command-line instruction deploys all dependencies declared in the 115 | [`package.json`](https://www.npmjs.org/doc/files/package.json.html) configuration 116 | file so that the application can be run. 117 | 118 | With `node2nix` you can use the Nix package manager for exactly the same purpose. 119 | Running the following command generates a collection of Nix expressions from 120 | `package.json`: 121 | 122 | ```bash 123 | $ node2nix 124 | ``` 125 | 126 | The above command generates three files: `node-packages.nix` capturing the 127 | packages that can be deployed including all its required dependencies, 128 | `node-env.nix` contains the build logic and `default.nix` is a composition 129 | expression allowing users to deploy the package. 130 | 131 | By running the following Nix command with these expressions, the project can be 132 | built: 133 | 134 | ```bash 135 | $ nix-build -A package 136 | ``` 137 | 138 | The above instruction places a `result` symlink in the current working dir 139 | pointing to the build result. An executable (that is part of the project) can be 140 | started as follows: 141 | 142 | ```bash 143 | $ ./result/bin/node2nix 144 | ``` 145 | 146 | Generating a tarball from a Node.js development project 147 | ------------------------------------------------------- 148 | The expressions that are generated by `node2nix` (shown earlier) can also be 149 | used to generate a tarball from the project: 150 | 151 | ```bash 152 | $ nix-build -A tarball 153 | ``` 154 | 155 | The above command-line instruction produces a tarball that is placed in the 156 | following location: 157 | 158 | ```bash 159 | $ ls result/tarballs/node2nix-1.0.1.tgz 160 | ``` 161 | 162 | The above tarball can be distributed to any user that has the NPM package 163 | manager installed, and installed by running: 164 | 165 | ```bash 166 | $ npm install node2nix-1.0.1.tgz 167 | ``` 168 | 169 | Deploying a development environment of a Node.js development project 170 | -------------------------------------------------------------------- 171 | Besides deploying a development project, it may also be useful to only install 172 | the project's dependencies and spawning a shell session in which they can be 173 | found. 174 | 175 | The following command-line instruction uses the earlier generated expressions 176 | to deploy all the dependencies and opens a development environment: 177 | 178 | ```bash 179 | $ nix-shell -A shell 180 | ``` 181 | 182 | Within this shell session, files can be modified and run without any hassle. 183 | For example, the following command should work: 184 | 185 | ```bash 186 | $ node bin/node2nix.js --help 187 | ``` 188 | 189 | Using the Node.js environment in other Nix derivations 190 | ------------------------------------------------------ 191 | The `node_modules` environment generated by `node2nix` can also be used in 192 | downstream Nix expressions. This can be useful when you want to build a project 193 | that requires running a build system such as Webpack in your Node.js environment. 194 | 195 | This environment can be found on the `nodeDependencies` attribute of the 196 | generated output. It contains two important paths: `lib/node_modules` and `bin`. 197 | The former should be copied or symlinked into your build directory, and the 198 | latter should be added to the PATH. (You can also use the `NODE_PATH` 199 | environment variable, but see the warnings about that 200 | [below](#creating-a-symlink-to-the-node_modules-folder-in-a-shell-session).) 201 | 202 | Here's an example derivation showing this technique: 203 | 204 | ```nix 205 | let 206 | nodeDependencies = (pkgs.callPackage ./default.nix {}).nodeDependencies; 207 | in 208 | 209 | stdenv.mkDerivation { 210 | name = "my-webpack-app"; 211 | src = ./my-app; 212 | buildInputs = [nodejs]; 213 | buildPhase = '' 214 | ln -s ${nodeDependencies}/lib/node_modules ./node_modules 215 | export PATH="${nodeDependencies}/bin:$PATH" 216 | 217 | # Build the distribution bundle in "dist" 218 | webpack 219 | cp -r dist $out/ 220 | ''; 221 | } 222 | ``` 223 | 224 | Deploying a collection of NPM packages from the NPM registry 225 | ------------------------------------------------------------ 226 | The secondary use of `node2nix` is deploying existing NPM packages from the NPM 227 | registry. 228 | 229 | Deployment of packages from the registry is driven by a JSON specification that 230 | looks as follows: 231 | 232 | ```json 233 | [ 234 | "async", 235 | "underscore", 236 | "slasp", 237 | { "mocha" : "1.21.x" }, 238 | { "mocha" : "1.20.x" }, 239 | { "nijs": "0.0.18" }, 240 | { "node2nix": "git://github.com/svanderburg/node2nix.git" } 241 | ] 242 | ``` 243 | 244 | The above specification is basically an array of objects. For each element that 245 | is a string, the `latest` version is obtained from the registry. 246 | 247 | To obtain a specific version of a package, an object must defined in which the 248 | keys are the name of the packages and the values the versions that must be 249 | obtained. 250 | 251 | Any version specification that NPM supports can be used, such as version numbers, 252 | version ranges, HTTP(S) URLs, Git URLs, and GitHub identifiers. 253 | 254 | Nix expressions can be generated from this JSON specification as follows: 255 | 256 | ```bash 257 | $ node2nix -i node-packages.json 258 | ``` 259 | 260 | And by using the generated Nix expressions, we can install `async` with Nix as 261 | follows: 262 | 263 | ```bash 264 | $ nix-env -f default.nix -iA async 265 | ``` 266 | 267 | For every package for which the latest version has been requested, we can 268 | directly refer to the name of the package to deploy it. 269 | 270 | For packages for which a specific version has been specified, we must refer to 271 | it using an attribute that name that is composed of its name and version 272 | specifier. 273 | 274 | The following command can be used to deploy the first specific version of `mocha` 275 | declared in the JSON configuration: 276 | 277 | ```bash 278 | $ nix-env -f default.nix -iA '"mocha-1.21.x"' 279 | ``` 280 | 281 | (As a sidenote: because the attribute name contains dots: `.`, that also serve 282 | as an attribute selector in the Nix expression language, we have to quote the 283 | attribute name) 284 | 285 | `node2nix` can be referenced as follows: 286 | 287 | ```bash 288 | $ nix-env -f default.nix -iA '"node2nix-git://github.com/svanderburg/node2nix.git"' 289 | ``` 290 | 291 | Since every NPM package resolves to a package name and version number we can also 292 | deploy any package by using an attribute consisting of its name and resolved 293 | version number. 294 | 295 | This command deploys NiJS version 0.0.18: 296 | 297 | ```bash 298 | $ nix-env -f default.nix -iA '"nijs-0.0.18"' 299 | ``` 300 | 301 | Using NPM lock files 302 | -------------------- 303 | Node.js 8.x and higher (that includes npm 5.x or higher) can also work with 304 | so-called *lock files* that pinpoint the exact versions used of all dependencies 305 | and transitive dependencies, and provides a content addressable cache to speed 306 | up deployments. 307 | 308 | Some Node.js development projects may include a `package-lock.json` file 309 | pinpointing the exact versions of the dependencies and transitive dependencies. 310 | `node2nix` can use this file to generate a Nix expression from it so that Nix 311 | uses the exact same packages: 312 | 313 | ```bash 314 | $ node2nix -l package-lock.json 315 | ``` 316 | 317 | Generating packages for specific Node.js versions 318 | ------------------------------------------------- 319 | By default, `node2nix` generates Nix expressions that should be used in 320 | conjuction with Node.js 12.x, which is currently the oldest supported LTS 321 | release. 322 | 323 | When it is desired, it is also possible to generate expressions for other 324 | Node.js versions. For example, Node.js 4.x does not use a 325 | flattening/deduplication algorithm, and 6.x that does not support lock files or 326 | caching. 327 | 328 | The old non-flattening structure can be simulated by adding the `--no-flatten` 329 | parameter. 330 | 331 | Additionally, to enable all flags to make generation for a certain Node.js 332 | work, `node2nix` provides convenience parameters. For example, by using the `-4` 333 | parameter, we can generate expressions that can be used with Node.js 4.x: 334 | 335 | ```bash 336 | $ node2nix -4 -i node-package.json 337 | ``` 338 | 339 | By running the following command, Nix deploys NiJS version 0.0.18 using Node.js 340 | 4.x and npm 2.x: 341 | 342 | ```bash 343 | $ nix-env -f default.nix -iA '"nijs-0.0.18"' 344 | ``` 345 | 346 | Advanced options 347 | ================ 348 | `node2nix` also has a number of advanced options. 349 | 350 | Development mode 351 | ---------------- 352 | By default, NPM packages are deployed in production mode, meaning that the 353 | development dependencies are not installed by default. By adding the 354 | `--development` command line option, you can also deploy the development 355 | dependencies: 356 | 357 | ```bash 358 | $ node2nix --development 359 | ``` 360 | 361 | Specifying paths 362 | ---------------- 363 | If no options are specified, `node2nix` makes implicit assumptions on the 364 | filenames of the input JSON specification and the output Nix expressions. These 365 | filenames can be modified with command-line options: 366 | 367 | ```bash 368 | $ node2nix --input package.json --output registry.nix --composition default.nix --node-env node-env.nix 369 | ``` 370 | 371 | Using alternative NPM registries 372 | -------------------------------- 373 | You can also use an alternative NPM registry (such as a private one), by adding 374 | the `--registry`, `--registry-auth-token` and `--registry-scope` option: 375 | 376 | ```bash 377 | $ node2nix -i node-packages.json --registry http://private.registry.local 378 | 379 | $ node2nix \ 380 | --registry "https://registry.npmjs.org" \ 381 | --registry "https://npm.pkg.github.com/" \ 382 | --registry-auth-token "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \ 383 | --registry-scope "@myorg" 384 | ``` 385 | 386 | Adding unspecified dependencies 387 | ------------------------------- 388 | A few exotic NPM packages may have dependencies on native libraries that reside 389 | somewhere on the user's host system. Unfortunately, NPM's metadata does not 390 | specify them, and as a consequence, it may result in failing Nix builds due to 391 | missing dependencies. 392 | 393 | As a solution, the generated expressions by `node2nix` are made overridable. The 394 | override mechanism can be used to manually inject additional unspecified 395 | dependencies. 396 | 397 | The easiest way to do this is to create a wrapper Nix expression that imports 398 | the generated composition expression from `node2nix` and injects additional 399 | dependencies. 400 | 401 | Consider the following package collection file (named: `node-packages.json`) 402 | that installs one NPM package named `floomatic`: 403 | 404 | ```json 405 | [ 406 | "floomatic" 407 | ] 408 | ``` 409 | 410 | We can generate Nix expressions from the above specification, by running: 411 | 412 | ```bash 413 | $ node2nix -i node-packages.json 414 | ``` 415 | 416 | One of floomatic's dependencies is an NPM package named `native-diff-match-patch` 417 | that requires the Qt 4.x library and pkgconfig, which are native dependencies not 418 | detected by the `node2nix` generator. 419 | 420 | With the following wrapper expression (named: `override.nix`), we can inject 421 | these dependencies ourselves: 422 | 423 | ```nix 424 | {pkgs ? import { 425 | inherit system; 426 | }, system ? builtins.currentSystem}: 427 | 428 | let 429 | nodePackages = import ./default.nix { 430 | inherit pkgs system; 431 | }; 432 | in 433 | nodePackages // { 434 | floomatic = nodePackages.floomatic.override { 435 | buildInputs = [ pkgs.pkgconfig pkgs.qt4 ]; 436 | }; 437 | } 438 | ``` 439 | 440 | The expression does the following: 441 | 442 | * We import the composition expression (`default.nix`) generated by `node2nix`. 443 | * We take the old derivation that builds the `floomatic` package, and we add the 444 | missing native dependencies as build inputs by defining an override. 445 | 446 | With the above wrapper expression, we can correctly deploy floomatic, by running: 447 | 448 | ```bash 449 | $ nix-build override.nix -A floomatic 450 | ``` 451 | 452 | Wrapping or patching the code or any of its dependencies 453 | -------------------------------------------------------- 454 | Some packages or any of its dependencies may also require some ad-hoc fixes to 455 | make them work. In such cases, we can implement a `preRebuild` hook with shell 456 | instructions that will be executed before the builder will run `npm rebuild` and 457 | `npm install`. 458 | 459 | For example, consider the `dnschain` package: 460 | 461 | ```json 462 | [ 463 | "dnschain" 464 | ] 465 | ``` 466 | 467 | We can generate Nix expressions from the above specification, by running: 468 | 469 | ```bash 470 | $ node2nix -i node-packages.json 471 | ``` 472 | 473 | `dnschain` has a practical problem -- it requires OpenSSL to be in the `PATH` of 474 | the user. We can create an `override.nix` expression implementing a `preRebuild` 475 | hook that wraps the executable in a script that adds `openssl` to the `PATH`: 476 | 477 | ```nix 478 | {pkgs ? import { 479 | inherit system; 480 | }, system ? builtins.currentSystem}: 481 | 482 | let 483 | nodePackages = import ./default.nix { 484 | inherit pkgs system; 485 | }; 486 | in 487 | nodePackages // { 488 | dnschain = nodePackages.dnschain.override { 489 | preRebuild = '' 490 | wrapProgram $out/bin/dnschain --suffix PATH : ${pkgs.openssl.bin}/bin 491 | ''; 492 | }; 493 | } 494 | ``` 495 | 496 | With the above wrapper expression, we can deploy a wrapped `dnschain` (that is 497 | able to find the `openssl` executable), by running: 498 | 499 | ```bash 500 | $ nix-build override.nix -A dnschain 501 | ``` 502 | 503 | Adding additional/global NPM packages to a packaging process 504 | ------------------------------------------------------------ 505 | Sometimes it may also be required to supplement a packaging process with 506 | additional NPM packages. For example, when building certain NPM projects, some 507 | dependencies have to be installed globally. 508 | 509 | A prominent example of such a workflow is a [Grunt](http://gruntjs.com) project. 510 | The grunt CLI is typically installed globally, whereas its plugins are installed 511 | as development dependencies. 512 | 513 | We can automate such a workflow as follows. Consider the following 514 | `package.json` example: 515 | 516 | ```json 517 | { 518 | "name": "grunt-test", 519 | "version": "0.0.1", 520 | "private": "true", 521 | "devDependencies": { 522 | "grunt": "*", 523 | "grunt-contrib-jshint": "*", 524 | "grunt-contrib-watch": "*" 525 | } 526 | } 527 | ``` 528 | 529 | The above configuration declares grunt and two grunt plugins (`jshint` and 530 | `watch`) as development dependencies. 531 | 532 | We can create a supplemental package specification that refers to additional 533 | NPM packages that are supposed to be installed globally: 534 | 535 | ```json 536 | [ 537 | "grunt-cli" 538 | ] 539 | ``` 540 | 541 | The above configuration (`supplement.json`) states that we need the `grunt-cli` 542 | as an additional package, installed globally. 543 | 544 | Furtheremore, you can provide specific versions of supplemental packages. 545 | Here is example of `supplement.json` with `grunt-cli` version `1.2.0`: 546 | 547 | ```json 548 | [ 549 | { 550 | "grunt-cli": "1.2.0" 551 | } 552 | ] 553 | ``` 554 | 555 | Running the following command-line instruction generates the Nix expressions for 556 | the project: 557 | 558 | ```bash 559 | $ node2nix -d -i package.json --supplement-input supplement.json 560 | ``` 561 | 562 | By overriding the generated expressions, we can instruct the builder to execute 563 | `grunt` after the dependencies have been deployed: 564 | 565 | ```nix 566 | { pkgs ? import {} 567 | , system ? builtins.currentSystem 568 | }: 569 | 570 | let 571 | nodePackages = import ./default.nix { 572 | inherit pkgs system; 573 | }; 574 | in 575 | nodePackages // { 576 | package = nodePackages.package.override { 577 | postInstall = "grunt"; 578 | }; 579 | } 580 | ``` 581 | 582 | The above expression (`override.nix`) defines a `postInstall` hook that executes 583 | grunt after the NPM package has been deployed. 584 | 585 | Running the following command executes the packaging process, including the 586 | grunt post-processing step: 587 | 588 | ```bash 589 | $ nix-build override.nix -A package 590 | ``` 591 | 592 | Using private Git repositories 593 | ------------------------------ 594 | In some development projects, it may be desired to deploy private Git 595 | repositories as dependencies. The `fetchgit {}` function in Nixpkgs, however, 596 | only supports public repositories. 597 | 598 | It is also possible to instruct the generator to use the `fetchgitPrivate {}` 599 | function, that adds support for private repositories that can be reached with 600 | SSH: 601 | 602 | ```bash 603 | $ node2nix --use-fetchgit-private 604 | ``` 605 | 606 | Before running the `node2nix` command shown above, you probably want to set 607 | up `ssh-agent` first and use `ssh-add` to add a private key to the keychain to 608 | prevent the generator from asking for passphrases. 609 | 610 | When deploying a project or package, you need to pass an additional parameter 611 | that provides an SSH configuration file with a reference to an identify file. 612 | The following SSH config file (e.g. `~/ssh_config`) suffices for me: 613 | 614 | ``` 615 | StrictHostKeyChecking=no 616 | UserKnownHostsFile /dev/null 617 | IdentityFile ~/id_rsa 618 | ``` 619 | 620 | When deploying a package with Nix, you must propagate the location of the SSH 621 | config file as a parameter: 622 | 623 | ```bash 624 | $ nix-build -A package -I ssh-config-file=~/ssh_config 625 | ``` 626 | 627 | It is also possible to provide the location of the config file by adapting the 628 | `NIX_PATH` environment variable, as opposed to using the `-I` parameter: 629 | 630 | ```bash 631 | $ export NIX_PATH=ssh-config-file=~/ssh_config:$NIX_PATH 632 | ``` 633 | 634 | The above approach also makes it possible to deploy a NPM package with private 635 | dependencies as part of a NixOS, NixOps or Disnix configuration. 636 | 637 | Disable cache bypassing 638 | ----------------------- 639 | In a Nix builder environment, the NPM packages cache is empty and NPM does not 640 | seem to trust dependencies that are already stored in the bundled 641 | `node_modules/` folder, because they lack the meta data that can be used for 642 | integrity checks. 643 | 644 | By default, `node2nix` bypasses the cache by augmenting package configuration 645 | files with these mandatory meta data fields. 646 | 647 | If older versions of NPM are used (any version before 5.x), this meta 648 | information is not required. Bypassing the cache can be disabled by providing 649 | the `--no-bypass-cache` parameter. 650 | 651 | Troubleshooting 652 | =============== 653 | This section contains some troubleshooting information for common problems. 654 | 655 | Deploying peer dependencies 656 | --------------------------- 657 | In NPM version 2.x and older, peer dependencies were automatically deployed if 658 | they were not declared as regular dependencies. In newer versions of NPM, this 659 | behaviour has changed -- peer dependencies are only used for version checks, 660 | but NPM no longer installs them. 661 | 662 | Some package deployments may still rely on the old behaviour and will fail to 663 | deploy. To generate expressions that install peer dependencies, you can add the 664 | `--include-peer-dependencies` parameter: 665 | 666 | ```bash 667 | $ node2nix --include-peer-dependencies 668 | ``` 669 | 670 | Stripping optional dependencies 671 | ------------------------------- 672 | When NPM packages with optional dependencies are published to the NPM registry, 673 | the optional dependencies become regular runtime dependencies. As a result, 674 | when deploying a package with a broken "integrated" optional dependency, the 675 | deployment with fail, unlike pure optional dependencies that are allowed to 676 | fail. 677 | 678 | To fix these package deployments, it is possible to strip the optional 679 | dependencies from packages installed from the NPM registry: 680 | 681 | ```bash 682 | $ node2nix --strip-optional-dependencies 683 | ``` 684 | 685 | Updating the package lock file 686 | ------------------------------ 687 | When deploying projects that provide a `package-lock.json` file, `node2nix` 688 | deployments will typically fail if the corresponding `package.json` 689 | configuration has changed after the generation of the lock file, because the 690 | dependency tree in the lock file may be incomplete. 691 | 692 | To fix this problem, `npm install` must be executed again so that the missing 693 | or changed dependencies are updated in the lock file. 694 | 695 | Creating a symlink to the node_modules folder in a shell session 696 | ---------------------------------------------------------------- 697 | In Nix shell sessions, that can be started with `nix-shell -A shell`, 698 | conventional Node.js projects will typically work, because there is a 699 | `NODE_PATH` environment variable that refers to a Nix store path that provides 700 | all the dependencies that the project needs. 701 | 702 | However, there are also a variety of Node.js/NPM-based build tools available, 703 | such as Grunt, Gulp, Babel or ESLint, that work with *plugins* that are stored 704 | in the `node_modules/` folder of the project. 705 | 706 | Unfortunately, these tools do not respect the `NODE_PATH` environment variable 707 | and, as a result, fail to work in a Nix shell session. 708 | 709 | As a workaround, you can create a symlink in the project's root folder to allow 710 | these tools to find their dependencies: 711 | 712 | ```bash 713 | $ ln -s $NODE_PATH node_modules 714 | ``` 715 | 716 | Keep in mind that the symlink needs to be removed again if you want to deploy 717 | the package with `nix-build` or `nix-env`. 718 | 719 | Disabling running NPM install 720 | ----------------------------- 721 | `node2nix` tries to mimic NPM's dependency resolver as closely as possible. 722 | However, it may happen that there is a small difference and the deployment fails 723 | a result. 724 | 725 | A mismatch is typically caused by versions that can't be reliably resolved (e.g. 726 | due to wildcards) or errors in lifting bundled dependencies. In many cases, the 727 | package should still work despite the error. 728 | 729 | To prevent the deployment from failing, we can disable the `npm install` step, 730 | by overriding the package: 731 | 732 | ```nix 733 | {pkgs ? import { 734 | inherit system; 735 | }, system ? builtins.currentSystem}: 736 | 737 | let 738 | nodePackages = import ./default.nix { 739 | inherit pkgs system; 740 | }; 741 | in 742 | nodePackages // { 743 | express = nodePackages.express.override { 744 | dontNpmInstall = true; 745 | }; 746 | } 747 | ``` 748 | 749 | By overriding a package and setting the `dontNpmInstall` parameter to `true`, we 750 | skip the install step (which merely serves as a check). The generated expression 751 | is actually responsible for obtaining and extracting the dependencies. 752 | 753 | API documentation 754 | ================= 755 | This package includes API documentation, which can be generated with 756 | [JSDoc](http://usejsdoc.org). 757 | 758 | License 759 | ======= 760 | The contents of this package is available under the 761 | [MIT license](http://opensource.org/licenses/MIT) 762 | 763 | Acknowledgements 764 | ================ 765 | This package is based on ideas and principles pioneered in 766 | [npm2nix](http://github.com/NixOS/npm2nix). 767 | -------------------------------------------------------------------------------- /bin/node2nix.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var optparse = require('optparse'); 6 | var node2nix = require('../lib/node2nix.js'); 7 | var Registry = require('../lib/Registry.js').Registry; 8 | 9 | /* Define command-line options */ 10 | 11 | var switches = [ 12 | ['-h', '--help', 'Shows help sections'], 13 | ['-v', '--version', 'Shows version'], 14 | ['-i', '--input FILE', 'Specifies a path to a JSON file containing an object with package settings or an array of dependencies (defaults to: package.json)'], 15 | ['-o', '--output FILE', 'Path to a Nix expression representing a registry of Node.js packages (defaults to: node-packages.nix)'], 16 | ['-c', '--composition FILE', 'Path to a Nix composition expression allowing someone to deploy the generated Nix packages from the command-line (defaults to: default.nix)'], 17 | ['-e', '--node-env FILE', 'Path to the Nix expression implementing functions that builds NPM packages (defaults to: node-env.nix)'], 18 | ['-l', '--lock FILE', 'Path to the package-lock.json file that pinpoints the variants of all dependencies'], 19 | ['-d', '--development', 'Specifies whether to do a development (non-production) deployment for a package.json deployment (false by default)'], 20 | ['-4', '--nodejs-4', 'Provides all settings to generate expression for usage with Node.js 4.x (default is: nodejs-14_x)'], 21 | ['-6', '--nodejs-6', 'Provides all settings to generate expression for usage with Node.js 6.x (default is: nodejs-14_x)'], 22 | ['-8', '--nodejs-8', 'Provides all settings to generate expression for usage with Node.js 8.x (default is: nodejs-14_x)'], 23 | ['-10', '--nodejs-10', 'Provides all settings to generate expression for usage with Node.js 10.x (default is: nodejs-14_x)'], 24 | ['-12', '--nodejs-12', 'Provides all settings to generate expression for usage with Node.js 12.x (default is: nodejs-14_x)'], 25 | ['-13', '--nodejs-13', 'Provides all settings to generate expression for usage with Node.js 13.x (default is: nodejs-14_x)'], 26 | ['-14', '--nodejs-14', 'Provides all settings to generate expression for usage with Node.js 14.x (default is: nodejs-14_x)'], 27 | ['-16', '--nodejs-16', 'Provides all settings to generate expression for usage with Node.js 16.x (default is: nodejs-14_x)'], 28 | ['-18', '--nodejs-18', 'Provides all settings to generate expression for usage with Node.js 18.x (default is: nodejs-14_x)'], 29 | ['--supplement-input FILE', 'A supplement package JSON file that are passed as build inputs to all packages defined in the input JSON file'], 30 | ['--supplement-output FILE', 'Path to a Nix expression representing a supplementing set of Nix packages provided as inputs to a project (defaults to: supplement.nix)'], 31 | ['--include-peer-dependencies', 'Specifies whether to include peer dependencies. In npm 2.x, this is the default. (true by default for Node.js 16+)'], 32 | ['--no-flatten', 'Simulate pre-npm 3.x isolated dependency structure. (false by default)'], 33 | ['--pkg-name NAME', 'Specifies the name of the Node.js package to use from Nixpkgs (defaults to: nodejs)'], 34 | ['--registry URL', 'URL referring to the NPM packages registry. It defaults to the official NPM one, but can be overridden to support private registries'], 35 | ['--registry-scope SCOPE', 'scoped package'], 36 | ['--registry-auth-token TOKEN', 'An optional token to access private NPM registry'], 37 | ['--no-bypass-cache', 'Specifies that package builds do not need to bypass the content addressable cache (required for NPM 5.x)'], 38 | ['--no-copy-node-env', 'Do not create a copy of the Nix expression that builds NPM packages'], 39 | ['--use-fetchgit-private', 'Use fetchGitPrivate instead of fetchgit in the generated Nix expressions'], 40 | ['--strip-optional-dependencies', 'Strips the optional dependencies from the regular dependencies in the NPM registry'] 41 | ]; 42 | 43 | var parser = new optparse.OptionParser(switches); 44 | 45 | /* Set some variables and their default values */ 46 | 47 | var help = false; 48 | var version = false; 49 | var production = true; 50 | var includePeerDependencies = true; 51 | var flatten = true; 52 | var inputJSON = "package.json"; 53 | var outputNix = "node-packages.nix"; 54 | var compositionNix = "default.nix"; 55 | var supplementJSON; 56 | var supplementNix = "supplement.nix"; 57 | var nodeEnvNix = "node-env.nix"; 58 | var lockJSON; 59 | var registries = []; 60 | var nodePackage = "nodejs-14_x"; 61 | var noCopyNodeEnv = false; 62 | var bypassCache = true; 63 | var useFetchGitPrivate = false; 64 | var stripOptionalDependencies = false; 65 | var executable; 66 | 67 | /* Define process rules for option parameters */ 68 | 69 | parser.on(function(arg, value) { 70 | process.stderr.write("node2nix: " + arg + ": invalid option\n"); 71 | process.exit(1); 72 | }) 73 | 74 | parser.on('help', function(arg, value) { 75 | help = true; 76 | }); 77 | 78 | parser.on('version', function(arg, value) { 79 | version = true; 80 | }); 81 | 82 | parser.on('input', function(arg, value) { 83 | inputJSON = value; 84 | }); 85 | 86 | parser.on('output', function(arg, value) { 87 | outputNix = value; 88 | }); 89 | 90 | parser.on('composition', function(arg, value) { 91 | compositionNix = value; 92 | }); 93 | 94 | parser.on('supplement-input', function(arg, value) { 95 | supplementJSON = value; 96 | }); 97 | 98 | parser.on('supplement-output', function(arg, value) { 99 | supplementNix = value; 100 | }); 101 | 102 | parser.on('node-env', function(arg, value) { 103 | nodeEnvNix = value; 104 | }); 105 | 106 | parser.on('lock', function(arg, value) { 107 | if(value) { 108 | lockJSON = value; 109 | } else { 110 | lockJSON = "package-lock.json"; 111 | } 112 | }); 113 | 114 | parser.on('development', function(arg, value) { 115 | production = false; 116 | }); 117 | 118 | parser.on('nodejs-4', function(arg, value) { 119 | flatten = false; 120 | nodePackage = "nodejs-4_x"; 121 | bypassCache = false; 122 | includePeerDependencies = false; 123 | }); 124 | 125 | parser.on('nodejs-6', function(arg, value) { 126 | flatten = true; 127 | nodePackage = "nodejs-6_x"; 128 | bypassCache = false; 129 | includePeerDependencies = false; 130 | }); 131 | 132 | parser.on('nodejs-8', function(arg, value) { 133 | flatten = true; 134 | nodePackage = "nodejs-8_x"; 135 | bypassCache = true; 136 | includePeerDependencies = false; 137 | }); 138 | 139 | parser.on('nodejs-10', function(arg, value) { 140 | flatten = true; 141 | nodePackage = "nodejs-10_x"; 142 | bypassCache = true; 143 | includePeerDependencies = false; 144 | }); 145 | 146 | parser.on('nodejs-12', function(arg, value) { 147 | flatten = true; 148 | nodePackage = "nodejs-12_x"; 149 | bypassCache = true; 150 | includePeerDependencies = false; 151 | }); 152 | 153 | parser.on('nodejs-13', function(arg, value) { 154 | flatten = true; 155 | nodePackage = "nodejs-13_x"; 156 | bypassCache = true; 157 | includePeerDependencies = false; 158 | }); 159 | 160 | parser.on('nodejs-14', function(arg, value) { 161 | flatten = true; 162 | nodePackage = "nodejs-14_x"; 163 | bypassCache = true; 164 | includePeerDependencies = false; 165 | }); 166 | 167 | parser.on('nodejs-16', function(arg, value) { 168 | flatten = true; 169 | nodePackage = "nodejs-16_x"; 170 | bypassCache = true; 171 | includePeerDependencies = true; 172 | }); 173 | 174 | parser.on('nodejs-17', function(arg, value) { 175 | flatten = true; 176 | nodePackage = "nodejs-17_x"; 177 | bypassCache = true; 178 | includePeerDependencies = true; 179 | }); 180 | 181 | parser.on('nodejs-18', function(arg, value) { 182 | flatten = true; 183 | nodePackage = "nodejs-18_x"; 184 | bypassCache = true; 185 | includePeerDependencies = true; 186 | }); 187 | 188 | parser.on('include-peer-dependencies', function(arg, value) { 189 | includePeerDependencies = true; 190 | }); 191 | 192 | parser.on('no-flatten', function(arg, value) { 193 | flatten = false; 194 | }); 195 | 196 | parser.on('pkg-name', function(arg, value) { 197 | nodePackage = value; 198 | }); 199 | 200 | var registryIndex = -1; 201 | parser.on('registry', function(arg, value) { 202 | registries.push(new Registry(value)); 203 | registryIndex++; 204 | }); 205 | 206 | parser.on('registry-auth-token', function(arg, value) { 207 | registries[registryIndex].authToken = value; 208 | }); 209 | 210 | parser.on('registry-scope', function(arg, value) { 211 | registries[registryIndex].scope = value; 212 | }); 213 | 214 | parser.on('no-bypass-cache', function(arg, value) { 215 | bypassCache = false; 216 | }); 217 | 218 | parser.on('no-copy-node-env', function(arg, value) { 219 | noCopyNodeEnv = true; 220 | }); 221 | 222 | parser.on('use-fetchgit-private', function(arg, value) { 223 | useFetchGitPrivate = true; 224 | }); 225 | 226 | parser.on('strip-optional-dependencies', function(arg, value) { 227 | stripOptionalDependencies = true; 228 | }); 229 | 230 | /* Define process rules for non-option parameters */ 231 | 232 | parser.on(1, function(opt) { 233 | executable = opt; 234 | }); 235 | 236 | /* Do the actual command-line parsing */ 237 | 238 | parser.parse(process.argv); 239 | 240 | /* Display the help, if it has been requested */ 241 | 242 | if(help) { 243 | function displayTab(len, maxlen) { 244 | for(var i = 0; i < maxlen - len; i++) { 245 | process.stdout.write(" "); 246 | } 247 | } 248 | 249 | process.stdout.write("Usage: " + executable + " [OPTION]\n\n"); 250 | 251 | process.stdout.write("Generates a set of Nix expressions from a NPM package's package.json\n"); 252 | process.stdout.write("configuration or a collection.json configuration containing a set of NPM\n"); 253 | process.stdout.write("dependency specifiers so that the packages can be deployed with Nix instead\n"); 254 | process.stdout.write("of NPM.\n\n"); 255 | 256 | process.stdout.write("Options:\n"); 257 | 258 | var maxlen = 30; 259 | 260 | for(var i = 0; i < switches.length; i++) { 261 | 262 | var currentSwitch = switches[i]; 263 | 264 | process.stdout.write(" "); 265 | 266 | if(currentSwitch.length == 3) { 267 | process.stdout.write(currentSwitch[0] + ", "+currentSwitch[1]); 268 | displayTab(currentSwitch[0].length + 2 + currentSwitch[1].length, maxlen); 269 | process.stdout.write(currentSwitch[2]); 270 | } else { 271 | process.stdout.write(currentSwitch[0]); 272 | displayTab(currentSwitch[0].length, maxlen); 273 | process.stdout.write(currentSwitch[1]); 274 | } 275 | 276 | process.stdout.write("\n"); 277 | } 278 | 279 | process.exit(0); 280 | } 281 | 282 | /* Display the version, if it has been requested */ 283 | 284 | if(version) { 285 | var version = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"))).version; 286 | 287 | process.stdout.write("node2nix " + version + "\n"); 288 | process.exit(0); 289 | } 290 | 291 | if(registries.length == 0) { 292 | registries.push(new Registry("https://registry.npmjs.org")); 293 | } 294 | 295 | /* Perform the NPM to Nix conversion */ 296 | node2nix.npmToNix(inputJSON, outputNix, compositionNix, nodeEnvNix, lockJSON, supplementJSON, supplementNix, production, includePeerDependencies, flatten, nodePackage, registries, noCopyNodeEnv, bypassCache, useFetchGitPrivate, stripOptionalDependencies, function(err) { 297 | if(err) { 298 | process.stderr.write(err + "\n"); 299 | process.exit(1); 300 | } else { 301 | process.exit(0); 302 | } 303 | }); 304 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # This script generates the Nix expressions for node2nix itself and its 4 | # testcases. 5 | # It can be invoked by installing its dependencies first, by either running: 6 | # 7 | # $ npm install 8 | # 9 | # or by starting a Nix shell: 10 | # 11 | # $ nix-shell -A shell 12 | 13 | node bin/node2nix -e nix/node-env.nix --nodejs-14 -d --no-copy-node-env 14 | cd tests 15 | node ../bin/node2nix -i tests.json -o node-packages-v14.nix -c default-v14.nix -e ../nix/node-env.nix --nodejs-14 --no-copy-node-env 16 | node ../bin/node2nix -i tests.json -o node-packages-v16.nix -c default-v16.nix -e ../nix/node-env.nix --nodejs-16 --no-copy-node-env 17 | cd grunt 18 | node ../../bin/node2nix -d -i package.json --supplement-input supplement.json -e ../../nix/node-env.nix --no-copy-node-env 19 | cd ../lockfile 20 | node ../../bin/node2nix -l package-lock.json -e ../../nix/node-env.nix --no-copy-node-env 21 | cd ../lockfile-v2 22 | node ../../bin/node2nix -l package-lock.json -e ../../nix/node-env.nix --no-copy-node-env 23 | cd ../scoped 24 | node ../../bin/node2nix -e ../../nix/node-env.nix --no-copy-node-env 25 | cd ../versionless 26 | node ../../bin/node2nix -e ../../nix/node-env.nix --no-copy-node-env 27 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-14_x"}: 6 | 7 | let 8 | nodeEnv = import ./nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /doc/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "--title" : "node2nix", 3 | "--builtin-classes" : true, 4 | "--no-source" : true 5 | } 6 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1634851050, 6 | "narHash": "sha256-N83GlSGPJJdcqhUxSCS/WwW5pksYf3VP1M13cDRTSVA=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "c91f3de5adaf1de973b797ef7485e441a65b8935", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1634782485, 21 | "narHash": "sha256-psfh4OQSokGXG0lpq3zKFbhOo3QfoeudRcaUnwMRkQo=", 22 | "owner": "nixos", 23 | "repo": "nixpkgs", 24 | "rev": "34ad3ffe08adfca17fcb4e4a47bb5f3b113687be", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "nixos", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Generate Nix expressions to build NPM packages"; 3 | 4 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 5 | inputs.flake-utils.url = "github:numtide/flake-utils"; 6 | 7 | outputs = { self, nixpkgs, flake-utils }: 8 | flake-utils.lib.eachDefaultSystem (system: 9 | let 10 | pkgs = nixpkgs.legacyPackages.${system}; 11 | inherit (import ./default.nix { inherit pkgs; }) 12 | sources package shell nodeDependencies; 13 | node2nix = package; 14 | app = flake-utils.lib.mkApp { 15 | drv = package; 16 | exePath = "/bin/node2nix"; 17 | }; 18 | overlays = final: prev: { node2nix = node2nix; }; 19 | in { 20 | packages.node2nix = node2nix; 21 | packages.default = node2nix; 22 | apps.node2nix = app; 23 | apps.default = app; 24 | nodeDependencies = nodeDependencies; 25 | nodeShell = shell; 26 | inherit overlays; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /lib/DeploymentConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Construct a new deployment config instance. 3 | * 4 | * @class DeploymentConfig 5 | * @classdesc Stores all global deployment configuration properties 6 | * 7 | * @constructor 8 | * @param {Array} registries An array of alternative registries for scoped packages 9 | * @param {Boolean} production Indicates whether we deploy in production mode or 10 | * development mode. In development mode, also the development dependencies 11 | * will be included. 12 | * @param {Boolean} includePeerDependencies Indicates whether to include peer dependencies with the package 13 | * @param {Boolean} flatten Indicates whether to create a flat dependency structure in which dependencies are as high as possible in the graph 14 | * @param {String} nodePackage Name of the Node.js package to use from Nixpkgs 15 | * @param {String} outputDir Directory in which the nix expression will be written 16 | * @param {Boolean} bypassCache Indicates that the content addressable cache should be bypassed 17 | * @param {Boolean} stripOptionalDependencies When enabled, the optional dependencies are stripped from the regular dependencies in the NPM registry 18 | */ 19 | function DeploymentConfig(registries, production, includePeerDependencies, flatten, nodePackage, outputDir, bypassCache, stripOptionalDependencies) { 20 | this.registries = registries; 21 | this.production = production; 22 | this.includePeerDependencies = includePeerDependencies; 23 | this.flatten = flatten; 24 | this.nodePackage = nodePackage; 25 | this.outputDir = outputDir; 26 | this.bypassCache = bypassCache; 27 | this.stripOptionalDependencies = stripOptionalDependencies; 28 | } 29 | 30 | exports.DeploymentConfig = DeploymentConfig; 31 | -------------------------------------------------------------------------------- /lib/Package.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var slasp = require('slasp'); 3 | var semver = require('semver'); 4 | var nijs = require('nijs'); 5 | var Source = require('./sources/Source.js').Source; 6 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 7 | 8 | /** 9 | * Creates a new package object. 10 | * 11 | * @class Package 12 | * @extends NixASTNode 13 | * @classdesc A representation of an NPM package that is obtained from an external source, 14 | * that may have dependencies on other packages, and may bundle packages in its 15 | * node_modules/ sub folder. 16 | * 17 | * @constructor 18 | * @param {DeploymentConfig} deploymentConfig An object capturing global deployment settings 19 | * @param {Object} lock Contents of a package lock file (or undefined if no lock exists) 20 | * @param {Package} parent Reference to the package that embeds the constructed 21 | * package, or null if there is no parent 22 | * @param {String} name Name of a Node.js package 23 | * @param {String} versionSpec Version specifier of a Node.js package, such as 24 | * an exact version number, version range, URL, or GitHub identifier 25 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 26 | * @param {Boolean} production Indicates whether we deploy in production mode or 27 | development mode. In development mode, also the development dependencies 28 | will be included. 29 | * @param {SourcesCache} sourcesCache Cache that contains references to all sources that need to be obtained 30 | */ 31 | function Package(deploymentConfig, lock, parent, name, versionSpec, baseDir, production, sourcesCache) { 32 | this.deploymentConfig = deploymentConfig; 33 | this.lock = lock; 34 | this.parent = parent; 35 | this.production = production; 36 | this.sourcesCache = sourcesCache; 37 | this.source = Source.constructSource(deploymentConfig.registries, baseDir, deploymentConfig.outputDir, name, versionSpec, deploymentConfig.stripOptionalDependencies); 38 | this.requiredDependencies = {}; 39 | this.providedDependencies = {}; 40 | } 41 | 42 | /* Package inherits from NixASTNode */ 43 | inherit(nijs.NixASTNode, Package); 44 | 45 | /** 46 | * Recursively checks the enclosing parent packages to see whether a dependency 47 | * exists that fits within the required version range. 48 | * 49 | * @method 50 | * @param {String} name Name of a Node.js package 51 | * @param {String} versionSpec Version specifier of a Node.js package, such as 52 | * an exact version number, version range, URL, or GitHub identifier 53 | * @return {Package} The nearest parent package matching the version specifier 54 | * or null if no such package exists 55 | */ 56 | Package.prototype.findMatchingProvidedDependencyByParent = function(name, versionSpec) { 57 | if(this.parent === null) { // If there is no parent, then we can also not provide a dependency 58 | return null; 59 | } else { 60 | var dependency = this.parent.providedDependencies[name]; 61 | 62 | if(dependency === undefined) { 63 | return this.parent.findMatchingProvidedDependencyByParent(name, versionSpec); // If the parent does not provide the dependency, try the parent's parent 64 | } else if(dependency === null) { 65 | return null; // If we have encountered a bundled dependency with the same name, consider it a conflict (is not a perfect resolution, but does not result in an error) 66 | } else { 67 | if(semver.satisfies(dependency.source.config.version, versionSpec, true)) { // If we found a dependency with the same name, see if the version fits 68 | return dependency; 69 | } else { 70 | return null; // If there is a version mismatch, then a conflicting version has been encountered 71 | } 72 | } 73 | } 74 | }; 75 | 76 | /** 77 | * Checks whether a dependency with a given name is already bundled with this 78 | * package. 79 | * 80 | * @method 81 | * @param {String} dependencyName Name of the dependency 82 | * @return {Boolean} true if the dependency is bundled, else false 83 | */ 84 | Package.prototype.isBundledDependency = function(dependencyName) { 85 | // Check the bundledDependencies option 86 | if(Array.isArray(this.source.config.bundledDependencies)) { 87 | for(var i = 0; i < this.source.config.bundledDependencies.length; i++) { 88 | if(dependencyName == this.source.config.bundledDependencies[i]) 89 | return true; 90 | } 91 | } 92 | 93 | // Check the bundleDependencies option 94 | if(Array.isArray(this.source.config.bundleDependencies)) { 95 | for(var i = 0; i < this.source.config.bundleDependencies.length; i++) { 96 | if(dependencyName == this.source.config.bundleDependencies[i]) 97 | return true; 98 | } 99 | } 100 | 101 | return false; 102 | }; 103 | 104 | /** 105 | * Bundles a dependency (that is fetched from an external location) to the 106 | * package (or a parent package if flattening has been enabled). A bundled 107 | * package will appear in the node_modules/ sub folder of the package. 108 | * 109 | * @method 110 | * @param {String} dependencyName Name of the dependency 111 | * @param {Package} pkg Package to bundle in the node_modules/ sub folder 112 | */ 113 | Package.prototype.bundleDependency = function(dependencyName, pkg) { 114 | this.requiredDependencies[dependencyName] = pkg; 115 | 116 | if(this.deploymentConfig.flatten) { // In flatten mode, bundle dependency with the highest parent where it is not conflicting 117 | if(this.parent !== null && this.parent.providedDependencies[dependencyName] === undefined && this.parent.requiredDependencies[dependencyName] === undefined) { 118 | this.parent.bundleDependency(dependencyName, pkg); 119 | } else { 120 | pkg.parent = this; 121 | this.providedDependencies[dependencyName] = pkg; 122 | } 123 | } else { 124 | this.providedDependencies[dependencyName] = pkg; // Bundle the dependency with the package 125 | } 126 | }; 127 | 128 | /** 129 | * Bundles a collection of dependencies with this package (or when flattening 130 | * mode has been enabled) any parent package that does not conflict and 131 | * automatically fetches its metadata by downloading it. 132 | * 133 | * @method 134 | * @param {Array} resolvedDependencies Memorizes the dependencies that have been resolved so that we can resolve their transitive dependencies later. 135 | * @param {Array} dependencies Dependencies to bundle with the package 136 | * @param {function(Object)} callback Callback that gets invoked then the work 137 | * is done. The first parameter is set to an error object if the operation 138 | * fails. 139 | */ 140 | Package.prototype.bundleDependencies = function(resolvedDependencies, dependencies, callback) { 141 | if(dependencies === undefined) { 142 | callback(); 143 | } else { 144 | var self = this; 145 | 146 | slasp.fromEach(function(callback) { 147 | callback(null, dependencies); 148 | }, function(dependencyName, callback) { 149 | var versionSpec = dependencies[dependencyName]; 150 | var parentDependency = self.findMatchingProvidedDependencyByParent(dependencyName, versionSpec); 151 | 152 | if(self.isBundledDependency(dependencyName)) { 153 | self.requiredDependencies[dependencyName] = null; 154 | callback(); 155 | } else if(parentDependency === null) { 156 | var pkg = new Package(self.deploymentConfig, self.lock, self, dependencyName, versionSpec, self.source.baseDir, true /* Never include development dependencies of transitive dependencies */, self.sourcesCache); 157 | 158 | slasp.sequence([ 159 | function(callback) { 160 | pkg.source.fetch(callback); 161 | }, 162 | 163 | function(callback) { 164 | self.sourcesCache.addSource(pkg.source); 165 | self.bundleDependency(dependencyName, pkg); 166 | resolvedDependencies[dependencyName] = pkg; 167 | callback(); 168 | } 169 | ], callback); 170 | } else { 171 | self.requiredDependencies[dependencyName] = parentDependency; // If there is a parent package that provides the requested dependency -> use it 172 | callback(); 173 | } 174 | 175 | }, callback); 176 | } 177 | }; 178 | 179 | Package.prototype.resolveDependenciesAndSources = function(callback) { 180 | var self = this; 181 | var resolvedDependencies = {}; 182 | 183 | slasp.sequence([ 184 | function(callback) { 185 | self.bundleDependencies(resolvedDependencies, self.source.config.dependencies, callback); 186 | }, 187 | 188 | function(callback) { 189 | /* Bundle the development dependencies, if applicable */ 190 | if(self.production) { 191 | callback(); 192 | } else { 193 | self.bundleDependencies(resolvedDependencies, self.source.config.devDependencies, callback); 194 | } 195 | }, 196 | 197 | function(callback) { 198 | if(self.deploymentConfig.includePeerDependencies) { 199 | /* Bundle the required peer dependencies, if applicable */ 200 | self.bundleDependencies(resolvedDependencies, self.source.config.peerDependencies, callback); 201 | } else { 202 | callback(); 203 | } 204 | }, 205 | 206 | function(callback) { 207 | /* Bundle transitive dependencies */ 208 | 209 | slasp.fromEach(function(callback) { 210 | callback(null, resolvedDependencies); 211 | }, function(dependencyName, callback) { 212 | var dependency = resolvedDependencies[dependencyName]; 213 | dependency.resolveDependenciesAndSources(callback); 214 | }, callback); 215 | } 216 | ], callback); 217 | }; 218 | 219 | Package.prototype.resolveDependenciesFromLockedDependencies = function(dependencyObj, callback) { 220 | var self = this; 221 | 222 | if(dependencyObj.dependencies === undefined) { 223 | callback(); 224 | } else { 225 | slasp.fromEach(function(callback) { 226 | callback(null, dependencyObj.dependencies); 227 | }, function(dependencyName, callback) { 228 | var dependency = dependencyObj.dependencies[dependencyName]; 229 | 230 | if(dependency.bundled) { // Bundled dependencies should not be included 231 | callback(); 232 | } else if(self.deploymentConfig.stripOptionalDependencies && dependency.optional) { // When the stripping optional dependencies feature has been enabled, remove all optional dependencies 233 | callback(); 234 | } else if(self.production && dependency.dev) { // Development dependencies should not be included in production mode 235 | callback(); 236 | } else { 237 | var pkg = new Package(self.deploymentConfig, self.lock, self, dependencyName, dependency.version, self.source.baseDir, self.production, self.sourcesCache); 238 | self.providedDependencies[dependencyName] = pkg; 239 | 240 | slasp.sequence([ 241 | function(callback) { 242 | pkg.source.convertFromLockedDependency(dependency, callback); 243 | }, 244 | 245 | function(callback) { 246 | self.sourcesCache.addSource(pkg.source); 247 | pkg.resolveDependenciesFromLockedDependencies(dependency, callback); 248 | } 249 | ], callback); 250 | } 251 | }, callback); 252 | } 253 | }; 254 | 255 | /** 256 | * Resolves all dependencies and transitive dependencies of this package. 257 | * 258 | * @method 259 | * @param {function(Object)} callback Callback that gets invoked then the work 260 | * is done. The first parameter is set to an error object if the operation 261 | * fails. 262 | */ 263 | Package.prototype.resolveDependencies = function(callback) { 264 | if(this.lock === undefined) { // If no lock file is present, let the tool fetch all dependencies and metadata 265 | this.resolveDependenciesAndSources(callback); 266 | } else { // If we have a lock file, use that to generate Nix expressions 267 | this.resolveDependenciesFromLockedDependencies(this.lock, callback); 268 | } 269 | }; 270 | 271 | /** 272 | * Composes an abstract syntax tree for the provided NPM dependencies by this 273 | * package. 274 | * 275 | * @method 276 | * @return {Array} An array representing a list of NPM package dependencies 277 | */ 278 | Package.prototype.generateDependencyAST = function() { 279 | var self = this; 280 | var dependencies = []; 281 | 282 | Object.keys(self.providedDependencies).sort().forEach(function(dependencyName) { 283 | var dependency = self.providedDependencies[dependencyName]; 284 | 285 | // For each dependency, refer to the source attribute set that defines it 286 | var ref = new nijs.NixAttrReference({ 287 | attrSetExpr: new nijs.NixExpression("sources"), 288 | refExpr: dependency.source.identifier 289 | }); 290 | 291 | var transitiveDependencies = dependency.generateDependencyAST(); 292 | var dependencyExpr; 293 | 294 | if(transitiveDependencies === undefined) { 295 | dependencyExpr = ref; // If a dependency has no dependencies of its own, we just refer to the attribute in the source set 296 | } else { 297 | // If a dependency has dependencies, we augment the reference with the set of dependencies that it needs 298 | dependencyExpr = new nijs.NixMergeAttrs({ 299 | left: ref, 300 | right: { 301 | dependencies: transitiveDependencies 302 | } 303 | }); 304 | } 305 | 306 | dependencies.push(dependencyExpr); 307 | }); 308 | 309 | if(dependencies.length == 0) { 310 | return undefined; // If no dependencies are required, simply compose no parameter. Though not mandatory, it improves readability of the generated expression 311 | } else { 312 | return dependencies; 313 | } 314 | }; 315 | 316 | /** 317 | * @see NixASTNode#toNixAST 318 | */ 319 | Package.prototype.toNixAST = function() { 320 | var homepage; 321 | 322 | if(typeof this.source.config.homepage == "string" && this.source.config.homepage) { 323 | homepage = this.source.config.homepage; 324 | } 325 | 326 | var ast = this.source.toNixAST(); 327 | ast.dependencies = this.generateDependencyAST(); 328 | ast.buildInputs = new nijs.NixExpression("globalBuildInputs"); 329 | ast.meta = { 330 | description: this.source.config.description, 331 | homepage: homepage, 332 | license: this.source.config.license 333 | }; 334 | ast.production = this.production; 335 | ast.bypassCache = this.deploymentConfig.bypassCache; 336 | ast.reconstructLock = (this.lock === undefined); 337 | 338 | return ast; 339 | }; 340 | 341 | exports.Package = Package; 342 | -------------------------------------------------------------------------------- /lib/Registry.js: -------------------------------------------------------------------------------- 1 | function Registry(url, scope, authToken) { 2 | this.url = url; 3 | this.scope = scope; 4 | this.authToken = authToken; 5 | } 6 | 7 | exports.Registry = Registry; 8 | -------------------------------------------------------------------------------- /lib/SourcesCache.js: -------------------------------------------------------------------------------- 1 | var nijs = require('nijs'); 2 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 3 | 4 | /** 5 | * Construct a new source cache instace. 6 | * 7 | * @class SourcesCache 8 | * @extends NixASTNode 9 | * @classdesc A cache store that memorizes all packages to obtain from external sources. 10 | * 11 | * @constructor 12 | */ 13 | function SourcesCache() { 14 | this.sources = {}; 15 | } 16 | 17 | /* SourcesCache inherits from NixASTNode */ 18 | inherit(nijs.NixASTNode, SourcesCache); 19 | 20 | /** 21 | * Checks whether a given source exists and if not, adds it to the cache. 22 | * 23 | * @method 24 | * @param {Source} source Any source object 25 | */ 26 | SourcesCache.prototype.addSource = function(source) { 27 | if(this.sources[source.identifier] === undefined) { 28 | this.sources[source.identifier] = source; 29 | } 30 | }; 31 | 32 | /** 33 | * @see NixASTNode#toNixAST 34 | */ 35 | SourcesCache.prototype.toNixAST = function() { 36 | var self = this; 37 | var ast = {}; 38 | 39 | Object.keys(self.sources).sort().forEach(function(identifier) { 40 | var source = self.sources[identifier]; 41 | ast[identifier] = source.toNixAST(); 42 | }); 43 | 44 | return ast; 45 | }; 46 | 47 | exports.SourcesCache = SourcesCache; 48 | -------------------------------------------------------------------------------- /lib/expressions/CollectionExpression.js: -------------------------------------------------------------------------------- 1 | var slasp = require('slasp'); 2 | var nijs = require('nijs'); 3 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 4 | var OutputExpression = require('./OutputExpression.js').OutputExpression; 5 | var Package = require('../Package.js').Package; 6 | 7 | /** 8 | * Creates a new collection expression instance. 9 | * 10 | * @class CollectionExpression 11 | * @extends OutputExpression 12 | * @classdesc Represents an expression that contains multiple end user NPM packages. 13 | * 14 | * @constructor 15 | * @param {DeploymentConfig} deploymentConfig An object capturing global deployment settings 16 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 17 | * @param {Array} dependencies An array of dependency specifications 18 | */ 19 | function CollectionExpression(deploymentConfig, baseDir, dependencies) { 20 | OutputExpression.call(this); 21 | 22 | // Compose package objects for all dependencies 23 | this.packages = {}; 24 | 25 | for(var i = 0; i < dependencies.length; i++) { 26 | var dependencySpec = dependencies[i]; 27 | var dependency; 28 | 29 | if(typeof dependencySpec == "string") { 30 | dependency = {}; 31 | dependency[dependencySpec] = "latest"; 32 | } else { 33 | dependency = dependencySpec; 34 | } 35 | 36 | for(dependencyName in dependency) { 37 | var versionSpec = dependency[dependencyName]; 38 | 39 | var identifier; 40 | 41 | if(versionSpec == "*" || versionSpec == "latest") { // Refer to packages of versions * or latest by name 42 | identifier = dependencyName; 43 | } else { 44 | identifier = dependencyName + "-" + versionSpec; 45 | } 46 | 47 | this.packages[identifier] = new Package(deploymentConfig, undefined, null, dependencyName, versionSpec, baseDir, deploymentConfig.production, this.sourcesCache); 48 | } 49 | } 50 | } 51 | 52 | /* CollectionExpression inherits from OutputExpression */ 53 | inherit(OutputExpression, CollectionExpression); 54 | 55 | /** 56 | * @see OutputExpression#resolveDependencies 57 | */ 58 | CollectionExpression.prototype.resolveDependencies = function(callback) { 59 | var self = this; 60 | 61 | slasp.fromEach(function(callback) { 62 | callback(null, self.packages); 63 | }, function(identifier, callback) { 64 | var pkg = self.packages[identifier]; 65 | 66 | slasp.sequence([ 67 | function(callback) { 68 | pkg.source.fetch(callback); 69 | }, 70 | 71 | function(callback) { 72 | pkg.resolveDependencies(callback); 73 | } 74 | ], callback); 75 | }, callback); 76 | }; 77 | 78 | /** 79 | * @see NixASTNode#toNixAST 80 | */ 81 | CollectionExpression.prototype.toNixAST = function() { 82 | var ast = OutputExpression.prototype.toNixAST.call(this); 83 | 84 | // Generate sub expression for all the packages in the collection 85 | var packagesExpr = {}; 86 | 87 | for(var identifier in this.packages) { 88 | var pkg = this.packages[identifier]; 89 | 90 | packagesExpr[identifier] = new nijs.NixFunInvocation({ 91 | funExpr: new nijs.NixAttrReference({ 92 | attrSetExpr: new nijs.NixExpression("nodeEnv"), 93 | refExpr: new nijs.NixExpression("buildNodePackage") 94 | }), 95 | paramExpr: pkg 96 | }); 97 | } 98 | 99 | // Attach sub expression to the function body 100 | ast.body.body = packagesExpr; 101 | 102 | return ast; 103 | }; 104 | 105 | exports.CollectionExpression = CollectionExpression; 106 | -------------------------------------------------------------------------------- /lib/expressions/CompositionExpression.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var nijs = require('nijs'); 3 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 4 | 5 | /* 6 | * Prefixes a relative path with ./ if needed so that it can be converted to a 7 | * value belonging to Nix's file type 8 | */ 9 | function prefixRelativePath(target) { 10 | if(path.isAbsolute(target) || target.substring(0, 2) == "./" || target.substring(0, 3) == "../") { 11 | return target; 12 | } else { 13 | return "./" + target; 14 | } 15 | } 16 | 17 | function composePathRelativeToCompositionExpression(compositionNix, exprNix) { 18 | return prefixRelativePath(path.relative(path.dirname(compositionNix), exprNix)); 19 | } 20 | 21 | /** 22 | * Creates a new composition expression instance. 23 | * 24 | * @class CompositionExpression 25 | * @extends NixASTNode 26 | * @classdesc Represents an expression that composes NPM packages 27 | * 28 | * @constructor 29 | * @param {String} compositionNix Path to which the generated composition expression is written 30 | * @param {String} nodePackage Name of the Node.js package to use from Nixpkgs 31 | * @param {String} nodeEnvNix Path to which the NPM package build expression is written 32 | * @param {String} packagesNix Path to a Nix expression containing packages and sources 33 | * @param {String} supplementNix Path to which the generated supplement expression is written 34 | * @param {Boolean} generateSupplement Indicates whether we need a reference to a supplement Nix expression 35 | * @param {Boolean} useFetchGitPrivate Indicates that the fetchgitPrivate function should be used instead of fetchgit 36 | */ 37 | function CompositionExpression(compositionNix, nodePackage, nodeEnvNix, packagesNix, supplementNix, generateSupplement, useFetchGitPrivate) { 38 | this.nodePackage = nodePackage; 39 | this.nodeEnvNixPath = composePathRelativeToCompositionExpression(compositionNix, nodeEnvNix); 40 | this.packagesNixPath = composePathRelativeToCompositionExpression(compositionNix, packagesNix); 41 | this.supplementNix = composePathRelativeToCompositionExpression(compositionNix, supplementNix); 42 | this.generateSupplement = generateSupplement; 43 | this.useFetchGitPrivate = useFetchGitPrivate; 44 | } 45 | 46 | /* CompositionExpression inherits from NixASTNode */ 47 | inherit(nijs.NixASTNode, CompositionExpression); 48 | 49 | /** 50 | * @see NixASTNode#toNixAST 51 | */ 52 | CompositionExpression.prototype.toNixAST = function() { 53 | var globalBuildInputs; 54 | var fetchgitAttr; 55 | 56 | // Determine which fetchgit function to use 57 | if(this.useFetchGitPrivate) { 58 | fetchgitAttr = new nijs.NixAttrReference({ 59 | attrSetExpr: new nijs.NixExpression("pkgs"), 60 | refExpr: new nijs.NixExpression("fetchgitPrivate") 61 | }); 62 | } else { 63 | fetchgitAttr = new nijs.NixInherit("pkgs"); 64 | } 65 | 66 | if(this.generateSupplement) { 67 | var supplementNixPath = prefixRelativePath(this.supplementNix); 68 | 69 | globalBuildInputs = new nijs.NixFunInvocation({ 70 | funExpr: new nijs.NixExpression("pkgs.lib.attrValues"), 71 | paramExpr: new nijs.NixFunInvocation({ 72 | funExpr: new nijs.NixImport(new nijs.NixFile({ value: supplementNixPath })), 73 | paramExpr: { 74 | nodeEnv: new nijs.NixInherit(), 75 | stdenv: new nijs.NixInherit("pkgs"), 76 | lib: new nijs.NixInherit("pkgs"), 77 | "nix-gitignore": new nijs.NixInherit("pkgs"), 78 | fetchurl: new nijs.NixInherit("pkgs"), 79 | fetchgit: fetchgitAttr 80 | } 81 | }) 82 | }); 83 | } 84 | 85 | return new nijs.NixFunction({ 86 | argSpec: { 87 | pkgs: new nijs.NixFunInvocation({ 88 | funExpr: new nijs.NixImport(new nijs.NixExpression("")), 89 | paramExpr: { 90 | system: new nijs.NixInherit() 91 | } 92 | }), 93 | system: new nijs.NixAttrReference({ 94 | attrSetExpr: new nijs.NixExpression("builtins"), 95 | refExpr: new nijs.NixExpression("currentSystem") 96 | }), 97 | nodejs: new nijs.NixAttrReference({ 98 | attrSetExpr: new nijs.NixExpression("pkgs"), 99 | refExpr: this.nodePackage 100 | }) 101 | }, 102 | body: new nijs.NixLet({ 103 | value: { 104 | globalBuildInputs: globalBuildInputs, 105 | 106 | nodeEnv: new nijs.NixFunInvocation({ 107 | funExpr: new nijs.NixImport(new nijs.NixFile({ value: this.nodeEnvNixPath })), 108 | paramExpr: { 109 | stdenv: new nijs.NixInherit("pkgs"), 110 | lib: new nijs.NixInherit("pkgs"), 111 | python2: new nijs.NixInherit("pkgs"), 112 | libtool: new nijs.NixIf({ 113 | ifExpr: new nijs.NixAttrReference({ 114 | attrSetExpr: new nijs.NixExpression("pkgs"), 115 | refExpr: new nijs.NixAttrReference({ 116 | attrSetExpr: new nijs.NixExpression("stdenv"), 117 | refExpr: new nijs.NixExpression("isDarwin") 118 | }), 119 | }), 120 | thenExpr: new nijs.NixAttrReference({ 121 | attrSetExpr: new nijs.NixExpression("pkgs"), 122 | refExpr: new nijs.NixAttrReference({ 123 | attrSetExpr: new nijs.NixExpression("darwin"), 124 | refExpr: new nijs.NixExpression("cctools") 125 | }), 126 | }), 127 | elseExpr: null 128 | }), 129 | runCommand: new nijs.NixInherit("pkgs"), 130 | writeTextFile: new nijs.NixInherit("pkgs"), 131 | writeShellScript: new nijs.NixInherit("pkgs"), 132 | pkgs: new nijs.NixInherit(), 133 | nodejs: new nijs.NixInherit() 134 | } 135 | }) 136 | }, 137 | body: new nijs.NixFunInvocation({ 138 | funExpr: new nijs.NixImport(new nijs.NixFile({ value: this.packagesNixPath })), 139 | paramExpr: { 140 | fetchurl: new nijs.NixInherit("pkgs"), 141 | "nix-gitignore": new nijs.NixInherit("pkgs"), 142 | stdenv: new nijs.NixInherit("pkgs"), 143 | lib: new nijs.NixInherit("pkgs"), 144 | fetchgit: fetchgitAttr, 145 | nodeEnv: new nijs.NixInherit(), 146 | globalBuildInputs: globalBuildInputs ? new nijs.NixInherit() : undefined 147 | } 148 | }) 149 | }) 150 | }); 151 | }; 152 | 153 | exports.CompositionExpression = CompositionExpression; 154 | -------------------------------------------------------------------------------- /lib/expressions/OutputExpression.js: -------------------------------------------------------------------------------- 1 | var nijs = require('nijs'); 2 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 3 | var SourcesCache = require('../SourcesCache.js').SourcesCache; 4 | 5 | /** 6 | * Creates a new output expression instance. 7 | * 8 | * @class OutputExpression 9 | * @extends NixASTNode 10 | * @classdesc Represents an expression that contains one or more NPM packages 11 | * 12 | * @constructor 13 | */ 14 | function OutputExpression() { 15 | this.sourcesCache = new SourcesCache(); 16 | } 17 | 18 | /* OutputExpression inherits from NixASTNode */ 19 | inherit(nijs.NixASTNode, OutputExpression); 20 | 21 | /** 22 | * Resolves the sources of the dependencies and all transitive dependencies. 23 | * 24 | * @method 25 | * @param {function(String)} callback Callback that gets invoked when the work 26 | * is done. In case of an error, the first parameter contains a string with 27 | * an error message. 28 | */ 29 | OutputExpression.prototype.resolveDependencies = function(callback) { 30 | callback("resolveDependencies() is unimplemented. Please use a prototype that inherits from OutputExpression!"); 31 | }; 32 | 33 | /** 34 | * @see NixASTNode#toNixAST 35 | */ 36 | OutputExpression.prototype.toNixAST = function() { 37 | return new nijs.NixFunction({ 38 | argSpec: { 39 | nodeEnv: undefined, 40 | fetchurl: undefined, 41 | fetchgit: undefined, 42 | "nix-gitignore": undefined, 43 | stdenv: undefined, 44 | lib: undefined, 45 | globalBuildInputs: [] 46 | }, 47 | body: new nijs.NixLet({ 48 | value: { 49 | sources: this.sourcesCache 50 | } 51 | }) 52 | }); 53 | }; 54 | 55 | exports.OutputExpression = OutputExpression; 56 | -------------------------------------------------------------------------------- /lib/expressions/PackageExpression.js: -------------------------------------------------------------------------------- 1 | var slasp = require('slasp'); 2 | var nijs = require('nijs'); 3 | var OutputExpression = require('./OutputExpression.js').OutputExpression; 4 | var Package = require('../Package.js').Package; 5 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 6 | 7 | /** 8 | * Creates a new package expression instance. 9 | * 10 | * @class PackageExpression 11 | * @extends OutputExpression 12 | * @classdesc Represents an expression that contains several jobs for a single package 13 | * 14 | * @constructor 15 | * @param {DeploymentConfig} deploymentConfig An object capturing global deployment settings 16 | * @param {Object} lock Contents of a package lock file (or undefined if no lock exists) 17 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 18 | * @param {String} name Name of a Node.js package 19 | * @param {String} versionSpec Version specifier of a Node.js package, such as 20 | * an exact version number, version range, URL, or GitHub identifier 21 | */ 22 | function PackageExpression(deploymentConfig, lock, baseDir, name, versionSpec) { 23 | OutputExpression.call(this); 24 | this.package = new Package(deploymentConfig, lock, null, name, "./.", baseDir, deploymentConfig.production, this.sourcesCache); 25 | } 26 | 27 | /* PackageExpression inherits from OutputExpression */ 28 | inherit(OutputExpression, PackageExpression); 29 | 30 | /** 31 | * @see OutputExpression#resolveDependencies 32 | */ 33 | PackageExpression.prototype.resolveDependencies = function(callback) { 34 | var self = this; 35 | 36 | slasp.sequence([ 37 | function(callback) { 38 | self.package.source.fetch(callback); 39 | }, 40 | function(callback) { 41 | self.package.resolveDependencies(callback); 42 | } 43 | ], callback); 44 | }; 45 | 46 | /** 47 | * @see NixASTNode#toNixAST 48 | */ 49 | PackageExpression.prototype.toNixAST = function() { 50 | var ast = OutputExpression.prototype.toNixAST.call(this); 51 | 52 | ast.body.value.args = this.package; 53 | ast.body.body = { 54 | args: new nijs.NixExpression("args"), 55 | sources: new nijs.NixExpression("sources"), 56 | tarball: new nijs.NixFunInvocation({ 57 | funExpr: new nijs.NixAttrReference({ 58 | attrSetExpr: new nijs.NixExpression("nodeEnv"), 59 | refExpr: new nijs.NixExpression("buildNodeSourceDist") 60 | }), 61 | paramExpr: new nijs.NixExpression("args") 62 | }), 63 | 64 | package: new nijs.NixFunInvocation({ 65 | funExpr: new nijs.NixAttrReference({ 66 | attrSetExpr: new nijs.NixExpression("nodeEnv"), 67 | refExpr: new nijs.NixExpression("buildNodePackage") 68 | }), 69 | paramExpr: new nijs.NixExpression("args") 70 | }), 71 | 72 | shell: new nijs.NixFunInvocation({ 73 | funExpr: new nijs.NixAttrReference({ 74 | attrSetExpr: new nijs.NixExpression("nodeEnv"), 75 | refExpr: new nijs.NixExpression("buildNodeShell") 76 | }), 77 | paramExpr: new nijs.NixExpression("args") 78 | }), 79 | 80 | nodeDependencies: new nijs.NixFunInvocation({ 81 | funExpr: new nijs.NixAttrReference({ 82 | attrSetExpr: new nijs.NixExpression("nodeEnv"), 83 | refExpr: new nijs.NixExpression("buildNodeDependencies") 84 | }), 85 | paramExpr: new nijs.NixFunInvocation({ 86 | funExpr: new nijs.NixFunInvocation({ 87 | funExpr: new nijs.NixExpression("lib.overrideExisting"), 88 | paramExpr: new nijs.NixExpression("args") 89 | }), 90 | paramExpr: { 91 | src: new nijs.NixFunInvocation({ 92 | funExpr: new nijs.NixExpression("stdenv.mkDerivation"), 93 | paramExpr: { 94 | name: new nijs.NixExpression('args.name + "-package-json"'), 95 | src: new nijs.NixFunInvocation({ 96 | funExpr: new nijs.NixFunInvocation({ 97 | funExpr: new nijs.NixExpression("nix-gitignore.gitignoreSourcePure"), 98 | paramExpr: ["*", "!package.json", "!package-lock.json"] 99 | }), 100 | paramExpr: new nijs.NixExpression("args.src") 101 | }), 102 | dontBuild: true, 103 | installPhase: "mkdir -p $out; cp -r ./* $out;" 104 | } 105 | }) 106 | } 107 | }) 108 | }) 109 | }; 110 | 111 | return ast; 112 | }; 113 | 114 | exports.PackageExpression = PackageExpression; 115 | -------------------------------------------------------------------------------- /lib/node2nix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains utility functions that generate Nix expressions from NPM package specifications 3 | * @module node2nix 4 | */ 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var slasp = require('slasp'); 8 | var nijs = require('nijs'); 9 | 10 | var CollectionExpression = require('./expressions/CollectionExpression.js').CollectionExpression; 11 | var PackageExpression = require('./expressions/PackageExpression.js').PackageExpression; 12 | var CompositionExpression = require('./expressions/CompositionExpression.js').CompositionExpression; 13 | var DeploymentConfig = require('./DeploymentConfig.js').DeploymentConfig; 14 | 15 | function copyNodeEnvExpr(nodeEnvNix, callback) { 16 | /* Compose a read stream that reads the build expression */ 17 | var rs = fs.createReadStream(path.join(path.dirname(module.filename), "..", "nix", "node-env.nix")); 18 | rs.on("error", function(err) { 19 | callback(err); 20 | }); 21 | 22 | /* Compose a write stream that writes the build expression */ 23 | var ws = fs.createWriteStream(nodeEnvNix); 24 | ws.on("error", function(err) { 25 | callback(err); 26 | }); 27 | ws.on("close", function() { 28 | callback(null); 29 | }); 30 | 31 | /* Pipe the data to actually copy stuff */ 32 | rs.pipe(ws); 33 | } 34 | 35 | /** 36 | * Writes a copy of node-env.nix to a specified path. 37 | * 38 | * @function 39 | * @param {String} nodeEnvNix Path to which the NPM package build expression is written 40 | * @param {function(String)} callback Callback function that gets invoked if the operation is done. 41 | * If an error has occured, the error parameter is set to the error message. 42 | */ 43 | exports.copyNodeEnvExpr = copyNodeEnvExpr; 44 | 45 | function npmToNix(inputJSON, outputNix, compositionNix, nodeEnvNix, lockJSON, supplementJSON, supplementNix, production, includePeerDependencies, flatten, nodePackage, registries, noCopyNodeEnv, bypassCache, useFetchGitPrivate, stripOptionalDependencies, callback) { 46 | var obj = JSON.parse(fs.readFileSync(inputJSON)); 47 | var version = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"))).version; 48 | var disclaimer = "# This file has been generated by node2nix " + version + ". Do not edit!\n\n"; 49 | var outputDir = path.dirname(outputNix); 50 | var baseDir = path.dirname(inputJSON); 51 | 52 | var lock; 53 | 54 | if(lockJSON !== undefined) { 55 | lock = JSON.parse(fs.readFileSync(lockJSON)); 56 | } 57 | 58 | var deploymentConfig = new DeploymentConfig(registries, production, includePeerDependencies, flatten, nodePackage, outputDir, bypassCache, stripOptionalDependencies); 59 | var expr; 60 | 61 | var displayLockWarning = false; 62 | 63 | slasp.sequence([ 64 | /* Generate a Nix expression */ 65 | function(callback) { 66 | if(typeof obj == "object" && obj !== null) { 67 | if(Array.isArray(obj)) { 68 | expr = new CollectionExpression(deploymentConfig, baseDir, obj); 69 | } else { 70 | // Display error if mandatory package.json attributes are not set 71 | if(!obj.name) { 72 | return callback("Mandatory name attribute is missing in package.json"); 73 | } 74 | 75 | // Parse package.json 76 | expr = new PackageExpression(deploymentConfig, lock, baseDir, obj.name, baseDir); 77 | 78 | // Display a warning if we expect a lock file to be used, but the user does not specify it 79 | displayLockWarning = bypassCache && !lockJSON && fs.existsSync(path.join(path.dirname(inputJSON), path.basename(inputJSON, ".json")) + "-lock.json"); 80 | } 81 | 82 | expr.resolveDependencies(callback); 83 | } else { 84 | callback("The provided JSON file must consist of an object or an array"); 85 | } 86 | }, 87 | 88 | /* Write the output Nix expression to the specified output file */ 89 | function(callback) { 90 | fs.writeFile(outputNix, disclaimer + nijs.jsToNix(expr, true) + "\n", callback); 91 | }, 92 | 93 | function(callback) { 94 | /* Generate the supplement Nix expression, if specified */ 95 | if(supplementJSON) { 96 | var obj = JSON.parse(fs.readFileSync(supplementJSON)); 97 | 98 | if(Array.isArray(obj)) { 99 | expr = new CollectionExpression(deploymentConfig, baseDir, obj); 100 | expr.resolveDependencies(callback); 101 | } else { 102 | callback("The supplement JSON file should be an array"); 103 | } 104 | } else { 105 | expr = undefined; 106 | callback(); 107 | } 108 | }, 109 | 110 | function(callback) { 111 | if(expr === undefined) { 112 | callback(); 113 | } else { 114 | /* Write the supplement Nix expression to the specified output file */ 115 | fs.writeFile(supplementNix, disclaimer + nijs.jsToNix(expr, true) + "\n", callback); 116 | } 117 | }, 118 | 119 | function(callback) { 120 | if(noCopyNodeEnv) { 121 | callback(); 122 | } else { 123 | /* Copy the node-env.nix expression */ 124 | copyNodeEnvExpr(nodeEnvNix, callback); 125 | } 126 | }, 127 | 128 | /* Generate and write a Nix composition expression to the specified output file */ 129 | function(callback) { 130 | expr = new CompositionExpression(compositionNix, nodePackage, nodeEnvNix, outputNix, supplementNix, (supplementJSON !== undefined), useFetchGitPrivate); 131 | fs.writeFile(compositionNix, disclaimer + nijs.jsToNix(expr, true) + "\n", callback); 132 | }, 133 | 134 | function(callback) { 135 | /* Display warnings that helps the user with some common mistakes */ 136 | if(displayLockWarning) { 137 | console.log("\nWARNING: A lock file exists in the repository, yet it is not used in the generation process!"); 138 | console.log("As a result, the deployment of the project may fail."); 139 | console.log("You probably want to run node2nix with the -l option to use the lock file!"); 140 | } 141 | 142 | if(!Array.isArray(obj) && fs.existsSync(path.join(baseDir, "node_modules"))) { 143 | console.log("\nWARNING: There is a node_modules/ folder in the root directory of the project!"); 144 | console.log("These packages will be included in the Nix build and influence the outcome."); 145 | console.log("If you don't want this to happen, then you should remove it before running any"); 146 | console.log("of the Nix commands!"); 147 | } 148 | callback(); 149 | } 150 | ], callback); 151 | } 152 | 153 | /** 154 | * Generates a Nix expression from a JSON file representing a Node.js package 155 | * configuration or an array of NPM dependencies. 156 | * 157 | * @function 158 | * @param {String} inputJSON Path to a package.json or arbitrary JSON file 159 | * @param {String} outputNix Path to which the generated registry expression is written 160 | * @param {String} compositionNix Path to which the generated composition expression is written 161 | * @param {String} nodeEnvNix Path to which the NPM package build expression is written 162 | * @param {String} supplementJSON Path to a supplement JSON file 163 | * @param {String} supplementNix Path to which the generated supplement expression is written 164 | * @param {Boolean} production Indicates whether to deploy the package in production mode 165 | * @param {Boolean} includePeerDependencies Indicates whether to include peer dependencies with the package 166 | * @param {Boolean} flatten Indicates whether to create a flat dependency structure in which dependencies are as high as possible in the graph 167 | * @param {String} nodePackage Name of the Node.js package to use from Nixpkgs 168 | * @param {Registry[]} registries URL and AuthToken 169 | * @param {Boolean} noCopyNodeEnv Indicates that no copy of the NPM package build expression should be made 170 | * @param {Boolean} bypassCache Indicates that the content addressable cache should be bypassed 171 | * @param {Boolean} useFetchGitPrivate Indicates that the fetchgitPrivate function should be used instead of fetchgit 172 | * @param {Boolean} stripOptionalDependencies When enabled, the optional dependencies are stripped from the regular dependencies in the NPM registry 173 | * @param {function(String, String)} callback Callback function that gets invoked when the work is done. 174 | * If an error occurs, the error parameter is set to contain the error 175 | * If the operation succeeds, it returns a string containing the registry expression containing the packages and all its dependencies 176 | */ 177 | exports.npmToNix = npmToNix; 178 | -------------------------------------------------------------------------------- /lib/sources/GitSource.js: -------------------------------------------------------------------------------- 1 | var url = require('url'); 2 | var path = require('path'); 3 | var child_process = require('child_process'); 4 | var temp = require('temp'); 5 | var slasp = require('slasp'); 6 | var nijs = require('nijs'); 7 | var findit = require('findit'); 8 | var fs = require('fs.extra'); 9 | var Source = require('./Source.js').Source; 10 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 11 | 12 | /** 13 | * Constructs a new GitSource instance. 14 | * 15 | * @class GitSource 16 | * @extends Source 17 | * @classdesc Represents a dependency source that is obtained by cloning a Git repository 18 | * 19 | * @constructor 20 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 21 | * @param {String} dependencyName Name of the dependency 22 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 23 | */ 24 | function GitSource(baseDir, dependencyName, versionSpec) { 25 | Source.call(this, baseDir, dependencyName, versionSpec); 26 | this.rev = ""; 27 | this.hash = ""; 28 | this.identifier = dependencyName + "-" + versionSpec; 29 | this.baseDir = path.join(baseDir, dependencyName); 30 | } 31 | 32 | /* GitSource inherits from Source */ 33 | inherit(Source, GitSource); 34 | 35 | GitSource.composeGitURL = function(baseURL, parsedUrl) { 36 | var hashComponent; 37 | 38 | if(parsedUrl.hash === null) { 39 | hashComponent = ""; 40 | } else { 41 | hashComponent = parsedUrl.hash; 42 | } 43 | 44 | return baseURL + "/" + parsedUrl.host + parsedUrl.path + hashComponent; 45 | }; 46 | 47 | /** 48 | * @see Source#fetch 49 | */ 50 | GitSource.prototype.fetch = function(callback) { 51 | var self = this; 52 | 53 | /* Parse the URL specifier, extract useful bits out of it and rewrite it into a usable git URL */ 54 | var parsedUrl = url.parse(self.versionSpec); 55 | 56 | switch(parsedUrl.protocol) { 57 | case "git+ssh:": 58 | parsedUrl.protocol = "ssh:"; 59 | break; 60 | case "git+http:": 61 | parsedUrl.protocol = "http:"; 62 | break; 63 | case "git+https:": 64 | parsedUrl.protocol = "https:"; 65 | break; 66 | default: 67 | parsedUrl.protocol = "git:"; 68 | break; 69 | } 70 | 71 | /* Compose the commitIsh out of the hash suffix, if applicable */ 72 | var commitIsh; 73 | 74 | if(parsedUrl.hash !== null) { 75 | commitIsh = parsedUrl.hash.substr(1); 76 | } else { 77 | commitIsh = null; 78 | } 79 | 80 | delete parsedUrl.hash; 81 | 82 | /* Compose a Git URL out of the parsed object */ 83 | self.url = parsedUrl.format(); 84 | 85 | /* Compose a metadata object out of the git repository */ 86 | var tmpDir; 87 | var repositoryDir; 88 | 89 | var filesToDelete = []; 90 | var dirsToDelete = []; 91 | 92 | slasp.sequence([ 93 | function(callback) { 94 | /* Create a temp folder */ 95 | temp.mkdir("node2nix-git-checkout-" + self.dependencyName.replace("/", "_slash_"), callback); 96 | }, 97 | 98 | function(callback, dirPath) { 99 | tmpDir = dirPath; 100 | 101 | process.stderr.write("Cloning git repository: "+self.url+"\n"); 102 | 103 | /* Do a git clone */ 104 | var gitClone = child_process.spawn("git", [ "clone", self.url ], { 105 | cwd: tmpDir, 106 | stdio: "inherit" 107 | }); 108 | 109 | gitClone.on("close", function(code) { 110 | if(code == 0) { 111 | callback(null); 112 | } else { 113 | callback("git clone exited with status: "+code); 114 | } 115 | }); 116 | }, 117 | 118 | function(callback) { 119 | /* Search for the main folder in the clone */ 120 | 121 | var finder = findit(tmpDir); 122 | finder.on("directory", function(dir, stat) { 123 | if(dir != tmpDir) { 124 | repositoryDir = dir; 125 | finder.stop(); 126 | } 127 | }); 128 | finder.on("stop", function() { 129 | callback(null); 130 | }); 131 | finder.on("end", function() { 132 | callback("Cannot find a checkout directory in the temp folder"); 133 | }); 134 | finder.on("error", function(err) { 135 | callback(err); 136 | }); 137 | }, 138 | 139 | function(callback) { 140 | /* When no commitIsh has been provide, parse the revision of HEAD */ 141 | var branch; 142 | 143 | if(commitIsh === null) { 144 | branch = "HEAD"; 145 | } else { 146 | branch = commitIsh; 147 | } 148 | 149 | process.stderr.write("Parsing the revision of commitish: "+branch+"\n"); 150 | 151 | /* Check whether the given commitish corresponds to a hash */ 152 | var gitRevParse = child_process.spawn("git", [ "rev-parse", branch ], { 153 | cwd: repositoryDir 154 | }); 155 | 156 | gitRevParse.stdout.on("data", function(data) { 157 | self.rev += data; 158 | }); 159 | gitRevParse.stderr.on("data", function(data) { 160 | process.stderr.write(data); 161 | }); 162 | gitRevParse.on("close", function(code) { 163 | if(code != 0) 164 | self.rev = ""; // If git rev-parse fails, we consider the commitIsh a branch/tag. 165 | 166 | callback(null); 167 | }); 168 | }, 169 | 170 | function(callback) { 171 | if(commitIsh !== null && self.rev == "") { 172 | /* If we were still unable to parse a revision, we try to parse the revision of the origin's branch */ 173 | process.stderr.write("Parsing the revision of commitish: origin/"+commitIsh+"\n"); 174 | 175 | /* Resolve the hash of the branch/tag */ 176 | var gitRevParse = child_process.spawn("git", [ "rev-parse", "origin/" + commitIsh ], { 177 | cwd: repositoryDir 178 | }); 179 | 180 | gitRevParse.stdout.on("data", function(data) { 181 | self.rev += data; 182 | }); 183 | gitRevParse.stderr.on("data", function(data) { 184 | process.stderr.write(data); 185 | }); 186 | gitRevParse.on("close", function(code) { 187 | if(code != 0) 188 | self.rev = ""; // If git rev-parse fails, we consider the commitIsh a pull request branch. 189 | callback(null); 190 | }); 191 | } else { 192 | callback(null); 193 | } 194 | }, 195 | 196 | function(callback) { 197 | /* Try to parse a commitIsh pull request branch, e.g. "pull/4779/head" */ 198 | if(commitIsh !== null && self.rev == "") { 199 | if (commitIsh.match(/^pull\/[0-9]+\/head$/) !== null) { 200 | /* Create a local branch for the pull request. */ 201 | var gitFetch = child_process.spawn("git", [ "fetch", "origin", `${commitIsh}:node2nix_pull_request_branch` ], { 202 | cwd: repositoryDir 203 | }); 204 | gitFetch.stderr.on("data", function(data) { 205 | process.stderr.write(data); 206 | }); 207 | gitFetch.on("close", function(code) { 208 | /* Resolve the hash */ 209 | var gitRevParse = child_process.spawn("git", [ "rev-parse", "node2nix_pull_request_branch" ], { 210 | cwd: repositoryDir 211 | }); 212 | 213 | gitRevParse.stdout.on("data", function(data) { 214 | self.rev += data; 215 | }); 216 | gitRevParse.stderr.on("data", function(data) { 217 | process.stderr.write(data); 218 | }); 219 | gitRevParse.on("close", function(code) { 220 | if (code == 0) { 221 | callback(null); 222 | } else { 223 | callback("Cannot find the corresponding revision of: "+commitIsh); 224 | } 225 | }); 226 | }); 227 | } else { 228 | callback("Cannot find the corresponding revision of: "+commitIsh); 229 | } 230 | } else { 231 | callback(null); 232 | } 233 | }, 234 | 235 | function(callback) { 236 | if(self.rev == "") { 237 | callback(null); 238 | } else { 239 | /* When we have resolved a revision, do a checkout of it */ 240 | self.rev = self.rev.substr(0, self.rev.length - 1); 241 | 242 | process.stderr.write("Checking out revision: "+self.rev+"\n"); 243 | 244 | /* Check out the corresponding revision */ 245 | var gitCheckout = child_process.spawn("git", [ "checkout", self.rev ], { 246 | cwd: repositoryDir, 247 | stdio: "inherit" 248 | }); 249 | 250 | gitCheckout.on("close", function(code) { 251 | if(code == 0) { 252 | callback(null); 253 | } else { 254 | callback("git checkout exited with status: "+code); 255 | } 256 | }); 257 | } 258 | }, 259 | 260 | function(callback) { 261 | /* Initialize all sub modules */ 262 | process.stderr.write("Initializing git sub modules\n"); 263 | 264 | var gitSubmoduleUpdate = child_process.spawn("git", [ "submodule", "update", "--init", "--recursive" ], { 265 | cwd: repositoryDir, 266 | stdio: "inherit" 267 | }); 268 | 269 | gitSubmoduleUpdate.on("close", function(code) { 270 | if(code == 0) { 271 | callback(null); 272 | } else { 273 | callback("git submodule exited with status: "+code); 274 | } 275 | }); 276 | }, 277 | 278 | function(callback) { 279 | fs.readFile(path.join(repositoryDir, "package.json"), callback); 280 | }, 281 | 282 | function(callback, packageJSON) { 283 | self.config = JSON.parse(packageJSON); 284 | 285 | /* Search for .git directories and files to prune out of the checkout */ 286 | var finder = findit(repositoryDir); 287 | 288 | finder.on("directory", function(dir, stat) { 289 | var base = path.basename(dir); 290 | if(base == ".git") { 291 | dirsToDelete.push(dir); 292 | } 293 | }); 294 | finder.on("file", function(file, stat) { 295 | var base = path.basename(file); 296 | if(base == ".git") { 297 | filesToDelete.push(file); 298 | } 299 | }); 300 | finder.on("end", function() { 301 | callback(null); 302 | }); 303 | finder.on("error", function(err) { 304 | callback(err); 305 | }); 306 | }, 307 | 308 | function(callback) { 309 | /* Delete files that are prefixed with .git */ 310 | var i; 311 | 312 | slasp.from(function(callback) { 313 | i = 0; 314 | callback(null); 315 | }, function(callback) { 316 | callback(null, i < filesToDelete.length); 317 | }, function(callback) { 318 | i++; 319 | callback(null); 320 | }, function(callback) { 321 | fs.unlink(filesToDelete[i], callback); 322 | }, callback); 323 | }, 324 | 325 | function(callback) { 326 | /* Delete directories that are prefixed with .git */ 327 | var i; 328 | 329 | slasp.from(function(callback) { 330 | i = 0; 331 | callback(null); 332 | }, function(callback) { 333 | callback(null, i < dirsToDelete.length); 334 | }, function(callback) { 335 | i++; 336 | callback(null); 337 | }, function(callback) { 338 | fs.rmrf(dirsToDelete[i], callback); 339 | }, callback); 340 | }, 341 | 342 | function(callback) { 343 | /* Compute the SHA256 of the checkout */ 344 | 345 | var nixHash = child_process.spawn("nix-hash", [ "--type", "sha256", repositoryDir ]); 346 | 347 | nixHash.stdout.on("data", function(data) { 348 | self.hash += data; 349 | }); 350 | nixHash.stderr.on("data", function(data) { 351 | process.stderr.write(data); 352 | }); 353 | nixHash.on("close", function(code) { 354 | if(code == 0) { 355 | callback(null); 356 | } else { 357 | callback("nix-hash exited with status: "+code); 358 | } 359 | }); 360 | } 361 | ], function(err) { 362 | if(tmpDir !== undefined) { // Remove the temp folder 363 | fs.rmrfSync(tmpDir); 364 | } 365 | 366 | callback(err); 367 | }); 368 | }; 369 | 370 | /** 371 | * @see Source#convertFromLockedDependency 372 | */ 373 | GitSource.prototype.convertFromLockedDependency = function(dependencyObj, callback) { 374 | this.fetch(callback); // A locked git dependency object does not provide any integrity metadata. We have to download and compute the hash ourselves. 375 | }; 376 | 377 | /** 378 | * @see NixASTNode#toNixAST 379 | */ 380 | GitSource.prototype.toNixAST = function() { 381 | var ast = Source.prototype.toNixAST.call(this); 382 | 383 | ast.src = new nijs.NixFunInvocation({ 384 | funExpr: new nijs.NixExpression("fetchgit"), 385 | paramExpr: { 386 | url: this.url, 387 | rev: this.rev, 388 | sha256: this.hash.substr(0, this.hash.length - 1) 389 | } 390 | }); 391 | 392 | return ast; 393 | }; 394 | 395 | exports.GitSource = GitSource; 396 | -------------------------------------------------------------------------------- /lib/sources/HTTPSource.js: -------------------------------------------------------------------------------- 1 | var url = require('url'); 2 | var path = require('path'); 3 | var crypto = require('crypto'); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var zlib = require('zlib'); 7 | var tar = require('tar'); 8 | var nijs = require('nijs'); 9 | var Source = require('./Source.js').Source; 10 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 11 | 12 | /** 13 | * Constructs a new HTTPSource instance. 14 | * 15 | * @class HTTPSource 16 | * @extends Source 17 | * @classdesc Represents a dependency source that is obtained by fetching a file from an external HTTP site 18 | * 19 | * @constructor 20 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 21 | * @param {String} dependencyName Name of the dependency 22 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 23 | */ 24 | function HTTPSource(baseDir, dependencyName, versionSpec) { 25 | Source.call(this, baseDir, dependencyName, versionSpec); 26 | this.identifier = dependencyName + "-" + versionSpec; 27 | this.baseDir = path.join(baseDir, dependencyName); 28 | } 29 | 30 | /* HTTPSource inherits from Source */ 31 | inherit(Source, HTTPSource); 32 | 33 | /** 34 | * @see Source#fetch 35 | */ 36 | HTTPSource.prototype.fetch = function(callback) { 37 | var self = this; 38 | 39 | /* Determine which client to use depending on the parsed protocol */ 40 | var parsedUrl = url.parse(self.versionSpec); 41 | var client; 42 | 43 | switch(parsedUrl.protocol) { 44 | case "http:": 45 | client = http; 46 | break; 47 | case "https:": 48 | client = https; 49 | break; 50 | default: 51 | return callback("Unsupported protocol: "+parsedUrl.protocol); 52 | } 53 | 54 | /* Request the package from the given URL */ 55 | 56 | var request = client.get(parsedUrl.href, function(res) { 57 | if(res.statusCode >= 300 && res.statusCode <= 308) { // If a redirect has been encountered => do the same operation with the target URL 58 | if(!res.headers.location) { 59 | callback("Bad HTTP response while GETting "+parsedUrl.href+" Redirect with no Location header"); 60 | } else { 61 | self.versionSpec = res.headers.location; 62 | self.fetch(callback); 63 | } 64 | } else { // Otherwise extract the package.json and compute the corresponding hash 65 | self.url = parsedUrl.href; 66 | process.stderr.write("fetching: "+self.url+"\n"); 67 | 68 | var gunzip = zlib.createGunzip(); 69 | gunzip.on("error", function(err) { 70 | callback("Error while gunzipping: "+err); 71 | }); 72 | 73 | var tarParser = new tar.Parse(); 74 | 75 | tarParser.on("error", function(err) { 76 | callback("Error while untarring: "+err); 77 | }); 78 | tarParser.on("entry", function(entry) { 79 | if(entry.path.match(/^[^/]*\/package\.json$/)) { // Search for a file named package.json in the tar file 80 | var packageJSON = ""; 81 | 82 | entry.on("data", function(chunk) { 83 | packageJSON += chunk; 84 | }); 85 | 86 | entry.on("end", function() { 87 | self.config = JSON.parse(packageJSON); 88 | }); 89 | } else { 90 | // For other files, simply skip them. We need these dummy callbacks because there is some kind of quirk in the API that terminates the program. 91 | entry.on("data", function() {}); 92 | entry.on("end", function() {}); 93 | } 94 | }); 95 | 96 | var computeHash = crypto.createHash('sha256'); 97 | 98 | /* Pipe gunzipped data to the tar parser */ 99 | gunzip.pipe(tarParser).on("finish", function() { 100 | callback(); // Everything finished 101 | }); 102 | 103 | res.on("data", function(chunk) { 104 | /* Retrieve data from the HTTP connection and feed it to the gunzip and hash streams */ 105 | gunzip.write(chunk); 106 | computeHash.update(chunk); 107 | }); 108 | res.on("end", function() { 109 | gunzip.end(); 110 | 111 | self.hashType = "sha256"; 112 | self.sha256 = computeHash.digest('hex'); 113 | }); 114 | res.on("error", function(err) { 115 | callback("Error with retrieving file from HTTP connection: "+err); 116 | }); 117 | } 118 | }); 119 | request.on("error", function(err) { 120 | callback("Error while GETting "+self.url+": "+err); 121 | }); 122 | }; 123 | 124 | /** 125 | * @see Source#convertFromLockedDependency 126 | */ 127 | HTTPSource.prototype.convertFromLockedDependency = function(dependencyObj, callback) { 128 | this.config = { 129 | name: this.dependencyName, 130 | version: 1 131 | }; 132 | 133 | this.url = dependencyObj.version; 134 | try { 135 | this.convertIntegrityStringToNixHash(dependencyObj.integrity); 136 | callback(); 137 | } catch(err) { 138 | callback(err); 139 | } 140 | }; 141 | 142 | /** 143 | * @see NixASTNode#toNixAST 144 | */ 145 | HTTPSource.prototype.toNixAST = function() { 146 | var ast = Source.prototype.toNixAST.call(this); 147 | 148 | var paramExpr = { 149 | name: path.basename(this.config.name) + "-" + this.config.version + ".tar.gz", 150 | url: this.url 151 | }; 152 | 153 | switch(this.hashType) { 154 | case "sha256": 155 | paramExpr["sha256"] = this.sha256; 156 | break; 157 | case "sha512": 158 | paramExpr["sha512"] = this.sha512; 159 | break; 160 | case "sha1": 161 | paramExpr["sha1"] = this.sha1; 162 | break; 163 | default: 164 | throw "Unknown hash type: "+this.hashType; 165 | }; 166 | 167 | ast.src = new nijs.NixFunInvocation({ 168 | funExpr: new nijs.NixExpression("fetchurl"), 169 | paramExpr: paramExpr 170 | }); 171 | 172 | return ast; 173 | }; 174 | 175 | exports.HTTPSource = HTTPSource; 176 | -------------------------------------------------------------------------------- /lib/sources/LocalSource.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var slasp = require('slasp'); 4 | var nijs = require('nijs'); 5 | var Source = require('./Source.js').Source; 6 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 7 | 8 | /** 9 | * Constructs a new LocalSource instance. 10 | * 11 | * @class LocalSource 12 | * @extends Source 13 | * @classdesc Represents a dependency source that is obtained from a directory on the local filesystem 14 | * 15 | * @constructor 16 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 17 | * @param {String} dependencyName Name of the dependency 18 | * @param {String} outputDir Directory in which the nix expression will be written 19 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 20 | */ 21 | function LocalSource(baseDir, dependencyName, outputDir, versionSpec) { 22 | Source.call(this, baseDir, dependencyName, versionSpec); 23 | this.outputDir = outputDir; 24 | } 25 | 26 | /* LocalSource inherits from Source */ 27 | inherit(Source, LocalSource); 28 | 29 | LocalSource.prototype.composeSourcePath = function(resolvedPath) { 30 | var srcPath; 31 | 32 | // Strip of file: prefix if necessary 33 | if(resolvedPath.substr(0, 5) === "file:") { 34 | srcPath = resolvedPath.substr(5); 35 | } else { 36 | srcPath = resolvedPath; 37 | } 38 | 39 | var first = this.versionSpec.substr(0, 1); 40 | 41 | if(first === '~' || first === '/') { // Path is absolute 42 | return this.versionSpec; 43 | } else { 44 | // Compose path relative to the output directory 45 | var absoluteOutputDir = path.resolve(this.outputDir); 46 | var absoluteSrcPath = path.resolve(absoluteOutputDir, srcPath); 47 | srcPath = path.relative(absoluteOutputDir, absoluteSrcPath); 48 | 49 | if(srcPath.substr(0, 1) !== ".") { 50 | srcPath = "./"+srcPath; // If a path does not start with a . prefix it, so that it is a valid path in the Nix language 51 | } 52 | 53 | return srcPath; 54 | } 55 | }; 56 | 57 | /** 58 | * @see Source#fetch 59 | */ 60 | LocalSource.prototype.fetch = function(callback) { 61 | var self = this; 62 | 63 | process.stderr.write("fetching local directory: " + self.versionSpec + " from " + self.baseDir +"\n"); 64 | 65 | var resolvedPath = path.resolve(self.baseDir, self.versionSpec); 66 | self.srcPath = self.composeSourcePath(resolvedPath); 67 | 68 | slasp.sequence([ 69 | function(callback) { 70 | fs.readFile(path.join(resolvedPath, "package.json"), callback); 71 | }, 72 | 73 | function(callback, packageJSON) { 74 | self.config = JSON.parse(packageJSON); 75 | self.identifier = self.config.name + "-" + self.versionSpec; 76 | self.baseDir = resolvedPath; 77 | callback(null); 78 | } 79 | ], callback); 80 | }; 81 | 82 | /** 83 | * @see Source#convertFromLockedDependency 84 | */ 85 | LocalSource.prototype.convertFromLockedDependency = function(dependencyObj, callback) { 86 | this.srcPath = this.composeSourcePath(dependencyObj.version); 87 | this.config = { 88 | name: this.dependencyName, 89 | version: 1 90 | }; 91 | this.identifier = this.dependencyName + "-" + this.versionSpec; 92 | callback(); 93 | }; 94 | 95 | /** 96 | * @see NixASTNode#toNixAST 97 | */ 98 | LocalSource.prototype.toNixAST = function() { 99 | var ast = Source.prototype.toNixAST.call(this); 100 | 101 | if(this.srcPath === "./") { 102 | ast.src = new nijs.NixFile({ value: "./." }); // ./ is not valid in the Nix expression language 103 | } else if(this.srcPath === "..") { 104 | ast.src = new nijs.NixFile({ value: "./.." }); // .. is not valid in the Nix expression language 105 | } else { 106 | ast.src = new nijs.NixFile({ value: this.srcPath }); 107 | } 108 | 109 | return ast; 110 | }; 111 | 112 | exports.LocalSource = LocalSource; 113 | -------------------------------------------------------------------------------- /lib/sources/NPMRegistrySource.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var slasp = require('slasp'); 3 | var semver = require('semver'); 4 | var npmconf = require('npmconf'); 5 | var nijs = require('nijs'); 6 | var RegClient = require('npm-registry-client'); 7 | var Source = require('./Source.js').Source; 8 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 9 | 10 | var client; 11 | 12 | /* Initialize client on first startup or return the existing one */ 13 | 14 | function initClient(callback) { 15 | if(client === undefined) { 16 | slasp.sequence([ 17 | function(callback) { 18 | /* Load NPM's configuration */ 19 | npmconf.load(callback); 20 | }, 21 | 22 | function(callback, config) { 23 | client = new RegClient(config); 24 | callback(null, client); 25 | } 26 | ], callback) 27 | } else { 28 | callback(null, client); 29 | } 30 | } 31 | 32 | /** 33 | * Constructs a new NPMRegistrySource instance. 34 | * 35 | * @class NPMRegistrySource 36 | * @extends Source 37 | * @classdesc Represents a dependency source that is obtained from the metadata of a package in the NPM registry 38 | * 39 | * @constructor 40 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 41 | * @param {String} dependencyName Name of the dependency 42 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 43 | * @param {Array} registries An array of alternative registries for scoped packages 44 | * @param {Boolean} stripOptionalDependencies When enabled, the optional dependencies are stripped from the regular dependencies 45 | */ 46 | function NPMRegistrySource(baseDir, dependencyName, versionSpec, registries, stripOptionalDependencies) { 47 | Source.call(this, baseDir, dependencyName, versionSpec); 48 | this.registries = registries; 49 | this.baseDir = path.join(baseDir, dependencyName); 50 | this.stripOptionalDependencies = stripOptionalDependencies; 51 | } 52 | 53 | /* NPMRegistrySource inherits from Source */ 54 | inherit(Source, NPMRegistrySource); 55 | 56 | /** 57 | * @see Source#fetch 58 | */ 59 | NPMRegistrySource.prototype.fetch = function(callback) { 60 | var self = this; 61 | 62 | if(self.versionSpec == "") // An empty versionSpec translates to * 63 | self.versionSpec = "*"; 64 | 65 | slasp.sequence([ 66 | initClient, 67 | 68 | function(callback, client) { 69 | /* Resolve npm: dependency specifier */ 70 | var npmProtocolPath; 71 | 72 | if(self.versionSpec.startsWith("npm:")) { 73 | var npmProtocolVersion = self.versionSpec.replace(/npm:@?/i, "").match(/@(.*)/i); 74 | npmProtocolPath = self.versionSpec.replace("npm:", ""); 75 | 76 | if (Array.isArray(npmProtocolVersion) && npmProtocolVersion.length > 1) { 77 | self.versionSpec = npmProtocolVersion[1]; 78 | npmProtocolPath = npmProtocolPath.replace(npmProtocolVersion[0], ""); 79 | } 80 | self.npmProtocolPath = npmProtocolPath; 81 | } 82 | 83 | /* For a scoped package, determine from which registry it needs to be obtained */ 84 | var selectedRegistry = self.registries[0]; 85 | 86 | if(self.dependencyName.startsWith("@")) { 87 | var scope = self.dependencyName.slice(0, self.dependencyName.indexOf("/")); 88 | var found = self.registries.find(function(registry) { 89 | return registry.scope == scope; 90 | }); 91 | 92 | if(found !== undefined) { 93 | selectedRegistry = found; 94 | } 95 | } 96 | 97 | /* Fetch package.json from the registry using the dependency name and version specification */ 98 | var dependencyName_ = npmProtocolPath ? npmProtocolPath : self.dependencyName; 99 | var url = selectedRegistry.url + "/" + dependencyName_.replace("/", "%2F"); // Escape / to make scoped packages work 100 | 101 | var clientParams = {}; 102 | if (selectedRegistry.authToken) { 103 | clientParams.auth = { 104 | token: selectedRegistry.authToken, 105 | alwaysAuth: true, 106 | }; 107 | } 108 | 109 | client.get(url, clientParams, function(err, data, raw, res) { 110 | if(err) { 111 | callback(err); 112 | } else if(data == undefined || data.versions === undefined) { 113 | callback("Error fetching package: " + self.dependencyName + " from NPM registry!"); 114 | } else { 115 | callback(null, data); 116 | } 117 | }); 118 | }, 119 | 120 | function(callback, result) { 121 | /* Fetch the right version (and corresponding metadata) from the versions object */ 122 | var versionIdentifiers = Object.keys(result.versions); 123 | var version; 124 | 125 | if(semver.validRange(self.versionSpec, true) === null) { // If the version specifier is not a valid semver range, we consider it a tag which we need to resolve to a version 126 | version = result['dist-tags'][self.versionSpec]; 127 | } else { 128 | version = self.versionSpec; 129 | } 130 | 131 | // Take the right version's metadata from the versions object 132 | var resolvedVersion = semver.maxSatisfying(versionIdentifiers, version, true); 133 | 134 | if(resolvedVersion === null) { 135 | callback("Cannot resolve version: "+ self.dependencyName + "@" + version); 136 | } else { 137 | self.config = result.versions[resolvedVersion]; 138 | self.config.name = self.npmProtocolPath ? self.dependencyName : self.config.name; 139 | self.identifier = self.config.name + "-" + self.config.version; 140 | 141 | if(self.stripOptionalDependencies && self.config.optionalDependencies !== undefined && self.config.dependencies !== undefined) { 142 | /* 143 | * The NPM registry has a weird oddity -- if a package has 144 | * optionalDependencies, then these dependencies are added 145 | * to the regular dependencies as well. We must deduct them 146 | * so that we only work with the mandatory dependencies. 147 | * Otherwise, certain builds may fail, because optional 148 | * dependencies can be broken. 149 | * 150 | * I'm actually quite curious to learn about the rationale 151 | * of this from the NPM developers. 152 | */ 153 | 154 | for(var dependencyName in self.config.optionalDependencies) { 155 | delete self.config.dependencies[dependencyName]; 156 | } 157 | } 158 | 159 | // Determine the output hash. If the package provides an integrity string, use it to compose a hash. Otherwise fall back to sha1 160 | 161 | if(self.config.dist.integrity !== undefined) { 162 | try { 163 | self.convertIntegrityStringToNixHash(self.config.dist.integrity); 164 | callback(); 165 | } catch(err) { 166 | callback(err); 167 | } 168 | } else { 169 | self.hashType = "sha1"; 170 | self.sha1 = self.config.dist.shasum; // SHA1 hashes are in hexadecimal notation which we can just adopt verbatim 171 | callback(); 172 | } 173 | } 174 | } 175 | ], callback); 176 | }; 177 | 178 | /** 179 | * @see Source#convertFromLockedDependency 180 | */ 181 | NPMRegistrySource.prototype.convertFromLockedDependency = function(dependencyObj, callback) { 182 | this.config = { 183 | name: this.dependencyName, 184 | version: dependencyObj.version, 185 | dist: { 186 | tarball: dependencyObj.resolved 187 | } 188 | }; 189 | this.identifier = this.dependencyName + "-" + dependencyObj.version; 190 | try { 191 | this.convertIntegrityStringToNixHash(dependencyObj.integrity); 192 | callback(); 193 | } catch(err) { 194 | callback(err); 195 | } 196 | }; 197 | 198 | /** 199 | * @see NixASTNode#toNixAST 200 | */ 201 | NPMRegistrySource.prototype.toNixAST = function() { 202 | var ast = Source.prototype.toNixAST.call(this); 203 | 204 | var paramExpr = { 205 | url: this.config.dist.tarball 206 | }; 207 | 208 | switch(this.hashType) { 209 | case "sha1": 210 | paramExpr.sha1 = this.sha1; 211 | break; 212 | case "sha512": 213 | paramExpr.sha512 = this.sha512; 214 | break; 215 | } 216 | 217 | ast.src = new nijs.NixFunInvocation({ 218 | funExpr: new nijs.NixExpression("fetchurl"), 219 | paramExpr: paramExpr 220 | }); 221 | 222 | return ast; 223 | }; 224 | 225 | exports.NPMRegistrySource = NPMRegistrySource; 226 | -------------------------------------------------------------------------------- /lib/sources/Source.js: -------------------------------------------------------------------------------- 1 | var url = require('url'); 2 | var semver = require('semver'); 3 | var nijs = require('nijs'); 4 | var inherit = require('nijs/lib/ast/util/inherit.js').inherit; 5 | var base64js = require('base64-js'); 6 | 7 | /** 8 | * Creates a new source instance. This function should never be used directly, 9 | * instead use: Source.constructSource to construct a source object. 10 | * 11 | * @class Source 12 | * @extends NixASTNode 13 | * @classdesc Represents a file that is obtained from an external source 14 | * 15 | * @constructor 16 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 17 | * @param {String} dependencyName Name of the dependency 18 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 19 | */ 20 | function Source(baseDir, dependencyName, versionSpec) { 21 | this.baseDir = baseDir; 22 | this.dependencyName = dependencyName; 23 | this.versionSpec = versionSpec; 24 | } 25 | 26 | /* Source inherits from NixASTNode */ 27 | inherit(nijs.NixASTNode, Source); 28 | 29 | /** 30 | * Constructs a specific kind of source by inspecting the version specifier. 31 | * 32 | * @param {Registry[]} registries 33 | * @param {String} baseDir Directory in which the referrer's package.json configuration resides 34 | * @param {String} outputDir Directory in which the nix expression will be written 35 | * @param {String} dependencyName Name of the dependency 36 | * @param {String} versionSpec Version specifier of the Node.js package to fetch 37 | * @param {Boolean} stripOptionalDependencies When enabled, the optional 38 | * dependencies are stripped from the regular dependencies in the NPM registry 39 | */ 40 | Source.constructSource = function(registries, baseDir, outputDir, dependencyName, versionSpec, stripOptionalDependencies) { 41 | // Load modules here, to prevent cycles in the include process 42 | var GitSource = require('./GitSource.js').GitSource; 43 | var HTTPSource = require('./HTTPSource.js').HTTPSource; 44 | var LocalSource = require('./LocalSource.js').LocalSource; 45 | var NPMRegistrySource = require('./NPMRegistrySource.js').NPMRegistrySource; 46 | 47 | var parsedVersionSpec = semver.validRange(versionSpec, true); 48 | var parsedUrl = url.parse(versionSpec); 49 | 50 | if(parsedVersionSpec !== null) { // If the version is valid semver range, fetch the package from the NPM registry 51 | return new NPMRegistrySource(baseDir, dependencyName, parsedVersionSpec, registries, stripOptionalDependencies); 52 | } else if(parsedUrl.protocol == "github:") { // If the version is a GitHub repository, compose the corresponding Git URL and do a Git checkout 53 | return new GitSource(baseDir, dependencyName, GitSource.composeGitURL("git+https://github.com", parsedUrl)); 54 | } else if(parsedUrl.protocol == "gist:") { // If the version is a GitHub gist repository, compose the corresponding Git URL and do a Git checkout 55 | return new GitSource(baseDir, dependencyName, GitSource.composeGitURL("git+https://gist.github.com", parsedUrl)); 56 | } else if(parsedUrl.protocol == "bitbucket:") { // If the version is a Bitbucket repository, compose the corresponding Git URL and do a Git checkout 57 | return new GitSource(baseDir, dependencyName, GitSource.composeGitURL("git+https://bitbucket.org", parsedUrl)); 58 | } else if(parsedUrl.protocol == "gitlab:") { // If the version is a Gitlab repository, compose the corresponding Git URL and do a Git checkout 59 | return new GitSource(baseDir, dependencyName, GitSource.composeGitURL("git+https://gitlab.com", parsedUrl)); 60 | } else if(typeof parsedUrl.protocol == "string" && parsedUrl.protocol.substr(0, 3) == "git") { // If the version is a Git URL do a Git checkout 61 | return new GitSource(baseDir, dependencyName, versionSpec); 62 | } else if(parsedUrl.protocol == "http:" || parsedUrl.protocol == "https:") { // If the version is an HTTP URL do a download 63 | return new HTTPSource(baseDir, dependencyName, versionSpec); 64 | } else if(versionSpec.match(/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9\.]+([#[a-zA-Z0-9_\-]|pull\/[0-9]+\/[a-zA-Z0-9\.]+[#[a-zA-Z0-9_\-]+)+]?$/)) { // If the version is a GitHub repository, compose the corresponding Git URL and do a Git checkout 65 | return new GitSource(baseDir, dependencyName, "git+https://github.com/"+versionSpec); 66 | } else if(parsedUrl.protocol == "file:") { // If the version is a file URL, simply compose a Nix path 67 | return new LocalSource(baseDir, dependencyName, outputDir, parsedUrl.path); 68 | } else if(versionSpec.substr(0, 3) == "../" || versionSpec.substr(0, 2) == "~/" || versionSpec.substr(0, 2) == "./" || versionSpec.substr(0, 1) == "/") { // If the version is a path, simply compose a Nix path 69 | return new LocalSource(baseDir, dependencyName, outputDir, versionSpec); 70 | } else { // In all other cases, just try the registry. Sometimes invalid semver ranges are encountered or a tag has been provided (e.g. 'latest', 'unstable') 71 | return new NPMRegistrySource(baseDir, dependencyName, versionSpec, registries, stripOptionalDependencies); 72 | } 73 | }; 74 | 75 | /** 76 | * Fetches the package metadata from the external source. 77 | * 78 | * @method 79 | * @param {function(String)} callback Callback that gets invoked when the work 80 | * is done. In case of an error, it will set the first parameter to contain 81 | * an error message. 82 | */ 83 | Source.prototype.fetch = function(callback) { 84 | callback("fetch() is not implemented, please use a prototype that inherits from Source"); 85 | }; 86 | 87 | /** 88 | * Takes a dependency object from a lock file and converts it into a source object. 89 | * 90 | * @param {Object} dependencyObj Dependency object from the lock file 91 | * @param {function(String)} callback Callback that gets invoked when the work 92 | * is done. In case of an error, it will set the first parameter to contain 93 | * an error message. 94 | */ 95 | Source.prototype.convertFromLockedDependency = function(dependencyObj, callback) { 96 | callback("convertFromLockedDependency() is not implemented, please use a prototype that inherits from Source"); 97 | }; 98 | 99 | /** 100 | * Converts NPM's integrity strings that have the following format: 101 | * - to a hash representation that Nix understands. 102 | * 103 | * @method 104 | * @param {String} integrity NPM integrity string 105 | */ 106 | Source.prototype.convertIntegrityStringToNixHash = function(integrity) { 107 | if(integrity.substr(0, 5) === "sha1-") { 108 | var hash = base64js.toByteArray(integrity.substring(5)); 109 | this.hashType = "sha1"; 110 | this.sha1 = Buffer.from(hash).toString('hex'); 111 | } else if(integrity.substr(0, 7) === "sha512-") { 112 | this.hashType = "sha512"; 113 | this.sha512 = integrity.substring(7); 114 | } else { 115 | throw "Unknown integrity string: "+integrity; 116 | } 117 | }; 118 | 119 | /** 120 | * @see NixASTNode#toNixAST 121 | */ 122 | Source.prototype.toNixAST = function() { 123 | return { 124 | name: this.config.name.replace("@", "_at_").replace("/", "_slash_"), // Escape characters from scoped package names that aren't allowed 125 | packageName: this.config.name, 126 | version: this.config.version 127 | }; 128 | }; 129 | 130 | exports.Source = Source; 131 | -------------------------------------------------------------------------------- /nix/node-env.nix: -------------------------------------------------------------------------------- 1 | # This file originates from node2nix 2 | 3 | {lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript}: 4 | 5 | let 6 | # Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master 7 | utillinux = if pkgs ? utillinux then pkgs.utillinux else pkgs.util-linux; 8 | 9 | python = if nodejs ? python then nodejs.python else python2; 10 | 11 | # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise 12 | tarWrapper = runCommand "tarWrapper" {} '' 13 | mkdir -p $out/bin 14 | 15 | cat > $out/bin/tar <> $out/nix-support/hydra-build-products 40 | ''; 41 | }; 42 | 43 | # Common shell logic 44 | installPackage = writeShellScript "install-package" '' 45 | installPackage() { 46 | local packageName=$1 src=$2 47 | 48 | local strippedName 49 | 50 | local DIR=$PWD 51 | cd $TMPDIR 52 | 53 | unpackFile $src 54 | 55 | # Make the base dir in which the target dependency resides first 56 | mkdir -p "$(dirname "$DIR/$packageName")" 57 | 58 | if [ -f "$src" ] 59 | then 60 | # Figure out what directory has been unpacked 61 | packageDir="$(find . -maxdepth 1 -type d | tail -1)" 62 | 63 | # Restore write permissions to make building work 64 | find "$packageDir" -type d -exec chmod u+x {} \; 65 | chmod -R u+w "$packageDir" 66 | 67 | # Move the extracted tarball into the output folder 68 | mv "$packageDir" "$DIR/$packageName" 69 | elif [ -d "$src" ] 70 | then 71 | # Get a stripped name (without hash) of the source directory. 72 | # On old nixpkgs it's already set internally. 73 | if [ -z "$strippedName" ] 74 | then 75 | strippedName="$(stripHash $src)" 76 | fi 77 | 78 | # Restore write permissions to make building work 79 | chmod -R u+w "$strippedName" 80 | 81 | # Move the extracted directory into the output folder 82 | mv "$strippedName" "$DIR/$packageName" 83 | fi 84 | 85 | # Change to the package directory to install dependencies 86 | cd "$DIR/$packageName" 87 | } 88 | ''; 89 | 90 | # Bundle the dependencies of the package 91 | # 92 | # Only include dependencies if they don't exist. They may also be bundled in the package. 93 | includeDependencies = {dependencies}: 94 | lib.optionalString (dependencies != []) ( 95 | '' 96 | mkdir -p node_modules 97 | cd node_modules 98 | '' 99 | + (lib.concatMapStrings (dependency: 100 | '' 101 | if [ ! -e "${dependency.packageName}" ]; then 102 | ${composePackage dependency} 103 | fi 104 | '' 105 | ) dependencies) 106 | + '' 107 | cd .. 108 | '' 109 | ); 110 | 111 | # Recursively composes the dependencies of a package 112 | composePackage = { name, packageName, src, dependencies ? [], ... }@args: 113 | builtins.addErrorContext "while evaluating node package '${packageName}'" '' 114 | installPackage "${packageName}" "${src}" 115 | ${includeDependencies { inherit dependencies; }} 116 | cd .. 117 | ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} 118 | ''; 119 | 120 | pinpointDependencies = {dependencies, production}: 121 | let 122 | pinpointDependenciesFromPackageJSON = writeTextFile { 123 | name = "pinpointDependencies.js"; 124 | text = '' 125 | var fs = require('fs'); 126 | var path = require('path'); 127 | 128 | function resolveDependencyVersion(location, name) { 129 | if(location == process.env['NIX_STORE']) { 130 | return null; 131 | } else { 132 | var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json"); 133 | 134 | if(fs.existsSync(dependencyPackageJSON)) { 135 | var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON)); 136 | 137 | if(dependencyPackageObj.name == name) { 138 | return dependencyPackageObj.version; 139 | } 140 | } else { 141 | return resolveDependencyVersion(path.resolve(location, ".."), name); 142 | } 143 | } 144 | } 145 | 146 | function replaceDependencies(dependencies) { 147 | if(typeof dependencies == "object" && dependencies !== null) { 148 | for(var dependency in dependencies) { 149 | var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency); 150 | 151 | if(resolvedVersion === null) { 152 | process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n"); 153 | } else { 154 | dependencies[dependency] = resolvedVersion; 155 | } 156 | } 157 | } 158 | } 159 | 160 | /* Read the package.json configuration */ 161 | var packageObj = JSON.parse(fs.readFileSync('./package.json')); 162 | 163 | /* Pinpoint all dependencies */ 164 | replaceDependencies(packageObj.dependencies); 165 | if(process.argv[2] == "development") { 166 | replaceDependencies(packageObj.devDependencies); 167 | } 168 | else { 169 | packageObj.devDependencies = {}; 170 | } 171 | replaceDependencies(packageObj.optionalDependencies); 172 | replaceDependencies(packageObj.peerDependencies); 173 | 174 | /* Write the fixed package.json file */ 175 | fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2)); 176 | ''; 177 | }; 178 | in 179 | '' 180 | node ${pinpointDependenciesFromPackageJSON} ${if production then "production" else "development"} 181 | 182 | ${lib.optionalString (dependencies != []) 183 | '' 184 | if [ -d node_modules ] 185 | then 186 | cd node_modules 187 | ${lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies} 188 | cd .. 189 | fi 190 | ''} 191 | ''; 192 | 193 | # Recursively traverses all dependencies of a package and pinpoints all 194 | # dependencies in the package.json file to the versions that are actually 195 | # being used. 196 | 197 | pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args: 198 | '' 199 | if [ -d "${packageName}" ] 200 | then 201 | cd "${packageName}" 202 | ${pinpointDependencies { inherit dependencies production; }} 203 | cd .. 204 | ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} 205 | fi 206 | ''; 207 | 208 | # Extract the Node.js source code which is used to compile packages with 209 | # native bindings 210 | nodeSources = runCommand "node-sources" {} '' 211 | tar --no-same-owner --no-same-permissions -xf ${nodejs.src} 212 | mv node-* $out 213 | ''; 214 | 215 | # Script that adds _integrity fields to all package.json files to prevent NPM from consulting the cache (that is empty) 216 | addIntegrityFieldsScript = writeTextFile { 217 | name = "addintegrityfields.js"; 218 | text = '' 219 | var fs = require('fs'); 220 | var path = require('path'); 221 | 222 | function augmentDependencies(baseDir, dependencies) { 223 | for(var dependencyName in dependencies) { 224 | var dependency = dependencies[dependencyName]; 225 | 226 | // Open package.json and augment metadata fields 227 | var packageJSONDir = path.join(baseDir, "node_modules", dependencyName); 228 | var packageJSONPath = path.join(packageJSONDir, "package.json"); 229 | 230 | if(fs.existsSync(packageJSONPath)) { // Only augment packages that exist. Sometimes we may have production installs in which development dependencies can be ignored 231 | console.log("Adding metadata fields to: "+packageJSONPath); 232 | var packageObj = JSON.parse(fs.readFileSync(packageJSONPath)); 233 | 234 | if(dependency.integrity) { 235 | packageObj["_integrity"] = dependency.integrity; 236 | } else { 237 | packageObj["_integrity"] = "sha1-000000000000000000000000000="; // When no _integrity string has been provided (e.g. by Git dependencies), add a dummy one. It does not seem to harm and it bypasses downloads. 238 | } 239 | 240 | if(dependency.resolved) { 241 | packageObj["_resolved"] = dependency.resolved; // Adopt the resolved property if one has been provided 242 | } else { 243 | packageObj["_resolved"] = dependency.version; // Set the resolved version to the version identifier. This prevents NPM from cloning Git repositories. 244 | } 245 | 246 | if(dependency.from !== undefined) { // Adopt from property if one has been provided 247 | packageObj["_from"] = dependency.from; 248 | } 249 | 250 | fs.writeFileSync(packageJSONPath, JSON.stringify(packageObj, null, 2)); 251 | } 252 | 253 | // Augment transitive dependencies 254 | if(dependency.dependencies !== undefined) { 255 | augmentDependencies(packageJSONDir, dependency.dependencies); 256 | } 257 | } 258 | } 259 | 260 | if(fs.existsSync("./package-lock.json")) { 261 | var packageLock = JSON.parse(fs.readFileSync("./package-lock.json")); 262 | 263 | if(![1, 2].includes(packageLock.lockfileVersion)) { 264 | process.stderr.write("Sorry, I only understand lock file versions 1 and 2!\n"); 265 | process.exit(1); 266 | } 267 | 268 | if(packageLock.dependencies !== undefined) { 269 | augmentDependencies(".", packageLock.dependencies); 270 | } 271 | } 272 | ''; 273 | }; 274 | 275 | # Reconstructs a package-lock file from the node_modules/ folder structure and package.json files with dummy sha1 hashes 276 | reconstructPackageLock = writeTextFile { 277 | name = "reconstructpackagelock.js"; 278 | text = '' 279 | var fs = require('fs'); 280 | var path = require('path'); 281 | 282 | var packageObj = JSON.parse(fs.readFileSync("package.json")); 283 | 284 | var lockObj = { 285 | name: packageObj.name, 286 | version: packageObj.version, 287 | lockfileVersion: 2, 288 | requires: true, 289 | packages: { 290 | "": { 291 | name: packageObj.name, 292 | version: packageObj.version, 293 | license: packageObj.license, 294 | bin: packageObj.bin, 295 | dependencies: packageObj.dependencies, 296 | engines: packageObj.engines, 297 | optionalDependencies: packageObj.optionalDependencies 298 | } 299 | }, 300 | dependencies: {} 301 | }; 302 | 303 | function augmentPackageJSON(filePath, packages, dependencies) { 304 | var packageJSON = path.join(filePath, "package.json"); 305 | if(fs.existsSync(packageJSON)) { 306 | var packageObj = JSON.parse(fs.readFileSync(packageJSON)); 307 | packages[filePath] = { 308 | version: packageObj.version, 309 | integrity: "sha1-000000000000000000000000000=", 310 | dependencies: packageObj.dependencies, 311 | engines: packageObj.engines, 312 | optionalDependencies: packageObj.optionalDependencies 313 | }; 314 | dependencies[packageObj.name] = { 315 | version: packageObj.version, 316 | integrity: "sha1-000000000000000000000000000=", 317 | dependencies: {} 318 | }; 319 | processDependencies(path.join(filePath, "node_modules"), packages, dependencies[packageObj.name].dependencies); 320 | } 321 | } 322 | 323 | function processDependencies(dir, packages, dependencies) { 324 | if(fs.existsSync(dir)) { 325 | var files = fs.readdirSync(dir); 326 | 327 | files.forEach(function(entry) { 328 | var filePath = path.join(dir, entry); 329 | var stats = fs.statSync(filePath); 330 | 331 | if(stats.isDirectory()) { 332 | if(entry.substr(0, 1) == "@") { 333 | // When we encounter a namespace folder, augment all packages belonging to the scope 334 | var pkgFiles = fs.readdirSync(filePath); 335 | 336 | pkgFiles.forEach(function(entry) { 337 | if(stats.isDirectory()) { 338 | var pkgFilePath = path.join(filePath, entry); 339 | augmentPackageJSON(pkgFilePath, packages, dependencies); 340 | } 341 | }); 342 | } else { 343 | augmentPackageJSON(filePath, packages, dependencies); 344 | } 345 | } 346 | }); 347 | } 348 | } 349 | 350 | processDependencies("node_modules", lockObj.packages, lockObj.dependencies); 351 | 352 | fs.writeFileSync("package-lock.json", JSON.stringify(lockObj, null, 2)); 353 | ''; 354 | }; 355 | 356 | # Script that links bins defined in package.json to the node_modules bin directory 357 | # NPM does not do this for top-level packages itself anymore as of v7 358 | linkBinsScript = writeTextFile { 359 | name = "linkbins.js"; 360 | text = '' 361 | var fs = require('fs'); 362 | var path = require('path'); 363 | 364 | var packageObj = JSON.parse(fs.readFileSync("package.json")); 365 | 366 | var nodeModules = Array(packageObj.name.split("/").length).fill("..").join(path.sep); 367 | 368 | if(packageObj.bin !== undefined) { 369 | fs.mkdirSync(path.join(nodeModules, ".bin")) 370 | 371 | if(typeof packageObj.bin == "object") { 372 | Object.keys(packageObj.bin).forEach(function(exe) { 373 | if(fs.existsSync(packageObj.bin[exe])) { 374 | console.log("linking bin '" + exe + "'"); 375 | fs.symlinkSync( 376 | path.join("..", packageObj.name, packageObj.bin[exe]), 377 | path.join(nodeModules, ".bin", exe) 378 | ); 379 | } 380 | else { 381 | console.log("skipping non-existent bin '" + exe + "'"); 382 | } 383 | }) 384 | } 385 | else { 386 | if(fs.existsSync(packageObj.bin)) { 387 | console.log("linking bin '" + packageObj.bin + "'"); 388 | fs.symlinkSync( 389 | path.join("..", packageObj.name, packageObj.bin), 390 | path.join(nodeModules, ".bin", packageObj.name.split("/").pop()) 391 | ); 392 | } 393 | else { 394 | console.log("skipping non-existent bin '" + packageObj.bin + "'"); 395 | } 396 | } 397 | } 398 | else if(packageObj.directories !== undefined && packageObj.directories.bin !== undefined) { 399 | fs.mkdirSync(path.join(nodeModules, ".bin")) 400 | 401 | fs.readdirSync(packageObj.directories.bin).forEach(function(exe) { 402 | if(fs.existsSync(path.join(packageObj.directories.bin, exe))) { 403 | console.log("linking bin '" + exe + "'"); 404 | fs.symlinkSync( 405 | path.join("..", packageObj.name, packageObj.directories.bin, exe), 406 | path.join(nodeModules, ".bin", exe) 407 | ); 408 | } 409 | else { 410 | console.log("skipping non-existent bin '" + exe + "'"); 411 | } 412 | }) 413 | } 414 | ''; 415 | }; 416 | 417 | prepareAndInvokeNPM = {packageName, bypassCache, reconstructLock, npmFlags, production}: 418 | let 419 | forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com"; 420 | in 421 | '' 422 | # Pinpoint the versions of all dependencies to the ones that are actually being used 423 | echo "pinpointing versions of dependencies..." 424 | source $pinpointDependenciesScriptPath 425 | 426 | # Patch the shebangs of the bundled modules to prevent them from 427 | # calling executables outside the Nix store as much as possible 428 | patchShebangs . 429 | 430 | # Deploy the Node.js package by running npm install. Since the 431 | # dependencies have been provided already by ourselves, it should not 432 | # attempt to install them again, which is good, because we want to make 433 | # it Nix's responsibility. If it needs to install any dependencies 434 | # anyway (e.g. because the dependency parameters are 435 | # incomplete/incorrect), it fails. 436 | # 437 | # The other responsibilities of NPM are kept -- version checks, build 438 | # steps, postprocessing etc. 439 | 440 | export HOME=$TMPDIR 441 | cd "${packageName}" 442 | runHook preRebuild 443 | 444 | ${lib.optionalString bypassCache '' 445 | ${lib.optionalString reconstructLock '' 446 | if [ -f package-lock.json ] 447 | then 448 | echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!" 449 | echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!" 450 | rm package-lock.json 451 | else 452 | echo "No package-lock.json file found, reconstructing..." 453 | fi 454 | 455 | node ${reconstructPackageLock} 456 | ''} 457 | 458 | node ${addIntegrityFieldsScript} 459 | ''} 460 | 461 | npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild 462 | 463 | runHook postRebuild 464 | 465 | if [ "''${dontNpmInstall-}" != "1" ] 466 | then 467 | # NPM tries to download packages even when they already exist if npm-shrinkwrap is used. 468 | rm -f npm-shrinkwrap.json 469 | 470 | npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install 471 | fi 472 | 473 | # Link executables defined in package.json 474 | node ${linkBinsScript} 475 | ''; 476 | 477 | # Builds and composes an NPM package including all its dependencies 478 | buildNodePackage = 479 | { name 480 | , packageName 481 | , version ? null 482 | , dependencies ? [] 483 | , buildInputs ? [] 484 | , production ? true 485 | , npmFlags ? "" 486 | , dontNpmInstall ? false 487 | , bypassCache ? false 488 | , reconstructLock ? false 489 | , preRebuild ? "" 490 | , dontStrip ? true 491 | , unpackPhase ? "true" 492 | , buildPhase ? "true" 493 | , meta ? {} 494 | , ... }@args: 495 | 496 | let 497 | extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ]; 498 | in 499 | stdenv.mkDerivation ({ 500 | name = "${name}${if version == null then "" else "-${version}"}"; 501 | buildInputs = [ tarWrapper python nodejs ] 502 | ++ lib.optional (stdenv.isLinux) utillinux 503 | ++ lib.optional (stdenv.isDarwin) libtool 504 | ++ buildInputs; 505 | 506 | inherit nodejs; 507 | 508 | inherit dontStrip; # Stripping may fail a build for some package deployments 509 | inherit dontNpmInstall preRebuild unpackPhase buildPhase; 510 | 511 | compositionScript = composePackage args; 512 | pinpointDependenciesScript = pinpointDependenciesOfPackage args; 513 | 514 | passAsFile = [ "compositionScript" "pinpointDependenciesScript" ]; 515 | 516 | installPhase = '' 517 | source ${installPackage} 518 | 519 | # Create and enter a root node_modules/ folder 520 | mkdir -p $out/lib/node_modules 521 | cd $out/lib/node_modules 522 | 523 | # Compose the package and all its dependencies 524 | source $compositionScriptPath 525 | 526 | ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }} 527 | 528 | # Create symlink to the deployed executable folder, if applicable 529 | if [ -d "$out/lib/node_modules/.bin" ] 530 | then 531 | ln -s $out/lib/node_modules/.bin $out/bin 532 | 533 | # Patch the shebang lines of all the executables 534 | ls $out/bin/* | while read i 535 | do 536 | file="$(readlink -f "$i")" 537 | chmod u+rwx "$file" 538 | patchShebangs "$file" 539 | done 540 | fi 541 | 542 | # Create symlinks to the deployed manual page folders, if applicable 543 | if [ -d "$out/lib/node_modules/${packageName}/man" ] 544 | then 545 | mkdir -p $out/share 546 | for dir in "$out/lib/node_modules/${packageName}/man/"* 547 | do 548 | mkdir -p $out/share/man/$(basename "$dir") 549 | for page in "$dir"/* 550 | do 551 | ln -s $page $out/share/man/$(basename "$dir") 552 | done 553 | done 554 | fi 555 | 556 | # Run post install hook, if provided 557 | runHook postInstall 558 | ''; 559 | 560 | meta = { 561 | # default to Node.js' platforms 562 | platforms = nodejs.meta.platforms; 563 | } // meta; 564 | } // extraArgs); 565 | 566 | # Builds a node environment (a node_modules folder and a set of binaries) 567 | buildNodeDependencies = 568 | { name 569 | , packageName 570 | , version ? null 571 | , src 572 | , dependencies ? [] 573 | , buildInputs ? [] 574 | , production ? true 575 | , npmFlags ? "" 576 | , dontNpmInstall ? false 577 | , bypassCache ? false 578 | , reconstructLock ? false 579 | , dontStrip ? true 580 | , unpackPhase ? "true" 581 | , buildPhase ? "true" 582 | , ... }@args: 583 | 584 | let 585 | extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ]; 586 | in 587 | stdenv.mkDerivation ({ 588 | name = "node-dependencies-${name}${if version == null then "" else "-${version}"}"; 589 | 590 | buildInputs = [ tarWrapper python nodejs ] 591 | ++ lib.optional (stdenv.isLinux) utillinux 592 | ++ lib.optional (stdenv.isDarwin) libtool 593 | ++ buildInputs; 594 | 595 | inherit dontStrip; # Stripping may fail a build for some package deployments 596 | inherit dontNpmInstall unpackPhase buildPhase; 597 | 598 | includeScript = includeDependencies { inherit dependencies; }; 599 | pinpointDependenciesScript = pinpointDependenciesOfPackage args; 600 | 601 | passAsFile = [ "includeScript" "pinpointDependenciesScript" ]; 602 | 603 | installPhase = '' 604 | source ${installPackage} 605 | 606 | mkdir -p $out/${packageName} 607 | cd $out/${packageName} 608 | 609 | source $includeScriptPath 610 | 611 | # Create fake package.json to make the npm commands work properly 612 | cp ${src}/package.json . 613 | chmod 644 package.json 614 | ${lib.optionalString bypassCache '' 615 | if [ -f ${src}/package-lock.json ] 616 | then 617 | cp ${src}/package-lock.json . 618 | chmod 644 package-lock.json 619 | fi 620 | ''} 621 | 622 | # Go to the parent folder to make sure that all packages are pinpointed 623 | cd .. 624 | ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} 625 | 626 | ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }} 627 | 628 | # Expose the executables that were installed 629 | cd .. 630 | ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} 631 | 632 | mv ${packageName} lib 633 | ln -s $out/lib/node_modules/.bin $out/bin 634 | ''; 635 | } // extraArgs); 636 | 637 | # Builds a development shell 638 | buildNodeShell = 639 | { name 640 | , packageName 641 | , version ? null 642 | , src 643 | , dependencies ? [] 644 | , buildInputs ? [] 645 | , production ? true 646 | , npmFlags ? "" 647 | , dontNpmInstall ? false 648 | , bypassCache ? false 649 | , reconstructLock ? false 650 | , dontStrip ? true 651 | , unpackPhase ? "true" 652 | , buildPhase ? "true" 653 | , ... }@args: 654 | 655 | let 656 | nodeDependencies = buildNodeDependencies args; 657 | extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "unpackPhase" "buildPhase" ]; 658 | in 659 | stdenv.mkDerivation ({ 660 | name = "node-shell-${name}${if version == null then "" else "-${version}"}"; 661 | 662 | buildInputs = [ python nodejs ] ++ lib.optional (stdenv.isLinux) utillinux ++ buildInputs; 663 | buildCommand = '' 664 | mkdir -p $out/bin 665 | cat > $out/bin/shell < 2 | , systems ? [ "i686-linux" "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ] 3 | }: 4 | 5 | let 6 | pkgs = import nixpkgs {}; 7 | 8 | version = (builtins.fromJSON (builtins.readFile ./package.json)).version; 9 | 10 | jobset = import ./default.nix { 11 | inherit pkgs; 12 | system = builtins.currentSystem; 13 | }; 14 | in 15 | rec { 16 | inherit (jobset) tarball; 17 | 18 | package = pkgs.lib.genAttrs systems (system: 19 | (import ./default.nix { 20 | pkgs = import nixpkgs { inherit system; }; 21 | inherit system; 22 | }).package.override { 23 | postInstall = '' 24 | mkdir -p $out/share/doc/node2nix 25 | $out/lib/node_modules/node2nix/node_modules/jsdoc/jsdoc.js -R README.md -r lib -d $out/share/doc/node2nix/apidox 26 | mkdir -p $out/nix-support 27 | echo "doc api $out/share/doc/node2nix/apidox" >> $out/nix-support/hydra-build-products 28 | ''; 29 | } 30 | ); 31 | 32 | tests = pkgs.lib.genAttrs systems (system: 33 | { 34 | v14 = import ./tests/override-v14.nix { 35 | pkgs = import nixpkgs { inherit system; }; 36 | inherit system; 37 | }; 38 | v16 = import ./tests/override-v16.nix { 39 | pkgs = import nixpkgs { inherit system; }; 40 | inherit system; 41 | }; 42 | grunt = import ./tests/grunt/override.nix { 43 | pkgs = import nixpkgs { inherit system; }; 44 | inherit system; 45 | }; 46 | lockfile = (import ./tests/lockfile { 47 | pkgs = import nixpkgs { inherit system; }; 48 | inherit system; 49 | }).package; 50 | lockfile-v2 = (import ./tests/lockfile-v2 { 51 | pkgs = import nixpkgs { inherit system; }; 52 | inherit system; 53 | }).package; 54 | scoped = (import ./tests/scoped { 55 | pkgs = import nixpkgs { inherit system; }; 56 | inherit system; 57 | }).package; 58 | versionless = (import ./tests/versionless { 59 | pkgs = import nixpkgs { inherit system; }; 60 | inherit system; 61 | }).package; 62 | }) // { 63 | cli = import ./tests/cli { 64 | inherit nixpkgs; 65 | node2nix = builtins.getAttr (builtins.currentSystem) package; 66 | }; 67 | }; 68 | 69 | release = pkgs.releaseTools.aggregate { 70 | name = "node2nix-${version}"; 71 | constituents = [ 72 | tarball 73 | ] 74 | ++ map (system: builtins.getAttr system package) systems 75 | ++ pkgs.lib.flatten (map (system: 76 | let 77 | tests_ = tests."${system}".v14; 78 | in 79 | map (name: builtins.getAttr name tests_) (builtins.attrNames tests_) 80 | ) systems) 81 | ++ pkgs.lib.flatten (map (system: 82 | let 83 | tests_ = tests."${system}".v16; 84 | in 85 | map (name: builtins.getAttr name tests_) (builtins.attrNames tests_) 86 | ) systems) 87 | ++ map (system: tests."${system}".grunt) systems 88 | ++ map (system: tests."${system}".lockfile) systems 89 | ++ map (system: tests."${system}".scoped) systems 90 | ++ [ tests.cli ]; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /tests/cli/default.nix: -------------------------------------------------------------------------------- 1 | {nixpkgs, node2nix}: 2 | 3 | let 4 | testprojects = ./testprojects; 5 | in 6 | with import "${nixpkgs}/nixos/lib/testing-python.nix" { system = builtins.currentSystem; }; 7 | 8 | simpleTest { 9 | nodes = { 10 | machine = {pkgs, ...}: 11 | 12 | { 13 | environment.systemPackages = [ node2nix ]; 14 | }; 15 | }; 16 | testScript = 17 | '' 18 | start_all() 19 | 20 | machine.succeed( 21 | "cp -av ${testprojects} testprojects" 22 | ) 23 | machine.succeed("chmod u+w testprojects") 24 | 25 | # Try to use an invalid option. This should fail. 26 | machine.fail("cd testprojects/complete; node2nix --invalid-option") 27 | 28 | # Try to generate Nix expressions for a package.json with no name. This should fail. 29 | machine.fail("cd testprojects/noname; node2nix") 30 | 31 | # Try to generate Nix expressions for a package.json with a name and version. This should succeed. 32 | machine.succeed("cd testprojects/complete; node2nix") 33 | 34 | # Try to generate Nix expression from package.json without specifying the lock file. This should raise a warning. 35 | output = machine.succeed("cd testprojects/lock; node2nix -8") 36 | if "lock file exists" in output: 37 | print("Lock file warning raised") 38 | else: 39 | raise Exception("A lock file warning should be raised") 40 | 41 | # Try to generate Nix expression in a project that has a node_modules/ sub folder. This should raise a warning. 42 | output = machine.succeed("cd testprojects/garbage; node2nix") 43 | 44 | if "node_modules/ folder in the root directory": 45 | print("node_modules/ warning raised") 46 | else: 47 | raise Exception("A node_modules/ warning should be raised") 48 | ''; 49 | } 50 | -------------------------------------------------------------------------------- /tests/cli/testprojects/complete/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testproject", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/cli/testprojects/garbage/node_modules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svanderburg/node2nix/315e1b85a6761152f57a41ccea5e2570981ec670/tests/cli/testprojects/garbage/node_modules/.gitkeep -------------------------------------------------------------------------------- /tests/cli/testprojects/garbage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testproject", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/cli/testprojects/lock/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testproject", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/cli/testprojects/lock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testproject", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/cli/testprojects/noname/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "private": true 4 | } 5 | -------------------------------------------------------------------------------- /tests/default-v14.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-14_x"}: 6 | 7 | let 8 | nodeEnv = import ../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages-v14.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/default-v16.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-16_x"}: 6 | 7 | let 8 | nodeEnv = import ../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages-v16.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/grunt/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | jshint: { 5 | files: ['Gruntfile.js', 'src/**/*.js'], 6 | options: { 7 | globals: { 8 | jQuery: true 9 | } 10 | } 11 | }, 12 | watch: { 13 | files: ['<%= jshint.files %>'], 14 | tasks: ['jshint'] 15 | } 16 | }); 17 | 18 | grunt.loadNpmTasks('grunt-contrib-jshint'); 19 | grunt.loadNpmTasks('grunt-contrib-watch'); 20 | 21 | grunt.registerTask('default', ['jshint']); 22 | }; 23 | -------------------------------------------------------------------------------- /tests/grunt/default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}: 6 | 7 | let 8 | globalBuildInputs = pkgs.lib.attrValues (import ./supplement.nix { 9 | inherit nodeEnv; 10 | inherit (pkgs) stdenv lib nix-gitignore fetchurl fetchgit; 11 | }); 12 | nodeEnv = import ../../nix/node-env.nix { 13 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 14 | inherit pkgs nodejs; 15 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 16 | }; 17 | in 18 | import ./node-packages.nix { 19 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 20 | inherit nodeEnv globalBuildInputs; 21 | } 22 | -------------------------------------------------------------------------------- /tests/grunt/override.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | , system ? builtins.currentSystem 3 | }: 4 | 5 | let 6 | nodePackages = import ./default.nix { 7 | inherit pkgs system; 8 | }; 9 | in 10 | nodePackages.package.override { 11 | postInstall = "grunt"; 12 | } 13 | -------------------------------------------------------------------------------- /tests/grunt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-test", 3 | "version": "0.0.1", 4 | "private": "true", 5 | "devDependencies": { 6 | "lodash": "4.x", 7 | "grunt": "*", 8 | "grunt-contrib-jshint": "*", 9 | "grunt-contrib-watch": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/grunt/src/test.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | console.log("Hello world! This is a test..."); 5 | }()); 6 | -------------------------------------------------------------------------------- /tests/grunt/supplement.json: -------------------------------------------------------------------------------- 1 | [ 2 | "grunt-cli" 3 | ] 4 | -------------------------------------------------------------------------------- /tests/lockfile-v2/a.js: -------------------------------------------------------------------------------- 1 | console.log("Hello world!"); 2 | 3 | -------------------------------------------------------------------------------- /tests/lockfile-v2/default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}: 6 | 7 | let 8 | nodeEnv = import ../../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/lockfile-v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockfile-v2", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "@sindresorhus/df": "*", 6 | "async": "*", 7 | "express": "git+https://github.com/strongloop/express.git#e1b45eb", 8 | "testa": "../testa" 9 | }, 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /tests/lockfile/a.js: -------------------------------------------------------------------------------- 1 | console.log("Hello world!"); 2 | 3 | -------------------------------------------------------------------------------- /tests/lockfile/default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}: 6 | 7 | let 8 | nodeEnv = import ../../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/lockfile/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockfile", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@sindresorhus/df": { 8 | "version": "4.0.0", 9 | "resolved": "https://registry.npmjs.org/@sindresorhus/df/-/df-4.0.0.tgz", 10 | "integrity": "sha512-s8SqHvZ9HgSPD2Jvd1d/Y0KtZ1AeakUN1phbu5G3vt5Ob4KUPp8HSZdOvKcIcpgIiI6tjbJPoKkzKLw6VsfgxQ==", 11 | "requires": { 12 | "execa": "^5.1.1" 13 | } 14 | }, 15 | "accepts": { 16 | "version": "1.3.8", 17 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 18 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 19 | "requires": { 20 | "mime-types": "~2.1.34", 21 | "negotiator": "0.6.3" 22 | } 23 | }, 24 | "array-flatten": { 25 | "version": "1.1.1", 26 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 27 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 28 | }, 29 | "async": { 30 | "version": "3.2.3", 31 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", 32 | "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" 33 | }, 34 | "body-parser": { 35 | "version": "1.19.0", 36 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 37 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 38 | "requires": { 39 | "bytes": "3.1.0", 40 | "content-type": "~1.0.4", 41 | "debug": "2.6.9", 42 | "depd": "~1.1.2", 43 | "http-errors": "1.7.2", 44 | "iconv-lite": "0.4.24", 45 | "on-finished": "~2.3.0", 46 | "qs": "6.7.0", 47 | "raw-body": "2.4.0", 48 | "type-is": "~1.6.17" 49 | } 50 | }, 51 | "bytes": { 52 | "version": "3.1.0", 53 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 54 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 55 | }, 56 | "content-disposition": { 57 | "version": "0.5.3", 58 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 59 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 60 | "requires": { 61 | "safe-buffer": "5.1.2" 62 | } 63 | }, 64 | "content-type": { 65 | "version": "1.0.4", 66 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 67 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 68 | }, 69 | "cookie": { 70 | "version": "0.4.0", 71 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 72 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 73 | }, 74 | "cookie-signature": { 75 | "version": "1.0.6", 76 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 77 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 78 | }, 79 | "cross-spawn": { 80 | "version": "7.0.3", 81 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 82 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 83 | "requires": { 84 | "path-key": "^3.1.0", 85 | "shebang-command": "^2.0.0", 86 | "which": "^2.0.1" 87 | } 88 | }, 89 | "debug": { 90 | "version": "2.6.9", 91 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 92 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 93 | "requires": { 94 | "ms": "2.0.0" 95 | } 96 | }, 97 | "depd": { 98 | "version": "1.1.2", 99 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 100 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 101 | }, 102 | "destroy": { 103 | "version": "1.0.4", 104 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 105 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 106 | }, 107 | "ee-first": { 108 | "version": "1.1.1", 109 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 110 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 111 | }, 112 | "encodeurl": { 113 | "version": "1.0.2", 114 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 115 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 116 | }, 117 | "escape-html": { 118 | "version": "1.0.3", 119 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 120 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 121 | }, 122 | "etag": { 123 | "version": "1.8.1", 124 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 125 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 126 | }, 127 | "execa": { 128 | "version": "5.1.1", 129 | "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", 130 | "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", 131 | "requires": { 132 | "cross-spawn": "^7.0.3", 133 | "get-stream": "^6.0.0", 134 | "human-signals": "^2.1.0", 135 | "is-stream": "^2.0.0", 136 | "merge-stream": "^2.0.0", 137 | "npm-run-path": "^4.0.1", 138 | "onetime": "^5.1.2", 139 | "signal-exit": "^3.0.3", 140 | "strip-final-newline": "^2.0.0" 141 | } 142 | }, 143 | "express": { 144 | "version": "git+https://github.com/strongloop/express.git#e1b45ebd050b6f06aa38cda5aaf0c21708b0c71e", 145 | "from": "git+https://github.com/strongloop/express.git#e1b45eb", 146 | "requires": { 147 | "accepts": "~1.3.7", 148 | "array-flatten": "1.1.1", 149 | "body-parser": "1.19.0", 150 | "content-disposition": "0.5.3", 151 | "content-type": "~1.0.4", 152 | "cookie": "0.4.0", 153 | "cookie-signature": "1.0.6", 154 | "debug": "2.6.9", 155 | "depd": "~1.1.2", 156 | "encodeurl": "~1.0.2", 157 | "escape-html": "~1.0.3", 158 | "etag": "~1.8.1", 159 | "finalhandler": "~1.1.2", 160 | "fresh": "0.5.2", 161 | "merge-descriptors": "1.0.1", 162 | "methods": "~1.1.2", 163 | "on-finished": "~2.3.0", 164 | "parseurl": "~1.3.3", 165 | "path-to-regexp": "0.1.7", 166 | "proxy-addr": "~2.0.5", 167 | "qs": "6.7.0", 168 | "range-parser": "~1.2.1", 169 | "safe-buffer": "5.1.2", 170 | "send": "0.17.1", 171 | "serve-static": "1.14.1", 172 | "setprototypeof": "1.1.1", 173 | "statuses": "~1.5.0", 174 | "type-is": "~1.6.18", 175 | "utils-merge": "1.0.1", 176 | "vary": "~1.1.2" 177 | } 178 | }, 179 | "finalhandler": { 180 | "version": "1.1.2", 181 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 182 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 183 | "requires": { 184 | "debug": "2.6.9", 185 | "encodeurl": "~1.0.2", 186 | "escape-html": "~1.0.3", 187 | "on-finished": "~2.3.0", 188 | "parseurl": "~1.3.3", 189 | "statuses": "~1.5.0", 190 | "unpipe": "~1.0.0" 191 | } 192 | }, 193 | "forwarded": { 194 | "version": "0.2.0", 195 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 196 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 197 | }, 198 | "fresh": { 199 | "version": "0.5.2", 200 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 201 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 202 | }, 203 | "get-stream": { 204 | "version": "6.0.1", 205 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 206 | "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" 207 | }, 208 | "http-errors": { 209 | "version": "1.7.2", 210 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 211 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 212 | "requires": { 213 | "depd": "~1.1.2", 214 | "inherits": "2.0.3", 215 | "setprototypeof": "1.1.1", 216 | "statuses": ">= 1.5.0 < 2", 217 | "toidentifier": "1.0.0" 218 | } 219 | }, 220 | "human-signals": { 221 | "version": "2.1.0", 222 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", 223 | "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" 224 | }, 225 | "iconv-lite": { 226 | "version": "0.4.24", 227 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 228 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 229 | "requires": { 230 | "safer-buffer": ">= 2.1.2 < 3" 231 | } 232 | }, 233 | "inherits": { 234 | "version": "2.0.3", 235 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 236 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 237 | }, 238 | "ipaddr.js": { 239 | "version": "1.9.1", 240 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 241 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 242 | }, 243 | "is-stream": { 244 | "version": "2.0.1", 245 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 246 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" 247 | }, 248 | "isexe": { 249 | "version": "2.0.0", 250 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 251 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 252 | }, 253 | "media-typer": { 254 | "version": "0.3.0", 255 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 256 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 257 | }, 258 | "merge-descriptors": { 259 | "version": "1.0.1", 260 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 261 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 262 | }, 263 | "merge-stream": { 264 | "version": "2.0.0", 265 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 266 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" 267 | }, 268 | "methods": { 269 | "version": "1.1.2", 270 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 271 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 272 | }, 273 | "mime": { 274 | "version": "1.6.0", 275 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 276 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 277 | }, 278 | "mime-db": { 279 | "version": "1.52.0", 280 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 281 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 282 | }, 283 | "mime-types": { 284 | "version": "2.1.35", 285 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 286 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 287 | "requires": { 288 | "mime-db": "1.52.0" 289 | } 290 | }, 291 | "mimic-fn": { 292 | "version": "2.1.0", 293 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 294 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" 295 | }, 296 | "ms": { 297 | "version": "2.0.0", 298 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 299 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 300 | }, 301 | "negotiator": { 302 | "version": "0.6.3", 303 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 304 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 305 | }, 306 | "npm-run-path": { 307 | "version": "4.0.1", 308 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 309 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 310 | "requires": { 311 | "path-key": "^3.0.0" 312 | } 313 | }, 314 | "on-finished": { 315 | "version": "2.3.0", 316 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 317 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 318 | "requires": { 319 | "ee-first": "1.1.1" 320 | } 321 | }, 322 | "onetime": { 323 | "version": "5.1.2", 324 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 325 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 326 | "requires": { 327 | "mimic-fn": "^2.1.0" 328 | } 329 | }, 330 | "parseurl": { 331 | "version": "1.3.3", 332 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 333 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 334 | }, 335 | "path-key": { 336 | "version": "3.1.1", 337 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 338 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" 339 | }, 340 | "path-to-regexp": { 341 | "version": "0.1.7", 342 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 343 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 344 | }, 345 | "proxy-addr": { 346 | "version": "2.0.7", 347 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 348 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 349 | "requires": { 350 | "forwarded": "0.2.0", 351 | "ipaddr.js": "1.9.1" 352 | } 353 | }, 354 | "qs": { 355 | "version": "6.7.0", 356 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 357 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 358 | }, 359 | "range-parser": { 360 | "version": "1.2.1", 361 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 362 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 363 | }, 364 | "raw-body": { 365 | "version": "2.4.0", 366 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 367 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 368 | "requires": { 369 | "bytes": "3.1.0", 370 | "http-errors": "1.7.2", 371 | "iconv-lite": "0.4.24", 372 | "unpipe": "1.0.0" 373 | } 374 | }, 375 | "safe-buffer": { 376 | "version": "5.1.2", 377 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 378 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 379 | }, 380 | "safer-buffer": { 381 | "version": "2.1.2", 382 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 383 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 384 | }, 385 | "send": { 386 | "version": "0.17.1", 387 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 388 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 389 | "requires": { 390 | "debug": "2.6.9", 391 | "depd": "~1.1.2", 392 | "destroy": "~1.0.4", 393 | "encodeurl": "~1.0.2", 394 | "escape-html": "~1.0.3", 395 | "etag": "~1.8.1", 396 | "fresh": "0.5.2", 397 | "http-errors": "~1.7.2", 398 | "mime": "1.6.0", 399 | "ms": "2.1.1", 400 | "on-finished": "~2.3.0", 401 | "range-parser": "~1.2.1", 402 | "statuses": "~1.5.0" 403 | }, 404 | "dependencies": { 405 | "ms": { 406 | "version": "2.1.1", 407 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 408 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 409 | } 410 | } 411 | }, 412 | "serve-static": { 413 | "version": "1.14.1", 414 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 415 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 416 | "requires": { 417 | "encodeurl": "~1.0.2", 418 | "escape-html": "~1.0.3", 419 | "parseurl": "~1.3.3", 420 | "send": "0.17.1" 421 | } 422 | }, 423 | "setprototypeof": { 424 | "version": "1.1.1", 425 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 426 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 427 | }, 428 | "shebang-command": { 429 | "version": "2.0.0", 430 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 431 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 432 | "requires": { 433 | "shebang-regex": "^3.0.0" 434 | } 435 | }, 436 | "shebang-regex": { 437 | "version": "3.0.0", 438 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 439 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" 440 | }, 441 | "signal-exit": { 442 | "version": "3.0.7", 443 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 444 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 445 | }, 446 | "statuses": { 447 | "version": "1.5.0", 448 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 449 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 450 | }, 451 | "strip-final-newline": { 452 | "version": "2.0.0", 453 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 454 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" 455 | }, 456 | "testa": { 457 | "version": "file:../testa" 458 | }, 459 | "toidentifier": { 460 | "version": "1.0.0", 461 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 462 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 463 | }, 464 | "type-is": { 465 | "version": "1.6.18", 466 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 467 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 468 | "requires": { 469 | "media-typer": "0.3.0", 470 | "mime-types": "~2.1.24" 471 | } 472 | }, 473 | "unpipe": { 474 | "version": "1.0.0", 475 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 476 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 477 | }, 478 | "utils-merge": { 479 | "version": "1.0.1", 480 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 481 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 482 | }, 483 | "vary": { 484 | "version": "1.1.2", 485 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 486 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 487 | }, 488 | "which": { 489 | "version": "2.0.2", 490 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 491 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 492 | "requires": { 493 | "isexe": "^2.0.0" 494 | } 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /tests/lockfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockfile", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "async": "*", 6 | "express": "git+https://github.com/strongloop/express.git#e1b45eb", 7 | "testa": "../testa", 8 | "@sindresorhus/df": "*" 9 | }, 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /tests/override-v14.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import { 2 | inherit system; 3 | }, system ? builtins.currentSystem}: 4 | 5 | let 6 | nodePackages = import ./default-v14.nix { 7 | inherit pkgs system; 8 | }; 9 | in 10 | nodePackages // { 11 | node-libcurl = nodePackages.node-libcurl.override { 12 | buildInputs = [ pkgs.curl ]; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /tests/override-v16.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import { 2 | inherit system; 3 | }, system ? builtins.currentSystem}: 4 | 5 | let 6 | nodePackages = import ./default-v16.nix { 7 | inherit pkgs system; 8 | }; 9 | in 10 | nodePackages // { 11 | node-libcurl = nodePackages.node-libcurl.override { 12 | buildInputs = [ pkgs.curl ]; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /tests/scoped/default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}: 6 | 7 | let 8 | nodeEnv = import ../../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/scoped/node-packages.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {nodeEnv, fetchurl, fetchgit, nix-gitignore, stdenv, lib, globalBuildInputs ? []}: 4 | 5 | let 6 | sources = {}; 7 | args = { 8 | name = "_at_myscope_slash_mypackage"; 9 | packageName = "@myscope/mypackage"; 10 | version = "0.0.1"; 11 | src = ./.; 12 | buildInputs = globalBuildInputs; 13 | meta = { 14 | }; 15 | production = true; 16 | bypassCache = true; 17 | reconstructLock = true; 18 | }; 19 | in 20 | { 21 | args = args; 22 | sources = sources; 23 | tarball = nodeEnv.buildNodeSourceDist args; 24 | package = nodeEnv.buildNodePackage args; 25 | shell = nodeEnv.buildNodeShell args; 26 | nodeDependencies = nodeEnv.buildNodeDependencies (lib.overrideExisting args { 27 | src = stdenv.mkDerivation { 28 | name = args.name + "-package-json"; 29 | src = nix-gitignore.gitignoreSourcePure [ 30 | "*" 31 | "!package.json" 32 | "!package-lock.json" 33 | ] args.src; 34 | dontBuild = true; 35 | installPhase = "mkdir -p $out; cp -r ./* $out;"; 36 | }; 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tests/scoped/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myscope/mypackage", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/testa/a.js: -------------------------------------------------------------------------------- 1 | exports.sayA = function() { 2 | console.log("A"); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/testa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testa", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./a.js" 6 | } -------------------------------------------------------------------------------- /tests/testb/b.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var testa = require('testa'); 3 | 4 | testa.sayA(); 5 | console.log("B"); 6 | -------------------------------------------------------------------------------- /tests/testb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testb", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "testa": "../testa" 7 | }, 8 | "bin": { 9 | "b": "./b.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/tests-notes.md: -------------------------------------------------------------------------------- 1 | Notes about the testcases 2 | ========================= 3 | NPM supports all kinds of dependency specifiers and can refer to packages 4 | coming from all kinds of sources. 5 | 6 | There are also many packages around that could potentialy trigger errors, 7 | because they have properties with weird implications that not even the NPM 8 | maintainers could initially think of. 9 | 10 | For validation purposes, I could implement test cases from scratch, but this is 11 | a lot of work. Instead, I took existing packages as test cases. 12 | 13 | The test scenarios are captured in the `tests.json` file (that is translated by 14 | `node2nix` into a set of Nix expressions). 15 | 16 | This code snippet explains the purpose of each test package: 17 | 18 | ```javascript 19 | [ 20 | // Testcase for a frequently used package 21 | "async", 22 | // Testcase for a frequently used package 23 | "grunt-cli", 24 | // Testcase for a frequently used package 25 | "bower", 26 | // Testcase for a frequently used package 27 | "coffee-script", 28 | // Testcase for an exact version specifier 29 | { "nijs": "0.0.25" }, 30 | // Testcase for a wildcard version specifier 31 | { "commander": "2.9.x" }, 32 | // Testcase for a tag as a version specifier 33 | { "underscore": "latest" }, 34 | // Testcase with a weird version specifier containing trailing 0s that can only be used with loose version dependency parsing 35 | { "esprima-fb": "~3001.0001.0000-dev-harmony-fb" }, 36 | // Testcase for a HTTP URL 37 | { "semver": "http://registry.npmjs.org/semver/-/semver-5.0.3.tgz" }, 38 | // Testcase for a generic Git over HTTPS URL 39 | { "express": "git+https://github.com/strongloop/express.git" }, 40 | // Testcase for a generic Git over HTTPS URL referring to a specific revision 41 | { "express": "git+https://github.com/strongloop/express.git#ef7ad68" }, 42 | // Testcase for a generic git over HTTPS URL referring to a branch name 43 | { "express": "git+https://github.com/strongloop/express.git#master" }, 44 | // Test for a Git repository with sub modules 45 | { "js-sequence-diagrams": "git+https://github.com/codimd/js-sequence-diagrams.git" }, 46 | // Testcase for a Github repository alias 47 | { "nijs": "svanderburg/nijs" }, 48 | // Testcase for a Github pull request branch alias 49 | { "mocha": "mochajs/mocha#pull/4779/head" }, 50 | // Testcase for a Git service provider (in this case GitHub) 51 | { "lodash": "github:lodash/lodash" }, 52 | // Testcase for a directory reference 53 | { "testb": "./testb" }, 54 | // Testcase for a package with cyclic dependencies 55 | "es5-ext", 56 | // Testcase for a scoped package 57 | "@sindresorhus/df", 58 | // Testcase for a scoped package referring to a Git repository 59 | { "@bengotow/slate-edit-list": "github:bengotow/slate-edit-list" }, 60 | // Testcase for a package with an external native dependency 61 | "node-libcurl", 62 | // Testcase for a package with native C code that needs to be compiled 63 | "libxmljs", 64 | // Testcase for a package with native C code and an Xcode-related workaround in node-gyp to make it compile on macOS 65 | { "bcrypt": "3.0.x" }, 66 | // Testcase for an NPM alias 67 | { "next": "npm:@blitzjs/next@10.2.3-0.37.0" } 68 | ] 69 | ``` 70 | -------------------------------------------------------------------------------- /tests/tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | "async", 3 | "grunt-cli", 4 | "bower", 5 | "coffee-script", 6 | { "nijs": "0.0.25" }, 7 | { "commander": "2.9.x" }, 8 | { "underscore": "latest" }, 9 | { "esprima-fb": "~3001.0001.0000-dev-harmony-fb" }, 10 | { "semver": "http://registry.npmjs.org/semver/-/semver-5.0.3.tgz" }, 11 | { "express": "git+https://github.com/strongloop/express.git" }, 12 | { "express": "git+https://github.com/strongloop/express.git#ef7ad68" }, 13 | { "express": "git+https://github.com/strongloop/express.git#master" }, 14 | { "js-sequence-diagrams": "git+https://github.com/codimd/js-sequence-diagrams.git" }, 15 | { "nijs": "svanderburg/nijs" }, 16 | { "mocha": "mochajs/mocha#pull/4779/head" }, 17 | { "lodash": "github:lodash/lodash" }, 18 | { "testb": "./testb" }, 19 | "es5-ext", 20 | "@sindresorhus/df", 21 | { "@bengotow/slate-edit-list": "github:bengotow/slate-edit-list" }, 22 | "node-libcurl", 23 | "libxmljs", 24 | { "bcrypt": "3.0.x" }, 25 | { "next": "npm:@blitzjs/next@10.2.3-0.37.0" } 26 | ] 27 | -------------------------------------------------------------------------------- /tests/versionless/default.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {pkgs ? import { 4 | inherit system; 5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}: 6 | 7 | let 8 | nodeEnv = import ../../nix/node-env.nix { 9 | inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; 10 | inherit pkgs nodejs; 11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; 12 | }; 13 | in 14 | import ./node-packages.nix { 15 | inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; 16 | inherit nodeEnv; 17 | } 18 | -------------------------------------------------------------------------------- /tests/versionless/node-packages.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by node2nix 1.11.1. Do not edit! 2 | 3 | {nodeEnv, fetchurl, fetchgit, nix-gitignore, stdenv, lib, globalBuildInputs ? []}: 4 | 5 | let 6 | sources = {}; 7 | args = { 8 | name = "versionless"; 9 | packageName = "versionless"; 10 | src = ./.; 11 | buildInputs = globalBuildInputs; 12 | meta = { 13 | }; 14 | production = true; 15 | bypassCache = true; 16 | reconstructLock = true; 17 | }; 18 | in 19 | { 20 | args = args; 21 | sources = sources; 22 | tarball = nodeEnv.buildNodeSourceDist args; 23 | package = nodeEnv.buildNodePackage args; 24 | shell = nodeEnv.buildNodeShell args; 25 | nodeDependencies = nodeEnv.buildNodeDependencies (lib.overrideExisting args { 26 | src = stdenv.mkDerivation { 27 | name = args.name + "-package-json"; 28 | src = nix-gitignore.gitignoreSourcePure [ 29 | "*" 30 | "!package.json" 31 | "!package-lock.json" 32 | ] args.src; 33 | dontBuild = true; 34 | installPhase = "mkdir -p $out; cp -r ./* $out;"; 35 | }; 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /tests/versionless/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "versionless" 3 | } 4 | --------------------------------------------------------------------------------