Skip to content

Commit

Permalink
fix(astro): handle symlinked content collection directories
Browse files Browse the repository at this point in the history
  • Loading branch information
ascorbic committed Jun 11, 2024
1 parent 02e5617 commit c9eb223
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-clouds-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Supports symlinked content collection directories
40 changes: 40 additions & 0 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,46 @@ export function getEntryConfigByExtMap<TEntryType extends ContentEntryType | Dat
return map;
}

export async function getSymlinkedContentCollections(
contentDir: URL
): Promise<Map<string, string>> {
const contentPaths = new Map<string, string>();
const contentDirPath = fileURLToPath(contentDir);

const contentDirEntries = await fsMod.promises.readdir(contentDir, { withFileTypes: true });
for (const entry of contentDirEntries) {
if (entry.isSymbolicLink()) {
const entryPath = path.join(contentDirPath, entry.name);
const realPath = await fsMod.promises.realpath(entryPath);
contentPaths.set(realPath, entry.name);
}
}
return contentPaths;
}

export function reverseSymlinks({
entry,
symlinks,
contentDir,
}: {
entry: string | URL;
contentDir: string | URL;
symlinks?: Map<string, string>;
}): string {
const entryPath = typeof entry === 'string' ? entry : fileURLToPath(entry);
const contentDirPath = typeof contentDir === 'string' ? contentDir : fileURLToPath(contentDir);
if (!symlinks) {
return entryPath;
}

for (const [realPath, symlinkName] of symlinks) {
if (entryPath.startsWith(realPath)) {
return path.join(contentDirPath, symlinkName, entryPath.replace(realPath, ''));
}
}
return entryPath;
}

export function getEntryCollectionName({
contentDir,
entry,
Expand Down
18 changes: 15 additions & 3 deletions packages/astro/src/content/vite-plugin-content-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import {
getEntryConfigByExtMap,
getEntryData,
getEntryType,
getSymlinkedContentCollections,
globalContentConfigObserver,
hasContentFlag,
parseEntrySlug,
reloadContentConfigObserver,
reverseSymlinks,
} from './utils.js';

function getContentRendererByViteId(
Expand Down Expand Up @@ -75,16 +77,26 @@ export function astroContentImportPlugin({
const dataEntryConfigByExt = getEntryConfigByExtMap(settings.dataEntryTypes);
const { contentDir } = contentPaths;
let shouldEmitFile = false;

let symlinks: Map<string, string>;
const plugins: Plugin[] = [
{
name: 'astro:content-imports',
config(_config, env) {
shouldEmitFile = env.command === 'build';
},
async buildStart() {
// Get symlinks once at build start
symlinks = await getSymlinkedContentCollections(contentDir);
},
async transform(_, viteId) {
if (hasContentFlag(viteId, DATA_FLAG)) {
const fileId = viteId.split('?')[0] ?? viteId;
// By default, Vite will resolve symlinks to their targets. We need to reverse this for
// content entries, so we can get the path relative to the content directory.
const fileId = reverseSymlinks({
entry: viteId.split('?')[0] ?? viteId,
contentDir,
symlinks,
});
// Data collections don't need to rely on the module cache.
// This cache only exists for the `render()` function specific to content.
const { id, data, collection, _internal } = await getDataEntryModule({
Expand All @@ -109,7 +121,7 @@ export const _internal = {
`;
return code;
} else if (hasContentFlag(viteId, CONTENT_FLAG)) {
const fileId = viteId.split('?')[0];
const fileId = reverseSymlinks({ entry: viteId.split('?')[0], contentDir, symlinks });
const { id, slug, collection, body, data, _internal } = await getContentEntryModule({
fileId,
entryConfigByExt: contentEntryConfigByExt,
Expand Down
22 changes: 22 additions & 0 deletions packages/astro/test/content-collections.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,28 @@ describe('Content Collections', () => {
subject: 'My Newsletter',
});
});

it('Handles symlinked content', async () => {
assert.ok(json.hasOwnProperty('withSymlinkedContent'));
assert.equal(Array.isArray(json.withSymlinkedContent), true);

const ids = json.withSymlinkedContent.map((item) => item.id);
assert.deepEqual(ids, ['first.md', 'second.md', 'third.md']);
assert.equal(json.withSymlinkedContent[0].data.title, 'First Blog');
});

it('Handles symlinked data', async () => {
assert.ok(json.hasOwnProperty('withSymlinkedData'));
assert.equal(Array.isArray(json.withSymlinkedData), true);

const ids = json.withSymlinkedData.map((item) => item.id);
assert.deepEqual(ids, ['welcome']);
assert.equal(
json.withSymlinkedData[0].data.alt,
'Futuristic landscape with chrome buildings and blue skies'
);
assert.notEqual(json.withSymlinkedData[0].data.src.src, undefined);
});
});

describe('Propagation', () => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const withSchemaConfig = defineCollection({
isDraft: z.boolean().default(false),
lang: z.enum(['en', 'fr', 'es']).default('en'),
publishedAt: z.date().transform((val) => new Date(val)),
})
}),
});

const withUnionSchema = defineCollection({
Expand All @@ -28,8 +28,27 @@ const withUnionSchema = defineCollection({
]),
});

const withSymlinkedData = defineCollection({
type: 'data',
schema: ({ image }) =>
z.object({
alt: z.string(),
src: image(),
}),
});

const withSymlinkedContent = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.date(),
}),
});

export const collections = {
'with-custom-slugs': withCustomSlugs,
'with-schema-config': withSchemaConfig,
'with-union-schema': withUnionSchema,
}
'with-symlinked-data': withSymlinkedData,
'with-symlinked-content': withSymlinkedContent,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export async function GET() {
const withSchemaConfig = stripAllRenderFn(await getCollection('with-schema-config'));
const withSlugConfig = stripAllRenderFn(await getCollection('with-custom-slugs'));
const withUnionSchema = stripAllRenderFn(await getCollection('with-union-schema'));
const withSymlinkedContent = stripAllRenderFn(await getCollection('with-symlinked-content'));
const withSymlinkedData = stripAllRenderFn(await getCollection('with-symlinked-data'));

return new Response(
devalue.stringify({ withoutConfig, withSchemaConfig, withSlugConfig, withUnionSchema })
devalue.stringify({ withoutConfig, withSchemaConfig, withSlugConfig, withUnionSchema, withSymlinkedContent, withSymlinkedData }),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "First Blog"
date: 2024-04-05
---

First blog content.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "Second Blog"
date: 2024-04-06
---

Second blog content.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "Third Blog"
date: 2024-04-07
---

Third blog content.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"alt": "Futuristic landscape with chrome buildings and blue skies",
"src": "../../assets/the-future.jpg"
}
1 change: 0 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c9eb223

Please sign in to comment.