mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -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
683417f334
commit
2e70741362
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 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 {
|
||||
/** The name of the integration. */
|
||||
name: string;
|
||||
/** The different hooks available to extend. */
|
||||
hooks: {
|
||||
'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>;
|
||||
};
|
||||
[K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]
|
||||
} & Partial<Record<string, unknown>>
|
||||
}
|
||||
|
||||
export type RewritePayload = string | URL | Request;
|
||||
|
|
|
@ -2,19 +2,16 @@ import { existsSync } from 'node:fs';
|
|||
import { unlink, writeFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import type { AstroConfig, AstroIntegration } from 'astro';
|
||||
import type { AstroConfig } from 'astro';
|
||||
import { build as esbuild } from 'esbuild';
|
||||
import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js';
|
||||
import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js';
|
||||
import { errorMap } from './integration/error-map.js';
|
||||
import { getConfigVirtualModContents } from './integration/vite-plugin-db.js';
|
||||
import { dbConfigSchema } from './schemas.js';
|
||||
import { type AstroDbIntegration } from './types.js';
|
||||
import './types.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.
|
||||
*/
|
||||
|
@ -31,7 +28,6 @@ export async function resolveDbConfig({
|
|||
const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = [];
|
||||
const integrationSeedPaths: Array<string | URL> = [];
|
||||
for (const integration of integrations) {
|
||||
if (!isDbIntegration(integration)) continue;
|
||||
const { name, hooks } = integration;
|
||||
if (hooks['astro:db:setup']) {
|
||||
hooks['astro:db:setup']({
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { AstroIntegration } from 'astro';
|
||||
import type { z } from 'zod';
|
||||
import type {
|
||||
MaybeArray,
|
||||
|
@ -88,13 +87,16 @@ interface LegacyIndexConfig<TColumns extends ColumnsConfig>
|
|||
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
|
||||
export type TextColumnOpts = z.input<typeof textColumnOptsSchema>;
|
||||
|
||||
export type AstroDbIntegration = AstroIntegration & {
|
||||
hooks: {
|
||||
'astro:db:setup'?: (options: {
|
||||
extendDb: (options: {
|
||||
configEntrypoint?: URL | string;
|
||||
seedEntrypoint?: URL | string;
|
||||
}) => void;
|
||||
}) => void | Promise<void>;
|
||||
};
|
||||
};
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Astro {
|
||||
export interface IntegrationHooks {
|
||||
'astro:db:setup'?: (options: {
|
||||
extendDb: (options: {
|
||||
configEntrypoint?: URL | string;
|
||||
seedEntrypoint?: URL | string;
|
||||
}) => void;
|
||||
}) => void | Promise<void>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getAstroStudioEnv } from '@astrojs/studio';
|
||||
import type { AstroConfig, AstroIntegration } from 'astro';
|
||||
import { loadEnv } from 'vite';
|
||||
import type { AstroDbIntegration } from './types.js';
|
||||
import './types.js';
|
||||
|
||||
export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
|
||||
|
||||
|
@ -19,7 +19,7 @@ export function getDbDirectoryUrl(root: URL | string) {
|
|||
return new URL('db/', root);
|
||||
}
|
||||
|
||||
export function defineDbIntegration(integration: AstroDbIntegration): AstroIntegration {
|
||||
export function defineDbIntegration(integration: AstroIntegration): AstroIntegration {
|
||||
return integration;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue