@sanity/codegen 5.7.2 → 5.9.0-watch-mode.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exports/index.js +3 -0
- package/dist/_exports/index.js.map +1 -1
- package/dist/actions/streamProcessor.js +108 -0
- package/dist/actions/streamProcessor.js.map +1 -0
- package/dist/actions/typegenGenerate.js +55 -0
- package/dist/actions/typegenGenerate.js.map +1 -0
- package/dist/actions/typegenWatch.js +114 -0
- package/dist/actions/typegenWatch.js.map +1 -0
- package/dist/actions/types.js +4 -1
- package/dist/actions/types.js.map +1 -1
- package/dist/commands/typegen/generate.js +126 -179
- package/dist/commands/typegen/generate.js.map +1 -1
- package/dist/index.d.ts +106 -0
- package/dist/typegen.telemetry.js +13 -0
- package/dist/typegen.telemetry.js.map +1 -0
- package/dist/utils/config.js +11 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/debug.js +4 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/promiseWithResolvers.js +16 -0
- package/dist/utils/promiseWithResolvers.js.map +1 -0
- package/dist/utils/telemetryLogger.js +17 -0
- package/dist/utils/telemetryLogger.js.map +1 -0
- package/dist/utils/test/testLongRunning.js +67 -0
- package/dist/utils/test/testLongRunning.js.map +1 -0
- package/oclif.manifest.json +8 -2
- package/package.json +9 -5
package/dist/_exports/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export { runTypegenGenerate } from '../actions/typegenGenerate.js';
|
|
2
|
+
export { runTypegenWatcher } from '../actions/typegenWatch.js';
|
|
1
3
|
export { TypegenGenerateCommand } from '../commands/typegen/generate.js';
|
|
2
4
|
export { configDefinition, readConfig } from '../readConfig.js';
|
|
3
5
|
export { readSchema } from '../readSchema.js';
|
|
4
6
|
export { safeParseQuery } from '../safeParseQuery.js';
|
|
7
|
+
export { TypegenWatchModeTrace, TypesGeneratedTrace } from '../typegen.telemetry.js';
|
|
5
8
|
export { findQueriesInPath } from '../typescript/findQueriesInPath.js';
|
|
6
9
|
export { findQueriesInSource } from '../typescript/findQueriesInSource.js';
|
|
7
10
|
export { getResolver } from '../typescript/moduleResolver.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/_exports/index.ts"],"sourcesContent":["export {TypegenGenerateCommand} from '../commands/typegen/generate.js'\nexport {\n type CodegenConfig,\n configDefinition,\n readConfig,\n type TypeGenConfig,\n} from '../readConfig.js'\nexport {readSchema} from '../readSchema.js'\nexport {safeParseQuery} from '../safeParseQuery.js'\nexport {findQueriesInPath} from '../typescript/findQueriesInPath.js'\nexport {findQueriesInSource} from '../typescript/findQueriesInSource.js'\nexport {getResolver} from '../typescript/moduleResolver.js'\nexport {registerBabel} from '../typescript/registerBabel.js'\nexport {\n type GenerateTypesOptions,\n TypeGenerator,\n type TypegenWorkerChannel,\n} from '../typescript/typeGenerator.js'\nexport {\n type EvaluatedModule,\n type EvaluatedQuery,\n type ExtractedModule,\n type ExtractedQuery,\n QueryExtractionError,\n} from '../typescript/types.js'\nexport {type FilterByType, type Get} from '../typeUtils.js'\n"],"names":["TypegenGenerateCommand","configDefinition","readConfig","readSchema","safeParseQuery","findQueriesInPath","findQueriesInSource","getResolver","registerBabel","TypeGenerator","QueryExtractionError"],"mappings":"AAAA,SAAQA,sBAAsB,QAAO,kCAAiC;AACtE,SAEEC,gBAAgB,EAChBC,UAAU,QAEL,mBAAkB;AACzB,SAAQC,UAAU,QAAO,mBAAkB;AAC3C,SAAQC,cAAc,QAAO,uBAAsB;AACnD,SAAQC,iBAAiB,QAAO,qCAAoC;AACpE,SAAQC,mBAAmB,QAAO,uCAAsC;AACxE,SAAQC,WAAW,QAAO,kCAAiC;AAC3D,SAAQC,aAAa,QAAO,iCAAgC;AAC5D,SAEEC,aAAa,QAER,iCAAgC;AACvC,SAKEC,oBAAoB,QACf,yBAAwB"}
|
|
1
|
+
{"version":3,"sources":["../../src/_exports/index.ts"],"sourcesContent":["export {runTypegenGenerate} from '../actions/typegenGenerate.js'\nexport {runTypegenWatcher} from '../actions/typegenWatch.js'\nexport {type GenerationResult, type RunTypegenOptions} from '../actions/types.js'\nexport {TypegenGenerateCommand} from '../commands/typegen/generate.js'\nexport {\n type CodegenConfig,\n configDefinition,\n readConfig,\n type TypeGenConfig,\n} from '../readConfig.js'\nexport {readSchema} from '../readSchema.js'\nexport {safeParseQuery} from '../safeParseQuery.js'\nexport {TypegenWatchModeTrace, TypesGeneratedTrace} from '../typegen.telemetry.js'\nexport {findQueriesInPath} from '../typescript/findQueriesInPath.js'\nexport {findQueriesInSource} from '../typescript/findQueriesInSource.js'\nexport {getResolver} from '../typescript/moduleResolver.js'\nexport {registerBabel} from '../typescript/registerBabel.js'\nexport {\n type GenerateTypesOptions,\n TypeGenerator,\n type TypegenWorkerChannel,\n} from '../typescript/typeGenerator.js'\nexport {\n type EvaluatedModule,\n type EvaluatedQuery,\n type ExtractedModule,\n type ExtractedQuery,\n QueryExtractionError,\n} from '../typescript/types.js'\nexport {type FilterByType, type Get} from '../typeUtils.js'\n"],"names":["runTypegenGenerate","runTypegenWatcher","TypegenGenerateCommand","configDefinition","readConfig","readSchema","safeParseQuery","TypegenWatchModeTrace","TypesGeneratedTrace","findQueriesInPath","findQueriesInSource","getResolver","registerBabel","TypeGenerator","QueryExtractionError"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,gCAA+B;AAChE,SAAQC,iBAAiB,QAAO,6BAA4B;AAE5D,SAAQC,sBAAsB,QAAO,kCAAiC;AACtE,SAEEC,gBAAgB,EAChBC,UAAU,QAEL,mBAAkB;AACzB,SAAQC,UAAU,QAAO,mBAAkB;AAC3C,SAAQC,cAAc,QAAO,uBAAsB;AACnD,SAAQC,qBAAqB,EAAEC,mBAAmB,QAAO,0BAAyB;AAClF,SAAQC,iBAAiB,QAAO,qCAAoC;AACpE,SAAQC,mBAAmB,QAAO,uCAAsC;AACxE,SAAQC,WAAW,QAAO,kCAAiC;AAC3D,SAAQC,aAAa,QAAO,iCAAgC;AAC5D,SAEEC,aAAa,QAER,iCAAgC;AACvC,SAKEC,oBAAoB,QACf,yBAAwB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
3
|
+
import { format, resolveConfig as resolvePrettierConfig } from 'prettier';
|
|
4
|
+
import { count } from '../utils/count.js';
|
|
5
|
+
import { debug } from '../utils/debug.js';
|
|
6
|
+
import { formatPath } from '../utils/formatPath.js';
|
|
7
|
+
import { getMessage } from '../utils/getMessage.js';
|
|
8
|
+
import { percent } from '../utils/percent.js';
|
|
9
|
+
import { generatedFileWarning } from './generatedFileWarning.js';
|
|
10
|
+
/**
|
|
11
|
+
* Processes the event stream from a typegen worker thread.
|
|
12
|
+
*
|
|
13
|
+
* Listens to worker channel events, displays progress via CLI spinners,
|
|
14
|
+
* writes the generated types to disk, and optionally formats with prettier.
|
|
15
|
+
*
|
|
16
|
+
* @param receiver - Worker channel receiver for typegen events
|
|
17
|
+
* @param options - Typegen configuration options
|
|
18
|
+
* @returns Generation result containing the generated code and statistics
|
|
19
|
+
*/ export async function processTypegenWorkerStream(receiver, options) {
|
|
20
|
+
const start = Date.now();
|
|
21
|
+
const { formatGeneratedCode, generates, schema } = options;
|
|
22
|
+
let code = '';
|
|
23
|
+
const spin = spinner().start(`Loading schema…`);
|
|
24
|
+
try {
|
|
25
|
+
await receiver.event.loadedSchema();
|
|
26
|
+
spin.succeed(`Schema loaded from ${formatPath(schema ?? '')}`);
|
|
27
|
+
spin.start('Generating schema types…');
|
|
28
|
+
const { expectedFileCount } = await receiver.event.typegenStarted();
|
|
29
|
+
const { schemaTypeDeclarations } = await receiver.event.generatedSchemaTypes();
|
|
30
|
+
const schemaTypesCount = schemaTypeDeclarations.length;
|
|
31
|
+
spin.text = 'Generating query types…';
|
|
32
|
+
let queriesCount = 0;
|
|
33
|
+
let evaluatedFiles = 0;
|
|
34
|
+
let filesWithErrors = 0;
|
|
35
|
+
let queryFilesCount = 0;
|
|
36
|
+
let typeNodesGenerated = 0;
|
|
37
|
+
let unknownTypeNodesGenerated = 0;
|
|
38
|
+
let emptyUnionTypeNodesGenerated = 0;
|
|
39
|
+
for await (const { errors, queries } of receiver.stream.evaluatedModules()){
|
|
40
|
+
evaluatedFiles++;
|
|
41
|
+
queriesCount += queries.length;
|
|
42
|
+
queryFilesCount += queries.length > 0 ? 1 : 0;
|
|
43
|
+
filesWithErrors += errors.length > 0 ? 1 : 0;
|
|
44
|
+
for (const { stats } of queries){
|
|
45
|
+
typeNodesGenerated += stats.allTypes;
|
|
46
|
+
unknownTypeNodesGenerated += stats.unknownTypes;
|
|
47
|
+
emptyUnionTypeNodesGenerated += stats.emptyUnions;
|
|
48
|
+
}
|
|
49
|
+
for (const error of errors){
|
|
50
|
+
spin.fail(getMessage(error));
|
|
51
|
+
}
|
|
52
|
+
if (!spin.isSpinning) {
|
|
53
|
+
spin.start();
|
|
54
|
+
}
|
|
55
|
+
spin.text = `Generating query types… (${percent(evaluatedFiles / expectedFileCount)})\n` + ` └─ Processed ${count(evaluatedFiles)} of ${count(expectedFileCount, 'files')}. ` + `Found ${count(queriesCount, 'queries', 'query')} from ${count(queryFilesCount, 'files')}.`;
|
|
56
|
+
}
|
|
57
|
+
const result = await receiver.event.typegenComplete();
|
|
58
|
+
code = `${generatedFileWarning}${result.code}`;
|
|
59
|
+
await writeFile(generates, code);
|
|
60
|
+
let formattingError = false;
|
|
61
|
+
if (formatGeneratedCode) {
|
|
62
|
+
spin.text = `Formatting generated types with prettier…`;
|
|
63
|
+
try {
|
|
64
|
+
const prettierConfig = await resolvePrettierConfig(generates);
|
|
65
|
+
const formattedCode = await format(code, {
|
|
66
|
+
...prettierConfig,
|
|
67
|
+
parser: 'typescript'
|
|
68
|
+
});
|
|
69
|
+
await writeFile(generates, formattedCode);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
formattingError = true;
|
|
72
|
+
spin.warn(`Failed to format generated types with prettier: ${getMessage(err)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (filesWithErrors > 0) {
|
|
76
|
+
spin.warn(`Encountered errors in ${count(filesWithErrors, 'files')} while generating types`);
|
|
77
|
+
}
|
|
78
|
+
const stats = {
|
|
79
|
+
duration: Date.now() - start,
|
|
80
|
+
emptyUnionTypeNodesGenerated,
|
|
81
|
+
filesWithErrors,
|
|
82
|
+
outputSize: Buffer.byteLength(code),
|
|
83
|
+
queriesCount,
|
|
84
|
+
queryFilesCount,
|
|
85
|
+
schemaTypesCount,
|
|
86
|
+
typeNodesGenerated,
|
|
87
|
+
unknownTypeNodesGenerated,
|
|
88
|
+
unknownTypeNodesRatio: typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0
|
|
89
|
+
};
|
|
90
|
+
let successText = `Successfully generated types to ${formatPath(generates)} in ${Number(stats.duration).toFixed(0)}ms` + `\n └─ ${count(queriesCount, 'queries', 'query')} and ${count(schemaTypesCount, 'schema types', 'schema type')}` + `\n └─ found queries in ${count(queryFilesCount, 'files', 'file')} after evaluating ${count(evaluatedFiles, 'files', 'file')}`;
|
|
91
|
+
if (formatGeneratedCode) {
|
|
92
|
+
successText += `\n └─ ${formattingError ? 'an error occured during formatting' : 'formatted the generated code with prettier'}`;
|
|
93
|
+
}
|
|
94
|
+
spin.succeed(successText);
|
|
95
|
+
return {
|
|
96
|
+
...stats,
|
|
97
|
+
code
|
|
98
|
+
};
|
|
99
|
+
} catch (err) {
|
|
100
|
+
spin.fail();
|
|
101
|
+
debug('error generating types', err);
|
|
102
|
+
throw err;
|
|
103
|
+
} finally{
|
|
104
|
+
receiver.unsubscribe();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=streamProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/actions/streamProcessor.ts"],"sourcesContent":["import {writeFile} from 'node:fs/promises'\n\nimport {spinner} from '@sanity/cli-core/ux'\nimport {WorkerChannelReceiver} from '@sanity/worker-channels'\nimport {format, resolveConfig as resolvePrettierConfig} from 'prettier'\n\nimport {TypeGenConfig} from '../readConfig.js'\nimport {count} from '../utils/count.js'\nimport {debug} from '../utils/debug.js'\nimport {formatPath} from '../utils/formatPath.js'\nimport {getMessage} from '../utils/getMessage.js'\nimport {percent} from '../utils/percent.js'\nimport {generatedFileWarning} from './generatedFileWarning.js'\nimport {TypegenWorkerChannel} from './types.js'\n\n/**\n * Processes the event stream from a typegen worker thread.\n *\n * Listens to worker channel events, displays progress via CLI spinners,\n * writes the generated types to disk, and optionally formats with prettier.\n *\n * @param receiver - Worker channel receiver for typegen events\n * @param options - Typegen configuration options\n * @returns Generation result containing the generated code and statistics\n */\nexport async function processTypegenWorkerStream(\n receiver: WorkerChannelReceiver<TypegenWorkerChannel>,\n options: TypeGenConfig,\n) {\n const start = Date.now()\n const {formatGeneratedCode, generates, schema} = options\n let code = ''\n\n const spin = spinner().start(`Loading schema…`)\n\n try {\n await receiver.event.loadedSchema()\n spin.succeed(`Schema loaded from ${formatPath(schema ?? '')}`)\n\n spin.start('Generating schema types…')\n const {expectedFileCount} = await receiver.event.typegenStarted()\n const {schemaTypeDeclarations} = await receiver.event.generatedSchemaTypes()\n const schemaTypesCount = schemaTypeDeclarations.length\n\n spin.text = 'Generating query types…'\n\n let queriesCount = 0\n let evaluatedFiles = 0\n let filesWithErrors = 0\n let queryFilesCount = 0\n let typeNodesGenerated = 0\n let unknownTypeNodesGenerated = 0\n let emptyUnionTypeNodesGenerated = 0\n\n for await (const {errors, queries} of receiver.stream.evaluatedModules()) {\n evaluatedFiles++\n queriesCount += queries.length\n queryFilesCount += queries.length > 0 ? 1 : 0\n filesWithErrors += errors.length > 0 ? 1 : 0\n\n for (const {stats} of queries) {\n typeNodesGenerated += stats.allTypes\n unknownTypeNodesGenerated += stats.unknownTypes\n emptyUnionTypeNodesGenerated += stats.emptyUnions\n }\n\n for (const error of errors) {\n spin.fail(getMessage(error))\n }\n\n if (!spin.isSpinning) {\n spin.start()\n }\n\n spin.text =\n `Generating query types… (${percent(evaluatedFiles / expectedFileCount)})\\n` +\n ` └─ Processed ${count(evaluatedFiles)} of ${count(expectedFileCount, 'files')}. ` +\n `Found ${count(queriesCount, 'queries', 'query')} from ${count(queryFilesCount, 'files')}.`\n }\n\n const result = await receiver.event.typegenComplete()\n code = `${generatedFileWarning}${result.code}`\n await writeFile(generates, code)\n\n let formattingError = false\n if (formatGeneratedCode) {\n spin.text = `Formatting generated types with prettier…`\n\n try {\n const prettierConfig = await resolvePrettierConfig(generates)\n const formattedCode = await format(code, {\n ...prettierConfig,\n parser: 'typescript' as const,\n })\n await writeFile(generates, formattedCode)\n } catch (err) {\n formattingError = true\n spin.warn(`Failed to format generated types with prettier: ${getMessage(err)}`)\n }\n }\n\n if (filesWithErrors > 0) {\n spin.warn(`Encountered errors in ${count(filesWithErrors, 'files')} while generating types`)\n }\n\n const stats = {\n duration: Date.now() - start,\n emptyUnionTypeNodesGenerated,\n filesWithErrors,\n outputSize: Buffer.byteLength(code),\n queriesCount,\n queryFilesCount,\n schemaTypesCount,\n typeNodesGenerated,\n unknownTypeNodesGenerated,\n unknownTypeNodesRatio:\n typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0,\n }\n\n let successText =\n `Successfully generated types to ${formatPath(generates)} in ${Number(stats.duration).toFixed(0)}ms` +\n `\\n └─ ${count(queriesCount, 'queries', 'query')} and ${count(schemaTypesCount, 'schema types', 'schema type')}` +\n `\\n └─ found queries in ${count(queryFilesCount, 'files', 'file')} after evaluating ${count(evaluatedFiles, 'files', 'file')}`\n\n if (formatGeneratedCode) {\n successText += `\\n └─ ${formattingError ? 'an error occured during formatting' : 'formatted the generated code with prettier'}`\n }\n\n spin.succeed(successText)\n\n return {\n ...stats,\n code,\n }\n } catch (err) {\n spin.fail()\n debug('error generating types', err)\n throw err\n } finally {\n receiver.unsubscribe()\n }\n}\n"],"names":["writeFile","spinner","format","resolveConfig","resolvePrettierConfig","count","debug","formatPath","getMessage","percent","generatedFileWarning","processTypegenWorkerStream","receiver","options","start","Date","now","formatGeneratedCode","generates","schema","code","spin","event","loadedSchema","succeed","expectedFileCount","typegenStarted","schemaTypeDeclarations","generatedSchemaTypes","schemaTypesCount","length","text","queriesCount","evaluatedFiles","filesWithErrors","queryFilesCount","typeNodesGenerated","unknownTypeNodesGenerated","emptyUnionTypeNodesGenerated","errors","queries","stream","evaluatedModules","stats","allTypes","unknownTypes","emptyUnions","error","fail","isSpinning","result","typegenComplete","formattingError","prettierConfig","formattedCode","parser","err","warn","duration","outputSize","Buffer","byteLength","unknownTypeNodesRatio","successText","Number","toFixed","unsubscribe"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
|
+
import { env } from 'node:process';
|
|
4
|
+
import { Worker } from 'node:worker_threads';
|
|
5
|
+
import { WorkerChannelReceiver } from '@sanity/worker-channels';
|
|
6
|
+
import { prepareConfig } from '../utils/config.js';
|
|
7
|
+
import { debug } from '../utils/debug.js';
|
|
8
|
+
import { processTypegenWorkerStream } from './streamProcessor.js';
|
|
9
|
+
/**
|
|
10
|
+
* Runs a single typegen generation.
|
|
11
|
+
*
|
|
12
|
+
* This is the programmatic API for generating TypeScript types from GROQ queries.
|
|
13
|
+
* It spawns a worker thread to perform the generation and displays progress via CLI spinners.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Configuration options including typegen config and working directory
|
|
16
|
+
* @returns Generation result containing the generated code and statistics
|
|
17
|
+
*/ export async function runTypegenGenerate(options) {
|
|
18
|
+
const { config, workDir } = options;
|
|
19
|
+
const { formatGeneratedCode, generates, overloadClientMethods, path, schema } = prepareConfig(config);
|
|
20
|
+
const outputPath = isAbsolute(generates) ? generates : join(workDir, generates);
|
|
21
|
+
// create output dir if it isn't there
|
|
22
|
+
const outputDir = dirname(outputPath);
|
|
23
|
+
await mkdir(outputDir, {
|
|
24
|
+
recursive: true
|
|
25
|
+
});
|
|
26
|
+
// set up worker
|
|
27
|
+
const workerPath = new URL('../actions/typegenGenerate.worker.js', import.meta.url);
|
|
28
|
+
const workerData = {
|
|
29
|
+
overloadClientMethods,
|
|
30
|
+
schemaPath: schema,
|
|
31
|
+
searchPath: path,
|
|
32
|
+
workDir
|
|
33
|
+
};
|
|
34
|
+
const worker = new Worker(workerPath, {
|
|
35
|
+
env,
|
|
36
|
+
workerData
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
const result = await processTypegenWorkerStream(WorkerChannelReceiver.from(worker), {
|
|
40
|
+
formatGeneratedCode,
|
|
41
|
+
generates: outputPath,
|
|
42
|
+
overloadClientMethods,
|
|
43
|
+
path,
|
|
44
|
+
schema
|
|
45
|
+
});
|
|
46
|
+
return result;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
debug('error generating types', err);
|
|
49
|
+
throw err;
|
|
50
|
+
} finally{
|
|
51
|
+
worker.terminate();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=typegenGenerate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/actions/typegenGenerate.ts"],"sourcesContent":["import {mkdir} from 'node:fs/promises'\nimport {dirname, isAbsolute, join} from 'node:path'\nimport {env} from 'node:process'\nimport {Worker} from 'node:worker_threads'\n\nimport {WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {prepareConfig} from '../utils/config.js'\nimport {debug} from '../utils/debug.js'\nimport {processTypegenWorkerStream} from './streamProcessor.js'\nimport {\n type GenerationResult,\n type RunTypegenOptions,\n TypegenGenerateTypesWorkerData,\n TypegenWorkerChannel,\n} from './types.js'\n\n/**\n * Runs a single typegen generation.\n *\n * This is the programmatic API for generating TypeScript types from GROQ queries.\n * It spawns a worker thread to perform the generation and displays progress via CLI spinners.\n *\n * @param options - Configuration options including typegen config and working directory\n * @returns Generation result containing the generated code and statistics\n */\nexport async function runTypegenGenerate(options: RunTypegenOptions): Promise<GenerationResult> {\n const {config, workDir} = options\n\n const {formatGeneratedCode, generates, overloadClientMethods, path, schema} =\n prepareConfig(config)\n\n const outputPath = isAbsolute(generates) ? generates : join(workDir, generates)\n\n // create output dir if it isn't there\n const outputDir = dirname(outputPath)\n await mkdir(outputDir, {recursive: true})\n\n // set up worker\n const workerPath = new URL('../actions/typegenGenerate.worker.js', import.meta.url)\n const workerData: TypegenGenerateTypesWorkerData = {\n overloadClientMethods,\n schemaPath: schema,\n searchPath: path,\n workDir,\n }\n const worker = new Worker(workerPath, {env, workerData})\n\n try {\n const result = await processTypegenWorkerStream(\n WorkerChannelReceiver.from<TypegenWorkerChannel>(worker),\n {\n formatGeneratedCode,\n generates: outputPath,\n overloadClientMethods,\n path,\n schema,\n },\n )\n\n return result\n } catch (err) {\n debug('error generating types', err)\n throw err\n } finally {\n worker.terminate()\n }\n}\n"],"names":["mkdir","dirname","isAbsolute","join","env","Worker","WorkerChannelReceiver","prepareConfig","debug","processTypegenWorkerStream","runTypegenGenerate","options","config","workDir","formatGeneratedCode","generates","overloadClientMethods","path","schema","outputPath","outputDir","recursive","workerPath","URL","url","workerData","schemaPath","searchPath","worker","result","from","err","terminate"],"mappings":"AAAA,SAAQA,KAAK,QAAO,mBAAkB;AACtC,SAAQC,OAAO,EAAEC,UAAU,EAAEC,IAAI,QAAO,YAAW;AACnD,SAAQC,GAAG,QAAO,eAAc;AAChC,SAAQC,MAAM,QAAO,sBAAqB;AAE1C,SAAQC,qBAAqB,QAAO,0BAAyB;AAE7D,SAAQC,aAAa,QAAO,qBAAoB;AAChD,SAAQC,KAAK,QAAO,oBAAmB;AACvC,SAAQC,0BAA0B,QAAO,uBAAsB;AAQ/D;;;;;;;;CAQC,GACD,OAAO,eAAeC,mBAAmBC,OAA0B;IACjE,MAAM,EAACC,MAAM,EAAEC,OAAO,EAAC,GAAGF;IAE1B,MAAM,EAACG,mBAAmB,EAAEC,SAAS,EAAEC,qBAAqB,EAAEC,IAAI,EAAEC,MAAM,EAAC,GACzEX,cAAcK;IAEhB,MAAMO,aAAajB,WAAWa,aAAaA,YAAYZ,KAAKU,SAASE;IAErE,sCAAsC;IACtC,MAAMK,YAAYnB,QAAQkB;IAC1B,MAAMnB,MAAMoB,WAAW;QAACC,WAAW;IAAI;IAEvC,gBAAgB;IAChB,MAAMC,aAAa,IAAIC,IAAI,wCAAwC,YAAYC,GAAG;IAClF,MAAMC,aAA6C;QACjDT;QACAU,YAAYR;QACZS,YAAYV;QACZJ;IACF;IACA,MAAMe,SAAS,IAAIvB,OAAOiB,YAAY;QAAClB;QAAKqB;IAAU;IAEtD,IAAI;QACF,MAAMI,SAAS,MAAMpB,2BACnBH,sBAAsBwB,IAAI,CAAuBF,SACjD;YACEd;YACAC,WAAWI;YACXH;YACAC;YACAC;QACF;QAGF,OAAOW;IACT,EAAE,OAAOE,KAAK;QACZvB,MAAM,0BAA0BuB;QAChC,MAAMA;IACR,SAAU;QACRH,OAAOI,SAAS;IAClB;AACF"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { error, log } from 'node:console';
|
|
2
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
3
|
+
import { chalk } from '@sanity/cli-core/ux';
|
|
4
|
+
import chokidar from 'chokidar';
|
|
5
|
+
import { debounce, mean } from 'lodash-es';
|
|
6
|
+
import { prepareConfig } from '../utils/config.js';
|
|
7
|
+
import { runTypegenGenerate } from './typegenGenerate.js';
|
|
8
|
+
const IGNORED_PATTERNS = [
|
|
9
|
+
'**/node_modules/**',
|
|
10
|
+
'**/.git/**',
|
|
11
|
+
'**/dist/**',
|
|
12
|
+
'**/lib/**',
|
|
13
|
+
'**/.sanity/**'
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Creates a typegen runner with concurrency control.
|
|
17
|
+
* If generation is already running, queues one more generation to run after completion.
|
|
18
|
+
* Multiple queued requests are coalesced into a single pending generation.
|
|
19
|
+
*/ export function createTypegenRunner(onGenerate) {
|
|
20
|
+
const state = {
|
|
21
|
+
isGenerating: false,
|
|
22
|
+
pendingGeneration: false
|
|
23
|
+
};
|
|
24
|
+
async function runGeneration() {
|
|
25
|
+
if (state.isGenerating) {
|
|
26
|
+
state.pendingGeneration = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
state.isGenerating = true;
|
|
30
|
+
state.pendingGeneration = false;
|
|
31
|
+
try {
|
|
32
|
+
await onGenerate();
|
|
33
|
+
} finally{
|
|
34
|
+
state.isGenerating = false;
|
|
35
|
+
// If a change came in during generation, run again
|
|
36
|
+
if (state.pendingGeneration) {
|
|
37
|
+
state.pendingGeneration = false;
|
|
38
|
+
await runGeneration();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
runGeneration,
|
|
44
|
+
state
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Starts a file watcher that triggers typegen on changes.
|
|
49
|
+
* Watches both query files (via patterns) and the schema JSON file.
|
|
50
|
+
* Implements debouncing and concurrency control to prevent multiple generations.
|
|
51
|
+
*/ export function runTypegenWatcher(options) {
|
|
52
|
+
const { config, workDir } = options;
|
|
53
|
+
const { path, schema } = prepareConfig(config);
|
|
54
|
+
const stats = {
|
|
55
|
+
failedCount: 0,
|
|
56
|
+
startTime: Date.now(),
|
|
57
|
+
successfulDurations: []
|
|
58
|
+
};
|
|
59
|
+
const { runGeneration } = createTypegenRunner(async ()=>{
|
|
60
|
+
try {
|
|
61
|
+
const { duration } = await runTypegenGenerate({
|
|
62
|
+
...options
|
|
63
|
+
});
|
|
64
|
+
stats.successfulDurations.push(duration);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const errorMessage = err instanceof Error ? err.message : err;
|
|
67
|
+
error(` ${chalk.red('›')} ${errorMessage}`);
|
|
68
|
+
stats.failedCount++;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Debounced generation trigger
|
|
72
|
+
const debouncedGenerate = debounce(runGeneration, 1000);
|
|
73
|
+
// Build absolute patterns for query files and schema file
|
|
74
|
+
const paths = Array.isArray(path) ? path : [
|
|
75
|
+
path
|
|
76
|
+
];
|
|
77
|
+
const absoluteQueryPatterns = paths.map((pattern)=>isAbsolute(pattern) ? pattern : join(workDir, pattern));
|
|
78
|
+
const absoluteSchemaPath = isAbsolute(schema) ? schema : join(workDir, schema);
|
|
79
|
+
const watchTargets = [
|
|
80
|
+
...absoluteQueryPatterns,
|
|
81
|
+
absoluteSchemaPath
|
|
82
|
+
];
|
|
83
|
+
// perform initial generation
|
|
84
|
+
debouncedGenerate();
|
|
85
|
+
// set up watcher
|
|
86
|
+
const watcher = chokidar.watch(watchTargets, {
|
|
87
|
+
cwd: workDir,
|
|
88
|
+
ignored: IGNORED_PATTERNS,
|
|
89
|
+
ignoreInitial: true
|
|
90
|
+
});
|
|
91
|
+
watcher.on('all', (event, filePath)=>{
|
|
92
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
93
|
+
const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath;
|
|
94
|
+
log(`[${timestamp}] ${event}: ${relativePath}`);
|
|
95
|
+
debouncedGenerate();
|
|
96
|
+
});
|
|
97
|
+
watcher.on('error', (err)=>{
|
|
98
|
+
error(`Watcher error: ${err.message}`);
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
getStats: ()=>({
|
|
102
|
+
averageGenerationDuration: mean(stats.successfulDurations) || 0,
|
|
103
|
+
generationFailedCount: stats.failedCount,
|
|
104
|
+
generationSuccessfulCount: stats.successfulDurations.length,
|
|
105
|
+
watcherDuration: Date.now() - stats.startTime
|
|
106
|
+
}),
|
|
107
|
+
stop: async ()=>{
|
|
108
|
+
await watcher.close();
|
|
109
|
+
},
|
|
110
|
+
watcher
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=typegenWatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/actions/typegenWatch.ts"],"sourcesContent":["import {error, log} from 'node:console'\nimport {isAbsolute, join, relative} from 'node:path'\n\nimport {chalk} from '@sanity/cli-core/ux'\nimport chokidar, {FSWatcher} from 'chokidar'\nimport {debounce, mean} from 'lodash-es'\n\nimport {TypegenWatchModeTraceAttributes} from '../typegen.telemetry.js'\nimport {prepareConfig} from '../utils/config.js'\nimport {runTypegenGenerate} from './typegenGenerate.js'\nimport {type RunTypegenOptions} from './types.js'\n\nconst IGNORED_PATTERNS = [\n '**/node_modules/**',\n '**/.git/**',\n '**/dist/**',\n '**/lib/**',\n '**/.sanity/**',\n]\n\n/** State for tracking generation status */\nexport interface WatchState {\n isGenerating: boolean\n pendingGeneration: boolean\n}\n\n/** Return type for createTypegenRunner */\ninterface TypegenRunner {\n runGeneration: () => Promise<void>\n state: WatchState\n}\n\ntype WatcherStats = Omit<Extract<TypegenWatchModeTraceAttributes, {step: 'stopped'}>, 'step'>\n\n/**\n * Creates a typegen runner with concurrency control.\n * If generation is already running, queues one more generation to run after completion.\n * Multiple queued requests are coalesced into a single pending generation.\n */\nexport function createTypegenRunner(onGenerate: () => Promise<unknown>): TypegenRunner {\n const state: WatchState = {\n isGenerating: false,\n pendingGeneration: false,\n }\n\n async function runGeneration(): Promise<void> {\n if (state.isGenerating) {\n state.pendingGeneration = true\n return\n }\n\n state.isGenerating = true\n state.pendingGeneration = false\n\n try {\n await onGenerate()\n } finally {\n state.isGenerating = false\n\n // If a change came in during generation, run again\n if (state.pendingGeneration) {\n state.pendingGeneration = false\n await runGeneration()\n }\n }\n }\n\n return {runGeneration, state}\n}\n\n/**\n * Starts a file watcher that triggers typegen on changes.\n * Watches both query files (via patterns) and the schema JSON file.\n * Implements debouncing and concurrency control to prevent multiple generations.\n */\nexport function runTypegenWatcher(options: RunTypegenOptions): {\n getStats: () => WatcherStats\n stop: () => Promise<void>\n watcher: FSWatcher\n} {\n const {config, workDir} = options\n const {path, schema} = prepareConfig(config)\n\n const stats = {\n failedCount: 0,\n startTime: Date.now(),\n successfulDurations: [] as number[],\n }\n\n const {runGeneration} = createTypegenRunner(async () => {\n try {\n const {duration} = await runTypegenGenerate({...options})\n stats.successfulDurations.push(duration)\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : err\n error(` ${chalk.red('›')} ${errorMessage}`)\n stats.failedCount++\n }\n })\n\n // Debounced generation trigger\n const debouncedGenerate = debounce(runGeneration, 1000)\n\n // Build absolute patterns for query files and schema file\n const paths = Array.isArray(path) ? path : [path]\n const absoluteQueryPatterns = paths.map((pattern) =>\n isAbsolute(pattern) ? pattern : join(workDir, pattern),\n )\n const absoluteSchemaPath = isAbsolute(schema) ? schema : join(workDir, schema)\n const watchTargets = [...absoluteQueryPatterns, absoluteSchemaPath]\n\n // perform initial generation\n debouncedGenerate()\n\n // set up watcher\n const watcher = chokidar.watch(watchTargets, {\n cwd: workDir,\n ignored: IGNORED_PATTERNS,\n ignoreInitial: true,\n })\n\n watcher.on('all', (event: string, filePath: string) => {\n const timestamp = new Date().toLocaleTimeString()\n const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath\n log(`[${timestamp}] ${event}: ${relativePath}`)\n debouncedGenerate()\n })\n\n watcher.on('error', (err: Error) => {\n error(`Watcher error: ${err.message}`)\n })\n\n return {\n getStats: () => ({\n averageGenerationDuration: mean(stats.successfulDurations) || 0,\n generationFailedCount: stats.failedCount,\n generationSuccessfulCount: stats.successfulDurations.length,\n watcherDuration: Date.now() - stats.startTime,\n }),\n stop: async () => {\n await watcher.close()\n },\n watcher,\n }\n}\n"],"names":["error","log","isAbsolute","join","relative","chalk","chokidar","debounce","mean","prepareConfig","runTypegenGenerate","IGNORED_PATTERNS","createTypegenRunner","onGenerate","state","isGenerating","pendingGeneration","runGeneration","runTypegenWatcher","options","config","workDir","path","schema","stats","failedCount","startTime","Date","now","successfulDurations","duration","push","err","errorMessage","Error","message","red","debouncedGenerate","paths","Array","isArray","absoluteQueryPatterns","map","pattern","absoluteSchemaPath","watchTargets","watcher","watch","cwd","ignored","ignoreInitial","on","event","filePath","timestamp","toLocaleTimeString","relativePath","getStats","averageGenerationDuration","generationFailedCount","generationSuccessfulCount","length","watcherDuration","stop","close"],"mappings":"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"}
|
package/dist/actions/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/actions/types.ts"],"sourcesContent":["import {WorkerChannel} from '@sanity/worker-channels'\n\nimport {type TypegenWorkerChannel as CodegenTypegenWorkerChannel} from '../typescript/typeGenerator.js'\n\nexport interface TypegenGenerateTypesWorkerData {\n schemaPath: string\n searchPath: string | string[]\n workDir: string\n\n overloadClientMethods?: boolean\n}\n\nexport type TypegenWorkerChannel = WorkerChannel.Definition<\n CodegenTypegenWorkerChannel['__definition'] & {\n loadedSchema: WorkerChannel.Event\n typegenComplete: WorkerChannel.Event<{code: string}>\n typegenStarted: WorkerChannel.Event<{expectedFileCount: number}>\n }\n>\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/actions/types.ts"],"sourcesContent":["import {spinner} from '@sanity/cli-core/ux'\nimport {WorkerChannel} from '@sanity/worker-channels'\n\nimport {TypeGenConfig} from '../readConfig.js'\nimport {type TypegenWorkerChannel as CodegenTypegenWorkerChannel} from '../typescript/typeGenerator.js'\nimport {telemetry} from '../utils/telemetryLogger.js'\n\n/**\n * Data passed to the typegen worker thread.\n * @internal\n */\nexport interface TypegenGenerateTypesWorkerData {\n /** Path to the schema JSON file */\n schemaPath: string\n /** Glob pattern(s) for finding query files */\n searchPath: string | string[]\n /** Working directory (project root) */\n workDir: string\n\n /** Whether to generate client method overloads */\n overloadClientMethods?: boolean\n}\n\n/**\n * Worker channel definition for typegen worker communication.\n * Extends the base TypegenWorkerChannel with additional events for progress tracking.\n * @internal\n */\nexport type TypegenWorkerChannel = WorkerChannel.Definition<\n CodegenTypegenWorkerChannel['__definition'] & {\n loadedSchema: WorkerChannel.Event\n typegenComplete: WorkerChannel.Event<{code: string}>\n typegenStarted: WorkerChannel.Event<{expectedFileCount: number}>\n }\n>\n\n/**\n * Options for running a single typegen generation.\n * This is the programmatic API for one-off generation without file watching.\n */\nexport interface RunTypegenOptions {\n /** Working directory (usually project root) */\n workDir: string\n\n /** Typegen configuration */\n config?: Partial<TypeGenConfig>\n\n /** Optional spinner instance for progress display */\n spin?: ReturnType<typeof spinner>\n\n /** Optional telemetry instance for tracking usage */\n telemetry?: typeof telemetry\n}\n\n/**\n * Result from a single generation run.\n * @internal\n */\nexport interface GenerationResult {\n code: string\n duration: number\n emptyUnionTypeNodesGenerated: number\n filesWithErrors: number\n outputSize: number\n queriesCount: number\n queryFilesCount: number\n schemaTypesCount: number\n typeNodesGenerated: number\n unknownTypeNodesGenerated: number\n unknownTypeNodesRatio: number\n}\n"],"names":[],"mappings":"AAsDA;;;CAGC,GACD,WAYC"}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
|
-
import { env } from 'node:process';
|
|
4
|
-
import { Worker } from 'node:worker_threads';
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
5
2
|
import { Flags } from '@oclif/core';
|
|
6
|
-
import { SanityCommand
|
|
3
|
+
import { SanityCommand } from '@sanity/cli-core';
|
|
7
4
|
import { chalk, spinner } from '@sanity/cli-core/ux';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { omit, once } from 'lodash-es';
|
|
6
|
+
import { runTypegenGenerate } from '../../actions/typegenGenerate.js';
|
|
7
|
+
import { runTypegenWatcher } from '../../actions/typegenWatch.js';
|
|
10
8
|
import { configDefinition, readConfig } from '../../readConfig.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
9
|
+
import { TypegenWatchModeTrace, TypesGeneratedTrace } from '../../typegen.telemetry.js';
|
|
10
|
+
import { debug } from '../../utils/debug.js';
|
|
11
|
+
import { promiseWithResolvers } from '../../utils/promiseWithResolvers.js';
|
|
12
|
+
import { telemetry } from '../../utils/telemetryLogger.js';
|
|
15
13
|
const description = `Sanity TypeGen (Beta)
|
|
16
14
|
This command is currently in beta and may undergo significant changes. Feedback is welcome!
|
|
17
15
|
|
|
@@ -32,7 +30,6 @@ The default configuration values listed above are used if not overridden in your
|
|
|
32
30
|
${chalk.bold('Note:')}
|
|
33
31
|
- 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.
|
|
34
32
|
- 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();
|
|
35
|
-
const debug = subdebug('typegen:generate');
|
|
36
33
|
/**
|
|
37
34
|
* @internal
|
|
38
35
|
*/ export class TypegenGenerateCommand extends SanityCommand {
|
|
@@ -46,193 +43,143 @@ const debug = subdebug('typegen:generate');
|
|
|
46
43
|
static flags = {
|
|
47
44
|
'config-path': Flags.string({
|
|
48
45
|
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.'
|
|
46
|
+
}),
|
|
47
|
+
watch: Flags.boolean({
|
|
48
|
+
default: false,
|
|
49
|
+
description: '[Default: false] Run the typegen in watch mode'
|
|
49
50
|
})
|
|
50
51
|
};
|
|
51
52
|
async run() {
|
|
52
53
|
const { flags } = await this.parse(TypegenGenerateCommand);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (flags.watch) {
|
|
55
|
+
await this.runWatcher();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await this.runSingle();
|
|
59
|
+
}
|
|
60
|
+
async getConfig() {
|
|
57
61
|
const spin = spinner({}).start('Loading config…');
|
|
58
|
-
let typegenConfig;
|
|
59
|
-
let configPath;
|
|
60
|
-
let typegenConfigMethod;
|
|
61
62
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
const { flags } = await this.parse(TypegenGenerateCommand);
|
|
64
|
+
const rootDir = await this.getProjectRoot();
|
|
65
|
+
const config = await this.getCliConfig();
|
|
66
|
+
const configPath = flags['config-path'];
|
|
67
|
+
const workDir = rootDir.directory;
|
|
68
|
+
// check if the legacy config exist
|
|
69
|
+
const legacyConfigPath = configPath || 'sanity-typegen.json';
|
|
70
|
+
let hasLegacyConfig = false;
|
|
71
|
+
try {
|
|
72
|
+
const file = await stat(legacyConfigPath);
|
|
73
|
+
hasLegacyConfig = file.isFile();
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {
|
|
76
|
+
spin.fail();
|
|
77
|
+
this.error(`Typegen config file not found: ${configPath}`, {
|
|
78
|
+
exit: 1
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {
|
|
82
|
+
spin.fail();
|
|
83
|
+
this.error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {
|
|
84
|
+
exit: 1
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// we have both legacy and cli config with typegen
|
|
89
|
+
if (config?.typegen && hasLegacyConfig) {
|
|
90
|
+
spin.warn(chalk.yellow(`You've specified typegen in your Sanity CLI config, but also have a typegen config.
|
|
91
|
+
|
|
92
|
+
The config from the Sanity CLI config is used.
|
|
93
|
+
`));
|
|
94
|
+
return {
|
|
95
|
+
config: configDefinition.parse(config.typegen || {}),
|
|
96
|
+
path: rootDir.path,
|
|
97
|
+
type: 'cli',
|
|
98
|
+
workDir
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// we only have legacy typegen config
|
|
102
|
+
if (hasLegacyConfig) {
|
|
103
|
+
spin.warn(chalk.yellow(`The separate typegen config has been deprecated. Use \`typegen\` in the sanity CLI config instead.
|
|
104
|
+
|
|
105
|
+
See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`));
|
|
106
|
+
return {
|
|
107
|
+
config: await readConfig(legacyConfigPath),
|
|
108
|
+
path: legacyConfigPath,
|
|
109
|
+
type: 'legacy',
|
|
110
|
+
workDir
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
spin.succeed(`Config loaded from sanity.cli.ts`);
|
|
114
|
+
// we only have cli config
|
|
115
|
+
return {
|
|
116
|
+
config: configDefinition.parse(config.typegen || {}),
|
|
117
|
+
path: rootDir.path,
|
|
118
|
+
type: 'cli',
|
|
119
|
+
workDir
|
|
120
|
+
};
|
|
121
|
+
} catch (err) {
|
|
69
122
|
spin.fail();
|
|
70
|
-
this.error(
|
|
123
|
+
this.error(`An error occured during config loading ${err}`, {
|
|
71
124
|
exit: 1
|
|
72
125
|
});
|
|
73
126
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
await mkdir(outputDir, {
|
|
78
|
-
recursive: true
|
|
79
|
-
});
|
|
80
|
-
const workerPath = new URL('../../actions/typegenGenerate.worker.js', import.meta.url);
|
|
81
|
-
const workerData = {
|
|
82
|
-
overloadClientMethods,
|
|
83
|
-
schemaPath,
|
|
84
|
-
searchPath,
|
|
85
|
-
workDir
|
|
86
|
-
};
|
|
87
|
-
const worker = new Worker(workerPath, {
|
|
88
|
-
env,
|
|
89
|
-
workerData
|
|
90
|
-
});
|
|
91
|
-
const receiver = WorkerChannelReceiver.from(worker);
|
|
127
|
+
}
|
|
128
|
+
async runSingle() {
|
|
129
|
+
const trace = telemetry.trace(TypesGeneratedTrace);
|
|
92
130
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
spin.start('Generating query types…');
|
|
102
|
-
let queriesCount = 0;
|
|
103
|
-
let evaluatedFiles = 0;
|
|
104
|
-
let filesWithErrors = 0;
|
|
105
|
-
let queryFilesCount = 0;
|
|
106
|
-
let typeNodesGenerated = 0;
|
|
107
|
-
let unknownTypeNodesGenerated = 0;
|
|
108
|
-
let emptyUnionTypeNodesGenerated = 0;
|
|
109
|
-
for await (const { errors, queries } of receiver.stream.evaluatedModules()){
|
|
110
|
-
evaluatedFiles++;
|
|
111
|
-
queriesCount += queries.length;
|
|
112
|
-
queryFilesCount += queries.length > 0 ? 1 : 0;
|
|
113
|
-
filesWithErrors += errors.length > 0 ? 1 : 0;
|
|
114
|
-
for (const { stats } of queries){
|
|
115
|
-
typeNodesGenerated += stats.allTypes;
|
|
116
|
-
unknownTypeNodesGenerated += stats.unknownTypes;
|
|
117
|
-
emptyUnionTypeNodesGenerated += stats.emptyUnions;
|
|
118
|
-
}
|
|
119
|
-
for (const error of errors){
|
|
120
|
-
spin.fail(getMessage(error));
|
|
121
|
-
}
|
|
122
|
-
spin.text = `Generating query types… (${percent(evaluatedFiles / expectedFileCount)})\n` + ` └─ Processed ${count(evaluatedFiles)} of ${count(expectedFileCount, 'files')}. ` + `Found ${count(queriesCount, 'queries', 'query')} from ${count(queryFilesCount, 'files')}.`;
|
|
123
|
-
}
|
|
124
|
-
const result = await receiver.event.typegenComplete();
|
|
125
|
-
const code = `${generatedFileWarning}${result.code}`;
|
|
126
|
-
await writeFile(outputPath, code);
|
|
127
|
-
spin.succeed(`Generated ${count(queriesCount, 'query types')} from ${count(queryFilesCount, 'files')} out of ${count(evaluatedFiles, 'scanned files')}`);
|
|
128
|
-
if (formatGeneratedCode) {
|
|
129
|
-
spin.start(`Formatting generated types with prettier…`);
|
|
130
|
-
try {
|
|
131
|
-
const prettier = await import('prettier');
|
|
132
|
-
const prettierConfig = await prettier.resolveConfig(outputPath);
|
|
133
|
-
const formattedCode = await prettier.format(code, {
|
|
134
|
-
...prettierConfig,
|
|
135
|
-
parser: 'typescript'
|
|
136
|
-
});
|
|
137
|
-
await writeFile(outputPath, formattedCode);
|
|
138
|
-
spin.succeed('Formatted generated types with prettier');
|
|
139
|
-
} catch (err) {
|
|
140
|
-
spin.warn(`Failed to format generated types with prettier: ${getMessage(err)}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
debug('trace', {
|
|
131
|
+
const { config: typegenConfig, type: typegenConfigMethod, workDir } = await this.getConfig();
|
|
132
|
+
trace.start();
|
|
133
|
+
const result = await runTypegenGenerate({
|
|
134
|
+
config: typegenConfig,
|
|
135
|
+
workDir
|
|
136
|
+
});
|
|
137
|
+
const traceStats = omit(result, 'code', 'duration');
|
|
138
|
+
trace.log({
|
|
144
139
|
configMethod: typegenConfigMethod,
|
|
145
|
-
configOverloadClientMethods: overloadClientMethods,
|
|
146
|
-
|
|
147
|
-
filesWithErrors,
|
|
148
|
-
outputSize: Buffer.byteLength(result.code),
|
|
149
|
-
queriesCount,
|
|
150
|
-
queryFilesCount,
|
|
151
|
-
schemaTypesCount,
|
|
152
|
-
typeNodesGenerated,
|
|
153
|
-
unknownTypeNodesGenerated,
|
|
154
|
-
unknownTypeNodesRatio: typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0
|
|
140
|
+
configOverloadClientMethods: typegenConfig.overloadClientMethods,
|
|
141
|
+
...traceStats
|
|
155
142
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// outputSize: Buffer.byteLength(result.code),
|
|
162
|
-
// queriesCount,
|
|
163
|
-
// queryFilesCount,
|
|
164
|
-
// schemaTypesCount,
|
|
165
|
-
// typeNodesGenerated,
|
|
166
|
-
// unknownTypeNodesGenerated,
|
|
167
|
-
// unknownTypeNodesRatio:
|
|
168
|
-
// typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0,
|
|
169
|
-
// })
|
|
170
|
-
if (filesWithErrors > 0) {
|
|
171
|
-
spin.warn(`Encountered errors in ${count(filesWithErrors, 'files')} while generating types`);
|
|
172
|
-
}
|
|
173
|
-
spin.succeed(`Successfully generated types to ${formatPath(generates)}`);
|
|
174
|
-
} catch (err) {
|
|
175
|
-
// trace.error(err)
|
|
176
|
-
debug('error generating types', err);
|
|
177
|
-
this.error(err instanceof Error ? err.message : 'Unknown error', {
|
|
143
|
+
trace.complete();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
debug(error);
|
|
146
|
+
trace.error(error);
|
|
147
|
+
this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {
|
|
178
148
|
exit: 1
|
|
179
149
|
});
|
|
180
|
-
} finally{
|
|
181
|
-
receiver.unsubscribe();
|
|
182
|
-
// trace.complete()
|
|
183
|
-
await worker.terminate();
|
|
184
150
|
}
|
|
185
151
|
}
|
|
186
|
-
async
|
|
187
|
-
const
|
|
188
|
-
const config = await this.getCliConfig();
|
|
189
|
-
// check if the legacy config exist
|
|
190
|
-
const legacyConfigPath = configPath || 'sanity-typegen.json';
|
|
191
|
-
let hasLegacyConfig = false;
|
|
152
|
+
async runWatcher() {
|
|
153
|
+
const trace = telemetry.trace(TypegenWatchModeTrace);
|
|
192
154
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
155
|
+
const { config: typegenConfig, workDir } = await this.getConfig();
|
|
156
|
+
trace.start();
|
|
157
|
+
const { promise, resolve } = promiseWithResolvers();
|
|
158
|
+
const typegenWatcher = runTypegenWatcher({
|
|
159
|
+
config: typegenConfig,
|
|
160
|
+
workDir
|
|
161
|
+
});
|
|
162
|
+
const stop = once(async ()=>{
|
|
163
|
+
process.off('SIGINT', stop);
|
|
164
|
+
process.off('SIGTERM', stop);
|
|
165
|
+
trace.log({
|
|
166
|
+
step: 'stopped',
|
|
167
|
+
...typegenWatcher.getStats()
|
|
204
168
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
// we only have legacy typegen config
|
|
220
|
-
if (hasLegacyConfig) {
|
|
221
|
-
spin.warn(chalk.yellow(`The separate typegen config has been deprecated. Use \`typegen\` in the sanity CLI config instead.
|
|
222
|
-
|
|
223
|
-
See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`));
|
|
224
|
-
return {
|
|
225
|
-
config: await readConfig(legacyConfigPath),
|
|
226
|
-
path: legacyConfigPath,
|
|
227
|
-
type: 'legacy'
|
|
228
|
-
};
|
|
169
|
+
trace.complete();
|
|
170
|
+
await typegenWatcher.stop();
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
process.on('SIGINT', stop);
|
|
174
|
+
process.on('SIGTERM', stop);
|
|
175
|
+
await promise;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
debug(error);
|
|
178
|
+
trace.error(error);
|
|
179
|
+
this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {
|
|
180
|
+
exit: 1
|
|
181
|
+
});
|
|
229
182
|
}
|
|
230
|
-
// we only have cli config
|
|
231
|
-
return {
|
|
232
|
-
config: configDefinition.parse(config.typegen || {}),
|
|
233
|
-
path: rootDir.path,
|
|
234
|
-
type: 'cli'
|
|
235
|
-
};
|
|
236
183
|
}
|
|
237
184
|
}
|
|
238
185
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/typegen/generate.ts"],"sourcesContent":["import {mkdir, stat, writeFile} from 'node:fs/promises'\nimport {dirname, isAbsolute, join} from 'node:path'\nimport {env} from 'node:process'\nimport {Worker} from 'node:worker_threads'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {chalk, spinner} from '@sanity/cli-core/ux'\nimport {WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {generatedFileWarning} from '../../actions/generatedFileWarning.js'\nimport {\n type TypegenGenerateTypesWorkerData,\n type TypegenWorkerChannel,\n} from '../../actions/types.js'\nimport {configDefinition, readConfig, type TypeGenConfig} from '../../readConfig.js'\nimport {count} from '../../utils/count.js'\nimport {formatPath} from '../../utils/formatPath.js'\nimport {getMessage} from '../../utils/getMessage.js'\nimport {percent} from '../../utils/percent.js'\n\nconst description = `Sanity TypeGen (Beta)\nThis command is currently in beta and may undergo significant changes. Feedback is welcome!\n\n${chalk.bold('Configuration:')}\nThis command can utilize configuration settings defined in a \\`sanity-typegen.json\\` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the \\`sanity schema extract\\` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe 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.\n\n${chalk.bold('Note:')}\n- 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.\n- 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()\n\nconst debug = subdebug('typegen:generate')\n\n/**\n * @internal\n */\nexport class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerateCommand> {\n static override description = description\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: `Generate TypeScript type definitions from a Sanity Studio schema extracted using the \\`sanity schema extract\\` command.`,\n },\n ]\n\n static override flags = {\n 'config-path': Flags.string({\n description:\n '[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.',\n }),\n }\n\n public async run() {\n const {flags} = await this.parse(TypegenGenerateCommand)\n const workDir = (await this.getProjectRoot()).directory\n\n // TODO: Add telemetry\n // const trace = telemetry.trace(TypesGeneratedTrace)\n // trace.start()\n\n const spin = spinner({}).start('Loading config…')\n\n let typegenConfig: TypeGenConfig\n let configPath: string | undefined\n let typegenConfigMethod: 'cli' | 'legacy'\n\n try {\n const result = await this.getConfig(spin, flags['config-path'])\n typegenConfig = result.config\n configPath = result.path\n typegenConfigMethod = result.type\n\n spin.succeed(`Config loaded from ${formatPath(configPath?.replace(workDir, '.') ?? '')}`)\n } catch (error) {\n debug('error loading config', error)\n spin.fail()\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {\n exit: 1,\n })\n }\n\n const {\n formatGeneratedCode,\n generates,\n overloadClientMethods,\n path: searchPath,\n schema: schemaPath,\n } = typegenConfig\n\n const outputPath = isAbsolute(typegenConfig.generates)\n ? typegenConfig.generates\n : join(workDir, typegenConfig.generates)\n\n const outputDir = dirname(outputPath)\n await mkdir(outputDir, {recursive: true})\n\n const workerPath = new URL('../../actions/typegenGenerate.worker.js', import.meta.url)\n const workerData: TypegenGenerateTypesWorkerData = {\n overloadClientMethods,\n schemaPath,\n searchPath,\n workDir,\n }\n\n const worker = new Worker(workerPath, {env, workerData})\n const receiver = WorkerChannelReceiver.from<TypegenWorkerChannel>(worker)\n\n try {\n spin.start(`Loading schema…`)\n await receiver.event.loadedSchema()\n spin.succeed(`Schema loaded from ${formatPath(schemaPath ?? '')}`)\n\n spin.start('Generating schema types…')\n const {expectedFileCount} = await receiver.event.typegenStarted()\n const {schemaTypeDeclarations} = await receiver.event.generatedSchemaTypes()\n const schemaTypesCount = schemaTypeDeclarations.length\n spin.succeed(`Generated ${count(schemaTypesCount, 'schema types')}`)\n\n spin.start('Generating query types…')\n let queriesCount = 0\n let evaluatedFiles = 0\n let filesWithErrors = 0\n let queryFilesCount = 0\n let typeNodesGenerated = 0\n let unknownTypeNodesGenerated = 0\n let emptyUnionTypeNodesGenerated = 0\n\n for await (const {errors, queries} of receiver.stream.evaluatedModules()) {\n evaluatedFiles++\n queriesCount += queries.length\n queryFilesCount += queries.length > 0 ? 1 : 0\n filesWithErrors += errors.length > 0 ? 1 : 0\n\n for (const {stats} of queries) {\n typeNodesGenerated += stats.allTypes\n unknownTypeNodesGenerated += stats.unknownTypes\n emptyUnionTypeNodesGenerated += stats.emptyUnions\n }\n\n for (const error of errors) {\n spin.fail(getMessage(error))\n }\n\n spin.text =\n `Generating query types… (${percent(evaluatedFiles / expectedFileCount)})\\n` +\n ` └─ Processed ${count(evaluatedFiles)} of ${count(expectedFileCount, 'files')}. ` +\n `Found ${count(queriesCount, 'queries', 'query')} from ${count(queryFilesCount, 'files')}.`\n }\n\n const result = await receiver.event.typegenComplete()\n const code = `${generatedFileWarning}${result.code}`\n await writeFile(outputPath, code)\n\n spin.succeed(\n `Generated ${count(queriesCount, 'query types')} from ${count(queryFilesCount, 'files')} out of ${count(evaluatedFiles, 'scanned files')}`,\n )\n\n if (formatGeneratedCode) {\n spin.start(`Formatting generated types with prettier…`)\n\n try {\n const prettier = await import('prettier')\n const prettierConfig = await prettier.resolveConfig(outputPath)\n const formattedCode = await prettier.format(code, {\n ...prettierConfig,\n parser: 'typescript' as const,\n })\n await writeFile(outputPath, formattedCode)\n\n spin.succeed('Formatted generated types with prettier')\n } catch (err) {\n spin.warn(`Failed to format generated types with prettier: ${getMessage(err)}`)\n }\n }\n\n debug('trace', {\n configMethod: typegenConfigMethod,\n configOverloadClientMethods: overloadClientMethods,\n emptyUnionTypeNodesGenerated,\n filesWithErrors,\n outputSize: Buffer.byteLength(result.code),\n queriesCount,\n queryFilesCount,\n schemaTypesCount,\n typeNodesGenerated,\n unknownTypeNodesGenerated,\n unknownTypeNodesRatio:\n typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0,\n })\n // trace.log({\n // configMethod: typegenConfigMethod,\n // configOverloadClientMethods: overloadClientMethods,\n // emptyUnionTypeNodesGenerated,\n // filesWithErrors,\n // outputSize: Buffer.byteLength(result.code),\n // queriesCount,\n // queryFilesCount,\n // schemaTypesCount,\n // typeNodesGenerated,\n // unknownTypeNodesGenerated,\n // unknownTypeNodesRatio:\n // typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0,\n // })\n\n if (filesWithErrors > 0) {\n spin.warn(`Encountered errors in ${count(filesWithErrors, 'files')} while generating types`)\n }\n\n spin.succeed(`Successfully generated types to ${formatPath(generates)}`)\n } catch (err) {\n // trace.error(err)\n debug('error generating types', err)\n this.error(err instanceof Error ? err.message : 'Unknown error', {exit: 1})\n } finally {\n receiver.unsubscribe()\n // trace.complete()\n await worker.terminate()\n }\n }\n\n private async getConfig(\n spin: ReturnType<typeof spinner>,\n configPath?: string,\n ): Promise<{config: TypeGenConfig; path?: string; type: 'cli' | 'legacy'}> {\n const rootDir = await this.getProjectRoot()\n const config = await this.getCliConfig()\n\n // check if the legacy config exist\n const legacyConfigPath = configPath || 'sanity-typegen.json'\n let hasLegacyConfig = false\n try {\n const file = await stat(legacyConfigPath)\n hasLegacyConfig = file.isFile()\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {\n throw new Error(`Typegen config file not found: ${configPath}`, {cause: err})\n }\n\n if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {\n throw new Error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {\n cause: err,\n })\n }\n }\n\n // we have both legacy and cli config with typegen\n if (config?.typegen && hasLegacyConfig) {\n spin.warn(\n chalk.yellow(\n `You've specified typegen in your Sanity CLI config, but also have a typegen config.\n\n The config from the Sanity CLI config is used.\n `,\n ),\n )\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n }\n }\n\n // we only have legacy typegen config\n if (hasLegacyConfig) {\n spin.warn(\n chalk.yellow(\n `The separate typegen config has been deprecated. Use \\`typegen\\` in the sanity CLI config instead.\n\n See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`,\n ),\n )\n return {\n config: await readConfig(legacyConfigPath),\n path: legacyConfigPath,\n type: 'legacy',\n }\n }\n\n // we only have cli config\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n }\n }\n}\n"],"names":["mkdir","stat","writeFile","dirname","isAbsolute","join","env","Worker","Flags","SanityCommand","subdebug","chalk","spinner","WorkerChannelReceiver","generatedFileWarning","configDefinition","readConfig","count","formatPath","getMessage","percent","description","bold","trim","debug","TypegenGenerateCommand","examples","command","flags","string","run","parse","workDir","getProjectRoot","directory","spin","start","typegenConfig","configPath","typegenConfigMethod","result","getConfig","config","path","type","succeed","replace","error","fail","Error","message","exit","formatGeneratedCode","generates","overloadClientMethods","searchPath","schema","schemaPath","outputPath","outputDir","recursive","workerPath","URL","url","workerData","worker","receiver","from","event","loadedSchema","expectedFileCount","typegenStarted","schemaTypeDeclarations","generatedSchemaTypes","schemaTypesCount","length","queriesCount","evaluatedFiles","filesWithErrors","queryFilesCount","typeNodesGenerated","unknownTypeNodesGenerated","emptyUnionTypeNodesGenerated","errors","queries","stream","evaluatedModules","stats","allTypes","unknownTypes","emptyUnions","text","typegenComplete","code","prettier","prettierConfig","resolveConfig","formattedCode","format","parser","err","warn","configMethod","configOverloadClientMethods","outputSize","Buffer","byteLength","unknownTypeNodesRatio","unsubscribe","terminate","rootDir","getCliConfig","legacyConfigPath","hasLegacyConfig","file","isFile","cause","typegen","yellow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/typegen/generate.ts"],"sourcesContent":["import {stat} from 'node:fs/promises'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {chalk, spinner} from '@sanity/cli-core/ux'\nimport {omit, once} from 'lodash-es'\n\nimport {runTypegenGenerate} from '../../actions/typegenGenerate.js'\nimport {runTypegenWatcher} from '../../actions/typegenWatch.js'\nimport {configDefinition, readConfig, type TypeGenConfig} from '../../readConfig.js'\nimport {TypegenWatchModeTrace, TypesGeneratedTrace} from '../../typegen.telemetry.js'\nimport {debug} from '../../utils/debug.js'\nimport {promiseWithResolvers} from '../../utils/promiseWithResolvers.js'\nimport {telemetry} from '../../utils/telemetryLogger.js'\n\nconst description = `Sanity TypeGen (Beta)\nThis command is currently in beta and may undergo significant changes. Feedback is welcome!\n\n${chalk.bold('Configuration:')}\nThis command can utilize configuration settings defined in a \\`sanity-typegen.json\\` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the \\`sanity schema extract\\` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe 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.\n\n${chalk.bold('Note:')}\n- 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.\n- 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()\n\n/**\n * @internal\n */\nexport class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerateCommand> {\n static override description = description\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: `Generate TypeScript type definitions from a Sanity Studio schema extracted using the \\`sanity schema extract\\` command.`,\n },\n ]\n\n static override flags = {\n 'config-path': Flags.string({\n description:\n '[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.',\n }),\n watch: Flags.boolean({\n default: false,\n description: '[Default: false] Run the typegen in watch mode',\n }),\n }\n\n public async run() {\n const {flags} = await this.parse(TypegenGenerateCommand)\n\n if (flags.watch) {\n await this.runWatcher()\n return\n }\n\n await this.runSingle()\n }\n\n private async getConfig(): Promise<{\n config: TypeGenConfig\n path?: string\n type: 'cli' | 'legacy'\n workDir: string\n }> {\n const spin = spinner({}).start('Loading config…')\n\n try {\n const {flags} = await this.parse(TypegenGenerateCommand)\n const rootDir = await this.getProjectRoot()\n const config = await this.getCliConfig()\n\n const configPath = flags['config-path']\n const workDir = rootDir.directory\n\n // check if the legacy config exist\n const legacyConfigPath = configPath || 'sanity-typegen.json'\n let hasLegacyConfig = false\n\n try {\n const file = await stat(legacyConfigPath)\n hasLegacyConfig = file.isFile()\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {\n spin.fail()\n this.error(`Typegen config file not found: ${configPath}`, {exit: 1})\n }\n\n if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {\n spin.fail()\n this.error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {\n exit: 1,\n })\n }\n }\n\n // we have both legacy and cli config with typegen\n if (config?.typegen && hasLegacyConfig) {\n spin.warn(\n chalk.yellow(\n `You've specified typegen in your Sanity CLI config, but also have a typegen config.\n\n The config from the Sanity CLI config is used.\n `,\n ),\n )\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n }\n\n // we only have legacy typegen config\n if (hasLegacyConfig) {\n spin.warn(\n chalk.yellow(\n `The separate typegen config has been deprecated. Use \\`typegen\\` in the sanity CLI config instead.\n\n See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`,\n ),\n )\n return {\n config: await readConfig(legacyConfigPath),\n path: legacyConfigPath,\n type: 'legacy',\n workDir,\n }\n }\n\n spin.succeed(`Config loaded from sanity.cli.ts`)\n\n // we only have cli config\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n } catch (err) {\n spin.fail()\n this.error(`An error occured during config loading ${err}`, {exit: 1})\n }\n }\n\n private async runSingle() {\n const trace = telemetry.trace(TypesGeneratedTrace)\n\n try {\n const {config: typegenConfig, type: typegenConfigMethod, workDir} = await this.getConfig()\n trace.start()\n\n const result = await runTypegenGenerate({\n config: typegenConfig,\n workDir,\n })\n\n const traceStats = omit(result, 'code', 'duration')\n\n trace.log({\n configMethod: typegenConfigMethod,\n configOverloadClientMethods: typegenConfig.overloadClientMethods,\n ...traceStats,\n })\n trace.complete()\n } catch (error) {\n debug(error)\n trace.error(error as Error)\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {\n exit: 1,\n })\n }\n }\n\n private async runWatcher() {\n const trace = telemetry.trace(TypegenWatchModeTrace)\n\n try {\n const {config: typegenConfig, workDir} = await this.getConfig()\n trace.start()\n\n const {promise, resolve} = promiseWithResolvers()\n\n const typegenWatcher = runTypegenWatcher({\n config: typegenConfig,\n workDir,\n })\n\n const stop = once(async () => {\n process.off('SIGINT', stop)\n process.off('SIGTERM', stop)\n\n trace.log({\n step: 'stopped',\n ...typegenWatcher.getStats(),\n })\n trace.complete()\n\n await typegenWatcher.stop()\n resolve()\n })\n\n process.on('SIGINT', stop)\n process.on('SIGTERM', stop)\n\n await promise\n } catch (error) {\n debug(error)\n trace.error(error as Error)\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {\n exit: 1,\n })\n }\n }\n}\n"],"names":["stat","Flags","SanityCommand","chalk","spinner","omit","once","runTypegenGenerate","runTypegenWatcher","configDefinition","readConfig","TypegenWatchModeTrace","TypesGeneratedTrace","debug","promiseWithResolvers","telemetry","description","bold","trim","TypegenGenerateCommand","examples","command","flags","string","watch","boolean","default","run","parse","runWatcher","runSingle","getConfig","spin","start","rootDir","getProjectRoot","config","getCliConfig","configPath","workDir","directory","legacyConfigPath","hasLegacyConfig","file","isFile","err","Error","code","fail","error","exit","typegen","warn","yellow","path","type","succeed","trace","typegenConfig","typegenConfigMethod","result","traceStats","log","configMethod","configOverloadClientMethods","overloadClientMethods","complete","message","promise","resolve","typegenWatcher","stop","process","off","step","getStats","on"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { BooleanFlag } from '@oclif/core/interfaces';
|
|
1
2
|
import { CustomOptions } from '@oclif/core/interfaces';
|
|
3
|
+
import { DefinedTelemetryTrace } from '@sanity/telemetry';
|
|
2
4
|
import { ExprNode } from 'groq-js';
|
|
5
|
+
import { FSWatcher } from 'chokidar';
|
|
3
6
|
import { OptionFlag } from '@oclif/core/interfaces';
|
|
4
7
|
import { SanityCommand } from '@sanity/cli-core';
|
|
5
8
|
import { SchemaType } from 'groq-js';
|
|
9
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
6
10
|
import * as t from '@babel/types';
|
|
11
|
+
import { TelemetryLogger } from '@sanity/telemetry';
|
|
7
12
|
import { TransformOptions } from '@babel/core';
|
|
8
13
|
import { WorkerChannel } from '@sanity/worker-channels';
|
|
9
14
|
import { WorkerChannelReporter } from '@sanity/worker-channels';
|
|
@@ -149,6 +154,24 @@ export declare interface GenerateTypesOptions {
|
|
|
149
154
|
schemaPath?: string;
|
|
150
155
|
}
|
|
151
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Result from a single generation run.
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
export declare interface GenerationResult {
|
|
162
|
+
code: string;
|
|
163
|
+
duration: number;
|
|
164
|
+
emptyUnionTypeNodesGenerated: number;
|
|
165
|
+
filesWithErrors: number;
|
|
166
|
+
outputSize: number;
|
|
167
|
+
queriesCount: number;
|
|
168
|
+
queryFilesCount: number;
|
|
169
|
+
schemaTypesCount: number;
|
|
170
|
+
typeNodesGenerated: number;
|
|
171
|
+
unknownTypeNodesGenerated: number;
|
|
172
|
+
unknownTypeNodesRatio: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
152
175
|
/**
|
|
153
176
|
* Get a deeply nested property type from a complex type structure. Safely navigates
|
|
154
177
|
* through nullable types (`T | null | undefined`) at each level, preserving the
|
|
@@ -378,6 +401,43 @@ export declare function readSchema(path: string): Promise<SchemaType>;
|
|
|
378
401
|
*/
|
|
379
402
|
export declare function registerBabel(babelOptions?: TransformOptions): void;
|
|
380
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Runs a single typegen generation.
|
|
406
|
+
*
|
|
407
|
+
* This is the programmatic API for generating TypeScript types from GROQ queries.
|
|
408
|
+
* It spawns a worker thread to perform the generation and displays progress via CLI spinners.
|
|
409
|
+
*
|
|
410
|
+
* @param options - Configuration options including typegen config and working directory
|
|
411
|
+
* @returns Generation result containing the generated code and statistics
|
|
412
|
+
*/
|
|
413
|
+
export declare function runTypegenGenerate(options: RunTypegenOptions): Promise<GenerationResult>;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Options for running a single typegen generation.
|
|
417
|
+
* This is the programmatic API for one-off generation without file watching.
|
|
418
|
+
*/
|
|
419
|
+
export declare interface RunTypegenOptions {
|
|
420
|
+
/** Working directory (usually project root) */
|
|
421
|
+
workDir: string;
|
|
422
|
+
/** Typegen configuration */
|
|
423
|
+
config?: Partial<TypeGenConfig>;
|
|
424
|
+
/** Optional spinner instance for progress display */
|
|
425
|
+
spin?: ReturnType<typeof spinner>;
|
|
426
|
+
/** Optional telemetry instance for tracking usage */
|
|
427
|
+
telemetry?: typeof telemetry;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Starts a file watcher that triggers typegen on changes.
|
|
432
|
+
* Watches both query files (via patterns) and the schema JSON file.
|
|
433
|
+
* Implements debouncing and concurrency control to prevent multiple generations.
|
|
434
|
+
*/
|
|
435
|
+
export declare function runTypegenWatcher(options: RunTypegenOptions): {
|
|
436
|
+
getStats: () => WatcherStats;
|
|
437
|
+
stop: () => Promise<void>;
|
|
438
|
+
watcher: FSWatcher;
|
|
439
|
+
};
|
|
440
|
+
|
|
381
441
|
/**
|
|
382
442
|
* safeParseQuery parses a GROQ query string, but first attempts to extract any parameters used in slices. This method is _only_
|
|
383
443
|
* intended for use in type generation where we don't actually execute the parsed AST on a dataset, and should not be used elsewhere.
|
|
@@ -388,6 +448,13 @@ export declare function safeParseQuery(query: string): ExprNode;
|
|
|
388
448
|
/** Builds a tuple from elements, stopping at the first `never`. */
|
|
389
449
|
declare type TakeUntilNever<T extends unknown[]> = T extends [infer H, ...infer Rest] ? [H] extends [never] ? [] : [H, ...TakeUntilNever<Rest>] : [];
|
|
390
450
|
|
|
451
|
+
/**
|
|
452
|
+
* TODO: Remove once we have a proper telemetry logger through the `@sanity/cli-core`
|
|
453
|
+
*
|
|
454
|
+
* This logger does nothing, except provide the interface to interact with
|
|
455
|
+
*/
|
|
456
|
+
declare const telemetry: TelemetryLogger<unknown>;
|
|
457
|
+
|
|
391
458
|
/**
|
|
392
459
|
* Statistics from the query type evaluation process.
|
|
393
460
|
* @public
|
|
@@ -429,11 +496,30 @@ export declare class TypegenGenerateCommand extends SanityCommand<typeof Typegen
|
|
|
429
496
|
}[];
|
|
430
497
|
static flags: {
|
|
431
498
|
'config-path': OptionFlag<string | undefined, CustomOptions>;
|
|
499
|
+
watch: BooleanFlag<boolean>;
|
|
432
500
|
};
|
|
433
501
|
run(): Promise<void>;
|
|
434
502
|
private getConfig;
|
|
503
|
+
private runSingle;
|
|
504
|
+
private runWatcher;
|
|
435
505
|
}
|
|
436
506
|
|
|
507
|
+
export declare const TypegenWatchModeTrace: DefinedTelemetryTrace<TypegenWatchModeTraceAttributes, void>;
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Attributes for typegen watch mode trace - tracks the start and stop of watch mode
|
|
511
|
+
* sessions with statistics about generation runs.
|
|
512
|
+
*/
|
|
513
|
+
declare type TypegenWatchModeTraceAttributes = {
|
|
514
|
+
averageGenerationDuration: number;
|
|
515
|
+
generationFailedCount: number;
|
|
516
|
+
generationSuccessfulCount: number;
|
|
517
|
+
step: 'stopped';
|
|
518
|
+
watcherDuration: number;
|
|
519
|
+
} | {
|
|
520
|
+
step: 'started';
|
|
521
|
+
};
|
|
522
|
+
|
|
437
523
|
export declare type TypegenWorkerChannel = WorkerChannel.Definition<{
|
|
438
524
|
evaluatedModules: WorkerChannel.Stream<EvaluatedModule>;
|
|
439
525
|
generatedQueryTypes: WorkerChannel.Event<{
|
|
@@ -463,4 +549,24 @@ export declare type TypegenWorkerChannel = WorkerChannel.Definition<{
|
|
|
463
549
|
}>;
|
|
464
550
|
}>;
|
|
465
551
|
|
|
552
|
+
export declare const TypesGeneratedTrace: DefinedTelemetryTrace<TypesGeneratedTraceAttributes, void>;
|
|
553
|
+
|
|
554
|
+
declare interface TypesGeneratedTraceAttributes {
|
|
555
|
+
configMethod: 'cli' | 'legacy';
|
|
556
|
+
configOverloadClientMethods: boolean;
|
|
557
|
+
emptyUnionTypeNodesGenerated: number;
|
|
558
|
+
filesWithErrors: number;
|
|
559
|
+
outputSize: number;
|
|
560
|
+
queriesCount: number;
|
|
561
|
+
queryFilesCount: number;
|
|
562
|
+
schemaTypesCount: number;
|
|
563
|
+
typeNodesGenerated: number;
|
|
564
|
+
unknownTypeNodesGenerated: number;
|
|
565
|
+
unknownTypeNodesRatio: number;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
declare type WatcherStats = Omit<Extract<TypegenWatchModeTraceAttributes, {
|
|
569
|
+
step: 'stopped';
|
|
570
|
+
}>, 'step'>;
|
|
571
|
+
|
|
466
572
|
export { }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineTrace } from '@sanity/telemetry';
|
|
2
|
+
export const TypesGeneratedTrace = defineTrace({
|
|
3
|
+
description: 'Trace emitted when generating TypeScript types for queries',
|
|
4
|
+
name: 'Types Generated',
|
|
5
|
+
version: 0
|
|
6
|
+
});
|
|
7
|
+
export const TypegenWatchModeTrace = defineTrace({
|
|
8
|
+
description: 'Trace emitted when typegen watch mode is run',
|
|
9
|
+
name: 'Typegen Watch Mode Started',
|
|
10
|
+
version: 0
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=typegen.telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/typegen.telemetry.ts"],"sourcesContent":["import {defineTrace} from '@sanity/telemetry'\n\ninterface TypesGeneratedTraceAttributes {\n configMethod: 'cli' | 'legacy'\n configOverloadClientMethods: boolean\n emptyUnionTypeNodesGenerated: number\n filesWithErrors: number\n outputSize: number\n queriesCount: number\n queryFilesCount: number\n schemaTypesCount: number\n typeNodesGenerated: number\n unknownTypeNodesGenerated: number\n unknownTypeNodesRatio: number\n}\n\nexport const TypesGeneratedTrace = defineTrace<TypesGeneratedTraceAttributes>({\n description: 'Trace emitted when generating TypeScript types for queries',\n name: 'Types Generated',\n version: 0,\n})\n\n/**\n * Attributes for typegen watch mode trace - tracks the start and stop of watch mode\n * sessions with statistics about generation runs.\n */\nexport type TypegenWatchModeTraceAttributes =\n | {\n averageGenerationDuration: number\n generationFailedCount: number\n generationSuccessfulCount: number\n step: 'stopped'\n watcherDuration: number\n }\n | {\n step: 'started'\n }\n\nexport const TypegenWatchModeTrace = defineTrace<TypegenWatchModeTraceAttributes>({\n description: 'Trace emitted when typegen watch mode is run',\n name: 'Typegen Watch Mode Started',\n version: 0,\n})\n"],"names":["defineTrace","TypesGeneratedTrace","description","name","version","TypegenWatchModeTrace"],"mappings":"AAAA,SAAQA,WAAW,QAAO,oBAAmB;AAgB7C,OAAO,MAAMC,sBAAsBD,YAA2C;IAC5EE,aAAa;IACbC,MAAM;IACNC,SAAS;AACX,GAAE;AAkBF,OAAO,MAAMC,wBAAwBL,YAA6C;IAChFE,aAAa;IACbC,MAAM;IACNC,SAAS;AACX,GAAE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function prepareConfig(config) {
|
|
2
|
+
return {
|
|
3
|
+
formatGeneratedCode: config?.formatGeneratedCode ?? false,
|
|
4
|
+
generates: config?.generates ?? 'sanity.types.ts',
|
|
5
|
+
overloadClientMethods: config?.overloadClientMethods ?? false,
|
|
6
|
+
path: config?.path ?? './src/**/*.{ts,tsx,js,jsx}',
|
|
7
|
+
schema: config?.schema ?? 'schema.json'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/config.ts"],"sourcesContent":["import {TypeGenConfig} from '../readConfig.js'\n\nexport function prepareConfig(config?: Partial<TypeGenConfig>): TypeGenConfig {\n return {\n formatGeneratedCode: config?.formatGeneratedCode ?? false,\n generates: config?.generates ?? 'sanity.types.ts',\n overloadClientMethods: config?.overloadClientMethods ?? false,\n path: config?.path ?? './src/**/*.{ts,tsx,js,jsx}',\n schema: config?.schema ?? 'schema.json',\n }\n}\n"],"names":["prepareConfig","config","formatGeneratedCode","generates","overloadClientMethods","path","schema"],"mappings":"AAEA,OAAO,SAASA,cAAcC,MAA+B;IAC3D,OAAO;QACLC,qBAAqBD,QAAQC,uBAAuB;QACpDC,WAAWF,QAAQE,aAAa;QAChCC,uBAAuBH,QAAQG,yBAAyB;QACxDC,MAAMJ,QAAQI,QAAQ;QACtBC,QAAQL,QAAQK,UAAU;IAC5B;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/debug.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nexport const debug = subdebug('typegen:generate')\n"],"names":["subdebug","debug"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,OAAO,MAAMC,QAAQD,SAAS,oBAAmB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const promiseWithResolvers = // @ts-expect-error TODO: replace with `Promise.withResolvers()` once it lands in node
|
|
2
|
+
Promise.withResolvers?.bind(Promise) || function promiseWithResolvers() {
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const promise = new Promise((res, rej)=>{
|
|
6
|
+
resolve = res;
|
|
7
|
+
reject = rej;
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
promise,
|
|
11
|
+
reject,
|
|
12
|
+
resolve
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=promiseWithResolvers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/promiseWithResolvers.ts"],"sourcesContent":["export const promiseWithResolvers =\n // @ts-expect-error TODO: replace with `Promise.withResolvers()` once it lands in node\n Promise.withResolvers?.bind(Promise) ||\n function promiseWithResolvers<T>() {\n let resolve!: (t: T) => void\n let reject!: (err: unknown) => void\n const promise = new Promise<T>((res, rej) => {\n resolve = res\n reject = rej\n })\n return {promise, reject, resolve}\n }\n"],"names":["promiseWithResolvers","Promise","withResolvers","bind","resolve","reject","promise","res","rej"],"mappings":"AAAA,OAAO,MAAMA,uBACX,sFAAsF;AACtFC,QAAQC,aAAa,EAAEC,KAAKF,YAC5B,SAASD;IACP,IAAII;IACJ,IAAIC;IACJ,MAAMC,UAAU,IAAIL,QAAW,CAACM,KAAKC;QACnCJ,UAAUG;QACVF,SAASG;IACX;IACA,OAAO;QAACF;QAASD;QAAQD;IAAO;AAClC,EAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: Remove once we have a proper telemetry logger through the `@sanity/cli-core`
|
|
3
|
+
*
|
|
4
|
+
* This logger does nothing, except provide the interface to interact with
|
|
5
|
+
*/ import { createBatchedStore, createSessionId } from '@sanity/telemetry';
|
|
6
|
+
const session = createSessionId();
|
|
7
|
+
const store = createBatchedStore(session, {
|
|
8
|
+
async resolveConsent () {
|
|
9
|
+
return {
|
|
10
|
+
status: 'denied'
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
sendEvents: async ()=>{}
|
|
14
|
+
});
|
|
15
|
+
export const telemetry = store.logger;
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=telemetryLogger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/telemetryLogger.ts"],"sourcesContent":["/**\n * TODO: Remove once we have a proper telemetry logger through the `@sanity/cli-core`\n *\n * This logger does nothing, except provide the interface to interact with\n */\n\nimport {createBatchedStore, createSessionId} from '@sanity/telemetry'\n\nconst session = createSessionId()\nconst store = createBatchedStore(session, {\n async resolveConsent() {\n return {status: 'denied'}\n },\n sendEvents: async () => {},\n})\n\nexport const telemetry = store.logger\n"],"names":["createBatchedStore","createSessionId","session","store","resolveConsent","status","sendEvents","telemetry","logger"],"mappings":"AAAA;;;;CAIC,GAED,SAAQA,kBAAkB,EAAEC,eAAe,QAAO,oBAAmB;AAErE,MAAMC,UAAUD;AAChB,MAAME,QAAQH,mBAAmBE,SAAS;IACxC,MAAME;QACJ,OAAO;YAACC,QAAQ;QAAQ;IAC1B;IACAC,YAAY,WAAa;AAC3B;AAEA,OAAO,MAAMC,YAAYJ,MAAMK,MAAM,CAAA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { once } from 'lodash-es';
|
|
5
|
+
const BIN = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../bin/run.js');
|
|
6
|
+
const isAssertionError = (e)=>e instanceof Error && e.name === 'AssertionError';
|
|
7
|
+
export async function testLongRunning(command, opts) {
|
|
8
|
+
const { cwd = process.cwd(), expect: assertion, interval = 150, timeout = 20_000 } = opts;
|
|
9
|
+
let stderr = '', stdout = '';
|
|
10
|
+
// spawn command in child process
|
|
11
|
+
const child = spawn('node', [
|
|
12
|
+
BIN,
|
|
13
|
+
...command
|
|
14
|
+
], {
|
|
15
|
+
cwd,
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
NO_COLOR: '1'
|
|
19
|
+
},
|
|
20
|
+
stdio: [
|
|
21
|
+
'pipe',
|
|
22
|
+
'pipe',
|
|
23
|
+
'pipe'
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
// track err and out
|
|
27
|
+
child.stdout?.on('data', (d)=>stdout += d);
|
|
28
|
+
child.stderr?.on('data', (d)=>stderr += d);
|
|
29
|
+
const kill = once(()=>child.kill('SIGTERM'));
|
|
30
|
+
// track errors from the child process
|
|
31
|
+
let exitError = null;
|
|
32
|
+
child.on('exit', (code)=>{
|
|
33
|
+
if (code === 0) return;
|
|
34
|
+
exitError = new Error(`Process exited with code ${code}\nstderr: ${stderr}`);
|
|
35
|
+
});
|
|
36
|
+
child.on('error', (e)=>{
|
|
37
|
+
exitError = e;
|
|
38
|
+
});
|
|
39
|
+
// assert once every interval until expectation met, error or timeout
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
async function assertExpectation() {
|
|
42
|
+
if (exitError) throw exitError;
|
|
43
|
+
try {
|
|
44
|
+
await assertion({
|
|
45
|
+
stderr,
|
|
46
|
+
stdout
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
stderr,
|
|
50
|
+
stdout
|
|
51
|
+
};
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (!isAssertionError(e)) throw e;
|
|
54
|
+
if (Date.now() - start > timeout) throw e;
|
|
55
|
+
return new Promise((resolve)=>{
|
|
56
|
+
setTimeout(()=>resolve(assertExpectation()), interval);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return await assertExpectation();
|
|
62
|
+
} finally{
|
|
63
|
+
kill();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=testLongRunning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/test/testLongRunning.ts"],"sourcesContent":["import {spawn} from 'node:child_process'\nimport path from 'node:path'\nimport {fileURLToPath} from 'node:url'\n\nimport {once} from 'lodash-es'\n\nconst BIN = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../bin/run.js')\n\ninterface Options {\n expect: (o: {stderr: string; stdout: string}) => Promise<void> | void\n\n cwd?: string\n interval?: number\n timeout?: number\n}\n\nconst isAssertionError = (e: unknown) => e instanceof Error && e.name === 'AssertionError'\n\nexport async function testLongRunning(\n command: string[],\n opts: Options,\n): Promise<{stderr: string; stdout: string}> {\n const {cwd = process.cwd(), expect: assertion, interval = 150, timeout = 20_000} = opts\n let stderr = '',\n stdout = ''\n\n // spawn command in child process\n const child = spawn('node', [BIN, ...command], {\n cwd,\n env: {...process.env, NO_COLOR: '1'},\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n // track err and out\n child.stdout?.on('data', (d) => (stdout += d))\n child.stderr?.on('data', (d) => (stderr += d))\n\n const kill = once(() => child.kill('SIGTERM'))\n\n // track errors from the child process\n let exitError: Error | null = null\n child.on('exit', (code) => {\n if (code === 0) return\n exitError = new Error(`Process exited with code ${code}\\nstderr: ${stderr}`)\n })\n\n child.on('error', (e) => {\n exitError = e\n })\n\n // assert once every interval until expectation met, error or timeout\n const start = Date.now()\n async function assertExpectation(): Promise<{stderr: string; stdout: string}> {\n if (exitError) throw exitError\n\n try {\n await assertion({stderr, stdout})\n return {stderr, stdout}\n } catch (e) {\n if (!isAssertionError(e)) throw e\n if (Date.now() - start > timeout) throw e\n\n return new Promise((resolve) => {\n setTimeout(() => resolve(assertExpectation()), interval)\n })\n }\n }\n\n try {\n return await assertExpectation()\n } finally {\n kill()\n }\n}\n"],"names":["spawn","path","fileURLToPath","once","BIN","resolve","dirname","url","isAssertionError","e","Error","name","testLongRunning","command","opts","cwd","process","expect","assertion","interval","timeout","stderr","stdout","child","env","NO_COLOR","stdio","on","d","kill","exitError","code","start","Date","now","assertExpectation","Promise","setTimeout"],"mappings":"AAAA,SAAQA,KAAK,QAAO,qBAAoB;AACxC,OAAOC,UAAU,YAAW;AAC5B,SAAQC,aAAa,QAAO,WAAU;AAEtC,SAAQC,IAAI,QAAO,YAAW;AAE9B,MAAMC,MAAMH,KAAKI,OAAO,CAACJ,KAAKK,OAAO,CAACJ,cAAc,YAAYK,GAAG,IAAI;AAUvE,MAAMC,mBAAmB,CAACC,IAAeA,aAAaC,SAASD,EAAEE,IAAI,KAAK;AAE1E,OAAO,eAAeC,gBACpBC,OAAiB,EACjBC,IAAa;IAEb,MAAM,EAACC,MAAMC,QAAQD,GAAG,EAAE,EAAEE,QAAQC,SAAS,EAAEC,WAAW,GAAG,EAAEC,UAAU,MAAM,EAAC,GAAGN;IACnF,IAAIO,SAAS,IACXC,SAAS;IAEX,iCAAiC;IACjC,MAAMC,QAAQvB,MAAM,QAAQ;QAACI;WAAQS;KAAQ,EAAE;QAC7CE;QACAS,KAAK;YAAC,GAAGR,QAAQQ,GAAG;YAAEC,UAAU;QAAG;QACnCC,OAAO;YAAC;YAAQ;YAAQ;SAAO;IACjC;IAEA,oBAAoB;IACpBH,MAAMD,MAAM,EAAEK,GAAG,QAAQ,CAACC,IAAON,UAAUM;IAC3CL,MAAMF,MAAM,EAAEM,GAAG,QAAQ,CAACC,IAAOP,UAAUO;IAE3C,MAAMC,OAAO1B,KAAK,IAAMoB,MAAMM,IAAI,CAAC;IAEnC,sCAAsC;IACtC,IAAIC,YAA0B;IAC9BP,MAAMI,EAAE,CAAC,QAAQ,CAACI;QAChB,IAAIA,SAAS,GAAG;QAChBD,YAAY,IAAIpB,MAAM,CAAC,yBAAyB,EAAEqB,KAAK,UAAU,EAAEV,QAAQ;IAC7E;IAEAE,MAAMI,EAAE,CAAC,SAAS,CAAClB;QACjBqB,YAAYrB;IACd;IAEA,qEAAqE;IACrE,MAAMuB,QAAQC,KAAKC,GAAG;IACtB,eAAeC;QACb,IAAIL,WAAW,MAAMA;QAErB,IAAI;YACF,MAAMZ,UAAU;gBAACG;gBAAQC;YAAM;YAC/B,OAAO;gBAACD;gBAAQC;YAAM;QACxB,EAAE,OAAOb,GAAG;YACV,IAAI,CAACD,iBAAiBC,IAAI,MAAMA;YAChC,IAAIwB,KAAKC,GAAG,KAAKF,QAAQZ,SAAS,MAAMX;YAExC,OAAO,IAAI2B,QAAQ,CAAC/B;gBAClBgC,WAAW,IAAMhC,QAAQ8B,sBAAsBhB;YACjD;QACF;IACF;IAEA,IAAI;QACF,OAAO,MAAMgB;IACf,SAAU;QACRN;IACF;AACF"}
|
package/oclif.manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"typegen:generate": {
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {},
|
|
6
|
-
"description": "Sanity TypeGen (Beta)\nThis command is currently in beta and may undergo significant changes. Feedback is welcome!\n\
|
|
6
|
+
"description": "Sanity TypeGen (Beta)\nThis command is currently in beta and may undergo significant changes. Feedback is welcome!\n\n\u001b[1mConfiguration:\u001b[22m\nThis command can utilize configuration settings defined in a `sanity-typegen.json` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the `sanity schema extract` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe 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.\n\n\u001b[1mNote:\u001b[22m\n- 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.\n- While this tool is in beta, we encourage you to experiment with these configurations and provide feedback to help improve its functionality and usability.",
|
|
7
7
|
"examples": [
|
|
8
8
|
{
|
|
9
9
|
"command": "<%= config.bin %> <%= command.id %>",
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"hasDynamicHelp": false,
|
|
18
18
|
"multiple": false,
|
|
19
19
|
"type": "option"
|
|
20
|
+
},
|
|
21
|
+
"watch": {
|
|
22
|
+
"description": "[Default: false] Run the typegen in watch mode",
|
|
23
|
+
"name": "watch",
|
|
24
|
+
"allowNo": false,
|
|
25
|
+
"type": "boolean"
|
|
20
26
|
}
|
|
21
27
|
},
|
|
22
28
|
"hasDynamicHelp": false,
|
|
@@ -35,5 +41,5 @@
|
|
|
35
41
|
]
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
|
-
"version": "5.
|
|
44
|
+
"version": "5.9.0-watch-mode.1"
|
|
39
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/codegen",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0-watch-mode.1",
|
|
4
4
|
"description": "Codegen toolkit for Sanity.io",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -24,12 +24,13 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
27
28
|
"source": "./src/_exports/index.ts",
|
|
28
|
-
"default": "./dist/index.js"
|
|
29
|
+
"default": "./dist/_exports/index.js"
|
|
29
30
|
},
|
|
30
31
|
"./package.json": "./package.json"
|
|
31
32
|
},
|
|
32
|
-
"main": "./dist/index.
|
|
33
|
+
"main": "./dist/_exports/index.js",
|
|
33
34
|
"types": "./dist/index.d.ts",
|
|
34
35
|
"bin": {
|
|
35
36
|
"sanity-typegen": "./bin/run.js"
|
|
@@ -53,16 +54,19 @@
|
|
|
53
54
|
"@oclif/core": "^4.8.0",
|
|
54
55
|
"@oclif/plugin-help": "^6.2.36",
|
|
55
56
|
"@sanity/cli-core": "^0.1.0-alpha.8",
|
|
57
|
+
"@sanity/telemetry": "^0.8.1",
|
|
56
58
|
"@sanity/worker-channels": "^1.1.0",
|
|
57
59
|
"debug": "^4.4.3",
|
|
58
60
|
"globby": "^11.1.0",
|
|
59
61
|
"groq": "^5.2.0",
|
|
60
62
|
"groq-js": "^1.25.0",
|
|
61
63
|
"json5": "^2.2.3",
|
|
64
|
+
"lodash-es": "^4.17.23",
|
|
62
65
|
"prettier": "^3.7.4",
|
|
63
66
|
"reselect": "^5.1.1",
|
|
64
67
|
"tsconfig-paths": "^4.2.0",
|
|
65
|
-
"zod": "^4.3.6"
|
|
68
|
+
"zod": "^4.3.6",
|
|
69
|
+
"chokidar": "^3.6.0"
|
|
66
70
|
},
|
|
67
71
|
"devDependencies": {
|
|
68
72
|
"@eslint/compat": "^2.0.1",
|
|
@@ -77,9 +81,9 @@
|
|
|
77
81
|
"@types/babel__register": "^7.17.3",
|
|
78
82
|
"@types/babel__traverse": "^7.28.0",
|
|
79
83
|
"@types/debug": "^4.1.12",
|
|
84
|
+
"@types/lodash-es": "^4.17.12",
|
|
80
85
|
"@types/node": "^20.19.28",
|
|
81
86
|
"@vitest/coverage-istanbul": "^4.0.17",
|
|
82
|
-
"chokidar": "^5.0.0",
|
|
83
87
|
"eslint": "^9.39.2",
|
|
84
88
|
"husky": "^9.1.7",
|
|
85
89
|
"lint-staged": "^16.2.7",
|