create-nexu 1.0.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/README.md +149 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +603 -0
- package/package.json +41 -0
- package/templates/default/.changeset/config.json +11 -0
- package/templates/default/.eslintignore +13 -0
- package/templates/default/.eslintrc.js +66 -0
- package/templates/default/.github/actions/quality/action.yml +53 -0
- package/templates/default/.github/dependabot.yml +51 -0
- package/templates/default/.github/workflows/deploy-dev.yml +83 -0
- package/templates/default/.github/workflows/deploy-prod.yml +83 -0
- package/templates/default/.github/workflows/deploy-rec.yml +83 -0
- package/templates/default/.husky/commit-msg +1 -0
- package/templates/default/.husky/pre-commit +1 -0
- package/templates/default/.prettierignore +7 -0
- package/templates/default/.prettierrc +19 -0
- package/templates/default/.vscode/extensions.json +14 -0
- package/templates/default/.vscode/settings.json +36 -0
- package/templates/default/apps/.gitkeep +0 -0
- package/templates/default/commitlint.config.js +26 -0
- package/templates/default/docker/docker-compose.dev.yml +49 -0
- package/templates/default/docker/docker-compose.prod.yml +64 -0
- package/templates/default/docker/docker-compose.yml +5 -0
- package/templates/default/package.json +56 -0
- package/templates/default/packages/cache/package.json +26 -0
- package/templates/default/packages/cache/src/index.ts +137 -0
- package/templates/default/packages/cache/tsconfig.json +9 -0
- package/templates/default/packages/cache/tsup.config.ts +9 -0
- package/templates/default/packages/config/eslint/index.js +20 -0
- package/templates/default/packages/config/package.json +9 -0
- package/templates/default/packages/config/typescript/base.json +26 -0
- package/templates/default/packages/constants/package.json +26 -0
- package/templates/default/packages/constants/src/index.ts +121 -0
- package/templates/default/packages/constants/tsconfig.json +9 -0
- package/templates/default/packages/constants/tsup.config.ts +9 -0
- package/templates/default/packages/logger/package.json +27 -0
- package/templates/default/packages/logger/src/index.ts +197 -0
- package/templates/default/packages/logger/tsconfig.json +11 -0
- package/templates/default/packages/logger/tsup.config.ts +9 -0
- package/templates/default/packages/result/package.json +26 -0
- package/templates/default/packages/result/src/index.ts +142 -0
- package/templates/default/packages/result/tsconfig.json +9 -0
- package/templates/default/packages/result/tsup.config.ts +9 -0
- package/templates/default/packages/types/package.json +26 -0
- package/templates/default/packages/types/src/index.ts +78 -0
- package/templates/default/packages/types/tsconfig.json +9 -0
- package/templates/default/packages/types/tsup.config.ts +10 -0
- package/templates/default/packages/ui/package.json +38 -0
- package/templates/default/packages/ui/src/components/Button.tsx +65 -0
- package/templates/default/packages/ui/src/components/Card.tsx +90 -0
- package/templates/default/packages/ui/src/components/Input.tsx +51 -0
- package/templates/default/packages/ui/src/index.ts +15 -0
- package/templates/default/packages/ui/tsconfig.json +11 -0
- package/templates/default/packages/ui/tsup.config.ts +11 -0
- package/templates/default/packages/utils/package.json +30 -0
- package/templates/default/packages/utils/src/index.test.ts +130 -0
- package/templates/default/packages/utils/src/index.ts +154 -0
- package/templates/default/packages/utils/tsconfig.json +10 -0
- package/templates/default/packages/utils/tsup.config.ts +10 -0
- package/templates/default/pnpm-workspace.yaml +3 -0
- package/templates/default/scripts/deploy.sh +25 -0
- package/templates/default/scripts/generate-app.sh +166 -0
- package/templates/default/scripts/publish-cli.sh +54 -0
- package/templates/default/scripts/setup.sh +70 -0
- package/templates/default/services/.env.example +16 -0
- package/templates/default/services/docker-compose.yml +207 -0
- package/templates/default/services/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/templates/default/services/grafana/provisioning/datasources/datasources.yml +9 -0
- package/templates/default/services/postgres/init/.gitkeep +2 -0
- package/templates/default/services/prometheus/prometheus.yml +13 -0
- package/templates/default/tsconfig.json +27 -0
- package/templates/default/turbo.json +40 -0
- package/templates/default/vitest.config.ts +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/add.ts
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import chalk2 from "chalk";
|
|
10
|
+
import fs2 from "fs-extra";
|
|
11
|
+
import inquirer from "inquirer";
|
|
12
|
+
import ora from "ora";
|
|
13
|
+
|
|
14
|
+
// src/utils/constants.ts
|
|
15
|
+
var TEMPLATE_DIRS = {
|
|
16
|
+
packages: "packages",
|
|
17
|
+
services: "services",
|
|
18
|
+
workflows: ".github/workflows",
|
|
19
|
+
actions: ".github/actions",
|
|
20
|
+
config: [
|
|
21
|
+
".eslintrc.js",
|
|
22
|
+
".eslintignore",
|
|
23
|
+
".prettierrc",
|
|
24
|
+
".prettierignore",
|
|
25
|
+
"tsconfig.json",
|
|
26
|
+
"turbo.json",
|
|
27
|
+
"vitest.config.ts",
|
|
28
|
+
"commitlint.config.js"
|
|
29
|
+
],
|
|
30
|
+
husky: ".husky",
|
|
31
|
+
vscode: ".vscode",
|
|
32
|
+
docker: "docker",
|
|
33
|
+
scripts: "scripts",
|
|
34
|
+
changeset: ".changeset"
|
|
35
|
+
};
|
|
36
|
+
var SHARED_PACKAGES = [
|
|
37
|
+
"cache",
|
|
38
|
+
"config",
|
|
39
|
+
"constants",
|
|
40
|
+
"logger",
|
|
41
|
+
"result",
|
|
42
|
+
"types",
|
|
43
|
+
"ui",
|
|
44
|
+
"utils"
|
|
45
|
+
];
|
|
46
|
+
var SERVICES = [
|
|
47
|
+
"postgres",
|
|
48
|
+
"redis",
|
|
49
|
+
"rabbitmq",
|
|
50
|
+
"kafka",
|
|
51
|
+
"prometheus",
|
|
52
|
+
"grafana",
|
|
53
|
+
"minio",
|
|
54
|
+
"elasticsearch"
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// src/utils/helpers.ts
|
|
58
|
+
import { execSync } from "child_process";
|
|
59
|
+
import path from "path";
|
|
60
|
+
import chalk from "chalk";
|
|
61
|
+
import fs from "fs-extra";
|
|
62
|
+
function isNexuProject(dir) {
|
|
63
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
64
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
65
|
+
try {
|
|
66
|
+
const packageJson = fs.readJsonSync(packageJsonPath);
|
|
67
|
+
return packageJson.name === "nexu" || fs.existsSync(path.join(dir, "turbo.json"));
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function exec(command, cwd) {
|
|
73
|
+
try {
|
|
74
|
+
return execSync(command, {
|
|
75
|
+
cwd,
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Command failed: ${command}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function copyWithMerge(src, dest, overwrite = false) {
|
|
84
|
+
if (fs.existsSync(dest) && !overwrite) {
|
|
85
|
+
if (fs.statSync(src).isDirectory() && fs.statSync(dest).isDirectory()) {
|
|
86
|
+
const files = fs.readdirSync(src);
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
copyWithMerge(path.join(src, file), path.join(dest, file), overwrite);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
fs.copySync(src, dest, { overwrite });
|
|
94
|
+
}
|
|
95
|
+
function log(message, type = "info") {
|
|
96
|
+
const colors = {
|
|
97
|
+
info: chalk.blue,
|
|
98
|
+
success: chalk.green,
|
|
99
|
+
warn: chalk.yellow,
|
|
100
|
+
error: chalk.red
|
|
101
|
+
};
|
|
102
|
+
const icons = {
|
|
103
|
+
info: "i",
|
|
104
|
+
success: "\u2713",
|
|
105
|
+
warn: "!",
|
|
106
|
+
error: "\u2717"
|
|
107
|
+
};
|
|
108
|
+
console.log(`${colors[type](icons[type])} ${message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/commands/add.ts
|
|
112
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
113
|
+
var __dirname = path2.dirname(__filename);
|
|
114
|
+
function getTemplateDir() {
|
|
115
|
+
const possiblePaths = [
|
|
116
|
+
path2.resolve(__dirname, "..", "..", "templates", "default"),
|
|
117
|
+
path2.resolve(__dirname, "..", "templates", "default")
|
|
118
|
+
];
|
|
119
|
+
for (const templatePath of possiblePaths) {
|
|
120
|
+
if (fs2.existsSync(templatePath)) {
|
|
121
|
+
return templatePath;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw new Error("Template directory not found. Please reinstall the package.");
|
|
125
|
+
}
|
|
126
|
+
async function add(component, options) {
|
|
127
|
+
console.log(chalk2.bold("\n\u2795 Add Component\n"));
|
|
128
|
+
const projectDir = process.cwd();
|
|
129
|
+
if (!isNexuProject(projectDir)) {
|
|
130
|
+
log("This does not appear to be a Nexu project.", "error");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const componentType = component.toLowerCase();
|
|
134
|
+
switch (componentType) {
|
|
135
|
+
case "package":
|
|
136
|
+
await addPackage(projectDir, options.name);
|
|
137
|
+
break;
|
|
138
|
+
case "service":
|
|
139
|
+
await addService(projectDir, options.name);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
log(`Unknown component type: ${component}. Use 'package' or 'service'.`, "error");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function addPackage(projectDir, packageName) {
|
|
147
|
+
const packagesDir = path2.join(projectDir, "packages");
|
|
148
|
+
const existingPackages = fs2.existsSync(packagesDir) ? fs2.readdirSync(packagesDir).filter((f) => fs2.statSync(path2.join(packagesDir, f)).isDirectory()) : [];
|
|
149
|
+
const availablePackages = SHARED_PACKAGES.filter((pkg) => !existingPackages.includes(pkg));
|
|
150
|
+
if (availablePackages.length === 0) {
|
|
151
|
+
log("All packages are already installed.", "info");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let selectedPackages;
|
|
155
|
+
if (packageName && availablePackages.includes(packageName)) {
|
|
156
|
+
selectedPackages = [packageName];
|
|
157
|
+
} else {
|
|
158
|
+
const { packages } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: "checkbox",
|
|
161
|
+
name: "packages",
|
|
162
|
+
message: "Select packages to add:",
|
|
163
|
+
choices: availablePackages.map((pkg) => ({
|
|
164
|
+
name: pkg,
|
|
165
|
+
value: pkg
|
|
166
|
+
})),
|
|
167
|
+
validate: (input) => {
|
|
168
|
+
if (input.length === 0) {
|
|
169
|
+
return "Please select at least one package";
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
]);
|
|
175
|
+
selectedPackages = packages;
|
|
176
|
+
}
|
|
177
|
+
const spinner = ora("Adding packages...").start();
|
|
178
|
+
try {
|
|
179
|
+
const templateDir = getTemplateDir();
|
|
180
|
+
for (const pkg of selectedPackages) {
|
|
181
|
+
const srcDir = path2.join(templateDir, "packages", pkg);
|
|
182
|
+
const destDir = path2.join(projectDir, "packages", pkg);
|
|
183
|
+
if (fs2.existsSync(srcDir)) {
|
|
184
|
+
fs2.copySync(srcDir, destDir);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
spinner.succeed(`Added ${selectedPackages.length} package(s): ${selectedPackages.join(", ")}`);
|
|
188
|
+
console.log("\nRun " + chalk2.cyan("pnpm install") + " to install dependencies.");
|
|
189
|
+
} catch (error) {
|
|
190
|
+
spinner.fail("Failed to add packages");
|
|
191
|
+
console.error(error);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function addService(projectDir, serviceName) {
|
|
196
|
+
const servicesDir = path2.join(projectDir, "services");
|
|
197
|
+
if (!fs2.existsSync(servicesDir)) {
|
|
198
|
+
const { createServices } = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: "confirm",
|
|
201
|
+
name: "createServices",
|
|
202
|
+
message: "Services directory does not exist. Create it?",
|
|
203
|
+
default: true
|
|
204
|
+
}
|
|
205
|
+
]);
|
|
206
|
+
if (!createServices) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
let selectedServices;
|
|
211
|
+
if (serviceName && SERVICES.includes(serviceName)) {
|
|
212
|
+
selectedServices = [serviceName];
|
|
213
|
+
} else {
|
|
214
|
+
const { services } = await inquirer.prompt([
|
|
215
|
+
{
|
|
216
|
+
type: "checkbox",
|
|
217
|
+
name: "services",
|
|
218
|
+
message: "Select services to configure:",
|
|
219
|
+
choices: SERVICES.map((svc) => ({
|
|
220
|
+
name: svc,
|
|
221
|
+
value: svc
|
|
222
|
+
})),
|
|
223
|
+
validate: (input) => {
|
|
224
|
+
if (input.length === 0) {
|
|
225
|
+
return "Please select at least one service";
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
]);
|
|
231
|
+
selectedServices = services;
|
|
232
|
+
}
|
|
233
|
+
const spinner = ora("Adding services configuration...").start();
|
|
234
|
+
try {
|
|
235
|
+
const templateDir = getTemplateDir();
|
|
236
|
+
const srcServicesDir = path2.join(templateDir, "services");
|
|
237
|
+
if (fs2.existsSync(srcServicesDir)) {
|
|
238
|
+
fs2.copySync(srcServicesDir, servicesDir, { overwrite: false });
|
|
239
|
+
}
|
|
240
|
+
spinner.succeed(`Services configuration added`);
|
|
241
|
+
console.log("\nAvailable services: " + chalk2.cyan(selectedServices.join(", ")));
|
|
242
|
+
console.log(
|
|
243
|
+
"Start with: " + chalk2.cyan("docker compose -f services/docker-compose.yml --profile <profile> up -d")
|
|
244
|
+
);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
spinner.fail("Failed to add services");
|
|
247
|
+
console.error(error);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/commands/init.ts
|
|
253
|
+
import path3 from "path";
|
|
254
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
255
|
+
import chalk3 from "chalk";
|
|
256
|
+
import fs3 from "fs-extra";
|
|
257
|
+
import inquirer2 from "inquirer";
|
|
258
|
+
import ora2 from "ora";
|
|
259
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
260
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
261
|
+
function getTemplateDir2() {
|
|
262
|
+
const possiblePaths = [
|
|
263
|
+
path3.resolve(__dirname2, "..", "..", "templates", "default"),
|
|
264
|
+
path3.resolve(__dirname2, "..", "templates", "default")
|
|
265
|
+
];
|
|
266
|
+
for (const templatePath of possiblePaths) {
|
|
267
|
+
if (fs3.existsSync(templatePath)) {
|
|
268
|
+
return templatePath;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
throw new Error("Template directory not found. Please reinstall the package.");
|
|
272
|
+
}
|
|
273
|
+
async function init(projectName, options) {
|
|
274
|
+
console.log(chalk3.bold("\n\u{1F680} Create Nexu Monorepo\n"));
|
|
275
|
+
if (!projectName) {
|
|
276
|
+
const answers = await inquirer2.prompt([
|
|
277
|
+
{
|
|
278
|
+
type: "input",
|
|
279
|
+
name: "projectName",
|
|
280
|
+
message: "Project name:",
|
|
281
|
+
default: "my-nexu-app",
|
|
282
|
+
validate: (input) => {
|
|
283
|
+
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
284
|
+
return "Project name can only contain lowercase letters, numbers, and hyphens";
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
]);
|
|
290
|
+
projectName = answers.projectName;
|
|
291
|
+
}
|
|
292
|
+
const projectDir = path3.resolve(process.cwd(), projectName);
|
|
293
|
+
if (fs3.existsSync(projectDir)) {
|
|
294
|
+
const { overwrite } = await inquirer2.prompt([
|
|
295
|
+
{
|
|
296
|
+
type: "confirm",
|
|
297
|
+
name: "overwrite",
|
|
298
|
+
message: `Directory ${projectName} already exists. Overwrite?`,
|
|
299
|
+
default: false
|
|
300
|
+
}
|
|
301
|
+
]);
|
|
302
|
+
if (!overwrite) {
|
|
303
|
+
log("Aborted.", "warn");
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
fs3.removeSync(projectDir);
|
|
307
|
+
}
|
|
308
|
+
const { selectedPackages } = await inquirer2.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: "checkbox",
|
|
311
|
+
name: "selectedPackages",
|
|
312
|
+
message: "Select packages to include:",
|
|
313
|
+
choices: SHARED_PACKAGES.map((pkg) => ({
|
|
314
|
+
name: pkg,
|
|
315
|
+
value: pkg,
|
|
316
|
+
checked: true
|
|
317
|
+
}))
|
|
318
|
+
}
|
|
319
|
+
]);
|
|
320
|
+
const { features } = await inquirer2.prompt([
|
|
321
|
+
{
|
|
322
|
+
type: "checkbox",
|
|
323
|
+
name: "features",
|
|
324
|
+
message: "Select additional features:",
|
|
325
|
+
choices: [
|
|
326
|
+
{ name: "Docker services (PostgreSQL, Redis, etc.)", value: "services", checked: true },
|
|
327
|
+
{ name: "GitHub Actions workflows", value: "workflows", checked: true },
|
|
328
|
+
{ name: "Changesets (versioning)", value: "changesets", checked: true },
|
|
329
|
+
{ name: "Husky (git hooks)", value: "husky", checked: true },
|
|
330
|
+
{ name: "VSCode settings", value: "vscode", checked: true }
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
]);
|
|
334
|
+
const spinner = ora2("Copying template...").start();
|
|
335
|
+
try {
|
|
336
|
+
const templateDir = getTemplateDir2();
|
|
337
|
+
fs3.copySync(templateDir, projectDir);
|
|
338
|
+
spinner.succeed("Template copied");
|
|
339
|
+
} catch (error) {
|
|
340
|
+
spinner.fail("Failed to copy template");
|
|
341
|
+
console.error(error);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
const packageJsonPath = path3.join(projectDir, "package.json");
|
|
345
|
+
let packageJsonContent = fs3.readFileSync(packageJsonPath, "utf-8");
|
|
346
|
+
packageJsonContent = packageJsonContent.replace("{{PROJECT_NAME}}", projectName);
|
|
347
|
+
fs3.writeFileSync(packageJsonPath, packageJsonContent);
|
|
348
|
+
const packagesToRemove = SHARED_PACKAGES.filter((pkg) => !selectedPackages.includes(pkg));
|
|
349
|
+
if (packagesToRemove.length > 0) {
|
|
350
|
+
const removeSpinner = ora2("Removing unselected packages...").start();
|
|
351
|
+
for (const pkg of packagesToRemove) {
|
|
352
|
+
const pkgDir = path3.join(projectDir, "packages", pkg);
|
|
353
|
+
if (fs3.existsSync(pkgDir)) {
|
|
354
|
+
fs3.removeSync(pkgDir);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
removeSpinner.succeed("Removed unselected packages");
|
|
358
|
+
}
|
|
359
|
+
if (!features.includes("services")) {
|
|
360
|
+
fs3.removeSync(path3.join(projectDir, "services"));
|
|
361
|
+
}
|
|
362
|
+
if (!features.includes("workflows")) {
|
|
363
|
+
fs3.removeSync(path3.join(projectDir, ".github", "workflows"));
|
|
364
|
+
fs3.removeSync(path3.join(projectDir, ".github", "actions"));
|
|
365
|
+
}
|
|
366
|
+
if (!features.includes("changesets")) {
|
|
367
|
+
fs3.removeSync(path3.join(projectDir, ".changeset"));
|
|
368
|
+
}
|
|
369
|
+
if (!features.includes("husky")) {
|
|
370
|
+
fs3.removeSync(path3.join(projectDir, ".husky"));
|
|
371
|
+
}
|
|
372
|
+
if (!features.includes("vscode")) {
|
|
373
|
+
fs3.removeSync(path3.join(projectDir, ".vscode"));
|
|
374
|
+
}
|
|
375
|
+
const packageJson = fs3.readJsonSync(packageJsonPath);
|
|
376
|
+
if (!features.includes("changesets")) {
|
|
377
|
+
delete packageJson.scripts["changeset"];
|
|
378
|
+
delete packageJson.scripts["version-packages"];
|
|
379
|
+
delete packageJson.scripts["release"];
|
|
380
|
+
delete packageJson.devDependencies["@changesets/cli"];
|
|
381
|
+
}
|
|
382
|
+
if (!features.includes("husky")) {
|
|
383
|
+
delete packageJson.scripts["prepare"];
|
|
384
|
+
delete packageJson.devDependencies["husky"];
|
|
385
|
+
delete packageJson.devDependencies["lint-staged"];
|
|
386
|
+
delete packageJson["lint-staged"];
|
|
387
|
+
}
|
|
388
|
+
fs3.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
389
|
+
if (!options.skipGit) {
|
|
390
|
+
const gitSpinner = ora2("Initializing git repository...").start();
|
|
391
|
+
try {
|
|
392
|
+
exec("git init", projectDir);
|
|
393
|
+
exec("git add .", projectDir);
|
|
394
|
+
exec('git commit -m "Initial commit from create-nexu"', projectDir);
|
|
395
|
+
gitSpinner.succeed("Git repository initialized");
|
|
396
|
+
} catch {
|
|
397
|
+
gitSpinner.warn("Failed to initialize git repository");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (!options.skipInstall) {
|
|
401
|
+
const installSpinner = ora2("Installing dependencies...").start();
|
|
402
|
+
try {
|
|
403
|
+
exec("pnpm install", projectDir);
|
|
404
|
+
installSpinner.succeed("Dependencies installed");
|
|
405
|
+
} catch {
|
|
406
|
+
installSpinner.warn('Failed to install dependencies. Run "pnpm install" manually.');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
console.log("\n" + chalk3.green.bold("\u2728 Project created successfully!\n"));
|
|
410
|
+
console.log("Next steps:\n");
|
|
411
|
+
console.log(chalk3.cyan(` cd ${projectName}`));
|
|
412
|
+
if (options.skipInstall) {
|
|
413
|
+
console.log(chalk3.cyan(" pnpm install"));
|
|
414
|
+
}
|
|
415
|
+
console.log(chalk3.cyan(" pnpm dev"));
|
|
416
|
+
console.log("\nTo create an app:");
|
|
417
|
+
console.log(chalk3.cyan(" pnpm generate:app <name> <port>"));
|
|
418
|
+
console.log("\nTo update with latest features:");
|
|
419
|
+
console.log(chalk3.cyan(" npx create-nexu update"));
|
|
420
|
+
console.log("");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/commands/update.ts
|
|
424
|
+
import path4 from "path";
|
|
425
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
426
|
+
import chalk4 from "chalk";
|
|
427
|
+
import fs4 from "fs-extra";
|
|
428
|
+
import inquirer3 from "inquirer";
|
|
429
|
+
import ora3 from "ora";
|
|
430
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
431
|
+
var __dirname3 = path4.dirname(__filename3);
|
|
432
|
+
function getTemplateDir3() {
|
|
433
|
+
const possiblePaths = [
|
|
434
|
+
path4.resolve(__dirname3, "..", "..", "templates", "default"),
|
|
435
|
+
path4.resolve(__dirname3, "..", "templates", "default")
|
|
436
|
+
];
|
|
437
|
+
for (const templatePath of possiblePaths) {
|
|
438
|
+
if (fs4.existsSync(templatePath)) {
|
|
439
|
+
return templatePath;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
throw new Error("Template directory not found. Please reinstall the package.");
|
|
443
|
+
}
|
|
444
|
+
async function update(options) {
|
|
445
|
+
console.log(chalk4.bold("\n\u{1F504} Update Nexu Project\n"));
|
|
446
|
+
const projectDir = process.cwd();
|
|
447
|
+
if (!isNexuProject(projectDir)) {
|
|
448
|
+
log(
|
|
449
|
+
"This does not appear to be a Nexu project. Run this command from the project root.",
|
|
450
|
+
"error"
|
|
451
|
+
);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
const updateAll = options.all || !options.packages && !options.config && !options.workflows && !options.services;
|
|
455
|
+
const updateTargets = {
|
|
456
|
+
packages: updateAll || options.packages,
|
|
457
|
+
config: updateAll || options.config,
|
|
458
|
+
workflows: updateAll || options.workflows,
|
|
459
|
+
services: updateAll || options.services
|
|
460
|
+
};
|
|
461
|
+
console.log("Components to update:");
|
|
462
|
+
if (updateTargets.packages) console.log(chalk4.cyan(" - Shared packages"));
|
|
463
|
+
if (updateTargets.config) console.log(chalk4.cyan(" - Configuration files"));
|
|
464
|
+
if (updateTargets.workflows) console.log(chalk4.cyan(" - GitHub workflows"));
|
|
465
|
+
if (updateTargets.services) console.log(chalk4.cyan(" - Docker services"));
|
|
466
|
+
console.log("");
|
|
467
|
+
if (options.dryRun) {
|
|
468
|
+
log("Dry run mode - no changes will be made", "info");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const { confirm } = await inquirer3.prompt([
|
|
472
|
+
{
|
|
473
|
+
type: "confirm",
|
|
474
|
+
name: "confirm",
|
|
475
|
+
message: "Proceed with update?",
|
|
476
|
+
default: true
|
|
477
|
+
}
|
|
478
|
+
]);
|
|
479
|
+
if (!confirm) {
|
|
480
|
+
log("Update cancelled.", "warn");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const templateDir = getTemplateDir3();
|
|
484
|
+
if (updateTargets.packages) {
|
|
485
|
+
await updatePackages(projectDir, templateDir);
|
|
486
|
+
}
|
|
487
|
+
if (updateTargets.config) {
|
|
488
|
+
await updateConfig(projectDir, templateDir);
|
|
489
|
+
}
|
|
490
|
+
if (updateTargets.workflows) {
|
|
491
|
+
await updateWorkflows(projectDir, templateDir);
|
|
492
|
+
}
|
|
493
|
+
if (updateTargets.services) {
|
|
494
|
+
await updateServices(projectDir, templateDir);
|
|
495
|
+
}
|
|
496
|
+
console.log("\n" + chalk4.green.bold("\u2728 Update complete!\n"));
|
|
497
|
+
console.log("Run " + chalk4.cyan("pnpm install") + " to install any new dependencies.");
|
|
498
|
+
console.log("");
|
|
499
|
+
}
|
|
500
|
+
async function updatePackages(projectDir, templateDir) {
|
|
501
|
+
const spinner = ora3("Updating shared packages...").start();
|
|
502
|
+
const existingPackages = fs4.existsSync(path4.join(projectDir, "packages")) ? fs4.readdirSync(path4.join(projectDir, "packages")).filter((f) => fs4.statSync(path4.join(projectDir, "packages", f)).isDirectory()) : [];
|
|
503
|
+
const { selectedPackages } = await inquirer3.prompt([
|
|
504
|
+
{
|
|
505
|
+
type: "checkbox",
|
|
506
|
+
name: "selectedPackages",
|
|
507
|
+
message: "Select packages to update:",
|
|
508
|
+
choices: SHARED_PACKAGES.map((pkg) => ({
|
|
509
|
+
name: pkg + (existingPackages.includes(pkg) ? " (update)" : " (add new)"),
|
|
510
|
+
value: pkg,
|
|
511
|
+
checked: existingPackages.includes(pkg)
|
|
512
|
+
}))
|
|
513
|
+
}
|
|
514
|
+
]);
|
|
515
|
+
spinner.start("Updating packages...");
|
|
516
|
+
for (const pkg of selectedPackages) {
|
|
517
|
+
const srcDir = path4.join(templateDir, "packages", pkg);
|
|
518
|
+
const destDir = path4.join(projectDir, "packages", pkg);
|
|
519
|
+
if (fs4.existsSync(srcDir)) {
|
|
520
|
+
let existingPkgJson = null;
|
|
521
|
+
const pkgJsonPath = path4.join(destDir, "package.json");
|
|
522
|
+
if (fs4.existsSync(pkgJsonPath)) {
|
|
523
|
+
existingPkgJson = fs4.readJsonSync(pkgJsonPath);
|
|
524
|
+
}
|
|
525
|
+
fs4.copySync(srcDir, destDir, { overwrite: true });
|
|
526
|
+
if (existingPkgJson) {
|
|
527
|
+
const newPkgJson = fs4.readJsonSync(pkgJsonPath);
|
|
528
|
+
newPkgJson.version = existingPkgJson.version;
|
|
529
|
+
fs4.writeJsonSync(pkgJsonPath, newPkgJson, { spaces: 2 });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
spinner.succeed(`Updated ${selectedPackages.length} packages`);
|
|
534
|
+
}
|
|
535
|
+
async function updateConfig(projectDir, templateDir) {
|
|
536
|
+
const spinner = ora3("Updating configuration files...").start();
|
|
537
|
+
const configFiles = TEMPLATE_DIRS.config;
|
|
538
|
+
let updated = 0;
|
|
539
|
+
for (const file of configFiles) {
|
|
540
|
+
const srcFile = path4.join(templateDir, file);
|
|
541
|
+
const destFile = path4.join(projectDir, file);
|
|
542
|
+
if (fs4.existsSync(srcFile)) {
|
|
543
|
+
fs4.copySync(srcFile, destFile, { overwrite: true });
|
|
544
|
+
updated++;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const huskySrc = path4.join(templateDir, ".husky");
|
|
548
|
+
const huskyDest = path4.join(projectDir, ".husky");
|
|
549
|
+
if (fs4.existsSync(huskySrc)) {
|
|
550
|
+
fs4.copySync(huskySrc, huskyDest, { overwrite: true });
|
|
551
|
+
}
|
|
552
|
+
const vscodeSrc = path4.join(templateDir, ".vscode");
|
|
553
|
+
const vscodeDest = path4.join(projectDir, ".vscode");
|
|
554
|
+
if (fs4.existsSync(vscodeSrc)) {
|
|
555
|
+
fs4.copySync(vscodeSrc, vscodeDest, { overwrite: true });
|
|
556
|
+
}
|
|
557
|
+
spinner.succeed(`Updated ${updated} configuration files`);
|
|
558
|
+
}
|
|
559
|
+
async function updateWorkflows(projectDir, templateDir) {
|
|
560
|
+
const spinner = ora3("Updating GitHub workflows...").start();
|
|
561
|
+
const workflowsSrc = path4.join(templateDir, ".github", "workflows");
|
|
562
|
+
const workflowsDest = path4.join(projectDir, ".github", "workflows");
|
|
563
|
+
if (fs4.existsSync(workflowsSrc)) {
|
|
564
|
+
fs4.ensureDirSync(workflowsDest);
|
|
565
|
+
fs4.copySync(workflowsSrc, workflowsDest, { overwrite: true });
|
|
566
|
+
}
|
|
567
|
+
const actionsSrc = path4.join(templateDir, ".github", "actions");
|
|
568
|
+
const actionsDest = path4.join(projectDir, ".github", "actions");
|
|
569
|
+
if (fs4.existsSync(actionsSrc)) {
|
|
570
|
+
fs4.ensureDirSync(actionsDest);
|
|
571
|
+
fs4.copySync(actionsSrc, actionsDest, { overwrite: true });
|
|
572
|
+
}
|
|
573
|
+
const dependabotSrc = path4.join(templateDir, ".github", "dependabot.yml");
|
|
574
|
+
const dependabotDest = path4.join(projectDir, ".github", "dependabot.yml");
|
|
575
|
+
if (fs4.existsSync(dependabotSrc)) {
|
|
576
|
+
fs4.copySync(dependabotSrc, dependabotDest, { overwrite: true });
|
|
577
|
+
}
|
|
578
|
+
spinner.succeed("Updated GitHub workflows and actions");
|
|
579
|
+
}
|
|
580
|
+
async function updateServices(projectDir, templateDir) {
|
|
581
|
+
const spinner = ora3("Updating Docker services...").start();
|
|
582
|
+
const servicesSrc = path4.join(templateDir, "services");
|
|
583
|
+
const servicesDest = path4.join(projectDir, "services");
|
|
584
|
+
if (fs4.existsSync(servicesSrc)) {
|
|
585
|
+
fs4.ensureDirSync(servicesDest);
|
|
586
|
+
copyWithMerge(servicesSrc, servicesDest, true);
|
|
587
|
+
}
|
|
588
|
+
spinner.succeed("Updated Docker services");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/index.ts
|
|
592
|
+
var program = new Command();
|
|
593
|
+
program.name("create-nexu").description("CLI to create and update Nexu monorepo projects").version("1.0.0");
|
|
594
|
+
program.argument("[project-name]", "Name of the project to create").option("-t, --template <template>", "Template to use (default, minimal)", "default").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").action((projectName, options) => {
|
|
595
|
+
if (!projectName && process.argv.length <= 2) {
|
|
596
|
+
program.help();
|
|
597
|
+
} else {
|
|
598
|
+
return init(projectName, options);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
program.command("update").description("Update an existing Nexu project with latest features").option("-p, --packages", "Update only shared packages").option("-c, --config", "Update only configuration files").option("-w, --workflows", "Update only GitHub workflows").option("-s, --services", "Update only Docker services").option("--all", "Update everything (default)").option("--dry-run", "Show what would be updated without making changes").action(update);
|
|
602
|
+
program.command("add <component>").description("Add a component to your project (package, service)").option("-n, --name <name>", "Name for the component").action(add);
|
|
603
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-nexu",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to create and update Nexu monorepo projects",
|
|
5
|
+
"author": "Nexu Team",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-nexu": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"templates"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"lint": "eslint src/",
|
|
22
|
+
"lint:fix": "eslint src/ --fix",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^12.0.0",
|
|
27
|
+
"fs-extra": "^11.2.0",
|
|
28
|
+
"inquirer": "^9.2.0",
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"ora": "^8.0.0",
|
|
31
|
+
"semver": "^7.6.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/fs-extra": "^11.0.0",
|
|
35
|
+
"@types/inquirer": "^9.0.0",
|
|
36
|
+
"@types/semver": "^7.5.0",
|
|
37
|
+
"@types/node": "^20.0.0",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.4.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "restricted",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|