Skip to content

Commit

Permalink
Merge pull request #207 from CloudCannon/feat/astro-5
Browse files Browse the repository at this point in the history
Add support for Astro 5
  • Loading branch information
bglw authored Jan 22, 2025
2 parents d7c7b67 + f60b7c8 commit f3641cc
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 21 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 20.x

- name: Ubuntu AppArmor fix
if: ${{ matrix.os == 'ubuntu-latest' }}
# Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
# See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
# this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
# The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
# This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
run: |
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
shell: bash
# TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
- name: Windows Node fix
if: ${{ matrix.os == 'windows-latest' }}
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 20.x
- name: Ubuntu AppArmor fix
if: ${{ matrix.os == 'ubuntu-latest' }}
# Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
# See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
# this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
# The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
# This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
run: |
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
shell: bash
# TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
- name: Windows Node fix
if: ${{ matrix.os == 'windows-latest' }}
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

## Unreleased

* Added support for Astro 5.
* Added support for `astro:env` and public environment variables in Astro components.
* Added support for the `getImage` function in Astro components.
* Added fallbacks for the `astro:actions`, `astro:i18n`, `astro middleware`, and `astro:transitions` virtual modules.

## v3.12.0 (October 28, 2024)

* Added a `--disable-bindings` flag to the `@bookshop/generate` command.
Expand Down
70 changes: 52 additions & 18 deletions javascript-modules/engines/astro-engine/lib/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ const { transform: bookshopTransform } = AstroPluginVite({
__removeClientDirectives: true,
});

const getEnvironmentDefines = () => {
return Object.entries(process.env ?? {}).reduce((acc, [key, value]) => {
if(key.startsWith("PUBLIC_")){
acc[`import.meta.env.${key}`] = JSON.stringify(value);
}
return acc;
}, {})
}

export const buildPlugins = [
sassPlugin({
filter: /\.module\.(s[ac]ss|css)$/,
Expand Down Expand Up @@ -83,7 +92,13 @@ export const buildPlugins = [

build.onResolve({ filter: /^astro:.*$/ }, async (args) => {
const type = args.path.replace("astro:", "");
if (type !== "content" && type !== "assets") {
if (type === "env/client" || type === "env"){
return { path: 'env', namespace: 'virtual' };
}
if (type === "transitions/client" || type === "transitions"){
return { path: join(dir, "modules", "transitions.js").replace("file:", "") };
}
if (!["content", "assets", "i18n", "actions", "middleware"].includes(type)) {
console.error(
`Error: The 'astro:${type}' module is not supported inside Bookshop components.`
);
Expand Down Expand Up @@ -118,6 +133,35 @@ export const buildPlugins = [
}
});

build.onLoad({ filter: /^env$/, namespace: 'virtual' }, async (args) => {
let contents = "";
Object.entries(astroConfig?.env?.schema ?? {}).forEach(([key, schema]) => {
if(schema.context !== "client" || schema.access !== "public"){
return;
}

try{
switch(schema.type){
case "boolean":
contents += `export const ${key} = ${!!process.env[key]};\n`
break;
case "number":
contents += `export const ${key} = ${Number(process.env[key])};\n`
break;
default:
contents += `export const ${key} = ${JSON.stringify(process.env[key] ?? "")};\n`
}
} catch(e){
//Error intentionally ignored
}
});
contents += "export const getSecret = () => console.warn(\"[Bookshop] getSecret is not supported in Bookshop. Please use an editing fallback instead.\");"
return {
contents,
loader: "js",
};
});

build.onLoad({ filter: /\.astro$/, namespace: "style" }, async (args) => {
let text = await fs.promises.readFile(args.path, "utf8");
let transformed = await transform(text, {
Expand Down Expand Up @@ -155,9 +199,9 @@ export const buildPlugins = [
loader: "ts",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true"},
});
let result = await bookshopTransform(
let result = bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);
Expand Down Expand Up @@ -189,10 +233,10 @@ export const buildPlugins = [
jsxFragment: "__React.Fragment",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
});

let result = await bookshopTransform(
let result = bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);
Expand All @@ -218,26 +262,16 @@ export const buildPlugins = [
loader: "ts",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
});

let result = await bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);

if (!result) {
console.warn("Bookshop transform failed:", args.path);
result = jsResult;
}

let importTransform = (await resolveConfig({}, "build")).plugins.find(
({ name }) => name === "vite:import-glob"
).transform;
let viteResult = await importTransform(result.code, args.path);
let viteResult = await importTransform(jsResult.code, args.path);

return {
contents: viteResult?.code ?? result.code,
contents: viteResult?.code ?? jsResult.code,
loader: "js",
};
});
Expand Down
23 changes: 22 additions & 1 deletion javascript-modules/engines/astro-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class Engine {
this.reactRoots.push({Component, props});
return { html: `<div data-react-root=${this.reactRoots.length-1}></div>` };
}

const reactNode = await Component(props);
return { html: renderToStaticMarkup(reactNode) };
},
Expand Down Expand Up @@ -118,12 +118,30 @@ export class Engine {

async renderAstroComponent(target, key, props, globals) {
const component = this.files?.[key];

let encryptionKey;
try{
encryptionKey = window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
)
} catch(err){
console.warn("[Bookshop] Could not generate a key for Astro component. This may cause issues with Astro components that use server-islands")
}

const SSRResult = {
styles: new Set(),
scripts: new Set(),
links: new Set(),
propagation: new Map(),
propagators: new Map(),
serverIslandNameMap: { get: () => "Bookshop" },
key: encryptionKey,
base: "/",
extraHead: [],
componentMetadata: new Map(),
renderers: this.renderers,
Expand Down Expand Up @@ -166,6 +184,9 @@ export class Engine {
flushSync(() => root.render(reactNode));
});
this.reactRoots = [];
target.querySelectorAll("link, [data-island-id]").forEach((node) => {
node.remove();
});
}

async eval(str, props = [{}]) {
Expand Down
51 changes: 51 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const actions = new Proxy({}, {
get() {
console.warn("[Bookshop] actions is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
}
});

export const defineAction = () => {
console.warn("[Bookshop] defineAction is not supported in Bookshop. Please use an editing fallback instead.");
return {
handler: () => {},
input: null
};
};

export const isInputError = (error) => {
console.warn("[Bookshop] isInputError is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};

export const isActionError = (error) => {
console.warn("[Bookshop] isActionError is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};

export class ActionError extends Error {
constructor(code, message) {
super(message);
console.warn("[Bookshop] ActionError is not supported in Bookshop. Please use an editing fallback instead.");
this.code = code;
}
}

export const getActionContext = (context) => {
console.warn("[Bookshop] getActionContext is not supported in Bookshop. Please use an editing fallback instead.");
return {
action: undefined,
setActionResult: () => {},
serializeActionResult: () => ({})
};
};

export const deserializeActionResult = (result) => {
console.warn("[Bookshop] deserializeActionResult is not supported in Bookshop. Please use an editing fallback instead.");
return {};
};

export const getActionPath = (action) => {
console.warn("[Bookshop] getActionPath is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};
27 changes: 27 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,30 @@ import PictureInternal from './picture.astro';

export const Image = ImageInternal;
export const Picture = PictureInternal;

export const getImage = async (options) => {
const resolvedSrc =
typeof options.src === "object" && "then" in options.src
? (await options.src).default ?? (await options.src)
: options.src;
return {
rawOptions: {
src: {
src: resolvedSrc,
},
},
options: {
src: {
src: resolvedSrc,
},
},
src: resolvedSrc,
srcSet: { values: [] },
attributes: { }
}
}

export const inferRemoteSize = async () => {
console.warn("[Bookshop] inferRemoteSize is not supported in Bookshop. Please use an editing fallback instead.");
return {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
---
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ export const getEntries = (entries) => {
export const getEntryBySlug = (collection, slug) => {
return getEntry({ collection, slug });
};

export const render = async () => ({ Content: () => "Content is not available when live editing", headings: [], remarkPluginFrontmatter: {} });

export const defineCollection = () => console.warn("[Bookshop] defineCollection is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
export const reference = () => console.warn("[Bookshop] reference is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
54 changes: 54 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const getRelativeLocaleUrl = (locale, path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getAbsoluteLocaleUrl = (locale, path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getRelativeLocaleUrlList = (path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return [];
};

export const getAbsoluteLocaleUrlList = (path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return [];
};

export const getPathByLocale = (locale) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getLocaleByPath = (path) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const redirectToDefaultLocale = (context, statusCode) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const redirectToFallback = (context, response) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const notFound = (context, response) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const middleware = (options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const requestHasLocale = (context) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};
19 changes: 19 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const sequence = (...handlers) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const defineMiddleware = (fn) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const createContext = (context) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return {};
};

export const trySerializeLocals = (value) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};
Loading

0 comments on commit f3641cc

Please sign in to comment.