summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/forty-trains-notice.md5
-rw-r--r--packages/astro/config.d.ts40
-rw-r--r--packages/astro/config.mjs16
-rw-r--r--packages/astro/package.json7
-rw-r--r--packages/astro/src/config/entrypoint.ts30
-rw-r--r--packages/astro/src/config/index.ts13
-rw-r--r--packages/astro/src/core/errors/errors-data.ts2
-rw-r--r--packages/astro/src/env/config.ts2
-rw-r--r--packages/astro/src/types/public/config.ts51
-rw-r--r--packages/astro/test/types/define-config.ts41
10 files changed, 125 insertions, 82 deletions
diff --git a/.changeset/forty-trains-notice.md b/.changeset/forty-trains-notice.md
new file mode 100644
index 000000000..29938198d
--- /dev/null
+++ b/.changeset/forty-trains-notice.md
@@ -0,0 +1,5 @@
+---
+'astro': minor
+---
+
+Improves `defineConfig` type safety. TypeScript will now error if a group of related configuration options do not have consistent types. For example, you will now see an error if your language set for `i18n.defaultLocale` is not one of the supported locales specified in `i18n.locales`.
diff --git a/packages/astro/config.d.ts b/packages/astro/config.d.ts
deleted file mode 100644
index de2a500c2..000000000
--- a/packages/astro/config.d.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/// <reference path='./client.d.ts' />
-type ViteUserConfig = import('vite').UserConfig;
-type ViteUserConfigFn = import('vite').UserConfigFn;
-type AstroUserConfig = import('./dist/types/public/config.js').AstroUserConfig;
-type AstroInlineConfig = import('./dist/types/public/config.js').AstroInlineConfig;
-type ImageServiceConfig = import('./dist/types/public/config.js').ImageServiceConfig;
-type SharpImageServiceConfig = import('./dist/assets/services/sharp.js').SharpImageServiceConfig;
-type EnvField = typeof import('./dist/env/config.js').envField;
-
-/**
- * See the full Astro Configuration API Documentation
- * https://astro.build/config
- */
-export function defineConfig(config: AstroUserConfig): AstroUserConfig;
-
-/**
- * Use Astro to generate a fully resolved Vite config
- */
-export function getViteConfig(
- config: ViteUserConfig,
- inlineAstroConfig?: AstroInlineConfig,
-): ViteUserConfigFn;
-
-/**
- * Return the configuration needed to use the Sharp-based image service
- */
-export function sharpImageService(config?: SharpImageServiceConfig): ImageServiceConfig;
-
-/**
- * Return the configuration needed to use the passthrough image service. This image services does not perform
- * any image transformations, and is mainly useful when your platform does not support other image services, or you are
- * not using Astro's built-in image processing.
- * See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
- */
-export function passthroughImageService(): ImageServiceConfig;
-
-/**
- * Return a valid env field to use in this Astro config for `experimental.env.schema`.
- */
-export declare const envField: EnvField;
diff --git a/packages/astro/config.mjs b/packages/astro/config.mjs
deleted file mode 100644
index d117d806e..000000000
--- a/packages/astro/config.mjs
+++ /dev/null
@@ -1,16 +0,0 @@
-export { defineConfig, getViteConfig } from './dist/config/index.js';
-export { envField } from './dist/env/config.js';
-
-export function sharpImageService(config = {}) {
- return {
- entrypoint: 'astro/assets/services/sharp',
- config,
- };
-}
-
-export function passthroughImageService() {
- return {
- entrypoint: 'astro/assets/services/noop',
- config: {},
- };
-}
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 7898e570d..663b7cd38 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -46,10 +46,7 @@
},
"./compiler-runtime": "./dist/runtime/compiler/index.js",
"./runtime/*": "./dist/runtime/*",
- "./config": {
- "types": "./config.d.ts",
- "default": "./config.mjs"
- },
+ "./config": "./dist/config/entrypoint.js",
"./container": {
"types": "./dist/container/index.d.ts",
"default": "./dist/container/index.js"
@@ -93,8 +90,6 @@
"types",
"astro.js",
"index.d.ts",
- "config.d.ts",
- "config.mjs",
"zod.d.ts",
"zod.mjs",
"env.d.ts",
diff --git a/packages/astro/src/config/entrypoint.ts b/packages/astro/src/config/entrypoint.ts
new file mode 100644
index 000000000..4951792d6
--- /dev/null
+++ b/packages/astro/src/config/entrypoint.ts
@@ -0,0 +1,30 @@
+// IMPORTANT: this file is the entrypoint for "astro/config". Keep it as light as possible!
+
+import type { SharpImageServiceConfig } from '../assets/services/sharp.js';
+import type { ImageServiceConfig } from '../types/public/index.js';
+
+export { defineConfig, getViteConfig } from './index.js';
+export { envField } from '../env/config.js';
+
+/**
+ * Return the configuration needed to use the Sharp-based image service
+ */
+export function sharpImageService(config: SharpImageServiceConfig = {}): ImageServiceConfig {
+ return {
+ entrypoint: 'astro/assets/services/sharp',
+ config,
+ };
+}
+
+/**
+ * Return the configuration needed to use the passthrough image service. This image services does not perform
+ * any image transformations, and is mainly useful when your platform does not support other image services, or you are
+ * not using Astro's built-in image processing.
+ * See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
+ */
+export function passthroughImageService(): ImageServiceConfig {
+ return {
+ entrypoint: 'astro/assets/services/noop',
+ config: {},
+ };
+}
diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts
index 0aa421f78..62316377a 100644
--- a/packages/astro/src/config/index.ts
+++ b/packages/astro/src/config/index.ts
@@ -1,13 +1,22 @@
import type { UserConfig as ViteUserConfig, UserConfigFn as ViteUserConfigFn } from 'vite';
import { Logger } from '../core/logger/core.js';
import { createRouteManifest } from '../core/routing/index.js';
-import type { AstroInlineConfig, AstroUserConfig } from '../types/public/config.js';
+import type { AstroInlineConfig, AstroUserConfig, Locales } from '../types/public/config.js';
import { createDevelopmentManifest } from '../vite-plugin-astro-server/plugin.js';
-export function defineConfig(config: AstroUserConfig) {
+/**
+ * See the full Astro Configuration API Documentation
+ * https://astro.build/config
+ */
+export function defineConfig<const TLocales extends Locales = never>(
+ config: AstroUserConfig<TLocales>,
+) {
return config;
}
+/**
+ * Use Astro to generate a fully resolved Vite config
+ */
export function getViteConfig(
userViteConfig: ViteUserConfig,
inlineAstroConfig: AstroInlineConfig = {},
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index 5ad2b939e..fb93e6e91 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -1160,8 +1160,8 @@ export const UnhandledRejection = {
* import { defineConfig } from 'astro'
* export default defineConfig({
* i18n: {
- * defaultLocale: 'en',
* locales: ['en', 'fr'],
+ * defaultLocale: 'en',
* },
* })
* ```
diff --git a/packages/astro/src/env/config.ts b/packages/astro/src/env/config.ts
index 629a1fe2e..beb91b43b 100644
--- a/packages/astro/src/env/config.ts
+++ b/packages/astro/src/env/config.ts
@@ -10,7 +10,7 @@ import type {
} from './schema.js';
/**
- * Return a valid env field to use in this Astro config for `experimental.env.schema`.
+ * Return a valid env field to use in this Astro config for `env.schema`.
*/
export const envField = {
string: (options: StringFieldInput): StringField => ({
diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts
index 4aec86f33..1e24f4194 100644
--- a/packages/astro/src/types/public/config.ts
+++ b/packages/astro/src/types/public/config.ts
@@ -16,6 +16,14 @@ import type { AstroIntegration } from './integrations.js';
export type Locales = (string | { codes: string[]; path: string })[];
+type NormalizeLocales<T extends Locales> = {
+ [K in keyof T]: T[K] extends string
+ ? T[K]
+ : T[K] extends { codes: Array<string> }
+ ? T[K]['codes'][number]
+ : never;
+}[number];
+
export interface ImageServiceConfig<T extends Record<string, any> = Record<string, any>> {
entrypoint: 'astro/assets/services/sharp' | (string & {});
config?: T;
@@ -101,8 +109,9 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
/**
* Astro User Config
* Docs: https://docs.astro.build/reference/configuration-reference/
- */
-export interface AstroUserConfig {
+ *
+ * Generics do not follow semver and may change at any time.
+ */ export interface AstroUserConfig<TLocales extends Locales = never> {
/**
* @docs
* @kind heading
@@ -1205,30 +1214,31 @@ export interface AstroUserConfig {
i18n?: {
/**
* @docs
- * @name i18n.defaultLocale
- * @type {string}
+ * @name i18n.locales
+ * @type {Locales}
* @version 3.5.0
* @description
*
- * The default locale of your website/application. This is a required field.
+ * A list of all locales supported by the website. This is a required field.
*
- * No particular language format or syntax is enforced, but we suggest using lower-case and hyphens as needed (e.g. "es", "pt-br") for greatest compatibility.
+ * Languages can be listed either as individual codes (e.g. `['en', 'es', 'pt-br']`) or mapped to a shared `path` of codes (e.g. `{ path: "english", codes: ["en", "en-US"]}`). These codes will be used to determine the URL structure of your deployed site.
+ *
+ * No particular language code format or syntax is enforced, but your project folders containing your content files must match exactly the `locales` items in the list. In the case of multiple `codes` pointing to a custom URL path prefix, store your content files in a folder with the same name as the `path` configured.
*/
- defaultLocale: string;
+ locales: [TLocales] extends [never] ? Locales : TLocales;
+
/**
* @docs
- * @name i18n.locales
- * @type {Locales}
+ * @name i18n.defaultLocale
+ * @type {string}
* @version 3.5.0
* @description
*
- * A list of all locales supported by the website, including the `defaultLocale`. This is a required field.
+ * The default locale of your website/application, that is one of the specified `locales`. This is a required field.
*
- * Languages can be listed either as individual codes (e.g. `['en', 'es', 'pt-br']`) or mapped to a shared `path` of codes (e.g. `{ path: "english", codes: ["en", "en-US"]}`). These codes will be used to determine the URL structure of your deployed site.
- *
- * No particular language code format or syntax is enforced, but your project folders containing your content files must match exactly the `locales` items in the list. In the case of multiple `codes` pointing to a custom URL path prefix, store your content files in a folder with the same name as the `path` configured.
+ * No particular language format or syntax is enforced, but we suggest using lower-case and hyphens as needed (e.g. "es", "pt-br") for greatest compatibility.
*/
- locales: Locales;
+ defaultLocale: [TLocales] extends [never] ? string : NormalizeLocales<NoInfer<TLocales>>;
/**
* @docs
@@ -1258,7 +1268,14 @@ export interface AstroUserConfig {
* })
* ```
*/
- fallback?: Record<string, string>;
+ fallback?: [TLocales] extends [never]
+ ? Record<string, string>
+ : {
+ [Locale in NormalizeLocales<NoInfer<TLocales>>]?: Exclude<
+ NormalizeLocales<NoInfer<TLocales>>,
+ Locale
+ >;
+ };
/**
* @docs
@@ -1444,7 +1461,9 @@ export interface AstroUserConfig {
*
* See the [Internationalization Guide](https://docs.astro.build/en/guides/internationalization/#domains) for more details, including the limitations of this feature.
*/
- domains?: Record<string, string>;
+ domains?: [TLocales] extends [never]
+ ? Record<string, string>
+ : Partial<Record<NormalizeLocales<NoInfer<TLocales>>, string>>;
};
/** ! WARNING: SUBJECT TO CHANGE */
diff --git a/packages/astro/test/types/define-config.ts b/packages/astro/test/types/define-config.ts
new file mode 100644
index 000000000..9015694e2
--- /dev/null
+++ b/packages/astro/test/types/define-config.ts
@@ -0,0 +1,41 @@
+import { describe, it } from 'node:test';
+import { defineConfig } from '../../dist/config/index.js';
+import type { AstroUserConfig } from '../../dist/types/public/index.js';
+import { expectTypeOf } from 'expect-type';
+
+describe('defineConfig()', () => {
+ it('Infers generics correctly', () => {
+ const config_0 = defineConfig({});
+ expectTypeOf(config_0).toEqualTypeOf<AstroUserConfig<never>>();
+ expectTypeOf(config_0.i18n!.defaultLocale).toEqualTypeOf<string>();
+
+ const config_1 = defineConfig({
+ i18n: {
+ locales: ['en'],
+ defaultLocale: 'en',
+ },
+ });
+ expectTypeOf(config_1).toEqualTypeOf<AstroUserConfig<['en']>>();
+ expectTypeOf(config_1.i18n!.defaultLocale).toEqualTypeOf<'en'>();
+
+ const config_2 = defineConfig({
+ i18n: {
+ locales: ['en', 'fr'],
+ defaultLocale: 'fr',
+ },
+ });
+ expectTypeOf(config_2).toEqualTypeOf<AstroUserConfig<['en', 'fr']>>();
+ expectTypeOf(config_2.i18n!.defaultLocale).toEqualTypeOf<'en' | 'fr'>();
+
+ const config_3 = defineConfig({
+ i18n: {
+ locales: ['en', { path: 'french', codes: ['fr', 'fr-FR'] }],
+ defaultLocale: 'en',
+ },
+ });
+ expectTypeOf(config_3).toEqualTypeOf<
+ AstroUserConfig<['en', { readonly path: 'french'; readonly codes: ['fr', 'fr-FR'] }]>
+ >();
+ expectTypeOf(config_3.i18n!.defaultLocale).toEqualTypeOf<'en' | 'fr' | 'fr-FR'>();
+ });
+});