@valbuild/create 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc.json +5 -0
- package/CHANGELOG.md +0 -0
- package/README.md +52 -0
- package/bin.js +3 -0
- package/dist/declarations/src/index.d.ts +1 -0
- package/dist/valbuild-create.cjs.d.ts +2 -0
- package/dist/valbuild-create.cjs.dev.js +232 -0
- package/dist/valbuild-create.cjs.js +7 -0
- package/dist/valbuild-create.cjs.prod.js +232 -0
- package/dist/valbuild-create.esm.js +225 -0
- package/package.json +49 -0
- package/src/degit.d.ts +12 -0
- package/src/index.ts +304 -0
- package/tsconfig.json +12 -0
package/CHANGELOG.md
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @valbuild/create
|
|
2
|
+
|
|
3
|
+
Bootstrap a Val project from the cli.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm create @valbuild@latest
|
|
9
|
+
# or with a project name
|
|
10
|
+
npm create @valbuild@latest my-val-app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You'll be prompted to select a template (e.g. "starter" or "minimal").
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
- `-h`, `--help` Show help
|
|
18
|
+
- `-v`, `--version` Show version
|
|
19
|
+
- `--root <path>` Specify the root directory for project creation (default: current directory)
|
|
20
|
+
|
|
21
|
+
## Templates
|
|
22
|
+
|
|
23
|
+
- **starter**: Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples
|
|
24
|
+
- **minimal**: Minimal Next.js app with Val and TypeScript
|
|
25
|
+
|
|
26
|
+
## Adding More Templates
|
|
27
|
+
|
|
28
|
+
To add more templates:
|
|
29
|
+
|
|
30
|
+
1. **Create a new public GitHub repo** under the `valbuild` org (e.g. `valbuild/template-nextjs-ecommerce`).
|
|
31
|
+
2. Add your template code to that repo.
|
|
32
|
+
3. In this CLI, open `src/index.ts` and add your template to the `TEMPLATES` array:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
{
|
|
36
|
+
name: "ecommerce",
|
|
37
|
+
description: "E-commerce starter with Val and Next.js",
|
|
38
|
+
repo: "valbuild/template-nextjs-ecommerce"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
4. (Optional) Publish your template repo with a clear README and keep it up to date.
|
|
43
|
+
|
|
44
|
+
## Contributing
|
|
45
|
+
|
|
46
|
+
- PRs for new templates, bugfixes, or UX improvements are welcome!
|
|
47
|
+
- Please keep template names and descriptions clear and future-proof.
|
|
48
|
+
- For major changes, open an issue to discuss your idea first.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
package/bin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export * from "./declarations/src/index.js";
|
|
2
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsYnVpbGQtY3JlYXRlLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi9kZWNsYXJhdGlvbnMvc3JjL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ==
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var degit = require('degit');
|
|
7
|
+
var prompts = require('@inquirer/prompts');
|
|
8
|
+
var chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
11
|
+
|
|
12
|
+
var degit__default = /*#__PURE__*/_interopDefault(degit);
|
|
13
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
14
|
+
|
|
15
|
+
const PKG = {
|
|
16
|
+
name: "@valbuild/create",
|
|
17
|
+
version: "0.1.0"
|
|
18
|
+
};
|
|
19
|
+
const TEMPLATES = [{
|
|
20
|
+
name: "starter",
|
|
21
|
+
description: "Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples",
|
|
22
|
+
repo: "valbuild/template-nextjs-starter",
|
|
23
|
+
default: true
|
|
24
|
+
}];
|
|
25
|
+
const DEFAULT_PROJECT_NAME = "my-val-app";
|
|
26
|
+
function printHelp() {
|
|
27
|
+
console.log(`
|
|
28
|
+
${chalk__default["default"].bold("Usage:")}
|
|
29
|
+
${chalk__default["default"].cyan("npm create @valbuild [project-name]")}
|
|
30
|
+
|
|
31
|
+
${chalk__default["default"].bold("Options:")}
|
|
32
|
+
-h, --help Show help
|
|
33
|
+
-v, --version Show version
|
|
34
|
+
--root <path> Specify the root directory for project creation (default: current directory)
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
function printVersion() {
|
|
38
|
+
console.log(`${PKG.name} v${PKG.version}`);
|
|
39
|
+
}
|
|
40
|
+
function handleExit() {
|
|
41
|
+
console.log(chalk__default["default"].yellow("\nAborted."));
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
process.on("SIGINT", handleExit);
|
|
45
|
+
|
|
46
|
+
// Timeline stepper logic
|
|
47
|
+
const timelineSteps = ["Enter project name", "Download template", "Install dependencies", "Complete!"];
|
|
48
|
+
function renderTimeline(currentStep, errorStep) {
|
|
49
|
+
const icons = {
|
|
50
|
+
pending: chalk__default["default"].gray("āÆ"),
|
|
51
|
+
active: chalk__default["default"].cyan("ā"),
|
|
52
|
+
done: chalk__default["default"].green("ā"),
|
|
53
|
+
error: chalk__default["default"].red("ā")
|
|
54
|
+
};
|
|
55
|
+
let out = "\n";
|
|
56
|
+
for (let i = 0; i < timelineSteps.length; i++) {
|
|
57
|
+
let status = "pending";
|
|
58
|
+
if (errorStep !== undefined && i === errorStep) status = "error";else if (i < currentStep) status = "done";else if (i === currentStep) status = "active";
|
|
59
|
+
const icon = icons[status];
|
|
60
|
+
out += ` ${icon} ${timelineSteps[i]}\n`;
|
|
61
|
+
if (i < timelineSteps.length - 1) out += ` ${chalk__default["default"].gray("ā")}\n`;
|
|
62
|
+
}
|
|
63
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
64
|
+
displayValLogo();
|
|
65
|
+
process.stdout.write(out + "\n");
|
|
66
|
+
}
|
|
67
|
+
function displayValLogo() {
|
|
68
|
+
const logo = chalk__default["default"].cyan(`
|
|
69
|
+
###########
|
|
70
|
+
###########
|
|
71
|
+
########### @@@@
|
|
72
|
+
########### @@
|
|
73
|
+
########### @@ @@ @@@@@@ @ @@
|
|
74
|
+
########### @@ @@ @@ @@ @@
|
|
75
|
+
########### @@ @@ %@ @ @@
|
|
76
|
+
#### ##### @@ @@ .@ .@ @@
|
|
77
|
+
### #### @@@@ @@: @@@. @@
|
|
78
|
+
#### ##### @@@@ @@@@ =@@@@@@@@@
|
|
79
|
+
###########
|
|
80
|
+
`);
|
|
81
|
+
process.stdout.write(logo);
|
|
82
|
+
}
|
|
83
|
+
function displaySuccessMessage(projectName) {
|
|
84
|
+
const nextSteps = chalk__default["default"].bold(`
|
|
85
|
+
${chalk__default["default"].cyan("Next steps:")}
|
|
86
|
+
${chalk__default["default"].cyan("cd")} ${chalk__default["default"].white(projectName)}
|
|
87
|
+
${chalk__default["default"].cyan("npm run dev")}
|
|
88
|
+
|
|
89
|
+
${chalk__default["default"].green("Happy coding! š")}
|
|
90
|
+
`);
|
|
91
|
+
process.stdout.write(nextSteps);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Template processing function
|
|
95
|
+
function processTemplateFiles(projectPath, projectName) {
|
|
96
|
+
const filesToProcess = ["package.json", "README.md", "next.config.js", "val.config.ts", "val.config.js"];
|
|
97
|
+
filesToProcess.forEach(filename => {
|
|
98
|
+
const filePath = path.join(projectPath, filename);
|
|
99
|
+
if (fs.existsSync(filePath)) {
|
|
100
|
+
try {
|
|
101
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
102
|
+
// Replace both {{PROJECT_NAME}} and {{projectName}} placeholders
|
|
103
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
104
|
+
content = content.replace(/\{\{projectName\}\}/g, projectName);
|
|
105
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Silently continue if file can't be processed
|
|
108
|
+
console.log(chalk__default["default"].dim(`Note: Could not process ${filename}`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function main() {
|
|
114
|
+
try {
|
|
115
|
+
console.log("here");
|
|
116
|
+
const args = process.argv.slice(2);
|
|
117
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
118
|
+
printHelp();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
122
|
+
printVersion();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Parse --root option
|
|
127
|
+
const rootIndex = args.findIndex(a => a === "--root");
|
|
128
|
+
let rootDir = process.cwd();
|
|
129
|
+
if (rootIndex !== -1 && args[rootIndex + 1]) {
|
|
130
|
+
rootDir = args[rootIndex + 1];
|
|
131
|
+
// Remove --root and its value from args for project name parsing
|
|
132
|
+
args.splice(rootIndex, 2);
|
|
133
|
+
}
|
|
134
|
+
let currentStep = 0;
|
|
135
|
+
renderTimeline(currentStep);
|
|
136
|
+
|
|
137
|
+
// Step 1: Enter project name
|
|
138
|
+
const projectName = await prompts.input({
|
|
139
|
+
message: chalk__default["default"].bold("What is your project named?"),
|
|
140
|
+
default: DEFAULT_PROJECT_NAME,
|
|
141
|
+
validate: value => {
|
|
142
|
+
if (!value || value.trim().length === 0) {
|
|
143
|
+
return "Project name cannot be empty";
|
|
144
|
+
}
|
|
145
|
+
if (value.includes(" ")) {
|
|
146
|
+
return "Project name cannot contain spaces";
|
|
147
|
+
}
|
|
148
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
149
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
currentStep++;
|
|
155
|
+
renderTimeline(currentStep);
|
|
156
|
+
|
|
157
|
+
// Step 2: Select template
|
|
158
|
+
const selectedTemplate = TEMPLATES[0];
|
|
159
|
+
currentStep++;
|
|
160
|
+
renderTimeline(currentStep);
|
|
161
|
+
|
|
162
|
+
// Step 3: Download template
|
|
163
|
+
const projectPath = path.join(rootDir, projectName);
|
|
164
|
+
if (fs.existsSync(projectPath)) {
|
|
165
|
+
renderTimeline(currentStep, currentStep);
|
|
166
|
+
console.error(chalk__default["default"].red(`ā Error: Directory "${projectName}" already exists.`));
|
|
167
|
+
console.error(chalk__default["default"].yellow("Please choose a different name or remove the existing directory."));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
fs.mkdirSync(projectPath, {
|
|
171
|
+
recursive: true
|
|
172
|
+
});
|
|
173
|
+
process.stdout.write(chalk__default["default"].bold("\nš„ Downloading template from GitHub...\n") + ` ${chalk__default["default"].dim(`https://github.com/${selectedTemplate.repo}`)}\n`);
|
|
174
|
+
try {
|
|
175
|
+
const emitter = degit__default["default"](selectedTemplate.repo, {
|
|
176
|
+
cache: false,
|
|
177
|
+
force: true,
|
|
178
|
+
verbose: false
|
|
179
|
+
});
|
|
180
|
+
await emitter.clone(projectPath);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
renderTimeline(currentStep, currentStep);
|
|
183
|
+
console.error(chalk__default["default"].red("ā Failed to download template:"));
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
185
|
+
if (errorMessage.includes("rate limit") || errorMessage.includes("403")) {
|
|
186
|
+
console.error(chalk__default["default"].yellow("GitHub rate limit exceeded. Please try again later or authenticate with GitHub."));
|
|
187
|
+
} else if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
188
|
+
console.error(chalk__default["default"].yellow(`Template repository not found: ${selectedTemplate.repo}`));
|
|
189
|
+
console.error(chalk__default["default"].yellow("Please check if the repository exists and is public."));
|
|
190
|
+
} else {
|
|
191
|
+
console.error(chalk__default["default"].yellow("Network error. Please check your internet connection."));
|
|
192
|
+
}
|
|
193
|
+
console.error(chalk__default["default"].dim("Error details:"), errorMessage);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
currentStep++;
|
|
197
|
+
renderTimeline(currentStep);
|
|
198
|
+
process.stdout.write(chalk__default["default"].green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
199
|
+
|
|
200
|
+
// Process template files
|
|
201
|
+
processTemplateFiles(projectPath, projectName);
|
|
202
|
+
|
|
203
|
+
// Change to project directory and install dependencies
|
|
204
|
+
process.stdout.write(chalk__default["default"].bold("\nš¦ Installing dependencies...\n"));
|
|
205
|
+
try {
|
|
206
|
+
child_process.execSync("npm install", {
|
|
207
|
+
cwd: projectPath,
|
|
208
|
+
stdio: "inherit" // Show npm output in real-time
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Clear the npm output and show success
|
|
212
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
213
|
+
displayValLogo();
|
|
214
|
+
currentStep++;
|
|
215
|
+
renderTimeline(currentStep);
|
|
216
|
+
process.stdout.write(chalk__default["default"].green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
217
|
+
process.stdout.write(chalk__default["default"].green("\nā
Dependencies installed successfully!\n"));
|
|
218
|
+
|
|
219
|
+
// Show final success message
|
|
220
|
+
displaySuccessMessage(projectName);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
renderTimeline(currentStep, currentStep);
|
|
223
|
+
console.error(chalk__default["default"].red('ā Failed to install dependencies. You can try running "npm install" manually.'));
|
|
224
|
+
console.error("Error:", error);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(chalk__default["default"].red("ā Failed to create project:"), error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
main();
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var degit = require('degit');
|
|
7
|
+
var prompts = require('@inquirer/prompts');
|
|
8
|
+
var chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
11
|
+
|
|
12
|
+
var degit__default = /*#__PURE__*/_interopDefault(degit);
|
|
13
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
14
|
+
|
|
15
|
+
const PKG = {
|
|
16
|
+
name: "@valbuild/create",
|
|
17
|
+
version: "0.1.0"
|
|
18
|
+
};
|
|
19
|
+
const TEMPLATES = [{
|
|
20
|
+
name: "starter",
|
|
21
|
+
description: "Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples",
|
|
22
|
+
repo: "valbuild/template-nextjs-starter",
|
|
23
|
+
default: true
|
|
24
|
+
}];
|
|
25
|
+
const DEFAULT_PROJECT_NAME = "my-val-app";
|
|
26
|
+
function printHelp() {
|
|
27
|
+
console.log(`
|
|
28
|
+
${chalk__default["default"].bold("Usage:")}
|
|
29
|
+
${chalk__default["default"].cyan("npm create @valbuild [project-name]")}
|
|
30
|
+
|
|
31
|
+
${chalk__default["default"].bold("Options:")}
|
|
32
|
+
-h, --help Show help
|
|
33
|
+
-v, --version Show version
|
|
34
|
+
--root <path> Specify the root directory for project creation (default: current directory)
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
function printVersion() {
|
|
38
|
+
console.log(`${PKG.name} v${PKG.version}`);
|
|
39
|
+
}
|
|
40
|
+
function handleExit() {
|
|
41
|
+
console.log(chalk__default["default"].yellow("\nAborted."));
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
process.on("SIGINT", handleExit);
|
|
45
|
+
|
|
46
|
+
// Timeline stepper logic
|
|
47
|
+
const timelineSteps = ["Enter project name", "Download template", "Install dependencies", "Complete!"];
|
|
48
|
+
function renderTimeline(currentStep, errorStep) {
|
|
49
|
+
const icons = {
|
|
50
|
+
pending: chalk__default["default"].gray("āÆ"),
|
|
51
|
+
active: chalk__default["default"].cyan("ā"),
|
|
52
|
+
done: chalk__default["default"].green("ā"),
|
|
53
|
+
error: chalk__default["default"].red("ā")
|
|
54
|
+
};
|
|
55
|
+
let out = "\n";
|
|
56
|
+
for (let i = 0; i < timelineSteps.length; i++) {
|
|
57
|
+
let status = "pending";
|
|
58
|
+
if (errorStep !== undefined && i === errorStep) status = "error";else if (i < currentStep) status = "done";else if (i === currentStep) status = "active";
|
|
59
|
+
const icon = icons[status];
|
|
60
|
+
out += ` ${icon} ${timelineSteps[i]}\n`;
|
|
61
|
+
if (i < timelineSteps.length - 1) out += ` ${chalk__default["default"].gray("ā")}\n`;
|
|
62
|
+
}
|
|
63
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
64
|
+
displayValLogo();
|
|
65
|
+
process.stdout.write(out + "\n");
|
|
66
|
+
}
|
|
67
|
+
function displayValLogo() {
|
|
68
|
+
const logo = chalk__default["default"].cyan(`
|
|
69
|
+
###########
|
|
70
|
+
###########
|
|
71
|
+
########### @@@@
|
|
72
|
+
########### @@
|
|
73
|
+
########### @@ @@ @@@@@@ @ @@
|
|
74
|
+
########### @@ @@ @@ @@ @@
|
|
75
|
+
########### @@ @@ %@ @ @@
|
|
76
|
+
#### ##### @@ @@ .@ .@ @@
|
|
77
|
+
### #### @@@@ @@: @@@. @@
|
|
78
|
+
#### ##### @@@@ @@@@ =@@@@@@@@@
|
|
79
|
+
###########
|
|
80
|
+
`);
|
|
81
|
+
process.stdout.write(logo);
|
|
82
|
+
}
|
|
83
|
+
function displaySuccessMessage(projectName) {
|
|
84
|
+
const nextSteps = chalk__default["default"].bold(`
|
|
85
|
+
${chalk__default["default"].cyan("Next steps:")}
|
|
86
|
+
${chalk__default["default"].cyan("cd")} ${chalk__default["default"].white(projectName)}
|
|
87
|
+
${chalk__default["default"].cyan("npm run dev")}
|
|
88
|
+
|
|
89
|
+
${chalk__default["default"].green("Happy coding! š")}
|
|
90
|
+
`);
|
|
91
|
+
process.stdout.write(nextSteps);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Template processing function
|
|
95
|
+
function processTemplateFiles(projectPath, projectName) {
|
|
96
|
+
const filesToProcess = ["package.json", "README.md", "next.config.js", "val.config.ts", "val.config.js"];
|
|
97
|
+
filesToProcess.forEach(filename => {
|
|
98
|
+
const filePath = path.join(projectPath, filename);
|
|
99
|
+
if (fs.existsSync(filePath)) {
|
|
100
|
+
try {
|
|
101
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
102
|
+
// Replace both {{PROJECT_NAME}} and {{projectName}} placeholders
|
|
103
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
104
|
+
content = content.replace(/\{\{projectName\}\}/g, projectName);
|
|
105
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Silently continue if file can't be processed
|
|
108
|
+
console.log(chalk__default["default"].dim(`Note: Could not process ${filename}`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function main() {
|
|
114
|
+
try {
|
|
115
|
+
console.log("here");
|
|
116
|
+
const args = process.argv.slice(2);
|
|
117
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
118
|
+
printHelp();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
122
|
+
printVersion();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Parse --root option
|
|
127
|
+
const rootIndex = args.findIndex(a => a === "--root");
|
|
128
|
+
let rootDir = process.cwd();
|
|
129
|
+
if (rootIndex !== -1 && args[rootIndex + 1]) {
|
|
130
|
+
rootDir = args[rootIndex + 1];
|
|
131
|
+
// Remove --root and its value from args for project name parsing
|
|
132
|
+
args.splice(rootIndex, 2);
|
|
133
|
+
}
|
|
134
|
+
let currentStep = 0;
|
|
135
|
+
renderTimeline(currentStep);
|
|
136
|
+
|
|
137
|
+
// Step 1: Enter project name
|
|
138
|
+
const projectName = await prompts.input({
|
|
139
|
+
message: chalk__default["default"].bold("What is your project named?"),
|
|
140
|
+
default: DEFAULT_PROJECT_NAME,
|
|
141
|
+
validate: value => {
|
|
142
|
+
if (!value || value.trim().length === 0) {
|
|
143
|
+
return "Project name cannot be empty";
|
|
144
|
+
}
|
|
145
|
+
if (value.includes(" ")) {
|
|
146
|
+
return "Project name cannot contain spaces";
|
|
147
|
+
}
|
|
148
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
149
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
currentStep++;
|
|
155
|
+
renderTimeline(currentStep);
|
|
156
|
+
|
|
157
|
+
// Step 2: Select template
|
|
158
|
+
const selectedTemplate = TEMPLATES[0];
|
|
159
|
+
currentStep++;
|
|
160
|
+
renderTimeline(currentStep);
|
|
161
|
+
|
|
162
|
+
// Step 3: Download template
|
|
163
|
+
const projectPath = path.join(rootDir, projectName);
|
|
164
|
+
if (fs.existsSync(projectPath)) {
|
|
165
|
+
renderTimeline(currentStep, currentStep);
|
|
166
|
+
console.error(chalk__default["default"].red(`ā Error: Directory "${projectName}" already exists.`));
|
|
167
|
+
console.error(chalk__default["default"].yellow("Please choose a different name or remove the existing directory."));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
fs.mkdirSync(projectPath, {
|
|
171
|
+
recursive: true
|
|
172
|
+
});
|
|
173
|
+
process.stdout.write(chalk__default["default"].bold("\nš„ Downloading template from GitHub...\n") + ` ${chalk__default["default"].dim(`https://github.com/${selectedTemplate.repo}`)}\n`);
|
|
174
|
+
try {
|
|
175
|
+
const emitter = degit__default["default"](selectedTemplate.repo, {
|
|
176
|
+
cache: false,
|
|
177
|
+
force: true,
|
|
178
|
+
verbose: false
|
|
179
|
+
});
|
|
180
|
+
await emitter.clone(projectPath);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
renderTimeline(currentStep, currentStep);
|
|
183
|
+
console.error(chalk__default["default"].red("ā Failed to download template:"));
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
185
|
+
if (errorMessage.includes("rate limit") || errorMessage.includes("403")) {
|
|
186
|
+
console.error(chalk__default["default"].yellow("GitHub rate limit exceeded. Please try again later or authenticate with GitHub."));
|
|
187
|
+
} else if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
188
|
+
console.error(chalk__default["default"].yellow(`Template repository not found: ${selectedTemplate.repo}`));
|
|
189
|
+
console.error(chalk__default["default"].yellow("Please check if the repository exists and is public."));
|
|
190
|
+
} else {
|
|
191
|
+
console.error(chalk__default["default"].yellow("Network error. Please check your internet connection."));
|
|
192
|
+
}
|
|
193
|
+
console.error(chalk__default["default"].dim("Error details:"), errorMessage);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
currentStep++;
|
|
197
|
+
renderTimeline(currentStep);
|
|
198
|
+
process.stdout.write(chalk__default["default"].green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
199
|
+
|
|
200
|
+
// Process template files
|
|
201
|
+
processTemplateFiles(projectPath, projectName);
|
|
202
|
+
|
|
203
|
+
// Change to project directory and install dependencies
|
|
204
|
+
process.stdout.write(chalk__default["default"].bold("\nš¦ Installing dependencies...\n"));
|
|
205
|
+
try {
|
|
206
|
+
child_process.execSync("npm install", {
|
|
207
|
+
cwd: projectPath,
|
|
208
|
+
stdio: "inherit" // Show npm output in real-time
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Clear the npm output and show success
|
|
212
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
213
|
+
displayValLogo();
|
|
214
|
+
currentStep++;
|
|
215
|
+
renderTimeline(currentStep);
|
|
216
|
+
process.stdout.write(chalk__default["default"].green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
217
|
+
process.stdout.write(chalk__default["default"].green("\nā
Dependencies installed successfully!\n"));
|
|
218
|
+
|
|
219
|
+
// Show final success message
|
|
220
|
+
displaySuccessMessage(projectName);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
renderTimeline(currentStep, currentStep);
|
|
223
|
+
console.error(chalk__default["default"].red('ā Failed to install dependencies. You can try running "npm install" manually.'));
|
|
224
|
+
console.error("Error:", error);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(chalk__default["default"].red("ā Failed to create project:"), error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
main();
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import degit from 'degit';
|
|
5
|
+
import { input } from '@inquirer/prompts';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const PKG = {
|
|
9
|
+
name: "@valbuild/create",
|
|
10
|
+
version: "0.1.0"
|
|
11
|
+
};
|
|
12
|
+
const TEMPLATES = [{
|
|
13
|
+
name: "starter",
|
|
14
|
+
description: "Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples",
|
|
15
|
+
repo: "valbuild/template-nextjs-starter",
|
|
16
|
+
default: true
|
|
17
|
+
}];
|
|
18
|
+
const DEFAULT_PROJECT_NAME = "my-val-app";
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.log(`
|
|
21
|
+
${chalk.bold("Usage:")}
|
|
22
|
+
${chalk.cyan("npm create @valbuild [project-name]")}
|
|
23
|
+
|
|
24
|
+
${chalk.bold("Options:")}
|
|
25
|
+
-h, --help Show help
|
|
26
|
+
-v, --version Show version
|
|
27
|
+
--root <path> Specify the root directory for project creation (default: current directory)
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
function printVersion() {
|
|
31
|
+
console.log(`${PKG.name} v${PKG.version}`);
|
|
32
|
+
}
|
|
33
|
+
function handleExit() {
|
|
34
|
+
console.log(chalk.yellow("\nAborted."));
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
process.on("SIGINT", handleExit);
|
|
38
|
+
|
|
39
|
+
// Timeline stepper logic
|
|
40
|
+
const timelineSteps = ["Enter project name", "Download template", "Install dependencies", "Complete!"];
|
|
41
|
+
function renderTimeline(currentStep, errorStep) {
|
|
42
|
+
const icons = {
|
|
43
|
+
pending: chalk.gray("āÆ"),
|
|
44
|
+
active: chalk.cyan("ā"),
|
|
45
|
+
done: chalk.green("ā"),
|
|
46
|
+
error: chalk.red("ā")
|
|
47
|
+
};
|
|
48
|
+
let out = "\n";
|
|
49
|
+
for (let i = 0; i < timelineSteps.length; i++) {
|
|
50
|
+
let status = "pending";
|
|
51
|
+
if (errorStep !== undefined && i === errorStep) status = "error";else if (i < currentStep) status = "done";else if (i === currentStep) status = "active";
|
|
52
|
+
const icon = icons[status];
|
|
53
|
+
out += ` ${icon} ${timelineSteps[i]}\n`;
|
|
54
|
+
if (i < timelineSteps.length - 1) out += ` ${chalk.gray("ā")}\n`;
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
57
|
+
displayValLogo();
|
|
58
|
+
process.stdout.write(out + "\n");
|
|
59
|
+
}
|
|
60
|
+
function displayValLogo() {
|
|
61
|
+
const logo = chalk.cyan(`
|
|
62
|
+
###########
|
|
63
|
+
###########
|
|
64
|
+
########### @@@@
|
|
65
|
+
########### @@
|
|
66
|
+
########### @@ @@ @@@@@@ @ @@
|
|
67
|
+
########### @@ @@ @@ @@ @@
|
|
68
|
+
########### @@ @@ %@ @ @@
|
|
69
|
+
#### ##### @@ @@ .@ .@ @@
|
|
70
|
+
### #### @@@@ @@: @@@. @@
|
|
71
|
+
#### ##### @@@@ @@@@ =@@@@@@@@@
|
|
72
|
+
###########
|
|
73
|
+
`);
|
|
74
|
+
process.stdout.write(logo);
|
|
75
|
+
}
|
|
76
|
+
function displaySuccessMessage(projectName) {
|
|
77
|
+
const nextSteps = chalk.bold(`
|
|
78
|
+
${chalk.cyan("Next steps:")}
|
|
79
|
+
${chalk.cyan("cd")} ${chalk.white(projectName)}
|
|
80
|
+
${chalk.cyan("npm run dev")}
|
|
81
|
+
|
|
82
|
+
${chalk.green("Happy coding! š")}
|
|
83
|
+
`);
|
|
84
|
+
process.stdout.write(nextSteps);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Template processing function
|
|
88
|
+
function processTemplateFiles(projectPath, projectName) {
|
|
89
|
+
const filesToProcess = ["package.json", "README.md", "next.config.js", "val.config.ts", "val.config.js"];
|
|
90
|
+
filesToProcess.forEach(filename => {
|
|
91
|
+
const filePath = join(projectPath, filename);
|
|
92
|
+
if (existsSync(filePath)) {
|
|
93
|
+
try {
|
|
94
|
+
let content = readFileSync(filePath, "utf-8");
|
|
95
|
+
// Replace both {{PROJECT_NAME}} and {{projectName}} placeholders
|
|
96
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
97
|
+
content = content.replace(/\{\{projectName\}\}/g, projectName);
|
|
98
|
+
writeFileSync(filePath, content, "utf-8");
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Silently continue if file can't be processed
|
|
101
|
+
console.log(chalk.dim(`Note: Could not process ${filename}`));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async function main() {
|
|
107
|
+
try {
|
|
108
|
+
console.log("here");
|
|
109
|
+
const args = process.argv.slice(2);
|
|
110
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
111
|
+
printHelp();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
115
|
+
printVersion();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse --root option
|
|
120
|
+
const rootIndex = args.findIndex(a => a === "--root");
|
|
121
|
+
let rootDir = process.cwd();
|
|
122
|
+
if (rootIndex !== -1 && args[rootIndex + 1]) {
|
|
123
|
+
rootDir = args[rootIndex + 1];
|
|
124
|
+
// Remove --root and its value from args for project name parsing
|
|
125
|
+
args.splice(rootIndex, 2);
|
|
126
|
+
}
|
|
127
|
+
let currentStep = 0;
|
|
128
|
+
renderTimeline(currentStep);
|
|
129
|
+
|
|
130
|
+
// Step 1: Enter project name
|
|
131
|
+
const projectName = await input({
|
|
132
|
+
message: chalk.bold("What is your project named?"),
|
|
133
|
+
default: DEFAULT_PROJECT_NAME,
|
|
134
|
+
validate: value => {
|
|
135
|
+
if (!value || value.trim().length === 0) {
|
|
136
|
+
return "Project name cannot be empty";
|
|
137
|
+
}
|
|
138
|
+
if (value.includes(" ")) {
|
|
139
|
+
return "Project name cannot contain spaces";
|
|
140
|
+
}
|
|
141
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
142
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
currentStep++;
|
|
148
|
+
renderTimeline(currentStep);
|
|
149
|
+
|
|
150
|
+
// Step 2: Select template
|
|
151
|
+
const selectedTemplate = TEMPLATES[0];
|
|
152
|
+
currentStep++;
|
|
153
|
+
renderTimeline(currentStep);
|
|
154
|
+
|
|
155
|
+
// Step 3: Download template
|
|
156
|
+
const projectPath = join(rootDir, projectName);
|
|
157
|
+
if (existsSync(projectPath)) {
|
|
158
|
+
renderTimeline(currentStep, currentStep);
|
|
159
|
+
console.error(chalk.red(`ā Error: Directory "${projectName}" already exists.`));
|
|
160
|
+
console.error(chalk.yellow("Please choose a different name or remove the existing directory."));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
mkdirSync(projectPath, {
|
|
164
|
+
recursive: true
|
|
165
|
+
});
|
|
166
|
+
process.stdout.write(chalk.bold("\nš„ Downloading template from GitHub...\n") + ` ${chalk.dim(`https://github.com/${selectedTemplate.repo}`)}\n`);
|
|
167
|
+
try {
|
|
168
|
+
const emitter = degit(selectedTemplate.repo, {
|
|
169
|
+
cache: false,
|
|
170
|
+
force: true,
|
|
171
|
+
verbose: false
|
|
172
|
+
});
|
|
173
|
+
await emitter.clone(projectPath);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
renderTimeline(currentStep, currentStep);
|
|
176
|
+
console.error(chalk.red("ā Failed to download template:"));
|
|
177
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
178
|
+
if (errorMessage.includes("rate limit") || errorMessage.includes("403")) {
|
|
179
|
+
console.error(chalk.yellow("GitHub rate limit exceeded. Please try again later or authenticate with GitHub."));
|
|
180
|
+
} else if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
181
|
+
console.error(chalk.yellow(`Template repository not found: ${selectedTemplate.repo}`));
|
|
182
|
+
console.error(chalk.yellow("Please check if the repository exists and is public."));
|
|
183
|
+
} else {
|
|
184
|
+
console.error(chalk.yellow("Network error. Please check your internet connection."));
|
|
185
|
+
}
|
|
186
|
+
console.error(chalk.dim("Error details:"), errorMessage);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
currentStep++;
|
|
190
|
+
renderTimeline(currentStep);
|
|
191
|
+
process.stdout.write(chalk.green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
192
|
+
|
|
193
|
+
// Process template files
|
|
194
|
+
processTemplateFiles(projectPath, projectName);
|
|
195
|
+
|
|
196
|
+
// Change to project directory and install dependencies
|
|
197
|
+
process.stdout.write(chalk.bold("\nš¦ Installing dependencies...\n"));
|
|
198
|
+
try {
|
|
199
|
+
execSync("npm install", {
|
|
200
|
+
cwd: projectPath,
|
|
201
|
+
stdio: "inherit" // Show npm output in real-time
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Clear the npm output and show success
|
|
205
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
206
|
+
displayValLogo();
|
|
207
|
+
currentStep++;
|
|
208
|
+
renderTimeline(currentStep);
|
|
209
|
+
process.stdout.write(chalk.green(`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`));
|
|
210
|
+
process.stdout.write(chalk.green("\nā
Dependencies installed successfully!\n"));
|
|
211
|
+
|
|
212
|
+
// Show final success message
|
|
213
|
+
displaySuccessMessage(projectName);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
renderTimeline(currentStep, currentStep);
|
|
216
|
+
console.error(chalk.red('ā Failed to install dependencies. You can try running "npm install" manually.'));
|
|
217
|
+
console.error("Error:", error);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error(chalk.red("ā Failed to create project:"), error);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valbuild/create",
|
|
3
|
+
"version": "0.81.0",
|
|
4
|
+
"description": "Create a new Val project with Next.js",
|
|
5
|
+
"main": "dist/valbuild-create.cjs.js",
|
|
6
|
+
"module": "dist/valbuild-create.esm.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"@valbuild/create": "./bin.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"module": "./dist/valbuild-create.esm.js",
|
|
13
|
+
"default": "./dist/valbuild-create.cjs.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"start": "tsx src/index.ts --",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"degit": "^2.8.4",
|
|
24
|
+
"@inquirer/prompts": "^3.0.2",
|
|
25
|
+
"chalk": "^4.1.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"tsx": "^4.0.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"val",
|
|
34
|
+
"valbuild",
|
|
35
|
+
"create",
|
|
36
|
+
"cli",
|
|
37
|
+
"nextjs"
|
|
38
|
+
],
|
|
39
|
+
"author": "Valbuild",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"preconstruct": {
|
|
42
|
+
"entrypoints": [
|
|
43
|
+
"index.ts"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.17.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/degit.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module "degit" {
|
|
2
|
+
interface DegitOptions {
|
|
3
|
+
cache?: boolean;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
verbose?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface DegitEmitter {
|
|
8
|
+
clone(dest: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
function degit(repo: string, opts?: DegitOptions): DegitEmitter;
|
|
11
|
+
export = degit;
|
|
12
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import degit from "degit";
|
|
5
|
+
import { input } from "@inquirer/prompts";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
|
|
8
|
+
const PKG = {
|
|
9
|
+
name: "@valbuild/create",
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface Template {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
default?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TEMPLATES: Template[] = [
|
|
21
|
+
{
|
|
22
|
+
name: "starter",
|
|
23
|
+
description:
|
|
24
|
+
"Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples",
|
|
25
|
+
repo: "valbuild/template-nextjs-starter",
|
|
26
|
+
default: true,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const DEFAULT_PROJECT_NAME = "my-val-app";
|
|
31
|
+
|
|
32
|
+
function printHelp() {
|
|
33
|
+
console.log(`
|
|
34
|
+
${chalk.bold("Usage:")}
|
|
35
|
+
${chalk.cyan("npm create @valbuild [project-name]")}
|
|
36
|
+
|
|
37
|
+
${chalk.bold("Options:")}
|
|
38
|
+
-h, --help Show help
|
|
39
|
+
-v, --version Show version
|
|
40
|
+
--root <path> Specify the root directory for project creation (default: current directory)
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printVersion() {
|
|
45
|
+
console.log(`${PKG.name} v${PKG.version}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleExit() {
|
|
49
|
+
console.log(chalk.yellow("\nAborted."));
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process.on("SIGINT", handleExit);
|
|
54
|
+
|
|
55
|
+
// Timeline stepper logic
|
|
56
|
+
const timelineSteps = [
|
|
57
|
+
"Enter project name",
|
|
58
|
+
"Download template",
|
|
59
|
+
"Install dependencies",
|
|
60
|
+
"Complete!",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
type StepStatus = "pending" | "active" | "done" | "error";
|
|
64
|
+
|
|
65
|
+
function renderTimeline(currentStep: number, errorStep?: number) {
|
|
66
|
+
const icons = {
|
|
67
|
+
pending: chalk.gray("āÆ"),
|
|
68
|
+
active: chalk.cyan("ā"),
|
|
69
|
+
done: chalk.green("ā"),
|
|
70
|
+
error: chalk.red("ā"),
|
|
71
|
+
};
|
|
72
|
+
let out = "\n";
|
|
73
|
+
for (let i = 0; i < timelineSteps.length; i++) {
|
|
74
|
+
let status: StepStatus = "pending";
|
|
75
|
+
if (errorStep !== undefined && i === errorStep) status = "error";
|
|
76
|
+
else if (i < currentStep) status = "done";
|
|
77
|
+
else if (i === currentStep) status = "active";
|
|
78
|
+
const icon = icons[status];
|
|
79
|
+
out += ` ${icon} ${timelineSteps[i]}\n`;
|
|
80
|
+
if (i < timelineSteps.length - 1) out += ` ${chalk.gray("ā")}\n`;
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
83
|
+
displayValLogo();
|
|
84
|
+
process.stdout.write(out + "\n");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function displayValLogo() {
|
|
88
|
+
const logo = chalk.cyan(`
|
|
89
|
+
###########
|
|
90
|
+
###########
|
|
91
|
+
########### @@@@
|
|
92
|
+
########### @@
|
|
93
|
+
########### @@ @@ @@@@@@ @ @@
|
|
94
|
+
########### @@ @@ @@ @@ @@
|
|
95
|
+
########### @@ @@ %@ @ @@
|
|
96
|
+
#### ##### @@ @@ .@ .@ @@
|
|
97
|
+
### #### @@@@ @@: @@@. @@
|
|
98
|
+
#### ##### @@@@ @@@@ =@@@@@@@@@
|
|
99
|
+
###########
|
|
100
|
+
`);
|
|
101
|
+
process.stdout.write(logo);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function displaySuccessMessage(projectName: string) {
|
|
105
|
+
const nextSteps = chalk.bold(`
|
|
106
|
+
${chalk.cyan("Next steps:")}
|
|
107
|
+
${chalk.cyan("cd")} ${chalk.white(projectName)}
|
|
108
|
+
${chalk.cyan("npm run dev")}
|
|
109
|
+
|
|
110
|
+
${chalk.green("Happy coding! š")}
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
process.stdout.write(nextSteps);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Template processing function
|
|
117
|
+
function processTemplateFiles(projectPath: string, projectName: string) {
|
|
118
|
+
const filesToProcess = [
|
|
119
|
+
"package.json",
|
|
120
|
+
"README.md",
|
|
121
|
+
"next.config.js",
|
|
122
|
+
"val.config.ts",
|
|
123
|
+
"val.config.js",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
filesToProcess.forEach((filename) => {
|
|
127
|
+
const filePath = join(projectPath, filename);
|
|
128
|
+
if (existsSync(filePath)) {
|
|
129
|
+
try {
|
|
130
|
+
let content = readFileSync(filePath, "utf-8");
|
|
131
|
+
// Replace both {{PROJECT_NAME}} and {{projectName}} placeholders
|
|
132
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
133
|
+
content = content.replace(/\{\{projectName\}\}/g, projectName);
|
|
134
|
+
writeFileSync(filePath, content, "utf-8");
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Silently continue if file can't be processed
|
|
137
|
+
console.log(chalk.dim(`Note: Could not process ${filename}`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function main() {
|
|
144
|
+
try {
|
|
145
|
+
console.log("here");
|
|
146
|
+
const args = process.argv.slice(2);
|
|
147
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
148
|
+
printHelp();
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
152
|
+
printVersion();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Parse --root option
|
|
157
|
+
const rootIndex = args.findIndex((a) => a === "--root");
|
|
158
|
+
let rootDir = process.cwd();
|
|
159
|
+
if (rootIndex !== -1 && args[rootIndex + 1]) {
|
|
160
|
+
rootDir = args[rootIndex + 1];
|
|
161
|
+
// Remove --root and its value from args for project name parsing
|
|
162
|
+
args.splice(rootIndex, 2);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let currentStep = 0;
|
|
166
|
+
renderTimeline(currentStep);
|
|
167
|
+
|
|
168
|
+
// Step 1: Enter project name
|
|
169
|
+
const projectName = await input({
|
|
170
|
+
message: chalk.bold("What is your project named?"),
|
|
171
|
+
default: DEFAULT_PROJECT_NAME,
|
|
172
|
+
validate: (value) => {
|
|
173
|
+
if (!value || value.trim().length === 0) {
|
|
174
|
+
return "Project name cannot be empty";
|
|
175
|
+
}
|
|
176
|
+
if (value.includes(" ")) {
|
|
177
|
+
return "Project name cannot contain spaces";
|
|
178
|
+
}
|
|
179
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
180
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
currentStep++;
|
|
186
|
+
renderTimeline(currentStep);
|
|
187
|
+
|
|
188
|
+
// Step 2: Select template
|
|
189
|
+
const selectedTemplate = TEMPLATES[0];
|
|
190
|
+
currentStep++;
|
|
191
|
+
renderTimeline(currentStep);
|
|
192
|
+
|
|
193
|
+
// Step 3: Download template
|
|
194
|
+
const projectPath = join(rootDir, projectName);
|
|
195
|
+
if (existsSync(projectPath)) {
|
|
196
|
+
renderTimeline(currentStep, currentStep);
|
|
197
|
+
console.error(
|
|
198
|
+
chalk.red(`ā Error: Directory "${projectName}" already exists.`),
|
|
199
|
+
);
|
|
200
|
+
console.error(
|
|
201
|
+
chalk.yellow(
|
|
202
|
+
"Please choose a different name or remove the existing directory.",
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
mkdirSync(projectPath, { recursive: true });
|
|
208
|
+
process.stdout.write(
|
|
209
|
+
chalk.bold("\nš„ Downloading template from GitHub...\n") +
|
|
210
|
+
` ${chalk.dim(`https://github.com/${selectedTemplate.repo}`)}\n`,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const emitter = degit(selectedTemplate.repo, {
|
|
215
|
+
cache: false,
|
|
216
|
+
force: true,
|
|
217
|
+
verbose: false,
|
|
218
|
+
});
|
|
219
|
+
await emitter.clone(projectPath);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
renderTimeline(currentStep, currentStep);
|
|
222
|
+
console.error(chalk.red("ā Failed to download template:"));
|
|
223
|
+
const errorMessage =
|
|
224
|
+
error instanceof Error ? error.message : String(error);
|
|
225
|
+
if (errorMessage.includes("rate limit") || errorMessage.includes("403")) {
|
|
226
|
+
console.error(
|
|
227
|
+
chalk.yellow(
|
|
228
|
+
"GitHub rate limit exceeded. Please try again later or authenticate with GitHub.",
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
} else if (
|
|
232
|
+
errorMessage.includes("not found") ||
|
|
233
|
+
errorMessage.includes("404")
|
|
234
|
+
) {
|
|
235
|
+
console.error(
|
|
236
|
+
chalk.yellow(
|
|
237
|
+
`Template repository not found: ${selectedTemplate.repo}`,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
console.error(
|
|
241
|
+
chalk.yellow("Please check if the repository exists and is public."),
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
console.error(
|
|
245
|
+
chalk.yellow("Network error. Please check your internet connection."),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
console.error(chalk.dim("Error details:"), errorMessage);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
currentStep++;
|
|
253
|
+
renderTimeline(currentStep);
|
|
254
|
+
process.stdout.write(
|
|
255
|
+
chalk.green(
|
|
256
|
+
`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`,
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Process template files
|
|
261
|
+
processTemplateFiles(projectPath, projectName);
|
|
262
|
+
|
|
263
|
+
// Change to project directory and install dependencies
|
|
264
|
+
process.stdout.write(chalk.bold("\nš¦ Installing dependencies...\n"));
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
execSync("npm install", {
|
|
268
|
+
cwd: projectPath,
|
|
269
|
+
stdio: "inherit", // Show npm output in real-time
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Clear the npm output and show success
|
|
273
|
+
process.stdout.write("\x1b[2J\x1b[0f"); // clear screen
|
|
274
|
+
displayValLogo();
|
|
275
|
+
currentStep++;
|
|
276
|
+
renderTimeline(currentStep);
|
|
277
|
+
process.stdout.write(
|
|
278
|
+
chalk.green(
|
|
279
|
+
`ā
Successfully downloaded template from ${selectedTemplate.repo}!\n`,
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
process.stdout.write(
|
|
283
|
+
chalk.green("\nā
Dependencies installed successfully!\n"),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Show final success message
|
|
287
|
+
displaySuccessMessage(projectName);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
renderTimeline(currentStep, currentStep);
|
|
290
|
+
console.error(
|
|
291
|
+
chalk.red(
|
|
292
|
+
'ā Failed to install dependencies. You can try running "npm install" manually.',
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
console.error("Error:", error);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error(chalk.red("ā Failed to create project:"), error);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
main();
|
package/tsconfig.json
ADDED