Skip to content

Commit

Permalink
Merge pull request #598 from Telegram-Mini-Apps/feature/create-mini-a…
Browse files Browse the repository at this point in the history
…pp-deprecation-warnings

Add deprecation warnings in `@telegram-apps/create-mini-app` for some templates
  • Loading branch information
heyqbnk authored Dec 17, 2024
2 parents 47e1524 + 320ab04 commit f343ca6
Show file tree
Hide file tree
Showing 19 changed files with 528 additions and 442 deletions.
6 changes: 6 additions & 0 deletions .changeset/mighty-baboons-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@telegram-apps/create-mini-app": minor
---

Deprecate JavaScript, jQuery and Pure TypeScript templates. Don't push any commits when the
template was cloned.
21 changes: 13 additions & 8 deletions packages/create-mini-app/src/cloneTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import chalk from 'chalk';

import { spawnWithSpinner } from './spawnWithSpinner.js';
import type { TemplateRepository } from './types.js';
import type { TemplateRepository } from './templates/types.js';
import type { CustomTheme } from './types.js';

/**
* Clones the template.
* @param rootDir - root directory.
* @param https - repository https link.
* @param ssh - repository ssh link.
* @param link - repository GitHub link.
* @param theme - inquirer theme.
*/
export async function cloneTemplate(
rootDir: string,
{
clone: { https, ssh },
link,
}: TemplateRepository,
theme: CustomTheme,
): Promise<void> {
const titleSuccess = `Cloned template: ${chalk.blue(link)}`;
const messageSuccess = `Cloned template: ${chalk.blue(link)}`;

// Clone the template using HTTPS.
try {
await spawnWithSpinner({
command: `git clone "${https}" "${rootDir}"`,
title: `Cloning the template from GitHub (HTTPS): ${chalk.bold.blue(https)}`,
titleFail: (error) => `Failed to load the template using HTTPS. ${error}`,
titleSuccess,
message: `Cloning the template from GitHub (HTTPS): ${chalk.bold.blue(https)}`,
messageFail: (error) => `Failed to load the template using HTTPS. ${error}`,
messageSuccess,
theme,
});
return;
} catch {
Expand All @@ -34,8 +38,9 @@ export async function cloneTemplate(
// Clone the template using SSH.
await spawnWithSpinner({
command: `git clone "${ssh}" "${rootDir}"`,
title: `Cloning the template from GitHub (SSH): ${chalk.bold.blue(ssh)}`,
titleFail: (error) => `Failed to load the template using SSH. ${error}`,
titleSuccess,
message: `Cloning the template from GitHub (SSH): ${chalk.bold.blue(ssh)}`,
messageFail: (error) => `Failed to load the template using SSH. ${error}`,
messageSuccess,
theme,
});
}
32 changes: 32 additions & 0 deletions packages/create-mini-app/src/createCustomTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { makeTheme } from '@inquirer/core';
import chalk from 'chalk';
import figures from 'figures';

import type { CustomTheme } from './types.js';

/**
* Creates a custom theme.
*/
export function createCustomTheme(): CustomTheme {
return makeTheme({
style: {
error(text: string): string {
return chalk.red(text);
},
success(text: string): string {
return chalk.green(text);
},
placeholder(text: string): string {
return chalk.dim(text);
},
warning(text: string): string {
return chalk.yellow(`${figures.warning} ${text}`);
},
},
prefix: {
idle: chalk.blue('?'),
done: chalk.green(figures.tick),
pointer: figures.arrowRight,
},
})
}
99 changes: 73 additions & 26 deletions packages/create-mini-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
import process from 'node:process';
import { rm } from 'node:fs/promises';
import { resolve } from 'node:path';
import { URL } from 'node:url';
import { existsSync } from 'node:fs';

import chalk from 'chalk';
import { program } from 'commander';

import { cloneTemplate } from './cloneTemplate.js';
import { isGitInstalled } from './isGitInstalled.js';
import { promptTemplate } from './prompts/promptTemplate/promptTemplate.js';
import { promptDirName } from './prompts/promptDirName.js';
import { promptGitRepo } from './prompts/promptGitRepo.js';
import { promptTemplate } from './templates/promptTemplate/promptTemplate.js';
import { spawnWithSpinner } from './spawnWithSpinner.js';
import { lines } from './utils/lines.js';
import type { TemplateRepository } from './types.js';
import type { TemplateRepository } from './templates/types.js';
import { input } from './prompts/input.js';
import { createCustomTheme } from './createCustomTheme.js';

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

Expand All @@ -22,73 +24,118 @@ program
.description(packageJson.description)
.version(packageJson.version)
.action(async () => {
const theme = createCustomTheme();

// Check if git is installed.
if (!await isGitInstalled()) {
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');
console.log(
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'),
);
process.exit(1);
}

// Prompt the project root directory name.
// Step 1: Prompt the project root directory.
let rootDir: string | null = null;
try {
rootDir = await promptDirName({ defaultValue: 'mini-app' });
rootDir = await input({
message: 'Directory name:',
required: true,
default: 'mini-app',
hint: 'This directory will be used as a root directory for the project. It is allowed to use alphanumeric latin letters, dashes and dots.',
theme,
validate(value) {
if (['.', '..'].includes(value)) {
return 'Value is not a valid directory name';
}

if (!value.match(/^[a-zA-Z0-9\-.]+$/)) {
return 'Value contains invalid symbols';
}

if (existsSync(resolve(value))) {
return `Directory "${value}" already exists`;
}
},
});
} catch {
process.exit(0);
}

// Prompt the target template.
// Step 2: Prompt the target template.
let repository: TemplateRepository;
try {
const { repository: promptRepo } = await promptTemplate({});
const { repository: promptRepo } = await promptTemplate({ theme });
repository = promptRepo;
} catch {
process.exit(0);
}

// Prompt Git repo information.
// Step 3: Prompt future Git repository information.
let gitRepo: string | undefined;
try {
gitRepo = await promptGitRepo({});
gitRepo = await input({
message: 'Git remote repository URL:',
validate(value) {
// Check if it is SSH connection string.
if (value.match(/\w+@[\w\-.]+:[\w-]+\/[\w./]+/)) {
return;
}

// Check if the specified value is URL.
try {
new URL(value);
return '';
} catch {
return 'Value is not considered as URL link or SSH connection string.';
}
},
theme,
hint: lines(
'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.',
`Leave value empty and press ${theme.style.key('enter')} to skip this step.`,
chalk.bold('Examples'),
'SSH: [email protected]:user/repo.git',
'URL: https://github.com/user/repo.git',
),
});
} catch {
process.exit(0);
}

// Clone the template.
// Step 4: Clone the template.
try {
await cloneTemplate(rootDir, repository);
await cloneTemplate(rootDir, repository, theme);
} catch {
process.exit(1);
}

// Remove the .git folder.
// Step 5: Remove the .git folder.
try {
await spawnWithSpinner({
title: 'Removing the .git directory.',
message: 'Removing the .git directory.',
command: () => rm(resolve(rootDir, '.git'), { recursive: true }),
titleFail: (err: string) => `Failed to remove the .git directory. Error: ${err}`,
titleSuccess: '.git directory removed.',
messageFail: (err: string) => `Failed to remove the .git directory. Error: ${err}`,
messageSuccess: '.git directory removed.',
theme,
});
} catch {
process.exit(1);
}

// Initialize new .git folder if required.
// Step 6: Initialize a new .git folder and configure remote.
if (gitRepo) {
try {
await spawnWithSpinner({
title: `Initializing Git repository: ${gitRepo}`,
message: `Initializing Git repository: ${gitRepo}`,
command: [
`cd "${rootDir}"`,
'git init',
'git add -A',
'git commit -m "first commit"',
'git branch -M master',
`git remote add origin "${gitRepo}"`,
'git push -u origin master',
].join(' && '),
titleFail: (error) => `Failed to initialize Git repository. ${error}`,
titleSuccess: 'Git repository initialized.',
})
messageFail: (error) => `Failed to initialize Git repository. ${error}`,
messageSuccess: `Git repository initialized. Remote "origin" was set to "${gitRepo}"`,
theme,
});
} catch {
// We are not doing anything as long as this step is not really that important.
// Nevertheless, a developer will be notified about something went wrong.
Expand Down
98 changes: 98 additions & 0 deletions packages/create-mini-app/src/prompts/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
createPrompt,
isEnterKey,
makeTheme,
useEffect,
useKeypress,
useState,
} from '@inquirer/core';
import figures from 'figures';

import { spaces } from '../utils/spaces.js';
import { lines } from '../utils/lines.js';
import type { CustomTheme } from '../types.js';

export const input = createPrompt<string, {
/**
* Input default value.
*/
default?: string;
/**
* Input-related hint.
*/
hint?: string;
/**
* Message to insert between the prefix and input.
*/
message?: string;
required?: boolean,
theme: CustomTheme;
/**
* Validation function.
* @param value
*/
validate?: (value: string) => string | undefined | null;
}>(({
theme,
default: defaultValue,
message,
validate,
hint,
required,
}, done) => {
const { style, prefix } = makeTheme(theme);
const [value, setValue] = useState('');
const [error, setError] = useState<string>();
const [completed, setCompleted] = useState(false);

function confirm(value: string): void {
setValue(value);
setError(undefined);
setCompleted(true);
done(value);
}

useEffect(() => {
completed && done(value);
}, [completed, done, value]);

useKeypress((key, rl) => {
if (isEnterKey(key)) {
if (error) {
return rl.write(value);
}
return value
? confirm(value)
: defaultValue
? confirm(defaultValue)
: required
? setError('The value must be provided')
: confirm('');
}

if (key.name === 'tab' && !value) {
rl.clearLine(0); // remove tab
const v = defaultValue || '';
rl.write(v);
setValue(v);
return;
}

const input = rl.line;
setError(input && validate && validate(input) || undefined);
setValue(input);
});

return [
spaces(
prefix[completed ? 'done' : 'idle'],
message && style.message(message, 'idle'),
// TODO: We need some specific style for it.
style.placeholder(completed ? figures.ellipsis : figures.pointerSmall),
completed
? style.answer(value)
: value || (defaultValue ? style.defaultAnswer(defaultValue) : ''),
),
completed ? undefined : lines(hint && style.help(hint), error && style.error(error)),
];
});
Loading

0 comments on commit f343ca6

Please sign in to comment.