@sanity/codegen
Version:
Codegen toolkit for Sanity.io
184 lines (175 loc) • 7.75 kB
JavaScript
import { stat } from 'node:fs/promises';
import { styleText } from 'node:util';
import { Flags } from '@oclif/core';
import { SanityCommand } from '@sanity/cli-core';
import { spinner } from '@sanity/cli-core/ux';
import { omit, once } from 'lodash-es';
import { runTypegenGenerate } from '../../actions/typegenGenerate.js';
import { runTypegenWatcher } from '../../actions/typegenWatch.js';
import { configDefinition, readConfig } from '../../readConfig.js';
import { TypegenWatchModeTrace, TypesGeneratedTrace } from '../../typegen.telemetry.js';
import { debug } from '../../utils/debug.js';
import { promiseWithResolvers } from '../../utils/promiseWithResolvers.js';
const description = `Sanity TypeGen
${styleText('bold', 'Configuration:')}
This command can utilize configuration settings defined in a \`sanity-typegen.json\` file. These settings include:
- "path": Specifies a glob pattern to locate your TypeScript or JavaScript files.
Default: "./src/**/*.{ts,tsx,js,jsx}"
- "schema": Defines the path to your Sanity schema file. This file should be generated using the \`sanity schema extract\` command.
Default: "schema.json"
- "generates": Indicates the path where the generated TypeScript type definitions will be saved.
Default: "./sanity.types.ts"
The default configuration values listed above are used if not overridden in your \`sanity-typegen.json\` configuration file. To customize the behavior of the type generation, adjust these properties in the configuration file according to your project's needs.
${styleText('bold', 'Note:')}
- The \`sanity schema extract\` command is a prerequisite for extracting your Sanity Studio schema into a \`schema.json\` file, which is then used by the \`sanity typegen generate\` command to generate type definitions.`.trim();
/**
* @internal
*/ export class TypegenGenerateCommand extends SanityCommand {
static description = description;
static examples = [
{
command: '<%= config.bin %> <%= command.id %>',
description: `Generate TypeScript type definitions from a Sanity Studio schema extracted using the \`sanity schema extract\` command.`
}
];
static flags = {
'config-path': Flags.string({
description: '[Default: sanity-typegen.json] Specifies the path to the typegen configuration file. This file should be a JSON file that contains settings for the type generation process.'
}),
watch: Flags.boolean({
default: false,
description: '[Default: false] Run the typegen in watch mode'
})
};
async run() {
const { flags } = await this.parse(TypegenGenerateCommand);
if (flags.watch) {
await this.runWatcher();
return;
}
await this.runSingle();
}
async getConfig() {
const spin = spinner({}).start('Loading config…');
try {
const { flags } = await this.parse(TypegenGenerateCommand);
const rootDir = await this.getProjectRoot();
const config = await this.getCliConfig();
const configPath = flags['config-path'];
const workDir = rootDir.directory;
// check if the legacy config exist
const legacyConfigPath = configPath || 'sanity-typegen.json';
let hasLegacyConfig = false;
try {
const file = await stat(legacyConfigPath);
hasLegacyConfig = file.isFile();
} catch (err) {
if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {
spin.fail();
this.error(`Typegen config file not found: ${configPath}`, {
exit: 1
});
}
if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {
spin.fail();
this.error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {
exit: 1
});
}
}
// we have both legacy and cli config with typegen
if (config?.typegen && hasLegacyConfig) {
spin.warn(styleText('yellow', `You've specified typegen in your Sanity CLI config, but also have a typegen config.
The config from the Sanity CLI config is used.
`));
return {
config: configDefinition.parse(config.typegen || {}),
path: rootDir.path,
type: 'cli',
workDir
};
}
// we only have legacy typegen config
if (hasLegacyConfig) {
spin.warn(styleText('yellow', `The separate typegen config has been deprecated. Use \`typegen\` in the sanity CLI config instead.
See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`));
return {
config: await readConfig(legacyConfigPath),
path: legacyConfigPath,
type: 'legacy',
workDir
};
}
spin.succeed(`Config loaded from sanity.cli.ts`);
// we only have cli config
return {
config: configDefinition.parse(config.typegen || {}),
path: rootDir.path,
type: 'cli',
workDir
};
} catch (err) {
spin.fail();
this.error(`An error occured during config loading ${err}`, {
exit: 1
});
}
}
async runSingle() {
const trace = this.telemetry.trace(TypesGeneratedTrace);
try {
const { config: typegenConfig, type: typegenConfigMethod, workDir } = await this.getConfig();
trace.start();
const result = await runTypegenGenerate({
config: typegenConfig,
workDir
});
const traceStats = omit(result, 'code', 'duration');
trace.log({
configMethod: typegenConfigMethod,
configOverloadClientMethods: typegenConfig.overloadClientMethods,
...traceStats
});
trace.complete();
} catch (error) {
debug(error);
trace.error(error);
this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {
exit: 1
});
}
}
async runWatcher() {
const trace = this.telemetry.trace(TypegenWatchModeTrace);
try {
const { config: typegenConfig, workDir } = await this.getConfig();
trace.start();
const { promise, resolve } = promiseWithResolvers();
const typegenWatcher = runTypegenWatcher({
config: typegenConfig,
workDir
});
const stop = once(async ()=>{
process.off('SIGINT', stop);
process.off('SIGTERM', stop);
trace.log({
step: 'stopped',
...typegenWatcher.getStats()
});
trace.complete();
await typegenWatcher.stop();
resolve();
});
process.on('SIGINT', stop);
process.on('SIGTERM', stop);
await promise;
} catch (error) {
debug(error);
trace.error(error);
this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {
exit: 1
});
}
}
}
//# sourceMappingURL=generate.js.map