@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,53 @@
1
+ import { createClient } from '@sanity/client';
2
+ import { vi } from 'vitest';
3
+ /**
4
+ * Creates a real Sanity client instance for testing that makes actual HTTP requests.
5
+ * Use with mockApi() to intercept and mock the HTTP calls.
6
+ *
7
+ * @public
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Mock getGlobalCliClient to return a test client
12
+ * vi.mock('@sanity/cli-core', async (importOriginal) => {
13
+ * const actual = await importOriginal<typeof import('@sanity/cli-core')>()
14
+ * const {createTestClient} = await import('@sanity/cli-test')
15
+ *
16
+ * return {
17
+ * ...actual,
18
+ * getGlobalCliClient: vi.fn().mockImplementation((opts) => {
19
+ * return Promise.resolve(createTestClient({
20
+ * apiVersion: opts.apiVersion,
21
+ * }))
22
+ * }),
23
+ * }
24
+ * })
25
+ *
26
+ * // Then use mockApi to intercept requests
27
+ * mockApi({
28
+ * apiVersion: 'v2025-02-19',
29
+ * method: 'get',
30
+ * uri: '/media-libraries',
31
+ * }).reply(200, {data: [...]})
32
+ * ```
33
+ */ export function createTestClient(options) {
34
+ const { apiVersion, projectId, token, ...rest } = options;
35
+ const client = createClient({
36
+ apiVersion,
37
+ projectId,
38
+ requestTagPrefix: 'sanity.cli',
39
+ token,
40
+ useCdn: false,
41
+ useProjectHostname: projectId ? true : false,
42
+ ...rest
43
+ });
44
+ /**
45
+ * Mock the request method of the client to return the actual response from the client
46
+ */ const request = vi.fn((...args)=>client.request(...args));
47
+ return {
48
+ client,
49
+ request
50
+ };
51
+ }
52
+
53
+ //# sourceMappingURL=createTestClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/createTestClient.ts"],"sourcesContent":["import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'\nimport {vi} from 'vitest'\n\n/**\n * Options for createTestClient\n *\n * @public\n */\nexport interface CreateTestClientOptions extends ClientConfig {\n /**\n * API version for the client\n */\n apiVersion: string\n\n /**\n * Authentication token\n */\n token?: string\n}\n\n/**\n * Creates a real Sanity client instance for testing that makes actual HTTP requests.\n * Use with mockApi() to intercept and mock the HTTP calls.\n *\n * @public\n *\n * @example\n * ```typescript\n * // Mock getGlobalCliClient to return a test client\n * vi.mock('@sanity/cli-core', async (importOriginal) => {\n * const actual = await importOriginal<typeof import('@sanity/cli-core')>()\n * const {createTestClient} = await import('@sanity/cli-test')\n *\n * return {\n * ...actual,\n * getGlobalCliClient: vi.fn().mockImplementation((opts) => {\n * return Promise.resolve(createTestClient({\n * apiVersion: opts.apiVersion,\n * }))\n * }),\n * }\n * })\n *\n * // Then use mockApi to intercept requests\n * mockApi({\n * apiVersion: 'v2025-02-19',\n * method: 'get',\n * uri: '/media-libraries',\n * }).reply(200, {data: [...]})\n * ```\n */\nexport function createTestClient(options: CreateTestClientOptions): {\n client: SanityClient\n request: ReturnType<typeof vi.fn>\n} {\n const {apiVersion, projectId, token, ...rest} = options\n\n const client = createClient({\n apiVersion,\n projectId,\n requestTagPrefix: 'sanity.cli',\n token,\n useCdn: false,\n useProjectHostname: projectId ? true : false,\n ...rest,\n })\n\n /**\n * Mock the request method of the client to return the actual response from the client\n */\n const request = vi.fn((...args: Parameters<typeof client.request>) => client.request(...args))\n\n return {\n client,\n request,\n }\n}\n"],"names":["createClient","vi","createTestClient","options","apiVersion","projectId","token","rest","client","requestTagPrefix","useCdn","useProjectHostname","request","fn","args"],"mappings":"AAAA,SAA2BA,YAAY,QAA0B,iBAAgB;AACjF,SAAQC,EAAE,QAAO,SAAQ;AAmBzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASC,iBAAiBC,OAAgC;IAI/D,MAAM,EAACC,UAAU,EAAEC,SAAS,EAAEC,KAAK,EAAE,GAAGC,MAAK,GAAGJ;IAEhD,MAAMK,SAASR,aAAa;QAC1BI;QACAC;QACAI,kBAAkB;QAClBH;QACAI,QAAQ;QACRC,oBAAoBN,YAAY,OAAO;QACvC,GAAGE,IAAI;IACT;IAEA;;GAEC,GACD,MAAMK,UAAUX,GAAGY,EAAE,CAAC,CAAC,GAAGC,OAA4CN,OAAOI,OAAO,IAAIE;IAExF,OAAO;QACLN;QACAI;IACF;AACF"}
@@ -0,0 +1,13 @@
1
+ import { vi } from 'vitest';
2
+ /**
3
+ * Creates a test token for the Sanity CLI
4
+ *
5
+ * @public
6
+ *
7
+ * @param token - The token to create
8
+ * @returns void
9
+ */ export function createTestToken(token) {
10
+ vi.stubEnv('SANITY_AUTH_TOKEN', token);
11
+ }
12
+
13
+ //# sourceMappingURL=createTestToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/createTestToken.ts"],"sourcesContent":["import {vi} from 'vitest'\n\n/**\n * Creates a test token for the Sanity CLI\n *\n * @public\n *\n * @param token - The token to create\n * @returns void\n */\nexport function createTestToken(token: string) {\n vi.stubEnv('SANITY_AUTH_TOKEN', token)\n}\n"],"names":["vi","createTestToken","token","stubEnv"],"mappings":"AAAA,SAAQA,EAAE,QAAO,SAAQ;AAEzB;;;;;;;CAOC,GACD,OAAO,SAASC,gBAAgBC,KAAa;IAC3CF,GAAGG,OAAO,CAAC,qBAAqBD;AAClC"}
@@ -0,0 +1,16 @@
1
+ import nock from 'nock';
2
+ /**
3
+ * Mocks the API calls, add some defaults so it doesn't cause too much friction
4
+ *
5
+ * @internal
6
+ */ export function mockApi({ apiHost = 'https://api.sanity.io', apiVersion = 'v2025-05-14', includeQueryTag = true, method = 'get', projectId, query = {}, uri }) {
7
+ const version = apiVersion.startsWith('v') ? apiVersion : `v${apiVersion}`;
8
+ const host = projectId ? `https://${projectId}.api.sanity.io` : apiHost;
9
+ const queryParams = includeQueryTag ? {
10
+ tag: 'sanity.cli',
11
+ ...query
12
+ } : query;
13
+ return nock(host)[method](`/${version}${uri}`).query(queryParams);
14
+ }
15
+
16
+ //# sourceMappingURL=mockApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/mockApi.ts"],"sourcesContent":["import nock from 'nock'\n\n/**\n * @internal\n */\nexport interface MockApiOptions {\n /**\n * Uri to mock\n */\n uri: string\n\n /**\n * Api host to mock, defaults to `https://api.sanity.io`\n */\n apiHost?: string\n\n /**\n * Api version to mock, defaults to `v2025-05-14`\n */\n apiVersion?: string\n\n /**\n * Whether to include `tag: 'sanity.cli'` in query parameters.\n * Defaults to `true`. Set to `false` for endpoints that don't use CLI tagging.\n */\n includeQueryTag?: boolean\n\n /**\n * HTTP method to mock\n *\n * Defaults to 'get'\n */\n method?: 'delete' | 'get' | 'patch' | 'post' | 'put'\n\n /**\n * Project ID to mock. When provided, constructs apiHost as `https://{projectId}.api.sanity.io`\n * Takes precedence over apiHost if both are provided.\n */\n projectId?: string\n\n /**\n * Query parameters to mock\n */\n query?: Record<string, string>\n}\n\n/**\n * Mocks the API calls, add some defaults so it doesn't cause too much friction\n *\n * @internal\n */\nexport function mockApi({\n apiHost = 'https://api.sanity.io',\n apiVersion = 'v2025-05-14',\n includeQueryTag = true,\n method = 'get',\n projectId,\n query = {},\n uri,\n}: MockApiOptions) {\n const version = apiVersion.startsWith('v') ? apiVersion : `v${apiVersion}`\n const host = projectId ? `https://${projectId}.api.sanity.io` : apiHost\n const queryParams = includeQueryTag ? {tag: 'sanity.cli', ...query} : query\n\n return nock(host)[method](`/${version}${uri}`).query(queryParams)\n}\n"],"names":["nock","mockApi","apiHost","apiVersion","includeQueryTag","method","projectId","query","uri","version","startsWith","host","queryParams","tag"],"mappings":"AAAA,OAAOA,UAAU,OAAM;AA8CvB;;;;CAIC,GACD,OAAO,SAASC,QAAQ,EACtBC,UAAU,uBAAuB,EACjCC,aAAa,aAAa,EAC1BC,kBAAkB,IAAI,EACtBC,SAAS,KAAK,EACdC,SAAS,EACTC,QAAQ,CAAC,CAAC,EACVC,GAAG,EACY;IACf,MAAMC,UAAUN,WAAWO,UAAU,CAAC,OAAOP,aAAa,CAAC,CAAC,EAAEA,YAAY;IAC1E,MAAMQ,OAAOL,YAAY,CAAC,QAAQ,EAAEA,UAAU,cAAc,CAAC,GAAGJ;IAChE,MAAMU,cAAcR,kBAAkB;QAACS,KAAK;QAAc,GAAGN,KAAK;IAAA,IAAIA;IAEtE,OAAOP,KAAKW,KAAK,CAACN,OAAO,CAAC,CAAC,CAAC,EAAEI,UAAUD,KAAK,EAAED,KAAK,CAACK;AACvD"}
@@ -0,0 +1,71 @@
1
+ import { convertToSystemPath } from '../utils/paths.js';
2
+ import { createTestToken } from './createTestToken.js';
3
+ /**
4
+ * Creates a testable subclass of a command with mocked SanityCommand dependencies.
5
+ *
6
+ * @public
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // Basic config mocking
11
+ * const TestAdd = mockSanityCommand(Add, {
12
+ * cliConfig: { api: { projectId: 'test-project' } }
13
+ * })
14
+ *
15
+ * // With mock API client
16
+ * const mockClient = {
17
+ * getDocument: vi.fn().mockResolvedValue({ _id: 'doc1', title: 'Test' }),
18
+ * fetch: vi.fn().mockResolvedValue([]),
19
+ * }
20
+ * const TestGet = mockSanityCommand(GetDocumentCommand, {
21
+ * cliConfig: { api: { projectId: 'test-project', dataset: 'production' } },
22
+ * projectApiClient: mockClient,
23
+ * })
24
+ *
25
+ * const {stdout} = await testCommand(TestGet, ['doc1'])
26
+ * expect(mockClient.getDocument).toHaveBeenCalledWith('doc1')
27
+ * ```
28
+ */ export function mockSanityCommand(CommandClass, options = {}) {
29
+ if (options.token) {
30
+ createTestToken(options.token);
31
+ }
32
+ // Auto-convert paths in projectRoot to platform-appropriate format
33
+ const projectRoot = options.projectRoot ? {
34
+ ...options.projectRoot,
35
+ directory: convertToSystemPath(options.projectRoot.directory),
36
+ path: convertToSystemPath(options.projectRoot.path)
37
+ } : undefined;
38
+ // Create a subclass that overrides methods when mocks are provided
39
+ // Note: we use @ts-expect-error because TypeScript can't properly infer
40
+ // the relationship between the generic CommandClass and SanityCommand
41
+ // @ts-expect-error - TypeScript struggles with abstract class subclassing
42
+ class MockedCommand extends CommandClass {
43
+ getCliConfig() {
44
+ if (options.cliConfigError) {
45
+ return Promise.reject(options.cliConfigError);
46
+ }
47
+ if (options.cliConfig) {
48
+ return Promise.resolve(options.cliConfig);
49
+ }
50
+ return super.getCliConfig();
51
+ }
52
+ getProjectRoot() {
53
+ if (options.cliConfigError && 'name' in options.cliConfigError && options.cliConfigError.name === 'ProjectRootNotFoundError') {
54
+ return Promise.reject(options.cliConfigError);
55
+ }
56
+ if (projectRoot) {
57
+ return Promise.resolve(projectRoot);
58
+ }
59
+ return super.getProjectRoot();
60
+ }
61
+ resolveIsInteractive() {
62
+ if (options.isInteractive !== undefined) {
63
+ return options.isInteractive;
64
+ }
65
+ return super.resolveIsInteractive();
66
+ }
67
+ }
68
+ return MockedCommand;
69
+ }
70
+
71
+ //# sourceMappingURL=mockSanityCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/mockSanityCommand.ts"],"sourcesContent":["import {type Command} from '@oclif/core'\nimport {type CliConfig, type ProjectRootResult, SanityCommand} from '@sanity/cli-core'\n\nimport {convertToSystemPath} from '../utils/paths.js'\nimport {createTestToken} from './createTestToken.js'\n\n/**\n * @public\n */\nexport interface MockSanityCommandOptions {\n /**\n * Mock CLI config (required if command uses getCliConfig or getProjectId)\n */\n cliConfig?: CliConfig\n /**\n * When provided, getCliConfig() will throw this error instead of returning a config.\n * Useful for simulating running outside a project directory.\n */\n cliConfigError?: Error\n /**\n * Mock whether the terminal is interactive (used by isUnattended)\n */\n isInteractive?: boolean\n /**\n * Mock project root result (required if command uses getProjectRoot)\n */\n projectRoot?: ProjectRootResult\n /**\n * Mock authentication token (passed to API clients, bypasses getCliToken)\n */\n token?: string\n}\n\n/**\n * Creates a testable subclass of a command with mocked SanityCommand dependencies.\n *\n * @public\n *\n * @example\n * ```ts\n * // Basic config mocking\n * const TestAdd = mockSanityCommand(Add, {\n * cliConfig: { api: { projectId: 'test-project' } }\n * })\n *\n * // With mock API client\n * const mockClient = {\n * getDocument: vi.fn().mockResolvedValue({ _id: 'doc1', title: 'Test' }),\n * fetch: vi.fn().mockResolvedValue([]),\n * }\n * const TestGet = mockSanityCommand(GetDocumentCommand, {\n * cliConfig: { api: { projectId: 'test-project', dataset: 'production' } },\n * projectApiClient: mockClient,\n * })\n *\n * const {stdout} = await testCommand(TestGet, ['doc1'])\n * expect(mockClient.getDocument).toHaveBeenCalledWith('doc1')\n * ```\n */\nexport function mockSanityCommand<T extends typeof SanityCommand<typeof Command>>(\n CommandClass: T,\n options: MockSanityCommandOptions = {},\n): T {\n if (options.token) {\n createTestToken(options.token)\n }\n\n // Auto-convert paths in projectRoot to platform-appropriate format\n const projectRoot = options.projectRoot\n ? {\n ...options.projectRoot,\n directory: convertToSystemPath(options.projectRoot.directory),\n path: convertToSystemPath(options.projectRoot.path),\n }\n : undefined\n\n // Create a subclass that overrides methods when mocks are provided\n // Note: we use @ts-expect-error because TypeScript can't properly infer\n // the relationship between the generic CommandClass and SanityCommand\n // @ts-expect-error - TypeScript struggles with abstract class subclassing\n class MockedCommand extends CommandClass {\n protected getCliConfig(): Promise<CliConfig> {\n if (options.cliConfigError) {\n return Promise.reject(options.cliConfigError)\n }\n if (options.cliConfig) {\n return Promise.resolve(options.cliConfig)\n }\n return super.getCliConfig()\n }\n\n protected getProjectRoot(): Promise<ProjectRootResult> {\n if (\n options.cliConfigError &&\n 'name' in options.cliConfigError &&\n options.cliConfigError.name === 'ProjectRootNotFoundError'\n ) {\n return Promise.reject(options.cliConfigError)\n }\n if (projectRoot) {\n return Promise.resolve(projectRoot)\n }\n return super.getProjectRoot()\n }\n\n protected resolveIsInteractive(): boolean {\n if (options.isInteractive !== undefined) {\n return options.isInteractive\n }\n return super.resolveIsInteractive()\n }\n }\n\n return MockedCommand as T\n}\n"],"names":["convertToSystemPath","createTestToken","mockSanityCommand","CommandClass","options","token","projectRoot","directory","path","undefined","MockedCommand","getCliConfig","cliConfigError","Promise","reject","cliConfig","resolve","getProjectRoot","name","resolveIsInteractive","isInteractive"],"mappings":"AAGA,SAAQA,mBAAmB,QAAO,oBAAmB;AACrD,SAAQC,eAAe,QAAO,uBAAsB;AA6BpD;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,kBACdC,YAAe,EACfC,UAAoC,CAAC,CAAC;IAEtC,IAAIA,QAAQC,KAAK,EAAE;QACjBJ,gBAAgBG,QAAQC,KAAK;IAC/B;IAEA,mEAAmE;IACnE,MAAMC,cAAcF,QAAQE,WAAW,GACnC;QACE,GAAGF,QAAQE,WAAW;QACtBC,WAAWP,oBAAoBI,QAAQE,WAAW,CAACC,SAAS;QAC5DC,MAAMR,oBAAoBI,QAAQE,WAAW,CAACE,IAAI;IACpD,IACAC;IAEJ,mEAAmE;IACnE,wEAAwE;IACxE,sEAAsE;IACtE,0EAA0E;IAC1E,MAAMC,sBAAsBP;QAChBQ,eAAmC;YAC3C,IAAIP,QAAQQ,cAAc,EAAE;gBAC1B,OAAOC,QAAQC,MAAM,CAACV,QAAQQ,cAAc;YAC9C;YACA,IAAIR,QAAQW,SAAS,EAAE;gBACrB,OAAOF,QAAQG,OAAO,CAACZ,QAAQW,SAAS;YAC1C;YACA,OAAO,KAAK,CAACJ;QACf;QAEUM,iBAA6C;YACrD,IACEb,QAAQQ,cAAc,IACtB,UAAUR,QAAQQ,cAAc,IAChCR,QAAQQ,cAAc,CAACM,IAAI,KAAK,4BAChC;gBACA,OAAOL,QAAQC,MAAM,CAACV,QAAQQ,cAAc;YAC9C;YACA,IAAIN,aAAa;gBACf,OAAOO,QAAQG,OAAO,CAACV;YACzB;YACA,OAAO,KAAK,CAACW;QACf;QAEUE,uBAAgC;YACxC,IAAIf,QAAQgB,aAAa,KAAKX,WAAW;gBACvC,OAAOL,QAAQgB,aAAa;YAC9B;YACA,OAAO,KAAK,CAACD;QACf;IACF;IAEA,OAAOT;AACT"}
@@ -0,0 +1,24 @@
1
+ import { vi } from 'vitest';
2
+ /**
3
+ * @public
4
+ * @param options - Options for mocking the telemetry store.
5
+ * @returns The mocked telemetry store.
6
+ */ export const mockTelemetry = (options = {})=>{
7
+ const telemetry = {
8
+ trace: vi.fn().mockReturnValue({
9
+ complete: vi.fn(),
10
+ error: vi.fn(),
11
+ log: vi.fn(),
12
+ newContext: vi.fn(),
13
+ start: vi.fn()
14
+ }),
15
+ updateUserProperties: vi.fn(),
16
+ ...options
17
+ };
18
+ globalThis[Symbol.for('sanity.cli.telemetry')] = {
19
+ logger: telemetry
20
+ };
21
+ return telemetry;
22
+ };
23
+
24
+ //# sourceMappingURL=mockTelemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/mockTelemetry.ts"],"sourcesContent":["import {type CLITelemetryStore} from '@sanity/cli-core'\nimport {vi} from 'vitest'\n\n/**\n * @public\n */\nexport interface MockTelemetryOptions {\n trace?: () => void\n updateUserProperties?: () => void\n}\n\n/**\n * @public\n * @param options - Options for mocking the telemetry store.\n * @returns The mocked telemetry store.\n */\nexport const mockTelemetry = (options: MockTelemetryOptions = {}): CLITelemetryStore => {\n const telemetry = {\n trace: vi.fn().mockReturnValue({\n complete: vi.fn(),\n error: vi.fn(),\n log: vi.fn(),\n newContext: vi.fn(),\n start: vi.fn(),\n }),\n updateUserProperties: vi.fn(),\n ...options,\n } as unknown as CLITelemetryStore\n\n // We are not using the export from @sanity/cli-core because\n // This can be used where `@sanity/cli-core` is mocked and vitest will run into issues.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(globalThis as any)[Symbol.for('sanity.cli.telemetry')] = {logger: telemetry}\n\n return telemetry\n}\n"],"names":["vi","mockTelemetry","options","telemetry","trace","fn","mockReturnValue","complete","error","log","newContext","start","updateUserProperties","globalThis","Symbol","for","logger"],"mappings":"AACA,SAAQA,EAAE,QAAO,SAAQ;AAUzB;;;;CAIC,GACD,OAAO,MAAMC,gBAAgB,CAACC,UAAgC,CAAC,CAAC;IAC9D,MAAMC,YAAY;QAChBC,OAAOJ,GAAGK,EAAE,GAAGC,eAAe,CAAC;YAC7BC,UAAUP,GAAGK,EAAE;YACfG,OAAOR,GAAGK,EAAE;YACZI,KAAKT,GAAGK,EAAE;YACVK,YAAYV,GAAGK,EAAE;YACjBM,OAAOX,GAAGK,EAAE;QACd;QACAO,sBAAsBZ,GAAGK,EAAE;QAC3B,GAAGH,OAAO;IACZ;IAKEW,UAAkB,CAACC,OAAOC,GAAG,CAAC,wBAAwB,GAAG;QAACC,QAAQb;IAAS;IAE7E,OAAOA;AACT,EAAC"}
@@ -0,0 +1,140 @@
1
+ import { exec as execNode } from 'node:child_process';
2
+ import { readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { basename, join } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import ora from 'ora';
6
+ import { glob } from 'tinyglobby';
7
+ import { fileExists } from '../utils/fileExists.js';
8
+ import { getFixturesPath, getTempPath } from '../utils/paths.js';
9
+ import { DEFAULT_FIXTURES } from './constants.js';
10
+ import { testCopyDirectory } from './testFixture.js';
11
+ const exec = promisify(execNode);
12
+ async function getAdditionalFixturePaths(fixtures) {
13
+ const paths = await glob(fixtures, {
14
+ absolute: true,
15
+ ignore: [
16
+ '**/node_modules/**',
17
+ '**/dist/**'
18
+ ],
19
+ onlyDirectories: true
20
+ });
21
+ const additionalFixtures = [];
22
+ for (const path of paths){
23
+ if (await fileExists(join(`${path}/package.json`))) {
24
+ additionalFixtures.push({
25
+ fixture: basename(path),
26
+ fromPath: path,
27
+ includeDist: false
28
+ });
29
+ }
30
+ }
31
+ return additionalFixtures;
32
+ }
33
+ /**
34
+ * Global setup function for initializing test fixtures.
35
+ *
36
+ * Copies fixtures from the bundled location to a temp directory
37
+ * and installs dependencies.
38
+ *
39
+ * Note: Fixtures are NOT built during setup. Tests that need built
40
+ * fixtures should build them as part of the test.
41
+ *
42
+ * This function is designed to be used with vitest globalSetup.
43
+ *
44
+ * @public
45
+ *
46
+ * @param options - Configuration options
47
+ * @example
48
+ * ```typescript
49
+ * // In vitest.config.ts
50
+ * export default defineConfig({
51
+ * test: {
52
+ * globalSetup: ['@sanity/cli-test/vitest']
53
+ * }
54
+ * })
55
+ * ```
56
+ */ export async function setup(_, options = {}) {
57
+ const { additionalFixtures, tempDir } = options;
58
+ const spinner = ora({
59
+ // Without this, the watch mode input is discarded
60
+ discardStdin: false,
61
+ text: 'Initializing test environment...'
62
+ }).start();
63
+ try {
64
+ const fixturesDir = getFixturesPath();
65
+ const tempDirectory = getTempPath(tempDir);
66
+ const allFixturePaths = [];
67
+ // Add the default fixtures
68
+ for (const [fixture, options] of Object.entries(DEFAULT_FIXTURES)){
69
+ allFixturePaths.push({
70
+ fixture,
71
+ fromPath: join(fixturesDir, fixture),
72
+ includeDist: 'includeDist' in options && options.includeDist ? options.includeDist : false
73
+ });
74
+ }
75
+ // Add the additional fixtures
76
+ if (additionalFixtures && additionalFixtures.length > 0) {
77
+ const additionalFixturePaths = await getAdditionalFixturePaths(additionalFixtures);
78
+ if (additionalFixturePaths.length > 0) {
79
+ allFixturePaths.push(...additionalFixturePaths);
80
+ } else {
81
+ spinner.warn(`No additional fixtures found, check the glob pattern: ${additionalFixtures.join(', ')}`);
82
+ }
83
+ }
84
+ for (const { fixture, fromPath, includeDist } of allFixturePaths){
85
+ const toPath = join(tempDirectory, `fixture-${fixture}`);
86
+ // Copy the fixture, excluding node_modules and dist
87
+ await testCopyDirectory(fromPath, toPath, [
88
+ 'node_modules',
89
+ ...includeDist ? [] : [
90
+ 'dist'
91
+ ]
92
+ ]);
93
+ // Replace the package.json name with a temp name
94
+ const packageJsonPath = join(toPath, 'package.json');
95
+ const packageJson = await readFile(packageJsonPath, 'utf8');
96
+ const packageJsonData = JSON.parse(packageJson);
97
+ packageJsonData.name = `${packageJsonData.name}-test`;
98
+ await writeFile(packageJsonPath, JSON.stringify(packageJsonData, null, 2));
99
+ // Run pnpm install --no-lockfile in the temp directory
100
+ try {
101
+ await exec(`pnpm install --prefer-offline --no-lockfile`, {
102
+ cwd: toPath
103
+ });
104
+ } catch (error) {
105
+ const execError = error;
106
+ spinner.fail('Failed to install dependencies');
107
+ console.error(execError.stderr || execError.stdout || execError.message);
108
+ throw new Error(`Error installing dependencies in ${toPath}: ${execError.stderr || execError.stdout || execError.message}`, {
109
+ cause: error
110
+ });
111
+ }
112
+ }
113
+ spinner.succeed('Test environment initialized');
114
+ } catch (error) {
115
+ spinner.fail('Failed to initialize test environment');
116
+ throw error;
117
+ }
118
+ }
119
+ /**
120
+ * Teardown function to clean up test fixtures.
121
+ *
122
+ * Removes the temp directory created by setupTestFixtures.
123
+ *
124
+ * This function is designed to be used with vitest globalSetup.
125
+ *
126
+ * @public
127
+ *
128
+ * @param options - Configuration options
129
+ */ export async function teardown(options = {}) {
130
+ const { tempDir } = options;
131
+ const tempDirectory = getTempPath(tempDir);
132
+ // Remove the tmp directory
133
+ await rm(tempDirectory, {
134
+ force: true,
135
+ maxRetries: 3,
136
+ recursive: true
137
+ }).catch(()=>{});
138
+ }
139
+
140
+ //# sourceMappingURL=setupFixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/setupFixtures.ts"],"sourcesContent":["import {exec as execNode} from 'node:child_process'\nimport {readFile, rm, writeFile} from 'node:fs/promises'\nimport {basename, join} from 'node:path'\nimport {promisify} from 'node:util'\n\nimport ora from 'ora'\nimport {glob} from 'tinyglobby'\nimport {type TestProject} from 'vitest/node'\n\nimport {fileExists} from '../utils/fileExists.js'\nimport {getFixturesPath, getTempPath} from '../utils/paths.js'\nimport {DEFAULT_FIXTURES} from './constants.js'\nimport {testCopyDirectory} from './testFixture.js'\n\nconst exec = promisify(execNode)\n\n/**\n * Options for setupTestFixtures\n *\n * @public\n */\nexport interface SetupTestFixturesOptions {\n /**\n * Glob patterns for additional fixture directories to set up.\n *\n * Each pattern is matched against directories in the current working directory.\n * Only directories containing a `package.json` file are included.\n *\n * @example\n * ```typescript\n * ['fixtures/*', 'dev/*']\n * ```\n */\n additionalFixtures?: string[]\n\n /**\n * Custom temp directory path. Defaults to process.cwd()/tmp\n */\n tempDir?: string\n}\n\nasync function getAdditionalFixturePaths(fixtures: string[]): Promise<FixtureDetails[]> {\n const paths = await glob(fixtures, {\n absolute: true,\n ignore: ['**/node_modules/**', '**/dist/**'],\n onlyDirectories: true,\n })\n\n const additionalFixtures: FixtureDetails[] = []\n\n for (const path of paths) {\n if (await fileExists(join(`${path}/package.json`))) {\n additionalFixtures.push({\n fixture: basename(path),\n fromPath: path,\n includeDist: false,\n })\n }\n }\n\n return additionalFixtures\n}\n\ninterface FixtureDetails {\n fixture: string\n fromPath: string\n includeDist: boolean\n}\n\n/**\n * Global setup function for initializing test fixtures.\n *\n * Copies fixtures from the bundled location to a temp directory\n * and installs dependencies.\n *\n * Note: Fixtures are NOT built during setup. Tests that need built\n * fixtures should build them as part of the test.\n *\n * This function is designed to be used with vitest globalSetup.\n *\n * @public\n *\n * @param options - Configuration options\n * @example\n * ```typescript\n * // In vitest.config.ts\n * export default defineConfig({\n * test: {\n * globalSetup: ['@sanity/cli-test/vitest']\n * }\n * })\n * ```\n */\nexport async function setup(_: TestProject, options: SetupTestFixturesOptions = {}): Promise<void> {\n const {additionalFixtures, tempDir} = options\n\n const spinner = ora({\n // Without this, the watch mode input is discarded\n discardStdin: false,\n text: 'Initializing test environment...',\n }).start()\n\n try {\n const fixturesDir = getFixturesPath()\n const tempDirectory = getTempPath(tempDir)\n\n const allFixturePaths: FixtureDetails[] = []\n\n // Add the default fixtures\n for (const [fixture, options] of Object.entries(DEFAULT_FIXTURES)) {\n allFixturePaths.push({\n fixture,\n fromPath: join(fixturesDir, fixture),\n includeDist: 'includeDist' in options && options.includeDist ? options.includeDist : false,\n })\n }\n\n // Add the additional fixtures\n if (additionalFixtures && additionalFixtures.length > 0) {\n const additionalFixturePaths = await getAdditionalFixturePaths(additionalFixtures)\n\n if (additionalFixturePaths.length > 0) {\n allFixturePaths.push(...additionalFixturePaths)\n } else {\n spinner.warn(\n `No additional fixtures found, check the glob pattern: ${additionalFixtures.join(', ')}`,\n )\n }\n }\n\n for (const {fixture, fromPath, includeDist} of allFixturePaths) {\n const toPath = join(tempDirectory, `fixture-${fixture}`)\n // Copy the fixture, excluding node_modules and dist\n await testCopyDirectory(fromPath, toPath, ['node_modules', ...(includeDist ? [] : ['dist'])])\n\n // Replace the package.json name with a temp name\n const packageJsonPath = join(toPath, 'package.json')\n const packageJson = await readFile(packageJsonPath, 'utf8')\n const packageJsonData = JSON.parse(packageJson)\n packageJsonData.name = `${packageJsonData.name}-test`\n await writeFile(packageJsonPath, JSON.stringify(packageJsonData, null, 2))\n\n // Run pnpm install --no-lockfile in the temp directory\n try {\n await exec(`pnpm install --prefer-offline --no-lockfile`, {\n cwd: toPath,\n })\n } catch (error) {\n const execError = error as {message: string; stderr?: string; stdout?: string}\n spinner.fail('Failed to install dependencies')\n console.error(execError.stderr || execError.stdout || execError.message)\n throw new Error(\n `Error installing dependencies in ${toPath}: ${execError.stderr || execError.stdout || execError.message}`,\n {cause: error},\n )\n }\n }\n\n spinner.succeed('Test environment initialized')\n } catch (error) {\n spinner.fail('Failed to initialize test environment')\n throw error\n }\n}\n\n/**\n * Options for teardownTestFixtures\n *\n * @public\n */\nexport interface TeardownTestFixturesOptions {\n /**\n * Custom temp directory path. Defaults to process.cwd()/tmp\n */\n tempDir?: string\n}\n\n/**\n * Teardown function to clean up test fixtures.\n *\n * Removes the temp directory created by setupTestFixtures.\n *\n * This function is designed to be used with vitest globalSetup.\n *\n * @public\n *\n * @param options - Configuration options\n */\nexport async function teardown(options: TeardownTestFixturesOptions = {}): Promise<void> {\n const {tempDir} = options\n const tempDirectory = getTempPath(tempDir)\n\n // Remove the tmp directory\n await rm(tempDirectory, {force: true, maxRetries: 3, recursive: true}).catch(() => {})\n}\n"],"names":["exec","execNode","readFile","rm","writeFile","basename","join","promisify","ora","glob","fileExists","getFixturesPath","getTempPath","DEFAULT_FIXTURES","testCopyDirectory","getAdditionalFixturePaths","fixtures","paths","absolute","ignore","onlyDirectories","additionalFixtures","path","push","fixture","fromPath","includeDist","setup","_","options","tempDir","spinner","discardStdin","text","start","fixturesDir","tempDirectory","allFixturePaths","Object","entries","length","additionalFixturePaths","warn","toPath","packageJsonPath","packageJson","packageJsonData","JSON","parse","name","stringify","cwd","error","execError","fail","console","stderr","stdout","message","Error","cause","succeed","teardown","force","maxRetries","recursive","catch"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Serializer for snapshot tests to normalize line endings and Windows ^ to Unix `\`
3
+ * @public
4
+ */ export const snapshotSerializer = {
5
+ serialize: (val)=>{
6
+ const normalized = val.replaceAll(/(\^|\\)(\s*\n)/g, '\\\n');
7
+ return `"${normalized}"`;
8
+ },
9
+ test: (val)=>typeof val === 'string'
10
+ };
11
+
12
+ //# sourceMappingURL=snapshotSerializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/snapshotSerializer.ts"],"sourcesContent":["/**\n * Serializer for snapshot tests to normalize line endings and Windows ^ to Unix `\\`\n * @public\n */\nexport const snapshotSerializer = {\n serialize: (val: string) => {\n const normalized = val.replaceAll(/(\\^|\\\\)(\\s*\\n)/g, '\\\\\\n')\n return `\"${normalized}\"`\n },\n test: (val: unknown) => typeof val === 'string',\n}\n"],"names":["snapshotSerializer","serialize","val","normalized","replaceAll","test"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,qBAAqB;IAChCC,WAAW,CAACC;QACV,MAAMC,aAAaD,IAAIE,UAAU,CAAC,mBAAmB;QACrD,OAAO,CAAC,CAAC,EAAED,WAAW,CAAC,CAAC;IAC1B;IACAE,MAAM,CAACH,MAAiB,OAAOA,QAAQ;AACzC,EAAC"}
@@ -0,0 +1,19 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { captureOutput } from './captureOutput.js';
3
+ import { mockSanityCommand } from './mockSanityCommand.js';
4
+ import { mockTelemetry } from './mockTelemetry.js';
5
+ /**
6
+ * @public
7
+ */ export async function testCommand(command, args, options) {
8
+ // Mock the global telemetry store so we don't crash.
9
+ mockTelemetry(options?.mocks);
10
+ // If mocks provided, wrap the command with mockSanityCommand
11
+ const CommandToRun = options?.mocks ? mockSanityCommand(command, options.mocks) : command;
12
+ const commandInstancePromise = ()=>CommandToRun.run(args || [], {
13
+ root: fileURLToPath(import.meta.url),
14
+ ...options?.config
15
+ });
16
+ return captureOutput(commandInstancePromise, options?.capture);
17
+ }
18
+
19
+ //# sourceMappingURL=testCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/testCommand.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\n\nimport {Command, Config} from '@oclif/core'\n\nimport {type CaptureOptions, captureOutput, type CaptureResult} from './captureOutput.js'\nimport {mockSanityCommand, type MockSanityCommandOptions} from './mockSanityCommand.js'\nimport {mockTelemetry, type MockTelemetryOptions} from './mockTelemetry.js'\n\ntype CommandClass = (new (argv: string[], config: Config) => Command) & typeof Command\n\n/**\n * @public\n */\nexport interface TestCommandOptions {\n /**\n * Options for capturing output\n */\n capture?: CaptureOptions\n /**\n * Partial oclif config overrides\n */\n config?: Partial<Config>\n /**\n * Mock options for SanityCommand dependencies (config, project root, API clients).\n * When provided, the command is automatically wrapped with mockSanityCommand.\n */\n mocks?: MockSanityCommandOptions & MockTelemetryOptions\n}\n\n/**\n * @public\n */\nexport async function testCommand(\n command: CommandClass,\n args?: string[],\n options?: TestCommandOptions,\n): Promise<CaptureResult<unknown>> {\n // Mock the global telemetry store so we don't crash.\n mockTelemetry(options?.mocks)\n\n // If mocks provided, wrap the command with mockSanityCommand\n const CommandToRun = options?.mocks\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (mockSanityCommand(command as any, options.mocks) as CommandClass)\n : command\n\n const commandInstancePromise = () =>\n CommandToRun.run(args || [], {\n root: fileURLToPath(import.meta.url),\n ...options?.config,\n })\n\n return captureOutput(commandInstancePromise, options?.capture)\n}\n"],"names":["fileURLToPath","captureOutput","mockSanityCommand","mockTelemetry","testCommand","command","args","options","mocks","CommandToRun","commandInstancePromise","run","root","url","config","capture"],"mappings":"AAAA,SAAQA,aAAa,QAAO,WAAU;AAItC,SAA6BC,aAAa,QAA2B,qBAAoB;AACzF,SAAQC,iBAAiB,QAAsC,yBAAwB;AACvF,SAAQC,aAAa,QAAkC,qBAAoB;AAuB3E;;CAEC,GACD,OAAO,eAAeC,YACpBC,OAAqB,EACrBC,IAAe,EACfC,OAA4B;IAE5B,qDAAqD;IACrDJ,cAAcI,SAASC;IAEvB,6DAA6D;IAC7D,MAAMC,eAAeF,SAASC,QAEzBN,kBAAkBG,SAAgBE,QAAQC,KAAK,IAChDH;IAEJ,MAAMK,yBAAyB,IAC7BD,aAAaE,GAAG,CAACL,QAAQ,EAAE,EAAE;YAC3BM,MAAMZ,cAAc,YAAYa,GAAG;YACnC,GAAGN,SAASO,MAAM;QACpB;IAEF,OAAOb,cAAcS,wBAAwBH,SAASQ;AACxD"}
@@ -0,0 +1,112 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { copyFile, mkdir, readdir, readFile, stat, symlink, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { getFixturesPath, getTempPath } from '../utils/paths.js';
5
+ import { DEFAULT_FIXTURES } from './constants.js';
6
+ /**
7
+ * Recursively copy a directory, skipping specified folders.
8
+ *
9
+ * @param srcDir - Source directory to copy from
10
+ * @param destDir - Destination directory to copy to
11
+ * @param skip - Array of directory/file names to skip (e.g., ['node_modules', 'dist'])
12
+ * @internal
13
+ */ export async function testCopyDirectory(srcDir, destDir, skip = []) {
14
+ await mkdir(destDir, {
15
+ recursive: true
16
+ });
17
+ const entries = await readdir(srcDir);
18
+ for (const entry of entries){
19
+ if (skip.includes(entry)) {
20
+ continue;
21
+ }
22
+ const srcPath = join(srcDir, entry);
23
+ const destPath = join(destDir, entry);
24
+ const stats = await stat(srcPath);
25
+ await (stats.isDirectory() ? testCopyDirectory(srcPath, destPath, skip) : copyFile(srcPath, destPath));
26
+ }
27
+ }
28
+ /**
29
+ * Clones a fixture directory into a temporary directory with an isolated copy.
30
+ *
31
+ * The function creates a unique temporary copy of the specified fixture with:
32
+ * - A random unique ID to avoid conflicts between parallel tests
33
+ * - Symlinked node_modules for performance (from the global setup version)
34
+ * - Modified package.json name to prevent conflicts
35
+ *
36
+ * The fixture is first looked up in the temp directory (if global setup ran),
37
+ * otherwise it falls back to the bundled fixtures in the package.
38
+ *
39
+ * @param fixtureName - The name of the fixture to clone (e.g., 'basic-app', 'basic-studio')
40
+ * @param options - Configuration options
41
+ * @returns The absolute path to the temporary directory containing the fixture
42
+ *
43
+ * @public
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import {testFixture} from '@sanity/cli-test'
48
+ * import {describe, test} from 'vitest'
49
+ *
50
+ * describe('my test suite', () => {
51
+ * test('should work with basic-studio', async () => {
52
+ * const cwd = await testFixture('basic-studio')
53
+ * // ... run your tests in this directory
54
+ * })
55
+ * })
56
+ * ```
57
+ */ export async function testFixture(fixtureName, options = {}) {
58
+ const { tempDir } = options;
59
+ const { includeDist = false } = fixtureName in DEFAULT_FIXTURES ? DEFAULT_FIXTURES[fixtureName] : {};
60
+ const tempDirectory = getTempPath(tempDir);
61
+ // Fixtures are cloned in the tmp directory by the setup function
62
+ let tempFixturePath = join(tempDirectory, `fixture-${fixtureName}`);
63
+ try {
64
+ const stats = await stat(tempFixturePath);
65
+ if (!stats.isDirectory()) {
66
+ throw new Error(`${tempFixturePath} is not a directory`);
67
+ }
68
+ } catch (err) {
69
+ // If the cloned fixture doesn't exist, copy from the bundled fixtures
70
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
71
+ tempFixturePath = join(getFixturesPath(), fixtureName);
72
+ } else {
73
+ throw err;
74
+ }
75
+ }
76
+ const tempId = randomBytes(8).toString('hex');
77
+ const tempPath = join(tempDirectory, `fixture-${fixtureName}-${tempId}`);
78
+ // Always skip node_modules (will be symlinked), tmp and (unless specifically included) dist
79
+ const skipDirs = [
80
+ 'node_modules',
81
+ 'tmp',
82
+ ...includeDist ? [] : [
83
+ 'dist'
84
+ ]
85
+ ];
86
+ // Copy the fixture to the temp directory
87
+ await testCopyDirectory(tempFixturePath, tempPath, skipDirs);
88
+ // Symlink the node_modules directory for performance
89
+ await symlink(join(tempFixturePath, 'node_modules'), join(tempPath, 'node_modules'));
90
+ // Replace the package.json name with a temp name
91
+ const packageJsonPath = join(tempPath, 'package.json');
92
+ const packageJson = await readFile(packageJsonPath, 'utf8');
93
+ const packageJsonData = JSON.parse(packageJson);
94
+ packageJsonData.name = `${packageJsonData.name}-${tempId}`;
95
+ await writeFile(packageJsonPath, JSON.stringify(packageJsonData, null, 2));
96
+ return tempPath;
97
+ }
98
+ /**
99
+ * @deprecated Use {@link testFixture} instead. This function will be removed in a future release.
100
+ *
101
+ * Clones an example (now called fixture) directory into a temporary directory with an isolated copy.
102
+ *
103
+ * @param exampleName - The name of the example/fixture to clone (e.g., 'basic-app', 'basic-studio')
104
+ * @param options - Configuration options
105
+ * @returns The absolute path to the temporary directory containing the example/fixture
106
+ *
107
+ * @public
108
+ */ export async function testExample(exampleName, options = {}) {
109
+ return testFixture(exampleName, options);
110
+ }
111
+
112
+ //# sourceMappingURL=testFixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/testFixture.ts"],"sourcesContent":["import {randomBytes} from 'node:crypto'\nimport {copyFile, mkdir, readdir, readFile, stat, symlink, writeFile} from 'node:fs/promises'\nimport {join} from 'node:path'\n\nimport {getFixturesPath, getTempPath} from '../utils/paths.js'\nimport {DEFAULT_FIXTURES, type FixtureName} from './constants.js'\n\n/**\n * @deprecated Use {@link TestFixtureOptions} instead. This type alias will be removed in a future release.\n * @public\n */\nexport type TestExampleOptions = TestFixtureOptions\n\n/**\n * Recursively copy a directory, skipping specified folders.\n *\n * @param srcDir - Source directory to copy from\n * @param destDir - Destination directory to copy to\n * @param skip - Array of directory/file names to skip (e.g., ['node_modules', 'dist'])\n * @internal\n */\nexport async function testCopyDirectory(\n srcDir: string,\n destDir: string,\n skip: string[] = [],\n): Promise<void> {\n await mkdir(destDir, {recursive: true})\n\n const entries = await readdir(srcDir)\n\n for (const entry of entries) {\n if (skip.includes(entry)) {\n continue\n }\n\n const srcPath = join(srcDir, entry)\n const destPath = join(destDir, entry)\n\n const stats = await stat(srcPath)\n\n await (stats.isDirectory()\n ? testCopyDirectory(srcPath, destPath, skip)\n : copyFile(srcPath, destPath))\n }\n}\n\n/**\n * @public\n */\nexport interface TestFixtureOptions {\n /**\n * Custom temp directory. Defaults to process.cwd()/tmp\n */\n tempDir?: string\n}\n\n/**\n * Clones a fixture directory into a temporary directory with an isolated copy.\n *\n * The function creates a unique temporary copy of the specified fixture with:\n * - A random unique ID to avoid conflicts between parallel tests\n * - Symlinked node_modules for performance (from the global setup version)\n * - Modified package.json name to prevent conflicts\n *\n * The fixture is first looked up in the temp directory (if global setup ran),\n * otherwise it falls back to the bundled fixtures in the package.\n *\n * @param fixtureName - The name of the fixture to clone (e.g., 'basic-app', 'basic-studio')\n * @param options - Configuration options\n * @returns The absolute path to the temporary directory containing the fixture\n *\n * @public\n *\n * @example\n * ```typescript\n * import {testFixture} from '@sanity/cli-test'\n * import {describe, test} from 'vitest'\n *\n * describe('my test suite', () => {\n * test('should work with basic-studio', async () => {\n * const cwd = await testFixture('basic-studio')\n * // ... run your tests in this directory\n * })\n * })\n * ```\n */\nexport async function testFixture(\n fixtureName: FixtureName | (string & {}),\n options: TestFixtureOptions = {},\n): Promise<string> {\n const {tempDir} = options\n const {includeDist = false} =\n fixtureName in DEFAULT_FIXTURES ? DEFAULT_FIXTURES[fixtureName as FixtureName] : {}\n\n const tempDirectory = getTempPath(tempDir)\n\n // Fixtures are cloned in the tmp directory by the setup function\n let tempFixturePath = join(tempDirectory, `fixture-${fixtureName}`)\n\n try {\n const stats = await stat(tempFixturePath)\n if (!stats.isDirectory()) {\n throw new Error(`${tempFixturePath} is not a directory`)\n }\n } catch (err: unknown) {\n // If the cloned fixture doesn't exist, copy from the bundled fixtures\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {\n tempFixturePath = join(getFixturesPath(), fixtureName)\n } else {\n throw err\n }\n }\n\n const tempId = randomBytes(8).toString('hex')\n const tempPath = join(tempDirectory, `fixture-${fixtureName}-${tempId}`)\n\n // Always skip node_modules (will be symlinked), tmp and (unless specifically included) dist\n const skipDirs = ['node_modules', 'tmp', ...(includeDist ? [] : ['dist'])]\n\n // Copy the fixture to the temp directory\n await testCopyDirectory(tempFixturePath, tempPath, skipDirs)\n\n // Symlink the node_modules directory for performance\n await symlink(join(tempFixturePath, 'node_modules'), join(tempPath, 'node_modules'))\n\n // Replace the package.json name with a temp name\n const packageJsonPath = join(tempPath, 'package.json')\n const packageJson = await readFile(packageJsonPath, 'utf8')\n const packageJsonData = JSON.parse(packageJson)\n packageJsonData.name = `${packageJsonData.name}-${tempId}`\n await writeFile(packageJsonPath, JSON.stringify(packageJsonData, null, 2))\n\n return tempPath\n}\n\n/**\n * @deprecated Use {@link testFixture} instead. This function will be removed in a future release.\n *\n * Clones an example (now called fixture) directory into a temporary directory with an isolated copy.\n *\n * @param exampleName - The name of the example/fixture to clone (e.g., 'basic-app', 'basic-studio')\n * @param options - Configuration options\n * @returns The absolute path to the temporary directory containing the example/fixture\n *\n * @public\n */\nexport async function testExample(\n exampleName: FixtureName | (string & {}),\n options: TestFixtureOptions = {},\n): Promise<string> {\n return testFixture(exampleName, options)\n}\n"],"names":["randomBytes","copyFile","mkdir","readdir","readFile","stat","symlink","writeFile","join","getFixturesPath","getTempPath","DEFAULT_FIXTURES","testCopyDirectory","srcDir","destDir","skip","recursive","entries","entry","includes","srcPath","destPath","stats","isDirectory","testFixture","fixtureName","options","tempDir","includeDist","tempDirectory","tempFixturePath","Error","err","code","tempId","toString","tempPath","skipDirs","packageJsonPath","packageJson","packageJsonData","JSON","parse","name","stringify","testExample","exampleName"],"mappings":"AAAA,SAAQA,WAAW,QAAO,cAAa;AACvC,SAAQC,QAAQ,EAAEC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,OAAO,EAAEC,SAAS,QAAO,mBAAkB;AAC7F,SAAQC,IAAI,QAAO,YAAW;AAE9B,SAAQC,eAAe,EAAEC,WAAW,QAAO,oBAAmB;AAC9D,SAAQC,gBAAgB,QAAyB,iBAAgB;AAQjE;;;;;;;CAOC,GACD,OAAO,eAAeC,kBACpBC,MAAc,EACdC,OAAe,EACfC,OAAiB,EAAE;IAEnB,MAAMb,MAAMY,SAAS;QAACE,WAAW;IAAI;IAErC,MAAMC,UAAU,MAAMd,QAAQU;IAE9B,KAAK,MAAMK,SAASD,QAAS;QAC3B,IAAIF,KAAKI,QAAQ,CAACD,QAAQ;YACxB;QACF;QAEA,MAAME,UAAUZ,KAAKK,QAAQK;QAC7B,MAAMG,WAAWb,KAAKM,SAASI;QAE/B,MAAMI,QAAQ,MAAMjB,KAAKe;QAEzB,MAAOE,CAAAA,MAAMC,WAAW,KACpBX,kBAAkBQ,SAASC,UAAUN,QACrCd,SAASmB,SAASC,SAAQ;IAChC;AACF;AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,eAAeG,YACpBC,WAAwC,EACxCC,UAA8B,CAAC,CAAC;IAEhC,MAAM,EAACC,OAAO,EAAC,GAAGD;IAClB,MAAM,EAACE,cAAc,KAAK,EAAC,GACzBH,eAAed,mBAAmBA,gBAAgB,CAACc,YAA2B,GAAG,CAAC;IAEpF,MAAMI,gBAAgBnB,YAAYiB;IAElC,iEAAiE;IACjE,IAAIG,kBAAkBtB,KAAKqB,eAAe,CAAC,QAAQ,EAAEJ,aAAa;IAElE,IAAI;QACF,MAAMH,QAAQ,MAAMjB,KAAKyB;QACzB,IAAI,CAACR,MAAMC,WAAW,IAAI;YACxB,MAAM,IAAIQ,MAAM,GAAGD,gBAAgB,mBAAmB,CAAC;QACzD;IACF,EAAE,OAAOE,KAAc;QACrB,sEAAsE;QACtE,IAAIA,eAAeD,SAAS,UAAUC,OAAOA,IAAIC,IAAI,KAAK,UAAU;YAClEH,kBAAkBtB,KAAKC,mBAAmBgB;QAC5C,OAAO;YACL,MAAMO;QACR;IACF;IAEA,MAAME,SAASlC,YAAY,GAAGmC,QAAQ,CAAC;IACvC,MAAMC,WAAW5B,KAAKqB,eAAe,CAAC,QAAQ,EAAEJ,YAAY,CAAC,EAAES,QAAQ;IAEvE,4FAA4F;IAC5F,MAAMG,WAAW;QAAC;QAAgB;WAAWT,cAAc,EAAE,GAAG;YAAC;SAAO;KAAE;IAE1E,yCAAyC;IACzC,MAAMhB,kBAAkBkB,iBAAiBM,UAAUC;IAEnD,qDAAqD;IACrD,MAAM/B,QAAQE,KAAKsB,iBAAiB,iBAAiBtB,KAAK4B,UAAU;IAEpE,iDAAiD;IACjD,MAAME,kBAAkB9B,KAAK4B,UAAU;IACvC,MAAMG,cAAc,MAAMnC,SAASkC,iBAAiB;IACpD,MAAME,kBAAkBC,KAAKC,KAAK,CAACH;IACnCC,gBAAgBG,IAAI,GAAG,GAAGH,gBAAgBG,IAAI,CAAC,CAAC,EAAET,QAAQ;IAC1D,MAAM3B,UAAU+B,iBAAiBG,KAAKG,SAAS,CAACJ,iBAAiB,MAAM;IAEvE,OAAOJ;AACT;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeS,YACpBC,WAAwC,EACxCpB,UAA8B,CAAC,CAAC;IAEhC,OAAOF,YAAYsB,aAAapB;AAClC"}
@@ -0,0 +1,43 @@
1
+ import { captureOutput } from './captureOutput.js';
2
+ /**
3
+ * Test an oclif hook
4
+ *
5
+ * @public
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const result = await testHook(hook, {
10
+ * Command: Command.loadable,
11
+ * context: {
12
+ * config: Config.load({
13
+ * // CLI root is the directory of the package that contains the hook.
14
+ * root: path.resolve(fileURLToPath(import.meta.url), '../../../root'),
15
+ * }),
16
+ * },
17
+ * })
18
+ * ```
19
+ *
20
+ * @param hook - The hook to test
21
+ * @param options - The options for the hook
22
+ * @returns The result of the hook
23
+ */ export async function testHook(hook, options) {
24
+ const { config } = options;
25
+ const contextDefault = {
26
+ config,
27
+ debug: console.log,
28
+ error: console.error,
29
+ exit: process.exit,
30
+ log: console.log,
31
+ warn: console.warn
32
+ };
33
+ const { Command, context = contextDefault } = options ?? {};
34
+ const commandInstancePromise = ()=>hook.call(context, {
35
+ argv: [],
36
+ Command,
37
+ config,
38
+ context
39
+ });
40
+ return captureOutput(commandInstancePromise);
41
+ }
42
+
43
+ //# sourceMappingURL=testHook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/testHook.ts"],"sourcesContent":["import {Command, Config} from '@oclif/core'\nimport {type Hook, type Hooks} from '@oclif/core/hooks'\n\nimport {captureOutput} from './captureOutput.js'\n\ninterface Options {\n config: Config\n\n Command?: Command.Class\n context?: Hook.Context\n}\n\n/**\n * Test an oclif hook\n *\n * @public\n *\n * @example\n * ```ts\n * const result = await testHook(hook, {\n * Command: Command.loadable,\n * context: {\n * config: Config.load({\n * // CLI root is the directory of the package that contains the hook.\n * root: path.resolve(fileURLToPath(import.meta.url), '../../../root'),\n * }),\n * },\n * })\n * ```\n *\n * @param hook - The hook to test\n * @param options - The options for the hook\n * @returns The result of the hook\n */\nexport async function testHook<T extends keyof Hooks>(hook: Hook<T>, options: Options) {\n const {config} = options\n\n const contextDefault = {\n config,\n debug: console.log,\n error: console.error,\n exit: process.exit,\n log: console.log,\n warn: console.warn,\n }\n\n const {Command, context = contextDefault} = options ?? {}\n\n const commandInstancePromise = () =>\n hook.call(context, {\n argv: [],\n Command,\n config,\n context,\n })\n\n return captureOutput(commandInstancePromise)\n}\n"],"names":["captureOutput","testHook","hook","options","config","contextDefault","debug","console","log","error","exit","process","warn","Command","context","commandInstancePromise","call","argv"],"mappings":"AAGA,SAAQA,aAAa,QAAO,qBAAoB;AAShD;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,eAAeC,SAAgCC,IAAa,EAAEC,OAAgB;IACnF,MAAM,EAACC,MAAM,EAAC,GAAGD;IAEjB,MAAME,iBAAiB;QACrBD;QACAE,OAAOC,QAAQC,GAAG;QAClBC,OAAOF,QAAQE,KAAK;QACpBC,MAAMC,QAAQD,IAAI;QAClBF,KAAKD,QAAQC,GAAG;QAChBI,MAAML,QAAQK,IAAI;IACpB;IAEA,MAAM,EAACC,OAAO,EAAEC,UAAUT,cAAc,EAAC,GAAGF,WAAW,CAAC;IAExD,MAAMY,yBAAyB,IAC7Bb,KAAKc,IAAI,CAACF,SAAS;YACjBG,MAAM,EAAE;YACRJ;YACAT;YACAU;QACF;IAEF,OAAOd,cAAce;AACvB"}
@@ -0,0 +1,13 @@
1
+ import { access } from 'node:fs/promises';
2
+ /**
3
+ * Checks if a file exists and can be "accessed".
4
+ * Prone to race conditions, but good enough for our use cases.
5
+ *
6
+ * @param filePath - The path to the file to check
7
+ * @returns A promise that resolves to true if the file exists, false otherwise
8
+ * @internal
9
+ */ export function fileExists(filePath) {
10
+ return access(filePath).then(()=>true, ()=>false);
11
+ }
12
+
13
+ //# sourceMappingURL=fileExists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/fileExists.ts"],"sourcesContent":["import {access} from 'node:fs/promises'\n\n/**\n * Checks if a file exists and can be \"accessed\".\n * Prone to race conditions, but good enough for our use cases.\n *\n * @param filePath - The path to the file to check\n * @returns A promise that resolves to true if the file exists, false otherwise\n * @internal\n */\nexport function fileExists(filePath: string): Promise<boolean> {\n return access(filePath).then(\n () => true,\n () => false,\n )\n}\n"],"names":["access","fileExists","filePath","then"],"mappings":"AAAA,SAAQA,MAAM,QAAO,mBAAkB;AAEvC;;;;;;;CAOC,GACD,OAAO,SAASC,WAAWC,QAAgB;IACzC,OAAOF,OAAOE,UAAUC,IAAI,CAC1B,IAAM,MACN,IAAM;AAEV"}