Yarn script fails when invoking other Yarn-scripts - npm

I've got an issue when invoking other Yarn scripts from a Yarn-script. What I want to achieve is having one yarn develop task that first invokes TypeScript and some other preprocessors before starting a couple of watchers and running my application. I'd like to use npm-run-all (or something similar) to invoke multiple targets as that would allow me to define separate targets that I can manually run as well without having duplicate script-entries in my package.json.
I've made a minimal reproduction case which has the following package.json:
{
"name": "test",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"task:one": "echo one",
"task:two": "echo two",
"run-all": "npm-run-all task:one task:two",
"run-all-yarn": "yarn task:one && yarn task:two"
},
"devDependencies": {
"npm-run-all": "^4.1.5"
}
}
Invoking yarn task:one results in an echo one as expected, as does yarn task:two. If I invoke yarn run-all though, I get an error:
$-> yarn run-all
yarn run v1.22.10
$ npm-run-all task:one task:two
$ echo one
error Couldn't find the binary echo one
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
ERROR: "task:one" exited with 1.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
yarn run-all-yarn fails with (almost exactly, sans the command being executed on the second line) the same error.
If instead of Yarn I use NPM (npm run run-all) it all works as expected:
$-> npm run run-all
> test#1.0.0 run-all /Users/basdalenoord/Projects/52DN/psyflix/test
> npm-run-all task:one task:two
> test#1.0.0 task:one /Users/basdalenoord/Projects/52DN/psyflix/test
> echo one
one
> test#1.0.0 task:two /Users/basdalenoord/Projects/52DN/psyflix/test
> echo two
two
I'm using Yarn version 1.22.17 provided by my node-version from nvm: ~/.nvm/versions/node/v14.18.1/bin/yarn
I'm a bit lost as to why running individual targets would be fine but combining them fails for yarn, whereas the same script works for npm.

Related

husky / lint-staged not running custom command or printing errors

I am trying to upgrade from husky#4.8 and lint-staged#11.2 to husky#8.0 and lint-staged##13.1 for my Nx repo. It seems that the only configuration I used to need was the following in package.json:
"scripts: {
"lint:fix": "ng lint -- --fix && npm run prettier:fix",
}
"husky": {
"hooks": {
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{ts,js,css,md,html}": "npm run lint:fix",
"relative": true
}
This no longer works with the new husky and lint-staged. I followed this tutorial and installed husky: npx husky-init && npm install, installed lint-staged: npm install --save-dev lint-staged, modified the .husky/pre-commit file:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
and added the .lintstagedrc.json file:
{
"*.ts": [
"prettier --write",
"eslint"
],
"*.html": [
"eslint",
"prettier --write"
],
"*.scss": "prettier --write"
}
When I make a new commit it seems to run. However it looks like this command either runs on every library or every staged file (I have a lot of libraries). When I change the file contents to:
{
"*.{js,ts,json,scss,md}": ["npm run lint:fix"]
}
it takes a while to run (though it seems to run and fix the linting) AND I don't get the output of my ng lint -- --fix command. If I go into the .husky/pre-commit file and change npx lint-staged to npm run lint:fix, it runs the ng lint -- --fix command, fixes what it can and prints out linting errors. However, the error doesn't prevent the commit and I have to commit any new changes that the linter fixed. With the old versions I didn't need to do this. Also I didn't need to commit a .husky/pre-commit file into my repo. (I see some workarounds that have a postinstall script that generates this file for the user).
How can I utilize lint-stage and husky newest versions to run my custom npm command so I can call ng lint like in the older versions and block commits on errors? Should I just use the older versions? Also what benefit do the newer versions provide?

Lint-staged aborts check with the first failing file; "Error: Unknown argument"

With "lint-staged": "^13.0.3" and the following .lintstagedrc config:
{
"src/**/*.{ts,html}": [
"ng lint"
],
"src/**/*.scss": [
"npx stylelint **/*.scss"
]
}
I'm trying to run npx lint-staged -q on the following files (all of them have lint problems that would come up with the individual lint command):
Running ng lint and npx stylelint **/*.scss directly will bring up the problems (detailed) and would not fail on the first file:
ng lint
npx stylelint **/*.scss
I'd like lint-staged's output to show all of the problems from the individual lint commands. What's wrong here?
Thanks
Ok, after further investigation;
The partial output problems seem to be introduced to lint-staged on versions >= 12.2.0, see on github. I've downgraded to "lint-staged": "12.1.7" and the problem seems to be gone.
Regarding the Unknown argument thing, it resolves by calling eslint instead of ng lint on the configuration file.

trouble with npm preinstall script

I'm trying to make the jump to pnpm from npm. I found a helpful hint to keep from running "npm install" after I make the change as described here: https://pnpm.js.org/en/only-allow-pnpm
Unfortunately my preinstall lifecycle override doesn't get executed. Seems to simple enough but npm install still works when I run something like "npm install #types/jest"
package.json:
{
"name": "react-sandbox",
"version": "0.1.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
npm version 6.14.2.
Any ideas?
Unfortunately, the preinstall script is executed only during argumentless installation. So when you run npm add #types/jest, that script will not be executed, thus npm won't be prevented from running.
But it will fail when running npm install.
As of now, there is no other way to prevent npm from execution.

Npm: Run postinstall script only when installed as dependency

I developed a npm package ("node_commons") which is included in my other project like this:
package.json (other project)
"node-commons": "git+ssh://git#stash.custom.domain.net:7999/npm/libs/node_commons.git"
The node_commons package is written in ES6 but this version is not supported later, therefore I use a postinstall script to transpile it with babel.
package.json (node_commons)
"postinstall": "babel src -d src"
This works fine. When the package is included as dependency in my project, the files are transpiled.
My problem: When I develop the node_commons package I use npm install to install the internal dependencies. But then I do not want to transpile it. I only want to transpile, when the package is installed as dependency (e.g. in my other project). Is there a way to do this?
Something like this:
package.json (node_commons)
"postinstall-as-dependency": "babel src -d src"
Create .no-postinstall file in module root.
Mac & Linux: Add following to package.json: "postinstall": "if [ ! -e .no-postinstall ]; then babel src -d src; fi"
Windows: Add following to package.json: "postinstall": if exist .no-postinstall () else (babel src -d src)"
Cross Platform (more work):
You can create a node script (i.e. ./my-script.js) and using fs module, check existence of .no-postinstall and execute babel src -d src using child_process.exec() as described in official doc here. Add your script to package.json: "postinstall": node my-script.js
Please note that I use Mac, so I can't verify Windows version.
Explanation
if [ ! -e .no-postinstall ] checks non-existence (with negation operator !) of given file. If file does not exist, it executes your script. Since you add .no-postinstall file to your module root, script does not get executed when you install your internal modules. On the other hand, modules installing your module as a dependency do not have .no-postinstall file in their root and your script get executed.
.no-postinstall is not a special name, you can use whatever name you choose.
In my case, I had a postinstall script that I only wanted to run when the package was NOT installed as a dependency.
Here is the package.json script I used:
{
[...]
"scripts": {
"#comment_postinstall": "Behavior of the node-js subscript below: if '.git' doesn't exist under my-lib, it's installed as a dep, so do nothing -- else, return error-code 1, causing patch-package to run.",
"postinstall": "node -e \"let myLibAsDep = !require('fs').existsSync('.git'); if (!myLibAsDep) process.exit(1);\" || patch-package"
},
[...]
}
You can of course just reverse the if (!myLibAsDep) if you want your script to run only when installed as a dependency.
If I understand correctly, you want your package to run a postinstall script only if user install it as a dependency (npm install node-common)?
When your postinstall script runs, it has the npm_config_save_dev available to it, which is 'true' when users install the package with the --save-dev flag:
"postinstall": "! [ $npm_config_save_dev ] && echo \"Installed as a dependency\" || \"Installed as a dev dependency\""
I had the same issue, I had a postinstall script which I wanted to run only when the package is installed as a dependency.
At the top of my postinstall script I added:
if (!process.cwd().includes('node_modules')) {
return;
}
and then it only ran when the module existed under a node_modules dir.
This should do the trick:
if (process.env.INIT_CWD === process.cwd())
process.exit()
Works with npm and pnpm. Don't know about yarn
https://docs.npmjs.com/cli/v8/using-npm/scripts#best-practices
I created a package to solve this problem, inspired by the answer from #venryx.
https://github.com/douglasjunior/ignore-dependency-scripts
Usage
Replace this:
// package.json
"name": "my-library",
"scripts:" {
// "start", "test", "build", etc
"postinstall/preinstall/prepare/etc": "your && scripts && here"
},
With this:
// package.json
"name": "my-library",
"scripts:" {
// "start", "test", "build", etc
"postinstall/preinstall/prepare/etc": "npx --yes ignore-dependency-scripts \"your && scripts && here\""
},
Replace your && scripts && here by any post/pre install script that you want, like husky install, npx pod-install or both.
Now, when you run yarn install or npm install in ./my-library the your && scripts && here will run normally.
But, when you install my-library as a dependency (aka yarn add url/to/my-library.git) in another repository, the your && scripts && here will be ignored.

Pass command line args to npm scripts in package.json

I have the below scripts in my package.json:
"scripts": {
"vumper": "node node_modules/vumper/index.js",
"format": "prettier --single-quote -width=80 --write package.json"
},
The 'vumper' package takes in a command line argument (such as 'dv'). What I would like to be able to do is have a command that runs both of these in succession.
Essentially, I would like to be able to run:
npm run vumber dv
and then
npm run format
but in one command, something like
npm run my-build dv
which would run both of the above commands, correctly accepting the command line argument 'dv' and passing it to the first npm run vumper. Is this possible?
Short Answer:
Essentially, what you're wanting is to have an npm-script something like this, whereby <arg-here> is provide via the CLI;
...
"scripts": {
"my-build": "npm run vumper <arg-here> && npm run format",
...
},
...
However, unfortunately npm does not have a built-in feature to achieve this.
The special npm option --, (refer to the end of Solution 1 below for further info about this option), can only be used to pass an argument to the END of a script but NOT into the MIDDLE. So, if your two commands were in the opposite order, the -- option could be used like this:
...
"scripts": {
"my-build": "npm run format && npm run vumper --",
...
},
...
To overcome the limitation of there being no built-in feature to pass an argument into the MIDDLE of a script consider the following solutions:
For a Bash only solution refer to the "Solution 1" section.
If cross platform support is required then follow the solution described in the "Solution 2" section.
Solution 1 - Bash (MacOS/Linux/ etc..):
Configure your my-build script in the scripts section of package.json to invoke a Bash shell function, as shown below:
package.json
...
"scripts": {
"my-build": "func() { npm run vumper \"$1\" && npm run format; }; func",
"vumper": "node node_modules/vumper/index.js",
"format": "prettier --single-quote -width=80 --write package.json"
},
...
Explanation:
The Bash function named func does the following:
Firstly runs npm run vumper <arg>. Whereby <arg> will be the shell argument passed via the CLI. It is referenced in the script using $1 (i.e. the first positional parameter/argument).
Subsequently it runs the script named format via the command npm run format.
These two npm run commands are chained using the && operator, so the second npm run format command will only run if the initial npm run vumper <arg> command completes successfully (i.e. returns a 0 exit code).
Running my-build script:
To invoke my-build via your CLI you'll need to run:
npm run my-build -- dv
Note:
In this instance the trailing dv part is the argument that will be passed to your vumper script.
The special option -- must be specified before the argument. The docs describe the -- option as:
... The special option -- is used by getopt to delimit the end of the options. npm will pass all the arguments after the -- directly to your script: ... The arguments will only be passed to the script specified after npm run and not to any pre or post script.
Solution 2 - Cross-platform:
For a cross-platform solution, (one which works successfully with Bash, Windows Command Prompt / cmd.exe, and PowerShell etc..), you'll need to utilize a nodejs helper script as follows.
run.js
Let's name the nodejs script run.js and save it in the projects root directory, at the same level as package.json.
const execSync = require('child_process').execSync;
const arg = process.argv[2] || 'dv'; // Default value `dv` if no args provided via CLI.
execSync('npm run vumper ' + arg, {stdio:[0, 1, 2]});
execSync('npm run format', {stdio:[0, 1, 2]});
package.json
Configure your my-build script to invoke run.js as follows:
...
"scripts": {
"my-build": "node run",
"vumper": "node node_modules/vumper/index.js",
"format": "prettier --single-quote -width=80 --write package.json"
},
...
Running my-build script:
As per Solution 1, to invoke my-build via your CLI you'll need to run:
npm run my-build -- dv
Explanation:
run.js utilizes process.argv to obtain the argument passed via the CLI (e.g. dv). If no argument is provided when running npm run my-build the default value, (i.e. dv), is passed to the vumper npm-script.
run.js also utilizes child_process.execSync(...) to shell-out/invoke the two npm run commands.
Npm now has a built-in option to pass cli arguments directly to scripts.
The cli arguments are stored in environmenet variables with prefix npm_config_<flagname>, and they required a very strict syntax, with the form --<flagname>=<flagvalue>.
Example:
"my-build": "npm run vumper %npm_config_myflag% && npm run format",
In the terminal, run npm run my-build --myflag=my_value to execute npm run vumper my_value && npm run format.
Note:
To refer the environment variable in the npm script, you have to use the platform specific syntax, ie %npm_config_myflag% in Windows or $npm_config_myflag in Linux.
UPDATE:
To avoid risks of conflict with the npm_config variables used to configure npm itself, just prefix your arguments with a unique prefix, such as the name of your app.
The potential conflict is a very common problem, which applies in many contexts: any application could use environment variables already used by other applications; for this reason, the environment variables are usually prefixed with the name of the application (eg NVM_HOME, JAVA_HOME). But this potential conflict is not a good reason to avoid using environment variables. The same in my opinion applies to npm params / npm_config env vars. The doc does not say anything about the risk of conflicts, implying I guess they should be managed as usual.
My preferred method is by using environment variables:
{
"scripts": {
"ncc-build": "ncc build $ACTION/src/index.ts -o $ACTION/dist",
"build:pr-changelog": "ACTION=pr-changelog npm run ncc-build",
}
}
It should work in UNIX systems. I'm not sure about windows platfrom compatibility though.
a different approach for doing this - to reach super deep into you dependency chain:
npm scripts section:
"test:local": "cross-env-shell UPDATE_BASELINE=false UPDATE_MODULE=%npm_config_vizdifsingle% run-p koa:ci wdio:local",
"test:remote": "cross-env-shell UPDATE_BASELINE=false UPDATE_MODULE=%npm_config_vizdifsingle% run-p localtunnel:start koa:ci wdio:remote"
by using crossenv and npm's value placement you can pass args to env.args
like this:
npm run test:local --vizdifsingle=some,value,or,values
it will be available to you in
process.env.npm_config_update_module