-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unknown Content Collection Error when content
directory is a symlink
#9088
Comments
I have the same issue with the latest version of Astro & Starlight. In
This results in the following error when I run
|
This was marked as "has workaround" because you don't need to use symlinks, but it's not exactly the same thing. I recently dug into this myself and it seems fixable. Some of the content collection code is explicitly blocking symlinks because we typically determine collections from the filepath. But IMO, symlinking the entire I traced the error thrown to this part of the code, which might be a good starting place for anyone looking to dig into this. https://github.com/withastro/astro/blob/main/packages/astro/src/content/utils.ts#L182-L197 |
Thanks for pointing me in that direction. I debugged it and it all looked ok. Despite symlinking I decided to manually install
I wonder if it's related to PNPM and the fact that the Astro Starlight app runs in Update: I removed the above dependencies because it felt too hacky. What helped, but also isn't ideal, was solution 1 in the PNPM FAQs. |
Can't we use the symbolic path as if the symlink were not there, similar to what most shells do? |
I wrote a quick, isolated hacky vite plugin that solves this problem: /** @typedef {NonNullable<import('astro').ViteUserConfig['plugins']>[number] } VitePluginOption */
/** @typedef {Extract<VitePluginOption, { name: string}>} VitePlugin */
/**
* Workaround to allow Astro content collections to work with symlinks and pnpm
* @param {Object} options
* @param {Record<string, string>} options.collections - a mapping of collection names to directories, relative to the root defined in astro.config.js
* @returns {VitePlugin}
*/
const symlinkPlugin = ({ collections }) => {
/**
* Finds the plugin object with the specified name
* @param {VitePluginOption[] | null | undefined} plugins
* @param {string} targetName
* @returns {VitePlugin | null}
*/
const resolveTargetPlugin = (plugins, targetName) => {
for (const plugin of plugins ?? []) {
if (!plugin || typeof plugin !== 'object' || plugin instanceof Promise) {
continue;
} else if (Array.isArray(plugin)) {
const target = resolveTargetPlugin(plugin, targetName);
if (target) return target;
} else if (plugin.name === targetName) {
return plugin;
}
}
return null;
};
return {
config: async ({ plugins, root }) => {
if (!root) {
throw new TypeError('Expected root to be defined in astro config');
}
const contentDir = path.resolve(root, './src/content');
const collectionDirents = await fs.readdir(contentDir, { encoding: 'utf-8', withFileTypes: true });
const targetName = 'astro:content-imports';
const target = resolveTargetPlugin(plugins, targetName);
const transform = target?.transform;
if (!target) {
throw new Error(`Failed to find target plugin: ${targetName}`);
} else if (typeof transform !== 'function') {
throw new Error(`Unexpected type of transform method: ${typeof transform}`);
}
/**
* Attempt to resolve the absolute path to the symbolic link for the real directory 'id'
* @param {string} id
*/
const resolveSymbolicLink = (id) => {
for (const [name, relpath] of Object.entries(collections)) {
const targetPrefix = path.resolve(root, relpath);
if (id.startsWith(targetPrefix)) {
const dirent = collectionDirents.find((dirent) => dirent.name === name);
if (!dirent) {
throw new Error(`Expected collection '${name}' does not exist in directory: ${contentDir}`);
} else if (!dirent.isSymbolicLink()) {
throw new Error(`File is not a symbolic link: ${path.join(dirent.path, dirent.name)}`);
}
return id.replace(targetPrefix, path.join(dirent.path, dirent.name));
}
}
throw new Error(`Failed to resolve symbolic link for ID: ${id}`);
};
target.transform = async function (code, id, options) {
/** @type {ReturnType<Extract<VitePlugin['transform'], Function>>} */
let result;
try {
result = await transform.call(this, code, id, options);
} catch (err) {
if (!(err instanceof Error && err.name === 'UnknownContentCollectionError')) {
throw err;
}
id = resolveSymbolicLink(id);
result = await transform.call(this, code, id, options);
}
return result;
};
},
name: 'symlink-plugin'
};
}; For example, if I have astro.config.js export default defineConfig({
...,
vite: {
plugins: [
symlinkPlugin({
collections: {
blog: '../../blog',
docs: '../../docs'
}
})
],
resolve: {
// the default behavior, but make sure this is not true as it is handled by plugin
preserveSymlinks: false
}
}
}) |
Astro Info
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
When
./src/content
is a symlink and viagetEntry
data is retrieved from any collection, aUnknown Content Collection Error
is thrown:What's the expected result?
It should be possible to retrieve collection data through symlinked
content
directories.Link to Minimal Reproducible Example
https://stackblitz.com/edit/github-tvxvbc
Participation
Tasks
The text was updated successfully, but these errors were encountered: