summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/three-olives-reflect.md25
-rw-r--r--packages/astro/src/actions/consts.ts2
-rw-r--r--packages/astro/src/content/consts.ts9
-rw-r--r--packages/astro/src/content/content-layer.ts11
-rw-r--r--packages/astro/src/content/types-generator.ts27
-rw-r--r--packages/astro/src/core/dev/restart.ts3
-rw-r--r--packages/astro/src/env/constants.ts2
-rw-r--r--packages/astro/src/integrations/hooks.ts11
-rw-r--r--packages/astro/src/preferences/constants.ts1
-rw-r--r--packages/astro/src/preferences/store.ts3
-rw-r--r--packages/astro/src/types/public/integrations.ts1
-rw-r--r--packages/astro/test/astro-sync.test.js26
-rw-r--r--packages/astro/test/units/integrations/api.test.js8
13 files changed, 87 insertions, 42 deletions
diff --git a/.changeset/three-olives-reflect.md b/.changeset/three-olives-reflect.md
new file mode 100644
index 000000000..62fed8f28
--- /dev/null
+++ b/.changeset/three-olives-reflect.md
@@ -0,0 +1,25 @@
+---
+'astro': minor
+---
+
+Adds a new `createCodegenDir()` function to the `astro:config:setup` hook in the Integrations API
+
+In 4.14, we introduced the `injectTypes` utility on the `astro:config:done` hook. It can create `.d.ts` files and make their types available to user's projects automatically. Under the hood, it creates a file in `<root>/.astro/integrations/<normalized_integration_name>`.
+
+While the `.astro` directory has always been the preferred place to write code generated files, it has also been prone to mistakes. For example, you can write a `.astro/types.d.ts` file, breaking Astro types. Or you can create a file that overrides a file created by another integration.
+
+In this release, `<root>/.astro/integrations/<normalized_integration_name>` can now be retrieved in the `astro:config:setup` hook by calling `createCodegenDir()`. It allows you to have a dedicated folder, avoiding conflicts with another integration or Astro itself. This directory is created by calling this function so it's safe to write files to it directly:
+
+```js
+import { writeFileSync } from 'node:fs'
+
+const integration = {
+ name: 'my-integration',
+ hooks: {
+ 'astro:config:setup': ({ createCodegenDir }) => {
+ const codegenDir = createCodegenDir()
+ writeFileSync(new URL('cache.json', codegenDir), '{}', 'utf-8')
+ }
+ }
+}
+```
diff --git a/packages/astro/src/actions/consts.ts b/packages/astro/src/actions/consts.ts
index beb8c45b6..6a55386d8 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 = 'astro/actions.d.ts';
+export const ACTIONS_TYPES_FILE = '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/content/consts.ts b/packages/astro/src/content/consts.ts
index 71ef0344d..51eb6b78e 100644
--- a/packages/astro/src/content/consts.ts
+++ b/packages/astro/src/content/consts.ts
@@ -33,10 +33,11 @@ export const CONTENT_FLAGS = [
CONTENT_MODULE_FLAG,
] as const;
-export const CONTENT_TYPES_FILE = 'astro/content.d.ts';
-
+export const CONTENT_TYPES_FILE = 'content.d.ts';
export const DATA_STORE_FILE = 'data-store.json';
-export const ASSET_IMPORTS_FILE = 'assets.mjs';
-export const MODULES_IMPORTS_FILE = 'modules.mjs';
+export const ASSET_IMPORTS_FILE = 'content-assets.mjs';
+export const MODULES_IMPORTS_FILE = 'content-modules.mjs';
+export const COLLECTIONS_MANIFEST_FILE = 'collections/collections.json';
+export const COLLECTIONS_DIR = 'collections/'
export const CONTENT_LAYER_TYPE = 'content_layer';
diff --git a/packages/astro/src/content/content-layer.ts b/packages/astro/src/content/content-layer.ts
index 2e092ae61..dcb7aca9f 100644
--- a/packages/astro/src/content/content-layer.ts
+++ b/packages/astro/src/content/content-layer.ts
@@ -8,6 +8,7 @@ import type { AstroSettings } from '../types/astro.js';
import type { ContentEntryType, RefreshContentOptions } from '../types/public/content.js';
import {
ASSET_IMPORTS_FILE,
+ COLLECTIONS_MANIFEST_FILE,
CONTENT_LAYER_TYPE,
DATA_STORE_FILE,
MODULES_IMPORTS_FILE,
@@ -214,14 +215,10 @@ export class ContentLayer {
return collection.loader.load(context);
}),
);
- if (!existsSync(this.#settings.config.cacheDir)) {
- await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
- }
+ await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
+ await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
const cacheFile = getDataStoreFile(this.#settings);
await this.#store.writeToDisk(cacheFile);
- if (!existsSync(this.#settings.dotAstroDir)) {
- await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
- }
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
await this.#store.writeAssetImports(assetImportsFile);
const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir);
@@ -233,7 +230,7 @@ export class ContentLayer {
}
async regenerateCollectionFileManifest() {
- const collectionsManifest = new URL('collections/collections.json', this.#settings.dotAstroDir);
+ const collectionsManifest = new URL(COLLECTIONS_MANIFEST_FILE, this.#settings.dotAstroDir);
this.#logger.debug('content', 'Regenerating collection file manifest');
if (existsSync(collectionsManifest)) {
try {
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index 9923a0c34..330c1973d 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -13,7 +13,12 @@ import type { Logger } from '../core/logger/core.js';
import { isRelativePath } from '../core/path.js';
import type { AstroSettings } from '../types/astro.js';
import type { ContentEntryType } from '../types/public/content.js';
-import { CONTENT_LAYER_TYPE, CONTENT_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js';
+import {
+ COLLECTIONS_DIR,
+ CONTENT_LAYER_TYPE,
+ CONTENT_TYPES_FILE,
+ VIRTUAL_MODULE_ID,
+} from './consts.js';
import {
type CollectionConfig,
type ContentConfig,
@@ -428,10 +433,8 @@ async function writeContentFiles({
let contentTypesStr = '';
let dataTypesStr = '';
- const collectionSchemasDir = new URL('./collections/', settings.dotAstroDir);
- if (!fs.existsSync(collectionSchemasDir)) {
- fs.mkdirSync(collectionSchemasDir, { recursive: true });
- }
+ const collectionSchemasDir = new URL(COLLECTIONS_DIR, settings.dotAstroDir);
+ fs.mkdirSync(collectionSchemasDir, { recursive: true });
for (const [collection, config] of Object.entries(contentConfig?.collections ?? {})) {
collectionEntryMap[JSON.stringify(collection)] ??= {
@@ -568,12 +571,8 @@ async function writeContentFiles({
);
}
- if (!fs.existsSync(settings.dotAstroDir)) {
- fs.mkdirSync(settings.dotAstroDir, { recursive: true });
- }
-
const configPathRelativeToCacheDir = normalizeConfigPath(
- new URL('astro', settings.dotAstroDir).pathname,
+ settings.dotAstroDir.pathname,
contentPaths.config.url.pathname,
);
@@ -591,9 +590,11 @@ async function writeContentFiles({
// 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)) {
- const filePath = fileURLToPath(new URL(CONTENT_TYPES_FILE, settings.dotAstroDir));
- await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
- await fs.promises.writeFile(filePath, typeTemplateContent, 'utf-8');
+ await fs.promises.writeFile(
+ new URL(CONTENT_TYPES_FILE, settings.dotAstroDir),
+ typeTemplateContent,
+ 'utf-8',
+ );
} else {
settings.injectedTypes.push({
filename: CONTENT_TYPES_FILE,
diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts
index a19f56e8a..92dcc28ae 100644
--- a/packages/astro/src/core/dev/restart.ts
+++ b/packages/astro/src/core/dev/restart.ts
@@ -12,6 +12,7 @@ import { createSafeError } from '../errors/index.js';
import { formatErrorMessage } from '../messages.js';
import type { Container } from './container.js';
import { createContainer, startContainer } from './container.js';
+import { SETTINGS_FILE } from '../../preferences/constants.js';
async function createRestartedContainer(
container: Container,
@@ -50,7 +51,7 @@ function shouldRestartContainer(
else {
shouldRestart = configRE.test(normalizedChangedFile);
const settingsPath = vite.normalizePath(
- fileURLToPath(new URL('settings.json', settings.dotAstroDir)),
+ fileURLToPath(new URL(SETTINGS_FILE, settings.dotAstroDir)),
);
if (settingsPath.endsWith(normalizedChangedFile)) {
shouldRestart = settings.preferences.ignoreNextPreferenceReload ? false : true;
diff --git a/packages/astro/src/env/constants.ts b/packages/astro/src/env/constants.ts
index ac2c2c297..220f63373 100644
--- a/packages/astro/src/env/constants.ts
+++ b/packages/astro/src/env/constants.ts
@@ -5,7 +5,7 @@ export const VIRTUAL_MODULES_IDS = {
};
export const VIRTUAL_MODULES_IDS_VALUES = new Set(Object.values(VIRTUAL_MODULES_IDS));
-export const ENV_TYPES_FILE = 'astro/env.d.ts';
+export const ENV_TYPES_FILE = 'env.d.ts';
const PKG_BASE = new URL('../../', import.meta.url);
export const MODULE_TEMPLATE_URL = new URL('templates/env.mjs', PKG_BASE);
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 5ac21c435..55297de87 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -112,13 +112,17 @@ 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 normalizeCodegenDir(integrationName: string): string {
+ return `./integrations/${integrationName.replace(SAFE_CHARS_RE, '_')}/`;
+}
+
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, '_')}`;
+ return `${normalizeCodegenDir(integrationName)}${filename.replace(SAFE_CHARS_RE, '_')}`;
}
export async function runHookConfigSetup({
@@ -234,6 +238,11 @@ export async function runHookConfigSetup({
);
updatedSettings.middlewares[order].push(entrypoint);
},
+ createCodegenDir: () => {
+ const codegenDir = new URL(normalizeCodegenDir(integration.name), settings.dotAstroDir);
+ fs.mkdirSync(codegenDir, { recursive: true });
+ return codegenDir;
+ },
logger: integrationLogger,
};
diff --git a/packages/astro/src/preferences/constants.ts b/packages/astro/src/preferences/constants.ts
new file mode 100644
index 000000000..108787a28
--- /dev/null
+++ b/packages/astro/src/preferences/constants.ts
@@ -0,0 +1 @@
+export const SETTINGS_FILE = 'settings.json';
diff --git a/packages/astro/src/preferences/store.ts b/packages/astro/src/preferences/store.ts
index c999566e8..373ec88c1 100644
--- a/packages/astro/src/preferences/store.ts
+++ b/packages/astro/src/preferences/store.ts
@@ -2,13 +2,14 @@ import fs from 'node:fs';
import path from 'node:path';
import dget from 'dlv';
import { dset } from 'dset';
+import { SETTINGS_FILE } from './constants.js';
export class PreferenceStore {
private file: string;
constructor(
private dir: string,
- filename = 'settings.json',
+ filename = SETTINGS_FILE,
) {
this.file = path.join(this.dir, filename);
}
diff --git a/packages/astro/src/types/public/integrations.ts b/packages/astro/src/types/public/integrations.ts
index 78c4104f1..73a25f63c 100644
--- a/packages/astro/src/types/public/integrations.ts
+++ b/packages/astro/src/types/public/integrations.ts
@@ -176,6 +176,7 @@ export interface BaseIntegrationHooks {
addClientDirective: (directive: ClientDirectiveConfig) => void;
addDevToolbarApp: (entrypoint: DevToolbarAppEntry) => void;
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
+ createCodegenDir: () => URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:config:done': (options: {
diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js
index f12fb5bc4..c8a2de49c 100644
--- a/packages/astro/test/astro-sync.test.js
+++ b/packages/astro/test/astro-sync.test.js
@@ -123,15 +123,15 @@ describe('astro sync', () => {
fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
'.astro/types.d.ts',
- `/// <reference path="astro/content.d.ts" />`,
+ `/// <reference path="content.d.ts" />`,
);
- fixture.thenFileShouldExist('.astro/astro/content.d.ts');
+ fixture.thenFileShouldExist('.astro/content.d.ts');
fixture.thenFileContentShouldInclude(
- '.astro/astro/content.d.ts',
+ '.astro/content.d.ts',
`declare module 'astro:content' {`,
'Types file does not include `astro:content` module declaration',
);
- fixture.thenFileShouldBeValidTypescript('.astro/astro/content.d.ts');
+ fixture.thenFileShouldBeValidTypescript('.astro/content.d.ts');
});
it('Writes types for empty collections', async () => {
@@ -139,7 +139,7 @@ describe('astro sync', () => {
fixture.clean();
await fixture.whenSyncing();
fixture.thenFileContentShouldInclude(
- '.astro/astro/content.d.ts',
+ '.astro/content.d.ts',
`"blog": Record<string, {
id: string;
slug: string;
@@ -151,7 +151,7 @@ describe('astro sync', () => {
'Types file does not include empty collection type',
);
fixture.thenFileContentShouldInclude(
- '.astro/astro/content.d.ts',
+ '.astro/content.d.ts',
`"blogMeta": Record<string, {
id: string;
collection: "blogMeta";
@@ -170,11 +170,11 @@ describe('astro sync', () => {
fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
'.astro/types.d.ts',
- `/// <reference path="astro/env.d.ts" />`,
+ `/// <reference path="env.d.ts" />`,
);
- fixture.thenFileShouldExist('.astro/astro/env.d.ts');
+ fixture.thenFileShouldExist('.astro/env.d.ts');
fixture.thenFileContentShouldInclude(
- '.astro/astro/env.d.ts',
+ '.astro/env.d.ts',
`declare module 'astro:env/client' {`,
'Types file does not include `astro:env` module declaration',
);
@@ -210,15 +210,15 @@ describe('astro sync', () => {
fixture.thenFileShouldExist('.astro/types.d.ts');
fixture.thenFileContentShouldInclude(
'.astro/types.d.ts',
- `/// <reference path="astro/actions.d.ts" />`,
+ `/// <reference path="actions.d.ts" />`,
);
- fixture.thenFileShouldExist('.astro/astro/actions.d.ts');
+ fixture.thenFileShouldExist('.astro/actions.d.ts');
fixture.thenFileContentShouldInclude(
- '.astro/astro/actions.d.ts',
+ '.astro/actions.d.ts',
`declare module "astro:actions" {`,
'Types file does not include `astro:actions` module declaration',
);
- fixture.thenFileShouldBeValidTypescript('.astro/astro/actions.d.ts');
+ fixture.thenFileShouldBeValidTypescript('.astro/actions.d.ts');
});
});
});
diff --git a/packages/astro/test/units/integrations/api.test.js b/packages/astro/test/units/integrations/api.test.js
index 0a0233215..f2365c006 100644
--- a/packages/astro/test/units/integrations/api.test.js
+++ b/packages/astro/test/units/integrations/api.test.js
@@ -2,6 +2,7 @@ import * as assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { validateSupportedFeatures } from '../../../dist/integrations/features-validation.js';
import {
+ normalizeCodegenDir,
normalizeInjectedTypeFilename,
runHookBuildSetup,
runHookConfigSetup,
@@ -12,6 +13,7 @@ const defaultConfig = {
root: new URL('./', import.meta.url),
srcDir: new URL('src/', import.meta.url),
};
+const dotAstroDir = new URL('./.astro/', defaultConfig.root);
describe('Integration API', () => {
it('runHookBuildSetup should work', async () => {
@@ -87,6 +89,7 @@ describe('Integration API', () => {
},
],
},
+ dotAstroDir,
},
});
assert.equal(updatedSettings.config.site, site);
@@ -122,6 +125,7 @@ describe('Integration API', () => {
},
],
},
+ dotAstroDir,
},
});
assert.equal(updatedSettings.config.site, site);
@@ -270,3 +274,7 @@ describe('normalizeInjectedTypeFilename', () => {
'./integrations/aA1-_____./types.d.ts',
);
});
+
+describe('normalizeCodegenDir', () => {
+ assert.equal(normalizeCodegenDir('aA1-*/_"~.'), './integrations/aA1-_____./');
+});