minutework 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/assets/claude-local/skills/README.md +2 -0
  2. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +14 -1
  3. package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
  4. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +10 -3
  5. package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +9 -6
  6. package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +5 -4
  7. package/assets/templates/next-tenant-app/README.md +26 -138
  8. package/assets/templates/next-tenant-app/package.json +1 -0
  9. package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
  10. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
  11. package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
  12. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
  13. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
  14. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
  15. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
  16. package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
  17. package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
  18. package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
  19. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
  20. package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
  21. package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
  22. package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
  23. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
  24. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
  25. package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
  26. package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
  27. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
  28. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
  29. package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
  30. package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
  31. package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
  32. package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
  33. package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
  34. package/assets/templates/next-tenant-app/template.json +3 -3
  35. package/assets/templates/next-tenant-app/template.schema.json +1 -0
  36. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
  37. package/package.json +2 -2
  38. package/vendor/workspace-mcp/types.d.ts +4 -0
  39. package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
  40. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
  41. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
  42. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
  43. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
  44. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
  45. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
  46. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
  47. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
  48. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
  49. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
  50. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
  51. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
  52. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
  53. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
  54. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
  55. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
  56. package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
  57. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
  58. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
  59. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
  60. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
  61. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
  62. package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
  63. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
  64. package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
  65. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
  66. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
  67. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
  68. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
  69. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
  70. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
  71. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
  72. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
  73. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
  74. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
  75. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
  76. package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
  77. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
  78. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
  79. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
  80. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
  81. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
  82. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
  83. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
  84. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
  85. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
  86. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
  87. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
  88. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
  89. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
  90. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
  91. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
@@ -1,42 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const fetchPasswordStatus = vi.fn();
4
- const readPlatformAuthState = vi.fn();
5
- const syncPlatformAuthStateToResponse = vi.fn();
6
-
7
- vi.mock("@/lib/platform/client.server", () => ({
8
- fetchPasswordStatus,
9
- }));
10
-
11
- vi.mock("@/lib/platform/session.server", () => ({
12
- readPlatformAuthState,
13
- syncPlatformAuthStateToResponse,
14
- }));
15
-
16
- describe("password status route", () => {
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
- });
20
-
21
- it("returns the proxied password status for authenticated sessions", async () => {
22
- const authState = { sessionId: "session-1", csrfToken: "csrf-1" };
23
-
24
- readPlatformAuthState.mockResolvedValue(authState);
25
- fetchPasswordStatus.mockResolvedValue({
26
- data: { has_password: true },
27
- authState,
28
- });
29
-
30
- const route = await import("./route");
31
- const response = await route.GET();
32
-
33
- expect(fetchPasswordStatus).toHaveBeenCalledWith(authState);
34
- expect(syncPlatformAuthStateToResponse).toHaveBeenCalledWith(
35
- response,
36
- authState,
37
- authState,
38
- );
39
- expect(response.status).toBe(200);
40
- await expect(response.json()).resolves.toEqual({ has_password: true });
41
- });
42
- });
@@ -1,29 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- import { fetchPasswordStatus } from "@/lib/platform/client.server";
4
- import { toPlatformErrorResponse } from "@/lib/platform/route-response";
5
- import {
6
- readPlatformAuthState,
7
- syncPlatformAuthStateToResponse,
8
- } from "@/lib/platform/session.server";
9
-
10
- export async function GET() {
11
- const authState = await readPlatformAuthState();
12
-
13
- if (!authState.sessionId) {
14
- return NextResponse.json({ detail: "Authentication required." }, { status: 401 });
15
- }
16
-
17
- try {
18
- const result = await fetchPasswordStatus(authState);
19
- const response = NextResponse.json(result.data, { status: 200 });
20
- syncPlatformAuthStateToResponse(response, authState, result.authState);
21
- return response;
22
- } catch (error) {
23
- return toPlatformErrorResponse(
24
- error,
25
- "Unable to load password status.",
26
- authState,
27
- );
28
- }
29
- }
@@ -1,26 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- import {
4
- loadCurrentSession,
5
- } from "@/lib/platform/client.server";
6
- import { toPlatformErrorResponse } from "@/lib/platform/route-response";
7
- import {
8
- readPlatformAuthState,
9
- syncPlatformAuthStateToResponse,
10
- } from "@/lib/platform/session.server";
11
-
12
- export async function GET() {
13
- const authState = await readPlatformAuthState();
14
- try {
15
- const result = await loadCurrentSession(authState);
16
- const response = NextResponse.json(result.data, { status: 200 });
17
- syncPlatformAuthStateToResponse(response, authState, result.authState);
18
- return response;
19
- } catch (error) {
20
- return toPlatformErrorResponse(
21
- error,
22
- "Unable to load the current platform session.",
23
- authState,
24
- );
25
- }
26
- }
@@ -1,40 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const fetchTenantCommandRun = vi.fn();
4
- const readPlatformAuthState = vi.fn();
5
- const syncPlatformAuthStateToResponse = vi.fn();
6
-
7
- vi.mock("@/lib/platform/client.server", () => ({
8
- fetchTenantCommandRun,
9
- }));
10
-
11
- vi.mock("@/lib/platform/env.server", () => ({
12
- env: {
13
- MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: false,
14
- },
15
- }));
16
-
17
- vi.mock("@/lib/platform/session.server", () => ({
18
- readPlatformAuthState,
19
- syncPlatformAuthStateToResponse,
20
- }));
21
-
22
- describe("runtime command run route", () => {
23
- beforeEach(() => {
24
- vi.clearAllMocks();
25
- vi.resetModules();
26
- });
27
-
28
- it("returns 404 when the example feature is disabled", async () => {
29
- const route = await import("./route");
30
- const response = await route.GET(
31
- new Request("http://localhost/api/gateway/commands/run-1?tenant_id=tenant-1"),
32
- { params: Promise.resolve({ runId: "run-1" }) },
33
- );
34
-
35
- expect(response.status).toBe(404);
36
- expect(readPlatformAuthState).not.toHaveBeenCalled();
37
- expect(fetchTenantCommandRun).not.toHaveBeenCalled();
38
- expect(syncPlatformAuthStateToResponse).not.toHaveBeenCalled();
39
- });
40
- });
@@ -1,47 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- import { fetchTenantCommandRun } from "@/lib/platform/client.server";
4
- import { env } from "@/lib/platform/env.server";
5
- import { toPlatformErrorResponse } from "@/lib/platform/route-response";
6
- import {
7
- readPlatformAuthState,
8
- syncPlatformAuthStateToResponse,
9
- } from "@/lib/platform/session.server";
10
-
11
- export async function GET(
12
- request: Request,
13
- { params }: { params: Promise<{ runId: string }> },
14
- ) {
15
- if (!env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE) {
16
- return new NextResponse(null, { status: 404 });
17
- }
18
-
19
- const authState = await readPlatformAuthState();
20
- if (!authState.sessionId) {
21
- return NextResponse.json({ detail: "Authentication required." }, { status: 401 });
22
- }
23
-
24
- const { runId } = await params;
25
- const url = new URL(request.url);
26
- const tenantId = url.searchParams.get("tenant_id") || "";
27
-
28
- if (!tenantId) {
29
- return NextResponse.json(
30
- { detail: "Missing tenant_id query parameter." },
31
- { status: 400 },
32
- );
33
- }
34
-
35
- try {
36
- const result = await fetchTenantCommandRun(authState, runId, tenantId);
37
- const response = NextResponse.json(result.data, { status: 200 });
38
- syncPlatformAuthStateToResponse(response, authState, result.authState);
39
- return response;
40
- } catch (error) {
41
- return toPlatformErrorResponse(
42
- error,
43
- "Unable to load command status.",
44
- authState,
45
- );
46
- }
47
- }
@@ -1,43 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const dispatchTenantCommand = vi.fn();
4
- const readPlatformAuthState = vi.fn();
5
- const syncPlatformAuthStateToResponse = vi.fn();
6
-
7
- vi.mock("@/lib/platform/client.server", () => ({
8
- dispatchTenantCommand,
9
- }));
10
-
11
- vi.mock("@/lib/platform/env.server", () => ({
12
- env: {
13
- MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: false,
14
- },
15
- }));
16
-
17
- vi.mock("@/lib/platform/session.server", () => ({
18
- readPlatformAuthState,
19
- syncPlatformAuthStateToResponse,
20
- }));
21
-
22
- describe("runtime command route", () => {
23
- beforeEach(() => {
24
- vi.clearAllMocks();
25
- vi.resetModules();
26
- });
27
-
28
- it("returns 404 when the example feature is disabled", async () => {
29
- const route = await import("./route");
30
- const response = await route.POST(
31
- new Request("http://localhost/api/gateway/commands", {
32
- method: "POST",
33
- headers: { "content-type": "application/json" },
34
- body: JSON.stringify({ command: "deploy" }),
35
- }),
36
- );
37
-
38
- expect(response.status).toBe(404);
39
- expect(readPlatformAuthState).not.toHaveBeenCalled();
40
- expect(dispatchTenantCommand).not.toHaveBeenCalled();
41
- expect(syncPlatformAuthStateToResponse).not.toHaveBeenCalled();
42
- });
43
- });
@@ -1,45 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- import { dispatchTenantCommand } from "@/lib/platform/client.server";
4
- import { commandDispatchRequestSchema } from "@/lib/platform/contracts";
5
- import { env } from "@/lib/platform/env.server";
6
- import { toPlatformErrorResponse } from "@/lib/platform/route-response";
7
- import {
8
- readPlatformAuthState,
9
- syncPlatformAuthStateToResponse,
10
- } from "@/lib/platform/session.server";
11
-
12
- export async function POST(request: Request) {
13
- if (!env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE) {
14
- return new NextResponse(null, { status: 404 });
15
- }
16
-
17
- const authState = await readPlatformAuthState();
18
-
19
- if (!authState.sessionId) {
20
- return NextResponse.json({ detail: "Authentication required." }, { status: 401 });
21
- }
22
-
23
- const body = await request.json().catch(() => null);
24
- const parsed = commandDispatchRequestSchema.safeParse(body);
25
-
26
- if (!parsed.success) {
27
- return NextResponse.json(
28
- { detail: "Invalid command payload." },
29
- { status: 400 },
30
- );
31
- }
32
-
33
- try {
34
- const result = await dispatchTenantCommand(authState, parsed.data);
35
- const response = NextResponse.json(result.data, { status: 200 });
36
- syncPlatformAuthStateToResponse(response, authState, result.authState);
37
- return response;
38
- } catch (error) {
39
- return toPlatformErrorResponse(
40
- error,
41
- "Unable to dispatch command.",
42
- authState,
43
- );
44
- }
45
- }
@@ -1,83 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const notFound = vi.fn(() => {
4
- throw new Error("notFound");
5
- });
6
- const resolveAuthenticatedSession = vi.fn();
7
-
8
- vi.mock("next/navigation", () => ({
9
- notFound,
10
- }));
11
-
12
- vi.mock("@/features/shell/components/private-app-shell", () => ({
13
- PrivateAppShell: () => null,
14
- }));
15
-
16
- vi.mock("@/lib/platform/auth.server", () => ({
17
- resolveAuthenticatedSession,
18
- }));
19
-
20
- vi.mock("@/lib/platform/endpoints.server", () => ({
21
- platformAuthEndpoints: {
22
- operatorConsole: "http://127.0.0.1:8000/ops/login/",
23
- },
24
- }));
25
-
26
- vi.mock("@/lib/platform/env.server", () => ({
27
- env: {
28
- MW_TEMPLATE_APP_NAME: "MinuteWork Combined Starter",
29
- MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: false,
30
- },
31
- }));
32
-
33
- describe("runtime commands example page", () => {
34
- beforeEach(() => {
35
- vi.clearAllMocks();
36
- vi.resetModules();
37
- });
38
-
39
- it("returns notFound before loading auth when the example is disabled", async () => {
40
- const page = await import("./page");
41
-
42
- await expect(page.default()).rejects.toThrow("notFound");
43
- expect(notFound).toHaveBeenCalledTimes(1);
44
- expect(resolveAuthenticatedSession).not.toHaveBeenCalled();
45
- });
46
-
47
- it("loads the private shell when the example is enabled", async () => {
48
- vi.doMock("@/lib/platform/env.server", () => ({
49
- env: {
50
- MW_TEMPLATE_APP_NAME: "MinuteWork Combined Starter",
51
- MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: true,
52
- },
53
- }));
54
- resolveAuthenticatedSession.mockResolvedValue({
55
- authenticated: true,
56
- user: {
57
- id: "user-1",
58
- username: "demo-user",
59
- email: "demo@example.com",
60
- },
61
- active_tenant_id: "tenant-1",
62
- active_tenant: {
63
- tenant_id: "tenant-1",
64
- tenant_slug: "alpha",
65
- tenant_name: "Alpha",
66
- role: "admin",
67
- },
68
- memberships: [
69
- {
70
- tenant_id: "tenant-1",
71
- tenant_slug: "alpha",
72
- tenant_name: "Alpha",
73
- role: "admin",
74
- },
75
- ],
76
- });
77
-
78
- const page = await import("./page");
79
-
80
- await expect(page.default()).resolves.toBeDefined();
81
- expect(resolveAuthenticatedSession).toHaveBeenCalledTimes(1);
82
- });
83
- });
@@ -1,30 +0,0 @@
1
- import { notFound } from "next/navigation";
2
-
3
- import { PrivateAppShell } from "@/features/shell/components/private-app-shell";
4
- import { resolveAuthenticatedSession } from "@/lib/platform/auth.server";
5
- import { platformAuthEndpoints } from "@/lib/platform/endpoints.server";
6
- import { env } from "@/lib/platform/env.server";
7
-
8
- export const metadata = {
9
- title: "Runtime Commands Example",
10
- description:
11
- "Optional runtime command example inside the authenticated /app surface.",
12
- };
13
-
14
- export default async function RuntimeCommandsExamplePage() {
15
- if (!env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE) {
16
- notFound();
17
- }
18
-
19
- const session = await resolveAuthenticatedSession();
20
-
21
- return (
22
- <PrivateAppShell
23
- initialSession={session}
24
- appName={env.MW_TEMPLATE_APP_NAME}
25
- operatorConsoleHref={platformAuthEndpoints.operatorConsole}
26
- runtimeCommandExampleEnabled={env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE}
27
- view="runtime-command-demo"
28
- />
29
- );
30
- }
@@ -1,62 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const resolveAuthenticatedSession = vi.fn();
4
-
5
- vi.mock("@/features/shell/components/private-app-shell", () => ({
6
- PrivateAppShell: () => null,
7
- }));
8
-
9
- vi.mock("@/lib/platform/auth.server", () => ({
10
- resolveAuthenticatedSession,
11
- }));
12
-
13
- vi.mock("@/lib/platform/endpoints.server", () => ({
14
- platformAuthEndpoints: {
15
- operatorConsole: "http://127.0.0.1:8000/ops/login/",
16
- },
17
- }));
18
-
19
- vi.mock("@/lib/platform/env.server", () => ({
20
- env: {
21
- MW_TEMPLATE_APP_NAME: "MinuteWork Combined Starter",
22
- MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: false,
23
- },
24
- }));
25
-
26
- describe("authenticated app home page", () => {
27
- beforeEach(() => {
28
- vi.clearAllMocks();
29
- vi.resetModules();
30
- });
31
-
32
- it("renders the private shell after the auth guard resolves", async () => {
33
- resolveAuthenticatedSession.mockResolvedValue({
34
- authenticated: true,
35
- user: {
36
- id: "user-1",
37
- username: "demo-user",
38
- email: "demo@example.com",
39
- },
40
- active_tenant_id: "tenant-1",
41
- active_tenant: {
42
- tenant_id: "tenant-1",
43
- tenant_slug: "alpha",
44
- tenant_name: "Alpha",
45
- role: "admin",
46
- },
47
- memberships: [
48
- {
49
- tenant_id: "tenant-1",
50
- tenant_slug: "alpha",
51
- tenant_name: "Alpha",
52
- role: "admin",
53
- },
54
- ],
55
- });
56
-
57
- const page = await import("./page");
58
-
59
- await expect(page.default()).resolves.toBeDefined();
60
- expect(resolveAuthenticatedSession).toHaveBeenCalledTimes(1);
61
- });
62
- });
@@ -1,88 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const originalEnv = { ...process.env };
4
- const resolveAuthenticatedSession = vi.fn();
5
-
6
- function restoreProcessEnv() {
7
- for (const key of Object.keys(process.env)) {
8
- if (!(key in originalEnv)) {
9
- delete process.env[key];
10
- }
11
- }
12
-
13
- Object.assign(process.env, originalEnv);
14
- }
15
-
16
- function applyPrivateOnlyServerEnv() {
17
- restoreProcessEnv();
18
- process.env.MW_PLATFORM_BASE_URL = "http://127.0.0.1:8000";
19
- process.env.MW_PUBLIC_CONTENT_SOURCE = "none";
20
- process.env.MW_TEMPLATE_APP_NAME = "Private Only Workspace";
21
- process.env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE = "false";
22
- delete process.env.MW_CONTENT_API_TOKEN;
23
- delete process.env.MW_PUBLIC_BASE_URL;
24
- delete process.env.MW_PUBLIC_SITE_PROPERTY_KEY;
25
- }
26
-
27
- vi.mock("@/features/shell/components/private-app-shell", () => ({
28
- PrivateAppShell: () => null,
29
- }));
30
-
31
- vi.mock("@/lib/platform/auth.server", () => ({
32
- resolveAuthenticatedSession,
33
- }));
34
-
35
- vi.mock("@/lib/platform/endpoints.server", () => ({
36
- platformAuthEndpoints: {
37
- operatorConsole: "http://127.0.0.1:8000/ops/login/",
38
- },
39
- }));
40
-
41
- describe("private app routes with disabled public content", () => {
42
- beforeEach(() => {
43
- vi.clearAllMocks();
44
- vi.resetModules();
45
- applyPrivateOnlyServerEnv();
46
- resolveAuthenticatedSession.mockResolvedValue({
47
- authenticated: true,
48
- user: {
49
- id: "user-1",
50
- username: "demo-user",
51
- email: "demo@example.com",
52
- },
53
- active_tenant_id: "tenant-1",
54
- active_tenant: {
55
- tenant_id: "tenant-1",
56
- tenant_slug: "alpha",
57
- tenant_name: "Alpha",
58
- role: "admin",
59
- },
60
- memberships: [
61
- {
62
- tenant_id: "tenant-1",
63
- tenant_slug: "alpha",
64
- tenant_name: "Alpha",
65
- role: "admin",
66
- },
67
- ],
68
- });
69
- });
70
-
71
- afterEach(() => {
72
- restoreProcessEnv();
73
- vi.resetModules();
74
- });
75
-
76
- it("imports /app entrypoints without CMS or public base environment", async () => {
77
- const [layout, page] = await Promise.all([
78
- import("./layout"),
79
- import("./page"),
80
- ]);
81
-
82
- await expect(layout.default({ children: "workspace" })).resolves.toBe(
83
- "workspace",
84
- );
85
- await expect(page.default()).resolves.toBeDefined();
86
- expect(resolveAuthenticatedSession).toHaveBeenCalledTimes(2);
87
- });
88
- });
@@ -1,70 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const notFound = vi.fn(() => {
4
- throw new Error("notFound");
5
- });
6
- const getEntry = vi.fn();
7
- const getSiteConfig = vi.fn();
8
- const listEntries = vi.fn();
9
-
10
- vi.mock("next/navigation", () => ({
11
- notFound,
12
- }));
13
-
14
- vi.mock("@/features/public-shell/components/content-article", () => ({
15
- ContentArticle: () => null,
16
- }));
17
-
18
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
19
- PublicSiteShell: ({ children }: { children: unknown }) => children,
20
- }));
21
-
22
- vi.mock("@/lib/content/adapter.server", () => ({
23
- getEntry,
24
- getSiteConfig,
25
- listEntries,
26
- }));
27
-
28
- describe("blog article page", () => {
29
- beforeEach(() => {
30
- vi.clearAllMocks();
31
- vi.resetModules();
32
- });
33
-
34
- it("returns notFound for unknown blog slugs", async () => {
35
- getSiteConfig.mockResolvedValue({
36
- collections: {
37
- blog: {
38
- title: "Starter Blog",
39
- description: "Blog",
40
- },
41
- },
42
- siteName: "MinuteWork Combined Starter",
43
- });
44
- getEntry.mockResolvedValue(null);
45
-
46
- const page = await import("./page");
47
-
48
- await expect(
49
- page.default({
50
- params: Promise.resolve({ slug: "missing-entry" }),
51
- }),
52
- ).rejects.toThrow("notFound");
53
- expect(notFound).toHaveBeenCalledTimes(1);
54
- });
55
-
56
- it("derives static params from published blog entries", async () => {
57
- listEntries.mockResolvedValue([
58
- { slug: ["public-site-api-default"] },
59
- { slug: ["launch-note"] },
60
- ]);
61
-
62
- const page = await import("./page");
63
-
64
- await expect(page.generateStaticParams()).resolves.toEqual([
65
- { slug: "public-site-api-default" },
66
- { slug: "launch-note" },
67
- ]);
68
- expect(page.dynamicParams).toBe(false);
69
- });
70
- });
@@ -1,46 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const getSiteConfig = vi.fn();
4
- const listEntries = vi.fn();
5
-
6
- vi.mock("@/features/public-shell/components/content-collection", () => ({
7
- ContentCollection: () => null,
8
- }));
9
-
10
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
11
- PublicSiteShell: ({ children }: { children: unknown }) => children,
12
- }));
13
-
14
- vi.mock("@/lib/content/adapter.server", () => ({
15
- getSiteConfig,
16
- listEntries,
17
- }));
18
-
19
- vi.mock("next/navigation", () => ({
20
- notFound: vi.fn(),
21
- }));
22
-
23
- describe("blog index page", () => {
24
- beforeEach(() => {
25
- vi.clearAllMocks();
26
- vi.resetModules();
27
- });
28
-
29
- it("renders the public blog collection", async () => {
30
- getSiteConfig.mockResolvedValue({
31
- collections: {
32
- blog: {
33
- eyebrow: "Blog",
34
- title: "Starter Blog",
35
- description: "Combined starter notes.",
36
- },
37
- },
38
- });
39
- listEntries.mockResolvedValue([]);
40
-
41
- const page = await import("./page");
42
-
43
- await expect(page.default()).resolves.toBeDefined();
44
- expect(listEntries).toHaveBeenCalledWith("blog");
45
- });
46
- });