[Content collections] Fix "underscore to ignore" warnings (#6122)

* refactor: fix underscore check

* fix: add "ignore list" to always silence log

* fix: hide log on file deleted

* refactor: move getEntryType to util

* test: getEntryType unit

* fix: handle all unsupported cases

* chore: changeset
This commit is contained in:
Ben Holmes 2023-02-03 16:48:34 -05:00 committed by GitHub
parent f7f4721231
commit 9f22ac3d09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 34 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Content collections: Fix accidental "use underscore to ignore" logs for `.DS_Store` files and underscored directory names.

View file

@ -6,8 +6,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
import { normalizePath, ViteDevServer } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { info, LogOptions, warn } from '../core/logger/core.js';
import { appendForwardSlash, isRelativePath } from '../core/path.js';
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
import { isRelativePath } from '../core/path.js';
import { CONTENT_TYPES_FILE } from './consts.js';
import {
ContentConfig,
ContentObservable,
@ -16,6 +16,7 @@ import {
getContentPaths,
getEntryInfo,
getEntrySlug,
getEntryType,
loadContentConfig,
NoCollectionError,
parseFrontmatter,
@ -111,7 +112,7 @@ export async function createContentTypesGenerator({
return { shouldGenerateTypes: true };
}
const fileType = getEntryType(fileURLToPath(event.entry), contentPaths);
if (fileType === 'generated-types') {
if (fileType === 'ignored') {
return { shouldGenerateTypes: false };
}
if (fileType === 'config') {
@ -125,22 +126,21 @@ export async function createContentTypesGenerator({
return { shouldGenerateTypes: true };
}
if (fileType === 'unknown') {
if (fileType === 'unsupported') {
// Avoid warning if file was deleted.
if (event.name === 'unlink') {
return { shouldGenerateTypes: false };
}
const entryInfo = getEntryInfo({
entry: event.entry,
contentDir: contentPaths.contentDir,
// Allow underscore `_` files outside collection directories
// Skip invalid file check. We already know its invalid.
allowFilesOutsideCollection: true,
});
if (entryInfo.id.startsWith('_') && (event.name === 'add' || event.name === 'change')) {
// Silently ignore `_` files.
return { shouldGenerateTypes: false };
} else {
return {
shouldGenerateTypes: false,
error: new UnsupportedFileTypeError(entryInfo.id),
};
}
return {
shouldGenerateTypes: false,
error: new UnsupportedFileTypeError(entryInfo.id),
};
}
const entryInfo = getEntryInfo({
entry: event.entry,
@ -289,23 +289,6 @@ function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey
delete contentTypes[collectionKey][entryKey];
}
export function getEntryType(
entryPath: string,
paths: ContentPaths
): 'content' | 'config' | 'unknown' | 'generated-types' {
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(base, dir).href === paths.config.href) {
return 'config';
} else if (new URL(base, dir).href === new URL(CONTENT_TYPES_FILE, paths.cacheDir).href) {
return 'generated-types';
} else {
return 'unknown';
}
}
async function writeContentFiles({
fs,
contentPaths,

View file

@ -2,12 +2,13 @@ import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter';
import type fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod';
import { AstroConfig, AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { CONTENT_TYPES_FILE } from './consts.js';
import { appendForwardSlash } from '../core/path.js';
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
export const collectionConfigParser = z.object({
schema: z.any().optional(),
@ -158,6 +159,38 @@ export function getEntryInfo({
return res;
}
export function getEntryType(
entryPath: string,
paths: Pick<ContentPaths, 'config'>
): 'content' | 'config' | 'ignored' | 'unsupported' {
const { dir: rawDir, ext, base } = path.parse(entryPath);
const dir = appendForwardSlash(pathToFileURL(rawDir).href);
const fileUrl = new URL(base, dir);
if (hasUnderscoreInPath(fileUrl) || isOnIgnoreList(fileUrl)) {
return 'ignored';
} else if ((contentFileExts as readonly string[]).includes(ext)) {
return 'content';
} else if (fileUrl.href === paths.config.href) {
return 'config';
} else {
return 'unsupported';
}
}
function isOnIgnoreList(fileUrl: URL) {
const { base } = path.parse(fileURLToPath(fileUrl));
return ['.DS_Store'].includes(base);
}
function hasUnderscoreInPath(fileUrl: URL): boolean {
const parts = fileUrl.pathname.split('/');
for (const part of parts) {
if (part.startsWith('_')) return true;
}
return false;
}
const flattenErrorPath = (errorPath: (string | number)[]) => errorPath.join('.');
const errorMap: z.ZodErrorMap = (error, ctx) => {

View file

@ -7,7 +7,7 @@ import { AstroErrorData } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/errors.js';
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
import { contentFileExts, CONTENT_FLAG } from './consts.js';
import { getEntryType } from './types-generator.js';
import { getEntryType } from './utils.js';
import {
ContentConfig,
getContentPaths,

View file

@ -0,0 +1,62 @@
import { getEntryType } from '../../../dist/content/utils.js';
import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
describe('Content Collections - getEntryType', () => {
const contentDir = new URL('src/content/', import.meta.url);
const contentPaths = {
config: new URL('src/content/config.ts', import.meta.url),
};
it('Returns "content" for Markdown files', () => {
for (const entryPath of ['blog/first-post.md', 'blog/first-post.mdx']) {
const entry = fileURLToPath(new URL(entryPath, contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('content');
}
});
it('Returns "content" for Markdown files in nested directories', () => {
for (const entryPath of ['blog/2021/01/01/index.md', 'blog/2021/01/01/index.mdx']) {
const entry = fileURLToPath(new URL(entryPath, contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('content');
}
});
it('Returns "config" for config files', () => {
const entry = fileURLToPath(contentPaths.config);
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('config');
});
it('Returns "unsupported" for non-Markdown files', () => {
const entry = fileURLToPath(new URL('blog/robots.txt', contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('unsupported');
});
it('Returns "ignored" for .DS_Store', () => {
const entry = fileURLToPath(new URL('blog/.DS_Store', contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('ignored');
});
it('Returns "ignored" for unsupported files using an underscore', () => {
const entry = fileURLToPath(new URL('blog/_draft-robots.txt', contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('ignored');
});
it('Returns "ignored" when using underscore on file name', () => {
const entry = fileURLToPath(new URL('blog/_first-post.md', contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('ignored');
});
it('Returns "ignored" when using underscore on directory name', () => {
const entry = fileURLToPath(new URL('blog/_draft/first-post.md', contentDir));
const type = getEntryType(entry, contentPaths);
expect(type).to.equal('ignored');
});
});