CVE-2023-28444: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: angular-server-side-configuration helps configure an angular application at runtime on the server or in a docker container via environment variables. angular-server-side-configuration detects used environment variables in TypeScript (.ts) files during build time of an Angular CLI project. The detected environment variables are written to a ngssc.json file in the output directory.
During deployment of an Angular based app, the environment variables based on the variables from ngssc.json are inserted into the apps index.html (or defined index file). With version 15.0.0 the environment variable detection was widened to the entire project, relative to the angular.json file from the Angular CLI. In a monorepo setup, this could lead to environment variables intended for a backend/service to be detected and written to the ngssc.json, which would then be populated and exposed via index.html. This has NO IMPACT, in a plain Angular project that has no backend component. This vulnerability has been mitigated in version 15.1.0, by adding an option `searchPattern` which restricts the detection file range by default. As a workaround, manually edit or create ngssc.json or run script after ngssc.json generation.

CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVSS v3 base score: 7.5
Published: 2023-03-24T20:15Z
Repository: https://github.com/kyubisation/angular-server-side-configuration
Language: TypeScript
Files changed: .gitignore, .nvmrc, README.md, package.json, projects/angular-server-side-configuration/builders/browser/schema.json, projects/angular-server-side-configuration/builders/dev-server/index.ts, projects/angular-server-side-configuration/builders/dev-server/schema.json, projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts, projects/angular-server-side-configuration/builders/ngsscbuild/index.ts, projects/angular-server-side-configuration/builders/ngsscbuild/schema.json, projects/angular-server-side-configuration/builders/ngsscbuild/schema.ts, projects/angular-server-side-configuration/ng-package.json, projects/angular-server-side-configuration/package.json, projects/angular-server-side-configuration/schematics/ng-add/index.ts, test/hello-world-app/.browserslistrc, test/hello-world-app/.editorconfig, test/hello-world-app/.gitignore, test/hello-world-app/README.md, test/hello-world-app/angular.json, test/hello-world-app/karma.conf.js, test/hello-world-app/protractor.conf.js, test/hello-world-app/src/app/app.component.html, test/hello-world-app/src/app/app.component.spec.ts, test/hello-world-app/src/app/app.component.ts, test/hello-world-app/src/app/app.module.ts, test/hello-world-app/src/app/app.server.module.ts, test/hello-world-app/src/index.html, test/hello-world-app/src/main.server.ts, test/hello-world-app/src/main.ts, test/hello-world-app/src/polyfills.ts, test/hello-world-app/src/src/locale/messages.xlf, test/hello-world-app/src/styles.css, test/hello-world-app/src/tsconfig.app.json, test/hello-world-app/src/tsconfig.server.json, test/hello-world-app/src/tsconfig.spec.json, test/hello-world-app/src/typings.d.ts, test/hello-world-app/tsconfig.json, test/jasmine.js, test/test-utils.ts, yarn.lock
Commit date: 2023-03-22 17:36:12 +0100
Commit message: feat: add searchPattern as an option and restrict default search pattern (#75)

With version 15, the behavior of the environment variable lookup changed to try to find occurrences in the whole workspace. This could lead to a potential information disclosure vulnerability, by exposing environment variables meant for a backend/Node.js application.
This PR mitigates this possibility, by restricting the lookup path of environment variables to `sourceRoot` of the related angular.json project and excluding files having `server` in their name (a common pattern for universal apps).
Diff: diff --git a/.gitignore b/.gitignore
index 242e9b6..7720b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,10 +8,11 @@ yarn-error.log*
 # Runtime data
 pids
 *.pid
 *.seed
 *.pid.lock
+test/test-project-*
 
 # Directory for instrumented libs generated by jscoverage/JSCover
 lib-cov
 
 # Coverage directory used by tools like istanbul
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..25bf17f
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+18
\ No newline at end of file
diff --git a/README.md b/README.md
index da9c3cd..fbec853 100644
--- a/README.md
+++ b/README.md
@@ -18,17 +18,37 @@ on the host serving the bundled angular files.
 
 Starting with version 13, there are experimental builders for the `build` and `serve` commands.
 To install the experimental builders run `ng add angular-server-side-configuration` and answer
 `Would you like to use the experimental builders for build and serve?` with `y(es)`.
 
+## Changes with version 15.1
+
+With the change in version 15 to search all files for environment variables, a potential
+information disclosure vulnerability was introduced, as unintended environment variables might
+be exposed.
+
+You might be affected, if you use environment variables in your angular.json workspace that expose
+application internals (like used services/tools) or secrets that are also available on the
+deployed environment (e.g. a monorepo that also contains a Node.js backend, which is deployed in
+the same system/container/pod as the frontend bundle).
+
+This should have little to no impact, if you only have a simple angular.json workspace without
+a backend component.
+
+With 15.1 this risk is mitigated, as the lookup path is restricted by adding an option
+`searchPattern` which defaults to `{sourceRoot}/**/!(*server*).ts` (only search `sourceRoot`
+of the related angular.json project and exclude files with `server` in the name).
+
+
 ## Changes with version 15
 
 With version 15 of the Angular CLI, environment files are no longer generated by default. In
 order to adapt, using `ng add angular-server-side-configuration` will try to add the import in
-either `app.module.ts`, `app.components.ts` or `main.ts` depending on availability. The ngssc
-builder will also now search all files for environment variable usages instead of just the
-defined environment file. Due to this, the `ngsscEnvironmentFile` option can be removed.
+either `environment.prod.ts`, `environment.ts`, `app.module.ts`, `app.components.ts` or `main.ts`
+depending on availability. The ngssc builder will also now search all files for environment
+variable usages instead of just the defined environment file. Due to this, the
+`ngsscEnvironmentFile` option can be removed.
 
 ## Getting Started
 
 ```
 ng add angular-server-side-configuration
@@ -72,11 +92,13 @@ used environment variables and generate an [ngssc.json](#ngsscjson) in the defin
           "options": {
             "additionalEnvironmentVariables": ["MANUAL_ENTRIES"],
             "browserTarget": "your-project-name:build",
             // Optional
             // (Defaults to the basename of the index option of the browser target)
-            "filePattern": "index.html"
+            "filePattern": "index.html",
+            // (Defaults to {sourceRoot}/**/!(*server*).ts)
+            "searchPattern": "src/environments/environment*.ts"
           },
           "configurations": {
             "production": {
               "browserTarget": "your-project-name:build:production"
             }
@@ -91,11 +113,11 @@ used environment variables and generate an [ngssc.json](#ngsscjson) in the defin
 ...
 ```
 
 To run the ngssc build, run the command `ng run your-project-name:ngsscbuild:production`.
 
-### environment.prod.ts
+### TypeScript files
 
 angular-server-side-configuration supports two variants for using environment variables:
 process.env._ or NG_ENV._
 
 #### process.env.\*
diff --git a/package.json b/package.json
index 8d9d949..6b8a11f 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,11 @@
     "build:ngssc": "ng run ngssc-app:ngsscbuild:production",
     "build:demo": "ng build ngssc-builders-app",
     "build:demo-i18n": "ng build ngssc-builders-app -c i18n",
     "build": "run-s build:*",
     "format": "prettier --write **/*.{js,ts,css,scss,json,md,html}",
+    "pretest:lib": "yarn -s build:lib",
     "test:lib": "node test/jasmine.js",
     "test:app": "ng test ngssc-app --configuration=ci",
     "test:cli": "cd cli && go test",
     "test:cli:coverage": "cd cli && go test -coverprofile=coverage.out && go tool cover -html=coverage.out",
     "test:container": "docker-compose up --build local-test",
@@ -76,10 +77,11 @@
     "@typescript-eslint/eslint-plugin": "^5.43.0",
     "@typescript-eslint/parser": "^5.43.0",
     "cross-env": "^7.0.3",
     "eslint": "^8.28.0",
     "eslint-plugin-import": "2.26.0",
+    "glob": "^9.3.1",
     "jasmine": "~4.5.0",
     "jasmine-core": "~4.5.0",
     "karma": "~6.4.0",
     "karma-chrome-launcher": "~3.1.0",
     "karma-coverage": "~2.2.0",
diff --git a/projects/angular-server-side-configuration/builders/browser/schema.json b/projects/angular-server-side-configuration/builders/browser/schema.json
index 8b4686f..05a9175 100644
--- a/projects/angular-server-side-configuration/builders/browser/schema.json
+++ b/projects/angular-server-side-configuration/builders/browser/schema.json
@@ -62,11 +62,13 @@
                 "description": "If the bundle will be referenced in the HTML file.",
                 "default": true
               }
             },
             "additionalProperties": false,
-            "required": ["input"]
+            "required": [
+              "input"
+            ]
           },
           {
             "type": "string",
             "description": "The file to include.",
             "pattern": "\\.[cm]?jsx?$"
@@ -98,11 +100,13 @@
                 "description": "If the bundle will be referenced in the HTML file.",
                 "default": true
               }
             },
             "additionalProperties": false,
-            "required": ["input"]
+            "required": [
+              "input"
+            ]
           },
           {
             "type": "string",
             "description": "The file to include.",
             "pattern": "\\.(?:css|scss|sass|less)$"
@@ -112,11 +116,16 @@
     },
     "inlineStyleLanguage": {
       "description": "The stylesheet language to use for the application's inline component styles.",
       "type": "string",
       "default": "css",
-      "enum": ["css", "less", "sass", "scss"]
+      "enum": [
+        "css",
+        "less",
+        "sass",
+        "scss"
+      ]
     },
     "stylePreprocessorOptions": {
       "description": "Options to pass to style preprocessors.",
       "type": "object",
       "properties": {
@@ -284,17 +293,25 @@
       "default": true
     },
     "i18nMissingTranslation": {
       "type": "string",
       "description": "How to handle missing translations for i18n.",
-      "enum": ["warning", "error", "ignore"],
+      "enum": [
+        "warning",
+        "error",
+        "ignore"
+      ],
       "default": "warning"
     },
     "i18nDuplicateTranslation": {
       "type": "string",
       "description": "How to handle duplicate translations for i18n.",
-      "enum": ["warning", "error", "ignore"],
+      "enum": [
+        "warning",
+        "error",
+        "ignore"
+      ],
       "default": "warning"
     },
     "localize": {
       "description": "Translate the bundles in one or more locales.",
       "oneOf": [
@@ -320,11 +337,16 @@
     },
     "outputHashing": {
       "type": "string",
       "description": "Define the output filename cache-busting hashing mode.",
       "default": "none",
-      "enum": ["none", "all", "media", "bundles"]
+      "enum": [
+        "none",
+        "all",
+        "media",
+        "bundles"
+      ]
     },
     "poll": {
       "type": "number",
       "description": "Enable and define the file watching poll time period in milliseconds."
     },
@@ -387,11 +409,13 @@
               "minLength": 1,
               "default": "index.html",
               "description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
             }
           },
-          "required": ["input"]
+          "required": [
+            "input"
+          ]
         }
       ]
     },
     "statsJson": {
       "type": "boolean",
@@ -412,11 +436,15 @@
     },
     "crossOrigin": {
       "type": "string",
       "description": "Define the crossorigin attribute setting of elements that provide CORS support.",
       "default": "none",
-      "enum": ["none", "anonymous", "use-credentials"]
+      "enum": [
+        "none",
+        "anonymous",
+        "use-credentials"
+      ]
     },
     "allowedCommonJsDependencies": {
       "description": "A list of CommonJS packages that are allowed to be used without a build time warning.",
       "type": "array",
       "items": {
@@ -430,14 +458,24 @@
     },
     "filePattern": {
       "type": "string",
       "description": "The file pattern, into which the environment variables should be inserted during ngssc insert (Defaults to index.html)",
       "default": ""
+    },
+    "searchPattern": {
+      "type": "string",
+      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
+      "default": ""
     }
   },
   "additionalProperties": false,
-  "required": ["outputPath", "index", "main", "tsConfig"],
+  "required": [
+    "outputPath",
+    "index",
+    "main",
+    "tsConfig"
+  ],
   "definitions": {
     "assetPattern": {
       "oneOf": [
         {
           "type": "object",
@@ -466,11 +504,15 @@
               "type": "string",
               "description": "Absolute path within the output."
             }
           },
           "additionalProperties": false,
-          "required": ["glob", "input", "output"]
+          "required": [
+            "glob",
+            "input",
+            "output"
+          ]
         },
         {
           "type": "string"
         }
       ]
@@ -488,11 +530,14 @@
               "type": "string",
               "pattern": "\\.(([cm]?j|t)sx?|json)$"
             }
           },
           "additionalProperties": false,
-          "required": ["src", "replaceWith"]
+          "required": [
+            "src",
+            "replaceWith"
+          ]
         },
         {
           "type": "object",
           "properties": {
             "replace": {
@@ -503,21 +548,32 @@
               "type": "string",
               "pattern": "\\.(([cm]?j|t)sx?|json)$"
             }
           },
           "additionalProperties": false,
-          "required": ["replace", "with"]
+          "required": [
+            "replace",
+            "with"
+          ]
         }
       ]
     },
     "budget": {
       "type": "object",
       "properties": {
         "type": {
           "type": "string",
           "description": "The type of budget.",
-          "enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
+          "enum": [
+            "all",
+            "allScript",
+            "any",
+            "anyScript",
+            "anyComponentStyle",
+            "bundle",
+            "initial"
+          ]
         },
         "name": {
           "type": "string",
           "description": "The name of the bundle."
         },
@@ -549,9 +605,11 @@
           "type": "string",
           "description": "The threshold for error relative to the baseline (min & max)."
         }
       },
       "additionalProperties": false,
-      "required": ["type"]
+      "required": [
+        "type"
+      ]
     }
   }
-}
+}
\ No newline at end of file
diff --git a/projects/angular-server-side-configuration/builders/dev-server/index.ts b/projects/angular-server-side-configuration/builders/dev-server/index.ts
index 05d4521..3c0df5d 100644
--- a/projects/angular-server-side-configuration/builders/dev-server/index.ts
+++ b/projects/angular-server-side-configuration/builders/dev-server/index.ts
@@ -31,11 +31,11 @@ export function ngsscServeWebpackBrowser(
     logging?: WebpackLoggingCallback;
     indexHtml?: IndexHtmlTransform;
   } = {}
 ): Observable<DevServerBuilderOutput> {
   transforms.indexHtml = async (content: string) => {
-    const ngsscContext = await detectVariables(context);
+    const ngsscContext = await detectVariables(context, options.searchPattern);
     const ngssc = buildNgssc(ngsscContext, options);
     const populatedVariables = populateVariables(ngssc.environmentVariables);
     context.logger.info(
       `Populated environment variables (Variant: ${ngssc.variant})\n${Object.entries(
         populatedVariables
diff --git a/projects/angular-server-side-configuration/builders/dev-server/schema.json b/projects/angular-server-side-configuration/builders/dev-server/schema.json
index f0f8c55..4bca6ce 100644
--- a/projects/angular-server-side-configuration/builders/dev-server/schema.json
+++ b/projects/angular-server-side-configuration/builders/dev-server/schema.json
@@ -102,10 +102,17 @@
     },
     "filePattern": {
       "type": "string",
       "description": "The file pattern, into which the environment variables should be inserted during ngssc insert (Defaults to index.html)",
       "default": ""
+    },
+    "searchPattern": {
+      "type": "string",
+      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
+      "default": ""
     }
   },
   "additionalProperties": false,
-  "required": ["browserTarget"]
-}
+  "required": [
+    "browserTarget"
+  ]
+}
\ No newline at end of file
diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts b/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts
index 8a422d2..f98759e 100644
--- a/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts
+++ b/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts
@@ -1,69 +1,27 @@
-import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'fs';
-import { tmpdir } from 'os';
-import { join } from 'path';
 import { Architect } from '@angular-devkit/architect';
-import { TestingArchitectHost } from '@angular-devkit/architect/testing';
-import { JsonObject, schema } from '@angular-devkit/core';
-import { Logger } from '@angular-devkit/core/src/logger';
+import { normalize, virtualFs } from '@angular-devkit/core';
 
 import { Ngssc } from 'angular-server-side-configuration';
 
-import { Schema } from './schema';
+import { createArchitect, host } from '../../../../test/test-utils';
 
 describe('Ngssc Builder', () => {
-  let tmpDir: string;
-  let distDir: string;
-  let architect: Architect;
-  let architectHost: TestingArchitectHost;
-  let logger: Logger;
-  let logs: string[];
-  const buildConfig = {
-    fileReplacements: [{ replace: 'dummy', with: 'environment.prod.ts' }],
-    index: 'src/index.html',
-    outputPath: 'dist',
-  };
+  const targetSpec = { project: 'app', target: 'ngsscbuild' };
+  let architect: Architect | undefined;
 
   beforeEach(async () => {
-    const registry = new schema.CoreSchemaRegistry();
-    registry.addPostTransform(schema.transforms.addUndefinedDefaults);
-
-    tmpDir = mkdtempSync(join(tmpdir(), 'ngssc-'));
-    writeFileSync(join(tmpDir, 'environment.prod.ts'), envContent, 'utf8');
-    distDir = join(tmpDir, 'dist');
-    mkdirSync(distDir);
-    writeFileSync(join(distDir, 'main.js'), '', 'utf8');
-    architectHost = new TestingArchitectHost(tmpDir, tmpDir);
-    architect = new Architect(architectHost, registry);
-
-    // This will either take a Node package name, or a path to the directory
-    // for the package.json file.
-    await architectHost.addBuilderFromPackage(
-      '../../../../projects/angular-server-side-configuration'
-    );
-    await architectHost.addBuilderFromPackage('..');
-
-    logs = [];
-    logger = new Logger('ngssc');
-    logger.subscribe((m) => logs.push(m.message));
+    architect = undefined;
+    await host.initialize().toPromise();
   });
+  afterEach(async () => host.restore().toPromise());
 
-  function addDummyBuildTarget(config: any = buildConfig) {
-    architectHost.addTarget(
-      { project: 'dummy', target: 'build' },
-      '@angular-devkit/architect:true',
-      config
-    );
-  }
+  async function runNgsscbuild() {
+    architect = (await createArchitect(host.root())).architect;
 
-  async function runNgsscbuild(options: Schema & JsonObject) {
     // A "run" can have multiple outputs, and contains progress information.
-    const run = await architect.scheduleBuilder(
-      'angular-server-side-configuration:ngsscbuild',
-      options,
-      { logger }
-    );
+    const run = await architect.scheduleTarget(targetSpec);
 
     // The "result" member (of type BuilderOutput) is the next output.
     const output = await run.result;
 
     // Stop the builder from running. This stops Architect from keeping
@@ -71,67 +29,38 @@ describe('Ngssc Builder', () => {
     // to be scheduled.
     await run.stop();
     return output;
   }
 
+  function readNgsscJson(): Ngssc {
+    const content = virtualFs.fileBufferToString(
+      host.scopedSync().read(normalize('dist/ngssc.json'))
+    );
+
+    return JSON.parse(content);
+  }
+
   it('should build with process variant', async () => {
-    addDummyBuildTarget();
-    const output = await runNgsscbuild({
-      additionalEnvironmentVariables: [],
-      aotSupport: false,
-      browserTarget: 'dummy:build',
-      filePattern: '',
-    });
+    const output = await runNgsscbuild();
 
     expect(output.success).toBe(true);
-    expect(logs.some((l) => l.includes('ngssc'))).toBeTruthy();
-    const ngssc: Ngssc = JSON.parse(readFileSync(join(distDir, 'ngssc.json'), 'utf8'));
+
+    const ngssc = readNgsscJson();
     expect(ngssc.variant).toEqual('process');
     expect(ngssc.filePattern).toEqual('index.html');
   });
 
   it('should aggregate environment variables', async () => {
     const expected = 'OTHER_VARIABLE';
-    addDummyBuildTarget();
-    const output = await runNgsscbuild({
-      additionalEnvironmentVariables: [expected],
-      aotSupport: false,
-      browserTarget: 'dummy:build',
-      filePattern: '',
-    });
+    host.replaceInFile(
+      'angular.json',
+      '"additionalEnvironmentVariables": [],',
+      `"additionalEnvironmentVariables": ["${expected}"],`
+    );
+    const output = await runNgsscbuild();
 
     expect(output.success).toBe(true);
-    expect(logs.some((l) => l.includes('ngssc'))).toBeTruthy();
-    const ngssc: Ngssc = JSON.parse(readFileSync(join(distDir, 'ngssc.json'), 'utf8'));
+
+    const ngssc = readNgsscJson();
     expect(ngssc.environmentVariables).toContain(expected);
   });
 });
-
-const envContent = `
-import 'angular-server-side-configuration/process';
-
-/**
- * How to use angular-server-side-configuration:
- *
- * Use process.env.NAME_OF_YOUR_ENVIRONMENT_VARIABLE
- *
- * export const environment = {
- *   stringValue: process.env.STRING_VALUE,
- *   stringValueWithDefault: process.env.STRING_VALUE || 'defaultValue',
- *   numberValue: Number(process.env.NUMBER_VALUE),
- *   numberValueWithDefault: Number(process.env.NUMBER_VALUE || 10),
- *   booleanValue: Boolean(process.env.BOOLEAN_VALUE),
- *   booleanValueInverted: process.env.BOOLEAN_VALUE_INVERTED !== 'false',
- * };
- */
-
-export const environment = {
-  production: process.env.PROD !== 'false',
-  apiBackend: process.env.API_BACKEND || 'http://example.com',
-  ternary: process.env.TERNARY ? 'asdf' : 'qwer',
-  simpleValue: process.env.SIMPLE_VALUE,
-  something: {
-    asdf: process.env.OMG || 'omg',
-    qwer: parseInt(process.env.NUMBER || ''),
-  }
-};
-`;
diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts b/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts
index b6f6c6d..4f0af16 100644
--- a/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts
+++ b/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts
@@ -1,11 +1,12 @@
-import { lstatSync, promises, readdirSync } from 'fs';
+import { promises } from 'fs';
 import { basename, join } from 'path';
 import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect';
+import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
 import { json, JsonObject } from '@angular-devkit/core';
 import { Ngssc } from 'angular-server-side-configuration';
-import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
+import { glob } from 'glob';
 import { Schema } from './schema';
 import { VariableDetector } from './variable-detector';
 import { NgsscContext } from './ngssc-context';
 
 export type NgsscBuildSchema = Schema;
@@ -31,19 +32,35 @@ export async function detectVariablesAndBuildNgsscJson(
   options: NgsscBuildSchema,
   browserOptions: BrowserBuilderOptions,
   context: BuilderContext,
   multiple: boolean = false
 ) {
-  const ngsscContext = await detectVariables(context);
+  const ngsscContext = await detectVariables(context, options.searchPattern);
   const outputPath = join(context.workspaceRoot, browserOptions.outputPath);
   const ngssc = buildNgssc(ngsscContext, options, browserOptions, multiple);
   await writeFileAsync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8');
 }
 
-export async function detectVariables(context: BuilderContext): Promise<NgsscContext> {
+export async function detectVariables(
+  context: BuilderContext,
+  searchPattern?: string | null
+): Promise<NgsscContext> {
+  const projectName = context.target && context.target.project;
+  if (!projectName) {
+    throw new Error('The builder requires a target.');
+  }
+
+  const projectMetadata = await context.getProjectMetadata(projectName);
+  const sourceRoot = projectMetadata.sourceRoot as string | undefined;
+  const defaultSearchPattern = sourceRoot ? `${sourceRoot}/**/!(*server*).ts` : '**/!(*server*).ts';
+
   const detector = new VariableDetector(context.logger);
-  const typeScriptFiles = findTypeScriptFiles(context.workspaceRoot);
+  const typeScriptFiles = await glob(searchPattern || defaultSearchPattern, {
+    absolute: true,
+    cwd: context.workspaceRoot,
+    ignore: ['**/node_modules/**', '**/*.spec.ts', '**/*.d.ts'],
+  });
   let ngsscContext: NgsscContext | null = null;
   for (const file of typeScriptFiles) {
     const fileContent = await readFileAsync(file, 'utf8');
     const innerNgsscContext = detector.detect(fileContent);
     if (!innerNgsscContext.variables.length) {
@@ -71,27 +88,10 @@ export async function detectVariables(context: BuilderContext): Promise<NgsscCon
   );
 
   return ngsscContext;
 }
 
-function findTypeScriptFiles(root: string): string[] {
-  const directory = root.replace(/\\/g, '/');
-  return readdirSync(directory)
-    .map((f) => `${directory}/${f}`)
-    .map((f) => {
-      const stat = lstatSync(f);
-      if (stat.isDirectory()) {
-        return findTypeScriptFiles(f);
-      } else if (stat.isFile() && f.endsWith('.ts') && !f.endsWith('.spec.ts')) {
-        return [f];
-      } else {
-        return [];
-      }
-    })
-    .reduce((current, next) => current.concat(next), []);
-}
-
 export function buildNgssc(
   ngsscContext: NgsscContext,
   options: NgsscBuildSchema,
   browserOptions?: BrowserBuilderOptions,
   multiple: boolean = false
diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/schema.json b/projects/angular-server-side-configuration/builders/ngsscbuild/schema.json
index bd3b6eb..429a1ed 100644
--- a/projects/angular-server-side-configuration/builders/ngsscbuild/schema.json
+++ b/projects/angular-server-side-configuration/builders/ngsscbuild/schema.json
@@ -12,8 +12,13 @@
     },
     "filePattern": {
       "type": "string",
       "description": "The file pattern, into which the environment variables should be inserted during ngssc insert (Defaults to index.html)",
       "default": ""
+    },
+    "searchPattern": {
+      "type": "string",
+      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
+      "default": ""
     }
   }
 }
diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/schema.ts b/projects/angular-server-side-configuration/builders/ngsscbuild/schema.ts
index 1804325..f667b84 100644
--- a/projects/angular-server-side-configuration/builders/ngsscbuild/schema.ts
+++ b/projects/angular-server-side-configuration/builders/ngsscbuild/schema.ts
@@ -1,5 +1,6 @@
 export interface Schema {
   additionalEnvironmentVariables: string[];
   browserTarget: string;
   filePattern: string | null;
+  searchPattern?: string | null;
 }
diff --git a/projects/angular-server-side-configuration/ng-package.json b/projects/angular-server-side-configuration/ng-package.json
index 8a0f36b..cc00bf3 100644
--- a/projects/angular-server-side-configuration/ng-package.json
+++ b/projects/angular-server-side-configuration/ng-package.json
@@ -1,7 +1,8 @@
 {
   "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
   "dest": "../../dist/angular-server-side-configuration",
   "lib": {
     "entryFile": "src/public-api.ts"
-  }
+  },
+  "allowedNonPeerDependencies": ["glob"]
 }
diff --git a/projects/angular-server-side-configuration/package.json b/projects/angular-server-side-configuration/package.json
index 803dbee..f2708cb 100644
--- a/projects/angular-server-side-configuration/package.json
+++ b/projects/angular-server-side-configuration/package.json
@@ -29,8 +29,9 @@
   "peerDependencies": {
     "@angular/common": "^15.0.0",
     "@angular/core": "^15.0.0"
   },
   "dependencies": {
-    "tslib": "^2.3.0"
+    "tslib": "^2.3.0",
+    "glob": "^9.3.1"
   }
 }
diff --git a/projects/angular-server-side-configuration/schematics/ng-add/index.ts b/projects/angular-server-side-configuration/schematics/ng-add/index.ts
index 1389b9f..582d7d0 100644
--- a/projects/angular-server-side-configuration/schematics/ng-add/index.ts
+++ b/projects/angular-server-side-configuration/schematics/ng-add/index.ts
@@ -85,10 +85,12 @@ function addDescriptionToMainFile(options: Schema): Rule {
       context.logger.warn(noAppropriateInsertFileWarning);
       return;
     }
 
     const insertFile = [
+      join(dirname(mainFile), 'environment.prod.ts'),
+      join(dirname(mainFile), 'environment.ts'),
       join(dirname(mainFile), 'app/app.module.ts'),
       join(dirname(mainFile), 'app/app.component.ts'),
       mainFile,
     ].find((f) => host.exists(f));
     if (!insertFile) {
diff --git a/test/hello-world-app/.browserslistrc b/test/hello-world-app/.browserslistrc
new file mode 100644
index 0000000..7fd7c3b
--- /dev/null
+++ b/test/hello-world-app/.browserslistrc
@@ -0,0 +1,4 @@
+# We want to run tests large with ever green browser so that 
+# we never trigger differential loading as this will slow down the tests.
+
+last 2 Chrome versions
\ No newline at end of file
diff --git a/test/hello-world-app/.editorconfig b/test/hello-world-app/.editorconfig
new file mode 100644
index 0000000..e89330a
--- /dev/null
+++ b/test/hello-world-app/.editorconfig
@@ -0,0 +1,13 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/test/hello-world-app/.gitignore b/test/hello-world-app/.gitignore
new file mode 100644
index 0000000..e3038ca
--- /dev/null
+++ b/test/hello-world-app/.gitignore
@@ -0,0 +1,3 @@
+# Don't ignore node_modules, this project is not meant to be installed.
+# Also, ~ import path in styles does only looks in the first node_modules found.
+# /node_modules
diff --git a/test/hello-world-app/README.md b/test/hello-world-app/README.md
new file mode 100644
index 0000000..563afe4
--- /dev/null
+++ b/test/hello-world-app/README.md
@@ -0,0 +1,27 @@
+# HelloWorldApp
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0-beta.1.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/main/README.md).
diff --git a/test/hello-world-app/angular.json b/test/hello-world-app/angular.json
new file mode 100644
index 0000000..359ac1b
--- /dev/null
+++ b/test/hello-world-app/angular.json
@@ -0,0 +1,162 @@
+{
+  "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json",
+  "version": 1,
+  "newProjectRoot": "./projects",
+  "cli": {
+    "cache": {
+      "enabled": false
+    }
+  },
+  "schematics": {},
+  "projects": {
+    "app": {
+      "root": "src",
+      "sourceRoot": "src",
+      "projectType": "application",
+      "prefix": "app",
+      "schematics": {},
+      "targets": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:browser",
+          "options": {
+            "outputPath": "dist",
+            "index": "src/index.html",
+            "main": "src/main.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "src/tsconfig.app.json",
+            "progress": false,
+            "sourceMap": false,
+            "aot": false,
+            "vendorChunk": true,
+            "buildOptimizer": false,
+            "optimization": false,
+            "extractLicenses": false,
+            "namedChunks": true,
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "src/styles.css"
+            ],
+            "scripts": []
+          },
+          "configurations": {
+            "production": {
+              "optimization": true,
+              "outputHashing": "all",
+              "sourceMap": false,
+              "namedChunks": false,
+              "aot": true,
+              "extractLicenses": true,
+              "vendorChunk": false,
+              "buildOptimizer": true
+            },
+            "inline-critical-css": {
+              "optimization": {
+                "styles": {
+                  "minify": true,
+                  "inlineCritical": true
+                },
+                "scripts": true,
+                "fonts": true
+              }
+            }
+          }
+        },
+        "server": {
+          "builder": "@angular-devkit/build-angular:server",
+          "options": {
+            "outputPath": "dist-server",
+            "main": "src/main.server.ts",
+            "tsConfig": "src/tsconfig.server.json",
+            "progress": false,
+            "sourceMap": true,
+            "optimization": false,
+            "extractLicenses": false
+          }
+        },
+        "app-shell": {
+          "builder": "@angular-devkit/build-angular:app-shell",
+          "options": {
+            "browserTarget": "app:build",
+            "serverTarget": "app:server"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "app:build:production"
+            }
+          }
+        },
+        "serve": {
+          "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "browserTarget": "app:build",
+            "watch": false
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "app:build:production"
+            }
+          }
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "browserTarget": "app:build",
+            "progress": false,
+            "outputPath": "src"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "polyfills": ["zone.js", "zone.js/testing"],
+
+            "tsConfig": "src/tsconfig.spec.json",
+            "karmaConfig": "karma.conf.js",
+            "browsers": "ChromeHeadlessCI",
+            "progress": false,
+            "watch": false,
+            "styles": [
+              {
+                "input": "src/styles.css"
+              }
+            ],
+            "scripts": [],
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ]
+          }
+        },
+        "ngsscbuild": {
+          "builder": "angular-server-side-configuration:ngsscbuild",
+          "options": {
+            "additionalEnvironmentVariables": [],
+            "browserTarget": "app:build"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "app:build:production"
+            }
+          }
+        }
+      }
+    },
+    "app-e2e": {
+      "root": "e2e",
+      "projectType": "application",
+      "targets": {
+        "e2e": {
+          "builder": "@angular-devkit/build-angular:protractor",
+          "options": {
+            "protractorConfig": "protractor.conf.js",
+            "devServerTarget": "app:serve",
+            "webdriverUpdate": false
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/test/hello-world-app/karma.conf.js b/test/hello-world-app/karma.conf.js
new file mode 100644
index 0000000..1fa1906
--- /dev/null
+++ b/test/hello-world-app/karma.conf.js
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+const path = require('path');
+process.env.CHROME_BIN = require('puppeteer').executablePath();
+
+module.exports = function(config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage'),
+      require('@angular-devkit/build-angular/plugins/karma'),
+    ],
+    client: {
+      clearContext: false, // leave Jasmine Spec Runner output visible in browser
+    },
+    jasmineHtmlReporter: {
+      suppressAll: true // removes the duplicated traces
+    },
+    coverageReporter: {
+      dir: path.join(__dirname, './coverage'),
+      subdir: '.',
+      reporters: [
+        {type: 'lcov'},
+      ],
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    customLaunchers: {
+      ChromeHeadlessCI: {
+        base: 'ChromeHeadless',
+        flags: ['--disable-gpu'],
+      },
+    },
+    singleRun: false,
+  });
+};
diff --git a/test/hello-world-app/protractor.conf.js b/test/hello-world-app/protractor.conf.js
new file mode 100644
index 0000000..9b57663
--- /dev/null
+++ b/test/hello-world-app/protractor.conf.js
@@ -0,0 +1,43 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
+const { resolve } = require('path');
+
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: ['./e2e/**/*.e2e-spec.ts'],
+  capabilities: {
+    browserName: 'chrome',
+    chromeOptions: {
+      args: ['--headless', '--disable-gpu', '--window-size=800,600'],
+      binary: require('puppeteer').executablePath(),
+    },
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {},
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: resolve(__dirname, './e2e/tsconfig.e2e.json'),
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({
+      spec: {
+        displayStacktrace: StacktraceOption.PRETTY
+      }
+    }));
+  },
+};
diff --git a/test/hello-world-app/src/app/app.component.css b/test/hello-world-app/src/app/app.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/test/hello-world-app/src/app/app.component.html b/test/hello-world-app/src/app/app.component.html
new file mode 100644
index 0000000..2486321
--- /dev/null
+++ b/test/hello-world-app/src/app/app.component.html
@@ -0,0 +1,22 @@
+<!--The content below is only a placeholder and can be replaced.-->
+<div style="text-align:center">
+  <h1>
+    Welcome to {{ title }}!
+  </h1>
+  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
+</div>
+<h2>Here are some links to help you start: </h2>
+<ul>
+  <li>
+    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
+  </li>
+  <li>
+    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
+  </li>
+  <li>
+    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
+  </li>
+</ul>
+
+<p i18n>i18n test</p>
+
diff --git a/test/hello-world-app/src/app/app.component.spec.ts b/test/hello-world-app/src/app/app.component.spec.ts
new file mode 100644
index 0000000..56f6ecb
--- /dev/null
+++ b/test/hello-world-app/src/app/app.component.spec.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+describe('AppComponent', () => {
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [AppComponent],
+    }).compileComponents();
+  });
+  it('should create the app', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  });
+  it(`should have as title 'app'`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('app');
+  });
+  it('should render title in a h1 tag', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    fixture.detectChanges();
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
+  });
+});
diff --git a/test/hello-world-app/src/app/app.component.ts b/test/hello-world-app/src/app/app.component.ts
new file mode 100644
index 0000000..38141a7
--- /dev/null
+++ b/test/hello-world-app/src/app/app.component.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.css'],
+})
+export class AppComponent {
+  title = process.env.TEST;
+}
diff --git a/test/hello-world-app/src/app/app.module.ts b/test/hello-world-app/src/app/app.module.ts
new file mode 100644
index 0000000..09f4f6f
--- /dev/null
+++ b/test/hello-world-app/src/app/app.module.ts
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+
+import { AppComponent } from './app.component';
+
+
+@NgModule({
+  declarations: [
+    AppComponent
+  ],
+  imports: [
+    BrowserModule
+  ],
+  providers: [],
+  bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/test/hello-world-app/src/app/app.server.module.ts b/test/hello-world-app/src/app/app.server.module.ts
new file mode 100644
index 0000000..cddaa2d
--- /dev/null
+++ b/test/hello-world-app/src/app/app.server.module.ts
@@ -0,0 +1,22 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { NgModule } from '@angular/core';
+import { ServerModule } from '@angular/platform-server';
+
+import { AppModule } from './app.module';
+import { AppComponent } from './app.component';
+
+@NgModule({
+  imports: [
+    AppModule,
+    ServerModule,
+  ],
+  bootstrap: [AppComponent],
+})
+export class AppServerModule {}
diff --git a/test/hello-world-app/src/assets/.gitkeep b/test/hello-world-app/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/test/hello-world-app/src/favicon.ico b/test/hello-world-app/src/favicon.ico
new file mode 100644
index 0000000..8081c7c
Binary files /dev/null and b/test/hello-world-app/src/favicon.ico differ
diff --git a/test/hello-world-app/src/index.html b/test/hello-world-app/src/index.html
new file mode 100644
index 0000000..189ff56
--- /dev/null
+++ b/test/hello-world-app/src/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>HelloWorldApp</title>
+  <base href="/">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+</head>
+<body>
+  <app-root></app-root>
+</body>
+</html>
diff --git a/test/hello-world-app/src/main.server.ts b/test/hello-world-app/src/main.server.ts
new file mode 100644
index 0000000..4448f40
--- /dev/null
+++ b/test/hello-world-app/src/main.server.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export { AppServerModule } from './app/app.server.module';
diff --git a/test/hello-world-app/src/main.ts b/test/hello-world-app/src/main.ts
new file mode 100644
index 0000000..2a16fe2
--- /dev/null
+++ b/test/hello-world-app/src/main.ts
@@ -0,0 +1,15 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+
+platformBrowserDynamic()
+  .bootstrapModule(AppModule)
+  .catch(err => console.log(err));
diff --git a/test/hello-world-app/src/polyfills.ts b/test/hello-world-app/src/polyfills.ts
new file mode 100644
index 0000000..f530c61
--- /dev/null
+++ b/test/hello-world-app/src/polyfills.ts
@@ -0,0 +1,70 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ *  (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js'; // Included with Angular CLI.
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/test/hello-world-app/src/spectrum.png b/test/hello-world-app/src/spectrum.png
new file mode 100644
index 0000000..2a5f123
Binary files /dev/null and b/test/hello-world-app/src/spectrum.png differ
diff --git a/test/hello-world-app/src/src/locale/messages.xlf b/test/hello-world-app/src/src/locale/messages.xlf
new file mode 100644
index 0000000..5ba84a9
--- /dev/null
+++ b/test/hello-world-app/src/src/locale/messages.xlf
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+  <file source-language="en" datatype="plaintext" original="ng2.template">
+    <body>
+      <trans-unit id="8261100fbc1830397116d0a032e7c8f54af93c68" datatype="html">
+        <source>i18n test</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/app.component.ts</context>
+          <context context-type="linenumber">21</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/app.component.ts</context>
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
diff --git a/test/hello-world-app/src/styles.css b/test/hello-world-app/src/styles.css
new file mode 100644
index 0000000..90d4ee0
--- /dev/null
+++ b/test/hello-world-app/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/test/hello-world-app/src/tsconfig.app.json b/test/hello-world-app/src/tsconfig.app.json
new file mode 100644
index 0000000..c8fa0d6
--- /dev/null
+++ b/test/hello-world-app/src/tsconfig.app.json
@@ -0,0 +1,14 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "types": ["@angular/localize", "node"]
+  },
+  "files": [
+    "main.ts",
+    "polyfills.ts"
+  ],
+  "include": [
+    "**/*.d.ts"
+  ]
+}
diff --git a/test/hello-world-app/src/tsconfig.server.json b/test/hello-world-app/src/tsconfig.server.json
new file mode 100644
index 0000000..9a492c7
--- /dev/null
+++ b/test/hello-world-app/src/tsconfig.server.json
@@ -0,0 +1,11 @@
+{
+  "extends": "./tsconfig.app.json",
+  "compilerOptions": {
+    "outDir": "../dist-server",
+    "baseUrl": "./",
+    "types": ["@angular/localize", "node"]
+  },
+  "files": [
+    "main.server.ts"
+  ]
+}
diff --git a/test/hello-world-app/src/tsconfig.spec.json b/test/hello-world-app/src/tsconfig.spec.json
new file mode 100644
index 0000000..a42459f
--- /dev/null
+++ b/test/hello-world-app/src/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/spec",
+    "types": [
+      "jasmine",
+      "@angular/localize"
+    ]
+  },
+  "files": [
+    "polyfills.ts"
+  ],
+  "include": [
+    "**/*.spec.ts",
+    "**/*.d.ts"
+  ]
+}
diff --git a/test/hello-world-app/src/typings.d.ts b/test/hello-world-app/src/typings.d.ts
new file mode 100644
index 0000000..9ba8f77
--- /dev/null
+++ b/test/hello-world-app/src/typings.d.ts
@@ -0,0 +1,13 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/* SystemJS module definition */
+declare var module: NodeModule;
+interface NodeModule {
+  id: string;
+}
diff --git a/test/hello-world-app/tsconfig.json b/test/hello-world-app/tsconfig.json
new file mode 100644
index 0000000..26bec6b
--- /dev/null
+++ b/test/hello-world-app/tsconfig.json
@@ -0,0 +1,27 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "baseUrl": "./",
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "target": "es2022",
+    "module": "es2022",
+    "useDefineForClassFields": false,
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2022",
+      "dom"
+    ]
+  },
+  "angularCompilerOptions": {
+    "enableIvy": true,
+    "disableTypeScriptVersionCheck": true,
+    "strictTemplates": true
+  }
+}
diff --git a/test/jasmine.js b/test/jasmine.js
index 2efafff..687bda0 100644
--- a/test/jasmine.js
+++ b/test/jasmine.js
@@ -7,7 +7,14 @@ if (module === require.main) {
   require('ts-node').register(tsconfig);
 
   const jasmine = new Jasmine({ projectBaseDir: path.resolve() });
   jasmine.exitOnCompletion = true;
   jasmine.addMatchingSpecFiles(['projects/angular-server-side-configuration/**/*.spec.ts']);
-  jasmine.execute();
+  jasmine
+    .execute()
+    .then((result) => {
+      if (result.failedExpectations.length) {
+        console.error(result.failedExpectations);
+      }
+    })
+    .catch((e) => console.error(e));
 }
diff --git a/test/test-utils.ts b/test/test-utils.ts
new file mode 100644
index 0000000..78c4567
--- /dev/null
+++ b/test/test-utils.ts
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import { Architect, BuilderOutput } from '@angular-devkit/architect';
+import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
+import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing';
+import { Path, getSystemPath, join, normalize, schema, workspaces } from '@angular-devkit/core';
+
+// Default timeout for large specs is 2.5 minutes.
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
+
+export const workspaceRoot = join(normalize(__dirname), `hello-world-app/`);
+export const host = new TestProjectHost(workspaceRoot);
+export const outputPath: Path = normalize('dist');
+
+export const browserTargetSpec = { project: 'app', target: 'build' };
+export const devServerTargetSpec = { project: 'app', target: 'serve' };
+export const extractI18nTargetSpec = { project: 'app', target: 'extract-i18n' };
+export const karmaTargetSpec = { project: 'app', target: 'test' };
+export const tslintTargetSpec = { project: 'app', target: 'lint' };
+export const protractorTargetSpec = { project: 'app-e2e', target: 'e2e' };
+
+export async function createArchitect(workspaceRoot: Path) {
+  const registry = new schema.CoreSchemaRegistry();
+  registry.addPostTransform(schema.transforms.addUndefinedDefaults);
+  const workspaceSysPath = getSystemPath(workspaceRoot);
+
+  const { workspace } = await workspaces.readWorkspace(
+    workspaceSysPath,
+    workspaces.createWorkspaceHost(host)
+  );
+  const architectHost = new TestingArchitectHost(
+    workspaceSysPath,
+    workspaceSysPath,
+    new WorkspaceNodeModulesArchitectHost(workspace, workspaceSysPath)
+  );
+  await architectHost.addBuilderFromPackage('..');
+  //require('ts-node').register(require('../projects/angular-server-side-configuration/builders/tsconfig.json'));
+  await architectHost.addBuilderFromPackage(
+    '../../../../dist/angular-server-side-configuration'
+  );
+  const architect = new Architect(architectHost, registry);
+
+  return {
+    workspace,
+    architectHost,
+    architect,
+  };
+}
+
+export interface BrowserBuildOutput {
+  output: BuilderOutput;
+  files: { [file: string]: Promise<string> };
+}
diff --git a/yarn.lock b/yarn.lock
index 19c1c82..396eaac 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4361,10 +4361,20 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7:
     inherits "2"
     minimatch "^3.1.1"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+glob@^9.3.1:
+  version "9.3.1"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.1.tgz#4a15cf72479a440f77e97805b21e1b5b6e4fb18a"
+  integrity sha512-qERvJb7IGsnkx6YYmaaGvDpf77c951hICMdWaFXyH3PlVob8sbPJJyJX0kWkiCWyXUzoy9UOTNjGg0RbD8bYIw==
+  dependencies:
+    fs.realpath "^1.0.0"
+    minimatch "^7.4.1"
+    minipass "^4.2.4"
+    path-scurry "^1.6.1"
+
 globals@^11.1.0:
   version "11.12.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
@@ -5431,10 +5441,15 @@ lru-cache@^6.0.0:
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
   integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
   dependencies:
     yallist "^4.0.0"
 
+lru-cache@^7.14.1:
+  version "7.18.3"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
+  integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
+
 lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1:
   version "7.14.1"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea"
   integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==
 
@@ -5642,10 +5657,17 @@ minimatch@^5.0.1:
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7"
   integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==
   dependencies:
     brace-expansion "^2.0.1"
 
+minimatch@^7.4.1:
+  version "7.4.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.2.tgz#157e847d79ca671054253b840656720cb733f10f"
+  integrity sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==
+  dependencies:
+    brace-expansion "^2.0.1"
+
 minimatch@~3.0.4:
   version "3.0.8"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
   integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==
   dependencies:
@@ -5728,10 +5750,15 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae"
   integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==
   dependencies:
     yallist "^4.0.0"
 
+minipass@^4.0.2, minipass@^4.2.4:
+  version "4.2.5"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb"
+  integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==
+
 minizlib@^2.1.1, minizlib@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
   integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
   dependencies:
@@ -6365,10 +6392,18 @@ path-key@^3.0.0, path-key@^3.1.0:
 path-parse@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
+path-scurry@^1.6.1:
+  version "1.6.2"
+  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.2.tgz#72783838113a4a0cec155323b2c01eb1b193396f"
+  integrity sha512-J6MQNh56h6eHFY3vsQ+Lq+zKPwn71POieutmVt2leU8W+zz8HVIdJyn3I3Zs6IKbIQtuKXirVjTBFNBcbFO44Q==
+  dependencies:
+    lru-cache "^7.14.1"
+    minipass "^4.0.2"
+
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
   integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
 

Vulnerable code: either `app.module.ts`, `app.components.ts` or `main.ts` depending on availability. The ngssc
builder will also now search all files for environment variable usages instead of just the
defined environment file. Due to this, the `ngsscEnvironmentFile` option can be removed.
            "filePattern": "index.html"
### environment.prod.ts
            "required": ["input"]
            "required": ["input"]
      "enum": ["css", "less", "sass", "scss"]
      "enum": ["warning", "error", "ignore"],
      "enum": ["warning", "error", "ignore"],
      "enum": ["none", "all", "media", "bundles"]
          "required": ["input"]
      "enum": ["none", "anonymous", "use-credentials"]
  "required": ["outputPath", "index", "main", "tsConfig"],
          "required": ["glob", "input", "output"]
          "required": ["src", "replaceWith"]
          "required": ["replace", "with"]
          "enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
      "required": ["type"]
}
    const ngsscContext = await detectVariables(context);
  "required": ["browserTarget"]
}
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { JsonObject, schema } from '@angular-devkit/core';
import { Logger } from '@angular-devkit/core/src/logger';
import { Schema } from './schema';
  let tmpDir: string;
  let distDir: string;
  let architect: Architect;
  let architectHost: TestingArchitectHost;
  let logger: Logger;
  let logs: string[];
  const buildConfig = {
    fileReplacements: [{ replace: 'dummy', with: 'environment.prod.ts' }],
    index: 'src/index.html',
    outputPath: 'dist',
  };
    const registry = new schema.CoreSchemaRegistry();
    registry.addPostTransform(schema.transforms.addUndefinedDefaults);

    tmpDir = mkdtempSync(join(tmpdir(), 'ngssc-'));
    writeFileSync(join(tmpDir, 'environment.prod.ts'), envContent, 'utf8');
    distDir = join(tmpDir, 'dist');
    mkdirSync(distDir);
    writeFileSync(join(distDir, 'main.js'), '', 'utf8');
    architectHost = new TestingArchitectHost(tmpDir, tmpDir);
    architect = new Architect(architectHost, registry);

    // This will either take a Node package name, or a path to the directory
    // for the package.json file.
    await architectHost.addBuilderFromPackage(
      '../../../../projects/angular-server-side-configuration'
    );
    await architectHost.addBuilderFromPackage('..');

    logs = [];
    logger = new Logger('ngssc');
    logger.subscribe((m) => logs.push(m.message));
  function addDummyBuildTarget(config: any = buildConfig) {
    architectHost.addTarget(
      { project: 'dummy', target: 'build' },
      '@angular-devkit/architect:true',
      config
    );
  }
  async function runNgsscbuild(options: Schema & JsonObject) {
    const run = await architect.scheduleBuilder(
      'angular-server-side-configuration:ngsscbuild',
      options,
      { logger }
    );
    addDummyBuildTarget();
    const output = await runNgsscbuild({
      additionalEnvironmentVariables: [],
      aotSupport: false,
      browserTarget: 'dummy:build',
      filePattern: '',
    });
    expect(logs.some((l) => l.includes('ngssc'))).toBeTruthy();
    const ngssc: Ngssc = JSON.parse(readFileSync(join(distDir, 'ngssc.json'), 'utf8'));
    addDummyBuildTarget();
    const output = await runNgsscbuild({
      additionalEnvironmentVariables: [expected],
      aotSupport: false,
      browserTarget: 'dummy:build',
      filePattern: '',
    });
    expect(logs.some((l) => l.includes('ngssc'))).toBeTruthy();
    const ngssc: Ngssc = JSON.parse(readFileSync(join(distDir, 'ngssc.json'), 'utf8'));

const envContent = `
import 'angular-server-side-configuration/process';

/**
 * How to use angular-server-side-configuration:
 *
 * Use process.env.NAME_OF_YOUR_ENVIRONMENT_VARIABLE
 *
 * export const environment = {
 *   stringValue: process.env.STRING_VALUE,
 *   stringValueWithDefault: process.env.STRING_VALUE || 'defaultValue',
 *   numberValue: Number(process.env.NUMBER_VALUE),
 *   numberValueWithDefault: Number(process.env.NUMBER_VALUE || 10),
 *   booleanValue: Boolean(process.env.BOOLEAN_VALUE),
 *   booleanValueInverted: process.env.BOOLEAN_VALUE_INVERTED !== 'false',
 * };
 */

export const environment = {
  production: process.env.PROD !== 'false',
  apiBackend: process.env.API_BACKEND || 'http://example.com',
  ternary: process.env.TERNARY ? 'asdf' : 'qwer',
  simpleValue: process.env.SIMPLE_VALUE,
  something: {
    asdf: process.env.OMG || 'omg',
    qwer: parseInt(process.env.NUMBER || ''),
  }
};
`;
import { lstatSync, promises, readdirSync } from 'fs';
import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
  const ngsscContext = await detectVariables(context);
export async function detectVariables(context: BuilderContext): Promise<NgsscContext> {
  const typeScriptFiles = findTypeScriptFiles(context.workspaceRoot);
function findTypeScriptFiles(root: string): string[] {
  const directory = root.replace(/\\/g, '/');
  return readdirSync(directory)
    .map((f) => `${directory}/${f}`)
    .map((f) => {
      const stat = lstatSync(f);
      if (stat.isDirectory()) {
        return findTypeScriptFiles(f);
      } else if (stat.isFile() && f.endsWith('.ts') && !f.endsWith('.spec.ts')) {
        return [f];
      } else {
        return [];
      }
    })
    .reduce((current, next) => current.concat(next), []);
}

  }
    "tslib": "^2.3.0"
  jasmine.execute();
Fixed code: test/test-project-*
18
## Changes with version 15.1

With the change in version 15 to search all files for environment variables, a potential
information disclosure vulnerability was introduced, as unintended environment variables might
be exposed.

You might be affected, if you use environment variables in your angular.json workspace that expose
application internals (like used services/tools) or secrets that are also available on the
deployed environment (e.g. a monorepo that also contains a Node.js backend, which is deployed in
the same system/container/pod as the frontend bundle).

This should have little to no impact, if you only have a simple angular.json workspace without
a backend component.

With 15.1 this risk is mitigated, as the lookup path is restricted by adding an option
`searchPattern` which defaults to `{sourceRoot}/**/!(*server*).ts` (only search `sourceRoot`
of the related angular.json project and exclude files with `server` in the name).


either `environment.prod.ts`, `environment.ts`, `app.module.ts`, `app.components.ts` or `main.ts`
depending on availability. The ngssc builder will also now search all files for environment
variable usages instead of just the defined environment file. Due to this, the
`ngsscEnvironmentFile` option can be removed.
            "filePattern": "index.html",
            // (Defaults to {sourceRoot}/**/!(*server*).ts)
            "searchPattern": "src/environments/environment*.ts"
### TypeScript files
    "pretest:lib": "yarn -s build:lib",
    "glob": "^9.3.1",
            "required": [
              "input"
            ]
            "required": [
              "input"
            ]
      "enum": [
        "css",
        "less",
        "sass",
        "scss"
      ]
      "enum": [
        "warning",
        "error",
        "ignore"
      ],
      "enum": [
        "warning",
        "error",
        "ignore"
      ],
      "enum": [
        "none",
        "all",
        "media",
        "bundles"
      ]
          "required": [
            "input"
          ]
      "enum": [
        "none",
        "anonymous",
        "use-credentials"
      ]
    },
    "searchPattern": {
      "type": "string",
      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
      "default": ""
  "required": [
    "outputPath",
    "index",
    "main",
    "tsConfig"
  ],
          "required": [
            "glob",
            "input",
            "output"
          ]
          "required": [
            "src",
            "replaceWith"
          ]
          "required": [
            "replace",
            "with"
          ]
          "enum": [
            "all",
            "allScript",
            "any",
            "anyScript",
            "anyComponentStyle",
            "bundle",
            "initial"
          ]
      "required": [
        "type"
      ]
}
    const ngsscContext = await detectVariables(context, options.searchPattern);
    },
    "searchPattern": {
      "type": "string",
      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
      "default": ""
  "required": [
    "browserTarget"
  ]
}
import { normalize, virtualFs } from '@angular-devkit/core';
import { createArchitect, host } from '../../../../test/test-utils';
  const targetSpec = { project: 'app', target: 'ngsscbuild' };
  let architect: Architect | undefined;
    architect = undefined;
    await host.initialize().toPromise();
  afterEach(async () => host.restore().toPromise());
  async function runNgsscbuild() {
    architect = (await createArchitect(host.root())).architect;
    const run = await architect.scheduleTarget(targetSpec);
  function readNgsscJson(): Ngssc {
    const content = virtualFs.fileBufferToString(
      host.scopedSync().read(normalize('dist/ngssc.json'))
    );

    return JSON.parse(content);
  }

    const output = await runNgsscbuild();

    const ngssc = readNgsscJson();
    host.replaceInFile(
      'angular.json',
      '"additionalEnvironmentVariables": [],',
      `"additionalEnvironmentVariables": ["${expected}"],`
    );
    const output = await runNgsscbuild();

    const ngssc = readNgsscJson();
import { promises } from 'fs';
import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
import { glob } from 'glob';
  const ngsscContext = await detectVariables(context, options.searchPattern);
export async function detectVariables(
  context: BuilderContext,
  searchPattern?: string | null
): Promise<NgsscContext> {
  const projectName = context.target && context.target.project;
  if (!projectName) {
    throw new Error('The builder requires a target.');
  }

  const projectMetadata = await context.getProjectMetadata(projectName);
  const sourceRoot = projectMetadata.sourceRoot as string | undefined;
  const defaultSearchPattern = sourceRoot ? `${sourceRoot}/**/!(*server*).ts` : '**/!(*server*).ts';

  const typeScriptFiles = await glob(searchPattern || defaultSearchPattern, {
    absolute: true,
    cwd: context.workspaceRoot,
    ignore: ['**/node_modules/**', '**/*.spec.ts', '**/*.d.ts'],
  });
    },
    "searchPattern": {
      "type": "string",
      "description": "The search pattern to use when searching for environment variable occurrences (Defaults to {sourceRoot}/**/!(*server*).ts)",
      "default": ""
  searchPattern?: string | null;
  },
  "allowedNonPeerDependencies": ["glob"]
    "tslib": "^2.3.0",
    "glob": "^9.3.1"
      join(dirname(mainFile), 'environment.prod.ts'),
      join(dirname(mainFile), 'environment.ts'),
# We want to run tests large with ever green browser so that 
# we never trigger differential loading as this will slow down the tests.

last 2 Chrome versions
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
# Don't ignore node_modules, this project is not meant to be installed.
# Also, ~ import path in styles does only looks in the first node_modules found.
# /node_modules
# HelloWorldApp

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0-beta.1.

## Development server

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.

## Code scaffolding

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

## Build

Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Running end-to-end tests

Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/main/README.md).
{
  "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json",
  "version": 1,
  "newProjectRoot": "./projects",
  "cli": {
    "cache": {
      "enabled": false
    }
  },
  "schematics": {},
  "projects": {
    "app": {
      "root": "src",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "targets": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "progress": false,
            "sourceMap": false,
            "aot": false,
            "vendorChunk": true,
            "buildOptimizer": false,
            "optimization": false,
            "extractLicenses": false,
            "namedChunks": true,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            },
            "inline-critical-css": {
              "optimization": {
                "styles": {
                  "minify": true,
                  "inlineCritical": true
                },
                "scripts": true,
                "fonts": true
              }
            }
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist-server",
            "main": "src/main.server.ts",
            "tsConfig": "src/tsconfig.server.json",
            "progress": false,
            "sourceMap": true,
            "optimization": false,
            "extractLicenses": false
          }
        },
        "app-shell": {
          "builder": "@angular-devkit/build-angular:app-shell",
          "options": {
            "browserTarget": "app:build",
            "serverTarget": "app:server"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "app:build",
            "watch": false
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "app:build",
            "progress": false,
            "outputPath": "src"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": ["zone.js", "zone.js/testing"],

            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "browsers": "ChromeHeadlessCI",
            "progress": false,
            "watch": false,
            "styles": [
              {
                "input": "src/styles.css"
              }
            ],
            "scripts": [],
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        },
        "ngsscbuild": {
          "builder": "angular-server-side-configuration:ngsscbuild",
          "options": {
            "additionalEnvironmentVariables": [],
            "browserTarget": "app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            }
          }
        }
      }
    },
    "app-e2e": {
      "root": "e2e",
      "projectType": "application",
      "targets": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "protractor.conf.js",
            "devServerTarget": "app:serve",
            "webdriverUpdate": false
          }
        }
      }
    }
  }
}
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

const path = require('path');
process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage'),
      require('@angular-devkit/build-angular/plugins/karma'),
    ],
    client: {
      clearContext: false, // leave Jasmine Spec Runner output visible in browser
    },
    jasmineHtmlReporter: {
      suppressAll: true // removes the duplicated traces
    },
    coverageReporter: {
      dir: path.join(__dirname, './coverage'),
      subdir: '.',
      reporters: [
        {type: 'lcov'},
      ],
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    customLaunchers: {
      ChromeHeadlessCI: {
        base: 'ChromeHeadless',
        flags: ['--disable-gpu'],
      },
    },
    singleRun: false,
  });
};
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
const { resolve } = require('path');

exports.config = {
  allScriptsTimeout: 11000,
  specs: ['./e2e/**/*.e2e-spec.ts'],
  capabilities: {
    browserName: 'chrome',
    chromeOptions: {
      args: ['--headless', '--disable-gpu', '--window-size=800,600'],
      binary: require('puppeteer').executablePath(),
    },
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {},
  },
  onPrepare() {
    require('ts-node').register({
      project: resolve(__dirname, './e2e/tsconfig.e2e.json'),
    });
    jasmine.getEnv().addReporter(new SpecReporter({
      spec: {
        displayStacktrace: StacktraceOption.PRETTY
      }
    }));
  },
};
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
  </li>
</ul>

<p i18n>i18n test</p>

/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AppComponent],
    }).compileComponents();
  });
  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });
  it(`should have as title 'app'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app');
  });
  it('should render title in a h1 tag', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
  });
});
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = process.env.TEST;
}
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>HelloWorldApp</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

export { AppServerModule } from './app/app.server.module';
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.log(err));
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/guide/browser-support
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.

/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 */
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 * because those flags need to be set before `zone.js` being loaded, and webpack
 * will put import in the top of bundle, so user need to create a separate file
 * in this directory (for example: zone-flags.ts), and put the following flags
 * into that file, and then add the following code before importing zone.js.
 * import './zone-flags.ts';
 *
 * The flags allowed in zone-flags.ts are listed here.
 *
 * The following flags will work for all browsers.
 *
 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 *
 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 *
 *  (window as any).__Zone_enable_cross_context_check = true;
 *
 */

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js'; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="8261100fbc1830397116d0a032e7c8f54af93c68" datatype="html">
        <source>i18n test</source>
        <context-group purpose="location">
          <context context-type="sourcefile">app/app.component.ts</context>
          <context context-type="linenumber">21</context>
        </context-group>
        <context-group purpose="location">
          <context context-type="sourcefile">app/app.component.ts</context>
          <context context-type="linenumber">23</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>
/* You can add global styles to this file, and also import other style files */
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "types": ["@angular/localize", "node"]
  },
  "files": [
    "main.ts",
    "polyfills.ts"
  ],
  "include": [
    "**/*.d.ts"
  ]
}
{
  "extends": "./tsconfig.app.json",
  "compilerOptions": {
    "outDir": "../dist-server",
    "baseUrl": "./",
    "types": ["@angular/localize", "node"]
  },
  "files": [
    "main.server.ts"
  ]
}
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/spec",
    "types": [
      "jasmine",
      "@angular/localize"
    ]
  },
  "files": [
    "polyfills.ts"
  ],
  "include": [
    "**/*.spec.ts",
    "**/*.d.ts"
  ]
}
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
  id: string;
}
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2022",
    "module": "es2022",
    "useDefineForClassFields": false,
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2022",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableIvy": true,
    "disableTypeScriptVersionCheck": true,
    "strictTemplates": true
  }
}
  jasmine
    .execute()
    .then((result) => {
      if (result.failedExpectations.length) {
        console.error(result.failedExpectations);
      }
    })
    .catch((e) => console.error(e));
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { Architect, BuilderOutput } from '@angular-devkit/architect';
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing';
import { Path, getSystemPath, join, normalize, schema, workspaces } from '@angular-devkit/core';

// Default timeout for large specs is 2.5 minutes.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;

export const workspaceRoot = join(normalize(__dirname), `hello-world-app/`);
export const host = new TestProjectHost(workspaceRoot);
export const outputPath: Path = normalize('dist');

export const browserTargetSpec = { project: 'app', target: 'build' };
export const devServerTargetSpec = { project: 'app', target: 'serve' };
export const extractI18nTargetSpec = { project: 'app', target: 'extract-i18n' };
export const karmaTargetSpec = { project: 'app', target: 'test' };
export const tslintTargetSpec = { project: 'app', target: 'lint' };
export const protractorTargetSpec = { project: 'app-e2e', target: 'e2e' };

export async function createArchitect(workspaceRoot: Path) {
  const registry = new schema.CoreSchemaRegistry();
  registry.addPostTransform(schema.transforms.addUndefinedDefaults);
  const workspaceSysPath = getSystemPath(workspaceRoot);

  const { workspace } = await workspaces.readWorkspace(
    workspaceSysPath,
    workspaces.createWorkspaceHost(host)
  );
  const architectHost = new TestingArchitectHost(
    workspaceSysPath,
    workspaceSysPath,
    new WorkspaceNodeModulesArchitectHost(workspace, workspaceSysPath)
  );
  await architectHost.addBuilderFromPackage('..');
  //require('ts-node').register(require('../projects/angular-server-side-configuration/builders/tsconfig.json'));
  await architectHost.addBuilderFromPackage(
    '../../../../dist/angular-server-side-configuration'
  );
  const architect = new Architect(architectHost, registry);

  return {
    workspace,
    architectHost,
    architect,
  };
}

export interface BrowserBuildOutput {
  output: BuilderOutput;
  files: { [file: string]: Promise<string> };
}
glob@^9.3.1:
  version "9.3.1"
  resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.1.tgz#4a15cf72479a440f77e97805b21e1b5b6e4fb18a"
  integrity sha512-qERvJb7IGsnkx6YYmaaGvDpf77c951hICMdWaFXyH3PlVob8sbPJJyJX0kWkiCWyXUzoy9UOTNjGg0RbD8bYIw==
  dependencies:
    fs.realpath "^1.0.0"
    minimatch "^7.4.1"
    minipass "^4.2.4"
    path-scurry "^1.6.1"

lru-cache@^7.14.1:
  version "7.18.3"
  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
  integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==

minimatch@^7.4.1:
  version "7.4.2"
  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.2.tgz#157e847d79ca671054253b840656720cb733f10f"
  integrity sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==
  dependencies:
    brace-expansion "^2.0.1"

minipass@^4.0.2, minipass@^4.2.4:
  version "4.2.5"
  resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb"
  integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==

path-scurry@^1.6.1:
  version "1.6.2"
  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.2.tgz#72783838113a4a0cec155323b2c01eb1b193396f"
  integrity sha512-J6MQNh56h6eHFY3vsQ+Lq+zKPwn71POieutmVt2leU8W+zz8HVIdJyn3I3Zs6IKbIQtuKXirVjTBFNBcbFO44Q==
  dependencies:
    lru-cache "^7.14.1"
    minipass "^4.0.2"