Phoenix framework - execute NPM script in subdirectory - npm

In my Phoenix app, I am running a React frontend that sits in an assets/ directory within the app... it has it's own package.json file, and I would like to be able to run aliased NPM scripts from the Phoenix app's mix.exs file. I've been experimenting with Elixir's System.cmd(), but it doesn't seem to be doing the trick.
Basically, I've set up a lint command for my frontend - in assets/package.json, I've got
"lint": "eslint ./*.js ./js/*.js || exit 0"
and running $ npm run lint from the assets/ directory works as expected... but it would be smashing to just be able to run the command from the app's top-level.
In my mix.exs file, I was experimenting with an alias like so:
defp aliases do
[
"lint": [System.cmd("npm", ["run lint"], cd: "assets")
]
end
but running mix lint produces a pretty wordy error:
** (FunctionClauseError) no function clause matching in Mix.Task.run_alias/3
The following arguments were given to Mix.Task.run_alias/3:
# 1
[{"\nUsage: npm <command>\n\nwhere <command> is one of:\n access, adduser, bin, bugs, c, cache, completion, config,\n ddp, dedupe, deprecate, dist-tag, docs, edit, explore, get,\n help, help-search, i, init, install, install-test, it, link,\n list, ln, login, logout, ls, outdated, owner, pack, ping,\n prefix, prune, publish, rb, rebuild, repo, restart, root,\n run, run-script, s, se, search, set, shrinkwrap, star,\n stars, start, stop, t, tag, team, test, tst, un, uninstall,\n unpublish, unstar, up, update, v, version, view, whoami\n\nnpm <cmd> -h quick help on <cmd>\nnpm -l display full usage info\nnpm help <term> search for help on <term>\nnpm help npm involved overview\n\nSpecify configs in the ini-formatted file:\n /Users/user/.npmrc\nor on the command line via: npm <command> --key value\nConfig info can be viewed via: npm help config\n\nnpm#3.10.10 /Users/user/.nvm/versions/node/v6.11.1/lib/node_modules/npm\n", 1}]
# 2
[]
# 3
:ok
Attempted function clauses (showing 3 out of 3):
defp run_alias([h | t], alias_args, _res) when is_binary(h)
defp run_alias([h | t], alias_args, _res) when is_function(h, 1)
defp run_alias([], _alias_task, res)
(mix) lib/mix/task.ex:331: Mix.Task.run_alias/3
(mix) lib/mix/task.ex:266: Mix.Task.run/2
(mix) lib/mix/cli.ex:75: Mix.CLI.run_task/2
(elixir) lib/code.ex:376: Code.require_file/2
Ok so obviously I'm doing something wrong. Is it possible to execute an NPM command on a sub-directory of a Phoenix app?

Firstly, run and lint should be separate strings in the arguments list.
Secondly, the code you have will immediately run the command instead of when the alias is invoked.
You can use a "run -e ..." alias to execute arbitrary code in an alias like this:
"lint": [~s|run -e 'System.cmd("npm", ["run", "lint"], cd: "assets")'|)
(I'm using ~s|| syntax just to make it easier to type a string with both single and double quotes.)

There is also an extension you can use to do this, but unfortunately it's unable to traverse paths. But maybe it will be patched.
https://github.com/verdammelt/mix_npm

An alternative to the ~s|| notation with a cd, we can also use the --prefix flag on the npm command.
lintjs: ["cmd npm run format:ci --prefix assets"]
If using an umbrella project, define the alias in the app where it runs, and add an alias in the project root.
<root>/mix.exs
defp aliases do
[
# run `mix setup` in all child apps
setup: ["cmd mix setup"],
# app-specific alias defined in onstage_web/mix.exs
lint: ["cmd --app myapp_name mix lintjs"]
]
end
<root>/apps/myapp_name/mix.exs
defp aliases do
[
# run npm install in child app
setup: ["deps.get", "cmd npm install --prefix assets"],,
# simplified/equivalent of accepted answer
lintjs: ["cmd npm run format:ci --prefix assets"]
]
end
Now, we can run mix lint from the project root to lint the JS in a child app.
Also, remember to run mix compile after making changes.

Related

Azure pipeline : Command Line ##[error]Bash exited with code '1'

I am trying to execute my cypress test using azure pipeline but while executing job facing below issue:
yml file:
# To configure triggers for Azure CI see
# https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml#tags
jobs:
- job: Cypress_tests
pool:
vmImage: 'ubuntu-22.04'
# Runs tests in parallel https://docs.cypress.io/guides/guides/parallelization
# https://learn.microsoft.com/en-us/azure/devops/pipelines/process/phases?view=azure-devops&tabs=yaml
strategy:
parallel: 2
steps:
- task: NodeTool#0
# Caches dependencies using npm lock file as key
# https://docs.cypress.io/guides/continuous-integration/introduction#Caching
- task: CacheBeta#1
inputs:
key: npm | package-lock.json
path: /home/vsts/.npm
restoreKeys: npm | package-lock.json
- task: CacheBeta#1
inputs:
key: cy | package-lock.json
path: /home/vsts/.cache/Cypress
restoreKeys: cy | package-lock.json
- script: npm ci --prefer-offline
# Starts web server for E2E tests - replace with your own server invocation
# https://docs.cypress.io/guides/continuous-integration/introduction#Boot-your-server
- script: npm start &
- script: npx wait-on 'http-get://localhost:3000' # Waits for above
# Runs tests in parallel and records to Cypress Cloud
# https://docs.cypress.io/guides/cloud/projects#Set-up-a-project-to-record
# https://docs.cypress.io/guides/guides/parallelization
- script: npx cypress run --record --parallel --ci-build-id $BUILD_BUILDNUMBER
# For recording and parallelization to work you must set your CYPRESS_RECORD_KEY
# in Azure DevOps → Your Pipeline → Edit → Variables
env:
CYPRESS_RECORD_KEY: $(CYPRESS_RECORD_KEY)
Error:
Starting: CmdLine
==============================================================================
Task : Command line
Description : Run a command line script using Bash on Linux and macOS and cmd.exe on Windows
Version : 2.212.0
Author : Microsoft Corporation
Help : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/command-line
==============================================================================
Generating script.
Script contents:
npm ci --prefer-offline
========================== Starting Command Output ===========================
/usr/bin/bash --noprofile --norc /home/vsts/work/_temp/82c8b231-ced2-40c3-9bca-ec69d4282405.sh
Usage: npm <command>
where <command> is one of:
access, adduser, bin, bugs, c, cache, completion, config,
ddp, dedupe, deprecate, dist-tag, docs, edit, explore, get,
help, help-search, i, init, install, install-test, it, link,
list, ln, login, logout, ls, outdated, owner, pack, ping,
prefix, prune, publish, rb, rebuild, repo, restart, root,
run, run-script, s, se, search, set, shrinkwrap, star,
stars, start, stop, t, tag, team, test, tst, un, uninstall,
unpublish, unstar, up, update, v, version, view, whoami
npm <cmd> -h quick help on <cmd>
npm -l display full usage info
npm help <term> search for help on <term>
npm help npm involved overview
Specify configs in the ini-formatted file:
The NodeTool#0 without any parameters sets the active node version to 6, the ci command probably didn't exist back then. Make sure you select a recent version of Node:
versionSpec - Version Spec
string. Optional. Use when versionSource = spec. Default value: 6.x.
Specifies the version spec of the version to get. Examples: 6.x, 4.x, 6.10.0, >=6.10.0.
- task: NodeTool#0
inputs:
versionSpec: "18.x"

npm run script, can't access command line args

I'm an electron beginner, and I'm trying to use it to package up a react-based app. I'm trying to run electron via a script entry in my package.json:
"electron-dev": "concurrently \"cross-env BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron . $npm_config_input \"",
That will run electron.js (which is what "main" is defined as earlier in the package.json file), but I need to pass in a command line argument. I've seen references that indicate $npm_config_input will have the argument passed in this way:
% npm run electron-dev --input=file.tif
But that $npm_config_input doesn't seem to get expanded for me. electron.js gets the literal string $npm_config_input. I'm confused why this isn't working.
It seems I could avoid this problem by doing this:
% npm run electron-dev -- --input=file.tif
But I don't know how to associate the input argument to the second command I'm starting using concurrently. It would be nice if I could use something like $1 or $npm_config_input in its definition. Does anyone have a solution for this?
I'm running this on Windows 10 using git bash. Other things generally work. I have nodejs 12.16.2 installed. TIA!
you almost got it right, just use process.argv.
for instance, create a file named cmd.js that looks like this
console.log(process.argv.slice(2));
now create a script hook for it by adding the following to package.json
"scripts": {
"foo": "node cmd.js"
}
now you can try it...
$ npm run foo -- arg1 arg2
> foo#1.0.0 foo /tmp/foo
> node cmd.js "arg1" "arg2"
[ 'arg1', 'arg2' ]
I believe I have found the answer. In electron.js, I printed process.argv to see if there was anything of use there. It turns out process.env.npm_config_input contains file.tif when I run this:
% npm run electron-dev --input=file.tif
That should work for me. I still don't understand why the other things I read about and tried didn't work.

Pass git commit message to npm script and append to predefined string

In an NPM project, I'd like to have a commit for each build version. This will allow me to go back to the current build version, fix a bug, without having to go through all the QA of a new version.
We can commit using npm scripts like this (see this answer):
package.json
"scripts": {
"git": "git add . && git commit -m",
}
Then invoke the script by running:
npm run git -- "Message of the commit"
I'd like to automate it to run after npm run build. For this purpose we can create a new command.
package.json
"scripts": {
"buildAndCommit": "npm run build && git add . && git commit -m",
}
This could be run using npm run buildAndCommit -- "commit for a new build"
The only thing left is that I'd like to identify this commit as one that could be linked to a commit. Is it possible to start the message automatically with "BUILD -" and to add to that the unique message which is passed in the command line? Something like:
package.json
"scripts": {
"buildAndCommit": "npm run build && git add . && git commit -'Build' + $uniqueMessageFromTheCommandLine`",
}
If it is not possible to template the string in package.json, how could I achieve it using a command line script? (Powershell is my command line tool).
Running on *nix platforms
On a *nix platform npm utilizes sh by default to execute the npm script(s). In this scenario you can simply use a shell function and reference the git message argument passed via the CLI using the $1 positional parameter.
Your npm script would be redefined like this:
"scripts": {
"build": "...",
"buildAndCommit": "func() { npm run build && git add . && git commit -m \"BUILD - $1\"; }; func"
}
Cross platform
Unfortunately, via Windows Powershell the solution is not quite as simple and terse.
When using Powershell npm utilizes cmd by default to execute npm script(s). Likewise npm utilizes cmd by default via other Windows consoles too, such as Command Prompt.
One way to achieve your requirement is to invoke a node.js via your npm script. The following provides two different methods that are essentially the same. Either will run successfully cross-platform (in your case via Powershell).
Method A - Using a separate node.js script
Create the following node.js script. Let's name the file script.js and save it in the root of the project directory, i.e. in the same directory where package.json resides.
script.js
const execSync = require('child_process').execSync;
const mssg = 'BUILD - ' + process.argv[2];
execSync('npm run build && git add . && git commit -m \"' + mssg + '\"', { stdio:[0, 1, 2] });
Explanation
The node.js builtin process.argv captures the argument at index two, i.e. the git commit message, that was provided via the CLI. The git commit message is concatenated with with the substring BUILD - to form the desired commit message. The resultant string is assigned to the variable mssg.
We then utilize the node.js builtin execSync() to execute your given npm script. As you can see, the value of the mssg variable is used as the git commit message.
The stdio option is utilized to ensure the correct configuration of the pipes, i.e. stdin, stdout, 'stderr', are established between the parent and child process.
Define your npm script named buildAndCommit as follows:
package.json
"scripts": {
"build": "...",
"buildAndCommit": "node script"
}
Above node invokes script.js.
Method B - Inline the node.js script in npm script
Alternatively, the aforementioned node.js script (i.e. script.js) can be provided inline in your npm script instead - therefore negating the use of a separate .js file.
package.json
"scripts": {
"build": "...",
"buildAndCommit": "node -e \"const mssg = 'BUILD - ' + process.argv[1]; require('child_process').execSync('npm run build && git add . && git commit -m \\\"' + mssg + '\\\"', { stdio:[0, 1, 2] })\""
}
This utilizes the same code from Method A albeit it slightly refactored. The notable differences are:
The nodejs command line option -e is utilized to evaluate the inline JavaScript.
process.argv this time will capture the argument, i.e. the git commit message, at index one in the array of arguments.
Additional escaping of the double quotes is necessary, i.e. \\\"
Running the npm script
Using either Method A or Method B run the command via your CLI as desired: For instance:
$ npm run buildAndCommit -- "commit for a new build"
This will produce the following git commit message:
BUILD - commit for a new build

Why is npm passing cmd line flags directly to my script?

I'm using npm to run a build and I'm trying to override my .npmrc config options using cmd line flags. However npm insists on passing these flags directly to my script rather than reading them as config options as described in the docs. What am I doing wrong?
From the cmd line I try to build a Tizen pacakge like so:
npm run package --tizen_profile myprofile
inside my package.json I have:
"package": "tizen package -t wgt --sign $npm_package_config_tizen_profile -- .buildResult/wgt -o .buildResult/wgt"
The result from running the command is:
package: `tizen package -t wgt --sign $npm_package_config_tizen_profile -- .buildResult/wgt -o .buildResult/wgt "myprofile"`
Where it should be:
package: `tizen package -t wgt --sign "myprofile"_tizen_profile -- .buildResult/wgt -o .buildResult/wgt`
It's like npm is merely appending the cmd line argument to the script command instead of plugging it in like a variable as described in the docs: https://docs.npmjs.com/misc/config
Has there been a recent update to npm which deprecates and removes this ability? I just updated to npm 6.x but it was working this way on 5.x as well.
you can try to rewrite your script within package.json without --sign flag like:
"package": "tizen package -t wgt -- .buildResult/wgt -o .buildResult/wgt"
and then pass it when you run npm command:
npm run package -- --sign myprofile
I assume that you can change the order of arguments, because --sign myprofile now will be at the very end of your command
UPDATE
here is another way to pass variables and place them in any place in your CLI command (without using npm config set). In my package.json:
"aaa": "ls $myoptionalflag && $mycmd"
this way I can pass any flag to la command (or not to pass at all) and I can pass any CLI command as mycmd variable. So, now I can run:
myoptionalflag=-la mycmd=pwd npm run aaa
which will execute
ls -la && pwd
or
mycmd=pwd npm run aaa
which will execute
ls && pwd
I FIGURED IT OUT!
The documentation is flawed as it doesn't tell you the correct syntax for passing npm config properties. I had to use:
npm run package --mypackagename:tizen_profile="myprofile"
where mypackagename is the name property used in package.json. Also note the key value syntax is --key=value and not --key value as described in the docs. Again, --key would be packagename:key using the name specified at the top level of your package.json.

Passing argument to the middle of an npm script

Title says it all. I want to be able to pass the argument to the middle of an npm script so that I may do the following.
$ npm run deploy -- <destination-path>
In package.json
"scripts": {
"deploy": "robocopy dist <destination-path> /E /NP"
}
Is this possible without using environment variables or npm's configuration variables?
Per Passing args into run-scripts #5518 it would appear that is it not possible to pass arguments to the middle of the script.
We are not going to support passing args into the middle of the script, sorry. If you really need this, write your test command using literally any of the command line parsers that anyone uses. (Minimist, dashdash, nopt, and commander all support this just fine.)
However, an alternative to this using the npm configuration block has been documented here. My implementation then looks like this:
"name": "foo"
"config": { "destination" : "deploy" },
"scripts": { "deploy": "robocopy dist %npm_package_config_destination% /E /NP" }
I can then override this on the command line and on my build server with:
npm run deploy --foo:destination=C:\path\to\deploy\dir
You can use an environment variable to set the destination path.
PATH=/path/to/file npm run deploy -- $PATH
or
export PATH=/path/to/file
npm run deploy -- $PATH
I have a different way to do that via shell.
If your npm command is:
"deploy": "robocopy dist ${1} /E /NP"
Where ${1} is the parameter you want to substitute.
Then wrap it in a function as follow:
"deploy": "func() { robocopy dist ${1} /E /NP";}; func"
then you can run a positional parameter substitution in shell as follow:
npm run deploy -- xyz
which would run
robocopy dist xyz /E /NP
And since this is a shell script, you can use default parameters as well:
"deploy": "func() { robocopy dist ${1:-xyz} /E /NP";}; func"
And you can use it as follows:
npm run deploy <==> robocopy dist xyz /E /NP
npm run deploy -- abc <==> robocopy dist abc /E /NP
You can make use of the arg structure of "sh -c". In my example below, I have to echo-feed the npm arg into a language parser. The argument for npm run foma <some word> will be in place of the $0:
"sayhello": "bash -c 'echo hello $0!"
A cross-platform solution I use is:
"arg-helper": "node -e \"process.stdout.write(require('child_process').execSync(process.argv[1].replace('$', process.argv[2] || '')))\"",
"sayhello": "npm run arg-helper \"echo hello $!\"
...
>npm run sayhello world
Levereging npm_config_* variables.
package.json
{
"scripts": {
"echoMyParam": "echo 'your param value is' $npm_config_foo"
}
}
Run
npm run echoMyParam --foo=bar
Result
your param value is bar
It's important to check the docs for other cases: https://docs.npmjs.com/using-npm/config