create-newt-app 0.0.2
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/dist/index.js +312 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { intro } from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
// package.json
|
|
8
|
+
var package_default = {
|
|
9
|
+
name: "create-newt-app",
|
|
10
|
+
version: "0.0.2",
|
|
11
|
+
private: false,
|
|
12
|
+
type: "module",
|
|
13
|
+
bin: {
|
|
14
|
+
"create-newt-app": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
files: [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
scripts: {
|
|
20
|
+
build: "tsup",
|
|
21
|
+
dev: "tsup --watch"
|
|
22
|
+
},
|
|
23
|
+
dependencies: {
|
|
24
|
+
"@clack/prompts": "^0.11.0",
|
|
25
|
+
"@newt-app/templates": "workspace:*",
|
|
26
|
+
chalk: "^5.3.0",
|
|
27
|
+
commander: "^12.1.0",
|
|
28
|
+
ejs: "^3.1.10",
|
|
29
|
+
execa: "^9.6.0",
|
|
30
|
+
zod: "^3.25.76"
|
|
31
|
+
},
|
|
32
|
+
devDependencies: {
|
|
33
|
+
"@types/ejs": "^3.1.5",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
tsup: "^8.5.0",
|
|
36
|
+
typescript: "^5.8.2"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
import { Command } from "commander";
|
|
42
|
+
import * as p from "@clack/prompts";
|
|
43
|
+
import { templates } from "@newt-app/templates";
|
|
44
|
+
|
|
45
|
+
// src/tasks.ts
|
|
46
|
+
import { execa } from "execa";
|
|
47
|
+
|
|
48
|
+
// src/utils.ts
|
|
49
|
+
import ejs from "ejs";
|
|
50
|
+
import { existsSync, promises } from "fs";
|
|
51
|
+
import path from "path";
|
|
52
|
+
import { getStaticFilePath } from "@newt-app/templates";
|
|
53
|
+
async function updatePackageJson(destDir, packages, templateData) {
|
|
54
|
+
for (const pkg of packages) {
|
|
55
|
+
const renderedPackage = ejs.render(pkg.package, templateData);
|
|
56
|
+
const packageJsonPath = path.join(destDir, pkg.module, "package.json");
|
|
57
|
+
let packageJsonContent;
|
|
58
|
+
try {
|
|
59
|
+
packageJsonContent = await promises.readFile(packageJsonPath, "utf8");
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (typeof err === "object" && err && err.code === "ENOENT") {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
67
|
+
const dependencyKey = pkg.dev ? "devDependencies" : "dependencies";
|
|
68
|
+
if (!packageJson[dependencyKey]) {
|
|
69
|
+
packageJson[dependencyKey] = {};
|
|
70
|
+
}
|
|
71
|
+
packageJson[dependencyKey][renderedPackage] = pkg.version;
|
|
72
|
+
await promises.writeFile(
|
|
73
|
+
packageJsonPath,
|
|
74
|
+
JSON.stringify(packageJson, null, 2)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function updateScripts(destDir, scripts, templateData) {
|
|
79
|
+
for (const script of scripts) {
|
|
80
|
+
const renderedScript = ejs.render(script.script, templateData);
|
|
81
|
+
const packageJsonPath = path.join(destDir, script.module, "package.json");
|
|
82
|
+
let packageJsonContent;
|
|
83
|
+
try {
|
|
84
|
+
packageJsonContent = await promises.readFile(packageJsonPath, "utf8");
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (typeof err === "object" && err && err.code === "ENOENT") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
92
|
+
if (!packageJson.scripts) {
|
|
93
|
+
packageJson.scripts = {};
|
|
94
|
+
}
|
|
95
|
+
packageJson.scripts[script.name] = renderedScript;
|
|
96
|
+
await promises.writeFile(
|
|
97
|
+
packageJsonPath,
|
|
98
|
+
JSON.stringify(packageJson, null, 2)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function renderTemplatesToDisk(basePackages, destDir, templateData) {
|
|
103
|
+
await promises.mkdir(destDir, { recursive: true });
|
|
104
|
+
await basePackages.reduce(async (prev, pkg) => {
|
|
105
|
+
await prev;
|
|
106
|
+
for (const template of pkg.templates) {
|
|
107
|
+
try {
|
|
108
|
+
const destPath = path.join(destDir, template.filename);
|
|
109
|
+
const destDirPath = path.dirname(destPath);
|
|
110
|
+
await promises.mkdir(destDirPath, { recursive: true });
|
|
111
|
+
const output = await ejs.render(template.template, templateData);
|
|
112
|
+
await promises.writeFile(destPath, output, "utf8");
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(`Failed to create ${template.filename}: ${error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (pkg.staticFiles) {
|
|
118
|
+
pkg.staticFiles.reduce(async (prev2, staticFile) => {
|
|
119
|
+
try {
|
|
120
|
+
await prev2;
|
|
121
|
+
const destPath = path.join(destDir, staticFile.filename);
|
|
122
|
+
const destDirPath = path.dirname(destPath);
|
|
123
|
+
await promises.mkdir(destDirPath, { recursive: true });
|
|
124
|
+
await promises.copyFile(getStaticFilePath(staticFile.src), destPath);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.log(e);
|
|
127
|
+
}
|
|
128
|
+
}, Promise.resolve());
|
|
129
|
+
}
|
|
130
|
+
}, Promise.resolve());
|
|
131
|
+
}
|
|
132
|
+
function validateProjectName(projectName) {
|
|
133
|
+
if (!projectName) {
|
|
134
|
+
return { valid: false, error: "Project name is required" };
|
|
135
|
+
}
|
|
136
|
+
if (projectName.length < 1) {
|
|
137
|
+
return {
|
|
138
|
+
valid: false,
|
|
139
|
+
error: "Project name must be at least 1 character long"
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (projectName.length > 214) {
|
|
143
|
+
return {
|
|
144
|
+
valid: false,
|
|
145
|
+
error: "Project name must be less than 214 characters"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const invalidChars = /[<>:"/\\|?*]/;
|
|
149
|
+
if (invalidChars.test(projectName)) {
|
|
150
|
+
return { valid: false, error: "Project name contains invalid characters" };
|
|
151
|
+
}
|
|
152
|
+
const targetPath = path.resolve(process.cwd(), projectName);
|
|
153
|
+
if (existsSync(targetPath)) {
|
|
154
|
+
return { valid: false, error: `Directory "${projectName}" already exists` };
|
|
155
|
+
}
|
|
156
|
+
return { valid: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/tasks.ts
|
|
160
|
+
async function scaffold(modules, options) {
|
|
161
|
+
const validation = validateProjectName(options.name);
|
|
162
|
+
if (!validation.valid) {
|
|
163
|
+
console.error(`Error: ${validation.error}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const templateData = {
|
|
167
|
+
projectName: options.name
|
|
168
|
+
};
|
|
169
|
+
await renderTemplatesToDisk(modules, options.name, templateData);
|
|
170
|
+
const packages = modules.map((mod) => mod.packages).filter((ele) => ele !== void 0).flat();
|
|
171
|
+
if (packages.length > 0) {
|
|
172
|
+
await updatePackageJson(options.name, packages, templateData);
|
|
173
|
+
}
|
|
174
|
+
const scripts = modules.map((mod) => mod.scripts).filter((ele) => ele !== void 0).flat();
|
|
175
|
+
if (scripts.length > 0) {
|
|
176
|
+
await updateScripts(options.name, scripts, templateData);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function pnpmInstall(cwd) {
|
|
180
|
+
return await execa("pnpm", ["install"], {
|
|
181
|
+
cwd
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async function pnpmFormat(cwd) {
|
|
185
|
+
return await execa("pnpm", ["format"], {
|
|
186
|
+
cwd
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function initGit(cwd) {
|
|
190
|
+
await execa("git", ["init"], {
|
|
191
|
+
cwd
|
|
192
|
+
});
|
|
193
|
+
await execa("git", ["add", "."], {
|
|
194
|
+
cwd
|
|
195
|
+
});
|
|
196
|
+
await execa("git", ["commit", "-m", "Initial commit"], {
|
|
197
|
+
cwd
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/index.ts
|
|
202
|
+
var TaskBuilder = class {
|
|
203
|
+
tasks = [];
|
|
204
|
+
add(task) {
|
|
205
|
+
this.tasks.push(task);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
async function doInit(options) {
|
|
209
|
+
const groupOpts = {
|
|
210
|
+
...!options.name && {
|
|
211
|
+
name: () => p.text({
|
|
212
|
+
message: "What is your project name?",
|
|
213
|
+
placeholder: "my-newt-app",
|
|
214
|
+
validate: (value) => {
|
|
215
|
+
if (!value) return "Project name is required";
|
|
216
|
+
if (value.length > 214) return "Project name is too long";
|
|
217
|
+
if (/[<>:"/\\|?*]/.test(value)) return "Project name contains invalid characters";
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
const group2 = await p.group(groupOpts, {
|
|
224
|
+
onCancel: () => {
|
|
225
|
+
console.log("Exiting.");
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
const allModules = [
|
|
230
|
+
templates.root,
|
|
231
|
+
templates.web,
|
|
232
|
+
templates.api,
|
|
233
|
+
templates.auth,
|
|
234
|
+
templates.ui,
|
|
235
|
+
templates.eslintConfig,
|
|
236
|
+
templates.typescriptConfig
|
|
237
|
+
];
|
|
238
|
+
const name = group2.name ?? options.name ?? "";
|
|
239
|
+
const taskBuilder = new TaskBuilder();
|
|
240
|
+
taskBuilder.add({
|
|
241
|
+
title: "Scaffolding project",
|
|
242
|
+
task: async () => {
|
|
243
|
+
try {
|
|
244
|
+
await scaffold(allModules, { name });
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.log(e);
|
|
247
|
+
}
|
|
248
|
+
return "Scaffolded.";
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
if (options.install) {
|
|
252
|
+
taskBuilder.add({
|
|
253
|
+
title: "Installing with pnpm",
|
|
254
|
+
task: async () => {
|
|
255
|
+
try {
|
|
256
|
+
await pnpmInstall(name);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.log(e);
|
|
259
|
+
}
|
|
260
|
+
return "Installed.";
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
taskBuilder.add({
|
|
264
|
+
title: "Formatting",
|
|
265
|
+
task: async () => {
|
|
266
|
+
await pnpmFormat(name);
|
|
267
|
+
return "Formatted.";
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (options.git) {
|
|
272
|
+
taskBuilder.add({
|
|
273
|
+
title: "Initializing git",
|
|
274
|
+
task: async () => {
|
|
275
|
+
await initGit(name);
|
|
276
|
+
return "Initialized git.";
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
await p.tasks(taskBuilder.tasks);
|
|
281
|
+
p.outro(`Done!`);
|
|
282
|
+
console.log("Next steps:");
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(chalk.blue(` cd ${name}`));
|
|
285
|
+
console.log(chalk.blue(` cp .env.example .env`));
|
|
286
|
+
console.log(chalk.blue(` # fill in DATABASE_URL`));
|
|
287
|
+
console.log(chalk.blue(` pnpm db:migrate`));
|
|
288
|
+
console.log(chalk.blue(` pnpm dev`));
|
|
289
|
+
console.log();
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(
|
|
292
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
293
|
+
);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
var program = new Command();
|
|
298
|
+
program.name("create-newt-app").version(package_default.version).description("Create a new newt-app monorepo").argument("[name]").option("-ni, --no-install", "Skip pnpm install", true).option("-ng, --no-git", "Skip git initialization", true).action(
|
|
299
|
+
async (name, options) => {
|
|
300
|
+
console.log("\n");
|
|
301
|
+
intro(`Create a ${chalk.blue("newt")} app.`);
|
|
302
|
+
await doInit({
|
|
303
|
+
name,
|
|
304
|
+
install: options.install,
|
|
305
|
+
git: options.git
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
program.parse();
|
|
310
|
+
export {
|
|
311
|
+
doInit
|
|
312
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-newt-app",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-newt-app": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@clack/prompts": "^0.11.0",
|
|
14
|
+
"chalk": "^5.3.0",
|
|
15
|
+
"commander": "^12.1.0",
|
|
16
|
+
"ejs": "^3.1.10",
|
|
17
|
+
"execa": "^9.6.0",
|
|
18
|
+
"zod": "^3.25.76",
|
|
19
|
+
"@newt-app/templates": "0.0.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/ejs": "^3.1.5",
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"tsup": "^8.5.0",
|
|
25
|
+
"typescript": "^5.8.2"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch"
|
|
30
|
+
}
|
|
31
|
+
}
|