minutework 0.1.31 → 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 (168) hide show
  1. package/EXTERNAL_ALPHA.md +33 -33
  2. package/README.md +34 -34
  3. package/assets/claude-local/CLAUDE.md.template +12 -12
  4. package/assets/claude-local/skills/README.md +3 -1
  5. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +17 -4
  6. package/assets/claude-local/skills/capability-gap-reporting/SKILL.md +3 -3
  7. package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
  8. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +12 -5
  9. package/assets/claude-local/skills/layering-and-import-modes/SKILL.md +2 -2
  10. package/assets/claude-local/skills/openclaw-skill-importer/SKILL.md +2 -2
  11. package/assets/claude-local/skills/project-overview-and-strategy/SKILL.md +8 -8
  12. package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +11 -8
  13. package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +6 -5
  14. package/assets/claude-local/skills/vuilder-discovery-output-contract/SKILL.md +6 -6
  15. package/assets/claude-local/skills/workspace-guidance-refresh/SKILL.md +4 -4
  16. package/assets/templates/fastapi-sidecar/pyproject.toml +1 -1
  17. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +1 -1
  18. package/assets/templates/mobile-app/.env.example +4 -4
  19. package/assets/templates/mobile-app/AGENTS.md +3 -3
  20. package/assets/templates/mobile-app/README.md +10 -10
  21. package/assets/templates/mobile-app/app/(app)/_layout.tsx +2 -2
  22. package/assets/templates/mobile-app/app/(app)/index.tsx +2 -2
  23. package/assets/templates/mobile-app/app/(auth)/login.tsx +3 -3
  24. package/assets/templates/mobile-app/app/_layout.tsx +1 -1
  25. package/assets/templates/mobile-app/babel.config.js +1 -1
  26. package/assets/templates/mobile-app/eas.json +1 -1
  27. package/assets/templates/mobile-app/expo-env.d.ts +1 -1
  28. package/assets/templates/mobile-app/metro.config.js +2 -2
  29. package/assets/templates/mobile-app/package.json +1 -1
  30. package/assets/templates/mobile-app/src/mw/client.ts +3 -3
  31. package/assets/templates/mobile-app/src/mw/contracts.ts +2 -2
  32. package/assets/templates/mobile-app/src/mw/endpoints.ts +2 -2
  33. package/assets/templates/mobile-app/src/mw/env.ts +4 -4
  34. package/assets/templates/mobile-app/src/mw/session.ts +1 -1
  35. package/assets/templates/mobile-app/template.json +1 -1
  36. package/assets/templates/mobile-app/tools/template/validate-template.mjs +2 -2
  37. package/assets/templates/mobile-app/tsconfig.json +1 -1
  38. package/assets/templates/next-tenant-app/.env.example +1 -1
  39. package/assets/templates/next-tenant-app/README.md +26 -138
  40. package/assets/templates/next-tenant-app/package.json +1 -0
  41. package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
  42. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
  43. package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
  44. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
  45. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
  46. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
  47. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
  48. package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
  49. package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
  50. package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
  51. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
  52. package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
  53. package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
  54. package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
  55. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
  56. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
  57. package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
  58. package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
  59. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
  60. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
  61. package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +1 -1
  62. package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
  63. package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
  64. package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
  65. package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
  66. package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
  67. package/assets/templates/next-tenant-app/template.json +3 -3
  68. package/assets/templates/next-tenant-app/template.schema.json +1 -0
  69. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
  70. package/assets/templates/next-tenant-app/tools/template/with-public-site-fixture.mjs +2 -2
  71. package/bin/minutework.js +1 -1
  72. package/dist/agent.js +7 -7
  73. package/dist/agent.js.map +1 -1
  74. package/dist/auth.js +7 -7
  75. package/dist/auth.js.map +1 -1
  76. package/dist/compile.js +5 -5
  77. package/dist/config.js +6 -6
  78. package/dist/config.js.map +1 -1
  79. package/dist/deploy.js +7 -7
  80. package/dist/deploy.js.map +1 -1
  81. package/dist/developer-client.js +2 -2
  82. package/dist/developer-client.js.map +1 -1
  83. package/dist/index.js +30 -30
  84. package/dist/index.js.map +1 -1
  85. package/dist/init.js +10 -10
  86. package/dist/init.js.map +1 -1
  87. package/dist/launcher.js +1 -1
  88. package/dist/launcher.js.map +1 -1
  89. package/dist/managed-engine.js +6 -6
  90. package/dist/managed-engine.js.map +1 -1
  91. package/dist/orchestrator-context.js +1 -1
  92. package/dist/orchestrator-context.js.map +1 -1
  93. package/dist/orchestrator.js +15 -15
  94. package/dist/orchestrator.js.map +1 -1
  95. package/dist/paths.js +1 -1
  96. package/dist/paths.js.map +1 -1
  97. package/dist/publish.js +3 -3
  98. package/dist/publish.js.map +1 -1
  99. package/dist/reporting.js +8 -8
  100. package/dist/reporting.js.map +1 -1
  101. package/dist/sandbox.js +5 -5
  102. package/dist/sandbox.js.map +1 -1
  103. package/dist/state.js +1 -1
  104. package/dist/state.js.map +1 -1
  105. package/dist/tokens.js +9 -9
  106. package/dist/tokens.js.map +1 -1
  107. package/dist/workspace-assets.js +6 -6
  108. package/dist/workspace-assets.js.map +1 -1
  109. package/dist/workspace.js +3 -3
  110. package/dist/workspace.js.map +1 -1
  111. package/package.json +3 -3
  112. package/vendor/workspace-mcp/context.d.ts +6 -6
  113. package/vendor/workspace-mcp/context.js +56 -56
  114. package/vendor/workspace-mcp/context.js.map +1 -1
  115. package/vendor/workspace-mcp/types.d.ts +4 -0
  116. package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
  117. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
  118. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
  119. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
  120. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
  121. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
  122. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
  123. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
  124. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
  125. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
  126. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
  127. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
  128. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
  129. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
  130. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
  131. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
  132. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
  133. package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
  134. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
  135. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
  136. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
  137. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
  138. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
  139. package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
  140. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
  141. package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
  142. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
  143. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
  144. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
  145. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
  146. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
  147. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
  148. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
  149. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
  150. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
  151. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
  152. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
  153. package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
  154. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
  155. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
  156. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
  157. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
  158. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
  159. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
  160. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
  161. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
  162. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
  163. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
  164. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
  165. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
  166. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
  167. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
  168. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
@@ -1,79 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const changePassword = vi.fn();
4
- const readPlatformAuthState = vi.fn();
5
- const syncPlatformAuthStateToResponse = vi.fn();
6
-
7
- vi.mock("@/lib/platform/client.server", () => ({
8
- changePassword,
9
- }));
10
-
11
- vi.mock("@/lib/platform/session.server", () => ({
12
- readPlatformAuthState,
13
- syncPlatformAuthStateToResponse,
14
- }));
15
-
16
- describe("password change route", () => {
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
- });
20
-
21
- it("rejects invalid password payloads before proxying upstream", async () => {
22
- readPlatformAuthState.mockResolvedValue({
23
- sessionId: "session-1",
24
- csrfToken: "csrf-1",
25
- });
26
-
27
- const route = await import("./route");
28
- const response = await route.POST(
29
- new Request("http://localhost/api/auth/password-change", {
30
- method: "POST",
31
- headers: { "content-type": "application/json" },
32
- body: JSON.stringify({
33
- current_password: "old",
34
- new_password: "new-password",
35
- confirm_password: "different-password",
36
- }),
37
- }),
38
- );
39
-
40
- expect(changePassword).not.toHaveBeenCalled();
41
- expect(response.status).toBe(400);
42
- });
43
-
44
- it("proxies valid password changes through the BFF", async () => {
45
- const authState = { sessionId: "session-1", csrfToken: "csrf-1" };
46
-
47
- readPlatformAuthState.mockResolvedValue(authState);
48
- changePassword.mockResolvedValue({
49
- data: { has_password: true },
50
- authState,
51
- });
52
-
53
- const route = await import("./route");
54
- const response = await route.POST(
55
- new Request("http://localhost/api/auth/password-change", {
56
- method: "POST",
57
- headers: { "content-type": "application/json" },
58
- body: JSON.stringify({
59
- current_password: "old-password",
60
- new_password: "new-password",
61
- confirm_password: "new-password",
62
- }),
63
- }),
64
- );
65
-
66
- expect(changePassword).toHaveBeenCalledWith(authState, {
67
- current_password: "old-password",
68
- new_password: "new-password",
69
- confirm_password: "new-password",
70
- });
71
- expect(syncPlatformAuthStateToResponse).toHaveBeenCalledWith(
72
- response,
73
- authState,
74
- authState,
75
- );
76
- expect(response.status).toBe(200);
77
- await expect(response.json()).resolves.toEqual({ has_password: true });
78
- });
79
- });
@@ -1,40 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- import { changePassword } from "@/lib/platform/client.server";
4
- import { passwordChangeRequestSchema } from "@/lib/platform/contracts";
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 POST(request: Request) {
12
- const authState = await readPlatformAuthState();
13
-
14
- if (!authState.sessionId) {
15
- return NextResponse.json({ detail: "Authentication required." }, { status: 401 });
16
- }
17
-
18
- const body = await request.json().catch(() => null);
19
- const parsed = passwordChangeRequestSchema.safeParse(body);
20
-
21
- if (!parsed.success) {
22
- return NextResponse.json(
23
- { detail: "Invalid password change payload." },
24
- { status: 400 },
25
- );
26
- }
27
-
28
- try {
29
- const result = await changePassword(authState, parsed.data);
30
- const response = NextResponse.json(result.data, { status: 200 });
31
- syncPlatformAuthStateToResponse(response, authState, result.authState);
32
- return response;
33
- } catch (error) {
34
- return toPlatformErrorResponse(
35
- error,
36
- "Unable to change password.",
37
- authState,
38
- );
39
- }
40
- }
@@ -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: "PandaWork 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: "PandaWork 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: "PandaWork 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
- });