# <%= classify(name) %>

This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version <%= angularLatestVersion %>.

## Code scaffolding

Angular CLI includes powerful code scaffolding tools. To generate a new component, run:

```bash
ng generate component component-name
```

For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:

```bash
ng generate --help
```

## Building

To build the library, run:

```bash
ng build <%= name %>
```

This command will compile your project, and the build artifacts will be placed in the `dist/` directory.

### Publishing the Library

Once the project is built, you can publish your library by following these steps:

1. Navigate to the `dist` directory:
   ```bash
   cd dist/<%= dasherize(name) %>
   ```

2. Run the `npm publish` command to publish your library to the npm registry:
   ```bash
   npm publish
   ```

## Running unit tests

To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:

```bash
ng test
```

## Running end-to-end tests

For end-to-end (e2e) testing, run:

```bash
ng e2e
```

Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.

## Additional Resources

For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.


/**
 * @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.dev/license
 */

import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';

import { join } from 'node:path';
import { Schema as ServerOptions } from './schema';

describe('SSR Schematic', () => {
  const defaultOptions: ServerOptions = {
    project: 'test-app',
  };

  const schematicRunner = new SchematicTestRunner(
    '@schematics/angular',
    require.resolve(join(__dirname, '../collection.json')),
  );

  let appTree: UnitTestTree;

  const workspaceOptions = {
    name: 'workspace',
    newProjectRoot: 'projects',
    version: '6.0.0',
  };

  beforeEach(async () => {
    appTree = await schematicRunner.runExternalSchematic(
      '@schematics/angular',
      'workspace',
      workspaceOptions,
    );
  });

  describe('non standalone application', () => {
    beforeEach(async () => {
      appTree = await schematicRunner.runExternalSchematic(
        '@schematics/angular',
        'application',
        {
          name: 'test-app',
          inlineStyle: false,
          inlineTemplate: false,
          routing: false,
          style: 'css',
          skipTests: false,
          standalone: false,
        },
        appTree,
      );
    });

    it('should add dependency: express', async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const filePath = '/package.json';
      const contents = tree.readContent(filePath);
      expect(contents).toContain('express');
    });

    it('should install npm dependencies', async () => {
      await schematicRunner.runSchematic('ssr', defaultOptions, appTree);
      expect(schematicRunner.tasks.length).toBe(1);
      expect(schematicRunner.tasks[0].name).toBe('node-package');
      expect((schematicRunner.tasks[0].options as { command: string }).command).toBe('install');
    });

    it(`should update 'tsconfig.app.json' files with Express main file`, async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);
      const { files } = tree.readJson('/projects/test-app/tsconfig.app.json') as {
        files: string[];
      };

      expect(files).toEqual(['src/main.ts', 'src/main.server.ts', 'src/server.ts']);
    });
  });

  describe('standalone application', () => {
    beforeEach(async () => {
      appTree = await schematicRunner.runExternalSchematic(
        '@schematics/angular',
        'application',
        {
          name: 'test-app',
          inlineStyle: false,
          inlineTemplate: false,
          routing: false,
          style: 'css',
          skipTests: false,
          standalone: true,
        },
        appTree,
      );
    });

    it('should add script section in package.json', async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);
      const { scripts } = tree.readJson('/package.json') as { scripts: Record<string, string> };

      expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/server/server.mjs`);
    });

    it('works when using a custom "outputPath.browser" and "outputPath.server" values', async () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const config = appTree.readJson('/angular.json') as any;
      const build = config.projects['test-app'].architect.build;

      build.options.outputPath = {
        base: build.options.outputPath,
        browser: 'public',
        server: 'node-server',
      };

      appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const { scripts } = tree.readJson('/package.json') as { scripts: Record<string, string> };
      expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/node-server/server.mjs`);

      const serverFileContent = tree.readContent('/projects/test-app/src/server.ts');
      expect(serverFileContent).toContain(`resolve(serverDistFolder, '../public')`);
    });

    it(`removes "outputPath.browser" when it's an empty string`, async () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const config = appTree.readJson('/angular.json') as any;
      const build = config.projects['test-app'].architect.build;

      build.options.outputPath = {
        base: build.options.outputPath,
        browser: '',
        server: 'node-server',
      };

      appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const { scripts } = tree.readJson('/package.json') as { scripts: Record<string, string> };
      expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/node-server/server.mjs`);

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const updatedConfig = tree.readJson('/angular.json') as any;
      expect(updatedConfig.projects['test-app'].architect.build.options.outputPath).toEqual({
        base: 'dist/test-app',
        server: 'node-server',
      });
    });
  });

  describe('Legacy browser builder', () => {
    function convertBuilderToLegacyBrowser(): void {
      const config = JSON.parse(appTree.readContent('/angular.json'));
      const build = config.projects['test-app'].architect.build;

      build.builder = '@angular-devkit/build-angular:browser';
      build.options = {
        ...build.options,
        main: build.options.browser,
        browser: undefined,
      };

      build.configurations.development = {
        ...build.configurations.development,
        vendorChunk: true,
        namedChunks: true,
        buildOptimizer: false,
      };

      appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
    }

    beforeEach(async () => {
      appTree = await schematicRunner.runExternalSchematic(
        '@schematics/angular',
        'application',
        {
          name: 'test-app',
          inlineStyle: false,
          inlineTemplate: false,
          routing: false,
          style: 'css',
          skipTests: false,
          standalone: false,
        },
        appTree,
      );

      convertBuilderToLegacyBrowser();
    });

    it(`should update 'tsconfig.server.json' files with Express main file`, async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const { files } = tree.readJson('/projects/test-app/tsconfig.server.json') as {
        files: string[];
      };

      expect(files).toEqual(['src/main.server.ts', 'src/server.ts']);
    });

    it(`should add export to main file in 'server.ts'`, async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const content = tree.readContent('/projects/test-app/src/server.ts');
      expect(content).toContain(`export default AppServerModule`);
    });

    it(`should add correct value to 'distFolder'`, async () => {
      const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);

      const content = tree.readContent('/projects/test-app/src/server.ts');
      expect(content).toContain(`const distFolder = join(process.cwd(), 'dist/test-app/browser');`);
    });
  });
});


import 'zone.js/node';

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr/node';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './main.server';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), '<%= browserDistDirectory %>');
  const indexHtml = existsSync(join(distFolder, 'index.original.html'))
    ? join(distFolder, 'index.original.html')
    : join(distFolder, 'index.html');

  const commonEngine = new CommonEngine();

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Angular engine
  server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        <% if (isStandalone) { %>bootstrap<% } else { %>bootstrap: AppServerModule<% } %>,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: distFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export default <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %>;


import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import express from 'express';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './main.server';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../<%= browserDistDirectory %>');
const indexHtml = join(serverDistFolder, 'index.server.html');

const app = express();
const commonEngine = new CommonEngine();

/**
 * Example Express Rest API endpoints can be defined here.
 * Uncomment and define endpoints as necessary.
 *
 * Example:
 * ```ts
 * app.get('/api/**', (req, res) => {
 *   // Handle API request
 * });
 * ```
 */

/**
 * Serve static files from /<%= browserDistDirectory %>
 */
app.get(
  '**',
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html'
  }),
);

/**
 * Handle all other requests by rendering the Angular application.
 */
app.get('**', (req, res, next) => {
  const { protocol, originalUrl, baseUrl, headers } = req;

  commonEngine
    .render({
      <% if (isStandalone) { %>bootstrap<% } else { %>bootstrap: AppServerModule<% } %>,
      documentFilePath: indexHtml,
      url: `${protocol}://${headers.host}${originalUrl}`,
      publicPath: browserDistFolder,
      providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
    })
    .then((html) => res.send(html))
    .catch((err) => next(err));
});

/**
 * Start the server if this module is the main entry point.
 * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
 */
if (isMainModule(import.meta.url)) {
  const port = process.env['PORT'] || 4000;
  app.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}


import {
  AngularNodeAppEngine,
  createNodeRequestHandler,
  isMainModule,
  writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../<%= browserDistDirectory %>');

const app = express();
const angularApp = new AngularNodeAppEngine();

/**
 * Example Express Rest API endpoints can be defined here.
 * Uncomment and define endpoints as necessary.
 *
 * Example:
 * ```ts
 * app.get('/api/**', (req, res) => {
 *   // Handle API request
 * });
 * ```
 */

/**
 * Serve static files from /<%= browserDistDirectory %>
 */
app.get(
  '**',
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html',
    setHeaders: (res) => {
      const headers = angularApp.getPrerenderHeaders(res.req);
      for (const [key, value] of headers) {
        res.setHeader(key, value);
      }
    },
  }),
);

/**
 * Handle all other requests by rendering the Angular application.
 */
app.get('**', (req, res, next) => {
  angularApp
    .render(req)
    .then((response) =>
      response ? writeResponseToNodeResponse(response, res) : next(),
    )
    .catch(next);
});

/**
 * Start the server if this module is the main entry point.
 * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
 */
if (isMainModule(import.meta.url)) {
  const port = process.env['PORT'] || 4000;
  app.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

/**
 * The request handler used by the Angular CLI (dev-server and during build).
 */
export const reqHandler = createNodeRequestHandler(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.dev/license
 */

import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { parse as parseJson } from 'jsonc-parser';
import { latestVersions } from '../utility/latest-versions';
import { Schema as WorkspaceOptions } from '../workspace/schema';
import { Schema as ApplicationOptions, Style, ViewEncapsulation } from './schema';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function readJsonFile(tree: UnitTestTree, path: string): any {
  return parseJson(tree.readContent(path).toString());
}

describe('Application Schematic', () => {
  const schematicRunner = new SchematicTestRunner(
    '@schematics/angular',
    require.resolve('../collection.json'),
  );

  const workspaceOptions: WorkspaceOptions = {
    name: 'workspace',
    newProjectRoot: 'projects',
    version: '6.0.0',
  };

  const defaultOptions: ApplicationOptions = {
    name: 'foo',
    skipPackageJson: false,
  };

  let workspaceTree: UnitTestTree;
  beforeEach(async () => {
    workspaceTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
  });

  it('should create all files of an application', async () => {
    const tree = await schematicRunner.runSchematic(
      'application',
      { ...defaultOptions, standalone: false },
      workspaceTree,
    );

    expect(tree.files).toEqual(
      jasmine.arrayContaining([
        '/projects/foo/tsconfig.app.json',
        '/projects/foo/tsconfig.spec.json',
        '/projects/foo/public/favicon.ico',
        '/projects/foo/src/index.html',
        '/projects/foo/src/main.ts',
        '/projects/foo/src/styles.css',
        '/projects/foo/src/app/app.module.ts',
        '/projects/foo/src/app/app.component.css',
        '/projects/foo/src/app/app.component.html',
        '/projects/foo/src/app/app.component.spec.ts',
        '/projects/foo/src/app/app.component.ts',
      ]),
    );
  });

  it('should add the application to the workspace', async () => {
    const options = { ...defaultOptions };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const workspace = JSON.parse(tree.readContent('/angular.json'));
    expect(workspace.projects.foo).toBeDefined();
  });

  it('should set the prefix to app if none is set', async () => {
    const options = { ...defaultOptions };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const workspace = JSON.parse(tree.readContent('/angular.json'));
    expect(workspace.projects.foo.prefix).toEqual('app');
  });

  it('should set the prefix correctly', async () => {
    const options = { ...defaultOptions, prefix: 'pre' };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const workspace = JSON.parse(tree.readContent('/angular.json'));
    expect(workspace.projects.foo.prefix).toEqual('pre');
  });

  it('should set the right paths in the tsconfig.app.json', async () => {
    const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree);

    const { files, extends: _extends } = readJsonFile(tree, '/projects/foo/tsconfig.app.json');
    expect(files).toEqual(['src/main.ts']);
    expect(_extends).toBe('../../tsconfig.json');
  });

  it('should set the right paths in the tsconfig.spec.json', async () => {
    const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree);

    const { extends: _extends } = readJsonFile(tree, '/projects/foo/tsconfig.spec.json');
    expect(_extends).toBe('../../tsconfig.json');
  });

  it('should install npm dependencies when `skipInstall` is false', async () => {
    await schematicRunner.runSchematic(
      'application',
      { ...defaultOptions, ssr: true, skipInstall: false },
      workspaceTree,
    );
    expect(schematicRunner.tasks.length).toBe(1);
    expect(schematicRunner.tasks[0].name).toBe('node-package');
    expect((schematicRunner.tasks[0].options as { command: string }).command).toBe('install');
  });

  it('should not install npm dependencies when `skipInstall` is true', async () => {
    await schematicRunner.runSchematic(
      'application',
      { ...defaultOptions, ssr: true, skipInstall: true },
      workspaceTree,
    );
    expect(schematicRunner.tasks.length).toBe(0);
  });

  it('should set the skipTests flag for other schematics when using --skipTests=true', async () => {
    const options: ApplicationOptions = { ...defaultOptions, skipTests: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const config = JSON.parse(tree.readContent('/angular.json'));
    const schematics = config.projects.foo.schematics;

    expect(schematics['@schematics/angular:class']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:component']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:directive']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:guard']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:interceptor']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:pipe']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:resolver']).toEqual({ skipTests: true });
    expect(schematics['@schematics/angular:service']).toEqual({ skipTests: true });
  });

  it('minimal=true should not create e2e and test targets', async () => {
    const options = { ...defaultOptions, minimal: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const config = JSON.parse(tree.readContent('/angular.json'));
    const architect = config.projects.foo.architect;
    expect(architect.test).not.toBeDefined();
    expect(architect.e2e).not.toBeDefined();
  });

  it('minimal=true should configure the schematics options for components', async () => {
    const options = { ...defaultOptions, minimal: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const config = JSON.parse(tree.readContent('/angular.json'));
    const schematics = config.projects.foo.schematics;
    expect(schematics['@schematics/angular:component']).toEqual({
      inlineTemplate: true,
      inlineStyle: true,
      skipTests: true,
    });
  });

  it('minimal=true allows inlineStyle=false when configuring the schematics options for components', async () => {
    const options = { ...defaultOptions, minimal: true, inlineStyle: false };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const config = JSON.parse(tree.readContent('/angular.json'));
    const schematics = config.projects.foo.schematics;
    expect(schematics['@schematics/angular:component']).toEqual({
      inlineTemplate: true,
      skipTests: true,
    });
  });

  it('minimal=true allows inlineTemplate=false when configuring the schematics options for components', async () => {
    const options = { ...defaultOptions, minimal: true, inlineTemplate: false };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const config = JSON.parse(tree.readContent('/angular.json'));
    const schematics = config.projects.foo.schematics;
    expect(schematics['@schematics/angular:component']).toEqual({
      inlineStyle: true,
      skipTests: true,
    });
  });

  it(`should create an application with SSR features when 'ssr=true'`, async () => {
    const options = { ...defaultOptions, ssr: true };
    const filePath = '/projects/foo/src/server.ts';
    expect(workspaceTree.exists(filePath)).toBeFalse();
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
    expect(tree.exists(filePath)).toBeTrue();
  });

  it(`should not create an application with SSR features when 'ssr=false'`, async () => {
    const options = { ...defaultOptions, ssr: false };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
    expect(tree.exists('/projects/foo/src/server.ts')).toBeFalse();
  });


it(`should create kebab-case project folder names with PascalCase project name`, async () => {
    const options: ApplicationOptions = { ...defaultOptions, name: 'MyCool' };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const exists = tree.exists('/projects/my-cool/tsconfig.app.json');
    expect(exists).toBeTrue();
  });

  it(`should create scoped kebab-case project folder names with PascalCase project name`, async () => {
    const options: ApplicationOptions = { ...defaultOptions, name: '@foo/MyCool' };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const exists = tree.exists('/projects/foo/my-cool/tsconfig.app.json');
    expect(exists).toBeTrue();
  });

  it('should support creating applications with `_` and `.` in name', async () => {
    const options = { ...defaultOptions, name: 'foo.bar_buz' };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    expect(tree.exists('/projects/foo.bar_buz/tsconfig.app.json')).toBeTrue();
  });

  it('should support creating scoped application', async () => {
    const scopedName = '@myscope/myapp';
    const options = { ...defaultOptions, name: scopedName };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const cfg = JSON.parse(tree.readContent('/angular.json'));
    expect(cfg.projects['@myscope/myapp']).toBeDefined();
  });

  it('should create correct files when using minimal', async () => {
    const options = { ...defaultOptions, minimal: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const files = tree.files;
    [
      '/projects/foo/tsconfig.spec.json',
      '/projects/foo/src/app/app.component.css',
      '/projects/foo/src/app/app.component.html',
      '/projects/foo/src/app/app.component.spec.ts',
    ].forEach((x) => expect(files).not.toContain(x));

    expect(files).toEqual(
      jasmine.arrayContaining([
        '/projects/foo/tsconfig.app.json',
        '/projects/foo/public/favicon.ico',
        '/projects/foo/src/index.html',
        '/projects/foo/src/main.ts',
        '/projects/foo/src/styles.css',
        '/projects/foo/src/app/app.component.ts',
      ]),
    );
  });

  it('should create correct files when using minimal and inlineStyle=false', async () => {
    const options = { ...defaultOptions, minimal: true, inlineStyle: false };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const files = tree.files;
    [
      '/projects/foo/tsconfig.spec.json',
      '/projects/foo/karma.conf.js',
      '/projects/foo/src/test.ts',
      '/projects/foo/src/app/app.component.html',
      '/projects/foo/src/app/app.component.spec.ts',
    ].forEach((x) => expect(files).not.toContain(x));

    expect(files).toEqual(
      jasmine.arrayContaining([
        '/projects/foo/tsconfig.app.json',
        '/projects/foo/public/favicon.ico',
        '/projects/foo/src/index.html',
        '/projects/foo/src/main.ts',
        '/projects/foo/src/styles.css',
        '/projects/foo/src/app/app.component.css',
        '/projects/foo/src/app/app.component.ts',
      ]),
    );
  });

  it('should create correct files when using minimal and inlineTemplate=false', async () => {
    const options = { ...defaultOptions, minimal: true, inlineTemplate: false };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const files = tree.files;
    [
      '/projects/foo/tsconfig.spec.json',
      '/projects/foo/karma.conf.js',
      '/projects/foo/src/test.ts',
      '/projects/foo/src/app/app.component.css',
      '/projects/foo/src/app/app.component.spec.ts',
    ].forEach((x) => expect(files).not.toContain(x));

    expect(files).toEqual(
      jasmine.arrayContaining([
        '/projects/foo/tsconfig.app.json',
        '/projects/foo/public/favicon.ico',
        '/projects/foo/src/index.html',
        '/projects/foo/src/main.ts',
        '/projects/foo/src/styles.css',
        '/projects/foo/src/app/app.component.html',
        '/projects/foo/src/app/app.component.ts',
      ]),
    );
  });

  it('should create all files of a standalone application', async () => {
    const options = { ...defaultOptions, standalone: true };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const files = tree.files;
    expect(files).toEqual(
      jasmine.arrayContaining([
        '/projects/foo/tsconfig.app.json',
        '/projects/foo/tsconfig.spec.json',
        '/projects/foo/public/favicon.ico',
        '/projects/foo/src/index.html',
        '/projects/foo/src/main.ts',
        '/projects/foo/src/styles.css',
        '/projects/foo/src/app/app.config.ts',
        '/projects/foo/src/app/app.component.css',
        '/projects/foo/src/app/app.component.html',
        '/projects/foo/src/app/app.component.spec.ts',
        '/projects/foo/src/app/app.component.ts',
      ]),
    );
  });

  it('should not create any module files', async () => {
    const options = { ...defaultOptions, standalone: true };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
    const moduleFiles = tree.files.filter((file) => file.endsWith('.module.ts'));
    expect(moduleFiles.length).toEqual(0);
  });

  it('should enable zone event coalescing by default', async () => {
    const options = { ...defaultOptions, standalone: true };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
    const appConfig = tree.readContent('/projects/foo/src/app/app.config.ts');
    expect(appConfig).toContain('provideZoneChangeDetection({ eventCoalescing: true })');
  });

  it('should create a standalone component', async () => {
    const options = { ...defaultOptions, standalone: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
    const component = tree.readContent('/projects/foo/src/app/app.component.ts');

    expect(component).not.toContain('standalone');
  });

  it('should create routing information by default', async () => {
    const options = { ...defaultOptions, standalone: true };

    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    expect(tree.files).toContain('/projects/foo/src/app/app.routes.ts');

    const component = tree.readContent('/projects/foo/src/app/app.component.ts');
    expect(component).toContain(`import { RouterOutlet } from '@angular/router';`);
    expect(component).toContain(`imports: [RouterOutlet]`);

    const config = tree.readContent('/projects/foo/src/app/app.config.ts');
    expect(config).toContain(`import { provideRouter } from '@angular/router';`);
    expect(config).toContain(`import { routes } from './app.routes';`);
    expect(config).toContain('provideRouter(routes)');
  });

  it('should create a main.ts', async () => {
    const options = { ...defaultOptions, standalone: true };
    const tree = await schematicRunner.runSchematic('application', options, workspaceTree);

    const main = tree.readContent('/projects/foo/src/main.ts');
    expect(main).toContain('bootstrapApplication');
  });


import { Routes } from '@angular/router';

export const routes: Routes = [];


/**
 * @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.dev/license
 */

import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';

import { Schema as ApplicationOptions } from '../application/schema';
import { Schema as WorkspaceOptions } from '../workspace/schema';

import { Schema as GuardOptions } from './schema';

describe('Guard Schematic', () => {
  const schematicRunner = new SchematicTestRunner(
    '@schematics/angular',
    require.resolve('../collection.json'),
  );
  const defaultOptions: GuardOptions = {
    name: 'foo',
    flat: true,
    project: 'bar',
  };
  const workspaceOptions: WorkspaceOptions = {
    name: 'workspace',
    newProjectRoot: 'projects',
    version: '6.0.0',
  };

  const appOptions: ApplicationOptions = {
    name: 'bar',
    inlineStyle: false,
    inlineTemplate: false,
    routing: false,
    skipTests: false,
    skipPackageJson: false,
  };
  let appTree: UnitTestTree;
  beforeEach(async () => {
    appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
    appTree = await schematicRunner.runSchematic('application', appOptions, appTree);
  });

  it('should create a (deprecated) class-based guard with --no-functional', async () => {
    const tree = await schematicRunner.runSchematic(
      'guard',
      { ...defaultOptions, functional: false },
      appTree,
    );

    const files = tree.files;
    expect(files).toContain('/projects/bar/src/app/foo.guard.spec.ts');
    expect(files).toContain('/projects/bar/src/app/foo.guard.ts');
  });

  it('should respect the skipTests flag', async () => {
    const options = { ...defaultOptions, skipTests: true };

    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const files = tree.files;
    expect(files).not.toContain('/projects/bar/src/app/foo.guard.spec.ts');
    expect(files).toContain('/projects/bar/src/app/foo.guard.ts');
  });

  it('should respect the flat flag', async () => {
    const options = { ...defaultOptions, flat: false };

    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const files = tree.files;
    expect(files).toContain('/projects/bar/src/app/foo/foo.guard.spec.ts');
    expect(files).toContain('/projects/bar/src/app/foo/foo.guard.ts');
  });

  it('should respect the sourceRoot value', async () => {
    const config = JSON.parse(appTree.readContent('/angular.json'));
    config.projects.bar.sourceRoot = 'projects/bar/custom';
    appTree.overwrite('/angular.json', JSON.stringify(config, null, 2));
    appTree = await schematicRunner.runSchematic('guard', defaultOptions, appTree);
    expect(appTree.files).toContain('/projects/bar/custom/app/foo.guard.ts');
  });

  it('should respect the implements value', async () => {
    const options = { ...defaultOptions, implements: ['CanActivate'], functional: false };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    expect(fileString).toContain('CanActivate');
    expect(fileString).toContain('canActivate');
    expect(fileString).not.toContain('CanActivateChild');
    expect(fileString).not.toContain('canActivateChild');
    expect(fileString).not.toContain('CanMatch');
    expect(fileString).not.toContain('canMatch');
  });

  it('should generate a functional guard by default', async () => {
    const options = { ...defaultOptions, implements: ['CanActivate'] };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    expect(fileString).toContain('export const fooGuard: CanActivateFn = (route, state) => {');
    expect(fileString).not.toContain('CanActivateChild');
    expect(fileString).not.toContain('canActivateChild');
    expect(fileString).not.toContain('CanMatch');
    expect(fileString).not.toContain('canMatch');
  });

  it('should generate a helper function to execute the guard in a test', async () => {
    const options = { ...defaultOptions, implements: ['CanActivate'] };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.spec.ts');
    expect(fileString).toContain('const executeGuard: CanActivateFn = (...guardParameters) => ');
    expect(fileString).toContain(
      'TestBed.runInInjectionContext(() => fooGuard(...guardParameters));',
    );
  });

  it('should generate CanDeactivateFn with unknown functional guard', async () => {
    const options = { ...defaultOptions, implements: ['CanDeactivate'] };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    expect(fileString).toContain(
      'export const fooGuard: CanDeactivateFn<unknown> = ' +
        '(component, currentRoute, currentState, nextState) => {',
    );
  });

  it('should respect the implements values in (deprecated) class-based guards', async () => {
    const implementationOptions = ['CanActivate', 'CanDeactivate', 'CanActivateChild'];
    const options = { ...defaultOptions, implements: implementationOptions, functional: false };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');

    // Should contain all implementations
    implementationOptions.forEach((implementation: string) => {
      expect(fileString).toContain(implementation);
      const functionName = `${implementation.charAt(0).toLowerCase()}${implementation.slice(1)}`;
      expect(fileString).toContain(functionName);
    });
  });

  it('should add correct imports based on CanMatch implementation in (deprecated) class-based guards', async () => {
    const implementationOptions = ['CanMatch'];
    const options = { ...defaultOptions, implements: implementationOptions, functional: false };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    const expectedImports = `import { CanMatch, GuardResult, MaybeAsync, Route, UrlSegment } from '@angular/router';`;

    expect(fileString).toContain(expectedImports);
  });

  it('should add correct imports based on CanActivate implementation in (deprecated) class-based guards', async () => {
    const implementationOptions = ['CanActivate'];
    const options = { ...defaultOptions, implements: implementationOptions, functional: false };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    const expectedImports =
      `import { ActivatedRouteSnapshot, CanActivate, GuardResult, ` +
      `MaybeAsync, RouterStateSnapshot } from '@angular/router';`;

    expect(fileString).toContain(expectedImports);
  });

  it('should add correct imports based on canActivate functional guard', async () => {
    const options = { ...defaultOptions, implements: ['CanActivate'] };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    const expectedImports = `import { CanActivateFn } from '@angular/router';`;

    expect(fileString).toContain(expectedImports);
  });

  it('should add correct imports if multiple implementations was selected in (deprecated) class-based guards', async () => {
    const implementationOptions = ['CanActivate', 'CanMatch', 'CanActivateChild'];
    const options = { ...defaultOptions, implements: implementationOptions, functional: false };
    const tree = await schematicRunner.runSchematic('guard', options, appTree);
    const fileString = tree.readContent('/projects/bar/src/app/foo.guard.ts');
    const expectedImports =
      `import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, CanMatch, GuardResult, ` +
      `MaybeAsync, Route, RouterStateSnapshot, UrlSegment } from '@angular/router';`;

    expect(fileString).toContain(expectedImports);
  });
});


import { TestBed } from '@angular/core/testing';

import { <%= classify(name) %>Guard } from './<%= dasherize(name) %>.guard';

describe('<%= classify(name) %>Guard', () => {
  let guard: <%= classify(name) %>Guard;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    guard = TestBed.inject(<%= classify(name) %>Guard);
  });

  it('should be created', () => {
    expect(guard).toBeTruthy();
  });
});


import { TestBed } from '@angular/core/testing';
import { <%= guardType %> } from '@angular/router';

import { <%= camelize(name) %>Guard } from './<%= dasherize(name) %>.guard';

describe('<%= camelize(name) %>Guard', () => {
  const executeGuard: <%= guardType %> = (...guardParameters) =>
      TestBed.runInInjectionContext(() => <%= camelize(name) %>Guard(...guardParameters));

  beforeEach(() => {
    TestBed.configureTestingModule({});
  });

  it('should be created', () => {
    expect(executeGuard).toBeTruthy();
  });
});


/**
 * @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.dev/license
 */

import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { Schema as NgNewOptions } from './schema';

describe('Ng New Schematic', () => {
  const schematicRunner = new SchematicTestRunner(
    '@schematics/angular',
    require.resolve('../collection.json'),
  );
  const defaultOptions: NgNewOptions = {
    name: 'foo',
    directory: 'bar',
    version: '6.0.0',
  };

  it('should create files of a workspace', async () => {
    const options = { ...defaultOptions };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const files = tree.files;
    expect(files).toContain('/bar/angular.json');
  });

  it('should create files of an application', async () => {
    const options = { ...defaultOptions };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const files = tree.files;
    expect(files).toEqual(
      jasmine.arrayContaining([
        '/bar/tsconfig.app.json',
        '/bar/src/main.ts',
        '/bar/src/app/app.config.ts',
      ]),
    );

    expect(files).not.toEqual(jasmine.arrayContaining(['/bar/src/app/app.module.ts']));
  });

  it('should create module files of a standalone=false application', async () => {
    const options = { ...defaultOptions, standalone: false };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const files = tree.files;
    expect(files).toEqual(
      jasmine.arrayContaining([
        '/bar/tsconfig.app.json',
        '/bar/src/main.ts',
        '/bar/src/app/app.module.ts',
      ]),
    );
  });

  it('should should set the prefix in angular.json and in app.component.ts', async () => {
    const options = { ...defaultOptions, prefix: 'pre' };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const content = tree.readContent('/bar/angular.json');
    expect(content).toMatch(/"prefix": "pre"/);
  });

  it('should set up the app module when standalone=false', async () => {
    const options: NgNewOptions = {
      name: 'foo',
      version: '6.0.0',
      standalone: false,
    };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const moduleContent = tree.readContent('/foo/src/app/app.module.ts');
    expect(moduleContent).toMatch(/declarations:\s*\[\s*AppComponent\s*\]/m);
  });

  it('createApplication=false should create an empty workspace', async () => {
    const options = { ...defaultOptions, createApplication: false };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const files = tree.files;
    expect(files).toContain('/bar/angular.json');
    expect(files).not.toContain('/bar/src');
  });

  it('minimal=true should not create an e2e target', async () => {
    const options = { ...defaultOptions, minimal: true };

    const tree = await schematicRunner.runSchematic('ng-new', options);
    const confContent = JSON.parse(tree.readContent('/bar/angular.json'));
    expect(confContent.projects.foo.e2e).toBeUndefined();
  });

  it('should add packageManager option in angular.json', async () => {
    const tree = await schematicRunner.runSchematic('ng-new', {
      ...defaultOptions,
      packageManager: 'npm',
    });
    const { cli } = JSON.parse(tree.readContent('/bar/angular.json'));
    expect(cli.packageManager).toBe('npm');
  });
});


describe('addRootImport', () => {
    it('should add a root import to an NgModule-based app', async () => {
      await setupProject();

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}.forRoot([])`,
        ),
        host,
      );

      const content = readFile('app/app.module.ts');

      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(content, `imports: [BrowserModule, MyModule.forRoot([])]`);
    });

    it('should add a root import to a standalone app', async () => {
      await setupProject(true);

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('app/app.config.ts');

      assertContains(content, `importProvidersFrom`);
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(content, `importProvidersFrom(MyModule)`);
    });

    it('should add a root import to a standalone app whose app config does not have a providers array', async () => {
      await setupProject(true);

      host.overwrite(
        getPathWithinProject('app/app.config.ts'),
        `
          import { ApplicationConfig } from '@angular/core';

          export const appConfig: ApplicationConfig = {};
        `,
      );

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('app/app.config.ts');

      assertContains(
        content,
        `import { ApplicationConfig, importProvidersFrom } from '@angular/core';`,
      );
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(content, `providers: [importProvidersFrom(MyModule)]`);
    });

    it('should add a root import to a standalone app with a config with providers', async () => {
      await setupProject(true);

      host.overwrite(
        getPathWithinProject('app/app.config.ts'),
        `
          import { ApplicationConfig } from '@angular/core';

          export const appConfig: ApplicationConfig = {
            providers: [
              {provide: 'foo', useValue: 123}
            ]
          };
        `,
      );

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('app/app.config.ts');

      assertContains(
        content,
        `import { ApplicationConfig, importProvidersFrom } from '@angular/core';`,
      );
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(
        content,
        `providers: [
        {provide: 'foo', useValue: 123},
        importProvidersFrom(MyModule)
      ]`,
      );
    });

    it(
      'should add a root import to a standalone app whose app config does not have have ' +
        'a providers array, but has another property',
      async () => {
        await setupProject(true);

        host.overwrite(
          getPathWithinProject('app/app.config.ts'),
          `
          import { ApplicationConfig } from '@angular/core';

          export const appConfig: ApplicationConfig = {
            otherProp: {},
          };
        `,
        );

        await testRule(
          addRootImport(
            projectName,
            ({ code, external }) => code`${external('MyModule', '@my/module')}`,
          ),
          host,
        );

        const content = readFile('app/app.config.ts');

        assertContains(
          content,
          `import { ApplicationConfig, importProvidersFrom } from '@angular/core';`,
        );
        assertContains(content, `import { MyModule } from '@my/module';`);
        assertContains(
          content,
          `
            export const appConfig: ApplicationConfig = {
              otherProp: {},
              providers: [importProvidersFrom(MyModule)]
            };
          `,
        );
      },
    );

    it('should add a root import to a standalone app with an inline app config', async () => {
      await setupProject(true);

      host.overwrite(
        getPathWithinProject('main.ts'),
        `
        import { bootstrapApplication } from '@angular/platform-browser';
        import { AppComponent } from './app/app.component';

        bootstrapApplication(AppComponent, {});
      `,
      );

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('main.ts');

      assertContains(content, `import { importProvidersFrom } from '@angular/core';`);
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(
        content,
        `bootstrapApplication(AppComponent, {
          providers: [importProvidersFrom(MyModule)]
        });`,
      );
    });

    it('should add a root import to a standalone app without an app config', async () => {
      await setupProject(true);

      host.overwrite(
        getPathWithinProject('main.ts'),
        `
        import { bootstrapApplication } from '@angular/platform-browser';
        import { AppComponent } from './app/app.component';

        bootstrapApplication(AppComponent);
      `,
      );

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('main.ts');

      assertContains(content, `import { importProvidersFrom } from '@angular/core';`);
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(
        content,
        `bootstrapApplication(AppComponent, {
          providers: [importProvidersFrom(MyModule)]
        });`,
      );
    });

    it('should add a root import to a standalone app with a merged app config', async () => {
      await setupProject(true);

      host.overwrite(
        getPathWithinProject('main.ts'),
        `
          import { mergeApplicationConfig } from '@angular/core';
          import { bootstrapApplication } from '@angular/platform-browser';
          import { AppComponent } from './app.component';

          bootstrapApplication(AppComponent, mergeApplicationConfig(a, b));
        `,
      );

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      const content = readFile('main.ts');

      assertContains(
        content,
        `import { mergeApplicationConfig, importProvidersFrom } from '@angular/core';`,
      );
      assertContains(content, `import { MyModule } from '@my/module';`);
      assertContains(
        content,
        `bootstrapApplication(AppComponent, mergeApplicationConfig(a, b, {
          providers: [importProvidersFrom(MyModule)]
        }));`,
      );
    });

    it('should alias symbols that conflict with existing code', async () => {
      await setupProject();

      await testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('BrowserModule', '@my/module')}.forRoot([])`,
        ),
        host,
      );

      const content = readFile('app/app.module.ts');

      assertContains(content, `import { BrowserModule as BrowserModule_alias } from '@my/module';`);
      assertContains(content, `imports: [BrowserModule, BrowserModule_alias.forRoot([])]`);
    });

    it('should throw an error if the bootstrapApplication code has no arguments', async () => {
      await setupProject(true);

      const mainPath = getPathWithinProject('main.ts');

      host.overwrite(
        mainPath,
        `
        import { bootstrapApplication } from '@angular/platform-browser';

        bootstrapApplication();
      `,
      );

      const promise = testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      // eslint-disable-next-line @typescript-eslint/no-floating-promises
      await expectAsync(promise).toBeRejectedWithError(
        `Cannot add provider to invalid bootstrapApplication call in ${mainPath}`,
      );
    });

    it('should throw an error if the bootstrapApplication call cannot be analyzed', async () => {
      await setupProject(true);

      const mainPath = getPathWithinProject('main.ts');

      host.overwrite(
        mainPath,
        `
        import { bootstrapApplication } from '@angular/platform-browser';
        import { AppComponent } from './app/app.component';
        import { appConfig } from '@external/app-config';

        bootstrapApplication(AppComponent, appConfig);
      `,
      );

      const promise = testRule(
        addRootImport(
          projectName,
          ({ code, external }) => code`${external('MyModule', '@my/module')}`,
        ),
        host,
      );

      // eslint-disable-next-line @typescript-eslint/no-floating-promises
      await expectAsync(promise).toBeRejectedWithError(
        `Cannot statically analyze bootstrapApplication call in ${mainPath}`,
      );
    });


# @angular-devkit/build-angular

This package contains [Architect builders](/packages/angular_devkit/architect/README.md) used to build and test Angular applications and libraries.

## Builders

| Name            | Description                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| application     | Build an Angular application targeting a browser and server environment using [esbuild](https://esbuild.github.io).                                                                                |
| app-shell       | Build an Angular [App shell](https://angular.dev/ecosystem/service-workers/app-shell).                                                                                |
| browser         | Build an Angular application targeting a browser environment using [Webpack](https://webpack.js.org).                                                                                |
| browser-esbuild | Build an Angular application targeting a browser environment using [esbuild](https://esbuild.github.io).                                                                                |
| dev-server      | A development server that provides live reloading.                                                                                |
| extract-i18n    | Extract i18n messages from an Angular application.                                                                                |
| karma           | Execute unit tests using [Karma](https://github.com/karma-runner/karma) test runner.                                                                                |
| ng-packagr      | Build and package an Angular library in [Angular Package Format (APF)](https://angular.dev/tools/libraries/angular-package-format) format using [ng-packagr](https://github.com/ng-packagr/ng-packagr). |
| prerender       | [Prerender](https://angular.dev/guide/prerendering) pages of your application. Prerendering is the process where a dynamic page is processed at build time generating static HTML.                      |
| server          | Build an Angular application targeting a [Node.js](https://nodejs.org) environment.                                                                                |
| ssr-dev-server  | A development server which offers live reload during development, but uses server-side rendering.                                                                                |
| protractor      | **Deprecated** - Run end-to-end tests using [Protractor](https://www.protractortest.org/) framework.                                                                                |

## Disclaimer

While the builders when executed via the Angular CLI and their associated options are considered stable, the programmatic APIs are not considered officially supported and are not subject to the breaking change guarantees of SemVer.


export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Configuration> {
  const { root, projectRoot, buildOptions, tsConfig, projectName, sourceRoot, tsConfigPath } = wco;
  const {
    cache,
    codeCoverage,
    crossOrigin = 'none',
    platform = 'browser',
    aot = true,
    codeCoverageExclude = [],
    main,
    sourceMap: {
      styles: stylesSourceMap,
      scripts: scriptsSourceMap,
      vendor: vendorSourceMap,
      hidden: hiddenSourceMap,
    },
    optimization: { styles: stylesOptimization, scripts: scriptsOptimization },
    commonChunk,
    vendorChunk,
    subresourceIntegrity,
    verbose,
    poll,
    webWorkerTsConfig,
    externalDependencies = [],
    allowedCommonJsDependencies,
  } = buildOptions;

  const isPlatformServer = buildOptions.platform === 'server';
  const extraPlugins: { apply(compiler: Compiler): void }[] = [];
  const extraRules: RuleSetRule[] = [];
  const entryPoints: Configuration['entry'] = {};

  // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround.
  // Once TypeScript provides support for keeping the dynamic import this workaround can be
  // changed to a direct dynamic import.
  const { VERSION: NG_VERSION } =
    await loadEsmModule<typeof import('@angular/compiler-cli')>('@angular/compiler-cli');
  const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await loadEsmModule<
    typeof import('@angular/compiler-cli/private/tooling')
  >('@angular/compiler-cli/private/tooling');

  // determine hashing format
  const hashFormat = getOutputHashFormat(buildOptions.outputHashing);

  if (buildOptions.progress) {
    extraPlugins.push(new ProgressPlugin(platform));
  }

  const localizePackageInitEntryPoint = '@angular/localize/init';
  const hasLocalizeType = tsConfig.options.types?.some(
    (t) => t === '@angular/localize' || t === localizePackageInitEntryPoint,
  );

  if (hasLocalizeType) {
    entryPoints['main'] = [localizePackageInitEntryPoint];
  }

  if (buildOptions.main) {
    const mainPath = path.resolve(root, buildOptions.main);
    if (Array.isArray(entryPoints['main'])) {
      entryPoints['main'].push(mainPath);
    } else {
      entryPoints['main'] = [mainPath];
    }
  }

  if (isPlatformServer) {
    // Fixes Critical dependency: the request of a dependency is an expression
    extraPlugins.push(new ContextReplacementPlugin(/@?hapi|express[\\/]/));

    if (
      isPackageInstalled(wco.root, '@angular/platform-server') &&
      Array.isArray(entryPoints['main'])
    ) {
      // This import must come before any imports (direct or transitive) that rely on DOM built-ins being
      // available, such as `@angular/elements`.
      entryPoints['main'].unshift('@angular/platform-server/init');
    }
  }

  const polyfills = [...buildOptions.polyfills];
  if (!aot) {
    polyfills.push('@angular/compiler');
  }

  if (polyfills.length) {
    // `zone.js/testing` is a **special** polyfill because when not imported in the main it fails with the below errors:
    // `Error: Expected to be running in 'ProxyZone', but it was not found.`
    // This was also the reason why previously it was imported in `test.ts` as the first module.
    // From Jia li:
    // This is because the jasmine functions such as beforeEach/it will not be patched by zone.js since
    // jasmine will not be loaded yet, so the ProxyZone will not be there. We have to load zone-testing.js after
    // jasmine is ready.
    // We could force loading 'zone.js/testing' prior to jasmine by changing the order of scripts in 'karma-context.html'.
    // But this has it's own problems as zone.js needs to be loaded prior to jasmine due to patching of timing functions
    // See: https://github.com/jasmine/jasmine/issues/1944
    // Thus the correct order is zone.js -> jasmine -> zone.js/testing.
    const zoneTestingEntryPoint = 'zone.js/testing';
    const polyfillsExludingZoneTesting = polyfills.filter((p) => p !== zoneTestingEntryPoint);

    if (Array.isArray(entryPoints['polyfills'])) {
      entryPoints['polyfills'].push(...polyfillsExludingZoneTesting);
    } else {
      entryPoints['polyfills'] = polyfillsExludingZoneTesting;
    }

    if (polyfillsExludingZoneTesting.length !== polyfills.length) {
      if (Array.isArray(entryPoints['main'])) {
        entryPoints['main'].unshift(zoneTestingEntryPoint);
      } else {
        entryPoints['main'] = [zoneTestingEntryPoint];
      }
    }
  }

  if (allowedCommonJsDependencies) {
    // When this is not defined it means the builder doesn't support showing common js usages.
    // When it does it will be an array.
    extraPlugins.push(
      new CommonJsUsageWarnPlugin({
        allowedDependencies: allowedCommonJsDependencies,
      }),
    );
  }

  // process global scripts
  // Add a new asset for each entry.
  for (const { bundleName, inject, paths } of globalScriptsByBundleName(buildOptions.scripts)) {
    // Lazy scripts don't get a hash, otherwise they can't be loaded by name.
    const hash = inject ? hashFormat.script : '';

    extraPlugins.push(
      new ScriptsWebpackPlugin({
        name: bundleName,
        sourceMap: scriptsSourceMap,
        scripts: paths,
        filename: `${path.basename(bundleName)}${hash}.js`,
        basePath: root,
      }),
    );
  }

  // process asset entries
  if (buildOptions.assets.length) {
    extraPlugins.push(
      new CopyWebpackPlugin({
        patterns: assetPatterns(root, buildOptions.assets),
      }),
    );
  }

  if (buildOptions.extractLicenses) {
    const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin;
    extraPlugins.push(
      new LicenseWebpackPlugin({
        stats: {
          warnings: false,
          errors: false,
        },
        perChunkOutput: false,
        outputFilename: '3rdpartylicenses.txt',
        skipChildCompilers: true,
      }),
    );
  }

  if (scriptsSourceMap || stylesSourceMap) {
    const include = [];
    if (scriptsSourceMap) {
      include.push(/js$/);
    }

    if (stylesSourceMap) {
      include.push(/css$/);
    }

    extraPlugins.push(new DevToolsIgnorePlugin());

    extraPlugins.push(
      new SourceMapDevToolPlugin({
        filename: '[file].map',
        include,
        // We want to set sourceRoot to  `webpack:///` for non
        // inline sourcemaps as otherwise paths to sourcemaps will be broken in browser
        // `webpack:///` is needed for Visual Studio breakpoints to work properly as currently
        // there is no way to set the 'webRoot'
        sourceRoot: 'webpack:///',
        moduleFilenameTemplate: '[resource-path]',
        append: hiddenSourceMap ? false : undefined,
      }),
    );
  }

  if (verbose) {
    extraPlugins.push(new WatchFilesLogsPlugin());
  }

  if (buildOptions.statsJson) {
    extraPlugins.push(
      new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'stats.json')),
    );
  }

  if (subresourceIntegrity) {
    extraPlugins.push(
      new SubresourceIntegrityPlugin({
        hashFuncNames: ['sha384'],
      }),
    );
  }

  if (scriptsSourceMap || stylesSourceMap) {
    extraRules.push({
      test: /\.[cm]?jsx?$/,
      enforce: 'pre',
      loader: require.resolve('source-map-loader'),
      options: {
        filterSourceMappingUrl: (_mapUri: string, resourcePath: string) => {
          if (vendorSourceMap) {
            // Consume all sourcemaps when vendor option is enabled.
            return true;
          }

          // Don't consume sourcemaps in node_modules when vendor is disabled.
          // But, do consume local libraries sourcemaps.
          return !resourcePath.includes('node_modules');
        },
      },
    });
  }

  if (main || polyfills) {
    extraRules.push({
      test: tsConfig.options.allowJs ? /\.[cm]?[tj]sx?$/ : /\.[cm]?tsx?$/,
      loader: AngularWebpackLoaderPath,
      // The below are known paths that are not part of the TypeScript compilation even when allowJs is enabled.
      exclude: [
        /[\\/]node_modules[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/,
      ],
    });
    extraPlugins.push(createIvyPlugin(wco, aot, tsConfigPath));
  }

  if (webWorkerTsConfig) {
    extraPlugins.push(createIvyPlugin(wco, false, path.resolve(wco.root, webWorkerTsConfig)));
  }

  const extraMinimizers = [];


# Overview

Jobs is a high-order API that adds inputs, runtime type checking, sequencing, and other
functionality on top of RxJS' `Observable`s.

# Background

An `Observable` (at a higher level) is a function that receives a `Subscriber`, and outputs
multiple values, and finishes once it calls the `Subscriber.prototype.complete()` method (in
JavaScript):

```javascript
const output1To10EverySecond = function (subscriber) {
  let t = 0;
  const i = setInterval(() => {
    t++;
    subscriber.next(t);
    if (t === 10) {
      subscriber.complete(t);
    }
  }, 1000);
  return () => clearInterval(i);
};

const stream$ = new Observable(output1To10EverySecond);
// Start the function, and output 1 to 100, once per line.
stream$.subscribe((x) => console.log(x));
```

This, of course, can be typed in TypeScript, but those types are not enforced at runtime.

# Glossary

- `job handler`. The function that implements the job's logic.
- `raw input`. The input observable sending messages to the job. These messages are of type
  `JobInboundMessage`.
- `raw output`. The output observer returned from the `job handler`. Messages on this observable
  are of type `JobOutboundMessage`.

# Description

A `JobHandler`, similar to observables, is a function that receives an argument and a context, and
returns an `Observable` of messages, which can include outputs that are typed at runtime (using a
Json Schema):

```javascript
const output1ToXEverySecond = function (x, context) {
  return new Observable((subscriber) => {
    let t = 0;

    // Notify our users that the actual work is started.
    subscriber.next({ kind: JobOutboundMessageKind.Start });
    const i = setInterval(() => {
      t++;
      subscriber.next({ kind: JobOutboundMessageKind.Output, value: t });
      if (t === x) {
        subscriber.next({ kind: JobOutboundMessageKind.End });
        subscriber.complete();
      }
    }, 1000);

    return () => {
      clearInterval(i);
    };
  });
};

// For now, jobs can not be called without a registry and scheduler.
const registry = new SimpleJobRegistry();
registry.register('output-from-1-to-x', output1ToXEverySecond, {
  argument: { type: 'number' },
  output: { type: 'number' },
});
const scheduler = new SimpleScheduler(registry);

// Need to keep the same name that the registry would understand.
// Count from 1 to 10.
const job = scheduler.schedule('output-from-1-to-x', 10);

// A Job<> instance has more members, but we only want the output values here.
job.output.subscribe((x) => console.log(x));
```

This seems like a lot of boilerplate in comparison, but there are a few advantages;

1. lifecycle. Jobs can tell when they start doing work and when work is done.
1. everything is typed, even at runtime.
1. the context also contains an input Observable that receives typed input messages, including
   input values, and stop requests.
1. jobs can also schedule other jobs and wait for them, even if they don't know if a job is
   implemented in the system.

## Diagram

A simpler way to think about jobs in contrast to observables is that job are closer to a Unix
process. It has an argument (command line flags), receive inputs (STDIN and interrupt signals),
and output values (STDOUT) as well as diagnostic (STDERR). They can be plugged one into another
(piping), and can be transformed, synchronized and scheduled (fork, exec, cron).

```plain
- given A the type of the argument
- given I the type of the input
- given O the type of the output

                              ,______________________
    JobInboundMessage<I> --> | handler(argument: A) |  --> JobOutboundMessage<O>
                                                            - JobOutboundMessageKind.Output
                                                            - ...
```

`JobInboundMessage` includes:

1. `JobInboundMessageKind.Ping`. A simple message that should be answered with
   `JobOutboundMessageKind.Pong` when the job is responsive. The `id` field of the message should
   be used when returning `Pong`.
1. `JobInboundMessageKind.Stop`. The job should be stopped. This is used when
   cancelling/unsubscribing from the `output` (or by calling `stop()`). Any inputs or outputs
   after this message will be ignored.
1. `JobInboundMessageKind.Input` is used when sending inputs to a job. These correspond to the
   `next` methods of an `Observer` and are reported to the job through its `context.input`
   Observable. There is no way to communicate an error to the job.

`JobOutboundMessage` includes:

1. `JobOutboundMessageKind.Ready`. The `Job<>` was created, its dependencies are done, and the
   library is validating Argument and calling the internal job code.
1. `JobOutboundMessageKind.Start`. The job code itself should send that message when started.
   `createJobHandler()` will do it automatically.
1. `JobOutboundMessageKind.End`. The job has ended. This is done by the job itself and should
   always be sent when completed. The scheduler will listen to this message to set the state and
   unblock dependent jobs. `createJobHandler()` automatically send this message.
1. `JobOutboundMessageKind.Pong`. The job should answer a `JobInboundMessageKind.Ping` message with
   this. Automatically done by `createJobHandler()`.
1. `JobOutboundMessageKind.Output`. An `Output` has been generated by the job.
1. `JobOutboundMessageKind.ChannelMessage`, `JobOutboundMessageKind.ChannelError` and
   `JobOutboundMessageKind.ChannelComplete` are used for output channels. These correspond to
   the `next`, `error` and `complete` methods of an `Observer` and are available to the callee
   through the `job.channels` map of Observable.

Utilities should have some filtering and dispatching to separate observables, as a convenience for
the user. An example of this would be the `Job.prototype.output` observable which only contains
the value contained by messages of type `JobOutboundMessageKind.Output`.


# Creating Jobs

A job is at its core a function with a description object attached to it. The description object
stores the JSON schemas used to validate the types of the argument passed in, the input and
output values. By default, a job accepts and can output any JSON object.

```typescript
import { Observable } from 'rxjs';
import { jobs } from '@angular-devkit/core';

const argument = {
  type: 'array',
  items: { type: 'number' },
};
const output = {
  type: 'number',
};

export function add(argument: number[]): Observable<jobs.JobOutboundMessage<number>> {
  return new Observable((o) => {
    o.next({ kind: jobs.JobOutboundMessageKind.Start });
    o.next({
      kind: jobs.JobOutboundMessageKind.Output,
      output: argument.reduce((total, curr) => total + curr, 0),
    });
    o.next({ kind: jobs.JobOutboundMessageKind.End });
    o.complete();
  });
}

// Add a property to `add` to make it officially a JobHandler. The Job system does not recognize
// any function as a JobHandler.
add.jobDescription = {
  argument: argument,
  output: output,
};

// Call the job with an array as argument, and log its output.
declare const scheduler: jobs.Scheduler;
scheduler.schedule('add', [1, 2, 3, 4]).output.subscribe((x) => console.log(x)); // Will output 10.
```

This is a lot of boilerplate, so we made some helpers to improve readability and manage argument,
input and output automatically:

```typescript
// Add is a JobHandler function, like the above.
export const add = jobs.createJobHandler<number[], number>((argument) =>
  argument.reduce((total, curr) => total + curr, 0),
);

// Schedule like above.
```

You can also return a Promise or an Observable, as jobs are asynchronous. This helper will set
start and end messages appropriately. It will also manage channels automatically (see below).

A more complex job can be declared like this:

```typescript
import { Observable } from 'rxjs';
import { jobs } from '@angular-devkit/core';

// Show progress with each count in a separate output channel. Output "more" in a channel.
export const count = jobs.createJobHandler<number, number>(
  // Receive a context that contains additional methods to create channels.
  (argument: number, { createChannel }) =>
    new Observable<number>((o) => {
      const side = createChannel('side', { type: 'string', const: 'more' });
      const progress = createChannel('progress', { type: 'number' });
      let i = 0;
      function doCount() {
        o.next(i++);
        progress.next(i / argument);
        side.next('more');

        if (i < argument) {
          setTimeout(doCount, 100);
        } else {
          o.complete();
        }
      }
      setTimeout(doCount, 100);
    }),
  {
    argument: { type: 'number' },
    output: { type: 'number' },
  },
);

// Get a hold of a scheduler that refers to the job above.
declare const scheduler: jobs.Scheduler;

const job = scheduler.schedule('count', 0);
job.getChannel('side').subscribe((x) => console.log(x));
// You can type a channel too. Messages will be filtered out.
job.getChannel<number>('progress', { type: 'number' }).subscribe((x) => console.log(x));
```

## Communicating With Jobs

Jobs can be started and updated in a separate process or thread, and as such communication with a
job should avoid using global objects (which might not be shared). The jobs API and schedulers
provide 2 communication streams (one for input and the other for output), named `inboundBus` and
`outboundBus`.

### Raw Input Stream

The `schedule()` function returns a `Job<>` interface that contains a `inboundBus` member of type
`Observer<JobInboundMessage>`. All messages sent _to_ the job goes through this stream. The `kind`
member of the `JobInboundMessage` interface dictates what kind of message it is sending:

1. `JobInboundMessageKind.Ping`. A simple message that should be answered with
   `JobOutboundMessageKind.Pong` when the job is responsive. The `id` field of the message should
   be used when returning `Pong`.
1. `JobInboundMessageKind.Stop`. The job should be stopped. This is used when
   cancelling/unsubscribing from the `output` (or by calling `stop()`). Any inputs or outputs
   after this message will be ignored.
1. `JobInboundMessageKind.Input` is used when sending inputs to a job. These correspond to the
   `next` methods of an `Observer` and are reported to the job through its `context.input`
   Observable. There is no way to communicate an error to the job.

Using the `createJobHandler()` helper, all those messages are automatically handled by the
boilerplate code. If you need direct access to raw inputs, you should subscribe to the
`context.inboundBus` Observable.

### Raw Output Stream

The `Job<>` interface also contains a `outboundBus` member (of type
`Observable<JobOutboundMessage<O>>` where `O` is the typed output of the job) which is the output
complement of `inboundBus`. All messages sent _from_ the job goes through this stream. The `kind`
member of the `JobOutboundMessage<O>` interface dictates what kind of message it is sending:

1. `JobOutboundMessageKind.Create`. The `Job<>` was created, its dependencies are done, and the
   library is validating Argument and calling the internal job code.
1. `JobOutboundMessageKind.Start`. The job code itself should send that message when started.
   `createJobHandler()` will do it automatically.
1. `JobOutboundMessageKind.End`. The job has ended. This is done by the job itself and should always
   be sent when completed. The scheduler will listen to this message to set the state and unblock
   dependent jobs. `createJobHandler()` automatically send this message.
1. `JobOutboundMessageKind.Pong`. The job should answer a `JobInboundMessageKind.Ping` message with
   this. Automatically done by `createJobHandler()`.
1. `JobOutboundMessageKind.Output`. An `Output` has been generated by the job.
1. `JobOutboundMessageKind.ChannelMessage`, `JobOutboundMessageKind.ChannelError` and
   `JobOutboundMessageKind.ChannelComplete` are used for output channels. These correspond to the
   `next`, `error` and `complete` methods of an `Observer` and are available to the callee through
   the `job.channels` map of Observable.

Those messages can be accessed directly through the `job.outboundBus` member. The job itself should
return an `Observable<JobOutboundMessage<O>>`. The `createJobHandler()` helper handles most of use
cases of this and makes it easier for jobs to handle this.

## Job Dispatchers

Dispatchers are a helper that redirect to different jobs given conditions. To create a job
dispatcher, use the `createDispatcher()` function:

```typescript
import { jobs } from '@angular-devkit/core';

// A dispatcher that installs node modules given a user's preference.
const dispatcher = jobs.createDispatcher({
  name: 'node-install',
  argument: { properties: { moduleName: { type: 'string' } } },
  output: { type: 'boolean' },
});

const npmInstall = jobs.createJobHandler(/* ... */, { name: 'npm-install' });
const yarnInstall = jobs.createJobHandler(/* ... */, { name: 'yarn-install' });
const pnpmInstall = jobs.createJobHandler(/* ... */, { name: 'pnpm-install' });

declare const registry: jobs.SimpleJobRegistry;
registry.register(dispatcher);
registry.register(npmInstall);
registry.register(yarnInstall);
registry.register(pnpmInstall);

// Default to npm.
dispatcher.setDefaultDelegate(npmInstall.name);
// If the user is asking for yarn over npm, uses it.
dispatcher.addConditionalDelegate(() => userWantsYarn, yarnInstall.name);
```


/**
 * @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.dev/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.dev/reference/versions#browser-support
 */

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

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

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

/***************************************************************************************************
 * APPLICATION IMPORTS
 */


/**
 * @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.dev/license
 */

import { existsSync } from 'fs';
import * as path from 'path';
import { URL, pathToFileURL } from 'url';
import { Compilation, Configuration } from 'webpack';

export interface EmittedFiles {
  id?: string;
  name?: string;
  file: string;
  initial: boolean;
  asset?: boolean;
  extension: string;
}

export function getEmittedFiles(compilation: Compilation): EmittedFiles[] {
  const files: EmittedFiles[] = [];
  const chunkFileNames = new Set<string>();

  // adds all chunks to the list of emitted files such as lazy loaded modules
  for (const chunk of compilation.chunks) {
    for (const file of chunk.files) {
      if (chunkFileNames.has(file)) {
        continue;
      }

      chunkFileNames.add(file);
      files.push({
        id: chunk.id?.toString(),
        name: chunk.name,
        file,
        extension: path.extname(file),
        initial: chunk.isOnlyInitial(),
      });
    }
  }

  // add all other files
  for (const file of Object.keys(compilation.assets)) {
    // Chunk files have already been added to the files list above
    if (chunkFileNames.has(file)) {
      continue;
    }

    files.push({ file, extension: path.extname(file), initial: false, asset: true });
  }

  return files;
}

/**
 * This uses a dynamic import to load a module which may be ESM.
 * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
 * will currently, unconditionally downlevel dynamic import into a require call.
 * require calls cannot load ESM code and will result in a runtime error. To workaround
 * this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
 * Once TypeScript provides support for keeping the dynamic import this workaround can
 * be dropped.
 *
 * @param modulePath The path of the module to load.
 * @returns A Promise that resolves to the dynamically imported module.
 */
function loadEsmModule<T>(modulePath: string | URL): Promise<T> {
  return new Function('modulePath', `return import(modulePath);`)(modulePath) as Promise<T>;
}

export async function getWebpackConfig(configPath: string): Promise<Configuration> {
  if (!existsSync(configPath)) {
    throw new Error(`Webpack configuration file ${configPath} does not exist.`);
  }

  switch (path.extname(configPath)) {
    case '.mjs':
      // Load the ESM configuration file using the TypeScript dynamic import workaround.
      // Once TypeScript provides support for keeping the dynamic import this workaround can be
      // changed to a direct dynamic import.
      return (await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath))).default;
    case '.cjs':
      return require(configPath);
    default:
      // The file could be either CommonJS or ESM.
      // CommonJS is tried first then ESM if loading fails.
      try {
        return require(configPath);
      } catch (e) {
        if ((e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ESM') {
          // Load the ESM configuration file using the TypeScript dynamic import workaround.
          // Once TypeScript provides support for keeping the dynamic import this workaround can be
          // changed to a direct dynamic import.
          return (await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath)))
            .default;
        }

        throw 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.dev/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.dev/reference/versions#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
 */


# 19.0.0-next.9 (2024-10-10)
## Breaking Changes
### compiler
- `this.foo` property reads no longer refer to template context variables. If you intended to read the template variable, do not use `this.`.
### core
- The deprecated `factories` property in `KeyValueDiffers` has been removed.
### localize
- The `name` option in the `ng add @localize` schematic has been removed in favor of the `project` option.
### platform-browser
- The deprecated `BrowserModule.withServerTransition` method has been removed. Please use the `APP_ID` DI token to set the application id instead.
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [09f589f000](https://github.com/angular/angular/commit/09f589f0006f4b428b675b83c12c0dc8ebb7e45f) | fix | `this.a` should always refer to class property `a` ([#55183](https://github.com/angular/angular/pull/55183)) |
| [e8d1944999](https://github.com/angular/angular/commit/e8d1944999e1fdfbd67630d475334c0d7f41a0eb) | fix | add multiple :host and nested selectors support ([#57796](https://github.com/angular/angular/pull/57796)) |
| [82144b6d63](https://github.com/angular/angular/commit/82144b6d63d072d112d1a7f4dcc018a1d64bb994) | fix | allow combinators inside pseudo selectors ([#57796](https://github.com/angular/angular/pull/57796)) |
| [292ea4714f](https://github.com/angular/angular/commit/292ea4714fb7e76cf1748d2f9059991e05c42574) | fix | fix comment typo ([#57796](https://github.com/angular/angular/pull/57796)) |
| [69529d8873](https://github.com/angular/angular/commit/69529d8873fbd7888ab68fddc6e7c654c5065764) | fix | fix parsing of the :host-context with pseudo selectors ([#57796](https://github.com/angular/angular/pull/57796)) |
| [2374b87b64](https://github.com/angular/angular/commit/2374b87b643e0373f85cf126d4b01b2fff785f64) | fix | preserve attributes attached to :host selector ([#57796](https://github.com/angular/angular/pull/57796)) |
| [46a6324c82](https://github.com/angular/angular/commit/46a6324c82a41b69c16a4c8c9f3fc52d1ecf6917) | fix | scope :host-context inside pseudo selectors, do not decrease specificity ([#57796](https://github.com/angular/angular/pull/57796)) |
| [bc5f1175e9](https://github.com/angular/angular/commit/bc5f1175e9f39dfa2699c4de19ee9af4ce4b50d1) | fix | transform pseudo selectors correctly for the encapsulated view ([#57796](https://github.com/angular/angular/pull/57796)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [8d8c03abc4](https://github.com/angular/angular/commit/8d8c03abc40099da268d7301f029954f3e3f1c90) | fix | defer symbols only used in types ([#58104](https://github.com/angular/angular/pull/58104)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [ee426c62f0](https://github.com/angular/angular/commit/ee426c62f07579ec7dc89ce9582972cc1e3471d4) | fix | allow signal write error ([#57973](https://github.com/angular/angular/pull/57973)) |
| [67db4305c2](https://github.com/angular/angular/commit/67db4305c2261625fd54d284c29e94e26cb19488) | fix | clean up afterRender after it is executed ([#58119](https://github.com/angular/angular/pull/58119)) |
| [656b5d3e78](https://github.com/angular/angular/commit/656b5d3e78004229a76488e0de1eb1d3508d8f6d) | fix | Re-assign error codes to be within core bounds (<1000) ([#53455](https://github.com/angular/angular/pull/53455)) |
| [97fb86d331](https://github.com/angular/angular/commit/97fb86d3310ae891ba4d894a8d3479eda08bd4c2) | perf | set encapsulation to `None` for empty component styles ([#57130](https://github.com/angular/angular/pull/57130)) |
| [c15ec36bd1](https://github.com/angular/angular/commit/c15ec36bd1dcff4c7c387337a5bcfd928994db2f) | refactor | remove deprecated `factories` Property in `KeyValueDiffers` ([#58064](https://github.com/angular/angular/pull/58064)) |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [bc83fc1e2e](https://github.com/angular/angular/commit/bc83fc1e2ebac1a99b6e8ed63cea48f48dd7c863) | feat | support converting to signal queries in VSCode extension ([#58106](https://github.com/angular/angular/pull/58106)) |
### localize
| Commit | Type | Description |
| -- | -- | -- |
| [9c3bd1b5d1](https://github.com/angular/angular/commit/9c3bd1b5d119bdcd4818892deae7f8a17861da42) | refactor | remove deprecated `name` option. ([#58063](https://github.com/angular/angular/pull/58063)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [c1aa411cf1](https://github.com/angular/angular/commit/c1aa411cf13259d991c8f224a2bafc3e9763fe8d) | fix | properly resolve tsconfig paths on windows ([#58137](https://github.com/angular/angular/pull/58137)) |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [5c61f46409](https://github.com/angular/angular/commit/5c61f46409855bb8fe66d71a9c16c00753032987) | refactor | remove deprecated `BrowserModule.withServerTransition` method ([#58062](https://github.com/angular/angular/pull/58062)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [9e82559de4](https://github.com/angular/angular/commit/9e82559de4e99a1aedf645a05b01fc08d3f4b1b1) | fix | destroy `PlatformRef` when error happens during the `bootstrap()` phase ([#58112](https://github.com/angular/angular/pull/58112)) |
### service-worker
| Commit | Type | Description |
| -- | -- | -- |
| [1479af978c](https://github.com/angular/angular/commit/1479af978cd2bbe4ee9f1ca9682684b8e5135fa7) | feat | finish implementation of refreshAhead feature ([#53356](https://github.com/angular/angular/pull/53356)) |


# 19.0.0-next.6 (2024-09-18)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [f611faadfe](https://github.com/angular/angular/commit/f611faadfedd8dc2c3291da98e2c2c60fe3984bd) | fix | extended diagnostics not validating ICUs ([#57845](https://github.com/angular/angular/pull/57845)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [3ebe6b4ad4](https://github.com/angular/angular/commit/3ebe6b4ad401337e18619edc34477ae98226fa3e) | feat | Add async `run` method on `ExperimentalPendingTasks` ([#56546](https://github.com/angular/angular/pull/56546)) |
| [1b1519224d](https://github.com/angular/angular/commit/1b1519224d10c1cd25d05d7b958772b9adee1e1a) | feat | mark input, output and model APIs as stable ([#57804](https://github.com/angular/angular/pull/57804)) |
| [e5adf92965](https://github.com/angular/angular/commit/e5adf9296595644e415d5c147df08890be01ba77) | feat | stabilize `@let` syntax ([#57813](https://github.com/angular/angular/pull/57813)) |
| [4231e8f28f](https://github.com/angular/angular/commit/4231e8f28ffe8f55dddc2af0647b5b04fa8445d7) | fix | Handle `@let` declaration with array when `preparingForHydration` ([#57816](https://github.com/angular/angular/pull/57816)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [59fe9bc772](https://github.com/angular/angular/commit/59fe9bc77236f1374427a851e55b0fa5216d9cf9) | feat | introduce signal input migration as `ng generate` schematic ([#57805](https://github.com/angular/angular/pull/57805)) |
| [6144612940](https://github.com/angular/angular/commit/614461294041d7a2331bf7528907f37353205115) | fix | account for explicit standalone: false in migration ([#57803](https://github.com/angular/angular/pull/57803)) |

# 18.2.5 (2024-09-18)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [e685ed883a](https://github.com/angular/angular/commit/e685ed883a09628c2b87a11a17ffb6d858d51c54) | fix | extended diagnostics not validating ICUs ([#57845](https://github.com/angular/angular/pull/57845)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [76709d5d6e](https://github.com/angular/angular/commit/76709d5d6ec1f83e3f44641704b540636f91b5f4) | fix | Handle `@let` declaration with array when `preparingForHydration` ([#57816](https://github.com/angular/angular/pull/57816)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [5c866942a1](https://github.com/angular/angular/commit/5c866942a1b8a60e3a024385048bbb2f52f84513) | fix | account for explicit standalone: false in migration ([#57803](https://github.com/angular/angular/pull/57803)) |

# 19.0.0-next.5 (2024-09-11)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [c93b510f9b](https://github.com/angular/angular/commit/c93b510f9b2e23aa7a3848a04c05249fde14a9b1) | feat | allow passing `undefined` without needing to include it in the type argument of `input` ([#57621](https://github.com/angular/angular/pull/57621)) |

# 18.2.4 (2024-09-11)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [b619d6987e](https://github.com/angular/angular/commit/b619d6987efe054b9b37c24e578f58792b25d146) | fix | produce less noisy errors when parsing control flow ([#57711](https://github.com/angular/angular/pull/57711)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [9895e4492f](https://github.com/angular/angular/commit/9895e4492fbe357b584ca5a6dd86d2c9d50d9fda) | fix | replace leftover modules with their exports during pruning ([#57684](https://github.com/angular/angular/pull/57684)) |

# 19.0.0-next.4 (2024-09-09)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [40ff18f87a](https://github.com/angular/angular/commit/40ff18f87a04fd1c00dea9fee1568bfe52acf25c) | fix | produce less noisy errors when parsing control flow ([#57711](https://github.com/angular/angular/pull/57711)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6ea8e1e9aa](https://github.com/angular/angular/commit/6ea8e1e9aae028572873cf97aa1949c8153f458f) | feat | Add a schematics to migrate to `standalone: false`. ([#57643](https://github.com/angular/angular/pull/57643)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [cbec46a51d](https://github.com/angular/angular/commit/cbec46a51d22a1238cc8bf1ebdf343d031b8f0ba) | feat | migrate .pipe calls in outputs used in tests ([#57691](https://github.com/angular/angular/pull/57691)) |
| [68e5370a66](https://github.com/angular/angular/commit/68e5370a66633f4b069d6adffa95c2ea94291820) | feat | remove complete calls for migrated outputs ([#57671](https://github.com/angular/angular/pull/57671)) |
| [9da21f798d](https://github.com/angular/angular/commit/9da21f798de2033af9d39a8a9b255ef2fe74f948) | feat | replace .next usage on outputs ([#57654](https://github.com/angular/angular/pull/57654)) |
| [71f5ef2aa5](https://github.com/angular/angular/commit/71f5ef2aa53a74bab7d0543f98870d81c44c4978) | fix | change imports to be G3 compatible ([#57654](https://github.com/angular/angular/pull/57654)) |
| [3a264db866](https://github.com/angular/angular/commit/3a264db86611cba9b69780d7f01ee25787278320) | fix | properly handle comments in output migration ([#57691](https://github.com/angular/angular/pull/57691)) |
| [fc95a9adff](https://github.com/angular/angular/commit/fc95a9adff42da53dfeee5df8c42be25e8559708) | fix | replace leftover modules with their exports during pruning ([#57684](https://github.com/angular/angular/pull/57684)) |