astro 3.1.1 → 3.1.3

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 (34) hide show
  1. package/components/ViewTransitions.astro +172 -123
  2. package/content-module.template.mjs +1 -1
  3. package/dist/assets/services/squoosh.js +3 -3
  4. package/dist/assets/utils/emitAsset.js +1 -4
  5. package/dist/assets/utils/metadata.d.ts +1 -1
  6. package/dist/assets/utils/metadata.js +6 -5
  7. package/dist/assets/vite-plugin-assets.js +17 -0
  8. package/dist/cli/add/index.js +10 -3
  9. package/dist/content/runtime.d.ts +3 -3
  10. package/dist/content/runtime.js +6 -11
  11. package/dist/core/build/generate.js +4 -2
  12. package/dist/core/build/plugins/plugin-middleware.js +1 -1
  13. package/dist/core/config/schema.d.ts +36 -60
  14. package/dist/core/config/schema.js +8 -23
  15. package/dist/core/constants.js +1 -1
  16. package/dist/core/cookies/index.d.ts +1 -1
  17. package/dist/core/cookies/index.js +7 -2
  18. package/dist/core/cookies/response.d.ts +1 -0
  19. package/dist/core/cookies/response.js +5 -1
  20. package/dist/core/dev/dev.js +1 -1
  21. package/dist/core/errors/errors-data.d.ts +31 -0
  22. package/dist/core/errors/errors-data.js +14 -0
  23. package/dist/core/messages.js +2 -2
  24. package/dist/core/middleware/callMiddleware.js +9 -2
  25. package/dist/core/middleware/loadMiddleware.js +1 -1
  26. package/dist/events/session.d.ts +14 -14
  27. package/dist/events/session.js +61 -53
  28. package/dist/integrations/astroFeaturesValidation.js +2 -2
  29. package/dist/integrations/index.js +3 -3
  30. package/dist/runtime/server/scripts.js +3 -3
  31. package/dist/vite-plugin-astro/hmr.js +1 -9
  32. package/dist/vite-plugin-config-alias/index.js +7 -4
  33. package/dist/vite-plugin-markdown/index.js +18 -26
  34. package/package.json +2 -2
@@ -17,18 +17,26 @@ const { fallback = 'animate' } = Astro.props as Props;
17
17
  index: number;
18
18
  scrollX: number;
19
19
  scrollY: number;
20
+ intraPage?: boolean;
20
21
  };
21
22
  type Events = 'astro:page-load' | 'astro:after-swap';
22
23
 
23
24
  // only update history entries that are managed by us
24
25
  // leave other entries alone and do not accidently add state.
25
26
  const persistState = (state: State) => history.state && history.replaceState(state, '');
27
+ // @ts-expect-error: startViewTransition might exist
26
28
  const supportsViewTransitions = !!document.startViewTransition;
27
29
  const transitionEnabledOnThisPage = () =>
28
30
  !!document.querySelector('[name="astro-view-transitions-enabled"]');
29
31
  const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
30
32
  const onPageLoad = () => triggerEvent('astro:page-load');
31
33
  const PERSIST_ATTR = 'data-astro-transition-persist';
34
+ const parser = new DOMParser();
35
+ // explained at its usage
36
+ let noopEl: HTMLDivElement;
37
+ if (import.meta.env.DEV) {
38
+ noopEl = document.createElement('div');
39
+ }
32
40
 
33
41
  // The History API does not tell you if navigation is forward or back, so
34
42
  // you can figure it using an index. On pushState the index is incremented so you
@@ -40,7 +48,7 @@ const { fallback = 'animate' } = Astro.props as Props;
40
48
  currentHistoryIndex = history.state.index;
41
49
  scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
42
50
  } else if (transitionEnabledOnThisPage()) {
43
- history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
51
+ history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
44
52
  }
45
53
  const throttle = (cb: (...args: any[]) => any, delay: number) => {
46
54
  let wait = false;
@@ -64,19 +72,28 @@ const { fallback = 'animate' } = Astro.props as Props;
64
72
  };
65
73
  };
66
74
 
67
- async function getHTML(href: string) {
75
+ // returns the contents of the page or null if the router can't deal with it.
76
+ async function fetchHTML(
77
+ href: string
78
+ ): Promise<null | { html: string; redirected?: string; mediaType: DOMParserSupportedType }> {
68
79
  try {
69
80
  const res = await fetch(href);
81
+ // drop potential charset (+ other name/value pairs) as parser needs the mediaType
82
+ const mediaType = res.headers.get('content-type')?.replace(/;.*$/, '');
83
+ // the DOMParser can handle two types of HTML
84
+ if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml') {
85
+ // everything else (e.g. audio/mp3) will be handled by the browser but not by us
86
+ return null;
87
+ }
70
88
  const html = await res.text();
71
89
  return {
72
- ok: res.ok,
73
90
  html,
74
91
  redirected: res.redirected ? res.url : undefined,
75
- // drop potential charset (+ other name/value pairs) as parser needs the mediaType
76
- mediaType: res.headers.get('content-type')?.replace(/;.*$/, ''),
92
+ mediaType,
77
93
  };
78
94
  } catch (err) {
79
- return { ok: false };
95
+ // can't fetch, let someone else deal with it.
96
+ return null;
80
97
  }
81
98
  }
82
99
 
@@ -98,19 +115,19 @@ const { fallback = 'animate' } = Astro.props as Props;
98
115
  let wait = Promise.resolve();
99
116
  for (const script of Array.from(document.scripts)) {
100
117
  if (script.dataset.astroExec === '') continue;
101
- const s = document.createElement('script');
102
- s.innerHTML = script.innerHTML;
118
+ const newScript = document.createElement('script');
119
+ newScript.innerHTML = script.innerHTML;
103
120
  for (const attr of script.attributes) {
104
121
  if (attr.name === 'src') {
105
122
  const p = new Promise((r) => {
106
- s.onload = r;
123
+ newScript.onload = r;
107
124
  });
108
125
  wait = wait.then(() => p as any);
109
126
  }
110
- s.setAttribute(attr.name, attr.value);
127
+ newScript.setAttribute(attr.name, attr.value);
111
128
  }
112
- s.dataset.astroExec = '';
113
- script.replaceWith(s);
129
+ newScript.dataset.astroExec = '';
130
+ script.replaceWith(newScript);
114
131
  }
115
132
  return wait;
116
133
  }
@@ -122,46 +139,60 @@ const { fallback = 'animate' } = Astro.props as Props;
122
139
  return style.animationIterationCount === 'infinite';
123
140
  }
124
141
 
125
- const parser = new DOMParser();
126
-
127
- // A noop element used to prevent styles from being removed
128
- if (import.meta.env.DEV) {
129
- var noopEl = document.createElement('div');
130
- }
142
+ const updateHistoryAndScrollPosition = (toLocation) => {
143
+ if (toLocation.href !== location.href) {
144
+ history.pushState(
145
+ { index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
146
+ '',
147
+ toLocation.href
148
+ );
149
+ // now we are on the new page for non-history navigations!
150
+ // (with history navigation page change happens before popstate is fired)
151
+ }
152
+ // freshly loaded pages start from the top
153
+ scrollTo({ left: 0, top: 0, behavior: 'instant' });
154
+
155
+ if (toLocation.hash) {
156
+ // because we are already on the target page ...
157
+ // ... what comes next is a intra-page navigation
158
+ // that won't reload the page but instead scroll to the fragment
159
+ location.href = toLocation.href;
160
+ }
161
+ };
131
162
 
132
- async function updateDOM(doc: Document, loc: URL, state?: State, fallback?: Fallback) {
133
- // Check for a head element that should persist, either because it has the data
134
- // attribute or is a link el.
163
+ // replace head and body of the windows document with contents from newDocument
164
+ // if !popstate, update the history entry and scroll position according to toLocation
165
+ // if popState is given, this holds the scroll position for history navigation
166
+ // if fallback === "animate" then simulate view transitions
167
+ async function updateDOM(
168
+ newDocument: Document,
169
+ toLocation: URL,
170
+ popState?: State,
171
+ fallback?: Fallback
172
+ ) {
173
+ // Check for a head element that should persist and returns it,
174
+ // either because it has the data attribute or is a link el.
135
175
  const persistedHeadElement = (el: HTMLElement): Element | null => {
136
176
  const id = el.getAttribute(PERSIST_ATTR);
137
- const newEl = id && doc.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
177
+ const newEl = id && newDocument.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
138
178
  if (newEl) {
139
179
  return newEl;
140
180
  }
141
181
  if (el.matches('link[rel=stylesheet]')) {
142
182
  const href = el.getAttribute('href');
143
- return doc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
144
- }
145
- if (el.tagName === 'SCRIPT') {
146
- let s1 = el as HTMLScriptElement;
147
- for (const s2 of doc.scripts) {
148
- if (
149
- // Inline
150
- (s1.textContent && s1.textContent === s2.textContent) ||
151
- // External
152
- (s1.type === s2.type && s1.src === s2.src)
153
- ) {
154
- return s2;
155
- }
156
- }
183
+ return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
157
184
  }
158
- // Only run this in dev. This will get stripped from production builds and is not needed.
185
+ // What follows is a fix for an issue (#8472) with missing client:only styles after transition.
186
+ // That problem exists only in dev mode where styles are injected into the page by Vite.
187
+ // Returning a noop element ensures that the styles are not removed from the old document.
188
+ // Guarding the code below with the dev mode check
189
+ // allows tree shaking to remove this code in production.
159
190
  if (import.meta.env.DEV) {
160
191
  if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
161
192
  const devId = el.dataset.viteDevId;
162
193
  // If this same style tag exists, remove it from the new page
163
194
  return (
164
- doc.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
195
+ newDocument.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
165
196
  // Otherwise, keep it anyways. This is client:only styles.
166
197
  noopEl
167
198
  );
@@ -171,10 +202,6 @@ const { fallback = 'animate' } = Astro.props as Props;
171
202
  };
172
203
 
173
204
  const swap = () => {
174
- // noscript tags inside head element are not honored on swap (#7969).
175
- // Remove them before swapping.
176
- doc.querySelectorAll('head noscript').forEach((el) => el.remove());
177
-
178
205
  // swap attributes of the html element
179
206
  // - delete all attributes from the current document
180
207
  // - insert all attributes from doc
@@ -183,10 +210,26 @@ const { fallback = 'animate' } = Astro.props as Props;
183
210
  const astro = [...html.attributes].filter(
184
211
  ({ name }) => (html.removeAttribute(name), name.startsWith('data-astro-'))
185
212
  );
186
- [...doc.documentElement.attributes, ...astro].forEach(({ name, value }) =>
213
+ [...newDocument.documentElement.attributes, ...astro].forEach(({ name, value }) =>
187
214
  html.setAttribute(name, value)
188
215
  );
189
216
 
217
+ // Replace scripts in both the head and body.
218
+ for (const s1 of document.scripts) {
219
+ for (const s2 of newDocument.scripts) {
220
+ if (
221
+ // Inline
222
+ (!s1.src && s1.textContent === s2.textContent) ||
223
+ // External
224
+ (s1.src && s1.type === s2.type && s1.src === s2.src)
225
+ ) {
226
+ // the old script is in the new document: we mark it as executed to prevent re-execution
227
+ s2.dataset.astroExec = '';
228
+ break;
229
+ }
230
+ }
231
+ }
232
+
190
233
  // Swap head
191
234
  for (const el of Array.from(document.head.children)) {
192
235
  const newEl = persistedHeadElement(el as HTMLElement);
@@ -199,12 +242,15 @@ const { fallback = 'animate' } = Astro.props as Props;
199
242
  el.remove();
200
243
  }
201
244
  }
245
+
202
246
  // Everything left in the new head is new, append it all.
203
- document.head.append(...doc.head.children);
247
+ document.head.append(...newDocument.head.children);
204
248
 
205
249
  // Persist elements in the existing body
206
250
  const oldBody = document.body;
207
- document.body.replaceWith(doc.body);
251
+
252
+ // this will reset scroll Position
253
+ document.body.replaceWith(newDocument.body);
208
254
  for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
209
255
  const id = el.getAttribute(PERSIST_ATTR);
210
256
  const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
@@ -215,39 +261,18 @@ const { fallback = 'animate' } = Astro.props as Props;
215
261
  }
216
262
  }
217
263
 
218
- // Simulate scroll behavior of Safari and
219
- // Chromium based browsers (Chrome, Edge, Opera, ...)
220
- scrollTo({ left: 0, top: 0, behavior: 'instant' });
221
-
222
- let initialScrollX = 0;
223
- let initialScrollY = 0;
224
- if (!state && loc.hash) {
225
- const id = decodeURIComponent(loc.hash.slice(1));
226
- const elem = document.getElementById(id);
227
- // prefer scrollIntoView() over scrollTo() because it takes scroll-padding into account
228
- if (elem) {
229
- elem.scrollIntoView();
230
- initialScrollX = Math.max(
231
- 0,
232
- elem.offsetLeft + elem.offsetWidth - document.documentElement.clientWidth
233
- );
234
- initialScrollY = elem.offsetTop;
235
- }
236
- } else if (state) {
237
- scrollTo(state.scrollX, state.scrollY); // usings default scrollBehavior
264
+ if (popState) {
265
+ scrollTo(popState.scrollX, popState.scrollY); // usings 'auto' scrollBehavior
266
+ } else {
267
+ updateHistoryAndScrollPosition(toLocation);
238
268
  }
239
- !state &&
240
- history.pushState(
241
- { index: ++currentHistoryIndex, scrollX: initialScrollX, scrollY: initialScrollY },
242
- '',
243
- loc.href
244
- );
269
+
245
270
  triggerEvent('astro:after-swap');
246
271
  };
247
272
 
248
273
  // Wait on links to finish, to prevent FOUC
249
274
  const links: Promise<any>[] = [];
250
- for (const el of doc.querySelectorAll('head link[rel=stylesheet]')) {
275
+ for (const el of newDocument.querySelectorAll('head link[rel=stylesheet]')) {
251
276
  // Do not preload links that are already on the page.
252
277
  if (
253
278
  !document.querySelector(
@@ -287,32 +312,44 @@ const { fallback = 'animate' } = Astro.props as Props;
287
312
  }
288
313
  }
289
314
 
290
- async function navigate(dir: Direction, loc: URL, state?: State) {
315
+ async function transition(direction: Direction, toLocation: URL, popState?: State) {
291
316
  let finished: Promise<void>;
292
- const href = loc.href;
293
- const { html, ok, mediaType, redirected } = await getHTML(href);
294
- // if there was a redirection, show the final URL in the browser's address bar
295
- redirected && (loc = new URL(redirected));
317
+ const href = toLocation.href;
318
+ const response = await fetchHTML(href);
296
319
  // If there is a problem fetching the new page, just do an MPA navigation to it.
297
- if (!ok || !(mediaType === 'text/html' || mediaType === 'application/xhtml+xml')) {
320
+ if (response === null) {
298
321
  location.href = href;
299
322
  return;
300
323
  }
324
+ // if there was a redirection, show the final URL in the browser's address bar
325
+ if (response.redirected) {
326
+ toLocation = new URL(response.redirected);
327
+ }
328
+
329
+ const newDocument = parser.parseFromString(response.html, response.mediaType);
330
+ // The next line might look like a hack,
331
+ // but it is actually necessary as noscript elements
332
+ // and their contents are returned as markup by the parser,
333
+ // see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
334
+ newDocument.querySelectorAll('noscript').forEach((el) => el.remove());
301
335
 
302
- const doc = parser.parseFromString(html, mediaType);
303
- if (!doc.querySelector('[name="astro-view-transitions-enabled"]')) {
336
+ if (!newDocument.querySelector('[name="astro-view-transitions-enabled"]')) {
304
337
  location.href = href;
305
338
  return;
306
339
  }
307
340
 
308
- // Now we are sure that we will push state, and it is time to create a state if it is still missing.
309
- !state && history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
310
-
311
- document.documentElement.dataset.astroTransition = dir;
341
+ if (!popState) {
342
+ // save the current scroll position before we change the DOM and transition to the new page
343
+ history.replaceState({ ...history.state, scrollX, scrollY }, '');
344
+ }
345
+ document.documentElement.dataset.astroTransition = direction;
312
346
  if (supportsViewTransitions) {
313
- finished = document.startViewTransition(() => updateDOM(doc, loc, state)).finished;
347
+ // @ts-expect-error: startViewTransition exist
348
+ finished = document.startViewTransition(() =>
349
+ updateDOM(newDocument, toLocation, popState)
350
+ ).finished;
314
351
  } else {
315
- finished = updateDOM(doc, loc, state, getFallback());
352
+ finished = updateDOM(newDocument, toLocation, popState, getFallback());
316
353
  }
317
354
  try {
318
355
  await finished;
@@ -328,7 +365,9 @@ const { fallback = 'animate' } = Astro.props as Props;
328
365
  // Prefetching
329
366
  function maybePrefetch(pathname: string) {
330
367
  if (document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
368
+ // @ts-expect-error: connection might exist
331
369
  if (navigator.connection) {
370
+ // @ts-expect-error: connection does exist
332
371
  let conn = navigator.connection;
333
372
  if (conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return;
334
373
  }
@@ -339,8 +378,6 @@ const { fallback = 'animate' } = Astro.props as Props;
339
378
  }
340
379
 
341
380
  if (supportsViewTransitions || getFallback() !== 'none') {
342
- markScriptsExec();
343
-
344
381
  document.addEventListener('click', (ev) => {
345
382
  let link = ev.target;
346
383
  if (link instanceof Element && link.tagName !== 'A') {
@@ -362,43 +399,49 @@ const { fallback = 'animate' } = Astro.props as Props;
362
399
  ev.ctrlKey || // new tab (windows)
363
400
  ev.altKey || // download
364
401
  ev.shiftKey || // new window
365
- ev.defaultPrevented ||
366
- !transitionEnabledOnThisPage()
402
+ ev.defaultPrevented
367
403
  ) {
368
404
  // No page transitions in these cases,
369
405
  // Let the browser standard action handle this
370
406
  return;
371
407
  }
372
- // We do not need to handle same page links because there are no page transitions
373
- // Same page means same path and same query params (but different hash)
374
- if (location.pathname === link.pathname && location.search === link.search) {
375
- if (link.hash) {
376
- // The browser default action will handle navigations with hash fragments
377
- return;
408
+ ev.preventDefault();
409
+ navigate(link.href);
410
+ });
411
+
412
+ function navigate(href) {
413
+ // not ours
414
+ if (!transitionEnabledOnThisPage()) {
415
+ location.href = href;
416
+ return;
417
+ }
418
+ const toLocation = new URL(href, location.href);
419
+ // We do not have page transitions on navigations to the same page (intra-page navigation)
420
+ // but we want to handle prevent reload on navigation to the same page
421
+ // Same page means same origin, path and query params (but maybe different hash)
422
+ if (
423
+ location.origin === toLocation.origin &&
424
+ location.pathname === toLocation.pathname &&
425
+ location.search === toLocation.search
426
+ ) {
427
+ // mark current position as non transition intra-page scrolling
428
+ if (location.href !== toLocation.href) {
429
+ history.replaceState({ ...history.state, intraPage: true }, '');
430
+ history.pushState(
431
+ { index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
432
+ '',
433
+ toLocation.href
434
+ );
435
+ }
436
+ if (toLocation.hash) {
437
+ location.href = toLocation.href;
378
438
  } else {
379
- // Special case: self link without hash
380
- // If handed to the browser it will reload the page
381
- // But we want to handle it like any other same page navigation
382
- // So we scroll to the top of the page but do not start page transitions
383
- ev.preventDefault();
384
- // push state on the first navigation but not if we were here already
385
- if (location.hash) {
386
- history.replaceState(
387
- { index: currentHistoryIndex, scrollX, scrollY: -(scrollY + 1) },
388
- ''
389
- );
390
- const newState: State = { index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 };
391
- history.pushState(newState, '', link.href);
392
- }
393
439
  scrollTo({ left: 0, top: 0, behavior: 'instant' });
394
- return;
395
440
  }
441
+ } else {
442
+ transition('forward', toLocation);
396
443
  }
397
-
398
- // these are the cases we will handle: same origin, different page
399
- ev.preventDefault();
400
- navigate('forward', new URL(link.href));
401
- });
444
+ }
402
445
 
403
446
  addEventListener('popstate', (ev) => {
404
447
  if (!transitionEnabledOnThisPage() && ev.state) {
@@ -406,7 +449,9 @@ const { fallback = 'animate' } = Astro.props as Props;
406
449
  // but the page we navigate to does (because it set the state).
407
450
  // Do a full page refresh to reload the client-side router from the new page.
408
451
  // Scroll restauration will then happen during the reload when the router's code is re-executed
409
- history.scrollRestoration && (history.scrollRestoration = 'manual');
452
+ if (history.scrollRestoration) {
453
+ history.scrollRestoration = 'manual';
454
+ }
410
455
  location.reload();
411
456
  return;
412
457
  }
@@ -429,13 +474,14 @@ const { fallback = 'animate' } = Astro.props as Props;
429
474
  }
430
475
 
431
476
  const state: State = history.state;
432
- const nextIndex = state.index;
433
- const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
434
- currentHistoryIndex = nextIndex;
435
- if (state.scrollY < 0) {
436
- scrollTo(state.scrollX, -(state.scrollY + 1));
477
+ if (state.intraPage) {
478
+ // this is non transition intra-page scrolling
479
+ scrollTo(state.scrollX, state.scrollY);
437
480
  } else {
438
- navigate(direction, new URL(location.href), state);
481
+ const nextIndex = state.index;
482
+ const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
483
+ currentHistoryIndex = nextIndex;
484
+ transition(direction, new URL(location.href), state);
439
485
  }
440
486
  });
441
487
 
@@ -457,6 +503,7 @@ const { fallback = 'animate' } = Astro.props as Props;
457
503
  { passive: true, capture: true }
458
504
  );
459
505
  });
506
+
460
507
  addEventListener('load', onPageLoad);
461
508
  // There's not a good way to record scroll position before a back button.
462
509
  // So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
@@ -466,5 +513,7 @@ const { fallback = 'animate' } = Astro.props as Props;
466
513
 
467
514
  if ('onscrollend' in window) addEventListener('scrollend', updateState);
468
515
  else addEventListener('scroll', throttle(updateState, 300));
516
+
517
+ markScriptsExec();
469
518
  }
470
519
  </script>
@@ -70,7 +70,7 @@ export const getEntryBySlug = createGetEntryBySlug({
70
70
  });
71
71
 
72
72
  export const getDataEntryById = createGetDataEntryById({
73
- dataCollectionToEntryMap,
73
+ getEntryImport: createGlobLookup(dataCollectionToEntryMap),
74
74
  });
75
75
 
76
76
  export const getEntry = createGetEntry({
@@ -18,8 +18,8 @@ const qualityTable = {
18
18
  webp: baseQuality
19
19
  // Squoosh's PNG encoder does not support a quality setting, so we can skip that here
20
20
  };
21
- async function getRotationForEXIF(inputBuffer) {
22
- const meta = await imageMetadata(inputBuffer);
21
+ async function getRotationForEXIF(inputBuffer, src) {
22
+ const meta = await imageMetadata(inputBuffer, src);
23
23
  if (!meta)
24
24
  return void 0;
25
25
  switch (meta.orientation) {
@@ -45,7 +45,7 @@ const service = {
45
45
  if (format === "svg")
46
46
  return { data: inputBuffer, format: "svg" };
47
47
  const operations = [];
48
- const rotation = await getRotationForEXIF(inputBuffer);
48
+ const rotation = await getRotationForEXIF(inputBuffer, transform.src);
49
49
  if (rotation) {
50
50
  operations.push(rotation);
51
51
  }
@@ -14,10 +14,7 @@ async function emitESMImage(id, watchMode, fileEmitter) {
14
14
  } catch (err) {
15
15
  return void 0;
16
16
  }
17
- const fileMetadata = await imageMetadata(fileData);
18
- if (!fileMetadata) {
19
- return void 0;
20
- }
17
+ const fileMetadata = await imageMetadata(fileData, id);
21
18
  const emittedImage = {
22
19
  src: "",
23
20
  ...fileMetadata
@@ -1,3 +1,3 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type { ImageMetadata } from '../types.js';
3
- export declare function imageMetadata(data: Buffer): Promise<Omit<ImageMetadata, 'src'> | undefined>;
3
+ export declare function imageMetadata(data: Buffer, src?: string): Promise<Omit<ImageMetadata, 'src'>>;
@@ -1,14 +1,15 @@
1
1
  import probe from "probe-image-size";
2
- async function imageMetadata(data) {
2
+ import { AstroError, AstroErrorData } from "../../core/errors/index.js";
3
+ async function imageMetadata(data, src) {
3
4
  const result = probe.sync(data);
4
5
  if (result === null) {
5
- throw new Error("Failed to probe image size.");
6
+ throw new AstroError({
7
+ ...AstroErrorData.NoImageMetadata,
8
+ message: AstroErrorData.NoImageMetadata.message(src)
9
+ });
6
10
  }
7
11
  const { width, height, type, orientation } = result;
8
12
  const isPortrait = (orientation || 0) >= 5;
9
- if (!width || !height || !type) {
10
- return void 0;
11
- }
12
13
  return {
13
14
  width: isPortrait ? height : width,
14
15
  height: isPortrait ? width : height,
@@ -1,5 +1,7 @@
1
1
  import MagicString from "magic-string";
2
2
  import { normalizePath } from "vite";
3
+ import { extendManualChunks } from "../core/build/plugins/util.js";
4
+ import { AstroError, AstroErrorData } from "../core/errors/index.js";
3
5
  import {
4
6
  appendForwardSlash,
5
7
  joinPaths,
@@ -21,6 +23,15 @@ function assets({
21
23
  // Expose the components and different utilities from `astro:assets` and handle serving images from `/_image` in dev
22
24
  {
23
25
  name: "astro:assets",
26
+ outputOptions(outputOptions) {
27
+ extendManualChunks(outputOptions, {
28
+ after(id) {
29
+ if (id.includes("astro/dist/assets/services/")) {
30
+ return `astro-assets-services`;
31
+ }
32
+ }
33
+ });
34
+ },
24
35
  async resolveId(id) {
25
36
  if (id === VIRTUAL_SERVICE_ID) {
26
37
  return await this.resolve(settings.config.image.service.entrypoint);
@@ -102,6 +113,12 @@ function assets({
102
113
  }
103
114
  if (assetRegex.test(id)) {
104
115
  const meta = await emitESMImage(id, this.meta.watchMode, this.emitFile);
116
+ if (!meta) {
117
+ throw new AstroError({
118
+ ...AstroErrorData.ImageNotFound,
119
+ message: AstroErrorData.ImageNotFound.message(id)
120
+ });
121
+ }
105
122
  return `export default ${JSON.stringify(meta)}`;
106
123
  }
107
124
  }
@@ -8,7 +8,12 @@ import { fileURLToPath, pathToFileURL } from "node:url";
8
8
  import ora from "ora";
9
9
  import preferredPM from "preferred-pm";
10
10
  import prompts from "prompts";
11
- import { loadTSConfig, resolveConfigPath, resolveRoot } from "../../core/config/index.js";
11
+ import {
12
+ loadTSConfig,
13
+ resolveConfig,
14
+ resolveConfigPath,
15
+ resolveRoot
16
+ } from "../../core/config/index.js";
12
17
  import {
13
18
  defaultTSConfig,
14
19
  presets,
@@ -20,7 +25,7 @@ import { appendForwardSlash } from "../../core/path.js";
20
25
  import { apply as applyPolyfill } from "../../core/polyfill.js";
21
26
  import { parseNpmName } from "../../core/util.js";
22
27
  import { eventCliSession, telemetry } from "../../events/index.js";
23
- import { createLoggerFromFlags } from "../flags.js";
28
+ import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
24
29
  import { generate, parse, t, visit } from "./babel.js";
25
30
  import { ensureImport } from "./imports.js";
26
31
  import { wrapDefaultExport } from "./wrapper.js";
@@ -66,7 +71,9 @@ async function getRegistry() {
66
71
  }
67
72
  }
68
73
  async function add(names, { flags }) {
69
- telemetry.record(eventCliSession("add"));
74
+ const inlineConfig = flagsToAstroInlineConfig(flags);
75
+ const { userConfig } = await resolveConfig(inlineConfig, "add");
76
+ telemetry.record(eventCliSession("add", userConfig));
70
77
  applyPolyfill();
71
78
  if (flags.help || names.length === 0) {
72
79
  printHelp({
@@ -13,7 +13,7 @@ export declare function createGetCollection({ contentCollectionToEntryMap, dataC
13
13
  contentCollectionToEntryMap: CollectionToEntryMap;
14
14
  dataCollectionToEntryMap: CollectionToEntryMap;
15
15
  getRenderEntryImport: GetEntryImport;
16
- }): (collection: string, filter?: ((entry: any) => unknown) | undefined) => Promise<any[]>;
16
+ }): (collection: string, filter?: ((entry: any) => unknown) | undefined) => Promise<any[] | undefined>;
17
17
  export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, }: {
18
18
  getEntryImport: GetEntryImport;
19
19
  getRenderEntryImport: GetEntryImport;
@@ -25,8 +25,8 @@ export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImp
25
25
  data: any;
26
26
  render(): Promise<RenderResult>;
27
27
  } | undefined>;
28
- export declare function createGetDataEntryById({ dataCollectionToEntryMap, }: {
29
- dataCollectionToEntryMap: CollectionToEntryMap;
28
+ export declare function createGetDataEntryById({ getEntryImport }: {
29
+ getEntryImport: GetEntryImport;
30
30
  }): (collection: string, id: string) => Promise<{
31
31
  id: any;
32
32
  collection: any;