summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2023-01-11 12:46:53 -0500
committerGravatar GitHub <noreply@github.com> 2023-01-11 12:46:53 -0500
commit665a2c2225e42881f5a9550599e8f3fc1deea0b4 (patch)
treeca534fe91b10b07f935ce8521d5f493b2ce45158
parent1f49cddf9e9ffc651efc171b2cbde9fbe9e8709d (diff)
downloadastro-665a2c2225e42881f5a9550599e8f3fc1deea0b4.tar.gz
astro-665a2c2225e42881f5a9550599e8f3fc1deea0b4.tar.zst
astro-665a2c2225e42881f5a9550599e8f3fc1deea0b4.zip
[Content collections] Improve content config handling (#5824)
* fix: always generate types on init * fix: skip type generation when no content dir found * fix: avoid stripping `.ts` for existsSync check * chore: changeset * fix: run type gen when content/ dir added in dev
-rw-r--r--.changeset/fluffy-onions-wink.md8
-rw-r--r--packages/astro/src/cli/sync/index.ts10
-rw-r--r--packages/astro/src/content/types-generator.ts37
-rw-r--r--packages/astro/src/content/utils.ts20
-rw-r--r--packages/astro/src/content/vite-plugin-content-server.ts61
5 files changed, 78 insertions, 58 deletions
diff --git a/.changeset/fluffy-onions-wink.md b/.changeset/fluffy-onions-wink.md
new file mode 100644
index 000000000..617ebd632
--- /dev/null
+++ b/.changeset/fluffy-onions-wink.md
@@ -0,0 +1,8 @@
+---
+'astro': patch
+---
+
+Better handle content type generation failures:
+- Generate types when content directory is empty
+- Log helpful error when running `astro sync` without a content directory
+- Avoid swallowing `config.ts` syntax errors from Vite
diff --git a/packages/astro/src/cli/sync/index.ts b/packages/astro/src/cli/sync/index.ts
index c91bd9bac..92cd05651 100644
--- a/packages/astro/src/cli/sync/index.ts
+++ b/packages/astro/src/cli/sync/index.ts
@@ -21,7 +21,15 @@ export async function sync(
fs,
settings,
});
- await contentTypesGenerator.init();
+ const typesResult = await contentTypesGenerator.init();
+ if (typesResult.typesGenerated === false) {
+ switch (typesResult.reason) {
+ case 'no-content-dir':
+ default:
+ info(logging, 'content', 'No content directory found. Skipping type generation.');
+ return 0;
+ }
+ }
} catch (e) {
throw new AstroError(AstroErrorData.GenerateContentTypesError);
}
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index 9df6bdb44..813f21c22 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -22,11 +22,6 @@ type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
type RawContentEvent = { name: ChokidarEvent; entry: string };
type ContentEvent = { name: ChokidarEvent; entry: URL };
-export type GenerateContentTypes = {
- init(): Promise<void>;
- queueEvent(event: RawContentEvent): void;
-};
-
type ContentTypesEntryMetadata = { slug: string };
type ContentTypes = Record<string, Record<string, ContentTypesEntryMetadata>>;
@@ -46,7 +41,7 @@ export async function createContentTypesGenerator({
fs,
logging,
settings,
-}: CreateContentGeneratorParams): Promise<GenerateContentTypes> {
+}: CreateContentGeneratorParams) {
const contentTypes: ContentTypes = {};
const contentPaths = getContentPaths(settings.config);
@@ -55,9 +50,15 @@ export async function createContentTypesGenerator({
const contentTypesBase = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8');
- async function init() {
- await handleEvent({ name: 'add', entry: contentPaths.config }, { logLevel: 'warn' });
- const globResult = await glob('./**/*.*', {
+ async function init(): Promise<
+ { typesGenerated: true } | { typesGenerated: false; reason: 'no-content-dir' }
+ > {
+ if (!fs.existsSync(contentPaths.contentDir)) {
+ return { typesGenerated: false, reason: 'no-content-dir' };
+ }
+
+ events.push(handleEvent({ name: 'add', entry: contentPaths.config }, { logLevel: 'warn' }));
+ const globResult = await glob('**', {
cwd: fileURLToPath(contentPaths.contentDir),
fs: {
readdir: fs.readdir.bind(fs),
@@ -74,6 +75,7 @@ export async function createContentTypesGenerator({
events.push(handleEvent({ name: 'add', entry }, { logLevel: 'warn' }));
}
await runEvents();
+ return { typesGenerated: true };
}
async function handleEvent(
@@ -109,10 +111,10 @@ export async function createContentTypesGenerator({
if (fileType === 'config') {
contentConfigObserver.set({ status: 'loading' });
const config = await loadContentConfig({ fs, settings });
- if (config instanceof Error) {
- contentConfigObserver.set({ status: 'error', error: config });
- } else {
+ if (config) {
contentConfigObserver.set({ status: 'loaded', config });
+ } else {
+ contentConfigObserver.set({ status: 'error' });
}
return { shouldGenerateTypes: true };
@@ -258,13 +260,13 @@ export function getEntryType(
entryPath: string,
paths: ContentPaths
): 'content' | 'config' | 'unknown' | 'generated-types' {
- const { dir: rawDir, ext, name, base } = path.parse(entryPath);
+ const { dir: rawDir, ext, base } = path.parse(entryPath);
const dir = appendForwardSlash(pathToFileURL(rawDir).href);
if ((contentFileExts as readonly string[]).includes(ext)) {
return 'content';
- } else if (new URL(name, dir).pathname === paths.config.pathname) {
+ } else if (new URL(base, dir).href === paths.config.href) {
return 'config';
- } else if (new URL(base, dir).pathname === new URL(CONTENT_TYPES_FILE, paths.cacheDir).pathname) {
+ } else if (new URL(base, dir).href === new URL(CONTENT_TYPES_FILE, paths.cacheDir).href) {
return 'generated-types';
} else {
return 'unknown';
@@ -313,6 +315,11 @@ async function writeContentFiles({
if (!isRelativePath(configPathRelativeToCacheDir))
configPathRelativeToCacheDir = './' + configPathRelativeToCacheDir;
+ // Remove `.ts` from import path
+ if (configPathRelativeToCacheDir.endsWith('.ts')) {
+ configPathRelativeToCacheDir = configPathRelativeToCacheDir.replace(/\.ts$/, '');
+ }
+
contentTypesBase = contentTypesBase.replace('// @@ENTRY_MAP@@', contentTypesStr);
contentTypesBase = contentTypesBase.replace(
"'@@CONTENT_CONFIG_TYPE@@'",
diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts
index a2599a9a6..a14be460a 100644
--- a/packages/astro/src/content/utils.ts
+++ b/packages/astro/src/content/utils.ts
@@ -201,16 +201,13 @@ export function parseFrontmatter(fileContents: string, filePath: string) {
}
}
-export class NotFoundError extends TypeError {}
-export class ZodParseError extends TypeError {}
-
export async function loadContentConfig({
fs,
settings,
}: {
fs: typeof fsMod;
settings: AstroSettings;
-}): Promise<ContentConfig | Error> {
+}): Promise<ContentConfig | undefined> {
const contentPaths = getContentPaths(settings.config);
const tempConfigServer: ViteDevServer = await createServer({
root: fileURLToPath(settings.config.root),
@@ -222,10 +219,13 @@ export async function loadContentConfig({
plugins: [astroContentVirtualModPlugin({ settings })],
});
let unparsedConfig;
+ if (!fs.existsSync(contentPaths.config)) {
+ return undefined;
+ }
try {
unparsedConfig = await tempConfigServer.ssrLoadModule(contentPaths.config.pathname);
- } catch {
- return new NotFoundError('Failed to resolve content config.');
+ } catch (e) {
+ throw e;
} finally {
await tempConfigServer.close();
}
@@ -233,14 +233,14 @@ export async function loadContentConfig({
if (config.success) {
return config.data;
} else {
- return new ZodParseError('Content config file is invalid.');
+ return undefined;
}
}
type ContentCtx =
| { status: 'loading' }
- | { status: 'loaded'; config: ContentConfig }
- | { status: 'error'; error: NotFoundError | ZodParseError };
+ | { status: 'error' }
+ | { status: 'loaded'; config: ContentConfig };
type Observable<C> = {
get: () => C;
@@ -292,6 +292,6 @@ export function getContentPaths({
contentDir: new URL('./content/', srcDir),
typesTemplate: new URL('types.d.ts', templateDir),
virtualModTemplate: new URL('virtual-mod.mjs', templateDir),
- config: new URL('./content/config', srcDir),
+ config: new URL('./content/config.ts', srcDir),
};
}
diff --git a/packages/astro/src/content/vite-plugin-content-server.ts b/packages/astro/src/content/vite-plugin-content-server.ts
index 347ebbe18..dda1a416f 100644
--- a/packages/astro/src/content/vite-plugin-content-server.ts
+++ b/packages/astro/src/content/vite-plugin-content-server.ts
@@ -5,13 +5,10 @@ import { pathToFileURL } from 'node:url';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { info, LogOptions } from '../core/logger/core.js';
+import { appendForwardSlash } from '../core/path.js';
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
import { contentFileExts, CONTENT_FLAG } from './consts.js';
-import {
- createContentTypesGenerator,
- GenerateContentTypes,
- getEntryType,
-} from './types-generator.js';
+import { createContentTypesGenerator, getEntryType } from './types-generator.js';
import {
ContentConfig,
contentObservable,
@@ -36,37 +33,33 @@ export function astroContentServerPlugin({
mode,
}: AstroContentServerPluginParams): Plugin[] {
const contentPaths = getContentPaths(settings.config);
- let contentDirExists = false;
- let contentGenerator: GenerateContentTypes;
const contentConfigObserver = contentObservable({ status: 'loading' });
+ async function initContentGenerator() {
+ const contentGenerator = await createContentTypesGenerator({
+ fs,
+ settings,
+ logging,
+ contentConfigObserver,
+ });
+ await contentGenerator.init();
+ return contentGenerator;
+ }
+
return [
{
name: 'astro-content-server-plugin',
async config(viteConfig) {
- try {
- await fs.promises.stat(contentPaths.contentDir);
- contentDirExists = true;
- } catch {
- /* silently move on */
- return;
- }
-
- if (contentDirExists && (mode === 'dev' || viteConfig.build?.ssr === true)) {
- contentGenerator = await createContentTypesGenerator({
- fs,
- settings,
- logging,
- contentConfigObserver,
- });
- await contentGenerator.init();
- info(logging, 'content', 'Types generated');
+ // Production build type gen
+ if (fs.existsSync(contentPaths.contentDir) && viteConfig.build?.ssr === true) {
+ await initContentGenerator();
}
},
async configureServer(viteServer) {
if (mode !== 'dev') return;
- if (contentDirExists) {
+ // Dev server type gen
+ if (fs.existsSync(contentPaths.contentDir)) {
info(
logging,
'content',
@@ -74,18 +67,22 @@ export function astroContentServerPlugin({
contentPaths.contentDir.href.replace(settings.config.root.href, '')
)} for changes`
);
- attachListeners();
+ await attachListeners();
} else {
- viteServer.watcher.on('addDir', (dir) => {
- if (pathToFileURL(dir).href === contentPaths.contentDir.href) {
+ viteServer.watcher.on('addDir', contentDirListener);
+ async function contentDirListener(dir: string) {
+ if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) {
info(logging, 'content', `Content dir found. Watching for changes`);
- contentDirExists = true;
- attachListeners();
+ await attachListeners();
+ viteServer.watcher.removeListener('addDir', contentDirListener);
}
- });
+ }
}
- function attachListeners() {
+ async function attachListeners() {
+ const contentGenerator = await initContentGenerator();
+ info(logging, 'content', 'Types generated');
+
viteServer.watcher.on('add', (entry) => {
contentGenerator.queueEvent({ name: 'add', entry });
});