diff --git a/.changeset/sixty-roses-learn.md b/.changeset/sixty-roses-learn.md new file mode 100644 index 00000000..ec65d482 --- /dev/null +++ b/.changeset/sixty-roses-learn.md @@ -0,0 +1,5 @@ +--- +'create-solana-dapp': patch +--- + +clean up init script utils diff --git a/src/utils/create-app-task-run-init-script.ts b/src/utils/create-app-task-run-init-script.ts index a13b2161..2324687d 100644 --- a/src/utils/create-app-task-run-init-script.ts +++ b/src/utils/create-app-task-run-init-script.ts @@ -1,13 +1,12 @@ import { log } from '@clack/prompts' -import { join } from 'node:path' -import { ensureTargetPath } from './ensure-target-path' import { GetArgsResult } from './get-args-result' -import { deleteInitScript, getInitScript, InitScript } from './get-init-script' import { getPackageJson } from './get-package-json' +import { initScriptDelete } from './init-script-delete' +import { initScriptInstructions } from './init-script-instructions' +import { initScriptRename } from './init-script-rename' +import { initScriptKey } from './init-script-schema' import { initScriptVersion } from './init-script-version' -import { searchAndReplace } from './search-and-replace' import { Task, taskFail } from './vendor/clack-tasks' -import { namesValues } from './vendor/names' export function createAppTaskRunInitScript(args: GetArgsResult): Task { return { @@ -15,74 +14,28 @@ export function createAppTaskRunInitScript(args: GetArgsResult): Task { title: 'Running init script', task: async (result) => { try { - const init = getInitScript(args.targetDirectory) + const { contents } = getPackageJson(args.targetDirectory) + const init = contents[initScriptKey] if (!init) { - return result({ message: 'Repository does not have an init script' }) + return result({ message: 'Init script not found' }) } if (args.verbose) { - log.warn(`Running init script`) + log.warn(`Init script started`) } await initScriptVersion(init.versions, args.verbose) - if (args.verbose) { - log.warn(`initCheckVersion done`) - } - await initRename(args, init, args.verbose) - if (args.verbose) { - log.warn(`initRename done`) - } - const instructions: string[] = (initInstructions(init) ?? []) + await initScriptRename(args, init.rename, args.verbose) + + const instructions: string[] = initScriptInstructions(init.instructions, args.verbose) ?.filter(Boolean) .map((msg) => msg.replace('{pm}', args.packageManager)) - if (args.verbose) { - log.warn(`initInstructions done`) - } - deleteInitScript(args.targetDirectory) - if (args.verbose) { - log.warn(`deleteInitScript done`) - } - return result({ message: 'Executed init script', instructions }) + initScriptDelete(args) + return result({ message: 'Init script done', instructions }) } catch (error) { - taskFail(`init: Error running init script: ${error}`) + taskFail(`Error running init script: ${error}`) } }, } } - -async function initRename(args: GetArgsResult, init: InitScript, verbose: boolean) { - const { contents } = getPackageJson(args.targetDirectory) - // Rename template from package.json to project name throughout the whole project - if (contents.name) { - await searchAndReplace(args.targetDirectory, [contents.name], [args.name], false, verbose) - } - - // Return early if there are no renames defined in the init script - if (!init?.rename) { - return - } - - // Loop through each word in the rename object - for (const from of Object.keys(init.rename)) { - // Get the 'to' property from the rename object - const to = init.rename[from].to.replace('{{name}}', args.name.replace(/-/g, '')) - - // Get the name matrix for the 'from' and the 'to' value - const fromNames = namesValues(from) - const toNames = namesValues(to) - - for (const path of init.rename[from].paths) { - const targetPath = join(args.targetDirectory, path) - if (!(await ensureTargetPath(targetPath))) { - console.error(`init-script.rename: target does not exist ${targetPath}`) - continue - } - await searchAndReplace(join(args.targetDirectory, path), fromNames, toNames, args.dryRun) - } - } -} - -function initInstructions(init: InitScript) { - return init?.instructions?.length === 0 ? [] : init?.instructions -} diff --git a/src/utils/create-app.ts b/src/utils/create-app.ts index dc9ffd59..5ddaedd9 100644 --- a/src/utils/create-app.ts +++ b/src/utils/create-app.ts @@ -11,7 +11,7 @@ export async function createApp(args: GetArgsResult) { createAppTaskCloneTemplate(args), // Install the dependencies createAppTaskInstallDependencies(args), - // Run the init script define in package.json .init property + // Run the (optional) init script defined in package.json createAppTaskRunInitScript(args), // Initialize git repository createAppTaskInitializeGit(args), diff --git a/src/utils/get-init-script.ts b/src/utils/get-init-script.ts deleted file mode 100644 index 96d070f8..00000000 --- a/src/utils/get-init-script.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { writeFileSync } from 'node:fs' -import { z } from 'zod' -import { getPackageJson } from './get-package-json' - -export const initScriptKey = 'create-solana-dapp' - -export function getInitScript(targetDirectory: string): InitScript | undefined { - const { contents } = getPackageJson(targetDirectory) - if (!contents) { - throw new Error('Error loading package.json') - } - const init = contents[initScriptKey] - if (!init) { - return undefined - } - const parsed = InitScriptSchema.safeParse(init) - if (!parsed.success) { - throw new Error(`Invalid init script: ${parsed.error.message}`) - } - return parsed.data -} - -export function deleteInitScript(targetDirectory: string) { - const { path, contents } = getPackageJson(targetDirectory) - delete contents[initScriptKey] - writeFileSync(path, JSON.stringify(contents, undefined, 2) + '\n') -} - -const InitScriptVersionsSchema = z.object({ - adb: z.string().optional(), - anchor: z.string().optional(), - solana: z.string().optional(), -}) - -const InitScriptSchema = z - .object({ - instructions: z.array(z.string()).optional(), - rename: z - .record( - z.object({ - to: z.string(), - paths: z.array(z.string()), - }), - ) - .optional(), - versions: InitScriptVersionsSchema.optional(), - }) - .optional() - -export type InitScript = z.infer -export type InitScriptVersions = z.infer diff --git a/src/utils/get-package-json.ts b/src/utils/get-package-json.ts index b0c19ed6..e3034b79 100644 --- a/src/utils/get-package-json.ts +++ b/src/utils/get-package-json.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync } from 'node:fs' import { z } from 'zod' import { getPackageJsonPath } from './get-package-json-path' +import { initScriptKey, InitScriptSchema } from './init-script-schema' export function getPackageJson(targetDirectory: string): { path: string; contents: PackageJson } { const path = getPackageJsonPath(targetDirectory) @@ -26,6 +27,7 @@ const PackageJsonSchema = z .object({ name: z.string().optional(), scripts: z.record(z.string()).optional(), + [initScriptKey]: InitScriptSchema.optional(), }) .passthrough() diff --git a/src/utils/get-version-command.ts b/src/utils/get-version-command.ts new file mode 100644 index 00000000..c0d84e45 --- /dev/null +++ b/src/utils/get-version-command.ts @@ -0,0 +1,37 @@ +const commandMap: Record = { + adb: { + command: 'adb --version', + name: 'Adb ', + regex: /Version (\d+\.\d+\.\d+)/, + }, + anchor: { + command: 'anchor --version', + name: 'Anchor', + regex: /anchor-cli (\d+\.\d+\.\d+)/, + }, + avm: { + command: 'avm --version', + name: 'AVM ', + regex: /avm (\d+\.\d+\.\d+)/, + }, + rust: { + command: 'rustc --version', + name: 'Rust ', + regex: /rustc (\d+\.\d+\.\d+)/, + }, + solana: { + command: 'solana --version', + name: 'Solana', + regex: /solana-cli (\d+\.\d+\.\d+)/, + }, +} + +export type VersionCommand = keyof typeof commandMap + +export function getVersionCommand(command: VersionCommand): { command: string; name: string; regex: RegExp } { + return commandMap[command] +} + +export function getVersionCommandNames(): string[] { + return Object.keys(commandMap) +} diff --git a/src/utils/get-version-urls.ts b/src/utils/get-version-urls.ts new file mode 100644 index 00000000..cd037b52 --- /dev/null +++ b/src/utils/get-version-urls.ts @@ -0,0 +1,31 @@ +import { VersionCommand } from './get-version-command' + +type VersionUrls = { + install?: string + update?: string +} + +const urls: Record = { + adb: { + install: + 'https://docs.expo.dev/get-started/set-up-your-environment/?platform=android&device=physical&mode=development-build&buildEnv=local', + }, + anchor: { + install: 'https://www.anchor-lang.com/docs/installation', + update: 'https://www.anchor-lang.com/release-notes/{required}', + }, + solana: { + install: 'https://docs.solana.com/cli/install-solana-cli-tools', + }, +} + +export function getVersionUrls(command: VersionCommand, required: string): VersionUrls { + const { install, update } = urls[command] ?? {} + + return install || update + ? { + install: install?.replace('{required}', required), + update: (update ?? install)?.replace('{required}', required), + } + : {} +} diff --git a/src/utils/get-version.ts b/src/utils/get-version.ts index c05efec9..ff5cab00 100644 --- a/src/utils/get-version.ts +++ b/src/utils/get-version.ts @@ -1,35 +1,8 @@ +import { getVersionCommand, VersionCommand } from './get-version-command' import { parseVersion } from './parse-version' -export const versionCommands: Record = { - adb: { - command: 'adb --version', - name: 'Adb ', - regex: /Version (\d+\.\d+\.\d+)/, - }, - anchor: { - command: 'anchor --version', - name: 'Anchor', - regex: /anchor-cli (\d+\.\d+\.\d+)/, - }, - avm: { - command: 'avm --version', - name: 'AVM ', - regex: /avm (\d+\.\d+\.\d+)/, - }, - rust: { - command: 'rustc --version', - name: 'Rust ', - regex: /rustc (\d+\.\d+\.\d+)/, - }, - solana: { - command: 'solana --version', - name: 'Solana', - regex: /solana-cli (\d+\.\d+\.\d+)/, - }, -} - -export function getVersion(command: keyof typeof versionCommands): string | undefined { - const cmd = versionCommands[command] +export function getVersion(command: VersionCommand): string | undefined { + const cmd = getVersionCommand(command) if (!cmd) { throw new Error(`Unknown command ${command}`) } diff --git a/src/utils/init-script-delete.ts b/src/utils/init-script-delete.ts new file mode 100644 index 00000000..22cd924a --- /dev/null +++ b/src/utils/init-script-delete.ts @@ -0,0 +1,15 @@ +import { log } from '@clack/prompts' +import { writeFileSync } from 'node:fs' +import { GetArgsResult } from './get-args-result' +import { getPackageJson } from './get-package-json' +import { initScriptKey } from './init-script-schema' + +export function initScriptDelete(args: GetArgsResult) { + const tag = `initScriptDelete` + const { path, contents } = getPackageJson(args.targetDirectory) + delete contents[initScriptKey] + writeFileSync(path, JSON.stringify(contents, undefined, 2) + '\n') + if (args.verbose) { + log.warn(`${tag}: deleted ${initScriptKey} from package.json`) + } +} diff --git a/src/utils/init-script-instructions.ts b/src/utils/init-script-instructions.ts new file mode 100644 index 00000000..272f8bb7 --- /dev/null +++ b/src/utils/init-script-instructions.ts @@ -0,0 +1,16 @@ +import { log } from '@clack/prompts' +import { InitScriptInstructions } from './init-script-schema' + +export function initScriptInstructions(instructions?: InitScriptInstructions, verbose = false): string[] { + const tag = `initScriptInstructions` + if (!instructions || instructions.length === 0) { + if (verbose) { + log.warn(`${tag}: no instructions found`) + } + return [] + } + if (verbose) { + log.warn(`${tag}: ${instructions.length} instructions found`) + } + return instructions +} diff --git a/src/utils/init-script-rename.ts b/src/utils/init-script-rename.ts new file mode 100644 index 00000000..33318902 --- /dev/null +++ b/src/utils/init-script-rename.ts @@ -0,0 +1,54 @@ +import { log } from '@clack/prompts' +import { join } from 'node:path' +import { ensureTargetPath } from './ensure-target-path' +import { GetArgsResult } from './get-args-result' +import { getPackageJson } from './get-package-json' +import { InitScriptRename } from './init-script-schema' +import { searchAndReplace } from './search-and-replace' +import { namesValues } from './vendor/names' + +export async function initScriptRename(args: GetArgsResult, rename?: InitScriptRename, verbose = false) { + const tag = `initScriptRename` + const { contents } = getPackageJson(args.targetDirectory) + // Rename template from package.json to project name throughout the whole project + if (contents.name) { + if (args.verbose) { + log.warn(`${tag}: renaming template name '${contents.name}' to project name '${args.name}'`) + } + await searchAndReplace(args.targetDirectory, [contents.name], [args.name], false, verbose) + } + + // Return early if there are no renames defined in the init script + if (!rename) { + if (args.verbose) { + log.warn(`${tag}: no renames found`) + } + return + } + + // Loop through each word in the rename object + for (const from of Object.keys(rename)) { + // Get the 'to' property from the rename object + const to = rename[from].to.replace('{{name}}', args.name.replace(/-/g, '')) + + // Get the name matrix for the 'from' and the 'to' value + const fromNames = namesValues(from) + const toNames = namesValues(to) + + for (const path of rename[from].paths) { + const targetPath = join(args.targetDirectory, path) + if (!(await ensureTargetPath(targetPath))) { + log.error(`${tag}: target does not exist ${targetPath}`) + continue + } + if (args.verbose) { + log.warn(`${tag}: ${targetPath} -> ${fromNames.join('|')} -> ${toNames.join('|')}`) + } + await searchAndReplace(targetPath, fromNames, toNames, args.dryRun, args.verbose) + } + } + + if (args.verbose) { + log.warn(`${tag}: done`) + } +} diff --git a/src/utils/init-script-schema.ts b/src/utils/init-script-schema.ts new file mode 100644 index 00000000..bfd24e00 --- /dev/null +++ b/src/utils/init-script-schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +// This is the key used in package.json to store the init script +export const initScriptKey = 'create-solana-dapp' + +export const InitScriptSchemaInstructions = z.array(z.string()) + +export const InitScriptSchemaVersions = z.object({ + adb: z.string().optional(), + anchor: z.string().optional(), + solana: z.string().optional(), +}) + +export const InitScriptSchemaRename = z.record( + z.object({ + to: z.string(), + // TODO: Rename 'paths' to 'in' (breaking change) + paths: z.array(z.string()), + }), +) + +export const InitScriptSchema = z.object({ + instructions: InitScriptSchemaInstructions.optional(), + rename: InitScriptSchemaRename.optional(), + versions: InitScriptSchemaVersions.optional(), +}) + +export type InitScriptInstructions = z.infer +export type InitScriptRename = z.infer +export type InitScriptVersions = z.infer diff --git a/src/utils/init-script-version-adb.ts b/src/utils/init-script-version-adb.ts deleted file mode 100644 index 446b49bc..00000000 --- a/src/utils/init-script-version-adb.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { log } from '@clack/prompts' -import { bold, yellow } from 'picocolors' -import { getVersion } from './get-version' -import { validateVersion } from './validate-version' - -export async function initScriptVersionAdb(required?: string, verbose = false) { - if (!required) { - return - } - try { - const { valid, version } = validateVersion({ required, version: getVersion('adb') }) - if (verbose) { - log.warn(`initScriptVersionAdb: required: ${required}, version: ${version ?? '*none*'}, valid: ${valid}`) - } - if (!version) { - log.warn( - [ - bold(yellow(`Could not find adb version. Please install adb.`)), - 'https://docs.expo.dev/get-started/set-up-your-environment/?platform=android&device=physical&mode=development-build&buildEnv=local', - ].join(' '), - ) - } else if (!valid) { - log.warn( - [ - yellow(`Found adb version ${version}. Expected adb version ${required}.`), - 'https://docs.expo.dev/get-started/set-up-your-environment/?platform=android&device=physical&mode=development-build&buildEnv=local', - ].join(' '), - ) - } - } catch (error_) { - log.warn(`Error ${error_}`) - } -} diff --git a/src/utils/init-script-version-anchor.ts b/src/utils/init-script-version-anchor.ts deleted file mode 100644 index daae2546..00000000 --- a/src/utils/init-script-version-anchor.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { log } from '@clack/prompts' -import { bold, yellow } from 'picocolors' -import { getVersion } from './get-version' -import { validateVersion } from './validate-version' - -export async function initScriptVersionAnchor(required?: string, verbose = false) { - if (!required) { - return - } - try { - const { valid, version } = validateVersion({ required, version: getVersion('anchor') }) - if (verbose) { - log.warn(`initScriptVersionAnchor: required: ${required}, version: ${version ?? '*none*'}, valid: ${valid}`) - } - if (!version) { - log.warn( - [ - bold(yellow(`Could not find Anchor version. Please install Anchor.`)), - 'https://www.anchor-lang.com/docs/installation', - ].join(' '), - ) - } else if (!valid) { - log.warn( - [ - yellow(`Found Anchor version ${version}. Expected Anchor version ${required}.`), - 'https://www.anchor-lang.com/release-notes/0.30.1', - ].join(' '), - ) - } - } catch (error_) { - log.warn(`Error ${error_}`) - } -} diff --git a/src/utils/init-script-version-check.ts b/src/utils/init-script-version-check.ts new file mode 100644 index 00000000..4b3b2d5b --- /dev/null +++ b/src/utils/init-script-version-check.ts @@ -0,0 +1,46 @@ +import { log } from '@clack/prompts' +import { bold, yellow } from 'picocolors' +import { getVersion } from './get-version' +import { VersionCommand } from './get-version-command' +import { getVersionUrls } from './get-version-urls' +import { validateVersion } from './validate-version' + +export async function initScriptVersionCheck(versionCommand: VersionCommand, required?: string, verbose = false) { + const tag = `initScriptVersionCheck` + if (!required) { + return + } + try { + const { valid, version } = validateVersion({ required, version: getVersion(versionCommand) }) + if (verbose) { + log.warn(`${tag}: required: ${required}, version: ${version ?? '*none*'}, valid: ${valid}`) + } + if (version && valid) { + // All good + return + } + + const { install, update } = getVersionUrls(versionCommand, required) + + if (!version) { + log.warn( + [ + bold(yellow(`Could not find ${versionCommand} version. Please install ${versionCommand}.`)), + install?.replace('{required}', required), + ].join(' '), + ) + return + } + if (!valid) { + log.warn( + [ + yellow(`Found ${versionCommand} version ${version}. Expected ${versionCommand} version ${required}.`), + update?.replace('{required}', required), + ].join(' '), + ) + return + } + } catch (error_) { + log.warn(`Error ${error_}`) + } +} diff --git a/src/utils/init-script-version-solana.ts b/src/utils/init-script-version-solana.ts deleted file mode 100644 index b3a55563..00000000 --- a/src/utils/init-script-version-solana.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { log } from '@clack/prompts' -import { bold, yellow } from 'picocolors' -import { getVersion } from './get-version' -import { validateVersion } from './validate-version' - -export async function initScriptVersionSolana(required?: string, verbose = false) { - if (!required) { - return - } - try { - const { valid, version } = validateVersion({ required, version: getVersion('solana') }) - if (verbose) { - log.warn(`initScriptVersionSolana: required: ${required}, version: ${version ?? '*none*'}, valid: ${valid}`) - } - if (!version) { - log.warn( - [ - bold(yellow(`Could not find Solana version. Please install Solana.`)), - 'https://docs.solana.com/cli/install-solana-cli-tools', - ].join(' '), - ) - } else if (!valid) { - log.warn( - [ - yellow(`Found Solana version ${version}. Expected Solana version ${required}.`), - 'https://docs.solana.com/cli/install-solana-cli-tools', - ].join(' '), - ) - } - } catch (error_) { - log.warn(`Error ${error_}`) - } -} diff --git a/src/utils/init-script-version.ts b/src/utils/init-script-version.ts index 4837d6e2..104a71d8 100644 --- a/src/utils/init-script-version.ts +++ b/src/utils/init-script-version.ts @@ -1,17 +1,19 @@ import { log } from '@clack/prompts' -import { InitScriptVersions } from './get-init-script' -import { initScriptVersionAdb } from './init-script-version-adb' -import { initScriptVersionAnchor } from './init-script-version-anchor' -import { initScriptVersionSolana } from './init-script-version-solana' +import { InitScriptVersions } from './init-script-schema' +import { initScriptVersionCheck } from './init-script-version-check' export async function initScriptVersion(versions?: InitScriptVersions, verbose = false) { + const tag = `initScriptVersion` if (!versions) { if (verbose) { - log.warn(`initScriptCheckVersion: no versions found`) + log.warn(`${tag}: no versions found`) } return } - await initScriptVersionAdb(versions.adb, verbose) - await initScriptVersionAnchor(versions.anchor, verbose) - await initScriptVersionSolana(versions.solana, verbose) + await initScriptVersionCheck('adb', versions.adb, verbose) + await initScriptVersionCheck('anchor', versions.anchor, verbose) + await initScriptVersionCheck('solana', versions.solana, verbose) + if (verbose) { + log.warn(`${tag}: done`) + } } diff --git a/src/utils/list-versions.ts b/src/utils/list-versions.ts index f68e3588..3742f9ba 100644 --- a/src/utils/list-versions.ts +++ b/src/utils/list-versions.ts @@ -1,8 +1,10 @@ -import { getVersion, versionCommands } from './get-version' +import { getVersion } from './get-version' +import { getVersionCommand, getVersionCommandNames } from './get-version-command' export function listVersions() { console.log(`Installed versions:`) - for (const command of Object.keys(versionCommands)) { - console.log(` ${versionCommands[command].name}: ${getVersion(command) ?? 'not installed'}`) + for (const command of getVersionCommandNames()) { + const cmd = getVersionCommand(command) + console.log(` ${cmd.name}: ${getVersion(command) ?? 'not installed'}`) } } diff --git a/test/get-version-commands.test.ts b/test/get-version-commands.test.ts new file mode 100644 index 00000000..16e43731 --- /dev/null +++ b/test/get-version-commands.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { getVersionCommand, getVersionCommandNames } from '../src/utils/get-version-command' + +describe('getVersionCommand', () => { + it('should have the expected commands names', () => { + expect(getVersionCommandNames()).toEqual(['adb', 'anchor', 'avm', 'rust', 'solana']) + }) + + it('should have correct structure for each command', () => { + for (const name of getVersionCommandNames()) { + const cmd = getVersionCommand(name) + expect(cmd).toHaveProperty('command', expect.any(String)) + expect(cmd).toHaveProperty('name', expect.any(String)) + expect(cmd).toHaveProperty('regex', expect.any(RegExp)) + } + }) +}) diff --git a/test/get-version-urls.test.ts b/test/get-version-urls.test.ts new file mode 100644 index 00000000..c36adb7e --- /dev/null +++ b/test/get-version-urls.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { VersionCommand } from '../src/utils/get-version-command' +import { getVersionUrls } from '../src/utils/get-version-urls' + +describe('getVersionUrls', () => { + it('should return install url for "adb"', () => { + const urls = getVersionUrls('adb' as VersionCommand, '') + expect(urls).toEqual({ + install: + 'https://docs.expo.dev/get-started/set-up-your-environment/?platform=android&device=physical&mode=development-build&buildEnv=local', + update: + 'https://docs.expo.dev/get-started/set-up-your-environment/?platform=android&device=physical&mode=development-build&buildEnv=local', + }) + }) + + it('should return install and update urls for "anchor" with required version', () => { + const requiredVersion = 'v0.20.1' + const urls = getVersionUrls('anchor' as VersionCommand, requiredVersion) + expect(urls).toEqual({ + install: 'https://www.anchor-lang.com/docs/installation', + update: `https://www.anchor-lang.com/release-notes/${requiredVersion}`, + }) + }) + + it('should return install url for "solana"', () => { + const urls = getVersionUrls('solana' as VersionCommand, '') + expect(urls).toEqual({ + install: 'https://docs.solana.com/cli/install-solana-cli-tools', + update: 'https://docs.solana.com/cli/install-solana-cli-tools', + }) + }) + + it('should return empty object for unknown command', () => { + const urls = getVersionUrls('unknown' as VersionCommand, '') + expect(urls).toEqual({}) + }) +}) diff --git a/test/get-version.test.ts b/test/get-version.test.ts index 5cd7010d..0367b4aa 100644 --- a/test/get-version.test.ts +++ b/test/get-version.test.ts @@ -1,25 +1,11 @@ import * as childProcess from 'node:child_process' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { getVersion, versionCommands } from '../src/utils/get-version' +import { getVersion } from '../src/utils/get-version' vi.mock('node:child_process', () => ({ execSync: vi.fn(), })) -describe('versionCommands', () => { - it('should have the expected commands', () => { - expect(Object.keys(versionCommands)).toEqual(['adb', 'anchor', 'avm', 'rust', 'solana']) - }) - - it('should have correct structure for each command', () => { - for (const cmd of Object.values(versionCommands)) { - expect(cmd).toHaveProperty('command', expect.any(String)) - expect(cmd).toHaveProperty('name', expect.any(String)) - expect(cmd).toHaveProperty('regex', expect.any(RegExp)) - } - }) -}) - describe('getVersion', () => { beforeEach(() => { vi.resetAllMocks() diff --git a/test/init-script-delete.test.ts b/test/init-script-delete.test.ts new file mode 100644 index 00000000..9f1b00ae --- /dev/null +++ b/test/init-script-delete.test.ts @@ -0,0 +1,86 @@ +import { log } from '@clack/prompts' +import { fs, vol } from 'memfs' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { GetArgsResult } from '../src/utils/get-args-result' +import { getPackageJson } from '../src/utils/get-package-json' +import { initScriptDelete } from '../src/utils/init-script-delete' +import { initScriptKey } from '../src/utils/init-script-schema' + +vi.mock('node:fs') + +vi.mock('@clack/prompts', () => ({ + log: { + warn: vi.fn(), + }, +})) + +describe('initScriptDelete', () => { + const targetDirectory = '/template' + const packageJsonPath = `${targetDirectory}/package.json` + + beforeEach(() => { + vol.reset() + }) + + const baseArgs: GetArgsResult = { + app: { name: 'test-app', version: '1.0.0' }, + dryRun: false, + name: 'test-project', + targetDirectory: '/template', + packageManager: 'npm', + skipGit: false, + skipInit: false, + skipInstall: false, + template: { name: 'basic', description: 'description', repository: '/template' }, + verbose: true, + } + + it('should remove the init script key from package.json', () => { + const packageJsonContent = { + name: 'my-app', + [initScriptKey]: { someKey: 'someValue' }, + } + + fs.mkdirSync(targetDirectory, { recursive: true }) + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent)) + + initScriptDelete(baseArgs) + + const { path: updatedPath, contents: updatedContents } = getPackageJson(targetDirectory) + + expect(updatedPath).toBe(packageJsonPath) + expect(updatedContents).not.toHaveProperty(initScriptKey) + }) + + it('should not throw error if init script key does not exist', () => { + const packageJsonContent = { + name: 'my-app', + } + + fs.mkdirSync(targetDirectory, { recursive: true }) + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent)) + + initScriptDelete(baseArgs) + + const { path: updatedPath, contents: updatedContents } = getPackageJson(targetDirectory) + + expect(updatedPath).toBe(packageJsonPath) + expect(updatedContents).toEqual(packageJsonContent) + }) + + it('should log message when verbose', () => { + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) + const packageJsonContent = { + name: 'my-app', + [initScriptKey]: { someKey: 'someValue' }, + } + + fs.mkdirSync(targetDirectory, { recursive: true }) + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent)) + + initScriptDelete(baseArgs) + + expect(log.warn).toHaveBeenCalledWith(`initScriptDelete: deleted ${initScriptKey} from package.json`) + consoleLogSpy.mockRestore() + }) +}) diff --git a/test/init-script-instructions.test.ts b/test/init-script-instructions.test.ts new file mode 100644 index 00000000..21e80406 --- /dev/null +++ b/test/init-script-instructions.test.ts @@ -0,0 +1,72 @@ +import { log } from '@clack/prompts' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { initScriptInstructions } from '../src/utils/init-script-instructions' +import { InitScriptInstructions } from '../src/utils/init-script-schema' + +vi.mock('@clack/prompts', () => ({ + log: { + warn: vi.fn(), + }, +})) +describe('initScriptInstructions', () => { + beforeEach(() => { + vi.spyOn(console, 'log').mockImplementation(() => {}) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should log "no instructions found" and return empty array when instructions is undefined and verbose is true', () => { + const result = initScriptInstructions(undefined, true) + expect(log.warn).toHaveBeenCalledWith('initScriptInstructions: no instructions found') + expect(result).toEqual([]) + }) + + it('should not log and return empty array when instructions is undefined and verbose is false', () => { + const result = initScriptInstructions(undefined, false) + expect(log.warn).not.toHaveBeenCalled() + expect(result).toEqual([]) + }) + + it('should log "no instructions found" and return empty array when instructions is empty and verbose is true', () => { + const instructions: InitScriptInstructions = [] + const result = initScriptInstructions(instructions, true) + expect(log.warn).toHaveBeenCalledWith('initScriptInstructions: no instructions found') + expect(result).toEqual([]) + }) + + it('should not log and return empty array when instructions is empty and verbose is false', () => { + const instructions: InitScriptInstructions = [] + const result = initScriptInstructions(instructions, false) + expect(log.warn).not.toHaveBeenCalled() + expect(result).toEqual([]) + }) + + it('should log the number of instructions and return the instructions when instructions are provided and verbose is true', () => { + const instructions: InitScriptInstructions = ['instr1', 'instr2'] + const result = initScriptInstructions(instructions, true) + expect(log.warn).toHaveBeenCalledWith('initScriptInstructions: 2 instructions found') + expect(result).toBe(instructions) + }) + + it('should not log and return the instructions when instructions are provided and verbose is false', () => { + const instructions: InitScriptInstructions = ['instr1', 'instr2'] + const result = initScriptInstructions(instructions, false) + expect(log.warn).not.toHaveBeenCalled() + expect(result).toBe(instructions) + }) + + it('should not log and return empty array when instructions is undefined and verbose is not provided', () => { + const result = initScriptInstructions(undefined) + expect(log.warn).not.toHaveBeenCalled() + expect(result).toEqual([]) + }) + + it('should not log and return the instructions when instructions are provided and verbose is not provided', () => { + const instructions: InitScriptInstructions = ['instr1', 'instr2'] + const result = initScriptInstructions(instructions) + expect(log.warn).not.toHaveBeenCalled() + expect(result).toBe(instructions) + }) +}) diff --git a/test/init-script-rename.test.ts b/test/init-script-rename.test.ts new file mode 100644 index 00000000..71fa4ea2 --- /dev/null +++ b/test/init-script-rename.test.ts @@ -0,0 +1,139 @@ +import { log } from '@clack/prompts' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { ensureTargetPath } from '../src/utils/ensure-target-path' +import { GetArgsResult } from '../src/utils/get-args-result' +import { getPackageJson } from '../src/utils/get-package-json' +import { initScriptRename } from '../src/utils/init-script-rename' +import { searchAndReplace } from '../src/utils/search-and-replace' +import { namesValues } from '../src/utils/vendor/names' + +vi.mock('../src/utils/ensure-target-path') +vi.mock('../src/utils/get-package-json') +vi.mock('../src/utils/search-and-replace') +vi.mock('../src/utils/vendor/names') +vi.mock('@clack/prompts', () => ({ + log: { + error: vi.fn(), + warn: vi.fn(), + }, +})) + +describe('initScriptRename', () => { + const packageJsonName = 'template-project' + + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + beforeEach(() => { + vi.resetAllMocks() + vi.mocked(getPackageJson).mockReturnValue({ + path: `${baseArgs.targetDirectory}/package.json`, + contents: { name: packageJsonName }, + }) + }) + + afterEach(() => { + vi.clearAllMocks() + consoleErrorSpy.mockClear() + }) + + const baseArgs: GetArgsResult = { + app: { name: 'test-app', version: '1.0.0' }, + dryRun: false, + name: 'test-project', + targetDirectory: '/template', + packageManager: 'npm', + skipGit: false, + skipInit: false, + skipInstall: false, + template: { name: 'basic', description: 'description', repository: '/template' }, + verbose: false, + } + + it('should rename the project based on package.json name', async () => { + const args = { ...baseArgs } + + await initScriptRename(args) + + expect(searchAndReplace).toHaveBeenCalledWith(args.targetDirectory, [packageJsonName], [args.name], false, false) + }) + + it('should return early if no rename object is provided', async () => { + const args = { ...baseArgs } + + await initScriptRename(args, undefined) + + expect(searchAndReplace).toHaveBeenCalledTimes(1) + expect(log.warn).not.toHaveBeenCalled() + }) + + it('should perform search and replace based on rename instructions', async () => { + const args = { ...baseArgs, verbose: true } + const rename = { + example: { + to: '{{name}}Example', + paths: ['some/path/to/file'], + }, + } + const exampleNames = ['Example'] + const newNameExamples = ['newprojectExample'] + vi.mocked(namesValues).mockImplementation((name) => (name === 'example' ? exampleNames : newNameExamples)) + vi.mocked(ensureTargetPath).mockResolvedValue(true) + + await initScriptRename(args, rename) + + expect(searchAndReplace).toHaveBeenCalledWith( + expect.stringContaining('some/path/to/file'), + exampleNames, + newNameExamples, + args.dryRun, + args.verbose, + ) + }) + + it('should log a message when verbose and no rename object is provided', async () => { + const args: GetArgsResult = { ...baseArgs, verbose: true } + await initScriptRename(args, undefined) + expect(log.warn).toHaveBeenCalledWith('initScriptRename: no renames found') + }) + + it('should log a message for each rename operation when verbose', async () => { + const args = { ...baseArgs, verbose: true } + const rename = { + example: { + to: '{{name}}Example', + paths: ['some/path/to/file'], + }, + } + + const exampleNames = ['Example'] + const newNameExamples = ['testprojectExample'] + vi.mocked(namesValues).mockImplementation((name) => (name === 'example' ? exampleNames : newNameExamples)) + vi.mocked(ensureTargetPath).mockResolvedValue(true) + + await initScriptRename(args, rename) + + expect(log.warn).toHaveBeenCalledWith( + expect.stringContaining('initScriptRename: /template/some/path/to/file -> Example -> testprojectExample'), + ) + expect(log.warn).toHaveBeenCalledWith('initScriptRename: done') + }) + + it('should log an error if a target path in rename does not exist', async () => { + const args = { ...baseArgs, verbose: true } + const rename = { + example: { + to: '{{name}}Example', + paths: ['nonexistent/path/to/file'], + }, + } + + vi.mocked(namesValues).mockReturnValue(['Example']) + vi.mocked(ensureTargetPath).mockResolvedValue(false) // Simulate nonexistent path + + await initScriptRename(args, rename) + + expect(log.error).toHaveBeenCalledWith(`initScriptRename: target does not exist /template/nonexistent/path/to/file`) + //been called once for the package.json rename + expect(searchAndReplace).toHaveBeenCalledTimes(1) + }) +}) diff --git a/test/init-script-version-anchor.test.ts b/test/init-script-version-anchor.test.ts deleted file mode 100644 index de459298..00000000 --- a/test/init-script-version-anchor.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { log } from '@clack/prompts' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { getVersion } from '../src/utils/get-version' -import { initScriptVersionAnchor } from '../src/utils/init-script-version-anchor' -import { validateVersion } from '../src/utils/validate-version' - -vi.mock('../src/utils/get-version') -vi.mock('../src/utils/validate-version') -vi.mock('@clack/prompts', () => ({ - log: { - warn: vi.fn(), - }, -})) - -describe('initScriptVersionAnchor', () => { - beforeEach(() => { - vi.resetAllMocks() - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should return early if no required version is provided', async () => { - await initScriptVersionAnchor() - expect(getVersion).not.toHaveBeenCalled() - expect(validateVersion).not.toHaveBeenCalled() - expect(log.warn).not.toHaveBeenCalled() - }) - - it('should log warning if anchor version is not found', async () => { - const required = '1.0.0' - vi.mocked(getVersion).mockReturnValue(undefined) - vi.mocked(validateVersion).mockReturnValue({ valid: false, version: undefined }) - await initScriptVersionAnchor(required) - expect(getVersion).toHaveBeenCalledWith('anchor') - expect(validateVersion).toHaveBeenCalledWith({ required, version: undefined }) - expect(log.warn).toHaveBeenCalledWith( - expect.stringContaining('Could not find Anchor version. Please install Anchor.'), - ) - }) - - it('should log warning if anchor version does not satisfy the requirement', async () => { - const required = '1.0.0' - const version = '0.9.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: false, version }) - await initScriptVersionAnchor(required) - expect(getVersion).toHaveBeenCalledWith('anchor') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).toHaveBeenCalledWith( - expect.stringContaining(`Found Anchor version ${version}. Expected Anchor version ${required}.`), - ) - }) - - it('should not log warning if anchor version satisfies the requirement', async () => { - const required = '1.0.0' - const version = '1.0.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionAnchor(required) - expect(getVersion).toHaveBeenCalledWith('anchor') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).not.toHaveBeenCalled() - }) - - it('should log verbose message if verbose is true', async () => { - const required = '1.0.0' - const version = '1.0.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionAnchor(required, true) - expect(getVersion).toHaveBeenCalledWith('anchor') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).toHaveBeenCalledWith( - `initScriptVersionAnchor: required: ${required}, version: ${version}, valid: true`, - ) - }) - - it('should log error if an exception occurs', async () => { - const required = '1.0.0' - const error = new Error('Test error') - vi.mocked(getVersion).mockImplementation(() => { - throw error - }) - await initScriptVersionAnchor(required) - expect(log.warn).toHaveBeenCalledWith(`Error ${error}`) - }) -}) diff --git a/test/init-script-version-adb.test.ts b/test/init-script-version-check.test.ts similarity index 62% rename from test/init-script-version-adb.test.ts rename to test/init-script-version-check.test.ts index 34d89b29..9bec80fa 100644 --- a/test/init-script-version-adb.test.ts +++ b/test/init-script-version-check.test.ts @@ -1,18 +1,20 @@ import { log } from '@clack/prompts' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { getVersion } from '../src/utils/get-version' -import { initScriptVersionAdb } from '../src/utils/init-script-version-adb' +import { getVersionUrls } from '../src/utils/get-version-urls' +import { initScriptVersionCheck } from '../src/utils/init-script-version-check' import { validateVersion } from '../src/utils/validate-version' vi.mock('../src/utils/get-version') vi.mock('../src/utils/validate-version') +vi.mock('../src/utils/get-version-urls') vi.mock('@clack/prompts', () => ({ log: { warn: vi.fn(), }, })) -describe('initScriptVersionAdb', () => { +describe('initScriptVersionCheck', () => { beforeEach(() => { vi.resetAllMocks() }) @@ -21,8 +23,10 @@ describe('initScriptVersionAdb', () => { vi.clearAllMocks() }) + const versionCommand = 'adb' + it('should return early if no required version is provided', async () => { - await initScriptVersionAdb() + await initScriptVersionCheck(versionCommand) expect(getVersion).not.toHaveBeenCalled() expect(validateVersion).not.toHaveBeenCalled() expect(log.warn).not.toHaveBeenCalled() @@ -30,25 +34,29 @@ describe('initScriptVersionAdb', () => { it('should log warning if adb version is not found', async () => { const required = '1.0.0' + const installMock = 'Install URL' vi.mocked(getVersion).mockReturnValue(undefined) vi.mocked(validateVersion).mockReturnValue({ valid: false, version: undefined }) - await initScriptVersionAdb(required) - expect(getVersion).toHaveBeenCalledWith('adb') + vi.mocked(getVersionUrls).mockReturnValue({ install: installMock, update: undefined }) + await initScriptVersionCheck(versionCommand, required) + expect(getVersion).toHaveBeenCalledWith(versionCommand) expect(validateVersion).toHaveBeenCalledWith({ required, version: undefined }) - expect(log.warn).toHaveBeenCalledWith(expect.stringContaining('Could not find adb version. Please install adb.')) + expect(log.warn).toHaveBeenCalledWith(expect.stringContaining('Could not find adb version')) + expect(log.warn).toHaveBeenCalledWith(expect.stringContaining(installMock)) }) it('should log warning if adb version does not satisfy the requirement', async () => { const required = '1.0.0' const version = '0.9.0' + const updateMock = 'Update URL' vi.mocked(getVersion).mockReturnValue(version) vi.mocked(validateVersion).mockReturnValue({ valid: false, version }) - await initScriptVersionAdb(required) - expect(getVersion).toHaveBeenCalledWith('adb') + vi.mocked(getVersionUrls).mockReturnValue({ install: undefined, update: updateMock }) + await initScriptVersionCheck(versionCommand, required) + expect(getVersion).toHaveBeenCalledWith(versionCommand) expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).toHaveBeenCalledWith( - expect.stringContaining(`Found adb version ${version}. Expected adb version ${required}.`), - ) + expect(log.warn).toHaveBeenCalledWith(expect.stringContaining(`Found adb version ${version}`)) + expect(log.warn).toHaveBeenCalledWith(expect.stringContaining(updateMock)) }) it('should not log warning if adb version satisfies the requirement', async () => { @@ -56,8 +64,8 @@ describe('initScriptVersionAdb', () => { const version = '1.0.0' vi.mocked(getVersion).mockReturnValue(version) vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionAdb(required) - expect(getVersion).toHaveBeenCalledWith('adb') + await initScriptVersionCheck(versionCommand, required) + expect(getVersion).toHaveBeenCalledWith(versionCommand) expect(validateVersion).toHaveBeenCalledWith({ required, version }) expect(log.warn).not.toHaveBeenCalled() }) @@ -67,11 +75,11 @@ describe('initScriptVersionAdb', () => { const version = '1.0.0' vi.mocked(getVersion).mockReturnValue(version) vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionAdb(required, true) - expect(getVersion).toHaveBeenCalledWith('adb') + await initScriptVersionCheck(versionCommand, required, true) + expect(getVersion).toHaveBeenCalledWith(versionCommand) expect(validateVersion).toHaveBeenCalledWith({ required, version }) expect(log.warn).toHaveBeenCalledWith( - `initScriptVersionAdb: required: ${required}, version: ${version}, valid: true`, + `initScriptVersionCheck: required: ${required}, version: ${version}, valid: true`, ) }) @@ -81,7 +89,7 @@ describe('initScriptVersionAdb', () => { vi.mocked(getVersion).mockImplementation(() => { throw error }) - await initScriptVersionAdb(required) + await initScriptVersionCheck(versionCommand, required) expect(log.warn).toHaveBeenCalledWith(`Error ${error}`) }) }) diff --git a/test/init-script-version-solana.test.ts b/test/init-script-version-solana.test.ts deleted file mode 100644 index 11a9217a..00000000 --- a/test/init-script-version-solana.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { log } from '@clack/prompts' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { getVersion } from '../src/utils/get-version' -import { initScriptVersionSolana } from '../src/utils/init-script-version-solana' -import { validateVersion } from '../src/utils/validate-version' - -vi.mock('../src/utils/get-version') -vi.mock('../src/utils/validate-version') -vi.mock('@clack/prompts', () => ({ - log: { - warn: vi.fn(), - }, -})) - -describe('initScriptVersionSolana', () => { - beforeEach(() => { - vi.resetAllMocks() - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should return early if no required version is provided', async () => { - await initScriptVersionSolana() - expect(getVersion).not.toHaveBeenCalled() - expect(validateVersion).not.toHaveBeenCalled() - expect(log.warn).not.toHaveBeenCalled() - }) - - it('should log warning if solana version is not found', async () => { - const required = '1.0.0' - vi.mocked(getVersion).mockReturnValue(undefined) - vi.mocked(validateVersion).mockReturnValue({ valid: false, version: undefined }) - await initScriptVersionSolana(required) - expect(getVersion).toHaveBeenCalledWith('solana') - expect(validateVersion).toHaveBeenCalledWith({ required, version: undefined }) - expect(log.warn).toHaveBeenCalledWith( - expect.stringContaining('Could not find Solana version. Please install Solana.'), - ) - }) - - it('should log warning if solana version does not satisfy the requirement', async () => { - const required = '1.0.0' - const version = '0.9.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: false, version }) - await initScriptVersionSolana(required) - expect(getVersion).toHaveBeenCalledWith('solana') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).toHaveBeenCalledWith( - expect.stringContaining(`Found Solana version ${version}. Expected Solana version ${required}.`), - ) - }) - - it('should not log warning if solana version satisfies the requirement', async () => { - const required = '1.0.0' - const version = '1.0.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionSolana(required) - expect(getVersion).toHaveBeenCalledWith('solana') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).not.toHaveBeenCalled() - }) - - it('should log verbose message if verbose is true', async () => { - const required = '1.0.0' - const version = '1.0.0' - vi.mocked(getVersion).mockReturnValue(version) - vi.mocked(validateVersion).mockReturnValue({ valid: true, version }) - await initScriptVersionSolana(required, true) - expect(getVersion).toHaveBeenCalledWith('solana') - expect(validateVersion).toHaveBeenCalledWith({ required, version }) - expect(log.warn).toHaveBeenCalledWith( - `initScriptVersionSolana: required: ${required}, version: ${version}, valid: true`, - ) - }) - - it('should log error if an exception occurs', async () => { - const required = '1.0.0' - const error = new Error('Test error') - vi.mocked(getVersion).mockImplementation(() => { - throw error - }) - await initScriptVersionSolana(required) - expect(log.warn).toHaveBeenCalledWith(`Error ${error}`) - }) -}) diff --git a/test/init-script-version.test.ts b/test/init-script-version.test.ts new file mode 100644 index 00000000..4f52aef5 --- /dev/null +++ b/test/init-script-version.test.ts @@ -0,0 +1,61 @@ +import { log } from '@clack/prompts' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { initScriptVersion } from '../src/utils/init-script-version' +import { initScriptVersionCheck } from '../src/utils/init-script-version-check' + +vi.mock('../src/utils/init-script-version-check') +vi.mock('@clack/prompts', () => ({ + log: { + warn: vi.fn(), + }, +})) + +describe('initScriptVersion', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('should return early if no versions are provided', async () => { + await initScriptVersion(undefined) + expect(initScriptVersionCheck).not.toHaveBeenCalled() + expect(initScriptVersionCheck).not.toHaveBeenCalled() + expect(initScriptVersionCheck).not.toHaveBeenCalled() + expect(log.warn).not.toHaveBeenCalled() + }) + + it('should call initScriptVersionCheck with provided versions', async () => { + const versions = { + adb: '1.0.0', + anchor: '2.0.0', + solana: '3.0.0', + } + await initScriptVersion(versions) + expect(initScriptVersionCheck).toHaveBeenCalledWith('adb', versions.adb, false) + expect(initScriptVersionCheck).toHaveBeenCalledWith('anchor', versions.anchor, false) + expect(initScriptVersionCheck).toHaveBeenCalledWith('solana', versions.solana, false) + }) + + it('should log verbose message if verbose is true with no versions', async () => { + const tag = 'initScriptVersion' + await initScriptVersion(undefined, true) + expect(log.warn).toHaveBeenCalledWith(`${tag}: no versions found`) + }) + + it('should log verbose message if verbose is true after version checks', async () => { + const versions = { + adb: '1.0.0', + anchor: '2.0.0', + solana: '3.0.0', + } + const tag = 'initScriptVersion' + await initScriptVersion(versions, true) + expect(initScriptVersionCheck).toHaveBeenCalledWith('adb', versions.adb, true) + expect(initScriptVersionCheck).toHaveBeenCalledWith('anchor', versions.anchor, true) + expect(initScriptVersionCheck).toHaveBeenCalledWith('solana', versions.solana, true) + expect(log.warn).toHaveBeenCalledWith(`${tag}: done`) + }) +})