Pass environment variable through PM2 to NextJS - shell

I have 2 package.json scripts that look like this:
"start": "next start -p $PORT",
"pm2_staging": "pm2 restart ecosystem.config.js --env staging",
And an ecosystem.config.js that looks like this:
module.exports = {
apps: [
{
name: 'test.co.uk',
script: 'npm',
args: 'start',
env_staging: {
API: 'staging',
NODE_ENV: 'production',
PORT: 3001,
},
},
],
};
I then run the following:
TEST_VAR='test' npm run pm2_staging
I would expect the following to happen:
The PM2 restart command fires
ecosystem.config.js fires the npm start command and sets some environment variables
The app starts and all env vars are available, including TEST_VAR (set in the original command)
What actually happens is all the env vars from the ecosystem are correctly set, but TEST_VAR is not available in the app. Why is this and how do I go about setting secret keys from CI tools if I can't do this?

I ran into the same problem tonight. After looking every where I found the config needed. The env variable needs to go in your ecosystem.config.js like below. In my case I put it at the root of my server
module.exports = {
apps : [{
name: 'nextjs',
script: 'yarn',
args:"start",
cwd:"/var/www/myapp/",
instances: 2,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
API_URL: 'YOUR ENV URL',
PORT:8000
}
}]
};
package.json like so
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start -p 8000"
},
...
and executed something like this
#!/bin/bash
cd /var/www/myapp/
git pull && yarn install && yarn build
cd ~/
pm2 start ecosystem.config.js --env production
Then the API_URL would be available in const API_URL = process.env.API_URL in your app.
These urls helped
https://willandskill.se/en/setup-a-next-js-project-with-pm2-nginx-and-yarn-on-ubuntu-18-04/
https://pm2.keymetrics.io/docs/usage/application-declaration/

Another idea would be passing all the env parameters of PM2 to the server via the following.
ecosystem.config.js being:
module.exports = {
apps: [{
name: "my-service-1",
script: "dist/server.js",
env_production: process.env, // pass all the env params of the container to node
}],
};

#webface
The environment variables in your example will not be available in Nextjs. To be available to both the client and server, variables must be prefixed with NEXT_PUBLIC (ie. NEXT_PUBLIC_API_VERSION).
These environment variables must be passed into the build process to be available at runtime.

Related

Vercel/NextJS: How to access serverless functions from frontend during local development?

My React/NextJS front end has a Button component that fetches data via a serverless function when the button is clicked. I want to test this functionality during local development with the Vercel dev/CLI tools. I am getting a 404 result when attempting to access my lambda functions. Here are the approximate steps that I've gone through so far:
Create package.json with a dev script:
...
"scripts": {
"dev": "yarn codegen && next --hostname=0.0.0.0 --port=3001",
}
...
Link to deployed vercel project
Create vercel.json to specify builds and routes:
...
"builds": [
{ "src": "*.html", "use": "#now/static" },
{ "src": "pages/api/*.py", "use": "#now/python" },
],
"routes": [
{ "src": "/api/validate", "dest": "/pages/api/validate.py" }
]
...
Create my test Lambda function (in python):
from http.server import BaseHTTPRequestHandler
from datetime import datetime
class handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')).encode())
return
Create my Button component:
...
<Button
variant="contained"
color="secondary"
onClick={() => {
fetch('api/validate')
.then(response => { console.log(response)
response.json() })
.then(data => console.log(data))
}}
>
Generate sample dataset
</Button>
...
Run vercel dev
Access website at localhost:3001 (next dev server address)
Click button
Result:
I'm receiving a 404 response
Note: I can access the lambda function from localhost:3000/pages/api/validate.py (vercel dev server address). This appears to manually kickstart the lambda function build and serve process. I thought that it should have been built and served already from the vercel.json specification and be available at localhost:3001/api/validate. This seems to agree with the Vercel documentation.
Note 2: Next dev/CLI tools build and serve javascript/typescript files just fine. I'm using python and Go functions as well, which are supported by Vercel dev/CLI but not Next
My solution was to use vercel dev instead of next dev or yarn dev, and to use an environment variable in a .env file that points to the function url. This env variable should be prepended with NEXT_PUBLIC_ so that it is registered by next.js and passed to process.env during the build process.
# .env
NEXT_PUBLIC_FUNCTIONS_BASE_URL="http://localhost:3000" # 3000 is vercel port
# component.js
...
fetch(process.env.NEXT_PUBLIC_FUNCTIONS_BASE_URL + '/api/function-name')
...
You need to pass the port from vercel dev to the upstream CLI, in this case next dev.
{
"scripts": {
"dev": "yarn codegen && next dev --port=$PORT",
}
}
Now when you run vercel dev, the ephemeral port will be proxied from the dev server.
You can also remove vercel.json if you rename /pages/api to /api.

How to pass `--` when used with pre npm scripts

How to pass others flags into npm command that has pre config
"prebuild": "npm run build:vendor",
"build": "cross-env NODE_ENV=production webpack --env.production -p",
When I run npm run build -- --env.produciton the flag --env.produciton does not work
I want to pass into webpack command.. ending like this
cross-env NODE_ENV=production webpack --env.production -p --env.production
two options to pass params, one over node cross-env:
"build": "cross-env NODE_ENV=production YOUR_ENV=yourName webpack -p"
if (process.env.YOUR_ENV === 'yourName') { }
the other with webpack:
"build": "webpack --env.NODE_ENV=local --env.YOUR_ENV yourName --progress"
const path = require('path');
module.exports = env => {
// Use env.YOUR_ENV here:
console.log('YOUR_ENV: ', env.YOUR_ENV); // 'yourName'
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
};
The param is always true if you won't set value.
https://webpack.js.org/guides/environment-variables/
Setting up your env variable without assignment, --env.production sets --env.production to true by default..

Heroku Deployment Error: displaying backend, not frontend

I am trying to deploy my local MERN application to Heroku. Application works offline. After deployment, when click "Open app", all I see is the data from the backend. Not the front end. Deployment here: https://whispering-falls-45660.herokuapp.com/.
Set up new Heroku project, and after successful "git push Heroku master", application only shows backend data. Heroku CLI version: heroku/7.24.1, Node version: v10.13.0.
Github repo: https://github.com/neilhsieh/whereToEat
Package.json file has the proper scripts in place as per Brad Traversy:
"scripts": {
"start": "node server.js",
"server": "nodemon server.js",
"client": "npm start --prefix client",
"dev": "concurrently \"npm run server\" \"npm run client\"",
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
},
server.js code has appropriate code to point to client build file:
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')) // relative path
})
}
Expecting front end deployed on Heroku, ended up only having backend.
UPDATE: Moved process.env.NODE_ENV test to before all API calls, this fixed my issue.
First if you have a route like this in your server/app.js file
app.get("/", (req, res) => res.send("Api Running"));
Definitely get rid of that
Second add these lines in your server/app.js file
const path = require("path");
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
});
}

Protractor passing parameters in script run command

I need to pass the credentials in command running a script.
For now, I am using in protractor file following part:
onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
if (browser.params.Url == 'http://devel/') {
browser.params.webmaster='abc';
browser.params.webmaspass='foo';
}
//(other environments)
else {
console.log('-------------error during log in');
}*/
}
and it was working fine, but I need to change it - I can't pass credentials in this way. I thought about changing it to:
if (browser.params.Url == 'http://devel/') {
browser.params.webmaster='';
browser.params.webmaspass='';
}
and run the script using
npm run dev-script --browser.params.Url='http://devel/' --browser.params.webmaster='abc' --browser.params.webmaspass='foo'
where package.json I have:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev-script": "protractor --params.Url=http://devel/ --browser.params.webmaster='' --browser.params.webmaspass=''"
},
(or any variation) But it fails - I can't update params during running script, I need to write down the credentials in the code (which I find a little unsafe)
I found issues like Protractor needs password for login => insecure? but it about Google Auth problems
Any idea?
You need to remove the variable assignment in the onPrepare. You are overwriting what you are passing in from the command line by setting it to an empty string.
When you pass them in from the command line they will be availble on the params object. There is no need to set them again in your onPrepare. Add a console.log() in your onPrepare and you will see.
Run it from the command line like this: protractor conf.js --params.webmaster=abc --params.webmaspass=foo --params.url=http://devel/
Again, if you log them in your onPrepare you will see that it is working. The way you currently have it you are just overwriting the values you are passing in through the command line.
onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
if (browser.params.Url == 'http://devel/') {
consoel.log(browser.params.webmaster) //should be abc
console.log(browser.params.webmaspass) //should be foo
}
//(other environments)
else {
console.log('-------------error during log in');
}*/
}
Another way you can do this is to set some environment variables before your test run and then you can access them in your scripts by using process.env.envVariableName or ${envVariableName}. Both ways will work.
set DEVEL_WEBMASTER=abc
set DEVEL_WEBMASPASS=foo
onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
if (browser.params.Url == 'http://devel/') {
browser.params.webmaster=process.env.DEVEL_WEBMASTER;
browser.params.webmaspass=process.env.DEVEL_WEBMASPASS;
}
//(other environments)
else {
console.log('-------------error during log in');
}*/
}
Just remember that if you use this method you would have to set the variables for each session. If you are planning to automate these tests using a CI environment you can just add them there as secret variables (if you have that option) and they will always be there ready and waiting. There will be no need to set them manually during each build.
What I did it here was create the scripts in my package.json:
scripts: {
"automation-test": "concurrently --raw --kill-others \"./node_modules/.bin/webdriver-manager start\" \"sleep 5 && ./node_modules/.bin/protractor configuration/protractor.config.js\"",
"automation:pending": "TAGS=#pending npm run automation-test"
}
And in my protractor.conf.js I just assign the value to a variable so I can use in my config. Like this:
let tags = process.env.TAGS;
Then the command that I run is just this:
npm run automation:pending
but I could pass the TAGS like this as well:
npm run automation-test TAGS=#pending
I have not seen the configuration file on the parameters of the command line. you must specify the configuration file:
example: protractor config.js --params ......
Do this in your script file: i have added a config file after the command protractor
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev-script": "protractor config.js --params.Url=http://devel/ --browser.params.webmaster='' --browser.params.webmaspass=''"
},

Dev dependencies needed for Heroku webpack build

I was under the impression that I wouldn't need the devDependencies to be included when I deploy to Heroku a react based app, using Webpack. For example, here's my package.
"scripts": {
"test": "",
"start": "./node_modules/.bin/babel-node server",
"build": "rimraf dist && export NODE_ENV=production && webpack --config ./webpack.production.config.js --progress --profile --colors",
"postinstall": "node deploy",
"eslint": "eslint .",
"jscs": "jscs ."
},
and deploy.js:
if (process.env.NODE_ENV === 'production') {
var child_process = require('child_process');
child_process.exec("webpack -p --config webpack.production.config.js", function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
}
and the Procfile
web ./node_modules/.bin/babel-node server.js
However, when I push to Heroku, I'm constantly getting a webpack command not recognized, so I included all of the devDependencies as normal dependencies to have it working. Am I missing something here?
Heroku set NPM_CONFIG_PRODUCTION to true by default to install dependencies only. If you would like to install devDependencies, you can disable production mode:
$ heroku config:set NPM_CONFIG_PRODUCTION=false
However, since you usually don’t want all devDependencies in your production builds, it’s preferable to move only the dependencies you actually need for production builds into dependencies (bower, grunt, gulp, etc).

Resources