diff --git a/.changeset/fresh-fans-study.md b/.changeset/fresh-fans-study.md
new file mode 100644
index 000000000000..9a837b1fd7a7
--- /dev/null
+++ b/.changeset/fresh-fans-study.md
@@ -0,0 +1,18 @@
+---
+'@astrojs/db': minor
+---
+
+Changes how type generation works
+
+The generated `.d.ts` file is now at a new location:
+
+```diff
+- .astro/db-types.d.ts
++ .astro/integrations/astro_db/db.d.ts
+```
+
+The following line can now be removed from `src/env.d.ts`:
+
+```diff
+- ///
+```
diff --git a/.changeset/mean-horses-kiss.md b/.changeset/mean-horses-kiss.md
new file mode 100644
index 000000000000..7d211e62674f
--- /dev/null
+++ b/.changeset/mean-horses-kiss.md
@@ -0,0 +1,35 @@
+---
+'astro': minor
+---
+
+Adds a new [`injectTypes()` utility](https://docs.astro.build/en/reference/integrations-reference/#injecttypes-options) to the Integration API and refactors how type generation works
+
+Use `injectTypes()` in the `astro:config:done` hook to inject types into your user's project by adding a new a `*.d.ts` file.
+
+The `filename` property will be used to generate a file at `/.astro/integrations//.d.ts` and must end with `".d.ts"`.
+
+The `content` property will create the body of the file, and must be valid TypeScript.
+
+Additionally, `injectTypes()` returns a URL to the normalized path so you can overwrite its content later on, or manipulate it in any way you want.
+
+```js
+// my-integration/index.js
+export default {
+ name: 'my-integration',
+ 'astro:config:done': ({ injectTypes }) => {
+ injectTypes({
+ filename: "types.d.ts",
+ content: "declare module 'virtual:my-integration' {}"
+ })
+ }
+};
+```
+
+Codegen has been refactored. Although `src/env.d.ts` will continue to work as is, we recommend you update it:
+
+```diff
+- ///
++ ///
+- ///
+- ///
+```
\ No newline at end of file
diff --git a/examples/basics/src/env.d.ts b/examples/basics/src/env.d.ts
index f964fe0cffd8..9bc5cb41c24e 100644
--- a/examples/basics/src/env.d.ts
+++ b/examples/basics/src/env.d.ts
@@ -1 +1 @@
-///
+///
\ No newline at end of file
diff --git a/examples/blog/src/env.d.ts b/examples/blog/src/env.d.ts
index acef35f175aa..e16c13c6952a 100644
--- a/examples/blog/src/env.d.ts
+++ b/examples/blog/src/env.d.ts
@@ -1,2 +1 @@
///
-///
diff --git a/examples/framework-alpine/src/env.d.ts b/examples/framework-alpine/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-alpine/src/env.d.ts
+++ b/examples/framework-alpine/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-lit/src/env.d.ts b/examples/framework-lit/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-lit/src/env.d.ts
+++ b/examples/framework-lit/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-multiple/src/env.d.ts b/examples/framework-multiple/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-multiple/src/env.d.ts
+++ b/examples/framework-multiple/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-preact/src/env.d.ts b/examples/framework-preact/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-preact/src/env.d.ts
+++ b/examples/framework-preact/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-react/src/env.d.ts b/examples/framework-react/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-react/src/env.d.ts
+++ b/examples/framework-react/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-solid/src/env.d.ts b/examples/framework-solid/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-solid/src/env.d.ts
+++ b/examples/framework-solid/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-svelte/src/env.d.ts b/examples/framework-svelte/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-svelte/src/env.d.ts
+++ b/examples/framework-svelte/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/framework-vue/src/env.d.ts b/examples/framework-vue/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/framework-vue/src/env.d.ts
+++ b/examples/framework-vue/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/hackernews/src/env.d.ts b/examples/hackernews/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/hackernews/src/env.d.ts
+++ b/examples/hackernews/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/middleware/src/env.d.ts b/examples/middleware/src/env.d.ts
index 44f67965a3cc..74b9019e5746 100644
--- a/examples/middleware/src/env.d.ts
+++ b/examples/middleware/src/env.d.ts
@@ -1,4 +1,4 @@
-///
+///
declare namespace App {
interface Locals {
user: {
diff --git a/examples/minimal/src/env.d.ts b/examples/minimal/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/minimal/src/env.d.ts
+++ b/examples/minimal/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/non-html-pages/src/env.d.ts b/examples/non-html-pages/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/non-html-pages/src/env.d.ts
+++ b/examples/non-html-pages/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/portfolio/src/env.d.ts b/examples/portfolio/src/env.d.ts
index acef35f175aa..e16c13c6952a 100644
--- a/examples/portfolio/src/env.d.ts
+++ b/examples/portfolio/src/env.d.ts
@@ -1,2 +1 @@
///
-///
diff --git a/examples/ssr/src/env.d.ts b/examples/ssr/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/ssr/src/env.d.ts
+++ b/examples/ssr/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/with-markdoc/src/env.d.ts b/examples/with-markdoc/src/env.d.ts
index acef35f175aa..e16c13c6952a 100644
--- a/examples/with-markdoc/src/env.d.ts
+++ b/examples/with-markdoc/src/env.d.ts
@@ -1,2 +1 @@
///
-///
diff --git a/examples/with-markdown-plugins/src/env.d.ts b/examples/with-markdown-plugins/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/with-markdown-plugins/src/env.d.ts
+++ b/examples/with-markdown-plugins/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/with-markdown-shiki/src/env.d.ts b/examples/with-markdown-shiki/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/with-markdown-shiki/src/env.d.ts
+++ b/examples/with-markdown-shiki/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/with-mdx/src/env.d.ts b/examples/with-mdx/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/with-mdx/src/env.d.ts
+++ b/examples/with-mdx/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/with-nanostores/src/env.d.ts b/examples/with-nanostores/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/with-nanostores/src/env.d.ts
+++ b/examples/with-nanostores/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/examples/with-tailwindcss/src/env.d.ts b/examples/with-tailwindcss/src/env.d.ts
index f964fe0cffd8..e16c13c6952a 100644
--- a/examples/with-tailwindcss/src/env.d.ts
+++ b/examples/with-tailwindcss/src/env.d.ts
@@ -1 +1 @@
-///
+///
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index ec7848a17ecc..129d52f638a6 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -2553,6 +2553,11 @@ export interface AstroAdapterFeatures {
functionPerRoute: boolean;
}
+export interface InjectedType {
+ filename: string;
+ content: string;
+}
+
export interface AstroSettings {
config: AstroConfig;
adapter: AstroAdapter | undefined;
@@ -2588,6 +2593,7 @@ export interface AstroSettings {
latestAstroVersion: string | undefined;
serverIslandMap: NonNullable;
serverIslandNameMap: NonNullable;
+ injectedTypes: Array;
}
export type AsyncRendererComponentFn = (
@@ -3289,6 +3295,7 @@ declare global {
'astro:config:done': (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
+ injectTypes: (injectedType: InjectedType) => URL;
logger: AstroIntegrationLogger;
}) => void | Promise;
'astro:server:setup': (options: {
diff --git a/packages/astro/src/actions/consts.ts b/packages/astro/src/actions/consts.ts
index 6a55386d869a..beb8c45b641d 100644
--- a/packages/astro/src/actions/consts.ts
+++ b/packages/astro/src/actions/consts.ts
@@ -1,6 +1,6 @@
export const VIRTUAL_MODULE_ID = 'astro:actions';
export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
-export const ACTIONS_TYPES_FILE = 'actions.d.ts';
+export const ACTIONS_TYPES_FILE = 'astro/actions.d.ts';
export const VIRTUAL_INTERNAL_MODULE_ID = 'astro:internal-actions';
export const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = '\0astro:internal-actions';
export const NOOP_ACTIONS = '\0noop-actions';
diff --git a/packages/astro/src/actions/index.ts b/packages/astro/src/actions/index.ts
index 04a911856f9c..2423b7017d43 100644
--- a/packages/astro/src/actions/index.ts
+++ b/packages/astro/src/actions/index.ts
@@ -30,9 +30,6 @@ export default function astroActions({
throw error;
}
- const stringifiedActionsImport = JSON.stringify(
- viteID(new URL('./actions', params.config.srcDir)),
- );
params.updateConfig({
vite: {
plugins: [vitePluginUserActions({ settings }), vitePluginActions(fs)],
@@ -49,11 +46,18 @@ export default function astroActions({
entrypoint: 'astro/actions/runtime/middleware.js',
order: 'post',
});
+ },
+ 'astro:config:done': (params) => {
+ const stringifiedActionsImport = JSON.stringify(
+ viteID(new URL('./actions', params.config.srcDir)),
+ );
+ settings.injectedTypes.push({
+ filename: ACTIONS_TYPES_FILE,
+ content: `declare module "astro:actions" {
+ type Actions = typeof import(${stringifiedActionsImport})["server"];
- await typegen({
- stringifiedActionsImport,
- root: params.config.root,
- fs,
+ export const actions: Actions;
+}`,
});
},
},
@@ -119,24 +123,3 @@ const vitePluginActions = (fs: typeof fsMod): VitePlugin => ({
return code;
},
});
-
-async function typegen({
- stringifiedActionsImport,
- root,
- fs,
-}: {
- stringifiedActionsImport: string;
- root: URL;
- fs: typeof fsMod;
-}) {
- const content = `declare module "astro:actions" {
- type Actions = typeof import(${stringifiedActionsImport})["server"];
-
- export const actions: Actions;
-}`;
-
- const dotAstroDir = new URL('.astro/', root);
-
- await fs.promises.mkdir(dotAstroDir, { recursive: true });
- await fs.promises.writeFile(new URL(ACTIONS_TYPES_FILE, dotAstroDir), content);
-}
diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts
index a95e1074a59a..ebb89e7356d6 100644
--- a/packages/astro/src/cli/check/index.ts
+++ b/packages/astro/src/cli/check/index.ts
@@ -30,7 +30,7 @@ export async function check(flags: Arguments) {
// For now, we run this once as usually `astro check --watch` is ran alongside `astro dev` which also calls `astro sync`.
const { default: sync } = await import('../../core/sync/index.js');
try {
- await sync({ inlineConfig: flagsToAstroInlineConfig(flags) });
+ await sync(flagsToAstroInlineConfig(flags));
} catch (_) {
return process.exit(1);
}
diff --git a/packages/astro/src/cli/sync/index.ts b/packages/astro/src/cli/sync/index.ts
index d10bbb7d286e..235b050a6b77 100644
--- a/packages/astro/src/cli/sync/index.ts
+++ b/packages/astro/src/cli/sync/index.ts
@@ -24,10 +24,7 @@ export async function sync({ flags }: SyncOptions) {
}
try {
- await _sync({
- inlineConfig: flagsToAstroInlineConfig(flags),
- telemetry: true,
- });
+ await _sync(flagsToAstroInlineConfig(flags), { telemetry: true });
return 0;
} catch (_) {
return 1;
diff --git a/packages/astro/src/content/consts.ts b/packages/astro/src/content/consts.ts
index b506e08ab03f..ac619c2b5e78 100644
--- a/packages/astro/src/content/consts.ts
+++ b/packages/astro/src/content/consts.ts
@@ -34,7 +34,7 @@ export const CONTENT_FLAGS = [
CONTENT_MODULE_FLAG,
] as const;
-export const CONTENT_TYPES_FILE = 'types.d.ts';
+export const CONTENT_TYPES_FILE = 'astro/content.d.ts';
export const DATA_STORE_FILE = 'data-store.json';
export const ASSET_IMPORTS_FILE = 'assets.mjs';
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index 8fa216fc1551..2394f43fa631 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -530,7 +530,7 @@ async function writeContentFiles({
}
const configPathRelativeToCacheDir = normalizeConfigPath(
- settings.dotAstroDir.pathname,
+ new URL('astro', settings.dotAstroDir).pathname,
contentPaths.config.url.pathname,
);
@@ -546,8 +546,17 @@ async function writeContentFiles({
contentConfig ? `typeof import(${configPathRelativeToCacheDir})` : 'never',
);
- await fs.promises.writeFile(
- new URL(CONTENT_TYPES_FILE, settings.dotAstroDir),
- typeTemplateContent,
- );
+ // If it's the first time, we inject types the usual way. sync() will handle creating files and references. If it's not the first time, we just override the dts content
+ if (settings.injectedTypes.some((t) => t.filename === CONTENT_TYPES_FILE)) {
+ fs.promises.writeFile(
+ new URL(CONTENT_TYPES_FILE, settings.dotAstroDir),
+ typeTemplateContent,
+ 'utf-8',
+ );
+ } else {
+ settings.injectedTypes.push({
+ filename: CONTENT_TYPES_FILE,
+ content: typeTemplateContent,
+ });
+ }
}
diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts
index c3c62edd45a0..6c878d7f3323 100644
--- a/packages/astro/src/core/config/settings.ts
+++ b/packages/astro/src/core/config/settings.ts
@@ -14,7 +14,8 @@ import { loadTSConfig } from './tsconfig.js';
export function createBaseSettings(config: AstroConfig): AstroSettings {
const { contentDir } = getContentPaths(config);
- const preferences = createPreferences(config);
+ const dotAstroDir = new URL('.astro/', config.root);
+ const preferences = createPreferences(config, dotAstroDir);
return {
config,
preferences,
@@ -106,8 +107,9 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
watchFiles: [],
devToolbarApps: [],
timer: new AstroTimer(),
- dotAstroDir: new URL('.astro/', config.root),
+ dotAstroDir,
latestAstroVersion: undefined, // Will be set later if applicable when the dev server starts
+ injectedTypes: [],
};
}
diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts
index 4c970c645225..235454cb5a70 100644
--- a/packages/astro/src/core/dev/restart.ts
+++ b/packages/astro/src/core/dev/restart.ts
@@ -31,7 +31,6 @@ async function createRestartedContainer(
}
const configRE = /.*astro.config.(?:mjs|cjs|js|ts)$/;
-const preferencesRE = /.*\.astro\/settings.json$/;
function shouldRestartContainer(
{ settings, inlineConfig, restartInFlight }: Container,
@@ -40,17 +39,19 @@ function shouldRestartContainer(
if (restartInFlight) return false;
let shouldRestart = false;
+ const normalizedChangedFile = vite.normalizePath(changedFile);
// If the config file changed, reload the config and restart the server.
if (inlineConfig.configFile) {
- shouldRestart = vite.normalizePath(inlineConfig.configFile) === vite.normalizePath(changedFile);
+ shouldRestart = vite.normalizePath(inlineConfig.configFile) === normalizedChangedFile;
}
// Otherwise, watch for any astro.config.* file changes in project root
else {
- const normalizedChangedFile = vite.normalizePath(changedFile);
shouldRestart = configRE.test(normalizedChangedFile);
-
- if (preferencesRE.test(normalizedChangedFile)) {
+ const settingsPath = vite.normalizePath(
+ fileURLToPath(new URL('settings.json', settings.dotAstroDir)),
+ );
+ if (settingsPath.match(normalizedChangedFile)) {
shouldRestart = settings.preferences.ignoreNextPreferenceReload ? false : true;
settings.preferences.ignoreNextPreferenceReload = false;
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index d4ad380c0be5..21ab487f0931 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -1291,6 +1291,17 @@ export const RewriteWithBodyUsed = {
'Astro.rewrite() cannot be used if the request body has already been read. If you need to read the body, first clone the request.',
} satisfies ErrorData;
+/**
+ * @docs
+ * @description
+ * An unknown error occured while reading or writing files to disk. It can be caused by many things, eg. missing permissions or a file not existing we attempt to read.
+ */
+export const UnknownFilesystemError = {
+ name: 'UnknownFilesystemError',
+ title: 'An unknown error occured while reading or writing files to disk.',
+ hint: 'It can be caused by many things, eg. missing permissions or a file not existing we attempt to read. Check the error cause for more details.',
+} satisfies ErrorData;
+
/**
* @docs
* @kind heading
diff --git a/packages/astro/src/core/index.ts b/packages/astro/src/core/index.ts
index e0f9f6c82412..bdd7c7f05939 100644
--- a/packages/astro/src/core/index.ts
+++ b/packages/astro/src/core/index.ts
@@ -23,4 +23,4 @@ export const build = (inlineConfig: AstroInlineConfig) => _build(inlineConfig);
* @experimental The JavaScript API is experimental
*/
// Wrap `_sync` to prevent exposing internal options
-export const sync = (inlineConfig: AstroInlineConfig) => _sync({ inlineConfig });
+export const sync = (inlineConfig: AstroInlineConfig) => _sync(inlineConfig);
diff --git a/packages/astro/src/core/sync/constants.ts b/packages/astro/src/core/sync/constants.ts
new file mode 100644
index 000000000000..7ff603105a75
--- /dev/null
+++ b/packages/astro/src/core/sync/constants.ts
@@ -0,0 +1,2 @@
+// TODO: use types.d.ts for backward compatibility. Use astro.d.ts in Astro 5.0
+export const REFERENCE_FILE = './types.d.ts';
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index 909974eddeb3..c9b2ec235b98 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -1,10 +1,8 @@
import fsMod, { existsSync } from 'node:fs';
import { performance } from 'node:perf_hooks';
-import { fileURLToPath } from 'node:url';
import { dim } from 'kleur/colors';
import { type HMRPayload, createServer } from 'vite';
import type { AstroConfig, AstroInlineConfig, AstroSettings } from '../../@types/astro.js';
-import { getPackage } from '../../cli/install-package.js';
import { DATA_STORE_FILE } from '../../content/consts.js';
import { globalContentLayer } from '../../content/content-layer.js';
import { DataStore, globalDataStore } from '../../content/data-store.js';
@@ -13,7 +11,7 @@ import { globalContentConfigObserver } from '../../content/utils.js';
import { syncAstroEnv } from '../../env/sync.js';
import { telemetry } from '../../events/index.js';
import { eventCliSession } from '../../events/session.js';
-import { runHookConfigSetup } from '../../integrations/hooks.js';
+import { runHookConfigDone, runHookConfigSetup } from '../../integrations/hooks.js';
import { getTimeStat } from '../build/util.js';
import { resolveConfig } from '../config/config.js';
import { createNodeLogger } from '../config/logging.js';
@@ -30,7 +28,7 @@ import {
import type { Logger } from '../logger/core.js';
import { formatErrorMessage } from '../messages.js';
import { ensureProcessNodeEnv } from '../util.js';
-import { setUpEnvTs } from './setup-env-ts.js';
+import { writeFiles } from './write-files.js';
export type SyncOptions = {
/**
@@ -46,15 +44,10 @@ export type SyncOptions = {
};
};
-type DBPackage = {
- typegen?: (args: Pick) => Promise;
-};
-
-export default async function sync({
- inlineConfig,
- fs,
- telemetry: _telemetry = false,
-}: { inlineConfig: AstroInlineConfig; fs?: typeof fsMod; telemetry?: boolean }) {
+export default async function sync(
+ inlineConfig: AstroInlineConfig,
+ { fs, telemetry: _telemetry = false }: { fs?: typeof fsMod; telemetry?: boolean } = {},
+) {
ensureProcessNodeEnv('production');
const logger = createNodeLogger(inlineConfig);
const { astroConfig, userConfig } = await resolveConfig(inlineConfig ?? {}, 'sync');
@@ -67,6 +60,7 @@ export default async function sync({
settings,
logger,
});
+ await runHookConfigDone({ settings, logger });
return await syncInternal({ settings, logger, fs, force: inlineConfig.force });
}
@@ -103,21 +97,9 @@ export async function syncInternal({
await clearContentLayerCache({ astroConfig: settings.config, logger, fs });
}
- const cwd = fileURLToPath(settings.config.root);
-
const timerStart = performance.now();
- const dbPackage = await getPackage(
- '@astrojs/db',
- logger,
- {
- optional: true,
- cwd,
- },
- [],
- );
try {
- await dbPackage?.typegen?.(settings.config);
if (!skip?.content) {
await syncContentCollections(settings, { fs, logger });
settings.timer.start('Sync content layer');
@@ -145,7 +127,7 @@ export async function syncInternal({
}
syncAstroEnv(settings, fs);
- await setUpEnvTs({ settings, logger, fs });
+ await writeFiles(settings, fs, logger);
logger.info('types', `Generated ${dim(getTimeStat(timerStart, performance.now()))}`);
} catch (err) {
const error = createSafeError(err);
diff --git a/packages/astro/src/core/sync/setup-env-ts.ts b/packages/astro/src/core/sync/setup-env-ts.ts
deleted file mode 100644
index 39eb062e5bcd..000000000000
--- a/packages/astro/src/core/sync/setup-env-ts.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import type fsMod from 'node:fs';
-import path from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { bold } from 'kleur/colors';
-import { normalizePath } from 'vite';
-import type { AstroSettings } from '../../@types/astro.js';
-import { ACTIONS_TYPES_FILE } from '../../actions/consts.js';
-import { CONTENT_TYPES_FILE } from '../../content/consts.js';
-import { ENV_TYPES_FILE } from '../../env/constants.js';
-import { type Logger } from '../logger/core.js';
-
-function getDotAstroTypeReference({
- settings,
- filename,
-}: { settings: AstroSettings; filename: string }) {
- const relativePath = normalizePath(
- path.relative(
- fileURLToPath(settings.config.srcDir),
- fileURLToPath(new URL(filename, settings.dotAstroDir)),
- ),
- );
-
- return `/// `;
-}
-
-type InjectedType = { filename: string; meetsCondition?: () => boolean | Promise };
-
-export async function setUpEnvTs({
- settings,
- logger,
- fs,
-}: {
- settings: AstroSettings;
- logger: Logger;
- fs: typeof fsMod;
-}) {
- const envTsPath = new URL('env.d.ts', settings.config.srcDir);
- const envTsPathRelativetoRoot = normalizePath(
- path.relative(fileURLToPath(settings.config.root), fileURLToPath(envTsPath)),
- );
-
- const injectedTypes: Array = [
- {
- filename: CONTENT_TYPES_FILE,
- meetsCondition: () => fs.existsSync(new URL(CONTENT_TYPES_FILE, settings.dotAstroDir)),
- },
- {
- filename: ACTIONS_TYPES_FILE,
- meetsCondition: () => fs.existsSync(new URL(ACTIONS_TYPES_FILE, settings.dotAstroDir)),
- },
- ];
- if (settings.config.experimental.env) {
- injectedTypes.push({
- filename: ENV_TYPES_FILE,
- });
- }
-
- if (fs.existsSync(envTsPath)) {
- const initialEnvContents = await fs.promises.readFile(envTsPath, 'utf-8');
- let typesEnvContents = initialEnvContents;
-
- for (const injectedType of injectedTypes) {
- if (!injectedType.meetsCondition || (await injectedType.meetsCondition?.())) {
- const expectedTypeReference = getDotAstroTypeReference({
- settings,
- filename: injectedType.filename,
- });
-
- if (!typesEnvContents.includes(expectedTypeReference)) {
- typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
- }
- }
- }
-
- if (initialEnvContents !== typesEnvContents) {
- logger.info('types', `Updated ${bold(envTsPathRelativetoRoot)} type declarations.`);
- await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
- }
- } else {
- // Otherwise, inject the `env.d.ts` file
- let referenceDefs: string[] = [];
- referenceDefs.push('/// ');
-
- for (const injectedType of injectedTypes) {
- if (!injectedType.meetsCondition || (await injectedType.meetsCondition?.())) {
- referenceDefs.push(getDotAstroTypeReference({ settings, filename: injectedType.filename }));
- }
- }
-
- await fs.promises.mkdir(settings.config.srcDir, { recursive: true });
- await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'), 'utf-8');
- logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
- }
-}
diff --git a/packages/astro/src/core/sync/write-files.ts b/packages/astro/src/core/sync/write-files.ts
new file mode 100644
index 000000000000..56ab131f1be7
--- /dev/null
+++ b/packages/astro/src/core/sync/write-files.ts
@@ -0,0 +1,78 @@
+import type fsMod from 'node:fs';
+import { dirname, relative } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { bold } from 'kleur/colors';
+import { normalizePath } from 'vite';
+import type { AstroSettings } from '../../@types/astro.js';
+import type { Logger } from '../logger/core.js';
+import { REFERENCE_FILE } from './constants.js';
+import { AstroError, AstroErrorData } from '../errors/index.js';
+
+export async function writeFiles(settings: AstroSettings, fs: typeof fsMod, logger: Logger) {
+ try {
+ writeInjectedTypes(settings, fs);
+ await setUpEnvTs(settings, fs, logger);
+ } catch (e) {
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: e });
+ }
+}
+
+function getTsReference(type: 'path' | 'types', value: string) {
+ return `/// `;
+}
+
+const CLIENT_TYPES_REFERENCE = getTsReference('types', 'astro/client');
+
+function writeInjectedTypes(settings: AstroSettings, fs: typeof fsMod) {
+ const references: Array = [];
+
+ for (const { filename, content } of settings.injectedTypes) {
+ const filepath = normalizePath(fileURLToPath(new URL(filename, settings.dotAstroDir)));
+ fs.mkdirSync(dirname(filepath), { recursive: true });
+ fs.writeFileSync(filepath, content, 'utf-8');
+ references.push(normalizePath(relative(fileURLToPath(settings.dotAstroDir), filepath)));
+ }
+
+ const astroDtsContent = `${CLIENT_TYPES_REFERENCE}\n${references.map((reference) => getTsReference('path', reference)).join('\n')}`;
+ if (references.length === 0) {
+ fs.mkdirSync(settings.dotAstroDir, { recursive: true });
+ }
+ fs.writeFileSync(
+ normalizePath(fileURLToPath(new URL(REFERENCE_FILE, settings.dotAstroDir))),
+ astroDtsContent,
+ 'utf-8',
+ );
+}
+
+async function setUpEnvTs(settings: AstroSettings, fs: typeof fsMod, logger: Logger) {
+ const envTsPath = normalizePath(fileURLToPath(new URL('env.d.ts', settings.config.srcDir)));
+ const envTsPathRelativetoRoot = normalizePath(
+ relative(fileURLToPath(settings.config.root), envTsPath),
+ );
+ const relativePath = normalizePath(
+ relative(
+ fileURLToPath(settings.config.srcDir),
+ fileURLToPath(new URL(REFERENCE_FILE, settings.dotAstroDir)),
+ ),
+ );
+ const expectedTypeReference = getTsReference('path', relativePath);
+
+ if (fs.existsSync(envTsPath)) {
+ const initialEnvContents = await fs.promises.readFile(envTsPath, 'utf-8');
+ let typesEnvContents = initialEnvContents;
+
+ if (!typesEnvContents.includes(expectedTypeReference)) {
+ typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
+ }
+
+ if (initialEnvContents !== typesEnvContents) {
+ logger.info('types', `Updated ${bold(envTsPathRelativetoRoot)} type declarations.`);
+ await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
+ }
+ } else {
+ // Otherwise, inject the `env.d.ts` file
+ await fs.promises.mkdir(settings.config.srcDir, { recursive: true });
+ await fs.promises.writeFile(envTsPath, expectedTypeReference, 'utf-8');
+ logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
+ }
+}
diff --git a/packages/astro/src/env/sync.ts b/packages/astro/src/env/sync.ts
index 9ba11469ad7a..27436f967be7 100644
--- a/packages/astro/src/env/sync.ts
+++ b/packages/astro/src/env/sync.ts
@@ -1,9 +1,9 @@
import fsMod from 'node:fs';
import type { AstroSettings } from '../@types/astro.js';
-import { ENV_TYPES_FILE, TYPES_TEMPLATE_URL } from './constants.js';
+import { TYPES_TEMPLATE_URL } from './constants.js';
import { getEnvFieldType } from './validators.js';
-export function syncAstroEnv(settings: AstroSettings, fs = fsMod) {
+export function syncAstroEnv(settings: AstroSettings, fs = fsMod): void {
if (!settings.config.experimental.env) {
return;
}
@@ -23,8 +23,10 @@ export function syncAstroEnv(settings: AstroSettings, fs = fsMod) {
}
const template = fs.readFileSync(TYPES_TEMPLATE_URL, 'utf-8');
- const dts = template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
+ const content = template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
- fs.mkdirSync(settings.dotAstroDir, { recursive: true });
- fs.writeFileSync(new URL(ENV_TYPES_FILE, settings.dotAstroDir), dts, 'utf-8');
+ settings.injectedTypes.push({
+ filename: 'astro/env.d.ts',
+ content,
+ });
}
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 9b2859e48b3c..36682fe2b256 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -100,6 +100,18 @@ export function getToolbarServerCommunicationHelpers(server: ViteDevServer) {
};
}
+// Will match any invalid characters (will be converted to _). We only allow a-zA-Z0-9.-_
+const SAFE_CHARS_RE = /[^\w.-]/g;
+
+export function normalizeInjectedTypeFilename(filename: string, integrationName: string): string {
+ if (!filename.endsWith('.d.ts')) {
+ throw new Error(
+ `Integration ${bold(integrationName)} is injecting a type that does not end with "${bold('.d.ts')}"`,
+ );
+ }
+ return `./integrations/${integrationName.replace(SAFE_CHARS_RE, '_')}/${filename.replace(SAFE_CHARS_RE, '_')}`;
+}
+
export async function runHookConfigSetup({
settings,
command,
@@ -327,6 +339,19 @@ export async function runHookConfigDone({
}
settings.adapter = adapter;
},
+ injectTypes(injectedType) {
+ const normalizedFilename = normalizeInjectedTypeFilename(
+ injectedType.filename,
+ integration.name,
+ );
+
+ settings.injectedTypes.push({
+ filename: normalizedFilename,
+ content: injectedType.content,
+ });
+
+ return new URL(normalizedFilename, settings.config.root);
+ },
logger: getLogger(integration, logger),
}),
logger,
diff --git a/packages/astro/src/preferences/index.ts b/packages/astro/src/preferences/index.ts
index 9318824bf67e..2b92c5fb6cb5 100644
--- a/packages/astro/src/preferences/index.ts
+++ b/packages/astro/src/preferences/index.ts
@@ -82,9 +82,9 @@ export function coerce(key: string, value: unknown) {
return value as any;
}
-export default function createPreferences(config: AstroConfig): AstroPreferences {
+export default function createPreferences(config: AstroConfig, dotAstroDir: URL): AstroPreferences {
const global = new PreferenceStore(getGlobalPreferenceDir());
- const project = new PreferenceStore(fileURLToPath(new URL('./.astro/', config.root)));
+ const project = new PreferenceStore(fileURLToPath(dotAstroDir));
const stores: Record = { global, project };
return {
diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js
index a92993eae368..dfe4755d1102 100644
--- a/packages/astro/test/astro-sync.test.js
+++ b/packages/astro/test/astro-sync.test.js
@@ -1,8 +1,10 @@
+// @ts-check
import assert from 'node:assert/strict';
import * as fs from 'node:fs';
-import { before, describe, it } from 'node:test';
+import { beforeEach, describe, it } from 'node:test';
import { fileURLToPath } from 'node:url';
import ts from 'typescript';
+import { normalizePath } from 'vite';
import { loadFixture } from './test-utils.js';
const createFixture = () => {
@@ -11,66 +13,79 @@ const createFixture = () => {
/** @type {Record} */
const writtenFiles = {};
+ /**
+ * @param {string} path
+ */
+ const getExpectedPath = (path) =>
+ normalizePath(fileURLToPath(new URL(path, astroFixture.config.root)));
+
return {
/** @param {string} root */
- async whenSyncing(root) {
+ async load(root) {
astroFixture = await loadFixture({ root });
-
- const envPath = new URL('env.d.ts', astroFixture.config.srcDir).href;
- const typesDtsPath = new URL('.astro/types.d.ts', astroFixture.config.root).href;
-
+ return astroFixture.config;
+ },
+ clean() {
+ const envPath = new URL('env.d.ts', astroFixture.config.srcDir);
+ if (fs.existsSync(envPath)) {
+ fs.unlinkSync(new URL('env.d.ts', astroFixture.config.srcDir));
+ }
+ fs.rmSync(new URL('./.astro/', astroFixture.config.root), { force: true, recursive: true });
+ },
+ async whenSyncing() {
const fsMock = {
...fs,
- existsSync(path, ...args) {
- if (path.toString() === envPath) {
- return false;
- }
- if (path.toString() === typesDtsPath) {
- return true;
- }
- return fs.existsSync(path, ...args);
- },
+ /**
+ * @param {fs.PathLike} path
+ * @param {string} contents
+ */
writeFileSync(path, contents) {
writtenFiles[path.toString()] = contents;
+ return fs.writeFileSync(path, contents);
},
promises: {
...fs.promises,
- async readFile(path, ...args) {
- if (path.toString() === envPath) {
- return `/// `;
- } else {
- return fs.promises.readFile(path, ...args);
- }
- },
- async writeFile(path, contents) {
+ /**
+ * @param {fs.PathLike} path
+ * @param {string} contents
+ */
+ writeFile(path, contents) {
writtenFiles[path.toString()] = contents;
+ return fs.promises.writeFile(path, contents);
},
},
};
- await astroFixture.sync({
- inlineConfig: { root: fileURLToPath(new URL(root, import.meta.url)) },
- fs: fsMock,
- });
+ await astroFixture.sync(
+ { root: fileURLToPath(astroFixture.config.root) },
+ {
+ // @ts-ignore
+ fs: fsMock,
+ },
+ );
},
/** @param {string} path */
thenFileShouldExist(path) {
- const expectedPath = new URL(path, astroFixture.config.root).href;
- assert.equal(writtenFiles.hasOwnProperty(expectedPath), true, `${path} does not exist`);
+ assert.equal(
+ writtenFiles.hasOwnProperty(getExpectedPath(path)),
+ true,
+ `${path} does not exist`,
+ );
},
/**
* @param {string} path
* @param {string} content
* @param {string | undefined} error
*/
- thenFileContentShouldInclude(path, content, error) {
- const expectedPath = new URL(path, astroFixture.config.root).href;
- assert.equal(writtenFiles[expectedPath].includes(content), true, error);
+ thenFileContentShouldInclude(path, content, error = undefined) {
+ assert.equal(writtenFiles[getExpectedPath(path)].includes(content), true, error);
},
+ /**
+ * @param {string} path
+ */
thenFileShouldBeValidTypescript(path) {
- const expectedPath = new URL(path, astroFixture.config.root).href;
try {
- const content = writtenFiles[expectedPath];
+ const content = writtenFiles[getExpectedPath(path)];
const result = ts.transpileModule(content, {
compilerOptions: {
module: ts.ModuleKind.ESNext,
@@ -91,33 +106,71 @@ const createFixture = () => {
describe('astro sync', () => {
/** @type {ReturnType} */
let fixture;
- before(async () => {
+ beforeEach(async () => {
fixture = createFixture();
});
- it('Writes `src/env.d.ts` if none exists', async () => {
- await fixture.whenSyncing('./fixtures/astro-basic/');
- fixture.thenFileShouldExist('src/env.d.ts');
- fixture.thenFileContentShouldInclude('src/env.d.ts', `/// `);
+ describe('References', () => {
+ it('Writes `src/env.d.ts` if none exists', async () => {
+ await fixture.load('./fixtures/astro-basic/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('src/env.d.ts');
+ fixture.thenFileContentShouldInclude(
+ 'src/env.d.ts',
+ `/// `,
+ );
+ });
+
+ it('Updates `src/env.d.ts` if one exists', async () => {
+ const config = await fixture.load('./fixtures/astro-basic/');
+ fixture.clean();
+ fs.writeFileSync(new URL('./env.d.ts', config.srcDir), '// whatever', 'utf-8');
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('src/env.d.ts');
+ fixture.thenFileContentShouldInclude(
+ 'src/env.d.ts',
+ `/// `,
+ );
+ });
+
+ it('Writes `src/types.d.ts`', async () => {
+ await fixture.load('./fixtures/astro-basic/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/types.d.ts',
+ `/// `,
+ );
+ });
});
describe('Content collections', () => {
- it('Writes types to `.astro`', async () => {
- await fixture.whenSyncing('./fixtures/content-collections/');
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/content-collections/');
+ fixture.clean();
+ await fixture.whenSyncing();
fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
'.astro/types.d.ts',
+ `/// `,
+ );
+ fixture.thenFileShouldExist('.astro/astro/content.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/astro/content.d.ts',
`declare module 'astro:content' {`,
'Types file does not include `astro:content` module declaration',
);
- fixture.thenFileShouldBeValidTypescript('.astro/types.d.ts');
+ fixture.thenFileShouldBeValidTypescript('.astro/astro/content.d.ts');
});
it('Writes types for empty collections', async () => {
- await fixture.whenSyncing('./fixtures/content-collections-empty-dir/');
- fixture.thenFileShouldExist('.astro/types.d.ts');
+ await fixture.load('./fixtures/content-collections-empty-dir/');
+ fixture.clean();
+ await fixture.whenSyncing();
fixture.thenFileContentShouldInclude(
- '.astro/types.d.ts',
+ '.astro/astro/content.d.ts',
`"blog": Record {
'Types file does not include empty collection type',
);
fixture.thenFileContentShouldInclude(
- '.astro/types.d.ts',
+ '.astro/astro/content.d.ts',
`"blogMeta": Record {
'Types file does not include empty collection type',
);
});
+ });
- it('Adds type reference to `src/env.d.ts`', async () => {
- await fixture.whenSyncing('./fixtures/content-collections/');
- fixture.thenFileShouldExist('src/env.d.ts');
+ describe('astro:env', () => {
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/astro-env/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
- 'src/env.d.ts',
- `/// `,
+ '.astro/types.d.ts',
+ `/// `,
);
- });
- });
-
- describe('Astro Env', () => {
- it('Writes types to `.astro`', async () => {
- await fixture.whenSyncing('./fixtures/astro-env/');
- fixture.thenFileShouldExist('.astro/env.d.ts');
+ fixture.thenFileShouldExist('.astro/astro/env.d.ts');
fixture.thenFileContentShouldInclude(
- '.astro/env.d.ts',
+ '.astro/astro/env.d.ts',
`declare module 'astro:env/client' {`,
'Types file does not include `astro:env` module declaration',
);
});
- it('Adds type reference to `src/env.d.ts`', async () => {
- await fixture.whenSyncing('./fixtures/astro-env/');
- fixture.thenFileShouldExist('src/env.d.ts');
- fixture.thenFileContentShouldInclude(
- 'src/env.d.ts',
- `/// `,
- );
- });
-
it('Does not throw if a public variable is required', async () => {
- let error = null;
try {
- await fixture.whenSyncing('./fixtures/astro-env-required-public/');
- } catch (e) {
- error = e;
+ await fixture.load('./fixtures/astro-env-required-public/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ assert.ok(true);
+ } catch {
+ assert.fail();
}
-
- assert.equal(error, null, 'Syncing should not throw astro:env validation errors');
});
});
- describe('Astro Actions', () => {
- // We can't check for the file existence or content yet because
- // it's an integration and does not use the fs mock
-
- it('Adds type reference to `src/env.d.ts`', async () => {
- await fixture.whenSyncing('./fixtures/actions/');
- fixture.thenFileShouldExist('src/env.d.ts');
+ describe('astro:actions', () => {
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/actions/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
- 'src/env.d.ts',
- `/// `,
+ '.astro/types.d.ts',
+ `/// `,
+ );
+ fixture.thenFileShouldExist('.astro/astro/actions.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/astro/actions.d.ts',
+ `declare module "astro:actions" {`,
+ 'Types file does not include `astro:actions` module declaration',
);
+ fixture.thenFileShouldBeValidTypescript('.astro/astro/actions.d.ts');
});
});
});
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index 2fcd840a8a54..95edeebd2673 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -24,6 +24,7 @@ process.env.ASTRO_TELEMETRY_DISABLED = true;
/**
* @typedef {import('../src/core/dev/dev').DevServer} DevServer
* @typedef {import('../src/@types/astro').AstroInlineConfig & { root?: string | URL }} AstroInlineConfig
+ * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
* @typedef {import('../src/core/app/index').App} App
* @typedef {import('../src/cli/check/index').AstroChecker} AstroChecker
@@ -49,6 +50,7 @@ process.env.ASTRO_TELEMETRY_DISABLED = true;
* @property {() => Promise} onNextChange
* @property {typeof check} check
* @property {typeof sync} sync
+ * @property {AstroConfig} config
*
* This function returns an instance of the Check
*
diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
index 73ce15377f34..3417650fc963 100644
--- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
+++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
@@ -8,13 +8,15 @@ const root = new URL('../../fixtures/content-mixed-errors/', import.meta.url);
async function sync({ fs }) {
try {
- await _sync({
- inlineConfig: {
+ await _sync(
+ {
root: fileURLToPath(root),
logLevel: 'silent',
},
- fs,
- });
+ {
+ fs,
+ },
+ );
return 0;
} catch (_) {
return 1;
@@ -121,11 +123,7 @@ title: Post
root,
);
- try {
- const res = await sync({ fs });
- assert.equal(res, 0);
- } catch (e) {
- assert.fail(`Did not expect sync to throw: ${e.message}`);
- }
+ const res = await sync({ fs });
+ assert.equal(res, 0);
});
});
diff --git a/packages/astro/test/units/dev/restart.test.js b/packages/astro/test/units/dev/restart.test.js
index a09bfce98c6f..339b95fc1acb 100644
--- a/packages/astro/test/units/dev/restart.test.js
+++ b/packages/astro/test/units/dev/restart.test.js
@@ -194,4 +194,31 @@ describe('dev container restarts', () => {
await restart.container.close();
}
});
+
+ it('Is able to restart project on .astro/settings.json changes', async () => {
+ const fs = createFs(
+ {
+ '/src/pages/index.astro': ``,
+ '/.astro/settings.json': `{}`,
+ },
+ root,
+ );
+
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
+ });
+ await startContainer(restart.container);
+ assert.equal(isStarted(restart.container), true);
+
+ try {
+ let restartComplete = restart.restarted();
+ fs.mkdirSync('/.astro/', { recursive: true });
+ fs.writeFileSync('/.astro/settings.json', `{ }`);
+ triggerFSEvent(restart.container, fs, '/.astro/settings.json', 'change');
+ await restartComplete;
+ } finally {
+ await restart.container.close();
+ }
+ });
});
diff --git a/packages/astro/test/units/integrations/api.test.js b/packages/astro/test/units/integrations/api.test.js
index 960e003fc3f1..6122ba6408f6 100644
--- a/packages/astro/test/units/integrations/api.test.js
+++ b/packages/astro/test/units/integrations/api.test.js
@@ -1,7 +1,11 @@
import * as assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { validateSupportedFeatures } from '../../../dist/integrations/features-validation.js';
-import { runHookBuildSetup, runHookConfigSetup } from '../../../dist/integrations/hooks.js';
+import {
+ normalizeInjectedTypeFilename,
+ runHookBuildSetup,
+ runHookConfigSetup,
+} from '../../../dist/integrations/hooks.js';
import { defaultLogger } from '../test-utils.js';
describe('Integration API', () => {
@@ -311,3 +315,20 @@ describe('Astro feature map', function () {
});
});
});
+
+describe('normalizeInjectedTypeFilename', () => {
+ // invalid filename
+ assert.throws(() => normalizeInjectedTypeFilename('types', 'integration'));
+ // valid filename
+ assert.doesNotThrow(() => normalizeInjectedTypeFilename('types.d.ts', 'integration'));
+ // filename normalization
+ assert.equal(
+ normalizeInjectedTypeFilename('aA1-*/_"~.d.ts', 'integration'),
+ './integrations/integration/aA1-_____.d.ts',
+ );
+ // integration name normalization
+ assert.equal(
+ normalizeInjectedTypeFilename('types.d.ts', 'aA1-*/_"~.'),
+ './integrations/aA1-_____./types.d.ts',
+ );
+});
diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts
index 19c712c802aa..054c74d74156 100644
--- a/packages/db/src/core/integration/index.ts
+++ b/packages/db/src/core/integration/index.ts
@@ -22,7 +22,7 @@ import { resolveDbConfig } from '../load-file.js';
import { SEED_DEV_FILE_NAME } from '../queries.js';
import { type VitePlugin, getDbDirectoryUrl } from '../utils.js';
import { fileURLIntegration } from './file-url.js';
-import { typegenInternal } from './typegen.js';
+import { getDtsContent } from './typegen.js';
import {
type LateSeedFiles,
type LateTables,
@@ -30,7 +30,6 @@ import {
resolved,
vitePluginDb,
} from './vite-plugin-db.js';
-import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js';
function astroDBIntegration(): AstroIntegration {
let connectToStudio = false;
@@ -102,11 +101,11 @@ function astroDBIntegration(): AstroIntegration {
updateConfig({
vite: {
assetsInclude: [DB_PATH],
- plugins: [dbPlugin, vitePluginInjectEnvTs(config, logger)],
+ plugins: [dbPlugin],
},
});
},
- 'astro:config:done': async ({ config }) => {
+ 'astro:config:done': async ({ config, injectTypes }) => {
if (command === 'preview') return;
// TODO: refine where we load tables
@@ -122,7 +121,10 @@ function astroDBIntegration(): AstroIntegration {
await writeFile(localDbUrl, '');
}
- await typegenInternal({ tables: tables.get() ?? {}, root: config.root });
+ injectTypes({
+ filename: 'db.d.ts',
+ content: getDtsContent(tables.get() ?? {}),
+ });
},
'astro:server:setup': async ({ server, logger }) => {
seedHandler.execute = async (fileUrl) => {
diff --git a/packages/db/src/core/integration/typegen.ts b/packages/db/src/core/integration/typegen.ts
index 725738fc5a51..91364b3c3dc1 100644
--- a/packages/db/src/core/integration/typegen.ts
+++ b/packages/db/src/core/integration/typegen.ts
@@ -1,18 +1,7 @@
-import { existsSync } from 'node:fs';
-import { mkdir, writeFile } from 'node:fs/promises';
-import type { AstroConfig } from 'astro';
-import { DB_TYPES_FILE, RUNTIME_IMPORT } from '../consts.js';
-import { resolveDbConfig } from '../load-file.js';
+import { RUNTIME_IMPORT } from '../consts.js';
import type { DBTable, DBTables } from '../types.js';
-// Exported for use in Astro core CLI
-export async function typegen(astroConfig: Pick) {
- const { dbConfig } = await resolveDbConfig(astroConfig);
-
- await typegenInternal({ tables: dbConfig.tables, root: astroConfig.root });
-}
-
-export async function typegenInternal({ tables, root }: { tables: DBTables; root: URL }) {
+export function getDtsContent(tables: DBTables) {
const content = `// This file is generated by Astro DB
declare module 'astro:db' {
${Object.entries(tables)
@@ -20,14 +9,7 @@ ${Object.entries(tables)
.join('\n')}
}
`;
-
- const dotAstroDir = new URL('.astro/', root);
-
- if (!existsSync(dotAstroDir)) {
- await mkdir(dotAstroDir);
- }
-
- await writeFile(new URL(DB_TYPES_FILE, dotAstroDir), content);
+ return content;
}
function generateTableType(name: string, table: DBTable): string {
diff --git a/packages/db/src/core/integration/vite-plugin-inject-env-ts.ts b/packages/db/src/core/integration/vite-plugin-inject-env-ts.ts
deleted file mode 100644
index 14257d480fef..000000000000
--- a/packages/db/src/core/integration/vite-plugin-inject-env-ts.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { existsSync } from 'node:fs';
-import { readFile, writeFile } from 'node:fs/promises';
-import path from 'node:path';
-import { fileURLToPath } from 'node:url';
-import type { AstroIntegrationLogger } from 'astro';
-import { bold } from 'kleur/colors';
-import { normalizePath } from 'vite';
-import { DB_TYPES_FILE } from '../consts.js';
-import type { VitePlugin } from '../utils.js';
-
-export function vitePluginInjectEnvTs(
- { srcDir, root }: { srcDir: URL; root: URL },
- logger: AstroIntegrationLogger,
-): VitePlugin {
- return {
- name: 'db-inject-env-ts',
- enforce: 'post',
- async config() {
- await setUpEnvTs({ srcDir, root, logger });
- },
- };
-}
-
-export async function setUpEnvTs({
- srcDir,
- root,
- logger,
-}: {
- srcDir: URL;
- root: URL;
- logger: AstroIntegrationLogger;
-}) {
- const envTsPath = getEnvTsPath({ srcDir });
- const envTsPathRelativetoRoot = normalizePath(
- path.relative(fileURLToPath(root), fileURLToPath(envTsPath)),
- );
-
- if (existsSync(envTsPath)) {
- let typesEnvContents = await readFile(envTsPath, 'utf-8');
- const dotAstroDir = new URL('.astro/', root);
-
- if (!existsSync(dotAstroDir)) return;
-
- const dbTypeReference = getDBTypeReference({ srcDir, dotAstroDir });
-
- if (!typesEnvContents.includes(dbTypeReference)) {
- typesEnvContents = `${dbTypeReference}\n${typesEnvContents}`;
- await writeFile(envTsPath, typesEnvContents, 'utf-8');
- logger.info(`Added ${bold(envTsPathRelativetoRoot)} types`);
- }
- }
-}
-
-function getDBTypeReference({ srcDir, dotAstroDir }: { srcDir: URL; dotAstroDir: URL }) {
- const dbTypesFile = new URL(DB_TYPES_FILE, dotAstroDir);
- const contentTypesRelativeToSrcDir = normalizePath(
- path.relative(fileURLToPath(srcDir), fileURLToPath(dbTypesFile)),
- );
-
- return `/// `;
-}
-
-function getEnvTsPath({ srcDir }: { srcDir: URL }) {
- return new URL('env.d.ts', srcDir);
-}
diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts
index b26507130471..f7022a24a236 100644
--- a/packages/db/src/index.ts
+++ b/packages/db/src/index.ts
@@ -1,4 +1,3 @@
export type { TableConfig } from './core/types.js';
export { cli } from './core/cli/index.js';
export { integration as default } from './core/integration/index.js';
-export { typegen } from './core/integration/typegen.js';