Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions bin/run.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import {execute} from '@oclif/core'
import {CLI_TELEMETRY_SYMBOL} from '@sanity/cli-core'

const err = '\u001B[31m\u001B[1mERROR:\u001B[22m\u001B[39m '
const nodeVersionParts = process.version.replace(/^v/i, '').split('.').map(Number)
Expand All @@ -21,11 +22,30 @@ function isSupportedNodeVersion(major, minor, patch) {
}

if (!isSupportedNodeVersion(majorVersion, minorVersion, patchVersion)) {
// eslint-disable-next-line no-console
console.error(
`${err}Node.js version >=20.19.1 <22 or >=22.12 required. You are running ${process.version}`,
)
// eslint-disable-next-line no-console
console.error('')
process.exit(1)
}

if (process.env.NODE_ENV !== 'production') {
/**
* Telemetry is added via a plugin in the main CLI.
* This adds a mock implementation of the telemetry API to allow running this CLI for testing.
*
* We won't be exposing this API to the public, so it's ok to use a globalThis assignment.
*/
globalThis[CLI_TELEMETRY_SYMBOL] = {
trace: () => ({
complete: () => {},
error: () => {},
log: () => {},
start: () => {},
}),
}
}

await execute({dir: import.meta.url})
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@
"@babel/types": "^7.28.6",
"@oclif/core": "^4.8.0",
"@oclif/plugin-help": "^6.2.36",
"@sanity/cli-core": "^0.1.0-alpha.8",
"@sanity/cli-core": "^0.1.0-alpha.10",
"@sanity/worker-channels": "^1.1.0",
"chokidar": "^3.6.0",
"debug": "^4.4.3",
"globby": "^11.1.0",
"groq": "^5.2.0",
Expand All @@ -77,16 +78,15 @@
"prettier": "^3.7.4",
"reselect": "^5.1.1",
"tsconfig-paths": "^4.2.0",
"zod": "^4.3.6",
"chokidar": "^3.6.0"
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/compat": "^2.0.1",
"@sanity/telemetry": "^0.8.0",
"@microsoft/api-extractor": "^7.55.2",
"@oclif/test": "^4.1.15",
"@sanity/cli-test": "^0.0.2-alpha.7",
"@sanity/eslint-config-cli": "0.0.0-alpha.1",
"@sanity/cli-test": "^0.0.2-alpha.9",
"@sanity/eslint-config-cli": "0.0.0-alpha.2",
"@sanity/telemetry": "^0.8.0",
"@swc/cli": "^0.7.9",
"@swc/core": "^1.15.8",
"@types/babel__core": "^7.20.5",
Expand Down
535 changes: 305 additions & 230 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/actions/typegenWatch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {error, log} from 'node:console'
import {isAbsolute, join, relative} from 'node:path'
import {styleText} from 'node:util'

import {chalk} from '@sanity/cli-core/ux'
import chokidar, {FSWatcher} from 'chokidar'
import {debounce, mean} from 'lodash-es'

Expand All @@ -19,7 +19,7 @@ const IGNORED_PATTERNS = [
]

/** State for tracking generation status */
export interface WatchState {
interface WatchState {
isGenerating: boolean
pendingGeneration: boolean
}
Expand All @@ -37,7 +37,7 @@ type WatcherStats = Omit<Extract<TypegenWatchModeTraceAttributes, {step: 'stoppe
* If generation is already running, queues one more generation to run after completion.
* Multiple queued requests are coalesced into a single pending generation.
*/
export function createTypegenRunner(onGenerate: () => Promise<unknown>): TypegenRunner {
function createTypegenRunner(onGenerate: () => Promise<unknown>): TypegenRunner {
const state: WatchState = {
isGenerating: false,
pendingGeneration: false,
Expand Down Expand Up @@ -93,7 +93,7 @@ export function runTypegenWatcher(options: RunTypegenOptions): {
stats.successfulDurations.push(duration)
} catch (err) {
const errorMessage = err instanceof Error ? err.message : err
error(` ${chalk.red('›')} ${errorMessage}`)
error(` ${styleText('red', '›')} ${errorMessage}`)
stats.failedCount++
}
})
Expand Down
4 changes: 0 additions & 4 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {WorkerChannel} from '@sanity/worker-channels'

import {TypeGenConfig} from '../readConfig.js'
import {type TypegenWorkerChannel as CodegenTypegenWorkerChannel} from '../typescript/typeGenerator.js'
import {telemetry} from '../utils/telemetryLogger.js'

/**
* Data passed to the typegen worker thread.
Expand Down Expand Up @@ -47,9 +46,6 @@ export interface RunTypegenOptions {

/** Optional spinner instance for progress display */
spin?: ReturnType<typeof spinner>

/** Optional telemetry instance for tracking usage */
telemetry?: typeof telemetry
}

/**
Expand Down
53 changes: 28 additions & 25 deletions src/commands/typegen/__tests__/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {readFile, writeFile} from 'node:fs/promises'
import {join} from 'node:path'

import {runCommand} from '@oclif/test'
import {testCommand, testExample} from '@sanity/cli-test'
import {testCommand, testFixture} from '@sanity/cli-test'
import {once} from 'lodash-es'
import {beforeEach, describe, expect, test, vi} from 'vitest'

Expand All @@ -20,15 +20,6 @@ const mockTrace = {
start: vi.fn(),
}

vi.mock('../../../utils/telemetryLogger.js', () => ({
telemetry: {
trace: vi.fn(() => mockTrace),
},
}))

// Import mock after vi.mock to access it in tests
const {telemetry} = await import('../../../utils/telemetryLogger.js')

describe('#typegen:generate', () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -95,7 +86,7 @@ describe('#typegen:generate', () => {
})

test('should error when no extracted schema is found', async () => {
const cwd = await testExample('basic-studio')
const cwd = await testFixture('basic-studio')
process.chdir(cwd)

const {error} = await testCommand(TypegenGenerateCommand, [])
Expand All @@ -107,7 +98,7 @@ describe('#typegen:generate', () => {
})

test('should generate types from queries', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

const {error, stderr} = await testCommand(TypegenGenerateCommand, [])
Expand All @@ -129,7 +120,7 @@ describe('#typegen:generate', () => {
})

test('should generate types when schema is an absolute path', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

// Create config with absolute schema path
Expand All @@ -155,7 +146,7 @@ describe('#typegen:generate', () => {
})

test('does not format generated types when formatGeneratedCode is false', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

await writeFile(
Expand All @@ -178,15 +169,21 @@ describe('#typegen:generate', () => {
})

test('emits TypesGeneratedTrace telemetry on successful generation', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

const {error} = await testCommand(TypegenGenerateCommand, [])
const mockTelemetry = vi.fn(() => mockTrace)

const {error} = await testCommand(TypegenGenerateCommand, [], {
mocks: {
trace: mockTelemetry,
},
})

expect(error).toBeUndefined()

// Verify telemetry.trace was called with TypesGeneratedTrace
expect(telemetry.trace).toHaveBeenCalledWith(TypesGeneratedTrace)
expect(mockTelemetry).toHaveBeenCalledWith(TypesGeneratedTrace)

// Verify the trace lifecycle methods were called in order
expect(mockTrace.start).toHaveBeenCalled()
Expand All @@ -205,23 +202,29 @@ describe('#typegen:generate', () => {
})

test('emits TypesGeneratedTrace error on failed generation', async () => {
const cwd = await testExample('basic-studio')
const cwd = await testFixture('basic-studio')
process.chdir(cwd)

const {error} = await testCommand(TypegenGenerateCommand, [])
const mockTelemetry = vi.fn(() => mockTrace)

const {error} = await testCommand(TypegenGenerateCommand, [], {
mocks: {
trace: mockTelemetry,
},
})

expect(error).toBeDefined()

// Verify telemetry.trace was called with TypesGeneratedTrace
expect(telemetry.trace).toHaveBeenCalledWith(TypesGeneratedTrace)
expect(mockTelemetry).toHaveBeenCalledWith(TypesGeneratedTrace)

// Verify error was logged
expect(mockTrace.error).toHaveBeenCalledWith(expect.any(Error))
expect(mockTrace.complete).not.toHaveBeenCalled()
})

test('shows warning when legacy config and cli config are present', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

await writeFile(
Expand Down Expand Up @@ -255,7 +258,7 @@ describe('#typegen:generate', () => {
})

test('shows warning when only legacy config is present', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

await writeFile(
Expand All @@ -279,7 +282,7 @@ describe('#typegen:generate', () => {
})

test('shows an error when the legacy config file passed as a flag does not exist', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

const {error} = await testCommand(TypegenGenerateCommand, ['--config-path', 'typegen.json'])
Expand All @@ -291,7 +294,7 @@ describe('#typegen:generate', () => {

describe('watch mode', () => {
test('generates on startup', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

await testLongRunning(['typegen', 'generate', '--watch'], {
Expand All @@ -305,7 +308,7 @@ describe('#typegen:generate', () => {
})

test('generates when a file is created', async () => {
const cwd = await testExample('dev')
const cwd = await testFixture('dev')
process.chdir(cwd)

const randomFilename = `${Math.random().toFixed(18)}file.ts`
Expand Down
18 changes: 10 additions & 8 deletions src/commands/typegen/generate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {stat} from 'node:fs/promises'
import {styleText} from 'node:util'

import {Flags} from '@oclif/core'
import {SanityCommand} from '@sanity/cli-core'
import {chalk, spinner} from '@sanity/cli-core/ux'
import {spinner} from '@sanity/cli-core/ux'
import {omit, once} from 'lodash-es'

import {runTypegenGenerate} from '../../actions/typegenGenerate.js'
Expand All @@ -11,12 +12,11 @@ import {configDefinition, readConfig, type TypeGenConfig} from '../../readConfig
import {TypegenWatchModeTrace, TypesGeneratedTrace} from '../../typegen.telemetry.js'
import {debug} from '../../utils/debug.js'
import {promiseWithResolvers} from '../../utils/promiseWithResolvers.js'
import {telemetry} from '../../utils/telemetryLogger.js'

const description = `Sanity TypeGen (Beta)
This command is currently in beta and may undergo significant changes. Feedback is welcome!

${chalk.bold('Configuration:')}
${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.
Expand All @@ -30,7 +30,7 @@ This command can utilize configuration settings defined in a \`sanity-typegen.js

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.

${chalk.bold('Note:')}
${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.
- While this tool is in beta, we encourage you to experiment with these configurations and provide feedback to help improve its functionality and usability.`.trim()

Expand Down Expand Up @@ -109,7 +109,8 @@ export class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerate
// we have both legacy and cli config with typegen
if (config?.typegen && hasLegacyConfig) {
spin.warn(
chalk.yellow(
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.
Expand All @@ -128,7 +129,8 @@ export class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerate
// we only have legacy typegen config
if (hasLegacyConfig) {
spin.warn(
chalk.yellow(
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`,
Expand Down Expand Up @@ -158,7 +160,7 @@ export class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerate
}

private async runSingle() {
const trace = telemetry.trace(TypesGeneratedTrace)
const trace = this.telemetry.trace(TypesGeneratedTrace)

try {
const {config: typegenConfig, type: typegenConfigMethod, workDir} = await this.getConfig()
Expand Down Expand Up @@ -187,7 +189,7 @@ export class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerate
}

private async runWatcher() {
const trace = telemetry.trace(TypegenWatchModeTrace)
const trace = this.telemetry.trace(TypegenWatchModeTrace)

try {
const {config: typegenConfig, workDir} = await this.getConfig()
Expand Down
17 changes: 0 additions & 17 deletions src/utils/telemetryLogger.ts

This file was deleted.

2 changes: 1 addition & 1 deletion test/cliTestSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {type TestProject} from 'vitest/node'

export function setup(project: TestProject) {
return cliSetup(project, {
additionalExamples: ['dev'],
additionalFixtures: ['dev'],
})
}

Expand Down