Angular (8) application build once (with production config) and deploy to multiple environments - azure-devops

I have a situation where I’m trying to build my angular application with production config and deploy to multiple environments, say, ng build --configuration=production
The work flow here is when I build using the above command (ng build --configuration=production), the environment.ts file gets replaced with environment.prod.ts
The configurations I have in environment.prod.ts is as follows,
export const environment = {
production: true,
environment: 'Production',
_webApiHost: 'prodsomename.company.com/api/',
};
The configurations I have in environmrnt.test.ts is as follows,
export const environment = {
production: true,
environment: 'Test',
_webApiHost: 'testsomename.company.com/api/',
};
The setting I have on angular.json file is as follows,
"configurations": {
"production": {
"fileReplacements": [ {
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
} ],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [ {
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
} ]
},
"test": {
"fileReplacements": [ {
"replace": "src/assets/configs/environment.ts",
"with": "src/assets/configs/environment.test.ts"
} ],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [ {
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
} ]
}
}
If I build the solution for every environment separately and deploy to appropriate environment as below figure,
it works like charm, which mean the,
testApp communicates to _webApiHost: testsomename.company.com/api/ and
prodApp communicates to _webApiHost: prodsomename.company.com/api/
In the above case the artifact which is tested by QA is different from the artifact which is deployed to production, which is not the ideal way of pushing the code to production.
But my concern is I want to build the app only once and deploy it to multiple environments, where each environment will communicate to appropriate api, like below figure,
When I build it using the command ng build --configuration=production, the environment.ts file will have production configurations,
export const environment = {
production: true,
environment: 'Production',
_webApiHost: 'prodsomename.company.com/api/',
};
So if that artifact is deployed to test environment,
the testApp is trying to communicate with _webApiHost: 'prodsomename.company.com/api/, which is not right.
Here is the Azure DevOps build pipeline powershell script I use to build the solution.
Set-Location "$(Build.Repository.LocalPath)\Buffini.Web.UI\Angular"
Write-Host 'Angular Install Starting'
npm install -g #angular/cli#8.0.6 -Verbose
Write-Host 'Angular Install Finished'
Write-Host 'NPM Install Starting'
npm install -Verbose
Write-Host 'NPM Install Finished'
Write-Host 'NPM Update Starting'
npm update -Verbose
Write-Host 'NPM Update Finished'
Write-Host 'NPM Audit Starting'
npm audit fix -Verbose
Write-Host 'NPM Audit Finished'
Write-Host 'Angular Build Starting'
ng build --configuration=production --deleteOutputPath=true
Write-Host 'Angular Build Finished'
I have tried searching for a solution online but I couldn’t find any.
Please help me in resolving the issue. I’ll highly appreciate your time and help on this. Thanks in advance.

To replace app configurations in runtime time. You need to create config.json file which contains the dynamic configurations (eg. _webApiHost). You can check the example code in this blog to fetch the config.json.
In the you pipeline, you can add extension tasks to replace the config.json contents before deploying to different environment(eg. test, production).
In this way you only need to build your angular app once, and only need replace the config.json contents accordingly before deploying to different environment.
The available extensions you can check out. Magic Chunks task, or
RegEx Match & Replace Task. You can check this thread for the example to use these tasks.

I won't pretend this an answer, necessarily, but it's long enough of a thought to not fit in the comments area. Perhaps you will find it helpful. (Full disclosure: I'm not using Azure, but rather GitLab. So there would be some translation necessary, regardless, if you find this approach of use.)
Anyway, I was asking the same question a while back. After some digging, I found this link helpful
Using that guidance, I did the following:
First, I do a basic docker build. In that build I have various environment files "ready for the asking" in a folder. The configuration file that actually drives the app is at the root.
I then do another docker build, this one whose sole purpose is to take the first build and give it the desired configuration. (I do it this way because the first build is slow, but I'd like to push to production without rebuilding.)
Next I do the environment build that I want.
For staging, for example: In my GitLab build CI/CD pipeline yaml, I have a line like this....
docker build -t xxxxx --build-arg SERVE_CONFIGURATION=staging -f [A-Docker-File] .
The docker file is the same for all environments, but based upon the passed in argument, this docker build pulls a different file and slams it into the driver's seat. Since there are no secrets in an Angular deployment, it doesn't matter that there are extra (unused) configuration files lurking in the folder structure (though if one were motivated, one could easily delete them.)
Anyway, inside that 2nd docker build, I have...
...
FROM registry.gitlab.com/xxxxxxxxxxxx/compiled as default-config
FROM registry.gitlab.com/xxxxxxxxxxxx/compiled as final-config
COPY --from=default-config /usr/share/nginx/html/environments/environment.$SERVE_CONFIGURATION.js /usr/share/nginx/html/environment.js
So this docker image is nothing more than a build off the prior image, but with the desired environment file in its proper place.
Anyway, I've probably left out some details, but I'm not sure this will help you and will stop here.

Related

vscode builtin node binary like atom

Hi in atom we got builtin node and npm binary without installing in the OS that can be call from extension.
This is the path of the default binary in atom
/usr/share/atom/resources/app/apm/bin/npm
/usr/share/atom/resources/app/apm/bin/npx
Does VSCode provide it ?
We have a strict rule not to cluttering our development computer with any unnecessary binary ( e.g we use docker for php, using virtualenv for python and node related project )
In our case for our web development ( php and python mainly ) we use babel to transpile all our file.js on save, in atom we use language-babel extension that allow us to transpile using atom node but with project node_modules package. So our babel dependencies is install inside a project and not clutter the OS, and doesn't disturb other project.
On VSCode babel extension I check they don't have this capabilities. Any info on this or is this not do able in VSCode ?
You can install and run VS Code without Node, which does suggest it has Node baked in. However, at this time (June 2022) you can't debug or run JavaScript on Node until you install Node separately.
Separate installation decouples your editor dependencies from your code dependencies.
Your code can debug/run on a version of Node that differs from the version VS Code uses.
Those not using Node are not obliged to install the CLI toolchain for it.
VS Code supports a multitude of languages. Baking in their toolchains would make it enormous.
This is the closest setup in vscode / codium for non nodejs project
OS only require to install nodejs for running babel ( npm , npx not require )
All node package inside project is install using docker
example using Makefile
SHELL := /bin/bash
THIS_FILE := $(lastword $(MAKEFILE_LIST))
PROJECT_NAME := "$$(basename `pwd` | cut -d. -f1 )"
yarn:
docker run --rm -it \
-v $$(pwd)/${PROJECT_NAME}:/srv/${PROJECT_NAME} \
-w /srv/${PROJECT_NAME} \
-e NODE_ENV=development \
--user $$(id -u):$$(id -g) \
node:lts-slim yarn $(filter-out $#,$(MAKECMDGOALS))
init and install babel inside project
make yarn init
make yarn -- add -D #babel/cli
make yarn -- add -D #babel/core
make yarn -- add -D #babel/preset-react
make yarn -- add -D babel-preset-minify
Create ${workspaceRoot}/.babelrc
{
"comments" : false,
"sourceMaps": true,
"only": [
"./asset/js/src"
],
"presets": [
"#babel/preset-react",
["minify", {
"mangle" : true,
"builtIns": false,
"keepClassName" : true
}]
]
}
Create ${workspaceRoot}/.vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label" : "Babel Watch",
"type": "shell",
"group": "none",
"command" : "${workspaceRoot}/node_modules/.bin/babel",
"args" : [
"${workspaceRoot}/asset/js/src/",
"--config-file=${workspaceRoot}/.babelrc",
"--out-dir=${workspaceRoot}/asset/js/dist/",
"--watch"
],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"runOptions": {
"runOn": "folderOpen"
}
}
]
}
Then Enable Automatic tasks in folder to run on folder open
Press F1
Search Tasks: Manage Automatic Task in Folder
Select Tasks: Manage Automatic Task in Folder
Select Allow Automatic Tasks in Folder
On next reopen folder / project babel will watch the folder specify in tasks
It looks like there is no way to allow automatic task in folder from .vscode/settings.json so need enable this on every project once in their own dev computer

AWS CDK asset path is incorrect

On September 6, I ran a build using CodePipeline. It generates a CloudFormation template for a project's stack using CDK. The stack has assets (a Lambda Layer), and the assets are correctly placed in the cdk.out folder. This can be seen in the CloudFormation template:
"Metadata": {
"aws:cdk:path": "MyStack/MyLayer/Resource",
"aws:asset:path": "asset.ccb8fd8b4259a8f517879d7aaa083679461d02b9d60bfd12725857d23567b70f",
"aws:asset:property": "Content"
}
Starting yesterday, builds were failing with "Uploaded file must be a non-empty zip". When I investigated further, I noticed that the template was no longer correct. It has the asset path set to the source code of the Lambda instead:
"Metadata": {
"aws:cdk:path": "MyStack/MyLayer/Resource",
"aws:asset:path": "/codebuild/output/src216693626/src/src/lambdas/layers",
"aws:asset:property": "Content"
}
When I build, I've added additional commands to the buildspec file which shows that the assets.abcdef folder has the layer and its dependencies, while the src folder does not. Yet the template is now different.
No code was changed in this time period, and I've tried both CDK version 1.105.0 and 1.119.0.
This code declares the Layer:
new lambdapython.PythonLayerVersion(this.stack, 'MyLayer', {
entry: path.join(__dirname, '../../src/lambdas/layers'),
description: 'Common utilities for the Lambdas',
compatibleRuntimes: [lambda.Runtime.PYTHON_3_8],
layerVersionName: `${Aws.STACK_NAME}Utils`,
});
Is there a known way for me to force the stack to use the assets in the cdk.out folder? Has something changed in the last couple of days with respect to how CDK generates the template's asset path?
It turns out that I had added a cdk ls to print out additional debugging information while troubleshooting another problem. That command re-synthesized the stack, but with the incorrect asset path.
build: {
commands: [
'cd ' + config.cdkDir,
'cdk synth',
'cdk ls --long'
]
}
The solution was to delete the cdk ls --long from the buildspec definition.

React-Snap with Create-React-App and Service Workers

So, my understanding is that react-snap as per its features "Works out-of-the-box with create-react-app - no code-changes required."
I read through the documentation and I see that it required some adjusting to work with Google Analytics which I implemented.
However, it also suggests changes to be made if one is going to use the default service worker that comes with CRA.
https://github.com/stereobooster/react-snap#service-workers
However, what is confusing is that it seems one has to perform a EJECT in order to make the necessary change.
navigateFallback: publicUrl + '/index.html',
You need to change this to an un-prerendered version of index.html - 200.html, otherwise you will see index.html flash on other pages (if you have any). See Configure sw-precache without ejecting for more information.
My question is - and note I am quite novice - does one have to eject? I kinda want to keep things simple. The only place I could find this line was in WebPack. navigateFallback
Also, if I don't see the negative side of the flashes on pages as per the documentation, is it okay to omit this step or will it have issues on other things?
Although this question is more than a year old, I'd like to take the opportunity as I've been able to implement service workers in react-snap (although with a varying degree of success).
Here's stereobooster's reference in GitHub:
https://github.com/stereobooster/react-snap/blob/master/doc/recipes.md#configure-sw-precache-without-ejecting
You can configure it without ejecting. What you need to do is the following:
Download and install sw-precache and ugfify-js:
npm install sw-precache uglify-js --save-dev
or
yarn add sw-precache uglify-js -D
Then, in your package.json add the following entries:
(Replace the build script with the following)
"scripts": {
"generate-sw": "sw-precache --root=build --config scripts/sw-precache-config.js && uglifyjs build/service-worker.js -o build/service-worker.js",
"build": "react-scripts build && react-snap && yarn run generate-sw"
}
Then, create a folder in the root level (next to your package.json) called scripts
and add sw-precache-config.js file.
module.exports = {
// a directory should be the same as "reactSnap.destination",
// which default value is `build`
staticFileGlobs: [
"build/static/css/*.css",
"build/static/js/*.js",
"build/shell.html",
"build/index.html"
],
stripPrefix: "build",
publicPath: ".",
// there is "reactSnap.include": ["/shell.html"] in package.json
navigateFallback: "/shell.html",
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
// configuration specific to this experiment
runtimeCaching: [
{
urlPattern: /api/,
handler: "fastest"
}
]
};
Note, if you're not using an app-shell but you're loading the whole page (Meaning there's no dyanmic content), replace where it says navigateFallback: "/shell.html" with navigateFallback: "/200.html"
This basically allows you to cache the entire page
You can look for more information here:
https://github.com/stereobooster/an-almost-static-stack
One thing that I'd recommend to check (I'm close to start that process as well) is the workbox-sw.
What to do if React-Snap fails
error at / TypeError: Cannot read property 'ok' of null
Or
ERROR: The process with PID 38776 (child process of PID 26920) could not be terminated. \node_modules\minimalcss\src\run.js:13:35)
Reason: There is no running instance of the task.
You may get these infamous errors. I don't know exactly what causes them, but I know they're mentioned here, and here. In this case, delete the build folder, open a new terminal window, and try again.
If the problem still persists, then break down the script:
Do:
"scripts": {
"build": "react-scripts build"
"postbuild": "react-snap",
"generate-sw": "sw-precache --root=build --config scripts/sw-precache-config.js && uglifyjs build/service-worker.js -o build/service-worker.js",
}
And try running them independently.

How do I define an extension for coffeeify with Budo dev server?

I'm trying to use coffeeify with budo so I do not have to add the extension to my require statements. I have tried passing these commands through budo's browserify options
budo src/app.coffee --live --serve bundle.js -- -t coffeeify --extension=".coffee"
budo src/app.coffee --live --serve bundle.js -- -t [coffeeify --extension=".coffee"]
I also tried inserting the browserify transform into my package.json
"browserify: {
"transform": ["coffeeify", {"extension": ".coffee"}]
}
Here is something that works for me (took me forever to figure it out, the hard part being getting watchify to work with coffeescript). Everything is in the package.yaml. Invoke npm start from your top folder and it will do the trick. npm puts all the locally installed node binaries in your PATH for you (they normally live under node_modules/.bin).
{
"name": "my-package",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "(cd src; budo app.coffee:bundle.js --dir . --live --verbose -- --extension=.coffee | garnish)"
},
"browserify": {
"extension": [ ".coffee" ],
"transform": [ ["coffeeify"], ["brfs"] ]
},
"devDependencies": {
"brfs": "1.4.1",
"browserify": "11.1.0",
"budo": "^5.1.5",
"coffee-script": "latest",
"coffeeify": "^1.1.0",
"garnish": "^3.2.1",
"watchify": "3.4.0"
}
}
I have my source code under the src folder, and a file named app.coffee which includes (or require in node.js terms) my whole application. I have an index.html in my src folder which reference the bundle.js through from an html script tag.
The command to start budo is inside my package.json. It does cd into my src folder first.
The trick is to specify some configuration in the browserify block: the extension .coffee needs to be present, and a list of transforms as well. I tried to have everything on the command line but never got it to work
After npm start is invoked, since I pass the --live argument to budo everything works like magic and edit/saves to my documents do trigger a browser reload/refresh.
To deploy or release you'll probably need another target to minify with uglify.js. I still have a script that does that manually in 2 steps, the first step calls browserify and the second step calls uglify.js explicitely.
As a remark, recent version of budo do the piping into garnish for you I've heard.
Another tip is to look at what the React folks are doing to transform their .jsx files, as it is in theory extremely close to what the coffeescript folks need to do. There seems to be a huge momentum around React so hopefully React people will have figured those build problems first.

Wintersmith: error Error loading plugin './node_modules/wintersmith-coffee/': Cannot find module './plugin'

I built a site with wintersmith in November of 2013. It's live at http://powma.com
I'm coming back to it, but it's not building :-{
I don't mind getting my hands dirty, but I don't know where to start. I'm getting this error:
error Error loading plugin './node_modules/wintersmith-coffee/': Cannot find module './plugin'
Any suggestions?
Thanks!
Mike
UPDATE
Hey, this is because the coffeescript wasn't getting compiled.
I installed it globally, but that didn't help.
$ sudo npm install -g coffee-script
I manually compiled it and moved to other errors. Any suggestions for what's missing?
$ coffee -c plugin.coffee
Here's my config.json:
{
"locals":
{ "url": "http://localhost:8080"
, "title": "Powma"
, "subTitle": "Linking you to technology"
, "motto": "We build exceptions sites and applications to connect people to products, services, and each other."
, "owner": "Michael Cole"
, "profilePicture": "/static/img/profile-professional.jpg"
, "inlineSpriteMaxBytes" : 10000
},
"views": "./views",
"plugins":
[ "./node_modules/wintersmith-coffee/"
, "./node_modules/wintersmith-stylus/"
],
"require": {
"moment": "moment",
"_": "underscore",
"typogr": "typogr"
},
"jade": {
"pretty": true
},
"markdown": {
"smartLists": true,
"smartypants": true
},
"paginator": {
"perPage": 3
}
}
And package.json:
{
"name": "Powma-com",
"version": "0.1.1",
"private": true,
"engines": {
"node": "0.10.17"
},
"dependencies": {
"moment": "2.0.x",
"underscore": "1.5.x",
"typogr": "0.5.x",
"wintersmith": "2.0.x",
"wintersmith-stylus": "git://github.com/MichaelJCole/wintersmith-stylus.git#master",
"wintersmith-coffee": "0.2.x",
"express": "3.4.x",
"sendgrid": "~0.3.0-rc.1.7",
"express-validator": "~0.8.0",
"underscore-express": "0.0.4"
}
}
This is a new dev laptop I'm working with so that may be part of the problem.
I worked around the issue, but didn't fix it. Do I really need to manually compile the coffeescript?
Thanks!
I solved this issue by explicitly specifying plugin.coffee in the config.json file.
{
...other stuff...
"plugins":
[ "./node_modules/wintersmith-coffee/plugin.coffee"
, "./node_modules/wintersmith-stylus/plugin.coffee"
],
...more stuff...
}
It looks like you're missing wintersmith-coffee in node_modules; make sure you have it installed locally with npm install wintersmith-coffee. You can also try removing it from config.json if you're not using it anywhere.
It would also be helpful to see both your config.json and package.json. Also make sure you run an npm install and npm update to make sure you have everything referenced in package.json installed and updated.
Update
Not having CoffeeScript installed could have been the issue. After installing that globally, I'm not sure if all of your shell sessions will pick up the command and use it without being restarted. With a new shell session, see if you can build the site. You can also try testing Wintersmith in isolation of your site. Try generating a sample site with wintersmith new somepath and see if you can run wintersmith build there. That would be a good start for narrowing down your issues between your site and your workstation setup.