angular-server-side-configuration
Version:
Configure an angular application on the server
221 lines (216 loc) • 8.2 kB
JavaScript
import { existsSync, readFileSync, writeFileSync, readdirSync, lstatSync } from 'fs';
import { dirname, join } from 'path';
// Source: https://github.com/fitzgen/glob-to-regexp/blob/master/index.js
function globToRegExp(glob, opts) {
if (typeof glob !== 'string') {
throw new TypeError('Expected a string');
}
var str = String(glob);
// The regexp we are building, as a string.
var reStr = '';
// Whether we are matching so called "extended" globs (like bash) and should
// support single character matching, matching ranges of characters, group
// matching, etc.
var extended = opts ? !!opts.extended : false;
// When globstar is _false_ (default), '/foo/*' is translated a regexp like
// '^\/foo\/.*$' which will match any string beginning with '/foo/'
// When globstar is _true_, '/foo/*' is translated to regexp like
// '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT
// which does not have a '/' to the right of it.
// E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but
// these will not '/foo/bar/baz', '/foo/bar/baz.txt'
// Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when
// globstar is _false_
var globstar = opts ? !!opts.globstar : false;
// If we are doing extended matching, this boolean is true when we are inside
// a group (eg {*.html,*.js}), and false otherwise.
var inGroup = false;
// RegExp flags (eg "i" ) to pass in to RegExp constructor.
var flags = opts && typeof opts.flags === 'string' ? opts.flags : '';
var c;
for (var i = 0, len = str.length; i < len; i++) {
c = str[i];
switch (c) {
case '/':
case '$':
case '^':
case '+':
case '.':
case '(':
case ')':
case '=':
case '!':
case '|':
reStr += '\\' + c;
break;
// @ts-ignore
case '?':
if (extended) {
reStr += '.';
break;
}
case '[':
// @ts-ignore
case ']':
if (extended) {
reStr += c;
break;
}
// @ts-ignore
case '{':
if (extended) {
inGroup = true;
reStr += '(';
break;
}
// @ts-ignore
case '}':
if (extended) {
inGroup = false;
reStr += ')';
break;
}
case ',':
if (inGroup) {
reStr += '|';
break;
}
reStr += '\\' + c;
break;
case '*':
// Move over all consecutive "*"'s.
// Also store the previous and next characters
var prevChar = str[i - 1];
var starCount = 1;
while (str[i + 1] === '*') {
starCount++;
i++;
}
var nextChar = str[i + 1];
if (!globstar) {
// globstar is disabled, so treat any number of "*" as one
reStr += '.*';
}
else {
// globstar is enabled, so determine if this is a globstar segment
var isGlobstar = starCount > 1 && // multiple "*"'s
(prevChar === '/' || prevChar === undefined) && // from the start of the segment
(nextChar === '/' || nextChar === undefined); // to the end of the segment
if (isGlobstar) {
// it's a globstar, so match zero or more path segments
reStr += '((?:[^/]*(?:/|$))*)';
i++; // move over the "/"
}
else {
// it's not a globstar, so only match one path segment
reStr += '([^/]*)';
}
}
break;
default:
reStr += c;
}
}
// When regexp 'g' flag is specified don't
// constrain the regular expression with ^ & $
if (!flags || !~flags.indexOf('g')) {
reStr = '^' + reStr + '$';
}
return new RegExp(reStr, flags);
}
function insert(options = {}) {
const directory = options.directory || process.cwd();
if (options.recursive) {
walk(directory, '**/ngssc.json')
.map((f) => dirname(f))
.forEach((d) => insertWithNgssc(d, !!options.dryRun));
}
else {
insertWithNgssc(directory, !!options.dryRun);
}
}
function insertWithNgssc(directory, dryRun) {
const ngsscPath = join(directory, 'ngssc.json');
if (!existsSync(ngsscPath)) {
throw new Error(`${ngsscPath} does not exist!`);
}
const ngssc = JSON.parse(readFileSync(ngsscPath, 'utf8'));
const populatedVariables = populateVariables(ngssc.environmentVariables);
log(`Populated environment variables (Variant: ${ngssc.variant}, ${ngsscPath})`);
Object.keys(populatedVariables).forEach((k) => ` ${k}: ${populatedVariables[k]}`);
const iife = generateIife(ngssc.variant, populatedVariables);
const htmlPattern = ngssc.filePattern || 'index.html';
const htmlFiles = walk(directory, htmlPattern);
if (!htmlFiles.length) {
log(`No files found with pattern ${htmlPattern} in ${directory}`);
return;
}
log(`Configuration will be inserted into ${htmlFiles.join(', ')}`);
if (dryRun) {
log('Dry run. Nothing will be inserted.');
}
else {
htmlFiles.forEach((f) => insertIntoHtml(f, iife));
}
}
function populateVariables(variables) {
const populatedVariables = {};
variables.forEach((v) => (populatedVariables[v] = v in process.env ? process.env[v] || '' : null));
return populatedVariables;
}
function generateIife(variant, populatedVariables) {
let iife;
if (variant === 'NG_ENV') {
iife = `(function(self){self.NG_ENV=${JSON.stringify(populatedVariables)};})(window)`;
}
else if (variant === 'global') {
iife = `(function(self){Object.assign(self,${JSON.stringify(populatedVariables)});})(window)`;
}
else {
iife = `(function(self){self.process=${JSON.stringify({ env: populatedVariables })};})(window)`;
}
return `<!--ngssc--><script>${iife}</script><!--/ngssc-->`;
}
function insertIntoHtml(file, iife) {
const fileContent = readFileSync(file, 'utf8');
if (/<!--ngssc-->[\w\W]*<!--\/ngssc-->/.test(fileContent)) {
writeFileSync(file, fileContent.replace(/<!--ngssc-->[\w\W]*<!--\/ngssc-->/, iife), 'utf8');
}
else if (/<!--\s*CONFIG\s*-->/.test(fileContent)) {
writeFileSync(file, fileContent.replace(/<!--\s*CONFIG\s*-->/, iife), 'utf8');
}
else if (fileContent.includes('</title>')) {
writeFileSync(file, fileContent.replace('</title>', `</title>${iife}`), 'utf8');
}
else {
writeFileSync(file, fileContent.replace('</head>', `${iife}</head>`), 'utf8');
}
}
function walk(root, filePattern) {
const fileRegex = globToRegExp(filePattern, { extended: true, globstar: true, flags: 'ig' });
const directory = root.replace(/\\/g, '/');
return readdirSync(directory)
.map((f) => `${directory}/${f}`)
.map((f) => {
const stat = lstatSync(f);
if (stat.isDirectory()) {
return walk(f, filePattern);
}
else if (stat.isFile() && fileRegex.test(f)) {
return [f];
}
else {
return [];
}
})
.reduce((current, next) => current.concat(next), []);
}
function log(message) {
// tslint:disable-next-line: no-console
console.log(message);
}
/**
* Generated bundle index. Do not edit.
*/
export { insert };
//# sourceMappingURL=angular-server-side-configuration.mjs.map