0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

fix: explicitly set dev mode for data store (#12947)

* fix: explicitly set dev mode for data store

* Error handling

* Add check for file before saving

* rm unused import

* Use explicit command

* Ensure dir before writing
This commit is contained in:
Matt Kane 2025-01-09 16:10:04 +00:00 committed by GitHub
parent 0414f618a6
commit 3c2292f2f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 91 additions and 35 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a bug that caused empty content collections when running dev with NODE_ENV set

View file

@ -278,8 +278,7 @@ export class ContentLayer {
);
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);
await this.#store.writeToDisk();
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);
@ -379,8 +378,7 @@ export async function simpleLoader<TData extends { id: string }>(
* During development, this is in the `.astro` directory so that the Vite watcher can see it.
* In production, it's in the cache directory so that it's preserved between builds.
*/
export function getDataStoreFile(settings: AstroSettings, isDev?: boolean) {
isDev ??= process?.env.NODE_ENV === 'development';
export function getDataStoreFile(settings: AstroSettings, isDev: boolean) {
return new URL(DATA_STORE_FILE, isDev ? settings.dotAstroDir : settings.config.cacheDir);
}

View file

@ -180,16 +180,15 @@ export default new Map([\n${lines.join(',\n')}]);
#saveToDiskDebounced() {
this.#dirty = true;
// Only save to disk if it has already been saved once
if (this.#file) {
if (this.#saveTimeout) {
clearTimeout(this.#saveTimeout);
}
this.#saveTimeout = setTimeout(() => {
this.#saveTimeout = undefined;
this.writeToDisk(this.#file!);
}, SAVE_DEBOUNCE_MS);
if (this.#file) {
this.writeToDisk();
}
}, SAVE_DEBOUNCE_MS);
}
#writing = new Set<string>();
@ -331,13 +330,15 @@ export default new Map([\n${lines.join(',\n')}]);
return devalue.stringify(this._collections);
}
async writeToDisk(filePath: PathLike) {
async writeToDisk() {
if (!this.#dirty) {
return;
}
if (!this.#file) {
throw new AstroError(AstroErrorData.UnknownFilesystemError);
}
try {
await this.#writeFileAtomic(filePath, this.toString());
this.#file = filePath;
await this.#writeFileAtomic(this.#file, this.toString());
this.#dirty = false;
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
@ -373,10 +374,16 @@ export default new Map([\n${lines.join(',\n')}]);
try {
if (existsSync(filePath)) {
const data = await fs.readFile(filePath, 'utf-8');
return MutableDataStore.fromString(data);
const store = await MutableDataStore.fromString(data);
store.#file = filePath;
return store;
} else {
await fs.mkdir(new URL('./', filePath), { recursive: true });
}
} catch {}
return new MutableDataStore();
const store = new MutableDataStore();
store.#file = filePath;
return store;
}
}

View file

@ -68,7 +68,8 @@ export default async function build(
const settings = await createSettings(astroConfig, fileURLToPath(astroConfig.root));
if (inlineConfig.force) {
await clearContentLayerCache({ settings, logger, fs });
// isDev is always false, because it's interested in the build command, not the output type
await clearContentLayerCache({ settings, logger, fs, isDev: false });
}
const builder = new AstroBuilder(settings, {
@ -154,6 +155,7 @@ class AstroBuilder {
logger,
fs,
manifest: this.manifest,
command: 'build',
});
return { viteConfig };

View file

@ -119,6 +119,7 @@ export async function createContainer({
},
force: inlineConfig?.force,
manifest,
command: 'dev',
});
const viteServer = await vite.createServer(viteConfig);

View file

@ -1,4 +1,4 @@
import fs, { existsSync } from 'node:fs';
import fs from 'node:fs';
import type http from 'node:http';
import type { AddressInfo } from 'node:net';
import { performance } from 'node:perf_hooks';
@ -87,22 +87,22 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
let store: MutableDataStore | undefined;
try {
const dataStoreFile = getDataStoreFile(restart.container.settings, true);
if (existsSync(dataStoreFile)) {
store = await MutableDataStore.fromFile(dataStoreFile);
}
} catch (err: any) {
logger.error('content', err.message);
}
if(!store) {
store = new MutableDataStore();
logger.error('content', 'Failed to create data store');
}
await attachContentServerListeners(restart.container);
const config = globalContentConfigObserver.get();
if (config.status === 'error') {
logger.error('content', config.error.message);
}
if (config.status === 'loaded') {
if (config.status === 'loaded' && store) {
const contentLayer = globalContentLayer.init({
settings: restart.container.settings,
logger,

View file

@ -1,4 +1,4 @@
import fsMod, { existsSync } from 'node:fs';
import fsMod from 'node:fs';
import { dirname, relative } from 'node:path';
import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
@ -49,6 +49,7 @@ export type SyncOptions = {
cleanup?: boolean;
};
manifest: ManifestData;
command: "build" | "dev" | "sync";
};
export default async function sync(
@ -78,6 +79,7 @@ export default async function sync(
fs,
force: inlineConfig.force,
manifest,
command: 'sync',
});
}
@ -88,12 +90,14 @@ export async function clearContentLayerCache({
settings,
logger,
fs = fsMod,
isDev,
}: {
settings: AstroSettings;
logger: Logger;
fs?: typeof fsMod;
isDev: boolean;
}) {
const dataStore = getDataStoreFile(settings);
const dataStore = getDataStoreFile(settings, isDev);
if (fs.existsSync(dataStore)) {
logger.debug('content', 'clearing data store');
await fs.promises.rm(dataStore, { force: true });
@ -115,9 +119,11 @@ export async function syncInternal({
skip,
force,
manifest,
command,
}: SyncOptions): Promise<void> {
const isDev = command === 'dev';
if (force) {
await clearContentLayerCache({ settings, logger, fs });
await clearContentLayerCache({ settings, logger, fs, isDev });
}
const timerStart = performance.now();
@ -127,15 +133,14 @@ export async function syncInternal({
settings.timer.start('Sync content layer');
let store: MutableDataStore | undefined;
try {
const dataStoreFile = getDataStoreFile(settings);
if (existsSync(dataStoreFile)) {
const dataStoreFile = getDataStoreFile(settings, isDev);
store = await MutableDataStore.fromFile(dataStoreFile);
}
} catch (err: any) {
logger.error('content', err.message);
}
if (!store) {
store = new MutableDataStore();
logger.error('content', 'Failed to load content store');
return;
}
const contentLayer = globalContentLayer.init({
settings,

View file

@ -2,15 +2,25 @@ import assert from 'node:assert/strict';
import { after, afterEach, before, describe, it } from 'node:test';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
import { existsSync, promises as fs } from 'node:fs';
describe('--mode', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;
let devDataStoreFile;
let prodDataStoreFile;
async function deleteDataStoreFiles() {
await fs.unlink(devDataStoreFile).catch(() => {});
await fs.unlink(prodDataStoreFile).catch(() => {});
}
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-mode/',
});
devDataStoreFile = new URL('./.astro/data-store.json', fixture.config.root);
prodDataStoreFile = new URL('./node_modules/.astro/data-store.json', fixture.config.root);
});
afterEach(() => {
@ -25,6 +35,7 @@ describe('--mode', () => {
describe('build', () => {
before(async () => {
await deleteDataStoreFiles();
await fixture.build();
});
@ -37,10 +48,15 @@ describe('--mode', () => {
assert.equal($('#env-title').text(), 'production');
assert.equal($('#env-astro-title').text(), 'production');
});
it('writes data store file in the correct location', async () => {
assert.ok(existsSync(prodDataStoreFile));
})
});
describe('build --mode testing --devOutput', () => {
before(async () => {
await deleteDataStoreFiles();
await fixture.build({ mode: 'testing' }, { devOutput: true });
});
@ -52,11 +68,17 @@ describe('--mode', () => {
assert.equal($('#env-prod').text(), 'false');
assert.equal($('#env-title').text(), '');
assert.equal($('#env-astro-title').text(), 'unset');
assert.ok
});
it('writes data store file in the correct location', async () => {
assert.ok(existsSync(prodDataStoreFile));
})
});
describe('build --mode staging', () => {
before(async () => {
await deleteDataStoreFiles();
await fixture.build({ mode: 'staging' });
});
@ -69,12 +91,18 @@ describe('--mode', () => {
assert.equal($('#env-title').text(), 'staging');
assert.equal($('#env-astro-title').text(), 'staging');
});
it('writes data store file in the correct location', async () => {
assert.ok(existsSync(prodDataStoreFile));
})
});
describe('dev', () => {
/** @type {import('./test-utils.js').DevServer} */
let devServer;
before(async () => {
await deleteDataStoreFiles();
devServer = await fixture.startDevServer();
});
after(async () => {
@ -92,12 +120,17 @@ describe('--mode', () => {
assert.equal($('#env-title').text(), 'development');
assert.equal($('#env-astro-title').text(), 'development');
});
it('writes data store file in the correct location', async () => {
assert.ok(existsSync(devDataStoreFile));
})
});
describe('dev --mode develop', () => {
/** @type {import('./test-utils.js').DevServer} */
let devServer;
before(async () => {
await deleteDataStoreFiles();
devServer = await fixture.startDevServer({ mode: 'develop' });
});
after(async () => {
@ -115,5 +148,10 @@ describe('--mode', () => {
assert.equal($('#env-title').text(), '');
assert.equal($('#env-astro-title').text(), 'unset');
});
it('writes data store file in the correct location', async () => {
assert.ok(existsSync(devDataStoreFile));
})
});
});

View file

@ -1,7 +1,7 @@
---
import { getEntry, render } from "astro:content"
// Skipping the broken page in production so the build doesn't fail
if(import.meta.env.PROD) {
if(import.meta.env.PROD || import.meta.env.MODE === "production") {
return new Response(null, { status: 404 })
}