Skip to content

Commit 938b902

Browse files
authored
feat: add retry for contract name input (#318)
1 parent da5df10 commit 938b902

File tree

4 files changed

+48
-10
lines changed

4 files changed

+48
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Changed
11+
12+
- If the input contract name is invalid, blueprint does not exit, but asks for the name again
13+
814
## [0.40.0] - 2025-08-18
915

1016
### Added

src/cli/create.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { lstat, mkdir, open, readdir, readFile } from 'fs/promises';
44

55
import arg from 'arg';
66
import { Project } from '@tact-lang/compiler';
7+
import chalk from 'chalk';
78

89
import { getConfig } from '../config/utils';
910
import { getRootTactConfig, TactConfig, updateRootTactConfig } from '../config/tact.config';
1011
import { Args, extractFirstArg, Runner } from './Runner';
1112
import { executeTemplate, TEMPLATES_DIR } from '../template';
12-
import { assertValidContractName, selectOption, toSnakeCase } from '../utils';
13+
import { validateContractName, selectOption, toSnakeCase } from '../utils';
1314
import { UIProvider } from '../ui/UIProvider';
1415
import { buildOne } from '../build';
1516
import { helpArgs, helpMessages, templateTypes } from './constants';
@@ -85,8 +86,15 @@ export const create: Runner = async (_args: Args, ui: UIProvider) => {
8586
return;
8687
}
8788

88-
const name = extractFirstArg(localArgs) ?? (await ui.input('Contract name (PascalCase)'));
89-
assertValidContractName(name);
89+
const argName = extractFirstArg(localArgs);
90+
if (argName !== undefined) {
91+
const error = validateContractName(argName);
92+
if (error) {
93+
throw new Error(error);
94+
}
95+
}
96+
97+
const name = argName ?? (await requestContractName('Contract name (PascalCase)', ui));
9098

9199
const which = (
92100
await selectOption(templateTypes, {
@@ -120,3 +128,16 @@ export const create: Runner = async (_args: Args, ui: UIProvider) => {
120128
await createFiles(path.join(TEMPLATES_DIR, lang, template), process.cwd(), replaces);
121129
}
122130
};
131+
132+
export async function requestContractName(message: string, ui: UIProvider): Promise<string> {
133+
while (true) {
134+
const name = await ui.input(message);
135+
const error = validateContractName(name);
136+
if (error !== undefined) {
137+
ui.write(chalk.redBright(`Error: `) + error);
138+
// ask user again
139+
continue;
140+
}
141+
return name;
142+
}
143+
}

src/cli/rename.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import arg from 'arg';
55

66
import { COMPILABLES_DIR, CONTRACTS_DIR, SCRIPTS_DIR, TACT_ROOT_CONFIG, TESTS_DIR, WRAPPERS_DIR } from '../paths';
77
import { Args, extractFirstArg, extractSecondArg, Runner, RunnerContext } from './Runner';
8-
import { assertValidContractName, findContracts, toLowerCase, toSnakeCase, extractFile } from '../utils';
8+
import { validateContractName, findContracts, toLowerCase, toSnakeCase, extractFile } from '../utils';
99
import { UIProvider } from '../ui/UIProvider';
1010
import { helpArgs, helpMessages } from './constants';
1111
import { selectContract } from './build';
12+
import { requestContractName } from './create';
1213

1314
export function renameExactIfRequired(
1415
str: string,
@@ -85,8 +86,16 @@ export const rename: Runner = async (_args: Args, ui: UIProvider, _context: Runn
8586

8687
const oldName = await selectContract(ui, extractFirstArg(localArgs));
8788

88-
const newName = extractSecondArg(localArgs) ?? (await ui.input('New contract name (PascalCase)'));
89-
assertValidContractName(newName);
89+
const argName = extractSecondArg(localArgs);
90+
if (argName !== undefined) {
91+
const error = validateContractName(argName);
92+
if (error) {
93+
throw new Error(error);
94+
}
95+
}
96+
97+
const newName = argName ?? (await requestContractName('New contract name (PascalCase)', ui));
98+
9099
const contracts = await findContracts();
91100
if (contracts.includes(newName)) {
92101
ui.write(`Contract with name ${newName} already exists.`);

src/utils/string.utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ export function toPascalCase(str: string): string {
2020
.replace(/\s+/g, ''); // Removes all spaces
2121
}
2222

23-
export function assertValidContractName(name: string) {
24-
if (name.length === 0) throw new Error(`Contract name cannot be empty.`);
23+
export function validateContractName(name: string): string | undefined {
24+
if (name.length === 0) return `Contract name cannot be empty.`;
2525

2626
if (name.toLowerCase() === 'contract') {
27-
throw new Error(`Contract name 'contract' is reserved. Please choose a different name.`);
27+
return `Contract name 'contract' is reserved. Please choose a different name.`;
2828
}
2929

3030
if (!isPascalCase(name)) {
31-
throw new Error(`Contract name '${name}' is not in PascalCase. Please try ${toPascalCase(name)}.`);
31+
return `Contract name '${name}' is not in PascalCase. Please try ${toPascalCase(name)}.`;
3232
}
33+
34+
return undefined;
3335
}

0 commit comments

Comments
 (0)