@sanity/cli-test 0.0.0-20260410130107

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -0
  3. package/dist/index.d.ts +531 -0
  4. package/dist/index.js +13 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/test/captureOutput.js +83 -0
  7. package/dist/test/captureOutput.js.map +1 -0
  8. package/dist/test/constants.js +24 -0
  9. package/dist/test/constants.js.map +1 -0
  10. package/dist/test/createTestClient.js +53 -0
  11. package/dist/test/createTestClient.js.map +1 -0
  12. package/dist/test/createTestToken.js +13 -0
  13. package/dist/test/createTestToken.js.map +1 -0
  14. package/dist/test/mockApi.js +16 -0
  15. package/dist/test/mockApi.js.map +1 -0
  16. package/dist/test/mockSanityCommand.js +71 -0
  17. package/dist/test/mockSanityCommand.js.map +1 -0
  18. package/dist/test/mockTelemetry.js +24 -0
  19. package/dist/test/mockTelemetry.js.map +1 -0
  20. package/dist/test/setupFixtures.js +140 -0
  21. package/dist/test/setupFixtures.js.map +1 -0
  22. package/dist/test/snapshotSerializer.js +12 -0
  23. package/dist/test/snapshotSerializer.js.map +1 -0
  24. package/dist/test/testCommand.js +19 -0
  25. package/dist/test/testCommand.js.map +1 -0
  26. package/dist/test/testFixture.js +112 -0
  27. package/dist/test/testFixture.js.map +1 -0
  28. package/dist/test/testHook.js +43 -0
  29. package/dist/test/testHook.js.map +1 -0
  30. package/dist/utils/fileExists.js +13 -0
  31. package/dist/utils/fileExists.js.map +1 -0
  32. package/dist/utils/paths.js +66 -0
  33. package/dist/utils/paths.js.map +1 -0
  34. package/dist/vitest.d.ts +121 -0
  35. package/dist/vitest.js +22 -0
  36. package/dist/vitest.js.map +1 -0
  37. package/dist/vitestWorker.js +135 -0
  38. package/dist/vitestWorker.js.map +1 -0
  39. package/fixtures/basic-app/package.json +25 -0
  40. package/fixtures/basic-app/sanity.cli.ts +12 -0
  41. package/fixtures/basic-app/src/App.css +20 -0
  42. package/fixtures/basic-app/src/App.tsx +26 -0
  43. package/fixtures/basic-app/src/ExampleComponent.css +84 -0
  44. package/fixtures/basic-app/src/ExampleComponent.tsx +38 -0
  45. package/fixtures/basic-app/tsconfig.json +17 -0
  46. package/fixtures/basic-functions/functions/test-function/index.ts +7 -0
  47. package/fixtures/basic-functions/package.json +14 -0
  48. package/fixtures/basic-functions/sanity.blueprint.ts +5 -0
  49. package/fixtures/basic-studio/package.json +28 -0
  50. package/fixtures/basic-studio/sanity.cli.ts +11 -0
  51. package/fixtures/basic-studio/sanity.config.ts +18 -0
  52. package/fixtures/basic-studio/schemaTypes/author.ts +52 -0
  53. package/fixtures/basic-studio/schemaTypes/blockContent.ts +71 -0
  54. package/fixtures/basic-studio/schemaTypes/category.ts +20 -0
  55. package/fixtures/basic-studio/schemaTypes/index.ts +6 -0
  56. package/fixtures/basic-studio/schemaTypes/post.ts +67 -0
  57. package/fixtures/basic-studio/tsconfig.json +17 -0
  58. package/fixtures/federated-studio/package.json +24 -0
  59. package/fixtures/federated-studio/sanity.cli.ts +14 -0
  60. package/fixtures/federated-studio/sanity.config.ts +17 -0
  61. package/fixtures/federated-studio/schemaTypes/index.ts +1 -0
  62. package/fixtures/federated-studio/tsconfig.json +17 -0
  63. package/fixtures/graphql-studio/package.json +25 -0
  64. package/fixtures/graphql-studio/sanity.cli.ts +20 -0
  65. package/fixtures/graphql-studio/sanity.config.ts +37 -0
  66. package/fixtures/graphql-studio/schemaTypes/author.ts +52 -0
  67. package/fixtures/graphql-studio/schemaTypes/blockContent.ts +71 -0
  68. package/fixtures/graphql-studio/schemaTypes/category.ts +20 -0
  69. package/fixtures/graphql-studio/schemaTypes/event.ts +25 -0
  70. package/fixtures/graphql-studio/schemaTypes/index.ts +10 -0
  71. package/fixtures/graphql-studio/schemaTypes/post.ts +67 -0
  72. package/fixtures/graphql-studio/tsconfig.json +17 -0
  73. package/fixtures/multi-workspace-studio/package.json +28 -0
  74. package/fixtures/multi-workspace-studio/sanity.cli.ts +11 -0
  75. package/fixtures/multi-workspace-studio/sanity.config.ts +37 -0
  76. package/fixtures/multi-workspace-studio/schemaTypes/author.ts +52 -0
  77. package/fixtures/multi-workspace-studio/schemaTypes/blockContent.ts +70 -0
  78. package/fixtures/multi-workspace-studio/schemaTypes/category.ts +20 -0
  79. package/fixtures/multi-workspace-studio/schemaTypes/index.ts +6 -0
  80. package/fixtures/multi-workspace-studio/schemaTypes/post.ts +67 -0
  81. package/fixtures/multi-workspace-studio/tsconfig.json +17 -0
  82. package/fixtures/prebuilt-app/README.md +3 -0
  83. package/fixtures/prebuilt-app/dist/favicon.ico +0 -0
  84. package/fixtures/prebuilt-app/dist/index.html +102 -0
  85. package/fixtures/prebuilt-app/dist/static/sanity-CtOxKsdo.css +24 -0
  86. package/fixtures/prebuilt-app/dist/static/sanity-D4a4eOYZ.js +17 -0
  87. package/fixtures/prebuilt-app/package.json +25 -0
  88. package/fixtures/prebuilt-app/sanity.cli.ts +12 -0
  89. package/fixtures/prebuilt-app/src/App.css +20 -0
  90. package/fixtures/prebuilt-app/src/App.tsx +24 -0
  91. package/fixtures/prebuilt-app/tsconfig.json +17 -0
  92. package/fixtures/prebuilt-studio/README.md +3 -0
  93. package/fixtures/prebuilt-studio/dist/favicon.ico +0 -0
  94. package/fixtures/prebuilt-studio/dist/index.html +113 -0
  95. package/fixtures/prebuilt-studio/dist/static/sanity-DxH-rpFr.js +9 -0
  96. package/fixtures/prebuilt-studio/package.json +25 -0
  97. package/fixtures/prebuilt-studio/sanity.cli.ts +11 -0
  98. package/fixtures/prebuilt-studio/sanity.config.ts +11 -0
  99. package/fixtures/prebuilt-studio/tsconfig.json +17 -0
  100. package/fixtures/worst-case-studio/.env +1 -0
  101. package/fixtures/worst-case-studio/README.md +22 -0
  102. package/fixtures/worst-case-studio/fieldComponentsTest.tsx +80 -0
  103. package/fixtures/worst-case-studio/package.json +33 -0
  104. package/fixtures/worst-case-studio/sanity.cli.ts +16 -0
  105. package/fixtures/worst-case-studio/sanity.config.tsx +55 -0
  106. package/fixtures/worst-case-studio/src/browserGlobalsCheck.ts +14 -0
  107. package/fixtures/worst-case-studio/src/defines.ts +8 -0
  108. package/fixtures/worst-case-studio/src/descriptionIcon.svg +7 -0
  109. package/fixtures/worst-case-studio/src/descriptionInput.module.css +13 -0
  110. package/fixtures/worst-case-studio/src/descriptionInput.tsx +55 -0
  111. package/fixtures/worst-case-studio/src/schemaTypes/author.ts +52 -0
  112. package/fixtures/worst-case-studio/src/schemaTypes/blockContent.ts +70 -0
  113. package/fixtures/worst-case-studio/src/schemaTypes/category.ts +20 -0
  114. package/fixtures/worst-case-studio/src/schemaTypes/index.ts +6 -0
  115. package/fixtures/worst-case-studio/src/schemaTypes/post.ts +71 -0
  116. package/fixtures/worst-case-studio/src/typings.d.ts +37 -0
  117. package/fixtures/worst-case-studio/tsconfig.json +22 -0
  118. package/package.json +97 -0
@@ -0,0 +1,531 @@
1
+ import { CliConfig } from "@sanity/cli-core";
2
+ import { ClientConfig } from "@sanity/client";
3
+ import { CLITelemetryStore } from "@sanity/cli-core";
4
+ import { Command } from "@oclif/core";
5
+ import { Config } from "@oclif/core";
6
+ import { Errors } from "@oclif/core";
7
+ import { Hook } from "@oclif/core/hooks";
8
+ import { Hooks } from "@oclif/core/hooks";
9
+ import nock from "nock";
10
+ import { ProjectRootResult } from "@sanity/cli-core";
11
+ import { SanityClient } from "@sanity/client";
12
+ import { SanityCommand } from "@sanity/cli-core";
13
+ import { TestProject } from "vitest/node";
14
+ import { vi } from "vitest";
15
+
16
+ declare interface CaptureOptions {
17
+ /**
18
+ * Whether to print the output to the console
19
+ */
20
+ print?: boolean;
21
+ /**
22
+ * Whether to strip ANSI escape codes from the output
23
+ */
24
+ stripAnsi?: boolean;
25
+ testNodeEnv?: string;
26
+ }
27
+
28
+ declare interface CaptureResult<T = unknown> {
29
+ stderr: string;
30
+ stdout: string;
31
+ error?: Error & Partial<Errors.CLIError>;
32
+ result?: T;
33
+ }
34
+
35
+ declare type CommandClass = (new (argv: string[], config: Config) => Command) &
36
+ typeof Command;
37
+
38
+ /**
39
+ * Converts Unix-style paths to platform-appropriate paths.
40
+ * On Windows:
41
+ * - Absolute paths starting with '/': adds drive letter and converts to backslashes
42
+ * - Relative/partial paths: converts forward slashes to backslashes
43
+ * On Unix: keeps paths as-is.
44
+ *
45
+ * @param pathStr - Unix-style path (e.g., '/test/path' or '.config/file.json')
46
+ * @returns Platform-appropriate path
47
+ * @internal
48
+ */
49
+ export declare function convertToSystemPath(pathStr: string): string;
50
+
51
+ /**
52
+ * Creates a real Sanity client instance for testing that makes actual HTTP requests.
53
+ * Use with mockApi() to intercept and mock the HTTP calls.
54
+ *
55
+ * @public
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Mock getGlobalCliClient to return a test client
60
+ * vi.mock('@sanity/cli-core', async (importOriginal) => {
61
+ * const actual = await importOriginal<typeof import('@sanity/cli-core')>()
62
+ * const {createTestClient} = await import('@sanity/cli-test')
63
+ *
64
+ * return {
65
+ * ...actual,
66
+ * getGlobalCliClient: vi.fn().mockImplementation((opts) => {
67
+ * return Promise.resolve(createTestClient({
68
+ * apiVersion: opts.apiVersion,
69
+ * }))
70
+ * }),
71
+ * }
72
+ * })
73
+ *
74
+ * // Then use mockApi to intercept requests
75
+ * mockApi({
76
+ * apiVersion: 'v2025-02-19',
77
+ * method: 'get',
78
+ * uri: '/media-libraries',
79
+ * }).reply(200, {data: [...]})
80
+ * ```
81
+ */
82
+ export declare function createTestClient(options: CreateTestClientOptions): {
83
+ client: SanityClient;
84
+ request: ReturnType<typeof vi.fn>;
85
+ };
86
+
87
+ /**
88
+ * Options for createTestClient
89
+ *
90
+ * @public
91
+ */
92
+ export declare interface CreateTestClientOptions extends ClientConfig {
93
+ /**
94
+ * API version for the client
95
+ */
96
+ apiVersion: string;
97
+ /**
98
+ * Authentication token
99
+ */
100
+ token?: string;
101
+ }
102
+
103
+ /**
104
+ * Creates a test token for the Sanity CLI
105
+ *
106
+ * @public
107
+ *
108
+ * @param token - The token to create
109
+ * @returns void
110
+ */
111
+ export declare function createTestToken(token: string): void;
112
+
113
+ /**
114
+ * Default fixtures bundled with the package and their options.
115
+ *
116
+ * @public
117
+ */
118
+ export declare const DEFAULT_FIXTURES: Record<FixtureName, FixtureOptions>;
119
+
120
+ /**
121
+ * @deprecated Use {@link FixtureName} instead. This type alias will be removed in a future release.
122
+ * @public
123
+ */
124
+ export declare type ExampleName = FixtureName;
125
+
126
+ /**
127
+ * Valid fixture name type.
128
+ * @public
129
+ */
130
+ export declare type FixtureName =
131
+ | "basic-app"
132
+ | "basic-functions"
133
+ | "basic-studio"
134
+ | "federated-studio"
135
+ | "graphql-studio"
136
+ | "multi-workspace-studio"
137
+ | "prebuilt-app"
138
+ | "prebuilt-studio"
139
+ | "worst-case-studio";
140
+
141
+ /**
142
+ * Options for each fixture.
143
+ * @public
144
+ */
145
+ export declare interface FixtureOptions {
146
+ includeDist?: boolean;
147
+ }
148
+
149
+ /**
150
+ * Gets the current Windows drive letter from process.cwd().
151
+ * Falls back to 'C:\\' if detection fails.
152
+ *
153
+ * @returns Drive letter with backslash (e.g., 'C:\\', 'D:\\') or empty string on Unix
154
+ * @internal
155
+ */
156
+ export declare function getCurrentDrive(): string;
157
+
158
+ /**
159
+ * Gets the path to the fixtures directory bundled with this package.
160
+ *
161
+ * The fixtures are copied during build and bundled with the published package.
162
+ * This function works the same whether the package is used in a monorepo
163
+ * or installed from npm.
164
+ *
165
+ * @returns Absolute path to the fixtures directory
166
+ * @internal
167
+ */
168
+ export declare function getFixturesPath(): string;
169
+
170
+ /**
171
+ * Gets the path to the temporary directory for test fixtures.
172
+ *
173
+ * Uses the initial working directory captured when this module was first loaded,
174
+ * not process.cwd() which may change during test execution.
175
+ *
176
+ * @param customTempDir - Optional custom temp directory path
177
+ * @returns Absolute path to temp directory (default: initial cwd/tmp)
178
+ * @internal
179
+ */
180
+ export declare function getTempPath(customTempDir?: string): string;
181
+
182
+ /**
183
+ * Mocks the API calls, add some defaults so it doesn't cause too much friction
184
+ *
185
+ * @internal
186
+ */
187
+ export declare function mockApi({
188
+ apiHost,
189
+ apiVersion,
190
+ includeQueryTag,
191
+ method,
192
+ projectId,
193
+ query,
194
+ uri,
195
+ }: MockApiOptions): nock.Interceptor;
196
+
197
+ /**
198
+ * @internal
199
+ */
200
+ export declare interface MockApiOptions {
201
+ /**
202
+ * Uri to mock
203
+ */
204
+ uri: string;
205
+ /**
206
+ * Api host to mock, defaults to `https://api.sanity.io`
207
+ */
208
+ apiHost?: string;
209
+ /**
210
+ * Api version to mock, defaults to `v2025-05-14`
211
+ */
212
+ apiVersion?: string;
213
+ /**
214
+ * Whether to include `tag: 'sanity.cli'` in query parameters.
215
+ * Defaults to `true`. Set to `false` for endpoints that don't use CLI tagging.
216
+ */
217
+ includeQueryTag?: boolean;
218
+ /**
219
+ * HTTP method to mock
220
+ *
221
+ * Defaults to 'get'
222
+ */
223
+ method?: "delete" | "get" | "patch" | "post" | "put";
224
+ /**
225
+ * Project ID to mock. When provided, constructs apiHost as `https://{projectId}.api.sanity.io`
226
+ * Takes precedence over apiHost if both are provided.
227
+ */
228
+ projectId?: string;
229
+ /**
230
+ * Query parameters to mock
231
+ */
232
+ query?: Record<string, string>;
233
+ }
234
+
235
+ /**
236
+ * Creates a testable subclass of a command with mocked SanityCommand dependencies.
237
+ *
238
+ * @public
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * // Basic config mocking
243
+ * const TestAdd = mockSanityCommand(Add, {
244
+ * cliConfig: { api: { projectId: 'test-project' } }
245
+ * })
246
+ *
247
+ * // With mock API client
248
+ * const mockClient = {
249
+ * getDocument: vi.fn().mockResolvedValue({ _id: 'doc1', title: 'Test' }),
250
+ * fetch: vi.fn().mockResolvedValue([]),
251
+ * }
252
+ * const TestGet = mockSanityCommand(GetDocumentCommand, {
253
+ * cliConfig: { api: { projectId: 'test-project', dataset: 'production' } },
254
+ * projectApiClient: mockClient,
255
+ * })
256
+ *
257
+ * const {stdout} = await testCommand(TestGet, ['doc1'])
258
+ * expect(mockClient.getDocument).toHaveBeenCalledWith('doc1')
259
+ * ```
260
+ */
261
+ export declare function mockSanityCommand<
262
+ T extends typeof SanityCommand<typeof Command>,
263
+ >(CommandClass: T, options?: MockSanityCommandOptions): T;
264
+
265
+ /**
266
+ * @public
267
+ */
268
+ export declare interface MockSanityCommandOptions {
269
+ /**
270
+ * Mock CLI config (required if command uses getCliConfig or getProjectId)
271
+ */
272
+ cliConfig?: CliConfig;
273
+ /**
274
+ * When provided, getCliConfig() will throw this error instead of returning a config.
275
+ * Useful for simulating running outside a project directory.
276
+ */
277
+ cliConfigError?: Error;
278
+ /**
279
+ * Mock whether the terminal is interactive (used by isUnattended)
280
+ */
281
+ isInteractive?: boolean;
282
+ /**
283
+ * Mock project root result (required if command uses getProjectRoot)
284
+ */
285
+ projectRoot?: ProjectRootResult;
286
+ /**
287
+ * Mock authentication token (passed to API clients, bypasses getCliToken)
288
+ */
289
+ token?: string;
290
+ }
291
+
292
+ /**
293
+ * @public
294
+ * @param options - Options for mocking the telemetry store.
295
+ * @returns The mocked telemetry store.
296
+ */
297
+ export declare const mockTelemetry: (
298
+ options?: MockTelemetryOptions,
299
+ ) => CLITelemetryStore;
300
+
301
+ /**
302
+ * @public
303
+ */
304
+ export declare interface MockTelemetryOptions {
305
+ trace?: () => void;
306
+ updateUserProperties?: () => void;
307
+ }
308
+
309
+ declare interface Options {
310
+ config: Config;
311
+ Command?: Command.Class;
312
+ context?: Hook.Context;
313
+ }
314
+
315
+ /**
316
+ * Global setup function for initializing test fixtures.
317
+ *
318
+ * Copies fixtures from the bundled location to a temp directory
319
+ * and installs dependencies.
320
+ *
321
+ * Note: Fixtures are NOT built during setup. Tests that need built
322
+ * fixtures should build them as part of the test.
323
+ *
324
+ * This function is designed to be used with vitest globalSetup.
325
+ *
326
+ * @public
327
+ *
328
+ * @param options - Configuration options
329
+ * @example
330
+ * ```typescript
331
+ * // In vitest.config.ts
332
+ * export default defineConfig({
333
+ * test: {
334
+ * globalSetup: ['@sanity/cli-test/vitest']
335
+ * }
336
+ * })
337
+ * ```
338
+ */
339
+ export declare function setup(
340
+ _: TestProject,
341
+ options?: SetupTestFixturesOptions,
342
+ ): Promise<void>;
343
+
344
+ /**
345
+ * Options for setupTestFixtures
346
+ *
347
+ * @public
348
+ */
349
+ export declare interface SetupTestFixturesOptions {
350
+ /**
351
+ * Glob patterns for additional fixture directories to set up.
352
+ *
353
+ * Each pattern is matched against directories in the current working directory.
354
+ * Only directories containing a `package.json` file are included.
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * ['fixtures/*', 'dev/*']
359
+ * ```
360
+ */
361
+ additionalFixtures?: string[];
362
+ /**
363
+ * Custom temp directory path. Defaults to process.cwd()/tmp
364
+ */
365
+ tempDir?: string;
366
+ }
367
+
368
+ /**
369
+ * Teardown function to clean up test fixtures.
370
+ *
371
+ * Removes the temp directory created by setupTestFixtures.
372
+ *
373
+ * This function is designed to be used with vitest globalSetup.
374
+ *
375
+ * @public
376
+ *
377
+ * @param options - Configuration options
378
+ */
379
+ export declare function teardown(
380
+ options?: TeardownTestFixturesOptions,
381
+ ): Promise<void>;
382
+
383
+ /**
384
+ * Options for teardownTestFixtures
385
+ *
386
+ * @public
387
+ */
388
+ export declare interface TeardownTestFixturesOptions {
389
+ /**
390
+ * Custom temp directory path. Defaults to process.cwd()/tmp
391
+ */
392
+ tempDir?: string;
393
+ }
394
+
395
+ /**
396
+ * @public
397
+ */
398
+ export declare function testCommand(
399
+ command: CommandClass,
400
+ args?: string[],
401
+ options?: TestCommandOptions,
402
+ ): Promise<CaptureResult<unknown>>;
403
+
404
+ /**
405
+ * @public
406
+ */
407
+ export declare interface TestCommandOptions {
408
+ /**
409
+ * Options for capturing output
410
+ */
411
+ capture?: CaptureOptions;
412
+ /**
413
+ * Partial oclif config overrides
414
+ */
415
+ config?: Partial<Config>;
416
+ /**
417
+ * Mock options for SanityCommand dependencies (config, project root, API clients).
418
+ * When provided, the command is automatically wrapped with mockSanityCommand.
419
+ */
420
+ mocks?: MockSanityCommandOptions & MockTelemetryOptions;
421
+ }
422
+
423
+ /**
424
+ * Recursively copy a directory, skipping specified folders.
425
+ *
426
+ * @param srcDir - Source directory to copy from
427
+ * @param destDir - Destination directory to copy to
428
+ * @param skip - Array of directory/file names to skip (e.g., ['node_modules', 'dist'])
429
+ * @internal
430
+ */
431
+ export declare function testCopyDirectory(
432
+ srcDir: string,
433
+ destDir: string,
434
+ skip?: string[],
435
+ ): Promise<void>;
436
+
437
+ /**
438
+ * @deprecated Use {@link testFixture} instead. This function will be removed in a future release.
439
+ *
440
+ * Clones an example (now called fixture) directory into a temporary directory with an isolated copy.
441
+ *
442
+ * @param exampleName - The name of the example/fixture to clone (e.g., 'basic-app', 'basic-studio')
443
+ * @param options - Configuration options
444
+ * @returns The absolute path to the temporary directory containing the example/fixture
445
+ *
446
+ * @public
447
+ */
448
+ export declare function testExample(
449
+ exampleName: FixtureName | (string & {}),
450
+ options?: TestFixtureOptions,
451
+ ): Promise<string>;
452
+
453
+ /**
454
+ * @deprecated Use {@link TestFixtureOptions} instead. This type alias will be removed in a future release.
455
+ * @public
456
+ */
457
+ export declare type TestExampleOptions = TestFixtureOptions;
458
+
459
+ /**
460
+ * Clones a fixture directory into a temporary directory with an isolated copy.
461
+ *
462
+ * The function creates a unique temporary copy of the specified fixture with:
463
+ * - A random unique ID to avoid conflicts between parallel tests
464
+ * - Symlinked node_modules for performance (from the global setup version)
465
+ * - Modified package.json name to prevent conflicts
466
+ *
467
+ * The fixture is first looked up in the temp directory (if global setup ran),
468
+ * otherwise it falls back to the bundled fixtures in the package.
469
+ *
470
+ * @param fixtureName - The name of the fixture to clone (e.g., 'basic-app', 'basic-studio')
471
+ * @param options - Configuration options
472
+ * @returns The absolute path to the temporary directory containing the fixture
473
+ *
474
+ * @public
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * import {testFixture} from '@sanity/cli-test'
479
+ * import {describe, test} from 'vitest'
480
+ *
481
+ * describe('my test suite', () => {
482
+ * test('should work with basic-studio', async () => {
483
+ * const cwd = await testFixture('basic-studio')
484
+ * // ... run your tests in this directory
485
+ * })
486
+ * })
487
+ * ```
488
+ */
489
+ export declare function testFixture(
490
+ fixtureName: FixtureName | (string & {}),
491
+ options?: TestFixtureOptions,
492
+ ): Promise<string>;
493
+
494
+ /**
495
+ * @public
496
+ */
497
+ export declare interface TestFixtureOptions {
498
+ /**
499
+ * Custom temp directory. Defaults to process.cwd()/tmp
500
+ */
501
+ tempDir?: string;
502
+ }
503
+
504
+ /**
505
+ * Test an oclif hook
506
+ *
507
+ * @public
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * const result = await testHook(hook, {
512
+ * Command: Command.loadable,
513
+ * context: {
514
+ * config: Config.load({
515
+ * // CLI root is the directory of the package that contains the hook.
516
+ * root: path.resolve(fileURLToPath(import.meta.url), '../../../root'),
517
+ * }),
518
+ * },
519
+ * })
520
+ * ```
521
+ *
522
+ * @param hook - The hook to test
523
+ * @param options - The options for the hook
524
+ * @returns The result of the hook
525
+ */
526
+ export declare function testHook<T extends keyof Hooks>(
527
+ hook: Hook<T>,
528
+ options: Options,
529
+ ): Promise<CaptureResult<Hooks[T]["return"]>>;
530
+
531
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export * from './test/constants.js';
2
+ export * from './test/createTestClient.js';
3
+ export * from './test/createTestToken.js';
4
+ export * from './test/mockApi.js';
5
+ export * from './test/mockSanityCommand.js';
6
+ export * from './test/mockTelemetry.js';
7
+ export * from './test/setupFixtures.js';
8
+ export * from './test/testCommand.js';
9
+ export * from './test/testFixture.js';
10
+ export * from './test/testHook.js';
11
+ export * from './utils/paths.js';
12
+
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './test/constants.js'\nexport * from './test/createTestClient.js'\nexport * from './test/createTestToken.js'\nexport * from './test/mockApi.js'\nexport * from './test/mockSanityCommand.js'\nexport * from './test/mockTelemetry.js'\nexport * from './test/setupFixtures.js'\nexport * from './test/testCommand.js'\nexport * from './test/testFixture.js'\nexport * from './test/testHook.js'\nexport * from './utils/paths.js'\n"],"names":[],"mappings":"AAAA,cAAc,sBAAqB;AACnC,cAAc,6BAA4B;AAC1C,cAAc,4BAA2B;AACzC,cAAc,oBAAmB;AACjC,cAAc,8BAA6B;AAC3C,cAAc,0BAAyB;AACvC,cAAc,0BAAyB;AACvC,cAAc,wBAAuB;AACrC,cAAc,wBAAuB;AACrC,cAAc,qBAAoB;AAClC,cAAc,mBAAkB"}
@@ -0,0 +1,83 @@
1
+ import ansis from 'ansis';
2
+ /**
3
+ * Capture the output of a command and return the result
4
+ *
5
+ * @param fn - The function to capture the output of
6
+ * @param opts - The options for the capture
7
+ * @returns The result of the command
8
+ * @internal
9
+ *
10
+ * Credits to oclif for the original implementation:
11
+ * https://github.com/oclif/test/blob/2a5407e6fc80d388043d10f6b7b8eaa586483015/src/index.ts
12
+ *
13
+ * We are not using the library directly since it does not support mocking code inside of the command
14
+ * possibly because the commands run in a different thread
15
+ */ export async function captureOutput(fn, opts) {
16
+ const print = opts?.print ?? false;
17
+ const stripAnsi = opts?.stripAnsi ?? true;
18
+ const testNodeEnv = opts?.testNodeEnv || 'test';
19
+ const originals = {
20
+ NODE_ENV: process.env.NODE_ENV,
21
+ stderrWrite: process.stderr.write,
22
+ stdoutWrite: process.stdout.write
23
+ };
24
+ const output = {
25
+ stderr: [],
26
+ stdout: []
27
+ };
28
+ const toString = (str)=>stripAnsi ? ansis.strip(str.toString()) : str.toString();
29
+ const getStderr = ()=>output.stderr.map((b)=>toString(b)).join('');
30
+ const getStdout = ()=>output.stdout.map((b)=>toString(b)).join('');
31
+ const mockWrite = (std)=>(chunk, encodingOrCb, cb)=>{
32
+ output[std].push(chunk.toString());
33
+ if (print) {
34
+ let callback = cb;
35
+ let encoding;
36
+ if (typeof encodingOrCb === 'function') {
37
+ callback = encodingOrCb;
38
+ } else {
39
+ encoding = encodingOrCb;
40
+ }
41
+ originals[`${std}Write`].apply(process[std], [
42
+ chunk,
43
+ encoding,
44
+ callback
45
+ ]);
46
+ } else if (typeof cb === 'function') {
47
+ cb();
48
+ } else if (typeof encodingOrCb === 'function') {
49
+ encodingOrCb();
50
+ }
51
+ return true;
52
+ };
53
+ process.stdout.write = mockWrite('stdout');
54
+ process.stderr.write = mockWrite('stderr');
55
+ process.env.NODE_ENV = testNodeEnv;
56
+ try {
57
+ const result = await fn();
58
+ return {
59
+ result,
60
+ stderr: getStderr(),
61
+ stdout: getStdout()
62
+ };
63
+ } catch (error) {
64
+ // Check if it's an oclif CLIError or a regular error
65
+ const processedError = error instanceof Error // Check if it's an Error (this includes CLIError)
66
+ ? Object.assign(error, {
67
+ message: toString(error.message)
68
+ }) // If so, process its message
69
+ : new Error(toString(String(error))) // Otherwise, create a new Error from string representation
70
+ ;
71
+ return {
72
+ error: processedError,
73
+ stderr: getStderr(),
74
+ stdout: getStdout()
75
+ };
76
+ } finally{
77
+ process.stdout.write = originals.stdoutWrite;
78
+ process.stderr.write = originals.stderrWrite;
79
+ process.env.NODE_ENV = originals.NODE_ENV;
80
+ }
81
+ }
82
+
83
+ //# sourceMappingURL=captureOutput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/captureOutput.ts"],"sourcesContent":["import {type Errors} from '@oclif/core'\nimport ansis from 'ansis'\n\nexport interface CaptureOptions {\n /**\n * Whether to print the output to the console\n */\n print?: boolean\n /**\n * Whether to strip ANSI escape codes from the output\n */\n stripAnsi?: boolean\n testNodeEnv?: string\n}\n\nexport interface CaptureResult<T = unknown> {\n stderr: string\n stdout: string\n\n error?: Error & Partial<Errors.CLIError>\n result?: T\n}\n\n/**\n * Capture the output of a command and return the result\n *\n * @param fn - The function to capture the output of\n * @param opts - The options for the capture\n * @returns The result of the command\n * @internal\n *\n * Credits to oclif for the original implementation:\n * https://github.com/oclif/test/blob/2a5407e6fc80d388043d10f6b7b8eaa586483015/src/index.ts\n *\n * We are not using the library directly since it does not support mocking code inside of the command\n * possibly because the commands run in a different thread\n */\nexport async function captureOutput<T>(\n fn: () => Promise<T>,\n opts?: CaptureOptions,\n): Promise<CaptureResult<T>> {\n const print = opts?.print ?? false\n const stripAnsi = opts?.stripAnsi ?? true\n const testNodeEnv = opts?.testNodeEnv || 'test'\n\n const originals = {\n NODE_ENV: process.env.NODE_ENV,\n stderrWrite: process.stderr.write,\n stdoutWrite: process.stdout.write,\n }\n\n const output: Record<'stderr' | 'stdout', string[]> = {\n stderr: [],\n stdout: [],\n }\n\n const toString = (str: string | Uint8Array): string =>\n stripAnsi ? ansis.strip(str.toString()) : str.toString()\n\n const getStderr = (): string => output.stderr.map((b) => toString(b)).join('')\n const getStdout = (): string => output.stdout.map((b) => toString(b)).join('')\n\n const mockWrite =\n (std: 'stderr' | 'stdout'): typeof process.stderr.write =>\n (\n chunk: string | Uint8Array,\n encodingOrCb?: ((err?: Error | null) => void) | BufferEncoding,\n cb?: (err?: Error | null) => void,\n ) => {\n output[std].push(chunk.toString())\n\n if (print) {\n let callback: ((err?: Error | null) => void) | undefined = cb\n let encoding: BufferEncoding | undefined\n if (typeof encodingOrCb === 'function') {\n callback = encodingOrCb\n } else {\n encoding = encodingOrCb\n }\n originals[`${std}Write`].apply(process[std], [chunk, encoding, callback])\n } else if (typeof cb === 'function') {\n cb()\n } else if (typeof encodingOrCb === 'function') {\n encodingOrCb()\n }\n return true\n }\n\n process.stdout.write = mockWrite('stdout')\n process.stderr.write = mockWrite('stderr')\n process.env.NODE_ENV = testNodeEnv\n\n try {\n const result = await fn()\n return {\n result,\n stderr: getStderr(),\n stdout: getStdout(),\n }\n } catch (error) {\n // Check if it's an oclif CLIError or a regular error\n const processedError =\n error instanceof Error // Check if it's an Error (this includes CLIError)\n ? Object.assign(error, {message: toString(error.message)}) // If so, process its message\n : new Error(toString(String(error))) // Otherwise, create a new Error from string representation\n\n return {\n error: processedError,\n stderr: getStderr(),\n stdout: getStdout(),\n }\n } finally {\n process.stdout.write = originals.stdoutWrite\n process.stderr.write = originals.stderrWrite\n process.env.NODE_ENV = originals.NODE_ENV\n }\n}\n"],"names":["ansis","captureOutput","fn","opts","print","stripAnsi","testNodeEnv","originals","NODE_ENV","process","env","stderrWrite","stderr","write","stdoutWrite","stdout","output","toString","str","strip","getStderr","map","b","join","getStdout","mockWrite","std","chunk","encodingOrCb","cb","push","callback","encoding","apply","result","error","processedError","Error","Object","assign","message","String"],"mappings":"AACA,OAAOA,WAAW,QAAO;AAsBzB;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeC,cACpBC,EAAoB,EACpBC,IAAqB;IAErB,MAAMC,QAAQD,MAAMC,SAAS;IAC7B,MAAMC,YAAYF,MAAME,aAAa;IACrC,MAAMC,cAAcH,MAAMG,eAAe;IAEzC,MAAMC,YAAY;QAChBC,UAAUC,QAAQC,GAAG,CAACF,QAAQ;QAC9BG,aAAaF,QAAQG,MAAM,CAACC,KAAK;QACjCC,aAAaL,QAAQM,MAAM,CAACF,KAAK;IACnC;IAEA,MAAMG,SAAgD;QACpDJ,QAAQ,EAAE;QACVG,QAAQ,EAAE;IACZ;IAEA,MAAME,WAAW,CAACC,MAChBb,YAAYL,MAAMmB,KAAK,CAACD,IAAID,QAAQ,MAAMC,IAAID,QAAQ;IAExD,MAAMG,YAAY,IAAcJ,OAAOJ,MAAM,CAACS,GAAG,CAAC,CAACC,IAAML,SAASK,IAAIC,IAAI,CAAC;IAC3E,MAAMC,YAAY,IAAcR,OAAOD,MAAM,CAACM,GAAG,CAAC,CAACC,IAAML,SAASK,IAAIC,IAAI,CAAC;IAE3E,MAAME,YACJ,CAACC,MACD,CACEC,OACAC,cACAC;YAEAb,MAAM,CAACU,IAAI,CAACI,IAAI,CAACH,MAAMV,QAAQ;YAE/B,IAAIb,OAAO;gBACT,IAAI2B,WAAuDF;gBAC3D,IAAIG;gBACJ,IAAI,OAAOJ,iBAAiB,YAAY;oBACtCG,WAAWH;gBACb,OAAO;oBACLI,WAAWJ;gBACb;gBACArB,SAAS,CAAC,GAAGmB,IAAI,KAAK,CAAC,CAAC,CAACO,KAAK,CAACxB,OAAO,CAACiB,IAAI,EAAE;oBAACC;oBAAOK;oBAAUD;iBAAS;YAC1E,OAAO,IAAI,OAAOF,OAAO,YAAY;gBACnCA;YACF,OAAO,IAAI,OAAOD,iBAAiB,YAAY;gBAC7CA;YACF;YACA,OAAO;QACT;IAEFnB,QAAQM,MAAM,CAACF,KAAK,GAAGY,UAAU;IACjChB,QAAQG,MAAM,CAACC,KAAK,GAAGY,UAAU;IACjChB,QAAQC,GAAG,CAACF,QAAQ,GAAGF;IAEvB,IAAI;QACF,MAAM4B,SAAS,MAAMhC;QACrB,OAAO;YACLgC;YACAtB,QAAQQ;YACRL,QAAQS;QACV;IACF,EAAE,OAAOW,OAAO;QACd,qDAAqD;QACrD,MAAMC,iBACJD,iBAAiBE,MAAM,kDAAkD;WACrEC,OAAOC,MAAM,CAACJ,OAAO;YAACK,SAASvB,SAASkB,MAAMK,OAAO;QAAC,GAAG,6BAA6B;WACtF,IAAIH,MAAMpB,SAASwB,OAAON,SAAS,2DAA2D;;QAEpG,OAAO;YACLA,OAAOC;YACPxB,QAAQQ;YACRL,QAAQS;QACV;IACF,SAAU;QACRf,QAAQM,MAAM,CAACF,KAAK,GAAGN,UAAUO,WAAW;QAC5CL,QAAQG,MAAM,CAACC,KAAK,GAAGN,UAAUI,WAAW;QAC5CF,QAAQC,GAAG,CAACF,QAAQ,GAAGD,UAAUC,QAAQ;IAC3C;AACF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Options for each fixture.
3
+ * @public
4
+ */ /**
5
+ * Default fixtures bundled with the package and their options.
6
+ *
7
+ * @public
8
+ */ export const DEFAULT_FIXTURES = {
9
+ 'basic-app': {},
10
+ 'basic-functions': {},
11
+ 'basic-studio': {},
12
+ 'federated-studio': {},
13
+ 'graphql-studio': {},
14
+ 'multi-workspace-studio': {},
15
+ 'prebuilt-app': {
16
+ includeDist: true
17
+ },
18
+ 'prebuilt-studio': {
19
+ includeDist: true
20
+ },
21
+ 'worst-case-studio': {}
22
+ };
23
+
24
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/constants.ts"],"sourcesContent":["/**\n * Options for each fixture.\n * @public\n */\nexport interface FixtureOptions {\n includeDist?: boolean\n}\n\n/**\n * Default fixtures bundled with the package and their options.\n *\n * @public\n */\nexport const DEFAULT_FIXTURES: Record<FixtureName, FixtureOptions> = {\n 'basic-app': {},\n 'basic-functions': {},\n 'basic-studio': {},\n 'federated-studio': {},\n 'graphql-studio': {},\n 'multi-workspace-studio': {},\n 'prebuilt-app': {includeDist: true},\n 'prebuilt-studio': {includeDist: true},\n 'worst-case-studio': {},\n} as const\n\n/**\n * Valid fixture name type.\n * @public\n */\nexport type FixtureName =\n | 'basic-app'\n | 'basic-functions'\n | 'basic-studio'\n | 'federated-studio'\n | 'graphql-studio'\n | 'multi-workspace-studio'\n | 'prebuilt-app'\n | 'prebuilt-studio'\n | 'worst-case-studio'\n\n/**\n * @deprecated Use {@link FixtureName} instead. This type alias will be removed in a future release.\n * @public\n */\nexport type ExampleName = FixtureName\n"],"names":["DEFAULT_FIXTURES","includeDist"],"mappings":"AAAA;;;CAGC,GAKD;;;;CAIC,GACD,OAAO,MAAMA,mBAAwD;IACnE,aAAa,CAAC;IACd,mBAAmB,CAAC;IACpB,gBAAgB,CAAC;IACjB,oBAAoB,CAAC;IACrB,kBAAkB,CAAC;IACnB,0BAA0B,CAAC;IAC3B,gBAAgB;QAACC,aAAa;IAAI;IAClC,mBAAmB;QAACA,aAAa;IAAI;IACrC,qBAAqB,CAAC;AACxB,EAAU"}