Skip to content

Commit 45902cf

Browse files
committed
build(avati): add template generator
Signed-off-by: Khaled Sameer <[email protected]>
1 parent 6041cb7 commit 45902cf

11 files changed

+991
-0
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"build": "lerna run build --stream --sort",
3030
"dev": "npm run dev --workspaces --recursive",
3131
"type-check": "tsc --noEmit",
32+
"create-package": "node scripts/create-package.mjs create",
3233
"clean": "npm run clean:dist && npm run clean:types && npm run clean:maps && npm run clean:jsmap",
3334
"clean:dist": "rimraf packages/*/dist",
3435
"clean:types": "rimraf packages/**/*.d.ts -g",

scripts/create-package.mjs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env node
2+
3+
/*
4+
* Copyright (c) 2024 Khaled Sameer <[email protected]>.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import { resolve, join } from 'path';
11+
import { mkdir, cp, readFile, writeFile, access } from 'fs/promises';
12+
import { createInterface } from 'readline';
13+
import { argv, exit, stdin, stdout } from 'process';
14+
import { fileURLToPath } from 'url';
15+
import { dirname } from 'path';
16+
17+
// Get current directory
18+
const __filename = fileURLToPath(import.meta.url);
19+
const __dirname = dirname(__filename);
20+
21+
const rl = createInterface({
22+
input: stdin,
23+
output: stdout
24+
});
25+
26+
const question = (query) => {
27+
return new Promise((resolve) => {
28+
rl.question(query, (answer) => {
29+
resolve(answer);
30+
});
31+
});
32+
};
33+
34+
const TEMPLATES = {
35+
'vanilla-ts': {
36+
name: 'Vanilla TypeScript',
37+
path: join(__dirname, 'templates/vanilla-ts')
38+
},
39+
'tsx': {
40+
name: 'TSX TypeScript',
41+
path: join(__dirname, 'templates/tsx')
42+
}
43+
};
44+
45+
async function validatePackageName(name) {
46+
if (name.length < 1) {
47+
return 'Package name cannot be empty';
48+
}
49+
if (!/^[a-z0-9-]+$/.test(name)) {
50+
return 'Package name can only contain lowercase letters, numbers, and hyphens';
51+
}
52+
return true;
53+
}
54+
55+
async function copyDirectory(src, dest) {
56+
try {
57+
await cp(src, dest, { recursive: true });
58+
} catch (error) {
59+
console.error(`Error copying directory: ${error}`);
60+
exit(1);
61+
}
62+
}
63+
64+
async function updatePackageJson(packagePath, packageName) {
65+
try {
66+
const packageJsonPath = join(packagePath, 'package.json');
67+
const content = await readFile(packageJsonPath, 'utf-8');
68+
const packageJson = JSON.parse(content);
69+
packageJson.name = `@avatijs/${packageName}`;
70+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
71+
} catch (error) {
72+
console.error(`Error updating package.json: ${error}`);
73+
exit(1);
74+
}
75+
}
76+
77+
async function updateReadme(packagePath, packageName) {
78+
try {
79+
const readmePath = join(packagePath, 'README.md');
80+
const content = await readFile(readmePath, 'utf-8');
81+
let updatedContent = content.replace(/{{package_name}}/g, packageName)
82+
updatedContent = updatedContent.replace(/{{npm_package_name}}/g, packageName.replace(/s+/g, '_'));
83+
await writeFile(readmePath, updatedContent);
84+
} catch (error) {
85+
console.error(`Error updating README.md: ${error}`);
86+
exit(1);
87+
}
88+
}
89+
90+
91+
async function createPackage() {
92+
try {
93+
// Get package name
94+
let packageName = '';
95+
let isValidName = false;
96+
97+
while (!isValidName) {
98+
packageName = await question('Package name: ');
99+
const validationResult = await validatePackageName(packageName);
100+
101+
if (validationResult === true) {
102+
isValidName = true;
103+
} else {
104+
console.error(`Error: ${validationResult}`);
105+
}
106+
}
107+
108+
// Select template
109+
console.log('\nAvailable templates:');
110+
Object.entries(TEMPLATES).forEach(([key, value], index) => {
111+
console.log(`${index + 1}. ${value.name}`);
112+
});
113+
114+
const templateChoice = await question('\nSelect template (1 or 2): ');
115+
const templateIndex = parseInt(templateChoice) - 1;
116+
const templateKeys = Object.keys(TEMPLATES);
117+
const selectedTemplate = templateKeys[templateIndex];
118+
119+
if (!selectedTemplate || !TEMPLATES[selectedTemplate]) {
120+
console.error('Invalid template selection');
121+
exit(1);
122+
}
123+
124+
const packagePath = join(process.cwd(), 'packages', packageName);
125+
126+
// Check if package already exists
127+
try {
128+
await access(packagePath);
129+
console.error(`Error: Package ${packageName} already exists!`);
130+
exit(1);
131+
} catch {
132+
// Package doesn't exist, we can proceed
133+
}
134+
135+
// Create package directory
136+
await mkdir(packagePath, { recursive: true });
137+
138+
// Copy template files
139+
await copyDirectory(TEMPLATES[selectedTemplate].path, packagePath);
140+
141+
// Update package.json
142+
await updatePackageJson(packagePath, packageName);
143+
144+
// Update README.md
145+
await updateReadme(packagePath, packageName);
146+
147+
console.log(`\nPackage ${packageName} created successfully!`);
148+
console.log('\nNext steps:');
149+
console.log(`1. yarn install`);
150+
151+
} catch (error) {
152+
console.error('Error creating package:', error);
153+
exit(1);
154+
} finally {
155+
rl.close();
156+
}
157+
}
158+
159+
// Handle CLI arguments
160+
if (argv[2] === 'create') {
161+
createPackage();
162+
} else {
163+
console.log('Usage: create-package create');
164+
exit(1);
165+
}

0 commit comments

Comments
 (0)