Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix intermittent CI build failures and add basic Angular functional tests #193

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 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
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ jobs:
restore-keys: |
${{ runner.os }}-20-
- name: Install deps
if: steps.node_modules_cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build
run: npm run build
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ node_modules

# firebase
.firebase
.apphosting
1,095 changes: 847 additions & 248 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/@apphosting/adapter-angular/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
/e2e
46 changes: 46 additions & 0 deletions packages/@apphosting/adapter-angular/e2e/csr/basics.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from "assert";
import { posix } from "path";

const host = process.env.HOST;
const enabledSSR = !!process.env.SSR;

if (!host) {
throw new Error("HOST environment variable expected");
}

describe(`client-side (SSR ${enabledSSR ? "enabled" : "disabled"})`, () => {
it("/", async () => {
const response = await fetch(host);
assert.ok(response.ok);
assert.equal(response.headers.get("content-type")?.toLowerCase(), "text/html; charset=utf-8");
assert.equal(response.headers.get("cache-control"), enabledSSR ? null : "public, max-age=0");
});

it("/ssg", async () => {
const response = await fetch(posix.join(host, "ssg"));
assert.ok(response.ok);
assert.equal(response.headers.get("content-type")?.toLowerCase(), "text/html; charset=utf-8");
assert.equal(response.headers.get("cache-control"), enabledSSR ? null : "public, max-age=0");
});

it("/favicon.ico", async () => {
const response = await fetch(posix.join(host, "favicon.ico"));
assert.ok(response.ok);
assert.equal(response.headers.get("content-type"), "image/x-icon");
assert.equal(response.headers.get("cache-control"), "public, max-age=31536000");
});

it("/deferrable-views", async () => {
const response = await fetch(posix.join(host, "deferrable-views"));
assert.ok(response.ok);
assert.equal(response.headers.get("content-type")?.toLowerCase(), "text/html; charset=utf-8");
assert.equal(response.headers.get("cache-control"), enabledSSR ? null : "public, max-age=0");
});

it(`404`, async () => {
const response = await fetch(posix.join(host, Math.random().toString()));
assert.ok(response.ok);
assert.equal(response.headers.get("content-type")?.toLowerCase(), "text/html; charset=utf-8");
assert.equal(response.headers.get("cache-control"), enabledSSR ? null : "public, max-age=0");
});
});
100 changes: 100 additions & 0 deletions packages/@apphosting/adapter-angular/e2e/run-local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { cp, readFile, writeFile } from "fs/promises";
import tmp from "tmp";
import promiseSpawn from "@npmcli/promise-spawn";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { parse as parseYaml } from "yaml";
import { spawn } from "child_process";

const starterTemplateDir = "../../../starters/angular/basic";

tmp.setGracefulCleanup();
const { name: cwd } = tmp.dirSync();
console.log(`Copying ${starterTemplateDir} to ${cwd}`);
await cp(starterTemplateDir, cwd, { recursive: true });
console.log("> npm i");
await promiseSpawn("npm", ["i"], { cwd, stdio: "inherit", shell: true });

const __dirname = dirname(fileURLToPath(import.meta.url));
const buildScript = join(__dirname, "../dist/bin/build.js");
const angularJSON = JSON.parse((await readFile(join(cwd, "angular.json"))).toString());

const errors: any[] = [];

for (const enableSSR of [false, true]) {
try {
angularJSON.projects["firebase-app-hosting-angular"].architect.build.options.ssr =
enableSSR && {
entry: "server.ts",
};
await writeFile(join(cwd, "angular.json"), JSON.stringify(angularJSON, null, 2));

await promiseSpawn("node", [buildScript], { cwd, stdio: "inherit", shell: true });

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

const runCommand = bundleYaml.runCommand;

if (typeof runCommand !== "string") {
throw new Error("runCommand must be a string");
}

console.log(`> ${runCommand}`);

const [runScript, ...runArgs] = runCommand.split(" ");
let resolveHostname: (it: string) => void;
let rejectHostname: () => void;
const hostnamePromise = new Promise<string>((resolve, reject) => {
resolveHostname = resolve;
rejectHostname = reject;
});
const port = 8080 + Math.floor(Math.random() * 1000);
const run = spawn(runScript, runArgs, {
cwd,
shell: true,
env: {
NODE_ENV: "production",
LOCAL: "1",
PORT: port.toString(),
},
});
run.stderr.on("data", (data) => console.error(data.toString()));
run.stdout.on("data", (data) => {
console.log(data.toString());
if (data.toString() === `Node Express server listening on http://localhost:${port}\n`) {
resolveHostname(`http://localhost:${port}`);
} else {
run.stdin.end();
run.kill("SIGKILL");
}
});
run.on("close", (code) => {
if (code) {
rejectHostname();
}
});
const HOST = await hostnamePromise;
console.log("> ts-mocha -p tsconfig.json e2e/**/*.spec.ts");
await promiseSpawn("ts-mocha", ["-p", "tsconfig.json", "e2e/**/*.spec.ts"], {
shell: true,
stdio: "inherit",
env: {
...process.env,
SSR: enableSSR ? "1" : undefined,
HOST,
},
}).finally(() => {
run.stdin.end();
run.kill("SIGKILL");
});
} catch (e) {
errors.push(e);
}
}

if (errors.length) {
console.error(errors);
process.exit(1);
}

process.exit(0);
19 changes: 19 additions & 0 deletions packages/@apphosting/adapter-angular/e2e/ssr/basics.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "assert";
import { posix } from "path";

const host = process.env.HOST;

if (!host) {
throw new Error("HOST environment variable expected");
}

if (process.env.SSR) {
describe("server-side", () => {
it("/ssr", async () => {
const response = await fetch(posix.join(host, "ssr"));
assert.ok(response.ok);
assert.equal(response.headers.get("content-type"), "text/html; charset=utf-8");
assert.equal(response.headers.get("cache-control"), null);
});
});
}
17 changes: 13 additions & 4 deletions packages/@apphosting/adapter-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
"type": "module",
"sideEffects": false,
"scripts": {
"build": "rm -rf dist && tsc && chmod +x ./dist/bin/*",
"test": "ts-mocha -p tsconfig.json src/**/*.spec.ts"
"build": "rm -rf dist && tsc && rollup -c --silent && chmod +x ./dist/bin/*",
"test": "npm run test:unit && npm run test:functional",
"test:unit": "ts-mocha -p tsconfig.json src/**/*.spec.ts",
"test:functional": "node --loader ts-node/esm ./e2e/run-local.ts"
},
"exports": {
".": {
Expand Down Expand Up @@ -62,14 +64,21 @@
"@angular-devkit/architect": "~0.1702.0",
"@angular-devkit/core": "~17.2.0",
"@angular/core": "~17.2.0",
"@npmcli/promise-spawn": "^7.0.2",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/fs-extra": "*",
"@types/mocha": "*",
"@types/tmp": "*",
"express": "^4.18.2",
"mocha": "*",
"rollup": "^4.12.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"semver": "*",
"tmp": "*",
"tmp": "^0.2.3",
"ts-mocha": "*",
"ts-node": "*",
"ts-node": "^10.9.2",
"typescript": "*"
}
}
12 changes: 12 additions & 0 deletions packages/@apphosting/adapter-angular/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import resolve from "@rollup/plugin-node-resolve";
import json from "@rollup/plugin-json";
import commonjs from "rollup-plugin-commonjs";

export default {
input: "./dist/simple-server/server.js",
output: {
file: "./dist/simple-server/bundled_server.mjs",
jamesdaniels marked this conversation as resolved.
Show resolved Hide resolved
format: "es",
},
plugins: [resolve({ preferBuiltins: true }), json(), commonjs()],
};
Loading
Loading