CVE-2023-28444 (TypeScript, published 2023-03-24T20:15Z)
Description: 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.

Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-200 \u2014 Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Repository: https://github.com/kyubisation/angular-server-side-configuration
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 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).

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"