Skip to content

Commit f343ca6

Browse files
authored
Merge pull request #598 from Telegram-Mini-Apps/feature/create-mini-app-deprecation-warnings
Add deprecation warnings in `@telegram-apps/create-mini-app` for some templates
2 parents 47e1524 + 320ab04 commit f343ca6

File tree

19 files changed

+528
-442
lines changed

19 files changed

+528
-442
lines changed

.changeset/mighty-baboons-count.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@telegram-apps/create-mini-app": minor
3+
---
4+
5+
Deprecate JavaScript, jQuery and Pure TypeScript templates. Don't push any commits when the
6+
template was cloned.
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
11
import chalk from 'chalk';
22

33
import { spawnWithSpinner } from './spawnWithSpinner.js';
4-
import type { TemplateRepository } from './types.js';
4+
import type { TemplateRepository } from './templates/types.js';
5+
import type { CustomTheme } from './types.js';
56

67
/**
78
* Clones the template.
89
* @param rootDir - root directory.
910
* @param https - repository https link.
1011
* @param ssh - repository ssh link.
1112
* @param link - repository GitHub link.
13+
* @param theme - inquirer theme.
1214
*/
1315
export async function cloneTemplate(
1416
rootDir: string,
1517
{
1618
clone: { https, ssh },
1719
link,
1820
}: TemplateRepository,
21+
theme: CustomTheme,
1922
): Promise<void> {
20-
const titleSuccess = `Cloned template: ${chalk.blue(link)}`;
23+
const messageSuccess = `Cloned template: ${chalk.blue(link)}`;
2124

2225
// Clone the template using HTTPS.
2326
try {
2427
await spawnWithSpinner({
2528
command: `git clone "${https}" "${rootDir}"`,
26-
title: `Cloning the template from GitHub (HTTPS): ${chalk.bold.blue(https)}`,
27-
titleFail: (error) => `Failed to load the template using HTTPS. ${error}`,
28-
titleSuccess,
29+
message: `Cloning the template from GitHub (HTTPS): ${chalk.bold.blue(https)}`,
30+
messageFail: (error) => `Failed to load the template using HTTPS. ${error}`,
31+
messageSuccess,
32+
theme,
2933
});
3034
return;
3135
} catch {
@@ -34,8 +38,9 @@ export async function cloneTemplate(
3438
// Clone the template using SSH.
3539
await spawnWithSpinner({
3640
command: `git clone "${ssh}" "${rootDir}"`,
37-
title: `Cloning the template from GitHub (SSH): ${chalk.bold.blue(ssh)}`,
38-
titleFail: (error) => `Failed to load the template using SSH. ${error}`,
39-
titleSuccess,
41+
message: `Cloning the template from GitHub (SSH): ${chalk.bold.blue(ssh)}`,
42+
messageFail: (error) => `Failed to load the template using SSH. ${error}`,
43+
messageSuccess,
44+
theme,
4045
});
4146
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { makeTheme } from '@inquirer/core';
2+
import chalk from 'chalk';
3+
import figures from 'figures';
4+
5+
import type { CustomTheme } from './types.js';
6+
7+
/**
8+
* Creates a custom theme.
9+
*/
10+
export function createCustomTheme(): CustomTheme {
11+
return makeTheme({
12+
style: {
13+
error(text: string): string {
14+
return chalk.red(text);
15+
},
16+
success(text: string): string {
17+
return chalk.green(text);
18+
},
19+
placeholder(text: string): string {
20+
return chalk.dim(text);
21+
},
22+
warning(text: string): string {
23+
return chalk.yellow(`${figures.warning} ${text}`);
24+
},
25+
},
26+
prefix: {
27+
idle: chalk.blue('?'),
28+
done: chalk.green(figures.tick),
29+
pointer: figures.arrowRight,
30+
},
31+
})
32+
}

packages/create-mini-app/src/index.ts

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
import process from 'node:process';
33
import { rm } from 'node:fs/promises';
44
import { resolve } from 'node:path';
5+
import { URL } from 'node:url';
6+
import { existsSync } from 'node:fs';
57

68
import chalk from 'chalk';
79
import { program } from 'commander';
810

911
import { cloneTemplate } from './cloneTemplate.js';
1012
import { isGitInstalled } from './isGitInstalled.js';
11-
import { promptTemplate } from './prompts/promptTemplate/promptTemplate.js';
12-
import { promptDirName } from './prompts/promptDirName.js';
13-
import { promptGitRepo } from './prompts/promptGitRepo.js';
13+
import { promptTemplate } from './templates/promptTemplate/promptTemplate.js';
1414
import { spawnWithSpinner } from './spawnWithSpinner.js';
1515
import { lines } from './utils/lines.js';
16-
import type { TemplateRepository } from './types.js';
16+
import type { TemplateRepository } from './templates/types.js';
17+
import { input } from './prompts/input.js';
18+
import { createCustomTheme } from './createCustomTheme.js';
1719

1820
import packageJson from '../package.json';
1921

@@ -22,73 +24,118 @@ program
2224
.description(packageJson.description)
2325
.version(packageJson.version)
2426
.action(async () => {
27+
const theme = createCustomTheme();
28+
2529
// Check if git is installed.
2630
if (!await isGitInstalled()) {
27-
console.error('To run this CLI tool, you must have git installed. Installation guide: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git');
31+
console.log(
32+
theme.style.error('To run this CLI tool, you must have git installed. Installation guide: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git'),
33+
);
2834
process.exit(1);
2935
}
3036

31-
// Prompt the project root directory name.
37+
// Step 1: Prompt the project root directory.
3238
let rootDir: string | null = null;
3339
try {
34-
rootDir = await promptDirName({ defaultValue: 'mini-app' });
40+
rootDir = await input({
41+
message: 'Directory name:',
42+
required: true,
43+
default: 'mini-app',
44+
hint: 'This directory will be used as a root directory for the project. It is allowed to use alphanumeric latin letters, dashes and dots.',
45+
theme,
46+
validate(value) {
47+
if (['.', '..'].includes(value)) {
48+
return 'Value is not a valid directory name';
49+
}
50+
51+
if (!value.match(/^[a-zA-Z0-9\-.]+$/)) {
52+
return 'Value contains invalid symbols';
53+
}
54+
55+
if (existsSync(resolve(value))) {
56+
return `Directory "${value}" already exists`;
57+
}
58+
},
59+
});
3560
} catch {
3661
process.exit(0);
3762
}
3863

39-
// Prompt the target template.
64+
// Step 2: Prompt the target template.
4065
let repository: TemplateRepository;
4166
try {
42-
const { repository: promptRepo } = await promptTemplate({});
67+
const { repository: promptRepo } = await promptTemplate({ theme });
4368
repository = promptRepo;
4469
} catch {
4570
process.exit(0);
4671
}
4772

48-
// Prompt Git repo information.
73+
// Step 3: Prompt future Git repository information.
4974
let gitRepo: string | undefined;
5075
try {
51-
gitRepo = await promptGitRepo({});
76+
gitRepo = await input({
77+
message: 'Git remote repository URL:',
78+
validate(value) {
79+
// Check if it is SSH connection string.
80+
if (value.match(/\w+@[\w\-.]+:[\w-]+\/[\w./]+/)) {
81+
return;
82+
}
83+
84+
// Check if the specified value is URL.
85+
try {
86+
new URL(value);
87+
return '';
88+
} catch {
89+
return 'Value is not considered as URL link or SSH connection string.';
90+
}
91+
},
92+
theme,
93+
hint: lines(
94+
'This value will be used to connect created project with your remote Git repository. It should either be an HTTPS link or SSH connection string.',
95+
`Leave value empty and press ${theme.style.key('enter')} to skip this step.`,
96+
chalk.bold('Examples'),
97+
'SSH: [email protected]:user/repo.git',
98+
'URL: https://github.com/user/repo.git',
99+
),
100+
});
52101
} catch {
53102
process.exit(0);
54103
}
55104

56-
// Clone the template.
105+
// Step 4: Clone the template.
57106
try {
58-
await cloneTemplate(rootDir, repository);
107+
await cloneTemplate(rootDir, repository, theme);
59108
} catch {
60109
process.exit(1);
61110
}
62111

63-
// Remove the .git folder.
112+
// Step 5: Remove the .git folder.
64113
try {
65114
await spawnWithSpinner({
66-
title: 'Removing the .git directory.',
115+
message: 'Removing the .git directory.',
67116
command: () => rm(resolve(rootDir, '.git'), { recursive: true }),
68-
titleFail: (err: string) => `Failed to remove the .git directory. Error: ${err}`,
69-
titleSuccess: '.git directory removed.',
117+
messageFail: (err: string) => `Failed to remove the .git directory. Error: ${err}`,
118+
messageSuccess: '.git directory removed.',
119+
theme,
70120
});
71121
} catch {
72122
process.exit(1);
73123
}
74124

75-
// Initialize new .git folder if required.
125+
// Step 6: Initialize a new .git folder and configure remote.
76126
if (gitRepo) {
77127
try {
78128
await spawnWithSpinner({
79-
title: `Initializing Git repository: ${gitRepo}`,
129+
message: `Initializing Git repository: ${gitRepo}`,
80130
command: [
81131
`cd "${rootDir}"`,
82132
'git init',
83-
'git add -A',
84-
'git commit -m "first commit"',
85-
'git branch -M master',
86133
`git remote add origin "${gitRepo}"`,
87-
'git push -u origin master',
88134
].join(' && '),
89-
titleFail: (error) => `Failed to initialize Git repository. ${error}`,
90-
titleSuccess: 'Git repository initialized.',
91-
})
135+
messageFail: (error) => `Failed to initialize Git repository. ${error}`,
136+
messageSuccess: `Git repository initialized. Remote "origin" was set to "${gitRepo}"`,
137+
theme,
138+
});
92139
} catch {
93140
// We are not doing anything as long as this step is not really that important.
94141
// Nevertheless, a developer will be notified about something went wrong.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
createPrompt,
3+
isEnterKey,
4+
makeTheme,
5+
useEffect,
6+
useKeypress,
7+
useState,
8+
} from '@inquirer/core';
9+
import figures from 'figures';
10+
11+
import { spaces } from '../utils/spaces.js';
12+
import { lines } from '../utils/lines.js';
13+
import type { CustomTheme } from '../types.js';
14+
15+
export const input = createPrompt<string, {
16+
/**
17+
* Input default value.
18+
*/
19+
default?: string;
20+
/**
21+
* Input-related hint.
22+
*/
23+
hint?: string;
24+
/**
25+
* Message to insert between the prefix and input.
26+
*/
27+
message?: string;
28+
required?: boolean,
29+
theme: CustomTheme;
30+
/**
31+
* Validation function.
32+
* @param value
33+
*/
34+
validate?: (value: string) => string | undefined | null;
35+
}>(({
36+
theme,
37+
default: defaultValue,
38+
message,
39+
validate,
40+
hint,
41+
required,
42+
}, done) => {
43+
const { style, prefix } = makeTheme(theme);
44+
const [value, setValue] = useState('');
45+
const [error, setError] = useState<string>();
46+
const [completed, setCompleted] = useState(false);
47+
48+
function confirm(value: string): void {
49+
setValue(value);
50+
setError(undefined);
51+
setCompleted(true);
52+
done(value);
53+
}
54+
55+
useEffect(() => {
56+
completed && done(value);
57+
}, [completed, done, value]);
58+
59+
useKeypress((key, rl) => {
60+
if (isEnterKey(key)) {
61+
if (error) {
62+
return rl.write(value);
63+
}
64+
return value
65+
? confirm(value)
66+
: defaultValue
67+
? confirm(defaultValue)
68+
: required
69+
? setError('The value must be provided')
70+
: confirm('');
71+
}
72+
73+
if (key.name === 'tab' && !value) {
74+
rl.clearLine(0); // remove tab
75+
const v = defaultValue || '';
76+
rl.write(v);
77+
setValue(v);
78+
return;
79+
}
80+
81+
const input = rl.line;
82+
setError(input && validate && validate(input) || undefined);
83+
setValue(input);
84+
});
85+
86+
return [
87+
spaces(
88+
prefix[completed ? 'done' : 'idle'],
89+
message && style.message(message, 'idle'),
90+
// TODO: We need some specific style for it.
91+
style.placeholder(completed ? figures.ellipsis : figures.pointerSmall),
92+
completed
93+
? style.answer(value)
94+
: value || (defaultValue ? style.defaultAnswer(defaultValue) : ''),
95+
),
96+
completed ? undefined : lines(hint && style.help(hint), error && style.error(error)),
97+
];
98+
});

0 commit comments

Comments
 (0)