Skip to content
Closed
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
3 changes: 3 additions & 0 deletions web/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ test_suite(
"//web/package_test:pack_contents_test",
"//web/submitqueue:test",
"//web/submitqueue:typecheck_typecheck_test",
"//web/service/submitqueue:lint",
"//web/service/submitqueue:test",
"//web/service/submitqueue:typecheck_typecheck_test",
],
)

Expand Down
182 changes: 181 additions & 1 deletion web/service/submitqueue/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,183 @@
load("@aspect_rules_js//contrib/nextjs:defs.bzl", "nextjs_standalone_build", "nextjs_standalone_server")
load("@aspect_rules_js//js:defs.bzl", "js_image_layer")
load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@npm//web/service/submitqueue:eslint/package_json.bzl", eslint = "bin")
load("@npm//web/service/submitqueue:next/package_json.bzl", next = "bin")
load("@npm//web/service/submitqueue:vitest/package_json.bzl", vitest = "bin")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")

package(default_visibility = ["//visibility:public"])

exports_files(["package.json"])
npm_link_all_packages(name = "node_modules")

ts_config(
name = "tsconfig",
src = "tsconfig.json",
)

ts_project(
name = "typecheck",
srcs = glob([
"src/**/*.ts",
"src/**/*.tsx",
]) + [
"next-env.d.ts",
"next.config.ts",
"vitest.config.ts",
],
declaration = False,
incremental = True,
no_emit = True,
tsconfig = ":tsconfig",
deps = [
":node_modules/@bufbuild/protobuf",
":node_modules/@connectrpc/connect",
":node_modules/@connectrpc/connect-node",
":node_modules/@submitqueue/api",
":node_modules/@submitqueue/web-submitqueue",
":node_modules/@types/node",
":node_modules/@types/react",
":node_modules/@types/react-dom",
":node_modules/next",
":node_modules/react",
":node_modules/react-dom",
":node_modules/server-only",
":node_modules/vitest",
],
)

eslint.eslint_test(
name = "lint",
args = ["."],
chdir = package_name(),
data = [
":node_modules",
":node_modules/eslint",
":node_modules/eslint-config-next",
"//web:node_modules/next",
"eslint.config.mjs",
"next-env.d.ts",
"next.config.ts",
"package.json",
"tsconfig.json",
"vitest.config.ts",
] + glob([
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.css",
]),
)

vitest.vitest_test(
name = "test",
args = [
"run",
"--config",
"vitest.config.ts",
],
chdir = package_name(),
data = [
":node_modules",
":node_modules/vitest",
"//web:node_modules/@bufbuild/protobuf",
"//web:node_modules/@connectrpc/connect",
"//web:node_modules/react",
"//web:node_modules/react-dom",
"//web/api:pkg",
"//web/submitqueue:pkg",
"//web:node_modules/vitest",
"vitest.config.ts",
] + glob([
"src/**/*.ts",
"src/**/*.tsx",
]),
)

next.next_binary(
name = "next",
)

nextjs_standalone_build(
name = "standalone",
args = ["--webpack"],
config = "next.config.ts",
data = [
":node_modules",
":node_modules/@bufbuild/protobuf",
":node_modules/@connectrpc/connect",
":node_modules/@connectrpc/connect-node",
":node_modules/@submitqueue/api",
":node_modules/@submitqueue/web-submitqueue",
":node_modules/next",
":node_modules/react",
":node_modules/react-dom",
":node_modules/server-only",
"//web/api:lib",
"//web/api:lib_types",
"//web:node_modules/@bufbuild/protobuf",
"//web:node_modules/@connectrpc/connect",
"//web:node_modules/next",
"//web:node_modules/react",
"//web:node_modules/react-dom",
"//web/submitqueue:lib",
"//web/submitqueue:lib_types",
"package.json",
"tsconfig.json",
],
env = {"NEXT_TELEMETRY_DISABLED": "1"},
next_js_binary = ":next",
srcs = ["next-env.d.ts"] + glob([
"src/**",
]),
)

nextjs_standalone_server(
name = "server",
app = ":standalone",
data = [
":node_modules/@bufbuild/protobuf",
":node_modules/@connectrpc/connect",
":node_modules/@connectrpc/connect-node",
":node_modules/next",
":node_modules/react",
":node_modules/react-dom",
":node_modules/server-only",
],
pkg = "service/submitqueue",
)

platform(
name = "linux_amd64",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
)

js_image_layer(
name = "server_layers",
binary = ":server",
platform = ":linux_amd64",
root = "/app",
)

oci_image(
name = "image",
base = "@node_22_20_0_bookworm_slim_linux_amd64",
entrypoint = ["/app/web/service/submitqueue/server"],
env = {
"HOSTNAME": "0.0.0.0",
"NODE_ENV": "production",
"PORT": "3000",
},
tars = [":server_layers"],
user = "node",
workdir = "/app/web/service/submitqueue/server.runfiles/_main",
)

oci_load(
name = "image_load",
image = ":image",
repo_tags = ["submitqueue-web-service:latest"],
)
7 changes: 7 additions & 0 deletions web/service/submitqueue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SubmitQueue web reference host

The Next.js demo host owns HTTP Basic authentication, gateway transport, dynamic rendering, readiness, refresh integration, and deployment. Page routes are added in the following stack changes.

Set `SUBMITQUEUE_WEB_TOKEN` to a nonempty password for username `test`, configure `SUBMITQUEUE_GATEWAY_URL`, and explicitly enable `SUBMITQUEUE_GATEWAY_ALLOW_PLAINTEXT=true` only for local h2c. The local demo uses password `test`.

`/healthz` validates configuration and pings the gateway. Bazel targets `:standalone`, `:server`, `:image`, and `:image_load` build and package the host; there is no web Dockerfile.
7 changes: 7 additions & 0 deletions web/service/submitqueue/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";

export default defineConfig([
...nextVitals,
globalIgnores([".next/**", "node_modules/**"]),
]);
7 changes: 7 additions & 0 deletions web/service/submitqueue/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
16 changes: 16 additions & 0 deletions web/service/submitqueue/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextConfig } from "next";
import path from "node:path";

const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
output: "standalone",
outputFileTracingRoot: path.join(import.meta.dirname, "../.."),
outputFileTracingExcludes: {
"*": ["**/node_modules/**"],
},
transpilePackages: ["@submitqueue/api", "@submitqueue/web-submitqueue"],
};

export default nextConfig;
9 changes: 8 additions & 1 deletion web/service/submitqueue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {},
"scripts": {
"build": "next build",
"dev": "next dev --hostname 0.0.0.0 --port 3000",
"lint": "eslint .",
"start": "next start --hostname 0.0.0.0 --port 3000",
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@bufbuild/protobuf": "2.15.0",
"@connectrpc/connect": "^2.1.1",
Expand Down
2 changes: 2 additions & 0 deletions web/service/submitqueue/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Disallow:
31 changes: 31 additions & 0 deletions web/service/submitqueue/src/app/(protected)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ReactNode } from "react";
import Link from "next/link";

import { requireAuthorization } from "../../server/request-auth";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export default async function ProtectedLayout({
children,
}: Readonly<{ children: ReactNode }>) {
await requireAuthorization();

return (
<div className="site-frame">
<header className="site-header">
<Link className="brand" href="/" aria-label="SubmitQueue queues">
<span className="brand-mark" aria-hidden="true">
SQ
</span>
<span>SubmitQueue</span>
</Link>
<span className="environment-pill">Local demo · read-only</span>
</header>
{children}
<footer className="site-footer">
Read-only demo · updates automatically
</footer>
</div>
);
}
11 changes: 11 additions & 0 deletions web/service/submitqueue/src/app/(protected)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { connection } from "next/server";
import { requireAuthorization } from "../../server/request-auth";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export default async function HomePage() {
await connection();
await requireAuthorization();
return <main className="shell"><h1>SubmitQueue</h1><p>Read-only demo host</p></main>;
}
Loading