alinea 2.0.0-preview.4 → 2.0.0-preview.5

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 (164) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.init/src/cms.ts +1 -4
  3. package/dist/LICENSES.md +107 -107
  4. package/dist/adapter/next/cms.js +5 -1
  5. package/dist/adapter/next/handler.js +35 -6
  6. package/dist/adapter/next/previews.js +7 -1
  7. package/dist/adapter/next/syncCheck.d.ts +12 -0
  8. package/dist/adapter/next/syncCheck.js +59 -0
  9. package/dist/adapter/next/with-alinea.d.ts +0 -8
  10. package/dist/adapter/next/with-alinea.js +43 -19
  11. package/dist/adapter/vite/plugin.js +9 -4
  12. package/dist/backend/Handler.js +106 -0
  13. package/dist/backend/api/ProxyMedia.d.ts +3 -0
  14. package/dist/backend/api/ProxyMedia.js +39 -0
  15. package/dist/backend/api/S3Uploads.d.ts +1 -1
  16. package/dist/backend/router/Router.js +2 -0
  17. package/dist/backend/util/Syncable.js +1 -1
  18. package/dist/bundled.d.ts +164 -70
  19. package/dist/chunks/{chunk-XMOVGDFI.js → chunk-A6M3PNJT.js} +1 -1
  20. package/dist/cli/Generate.js +1 -3
  21. package/dist/cli/Serve.js +3 -2
  22. package/dist/cli/bin.js +1 -1
  23. package/dist/cli/generate/GenerateDashboard.js +1 -0
  24. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  25. package/dist/cli/serve/CreateLocalServer.js +42 -34
  26. package/dist/cli/static/init/cms.js +1 -4
  27. package/dist/cli/util/ContentType.d.ts +1 -0
  28. package/dist/cli/util/ContentType.js +36 -0
  29. package/dist/cloud/CloudHandshake.d.ts +6 -0
  30. package/dist/cloud/CloudHandshake.js +67 -10
  31. package/dist/cloud/CloudRemote.js +24 -11
  32. package/dist/components/Calendar.js +4 -4
  33. package/dist/components/Checkbox.js +3 -3
  34. package/dist/components/ColorSwatchPicker.js +2 -2
  35. package/dist/components/ComboBox.js +9 -9
  36. package/dist/components/DateField.js +4 -4
  37. package/dist/components/DatePicker.js +3 -3
  38. package/dist/components/DateRangePicker.js +8 -8
  39. package/dist/components/Disclosure.js +7 -7
  40. package/dist/components/Label.js +4 -4
  41. package/dist/components/List.d.ts +1 -1
  42. package/dist/components/List.js +35 -34
  43. package/dist/components/Menu.js +4 -4
  44. package/dist/components/Modal.js +2 -2
  45. package/dist/components/MultipleSelect.js +23 -23
  46. package/dist/components/NumberField.js +6 -6
  47. package/dist/components/ProgressCircle.js +1 -1
  48. package/dist/components/SearchField.js +7 -7
  49. package/dist/components/Select.js +9 -9
  50. package/dist/components/Surface.js +2 -2
  51. package/dist/components/Table.js +7 -7
  52. package/dist/components/Tabs.js +2 -2
  53. package/dist/components/TagGroup.js +2 -2
  54. package/dist/components/TextField.js +6 -6
  55. package/dist/components/TimeField.js +3 -3
  56. package/dist/components/Toggle.js +2 -2
  57. package/dist/components/Toolbar.js +2 -2
  58. package/dist/components/Tooltip.js +2 -2
  59. package/dist/components/Tree.js +6 -6
  60. package/dist/core/Config.d.ts +10 -3
  61. package/dist/core/Config.js +16 -1
  62. package/dist/core/Graph.d.ts +2 -2
  63. package/dist/core/Role.js +16 -14
  64. package/dist/core/TextDoc.d.ts +2 -0
  65. package/dist/core/TextDoc.js +1 -0
  66. package/dist/core/Workspace.d.ts +6 -5
  67. package/dist/core/db/EntryIndex.d.ts +3 -0
  68. package/dist/core/db/EntryIndex.js +62 -0
  69. package/dist/core/db/EntryTransaction.js +33 -12
  70. package/dist/core/db/EntryUrlConflictError.js +1 -1
  71. package/dist/core/db/LinkResolver.d.ts +8 -2
  72. package/dist/core/db/LinkResolver.js +27 -5
  73. package/dist/core/field/RichTextField.js +16 -10
  74. package/dist/core/media/MediaLocation.d.ts +9 -15
  75. package/dist/core/media/MediaLocation.js +29 -19
  76. package/dist/core/media/MediaTypes.js +1 -2
  77. package/dist/dashboard/App.js +1 -1
  78. package/dist/dashboard/app/AccessDenied.js +2 -2
  79. package/dist/dashboard/app/ActivityStatus.js +14 -13
  80. package/dist/dashboard/app/AppShell.js +1 -1
  81. package/dist/dashboard/app/AuthView.js +9 -9
  82. package/dist/dashboard/app/Badge.js +2 -2
  83. package/dist/dashboard/app/CompactField.js +13 -13
  84. package/dist/dashboard/app/DashboardErrorBoundary.js +4 -4
  85. package/dist/dashboard/app/EntryFields.d.ts +3 -1
  86. package/dist/dashboard/app/EntryFields.js +11 -8
  87. package/dist/dashboard/app/EntryHeader.js +117 -85
  88. package/dist/dashboard/app/EntryHeaderActions.d.ts +21 -4
  89. package/dist/dashboard/app/EntryHeaderActions.js +48 -27
  90. package/dist/dashboard/app/EntryReferences.js +9 -9
  91. package/dist/dashboard/app/EntrySidebar.js +12 -12
  92. package/dist/dashboard/app/EntrySidebarPreview.js +11 -11
  93. package/dist/dashboard/app/EntryTranslationBanner.d.ts +3 -1
  94. package/dist/dashboard/app/EntryTranslationBanner.js +24 -21
  95. package/dist/dashboard/app/Explorer.d.ts +4 -2
  96. package/dist/dashboard/app/Explorer.js +50 -46
  97. package/dist/dashboard/app/ExplorerCards.js +28 -28
  98. package/dist/dashboard/app/ExplorerFileCard.js +19 -19
  99. package/dist/dashboard/app/ExplorerList.js +12 -13
  100. package/dist/dashboard/app/ExplorerModal.js +5 -5
  101. package/dist/dashboard/app/ExplorerTable.js +46 -46
  102. package/dist/dashboard/app/LinkPicker.js +5 -5
  103. package/dist/dashboard/app/LocaleMenu.d.ts +7 -0
  104. package/dist/dashboard/app/LocaleMenu.js +26 -10
  105. package/dist/dashboard/app/ReadOnlyBadge.d.ts +4 -0
  106. package/dist/dashboard/app/ReadOnlyBadge.js +33 -0
  107. package/dist/dashboard/app/SidebarTree.d.ts +3 -6
  108. package/dist/dashboard/app/SidebarTree.js +54 -49
  109. package/dist/dashboard/app/WorkspaceMenu.js +12 -12
  110. package/dist/dashboard/app/WorkspaceRoots.js +24 -24
  111. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -3
  112. package/dist/dashboard/app/editor/FileEditor.js +31 -34
  113. package/dist/dashboard/app/editor/FilePreview.js +6 -6
  114. package/dist/dashboard/app/modals/CreateEntry.js +5 -5
  115. package/dist/dashboard/app/pages/EntryPage.js +73 -30
  116. package/dist/dashboard/app/pages/SplashPage.js +47 -47
  117. package/dist/dashboard/app/pages/UsersPage.js +34 -34
  118. package/dist/dashboard/app/ui/DashboardModal.js +18 -18
  119. package/dist/dashboard/app/ui/Rail.js +3 -3
  120. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  121. package/dist/dashboard/atoms/activity.d.ts +0 -1
  122. package/dist/dashboard/atoms/activity.js +1 -2
  123. package/dist/dashboard/atoms/editor.d.ts +2 -1
  124. package/dist/dashboard/atoms/editor.js +4 -2
  125. package/dist/dashboard/atoms/entry.d.ts +2 -0
  126. package/dist/dashboard/atoms/entry.js +32 -14
  127. package/dist/dashboard/atoms/explorer.d.ts +4 -3
  128. package/dist/dashboard/atoms/explorer.js +61 -22
  129. package/dist/dashboard/atoms/link.d.ts +1 -1
  130. package/dist/dashboard/atoms/link.js +39 -55
  131. package/dist/dashboard/atoms/root.d.ts +7 -2
  132. package/dist/dashboard/atoms/root.js +51 -16
  133. package/dist/dashboard/atoms/upload.d.ts +1 -0
  134. package/dist/dashboard/atoms/upload.js +2 -1
  135. package/dist/dashboard/boot/DashboardWorker.js +1 -0
  136. package/dist/dashboard/fixture/cms.js +1 -4
  137. package/dist/dashboard/hook/UseSaveShortcut.d.ts +1 -0
  138. package/dist/dashboard/hook/UseSaveShortcut.js +24 -0
  139. package/dist/dashboard/hooks.d.ts +1 -1
  140. package/dist/dashboard/hooks.js +12 -3
  141. package/dist/field/code/CodeField.view.js +9 -9
  142. package/dist/field/link/ImageLink.js +1 -3
  143. package/dist/field/link/LinkField.view.js +84 -58
  144. package/dist/field/list/ListField.view.js +13 -13
  145. package/dist/field/metadata/MetadataField.d.ts +3 -3
  146. package/dist/field/metadata/MetadataField.view.js +24 -17
  147. package/dist/field/object/ObjectField.view.js +11 -4
  148. package/dist/field/richtext/PickTextLink.js +5 -2
  149. package/dist/field/richtext/ReferenceLink.d.ts +2 -0
  150. package/dist/field/richtext/ReferenceLink.js +5 -0
  151. package/dist/field/richtext/RichTextBlock.js +2 -2
  152. package/dist/field/richtext/RichTextExtensions.js +22 -22
  153. package/dist/field/richtext/RichTextField.view.js +4 -4
  154. package/dist/field/richtext/extensions/Link.d.ts +1 -0
  155. package/dist/field/richtext/extensions/Link.js +1 -0
  156. package/dist/field/time/TimeField.d.ts +0 -2
  157. package/dist/field/time/TimeField.view.js +1 -1
  158. package/dist/index.css +44 -38
  159. package/dist/next.cjs +96 -19
  160. package/dist/picker/entry/EntryPicker.js +10 -12
  161. package/dist/picker/entry/EntryReference.d.ts +2 -0
  162. package/dist/picker/entry/EntryReference.js +1 -0
  163. package/dist/types.d.ts +0 -1
  164. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  // package.json with { type: 'json' }
2
2
  var package_default = {
3
3
  name: "alinea",
4
- version: "2.0.0-preview.4",
4
+ version: "2.0.0-preview.5",
5
5
  description: "Headless git-based CMS",
6
6
  repository: "github:alineacms/alinea",
7
7
  license: "MIT",
@@ -24,9 +24,7 @@ var require2 = createRequire(import.meta.url);
24
24
  var alineaPackageDir = path.dirname(require2.resolve("alinea/package.json"));
25
25
  async function generatePackage(context, cms) {
26
26
  const { config } = cms;
27
- if (!config.dashboardFile) return;
28
- const staticFile = config.dashboardFile ? join(config.publicDir, config.dashboardFile) : void 0;
29
- if (!staticFile) return;
27
+ const staticFile = join(config.publicDir, Config.dashboardFile(config));
30
28
  await generateDashboard(context, cms, Config.handlerUrl(config), staticFile);
31
29
  return basename(staticFile);
32
30
  }
package/dist/cli/Serve.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-XMOVGDFI.js";
3
+ } from "../chunks/chunk-A6M3PNJT.js";
4
4
  import "../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/cli/Serve.ts
@@ -24,7 +24,7 @@ async function serve(options) {
24
24
  const apiKey = process.env.ALINEA_API_KEY ?? createId();
25
25
  const nodeServer = startServer(preferredPort, 0, cmd === "build");
26
26
  const dashboardUrl = nodeServer.then(
27
- (server) => `http://${server.hostname}:${server.port}`
27
+ (server) => `http://localhost:${server.port}`
28
28
  );
29
29
  let devServer;
30
30
  try {
@@ -60,6 +60,7 @@ async function serve(options) {
60
60
  options.onAfterGenerate?.({
61
61
  ALINEA_DEV_SERVER: url,
62
62
  ALINEA_ADMIN_PATH: Config.adminPath(config),
63
+ ALINEA_HANDLER_URL: Config.handlerUrl(config),
63
64
  ALINEA_API_KEY: apiKey
64
65
  });
65
66
  });
package/dist/cli/bin.js CHANGED
@@ -220,7 +220,7 @@ var lib_default2 = (e, t2) => new o(e, t2);
220
220
  // package.json
221
221
  var package_default = {
222
222
  name: "alinea",
223
- version: "2.0.0-preview.4",
223
+ version: "2.0.0-preview.5",
224
224
  description: "Headless git-based CMS",
225
225
  repository: "github:alineacms/alinea",
226
226
  license: "MIT",
@@ -95,6 +95,7 @@ async function generateDashboard({ configLocation, rootDir, configDir }, cms, ha
95
95
  define: {
96
96
  "process.env.NODE_ENV": '"production"',
97
97
  "process.env.ALINEA_BUILD_ID": JSON.stringify(buildId),
98
+ "process.env.ALINEA_FORCE_AUTH": "true",
98
99
  ...publicDefines(process.env)
99
100
  },
100
101
  ...buildOptions,
@@ -1,7 +1,7 @@
1
1
  import type { Handler } from 'alinea/backend/Handler';
2
2
  import type { CMS } from 'alinea/core/CMS';
3
3
  import type { User } from 'alinea/core/User';
4
- import { type Request, Response } from '@alinea/iso';
4
+ import { Request, Response } from '@alinea/iso';
5
5
  import type { ServeContext } from './ServeContext.js';
6
6
  export declare function createLocalServer({ cmd, configLocation, rootDir, staticDir, alineaDev, buildOptions, production, liveReload, buildId, apiKey }: ServeContext, cms: CMS, handleApi: Handler, user: User): {
7
7
  close(): void;
@@ -2,46 +2,19 @@ import "../../chunks/chunk-CQ52GEHY.js";
2
2
 
3
3
  // src/cli/serve/CreateLocalServer.ts
4
4
  import { router } from "alinea/backend/router/Router";
5
+ import { Config } from "alinea/core/Config";
5
6
  import { HttpError } from "alinea/core/HttpError";
6
7
  import { trigger } from "alinea/core/Trigger";
7
8
  import { assertUploadSize } from "alinea/core/media/UploadLimits";
8
- import { ReadableStream, Response } from "@alinea/iso";
9
+ import { ReadableStream, Request, Response } from "@alinea/iso";
9
10
  import fs from "node:fs";
10
11
  import path from "node:path";
11
12
  import { buildEmitter } from "../build/BuildEmitter.js";
13
+ import { contentType } from "../util/ContentType.js";
12
14
  import { ignorePlugin } from "../util/IgnorePlugin.js";
13
15
  import { publicDefines } from "../util/PublicDefines.js";
14
16
  import { reportFatal } from "../util/Report.js";
15
17
  import { viewsPlugin } from "../util/ViewsPlugin.js";
16
- var mimeTypes = new Map(
17
- Object.entries({
18
- // Text
19
- ".css": "text/css; charset=utf-8",
20
- ".htm": "text/html; charset=utf-8",
21
- ".html": "text/html; charset=utf-8",
22
- ".js": "text/javascript; charset=utf-8",
23
- ".json": "application/json",
24
- ".mjs": "text/javascript; charset=utf-8",
25
- ".xml": "text/xml; charset=utf-8",
26
- // Images
27
- ".gif": "image/gif",
28
- ".jpeg": "image/jpeg",
29
- ".jpg": "image/jpeg",
30
- ".png": "image/png",
31
- ".svg": "image/svg+xml",
32
- ".webp": "image/webp",
33
- // Fonts
34
- ".eot": "application/vnd.ms-fontobject",
35
- ".otf": "font/otf",
36
- ".sfnt": "font/sfnt",
37
- ".ttf": "font/ttf",
38
- ".woff": "font/woff",
39
- ".woff2": "font/woff2",
40
- // Other
41
- ".pdf": "application/pdf",
42
- ".wasm": "application/wasm"
43
- })
44
- );
45
18
  function buildFiles(outdir, result) {
46
19
  return new Map(
47
20
  result.outputFiles.map((file) => {
@@ -72,9 +45,11 @@ function createLocalServer({
72
45
  });
73
46
  }
74
47
  const devDir = path.join(staticDir, "dev");
48
+ const publicDir = path.join(rootDir, cms.config.publicDir ?? "public");
49
+ const adminPath = Config.adminPath(cms.config);
75
50
  const getPath = (url) => {
76
- if (url.pathname.startsWith("/admin"))
77
- return url.pathname.slice("/admin".length);
51
+ if (url.pathname === adminPath || url.pathname.startsWith(`${adminPath}/`))
52
+ return url.pathname.slice(adminPath.length);
78
53
  return url.pathname;
79
54
  };
80
55
  const matcher = router.matcher(getPath);
@@ -157,11 +132,34 @@ function createLocalServer({
157
132
  const extension = path.extname(fileName);
158
133
  return new Response(file.contents, {
159
134
  headers: {
160
- "content-type": mimeTypes.get(extension) || "application/octet-stream",
135
+ "content-type": contentType(extension),
161
136
  etag
162
137
  }
163
138
  });
164
139
  }
140
+ async function servePublicFile(request) {
141
+ if (request.method !== "GET" && request.method !== "HEAD") return;
142
+ const pathname = decodeURIComponent(new URL(request.url).pathname);
143
+ const location = path.resolve(publicDir, pathname.replace(/^[/\\]+/, ""));
144
+ const relative = path.relative(publicDir, location);
145
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
146
+ return;
147
+ try {
148
+ const stat = await fs.promises.stat(location);
149
+ if (!stat.isFile()) return;
150
+ const body = request.method === "HEAD" ? void 0 : await fs.promises.readFile(location);
151
+ return new Response(body, {
152
+ headers: {
153
+ "content-length": String(stat.size),
154
+ "content-type": contentType(path.extname(location))
155
+ }
156
+ });
157
+ } catch (error) {
158
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
159
+ return;
160
+ throw error;
161
+ }
162
+ }
165
163
  const httpRouter = router(
166
164
  matcher.get("/~dev").map(() => {
167
165
  const stream = new ReadableStream({
@@ -224,6 +222,15 @@ function createLocalServer({
224
222
  }
225
223
  }),
226
224
  router.compress(
225
+ matcher.all("/file/*").map(({ request, params }) => {
226
+ if (request.method !== "GET" && request.method !== "HEAD") return;
227
+ const url = new URL(request.url);
228
+ url.pathname = "/api";
229
+ url.search = new URLSearchParams({
230
+ file: String(params.wild)
231
+ }).toString();
232
+ return devHandler(new Request(url, request));
233
+ }),
227
234
  matcher.all("/api").map(async ({ url, request }) => {
228
235
  return devHandler(request);
229
236
  }),
@@ -245,7 +252,8 @@ function createLocalServer({
245
252
  { headers: { "content-type": "text/html" } }
246
253
  );
247
254
  }),
248
- serveBrowserBuild
255
+ serveBrowserBuild,
256
+ servePublicFile
249
257
  )
250
258
  ).notFound(() => new Response("Not found", { status: 404 }));
251
259
  function uploadCorsHeaders(request) {
@@ -17,7 +17,6 @@ export const cms = createCMS({
17
17
  main: Config.workspace('Example', {
18
18
  source: 'content',
19
19
  mediaDir: 'public/media',
20
- mediaUrl: '/media',
21
20
  roots: {
22
21
  pages: Config.root('Example site', {
23
22
  contains: ['Page']
@@ -39,7 +38,5 @@ export const cms = createCMS({
39
38
 
40
39
  // The handler route URL
41
40
  handlerUrl: '/api/cms',
42
-
43
- // The admin dashboard will be bundled in this static file
44
- dashboardFile: 'admin.html'
41
+ adminPath: '/admin'
45
42
  })
@@ -0,0 +1 @@
1
+ export declare function contentType(extension: string): string;
@@ -0,0 +1,36 @@
1
+ import "../../chunks/chunk-CQ52GEHY.js";
2
+
3
+ // src/cli/util/ContentType.ts
4
+ var contentTypes = {
5
+ // Text
6
+ ".css": "text/css; charset=utf-8",
7
+ ".htm": "text/html; charset=utf-8",
8
+ ".html": "text/html; charset=utf-8",
9
+ ".js": "text/javascript; charset=utf-8",
10
+ ".json": "application/json",
11
+ ".mjs": "text/javascript; charset=utf-8",
12
+ ".xml": "text/xml; charset=utf-8",
13
+ // Images
14
+ ".gif": "image/gif",
15
+ ".jpeg": "image/jpeg",
16
+ ".jpg": "image/jpeg",
17
+ ".png": "image/png",
18
+ ".svg": "image/svg+xml",
19
+ ".webp": "image/webp",
20
+ // Fonts
21
+ ".eot": "application/vnd.ms-fontobject",
22
+ ".otf": "font/otf",
23
+ ".sfnt": "font/sfnt",
24
+ ".ttf": "font/ttf",
25
+ ".woff": "font/woff",
26
+ ".woff2": "font/woff2",
27
+ // Other
28
+ ".pdf": "application/pdf",
29
+ ".wasm": "application/wasm"
30
+ };
31
+ function contentType(extension) {
32
+ return contentTypes[extension.toLowerCase()] ?? "application/octet-stream";
33
+ }
34
+ export {
35
+ contentType
36
+ };
@@ -3,5 +3,11 @@ interface VerifyCloudHandshakeOptions {
3
3
  handshakeId: string;
4
4
  origin: string;
5
5
  }
6
+ export declare class CloudHandshakeError extends Error {
7
+ name: string;
8
+ }
9
+ export declare class CloudHandshakeServiceError extends Error {
10
+ name: string;
11
+ }
6
12
  export declare function verifyCloudHandshake(token: string, options: VerifyCloudHandshakeOptions): Promise<void>;
7
13
  export {};
@@ -4,25 +4,72 @@ import "../chunks/chunk-CQ52GEHY.js";
4
4
  import { decode, verify } from "alinea/core/util/JWT";
5
5
  import { isRecord } from "alinea/core/util/Objects";
6
6
  import { cloudConfig } from "./CloudConfig.js";
7
+ var CloudHandshakeError = class extends Error {
8
+ name = "CloudHandshakeError";
9
+ };
10
+ var CloudHandshakeServiceError = class extends Error {
11
+ name = "CloudHandshakeServiceError";
12
+ };
7
13
  var keysPromise;
8
14
  var keysExpireAt = 0;
9
15
  var lastForcedRefreshAt = 0;
10
16
  var keyCacheLifetime = 5 * 60 * 1e3;
11
17
  var forcedRefreshInterval = 60 * 1e3;
18
+ var verificationMessages = {
19
+ "Invalid signature": "Handshake token signature is invalid",
20
+ "Token expired": "Handshake token has expired",
21
+ "Token not yet valid": "Handshake token is not yet valid",
22
+ "Invalid exp value": "Handshake token expiration is invalid",
23
+ "Invalid nbf value": "Handshake token not-before claim is invalid"
24
+ };
12
25
  async function verifyCloudHandshake(token, options) {
13
- const { header } = decode(token);
14
- if (header.alg !== "RS256" || typeof header.kid !== "string")
15
- throw new Error("Invalid handshake token header");
26
+ const { header } = decodeHandshakeToken(token);
27
+ if (!isRecord(header) || header.alg !== "RS256" || typeof header.kid !== "string" || !header.kid)
28
+ throw new CloudHandshakeError("Handshake token header is invalid");
16
29
  let key = (await cloudKeys()).find((key2) => key2.kid === header.kid);
17
30
  if (!key) {
18
31
  key = (await refreshCloudKeys()).find((key2) => key2.kid === header.kid);
19
32
  }
20
- if (!key) throw new Error("Unknown handshake signing key");
21
- const payload = await verify(token, key, { algorithms: ["RS256"] });
22
- if (!isRecord(payload) || payload.purpose !== "handshake" || payload.handshake_id !== options.handshakeId || payload.aud !== options.clientId || payload.iss !== normalizedCloudUrl() || payload.origin !== options.origin || typeof payload.project !== "string" || typeof payload.iat !== "number" || typeof payload.exp !== "number") {
23
- throw new Error("Invalid handshake token claims");
33
+ if (!key)
34
+ throw new CloudHandshakeError("Handshake token signing key is unknown");
35
+ const payload = await verify(token, key, { algorithms: ["RS256"] }).catch(
36
+ (cause) => {
37
+ throw verificationError(cause);
38
+ }
39
+ );
40
+ if (!isRecord(payload))
41
+ throw new CloudHandshakeError("Handshake token payload is invalid");
42
+ assertClaim("purpose", payload.purpose, "handshake");
43
+ assertClaim("ID", payload.handshake_id, options.handshakeId);
44
+ assertClaim("audience", payload.aud, options.clientId);
45
+ assertClaim("issuer", payload.iss, normalizedCloudUrl());
46
+ assertClaim("origin", payload.origin, options.origin);
47
+ if (typeof payload.project !== "string")
48
+ throw new CloudHandshakeError("Handshake project claim is invalid");
49
+ if (typeof payload.iat !== "number")
50
+ throw new CloudHandshakeError("Handshake issued-at claim is invalid");
51
+ if (typeof payload.exp !== "number")
52
+ throw new CloudHandshakeError("Handshake expiration claim is invalid");
53
+ }
54
+ function assertClaim(name, received, expected) {
55
+ if (received !== expected)
56
+ throw new CloudHandshakeError(
57
+ `Handshake ${name} mismatch: expected ${expected}, received ${String(received)}`
58
+ );
59
+ }
60
+ function decodeHandshakeToken(token) {
61
+ try {
62
+ return decode(token);
63
+ } catch (cause) {
64
+ throw new CloudHandshakeError("Handshake token is malformed", { cause });
24
65
  }
25
66
  }
67
+ function verificationError(cause) {
68
+ const reason = cause instanceof Error && verificationMessages[cause.message];
69
+ return reason ? new CloudHandshakeError(reason, { cause }) : new CloudHandshakeServiceError("Could not verify handshake token", {
70
+ cause
71
+ });
72
+ }
26
73
  function cloudKeys() {
27
74
  if (keysPromise && (keysExpireAt === 0 || Date.now() < keysExpireAt))
28
75
  return keysPromise;
@@ -40,16 +87,24 @@ function loadCloudKeys() {
40
87
  keysExpireAt = 0;
41
88
  keysPromise = fetch(cloudConfig.jwks).then(async (response) => {
42
89
  if (!response.ok)
43
- throw new Error(`Could not load handshake keys: ${response.status}`);
90
+ throw new CloudHandshakeServiceError(
91
+ `Could not load handshake signing keys: ${response.status}`
92
+ );
44
93
  const body = await response.json();
45
94
  if (!isRecord(body) || !Array.isArray(body.keys))
46
- throw new Error("Invalid handshake key response");
95
+ throw new CloudHandshakeServiceError(
96
+ "Cloud returned invalid handshake signing keys"
97
+ );
47
98
  keysExpireAt = Date.now() + keyCacheLifetime;
48
99
  return body.keys.filter(isCloudKey);
49
100
  }).catch((cause) => {
50
101
  keysPromise = void 0;
51
102
  keysExpireAt = 0;
52
- throw cause;
103
+ if (cause instanceof CloudHandshakeServiceError) throw cause;
104
+ throw new CloudHandshakeServiceError(
105
+ "Could not load handshake signing keys",
106
+ { cause }
107
+ );
53
108
  });
54
109
  return keysPromise;
55
110
  }
@@ -60,5 +115,7 @@ function normalizedCloudUrl() {
60
115
  return cloudConfig.url.replace(/\/$/, "");
61
116
  }
62
117
  export {
118
+ CloudHandshakeError,
119
+ CloudHandshakeServiceError,
63
120
  verifyCloudHandshake
64
121
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-XMOVGDFI.js";
3
+ } from "../chunks/chunk-A6M3PNJT.js";
4
4
  import "../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/cloud/CloudRemote.ts
@@ -15,12 +15,17 @@ import { HttpError } from "alinea/core/HttpError";
15
15
  import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
16
16
  import { ReadonlyTree } from "alinea/core/source/Tree";
17
17
  import { base64 } from "alinea/core/util/Encoding";
18
- import { entries, values } from "alinea/core/util/Objects";
18
+ import { entries } from "alinea/core/util/Objects";
19
19
  import { Workspace } from "alinea/core/Workspace";
20
+ import { MediaLocation } from "alinea/core/media/MediaLocation";
20
21
  import { Response } from "@alinea/iso";
21
22
  import { AuthResultType } from "./AuthResult.js";
22
23
  import { cloudConfig } from "./CloudConfig.js";
23
- import { verifyCloudHandshake } from "./CloudHandshake.js";
24
+ import {
25
+ CloudHandshakeError,
26
+ CloudHandshakeServiceError,
27
+ verifyCloudHandshake
28
+ } from "./CloudHandshake.js";
24
29
  var CloudRemote = class extends OAuth2 {
25
30
  #context;
26
31
  #config;
@@ -125,9 +130,9 @@ var CloudRemote = class extends OAuth2 {
125
130
  }
126
131
  // The cloud server will request a handshake confirmation on this route
127
132
  case AuthAction.Handshake: {
128
- const handShakeId = url.searchParams.get("handshake_id");
133
+ const handshakeId = url.searchParams.get("handshake_id");
129
134
  const handshakeToken = url.searchParams.get("handshake_token");
130
- if (!handShakeId || !handshakeToken)
135
+ if (!handshakeId || !handshakeToken)
131
136
  throw new HttpError(
132
137
  400,
133
138
  "Provide valid handshake credentials to initiate handshake"
@@ -138,14 +143,22 @@ var CloudRemote = class extends OAuth2 {
138
143
  try {
139
144
  await verifyCloudHandshake(handshakeToken, {
140
145
  clientId,
141
- handshakeId: handShakeId,
146
+ handshakeId,
142
147
  origin: new URL(ctx.handlerUrl).origin
143
148
  });
144
149
  } catch (cause) {
145
- throw new HttpError(401, "Invalid handshake token", { cause });
150
+ if (cause instanceof CloudHandshakeError)
151
+ throw new HttpError(
152
+ 401,
153
+ `Invalid handshake token: ${cause.message}`,
154
+ { cause }
155
+ );
156
+ if (cause instanceof CloudHandshakeServiceError)
157
+ throw new HttpError(503, cause.message, { cause });
158
+ throw new HttpError(500, "Could not verify handshake token", { cause });
146
159
  }
147
160
  const body = {
148
- handshake_id: handShakeId,
161
+ handshake_id: handshakeId,
149
162
  handshake_token: handshakeToken,
150
163
  status: {
151
164
  version: package_default.version,
@@ -157,9 +170,9 @@ var CloudRemote = class extends OAuth2 {
157
170
  };
158
171
  }),
159
172
  enableOAuth2: true,
160
- sourceDirectories: values(config.workspaces).flatMap((workspace) => {
161
- const { source, mediaDir } = Workspace.data(workspace);
162
- return [source, mediaDir];
173
+ sourceDirectories: entries(config.workspaces).flatMap(([key, workspace]) => {
174
+ const { source } = Workspace.data(workspace);
175
+ return [source, MediaLocation.directory(config, key)];
163
176
  }).filter(Boolean)
164
177
  }
165
178
  };
@@ -27,17 +27,17 @@ import {
27
27
 
28
28
  // src/components/Calendar.module.css
29
29
  var Calendar_default = {
30
+ "Calendar-heading": "alinea-Calendar-heading",
31
+ "CalendarHeading": "alinea-Calendar-heading",
30
32
  "Calendar-button": "alinea-Calendar-button",
31
33
  "CalendarButton": "alinea-Calendar-button",
32
34
  "Calendar-grid": "alinea-Calendar-grid",
33
35
  "CalendarGrid": "alinea-Calendar-grid",
34
- "Calendar-cell": "alinea-Calendar-cell",
35
- "CalendarCell": "alinea-Calendar-cell",
36
36
  "Calendar": "alinea-Calendar",
37
37
  "Calendar-header": "alinea-Calendar-header",
38
38
  "CalendarHeader": "alinea-Calendar-header",
39
- "Calendar-heading": "alinea-Calendar-heading",
40
- "CalendarHeading": "alinea-Calendar-heading"
39
+ "Calendar-cell": "alinea-Calendar-cell",
40
+ "CalendarCell": "alinea-Calendar-cell"
41
41
  };
42
42
 
43
43
  // src/components/Calendar.tsx
@@ -16,11 +16,11 @@ import "../chunks/chunk-CQ52GEHY.js";
16
16
 
17
17
  // src/components/Checkbox.module.css
18
18
  var Checkbox_default = {
19
- "Checkbox": "alinea-Checkbox",
19
+ "Checkbox-mark": "alinea-Checkbox-mark",
20
+ "CheckboxMark": "alinea-Checkbox-mark",
20
21
  "Checkbox-box": "alinea-Checkbox-box",
21
22
  "CheckboxBox": "alinea-Checkbox-box",
22
- "Checkbox-mark": "alinea-Checkbox-mark",
23
- "CheckboxMark": "alinea-Checkbox-mark"
23
+ "Checkbox": "alinea-Checkbox"
24
24
  };
25
25
 
26
26
  // src/components/Checkbox.tsx
@@ -20,8 +20,8 @@ import { ColorSwatch } from "./ColorSwatch.js";
20
20
 
21
21
  // src/components/ColorSwatchPicker.module.css
22
22
  var ColorSwatchPicker_default = {
23
- "ColorSwatchPicker": "alinea-ColorSwatchPicker",
24
- "ColorSwatchPickerItem": "alinea-ColorSwatchPickerItem"
23
+ "ColorSwatchPickerItem": "alinea-ColorSwatchPickerItem",
24
+ "ColorSwatchPicker": "alinea-ColorSwatchPicker"
25
25
  };
26
26
 
27
27
  // src/components/ColorSwatchPicker.tsx
@@ -29,24 +29,24 @@ import {
29
29
 
30
30
  // src/components/ComboBox.module.css
31
31
  var ComboBox_default = {
32
- "ComboBoxPopover-listbox": "alinea-ComboBoxPopover-listbox",
33
- "ComboBoxPopoverListbox": "alinea-ComboBoxPopover-listbox",
34
32
  "ComboBoxItem-check": "alinea-ComboBoxItem-check",
35
33
  "ComboBoxItemCheck": "alinea-ComboBoxItem-check",
36
- "ComboBoxTrigger-actions": "alinea-ComboBoxTrigger-actions",
37
- "ComboBoxTriggerActions": "alinea-ComboBoxTrigger-actions",
38
34
  "ComboBox": "alinea-ComboBox",
39
- "ComboBoxTrigger-button-value": "alinea-ComboBoxTrigger-button-value",
40
- "ComboBoxTriggerButtonValue": "alinea-ComboBoxTrigger-button-value",
41
35
  "ComboBoxTrigger-button-arrow": "alinea-ComboBoxTrigger-button-arrow",
42
36
  "ComboBoxTriggerButtonArrow": "alinea-ComboBoxTrigger-button-arrow",
43
- "ComboBoxTrigger": "alinea-ComboBoxTrigger",
37
+ "ComboBoxTrigger-input": "alinea-ComboBoxTrigger-input",
38
+ "ComboBoxTriggerInput": "alinea-ComboBoxTrigger-input",
44
39
  "ComboBoxClear": "alinea-ComboBoxClear",
45
40
  "ComboBoxItem": "alinea-ComboBoxItem",
41
+ "ComboBoxTrigger-actions": "alinea-ComboBoxTrigger-actions",
42
+ "ComboBoxTriggerActions": "alinea-ComboBoxTrigger-actions",
43
+ "ComboBoxTrigger-button-value": "alinea-ComboBoxTrigger-button-value",
44
+ "ComboBoxTriggerButtonValue": "alinea-ComboBoxTrigger-button-value",
45
+ "ComboBoxTrigger": "alinea-ComboBoxTrigger",
46
46
  "ComboBoxTrigger-button": "alinea-ComboBoxTrigger-button",
47
47
  "ComboBoxTriggerButton": "alinea-ComboBoxTrigger-button",
48
- "ComboBoxTrigger-input": "alinea-ComboBoxTrigger-input",
49
- "ComboBoxTriggerInput": "alinea-ComboBoxTrigger-input"
48
+ "ComboBoxPopover-listbox": "alinea-ComboBoxPopover-listbox",
49
+ "ComboBoxPopoverListbox": "alinea-ComboBoxPopover-listbox"
50
50
  };
51
51
 
52
52
  // src/components/ComboBox.tsx
@@ -19,11 +19,11 @@ import "../chunks/chunk-CQ52GEHY.js";
19
19
 
20
20
  // src/components/DateField.module.css
21
21
  var DateField_default = {
22
- "DateField-input": "alinea-DateField-input",
23
- "DateFieldInput": "alinea-DateField-input",
24
- "DateField": "alinea-DateField",
25
22
  "DateField-input-segment": "alinea-DateField-input-segment",
26
- "DateFieldInputSegment": "alinea-DateField-input-segment"
23
+ "DateFieldInputSegment": "alinea-DateField-input-segment",
24
+ "DateField": "alinea-DateField",
25
+ "DateField-input": "alinea-DateField-input",
26
+ "DateFieldInput": "alinea-DateField-input"
27
27
  };
28
28
 
29
29
  // src/components/DateField.tsx
@@ -25,14 +25,14 @@ import { Calendar } from "./Calendar.js";
25
25
 
26
26
  // src/components/DatePicker.module.css
27
27
  var DatePicker_default = {
28
- "DatePicker": "alinea-DatePicker",
29
28
  "DatePicker-input": "alinea-DatePicker-input",
30
29
  "DatePickerInput": "alinea-DatePicker-input",
31
30
  "DatePicker-input-segment": "alinea-DatePicker-input-segment",
32
31
  "DatePickerInputSegment": "alinea-DatePicker-input-segment",
33
- "Icon": "alinea-Icon",
34
32
  "DatePicker-trigger": "alinea-DatePicker-trigger",
35
- "DatePickerTrigger": "alinea-DatePicker-trigger"
33
+ "DatePickerTrigger": "alinea-DatePicker-trigger",
34
+ "Icon": "alinea-Icon",
35
+ "DatePicker": "alinea-DatePicker"
36
36
  };
37
37
 
38
38
  // src/components/DatePicker.tsx
@@ -25,18 +25,18 @@ import { RangeCalendar } from "./Calendar.js";
25
25
 
26
26
  // src/components/DateRangePicker.module.css
27
27
  var DateRangePicker_default = {
28
- "DateRangePicker-input-segment": "alinea-DateRangePicker-input-segment",
29
- "DateRangePickerInputSegment": "alinea-DateRangePicker-input-segment",
30
- "DateRangePicker-trigger": "alinea-DateRangePicker-trigger",
31
- "DateRangePickerTrigger": "alinea-DateRangePicker-trigger",
32
- "DateRangePicker-inputs": "alinea-DateRangePicker-inputs",
33
- "DateRangePickerInputs": "alinea-DateRangePicker-inputs",
34
28
  "DateRangePicker-input": "alinea-DateRangePicker-input",
35
29
  "DateRangePickerInput": "alinea-DateRangePicker-input",
30
+ "DateRangePicker-inputs": "alinea-DateRangePicker-inputs",
31
+ "DateRangePickerInputs": "alinea-DateRangePicker-inputs",
36
32
  "DateRangePicker-separator": "alinea-DateRangePicker-separator",
37
33
  "DateRangePickerSeparator": "alinea-DateRangePicker-separator",
38
- "Icon": "alinea-Icon",
39
- "DateRangePicker": "alinea-DateRangePicker"
34
+ "DateRangePicker": "alinea-DateRangePicker",
35
+ "DateRangePicker-trigger": "alinea-DateRangePicker-trigger",
36
+ "DateRangePickerTrigger": "alinea-DateRangePicker-trigger",
37
+ "DateRangePicker-input-segment": "alinea-DateRangePicker-input-segment",
38
+ "DateRangePickerInputSegment": "alinea-DateRangePicker-input-segment",
39
+ "Icon": "alinea-Icon"
40
40
  };
41
41
 
42
42
  // src/components/DateRangePicker.tsx
@@ -25,17 +25,17 @@ import { Icon } from "./Icon.js";
25
25
  var Disclosure_default = {
26
26
  "Disclosure-panel-content": "alinea-Disclosure-panel-content",
27
27
  "DisclosurePanelContent": "alinea-Disclosure-panel-content",
28
+ "Disclosure-chevron": "alinea-Disclosure-chevron",
29
+ "DisclosureChevron": "alinea-Disclosure-chevron",
30
+ "Disclosure-heading": "alinea-Disclosure-heading",
31
+ "DisclosureHeading": "alinea-Disclosure-heading",
28
32
  "Disclosure-title": "alinea-Disclosure-title",
29
33
  "DisclosureTitle": "alinea-Disclosure-title",
30
- "Disclosure-panel": "alinea-Disclosure-panel",
31
- "DisclosurePanel": "alinea-Disclosure-panel",
34
+ "Disclosure": "alinea-Disclosure",
32
35
  "Disclosure-trigger": "alinea-Disclosure-trigger",
33
36
  "DisclosureTrigger": "alinea-Disclosure-trigger",
34
- "Disclosure": "alinea-Disclosure",
35
- "Disclosure-heading": "alinea-Disclosure-heading",
36
- "DisclosureHeading": "alinea-Disclosure-heading",
37
- "Disclosure-chevron": "alinea-Disclosure-chevron",
38
- "DisclosureChevron": "alinea-Disclosure-chevron"
37
+ "Disclosure-panel": "alinea-Disclosure-panel",
38
+ "DisclosurePanel": "alinea-Disclosure-panel"
39
39
  };
40
40
 
41
41
  // src/components/Disclosure.tsx