mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
feature(astrojs/cloudflare): add support for splitted
SSR bundles (#7464)
* initial commit * try to fix windows * output files directly into the correct folder * allow for rest parameters * use fixed hook * improve tests * apply doc's team suggestions for README Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * try to fix prerendering * apply doc's team suggestion for changeset Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * bump to minor * readme update * resolve review comments * optimize memory allocation * resolve review comments * add removed link, to make sure old docs keep same * resolve comment Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
This commit is contained in:
parent
7ae6e89292
commit
1a59185ddd
16 changed files with 337 additions and 45 deletions
7
.changeset/healthy-books-study.md
Normal file
7
.changeset/healthy-books-study.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
'@astrojs/cloudflare': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Split Support in Cloudflare
|
||||||
|
|
||||||
|
Adds support for configuring `build.split` when using the Cloudflare adapter
|
|
@ -48,7 +48,11 @@ Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode
|
||||||
|
|
||||||
For most projects the adapter default of `advanced` will be sufficient; the `dist` folder will contain your compiled project. Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) such as [Sentry](https://developers.cloudflare.com/pages/platform/functions/plugins/sentry/) or write custom code to enable logging.
|
For most projects the adapter default of `advanced` will be sufficient; the `dist` folder will contain your compiled project. Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) such as [Sentry](https://developers.cloudflare.com/pages/platform/functions/plugins/sentry/) or write custom code to enable logging.
|
||||||
|
|
||||||
In directory mode the adapter will compile the client side part of your app the same way, but moves the worker script into a `functions` folder in the project root. The adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional plugins and pages middleware which can be checked into version control. Cloudflare documentation contains more information about [writing custom functions](https://developers.cloudflare.com/pages/platform/functions/).
|
In directory mode, the adapter will compile the client side part of your app the same way by default, but moves the worker script into a `functions` folder in the project root. In this case, the adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional plugins and pages middleware which can be checked into version control.
|
||||||
|
|
||||||
|
With the build configuration `split: true`, the adapter instead compiles a separate bundle for each page. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing `functions` files with identical names, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
|
||||||
|
|
||||||
|
Note that this adapter does not support using [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/). Astro will bundle the [Astro middleware](https://docs.astro.build/en/guides/middleware/) into each page.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// directory mode
|
// directory mode
|
||||||
|
|
|
@ -35,7 +35,8 @@
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
||||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
||||||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
||||||
"test": "mocha --exit --timeout 30000 test/"
|
"test": "mocha --exit --timeout 30000 test/",
|
||||||
|
"test:match": "mocha --exit --timeout 30000 -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/underscore-redirects": "^0.1.0",
|
"@astrojs/underscore-redirects": "^0.1.0",
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
|
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
|
||||||
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
|
import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import { dirname } from 'path';
|
||||||
import glob from 'tiny-glob';
|
import glob from 'tiny-glob';
|
||||||
import { fileURLToPath, pathToFileURL } from 'url';
|
import { fileURLToPath, pathToFileURL } from 'url';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ interface BuildConfig {
|
||||||
server: URL;
|
server: URL;
|
||||||
client: URL;
|
client: URL;
|
||||||
serverEntry: string;
|
serverEntry: string;
|
||||||
|
split?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
||||||
|
@ -21,7 +23,7 @@ export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
||||||
? {
|
? {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
||||||
exports: ['onRequest'],
|
exports: ['onRequest', 'manifest'],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
|
@ -41,6 +43,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
let _config: AstroConfig;
|
let _config: AstroConfig;
|
||||||
let _buildConfig: BuildConfig;
|
let _buildConfig: BuildConfig;
|
||||||
const isModeDirectory = args?.mode === 'directory';
|
const isModeDirectory = args?.mode === 'directory';
|
||||||
|
let _entryPoints = new Map<RouteData, URL>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
|
@ -90,7 +93,64 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
vite.ssr.target = 'webworker';
|
vite.ssr.target = 'webworker';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'astro:build:ssr': ({ manifest, entryPoints }) => {
|
||||||
|
_entryPoints = entryPoints;
|
||||||
|
},
|
||||||
'astro:build:done': async ({ pages, routes, dir }) => {
|
'astro:build:done': async ({ pages, routes, dir }) => {
|
||||||
|
const functionsUrl = new URL('functions/', _config.root);
|
||||||
|
|
||||||
|
if (isModeDirectory) {
|
||||||
|
await fs.promises.mkdir(functionsUrl, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isModeDirectory && _buildConfig.split) {
|
||||||
|
const entryPointsRouteData = [..._entryPoints.keys()]
|
||||||
|
const entryPointsURL = [..._entryPoints.values()]
|
||||||
|
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
|
||||||
|
const outputDir = fileURLToPath(new URL('.astro', _buildConfig.server));
|
||||||
|
|
||||||
|
// NOTE: AFAIK, esbuild keeps the order of the entryPoints array
|
||||||
|
const { outputFiles } = await esbuild.build({
|
||||||
|
target: 'es2020',
|
||||||
|
platform: 'browser',
|
||||||
|
conditions: ['workerd', 'worker', 'browser'],
|
||||||
|
entryPoints: entryPaths,
|
||||||
|
outdir: outputDir,
|
||||||
|
allowOverwrite: true,
|
||||||
|
format: 'esm',
|
||||||
|
bundle: true,
|
||||||
|
minify: _config.vite?.build?.minify !== false,
|
||||||
|
banner: {
|
||||||
|
js: SHIM,
|
||||||
|
},
|
||||||
|
logOverride: {
|
||||||
|
'ignored-bare-import': 'silent',
|
||||||
|
},
|
||||||
|
write: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// loop through all bundled files and write them to the functions folder
|
||||||
|
for (const [index, outputFile] of outputFiles.entries()) {
|
||||||
|
// we need to make sure the filename in the functions folder
|
||||||
|
// matches to cloudflares routing capabilities (see their docs)
|
||||||
|
// IN: src/pages/[language]/files/[...path].astro
|
||||||
|
// OUT: [language]/files/[[path]].js
|
||||||
|
const fileName = entryPointsRouteData[index].component
|
||||||
|
.replace('src/pages/', '')
|
||||||
|
.replace('.astro', '.js')
|
||||||
|
.replace(/(\[\.\.\.)(\w+)(\])/g, (_match, _p1, p2, _p3) => {
|
||||||
|
return `[[${p2}]]`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileUrl = new URL(fileName, functionsUrl)
|
||||||
|
const newFileDir = dirname(fileURLToPath(fileUrl));
|
||||||
|
if (!fs.existsSync(newFileDir)) {
|
||||||
|
fs.mkdirSync(newFileDir, { recursive: true });
|
||||||
|
}
|
||||||
|
await fs.promises.writeFile(fileUrl, outputFile.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
|
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
|
||||||
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
|
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
|
||||||
const buildPath = fileURLToPath(entryUrl);
|
const buildPath = fileURLToPath(entryUrl);
|
||||||
|
@ -118,7 +178,14 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
// Rename to worker.js
|
// Rename to worker.js
|
||||||
await fs.promises.rename(buildPath, finalBuildUrl);
|
await fs.promises.rename(buildPath, finalBuildUrl);
|
||||||
|
|
||||||
// throw the server folder in the bin
|
if (isModeDirectory) {
|
||||||
|
const directoryUrl = new URL('[[path]].js', functionsUrl);
|
||||||
|
await fs.promises.rename(finalBuildUrl, directoryUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// // // throw the server folder in the bin
|
||||||
const serverUrl = new URL(_buildConfig.server);
|
const serverUrl = new URL(_buildConfig.server);
|
||||||
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
||||||
|
|
||||||
|
@ -225,14 +292,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isModeDirectory) {
|
|
||||||
const functionsUrl = new URL('functions/', _config.root);
|
|
||||||
await fs.promises.mkdir(functionsUrl, { recursive: true });
|
|
||||||
|
|
||||||
const directoryUrl = new URL('[[path]].js', functionsUrl);
|
|
||||||
await fs.promises.rename(finalBuildUrl, directoryUrl);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,5 +61,5 @@ export function createExports(manifest: SSRManifest) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return { onRequest };
|
return { onRequest, manifest };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import cloudflare from '../dist/index.js';
|
||||||
|
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
|
describe('Cloudflare SSR split', () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/split/',
|
||||||
|
adapter: cloudflare({ mode: 'directory' }),
|
||||||
|
output: "server",
|
||||||
|
build: {
|
||||||
|
split: true,
|
||||||
|
excludeMiddleware: false
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
minify: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
fixture.clean();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates functions folders inside the project root, and checks that each page is emitted by astro', async () => {
|
||||||
|
expect(await fixture.pathExists('../functions')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/index.js')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/blog/cool.js')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/blog/[post].js')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/[person]/[car].js')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/files/[[path]].js')).to.be.true;
|
||||||
|
expect(await fixture.pathExists('../functions/[language]/files/[[path]].js')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates pre-rendered files', async () => {
|
||||||
|
expect(await fixture.pathExists('./prerender/index.html')).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
9
packages/integrations/cloudflare/test/fixtures/split/package.json
vendored
Normal file
9
packages/integrations/cloudflare/test/fixtures/split/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "@test/astro-cloudflare-split",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/cloudflare": "workspace:*",
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
10
packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts
vendored
Normal file
10
packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineMiddleware } from "astro/middleware";
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware(({ locals, request }, next) => {
|
||||||
|
// intercept response data from a request
|
||||||
|
// optionally, transform the response by modifying `locals`
|
||||||
|
locals.title = "New title"
|
||||||
|
|
||||||
|
// return a Response or the result of calling `next()`
|
||||||
|
return next()
|
||||||
|
});
|
37
packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro
vendored
Normal file
37
packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
slug: undefined,
|
||||||
|
title: 'Root level',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'test.png',
|
||||||
|
title: "One level"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'assets/test.png',
|
||||||
|
title: "Two levels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'assets/images/test.png',
|
||||||
|
title: 'Three levels',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const { path } = Astro.params;
|
||||||
|
const page = files.find((page) => page.slug === path);
|
||||||
|
const { title } = page;
|
||||||
|
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Files / Rest Parameters / {title}</h1>
|
||||||
|
<p>DEBUG: {path} </p>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro
vendored
Normal file
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
const { person, car } = Astro.params;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1> {person} / {car}</h1>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro
vendored
Normal file
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
const { post } = Astro.params;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Blog / {post}</h1>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
11
packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro
vendored
Normal file
11
packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Blog / Cool</h1>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
37
packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro
vendored
Normal file
37
packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
slug: undefined,
|
||||||
|
title: 'Root level',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'test.png',
|
||||||
|
title: "One level"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'assets/test.png',
|
||||||
|
title: "Two levels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'assets/images/test.png',
|
||||||
|
title: 'Three levels',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const { path } = Astro.params;
|
||||||
|
const page = files.find((page) => page.slug === path);
|
||||||
|
const { title } = page;
|
||||||
|
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Files / Rest Parameters / {title}</h1>
|
||||||
|
<p>DEBUG: {path} </p>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
22
packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro
vendored
Normal file
22
packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
const data = Astro.locals;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Index</h1>
|
||||||
|
<p>Middleware ({data.title})</p>
|
||||||
|
<p><a href="/prerender/">prerender</a></p>
|
||||||
|
<p><a href="/blog/cool/">sub-route</a></p>
|
||||||
|
<p><a href="/blog/dynamic-post/">dynamic route in static sub-route</a></p>
|
||||||
|
<p><a href="/mustermann/bmw/">dynamic route in dynamic sub-route</a></p>
|
||||||
|
<p><a href="/files/">rest parameters root level</a></p>
|
||||||
|
<p><a href="/files/test.png/">rest parameters one level</a></p>
|
||||||
|
<p><a href="/files/assets/test.png/">rest parameters two level</a></p>
|
||||||
|
<p><a href="/files/assets/images/test.png/">rest parameters three level</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro
vendored
Normal file
14
packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
export const prerender = true;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Prerender</h1>
|
||||||
|
<p><a href="/">index</a></p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
|
@ -3675,6 +3675,15 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../../../astro
|
version: link:../../../../../astro
|
||||||
|
|
||||||
|
packages/integrations/cloudflare/test/fixtures/split:
|
||||||
|
dependencies:
|
||||||
|
'@astrojs/cloudflare':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../../../astro
|
||||||
|
|
||||||
packages/integrations/cloudflare/test/fixtures/with-solid-js:
|
packages/integrations/cloudflare/test/fixtures/with-solid-js:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/cloudflare':
|
'@astrojs/cloudflare':
|
||||||
|
|
Loading…
Reference in a new issue