How can I dynamically edit a .env file from an npm script? - javascript

I've got a .env file for a project that I'm working on, I won't reveal the whole file because it contains sensitive data, but, in that file I have an item called 'STATUS'.
NOTE: This is for a Discord bot,
The 'STATUS' variable looks like this: STATUS=DEVELOPMENT
In my code I have a handler that deploys commands to all servers or just my specific server relative to the value of 'STATUS'.
example:
if STATUS was equal to DEVELOPMENT than it would deploy the commands to the development server and if it was equal to PRODUCTION then it would deploy the commands to all of the servers the bot is in.
That code looks something like this:
if (STATUS == "DEVELOPMENT") {
Routes.applicationGuildCommands(CLIENT_ID, process.env.DEVELOPMENT_GUILD_ID),
{ body: slashCommands },
console.log(
chalk.yellow(`Slash Commands • Registered Locally to
the development server`)
);
} else if (STATUS == "PRODUCTION") {
await rest.put(
Routes.applicationCommands(CLIENT_ID),
{ body: slashCommands },
console.log(chalk.yellow("Slash Commands • Registered Globally"))
);
}
In my package.json file I would like to have two scripts that control if the commands get pushed to production or development.
example:
"scripts": {
"prod": "changes the 'STATUS' to 'PRODUCTION' and runs the file",
"dev": "changes the 'STATUS' to 'DEVELOPMENT' and runs the file"
},

You can just create a simple utility JS script to do the work:
// status.js
const fs = require("fs")
const path = require("path")
// get the first argument passed to this file:
const status = process.argv[3] // the first and second element will always be `node` and `filename.js`, respectively
if(!status) {
throw new Error("You must supply a status to change to")
}
const envPath = path.join(process.cwd(), ".env") // resolve to the directory calling this script
// parse the environment variable file
let env = fs.readFileSync(envPath)
env = env.split(/\r?\n/g) // optional linefeed character
let prevExists = false
for(let lineNumber in env) {
if(env[lineNumber].startsWith("STATUS=")) {
prevExists = true
env[lineNumber] = `STATUS=${status}`
break
}
}
if(!prevExists) env.push(`STATUS=${status}`)
const newEnv = env.join("\n")
fs.writeFileSync(envPath, newEnv)
console.log(`Successfully changed the status to "${status}"`)
Then in your package.json, you can put the following:
"scripts": {
"prod": "node status.js PRODUCTION && command to deploy server",
"dev": "node status.js DEVELOPMENT && command to deploy server"
}

Related

starting a next server on cpanel throwing 503 service unavailable

I'm attempting to deploy a NextJS app on my shared hosting server using the cPanel Setup Node.JS App section, but when I start the build - despite getting ready on http://localhost:3000 - the site throws a 503 error.
I've uploaded the build folder alongside the next.config.js, package-lock.json, package.json and server.js to the application root, and this is my current file structure:
next_main
build (.next folder)
node_modules
next.config.js
package-lock.json
package.json
server.js
This is my server.js file (exactly the same as what Next provided in their custom server docs):
const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const hostname = "localhost";
const port = 3000;
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer(async (request, response) => {
try{
const parsedURL = parse(request.url, true);
const { pathname, query } = parsedURL;
switch(pathname){
case "/a":
case "/b":
await app.render(request, response, pathname, query);
break;
default:
await handle(request, response, parsedURL);
}
} catch(error){
console.error("Error occurred.", request.url, error);
response.statusCode = 500;
response.end("Internal server error.");
}
}).listen(port, error => {
if(error) throw error;
console.log(`> Ready on http://${hostname}:${port}`);
});
}).catch(error => {
if(error) throw error;
});
Failed to load next.config.js was also output in my stderr file, despite next.config.js being provided.
I've attached the current settings I have applied in my cPanel.
Please note that I do not have root access to the terminal, and am restricted to the next_main environment when running any NPM scripts.
Make sure you add all environmental variables in the .env file. add your variable here

How to set basePath for a static exported NextJS app

I need to build and deploy a React / NextJS app to a Weblogic J2ee server with a specific context. I have some React experience, but taking my first steps with NextJS.
Currently the build/verification steps are;
Create a vanilla NextJS app
Add a next.config.js with a module.export to change the basepath
module.exports = {
basePath: '/test'
}
Execute npm run dev the application is available on 'http://localhost:3000/test'
Add an export script to the package.json "export": "next build && next export" to support static export
Add the export below to resolve issue 21079
//https://github.com/vercel/next.js/issues/21079
module.exports = {
images: {
loader: "imgix",
path: "",
}
}
Executed npm run export to create a static HTML export. Export is completed successfully to the out folder.
When inspecting the index.html in the out folder, all references to the static content still starts with /_next/static and not with /test/_next/static.
So this can be a misinterpretation of my side, please correct me if i am wrong here.
To be able to test the vanilla app on the J2EE applicationserver it has to be packed into a war file. To accomplish this i added the file warpack/warpack.ts to the project.
const fs = require('fs');
const archiver = require('archiver');
const rimraf = require('rimraf') ;
const distFolder = 'dist' ;
const warFile = distFolder + '/test.war';
const buildFolder = 'out';
const contextRoot = 'test';
// Destroy dist folder
rimraf(distFolder, (error) => {
if (!error) {
// Create dist folder
if (!fs.existsSync(distFolder)){
fs.mkdirSync(distFolder);
}
const output = fs.createWriteStream(warFile);
const archive = archiver('zip', {});
output.on('close', () => {
console.log('war (' + warFile + ') ' + archive.pointer() + ' total bytes');
});
// write archive to output file
archive.pipe(output);
// add build folder to archive
archive.directory(buildFolder,'');
// add weblogic.xml
const weblogicXML = '<?xml version="1.0" encoding="UTF-8"?><weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.2/weblogic-web-app.xsd"><weblogic-version>10.3.6</weblogic-version><context-root>' + contextRoot '</context-root><description>Test NextJS</description></weblogic-web-app>'
archive.append(weblogicXML,{ name: 'WEB-INF/weblogic.xml' });
const manifestMF = 'Manifest-Version: 1.0\nBuild-Tag: 0.0.1-SNAPSHOT\nWeblogic-Application-Version: 0.0.1-SNAPSHOT';
archive.append(manifestMF,{ name: 'META-INF/MANIFEST.MF' });
archive.finalize();
} else {
console.log('Failed to delete "' + distFolder + '" folder.') ;
process.exit(1);
};
});
Installed the required packages for webpack.ts
npm install fs --save-dev
npm install rimraf --save-dev
npm install archiver --save-dev
Added the script "warpack": "next build && next export && node warpack/warpack.ts" to build, export and pack the static app to an war.
After deployment of the war-file the page can be loaded on http://something/test but shows an empty page.
Network development tools indicate that the requests are made to the root of the application server, not to the configured basepath.
GET http://host:8001/static/css/main.09371e9d.chunk.css net::ERR_ABORTED 404 (Not Found)
GET http://host/static/js/2.0850eeb7.chunk.js net::ERR_ABORTED 404 (Not Found)
GET http://host/static/js/main.dc0c945b.chunk.js net::ERR_ABORTED 404 (Not Found)
​
Too much focus on basePath value instead on correct syntax of next.config.js.
Second module export in next.config.js overwrote first.
Wrong
module.exports = {
basePath: '/test'
}
//https://github.com/vercel/next.js/issues/21079
module.exports = {
images: {
loader: "imgix",
path: "",
}
}
Correct
module.exports = {
basePath: '/test',
assetPrefix: "/test/",
//https://github.com/vercel/next.js/issues/21079
images: {
loader: "imgix",
path: ""
}
}
You can use env check to invoke only for prod environment if you wish to like:
module.exports = {
basePath: "/test"
assetPrefix: process.env.NODE_ENV === "production" ? "/test/" : undefined,
}

Winston Logging to File is Delayed, Logging to Console is Not (Node.js)

I have a Node.js app that runs shell commands using execSync and uses winston to log messages to both Console and File transports.
Problem: By tailing the log file, we can see that the message being logged to console is not being logged immediately to file until the script has finished running.
Is there a way to get Winston flushing the logs immediately to file?
Using node 12.14.1, winston 3.2.1, Ubuntu 18.04.3
In the middle of the run
(Left) tail -f logs/test.log
(Right) node test.js
End of the run
(Left) tail -f logs/test.log
(Right) node test.js
Nodejs Code
const winston = require('winston');
const { execSync } = require('child_process');
const options = {
file: {
level: 'debug',
filename: `./logs/test.log`,
},
console: {
level: 'debug'
}
};
let logger = winston.createLogger({
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console),
]
});
logger.info('First log message')
for (const name of ['jack','ryan','tom']) {
const output = execSync('sleep 1'); // simulate a command that does some work
logger.debug(name)
}

Strapi custom routes to redirect to public directory

I deployed my react app to /public directory in strapi, everything work's correctly but, when I refreshed page, strapi override my react-router routs.
So... how can I redirect strapi to open public directory when i use specific routs?
e.g redirect /posts to public directory?
Strapi /public folder is here to server public assets and not to host your front end application. And it's not a good practice to do that.
I had to write that before answering your question.
Here is how static files are served.
https://github.com/strapi/strapi/blob/master/packages/strapi/lib/middlewares/public/index.js
It uses the public middleware.
So you will have to create your own middleware by following this documentation.
https://strapi.io/documentation/3.x.x/advanced/middlewares.html#custom-middlewares
So in ./middelwares/custom/index.js add the following code:
const path = require('path');
module.exports = strapi => {
return {
initialize: function(cb) {
strapi.router.route({
method: 'GET',
path: '/post',
handler: [
async (ctx, next) => {
ctx.url = path.basename(`${ctx.url}/index.html`);
await next();
},
strapi.koaMiddlewares.static(strapi.config.middleware.settings.public.path || strapi.config.paths.static, {
maxage: strapi.config.middleware.settings.public.maxAge,
defer: true
})
]
});
cb();
}
};
};
Then you will have to enable your middleware.
You will have to update the ./config/custom.json file with the following code:
{
"myCustomConfiguration": "This configuration is accessible through strapi.config.myCustomConfiguration",
"custom": {
"enabled": true
}
}
That's it!
I build my Strapi and CRA (create-react-app) at the build time, and says I want to mount my react app under /dashboard path.
and the file structure is:
yourapp/
└── apps/
├── frontend (react app)
└── backend (strapi)
add a homepage property in frontend's package.json if you are using CRA, this will tell Webpack to add a prefix to your static assets, e.g
// in frontend's package.json
{
...
"homepage": "/dashboard"
}
move your built react app to a subfolder /dashboard of backend project, by modifying the yarn build script, I'm doing like this, be careful before copy/paste my code, there is a rm -rf cmd.
// package.json in root path
{
...
"scripts": {
"build": "yarn build:front && yarn build:back && rm -rf apps/backend/dashboard && mv apps/frontend/build apps/backend/dashboard",
...
}
}
add a custom middleware in Strapi to be your "view router", that will handle all requests to /dashboard/* to serve the react app assets under apps/backend/dashboard
create a file under <strapiapp>/middlewares/viewRouter/index.js
const path = require("path");
const koaStatic = require("koa-static");
const fs = require("fs");
module.exports = strapi => {
return {
async initialize() {
const { maxAge } = strapi.config.middleware.settings.public;
const basename = "/dashboard";
const dashboardDir = path.resolve(strapi.dir, "dashboard");
// Serve dashboard assets.
strapi.router.get(
`${basename}/*`,
async (ctx, next) => {
ctx.url = ctx.url.replace(/^\/dashboard/, "");
if (!ctx.url) ctx.url = basename;
await next();
},
koaStatic(dashboardDir, {
index: "index.html",
maxage: maxAge,
defer: false
})
);
const validRoutes = [
"/dashboard",
"/subpath1",
"/subpath2"
];
// server dashboard assets and all routers
strapi.router.get(`${basename}*`, ctx => {
const routePath = ctx.url.split("?")[0];
let fileName = ctx.url;
if (validRoutes.includes(routePath)) fileName = "index.html";
ctx.type = "html";
ctx.body = fs.createReadStream(
path.join(dashboardDir + `/${fileName}`)
);
});
}
};
};
enable the custom middleware in <strapiapp>/config/custom.json
{
"myCustomConfiguration": "This configuration is accessible through strapi.config.myCustomConfiguration",
"viewRouter": { // use the middleware name
"enabled": true
}
}
and visit http://localhost:1337/dashboard you'll see the react page.
The actual answer for strapi#4.3.2 is here
I faced the same problem. All you need to do are these two steps:
Create a custom middleware. I named it spa.js and put it in the folder /src/middlewares/spa.js (I am not sure about naming). I didn't have this folder before. I created it by myself. The file spa.js should contain a code like this:
module.exports = () => {
return async (ctx, next) => {
const url = ctx.url;
// Here you should process your redirects on index.html always,
// except URLs like `admin`, `content-manager`, `i18n`, `static`, `api`, `graphql`, `uploads` etc.
// These settings should be completely the same as in your Nginx config
// for SPA (Single Page Application). Usually, we set up `Location` in Nginx
if (!url.match(/\/admin|\/content-manager\/|\/i18n\/|\/static|\/graphql|\/uploads\/|\.json/)) {
ctx.url = '/';
}
// let strapi go further
await next();
};
};
Register your new middleware in /config/middlewares.js. I had this file and it contained only strings ('strapi::errors', 'strapi::security', 'strapi::cors',). I added an object with a resolve field with a relative path to my new middleware spa.js. There are different options for how you can set up this path, you can also use just a name.
My /config/middleware.js looks like this now:
module.exports = [
{
resolve: './src/middlewares/spa.js',
},
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];
Relaunch your server by strapi start. It should work. Routes after reloading any page that was reached by React-router before will work as they should work in SPA. All routes go to / (index.html)
UPD: Please, be careful. I see many routes for "internal use". For example, /content-manager/ and /i18n/. My admin panel didn't work unless I add the content-manager route. I suppose there may be many new routes in the future and we should mark in our middleware only allowed routes that are redirected and don't change behavior for other routes.

How to exit webpack-dev-server execution after running test suite

I've created a webpack configuration file that builds the app, runs a dev server using webpack-dev-server and runs the test suite as below.
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3000,
watchOptions: {
open: false
},
historyApiFallback: {
index: "/"
}
},
plugins: [
new WebpackShellPlugin({
onBuildEnd: [`./node_modules/.bin/cucumber-js ${testScope}`]
})
]
The command to start the whole thing is below
webpack-dev-server --config webpack.test.js
Everything is ok, but... what now? It never stops! I need some way to exit with a successful status when the test suite is ok. Otherwise, it would cause an infinite deploy (I use bitbucket pipelines to deploy the app).
Does someone know a way to exit the devServer execution after running a specific plugin?
Likely it is the difference between npm run start and npm start
When you use npm start, it runs node server.js which doesn't run inline with your console.
If you use npm run start, it executes the start script defined in your package.json using the shell of your operating system. Therefore, even if you are using Cygwin (or similar), it should still run it using cmd.exe.
Solution
Use npm run start instead of npm start and it should kill the process when you CTRL + C
I solved my own problem by creating a webpack plugin and added it to my config file. The plugin code is below. It runs the command I pass to it and exit the process, firing error if the command was successful or success if it was everything ok.
const spawn = require("child_process").spawn;
module.exports = class RunScriptAndExit {
constructor(command) {
this.command = command;
}
apply(compiler) {
compiler.plugin("after-emit", (compilation, callback) => {
const { command, args } = this.serializeScript(this.command);
const proc = spawn(command, args, { stdio: "inherit" });
proc.on("close", this.exit);
callback();
});
}
serializeScript(script) {
if (typeof script === "string") {
const [command, ...args] = script.split(" ");
return { command, args };
}
const { command, args } = script;
return { command, args };
}
exit(error, stdout, stderr) {
if (error) {
process.exit(1);
} else {
process.exit(0);
}
}
};
I added it to my plugin list as below:
plugins: [
new RunScriptAndExit(`./node_modules/.bin/cucumber-js ${testScope}`)
]

Categories

Resources