@sanity/cli 8.21.0 → 8.22.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.
Files changed (80) hide show
  1. package/README.md +106 -47
  2. package/codemods/dashboard.js +111 -0
  3. package/dist/actions/build/buildApp.js +1 -1
  4. package/dist/actions/build/buildApp.js.map +1 -1
  5. package/dist/actions/build/buildStudio.js +1 -1
  6. package/dist/actions/build/buildStudio.js.map +1 -1
  7. package/dist/actions/codemods/dashboard.js +113 -0
  8. package/dist/actions/codemods/dashboard.js.map +1 -0
  9. package/dist/actions/codemods/index.js +2 -0
  10. package/dist/actions/codemods/index.js.map +1 -1
  11. package/dist/actions/codemods/types.js.map +1 -1
  12. package/dist/actions/deploy/deployApp.js +13 -6
  13. package/dist/actions/deploy/deployApp.js.map +1 -1
  14. package/dist/actions/deploy/deployChecks.js +12 -7
  15. package/dist/actions/deploy/deployChecks.js.map +1 -1
  16. package/dist/actions/deploy/deployRunner.js +14 -8
  17. package/dist/actions/deploy/deployRunner.js.map +1 -1
  18. package/dist/actions/deploy/deployStudio.js +39 -4
  19. package/dist/actions/deploy/deployStudio.js.map +1 -1
  20. package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js +6 -15
  21. package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js.map +1 -1
  22. package/dist/actions/deploy/resolveDeployTarget.js +7 -6
  23. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  24. package/dist/actions/init/project/promptForAppTemplateSetup.js +22 -0
  25. package/dist/actions/init/project/promptForAppTemplateSetup.js.map +1 -1
  26. package/dist/actions/schema/deploySchemas.js.map +1 -1
  27. package/dist/actions/schema/extractSchemaWatcher.js +69 -14
  28. package/dist/actions/schema/extractSchemaWatcher.js.map +1 -1
  29. package/dist/actions/schema/listSchemas.js +2 -3
  30. package/dist/actions/schema/listSchemas.js.map +1 -1
  31. package/dist/actions/schema/updateWorkspaceSchema.js.map +1 -1
  32. package/dist/actions/typegen/generateResourceTypes.js +11 -8
  33. package/dist/actions/typegen/generateResourceTypes.js.map +1 -1
  34. package/dist/actions/typegen/watchResourceTypes.js +208 -0
  35. package/dist/actions/typegen/watchResourceTypes.js.map +1 -0
  36. package/dist/actions/undeploy/runUndeploy.js +14 -8
  37. package/dist/actions/undeploy/runUndeploy.js.map +1 -1
  38. package/dist/commands/codemod.js +15 -4
  39. package/dist/commands/codemod.js.map +1 -1
  40. package/dist/commands/context/get.js +3 -8
  41. package/dist/commands/context/get.js.map +1 -1
  42. package/dist/commands/context/imports/download.js +3 -8
  43. package/dist/commands/context/imports/download.js.map +1 -1
  44. package/dist/commands/context/imports/get.js +3 -8
  45. package/dist/commands/context/imports/get.js.map +1 -1
  46. package/dist/commands/context/imports/list.js +3 -8
  47. package/dist/commands/context/imports/list.js.map +1 -1
  48. package/dist/commands/context/jobs/get.js +8 -8
  49. package/dist/commands/context/jobs/get.js.map +1 -1
  50. package/dist/commands/context/list.js +2 -6
  51. package/dist/commands/context/list.js.map +1 -1
  52. package/dist/commands/deploy.js +1 -0
  53. package/dist/commands/deploy.js.map +1 -1
  54. package/dist/commands/doctor.js +2 -1
  55. package/dist/commands/doctor.js.map +1 -1
  56. package/dist/commands/openapi/list.js +2 -4
  57. package/dist/commands/openapi/list.js.map +1 -1
  58. package/dist/commands/projects/create.js +3 -6
  59. package/dist/commands/projects/create.js.map +1 -1
  60. package/dist/commands/schemas/list.js +1 -3
  61. package/dist/commands/schemas/list.js.map +1 -1
  62. package/dist/commands/tokens/create.js +2 -5
  63. package/dist/commands/tokens/create.js.map +1 -1
  64. package/dist/commands/tokens/list.js +2 -6
  65. package/dist/commands/tokens/list.js.map +1 -1
  66. package/dist/commands/tokens/rotate.js +2 -5
  67. package/dist/commands/tokens/rotate.js.map +1 -1
  68. package/dist/commands/typegen/generate.js +83 -45
  69. package/dist/commands/typegen/generate.js.map +1 -1
  70. package/dist/commands/undeploy.js +1 -0
  71. package/dist/commands/undeploy.js.map +1 -1
  72. package/dist/commands/whoami.js +5 -11
  73. package/dist/commands/whoami.js.map +1 -1
  74. package/dist/exports/invokeSanityCli/index.js +26 -6
  75. package/dist/exports/invokeSanityCli/index.js.map +1 -1
  76. package/dist/generated/apiRoutes.js +11 -26
  77. package/dist/generated/apiRoutes.js.map +1 -1
  78. package/dist/telemetry/init.telemetry.js.map +1 -1
  79. package/oclif.manifest.json +2340 -2311
  80. package/package.json +6 -5
@@ -1,21 +1,64 @@
1
- import { dirname, isAbsolute, join, relative } from 'node:path';
1
+ import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
2
2
  import { createSchemaPatternMatcher, formatSchemaValidation, runSchemaExtraction, SchemaExtractionError } from '@sanity/cli-build/_internal/extract';
3
3
  import { exitCodes } from '@sanity/cli-core';
4
4
  import { spinner } from '@sanity/cli-core/ux';
5
5
  import { watch as chokidarWatch } from 'chokidar';
6
6
  import debounce from 'lodash-es/debounce.js';
7
+ import picomatch from 'picomatch';
7
8
  import { schemasExtractDebug } from './utils/debug.js';
8
9
  /** Default glob patterns to watch for schema changes */ export const DEFAULT_WATCH_PATTERNS = [
9
10
  'sanity.config.{js,jsx,ts,tsx,mjs}',
10
11
  'schema*/**/*.{js,jsx,ts,tsx,mjs}'
11
12
  ];
12
- /** Default patterns to ignore when watching */ const IGNORED_PATTERNS = [
13
- '**/node_modules/**',
14
- '**/.git/**',
15
- '**/dist/**',
16
- '**/lib/**',
17
- '**/.sanity/**'
18
- ];
13
+ /**
14
+ * Directory names never descended into, unless a watch pattern names one directly. chokidar 4 and
15
+ * later reject glob `ignored` patterns, so this matches on the directory name alone.
16
+ */ const IGNORED_DIRECTORIES = new Set([
17
+ '.astro',
18
+ '.cache',
19
+ '.git',
20
+ '.next',
21
+ '.nuxt',
22
+ '.output',
23
+ '.sanity',
24
+ '.svelte-kit',
25
+ '.turbo',
26
+ '.vercel',
27
+ 'build',
28
+ 'coverage',
29
+ 'dist',
30
+ 'lib',
31
+ 'node_modules',
32
+ 'out'
33
+ ]);
34
+ /**
35
+ * Returns `pattern` relative to `workDir` with forward slashes, so one string can feed both the
36
+ * base scan and the matcher. A pattern with no glob characters names a file or a directory, and a
37
+ * directory matches everything inside it.
38
+ */ function normalizeWatchPattern(pattern, workDir) {
39
+ const normalized = relative(workDir, resolve(workDir, pattern)).replaceAll('\\', '/');
40
+ if (!normalized) return [
41
+ '**'
42
+ ];
43
+ return picomatch.scan(normalized).isGlob ? [
44
+ normalized
45
+ ] : [
46
+ normalized,
47
+ `${normalized}/**`
48
+ ];
49
+ }
50
+ /**
51
+ * Returns the directories to hand chokidar for `patterns`, deduplicated.
52
+ *
53
+ * chokidar 4 and later do not expand globs, so a pattern cannot be watched directly: it has to
54
+ * be reduced to its static base directory, with `isMatch` filtering the events that base emits.
55
+ * Both default patterns begin with a glob segment, so both reduce to `workDir` itself.
56
+ */ function watchRootsFor(patterns, workDir) {
57
+ const bases = patterns.map((pattern)=>resolve(workDir, picomatch.scan(pattern).base));
58
+ return [
59
+ ...new Set(bases)
60
+ ];
61
+ }
19
62
  /**
20
63
  * Creates an extraction runner with concurrency control.
21
64
  * If extraction is already running, queues one more extraction to run after completion.
@@ -90,7 +133,6 @@ import { schemasExtractDebug } from './utils/debug.js';
90
133
  };
91
134
  // Run initial extraction
92
135
  await runExtraction();
93
- const absoluteWatchPatterns = watchPatterns.map((pattern)=>join(workDir, pattern));
94
136
  // Create extraction runner with concurrency control
95
137
  const { runExtraction: runConcurrentExtraction } = createExtractionRunner(async ()=>{
96
138
  await runExtraction();
@@ -99,14 +141,23 @@ import { schemasExtractDebug } from './utils/debug.js';
99
141
  const debouncedExtract = debounce(()=>{
100
142
  void runConcurrentExtraction();
101
143
  }, 1000);
102
- const { isMatch } = createSchemaPatternMatcher(watchPatterns);
103
- const watcher = chokidarWatch(absoluteWatchPatterns, {
144
+ const normalizedPatterns = watchPatterns.flatMap((pattern)=>normalizeWatchPattern(pattern, workDir));
145
+ const { isMatch } = createSchemaPatternMatcher(normalizedPatterns);
146
+ const watchRoots = watchRootsFor(normalizedPatterns, workDir);
147
+ // A pattern can name a directory this would otherwise skip, such as the documented
148
+ // `--watch-patterns "lib/**/*.ts"`. chokidar asks about each root before descending, so
149
+ // skipping one there would leave it unwatched and silently match nothing.
150
+ const watchRootSet = new Set(watchRoots);
151
+ // Every extraction rewrites this file. Watching it would make each run trigger the next.
152
+ const outputFile = resolve(workDir, outputPath).toLowerCase();
153
+ const isOutputFile = (path)=>resolve(workDir, path).toLowerCase() === outputFile;
154
+ const watcher = chokidarWatch(watchRoots, {
104
155
  cwd: workDir,
105
- ignored: IGNORED_PATTERNS,
156
+ ignored: (path)=>isOutputFile(path) || !watchRootSet.has(resolve(workDir, path)) && IGNORED_DIRECTORIES.has(basename(path)),
106
157
  ignoreInitial: true
107
158
  });
108
159
  watcher.on('all', (event, filePath)=>{
109
- if (!isMatch(filePath, workDir)) {
160
+ if (isOutputFile(filePath) || !isMatch(filePath, workDir)) {
110
161
  return;
111
162
  }
112
163
  const timestamp = new Date().toLocaleTimeString();
@@ -115,7 +166,11 @@ import { schemasExtractDebug } from './utils/debug.js';
115
166
  debouncedExtract();
116
167
  });
117
168
  watcher.on('error', (err)=>{
118
- output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
169
+ // `output.error` throws unless told not to exit. A watch session should survive a transient
170
+ // fs error such as EMFILE or an inotify limit, the way the typegen watcher does.
171
+ output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`, {
172
+ exit: false
173
+ });
119
174
  });
120
175
  return {
121
176
  close: ()=>watcher.close(),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/schema/extractSchemaWatcher.ts"],"sourcesContent":["import {dirname, isAbsolute, join, relative} from 'node:path'\n\nimport {\n createSchemaPatternMatcher,\n type ExtractOptions,\n formatSchemaValidation,\n runSchemaExtraction,\n SchemaExtractionError,\n} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {watch as chokidarWatch, type FSWatcher} from 'chokidar'\nimport debounce from 'lodash-es/debounce.js'\n\nimport {schemasExtractDebug} from './utils/debug.js'\n\n/** Default glob patterns to watch for schema changes */\nexport const DEFAULT_WATCH_PATTERNS = [\n 'sanity.config.{js,jsx,ts,tsx,mjs}',\n 'schema*/**/*.{js,jsx,ts,tsx,mjs}',\n]\n\n/** Default patterns to ignore when watching */\nconst IGNORED_PATTERNS = [\n '**/node_modules/**',\n '**/.git/**',\n '**/dist/**',\n '**/lib/**',\n '**/.sanity/**',\n]\n\ninterface ExtractSchemaWatcherOptions {\n extractOptions: ExtractOptions\n output: Output\n watchPatterns: string[]\n\n onExtraction?: (result: {duration: number; success: boolean}) => void\n}\n\ninterface ExtractSchemaWatcher {\n close: () => Promise<void>\n watcher: FSWatcher\n}\n\n/** State for tracking extraction status */\ninterface WatchState {\n isExtracting: boolean\n pendingExtraction: boolean\n}\n\n/** Return type for createExtractionRunner */\ninterface ExtractionRunner {\n runExtraction: () => Promise<void>\n state: WatchState\n}\n\n/**\n * Creates an extraction runner with concurrency control.\n * If extraction is already running, queues one more extraction to run after completion.\n * Multiple queued requests are coalesced into a single pending extraction.\n */\nfunction createExtractionRunner(onExtract: () => Promise<void>): ExtractionRunner {\n const state: WatchState = {\n isExtracting: false,\n pendingExtraction: false,\n }\n\n async function runExtraction(): Promise<void> {\n if (state.isExtracting) {\n state.pendingExtraction = true\n return\n }\n\n state.isExtracting = true\n state.pendingExtraction = false\n\n try {\n await onExtract()\n } finally {\n state.isExtracting = false\n\n // If a change came in during extraction, run again\n if (state.pendingExtraction) {\n state.pendingExtraction = false\n await runExtraction()\n }\n }\n }\n\n return {runExtraction, state}\n}\n\n/**\n * Starts a schema watcher that extracts schema on file changes.\n * Returns a watcher instance and a stop function.\n */\nexport async function startExtractSchemaWatcher(\n options: ExtractSchemaWatcherOptions,\n): Promise<ExtractSchemaWatcher> {\n const {extractOptions, onExtraction, output, watchPatterns} = options\n\n const {configPath, enforceRequiredFields, outputPath} = extractOptions\n const workDir = dirname(configPath)\n\n // Helper function to run extraction with spinner and error handling\n const runExtraction = async (): Promise<boolean> => {\n const spin = spinner(\n enforceRequiredFields\n ? 'Extracting schema with enforced required fields'\n : 'Extracting schema...',\n ).start()\n const extractionStartTime = Date.now()\n\n try {\n await runSchemaExtraction(extractOptions)\n\n spin.succeed(\n enforceRequiredFields\n ? `Extracted schema to ${outputPath} with enforced required fields`\n : `Extracted schema to ${outputPath}`,\n )\n\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: true})\n\n return true\n } catch (err) {\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: false})\n\n schemasExtractDebug('Failed to extract schema', err)\n spin.fail('Extraction failed')\n\n // Display validation errors if available\n if (err instanceof SchemaExtractionError && err.validation && err.validation.length > 0) {\n output.log('')\n output.log(formatSchemaValidation(err.validation))\n } else if (err instanceof Error) {\n output.error(err.message, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n return false\n }\n }\n\n // Run initial extraction\n await runExtraction()\n\n const absoluteWatchPatterns = watchPatterns.map((pattern) => join(workDir, pattern))\n\n // Create extraction runner with concurrency control\n const {runExtraction: runConcurrentExtraction} = createExtractionRunner(async () => {\n await runExtraction()\n })\n\n // Debounced extraction trigger (1 second delay)\n const debouncedExtract = debounce(() => {\n void runConcurrentExtraction()\n }, 1000)\n\n const {isMatch} = createSchemaPatternMatcher(watchPatterns)\n\n const watcher: FSWatcher = chokidarWatch(absoluteWatchPatterns, {\n cwd: workDir,\n ignored: IGNORED_PATTERNS,\n ignoreInitial: true,\n })\n\n watcher.on('all', (event, filePath) => {\n if (!isMatch(filePath, workDir)) {\n return\n }\n\n const timestamp = new Date().toLocaleTimeString()\n const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath\n output.log(`[${timestamp}] ${event}: ${relativePath}`)\n debouncedExtract()\n })\n\n watcher.on('error', (err) => {\n output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n return {\n close: () => watcher.close(),\n watcher,\n }\n}\n"],"names":["dirname","isAbsolute","join","relative","createSchemaPatternMatcher","formatSchemaValidation","runSchemaExtraction","SchemaExtractionError","exitCodes","spinner","watch","chokidarWatch","debounce","schemasExtractDebug","DEFAULT_WATCH_PATTERNS","IGNORED_PATTERNS","createExtractionRunner","onExtract","state","isExtracting","pendingExtraction","runExtraction","startExtractSchemaWatcher","options","extractOptions","onExtraction","output","watchPatterns","configPath","enforceRequiredFields","outputPath","workDir","spin","start","extractionStartTime","Date","now","succeed","duration","success","err","fail","validation","length","log","Error","error","message","exit","RUNTIME_ERROR","absoluteWatchPatterns","map","pattern","runConcurrentExtraction","debouncedExtract","isMatch","watcher","cwd","ignored","ignoreInitial","on","event","filePath","timestamp","toLocaleTimeString","relativePath","String","close"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/schema/extractSchemaWatcher.ts"],"sourcesContent":["import {basename, dirname, isAbsolute, relative, resolve} from 'node:path'\n\nimport {\n createSchemaPatternMatcher,\n type ExtractOptions,\n formatSchemaValidation,\n runSchemaExtraction,\n SchemaExtractionError,\n} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {watch as chokidarWatch, type FSWatcher} from 'chokidar'\nimport debounce from 'lodash-es/debounce.js'\nimport picomatch from 'picomatch'\n\nimport {schemasExtractDebug} from './utils/debug.js'\n\n/** Default glob patterns to watch for schema changes */\nexport const DEFAULT_WATCH_PATTERNS = [\n 'sanity.config.{js,jsx,ts,tsx,mjs}',\n 'schema*/**/*.{js,jsx,ts,tsx,mjs}',\n]\n\n/**\n * Directory names never descended into, unless a watch pattern names one directly. chokidar 4 and\n * later reject glob `ignored` patterns, so this matches on the directory name alone.\n */\nconst IGNORED_DIRECTORIES = new Set([\n '.astro',\n '.cache',\n '.git',\n '.next',\n '.nuxt',\n '.output',\n '.sanity',\n '.svelte-kit',\n '.turbo',\n '.vercel',\n 'build',\n 'coverage',\n 'dist',\n 'lib',\n 'node_modules',\n 'out',\n])\n\n/**\n * Returns `pattern` relative to `workDir` with forward slashes, so one string can feed both the\n * base scan and the matcher. A pattern with no glob characters names a file or a directory, and a\n * directory matches everything inside it.\n */\nfunction normalizeWatchPattern(pattern: string, workDir: string): string[] {\n const normalized = relative(workDir, resolve(workDir, pattern)).replaceAll('\\\\', '/')\n if (!normalized) return ['**']\n return picomatch.scan(normalized).isGlob ? [normalized] : [normalized, `${normalized}/**`]\n}\n\n/**\n * Returns the directories to hand chokidar for `patterns`, deduplicated.\n *\n * chokidar 4 and later do not expand globs, so a pattern cannot be watched directly: it has to\n * be reduced to its static base directory, with `isMatch` filtering the events that base emits.\n * Both default patterns begin with a glob segment, so both reduce to `workDir` itself.\n */\nfunction watchRootsFor(patterns: string[], workDir: string): string[] {\n const bases = patterns.map((pattern) => resolve(workDir, picomatch.scan(pattern).base))\n return [...new Set(bases)]\n}\n\ninterface ExtractSchemaWatcherOptions {\n extractOptions: ExtractOptions\n output: Output\n watchPatterns: string[]\n\n onExtraction?: (result: {duration: number; success: boolean}) => void\n}\n\ninterface ExtractSchemaWatcher {\n close: () => Promise<void>\n watcher: FSWatcher\n}\n\n/** State for tracking extraction status */\ninterface WatchState {\n isExtracting: boolean\n pendingExtraction: boolean\n}\n\n/** Return type for createExtractionRunner */\ninterface ExtractionRunner {\n runExtraction: () => Promise<void>\n state: WatchState\n}\n\n/**\n * Creates an extraction runner with concurrency control.\n * If extraction is already running, queues one more extraction to run after completion.\n * Multiple queued requests are coalesced into a single pending extraction.\n */\nfunction createExtractionRunner(onExtract: () => Promise<void>): ExtractionRunner {\n const state: WatchState = {\n isExtracting: false,\n pendingExtraction: false,\n }\n\n async function runExtraction(): Promise<void> {\n if (state.isExtracting) {\n state.pendingExtraction = true\n return\n }\n\n state.isExtracting = true\n state.pendingExtraction = false\n\n try {\n await onExtract()\n } finally {\n state.isExtracting = false\n\n // If a change came in during extraction, run again\n if (state.pendingExtraction) {\n state.pendingExtraction = false\n await runExtraction()\n }\n }\n }\n\n return {runExtraction, state}\n}\n\n/**\n * Starts a schema watcher that extracts schema on file changes.\n * Returns a watcher instance and a stop function.\n */\nexport async function startExtractSchemaWatcher(\n options: ExtractSchemaWatcherOptions,\n): Promise<ExtractSchemaWatcher> {\n const {extractOptions, onExtraction, output, watchPatterns} = options\n\n const {configPath, enforceRequiredFields, outputPath} = extractOptions\n const workDir = dirname(configPath)\n\n // Helper function to run extraction with spinner and error handling\n const runExtraction = async (): Promise<boolean> => {\n const spin = spinner(\n enforceRequiredFields\n ? 'Extracting schema with enforced required fields'\n : 'Extracting schema...',\n ).start()\n const extractionStartTime = Date.now()\n\n try {\n await runSchemaExtraction(extractOptions)\n\n spin.succeed(\n enforceRequiredFields\n ? `Extracted schema to ${outputPath} with enforced required fields`\n : `Extracted schema to ${outputPath}`,\n )\n\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: true})\n\n return true\n } catch (err) {\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: false})\n\n schemasExtractDebug('Failed to extract schema', err)\n spin.fail('Extraction failed')\n\n // Display validation errors if available\n if (err instanceof SchemaExtractionError && err.validation && err.validation.length > 0) {\n output.log('')\n output.log(formatSchemaValidation(err.validation))\n } else if (err instanceof Error) {\n output.error(err.message, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n return false\n }\n }\n\n // Run initial extraction\n await runExtraction()\n\n // Create extraction runner with concurrency control\n const {runExtraction: runConcurrentExtraction} = createExtractionRunner(async () => {\n await runExtraction()\n })\n\n // Debounced extraction trigger (1 second delay)\n const debouncedExtract = debounce(() => {\n void runConcurrentExtraction()\n }, 1000)\n\n const normalizedPatterns = watchPatterns.flatMap((pattern) =>\n normalizeWatchPattern(pattern, workDir),\n )\n const {isMatch} = createSchemaPatternMatcher(normalizedPatterns)\n\n const watchRoots = watchRootsFor(normalizedPatterns, workDir)\n // A pattern can name a directory this would otherwise skip, such as the documented\n // `--watch-patterns \"lib/**/*.ts\"`. chokidar asks about each root before descending, so\n // skipping one there would leave it unwatched and silently match nothing.\n const watchRootSet = new Set(watchRoots)\n // Every extraction rewrites this file. Watching it would make each run trigger the next.\n const outputFile = resolve(workDir, outputPath).toLowerCase()\n const isOutputFile = (path: string) => resolve(workDir, path).toLowerCase() === outputFile\n\n const watcher: FSWatcher = chokidarWatch(watchRoots, {\n cwd: workDir,\n ignored: (path) =>\n isOutputFile(path) ||\n (!watchRootSet.has(resolve(workDir, path)) && IGNORED_DIRECTORIES.has(basename(path))),\n ignoreInitial: true,\n })\n\n watcher.on('all', (event, filePath) => {\n if (isOutputFile(filePath) || !isMatch(filePath, workDir)) {\n return\n }\n\n const timestamp = new Date().toLocaleTimeString()\n const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath\n output.log(`[${timestamp}] ${event}: ${relativePath}`)\n debouncedExtract()\n })\n\n watcher.on('error', (err) => {\n // `output.error` throws unless told not to exit. A watch session should survive a transient\n // fs error such as EMFILE or an inotify limit, the way the typegen watcher does.\n output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`, {\n exit: false,\n })\n })\n\n return {\n close: () => watcher.close(),\n watcher,\n }\n}\n"],"names":["basename","dirname","isAbsolute","relative","resolve","createSchemaPatternMatcher","formatSchemaValidation","runSchemaExtraction","SchemaExtractionError","exitCodes","spinner","watch","chokidarWatch","debounce","picomatch","schemasExtractDebug","DEFAULT_WATCH_PATTERNS","IGNORED_DIRECTORIES","Set","normalizeWatchPattern","pattern","workDir","normalized","replaceAll","scan","isGlob","watchRootsFor","patterns","bases","map","base","createExtractionRunner","onExtract","state","isExtracting","pendingExtraction","runExtraction","startExtractSchemaWatcher","options","extractOptions","onExtraction","output","watchPatterns","configPath","enforceRequiredFields","outputPath","spin","start","extractionStartTime","Date","now","succeed","duration","success","err","fail","validation","length","log","Error","error","message","exit","RUNTIME_ERROR","runConcurrentExtraction","debouncedExtract","normalizedPatterns","flatMap","isMatch","watchRoots","watchRootSet","outputFile","toLowerCase","isOutputFile","path","watcher","cwd","ignored","has","ignoreInitial","on","event","filePath","timestamp","toLocaleTimeString","relativePath","String","close"],"mappings":"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"}
@@ -32,7 +32,7 @@ export async function listSchemas(options) {
32
32
  throw new Error(id ? `Schema for id "${id}" not found in ${datasetString}` : `No schemas found in ${datasetString}`);
33
33
  }
34
34
  if (json) {
35
- output.log(JSON.stringify(id ? parsedSchemas[0] : parsedSchemas, null, 2));
35
+ output.logJson(id ? parsedSchemas[0] : parsedSchemas);
36
36
  } else {
37
37
  printSchemas({
38
38
  output,
@@ -60,13 +60,12 @@ function parseSchemas(schemas, output, json) {
60
60
  return schemas.map((schema)=>{
61
61
  if (schema.status === 'fulfilled') return schema.value;
62
62
  const error = schema.reason;
63
+ const logDiagnostic = json ? output.logDiagnostic.bind(output) : output.log.bind(output);
63
64
  if (error instanceof DatasetError) {
64
65
  if ('cause' in error && error.cause && typeof error.cause === 'object' && 'statusCode' in error.cause && error.cause.statusCode === 401) {
65
- const logDiagnostic = json ? output.warn.bind(output) : output.log.bind(output);
66
66
  logDiagnostic(styleText('yellow', `${logSymbols.warning} ↳ No permissions to read schema from "${error.dataset}". ${SCHEMA_PERMISSION_HELP_TEXT}:\n ${styleText('red', `${error.message}`)}`));
67
67
  return [];
68
68
  }
69
- const logDiagnostic = json ? output.warn.bind(output) : output.log.bind(output);
70
69
  logDiagnostic(styleText('red', `↳ Failed to fetch schema from "${error.dataset}":\n ${error.message}`));
71
70
  return [];
72
71
  } else {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/schema/listSchemas.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type StoredWorkspaceSchema} from '@sanity/cli-build/_internal/manifest'\nimport {type Output, studioWorkerTask} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\nimport {type Workspace} from 'sanity'\n\nimport {getSchemas} from '../../services/schemas.js'\nimport {Table} from '../../util/responsiveTable.js'\nimport {isDefined} from './schemaTypeHelpers.js'\nimport {type UniqWorkspaceWorkerData} from './types.js'\nimport {getDatasetsOutString} from './utils/schemaStoreOutStrings.js'\nimport {SCHEMA_PERMISSION_HELP_TEXT} from './utils/schemaStoreValidation.js'\n\ninterface ListSchemasOptions {\n configPath: string\n json: boolean\n output: Output\n workDir: string\n\n id?: string\n}\n\nclass DatasetError extends Error {\n public dataset: string\n public projectId: string\n constructor(args: {dataset: string; options?: ErrorOptions; projectId: string}) {\n super((args.options?.cause as {message?: string})?.message, args.options)\n this.projectId = args.projectId\n this.dataset = args.dataset\n this.name = 'DatasetError'\n }\n}\n\nexport async function listSchemas(options: ListSchemasOptions): Promise<void> {\n const {configPath, id, json, output, workDir} = options\n\n const projectDatasets = await studioWorkerTask<(Workspace & {key: string})[]>(\n new URL('uniqueWorkspaces.worker.js', import.meta.url),\n {\n name: 'uniqueWorkspaces',\n studioRootPath: workDir,\n workerData: {configPath} satisfies UniqWorkspaceWorkerData,\n },\n )\n\n const schemas = await getDatasetSchemas(projectDatasets, id)\n const parsedSchemas = parseSchemas(schemas, output, json)\n\n if (parsedSchemas.length === 0) {\n const datasetString = getDatasetsOutString(projectDatasets.map((dataset) => dataset.dataset))\n\n throw new Error(\n id\n ? `Schema for id \"${id}\" not found in ${datasetString}`\n : `No schemas found in ${datasetString}`,\n )\n }\n\n if (json) {\n output.log(JSON.stringify(id ? parsedSchemas[0] : parsedSchemas, null, 2))\n } else {\n printSchemas({output, schemas: parsedSchemas, workspaces: projectDatasets})\n }\n}\n\nasync function getDatasetSchemas(projectDatasets: Workspace[], id?: string) {\n return Promise.allSettled(\n projectDatasets.map(async ({dataset, projectId}) => {\n try {\n return await getSchemas(dataset, projectId, id)\n } catch (error) {\n throw new DatasetError({dataset, options: {cause: error}, projectId})\n }\n }),\n )\n}\n\nfunction parseSchemas(\n schemas: PromiseSettledResult<StoredWorkspaceSchema[]>[],\n output: Output,\n json: boolean,\n) {\n return schemas\n .map((schema) => {\n if (schema.status === 'fulfilled') return schema.value\n\n const error = schema.reason\n\n if (error instanceof DatasetError) {\n if (\n 'cause' in error &&\n error.cause &&\n typeof error.cause === 'object' &&\n 'statusCode' in error.cause &&\n error.cause.statusCode === 401\n ) {\n const logDiagnostic = json ? output.warn.bind(output) : output.log.bind(output)\n logDiagnostic(\n styleText(\n 'yellow',\n `${logSymbols.warning} ↳ No permissions to read schema from \"${error.dataset}\". ${\n SCHEMA_PERMISSION_HELP_TEXT\n }:\\n ${styleText('red', `${error.message}`)}`,\n ),\n )\n return []\n }\n\n const logDiagnostic = json ? output.warn.bind(output) : output.log.bind(output)\n logDiagnostic(\n styleText('red', `↳ Failed to fetch schema from \"${error.dataset}\":\\n ${error.message}`),\n )\n return []\n } else {\n //hubris inc: given the try-catch wrapping all the full promise \"this should never happen\"\n throw error\n }\n })\n .filter((schema) => isDefined(schema))\n .flat()\n}\n\nfunction printSchemas({\n output,\n schemas,\n workspaces,\n}: {\n output: Output\n schemas: StoredWorkspaceSchema[]\n workspaces: Workspace[]\n}) {\n const rows = schemas\n .toSorted((a, b) => -(a._createdAt || '').localeCompare(b._createdAt || ''))\n .map(({_createdAt: createdAt, _id: id, workspace}) => {\n const workspaceData = workspaces.find((w) => w.name === workspace.name)\n if (!workspaceData) return\n\n return {\n CreatedAt: createdAt,\n Dataset: workspaceData.dataset,\n Id: id,\n ProjectId: workspaceData.projectId,\n Workspace: workspace.name,\n }\n })\n .filter((schema) => isDefined(schema))\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'Id'},\n {alignment: 'left', name: 'Workspace'},\n {alignment: 'left', name: 'Dataset'},\n {alignment: 'left', name: 'ProjectId'},\n {alignment: 'left', name: 'CreatedAt'},\n ],\n })\n\n table.addRows(rows)\n output.log(table.render())\n}\n"],"names":["styleText","studioWorkerTask","logSymbols","getSchemas","Table","isDefined","getDatasetsOutString","SCHEMA_PERMISSION_HELP_TEXT","DatasetError","Error","dataset","projectId","args","options","cause","message","name","listSchemas","configPath","id","json","output","workDir","projectDatasets","URL","url","studioRootPath","workerData","schemas","getDatasetSchemas","parsedSchemas","parseSchemas","length","datasetString","map","log","JSON","stringify","printSchemas","workspaces","Promise","allSettled","error","schema","status","value","reason","statusCode","logDiagnostic","warn","bind","warning","filter","flat","rows","toSorted","a","b","_createdAt","localeCompare","createdAt","_id","workspace","workspaceData","find","w","CreatedAt","Dataset","Id","ProjectId","Workspace","table","columns","alignment","addRows","render"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/schema/listSchemas.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type StoredWorkspaceSchema} from '@sanity/cli-build/_internal/manifest'\nimport {type Output, studioWorkerTask} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\nimport {type Workspace} from 'sanity'\n\nimport {getSchemas} from '../../services/schemas.js'\nimport {Table} from '../../util/responsiveTable.js'\nimport {isDefined} from './schemaTypeHelpers.js'\nimport {type UniqWorkspaceWorkerData} from './types.js'\nimport {getDatasetsOutString} from './utils/schemaStoreOutStrings.js'\nimport {SCHEMA_PERMISSION_HELP_TEXT} from './utils/schemaStoreValidation.js'\n\ninterface ListSchemasOptions {\n configPath: string\n json: boolean\n output: Output\n workDir: string\n\n id?: string\n}\n\nclass DatasetError extends Error {\n public dataset: string\n public projectId: string\n constructor(args: {dataset: string; options?: ErrorOptions; projectId: string}) {\n super((args.options?.cause as {message?: string})?.message, args.options)\n this.projectId = args.projectId\n this.dataset = args.dataset\n this.name = 'DatasetError'\n }\n}\n\nexport async function listSchemas(options: ListSchemasOptions): Promise<void> {\n const {configPath, id, json, output, workDir} = options\n\n const projectDatasets = await studioWorkerTask<(Workspace & {key: string})[]>(\n new URL('uniqueWorkspaces.worker.js', import.meta.url),\n {\n name: 'uniqueWorkspaces',\n studioRootPath: workDir,\n workerData: {configPath} satisfies UniqWorkspaceWorkerData,\n },\n )\n\n const schemas = await getDatasetSchemas(projectDatasets, id)\n const parsedSchemas = parseSchemas(schemas, output, json)\n\n if (parsedSchemas.length === 0) {\n const datasetString = getDatasetsOutString(projectDatasets.map((dataset) => dataset.dataset))\n\n throw new Error(\n id\n ? `Schema for id \"${id}\" not found in ${datasetString}`\n : `No schemas found in ${datasetString}`,\n )\n }\n\n if (json) {\n output.logJson(id ? parsedSchemas[0] : parsedSchemas)\n } else {\n printSchemas({output, schemas: parsedSchemas, workspaces: projectDatasets})\n }\n}\n\nasync function getDatasetSchemas(projectDatasets: Workspace[], id?: string) {\n return Promise.allSettled(\n projectDatasets.map(async ({dataset, projectId}) => {\n try {\n return await getSchemas(dataset, projectId, id)\n } catch (error) {\n throw new DatasetError({dataset, options: {cause: error}, projectId})\n }\n }),\n )\n}\n\nfunction parseSchemas(\n schemas: PromiseSettledResult<StoredWorkspaceSchema[]>[],\n output: Output,\n json: boolean,\n) {\n return schemas\n .map((schema) => {\n if (schema.status === 'fulfilled') return schema.value\n\n const error = schema.reason\n const logDiagnostic = json ? output.logDiagnostic.bind(output) : output.log.bind(output)\n\n if (error instanceof DatasetError) {\n if (\n 'cause' in error &&\n error.cause &&\n typeof error.cause === 'object' &&\n 'statusCode' in error.cause &&\n error.cause.statusCode === 401\n ) {\n logDiagnostic(\n styleText(\n 'yellow',\n `${logSymbols.warning} ↳ No permissions to read schema from \"${error.dataset}\". ${\n SCHEMA_PERMISSION_HELP_TEXT\n }:\\n ${styleText('red', `${error.message}`)}`,\n ),\n )\n return []\n }\n\n logDiagnostic(\n styleText('red', `↳ Failed to fetch schema from \"${error.dataset}\":\\n ${error.message}`),\n )\n return []\n } else {\n //hubris inc: given the try-catch wrapping all the full promise \"this should never happen\"\n throw error\n }\n })\n .filter((schema) => isDefined(schema))\n .flat()\n}\n\nfunction printSchemas({\n output,\n schemas,\n workspaces,\n}: {\n output: Output\n schemas: StoredWorkspaceSchema[]\n workspaces: Workspace[]\n}) {\n const rows = schemas\n .toSorted((a, b) => -(a._createdAt || '').localeCompare(b._createdAt || ''))\n .map(({_createdAt: createdAt, _id: id, workspace}) => {\n const workspaceData = workspaces.find((w) => w.name === workspace.name)\n if (!workspaceData) return\n\n return {\n CreatedAt: createdAt,\n Dataset: workspaceData.dataset,\n Id: id,\n ProjectId: workspaceData.projectId,\n Workspace: workspace.name,\n }\n })\n .filter((schema) => isDefined(schema))\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'Id'},\n {alignment: 'left', name: 'Workspace'},\n {alignment: 'left', name: 'Dataset'},\n {alignment: 'left', name: 'ProjectId'},\n {alignment: 'left', name: 'CreatedAt'},\n ],\n })\n\n table.addRows(rows)\n output.log(table.render())\n}\n"],"names":["styleText","studioWorkerTask","logSymbols","getSchemas","Table","isDefined","getDatasetsOutString","SCHEMA_PERMISSION_HELP_TEXT","DatasetError","Error","dataset","projectId","args","options","cause","message","name","listSchemas","configPath","id","json","output","workDir","projectDatasets","URL","url","studioRootPath","workerData","schemas","getDatasetSchemas","parsedSchemas","parseSchemas","length","datasetString","map","logJson","printSchemas","workspaces","Promise","allSettled","error","schema","status","value","reason","logDiagnostic","bind","log","statusCode","warning","filter","flat","rows","toSorted","a","b","_createdAt","localeCompare","createdAt","_id","workspace","workspaceData","find","w","CreatedAt","Dataset","Id","ProjectId","Workspace","table","columns","alignment","addRows","render"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/schema/updateWorkspaceSchema.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {\n CURRENT_WORKSPACE_SCHEMA_VERSION,\n type ManifestSchemaType,\n type StoredWorkspaceSchema,\n} from '@sanity/cli-build/_internal/manifest'\nimport {spinner} from '@sanity/cli-core/ux'\nimport partition from 'lodash-es/partition.js'\n\nimport {updateSchemas} from '../../services/schemas.js'\nimport {SCHEMA_PERMISSION_HELP_TEXT} from './utils/schemaStoreValidation.js'\nimport {getWorkspaceSchemaId} from './utils/workspaceSchemaId.js'\n\n/**\n * Workspace metadata + pre-extracted manifest schema for deployment.\n * The schema must already be in ManifestSchemaType[] format (extracted from\n * the live Schema object before any structured clone boundary).\n */\nexport interface WorkspaceSchemaInput {\n dataset: string\n manifestSchema: ManifestSchemaType[]\n name: string\n projectId: string\n\n title?: string\n}\n\n/**\n * Updates the schemas for a list of workspaces.\n */\nexport async function updateWorkspacesSchemas(args: {\n tag?: string\n verbose: boolean\n workspaces: WorkspaceSchemaInput[]\n}) {\n const {tag, verbose, workspaces} = args\n\n /* Known caveat: we _don't_ rollback failed operations or partial success */\n const results = await Promise.allSettled(\n workspaces.map(async (workspace): Promise<void> => {\n await updateWorkspaceSchema({\n tag,\n verbose,\n workspace,\n })\n }),\n )\n\n const [fulfilledUpdates, rejectedUpdates] = partition(\n results,\n (result) => result.status === 'fulfilled',\n )\n\n if (rejectedUpdates.length > 0) {\n throw new Error(\n `Failed to deploy ${rejectedUpdates.length}/${workspaces.length} schemas. Successfully deployed ${fulfilledUpdates.length}/${workspaces.length} schemas.`,\n )\n }\n\n spinner(`Deployed ${fulfilledUpdates.length}/${workspaces.length} schemas`).succeed()\n}\n\n/**\n * Updates a workspace schema in the dataset.\n */\nasync function updateWorkspaceSchema(args: {\n tag?: string\n verbose: boolean\n workspace: WorkspaceSchemaInput\n}) {\n const {tag, verbose, workspace} = args\n\n const {dataset, manifestSchema, projectId} = workspace\n\n const {idWarning, safeBaseId: id} = getWorkspaceSchemaId({\n tag,\n workspaceName: workspace.name,\n })\n\n if (idWarning) ux.warn(idWarning)\n\n try {\n await updateSchemas<Omit<StoredWorkspaceSchema, '_id' | '_type'>[]>(dataset, projectId, [\n {\n // the API will stringify the schema – we send as JSON\n schema: manifestSchema,\n tag,\n version: CURRENT_WORKSPACE_SCHEMA_VERSION,\n workspace: {\n name: workspace.name,\n title: workspace.title,\n },\n },\n ])\n\n if (verbose) {\n ux.stdout(\n styleText('gray', `↳ schemaId: ${id}, projectId: ${projectId}, dataset: ${dataset}`),\n )\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (\n err instanceof Error &&\n 'statusCode' in err &&\n (err as {statusCode: unknown}).statusCode === 401\n ) {\n ux.warn(\n `↳ No permissions to write schema for workspace \"${workspace.name}\" in dataset \"${workspace.dataset}\". ${\n SCHEMA_PERMISSION_HELP_TEXT\n }:\\n ${styleText('red', message)}`,\n )\n } else {\n ux.stdout(\n styleText(\n 'red',\n `↳ Error deploying schema for workspace \"${workspace.name}\":\\n ${styleText('red', message)}`,\n ),\n )\n }\n\n throw err\n }\n}\n"],"names":["styleText","ux","CURRENT_WORKSPACE_SCHEMA_VERSION","spinner","partition","updateSchemas","SCHEMA_PERMISSION_HELP_TEXT","getWorkspaceSchemaId","updateWorkspacesSchemas","args","tag","verbose","workspaces","results","Promise","allSettled","map","workspace","updateWorkspaceSchema","fulfilledUpdates","rejectedUpdates","result","status","length","Error","succeed","dataset","manifestSchema","projectId","idWarning","safeBaseId","id","workspaceName","name","warn","schema","version","title","stdout","err","message","String","statusCode"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,iBAAgB;AACjC,SACEC,gCAAgC,QAG3B,uCAAsC;AAC7C,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,OAAOC,eAAe,yBAAwB;AAE9C,SAAQC,aAAa,QAAO,4BAA2B;AACvD,SAAQC,2BAA2B,QAAO,mCAAkC;AAC5E,SAAQC,oBAAoB,QAAO,+BAA8B;AAgBjE;;CAEC,GACD,OAAO,eAAeC,wBAAwBC,IAI7C;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEC,UAAU,EAAC,GAAGH;IAEnC,0EAA0E,GAC1E,MAAMI,UAAU,MAAMC,QAAQC,UAAU,CACtCH,WAAWI,GAAG,CAAC,OAAOC;QACpB,MAAMC,sBAAsB;YAC1BR;YACAC;YACAM;QACF;IACF;IAGF,MAAM,CAACE,kBAAkBC,gBAAgB,GAAGhB,UAC1CS,SACA,CAACQ,SAAWA,OAAOC,MAAM,KAAK;IAGhC,IAAIF,gBAAgBG,MAAM,GAAG,GAAG;QAC9B,MAAM,IAAIC,MACR,CAAC,iBAAiB,EAAEJ,gBAAgBG,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,gCAAgC,EAAEJ,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,SAAS,CAAC;IAE7J;IAEApB,QAAQ,CAAC,SAAS,EAAEgB,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,QAAQ,CAAC,EAAEE,OAAO;AACrF;AAEA;;CAEC,GACD,eAAeP,sBAAsBT,IAIpC;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEM,SAAS,EAAC,GAAGR;IAElC,MAAM,EAACiB,OAAO,EAAEC,cAAc,EAAEC,SAAS,EAAC,GAAGX;IAE7C,MAAM,EAACY,SAAS,EAAEC,YAAYC,EAAE,EAAC,GAAGxB,qBAAqB;QACvDG;QACAsB,eAAef,UAAUgB,IAAI;IAC/B;IAEA,IAAIJ,WAAW5B,GAAGiC,IAAI,CAACL;IAEvB,IAAI;QACF,MAAMxB,cAA8DqB,SAASE,WAAW;YACtF;gBACE,sDAAsD;gBACtDO,QAAQR;gBACRjB;gBACA0B,SAASlC;gBACTe,WAAW;oBACTgB,MAAMhB,UAAUgB,IAAI;oBACpBI,OAAOpB,UAAUoB,KAAK;gBACxB;YACF;SACD;QAED,IAAI1B,SAAS;YACXV,GAAGqC,MAAM,CACPtC,UAAU,QAAQ,CAAC,YAAY,EAAE+B,GAAG,aAAa,EAAEH,UAAU,WAAW,EAAEF,SAAS;QAEvF;IACF,EAAE,OAAOa,KAAK;QACZ,MAAMC,UAAUD,eAAef,QAAQe,IAAIC,OAAO,GAAGC,OAAOF;QAC5D,IACEA,eAAef,SACf,gBAAgBe,OAChB,AAACA,IAA8BG,UAAU,KAAK,KAC9C;YACAzC,GAAGiC,IAAI,CACL,CAAC,gDAAgD,EAAEjB,UAAUgB,IAAI,CAAC,cAAc,EAAEhB,UAAUS,OAAO,CAAC,GAAG,EACrGpB,4BACD,KAAK,EAAEN,UAAU,OAAOwC,UAAU;QAEvC,OAAO;YACLvC,GAAGqC,MAAM,CACPtC,UACE,OACA,CAAC,wCAAwC,EAAEiB,UAAUgB,IAAI,CAAC,MAAM,EAAEjC,UAAU,OAAOwC,UAAU;QAGnG;QAEA,MAAMD;IACR;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/schema/updateWorkspaceSchema.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {\n CURRENT_WORKSPACE_SCHEMA_VERSION,\n type StoredWorkspaceSchema,\n type WorkspaceSchemaInput,\n} from '@sanity/cli-build/_internal/manifest'\nimport {spinner} from '@sanity/cli-core/ux'\nimport partition from 'lodash-es/partition.js'\n\nimport {updateSchemas} from '../../services/schemas.js'\nimport {SCHEMA_PERMISSION_HELP_TEXT} from './utils/schemaStoreValidation.js'\nimport {getWorkspaceSchemaId} from './utils/workspaceSchemaId.js'\n\n/**\n * Updates the schemas for a list of workspaces.\n */\nexport async function updateWorkspacesSchemas(args: {\n tag?: string\n verbose: boolean\n workspaces: WorkspaceSchemaInput[]\n}) {\n const {tag, verbose, workspaces} = args\n\n /* Known caveat: we _don't_ rollback failed operations or partial success */\n const results = await Promise.allSettled(\n workspaces.map(async (workspace): Promise<void> => {\n await updateWorkspaceSchema({\n tag,\n verbose,\n workspace,\n })\n }),\n )\n\n const [fulfilledUpdates, rejectedUpdates] = partition(\n results,\n (result) => result.status === 'fulfilled',\n )\n\n if (rejectedUpdates.length > 0) {\n throw new Error(\n `Failed to deploy ${rejectedUpdates.length}/${workspaces.length} schemas. Successfully deployed ${fulfilledUpdates.length}/${workspaces.length} schemas.`,\n )\n }\n\n spinner(`Deployed ${fulfilledUpdates.length}/${workspaces.length} schemas`).succeed()\n}\n\n/**\n * Updates a workspace schema in the dataset.\n */\nasync function updateWorkspaceSchema(args: {\n tag?: string\n verbose: boolean\n workspace: WorkspaceSchemaInput\n}) {\n const {tag, verbose, workspace} = args\n\n const {dataset, manifestSchema, projectId} = workspace\n\n const {idWarning, safeBaseId: id} = getWorkspaceSchemaId({\n tag,\n workspaceName: workspace.name,\n })\n\n if (idWarning) ux.warn(idWarning)\n\n try {\n await updateSchemas<Omit<StoredWorkspaceSchema, '_id' | '_type'>[]>(dataset, projectId, [\n {\n // the API will stringify the schema – we send as JSON\n schema: manifestSchema,\n tag,\n version: CURRENT_WORKSPACE_SCHEMA_VERSION,\n workspace: {\n name: workspace.name,\n title: workspace.title,\n },\n },\n ])\n\n if (verbose) {\n ux.stdout(\n styleText('gray', `↳ schemaId: ${id}, projectId: ${projectId}, dataset: ${dataset}`),\n )\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (\n err instanceof Error &&\n 'statusCode' in err &&\n (err as {statusCode: unknown}).statusCode === 401\n ) {\n ux.warn(\n `↳ No permissions to write schema for workspace \"${workspace.name}\" in dataset \"${workspace.dataset}\". ${\n SCHEMA_PERMISSION_HELP_TEXT\n }:\\n ${styleText('red', message)}`,\n )\n } else {\n ux.stdout(\n styleText(\n 'red',\n `↳ Error deploying schema for workspace \"${workspace.name}\":\\n ${styleText('red', message)}`,\n ),\n )\n }\n\n throw err\n }\n}\n"],"names":["styleText","ux","CURRENT_WORKSPACE_SCHEMA_VERSION","spinner","partition","updateSchemas","SCHEMA_PERMISSION_HELP_TEXT","getWorkspaceSchemaId","updateWorkspacesSchemas","args","tag","verbose","workspaces","results","Promise","allSettled","map","workspace","updateWorkspaceSchema","fulfilledUpdates","rejectedUpdates","result","status","length","Error","succeed","dataset","manifestSchema","projectId","idWarning","safeBaseId","id","workspaceName","name","warn","schema","version","title","stdout","err","message","String","statusCode"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,iBAAgB;AACjC,SACEC,gCAAgC,QAG3B,uCAAsC;AAC7C,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,OAAOC,eAAe,yBAAwB;AAE9C,SAAQC,aAAa,QAAO,4BAA2B;AACvD,SAAQC,2BAA2B,QAAO,mCAAkC;AAC5E,SAAQC,oBAAoB,QAAO,+BAA8B;AAEjE;;CAEC,GACD,OAAO,eAAeC,wBAAwBC,IAI7C;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEC,UAAU,EAAC,GAAGH;IAEnC,0EAA0E,GAC1E,MAAMI,UAAU,MAAMC,QAAQC,UAAU,CACtCH,WAAWI,GAAG,CAAC,OAAOC;QACpB,MAAMC,sBAAsB;YAC1BR;YACAC;YACAM;QACF;IACF;IAGF,MAAM,CAACE,kBAAkBC,gBAAgB,GAAGhB,UAC1CS,SACA,CAACQ,SAAWA,OAAOC,MAAM,KAAK;IAGhC,IAAIF,gBAAgBG,MAAM,GAAG,GAAG;QAC9B,MAAM,IAAIC,MACR,CAAC,iBAAiB,EAAEJ,gBAAgBG,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,gCAAgC,EAAEJ,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,SAAS,CAAC;IAE7J;IAEApB,QAAQ,CAAC,SAAS,EAAEgB,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,QAAQ,CAAC,EAAEE,OAAO;AACrF;AAEA;;CAEC,GACD,eAAeP,sBAAsBT,IAIpC;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEM,SAAS,EAAC,GAAGR;IAElC,MAAM,EAACiB,OAAO,EAAEC,cAAc,EAAEC,SAAS,EAAC,GAAGX;IAE7C,MAAM,EAACY,SAAS,EAAEC,YAAYC,EAAE,EAAC,GAAGxB,qBAAqB;QACvDG;QACAsB,eAAef,UAAUgB,IAAI;IAC/B;IAEA,IAAIJ,WAAW5B,GAAGiC,IAAI,CAACL;IAEvB,IAAI;QACF,MAAMxB,cAA8DqB,SAASE,WAAW;YACtF;gBACE,sDAAsD;gBACtDO,QAAQR;gBACRjB;gBACA0B,SAASlC;gBACTe,WAAW;oBACTgB,MAAMhB,UAAUgB,IAAI;oBACpBI,OAAOpB,UAAUoB,KAAK;gBACxB;YACF;SACD;QAED,IAAI1B,SAAS;YACXV,GAAGqC,MAAM,CACPtC,UAAU,QAAQ,CAAC,YAAY,EAAE+B,GAAG,aAAa,EAAEH,UAAU,WAAW,EAAEF,SAAS;QAEvF;IACF,EAAE,OAAOa,KAAK;QACZ,MAAMC,UAAUD,eAAef,QAAQe,IAAIC,OAAO,GAAGC,OAAOF;QAC5D,IACEA,eAAef,SACf,gBAAgBe,OAChB,AAACA,IAA8BG,UAAU,KAAK,KAC9C;YACAzC,GAAGiC,IAAI,CACL,CAAC,gDAAgD,EAAEjB,UAAUgB,IAAI,CAAC,cAAc,EAAEhB,UAAUS,OAAO,CAAC,GAAG,EACrGpB,4BACD,KAAK,EAAEN,UAAU,OAAOwC,UAAU;QAEvC,OAAO;YACLvC,GAAGqC,MAAM,CACPtC,UACE,OACA,CAAC,wCAAwC,EAAEiB,UAAUgB,IAAI,CAAC,MAAM,EAAEjC,UAAU,OAAOwC,UAAU;QAGnG;QAEA,MAAMD;IACR;AACF"}
@@ -6,11 +6,12 @@ import { getResourceKey } from './parseTypegenResources.js';
6
6
  /**
7
7
  * Locates the schema for every resource before anything is generated.
8
8
  *
9
- * A resource with a `schema` path uses that file, which must exist and parse. One without
10
- * fetches the schema bound to its dataset. Doing this first means a missing binding or a
11
- * missing or malformed file fails the run before any output is written.
9
+ * A resource with a `schema` path uses that file, which must exist and parse unless
10
+ * `checkSchemaFiles` is `false`. One without fetches the schema bound to its dataset. Doing this
11
+ * first means a missing binding or a missing or malformed file fails the run before any output is
12
+ * written.
12
13
  */ export async function resolveResourceSchemas(options) {
13
- const { onResolved, onResolving, resources, workDir } = options;
14
+ const { checkSchemaFiles = true, onResolved, onResolving, resources, workDir } = options;
14
15
  const resolved = [];
15
16
  for (const resource of resources){
16
17
  const key = getResourceKey(resource);
@@ -31,10 +32,12 @@ import { getResourceKey } from './parseTypegenResources.js';
31
32
  }
32
33
  } else {
33
34
  const schemaPath = isAbsolute(resource.schema) ? resource.schema : join(workDir, resource.schema);
34
- await assertExtractedSchemaFile(schemaPath, {
35
- key,
36
- schema: resource.schema
37
- });
35
+ if (checkSchemaFiles) {
36
+ await assertExtractedSchemaFile(schemaPath, {
37
+ key,
38
+ schema: resource.schema
39
+ });
40
+ }
38
41
  // Codegen reads the file again, so the generated output names it as its source, as it
39
42
  // does without resources.
40
43
  entry = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/typegen/generateResourceTypes.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {isAbsolute, join} from 'node:path'\n\nimport {type TypeGenResource} from '@sanity/cli-core/types'\nimport {\n type GenerationResult,\n runTypegenGenerate,\n type TypeGenConfig,\n type TypegenProgressEvent,\n} from '@sanity/codegen'\n\nimport {type ExtractedSchema, fetchResourceSchema} from './fetchResourceSchema.js'\nimport {getResourceKey} from './parseTypegenResources.js'\n\n/**\n * A resource whose schema has been located, ready to generate.\n */\nexport type ResolvedResource =\n | {key: string; resource: TypeGenResource; schema: ExtractedSchema; schemaVersion: string}\n | {key: string; resource: TypeGenResource; schemaPath: string}\n\n/**\n * Locates the schema for every resource before anything is generated.\n *\n * A resource with a `schema` path uses that file, which must exist and parse. One without\n * fetches the schema bound to its dataset. Doing this first means a missing binding or a\n * missing or malformed file fails the run before any output is written.\n */\nexport async function resolveResourceSchemas(options: {\n /** Called once a resource's schema is located. */\n onResolved?: (resolved: ResolvedResource) => void\n /** Called before a resource's schema is located. */\n onResolving?: (resource: TypeGenResource, key: string) => void\n resources: TypeGenResource[]\n workDir: string\n}): Promise<ResolvedResource[]> {\n const {onResolved, onResolving, resources, workDir} = options\n const resolved: ResolvedResource[] = []\n\n for (const resource of resources) {\n const key = getResourceKey(resource)\n onResolving?.(resource, key)\n let entry: ResolvedResource\n\n if (resource.schema === undefined) {\n try {\n entry = {key, resource, ...(await fetchResourceSchema(resource))}\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to get the schema for \"${key}\": ${message}`, {cause: error})\n }\n } else {\n const schemaPath = isAbsolute(resource.schema)\n ? resource.schema\n : join(workDir, resource.schema)\n await assertExtractedSchemaFile(schemaPath, {key, schema: resource.schema})\n // Codegen reads the file again, so the generated output names it as its source, as it\n // does without resources.\n entry = {key, resource, schemaPath: resource.schema}\n }\n\n resolved.push(entry)\n onResolved?.(entry)\n }\n\n return resolved\n}\n\n/**\n * Generates one output file per resource, registering its types under the resource key.\n */\nexport async function generateResourceTypes(options: {\n /** Settings shared by every resource. */\n config: TypeGenConfig\n /** Returns the progress handler for one resource's generation. */\n onProgress?: (resolved: ResolvedResource) => ((event: TypegenProgressEvent) => void) | undefined\n resolved: ResolvedResource[]\n workDir: string\n}): Promise<{key: string; result: GenerationResult}[]> {\n const {config, onProgress, resolved, workDir} = options\n const results: {key: string; result: GenerationResult}[] = []\n\n for (const entry of resolved) {\n const {dataset, generates, path, projectId} = entry.resource\n let result: GenerationResult\n try {\n result = await runTypegenGenerate({\n config: {\n ...config,\n generates,\n path: path ?? config.path,\n schema: 'schemaPath' in entry ? entry.schemaPath : config.schema,\n },\n extractedSchema: 'schema' in entry ? entry.schema : undefined,\n onProgress: onProgress?.(entry),\n resource: {dataset, projectId},\n workDir,\n })\n } catch (error) {\n // Codegen writes a resource's file once its generation succeeds, so the files of this\n // resource and the ones after it are unchanged. Files of earlier resources are not\n // rolled back.\n const message = error instanceof Error ? error.message : String(error)\n const written = results.map(({key}) => `\"${key}\"`).join(', ')\n throw new Error(\n `Failed to generate types for \"${entry.key}\": ${message}` +\n (written\n ? `\\nTypes for ${written} were updated. The other output files are unchanged.`\n : ''),\n {cause: error},\n )\n }\n results.push({key: entry.key, result})\n }\n\n return results\n}\n\n/**\n * Checks that a local schema file exists and holds an extracted schema, which is a JSON array.\n */\nasync function assertExtractedSchemaFile(\n schemaPath: string,\n {key, schema}: {key: string; schema: string},\n): Promise<void> {\n let content: string\n try {\n content = await readFile(schemaPath, 'utf8')\n } catch {\n throw new Error(\n `Schema file not found for \"${key}\": ${schema}. Run \"sanity schema extract\" to create it.`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Schema file for \"${key}\" is not valid JSON: ${schema}. ${message}`, {\n cause: error,\n })\n }\n\n if (!Array.isArray(parsed)) {\n throw new TypeError(\n `Schema file for \"${key}\" is not an extracted schema: ${schema}. Run \"sanity schema extract\" to recreate it.`,\n )\n }\n}\n"],"names":["readFile","isAbsolute","join","runTypegenGenerate","fetchResourceSchema","getResourceKey","resolveResourceSchemas","options","onResolved","onResolving","resources","workDir","resolved","resource","key","entry","schema","undefined","error","message","Error","String","cause","schemaPath","assertExtractedSchemaFile","push","generateResourceTypes","config","onProgress","results","dataset","generates","path","projectId","result","extractedSchema","written","map","content","parsed","JSON","parse","Array","isArray","TypeError"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/typegen/generateResourceTypes.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {isAbsolute, join} from 'node:path'\n\nimport {type TypeGenResource} from '@sanity/cli-core/types'\nimport {\n type GenerationResult,\n runTypegenGenerate,\n type TypeGenConfig,\n type TypegenProgressEvent,\n} from '@sanity/codegen'\n\nimport {type ExtractedSchema, fetchResourceSchema} from './fetchResourceSchema.js'\nimport {getResourceKey} from './parseTypegenResources.js'\n\n/**\n * A resource whose schema has been located, ready to generate.\n */\nexport type ResolvedResource =\n | {key: string; resource: TypeGenResource; schema: ExtractedSchema; schemaVersion: string}\n | {key: string; resource: TypeGenResource; schemaPath: string}\n\n/**\n * Locates the schema for every resource before anything is generated.\n *\n * A resource with a `schema` path uses that file, which must exist and parse unless\n * `checkSchemaFiles` is `false`. One without fetches the schema bound to its dataset. Doing this\n * first means a missing binding or a missing or malformed file fails the run before any output is\n * written.\n */\nexport async function resolveResourceSchemas(options: {\n /**\n * Whether a local schema file must exist and parse now. Watch mode passes `false`, because it\n * generates once the file appears, as it does without resources.\n * @defaultValue true\n */\n checkSchemaFiles?: boolean\n /** Called once a resource's schema is located. */\n onResolved?: (resolved: ResolvedResource) => void\n /** Called before a resource's schema is located. */\n onResolving?: (resource: TypeGenResource, key: string) => void\n resources: TypeGenResource[]\n workDir: string\n}): Promise<ResolvedResource[]> {\n const {checkSchemaFiles = true, onResolved, onResolving, resources, workDir} = options\n const resolved: ResolvedResource[] = []\n\n for (const resource of resources) {\n const key = getResourceKey(resource)\n onResolving?.(resource, key)\n let entry: ResolvedResource\n\n if (resource.schema === undefined) {\n try {\n entry = {key, resource, ...(await fetchResourceSchema(resource))}\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to get the schema for \"${key}\": ${message}`, {cause: error})\n }\n } else {\n const schemaPath = isAbsolute(resource.schema)\n ? resource.schema\n : join(workDir, resource.schema)\n if (checkSchemaFiles) {\n await assertExtractedSchemaFile(schemaPath, {key, schema: resource.schema})\n }\n // Codegen reads the file again, so the generated output names it as its source, as it\n // does without resources.\n entry = {key, resource, schemaPath: resource.schema}\n }\n\n resolved.push(entry)\n onResolved?.(entry)\n }\n\n return resolved\n}\n\n/**\n * Generates one output file per resource, registering its types under the resource key.\n */\nexport async function generateResourceTypes(options: {\n /** Settings shared by every resource. */\n config: TypeGenConfig\n /** Returns the progress handler for one resource's generation. */\n onProgress?: (resolved: ResolvedResource) => ((event: TypegenProgressEvent) => void) | undefined\n resolved: ResolvedResource[]\n workDir: string\n}): Promise<{key: string; result: GenerationResult}[]> {\n const {config, onProgress, resolved, workDir} = options\n const results: {key: string; result: GenerationResult}[] = []\n\n for (const entry of resolved) {\n const {dataset, generates, path, projectId} = entry.resource\n let result: GenerationResult\n try {\n result = await runTypegenGenerate({\n config: {\n ...config,\n generates,\n path: path ?? config.path,\n schema: 'schemaPath' in entry ? entry.schemaPath : config.schema,\n },\n extractedSchema: 'schema' in entry ? entry.schema : undefined,\n onProgress: onProgress?.(entry),\n resource: {dataset, projectId},\n workDir,\n })\n } catch (error) {\n // Codegen writes a resource's file once its generation succeeds, so the files of this\n // resource and the ones after it are unchanged. Files of earlier resources are not\n // rolled back.\n const message = error instanceof Error ? error.message : String(error)\n const written = results.map(({key}) => `\"${key}\"`).join(', ')\n throw new Error(\n `Failed to generate types for \"${entry.key}\": ${message}` +\n (written\n ? `\\nTypes for ${written} were updated. The other output files are unchanged.`\n : ''),\n {cause: error},\n )\n }\n results.push({key: entry.key, result})\n }\n\n return results\n}\n\n/**\n * Checks that a local schema file exists and holds an extracted schema, which is a JSON array.\n */\nasync function assertExtractedSchemaFile(\n schemaPath: string,\n {key, schema}: {key: string; schema: string},\n): Promise<void> {\n let content: string\n try {\n content = await readFile(schemaPath, 'utf8')\n } catch {\n throw new Error(\n `Schema file not found for \"${key}\": ${schema}. Run \"sanity schema extract\" to create it.`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Schema file for \"${key}\" is not valid JSON: ${schema}. ${message}`, {\n cause: error,\n })\n }\n\n if (!Array.isArray(parsed)) {\n throw new TypeError(\n `Schema file for \"${key}\" is not an extracted schema: ${schema}. Run \"sanity schema extract\" to recreate it.`,\n )\n }\n}\n"],"names":["readFile","isAbsolute","join","runTypegenGenerate","fetchResourceSchema","getResourceKey","resolveResourceSchemas","options","checkSchemaFiles","onResolved","onResolving","resources","workDir","resolved","resource","key","entry","schema","undefined","error","message","Error","String","cause","schemaPath","assertExtractedSchemaFile","push","generateResourceTypes","config","onProgress","results","dataset","generates","path","projectId","result","extractedSchema","written","map","content","parsed","JSON","parse","Array","isArray","TypeError"],"mappings":"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"}
@@ -0,0 +1,208 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { basename, relative, resolve, sep } from 'node:path';
3
+ import { watch } from 'chokidar';
4
+ import debounce from 'lodash-es/debounce.js';
5
+ import mean from 'lodash-es/mean.js';
6
+ import picomatch from 'picomatch';
7
+ import { toForwardSlashes } from '../../util/toForwardSlashes.js';
8
+ import { generateResourceTypes } from './generateResourceTypes.js';
9
+ /** Delay after the last change before generating again, as in watch mode without resources. */ const DEBOUNCE_MS = 1000;
10
+ /** Directories that are never watched. Codegen's query scan skips `node_modules` as well. */ const IGNORED_DIRECTORIES = new Set([
11
+ '.git',
12
+ 'node_modules'
13
+ ]);
14
+ /** How often to check that every watched base directory and schema file still exists. */ const EXISTENCE_CHECK_MS = 1000;
15
+ /**
16
+ * Generates types for every resource, then generates them again whenever a query file or a local
17
+ * schema file changes.
18
+ *
19
+ * The caller resolves the schemas, once. A schema fetched from a dataset's binding is used until
20
+ * the watcher stops, so a schema deployed while it runs is not picked up until it restarts. A
21
+ * local schema file is read again on every run, and a missing one fails only that resource's
22
+ * generation until the file appears.
23
+ *
24
+ * Each resource generates on its own, so a broken schema or query in one does not hold back the
25
+ * others. Generated files are not watched: outputs usually sit inside the query pattern, such as
26
+ * `./src/sanity.types.production.ts` under `./src/**`, and writing one would start the next run.
27
+ */ export function watchResourceTypes(options) {
28
+ const { config, onChange, onError, onProgress, resolved, workDir } = options;
29
+ const queryMatchers = resolved.map(({ resource })=>createQueryMatcher(resource.path ?? config.path, workDir));
30
+ const schemaFiles = new Set(resolved.flatMap((entry)=>'schemaPath' in entry ? [
31
+ resolve(workDir, entry.schemaPath)
32
+ ] : []));
33
+ // Lowercased, because two names that differ only in case are the same file on the default
34
+ // macOS and Windows file systems.
35
+ const outputFiles = new Set(resolved.map(({ resource })=>resolve(workDir, resource.generates).toLowerCase()));
36
+ const roots = [
37
+ ...new Set([
38
+ ...queryMatchers.flatMap(({ bases })=>bases),
39
+ ...schemaFiles
40
+ ])
41
+ ];
42
+ const startTime = Date.now();
43
+ const successfulDurations = [];
44
+ let failedCount = 0;
45
+ let activeRun;
46
+ let isPending = false;
47
+ let isStopped = false;
48
+ async function generateEach() {
49
+ for (const entry of resolved){
50
+ if (isStopped) return;
51
+ const generationStartTime = Date.now();
52
+ try {
53
+ await generateResourceTypes({
54
+ config,
55
+ onProgress,
56
+ resolved: [
57
+ entry
58
+ ],
59
+ workDir
60
+ });
61
+ successfulDurations.push(Date.now() - generationStartTime);
62
+ } catch (error) {
63
+ failedCount++;
64
+ onError?.(error);
65
+ }
66
+ }
67
+ }
68
+ // Runs one generation at a time. Changes during a run queue a single follow-up run.
69
+ async function runGeneration() {
70
+ if (isStopped) return;
71
+ if (activeRun) {
72
+ isPending = true;
73
+ return;
74
+ }
75
+ isPending = false;
76
+ activeRun = generateEach();
77
+ try {
78
+ await activeRun;
79
+ } finally{
80
+ activeRun = undefined;
81
+ }
82
+ if (isPending) await runGeneration();
83
+ }
84
+ const scheduleGeneration = debounce(()=>void runGeneration(), DEBOUNCE_MS);
85
+ const missingRoots = new Set(roots.filter((root)=>!existsSync(root)));
86
+ const watcher = watch(roots.filter((root)=>!missingRoots.has(root)), {
87
+ ignored: (path)=>IGNORED_DIRECTORIES.has(basename(path)) || outputFiles.has(resolve(path).toLowerCase()),
88
+ ignoreInitial: true
89
+ });
90
+ // chokidar stops watching a path once it is removed, as when switching git branches, and does
91
+ // not reliably notice it come back. A watch set up while its directory is removed and created
92
+ // again also fails without an error. So the watcher checks every root's existence itself. A root
93
+ // that is missing is watched again once it exists, and once more on the next check in case that
94
+ // watch failed. Every resource is then generated, which also picks up files written before the
95
+ // new watch was ready.
96
+ let addedOnLastCheck = [];
97
+ const existenceCheck = setInterval(()=>{
98
+ const added = [];
99
+ for (const root of roots){
100
+ if (!existsSync(root)) {
101
+ missingRoots.add(root);
102
+ } else if (missingRoots.delete(root)) {
103
+ watcher.add(root);
104
+ added.push(root);
105
+ onChange?.('add', toForwardSlashes(relative(workDir, root)));
106
+ scheduleGeneration();
107
+ } else if (addedOnLastCheck.includes(root)) {
108
+ watcher.add(root);
109
+ }
110
+ }
111
+ addedOnLastCheck = added;
112
+ }, EXISTENCE_CHECK_MS);
113
+ watcher.on('all', (event, filePath)=>{
114
+ const absolutePath = resolve(workDir, filePath);
115
+ // A root removed and created again between two existence checks is watched again too.
116
+ if (event === 'unlink' || event === 'unlinkDir') {
117
+ for (const root of roots){
118
+ if (root === absolutePath || root.startsWith(absolutePath + sep)) {
119
+ missingRoots.add(root);
120
+ }
121
+ }
122
+ }
123
+ if (event !== 'add' && event !== 'change' && event !== 'unlink') return;
124
+ if (outputFiles.has(absolutePath.toLowerCase())) return;
125
+ const relativePath = toForwardSlashes(relative(workDir, absolutePath));
126
+ if (!schemaFiles.has(absolutePath) && !queryMatchers.some(({ isMatch })=>isMatch(relativePath))) {
127
+ return;
128
+ }
129
+ onChange?.(event, relativePath);
130
+ scheduleGeneration();
131
+ });
132
+ watcher.on('error', (error)=>{
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ onError?.(new Error(`Watcher error: ${message}`, {
135
+ cause: error
136
+ }));
137
+ });
138
+ void runGeneration();
139
+ return {
140
+ getStats: ()=>({
141
+ averageGenerationDuration: mean(successfulDurations) || 0,
142
+ generationFailedCount: failedCount,
143
+ generationSuccessfulCount: successfulDurations.length,
144
+ watcherDuration: Date.now() - startTime
145
+ }),
146
+ stop: async ()=>{
147
+ isStopped = true;
148
+ clearInterval(existenceCheck);
149
+ scheduleGeneration.cancel();
150
+ await watcher.close();
151
+ await activeRun;
152
+ }
153
+ };
154
+ }
155
+ /**
156
+ * Matches paths relative to `workDir` against one resource's query patterns, as codegen's query
157
+ * scan reads them with globby:
158
+ *
159
+ * - A pattern starting with `!` excludes files from the patterns listed before it.
160
+ * - A pattern without glob characters names a file or a directory, and a directory includes or
161
+ * excludes everything inside it. `.` names `workDir`.
162
+ *
163
+ * chokidar 4 and later do not expand globs, so `bases` holds each pattern's static base directory
164
+ * to watch.
165
+ */ function createQueryMatcher(path, workDir) {
166
+ const includes = [];
167
+ for (const pattern of Array.isArray(path) ? path : [
168
+ path
169
+ ]){
170
+ const isExclude = pattern.startsWith('!');
171
+ const body = isExclude ? pattern.slice(1) : pattern;
172
+ if (!body) continue;
173
+ const patterns = normalizePattern(body, workDir);
174
+ if (isExclude) {
175
+ for (const include of includes)include.excludes.push(...patterns);
176
+ } else {
177
+ includes.push({
178
+ excludes: [],
179
+ patterns
180
+ });
181
+ }
182
+ }
183
+ const matchers = includes.map(({ excludes, patterns })=>picomatch(patterns, excludes.length > 0 ? {
184
+ ignore: excludes
185
+ } : {}));
186
+ return {
187
+ bases: includes.map(({ patterns })=>resolve(workDir, picomatch.scan(patterns[0]).base)),
188
+ isMatch: (relativePath)=>matchers.some((isMatch)=>isMatch(relativePath))
189
+ };
190
+ }
191
+ /**
192
+ * Returns picomatch patterns relative to `workDir` with forward slashes, with `.` and `..`
193
+ * segments resolved, to compare with event paths. A pattern without glob characters also matches
194
+ * everything inside it.
195
+ */ function normalizePattern(pattern, workDir) {
196
+ const normalized = toForwardSlashes(relative(workDir, resolve(workDir, pattern)));
197
+ if (!normalized) return [
198
+ '**'
199
+ ];
200
+ return picomatch.scan(normalized).isGlob ? [
201
+ normalized
202
+ ] : [
203
+ normalized,
204
+ `${normalized}/**`
205
+ ];
206
+ }
207
+
208
+ //# sourceMappingURL=watchResourceTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/typegen/watchResourceTypes.ts"],"sourcesContent":["import {existsSync} from 'node:fs'\nimport {basename, relative, resolve, sep} from 'node:path'\n\nimport {type TypeGenConfig, type TypegenProgressEvent} from '@sanity/codegen'\nimport {watch} from 'chokidar'\nimport debounce from 'lodash-es/debounce.js'\nimport mean from 'lodash-es/mean.js'\nimport picomatch from 'picomatch'\n\nimport {toForwardSlashes} from '../../util/toForwardSlashes.js'\nimport {generateResourceTypes, type ResolvedResource} from './generateResourceTypes.js'\n\n/** Delay after the last change before generating again, as in watch mode without resources. */\nconst DEBOUNCE_MS = 1000\n\n/** Directories that are never watched. Codegen's query scan skips `node_modules` as well. */\nconst IGNORED_DIRECTORIES = new Set(['.git', 'node_modules'])\n\n/** How often to check that every watched base directory and schema file still exists. */\nconst EXISTENCE_CHECK_MS = 1000\n\n/**\n * A running watcher started by {@link watchResourceTypes}.\n */\nexport interface ResourceTypesWatcher {\n /** Statistics for the watch mode trace, counting each resource's generation. */\n getStats: () => {\n averageGenerationDuration: number\n generationFailedCount: number\n generationSuccessfulCount: number\n watcherDuration: number\n }\n /** Stops watching. Resolves once the resource generating at the time, if any, is written. */\n stop: () => Promise<void>\n}\n\n/**\n * Generates types for every resource, then generates them again whenever a query file or a local\n * schema file changes.\n *\n * The caller resolves the schemas, once. A schema fetched from a dataset's binding is used until\n * the watcher stops, so a schema deployed while it runs is not picked up until it restarts. A\n * local schema file is read again on every run, and a missing one fails only that resource's\n * generation until the file appears.\n *\n * Each resource generates on its own, so a broken schema or query in one does not hold back the\n * others. Generated files are not watched: outputs usually sit inside the query pattern, such as\n * `./src/sanity.types.production.ts` under `./src/**`, and writing one would start the next run.\n */\nexport function watchResourceTypes(options: {\n /** Settings shared by every resource. */\n config: TypeGenConfig\n /** Called for each change that schedules a run, with the path relative to `workDir`. */\n onChange?: (event: string, relativePath: string) => void\n /** Called when a resource's generation or the file watcher fails. The watcher keeps running. */\n onError?: (error: unknown) => void\n /** Returns the progress handler for one resource's generation. */\n onProgress?: (resolved: ResolvedResource) => ((event: TypegenProgressEvent) => void) | undefined\n resolved: ResolvedResource[]\n workDir: string\n}): ResourceTypesWatcher {\n const {config, onChange, onError, onProgress, resolved, workDir} = options\n\n const queryMatchers = resolved.map(({resource}) =>\n createQueryMatcher(resource.path ?? config.path, workDir),\n )\n const schemaFiles = new Set(\n resolved.flatMap((entry) =>\n 'schemaPath' in entry ? [resolve(workDir, entry.schemaPath)] : [],\n ),\n )\n // Lowercased, because two names that differ only in case are the same file on the default\n // macOS and Windows file systems.\n const outputFiles = new Set(\n resolved.map(({resource}) => resolve(workDir, resource.generates).toLowerCase()),\n )\n const roots = [...new Set([...queryMatchers.flatMap(({bases}) => bases), ...schemaFiles])]\n\n const startTime = Date.now()\n const successfulDurations: number[] = []\n let failedCount = 0\n let activeRun: Promise<void> | undefined\n let isPending = false\n let isStopped = false\n\n async function generateEach(): Promise<void> {\n for (const entry of resolved) {\n if (isStopped) return\n const generationStartTime = Date.now()\n try {\n await generateResourceTypes({config, onProgress, resolved: [entry], workDir})\n successfulDurations.push(Date.now() - generationStartTime)\n } catch (error) {\n failedCount++\n onError?.(error)\n }\n }\n }\n\n // Runs one generation at a time. Changes during a run queue a single follow-up run.\n async function runGeneration(): Promise<void> {\n if (isStopped) return\n if (activeRun) {\n isPending = true\n return\n }\n\n isPending = false\n activeRun = generateEach()\n try {\n await activeRun\n } finally {\n activeRun = undefined\n }\n\n if (isPending) await runGeneration()\n }\n\n const scheduleGeneration = debounce(() => void runGeneration(), DEBOUNCE_MS)\n\n const missingRoots = new Set(roots.filter((root) => !existsSync(root)))\n\n const watcher = watch(\n roots.filter((root) => !missingRoots.has(root)),\n {\n ignored: (path) =>\n IGNORED_DIRECTORIES.has(basename(path)) || outputFiles.has(resolve(path).toLowerCase()),\n ignoreInitial: true,\n },\n )\n\n // chokidar stops watching a path once it is removed, as when switching git branches, and does\n // not reliably notice it come back. A watch set up while its directory is removed and created\n // again also fails without an error. So the watcher checks every root's existence itself. A root\n // that is missing is watched again once it exists, and once more on the next check in case that\n // watch failed. Every resource is then generated, which also picks up files written before the\n // new watch was ready.\n let addedOnLastCheck: string[] = []\n const existenceCheck = setInterval(() => {\n const added: string[] = []\n for (const root of roots) {\n if (!existsSync(root)) {\n missingRoots.add(root)\n } else if (missingRoots.delete(root)) {\n watcher.add(root)\n added.push(root)\n onChange?.('add', toForwardSlashes(relative(workDir, root)))\n scheduleGeneration()\n } else if (addedOnLastCheck.includes(root)) {\n watcher.add(root)\n }\n }\n addedOnLastCheck = added\n }, EXISTENCE_CHECK_MS)\n\n watcher.on('all', (event, filePath) => {\n const absolutePath = resolve(workDir, filePath)\n\n // A root removed and created again between two existence checks is watched again too.\n if (event === 'unlink' || event === 'unlinkDir') {\n for (const root of roots) {\n if (root === absolutePath || root.startsWith(absolutePath + sep)) {\n missingRoots.add(root)\n }\n }\n }\n\n if (event !== 'add' && event !== 'change' && event !== 'unlink') return\n if (outputFiles.has(absolutePath.toLowerCase())) return\n\n const relativePath = toForwardSlashes(relative(workDir, absolutePath))\n if (\n !schemaFiles.has(absolutePath) &&\n !queryMatchers.some(({isMatch}) => isMatch(relativePath))\n ) {\n return\n }\n\n onChange?.(event, relativePath)\n scheduleGeneration()\n })\n\n watcher.on('error', (error) => {\n const message = error instanceof Error ? error.message : String(error)\n onError?.(new Error(`Watcher error: ${message}`, {cause: error}))\n })\n\n void runGeneration()\n\n return {\n getStats: () => ({\n averageGenerationDuration: mean(successfulDurations) || 0,\n generationFailedCount: failedCount,\n generationSuccessfulCount: successfulDurations.length,\n watcherDuration: Date.now() - startTime,\n }),\n stop: async () => {\n isStopped = true\n clearInterval(existenceCheck)\n scheduleGeneration.cancel()\n await watcher.close()\n await activeRun\n },\n }\n}\n\n/**\n * Matches paths relative to `workDir` against one resource's query patterns, as codegen's query\n * scan reads them with globby:\n *\n * - A pattern starting with `!` excludes files from the patterns listed before it.\n * - A pattern without glob characters names a file or a directory, and a directory includes or\n * excludes everything inside it. `.` names `workDir`.\n *\n * chokidar 4 and later do not expand globs, so `bases` holds each pattern's static base directory\n * to watch.\n */\nfunction createQueryMatcher(\n path: string | string[],\n workDir: string,\n): {bases: string[]; isMatch: (relativePath: string) => boolean} {\n const includes: {excludes: string[]; patterns: string[]}[] = []\n for (const pattern of Array.isArray(path) ? path : [path]) {\n const isExclude = pattern.startsWith('!')\n const body = isExclude ? pattern.slice(1) : pattern\n if (!body) continue\n const patterns = normalizePattern(body, workDir)\n if (isExclude) {\n for (const include of includes) include.excludes.push(...patterns)\n } else {\n includes.push({excludes: [], patterns})\n }\n }\n\n const matchers = includes.map(({excludes, patterns}) =>\n picomatch(patterns, excludes.length > 0 ? {ignore: excludes} : {}),\n )\n return {\n bases: includes.map(({patterns}) => resolve(workDir, picomatch.scan(patterns[0]).base)),\n isMatch: (relativePath) => matchers.some((isMatch) => isMatch(relativePath)),\n }\n}\n\n/**\n * Returns picomatch patterns relative to `workDir` with forward slashes, with `.` and `..`\n * segments resolved, to compare with event paths. A pattern without glob characters also matches\n * everything inside it.\n */\nfunction normalizePattern(pattern: string, workDir: string): string[] {\n const normalized = toForwardSlashes(relative(workDir, resolve(workDir, pattern)))\n if (!normalized) return ['**']\n return picomatch.scan(normalized).isGlob ? [normalized] : [normalized, `${normalized}/**`]\n}\n"],"names":["existsSync","basename","relative","resolve","sep","watch","debounce","mean","picomatch","toForwardSlashes","generateResourceTypes","DEBOUNCE_MS","IGNORED_DIRECTORIES","Set","EXISTENCE_CHECK_MS","watchResourceTypes","options","config","onChange","onError","onProgress","resolved","workDir","queryMatchers","map","resource","createQueryMatcher","path","schemaFiles","flatMap","entry","schemaPath","outputFiles","generates","toLowerCase","roots","bases","startTime","Date","now","successfulDurations","failedCount","activeRun","isPending","isStopped","generateEach","generationStartTime","push","error","runGeneration","undefined","scheduleGeneration","missingRoots","filter","root","watcher","has","ignored","ignoreInitial","addedOnLastCheck","existenceCheck","setInterval","added","add","delete","includes","on","event","filePath","absolutePath","startsWith","relativePath","some","isMatch","message","Error","String","cause","getStats","averageGenerationDuration","generationFailedCount","generationSuccessfulCount","length","watcherDuration","stop","clearInterval","cancel","close","pattern","Array","isArray","isExclude","body","slice","patterns","normalizePattern","include","excludes","matchers","ignore","scan","base","normalized","isGlob"],"mappings":"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"}