[Content collections] Move generated types to .astro
directory (#5786)
* feat: change cacheDir to `.astro` * feat: write reference in env.d.ts if none exists * chore: update with-content types * test: env.d.ts transform * nit: setUp -> add * refactor: content.d.ts -> types.d.ts * chore: update confirmation log * chore: changeset * feat: inject env.d.ts if none exists * feat: set up env.d.ts on `astro sync` * chore: duplicate envTsPathRelative * docs: update changeset * fix: make srcDir if none exists * fix: types.generated -> .astro in gitignore * feat: add env.d.ts to test gitignore * chore: remove env.d.ts from content-collections * test: move sync tests to `astro sync`, add file write test * refactor: simplify test gitignore to base * fix: add / to `.astro` bc that scares me
This commit is contained in:
parent
813073addd
commit
c2180746b4
21 changed files with 263 additions and 97 deletions
9
.changeset/curvy-foxes-reply.md
Normal file
9
.changeset/curvy-foxes-reply.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Move generated content collection types to a `.astro` directory. This replaces the previously generated `src/content/types.generated.d.ts` file.
|
||||||
|
|
||||||
|
If you're using Git for version control, we recommend ignoring this generated directory by adding `.astro` to your .gitignore.
|
||||||
|
|
||||||
|
Astro will also generate the [TypeScript reference path](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-path-) to include `.astro` types in your project. This will update your project's `src/env.d.ts` file, or write one if none exists.
|
|
@ -55,44 +55,45 @@ declare module 'astro:content' {
|
||||||
};
|
};
|
||||||
|
|
||||||
const entryMap: {
|
const entryMap: {
|
||||||
blog: {
|
"blog": {
|
||||||
'first-post.md': {
|
"first-post.md": {
|
||||||
id: 'first-post.md';
|
id: "first-post.md",
|
||||||
slug: 'first-post';
|
slug: "first-post",
|
||||||
body: string;
|
body: string,
|
||||||
collection: 'blog';
|
collection: "blog",
|
||||||
data: InferEntrySchema<'blog'>;
|
data: InferEntrySchema<"blog">
|
||||||
};
|
},
|
||||||
'markdown-style-guide.md': {
|
"markdown-style-guide.md": {
|
||||||
id: 'markdown-style-guide.md';
|
id: "markdown-style-guide.md",
|
||||||
slug: 'markdown-style-guide';
|
slug: "markdown-style-guide",
|
||||||
body: string;
|
body: string,
|
||||||
collection: 'blog';
|
collection: "blog",
|
||||||
data: InferEntrySchema<'blog'>;
|
data: InferEntrySchema<"blog">
|
||||||
};
|
},
|
||||||
'second-post.md': {
|
"second-post.md": {
|
||||||
id: 'second-post.md';
|
id: "second-post.md",
|
||||||
slug: 'second-post';
|
slug: "second-post",
|
||||||
body: string;
|
body: string,
|
||||||
collection: 'blog';
|
collection: "blog",
|
||||||
data: InferEntrySchema<'blog'>;
|
data: InferEntrySchema<"blog">
|
||||||
};
|
},
|
||||||
'third-post.md': {
|
"third-post.md": {
|
||||||
id: 'third-post.md';
|
id: "third-post.md",
|
||||||
slug: 'third-post';
|
slug: "third-post",
|
||||||
body: string;
|
body: string,
|
||||||
collection: 'blog';
|
collection: "blog",
|
||||||
data: InferEntrySchema<'blog'>;
|
data: InferEntrySchema<"blog">
|
||||||
};
|
},
|
||||||
'using-mdx.mdx': {
|
"using-mdx.mdx": {
|
||||||
id: 'using-mdx.mdx';
|
id: "using-mdx.mdx",
|
||||||
slug: 'using-mdx';
|
slug: "using-mdx",
|
||||||
body: string;
|
body: string,
|
||||||
collection: 'blog';
|
collection: "blog",
|
||||||
data: InferEntrySchema<'blog'>;
|
data: InferEntrySchema<"blog">
|
||||||
};
|
},
|
||||||
};
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ContentConfig = typeof import('./config');
|
type ContentConfig = typeof import("../src/content/config");
|
||||||
}
|
}
|
1
examples/with-content/src/env.d.ts
vendored
1
examples/with-content/src/env.d.ts
vendored
|
@ -1 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { contentObservable, createContentTypesGenerator } from '../../content/in
|
||||||
import { getTimeStat } from '../../core/build/util.js';
|
import { getTimeStat } from '../../core/build/util.js';
|
||||||
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
||||||
import { info, LogOptions } from '../../core/logger/core.js';
|
import { info, LogOptions } from '../../core/logger/core.js';
|
||||||
|
import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js';
|
||||||
|
|
||||||
export async function sync(
|
export async function sync(
|
||||||
settings: AstroSettings,
|
settings: AstroSettings,
|
||||||
|
@ -26,6 +27,7 @@ export async function sync(
|
||||||
}
|
}
|
||||||
|
|
||||||
info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
|
info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
|
||||||
|
await setUpEnvTs({ settings, logging, fs });
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,4 @@ export const VIRTUAL_MODULE_ID = 'astro:content';
|
||||||
export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@';
|
export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@';
|
||||||
export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@';
|
export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@';
|
||||||
|
|
||||||
export const CONTENT_BASE = 'types.generated';
|
export const CONTENT_TYPES_FILE = 'types.d.ts';
|
||||||
export const CONTENT_FILE = CONTENT_BASE + '.mjs';
|
|
||||||
export const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts';
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export { createContentTypesGenerator } from './types-generator.js';
|
export { createContentTypesGenerator } from './types-generator.js';
|
||||||
export { contentObservable, getContentPaths } from './utils.js';
|
export { contentObservable, getContentPaths, getDotAstroTypeReference } from './utils.js';
|
||||||
export {
|
export {
|
||||||
astroBundleDelayedAssetPlugin,
|
astroBundleDelayedAssetPlugin,
|
||||||
astroDelayedAssetPlugin,
|
astroDelayedAssetPlugin,
|
||||||
|
|
|
@ -7,12 +7,14 @@ import { normalizePath } 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 { appendForwardSlash, isRelativePath } from '../core/path.js';
|
||||||
|
import { getEnvTsPath } from '../vite-plugin-inject-env-ts/index.js';
|
||||||
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
|
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
|
||||||
import {
|
import {
|
||||||
ContentConfig,
|
ContentConfig,
|
||||||
ContentObservable,
|
ContentObservable,
|
||||||
ContentPaths,
|
ContentPaths,
|
||||||
getContentPaths,
|
getContentPaths,
|
||||||
|
getDotAstroTypeReference,
|
||||||
getEntryInfo,
|
getEntryInfo,
|
||||||
loadContentConfig,
|
loadContentConfig,
|
||||||
NoCollectionError,
|
NoCollectionError,
|
||||||
|
@ -48,15 +50,12 @@ export async function createContentTypesGenerator({
|
||||||
settings,
|
settings,
|
||||||
}: CreateContentGeneratorParams): Promise<GenerateContentTypes> {
|
}: CreateContentGeneratorParams): Promise<GenerateContentTypes> {
|
||||||
const contentTypes: ContentTypes = {};
|
const contentTypes: ContentTypes = {};
|
||||||
const contentPaths: ContentPaths = getContentPaths({ srcDir: settings.config.srcDir });
|
const contentPaths = getContentPaths(settings.config);
|
||||||
|
|
||||||
let events: Promise<{ shouldGenerateTypes: boolean; error?: Error }>[] = [];
|
let events: Promise<{ shouldGenerateTypes: boolean; error?: Error }>[] = [];
|
||||||
let debounceTimeout: NodeJS.Timeout | undefined;
|
let debounceTimeout: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
const contentTypesBase = await fs.promises.readFile(
|
const contentTypesBase = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8');
|
||||||
new URL(CONTENT_TYPES_FILE, contentPaths.generatedInputDir),
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await handleEvent({ name: 'add', entry: contentPaths.config }, { logLevel: 'warn' });
|
await handleEvent({ name: 'add', entry: contentPaths.config }, { logLevel: 'warn' });
|
||||||
|
@ -306,6 +305,10 @@ async function writeContentFiles({
|
||||||
contentTypesStr += `},\n`;
|
contentTypesStr += `},\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(contentPaths.cacheDir)) {
|
||||||
|
fs.mkdirSync(contentPaths.cacheDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
let configPathRelativeToCacheDir = normalizePath(
|
let configPathRelativeToCacheDir = normalizePath(
|
||||||
path.relative(contentPaths.cacheDir.pathname, contentPaths.config.pathname)
|
path.relative(contentPaths.cacheDir.pathname, contentPaths.config.pathname)
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,8 +5,9 @@ import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { createServer, ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
|
import { createServer, ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { 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 { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
|
import { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
|
||||||
|
|
||||||
export const collectionConfigParser = z.object({
|
export const collectionConfigParser = z.object({
|
||||||
|
@ -26,6 +27,15 @@ export const collectionConfigParser = z.object({
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function getDotAstroTypeReference({ root, srcDir }: { root: URL; srcDir: URL }) {
|
||||||
|
const { cacheDir } = getContentPaths({ root, srcDir });
|
||||||
|
const contentTypesRelativeToSrcDir = normalizePath(
|
||||||
|
path.relative(fileURLToPath(srcDir), fileURLToPath(new URL(CONTENT_TYPES_FILE, cacheDir)))
|
||||||
|
);
|
||||||
|
|
||||||
|
return `/// <reference path=${JSON.stringify(contentTypesRelativeToSrcDir)} />`;
|
||||||
|
}
|
||||||
|
|
||||||
export const contentConfigParser = z.object({
|
export const contentConfigParser = z.object({
|
||||||
collections: z.record(collectionConfigParser),
|
collections: z.record(collectionConfigParser),
|
||||||
});
|
});
|
||||||
|
@ -201,7 +211,7 @@ export async function loadContentConfig({
|
||||||
fs: typeof fsMod;
|
fs: typeof fsMod;
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
}): Promise<ContentConfig | Error> {
|
}): Promise<ContentConfig | Error> {
|
||||||
const contentPaths = getContentPaths({ srcDir: settings.config.srcDir });
|
const contentPaths = getContentPaths(settings.config);
|
||||||
const tempConfigServer: ViteDevServer = await createServer({
|
const tempConfigServer: ViteDevServer = await createServer({
|
||||||
root: fileURLToPath(settings.config.root),
|
root: fileURLToPath(settings.config.root),
|
||||||
server: { middlewareMode: true, hmr: false },
|
server: { middlewareMode: true, hmr: false },
|
||||||
|
@ -267,16 +277,21 @@ export function contentObservable(initialCtx: ContentCtx): ContentObservable {
|
||||||
export type ContentPaths = {
|
export type ContentPaths = {
|
||||||
contentDir: URL;
|
contentDir: URL;
|
||||||
cacheDir: URL;
|
cacheDir: URL;
|
||||||
generatedInputDir: URL;
|
typesTemplate: URL;
|
||||||
|
virtualModTemplate: URL;
|
||||||
config: URL;
|
config: URL;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getContentPaths({ srcDir }: { srcDir: URL }): ContentPaths {
|
export function getContentPaths({
|
||||||
|
srcDir,
|
||||||
|
root,
|
||||||
|
}: Pick<AstroConfig, 'root' | 'srcDir'>): ContentPaths {
|
||||||
|
const templateDir = new URL('../../src/content/template/', import.meta.url);
|
||||||
return {
|
return {
|
||||||
// Output generated types in content directory. May change in the future!
|
cacheDir: new URL('.astro/', root),
|
||||||
cacheDir: new URL('./content/', srcDir),
|
|
||||||
contentDir: new URL('./content/', srcDir),
|
contentDir: new URL('./content/', srcDir),
|
||||||
generatedInputDir: new URL('../../src/content/template/', import.meta.url),
|
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', srcDir),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
import {
|
import {
|
||||||
ContentConfig,
|
ContentConfig,
|
||||||
contentObservable,
|
contentObservable,
|
||||||
ContentPaths,
|
|
||||||
getContentPaths,
|
getContentPaths,
|
||||||
getEntryData,
|
getEntryData,
|
||||||
getEntryInfo,
|
getEntryInfo,
|
||||||
|
@ -36,7 +35,7 @@ export function astroContentServerPlugin({
|
||||||
logging,
|
logging,
|
||||||
mode,
|
mode,
|
||||||
}: AstroContentServerPluginParams): Plugin[] {
|
}: AstroContentServerPluginParams): Plugin[] {
|
||||||
const contentPaths: ContentPaths = getContentPaths({ srcDir: settings.config.srcDir });
|
const contentPaths = getContentPaths(settings.config);
|
||||||
let contentDirExists = false;
|
let contentDirExists = false;
|
||||||
let contentGenerator: GenerateContentTypes;
|
let contentGenerator: GenerateContentTypes;
|
||||||
const contentConfigObserver = contentObservable({ status: 'loading' });
|
const contentConfigObserver = contentObservable({ status: 'loading' });
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { Plugin } from 'vite';
|
||||||
import { normalizePath } from 'vite';
|
import { normalizePath } from 'vite';
|
||||||
import type { AstroSettings } from '../@types/astro.js';
|
import type { AstroSettings } from '../@types/astro.js';
|
||||||
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
|
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
|
||||||
import { contentFileExts, CONTENT_FILE, VIRTUAL_MODULE_ID } from './consts.js';
|
import { contentFileExts, VIRTUAL_MODULE_ID } from './consts.js';
|
||||||
import { getContentPaths } from './utils.js';
|
import { getContentPaths } from './utils.js';
|
||||||
|
|
||||||
interface AstroContentVirtualModPluginParams {
|
interface AstroContentVirtualModPluginParams {
|
||||||
|
@ -14,15 +14,17 @@ interface AstroContentVirtualModPluginParams {
|
||||||
export function astroContentVirtualModPlugin({
|
export function astroContentVirtualModPlugin({
|
||||||
settings,
|
settings,
|
||||||
}: AstroContentVirtualModPluginParams): Plugin {
|
}: AstroContentVirtualModPluginParams): Plugin {
|
||||||
const paths = getContentPaths({ srcDir: settings.config.srcDir });
|
const contentPaths = getContentPaths(settings.config);
|
||||||
const relContentDir = normalizePath(
|
const relContentDir = normalizePath(
|
||||||
appendForwardSlash(
|
appendForwardSlash(
|
||||||
prependForwardSlash(path.relative(settings.config.root.pathname, paths.contentDir.pathname))
|
prependForwardSlash(
|
||||||
|
path.relative(settings.config.root.pathname, contentPaths.contentDir.pathname)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const entryGlob = `${relContentDir}**/*{${contentFileExts.join(',')}}`;
|
const entryGlob = `${relContentDir}**/*{${contentFileExts.join(',')}}`;
|
||||||
const astroContentModContents = fsMod
|
const virtualModContents = fsMod
|
||||||
.readFileSync(new URL(CONTENT_FILE, paths.generatedInputDir), 'utf-8')
|
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
|
||||||
.replace('@@CONTENT_DIR@@', relContentDir)
|
.replace('@@CONTENT_DIR@@', relContentDir)
|
||||||
.replace('@@ENTRY_GLOB_PATH@@', entryGlob)
|
.replace('@@ENTRY_GLOB_PATH@@', entryGlob)
|
||||||
.replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob);
|
.replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob);
|
||||||
|
@ -40,7 +42,7 @@ export function astroContentVirtualModPlugin({
|
||||||
load(id) {
|
load(id) {
|
||||||
if (id === astroContentVirtualModuleId) {
|
if (id === astroContentVirtualModuleId) {
|
||||||
return {
|
return {
|
||||||
code: astroContentModContents,
|
code: virtualModContents,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,7 @@ import markdownVitePlugin from '../vite-plugin-markdown/index.js';
|
||||||
import astroScannerPlugin from '../vite-plugin-scanner/index.js';
|
import astroScannerPlugin from '../vite-plugin-scanner/index.js';
|
||||||
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
||||||
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
||||||
|
import { astroInjectEnvTsPlugin } from '../vite-plugin-inject-env-ts/index.js';
|
||||||
|
|
||||||
interface CreateViteOptions {
|
interface CreateViteOptions {
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
|
@ -102,6 +103,7 @@ export async function createVite(
|
||||||
astroScriptsPageSSRPlugin({ settings }),
|
astroScriptsPageSSRPlugin({ settings }),
|
||||||
astroHeadPropagationPlugin({ settings }),
|
astroHeadPropagationPlugin({ settings }),
|
||||||
astroScannerPlugin({ settings, logging }),
|
astroScannerPlugin({ settings, logging }),
|
||||||
|
astroInjectEnvTsPlugin({ settings, logging, fs }),
|
||||||
...(settings.config.experimental.contentCollections
|
...(settings.config.experimental.contentCollections
|
||||||
? [
|
? [
|
||||||
astroContentVirtualModPlugin({ settings }),
|
astroContentVirtualModPlugin({ settings }),
|
||||||
|
|
79
packages/astro/src/vite-plugin-inject-env-ts/index.ts
Normal file
79
packages/astro/src/vite-plugin-inject-env-ts/index.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import type { AstroSettings } from '../@types/astro.js';
|
||||||
|
import type fsMod from 'node:fs';
|
||||||
|
import { normalizePath, Plugin } from 'vite';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { getContentPaths, getDotAstroTypeReference } from '../content/index.js';
|
||||||
|
import { info, LogOptions } from '../core/logger/core.js';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { bold } from 'kleur/colors';
|
||||||
|
|
||||||
|
export function getEnvTsPath({ srcDir }: { srcDir: URL }) {
|
||||||
|
return new URL('env.d.ts', srcDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function astroInjectEnvTsPlugin({
|
||||||
|
settings,
|
||||||
|
logging,
|
||||||
|
fs,
|
||||||
|
}: {
|
||||||
|
settings: AstroSettings;
|
||||||
|
logging: LogOptions;
|
||||||
|
fs: typeof fsMod;
|
||||||
|
}): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'astro-inject-env-ts',
|
||||||
|
// Use `post` to ensure project setup is complete
|
||||||
|
// Ex. `.astro` types have been written
|
||||||
|
enforce: 'post',
|
||||||
|
async config() {
|
||||||
|
await setUpEnvTs({ settings, logging, fs });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setUpEnvTs({
|
||||||
|
settings,
|
||||||
|
logging,
|
||||||
|
fs,
|
||||||
|
}: {
|
||||||
|
settings: AstroSettings;
|
||||||
|
logging: LogOptions;
|
||||||
|
fs: typeof fsMod;
|
||||||
|
}) {
|
||||||
|
const envTsPath = getEnvTsPath(settings.config);
|
||||||
|
const dotAstroDir = getContentPaths(settings.config).cacheDir;
|
||||||
|
const dotAstroTypeReference = getDotAstroTypeReference(settings.config);
|
||||||
|
const envTsPathRelativetoRoot = normalizePath(
|
||||||
|
path.relative(fileURLToPath(settings.config.root), fileURLToPath(envTsPath))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fs.existsSync(envTsPath)) {
|
||||||
|
// Add `.astro` types reference if none exists
|
||||||
|
if (!fs.existsSync(dotAstroDir)) return;
|
||||||
|
|
||||||
|
let typesEnvContents = await fs.promises.readFile(envTsPath, 'utf-8');
|
||||||
|
const expectedTypeReference = getDotAstroTypeReference(settings.config);
|
||||||
|
|
||||||
|
if (!typesEnvContents.includes(expectedTypeReference)) {
|
||||||
|
typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
|
||||||
|
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
|
||||||
|
info(logging, 'content', `Added ${bold(envTsPathRelativetoRoot)} types`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, inject the `env.d.ts` file
|
||||||
|
let referenceDefs: string[] = [];
|
||||||
|
if (settings.config.integrations.find((i) => i.name === '@astrojs/image')) {
|
||||||
|
referenceDefs.push('/// <reference types="@astrojs/image/client" />');
|
||||||
|
} else {
|
||||||
|
referenceDefs.push('/// <reference types="astro/client" />');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(dotAstroDir)) {
|
||||||
|
referenceDefs.push(dotAstroTypeReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.mkdir(settings.config.srcDir, { recursive: true });
|
||||||
|
await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'), 'utf-8');
|
||||||
|
info(logging, 'astro', `Added ${bold(envTsPathRelativetoRoot)} types`);
|
||||||
|
}
|
||||||
|
}
|
88
packages/astro/test/astro-sync.test.js
Normal file
88
packages/astro/test/astro-sync.test.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('astro sync', () => {
|
||||||
|
let fixture;
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({ root: './fixtures/content-collections/' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Writes types to `.astro`', async () => {
|
||||||
|
let writtenFiles = {};
|
||||||
|
const fsMock = {
|
||||||
|
...fs,
|
||||||
|
promises: {
|
||||||
|
...fs.promises,
|
||||||
|
async writeFile(path, contents) {
|
||||||
|
writtenFiles[path] = contents;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await fixture.sync({ fs: fsMock });
|
||||||
|
|
||||||
|
const expectedTypesFile = new URL('.astro/types.d.ts', fixture.config.root).href;
|
||||||
|
expect(writtenFiles).to.haveOwnProperty(expectedTypesFile);
|
||||||
|
// smoke test `astro check` asserts whether content types pass.
|
||||||
|
expect(writtenFiles[expectedTypesFile]).to.include(
|
||||||
|
`declare module 'astro:content' {`,
|
||||||
|
'Types file does not include `astro:content` module declaration'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Adds type reference to `src/env.d.ts`', async () => {
|
||||||
|
let writtenFiles = {};
|
||||||
|
const typesEnvPath = new URL('env.d.ts', fixture.config.srcDir).href;
|
||||||
|
const fsMock = {
|
||||||
|
...fs,
|
||||||
|
existsSync(path, ...args) {
|
||||||
|
if (path.toString() === typesEnvPath) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return fs.existsSync(path, ...args);
|
||||||
|
},
|
||||||
|
promises: {
|
||||||
|
...fs.promises,
|
||||||
|
async readFile(path, ...args) {
|
||||||
|
if (path.toString() === typesEnvPath) {
|
||||||
|
return `/// <reference path="astro/client" />`;
|
||||||
|
} else {
|
||||||
|
return fs.promises.readFile(path, ...args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async writeFile(path, contents) {
|
||||||
|
writtenFiles[path] = contents;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await fixture.sync({ fs: fsMock });
|
||||||
|
|
||||||
|
expect(writtenFiles, 'Did not try to update env.d.ts file.').to.haveOwnProperty(typesEnvPath);
|
||||||
|
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference path="../.astro/types.d.ts" />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Writes `src/env.d.ts` if none exists', async () => {
|
||||||
|
let writtenFiles = {};
|
||||||
|
const typesEnvPath = new URL('env.d.ts', fixture.config.srcDir).href;
|
||||||
|
const fsMock = {
|
||||||
|
...fs,
|
||||||
|
existsSync(path, ...args) {
|
||||||
|
if (path.toString() === typesEnvPath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fs.existsSync(path, ...args);
|
||||||
|
},
|
||||||
|
promises: {
|
||||||
|
...fs.promises,
|
||||||
|
async writeFile(path, contents) {
|
||||||
|
writtenFiles[path] = contents;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await fixture.sync({ fs: fsMock });
|
||||||
|
|
||||||
|
expect(writtenFiles, 'Did not try to write env.d.ts file.').to.haveOwnProperty(typesEnvPath);
|
||||||
|
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference types="astro/client" />`);
|
||||||
|
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference path="../.astro/types.d.ts" />`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,3 @@
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as devalue from 'devalue';
|
import * as devalue from 'devalue';
|
||||||
import * as cheerio from 'cheerio';
|
import * as cheerio from 'cheerio';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
@ -6,36 +5,6 @@ import { loadFixture } from './test-utils.js';
|
||||||
import testAdapter from './test-adapter.js';
|
import testAdapter from './test-adapter.js';
|
||||||
|
|
||||||
describe('Content Collections', () => {
|
describe('Content Collections', () => {
|
||||||
describe('Type generation', () => {
|
|
||||||
let fixture;
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({ root: './fixtures/content-collections/' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Writes types to `src/content/`', async () => {
|
|
||||||
let writtenFiles = {};
|
|
||||||
const fsMock = {
|
|
||||||
...fs,
|
|
||||||
promises: {
|
|
||||||
...fs.promises,
|
|
||||||
async writeFile(path, contents) {
|
|
||||||
writtenFiles[path] = contents;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const expectedTypesFile = new URL('./content/types.generated.d.ts', fixture.config.srcDir)
|
|
||||||
.href;
|
|
||||||
await fixture.sync({ fs: fsMock });
|
|
||||||
expect(Object.keys(writtenFiles)).to.have.lengthOf(1);
|
|
||||||
expect(writtenFiles).to.haveOwnProperty(expectedTypesFile);
|
|
||||||
// smoke test `astro check` asserts whether content types pass.
|
|
||||||
expect(writtenFiles[expectedTypesFile]).to.include(
|
|
||||||
`declare module 'astro:content' {`,
|
|
||||||
'Types file does not include `astro:content` module declaration'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Query', () => {
|
describe('Query', () => {
|
||||||
let fixture;
|
let fixture;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|
2
packages/astro/test/fixtures/.gitignore
vendored
Normal file
2
packages/astro/test/fixtures/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.astro/
|
||||||
|
env.d.ts
|
|
@ -1 +0,0 @@
|
||||||
types.generated.d.ts
|
|
|
@ -1 +0,0 @@
|
||||||
types.generated.d.ts
|
|
|
@ -1 +0,0 @@
|
||||||
types.generated.d.ts
|
|
|
@ -1 +0,0 @@
|
||||||
types.generated.d.ts
|
|
Loading…
Add table
Reference in a new issue