Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6b21531
replace virtual modules with generated files, formalize public helper…
BoDonkey Jun 3, 2026
9048c2e
Add changeset
BoDonkey Jun 3, 2026
f1333cf
Helpers cleanup
BoDonkey Jun 4, 2026
a33b84a
Path correction
BoDonkey Jun 4, 2026
a70b170
Add .d.ts and tsconfig files
BoDonkey Jun 4, 2026
83272f4
Remove first design doc
BoDonkey Jun 4, 2026
e9d94cd
Remove final design doc
BoDonkey Jun 4, 2026
99644c0
Better static cache root dir behavior and docs fix
BoDonkey Jun 4, 2026
ffaecff
Bump dependancy
BoDonkey Jun 5, 2026
ea8f042
Refactor for non-bc
BoDonkey Jun 5, 2026
df33277
Migration and version number
BoDonkey Jun 5, 2026
2ab77f9
Response to comments.
BoDonkey Jun 6, 2026
93157d9
Deprecate old files
BoDonkey Jun 6, 2026
564aabb
Fixes upgrade header handling
BoDonkey Jun 17, 2026
32f7a9a
Add tests
BoDonkey Jun 18, 2026
ffdffdc
Fix raw-text sanitization bypass vulnerability and add regression tes…
Dipanshusinghh Jun 4, 2026
c21d5fb
changeset for singh contribution (#5442)
boutell Jun 4, 2026
62d710d
Fix relationship select scrolling issue (#5445)
myovchev Jun 5, 2026
f1dd138
jsx changeset (#5446)
boutell Jun 5, 2026
3bc03d8
Ensure install of the project root for astro projects (#5449)
myovchev Jun 8, 2026
8899103
test node 26 (#5450)
boutell Jun 8, 2026
4e1be7a
Add link for telemetry policy (#5455)
BoDonkey Jun 9, 2026
ff6cbb6
remove absent options (#5456)
boutell Jun 9, 2026
043c215
Remove consumed 4.30.0 changesets from main (#5454)
BoDonkey Jun 10, 2026
6fba442
cli links that are correct, or will be post publish (#5458)
boutell Jun 10, 2026
a6daca5
release db connect to solve chicken and egg problem in cypress-tools …
boutell Jun 10, 2026
227b638
Corrects documentation links (#5457)
BoDonkey Jun 10, 2026
6e6b98c
Merge commit from fork
boutell Jun 10, 2026
6d48f1f
Merge commit from fork
boutell Jun 10, 2026
20a29ea
Merge commit from fork
boutell Jun 10, 2026
736d6c4
Merge commit from fork
boutell Jun 10, 2026
5fda79e
Mergeback latest (#5468)
boutell Jun 10, 2026
5094022
Hotfix cli links (#5469)
BoDonkey Jun 10, 2026
7a29be1
Fix asset URLs when a site prefix is configured (#5448)
Manohar2503 Jun 11, 2026
56a844a
Mergeup latest to main (#5473)
boutell Jun 11, 2026
1937537
fix: treat col as a self-closing tag (#5447)
vansh1011 Jun 16, 2026
57a01ab
Add explicit test for inline configuration collapsing (#5475)
myovchev Jun 17, 2026
334dc61
Pro 9442 insensitive redirects (#5479)
BoDonkey Jun 17, 2026
91ecf47
Bump undici, fix a leak (#5480)
myovchev Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/six-socks-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@apostrophecms/apostrophe-astro": major
---

- Replace vite-plugin-apostrophe-config and vite-plugin-apostrophe-doctype with
vite/vite-plugin-apostrophe-generated-config.js, which writes real files to
node_modules/.apostrophe-astro-config/ (config.js, doctypes.js)
- Register Vite aliases for apostrophe-astro-config/config and /doctypes
- Update all 10 internal virtual: imports to alias specifiers
- Rename static build cache dir to node_modules/.apostrophe-astro-static/
- Add helpers/server/ (aposFetch, aposPageFetch, getAposHost, isStaticBuild)
- Add helpers/universal/ (URL, slug, styles, attachment helpers)
- Add helpers/client/index.js (reserved)
- Reduce lib/aposPageFetch.js, lib/util.js, lib/aposSetQueryParameter.js to deprecated shims
- Add package.json exports map; bump version to 2.0.0
- Add MIGRATION.md
124 changes: 124 additions & 0 deletions packages/apostrophe-astro/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Migrating to @apostrophecms/apostrophe-astro v2

## Overview

v2 ships two coordinated breaking changes:

1. **Generated runtime files** replace Vite virtual modules for Astro 6 / Vite 7 compatibility.
2. **Public helper import paths** are formalized. `helpers/server`, `helpers/universal`, and `helpers/client` are the new stable entry points. `lib/` is now internal.

Most projects need only the import-path changes described below. Integration options, component paths, and injected routes are unchanged.

---

## What stays the same

- `apostropheIntegration()` options (`aposHost`, `widgetsMapping`, `templatesMapping`, `onBeforeWidgetRender`, `staticBuild`, etc.) are unchanged.
- Component import paths (`@apostrophecms/apostrophe-astro/components/*`, `.../components/layouts/*`, `.../widgets/*`) are unchanged.
- Injected routes (`/apos-frontend/[...slug]`, `/api/v1/[...slug]`, etc.) are unchanged.

---

## Required changes

### 1. Update `lib/aposPageFetch.js` imports

```js
// Before
import aposPageFetch from '@apostrophecms/apostrophe-astro/lib/aposPageFetch.js';

// After
import { aposPageFetch } from '@apostrophecms/apostrophe-astro/helpers/server';
```

### 2. Update `lib/aposSetQueryParameter.js` imports

```js
// Before
import setParameter from '@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter.js';
// or
import setParameter from '@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter';

// After
import { aposSetQueryParameter } from '@apostrophecms/apostrophe-astro/helpers/universal';
```

### 3. Update `lib/util` imports

```js
// Before
import { slugify } from '@apostrophecms/apostrophe-astro/lib/util';
// or
import { slugify } from '@apostrophecms/apostrophe-astro/lib/util.js';

// After
import { slugify } from '@apostrophecms/apostrophe-astro/helpers/universal';
```

### 4. Update `lib/static.js` imports

```js
// Before
import { getAllStaticPaths } from '@apostrophecms/apostrophe-astro/lib/static.js';

// After
import { getAllStaticPaths } from '@apostrophecms/apostrophe-astro/helpers/server';
```

### 5. Update `lib/aposStyles.js` and `lib/attachment.js` imports

These files are no longer part of the public API.

```js
// Before
import { stylesAttributes } from '@apostrophecms/apostrophe-astro/lib/aposStyles.js';
import { getAttachmentUrl } from '@apostrophecms/apostrophe-astro/lib/attachment.js';

// After
import { stylesAttributes, getAttachmentUrl } from '@apostrophecms/apostrophe-astro/helpers/universal';
```

---

## Deprecated shims (v2)

The following `lib/` paths remain exported in v2 as compatibility shims. They continue to work, but new and migrated code should use the helper entry points. Migrate before v3.

| Old path | New path |
| --- | --- |
| `@apostrophecms/apostrophe-astro/lib/aposPageFetch.js` | `@apostrophecms/apostrophe-astro/helpers/server` |
| `@apostrophecms/apostrophe-astro/lib/static.js` | `@apostrophecms/apostrophe-astro/helpers/server` |
| `@apostrophecms/apostrophe-astro/lib/util` | `@apostrophecms/apostrophe-astro/helpers/universal` |
| `@apostrophecms/apostrophe-astro/lib/util.js` | `@apostrophecms/apostrophe-astro/helpers/universal` |
| `@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter` | `@apostrophecms/apostrophe-astro/helpers/universal` |
| `@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter.js` | `@apostrophecms/apostrophe-astro/helpers/universal` |

---

## Unsupported usage that must be removed

### Virtual module imports

If your project imports `virtual:apostrophe-config` or `virtual:apostrophe-doctypes` directly, those imports are unsupported and must be removed. These were private implementation details and have been replaced by generated files that are not part of the public API.

### Unlisted `lib/` paths

Any import of a `lib/` path not listed in the deprecated shims table above (e.g. `lib/aposRequest.js`, `lib/aposResponse.js`, `lib/format.js`) will fail under the v2 exports map. These are internal modules with no public equivalent. If you need functionality that was only accessible via an internal path, open an issue to discuss adding a proper public helper.

---

## Helper import contract

| Import path | Use in |
| --- | --- |
| `@apostrophecms/apostrophe-astro/helpers/server` | Astro frontmatter, server endpoints, SSR routes, prerendering. Depends on generated config and Node.js built-ins — do not use in client scripts. |
| `@apostrophecms/apostrophe-astro/helpers/universal` | Utilities that work in both server and client contexts. Pure functions only — no generated config, no `process.env`, no Node.js built-ins. |
| `@apostrophecms/apostrophe-astro/helpers/client` | Reserved for future browser-only helpers. Empty in v2. |

There is no top-level `helpers` barrel — always use one of the three explicit category paths.

---

## Static build cache directory

The static build cache has moved from `node_modules/.apostrophe-astro/` to `node_modules/.apostrophe-astro-static/`. Both directories live under `node_modules/` and require no `.gitignore` changes. This is an internal implementation detail with no user-facing impact.
4 changes: 2 additions & 2 deletions packages/apostrophe-astro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ Your `[...slug].astro` component should look like this:

```js
---
import aposPageFetch from '@apostrophecms/apostrophe-astro/lib/aposPageFetch.js';
import { aposPageFetch } from '@apostrophecms/apostrophe-astro/helpers/server';
import AposLayout from '@apostrophecms/apostrophe-astro/components/layouts/AposLayout.astro';
import AposTemplate from '@apostrophecms/apostrophe-astro/components/AposTemplate.astro';

Expand Down Expand Up @@ -651,7 +651,7 @@ links to each page of blog posts:

```js
---
import setParameter from '@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter.js';
import { aposSetQueryParameter as setParameter } from '@apostrophecms/apostrophe-astro/helpers/universal';

const {
pieces,
Expand Down
2 changes: 1 addition & 1 deletion packages/apostrophe-astro/components/AposTemplate.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { templates } from 'virtual:apostrophe-doctypes';
import { templates } from 'apostrophe-astro-config/doctypes';

const { aposData } = Astro.props;

Expand Down
2 changes: 1 addition & 1 deletion packages/apostrophe-astro/components/AposWidget.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { widgets } from "virtual:apostrophe-doctypes";
import { widgets } from 'apostrophe-astro-config/doctypes';

const { widget, options, ...props } = Astro.props;
const isEdit = widget._edit && Astro.url.searchParams.get("aposEdit");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import config from "virtual:apostrophe-config";
import config from 'apostrophe-astro-config/config';

const { title, bodyClass, aposData } = Astro.props;
const { viewTransitionWorkaround } = config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import AposRunLayout from "./AposRunLayout.astro";
import AposEditLayout from "./AposEditLayout.astro";
import AposRefreshLayout from "./AposRefreshLayout.astro";
import config from 'virtual:apostrophe-config';
import config from 'apostrophe-astro-config/config';

const { aposData } = Astro.props;

Expand Down
2 changes: 1 addition & 1 deletion packages/apostrophe-astro/endpoints/renderWidget.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import AposWidget from "../components/AposWidget.astro";
import aposRequest from "../lib/aposRequest.js";
import aposResponse from "../lib/aposResponse.js";
import { onBeforeWidgetRenderHook } from "virtual:apostrophe-doctypes";
import { onBeforeWidgetRenderHook } from 'apostrophe-astro-config/doctypes';

const request = aposRequest(Astro.request);
const response = await aposResponse(request);
Expand Down
10 changes: 10 additions & 0 deletions packages/apostrophe-astro/helpers/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Client-only public helpers for @apostrophecms/apostrophe-astro.
*
* Reserved for future browser-only helpers that depend on browser APIs
* such as `window` or `document`. No helpers are in this category yet.
*
* Do not add server-side or Node.js-dependent code here.
*
* @module @apostrophecms/apostrophe-astro/helpers/client
*/
3 changes: 0 additions & 3 deletions packages/apostrophe-astro/helpers/index.js

This file was deleted.

143 changes: 143 additions & 0 deletions packages/apostrophe-astro/helpers/server/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import config from 'apostrophe-astro-config/config';
import { getAposHost } from './url.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/helpers/server/static.js re-exports the lib so that the project can import from /helpers/server , however the helpers/server/fetch.js now is reverted (as requested) but doesn't re-export the lib. This sends mixed signals - the project will import static from helpers but the standard page loading from /lib? I think aposPageFetch() should be also re-exported here.

import aposResponse from '../../lib/aposResponse.js';
import aposRequest from '../../lib/aposRequest.js';

/**
* A transparent proxy around the native `fetch` API for **server-side
* Astro code only** (`.astro` frontmatter, server endpoints, etc.).
*
* **Do NOT use in client-side code** — it depends on
* `apostrophe-astro-config/config` and exposes the internal backend host.
* For browser requests use plain `fetch` with relative URLs
* (e.g. `/api/v1/...`).
*
* What it does on top of native `fetch`:
* - Prepends the Apostrophe backend host (`getAposHost()`) to relative
* URLs (paths starting with `/`).
* - Injects the `x-apos-static-base-url: 1` header during static builds
* so the backend returns path-only URLs in its responses.
*
* Accepts the same arguments as `fetch(input, init?)` and returns a
* standard `Response`. All `init` options (method, body, headers, signal,
* etc.) are preserved and merged.
*
* @param {string|URL|Request} input - URL or Request object. Relative
* paths (starting with `/`) are resolved against `getAposHost()`.
* @param {RequestInit} [init] - Optional fetch init options.
* @returns {Promise<Response>}
*
* @example
* ```astro
* ---
* import { aposFetch } from '@apostrophecms/apostrophe-astro/helpers/server';
* const response = await aposFetch('/api/v1/article?perPage=5');
* const data = await response.json();
* ---
* ```
*/
export async function aposFetch(input, init) {
let url = input;

if (typeof url === 'string' && url.startsWith('/')) {
url = getAposHost() + url;
}
const headers = new Headers(init?.headers);
if (config.staticBuild) {
headers.set('x-apos-static-base-url', '1');
}

return fetch(url, {
...init || {},
headers
});
}

/**
* Fetch a full Apostrophe page data object for the given Astro request.
*
* This is the primary entry point for SSR and static-build page routes.
* It wraps `aposRequest` and `aposResponse` to forward the incoming
* request to the Apostrophe backend and return the parsed JSON page data,
* including automatic handling of trailing-slash redirects.
*
* For static builds, use this inside `getStaticPaths` / your page
* frontmatter to retrieve the `aposData` prop.
*
* @param {Request} req - The incoming Astro request (`Astro.request`).
* @returns {Promise<object>} The Apostrophe page data object. On error,
* returns an object with `errorFetchingPage` set to the caught error
* and `page.type` set to `'apos-fetch-error'`.
*
* @example
* ```astro
* ---
* import { aposPageFetch } from '@apostrophecms/apostrophe-astro/helpers/server';
* const aposData = await aposPageFetch(Astro.request);
* ---
* ```
*/
export async function aposPageFetch(req) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be part of public documentation and should have explicit "for internal use only" here.
I understand why it's refactored and why here - the slug.astro in projects probably uses it. However, it's meant to be used only there, in our entrypoint. aposFetch is the tool that can be exposed for developers.

I'd argue that this refactoring makes no sense - we can only re-export it from lib/ (it is indeed internal thing) - similar with what we do for static.

let aposData = {};
try {
let request = aposRequest(req);
if (request.method === 'HEAD') {
request = new Request(request, {
method: 'GET'
});
}
const response = await aposResponse(request);
let headers = response.headers;
aposData = await response.json();

// Apostrophe's external-front middleware returns redirects as JSON
// (e.g. { redirect: true, url: '/fr/', status: 302 }). When the
// redirect only adds or removes a trailing slash we should follow
// it internally rather than bouncing the browser — otherwise locale
// home pages like /fr/ cause an infinite redirect loop.
// Skip the site root "/" — it never needs this treatment.
//
// When a prefix is configured, Apostrophe returns redirect URLs
// without the prefix (it's a routing concern, not stored in page
// data). Strip the prefix from `from` so both sides compare on
// the same terms, then re-add it when constructing the retry URL.
if (aposData.redirect && aposData.url !== '/') {
const prefix = config.aposPrefix || '';
let from = new URL(request.url).pathname.replace(/\/+$/, '');
if (prefix && from.startsWith(prefix + '/')) {
from = from.slice(prefix.length);
} else if (prefix && from === prefix) {
from = '/';
}
const to = (aposData.url || '').replace(/\/+$/, '');
if (from === to) {
const retryUrl = prefix + aposData.url;
const retry = new Request(new URL(retryUrl, request.url), request);
const retryResponse = await aposResponse(retry);
headers = retryResponse.headers;
const retryData = await retryResponse.json();
// Safety check: if the retry itself redirects to the same
// URL we just tried, we've hit an infinite redirect loop.
// Return an error instead of bouncing forever.
if (retryData.redirect && retryData.url === aposData.url) {
throw new Error(
`Infinite redirect detected: ${aposData.url} redirects back to itself`
);
}
aposData = retryData;
}
}

aposData.aposResponseHeaders = headers;
if (aposData.template === '@apostrophecms/page:notFound') {
aposData.notFound = true;
}
} catch (e) {
console.error('error:', e);
aposData.errorFetchingPage = e;
aposData.page = {
type: 'apos-fetch-error'
};
}
return aposData;
}
14 changes: 14 additions & 0 deletions packages/apostrophe-astro/helpers/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Server-only public helpers for @apostrophecms/apostrophe-astro.
*
* Use these in Astro frontmatter, server endpoints, prerendering routes,
* and any other server-side code. Do not import this module from
* client-side scripts — it depends on generated integration config and
* Node.js internals unavailable in browsers.
*
* @module @apostrophecms/apostrophe-astro/helpers/server
*/

export { aposFetch, aposPageFetch } from './fetch.js';
export { getAposHost, isStaticBuild } from './url.js';
export { getAllStaticPaths, getAllUrlMetadata, getLocales } from './static.js';
9 changes: 9 additions & 0 deletions packages/apostrophe-astro/helpers/server/static.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Static build helpers — server-only.
*
* Re-exports the public static-build functions from `lib/static.js`.
* Use these in `getStaticPaths()` inside your `[...slug].astro` page
* to fetch all page paths and props from the Apostrophe backend.
*/

export { getAllStaticPaths, getAllUrlMetadata, getLocales } from '../../lib/static.js';
Loading
Loading