Skip to content

Commit

Permalink
Angular use spec (#261)
Browse files Browse the repository at this point in the history
* add output bundle spec to common module

* bump common module version

* use new output bundle spec

* resolve merge

* copy over all resources

* add packagejson update

* fix lint

* refactor angular to use spec

* undo nextjs changes

* fix lint

* lint

* fix e2e test

* bump versions
  • Loading branch information
Yuangwang authored Sep 26, 2024
1 parent 717eded commit 9719c22
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 87 deletions.
10 changes: 2 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/@apphosting/adapter-angular/e2e/run-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const tests = await Promise.all(

const bundleYaml = parseYaml(readFileSync(join(cwd, ".apphosting/bundle.yaml")).toString());

const runCommand = bundleYaml.runCommand;
const runCommand = bundleYaml.serverConfig.runCommand;

if (typeof runCommand !== "string") {
throw new Error("runCommand must be a string");
Expand Down
2 changes: 1 addition & 1 deletion packages/@apphosting/adapter-angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apphosting/adapter-angular",
"version": "17.2.8",
"version": "17.2.9",
"main": "dist/index.js",
"description": "Experimental addon to the Firebase CLI to add web framework support",
"repository": {
Expand Down
51 changes: 21 additions & 30 deletions packages/@apphosting/adapter-angular/src/bin/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,32 @@ describe("build commands", () => {
beforeEach(() => {
tmpDir = generateTmpDir();
outputBundleOptions = {
baseDirectory: resolve(tmpDir, "dist", "test"),
browserDirectory: resolve(tmpDir, ".apphosting", "dist", "browser"),
browserDirectory: resolve(tmpDir, "dist", "test", "browser"),
bundleYamlPath: resolve(tmpDir, ".apphosting", "bundle.yaml"),
outputBaseDirectory: resolve(tmpDir, ".apphosting", "dist"),
outputDirectory: resolve(tmpDir, ".apphosting"),
serverFilePath: resolve(tmpDir, ".apphosting", "dist", "server", "server.mjs"),
serverFilePath: resolve(tmpDir, "dist", "test", "server", "server.mjs"),
needsServerGenerated: false,
};
defaultAngularVersion = "17.3.8";
});

it("expects all output bundle files to be generated", async () => {
const { generateOutputDirectory, validateOutputDirectory, createMetadata } = await importUtils;
const { generateBuildOutput, validateOutputDirectory, createMetadata } = await importUtils;
const files = {
"dist/test/browser/browserfile": "",
"dist/test/server/server.mjs": "",
};
const packageVersion = createMetadata(defaultAngularVersion).adapterVersion;
generateTestFiles(tmpDir, files);
await generateOutputDirectory(tmpDir, outputBundleOptions, defaultAngularVersion);
await generateBuildOutput(tmpDir, outputBundleOptions, defaultAngularVersion);
await validateOutputDirectory(outputBundleOptions);

const expectedFiles = {
".apphosting/dist/browser/browserfile": "",
".apphosting/dist/server/server.mjs": "",
".apphosting/bundle.yaml": `
runCommand: node .apphosting/dist/server/server.mjs
neededDirs:
- .apphosting
staticAssets:
- .apphosting/dist/browser
env: []
"dist/test/browser/browserfile": "",
"dist/test/server/server.mjs": "",
".apphosting/bundle.yaml": `version: v1
serverConfig:
runCommand: node dist/test/server/server.mjs
environmentVariables: []
metadata:
adapterPackageName: "@apphosting/adapter-angular"
adapterVersion: ${packageVersion}
Expand All @@ -56,43 +50,40 @@ metadata:
});

it("expects SSR_PORT variable is added to bundle.yaml for Angular v17.3.2", async () => {
const { generateOutputDirectory } = await importUtils;
const { generateBuildOutput } = await importUtils;
const files = {
"dist/test/browser/browserfile": "",
"dist/test/server/server.mjs": "",
};
generateTestFiles(tmpDir, files);
await generateOutputDirectory(tmpDir, outputBundleOptions, "17.3.2");
await generateBuildOutput(tmpDir, outputBundleOptions, "17.3.2");

const expectedContents = `env:
- variable: SSR_PORT
value: "8080"
availability: RUNTIME
`;
const expectedContents = ` environmentVariables:
- variable: SSR_PORT
value: "8080"
availability:
- RUNTIME`;
validateFileExistsAndContains(tmpDir, ".apphosting/bundle.yaml", expectedContents);
});

it("test failed validateOutputDirectory", async () => {
const { generateOutputDirectory, validateOutputDirectory } = await importUtils;
const { generateBuildOutput, validateOutputDirectory } = await importUtils;
const files = {
"dist/test/browser/browserfile": "",
"dist/test/server/notserver.mjs": "",
};
generateTestFiles(tmpDir, files);
await generateOutputDirectory(tmpDir, outputBundleOptions, defaultAngularVersion);
await generateBuildOutput(tmpDir, outputBundleOptions, defaultAngularVersion);
assert.rejects(async () => await validateOutputDirectory(outputBundleOptions));
});

it("test populate output bundle options", async () => {
const { populateOutputBundleOptions } = await importUtils;
const expectedOutputBundleOptions = {
baseDirectory: "/test",
browserDirectory: resolve(".apphosting", "browser"),
browserDirectory: "/browser",
bundleYamlPath: resolve(".apphosting", "bundle.yaml"),
outputBaseDirectory: resolve(".apphosting", "dist"),
outputDirectory: resolve("", ".apphosting"),
needsServerGenerated: false,
serverFilePath: resolve(".apphosting", "server", "server.mjs"),
serverFilePath: path.join("/server", "server.mjs"),
};
const outputPaths = {
root: new URL("file:///test"),
Expand Down
6 changes: 3 additions & 3 deletions packages/@apphosting/adapter-angular/src/bin/build.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#! /usr/bin/env node
import {
generateOutputDirectory,
generateBuildOutput,
checkBuildConditions,
validateOutputDirectory,
parseOutputBundleOptions,
} from "../utils.js";
import { getBuildOptions, runBuild } from "@apphosting/common";

const root = process.cwd();
const opts = getBuildOptions();

// Check build conditions, which vary depending on your project structure (standalone or monorepo)
Expand All @@ -23,6 +22,7 @@ if (!output) {
throw new Error("No output from Angular build command, expecting a build manifest file.");
}
const outputBundleOptions = parseOutputBundleOptions(output);
await generateOutputDirectory(root, outputBundleOptions, process.env.FRAMEWORK_VERSION);
const root = process.cwd();
await generateBuildOutput(root, outputBundleOptions, process.env.FRAMEWORK_VERSION);

await validateOutputDirectory(outputBundleOptions);
3 changes: 0 additions & 3 deletions packages/@apphosting/adapter-angular/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { URL } from "node:url";
// options to help generate output directory
export interface OutputBundleOptions {
bundleYamlPath: string;
outputDirectory: string;
baseDirectory: string;
outputBaseDirectory: string;
serverFilePath: string;
browserDirectory: string;
needsServerGenerated: boolean;
Expand Down
64 changes: 27 additions & 37 deletions packages/@apphosting/adapter-angular/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import { fileURLToPath } from "url";
import { execSync } from "child_process";
import { resolve, normalize, relative, dirname, join } from "path";
import { stringify as yamlStringify } from "yaml";
import {
Availability,
EnvironmentVariable,
Metadata,
OutputBundleOptions,
OutputPaths,
buildManifestSchema,
} from "./interface.js";
import { OutputBundleOptions, OutputPaths, buildManifestSchema } from "./interface.js";
import { createRequire } from "node:module";
import stripAnsi from "strip-ansi";
import { BuildOptions } from "@apphosting/common";
import {
BuildOptions,
OutputBundleConfig,
EnvVarConfig,
Metadata,
Availability,
} from "@apphosting/common";

// fs-extra is CJS, readJson can't be imported using shorthand
export const { writeFile, move, readJson, mkdir, copyFile, readFileSync, existsSync } = fsExtra;
Expand Down Expand Up @@ -95,14 +94,10 @@ export function populateOutputBundleOptions(outputPaths: OutputPaths): OutputBun
serverRelativePath = relative(baseDirectory, fileURLToPath(outputPaths["server"]));
needsServerGenerated = false;
}

return {
bundleYamlPath: resolve(outputBundleDir, "bundle.yaml"),
outputDirectory: outputBundleDir,
baseDirectory,
outputBaseDirectory: resolve(outputBundleDir, "dist"),
serverFilePath: resolve(outputBundleDir, "dist", serverRelativePath, "server.mjs"),
browserDirectory: resolve(outputBundleDir, "dist", browserRelativePath),
serverFilePath: resolve(baseDirectory, serverRelativePath, "server.mjs"),
browserDirectory: resolve(baseDirectory, browserRelativePath),
needsServerGenerated,
};
}
Expand Down Expand Up @@ -161,32 +156,28 @@ export function createMetadata(angularVersion: string): Metadata {
}

/**
* Move the base output directory, which contains the server and browser bundle directory, and prerendered routes
* as well as generating bundle.yaml.
* Generate the bundle.yaml
*/
export async function generateOutputDirectory(
export async function generateBuildOutput(
cwd: string,
outputBundleOptions: OutputBundleOptions,
angularVersion: string,
): Promise<void> {
await move(outputBundleOptions.baseDirectory, outputBundleOptions.outputBaseDirectory, {
overwrite: true,
});
if (outputBundleOptions.needsServerGenerated) {
await generateServer(outputBundleOptions);
}
await generateBundleYaml(outputBundleOptions, cwd, angularVersion);
}

// add environment variable to bundle.yaml if needed for specific versions
function generateEnvVars(angularVersion: string): EnvironmentVariable[] {
const runtimeEnvVars: EnvironmentVariable[] = [];
function generateEnvVars(angularVersion: string): EnvVarConfig[] {
const runtimeEnvVars: EnvVarConfig[] = [];
// add env var to solve angular port issue, existing only for Angular v17.3.2 (b/332896115)
if (angularVersion === "17.3.2") {
const ssrPortEnvVar: EnvironmentVariable = {
const ssrPortEnvVar: EnvVarConfig = {
variable: "SSR_PORT",
value: "8080",
availability: Availability.Runtime,
availability: [Availability.Runtime],
};
runtimeEnvVars.push(ssrPortEnvVar);
}
Expand All @@ -195,20 +186,20 @@ function generateEnvVars(angularVersion: string): EnvironmentVariable[] {

// Generate bundle.yaml
async function generateBundleYaml(
outputBundleOptions: OutputBundleOptions,
opts: OutputBundleOptions,
cwd: string,
angularVersion: string,
): Promise<void> {
await writeFile(
outputBundleOptions.bundleYamlPath,
yamlStringify({
runCommand: `node ${normalize(relative(cwd, outputBundleOptions.serverFilePath))}`,
neededDirs: [normalize(relative(cwd, outputBundleOptions.outputDirectory))],
staticAssets: [normalize(relative(cwd, outputBundleOptions.browserDirectory))],
env: generateEnvVars(angularVersion),
metadata: createMetadata(angularVersion),
}),
);
await mkdir(dirname(opts.bundleYamlPath));
const outputBundle: OutputBundleConfig = {
version: "v1",
serverConfig: {
runCommand: `node ${normalize(relative(cwd, opts.serverFilePath))}`,
environmentVariables: generateEnvVars(angularVersion),
},
metadata: createMetadata(angularVersion),
};
await writeFile(opts.bundleYamlPath, yamlStringify(outputBundle));
}

// Generate server file for CSR apps
Expand All @@ -222,7 +213,6 @@ export async function validateOutputDirectory(
outputBundleOptions: OutputBundleOptions,
): Promise<void> {
if (
!(await fsExtra.exists(outputBundleOptions.outputDirectory)) ||
!(await fsExtra.exists(outputBundleOptions.browserDirectory)) ||
!(await fsExtra.exists(outputBundleOptions.serverFilePath)) ||
!(await fsExtra.exists(outputBundleOptions.bundleYamlPath))
Expand Down
4 changes: 2 additions & 2 deletions packages/@apphosting/adapter-nextjs/e2e/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("app", () => {
const initialResponse = await fetch(posix.join(host, "isr", "demand"));
assert.ok(initialResponse.ok);
const initialText = await initialResponse.text();
const initialUUID = initialText.match(/UUID<\/p>\s*<h2>([^<]+)<\/h2>/)?.[1];
const initialUUID = /UUID<\/p>\s*<h2>([^<]+)<\/h2>/.exec(initialText)?.[1];

// Trigger revalidation
const revalidateResponse = await fetch(posix.join(host, "isr", "demand", "revalidate"), {
Expand All @@ -108,7 +108,7 @@ describe("app", () => {
const newResponse = await fetch(posix.join(host, "isr", "demand"));
assert.ok(newResponse.ok);
const newText = await newResponse.text();
const newUUID = newText.match(/UUID<\/p>\s*<h2>([^<]+)<\/h2>/)?.[1];
const newUUID = /UUID<\/p>\s*<h2>([^<]+)<\/h2>/.exec(newText)?.[1];

// Check if the UUID has changed, indicating successful revalidation
assert.notEqual(initialUUID, newUUID, "UUID should change after revalidation");
Expand Down
2 changes: 1 addition & 1 deletion packages/@apphosting/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apphosting/common",
"version": "0.0.3",
"version": "0.0.4",
"description": "Shared library code for App Hosting framework adapters",
"author": {
"name": "Firebase",
Expand Down
2 changes: 1 addition & 1 deletion packages/@apphosting/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface EnvVarConfig {
// Represents where environment variables are made available
export enum Availability {
// Runtime environment variables are available on the server when the app is run
Runtime,
Runtime = "RUNTIME",
}

// Options to configure the build of a framework application
Expand Down

0 comments on commit 9719c22

Please sign in to comment.