@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 ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "targets": {
3
+ "node": 16
4
+ }
5
+ }
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,3 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ require("./create");
@@ -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,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./valbuild-create.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./valbuild-create.cjs.dev.js");
7
+ }
@@ -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
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["es2020", "DOM"],
4
+ "strict": true,
5
+ "isolatedModules": true,
6
+ "esModuleInterop": true,
7
+ "module": "esnext",
8
+ "moduleResolution": "node",
9
+ "skipLibCheck": true,
10
+ "target": "ESNext"
11
+ }
12
+ }