Skip to content

Commit

Permalink
[Content] Add astro sync type gen command (#5647)
Browse files Browse the repository at this point in the history
* feat: add `astro sync` command

* chore: move fixture types.generated to gitignore

* test: types generate with astro:content

* chore: changeset

* docs: Astro error for CLI errors
  • Loading branch information
bholmesdev committed Dec 27, 2022
1 parent 68c20be commit d72da52
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 133 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-suns-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Add `astro sync` CLI command for type generation
10 changes: 10 additions & 0 deletions packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type CLICommand =
| 'build'
| 'preview'
| 'reload'
| 'sync'
| 'check'
| 'telemetry';

Expand All @@ -48,6 +49,7 @@ function printAstroHelp() {
['dev', 'Start the development server.'],
['docs', 'Open documentation in your web browser.'],
['preview', 'Preview your build locally.'],
['sync', 'Generate content collection types.'],
['telemetry', 'Configure telemetry settings.'],
],
'Global Flags': [
Expand All @@ -74,6 +76,7 @@ async function printVersion() {
function resolveCommand(flags: Arguments): CLICommand {
const cmd = flags._[2] as string;
if (cmd === 'add') return 'add';
if (cmd === 'sync') return 'sync';
if (cmd === 'telemetry') return 'telemetry';
if (flags.version) return 'version';
else if (flags.help) return 'help';
Expand Down Expand Up @@ -202,6 +205,13 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
return process.exit(ret);
}

case 'sync': {
const { sync } = await import('./sync/index.js');

const ret = await sync(settings, { logging, fs });
return process.exit(ret);
}

case 'preview': {
const { default: preview } = await import('../core/preview/index.js');

Expand Down
31 changes: 31 additions & 0 deletions packages/astro/src/cli/sync/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type fsMod from 'node:fs';
import { performance } from 'node:perf_hooks';
import { dim } from 'kleur/colors';
import type { AstroSettings } from '../../@types/astro';
import { info, LogOptions } from '../../core/logger/core.js';
import { contentObservable, createContentTypesGenerator } from '../../content/index.js';
import { getTimeStat } from '../../core/build/util.js';
import { AstroError, AstroErrorData } from '../../core/errors/index.js';

export async function sync(
settings: AstroSettings,
{ logging, fs }: { logging: LogOptions; fs: typeof fsMod }
): Promise<0 | 1> {
const timerStart = performance.now();

try {
const contentTypesGenerator = await createContentTypesGenerator({
contentConfigObserver: contentObservable({ status: 'loading' }),
logging,
fs,
settings,
});
await contentTypesGenerator.init();
} catch (e) {
throw new AstroError(AstroErrorData.GenerateContentTypesError);
}

info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);

return 0;
}
2 changes: 2 additions & 0 deletions packages/astro/src/content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export {
} from './vite-plugin-content-assets.js';
export { astroContentServerPlugin } from './vite-plugin-content-server.js';
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
export { contentObservable } from './utils.js';
export { createContentTypesGenerator } from './types-generator.js';
15 changes: 10 additions & 5 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import glob from 'fast-glob';
import { cyan } from 'kleur/colors';
import fsMod from 'node:fs';
import type fsMod from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { info, LogOptions, warn } from '../core/logger/core.js';
import { appendForwardSlash, isRelativePath } from '../core/path.js';
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
import { ContentConfig, ContentObservable, ContentPaths, loadContentConfig } from './utils.js';
import {
ContentConfig,
ContentObservable,
ContentPaths,
getContentPaths,
loadContentConfig,
} from './utils.js';

type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
type RawContentEvent = { name: ChokidarEvent; entry: string };
Expand All @@ -28,7 +34,6 @@ type ContentTypesEntryMetadata = { slug: string };
type ContentTypes = Record<string, Record<string, ContentTypesEntryMetadata>>;

type CreateContentGeneratorParams = {
contentPaths: ContentPaths;
contentConfigObserver: ContentObservable;
logging: LogOptions;
settings: AstroSettings;
Expand All @@ -40,18 +45,18 @@ type EventOpts = { logLevel: 'info' | 'warn' };
class UnsupportedFileTypeError extends Error {}

export async function createContentTypesGenerator({
contentPaths,
contentConfigObserver,
fs,
logging,
settings,
}: CreateContentGeneratorParams): Promise<GenerateContentTypes> {
const contentTypes: ContentTypes = {};
const contentPaths: ContentPaths = getContentPaths({ srcDir: settings.config.srcDir });

let events: Promise<{ shouldGenerateTypes: boolean; error?: Error }>[] = [];
let debounceTimeout: NodeJS.Timeout | undefined;

const contentTypesBase = await fsMod.promises.readFile(
const contentTypesBase = await fs.promises.readFile(
new URL(CONTENT_TYPES_FILE, contentPaths.generatedInputDir),
'utf-8'
);
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/content/vite-plugin-content-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export function astroContentServerPlugin({
settings,
logging,
contentConfigObserver,
contentPaths,
});
await contentGenerator.init();
info(logging, 'content', 'Types generated');
Expand Down
24 changes: 24 additions & 0 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,30 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
message: (legacyConfigKey: string) => `Legacy configuration detected: \`${legacyConfigKey}\`.`,
hint: 'Please update your configuration to the new format.\nSee https://astro.build/config for more information.',
},
/**
* @docs
* @kind heading
* @name CLI Errors
*/
// CLI Errors - 8xxx
UnknownCLIError: {
title: 'Unknown CLI Error.',
code: 8000,
},
/**
* @docs
* @description
* `astro sync` command failed to generate content collection types.
* @see
* - [Content collections documentation](https://docs.astro.build/en/guides/content-collections/)
*/
GenerateContentTypesError: {
title: 'Failed to generate content types.',
code: 8001,
message: '`astro sync` command failed to generate content collection types.',
hint: 'Check your `src/content/config.*` file for typos.',
},

// Generic catch-all
UnknownError: {
title: 'Unknown Error.',
Expand Down
35 changes: 33 additions & 2 deletions packages/astro/test/content-collections.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import * as fs from 'node:fs';
import * as devalue from 'devalue';
import * as cheerio from 'cheerio';
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
import * as devalue from 'devalue';
import * as cheerio from 'cheerio';

describe('Content Collections', () => {
describe('Type generation', () => {
let fixture;
before(async () => {
fixture = await loadFixture({ root: './fixtures/content-collections/' });
});

it('Writes types to `src/content/`', async () => {
let writtenFiles = {};
const fsMock = {
...fs,
promises: {
...fs.promises,
async writeFile(path, contents) {
writtenFiles[path] = contents;
},
},
};
const expectedTypesFile = new URL('./content/types.generated.d.ts', fixture.config.srcDir)
.href;
await fixture.sync({ fs: fsMock });
expect(Object.keys(writtenFiles)).to.have.lengthOf(1);
expect(writtenFiles).to.haveOwnProperty(expectedTypesFile);
// smoke test `astro check` asserts whether content types pass.
expect(writtenFiles[expectedTypesFile]).to.include(
`declare module 'astro:content' {`,
'Types file does not include `astro:content` module declaration'
);
});
});

describe('Query', () => {
let fixture;
before(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
types.generated.d.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createSettings } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js';
import build from '../dist/core/build/index.js';
import preview from '../dist/core/preview/index.js';
import { sync } from '../dist/cli/sync/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js';
import os from 'os';
import stripAnsi from 'strip-ansi';
Expand Down Expand Up @@ -139,6 +140,7 @@ export async function loadFixture(inlineConfig) {

return {
build: (opts = {}) => build(settings, { logging, telemetry, ...opts }),
sync: (opts) => sync(settings, { logging, fs, ...opts }),
startDevServer: async (opts = {}) => {
devServer = await dev(settings, { logging, telemetry, ...opts });
config.server.host = parseAddressToHost(devServer.address.address); // update host
Expand Down

0 comments on commit d72da52

Please sign in to comment.