Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/frameworks/angular/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getBuilderType, BuilderType } from "./utils";
import { expect } from "chai";

describe("getBuilderType", () => {
it("should return the correct builder type for valid builders", () => {
expect(getBuilderType("@angular-devkit/build-angular:browser")).to.equal(BuilderType.BROWSER);
expect(getBuilderType("@angular-devkit/build-angular:server")).to.equal(BuilderType.SERVER);
expect(getBuilderType("@angular-devkit/build-angular:dev-server")).to.equal(
BuilderType.DEV_SERVER,
);
expect(getBuilderType("@angular-devkit/build-angular:ssr-dev-server")).to.equal(
BuilderType.SSR_DEV_SERVER,
);
expect(getBuilderType("@angular-devkit/build-angular:prerender")).to.equal(
BuilderType.PRERENDER,
);
expect(getBuilderType("@angular-devkit/build-angular:application")).to.equal(
BuilderType.APPLICATION,
);
expect(getBuilderType("@angular-devkit/build-angular:browser-esbuild")).to.equal(
BuilderType.BROWSER_ESBUILD,
);
expect(getBuilderType("@angular-devkit/build-angular:deploy")).to.equal(BuilderType.DEPLOY);
});

it("should return null for invalid builders", () => {
expect(getBuilderType("@angular-devkit/build-angular:invalid")).to.be.null;
expect(getBuilderType("invalid")).to.be.null;
expect(getBuilderType(":")).to.be.null;
expect(getBuilderType("::")).to.be.null;
expect(getBuilderType("random:string")).to.be.null;
});

it("should handle builders with no colon", () => {
expect(getBuilderType("@angular-devkit/build-angular")).to.be.null;
});
});
159 changes: 60 additions & 99 deletions src/frameworks/angular/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,55 +66,32 @@ async function localesForTarget(
return { locales, defaultLocale };
}

const enum ExpectedBuilder {
APPLICATION = "@angular-devkit/build-angular:application",
BROWSER_ESBUILD = "@angular-devkit/build-angular:browser-esbuild",
DEPLOY = "@angular/fire:deploy",
DEV_SERVER = "@angular-devkit/build-angular:dev-server",
LEGACY_BROWSER = "@angular-devkit/build-angular:browser",
LEGACY_NGUNIVERSAL_PRERENDER = "@nguniversal/builders:prerender",
LEGACY_DEVKIT_PRERENDER = "@angular-devkit/build-angular:prerender",
LEGACY_SERVER = "@angular-devkit/build-angular:server",
LEGACY_NGUNIVERSAL_SSR_DEV_SERVER = "@nguniversal/builders:ssr-dev-server",
LEGACY_DEVKIT_SSR_DEV_SERVER = "@angular-devkit/build-angular:ssr-dev-server",
export enum BuilderType {
DEPLOY = "deploy",
DEV_SERVER = "dev-server",
SSR_DEV_SERVER = "ssr-dev-server",
SERVER = "server",
BROWSER = "browser",
BROWSER_ESBUILD = "browser-esbuild",
APPLICATION = "application",
PRERENDER = "prerender",
}

const enum ThirdPartyBuilder {
CUSTOM_WEBPACK_DEV_SERVER = "@angular-builders/custom-webpack:dev-server",
CUSTOM_WEBPACK_SERVER = "@angular-builders/custom-webpack:server",
CUSTOM_WEBPACK_BROWSER = "@angular-builders/custom-webpack:browser",
NGX_ENV_APPLICATION = "@ngx-env/builder:application",
NGX_ENV_DEV_SERVER = "@ngx-env/builder:dev-server",
}

const DEV_SERVER_TARGETS: string[] = [
ExpectedBuilder.DEV_SERVER,
ExpectedBuilder.LEGACY_NGUNIVERSAL_SSR_DEV_SERVER,
ExpectedBuilder.LEGACY_DEVKIT_SSR_DEV_SERVER,
ThirdPartyBuilder.CUSTOM_WEBPACK_DEV_SERVER,
ThirdPartyBuilder.CUSTOM_WEBPACK_SERVER,
ThirdPartyBuilder.NGX_ENV_DEV_SERVER,
];
const DEV_SERVER_TARGETS: BuilderType[] = [BuilderType.DEV_SERVER, BuilderType.SSR_DEV_SERVER];

function getValidBuilders(purpose: BUILD_TARGET_PURPOSE): string[] {
function getValidBuilderTypes(purpose: BUILD_TARGET_PURPOSE): BuilderType[] {
return [
ExpectedBuilder.APPLICATION,
ExpectedBuilder.BROWSER_ESBUILD,
ExpectedBuilder.DEPLOY,
ExpectedBuilder.LEGACY_BROWSER,
ExpectedBuilder.LEGACY_DEVKIT_PRERENDER,
ExpectedBuilder.LEGACY_NGUNIVERSAL_PRERENDER,
BuilderType.APPLICATION,
BuilderType.BROWSER_ESBUILD,
BuilderType.DEPLOY,
BuilderType.BROWSER,
BuilderType.PRERENDER,
...(purpose === "deploy" ? [] : DEV_SERVER_TARGETS),
];
}

function getValidThirdPartyBuilders(): string[] {
return [ThirdPartyBuilder.CUSTOM_WEBPACK_BROWSER, ThirdPartyBuilder.NGX_ENV_APPLICATION];
}

export async function getAllTargets(purpose: BUILD_TARGET_PURPOSE, dir: string) {
const validBuilders = getValidBuilders(purpose);
const validThirdPartyBuilders = getValidThirdPartyBuilders();
const validBuilderTypes = getValidBuilderTypes(purpose);
const [{ NodeJsAsyncHost }, { workspaces }, { targetStringFromTarget }] = await Promise.all([
relativeRequire(dir, "@angular-devkit/core/node"),
relativeRequire(dir, "@angular-devkit/core"),
Expand All @@ -127,15 +104,9 @@ export async function getAllTargets(purpose: BUILD_TARGET_PURPOSE, dir: string)
workspace.projects.forEach((projectDefinition, project) => {
if (projectDefinition.extensions.projectType !== "application") return;
projectDefinition.targets.forEach((targetDefinition, target) => {
if (
!validBuilders.includes(targetDefinition.builder) &&
!validThirdPartyBuilders.includes(targetDefinition.builder)
) {
const builderType = getBuilderType(targetDefinition.builder);
if (builderType && !validBuilderTypes.includes(builderType)) {
return;
} else if (validThirdPartyBuilders.includes(targetDefinition.builder)) {
console.warn(
"You are using an unofficial Angular builder, in case of unexpected behavior please file a bug at https://github.com/firebase/firebase-tools/issues.",
);
}
const configurations = Object.keys(targetDefinition.configurations || {});
if (!configurations.includes("production")) configurations.push("production");
Expand Down Expand Up @@ -212,39 +183,32 @@ export async function getContext(dir: string, targetOrConfiguration?: string) {

if (overrideTarget) {
const target = workspaceProject.targets.get(overrideTarget.target)!;
const builder = target.builder;
switch (builder) {
case ExpectedBuilder.DEPLOY:
const builderType = getBuilderType(target.builder);
switch (builderType) {
case BuilderType.DEPLOY:
deployTarget = overrideTarget;
break;
case ExpectedBuilder.APPLICATION:
case ThirdPartyBuilder.NGX_ENV_APPLICATION:
case BuilderType.APPLICATION:
buildTarget = overrideTarget;
break;
case ExpectedBuilder.BROWSER_ESBUILD:
case ExpectedBuilder.LEGACY_BROWSER:
case ThirdPartyBuilder.CUSTOM_WEBPACK_BROWSER:
case BuilderType.BROWSER:
case BuilderType.BROWSER_ESBUILD:
browserTarget = overrideTarget;
break;
case ExpectedBuilder.LEGACY_DEVKIT_PRERENDER:
case ExpectedBuilder.LEGACY_NGUNIVERSAL_PRERENDER:
case BuilderType.PRERENDER:
prerenderTarget = overrideTarget;
break;
case ExpectedBuilder.DEV_SERVER:
case ExpectedBuilder.LEGACY_NGUNIVERSAL_SSR_DEV_SERVER:
case ExpectedBuilder.LEGACY_DEVKIT_SSR_DEV_SERVER:
case ThirdPartyBuilder.CUSTOM_WEBPACK_DEV_SERVER:
case ThirdPartyBuilder.CUSTOM_WEBPACK_SERVER:
case ThirdPartyBuilder.NGX_ENV_DEV_SERVER:
case BuilderType.DEV_SERVER:
case BuilderType.SSR_DEV_SERVER:
serveTarget = overrideTarget;
break;
default:
throw new FirebaseError(`builder ${builder} not known.`);
throw new FirebaseError(`builder type ${builderType} not known.`);
}
} else if (workspaceProject.targets.has("deploy")) {
const { builder, defaultConfiguration = "production" } =
workspaceProject.targets.get("deploy")!;
if (builder === ExpectedBuilder.DEPLOY) {
if (getBuilderType(builder) === BuilderType.DEPLOY) {
deployTarget = {
project,
target: "deploy",
Expand Down Expand Up @@ -324,15 +288,13 @@ export async function getContext(dir: string, targetOrConfiguration?: string) {
if (!buildTarget && !browserTarget && workspaceProject.targets.has("build")) {
const { builder, defaultConfiguration = "production" } =
workspaceProject.targets.get("build")!;
const builderType = getBuilderType(builder);
const target = {
project,
target: "build",
configuration: configuration || defaultConfiguration,
};
if (
builder === ExpectedBuilder.LEGACY_BROWSER ||
builder === ExpectedBuilder.BROWSER_ESBUILD
) {
if (builderType === BuilderType.BROWSER || builderType === BuilderType.BROWSER_ESBUILD) {
browserTarget = target;
} else {
buildTarget = target;
Expand Down Expand Up @@ -383,30 +345,19 @@ export async function getContext(dir: string, targetOrConfiguration?: string) {
const definition = workspaceProject.targets.get(target.target);
if (!definition) throw new FirebaseError(`${target} could not be found in your angular.json`);
const { builder } = definition;
if (target === deployTarget && builder === ExpectedBuilder.DEPLOY) continue;
if (target === buildTarget && builder === ExpectedBuilder.APPLICATION) continue;
if (target === buildTarget && builder === ExpectedBuilder.LEGACY_BROWSER) continue;
if (target === buildTarget && builder === ThirdPartyBuilder.CUSTOM_WEBPACK_BROWSER) continue;
if (target === buildTarget && builder === ThirdPartyBuilder.NGX_ENV_APPLICATION) continue;
if (target === browserTarget && builder === ExpectedBuilder.BROWSER_ESBUILD) continue;
if (target === browserTarget && builder === ThirdPartyBuilder.CUSTOM_WEBPACK_BROWSER)
continue;
if (target === browserTarget && builder === ThirdPartyBuilder.NGX_ENV_APPLICATION) continue;
if (target === browserTarget && builder === ExpectedBuilder.LEGACY_BROWSER) continue;
if (target === prerenderTarget && builder === ExpectedBuilder.LEGACY_DEVKIT_PRERENDER)
continue;
if (target === prerenderTarget && builder === ExpectedBuilder.LEGACY_NGUNIVERSAL_PRERENDER)
continue;
if (target === serverTarget && builder === ExpectedBuilder.LEGACY_SERVER) continue;
if (target === serveTarget && builder === ExpectedBuilder.LEGACY_NGUNIVERSAL_SSR_DEV_SERVER)
continue;
if (target === serveTarget && builder === ExpectedBuilder.LEGACY_DEVKIT_SSR_DEV_SERVER)
continue;
if (target === serveTarget && builder === ExpectedBuilder.DEV_SERVER) continue;
if (target === serveTarget && builder === ThirdPartyBuilder.CUSTOM_WEBPACK_DEV_SERVER)
continue;
if (target === serveTarget && builder === ThirdPartyBuilder.CUSTOM_WEBPACK_SERVER) continue;
if (target === serveTarget && builder === ThirdPartyBuilder.NGX_ENV_DEV_SERVER) continue;
const builderType = getBuilderType(builder);
if (target === deployTarget && builderType === BuilderType.DEPLOY) continue;
if (target === buildTarget && builderType === BuilderType.APPLICATION) continue;
if (target === buildTarget && builderType === BuilderType.BROWSER) continue;
if (target === browserTarget && builderType === BuilderType.BROWSER_ESBUILD) continue;
if (target === browserTarget && builderType === BuilderType.BROWSER) continue;
if (target === browserTarget && builderType === BuilderType.APPLICATION) continue;
if (target === prerenderTarget && builderType === BuilderType.PRERENDER) continue;
if (target === prerenderTarget && builderType === BuilderType.PRERENDER) continue;
if (target === serverTarget && builderType === BuilderType.SERVER) continue;
if (target === serveTarget && builderType === BuilderType.SSR_DEV_SERVER) continue;
if (target === serveTarget && builderType === BuilderType.DEV_SERVER) continue;
if (target === serveTarget && builderType === BuilderType.SERVER) continue;
throw new FirebaseError(
`${definition.builder} (${targetString}) is not a recognized builder. Please check your angular.json`,
);
Expand Down Expand Up @@ -464,11 +415,7 @@ export async function getBrowserConfig(sourceDir: string, configuration: string)

const outputPath = join(
targetOptions.outputPath,
buildTarget &&
(builderName === ExpectedBuilder.APPLICATION ||
builderName === ThirdPartyBuilder.NGX_ENV_APPLICATION)
? "browser"
: "",
buildTarget && getBuilderType(builderName) === BuilderType.APPLICATION ? "browser" : "",
);
return { locales, baseHref, outputPath, defaultLocale };
}
Expand Down Expand Up @@ -623,3 +570,17 @@ function throwCannotDetermineTarget(error?: Error): never {
{ original: error },
);
}

/**
* Extracts the builder type from a full builder string (everything after the colon)
* @example
* getBuilderType("@angular-devkit/build-angular:browser") // returns "browser"
*/
export function getBuilderType(builder: string): BuilderType | null {
const colonIndex = builder.lastIndexOf(":");
const builderType = colonIndex >= 0 ? builder.slice(colonIndex + 1) : undefined;
if (!builderType || !Object.values(BuilderType).includes(builderType as BuilderType)) {
return null;
}
return builderType as BuilderType;
}