@sanity/cli-test 0.0.2-alpha.1 → 0.0.2-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -0
- package/dist/index.d.ts +516 -3
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/test/constants.js +21 -0
- package/dist/test/constants.js.map +1 -0
- package/dist/test/createTestClient.js +53 -0
- package/dist/test/createTestClient.js.map +1 -0
- package/dist/test/createTestToken.js +13 -0
- package/dist/test/createTestToken.js.map +1 -0
- package/dist/test/mockApi.js +5 -3
- package/dist/test/mockApi.js.map +1 -1
- package/dist/test/mockSanityCommand.js +68 -0
- package/dist/test/mockSanityCommand.js.map +1 -0
- package/dist/test/mockTelemetry.js +22 -0
- package/dist/test/mockTelemetry.js.map +1 -0
- package/dist/test/setupFixtures.js +138 -0
- package/dist/test/setupFixtures.js.map +1 -0
- package/dist/test/snapshotSerializer.js +12 -0
- package/dist/test/snapshotSerializer.js.map +1 -0
- package/dist/test/testCommand.js +11 -4
- package/dist/test/testCommand.js.map +1 -1
- package/dist/test/testFixture.js +112 -0
- package/dist/test/testFixture.js.map +1 -0
- package/dist/test/testHook.js +23 -7
- package/dist/test/testHook.js.map +1 -1
- package/dist/utils/fileExists.js +13 -0
- package/dist/utils/fileExists.js.map +1 -0
- package/dist/utils/paths.js +66 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/vitest.d.ts +116 -0
- package/dist/vitest.js +22 -0
- package/dist/vitest.js.map +1 -0
- package/dist/vitestWorker.js +135 -0
- package/dist/vitestWorker.js.map +1 -0
- package/fixtures/basic-app/package.json +26 -0
- package/fixtures/basic-app/sanity.cli.ts +12 -0
- package/fixtures/basic-app/src/App.css +20 -0
- package/fixtures/basic-app/src/App.tsx +26 -0
- package/fixtures/basic-app/src/ExampleComponent.css +84 -0
- package/fixtures/basic-app/src/ExampleComponent.tsx +38 -0
- package/fixtures/basic-app/tsconfig.json +17 -0
- package/fixtures/basic-studio/package.json +28 -0
- package/fixtures/basic-studio/sanity.cli.ts +11 -0
- package/fixtures/basic-studio/sanity.config.ts +18 -0
- package/fixtures/basic-studio/schemaTypes/author.ts +52 -0
- package/fixtures/basic-studio/schemaTypes/blockContent.ts +71 -0
- package/fixtures/basic-studio/schemaTypes/category.ts +20 -0
- package/fixtures/basic-studio/schemaTypes/index.ts +6 -0
- package/fixtures/basic-studio/schemaTypes/post.ts +67 -0
- package/fixtures/basic-studio/tsconfig.json +17 -0
- package/fixtures/multi-workspace-studio/package.json +28 -0
- package/fixtures/multi-workspace-studio/sanity.cli.ts +11 -0
- package/fixtures/multi-workspace-studio/sanity.config.ts +37 -0
- package/fixtures/multi-workspace-studio/schemaTypes/author.ts +52 -0
- package/fixtures/multi-workspace-studio/schemaTypes/blockContent.ts +70 -0
- package/fixtures/multi-workspace-studio/schemaTypes/category.ts +20 -0
- package/fixtures/multi-workspace-studio/schemaTypes/index.ts +6 -0
- package/fixtures/multi-workspace-studio/schemaTypes/post.ts +67 -0
- package/fixtures/multi-workspace-studio/tsconfig.json +17 -0
- package/fixtures/prebuilt-app/README.md +3 -0
- package/fixtures/prebuilt-app/dist/favicon.ico +0 -0
- package/fixtures/prebuilt-app/dist/index.html +102 -0
- package/fixtures/prebuilt-app/dist/static/sanity-CtOxKsdo.css +24 -0
- package/fixtures/prebuilt-app/dist/static/sanity-D4a4eOYZ.js +17 -0
- package/fixtures/prebuilt-app/package.json +26 -0
- package/fixtures/prebuilt-app/sanity.cli.ts +12 -0
- package/fixtures/prebuilt-app/src/App.css +20 -0
- package/fixtures/prebuilt-app/src/App.tsx +24 -0
- package/fixtures/prebuilt-app/tsconfig.json +17 -0
- package/fixtures/prebuilt-studio/README.md +3 -0
- package/fixtures/prebuilt-studio/dist/favicon.ico +0 -0
- package/fixtures/prebuilt-studio/dist/index.html +113 -0
- package/fixtures/prebuilt-studio/dist/static/sanity-DxH-rpFr.js +9 -0
- package/fixtures/prebuilt-studio/package.json +25 -0
- package/fixtures/prebuilt-studio/sanity.cli.ts +11 -0
- package/fixtures/prebuilt-studio/sanity.config.ts +11 -0
- package/fixtures/prebuilt-studio/tsconfig.json +17 -0
- package/fixtures/worst-case-studio/README.md +21 -0
- package/fixtures/worst-case-studio/package.json +32 -0
- package/fixtures/worst-case-studio/sanity.cli.ts +16 -0
- package/fixtures/worst-case-studio/sanity.config.tsx +49 -0
- package/fixtures/worst-case-studio/src/defines.ts +8 -0
- package/fixtures/worst-case-studio/src/descriptionIcon.svg +7 -0
- package/fixtures/worst-case-studio/src/descriptionInput.module.css +13 -0
- package/fixtures/worst-case-studio/src/descriptionInput.tsx +55 -0
- package/fixtures/worst-case-studio/src/schemaTypes/author.ts +52 -0
- package/fixtures/worst-case-studio/src/schemaTypes/blockContent.ts +70 -0
- package/fixtures/worst-case-studio/src/schemaTypes/category.ts +20 -0
- package/fixtures/worst-case-studio/src/schemaTypes/index.ts +6 -0
- package/fixtures/worst-case-studio/src/schemaTypes/post.ts +71 -0
- package/fixtures/worst-case-studio/src/typings.d.ts +37 -0
- package/fixtures/worst-case-studio/tsconfig.json +22 -0
- package/package.json +52 -22
- package/dist/test/captureOutput.d.ts +0 -33
- package/dist/test/mockApi.d.ts +0 -34
- package/dist/test/testCommand.d.ts +0 -6
- package/dist/test/testHook.d.ts +0 -8
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
let projectRoot = options.projectRoot;
|
|
34
|
+
if (projectRoot) {
|
|
35
|
+
projectRoot = {
|
|
36
|
+
...projectRoot,
|
|
37
|
+
directory: convertToSystemPath(projectRoot.directory),
|
|
38
|
+
path: convertToSystemPath(projectRoot.path)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Create a subclass that overrides methods when mocks are provided
|
|
42
|
+
// Note: we use @ts-expect-error because TypeScript can't properly infer
|
|
43
|
+
// the relationship between the generic CommandClass and SanityCommand
|
|
44
|
+
// @ts-expect-error - TypeScript struggles with abstract class subclassing
|
|
45
|
+
class MockedCommand extends CommandClass {
|
|
46
|
+
getCliConfig() {
|
|
47
|
+
if (options.cliConfig) {
|
|
48
|
+
return Promise.resolve(options.cliConfig);
|
|
49
|
+
}
|
|
50
|
+
return super.getCliConfig();
|
|
51
|
+
}
|
|
52
|
+
getProjectRoot() {
|
|
53
|
+
if (options.projectRoot) {
|
|
54
|
+
return Promise.resolve(options.projectRoot);
|
|
55
|
+
}
|
|
56
|
+
return super.getProjectRoot();
|
|
57
|
+
}
|
|
58
|
+
resolveIsInteractive() {
|
|
59
|
+
if (options.isInteractive !== undefined) {
|
|
60
|
+
return options.isInteractive;
|
|
61
|
+
}
|
|
62
|
+
return super.resolveIsInteractive();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return MockedCommand;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//# 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 * 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 let projectRoot = options.projectRoot\n if (projectRoot) {\n projectRoot = {\n ...projectRoot,\n directory: convertToSystemPath(projectRoot.directory),\n path: convertToSystemPath(projectRoot.path),\n }\n }\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.cliConfig) {\n return Promise.resolve(options.cliConfig)\n }\n return super.getCliConfig()\n }\n\n protected getProjectRoot(): Promise<ProjectRootResult> {\n if (options.projectRoot) {\n return Promise.resolve(options.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","MockedCommand","getCliConfig","cliConfig","Promise","resolve","getProjectRoot","resolveIsInteractive","isInteractive","undefined"],"mappings":"AAGA,SAAQA,mBAAmB,QAAO,oBAAmB;AACrD,SAAQC,eAAe,QAAO,uBAAsB;AAwBpD;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,kBACdC,YAAe,EACfC,UAAoC,CAAC,CAAC;IAEtC,IAAIA,QAAQC,KAAK,EAAE;QACjBJ,gBAAgBG,QAAQC,KAAK;IAC/B;IAEA,mEAAmE;IACnE,IAAIC,cAAcF,QAAQE,WAAW;IACrC,IAAIA,aAAa;QACfA,cAAc;YACZ,GAAGA,WAAW;YACdC,WAAWP,oBAAoBM,YAAYC,SAAS;YACpDC,MAAMR,oBAAoBM,YAAYE,IAAI;QAC5C;IACF;IAEA,mEAAmE;IACnE,wEAAwE;IACxE,sEAAsE;IACtE,0EAA0E;IAC1E,MAAMC,sBAAsBN;QAChBO,eAAmC;YAC3C,IAAIN,QAAQO,SAAS,EAAE;gBACrB,OAAOC,QAAQC,OAAO,CAACT,QAAQO,SAAS;YAC1C;YACA,OAAO,KAAK,CAACD;QACf;QAEUI,iBAA6C;YACrD,IAAIV,QAAQE,WAAW,EAAE;gBACvB,OAAOM,QAAQC,OAAO,CAACT,QAAQE,WAAW;YAC5C;YACA,OAAO,KAAK,CAACQ;QACf;QAEUC,uBAAgC;YACxC,IAAIX,QAAQY,aAAa,KAAKC,WAAW;gBACvC,OAAOb,QAAQY,aAAa;YAC9B;YACA,OAAO,KAAK,CAACD;QACf;IACF;IAEA,OAAON;AACT"}
|
|
@@ -0,0 +1,22 @@
|
|
|
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')] = telemetry;
|
|
19
|
+
return telemetry;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//# 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')] = telemetry\n\n return telemetry\n}\n"],"names":["vi","mockTelemetry","options","telemetry","trace","fn","mockReturnValue","complete","error","log","newContext","start","updateUserProperties","globalThis","Symbol","for"],"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,GAAGZ;IAE3D,OAAOA;AACT,EAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
}
|
|
110
|
+
}
|
|
111
|
+
spinner.succeed('Test environment initialized');
|
|
112
|
+
} catch (error) {
|
|
113
|
+
spinner.fail('Failed to initialize test environment');
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Teardown function to clean up test fixtures.
|
|
119
|
+
*
|
|
120
|
+
* Removes the temp directory created by setupTestFixtures.
|
|
121
|
+
*
|
|
122
|
+
* This function is designed to be used with vitest globalSetup.
|
|
123
|
+
*
|
|
124
|
+
* @public
|
|
125
|
+
*
|
|
126
|
+
* @param options - Configuration options
|
|
127
|
+
*/ export async function teardown(options = {}) {
|
|
128
|
+
const { tempDir } = options;
|
|
129
|
+
const tempDirectory = getTempPath(tempDir);
|
|
130
|
+
// Remove the tmp directory
|
|
131
|
+
await rm(tempDirectory, {
|
|
132
|
+
force: true,
|
|
133
|
+
maxRetries: 3,
|
|
134
|
+
recursive: true
|
|
135
|
+
}).catch(()=>{});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//# 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 )\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","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"}
|
package/dist/test/testCommand.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { fileURLToPath } from 'node:url';
|
|
3
2
|
import { captureOutput } from './captureOutput.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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),
|
|
7
14
|
...options?.config
|
|
8
15
|
});
|
|
9
16
|
return captureOutput(commandInstancePromise, options?.capture);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/testCommand.ts"],"sourcesContent":["import
|
|
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"}
|
package/dist/test/testHook.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { Config } from '@oclif/core';
|
|
4
1
|
import { captureOutput } from './captureOutput.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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;
|
|
9
25
|
const contextDefault = {
|
|
10
26
|
config,
|
|
11
27
|
debug: console.log,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/testHook.ts"],"sourcesContent":["import
|
|
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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
// Capture the initial working directory before any tests change it
|
|
3
|
+
const INITIAL_CWD = process.cwd();
|
|
4
|
+
/**
|
|
5
|
+
* Gets the path to the fixtures directory bundled with this package.
|
|
6
|
+
*
|
|
7
|
+
* The fixtures are copied during build and bundled with the published package.
|
|
8
|
+
* This function works the same whether the package is used in a monorepo
|
|
9
|
+
* or installed from npm.
|
|
10
|
+
*
|
|
11
|
+
* @returns Absolute path to the fixtures directory
|
|
12
|
+
* @internal
|
|
13
|
+
*/ export function getFixturesPath() {
|
|
14
|
+
// From dist/utils/paths.js -> ../../fixtures
|
|
15
|
+
return resolve(import.meta.dirname, '../../fixtures');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Gets the path to the temporary directory for test fixtures.
|
|
19
|
+
*
|
|
20
|
+
* Uses the initial working directory captured when this module was first loaded,
|
|
21
|
+
* not process.cwd() which may change during test execution.
|
|
22
|
+
*
|
|
23
|
+
* @param customTempDir - Optional custom temp directory path
|
|
24
|
+
* @returns Absolute path to temp directory (default: initial cwd/tmp)
|
|
25
|
+
* @internal
|
|
26
|
+
*/ export function getTempPath(customTempDir) {
|
|
27
|
+
return customTempDir || resolve(INITIAL_CWD, 'tmp');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Gets the current Windows drive letter from process.cwd().
|
|
31
|
+
* Falls back to 'C:\\' if detection fails.
|
|
32
|
+
*
|
|
33
|
+
* @returns Drive letter with backslash (e.g., 'C:\\', 'D:\\') or empty string on Unix
|
|
34
|
+
* @internal
|
|
35
|
+
*/ export function getCurrentDrive() {
|
|
36
|
+
if (process.platform !== 'win32') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const match = cwd.match(/^([A-Z]:)[\\/]/);
|
|
41
|
+
return match ? match[1] + '\\' : 'C:\\';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Converts Unix-style paths to platform-appropriate paths.
|
|
45
|
+
* On Windows:
|
|
46
|
+
* - Absolute paths starting with '/': adds drive letter and converts to backslashes
|
|
47
|
+
* - Relative/partial paths: converts forward slashes to backslashes
|
|
48
|
+
* On Unix: keeps paths as-is.
|
|
49
|
+
*
|
|
50
|
+
* @param pathStr - Unix-style path (e.g., '/test/path' or '.config/file.json')
|
|
51
|
+
* @returns Platform-appropriate path
|
|
52
|
+
* @internal
|
|
53
|
+
*/ export function convertToSystemPath(pathStr) {
|
|
54
|
+
if (process.platform === 'win32') {
|
|
55
|
+
if (pathStr.startsWith('/')) {
|
|
56
|
+
// Absolute Unix path - add drive letter
|
|
57
|
+
const drive = getCurrentDrive();
|
|
58
|
+
return `${drive}${pathStr.slice(1).replaceAll('/', '\\')}`;
|
|
59
|
+
}
|
|
60
|
+
// Relative/partial path - just convert separators
|
|
61
|
+
return pathStr.replaceAll('/', '\\');
|
|
62
|
+
}
|
|
63
|
+
return pathStr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/paths.ts"],"sourcesContent":["import {resolve} from 'node:path'\n\n// Capture the initial working directory before any tests change it\nconst INITIAL_CWD = process.cwd()\n\n/**\n * Gets the path to the fixtures directory bundled with this package.\n *\n * The fixtures are copied during build and bundled with the published package.\n * This function works the same whether the package is used in a monorepo\n * or installed from npm.\n *\n * @returns Absolute path to the fixtures directory\n * @internal\n */\nexport function getFixturesPath(): string {\n // From dist/utils/paths.js -> ../../fixtures\n return resolve(import.meta.dirname, '../../fixtures')\n}\n\n/**\n * Gets the path to the temporary directory for test fixtures.\n *\n * Uses the initial working directory captured when this module was first loaded,\n * not process.cwd() which may change during test execution.\n *\n * @param customTempDir - Optional custom temp directory path\n * @returns Absolute path to temp directory (default: initial cwd/tmp)\n * @internal\n */\nexport function getTempPath(customTempDir?: string): string {\n return customTempDir || resolve(INITIAL_CWD, 'tmp')\n}\n\n/**\n * Gets the current Windows drive letter from process.cwd().\n * Falls back to 'C:\\\\' if detection fails.\n *\n * @returns Drive letter with backslash (e.g., 'C:\\\\', 'D:\\\\') or empty string on Unix\n * @internal\n */\nexport function getCurrentDrive(): string {\n if (process.platform !== 'win32') {\n return ''\n }\n const cwd = process.cwd()\n const match = cwd.match(/^([A-Z]:)[\\\\/]/)\n return match ? match[1] + '\\\\' : 'C:\\\\'\n}\n\n/**\n * Converts Unix-style paths to platform-appropriate paths.\n * On Windows:\n * - Absolute paths starting with '/': adds drive letter and converts to backslashes\n * - Relative/partial paths: converts forward slashes to backslashes\n * On Unix: keeps paths as-is.\n *\n * @param pathStr - Unix-style path (e.g., '/test/path' or '.config/file.json')\n * @returns Platform-appropriate path\n * @internal\n */\nexport function convertToSystemPath(pathStr: string): string {\n if (process.platform === 'win32') {\n if (pathStr.startsWith('/')) {\n // Absolute Unix path - add drive letter\n const drive = getCurrentDrive()\n return `${drive}${pathStr.slice(1).replaceAll('/', '\\\\')}`\n }\n // Relative/partial path - just convert separators\n return pathStr.replaceAll('/', '\\\\')\n }\n return pathStr\n}\n"],"names":["resolve","INITIAL_CWD","process","cwd","getFixturesPath","dirname","getTempPath","customTempDir","getCurrentDrive","platform","match","convertToSystemPath","pathStr","startsWith","drive","slice","replaceAll"],"mappings":"AAAA,SAAQA,OAAO,QAAO,YAAW;AAEjC,mEAAmE;AACnE,MAAMC,cAAcC,QAAQC,GAAG;AAE/B;;;;;;;;;CASC,GACD,OAAO,SAASC;IACd,6CAA6C;IAC7C,OAAOJ,QAAQ,YAAYK,OAAO,EAAE;AACtC;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASC,YAAYC,aAAsB;IAChD,OAAOA,iBAAiBP,QAAQC,aAAa;AAC/C;AAEA;;;;;;CAMC,GACD,OAAO,SAASO;IACd,IAAIN,QAAQO,QAAQ,KAAK,SAAS;QAChC,OAAO;IACT;IACA,MAAMN,MAAMD,QAAQC,GAAG;IACvB,MAAMO,QAAQP,IAAIO,KAAK,CAAC;IACxB,OAAOA,QAAQA,KAAK,CAAC,EAAE,GAAG,OAAO;AACnC;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,oBAAoBC,OAAe;IACjD,IAAIV,QAAQO,QAAQ,KAAK,SAAS;QAChC,IAAIG,QAAQC,UAAU,CAAC,MAAM;YAC3B,wCAAwC;YACxC,MAAMC,QAAQN;YACd,OAAO,GAAGM,QAAQF,QAAQG,KAAK,CAAC,GAAGC,UAAU,CAAC,KAAK,OAAO;QAC5D;QACA,kDAAkD;QAClD,OAAOJ,QAAQI,UAAU,CAAC,KAAK;IACjC;IACA,OAAOJ;AACT"}
|