mirror of
https://github.com/withastro/astro.git
synced 2025-01-06 22:10:10 -05:00
feat: Refactor integration hooks type (#11304)
* feat: Refactor integration hooks type * Revert formatting changes * More detailed changelog * Add changeset for Astro DB * Apply suggestions from code review Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
a3b2c3230d
commit
b14c2e3952
6 changed files with 179 additions and 91 deletions
23
.changeset/blue-colts-film.md
Normal file
23
.changeset/blue-colts-film.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
'@astrojs/db': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Removes the `AstroDbIntegration` type
|
||||||
|
|
||||||
|
Astro integration hooks can now be extended and as such `@astrojs/db` no longer needs to declare it's own integration type. Using `AstroIntegration` will have the same type.
|
||||||
|
|
||||||
|
If you were using the `AstroDbIntegration` type, apply this change to your integration code:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- import { defineDbIntegration, type AstroDbIntegration } from '@astrojs/db/utils';
|
||||||
|
+ import { defineDbIntegration } from '@astrojs/db/utils';
|
||||||
|
import type { AstroIntegration } from 'astro';
|
||||||
|
|
||||||
|
- export default (): AstroDbIntegration => {
|
||||||
|
+ export default (): AstroIntegration => {
|
||||||
|
return defineDbIntegration({
|
||||||
|
name: 'your-integration',
|
||||||
|
hooks: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
58
.changeset/curvy-otters-jog.md
Normal file
58
.changeset/curvy-otters-jog.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Refactors the type for integration hooks so that integration authors writing custom integration hooks can now allow runtime interactions between their integration and other integrations.
|
||||||
|
|
||||||
|
This internal change should not break existing code for integration authors.
|
||||||
|
|
||||||
|
To declare your own hooks for your integration, extend the `Astro.IntegrationHooks` interface:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// your-integration/types.ts
|
||||||
|
declare global {
|
||||||
|
namespace Astro {
|
||||||
|
interface IntegrationHooks {
|
||||||
|
'myLib:eventHappened': (your: string, parameters: number) => Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Call your hooks on all other integrations installed in a project at the appropriate time. For example, you can call your hook on initialization before either the Vite or Astro config have resolved:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// your-integration/index.ts
|
||||||
|
import './types.ts';
|
||||||
|
|
||||||
|
export default (): AstroIntegration => {
|
||||||
|
return {
|
||||||
|
name: 'your-integration',
|
||||||
|
hooks: {
|
||||||
|
'astro:config:setup': async ({ config }) => {
|
||||||
|
for (const integration of config.integrations) {
|
||||||
|
await integration.hooks['myLib:eventHappened'].?('your values', 123);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Other integrations can also now declare your hooks:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// other-integration/index.ts
|
||||||
|
import 'your-integration/types.ts';
|
||||||
|
|
||||||
|
export default (): AstroIntegration => {
|
||||||
|
return {
|
||||||
|
name: 'other-integration',
|
||||||
|
hooks: {
|
||||||
|
'myLib:eventHappened': async (your, values) => {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -3056,83 +3056,92 @@ export type HookParameters<
|
||||||
Fn = AstroIntegration['hooks'][Hook],
|
Fn = AstroIntegration['hooks'][Hook],
|
||||||
> = Fn extends (...args: any) => any ? Parameters<Fn>[0] : never;
|
> = Fn extends (...args: any) => any ? Parameters<Fn>[0] : never;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace Astro {
|
||||||
|
export interface IntegrationHooks {
|
||||||
|
'astro:config:setup': (options: {
|
||||||
|
config: AstroConfig;
|
||||||
|
command: 'dev' | 'build' | 'preview';
|
||||||
|
isRestart: boolean;
|
||||||
|
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
|
||||||
|
addRenderer: (renderer: AstroRenderer) => void;
|
||||||
|
addWatchFile: (path: URL | string) => void;
|
||||||
|
injectScript: (stage: InjectedScriptStage, content: string) => void;
|
||||||
|
injectRoute: (injectRoute: InjectedRoute) => void;
|
||||||
|
addClientDirective: (directive: ClientDirectiveConfig) => void;
|
||||||
|
/**
|
||||||
|
* @deprecated Use `addDevToolbarApp` instead.
|
||||||
|
* TODO: Fully remove in Astro 5.0
|
||||||
|
*/
|
||||||
|
addDevOverlayPlugin: (entrypoint: string) => void;
|
||||||
|
// TODO: Deprecate the `string` overload once a few apps have been migrated to the new API.
|
||||||
|
addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void;
|
||||||
|
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
|
||||||
|
// This may require some refactoring of `scripts`, `styles`, and `links` into something
|
||||||
|
// more generalized. Consider the SSR use-case as well.
|
||||||
|
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:config:done': (options: {
|
||||||
|
config: AstroConfig;
|
||||||
|
setAdapter: (adapter: AstroAdapter) => void;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:server:setup': (options: {
|
||||||
|
server: vite.ViteDevServer;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:server:start': (options: {
|
||||||
|
address: AddressInfo;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
|
||||||
|
'astro:build:ssr': (options: {
|
||||||
|
manifest: SerializedSSRManifest;
|
||||||
|
/**
|
||||||
|
* This maps a {@link RouteData} to an {@link URL}, this URL represents
|
||||||
|
* the physical file you should import.
|
||||||
|
*/
|
||||||
|
entryPoints: Map<RouteData, URL>;
|
||||||
|
/**
|
||||||
|
* File path of the emitted middleware
|
||||||
|
*/
|
||||||
|
middlewareEntryPoint: URL | undefined;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:build:start': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
|
||||||
|
'astro:build:setup': (options: {
|
||||||
|
vite: vite.InlineConfig;
|
||||||
|
pages: Map<string, PageBuildData>;
|
||||||
|
target: 'client' | 'server';
|
||||||
|
updateConfig: (newConfig: vite.InlineConfig) => void;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:build:generated': (options: {
|
||||||
|
dir: URL;
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
'astro:build:done': (options: {
|
||||||
|
pages: { pathname: string }[];
|
||||||
|
dir: URL;
|
||||||
|
routes: RouteData[];
|
||||||
|
logger: AstroIntegrationLogger;
|
||||||
|
cacheManifest: boolean;
|
||||||
|
}) => void | Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface AstroIntegration {
|
export interface AstroIntegration {
|
||||||
/** The name of the integration. */
|
/** The name of the integration. */
|
||||||
name: string;
|
name: string;
|
||||||
/** The different hooks available to extend. */
|
/** The different hooks available to extend. */
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup'?: (options: {
|
[K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]
|
||||||
config: AstroConfig;
|
} & Partial<Record<string, unknown>>
|
||||||
command: 'dev' | 'build' | 'preview';
|
|
||||||
isRestart: boolean;
|
|
||||||
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
|
|
||||||
addRenderer: (renderer: AstroRenderer) => void;
|
|
||||||
addWatchFile: (path: URL | string) => void;
|
|
||||||
injectScript: (stage: InjectedScriptStage, content: string) => void;
|
|
||||||
injectRoute: (injectRoute: InjectedRoute) => void;
|
|
||||||
addClientDirective: (directive: ClientDirectiveConfig) => void;
|
|
||||||
/**
|
|
||||||
* @deprecated Use `addDevToolbarApp` instead.
|
|
||||||
* TODO: Fully remove in Astro 5.0
|
|
||||||
*/
|
|
||||||
addDevOverlayPlugin: (entrypoint: string) => void;
|
|
||||||
// TODO: Deprecate the `string` overload once a few apps have been migrated to the new API.
|
|
||||||
addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void;
|
|
||||||
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
|
|
||||||
// This may require some refactoring of `scripts`, `styles`, and `links` into something
|
|
||||||
// more generalized. Consider the SSR use-case as well.
|
|
||||||
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:config:done'?: (options: {
|
|
||||||
config: AstroConfig;
|
|
||||||
setAdapter: (adapter: AstroAdapter) => void;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:server:setup'?: (options: {
|
|
||||||
server: vite.ViteDevServer;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:server:start'?: (options: {
|
|
||||||
address: AddressInfo;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
|
|
||||||
'astro:build:ssr'?: (options: {
|
|
||||||
manifest: SerializedSSRManifest;
|
|
||||||
/**
|
|
||||||
* This maps a {@link RouteData} to an {@link URL}, this URL represents
|
|
||||||
* the physical file you should import.
|
|
||||||
*/
|
|
||||||
entryPoints: Map<RouteData, URL>;
|
|
||||||
/**
|
|
||||||
* File path of the emitted middleware
|
|
||||||
*/
|
|
||||||
middlewareEntryPoint: URL | undefined;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
|
|
||||||
'astro:build:setup'?: (options: {
|
|
||||||
vite: vite.InlineConfig;
|
|
||||||
pages: Map<string, PageBuildData>;
|
|
||||||
target: 'client' | 'server';
|
|
||||||
updateConfig: (newConfig: vite.InlineConfig) => void;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:build:generated'?: (options: {
|
|
||||||
dir: URL;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
'astro:build:done'?: (options: {
|
|
||||||
pages: { pathname: string }[];
|
|
||||||
dir: URL;
|
|
||||||
routes: RouteData[];
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
cacheManifest: boolean;
|
|
||||||
}) => void | Promise<void>;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RewritePayload = string | URL | Request;
|
export type RewritePayload = string | URL | Request;
|
||||||
|
|
|
@ -2,19 +2,16 @@ import { existsSync } from 'node:fs';
|
||||||
import { unlink, writeFile } from 'node:fs/promises';
|
import { unlink, writeFile } from 'node:fs/promises';
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import type { AstroConfig, AstroIntegration } from 'astro';
|
import type { AstroConfig } from 'astro';
|
||||||
import { build as esbuild } from 'esbuild';
|
import { build as esbuild } from 'esbuild';
|
||||||
import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js';
|
import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js';
|
||||||
import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js';
|
import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js';
|
||||||
import { errorMap } from './integration/error-map.js';
|
import { errorMap } from './integration/error-map.js';
|
||||||
import { getConfigVirtualModContents } from './integration/vite-plugin-db.js';
|
import { getConfigVirtualModContents } from './integration/vite-plugin-db.js';
|
||||||
import { dbConfigSchema } from './schemas.js';
|
import { dbConfigSchema } from './schemas.js';
|
||||||
import { type AstroDbIntegration } from './types.js';
|
import './types.js';
|
||||||
import { getAstroEnv, getDbDirectoryUrl } from './utils.js';
|
import { getAstroEnv, getDbDirectoryUrl } from './utils.js';
|
||||||
|
|
||||||
const isDbIntegration = (integration: AstroIntegration): integration is AstroDbIntegration =>
|
|
||||||
'astro:db:setup' in integration.hooks;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a user’s `astro:db` configuration file and additional configuration files provided by integrations.
|
* Load a user’s `astro:db` configuration file and additional configuration files provided by integrations.
|
||||||
*/
|
*/
|
||||||
|
@ -31,7 +28,6 @@ export async function resolveDbConfig({
|
||||||
const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = [];
|
const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = [];
|
||||||
const integrationSeedPaths: Array<string | URL> = [];
|
const integrationSeedPaths: Array<string | URL> = [];
|
||||||
for (const integration of integrations) {
|
for (const integration of integrations) {
|
||||||
if (!isDbIntegration(integration)) continue;
|
|
||||||
const { name, hooks } = integration;
|
const { name, hooks } = integration;
|
||||||
if (hooks['astro:db:setup']) {
|
if (hooks['astro:db:setup']) {
|
||||||
hooks['astro:db:setup']({
|
hooks['astro:db:setup']({
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { AstroIntegration } from 'astro';
|
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
import type {
|
import type {
|
||||||
MaybeArray,
|
MaybeArray,
|
||||||
|
@ -88,13 +87,16 @@ interface LegacyIndexConfig<TColumns extends ColumnsConfig>
|
||||||
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
|
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
|
||||||
export type TextColumnOpts = z.input<typeof textColumnOptsSchema>;
|
export type TextColumnOpts = z.input<typeof textColumnOptsSchema>;
|
||||||
|
|
||||||
export type AstroDbIntegration = AstroIntegration & {
|
declare global {
|
||||||
hooks: {
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
'astro:db:setup'?: (options: {
|
namespace Astro {
|
||||||
extendDb: (options: {
|
export interface IntegrationHooks {
|
||||||
configEntrypoint?: URL | string;
|
'astro:db:setup'?: (options: {
|
||||||
seedEntrypoint?: URL | string;
|
extendDb: (options: {
|
||||||
}) => void;
|
configEntrypoint?: URL | string;
|
||||||
}) => void | Promise<void>;
|
seedEntrypoint?: URL | string;
|
||||||
};
|
}) => void;
|
||||||
};
|
}) => void | Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getAstroStudioEnv } from '@astrojs/studio';
|
import { getAstroStudioEnv } from '@astrojs/studio';
|
||||||
import type { AstroConfig, AstroIntegration } from 'astro';
|
import type { AstroConfig, AstroIntegration } from 'astro';
|
||||||
import { loadEnv } from 'vite';
|
import { loadEnv } from 'vite';
|
||||||
import type { AstroDbIntegration } from './types.js';
|
import './types.js';
|
||||||
|
|
||||||
export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
|
export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export function getDbDirectoryUrl(root: URL | string) {
|
||||||
return new URL('db/', root);
|
return new URL('db/', root);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defineDbIntegration(integration: AstroDbIntegration): AstroIntegration {
|
export function defineDbIntegration(integration: AstroIntegration): AstroIntegration {
|
||||||
return integration;
|
return integration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue