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,123 +0,0 @@
1
- import { z } from "zod";
2
-
3
- import { contentStructurePageSchema, siteConfigSchema } from "./contracts";
4
-
5
- /**
6
- * Release manifest contract for Builder output.
7
- *
8
- * When Builder composes a site, it emits a manifest that describes what
9
- * was generated. This manifest is inspectable, diffable, and can be
10
- * used to verify that releases match expectations.
11
- *
12
- * The manifest is NOT the release artifact itself — it describes the
13
- * composition that produced the artifact.
14
- */
15
-
16
- export const releaseManifestSchema = z.object({
17
- /** Manifest format version. */
18
- manifest_version: z.literal(1),
19
-
20
- /** The app pack ID that was composed. */
21
- app_pack_id: z.string().min(1),
22
-
23
- /** Snapshot schema version for compatibility gating. */
24
- snapshot_schema_version: z.number().int().positive(),
25
-
26
- /** Release class from the hosted vocabulary. */
27
- release_class: z.enum(["static_export", "ssr_container", "runtime_local_sidecar"]),
28
-
29
- /** Summary of what was composed. */
30
- composition: z.object({
31
- config_updated: z.boolean(),
32
- pages: z.array(
33
- z.object({
34
- page_key: z.string().min(1),
35
- path: z.string().min(1),
36
- title: z.string().min(1),
37
- section_count: z.number().int().min(0),
38
- }),
39
- ),
40
- navigation: z.array(
41
- z.object({
42
- nav_key: z.string().min(1),
43
- slot: z.string().min(1),
44
- item_count: z.number().int().min(0),
45
- }),
46
- ),
47
- forms: z.array(
48
- z.object({
49
- form_key: z.string().min(1),
50
- field_count: z.number().int().min(0),
51
- }),
52
- ),
53
- }),
54
-
55
- /** Optional metadata for attached external renderers that sync against the standard artifact graph. */
56
- attached_site: z
57
- .object({
58
- mode: z.literal("attached_app"),
59
- adapter_module: z.string().min(1),
60
- renderer: z.string().min(1),
61
- publish_target: z.string().min(1),
62
- })
63
- .optional(),
64
-
65
- /** Digest of the composed content for integrity verification. */
66
- content_digest: z.string().min(1),
67
-
68
- /** Timestamp of the manifest generation. */
69
- generated_at: z.string().min(1),
70
- });
71
-
72
- export type ReleaseManifest = z.infer<typeof releaseManifestSchema>;
73
-
74
- /**
75
- * Build a release manifest from a Builder composition result.
76
- *
77
- * This is called by Builder after compose_site_from_builder_payload
78
- * to produce an inspectable record of what was generated.
79
- */
80
- export function buildReleaseManifest(input: {
81
- appPackId: string;
82
- snapshotSchemaVersion: number;
83
- releaseClass: "static_export" | "ssr_container" | "runtime_local_sidecar";
84
- config: Record<string, unknown> | null;
85
- pages: { page_key: string; path: string; title: string; sections: unknown[] }[];
86
- navigation: { nav_key: string; slot: string; items: unknown[] }[];
87
- forms: { form_key: string; schema?: { fields?: unknown[] } }[];
88
- attachedSite?: {
89
- mode: "attached_app";
90
- adapter_module: string;
91
- renderer: string;
92
- publish_target: string;
93
- } | null;
94
- contentDigest: string;
95
- }): ReleaseManifest {
96
- return {
97
- manifest_version: 1,
98
- app_pack_id: input.appPackId,
99
- snapshot_schema_version: input.snapshotSchemaVersion,
100
- release_class: input.releaseClass,
101
- composition: {
102
- config_updated: input.config !== null,
103
- pages: input.pages.map((p) => ({
104
- page_key: p.page_key,
105
- path: p.path,
106
- title: p.title,
107
- section_count: p.sections?.length ?? 0,
108
- })),
109
- navigation: input.navigation.map((n) => ({
110
- nav_key: n.nav_key,
111
- slot: n.slot,
112
- item_count: n.items?.length ?? 0,
113
- })),
114
- forms: input.forms.map((f) => ({
115
- form_key: f.form_key,
116
- field_count: f.schema?.fields?.length ?? 0,
117
- })),
118
- },
119
- attached_site: input.attachedSite ?? undefined,
120
- content_digest: input.contentDigest,
121
- generated_at: new Date().toISOString(),
122
- };
123
- }
@@ -1,75 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const redirect = vi.fn((href: string) => {
4
- throw new Error(`redirect:${href}`);
5
- });
6
- const resolveCurrentSession = vi.fn();
7
- const readPlatformAuthState = vi.fn();
8
-
9
- vi.mock("next/navigation", () => ({
10
- redirect,
11
- }));
12
-
13
- vi.mock("@/lib/platform/client.server", () => ({
14
- resolveCurrentSession,
15
- }));
16
-
17
- vi.mock("@/lib/platform/session.server", () => ({
18
- readPlatformAuthState,
19
- }));
20
-
21
- describe("resolveAuthenticatedSession", () => {
22
- beforeEach(() => {
23
- vi.clearAllMocks();
24
- vi.resetModules();
25
- });
26
-
27
- it("redirects anonymous sessions to /login", async () => {
28
- readPlatformAuthState.mockResolvedValue(null);
29
- resolveCurrentSession.mockResolvedValue({ authenticated: false });
30
-
31
- const auth = await import("./auth.server");
32
-
33
- await expect(auth.resolveAuthenticatedSession()).rejects.toThrow(
34
- "redirect:/login",
35
- );
36
- expect(redirect).toHaveBeenCalledWith("/login");
37
- });
38
-
39
- it("returns the authenticated platform session", async () => {
40
- const session = {
41
- authenticated: true as const,
42
- user: {
43
- id: "user-1",
44
- username: "demo-user",
45
- email: "demo@example.com",
46
- },
47
- active_tenant_id: "tenant-1",
48
- active_tenant: {
49
- tenant_id: "tenant-1",
50
- tenant_slug: "alpha",
51
- tenant_name: "Alpha",
52
- role: "admin",
53
- },
54
- memberships: [
55
- {
56
- tenant_id: "tenant-1",
57
- tenant_slug: "alpha",
58
- tenant_name: "Alpha",
59
- role: "admin",
60
- },
61
- ],
62
- };
63
-
64
- readPlatformAuthState.mockResolvedValue({
65
- sessionId: "session-1",
66
- csrfToken: "csrf-1",
67
- });
68
- resolveCurrentSession.mockResolvedValue(session);
69
-
70
- const auth = await import("./auth.server");
71
-
72
- await expect(auth.resolveAuthenticatedSession()).resolves.toEqual(session);
73
- expect(redirect).not.toHaveBeenCalled();
74
- });
75
- });
@@ -1,25 +0,0 @@
1
- import "server-only";
2
-
3
- import { cache } from "react";
4
- import { redirect } from "next/navigation";
5
-
6
- import { appRoutes } from "@/lib/app-routes";
7
- import { resolveCurrentSession } from "@/lib/platform/client.server";
8
- import type { AuthenticatedPlatformSession } from "@/lib/platform/contracts";
9
- import { readPlatformAuthState } from "@/lib/platform/session.server";
10
-
11
- const loadResolvedPlatformSession = cache(async () =>
12
- resolveCurrentSession(await readPlatformAuthState()),
13
- );
14
-
15
- export const resolvePlatformSession = loadResolvedPlatformSession;
16
-
17
- export async function resolveAuthenticatedSession(): Promise<AuthenticatedPlatformSession> {
18
- const session = await loadResolvedPlatformSession();
19
-
20
- if (!session.authenticated) {
21
- redirect(appRoutes.login);
22
- }
23
-
24
- return session;
25
- }
@@ -1,170 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
-
3
- import {
4
- dispatchTenantCommand,
5
- loadCurrentSession,
6
- loginWithPassword,
7
- } from "@/lib/platform/client.server";
8
-
9
- const sessionPayload = {
10
- user: {
11
- id: "user-1",
12
- username: "demo-user",
13
- email: "demo@example.com",
14
- },
15
- active_tenant_id: "tenant-1",
16
- active_tenant: {
17
- tenant_id: "tenant-1",
18
- tenant_slug: "alpha",
19
- tenant_name: "Alpha",
20
- role: "admin",
21
- },
22
- memberships: [
23
- {
24
- tenant_id: "tenant-1",
25
- tenant_slug: "alpha",
26
- tenant_name: "Alpha",
27
- role: "admin",
28
- },
29
- ],
30
- };
31
-
32
- const commandRunPayload = {
33
- run_id: "run-1",
34
- tenant_id: "tenant-1",
35
- runtime_id: "runtime-1",
36
- template_key: "echo_hello",
37
- status: "queued",
38
- safe_summary: "Queued for dispatch.",
39
- exit_code: null,
40
- stdout_tail: "",
41
- stderr_tail: "",
42
- source_ref: "runtime:command:1",
43
- dispatch_error: "",
44
- };
45
-
46
- function jsonResponse(
47
- body: unknown,
48
- init?: {
49
- status?: number;
50
- headers?: Record<string, string>;
51
- },
52
- ) {
53
- return new Response(body === null ? null : JSON.stringify(body), {
54
- status: init?.status ?? 200,
55
- headers: {
56
- "content-type": "application/json",
57
- ...init?.headers,
58
- },
59
- });
60
- }
61
-
62
- describe("platform client", () => {
63
- afterEach(() => {
64
- vi.unstubAllGlobals();
65
- });
66
-
67
- it("bootstraps an anonymous csrf token while resolving the session", async () => {
68
- const fetchMock = vi.fn().mockResolvedValue(
69
- jsonResponse(
70
- { detail: "Authentication required." },
71
- {
72
- status: 401,
73
- headers: {
74
- "set-cookie": "csrftoken=bootstrap-token; Path=/; SameSite=Lax",
75
- },
76
- },
77
- ),
78
- );
79
-
80
- vi.stubGlobal("fetch", fetchMock);
81
-
82
- const result = await loadCurrentSession(null);
83
-
84
- expect(result.data).toEqual({ authenticated: false });
85
- expect(result.authState).toEqual({
86
- sessionId: null,
87
- csrfToken: "bootstrap-token",
88
- });
89
- expect(fetchMock).toHaveBeenCalledTimes(1);
90
- });
91
-
92
- it("bootstraps csrf before login and persists the returned session cookies", async () => {
93
- const fetchMock = vi
94
- .fn()
95
- .mockResolvedValueOnce(
96
- jsonResponse(
97
- { detail: "Authentication required." },
98
- {
99
- status: 401,
100
- headers: {
101
- "set-cookie": "csrftoken=bootstrap-token; Path=/; SameSite=Lax",
102
- },
103
- },
104
- ),
105
- )
106
- .mockResolvedValueOnce(
107
- jsonResponse(sessionPayload, {
108
- status: 200,
109
- headers: {
110
- "set-cookie":
111
- "sessionid=session-1; Path=/; HttpOnly, csrftoken=rotated-token; Path=/; SameSite=Lax",
112
- },
113
- }),
114
- );
115
-
116
- vi.stubGlobal("fetch", fetchMock);
117
-
118
- const result = await loginWithPassword(null, {
119
- username: "demo-user",
120
- password: "demo-password",
121
- });
122
-
123
- const loginHeaders = new Headers(fetchMock.mock.calls[1]?.[1]?.headers);
124
-
125
- expect(loginHeaders.get("x-csrftoken")).toBe("bootstrap-token");
126
- expect(loginHeaders.get("cookie")).toBe("csrftoken=bootstrap-token");
127
- expect(result.authState).toEqual({
128
- sessionId: "session-1",
129
- csrfToken: "rotated-token",
130
- });
131
- expect(result.data.authenticated).toBe(true);
132
- expect(result.data.active_tenant_id).toBe("tenant-1");
133
- });
134
-
135
- it("forwards the stored session and csrf token on unsafe tenant mutations", async () => {
136
- const fetchMock = vi
137
- .fn()
138
- .mockResolvedValueOnce(
139
- jsonResponse(sessionPayload, {
140
- status: 200,
141
- headers: {
142
- "set-cookie": "csrftoken=csrf-1; Path=/; SameSite=Lax",
143
- },
144
- }),
145
- )
146
- .mockResolvedValueOnce(jsonResponse(commandRunPayload));
147
-
148
- vi.stubGlobal("fetch", fetchMock);
149
-
150
- const result = await dispatchTenantCommand(
151
- { sessionId: "session-1", csrfToken: null },
152
- {
153
- tenant_id: "tenant-1",
154
- template_key: "echo_hello",
155
- },
156
- );
157
-
158
- const commandHeaders = new Headers(fetchMock.mock.calls[1]?.[1]?.headers);
159
-
160
- expect(commandHeaders.get("cookie")).toBe(
161
- "sessionid=session-1; csrftoken=csrf-1",
162
- );
163
- expect(commandHeaders.get("x-csrftoken")).toBe("csrf-1");
164
- expect(result.authState).toEqual({
165
- sessionId: "session-1",
166
- csrfToken: "csrf-1",
167
- });
168
- expect(result.data.run_id).toBe("run-1");
169
- });
170
- });