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

View file

@ -2,12 +2,13 @@ import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter'; import matter from 'gray-matter';
import type fsMod from 'node:fs'; import type fsMod from 'node:fs';
import path from 'node:path'; 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 { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod'; import { z } from 'zod';
import { AstroConfig, AstroSettings } from '../@types/astro.js'; import { AstroConfig, AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.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({ export const collectionConfigParser = z.object({
schema: z.any().optional(), schema: z.any().optional(),
@ -158,6 +159,38 @@ export function getEntryInfo({
return res; 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 flattenErrorPath = (errorPath: (string | number)[]) => errorPath.join('.');
const errorMap: z.ZodErrorMap = (error, ctx) => { 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 { AstroError } from '../core/errors/errors.js';
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js'; import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
import { contentFileExts, CONTENT_FLAG } from './consts.js'; import { contentFileExts, CONTENT_FLAG } from './consts.js';
import { getEntryType } from './types-generator.js'; import { getEntryType } from './utils.js';
import { import {
ContentConfig, ContentConfig,
getContentPaths, 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');
});
});