alinea 2.0.0-preview.4 → 2.0.0-preview.6

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 (187) 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 +137 -137
  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/GithubApi.js +16 -7
  14. package/dist/backend/api/ProxyMedia.d.ts +3 -0
  15. package/dist/backend/api/ProxyMedia.js +39 -0
  16. package/dist/backend/api/S3Uploads.d.ts +1 -1
  17. package/dist/backend/router/Router.js +2 -0
  18. package/dist/backend/util/Syncable.js +1 -1
  19. package/dist/bundled.d.ts +166 -73
  20. package/dist/chunks/{chunk-XMOVGDFI.js → chunk-7IOKMUKO.js} +1 -1
  21. package/dist/chunks/{chunk-O7XWTMCB.js → chunk-D6RGFNUW.js} +73 -72
  22. package/dist/chunks/{chunk-VXZM6V4P.js → chunk-OFEKTDRF.js} +41 -41
  23. package/dist/cli/Generate.js +3 -4
  24. package/dist/cli/Serve.js +3 -2
  25. package/dist/cli/bin.js +1 -1
  26. package/dist/cli/generate/FillCache.d.ts +1 -1
  27. package/dist/cli/generate/FillCache.js +20 -9
  28. package/dist/cli/generate/GenerateDashboard.js +1 -0
  29. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  30. package/dist/cli/serve/CreateLocalServer.js +42 -34
  31. package/dist/cli/serve/DevServer.js +1 -0
  32. package/dist/cli/static/init/cms.js +1 -4
  33. package/dist/cli/util/ContentType.d.ts +1 -0
  34. package/dist/cli/util/ContentType.js +36 -0
  35. package/dist/cli/util/Emitter.js +1 -1
  36. package/dist/cloud/CloudHandshake.d.ts +6 -0
  37. package/dist/cloud/CloudHandshake.js +67 -10
  38. package/dist/cloud/CloudRemote.js +24 -11
  39. package/dist/components/Breadcrumbs.js +7 -7
  40. package/dist/components/Button.js +5 -5
  41. package/dist/components/Calendar.js +7 -7
  42. package/dist/components/Checkbox.js +8 -8
  43. package/dist/components/CheckboxGroup.js +5 -5
  44. package/dist/components/ColorSwatch.js +5 -5
  45. package/dist/components/ColorSwatchPicker.js +7 -7
  46. package/dist/components/ComboBox.js +14 -14
  47. package/dist/components/DateField.js +8 -8
  48. package/dist/components/DatePicker.js +10 -10
  49. package/dist/components/DateRangePicker.js +11 -11
  50. package/dist/components/Dialog.js +5 -5
  51. package/dist/components/Disclosure.js +11 -11
  52. package/dist/components/DropZone.js +5 -5
  53. package/dist/components/Label.js +9 -9
  54. package/dist/components/Link.js +5 -5
  55. package/dist/components/List.d.ts +1 -1
  56. package/dist/components/List.js +37 -36
  57. package/dist/components/Menu.js +9 -9
  58. package/dist/components/Modal.js +8 -8
  59. package/dist/components/MultipleSelect.js +33 -33
  60. package/dist/components/NumberField.js +11 -11
  61. package/dist/components/Popover.js +5 -5
  62. package/dist/components/ProgressCircle.js +8 -8
  63. package/dist/components/RadioGroup.js +7 -7
  64. package/dist/components/SearchField.js +10 -10
  65. package/dist/components/Select.js +12 -12
  66. package/dist/components/Surface.js +3 -3
  67. package/dist/components/Switch.js +7 -7
  68. package/dist/components/Table.js +11 -11
  69. package/dist/components/Tabs.js +9 -9
  70. package/dist/components/TagGroup.js +7 -7
  71. package/dist/components/TextField.js +10 -10
  72. package/dist/components/TimeField.js +5 -5
  73. package/dist/components/Toggle.js +5 -5
  74. package/dist/components/Toolbar.js +6 -6
  75. package/dist/components/Tooltip.js +8 -8
  76. package/dist/components/Tree.js +13 -13
  77. package/dist/core/Config.d.ts +10 -3
  78. package/dist/core/Config.js +16 -1
  79. package/dist/core/Graph.d.ts +2 -2
  80. package/dist/core/Role.js +16 -14
  81. package/dist/core/TextDoc.d.ts +2 -0
  82. package/dist/core/TextDoc.js +1 -0
  83. package/dist/core/Workspace.d.ts +6 -5
  84. package/dist/core/db/EntryIndex.d.ts +3 -0
  85. package/dist/core/db/EntryIndex.js +55 -0
  86. package/dist/core/db/EntryTransaction.js +34 -32
  87. package/dist/core/db/EntryUrlConflictError.js +1 -1
  88. package/dist/core/db/LinkResolver.d.ts +8 -2
  89. package/dist/core/db/LinkResolver.js +27 -5
  90. package/dist/core/field/RichTextField.js +16 -10
  91. package/dist/core/media/MediaLocation.d.ts +9 -15
  92. package/dist/core/media/MediaLocation.js +29 -19
  93. package/dist/core/media/MediaTypes.js +1 -2
  94. package/dist/dashboard/App.js +4 -4
  95. package/dist/dashboard/app/ActivityStatus.js +31 -37
  96. package/dist/dashboard/app/AppShell.js +2 -2
  97. package/dist/dashboard/app/AuthView.js +12 -12
  98. package/dist/dashboard/app/Badge.js +2 -2
  99. package/dist/dashboard/app/CompactField.js +9 -9
  100. package/dist/dashboard/app/DashboardErrorBoundary.js +6 -6
  101. package/dist/dashboard/app/EntryFields.d.ts +3 -1
  102. package/dist/dashboard/app/EntryFields.js +11 -8
  103. package/dist/dashboard/app/EntryHeader.js +116 -84
  104. package/dist/dashboard/app/EntryHeaderActions.d.ts +21 -4
  105. package/dist/dashboard/app/EntryHeaderActions.js +48 -27
  106. package/dist/dashboard/app/EntryReferences.js +6 -6
  107. package/dist/dashboard/app/EntrySidebar.js +15 -15
  108. package/dist/dashboard/app/EntrySidebarPreview.js +4 -4
  109. package/dist/dashboard/app/EntryTranslationBanner.d.ts +3 -1
  110. package/dist/dashboard/app/EntryTranslationBanner.js +23 -20
  111. package/dist/dashboard/app/Explorer.d.ts +4 -2
  112. package/dist/dashboard/app/Explorer.js +53 -49
  113. package/dist/dashboard/app/ExplorerCards.js +31 -31
  114. package/dist/dashboard/app/ExplorerFileCard.js +16 -16
  115. package/dist/dashboard/app/ExplorerList.js +14 -15
  116. package/dist/dashboard/app/ExplorerModal.js +4 -4
  117. package/dist/dashboard/app/ExplorerTable.js +54 -54
  118. package/dist/dashboard/app/LinkPicker.js +5 -5
  119. package/dist/dashboard/app/LocaleMenu.d.ts +7 -0
  120. package/dist/dashboard/app/LocaleMenu.js +34 -18
  121. package/dist/dashboard/app/ReadOnlyBadge.d.ts +4 -0
  122. package/dist/dashboard/app/ReadOnlyBadge.js +33 -0
  123. package/dist/dashboard/app/SidebarTree.d.ts +3 -6
  124. package/dist/dashboard/app/SidebarTree.js +62 -57
  125. package/dist/dashboard/app/WorkspaceMenu.js +16 -16
  126. package/dist/dashboard/app/WorkspaceRoots.js +21 -21
  127. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -3
  128. package/dist/dashboard/app/editor/FileEditor.js +24 -27
  129. package/dist/dashboard/app/editor/FilePreview.js +4 -4
  130. package/dist/dashboard/app/modals/CreateEntry.js +5 -5
  131. package/dist/dashboard/app/pages/EntryPage.js +72 -29
  132. package/dist/dashboard/app/pages/RootPage.js +3 -3
  133. package/dist/dashboard/app/pages/SplashPage.js +45 -45
  134. package/dist/dashboard/app/pages/UsersPage.js +36 -36
  135. package/dist/dashboard/app/ui/DashboardModal.js +21 -21
  136. package/dist/dashboard/app/ui/Rail.js +5 -5
  137. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  138. package/dist/dashboard/atoms/activity.d.ts +0 -2
  139. package/dist/dashboard/atoms/activity.js +4 -10
  140. package/dist/dashboard/atoms/editor.d.ts +2 -1
  141. package/dist/dashboard/atoms/editor.js +4 -2
  142. package/dist/dashboard/atoms/entry.d.ts +2 -0
  143. package/dist/dashboard/atoms/entry.js +32 -14
  144. package/dist/dashboard/atoms/explorer.d.ts +4 -3
  145. package/dist/dashboard/atoms/explorer.js +61 -22
  146. package/dist/dashboard/atoms/link.d.ts +1 -1
  147. package/dist/dashboard/atoms/link.js +39 -55
  148. package/dist/dashboard/atoms/root.d.ts +7 -2
  149. package/dist/dashboard/atoms/root.js +51 -16
  150. package/dist/dashboard/atoms/upload.d.ts +1 -0
  151. package/dist/dashboard/atoms/upload.js +2 -1
  152. package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
  153. package/dist/dashboard/boot/DashboardWorker.js +2 -28
  154. package/dist/dashboard/fixture/cms.js +1 -4
  155. package/dist/dashboard/hook/UseSaveShortcut.d.ts +1 -0
  156. package/dist/dashboard/hook/UseSaveShortcut.js +24 -0
  157. package/dist/dashboard/hooks.d.ts +1 -1
  158. package/dist/dashboard/hooks.js +12 -3
  159. package/dist/field/check/CheckField.view.js +2 -2
  160. package/dist/field/code/CodeField.view.js +11 -11
  161. package/dist/field/date/DateField.view.js +5 -5
  162. package/dist/field/link/ImageLink.js +1 -3
  163. package/dist/field/link/LinkField.view.js +88 -62
  164. package/dist/field/list/ListField.view.js +25 -25
  165. package/dist/field/metadata/MetadataField.d.ts +3 -3
  166. package/dist/field/metadata/MetadataField.view.js +27 -20
  167. package/dist/field/object/ObjectField.view.js +11 -4
  168. package/dist/field/richtext/PickTextLink.js +5 -2
  169. package/dist/field/richtext/ReferenceLink.d.ts +2 -0
  170. package/dist/field/richtext/ReferenceLink.js +5 -0
  171. package/dist/field/richtext/RichTextBlock.js +2 -2
  172. package/dist/field/richtext/RichTextExtensions.js +24 -24
  173. package/dist/field/richtext/RichTextField.view.js +2 -2
  174. package/dist/field/richtext/RichTextInsertMenu.js +3 -3
  175. package/dist/field/richtext/extensions/Link.d.ts +1 -0
  176. package/dist/field/richtext/extensions/Link.js +1 -0
  177. package/dist/field/select/SelectField.view.js +1 -1
  178. package/dist/field/time/TimeField.d.ts +0 -2
  179. package/dist/field/time/TimeField.view.js +1 -1
  180. package/dist/index.css +44 -38
  181. package/dist/next.cjs +96 -19
  182. package/dist/picker/entry/EntryPicker.js +10 -12
  183. package/dist/picker/entry/EntryReference.d.ts +2 -0
  184. package/dist/picker/entry/EntryReference.js +1 -0
  185. package/dist/types.d.ts +0 -1
  186. package/package.json +1 -1
  187. package/dist/chunks/{chunk-6V4TAD2N.js → chunk-DNJX6CRV.js} +4 -4
@@ -1,6 +1,7 @@
1
1
  import "../../chunks/chunk-CQ52GEHY.js";
2
2
 
3
3
  // src/adapter/next/with-alinea.ts
4
+ import { join } from "alinea/core/util/Paths";
4
5
  import { readFileSync } from "node:fs";
5
6
  import { createRequire } from "node:module";
6
7
  import { resolve } from "node:path";
@@ -26,23 +27,13 @@ function withAlinea(config = {}) {
26
27
  } catch {
27
28
  console.warn("Alinea could not determine Next.js version, assuming 15+");
28
29
  }
29
- const imagesConfig = config.images ?? {};
30
- const remotePatterns = [
31
- ...imagesConfig.remotePatterns ?? [],
32
- {
33
- protocol: "https",
34
- hostname: "uploads.alinea.cloud"
35
- }
36
- ];
37
- const images = {
38
- ...imagesConfig,
39
- remotePatterns
40
- };
41
30
  const redirects = adminPath ? createRedirects(config, adminPath) : config.redirects;
42
- const rewrites = adminPath ? createRewrites(config, adminPath) : config.rewrites;
31
+ const rewrites = adminPath ? createRewrites(config, adminPath, settings.handlerUrl) : config.rewrites;
32
+ const images = adminPath ? createImages(config, adminPath) : config.images;
43
33
  const env = settings ? {
44
34
  ...config.env,
45
- ALINEA_ADMIN_PATH: settings.adminPath
35
+ ALINEA_ADMIN_PATH: settings.adminPath,
36
+ ALINEA_HANDLER_URL: settings.handlerUrl
46
37
  } : config.env;
47
38
  if (nextVersion < 15)
48
39
  return {
@@ -54,9 +45,9 @@ function withAlinea(config = {}) {
54
45
  "@alinea/generated"
55
46
  ]
56
47
  },
57
- images,
58
48
  redirects,
59
49
  rewrites,
50
+ images,
60
51
  env
61
52
  };
62
53
  return {
@@ -65,12 +56,32 @@ function withAlinea(config = {}) {
65
56
  ...config.serverExternalPackages ?? [],
66
57
  "@alinea/generated"
67
58
  ],
68
- images,
69
59
  redirects,
70
60
  rewrites,
61
+ images,
71
62
  env
72
63
  };
73
64
  }
65
+ function createImages(config, adminPath) {
66
+ const filePattern = `${adminPath}/file/**`;
67
+ const localPatterns = config.images?.localPatterns;
68
+ if (!localPatterns)
69
+ return {
70
+ ...config.images,
71
+ localPatterns: [{ pathname: filePattern }, { pathname: "/**" }]
72
+ };
73
+ if (localPatterns.some((pattern) => pattern.pathname === filePattern))
74
+ return config.images;
75
+ return {
76
+ ...config.images,
77
+ localPatterns: [
78
+ ...localPatterns,
79
+ {
80
+ pathname: filePattern
81
+ }
82
+ ]
83
+ };
84
+ }
74
85
  function createRedirects(config, adminPath) {
75
86
  const dev = process.env.ALINEA_DEV_SERVER;
76
87
  if (!dev) return config.redirects;
@@ -91,7 +102,7 @@ var emptyRewrites = {
91
102
  afterFiles: [],
92
103
  fallback: []
93
104
  };
94
- function createRewrites(config, adminPath) {
105
+ function createRewrites(config, adminPath, handlerUrl) {
95
106
  return async () => {
96
107
  const devServer = process.env.ALINEA_DEV_SERVER;
97
108
  const nodeEnv = process.env.NODE_ENV;
@@ -112,6 +123,10 @@ function createRewrites(config, adminPath) {
112
123
  ...rewrites,
113
124
  beforeFiles: [
114
125
  ...rewrites.beforeFiles,
126
+ {
127
+ source: `${adminPath}/file/:file*`,
128
+ destination: `${devServer}/api?file=:file*&delivery=proxy`
129
+ },
115
130
  {
116
131
  source: `${adminPath}/:path*`,
117
132
  destination: `${devServer}${adminPath}/:path*`
@@ -121,6 +136,14 @@ function createRewrites(config, adminPath) {
121
136
  }
122
137
  return {
123
138
  ...rewrites,
139
+ beforeFiles: [
140
+ ...rewrites.beforeFiles,
141
+ {
142
+ source: `${adminPath}/file/:file*`,
143
+ // Next's internal image optimizer does not follow redirects.
144
+ destination: `${handlerUrl}?file=:file*&delivery=proxy`
145
+ }
146
+ ],
124
147
  afterFiles: [
125
148
  ...rewrites.afterFiles,
126
149
  {
@@ -135,11 +158,12 @@ function resolveSettings(config) {
135
158
  const adminPath = config.env?.ALINEA_ADMIN_PATH ?? process.env.ALINEA_ADMIN_PATH;
136
159
  if (!adminPath) return;
137
160
  return {
138
- adminPath: normalizeBasePath(adminPath)
161
+ adminPath: normalizeBasePath(adminPath),
162
+ handlerUrl: config.env?.ALINEA_HANDLER_URL ?? process.env.ALINEA_HANDLER_URL ?? "/api/cms"
139
163
  };
140
164
  }
141
165
  function normalizeBasePath(value) {
142
- return value.startsWith("/") ? value : `/${value}`;
166
+ return join("/", value, ".");
143
167
  }
144
168
  export {
145
169
  createCMS,
@@ -3,6 +3,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
3
3
  // src/adapter/vite/plugin.ts
4
4
  import { nodeHandler } from "alinea/backend/router/NodeHandler";
5
5
  import { createDevServer } from "alinea/cli/serve/DevServer";
6
+ import { Config } from "alinea/core/Config";
6
7
  function createFallbackUrl(server) {
7
8
  const address = server.httpServer?.address();
8
9
  const protocol = server.config.server.https ? "https" : "http";
@@ -28,19 +29,23 @@ function waitForServerUrl(server) {
28
29
  server.httpServer?.once("listening", resolveUrl);
29
30
  });
30
31
  }
31
- function isAlineaRequest(request) {
32
+ function isAlineaRequest(request, adminPath) {
32
33
  const url = new URL(request.url);
33
- return url.pathname === "/admin" || url.pathname.startsWith("/admin/") || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
34
+ return url.pathname === adminPath || url.pathname.startsWith(`${adminPath}/`) || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
34
35
  }
35
36
  function alineaPlugin(dir) {
36
37
  let devServer;
38
+ let adminPath = "/admin";
37
39
  return {
38
40
  name: "alinea-plugin",
39
41
  configureServer(server) {
40
42
  const serverUrl = waitForServerUrl(server);
41
43
  const devServerPromise = createDevServer(dir, {
42
44
  cmd: "dev",
43
- dashboardUrl: serverUrl
45
+ dashboardUrl: serverUrl,
46
+ onAfterGenerate(_, config) {
47
+ adminPath = Config.adminPath(config);
48
+ }
44
49
  }).then((result) => {
45
50
  devServer = result;
46
51
  return result;
@@ -50,7 +55,7 @@ function alineaPlugin(dir) {
50
55
  });
51
56
  server.middlewares.use(
52
57
  nodeHandler(async (request) => {
53
- if (!isAlineaRequest(request)) return void 0;
58
+ if (!isAlineaRequest(request, adminPath)) return void 0;
54
59
  const localServer = await devServerPromise;
55
60
  return localServer.handle(request);
56
61
  })
@@ -17,16 +17,20 @@ import {
17
17
  BLOB_SEQUENCE_CONTENT_TYPE,
18
18
  encodeBlobSequence
19
19
  } from "alinea/core/BlobTransport";
20
+ import { Config } from "alinea/core/Config";
20
21
  import { developmentKeyHeader } from "alinea/core/Connection";
21
22
  import { ErrorCode, HttpError } from "alinea/core/HttpError";
23
+ import { MediaLocation } from "alinea/core/media/MediaLocation";
22
24
  import { assertUploadSize } from "alinea/core/media/UploadLimits";
23
25
  import { Permission, Policy } from "alinea/core/Role";
24
26
  import { getScope } from "alinea/core/Scope";
25
27
  import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
26
28
  import { base64 } from "alinea/core/util/Encoding";
27
29
  import { isRecord } from "alinea/core/util/Objects";
30
+ import { isAbsolute, normalize } from "alinea/core/util/Paths";
28
31
  import { InvalidCredentialsError, MissingCredentialsError } from "./Auth.js";
29
32
  import { HandleAction } from "./HandleAction.js";
33
+ import { isRedirectStatus, proxyMediaUrl } from "./api/ProxyMedia.js";
30
34
  import { applyPreview, decodePreviewRequest } from "./resolver/ParsePreview.js";
31
35
  import { compressResponse } from "./router/Router.js";
32
36
  import { createThrottledSync } from "./util/Syncable.js";
@@ -75,6 +79,73 @@ function createHandler({
75
79
  }
76
80
  });
77
81
  }
82
+ if (!params.has("action") && params.has("file") && (request.method === "GET" || request.method === "HEAD")) {
83
+ const file = string(params.get("file"));
84
+ const proxy = params.get("delivery") === "proxy";
85
+ const normalized = normalize(file);
86
+ if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../"))
87
+ throw new HttpError(400, "Invalid file path");
88
+ await periodicSync(cnx);
89
+ const requestedUrl = Config.filePathname(cms.config, normalized);
90
+ const entry = local.index.findByUrl(
91
+ requestedUrl,
92
+ (entry2) => entry2.type === "MediaFile" && entry2.status === "published" && entry2.main
93
+ );
94
+ if (!entry) return new Response("Not found", { status: 404 });
95
+ if (entry.url !== requestedUrl && !proxy)
96
+ return new Response(null, {
97
+ status: 308,
98
+ headers: { location: entry.url }
99
+ });
100
+ const wasBuilt = local.index.initialSync?.has(entry.filePath) ?? false;
101
+ const previewUrl = entry.data.previewUrl;
102
+ const location = entry.data.location;
103
+ if (typeof location !== "string")
104
+ return new Response("Not found", { status: 404 });
105
+ const previewSource = typeof previewUrl === "string" && previewUrl ? new URL(previewUrl, request.url).href : void 0;
106
+ async function proxyMedia(source2, cacheControl, isAllowed) {
107
+ const previewEntryId = uploadPreviewEntryId(
108
+ source2,
109
+ context.handlerUrl
110
+ );
111
+ let response;
112
+ if (previewEntryId && cnx.previewUpload) {
113
+ response = await cnx.previewUpload(previewEntryId);
114
+ } else {
115
+ response = await proxyMediaUrl(
116
+ request,
117
+ new URL(source2, request.url),
118
+ isAllowed
119
+ );
120
+ }
121
+ return mediaResponse(request, response, cacheControl);
122
+ }
123
+ if (!wasBuilt && previewSource) {
124
+ if (proxy) return proxyMedia(previewSource, "private, no-store");
125
+ return redirectFile(previewSource, "private, no-store");
126
+ }
127
+ const source = MediaLocation.sourceUrl(
128
+ cms.config,
129
+ entry.workspace,
130
+ location
131
+ );
132
+ if (!source || source === requestedUrl)
133
+ return new Response("Not found", { status: 404 });
134
+ if (!proxy) return redirectFile(source, "public, max-age=60");
135
+ const configuredBase = Config.baseUrl(
136
+ cms.config,
137
+ context.isDev ? "development" : "production"
138
+ );
139
+ const deliveryBase = configuredBase ?? context.handlerUrl;
140
+ const sourceUrl = new URL(source, deliveryBase);
141
+ const deliveryOrigin = new URL(deliveryBase).origin;
142
+ const handlerPath = context.handlerUrl.pathname;
143
+ return proxyMedia(
144
+ sourceUrl.href,
145
+ "public, max-age=60",
146
+ (url2) => url2.origin === deliveryOrigin && url2.pathname !== handlerPath && !url2.pathname.startsWith(`${handlerPath}/`)
147
+ );
148
+ }
78
149
  const action = params.get("action");
79
150
  const expectJson = () => {
80
151
  const acceptsJson = request.headers.get("accept")?.includes("application/json");
@@ -347,6 +418,41 @@ function createHandler({
347
418
  }
348
419
  };
349
420
  }
421
+ function redirectFile(location, cacheControl) {
422
+ return new Response(null, {
423
+ status: 307,
424
+ headers: { "cache-control": cacheControl, location }
425
+ });
426
+ }
427
+ function uploadPreviewEntryId(previewSource, handlerUrl) {
428
+ const previewUrl = new URL(previewSource);
429
+ if (previewUrl.origin !== handlerUrl.origin || previewUrl.pathname !== handlerUrl.pathname || previewUrl.searchParams.get("action") !== HandleAction.Upload)
430
+ return;
431
+ return previewUrl.searchParams.get("entryId") ?? void 0;
432
+ }
433
+ function mediaResponse(request, upstream, cacheControl) {
434
+ if (isRedirectStatus(upstream.status))
435
+ return new Response("Media backend returned a redirect", { status: 502 });
436
+ const responseHeaders = new Headers({
437
+ "cache-control": upstream.ok || upstream.status === 304 ? cacheControl : "no-store"
438
+ });
439
+ for (const name of [
440
+ "accept-ranges",
441
+ "content-disposition",
442
+ "content-length",
443
+ "content-range",
444
+ "content-type",
445
+ "etag",
446
+ "last-modified"
447
+ ]) {
448
+ const value = upstream.headers.get(name);
449
+ if (value) responseHeaders.set(name, value);
450
+ }
451
+ return new Response(request.method === "HEAD" ? null : upstream.body, {
452
+ status: upstream.status,
453
+ headers: responseHeaders
454
+ });
455
+ }
350
456
  function parseUser(input) {
351
457
  if (!isRecord(input)) throw new HttpError(400, "Expected user object");
352
458
  const { sub, name, email, roles } = input;
@@ -10,7 +10,7 @@ import {
10
10
  import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
11
11
  import { base64, btoa } from "alinea/core/util/Encoding";
12
12
  import { fileVersions } from "alinea/core/util/EntryFilenames";
13
- import { join } from "alinea/core/util/Paths";
13
+ import { join, relative } from "alinea/core/util/Paths";
14
14
  var GithubApi = class extends GithubSource {
15
15
  #options;
16
16
  constructor(options) {
@@ -129,8 +129,8 @@ Co-authored-by: ${user.name} <${user.email}>`;
129
129
  const prev = fileEntry?.previous_filename;
130
130
  if (prev) {
131
131
  const prefix = rootDir ? `${rootDir}/` : "";
132
- const relative = prev.startsWith(prefix) ? prev.slice(prefix.length) : prev;
133
- queue.push(...fileVersions(relative));
132
+ const relative2 = prev.startsWith(prefix) ? prev.slice(prefix.length) : prev;
133
+ queue.push(...fileVersions(relative2));
134
134
  }
135
135
  }
136
136
  }
@@ -194,6 +194,12 @@ Co-authored-by: ${user.name} <${user.email}>`;
194
194
  throw new ShaMismatchError(actualSha, expectedHeadOid);
195
195
  }
196
196
  }
197
+ console.error("Alinea GitHub commit failed", {
198
+ expectedHeadOid,
199
+ additions: additions.map((change) => change.path),
200
+ deletions: deletions.map((change) => change.path),
201
+ error
202
+ });
197
203
  throw error;
198
204
  });
199
205
  }
@@ -205,13 +211,13 @@ Co-authored-by: ${user.name} <${user.email}>`;
205
211
  switch (change.op) {
206
212
  case "addContent": {
207
213
  additions.push({
208
- path: join(this.contentLocation, change.path),
214
+ path: repositoryPath(this.contentLocation, change.path),
209
215
  contents: btoa(change.contents)
210
216
  });
211
217
  break;
212
218
  }
213
219
  case "uploadFile": {
214
- const file = join(rootDir, change.location);
220
+ const file = repositoryPath(rootDir, change.location);
215
221
  additions.push({
216
222
  path: file,
217
223
  contents: await this.#fetchUploadedContent(change.url)
@@ -219,12 +225,12 @@ Co-authored-by: ${user.name} <${user.email}>`;
219
225
  break;
220
226
  }
221
227
  case "deleteContent": {
222
- const file = join(this.contentLocation, change.path);
228
+ const file = repositoryPath(this.contentLocation, change.path);
223
229
  deletions.push({ path: file });
224
230
  break;
225
231
  }
226
232
  case "removeFile": {
227
- const file = join(rootDir, change.location);
233
+ const file = repositoryPath(rootDir, change.location);
228
234
  deletions.push({ path: file });
229
235
  break;
230
236
  }
@@ -255,6 +261,9 @@ Co-authored-by: ${user.name} <${user.email}>`;
255
261
  return base64.stringify(new Uint8Array(await response.arrayBuffer()));
256
262
  }
257
263
  };
264
+ function repositoryPath(...segments) {
265
+ return relative("/", join("/", ...segments));
266
+ }
258
267
  export {
259
268
  GithubApi
260
269
  };
@@ -0,0 +1,3 @@
1
+ import { type Request, Response } from '@alinea/iso';
2
+ export declare function proxyMediaUrl(request: Request, source: URL, isAllowed?: (url: URL) => boolean): Promise<Response>;
3
+ export declare function isRedirectStatus(status: number): boolean;
@@ -0,0 +1,39 @@
1
+ import "../../chunks/chunk-CQ52GEHY.js";
2
+
3
+ // src/backend/api/ProxyMedia.ts
4
+ import { Headers, Response } from "@alinea/iso";
5
+ async function proxyMediaUrl(request, source, isAllowed = (url) => url.origin === source.origin) {
6
+ const headers = new Headers();
7
+ for (const name of [
8
+ "range",
9
+ "if-range",
10
+ "if-none-match",
11
+ "if-modified-since"
12
+ ]) {
13
+ const value = request.headers.get(name);
14
+ if (value) headers.set(name, value);
15
+ }
16
+ let current = source;
17
+ for (let redirects = 0; ; redirects++) {
18
+ if (!isAllowed(current))
19
+ return new Response("Invalid media source", { status: 502 });
20
+ const response = await fetch(current, {
21
+ method: request.method,
22
+ headers,
23
+ redirect: "manual",
24
+ signal: request.signal
25
+ });
26
+ const location = response.headers.get("location");
27
+ if (!isRedirectStatus(response.status) || !location) return response;
28
+ if (redirects === 3)
29
+ return new Response("Too many media redirects", { status: 502 });
30
+ current = new URL(location, current);
31
+ }
32
+ }
33
+ function isRedirectStatus(status) {
34
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
35
+ }
36
+ export {
37
+ isRedirectStatus,
38
+ proxyMediaUrl
39
+ };
@@ -13,7 +13,7 @@ export interface S3UploadsOptions {
13
13
  previewExpiresIn?: number;
14
14
  }
15
15
  interface S3PresignOptions {
16
- method: 'GET' | 'PUT';
16
+ method: 'GET' | 'HEAD' | 'PUT';
17
17
  url: URL;
18
18
  region: string;
19
19
  accessKeyId: string;
@@ -195,6 +195,8 @@ function callHandler(handler, input) {
195
195
  function compressResponse(request, response) {
196
196
  const body = response.body;
197
197
  if (!body) return response;
198
+ if (response.status === 206 || response.headers.has("content-range"))
199
+ return response;
198
200
  const isCompressed = response.headers.get("content-encoding");
199
201
  if (isCompressed) return response;
200
202
  const accept = request.headers.get("accept-encoding");
@@ -5,7 +5,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/backend/util/Syncable.ts
7
7
  function createThrottledSync() {
8
- let lastSync = Date.now();
8
+ let lastSync = 0;
9
9
  const limit = pLimit(1);
10
10
  return async function throttle(sync, intervalSeconds = 60) {
11
11
  return limit(async () => {