feat(server,app,website): prefix service URLs with project slug
Service hostnames now follow DNS convention with a project label so
independently opened projects no longer collide on routes like
`web.localhost`. Format becomes `{script}.{project}.localhost` for
default branches and `{script}.{branch}.{project}.localhost` for other
branches. The project slug comes from the GitHub repository name when a
remote exists, otherwise from the workspace directory basename.
Server changes:
- `buildScriptHostname` takes `{ projectSlug, branchName, scriptName }`
and slugifies all labels at the boundary; `untitled` fallback only
applies at hostname-label sites, never to shared `slugify`.
- `deriveProjectSlug(cwd)` and `parseGitHubRepoNameFromRemote` derive
the slug; GitHub URL parsing now requires `host === "github.com"` to
avoid embedded-path false matches.
- `ScriptRouteEntry` carries `projectSlug`; branch-rename handler reuses
it instead of re-reading git in hot paths.
- Status projection derives slug once per call and uses route hostname
when available; only falls back to building when no route exists.
- Spawn cleanup removes routes by `{ workspaceId, scriptName }` so
cleanup after a branch rename doesn't leave a stale entry behind.
App and shared message tests updated to the new fixture shape; service
URLs continue to be opaque server-provided strings.
Landing-page service URL examples updated; allowlist docs unchanged.
This commit is contained in:
@@ -2,28 +2,70 @@ import { describe, expect, it } from "vitest";
|
||||
import { buildScriptHostname } from "./script-hostname.js";
|
||||
|
||||
describe("buildScriptHostname", () => {
|
||||
it("slugifies script names with spaces on default branches", () => {
|
||||
expect(buildScriptHostname(null, "npm run dev")).toBe("npm-run-dev.localhost");
|
||||
it("builds default branch hostnames with script and project labels", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "paseo",
|
||||
branchName: null,
|
||||
scriptName: "web",
|
||||
}),
|
||||
).toBe("web.paseo.localhost");
|
||||
});
|
||||
|
||||
it("slugifies script names with special characters", () => {
|
||||
expect(buildScriptHostname(null, "Web/API @ Dev")).toBe("web-api-dev.localhost");
|
||||
it("omits the branch label for main and master", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "paseo",
|
||||
branchName: "main",
|
||||
scriptName: "web",
|
||||
}),
|
||||
).toBe("web.paseo.localhost");
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "paseo",
|
||||
branchName: "master",
|
||||
scriptName: "web",
|
||||
}),
|
||||
).toBe("web.paseo.localhost");
|
||||
});
|
||||
|
||||
it("omits the branch prefix for main and master", () => {
|
||||
expect(buildScriptHostname("main", "npm run dev")).toBe("npm-run-dev.localhost");
|
||||
expect(buildScriptHostname("master", "npm run dev")).toBe("npm-run-dev.localhost");
|
||||
it("builds non-default branch hostnames with script, branch, and project labels", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "paseo",
|
||||
branchName: "feature-auth",
|
||||
scriptName: "web",
|
||||
}),
|
||||
).toBe("web.feature-auth.paseo.localhost");
|
||||
});
|
||||
|
||||
it("adds a slugified branch prefix for non-default branches", () => {
|
||||
expect(buildScriptHostname("feature/cool stuff", "api")).toBe(
|
||||
"feature-cool-stuff.api.localhost",
|
||||
);
|
||||
it("slugifies script, default branch project, and non-default branch labels", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "Paseo App",
|
||||
branchName: "Feature/Auth Flow",
|
||||
scriptName: "Web/API @ Dev",
|
||||
}),
|
||||
).toBe("web-api-dev.feature-auth-flow.paseo-app.localhost");
|
||||
});
|
||||
|
||||
it("slugifies both the branch name and script name together", () => {
|
||||
expect(buildScriptHostname("feat/add auth", "npm run dev")).toBe(
|
||||
"feat-add-auth.npm-run-dev.localhost",
|
||||
);
|
||||
it("accepts already slugified labels because slugify is idempotent", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "paseo-app",
|
||||
branchName: "feature-auth-flow",
|
||||
scriptName: "web-api-dev",
|
||||
}),
|
||||
).toBe("web-api-dev.feature-auth-flow.paseo-app.localhost");
|
||||
});
|
||||
|
||||
it("uses untitled as the hostname-label fallback when labels collapse to empty", () => {
|
||||
expect(
|
||||
buildScriptHostname({
|
||||
projectSlug: "日本語",
|
||||
branchName: "***",
|
||||
scriptName: "---",
|
||||
}),
|
||||
).toBe("untitled.untitled.untitled.localhost");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user