| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- import chalk from 'chalk';
- import * as fs from 'node:fs';
- import { parseArgs } from 'node:util';
- import prompts from 'prompts';
- import { Language } from './getLanguage';
- import { kebabCase } from './stringHelpers';
- import { toMinorSolanaVersion } from './solanaCli';
- export const allClients = ['js', 'rust'] as const;
- export type Client = (typeof allClients)[number];
- export type Inputs = {
- jsClient: boolean;
- jsClientPackageName: string;
- organizationName: string;
- programAddress?: string;
- programCrateName: string;
- programFramework: 'shank' | 'anchor';
- programName: string;
- rustClient: boolean;
- rustClientCrateName: string;
- shouldOverride: boolean;
- solanaVersion?: string;
- targetDirectoryName: string;
- useDefaults: boolean;
- };
- export async function getInputs(language: Language): Promise<Inputs> {
- const argInputs = getInputsFromArgs(language);
- const defaultInputs = getDefaultInputs(argInputs);
- if (argInputs.useDefaults) {
- return defaultInputs;
- }
- return getInputsFromPrompts(language, argInputs);
- }
- async function getInputsFromPrompts(
- language: Language,
- argInputs: Partial<Inputs>
- ): Promise<Inputs> {
- type PromptInputs = {
- programName?: string;
- shouldOverride?: boolean;
- organizationName?: string;
- programCrateName?: string;
- programFramework?: 'shank' | 'anchor';
- clients?: Array<'js' | 'rust'>;
- jsClientPackageName?: string;
- rustClientCrateName?: string;
- };
- let defaultInputs = getDefaultInputs(argInputs);
- function parsePromptInputs(promptInputs: PromptInputs): Inputs {
- const inputs = {} as Partial<Inputs>;
- if (promptInputs.programName) inputs.programName = promptInputs.programName;
- if (promptInputs.shouldOverride !== undefined)
- inputs.shouldOverride = promptInputs.shouldOverride;
- if (promptInputs.organizationName)
- inputs.organizationName = promptInputs.organizationName;
- if (promptInputs.programCrateName)
- inputs.programCrateName = promptInputs.programCrateName;
- if (promptInputs.programFramework)
- inputs.programFramework = promptInputs.programFramework;
- if (promptInputs.clients !== undefined) {
- inputs.jsClient = promptInputs.clients.includes('js');
- inputs.rustClient = promptInputs.clients.includes('rust');
- }
- if (promptInputs.jsClientPackageName)
- inputs.jsClientPackageName = promptInputs.jsClientPackageName;
- if (promptInputs.rustClientCrateName)
- inputs.rustClientCrateName = promptInputs.rustClientCrateName;
- return getDefaultInputs({ ...argInputs, ...inputs });
- }
- try {
- const promptInputs: PromptInputs = await prompts(
- [
- {
- name: 'programName',
- type: argInputs.programName ? null : 'text',
- message: language.programName.message,
- initial: () => defaultInputs.programName,
- },
- {
- name: 'shouldOverride',
- type: (_, values) => {
- if (argInputs.shouldOverride) return null;
- defaultInputs = parsePromptInputs(values);
- return canSkipEmptying(defaultInputs.targetDirectoryName)
- ? null
- : 'toggle';
- },
- message: () => {
- const dirForPrompt =
- defaultInputs.targetDirectoryName === '.'
- ? language.shouldOverride.dirForPrompts!.current
- : `${language.shouldOverride.dirForPrompts!.target} "${defaultInputs.targetDirectoryName}"`;
- return `${dirForPrompt} ${language.shouldOverride.message}`;
- },
- initial: false,
- active: language.defaultToggleOptions.active,
- inactive: language.defaultToggleOptions.inactive,
- },
- {
- name: 'overwriteChecker',
- type: (_, values) => {
- if (values.shouldOverride === false) {
- throw new Error(
- chalk.red('✖') + ` ${language.errors.operationCancelled}`
- );
- }
- return null;
- },
- },
- {
- name: 'organizationName',
- type: argInputs.organizationName ? null : 'text',
- message: language.organizationName.message,
- initial: () => defaultInputs.organizationName,
- },
- {
- name: 'programCrateName',
- type: argInputs.programCrateName ? null : 'text',
- message: language.programCrateName.message,
- initial: (_, values) => {
- defaultInputs = parsePromptInputs(values);
- return defaultInputs.programCrateName;
- },
- },
- {
- name: 'programFramework',
- type: argInputs.programFramework ? null : 'select',
- message: language.programFramework.message,
- hint: language.instructions.select,
- initial: 0,
- choices: [
- {
- title: language.programFramework.selectOptions!.shank.title,
- description: language.programFramework.selectOptions!.shank.desc,
- value: 'shank',
- },
- {
- title: language.programFramework.selectOptions!.anchor.title,
- description: language.programFramework.selectOptions!.anchor.desc,
- value: 'anchor',
- disabled: true,
- },
- ],
- },
- {
- name: 'clients',
- type: () => {
- const hasSelectedClients = [
- argInputs.jsClient,
- argInputs.rustClient,
- ].every((client) => typeof client === 'boolean');
- return hasSelectedClients ? null : 'multiselect';
- },
- message: language.clients.message,
- hint: language.clients.hint,
- instructions: language.instructions.multiselect,
- choices: allClients.map((client) => ({
- title: language.clients.selectOptions![client].title,
- description: language.clients.selectOptions![client].desc,
- value: client,
- selected: true,
- })),
- },
- {
- name: 'jsClientPackageName',
- type: (_, values) => {
- if (argInputs.jsClientPackageName) return null;
- defaultInputs = parsePromptInputs(values);
- return defaultInputs.jsClient ? 'text' : null;
- },
- message: language.jsClientPackageName.message,
- initial: () => defaultInputs.jsClientPackageName,
- },
- {
- name: 'rustClientCrateName',
- type: (_, values) => {
- if (argInputs.rustClientCrateName) return null;
- defaultInputs = parsePromptInputs(values);
- return defaultInputs.rustClient ? 'text' : null;
- },
- message: language.rustClientCrateName.message,
- initial: () => defaultInputs.rustClientCrateName,
- },
- ],
- {
- onCancel: () => {
- throw new Error(
- chalk.red('✖') + ` ${language.errors.operationCancelled}`
- );
- },
- }
- );
- // Add a line break after the prompts
- console.log('');
- return parsePromptInputs(promptInputs);
- } catch (cancelled) {
- console.log((cancelled as Error).message);
- process.exit(1);
- }
- }
- function getInputsFromArgs(language: Language): Partial<Inputs> {
- type ArgInputs = {
- address?: string;
- anchorProgram: boolean;
- clients: Array<'js' | 'rust'>;
- force: boolean;
- noClients: boolean;
- organizationName?: string;
- programName?: string;
- shankProgram: boolean;
- solanaVersion?: string;
- useDefaults: boolean;
- targetDirectoryName?: string;
- };
- function parseArgInputs(argInputs: ArgInputs): Partial<Inputs> {
- const inputs = {} as Partial<Inputs>;
- if (argInputs.address) inputs.programAddress = argInputs.address;
- if (argInputs.organizationName)
- inputs.organizationName = kebabCase(argInputs.organizationName);
- if (argInputs.programName)
- inputs.programName = kebabCase(argInputs.programName);
- if (argInputs.solanaVersion)
- inputs.solanaVersion = toMinorSolanaVersion(
- language,
- argInputs.solanaVersion
- );
- if (argInputs.targetDirectoryName)
- inputs.targetDirectoryName = argInputs.targetDirectoryName;
- if (argInputs.force) inputs.shouldOverride = true;
- if (argInputs.useDefaults) inputs.useDefaults = true;
- if (argInputs.anchorProgram) {
- inputs.programFramework = 'anchor';
- } else if (argInputs.shankProgram) {
- inputs.programFramework = 'shank';
- }
- if (argInputs.noClients) {
- inputs.jsClient = false;
- inputs.rustClient = false;
- } else if (argInputs.clients) {
- inputs.jsClient = argInputs.clients.includes('js');
- inputs.rustClient = argInputs.clients.includes('rust');
- }
- return inputs;
- }
- const args = process.argv.slice(2);
- const { values: options, positionals } = parseArgs({
- args,
- options: {
- address: { type: 'string' },
- anchor: { type: 'boolean' },
- client: { type: 'string', multiple: true },
- default: { type: 'boolean', short: 'd' },
- force: { type: 'boolean' },
- 'no-clients': { type: 'boolean' },
- org: { type: 'string' },
- shank: { type: 'boolean' },
- solana: { type: 'string' },
- },
- strict: false,
- });
- return parseArgInputs({
- address: options.address,
- anchorProgram: options.anchor ?? false,
- clients: options.client,
- force: options.force ?? false,
- noClients: options['no-clients'] ?? false,
- organizationName: options.org,
- programName: positionals[1],
- shankProgram: options.shank ?? false,
- solanaVersion: options.solana,
- useDefaults: options.default ?? false,
- targetDirectoryName: positionals[0],
- } as ArgInputs);
- }
- export function getDefaultInputs(partialInputs: Partial<Inputs>): Inputs {
- const organizationName = kebabCase(
- partialInputs.organizationName ?? 'solana-program'
- );
- const parsedTargetDirectoryName = partialInputs.targetDirectoryName
- ? partialInputs.targetDirectoryName.split('/').pop()
- : '';
- const programName = kebabCase(
- partialInputs.programName ?? (parsedTargetDirectoryName || 'my-program')
- );
- const programCrateName =
- partialInputs.programCrateName ?? `${organizationName}-${programName}`;
- return {
- jsClient: true,
- jsClientPackageName: `@${organizationName}/${programName}`,
- organizationName,
- programCrateName,
- programFramework: 'shank',
- programName,
- rustClient: true,
- rustClientCrateName: `${programCrateName}-client`,
- shouldOverride: false,
- targetDirectoryName: programName,
- useDefaults: false,
- ...partialInputs,
- };
- }
- function canSkipEmptying(dir: fs.PathLike) {
- if (!fs.existsSync(dir)) {
- return true;
- }
- const files = fs.readdirSync(dir);
- if (files.length === 0) {
- return true;
- }
- if (files.length === 1 && files[0] === '.git') {
- return true;
- }
- return false;
- }
|