refactor(housekeeping): restore ase preview namespace

This commit is contained in:
Simo committed 2026-08-31 18:45:44 +02:00
1 parent 3da84ffd87
commit d868c990bf
21 files changed
+133 -112

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -9,7 +9,7 @@ export const contentManifest = {
labelKey: "pages.housekeeping.domains.content.title", labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description", descriptionKey: "pages.housekeeping.domains.content.description",
iconId: "file-text", iconId: "file-text",
previewHref: "/admin-next/content", previewHref: "/ase-next/content",
capability: anyCapability( capability: anyCapability(
PERMS.NEWS_VIEW, PERMS.NEWS_VIEW,
PERMS.PAGES_VIEW, PERMS.PAGES_VIEW,
@@ -9,7 +9,7 @@ export const economyManifest = {
labelKey: "pages.housekeeping.domains.economy.title", labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description", descriptionKey: "pages.housekeeping.domains.economy.description",
iconId: "gem", iconId: "gem",
previewHref: "/admin-next/economy", previewHref: "/ase-next/economy",
capability: anyCapability( capability: anyCapability(
PERMS.CATALOG_VIEW, PERMS.CATALOG_VIEW,
PERMS.SHOP_VIEW, PERMS.SHOP_VIEW,
@@ -9,7 +9,7 @@ export const hotelManifest = {
labelKey: "pages.housekeeping.domains.hotel.title", labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description", descriptionKey: "pages.housekeeping.domains.hotel.description",
iconId: "hotel", iconId: "hotel",
previewHref: "/admin-next/hotel", previewHref: "/ase-next/hotel",
capability: anyCapability( capability: anyCapability(
PERMS.ROOMS_VIEW, PERMS.ROOMS_VIEW,
PERMS.RADIO_VIEW, PERMS.RADIO_VIEW,
@@ -9,7 +9,7 @@ export const operationsManifest = {
labelKey: "pages.housekeeping.domains.operations.title", labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description", descriptionKey: "pages.housekeeping.domains.operations.description",
iconId: "inbox", iconId: "inbox",
previewHref: "/admin-next/operations", previewHref: "/ase-next/operations",
capability: anyCapability(PERMS.ADMIN_DASHBOARD), capability: anyCapability(PERMS.ADMIN_DASHBOARD),
routes: [], routes: [],
searchProviders: [], searchProviders: [],
@@ -9,7 +9,7 @@ export const peopleManifest = {
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description", descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users", iconId: "users",
previewHref: "/admin-next/people", previewHref: "/ase-next/people",
capability: anyCapability( capability: anyCapability(
PERMS.USERS_VIEW, PERMS.USERS_VIEW,
PERMS.MODERATION_VIEW, PERMS.MODERATION_VIEW,
@@ -9,7 +9,7 @@ export const systemManifest = {
labelKey: "pages.housekeeping.domains.system.title", labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description", descriptionKey: "pages.housekeeping.domains.system.description",
iconId: "settings", iconId: "settings",
previewHref: "/admin-next/system", previewHref: "/ase-next/system",
capability: anyCapability( capability: anyCapability(
PERMS.SETTINGS_VIEW, PERMS.SETTINGS_VIEW,
PERMS.LOGS_VIEW, PERMS.LOGS_VIEW,
@@ -28,7 +28,7 @@ const workItem = {
occurredAt: "2026-08-24T12:00:00.000Z", occurredAt: "2026-08-24T12:00:00.000Z",
titleKey: "pages.housekeeping.items.ticket", titleKey: "pages.housekeeping.items.ticket",
context: { ticketId: "42" }, context: { ticketId: "42" },
href: "/admin-next/people/tickets/42", href: "/ase-next/people/tickets/42",
freshness: "fresh", freshness: "fresh",
} satisfies HousekeepingWorkItem; } satisfies HousekeepingWorkItem;
@@ -36,7 +36,7 @@ const searchResult = {
id: "user-42", id: "user-42",
domain: "people", domain: "people",
title: "operator", title: "operator",
href: "/admin-next/people/users/42", href: "/ase-next/people/users/42",
} satisfies HousekeepingSearchResult; } satisfies HousekeepingSearchResult;
const searchProvider: HousekeepingSearchProvider = { const searchProvider: HousekeepingSearchProvider = {
@@ -87,7 +87,7 @@ const manifest: HousekeepingDomainManifest = {
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description", descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users", iconId: "users",
previewHref: "/admin-next/people", previewHref: "/ase-next/people",
capability, capability,
routes: [], routes: [],
searchProviders: [searchProvider], searchProviders: [searchProvider],
@@ -17,7 +17,7 @@ export interface HousekeepingDomainManifest {
labelKey: string; labelKey: string;
descriptionKey: string; descriptionKey: string;
iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings"; iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings";
previewHref: `/admin-next/${HousekeepingDomainId}`; previewHref: `/ase-next/${HousekeepingDomainId}`;
capability: CapabilityRequirement; capability: CapabilityRequirement;
routes: readonly HousekeepingRouteDefinition[]; routes: readonly HousekeepingRouteDefinition[];
searchProviders: readonly HousekeepingSearchProvider[]; searchProviders: readonly HousekeepingSearchProvider[];
@@ -555,7 +555,7 @@ describe("housekeeping foundation completion contracts", () => {
for (const path of [ for (const path of [
"src/app/admin/layout.tsx", "src/app/admin/layout.tsx",
"src/app/mod/layout.tsx", "src/app/mod/layout.tsx",
"src/app/admin-next/layout.tsx", "src/app/ase-next/layout.tsx",
]) { ]) {
expect(existsSync(path), path).toBe(true); expect(existsSync(path), path).toBe(true);
} }
@@ -23,7 +23,7 @@ describe("housekeeping navigation", () => {
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description", descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users", iconId: "users",
previewHref: "/admin-next/people", previewHref: "/ase-next/people",
capability: anyCapability(PERMS.MOD_CFH_VIEW), capability: anyCapability(PERMS.MOD_CFH_VIEW),
routes: [], routes: [],
searchProviders: [], searchProviders: [],
@@ -35,7 +35,7 @@ describe("housekeeping navigation", () => {
labelKey: "pages.housekeeping.domains.economy.title", labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description", descriptionKey: "pages.housekeeping.domains.economy.description",
iconId: "gem", iconId: "gem",
previewHref: "/admin-next/economy", previewHref: "/ase-next/economy",
capability: anyCapability(PERMS.CATALOG_VIEW), capability: anyCapability(PERMS.CATALOG_VIEW),
routes: [], routes: [],
searchProviders: [], searchProviders: [],
@@ -53,7 +53,7 @@ describe("housekeeping navigation", () => {
expect(navigation).toEqual([ expect(navigation).toEqual([
{ {
id: "people", id: "people",
href: "/admin-next/people", href: "/ase-next/people",
iconId: "users", iconId: "users",
label: "pages.housekeeping.domains.people.title", label: "pages.housekeeping.domains.people.title",
description: "pages.housekeeping.domains.people.description", description: "pages.housekeeping.domains.people.description",
@@ -69,19 +69,19 @@ describe("housekeeping navigation", () => {
labelKey: "people.title", labelKey: "people.title",
descriptionKey: "people.description", descriptionKey: "people.description",
iconId: "users", iconId: "users",
previewHref: "/admin-next/people", previewHref: "/ase-next/people",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
routes: [ routes: [
{ {
id: "users", id: "users",
labelKey: "people.users", labelKey: "people.users",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}, },
{ {
id: "bans", id: "bans",
labelKey: "people.bans", labelKey: "people.bans",
href: "/admin-next/people/bans", href: "/ase-next/people/bans",
capability: anyCapability(PERMS.BANS_VIEW), capability: anyCapability(PERMS.BANS_VIEW),
}, },
], ],
@@ -94,7 +94,7 @@ describe("housekeeping navigation", () => {
labelKey: "system.title", labelKey: "system.title",
descriptionKey: "system.description", descriptionKey: "system.description",
iconId: "settings", iconId: "settings",
previewHref: "/admin-next/system", previewHref: "/ase-next/system",
capability: anyCapability(PERMS.SETTINGS_VIEW), capability: anyCapability(PERMS.SETTINGS_VIEW),
routes: [], routes: [],
searchProviders: [], searchProviders: [],
@@ -113,14 +113,14 @@ describe("housekeeping navigation", () => {
expect(navigation).toEqual([ expect(navigation).toEqual([
{ {
id: "people", id: "people",
href: "/admin-next/people", href: "/ase-next/people",
iconId: "users", iconId: "users",
label: "translated:people.title", label: "translated:people.title",
description: "translated:people.description", description: "translated:people.description",
items: [ items: [
{ {
id: "users", id: "users",
href: "/admin-next/people/users", href: "/ase-next/people/users",
label: "translated:people.users", label: "translated:people.users",
}, },
], ],
@@ -75,12 +75,12 @@ describe("HousekeepingPageState", () => {
state="error" state="error"
title="Could not load" title="Could not load"
description="Try again later" description="Try again later"
retryAction={<a href="/admin-next/operations">Retry preview</a>} retryAction={<a href="/ase-next/operations">Retry preview</a>}
/>, />,
); );
expect(html).toContain('role="alert"'); expect(html).toContain('role="alert"');
expect(html).toContain('href="/admin-next/operations"'); expect(html).toContain('href="/ase-next/operations"');
expect(html).toContain(">Retry preview<"); expect(html).toContain(">Retry preview<");
}); });
}); });
@@ -92,7 +92,7 @@ describe("HousekeepingPageShell", () => {
title="People" title="People"
description="Review operator-facing people data" description="Review operator-facing people data"
context={<span>Preview context</span>} context={<span>Preview context</span>}
primaryAction={<a href="/admin-next/people/new">Create preview</a>} primaryAction={<a href="/ase-next/people/new">Create preview</a>}
> >
<p>Page body</p> <p>Page body</p>
</HousekeepingPageShell>, </HousekeepingPageShell>,
@@ -102,7 +102,7 @@ describe("HousekeepingPageShell", () => {
expect(html).toContain(">People<"); expect(html).toContain(">People<");
expect(html).toContain(">Review operator-facing people data<"); expect(html).toContain(">Review operator-facing people data<");
expect(html).toContain(">Preview context<"); expect(html).toContain(">Preview context<");
expect(html).toContain('href="/admin-next/people/new"'); expect(html).toContain('href="/ase-next/people/new"');
expect(html).toContain(">Create preview<"); expect(html).toContain(">Create preview<");
expect(html).toContain(">Page body</p>"); expect(html).toContain(">Page body</p>");
}); });
@@ -0,0 +1,21 @@
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
describe("Housekeeping preview namespace", () => {
it("uses /ase-next and removes the previous runtime namespace", () => {
const removedNamespace = ["/admin", "-next"].join("");
const files = execFileSync("git", ["ls-files", "src", ".env.example"], {
encoding: "utf8",
})
.trim()
.split(/\r?\n/)
.filter(Boolean);
const offenders = files.filter((file) =>
readFileSync(file, "utf8").includes(removedNamespace),
);
expect(offenders).toEqual([]);
});
});
@@ -78,16 +78,16 @@ vi.mock("@/app/actions", () => {
throw new Error("preview routes must not import actions"); throw new Error("preview routes must not import actions");
}); });
import AdminNextDomainLayout from "@/app/admin-next/[domain]/layout"; import AdminNextDomainLayout from "@/app/ase-next/[domain]/layout";
import AdminNextDomainPage from "@/app/admin-next/[domain]/page"; import AdminNextDomainPage from "@/app/ase-next/[domain]/page";
import AdminNextLayout from "@/app/admin-next/layout"; import AdminNextLayout from "@/app/ase-next/layout";
import AdminNextPage from "@/app/admin-next/page"; import AdminNextPage from "@/app/ase-next/page";
const routeFiles = [ const routeFiles = [
"src/app/admin-next/layout.tsx", "src/app/ase-next/layout.tsx",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
"src/app/admin-next/[domain]/layout.tsx", "src/app/ase-next/[domain]/layout.tsx",
"src/app/admin-next/[domain]/page.tsx", "src/app/ase-next/[domain]/page.tsx",
] as const; ] as const;
const forbiddenModuleRoots = [ const forbiddenModuleRoots = [
@@ -360,7 +360,7 @@ async function renderRoute(route: ReactNode | Promise<ReactNode>) {
return renderToStaticMarkup(await route); return renderToStaticMarkup(await route);
} }
describe("/admin-next preview gate", () => { describe("/ase-next preview gate", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
routeMocks.env.NODE_ENV = "test"; routeMocks.env.NODE_ENV = "test";
@@ -402,7 +402,7 @@ describe("/admin-next preview gate", () => {
); );
}); });
describe("/admin-next first visible domain", () => { describe("/ase-next first visible domain", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -413,9 +413,9 @@ describe("/admin-next first visible domain", () => {
); );
await expect(AdminNextPage()).rejects.toThrow( await expect(AdminNextPage()).rejects.toThrow(
"NEXT_REDIRECT:/admin-next/operations", "NEXT_REDIRECT:/ase-next/operations",
); );
expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/operations"); expect(routeMocks.redirect).toHaveBeenCalledWith("/ase-next/operations");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1, 1,
); );
@@ -428,9 +428,9 @@ describe("/admin-next first visible domain", () => {
); );
await expect(AdminNextPage()).rejects.toThrow( await expect(AdminNextPage()).rejects.toThrow(
"NEXT_REDIRECT:/admin-next/people", "NEXT_REDIRECT:/ase-next/people",
); );
expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/people"); expect(routeMocks.redirect).toHaveBeenCalledWith("/ase-next/people");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1, 1,
); );
@@ -450,7 +450,7 @@ describe("/admin-next first visible domain", () => {
}); });
}); });
describe("/admin-next/[domain] layout", () => { describe("/ase-next/[domain] layout", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -515,7 +515,7 @@ describe("/admin-next/[domain] layout", () => {
}); });
}); });
describe("/admin-next/[domain] page", () => { describe("/ase-next/[domain] page", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -561,247 +561,247 @@ describe("preview route import boundary", () => {
it.each([ it.each([
[ [
"relative database import with resolver extension", "relative database import with resolver extension",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../../lib/db.js";', 'import db from "../../lib/db.js";',
"src/lib/db", "src/lib/db",
], ],
[ [
"aliased database import with resolver extension", "aliased database import with resolver extension",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "@/lib/db.js";', 'import db from "@/lib/db.js";',
"src/lib/db", "src/lib/db",
], ],
[ [
"action root import with resolver extension", "action root import with resolver extension",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import actions from "../../actions.mjs";', 'import actions from "../../actions.mjs";',
"src/actions", "src/actions",
], ],
[ [
"legacy mod root import with resolver extension", "legacy mod root import with resolver extension",
"src/app/admin-next/layout.tsx", "src/app/ase-next/layout.tsx",
'import mod from "../mod.cjs";', 'import mod from "../mod.cjs";',
"src/app/mod", "src/app/mod",
], ],
[ [
"database import with query suffix", "database import with query suffix",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../../lib/db?server-only";', 'import db from "../../lib/db?server-only";',
"src/lib/db", "src/lib/db",
], ],
[ [
"action import with hash suffix", "action import with hash suffix",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import("../../actions/users#server")', 'import("../../actions/users#server")',
"src/actions/users", "src/actions/users",
], ],
[ [
"Windows-style relative database import", "Windows-style relative database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
String.raw`import db from "..\\..\\lib\\db";`, String.raw`import db from "..\\..\\lib\\db";`,
"src/lib/db", "src/lib/db",
], ],
[ [
"Windows-style aliased database import", "Windows-style aliased database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
String.raw`import db from "@\\lib\\db";`, String.raw`import db from "@\\lib\\db";`,
"src/lib/db", "src/lib/db",
], ],
[ [
"percent-encoded database import", "percent-encoded database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../../lib/%64%62";', 'import db from "../../lib/%64%62";',
"src/lib/db", "src/lib/db",
], ],
[ [
"optional CommonJS database require", "optional CommonJS database require",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'require?.("../../lib/db")', 'require?.("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"module database require", "module database require",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'module.require("../../lib/db")', 'module.require("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"require.resolve database access", "require.resolve database access",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'require.resolve("../../lib/db")', 'require.resolve("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"optional module database require", "optional module database require",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'module.require?.("../../lib/db")', 'module.require?.("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"optional require.resolve database access", "optional require.resolve database access",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'require.resolve?.("../../lib/db")', 'require.resolve?.("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"TypeScript import-equals database access", "TypeScript import-equals database access",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db = require("../../lib/db");', 'import db = require("../../lib/db");',
"src/lib/db", "src/lib/db",
], ],
[ [
"U+2028 line-continuation database import", "U+2028 line-continuation database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../\\' + "\u2028" + '../lib/db";', 'import db from "../\\' + "\u2028" + '../lib/db";',
"src/lib/db", "src/lib/db",
], ],
[ [
"U+2029 line-continuation database import", "U+2029 line-continuation database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../\\' + "\u2029" + '../lib/db";', 'import db from "../\\' + "\u2029" + '../lib/db";',
"src/lib/db", "src/lib/db",
], ],
[ [
"parenthesized dynamic action import", "parenthesized dynamic action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import(("../../actions/users"))', 'import(("../../actions/users"))',
"src/actions/users", "src/actions/users",
], ],
[ [
"regex-brace template-expression action import", "regex-brace template-expression action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
`const x = \`${interpolationOpen}/}/.test(value) ? import("../../actions/users") : null}\`;`, `const x = \`${interpolationOpen}/}/.test(value) ? import("../../actions/users") : null}\`;`,
"src/actions/users", "src/actions/users",
], ],
[ [
"CommonJS database require", "CommonJS database require",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'require("../../lib/db")', 'require("../../lib/db")',
"src/lib/db", "src/lib/db",
], ],
[ [
"template-literal dynamic action import", "template-literal dynamic action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
"import(`../../actions/users`)", "import(`../../actions/users`)",
"src/actions/users", "src/actions/users",
], ],
[ [
"TypeScript-asserted dynamic action import", "TypeScript-asserted dynamic action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import(("../../actions/users" as string))', 'import(("../../actions/users" as string))',
"src/actions/users", "src/actions/users",
], ],
[ [
"template-expression dynamic action import", "template-expression dynamic action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
`const x = \`${interpolationOpen}import("../../actions/users")}\`;`, `const x = \`${interpolationOpen}import("../../actions/users")}\`;`,
"src/actions/users", "src/actions/users",
], ],
[ [
"nested template-expression dynamic action import", "nested template-expression dynamic action import",
"src/app/admin-next/[domain]/page.tsx", "src/app/ase-next/[domain]/page.tsx",
`const x = \`${interpolationOpen}ready ? \`${interpolationOpen}import("../../../actions/nested")}\` : ""}\`;`, `const x = \`${interpolationOpen}ready ? \`${interpolationOpen}import("../../../actions/nested")}\` : ""}\`;`,
"src/actions/nested", "src/actions/nested",
], ],
[ [
"unicode escaped dynamic app-action import", "unicode escaped dynamic app-action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import("\\u002e\\u002e/actions/users")', 'import("\\u002e\\u002e/actions/users")',
"src/app/actions/users", "src/app/actions/users",
], ],
[ [
"code-point escaped dynamic app-action import", "code-point escaped dynamic app-action import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import("\\u{2e}\\u{2e}/actions/users")', 'import("\\u{2e}\\u{2e}/actions/users")',
"src/app/actions/users", "src/app/actions/users",
], ],
[ [
"hex escaped export-from auth import", "hex escaped export-from auth import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'export * from "\\x2e\\x2e/\\x2e\\x2e/lib/auth";', 'export * from "\\x2e\\x2e/\\x2e\\x2e/lib/auth";',
"src/lib/auth", "src/lib/auth",
], ],
[ [
"escaped-slash permissions import", "escaped-slash permissions import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import permissions from "..\\/..\\/lib\\/permissions";', 'import permissions from "..\\/..\\/lib\\/permissions";',
"src/lib/permissions", "src/lib/permissions",
], ],
[ [
"unknown escape database import", "unknown escape database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../../\\lib/db";', 'import db from "../../\\lib/db";',
"src/lib/db", "src/lib/db",
], ],
[ [
"line-continuation database import", "line-continuation database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import db from "../\\' + "\n" + '../lib/db";', 'import db from "../\\' + "\n" + '../lib/db";',
"src/lib/db", "src/lib/db",
], ],
[ [
"aliased database descendant import", "aliased database descendant import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import { query } from "@/lib/db/query";', 'import { query } from "@/lib/db/query";',
"src/lib/db/query", "src/lib/db/query",
], ],
[ [
"root relative database import", "root relative database import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import { db } from "../../lib/db";', 'import { db } from "../../lib/db";',
"src/lib/db", "src/lib/db",
], ],
[ [
"domain relative auth side-effect import", "domain relative auth side-effect import",
"src/app/admin-next/[domain]/layout.tsx", "src/app/ase-next/[domain]/layout.tsx",
'import "../../../lib/auth";', 'import "../../../lib/auth";',
"src/lib/auth", "src/lib/auth",
], ],
[ [
"domain relative permissions export", "domain relative permissions export",
"src/app/admin-next/[domain]/page.tsx", "src/app/ase-next/[domain]/page.tsx",
'export { getAdminContext } from "../../../lib/permissions";', 'export { getAdminContext } from "../../../lib/permissions";',
"src/lib/permissions", "src/lib/permissions",
], ],
[ [
"root relative action dynamic import", "root relative action dynamic import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import("../../actions/users")', 'import("../../actions/users")',
"src/actions/users", "src/actions/users",
], ],
[ [
"root relative app action export", "root relative app action export",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'export * from "../actions";', 'export * from "../actions";',
"src/app/actions", "src/app/actions",
], ],
[ [
"domain relative legacy admin import", "domain relative legacy admin import",
"src/app/admin-next/[domain]/layout.tsx", "src/app/ase-next/[domain]/layout.tsx",
'import page from "../../admin/users/page";', 'import page from "../../admin/users/page";',
"src/app/admin/users/page", "src/app/admin/users/page",
], ],
[ [
"root relative legacy mod dynamic import", "root relative legacy mod dynamic import",
"src/app/admin-next/layout.tsx", "src/app/ase-next/layout.tsx",
'import("../mod/users/page")', 'import("../mod/users/page")',
"src/app/mod/users/page", "src/app/mod/users/page",
], ],
[ [
"Prisma TypeScript import type", "Prisma TypeScript import type",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'type PrismaClient = import("@prisma/client").PrismaClient;', 'type PrismaClient = import("@prisma/client").PrismaClient;',
"@prisma/client", "@prisma/client",
], ],
[ [
"Drizzle package import", "Drizzle package import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import { sql } from "drizzle-orm";', 'import { sql } from "drizzle-orm";',
"drizzle-orm", "drizzle-orm",
], ],
[ [
"mysql2 package import", "mysql2 package import",
"src/app/admin-next/page.tsx", "src/app/ase-next/page.tsx",
'import type { Pool } from "mysql2";', 'import type { Pool } from "mysql2";',
"mysql2", "mysql2",
], ],
@@ -834,7 +834,7 @@ describe("preview route import boundary", () => {
], ],
] as const)("fails closed for non-literal %s", (_name, source, violation) => { ] as const)("fails closed for non-literal %s", (_name, source, violation) => {
expect( expect(
findRouteImportBoundaryViolations(source, "src/app/admin-next/page.tsx"), findRouteImportBoundaryViolations(source, "src/app/ase-next/page.tsx"),
).toContain(violation); ).toContain(violation);
}); });
@@ -843,7 +843,7 @@ describe("preview route import boundary", () => {
'import database from "@/lib/database.js?raw";', 'import database from "@/lib/database.js?raw";',
'import dbTools from "../../lib/db-tools.ts";', 'import dbTools from "../../lib/db-tools.ts";',
'import auth from "../../lib/authentication";', 'import auth from "../../lib/authentication";',
'import preview from "../admin-next-shared";', 'import preview from "../ase-next-shared";',
'import prismaTools from "@prisma/client-tools";', 'import prismaTools from "@prisma/client-tools";',
'import drizzleTools from "drizzle-orm-kit";', 'import drizzleTools from "drizzle-orm-kit";',
'import mysqlTools from "mysql2-wrapper";', 'import mysqlTools from "mysql2-wrapper";',
@@ -853,7 +853,7 @@ describe("preview route import boundary", () => {
].join("\n"); ].join("\n");
expect( expect(
findRouteImportBoundaryViolations(source, "src/app/admin-next/page.tsx"), findRouteImportBoundaryViolations(source, "src/app/ase-next/page.tsx"),
).toEqual([]); ).toEqual([]);
}); });
}); });
@@ -25,7 +25,7 @@ const manifest = (
labelKey: `pages.housekeeping.domains.${id}.title`, labelKey: `pages.housekeeping.domains.${id}.title`,
descriptionKey: `pages.housekeeping.domains.${id}.description`, descriptionKey: `pages.housekeeping.domains.${id}.description`,
iconId: "settings", iconId: "settings",
previewHref: `/admin-next/${id}`, previewHref: `/ase-next/${id}`,
capability: anyCapability(capabilitySlug), capability: anyCapability(capabilitySlug),
routes: [], routes: [],
searchProviders: [], searchProviders: [],
@@ -73,7 +73,7 @@ const expectedManifests = [
{ {
id: "operations", id: "operations",
iconId: "inbox", iconId: "inbox",
previewHref: "/admin-next/operations", previewHref: "/ase-next/operations",
labelKey: "pages.housekeeping.domains.operations.title", labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description", descriptionKey: "pages.housekeeping.domains.operations.description",
slugs: [PERMS.ADMIN_DASHBOARD], slugs: [PERMS.ADMIN_DASHBOARD],
@@ -81,7 +81,7 @@ const expectedManifests = [
{ {
id: "people", id: "people",
iconId: "users", iconId: "users",
previewHref: "/admin-next/people", previewHref: "/ase-next/people",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description", descriptionKey: "pages.housekeeping.domains.people.description",
slugs: [ slugs: [
@@ -112,7 +112,7 @@ const expectedManifests = [
{ {
id: "content", id: "content",
iconId: "file-text", iconId: "file-text",
previewHref: "/admin-next/content", previewHref: "/ase-next/content",
labelKey: "pages.housekeeping.domains.content.title", labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description", descriptionKey: "pages.housekeeping.domains.content.description",
slugs: [ slugs: [
@@ -135,7 +135,7 @@ const expectedManifests = [
{ {
id: "economy", id: "economy",
iconId: "gem", iconId: "gem",
previewHref: "/admin-next/economy", previewHref: "/ase-next/economy",
labelKey: "pages.housekeeping.domains.economy.title", labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description", descriptionKey: "pages.housekeeping.domains.economy.description",
slugs: [ slugs: [
@@ -148,7 +148,7 @@ const expectedManifests = [
{ {
id: "hotel", id: "hotel",
iconId: "hotel", iconId: "hotel",
previewHref: "/admin-next/hotel", previewHref: "/ase-next/hotel",
labelKey: "pages.housekeeping.domains.hotel.title", labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description", descriptionKey: "pages.housekeeping.domains.hotel.description",
slugs: [ slugs: [
@@ -165,7 +165,7 @@ const expectedManifests = [
{ {
id: "system", id: "system",
iconId: "settings", iconId: "settings",
previewHref: "/admin-next/system", previewHref: "/ase-next/system",
labelKey: "pages.housekeeping.domains.system.title", labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description", descriptionKey: "pages.housekeeping.domains.system.description",
slugs: [ slugs: [
@@ -208,7 +208,7 @@ describe("housekeeping registry", () => {
it("rejects invalid preview and empty translation keys", () => { it("rejects invalid preview and empty translation keys", () => {
expect(() => expect(() =>
createHousekeepingRegistry([ createHousekeepingRegistry([
{ ...manifest("people"), previewHref: "/admin-next/operations" }, { ...manifest("people"), previewHref: "/ase-next/operations" },
]), ]),
).toThrow("invalid preview href"); ).toThrow("invalid preview href");
expect(() => expect(() =>
@@ -232,13 +232,13 @@ describe("housekeeping registry", () => {
{ {
id: "users", id: "users",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}, },
{ {
id: "users", id: "users",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/staff", href: "/ase-next/people/staff",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}, },
], ],
@@ -253,13 +253,13 @@ describe("housekeeping registry", () => {
{ {
id: "users", id: "users",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}, },
{ {
id: "staff", id: "staff",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}, },
], ],
@@ -272,7 +272,7 @@ describe("housekeeping registry", () => {
const route = { const route = {
id: "users", id: "users",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}; };
@@ -481,7 +481,7 @@ describe("housekeeping registry", () => {
{ {
id: "users", id: "users",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users", href: "/ase-next/people/users",
capability: { mode: "any", slugs: [] }, capability: { mode: "any", slugs: [] },
}, },
], ],
@@ -494,7 +494,7 @@ describe("housekeeping registry", () => {
const route = { const route = {
id: "shared", id: "shared",
labelKey: "pages.housekeeping.domains.people.title", labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/shared", href: "/ase-next/shared",
capability: anyCapability(PERMS.USERS_VIEW), capability: anyCapability(PERMS.USERS_VIEW),
}; };
@@ -503,7 +503,7 @@ describe("housekeeping registry", () => {
{ ...manifest("people"), routes: [route] }, { ...manifest("people"), routes: [route] },
{ {
...manifest("content"), ...manifest("content"),
routes: [{ ...route, href: "/admin-next/content/shared" }], routes: [{ ...route, href: "/ase-next/content/shared" }],
}, },
]), ]),
).toThrow("duplicate route id"); ).toThrow("duplicate route id");
@@ -34,7 +34,7 @@ export function createHousekeepingRegistry(
} }
domainIds.add(manifest.id); domainIds.add(manifest.id);
if (manifest.previewHref !== `/admin-next/${manifest.id}`) { if (manifest.previewHref !== `/ase-next/${manifest.id}`) {
throw new Error(`invalid preview href: ${manifest.previewHref}`); throw new Error(`invalid preview href: ${manifest.previewHref}`);
} }
validateNonEmpty(manifest.labelKey, "label key"); validateNonEmpty(manifest.labelKey, "label key");
@@ -17,17 +17,17 @@ const labels = {
const domains: readonly HousekeepingNavigationDomain[] = [ const domains: readonly HousekeepingNavigationDomain[] = [
{ {
id: "operations", id: "operations",
href: "/admin-next/operations", href: "/ase-next/operations",
iconId: "inbox", iconId: "inbox",
label: "Operations", label: "Operations",
description: "Manage operations", description: "Manage operations",
items: [ items: [
{ id: "queue", href: "/admin-next/operations/queue", label: "Queue" }, { id: "queue", href: "/ase-next/operations/queue", label: "Queue" },
], ],
}, },
{ {
id: "people", id: "people",
href: "/admin-next/people", href: "/ase-next/people",
iconId: "users", iconId: "users",
label: "People", label: "People",
description: "Manage people", description: "Manage people",
@@ -35,7 +35,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
}, },
{ {
id: "content", id: "content",
href: "/admin-next/content", href: "/ase-next/content",
iconId: "file-text", iconId: "file-text",
label: "Content", label: "Content",
description: "Manage content", description: "Manage content",
@@ -43,7 +43,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
}, },
{ {
id: "economy", id: "economy",
href: "/admin-next/economy", href: "/ase-next/economy",
iconId: "gem", iconId: "gem",
label: "Economy", label: "Economy",
description: "Manage economy", description: "Manage economy",
@@ -51,7 +51,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
}, },
{ {
id: "hotel", id: "hotel",
href: "/admin-next/hotel", href: "/ase-next/hotel",
iconId: "hotel", iconId: "hotel",
label: "Hotel", label: "Hotel",
description: "Manage hotel", description: "Manage hotel",
@@ -59,7 +59,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
}, },
{ {
id: "system", id: "system",
href: "/admin-next/system", href: "/ase-next/system",
iconId: "settings", iconId: "settings",
label: "System", label: "System",
description: "Manage system", description: "Manage system",
@@ -178,7 +178,7 @@ describe("HousekeepingShell", () => {
expect(html).toContain('<nav aria-label="Operations navigation"'); expect(html).toContain('<nav aria-label="Operations navigation"');
expect(html).toContain('<main id="housekeeping-content"'); expect(html).toContain('<main id="housekeeping-content"');
expect(html).toContain(">Deck body</p>"); expect(html).toContain(">Deck body</p>");
expect(html).toContain('href="/admin-next/operations/queue"'); expect(html).toContain('href="/ase-next/operations/queue"');
expect(html).toContain(">Queue<"); expect(html).toContain(">Queue<");
expect(html).toContain(">Nora<"); expect(html).toContain(">Nora<");
expect(html).toContain(">6<"); expect(html).toContain(">6<");
@@ -196,7 +196,7 @@ describe("HousekeepingShell", () => {
const activeAnchors = allAnchors.filter((anchor) => const activeAnchors = allAnchors.filter((anchor) =>
anchor.includes('aria-current="page"'), anchor.includes('aria-current="page"'),
); );
const peopleAnchor = anchorForHref(html, "/admin-next/people"); const peopleAnchor = anchorForHref(html, "/ase-next/people");
for (const iconClass of [ for (const iconClass of [
"lucide-inbox", "lucide-inbox",
"lucide-users", "lucide-users",
@@ -222,7 +222,7 @@ describe("HousekeepingShell", () => {
); );
expect(() => renderShell("system", domainsWithoutSystem)).not.toThrow(); expect(() => renderShell("system", domainsWithoutSystem)).not.toThrow();
const html = renderShell("system", domainsWithoutSystem); const html = renderShell("system", domainsWithoutSystem);
expect(html).not.toContain('href="/admin-next/operations/queue"'); expect(html).not.toContain('href="/ase-next/operations/queue"');
expect(html).not.toContain(">Queue<"); expect(html).not.toContain(">Queue<");
}); });
+1 -1
View File
@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
const ROOTS = [ const ROOTS = [
"src/app/admin", "src/app/admin",
"src/components/admin", "src/components/admin",
"src/app/admin-next", "src/app/ase-next",
"src/features/housekeeping", "src/features/housekeeping",
]; ];
const GRAPHICAL_ALLOWLIST = [ const GRAPHICAL_ALLOWLIST = [