Skip to content

Commit c54c7b9

Browse files
1cedrussinzii
andauthored
feat: add create-typink package (#127)
* Setup package * Update package.json * Add basic app flows * Rename package, add template, wallet connector, and network options * Update README.md * Add TODOs * Add prettier format task, refactor files * Fix networks need to be an array * Fix yarn prettify * Force at least one network to be choose * Add handler for template files * Refactoring 🔧 * run directly via tsx * Deploy contract in astar and alephzero * Handle preset contract option * Refactoring * Handle wallet connector options * Fix arg not capture `--template` value * Not create files if template render nothing * Remove `--version` arg, `default` is default value of `--template` * Fix `bin` import * Use `validate-npm-package-name` instead of regex * Refactoring 🔧🔧 * Fix imports * Github Action first run expect to be failed * Update template for wallet connector options * Github Action for testing * Refactoring 🔧 * require node > v20 * revert changes * update dapp styling * add version/author * refactoring files * fix esm --------- Co-authored-by: Thang X. Vu <thang@coongcrafts.io>
1 parent c6bc16d commit c54c7b9

File tree

90 files changed

+6776
-11
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+6776
-11
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Create Typink Tests
2+
3+
on:
4+
push:
5+
workflow_dispatch:
6+
merge_group:
7+
8+
jobs:
9+
create-typink-tests:
10+
runs-on: ubuntu-latest
11+
12+
strategy:
13+
matrix:
14+
node-version: [20.x]
15+
16+
steps:
17+
- uses: actions/checkout@v3
18+
- name: Use Node.js ${{ matrix.node-version }}
19+
uses: actions/setup-node@v3
20+
with:
21+
node-version: ${{ matrix.node-version }}
22+
cache: 'yarn'
23+
- run: yarn install --immutable
24+
- run: yarn build
25+
- name: Create app with create-typink (Default)
26+
run: |
27+
export YARN_ENABLE_IMMUTABLE_INSTALLS=false
28+
cd ..
29+
node ./typink/packages/create-typink/dist/bin/create-typink.mjs --no-git -n typink-app-default -t default -p greeter -N "Pop Testnet" -N "Aleph Zero Testnet" -w Default
30+
ls -la
31+
cd ./typink-app-default
32+
ls -la
33+
- name: Try to build (Default)
34+
run: |
35+
cd ../typink-app-default
36+
yarn build
37+
- name: Create app with create-typink (SubConnect V2)
38+
run: |
39+
export YARN_ENABLE_IMMUTABLE_INSTALLS=false
40+
cd ..
41+
node ./typink/packages/create-typink/dist/bin/create-typink.mjs --no-git -n typink-app-subconnect-v2 -t default -p greeter -N "Pop Testnet" -N "Aleph Zero Testnet" -w "SubConnect V2"
42+
ls -la
43+
cd ./typink-app-subconnect-v2
44+
ls -la
45+
- name: Try to build (SubConnect V2)
46+
run: |
47+
cd ../typink-app-subconnect-v2
48+
yarn build
49+
- name: Create app with create-typink (Talisman Connect)
50+
run: |
51+
export YARN_ENABLE_IMMUTABLE_INSTALLS=false
52+
cd ..
53+
node ./typink/packages/create-typink/dist/bin/create-typink.mjs --no-git -n typink-app-talisman-connect -t default -p greeter -N "Pop Testnet" -N "Aleph Zero Testnet" -w "Talisman Connect"
54+
ls -la
55+
cd ./typink-app-talisman-connect
56+
ls -la
57+
- name: Try to build (Talisman Connect)
58+
run: |
59+
cd ../typink-app-talisman-connect
60+
yarn build

examples/demo/src/contracts/deployments.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { alephZeroTestnet, ContractDeployment, popTestnet } from 'typink';
1+
import { alephZeroTestnet, ContractDeployment, popTestnet, alephZero, astar } from 'typink';
22
import greeterMetadata from './artifacts/greeter/greeter.json';
33
import psp22Metadata from './artifacts/psp22/psp22.json';
44

@@ -22,6 +22,18 @@ export const greeterDeployments: ContractDeployment[] = [
2222
network: alephZeroTestnet.id,
2323
address: '5CDia8Y46K7CbD2vLej2SjrvxpfcbrLVqK2He3pTJod2Eyik',
2424
},
25+
{
26+
id: ContractId.GREETER,
27+
metadata: greeterMetadata as any,
28+
network: alephZero.id,
29+
address: '5CYZtKBxuva33JREQkbeaE4ed2niWb1ijS4pgXbFD61yZti1',
30+
},
31+
{
32+
id: ContractId.GREETER,
33+
metadata: greeterMetadata as any,
34+
network: astar.id,
35+
address: 'WejJavPYsGgcY8Dr5KQSJrTssxUh5EbeYiCfdddeo5aTbse',
36+
},
2537
];
2638

2739
export const psp22Deployments: ContractDeployment[] = [
@@ -37,6 +49,18 @@ export const psp22Deployments: ContractDeployment[] = [
3749
network: alephZeroTestnet.id,
3850
address: '5G5moUCkx5E2TD3CcRWvweg7rpCLngRmwukuKdaohvfBBmXr',
3951
},
52+
{
53+
id: ContractId.PSP22,
54+
metadata: psp22Metadata as any,
55+
network: alephZero.id,
56+
address: '5EkDPuyLdubc4uUmEhzMFRtcNtmxSoUecfcc9wWLUR7ZFXbb',
57+
},
58+
{
59+
id: ContractId.PSP22,
60+
metadata: psp22Metadata as any,
61+
network: astar.id,
62+
address: 'YFhgALp2qPtPe1pTFereqqB4RUXKzVM7YtCYfqQX412GWHr',
63+
},
4064
];
4165

4266
export const deployments = [...greeterDeployments, ...psp22Deployments];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"vitest": "^2.1.8"
3939
},
4040
"engines": {
41-
"node": ">=18"
41+
"node": ">=20"
4242
},
4343
"license": "MIT"
4444
}

packages/create-typink/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Thang X. Vu <thang@dedot.dev>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/create-typink/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# create-typink
2+
3+
A CLI tool to create a new project using Typink
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
import { createTypink } from '../index.js';
3+
4+
createTypink();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "create-typink",
3+
"version": "0.0.0",
4+
"author": "Tung Vu <tung@dedot.dev>",
5+
"main": "src/index.ts",
6+
"type": "module",
7+
"scripts": {
8+
"build": "tsc --project tsconfig.build.json && cp -R ./bin ./dist && cp -R ./src/templates ./dist",
9+
"clean": "rm -rf ./dist && rm -rf ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo"
10+
},
11+
"bin": "./bin/create-typink.mjs",
12+
"dependencies": {
13+
"arg": "^5.0.2",
14+
"chalk": "^5.4.1",
15+
"ejs": "^3.1.10",
16+
"execa": "^9.5.2",
17+
"inquirer": "^12.3.2",
18+
"listr2": "^8.2.5",
19+
"prettier": "^3.4.2",
20+
"validate-npm-package-name": "^6.0.0"
21+
},
22+
"publishConfig": {
23+
"access": "public",
24+
"directory": "dist"
25+
},
26+
"license": "MIT",
27+
"devDependencies": {
28+
"@types/ejs": "^3",
29+
"@types/validate-npm-package-name": "^4.0.2"
30+
}
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Options } from './types.js';
2+
import { Listr } from 'listr2';
3+
import { fileURLToPath } from 'url';
4+
import * as path from 'path';
5+
import chalk from 'chalk';
6+
import {
7+
createProjectDirectory,
8+
createFirstCommit,
9+
copyTemplateFiles,
10+
prettierFormat,
11+
installPackages,
12+
} from './tasks/index.js';
13+
14+
export async function createProject(options: Options) {
15+
const { projectName, skipInstall, noGit } = options;
16+
17+
const __filename = fileURLToPath(import.meta.url);
18+
const __dirname = path.dirname(__filename);
19+
20+
const templateDirectory = path.resolve(__dirname, './templates');
21+
const targetDirectory = path.resolve(process.cwd(), projectName!);
22+
23+
const tasks = new Listr(
24+
[
25+
{
26+
title: `📁 Create project directory ${targetDirectory}`,
27+
task: () => createProjectDirectory(projectName!),
28+
},
29+
{
30+
title: `🚀 Creating a new Typink app in ${chalk.green.bold(projectName)}`,
31+
task: () => copyTemplateFiles(options, templateDirectory, targetDirectory),
32+
},
33+
{
34+
title: '📦 Installing dependencies with yarn, this could take a while',
35+
task: () => installPackages(targetDirectory),
36+
skip: skipInstall,
37+
},
38+
{
39+
title: '🧹 Formatting the code with Prettier',
40+
task: () => prettierFormat(targetDirectory, options),
41+
},
42+
{
43+
title: `🚨 Create the very first Git commit`,
44+
task: () => createFirstCommit(targetDirectory),
45+
skip: noGit,
46+
},
47+
],
48+
{ rendererOptions: { suffixSkips: true }, exitOnError: true },
49+
);
50+
51+
await tasks.run();
52+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import chalk from 'chalk';
2+
import {
3+
parseArguments,
4+
renderIntroArt,
5+
promptMissingOptions,
6+
renderHelpMessage,
7+
renderOutroMessage,
8+
} from './utils/index.js';
9+
import { createProject } from './createProject.js';
10+
import { fileURLToPath } from 'url';
11+
12+
export async function createTypink() {
13+
try {
14+
renderIntroArt();
15+
16+
const args = parseArguments();
17+
18+
if (args.help) {
19+
renderHelpMessage();
20+
return;
21+
}
22+
23+
const options = await promptMissingOptions(args);
24+
25+
await createProject(options);
26+
27+
renderOutroMessage(options);
28+
} catch (error) {
29+
console.error(chalk.red.bold('🚨 An error occurred:'), error);
30+
console.error(chalk.red.bold('🚨 Sorry, exiting...'));
31+
}
32+
}
33+
34+
// run directly from root folder: tsx ./packages/create-typink/src/index.ts
35+
const __filename = fileURLToPath(import.meta.url);
36+
if (process.argv[1] === __filename) {
37+
createTypink().catch(console.error);
38+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { execa } from 'execa';
2+
import { Options } from '../types.js';
3+
import * as fs from 'fs';
4+
import * as ejs from 'ejs';
5+
import * as path from 'path';
6+
import { stringCamelCase } from '@dedot/utils';
7+
import { IS_IGNORE_FILES, IS_TEMPLATE_FILE } from '../utils/index.js';
8+
9+
export async function copyTemplateFiles(options: Options, templatesDir: string, targetDir: string) {
10+
const { projectName, noGit, template } = options;
11+
12+
const templateDir = `${templatesDir}/${template}`;
13+
14+
if (!fs.existsSync(templateDir)) {
15+
throw new Error(`Template directory not found: ${templateDir}`);
16+
}
17+
18+
await fs.promises.cp(templateDir, targetDir, { recursive: true });
19+
20+
const packageJsonPath = `${targetDir}/package.json`;
21+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
22+
23+
packageJson.name = projectName;
24+
await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
25+
26+
processPresetContract(options, targetDir);
27+
processTemplateFiles(options, targetDir);
28+
29+
if (!noGit) {
30+
await execa('git', ['init'], { cwd: targetDir });
31+
await execa('git', ['checkout', '-b', 'main'], { cwd: targetDir });
32+
}
33+
}
34+
35+
export async function processPresetContract(options: Options, targetDir: string) {
36+
const dirsToCheck = [`${targetDir}/contracts/artifacts`, `${targetDir}/contracts/types`];
37+
38+
dirsToCheck.forEach(async (dir) => {
39+
for (const file of await fs.promises.readdir(dir, { withFileTypes: true })) {
40+
if (file.name === options.presetContract) {
41+
continue;
42+
}
43+
44+
await fs.promises.rm(path.join(dir, file.name), { recursive: true });
45+
}
46+
});
47+
}
48+
49+
export async function processTemplateFiles(rawOptions: Options, targetDir: string) {
50+
const options = {
51+
...rawOptions,
52+
networks: rawOptions.networks?.map(stringCamelCase),
53+
};
54+
55+
await processTemplateFilesRecursive(options, targetDir);
56+
}
57+
58+
async function processTemplateFilesRecursive(options: any, dir: string) {
59+
if (IS_IGNORE_FILES.test(dir)) {
60+
return;
61+
}
62+
63+
const files = await fs.promises.readdir(dir, { withFileTypes: true });
64+
65+
for (const file of files) {
66+
const filePath = path.join(dir, file.name);
67+
68+
if (file.isDirectory()) {
69+
await processTemplateFilesRecursive(options, filePath);
70+
} else {
71+
if (IS_TEMPLATE_FILE.test(filePath)) {
72+
const content = fs.readFileSync(filePath, 'utf-8');
73+
const result = ejs.render(content, { options });
74+
75+
if (result.trim() !== '') {
76+
await fs.promises.writeFile(filePath.replace('.template.ejs', ''), result);
77+
}
78+
79+
await fs.promises.rm(filePath);
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)