create-next-pro-cli 0.1.13 → 0.1.16

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.
@@ -1,201 +0,0 @@
1
- import { join } from "node:path";
2
- import { mkdir, readFile, writeFile, readdir } from "node:fs/promises";
3
- import prompts from "prompts";
4
-
5
- import { capitalize, toFileName, loadConfig } from "./utils";
6
-
7
- import { existsSync, statSync } from "node:fs";
8
-
9
- export async function addPage(args: string[]) {
10
- let pageName = args[1];
11
- if (!pageName || pageName.startsWith("-")) {
12
- const response = await prompts.prompt({
13
- type: "text",
14
- name: "pageName",
15
- message: "📝 Page name to add:",
16
- validate: (name: string) => (name ? true : "Page name is required"),
17
- });
18
- pageName = response.pageName;
19
- }
20
-
21
- // Handle nested pages
22
- let parentName = null;
23
- let childName = null;
24
- if (pageName.includes(".")) {
25
- [parentName, childName] = pageName.split(".");
26
- }
27
-
28
- let shortFlags = args.find((arg) => /^-[A-Za-z]+$/.test(arg));
29
- let longFlags = new Set(args.filter((a) => a.startsWith("--")));
30
- const flags = new Set<string>();
31
-
32
- if (!shortFlags && Array.from(longFlags).length === 0) {
33
- shortFlags = "-LPl";
34
- }
35
-
36
- if (shortFlags) {
37
- for (const char of shortFlags.slice(1)) {
38
- switch (char) {
39
- case "L":
40
- flags.add("layout");
41
- break;
42
- case "P":
43
- flags.add("page");
44
- break;
45
- case "l":
46
- flags.add("loading");
47
- break;
48
- case "n":
49
- flags.add("not-found");
50
- break;
51
- case "e":
52
- flags.add("error");
53
- break;
54
- case "g":
55
- flags.add("global-error");
56
- break;
57
- case "r":
58
- flags.add("route");
59
- break;
60
- case "t":
61
- flags.add("template");
62
- break;
63
- case "d":
64
- flags.add("default");
65
- break;
66
- }
67
- }
68
- }
69
-
70
- for (const flag of [
71
- "layout",
72
- "page",
73
- "loading",
74
- "not-found",
75
- "error",
76
- "global-error",
77
- "route",
78
- "template",
79
- "default",
80
- ]) {
81
- if (longFlags.has("--" + flag)) flags.add(flag);
82
- }
83
-
84
- const config = await loadConfig();
85
- if (!config) {
86
- console.error("❌ Configuration file cnp.config.json not found. Run this command from the project root.");
87
- return;
88
- }
89
- const useI18n = !!config.useI18n;
90
-
91
- const srcSegments = ["src", "app"];
92
- if (useI18n) srcSegments.push("[locale]");
93
- const srcPath = join(process.cwd(), ...srcSegments);
94
- if (!existsSync(srcPath)) {
95
- console.error(`❌ Expected directory not found: ${srcPath}`);
96
- return;
97
- }
98
-
99
- let messagesPath: string | null = null;
100
- let locales: string[] = [];
101
- if (useI18n) {
102
- messagesPath = join(process.cwd(), "messages");
103
- if (!existsSync(messagesPath)) {
104
- console.error("❌ Messages directory missing. Ensure i18n was configured.");
105
- return;
106
- }
107
- const entries = await readdir(messagesPath, { withFileTypes: true });
108
- locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
109
- }
110
-
111
- const templatePath = join(import.meta.dir, "..", "..", "templates", "Page");
112
-
113
- // Create folders/files for nested or simple page
114
- let uiPageDir, localePagePath, jsonFileName;
115
- if (parentName && childName) {
116
- uiPageDir = join(process.cwd(), "src", "ui", parentName, childName);
117
- localePagePath = join(srcPath, parentName, childName);
118
- jsonFileName = parentName;
119
- } else {
120
- uiPageDir = join(process.cwd(), "src", "ui", pageName);
121
- localePagePath = join(srcPath, pageName);
122
- jsonFileName = pageName;
123
- }
124
- if (!existsSync(uiPageDir)) {
125
- await mkdir(uiPageDir, { recursive: true });
126
- }
127
- const uiPageFile = join(uiPageDir, "page-ui.tsx");
128
- const uiPageTemplate = join(templatePath, "page-ui.tsx");
129
- if (existsSync(uiPageTemplate)) {
130
- let uiContent = await readFile(uiPageTemplate, "utf-8");
131
- uiContent = uiContent
132
- .replace(/template/g, childName || pageName)
133
- .replace(/Template/g, capitalize(childName || pageName));
134
- await writeFile(uiPageFile, uiContent);
135
- console.log(`📄 File created: ${uiPageFile}`);
136
- } else {
137
- console.warn("⚠️ Template page-ui.tsx manquant.");
138
- }
139
- if (!existsSync(localePagePath)) {
140
- await mkdir(localePagePath, { recursive: true });
141
- }
142
- for (const flag of flags) {
143
- const filename = toFileName(flag);
144
- const src = join(templatePath, filename);
145
- const dst = join(localePagePath, filename);
146
- if (!existsSync(src)) {
147
- console.warn(`⚠️ Missing template file: ${filename}`);
148
- continue;
149
- }
150
- const content = await readFile(src, "utf-8");
151
- const replaced = content
152
- .replace(/template/g, childName || pageName)
153
- .replace(/Template/g, capitalize(childName || pageName));
154
- await writeFile(dst, replaced);
155
- console.log(`📄 File created: ${dst}`);
156
- }
157
-
158
- if (useI18n && messagesPath) {
159
- const jsonTemplate = join(templatePath, "page.json");
160
- if (!existsSync(jsonTemplate)) {
161
- console.warn("⚠️ Missing template page.json.");
162
-
163
- } else {
164
- const content = await readFile(jsonTemplate, "utf-8");
165
- const replaced = content
166
- .replace(/template/g, childName || pageName)
167
- .replace(/Template/g, capitalize(childName || pageName));
168
- for (const locale of locales) {
169
- // Only process if messages/<locale> is a directory
170
- const localeDir = join(messagesPath, locale);
171
- if (!existsSync(localeDir) || !statSync(localeDir).isDirectory())
172
- continue;
173
- const jsonTarget = join(messagesPath, locale, `${jsonFileName}.json`);
174
- let current: Record<string, any> = {};
175
- if (existsSync(jsonTarget)) {
176
- const jsonFile = await readFile(jsonTarget, "utf-8");
177
- try {
178
- current = JSON.parse(jsonFile) as Record<string, any>;
179
- } catch {
180
- current = {};
181
- }
182
- }
183
- if (parentName && childName) {
184
- current[childName] = JSON.parse(replaced);
185
- } else {
186
- // fichier simple
187
- current = JSON.parse(replaced);
188
- }
189
- await writeFile(jsonTarget, JSON.stringify(current, null, 2));
190
- }
191
- }
192
- } else {
193
- console.log("ℹ️ Skipping translation templates; next-intl not enabled.");
194
- }
195
-
196
- console.log(
197
- `✅ Page "${pageName}" with templates added${
198
- useI18n ? " for each locale" : ""
199
- }.`
200
- );
201
- }
@@ -1,18 +0,0 @@
1
- import { scaffoldProject } from "../scaffold";
2
- export async function createProject(nameArg: string, force: boolean) {
3
- const response = {
4
- projectName: nameArg,
5
- useTypescript: true,
6
- useEslint: true,
7
- useTailwind: true,
8
- useSrcDir: true,
9
- useTurbopack: true,
10
- useI18n: true,
11
- customAlias: false,
12
- importAlias: "@/*",
13
- force,
14
- };
15
-
16
- console.log(`📦 Creating project "${response.projectName}"...`);
17
- await scaffoldProject(response);
18
- }
@@ -1,79 +0,0 @@
1
- import { scaffoldProject } from "../scaffold";
2
- import prompts from "prompts";
3
- export async function createProjectWithPrompt() {
4
- const response = await prompts.prompt([
5
- {
6
- type: "text",
7
- name: "projectName",
8
- message: "🧱 Project name:",
9
- initial: "my-next-app",
10
- },
11
- {
12
- type: "toggle",
13
- name: "useTypescript",
14
- message: "✔ Use TypeScript?",
15
- initial: true,
16
- active: "Yes",
17
- inactive: "No",
18
- },
19
- {
20
- type: "toggle",
21
- name: "useEslint",
22
- message: "✔ Use ESLint?",
23
- initial: true,
24
- active: "Yes",
25
- inactive: "No",
26
- },
27
- {
28
- type: "toggle",
29
- name: "useTailwind",
30
- message: "✔ Use Tailwind CSS?",
31
- initial: true,
32
- active: "Yes",
33
- inactive: "No",
34
- },
35
- {
36
- type: "toggle",
37
- name: "useSrcDir",
38
- message: "✔ Use `src/` directory?",
39
- initial: false,
40
- active: "Yes",
41
- inactive: "No",
42
- },
43
- {
44
- type: "toggle",
45
- name: "useTurbopack",
46
- message: "✔ Use Turbopack for `next dev`?",
47
- initial: true,
48
- active: "Yes",
49
- inactive: "No",
50
- },
51
- {
52
- type: "toggle",
53
- name: "useI18n",
54
- message: "✔ Use i18n with next-intl for translations?",
55
- initial: true,
56
- active: "Yes",
57
- inactive: "No",
58
- },
59
- {
60
- type: "toggle",
61
- name: "customAlias",
62
- message: "✔ Customize import alias (`@/*` by default)?",
63
- initial: false,
64
- active: "Yes",
65
- inactive: "No",
66
- },
67
- {
68
- type: (prev: boolean) => (prev ? "text" : null),
69
- name: "importAlias",
70
- message: "✔ What import alias would you like?",
71
- initial: "@core/*",
72
- },
73
- ]);
74
-
75
- console.log("\n✅ Your choices:");
76
- console.log(response);
77
-
78
- await scaffoldProject(response);
79
- }
package/src/lib/rmPage.ts DELETED
@@ -1,52 +0,0 @@
1
- import { join } from "node:path";
2
- import { writeFile, readdir } from "node:fs/promises";
3
- import prompts from "prompts";
4
- import { existsSync } from "node:fs";
5
-
6
- export async function rmPage(args: string[]) {
7
- let pageName = args[1];
8
- if (!pageName || pageName.startsWith("-")) {
9
- const response = await prompts.prompt({
10
- type: "text",
11
- name: "pageName",
12
- message: "🗑️ Page name to remove:",
13
- validate: (name: string) => (name ? true : "Page name is required"),
14
- });
15
- pageName = response.pageName;
16
- }
17
-
18
- // Remove translation files messages/<lang>/<PageName>.json
19
- const messagesPath = join(process.cwd(), "messages");
20
- const entries = await readdir(messagesPath, { withFileTypes: true });
21
- const langDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
22
- for (const locale of langDirs) {
23
- const jsonTarget = join(messagesPath, locale, `${pageName}.json`);
24
- if (existsSync(jsonTarget)) {
25
- await writeFile(jsonTarget, "");
26
- await import("node:child_process").then((cp) =>
27
- cp.execSync(`rm -f '${jsonTarget}'`)
28
- );
29
- console.log(`🗑️ Deleted: ${jsonTarget}`);
30
- }
31
- }
32
-
33
- // Remove folder src/ui/<PageName>
34
- const uiPageDir = join(process.cwd(), "src", "ui", pageName);
35
- if (existsSync(uiPageDir)) {
36
- await import("node:child_process").then((cp) =>
37
- cp.execSync(`rm -rf '${uiPageDir}'`)
38
- );
39
- console.log(`🗑️ Deleted: ${uiPageDir}`);
40
- }
41
-
42
- // Remove folder src/app/[locale]/<PageName>
43
- const appLocaleDir = join(process.cwd(), "src", "app", "[locale]", pageName);
44
- if (existsSync(appLocaleDir)) {
45
- await import("node:child_process").then((cp) =>
46
- cp.execSync(`rm -rf '${appLocaleDir}'`)
47
- );
48
- console.log(`🗑️ Deleted: ${appLocaleDir}`);
49
- }
50
-
51
- console.log(`✅ Page "${pageName}" deleted.`);
52
- }
@@ -1,23 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { toFileName } from "./utils";
3
-
4
- describe("toFileName", () => {
5
- const cases: [string, string][] = [
6
- ["layout", "layout.tsx"],
7
- ["page", "page.tsx"],
8
- ["loading", "loading.tsx"],
9
- ["not-found", "not-found.tsx"],
10
- ["error", "error.tsx"],
11
- ["global-error", "global-error.tsx"],
12
- ["route", "route.ts"],
13
- ["template", "template.tsx"],
14
- ["default", "default.tsx"],
15
- ["foo", "foo.tsx"],
16
- ];
17
-
18
- for (const [key, expected] of cases) {
19
- test(`maps "${key}" to "${expected}"`, () => {
20
- expect(toFileName(key)).toBe(expected);
21
- });
22
- }
23
- });
package/src/lib/utils.ts DELETED
@@ -1,60 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { existsSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- /**
6
- * Capitalize the first letter of a string.
7
- */
8
- export function capitalize(str: string): string {
9
- return str.charAt(0).toUpperCase() + str.slice(1);
10
- }
11
-
12
- export interface CNPConfig {
13
- useI18n?: boolean;
14
- [key: string]: any;
15
- }
16
-
17
- /**
18
- * Load CLI configuration from the project root.
19
- * Returns null if the configuration file is missing or invalid.
20
- */
21
- export async function loadConfig(): Promise<CNPConfig | null> {
22
- const configPath = join(process.cwd(), "cnp.config.json");
23
- if (!existsSync(configPath)) return null;
24
- try {
25
- const raw = await readFile(configPath, "utf-8");
26
- return JSON.parse(raw) as CNPConfig;
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- /**
33
- * Map a key to its corresponding file name for page/component templates.
34
- * @param key string
35
- * @returns file name string
36
- */
37
- export function toFileName(key: string): string {
38
- switch (key) {
39
- case "layout":
40
- return "layout.tsx";
41
- case "page":
42
- return "page.tsx";
43
- case "loading":
44
- return "loading.tsx";
45
- case "not-found":
46
- return "not-found.tsx";
47
- case "error":
48
- return "error.tsx";
49
- case "global-error":
50
- return "global-error.tsx";
51
- case "route":
52
- return "route.ts";
53
- case "template":
54
- return "template.tsx";
55
- case "default":
56
- return "default.tsx";
57
- default:
58
- return `${key}.tsx`;
59
- }
60
- }
@@ -1,89 +0,0 @@
1
- // src/scaffold-dev.ts
2
-
3
- import { mkdir, writeFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
-
6
- /**
7
- * Experimental tool for generating a Next.js project template structure.
8
- * This script is under development and will evolve to allow creation of new project templates in the future.
9
- *
10
- * @param options Object containing the project name
11
- */
12
- async function scaffoldTemplate(options: { projectName: string }) {
13
- const base = options.projectName;
14
- console.log(`\n📁 Creating template directory: ${base}`);
15
-
16
- // List of folders to create for the template
17
- const folders = [
18
- "app/[locale]/_main",
19
- "app/[locale]/dashboard",
20
- "lib/i18n",
21
- "messages/en",
22
- "messages/fr",
23
- "public",
24
- "styles",
25
- ];
26
-
27
- // List of files to create for the template
28
- const files = [
29
- "app/[locale]/layout.tsx",
30
- "app/[locale]/page.tsx",
31
- "app/[locale]/not-found.tsx",
32
- "app/[locale]/error.tsx",
33
-
34
- "app/[locale]/_main/page.tsx",
35
- "app/[locale]/_main/layout.tsx",
36
- "app/[locale]/_main/loading.tsx",
37
- "app/[locale]/_main/template.tsx",
38
-
39
- "app/[locale]/dashboard/page.tsx",
40
- "app/[locale]/dashboard/layout.tsx",
41
- "app/[locale]/dashboard/loading.tsx",
42
- "app/[locale]/dashboard/template.tsx",
43
-
44
- "lib/i18n/routing.ts",
45
- "lib/i18n/request.ts",
46
- "lib/i18n/navigation.ts",
47
-
48
- "messages/en/home.json",
49
- "messages/en/dashboard.json",
50
- "messages/en/navbar.json",
51
-
52
- "messages/fr/home.json",
53
- "messages/fr/dashboard.json",
54
- "messages/fr/navbar.json",
55
-
56
- "styles/globals.css",
57
- "middleware.ts",
58
- "next.config.ts",
59
- "postcss.config.mjs",
60
- "eslint.config.mjs",
61
- "tailwind.config.ts",
62
- "tsconfig.json",
63
- "next-env.d.ts",
64
- "package.json",
65
- "README.md",
66
- ];
67
-
68
- // Create all folders
69
- for (const folder of folders) {
70
- await mkdir(join(base, folder), { recursive: true });
71
- }
72
-
73
- // Create all files with a placeholder comment
74
- for (const file of files) {
75
- const fullPath = join(base, file);
76
- await writeFile(fullPath, `// ${file}`);
77
- }
78
-
79
- console.log("✅ Full template generated.");
80
- }
81
-
82
- // Direct execution if this file is run as a script
83
- if (require.main === module) {
84
- const projectName = process.argv[2] || "template-next-app";
85
- scaffoldTemplate({ projectName }).catch((err) => {
86
- console.error("Error during template generation:", err);
87
- process.exit(1);
88
- });
89
- }
package/src/scaffold.ts DELETED
@@ -1,86 +0,0 @@
1
- // src/scaffold.ts
2
-
3
- import { cp, mkdir, rm, writeFile, readFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { existsSync } from "node:fs";
6
-
7
- /**
8
- * Options for scaffolding a Next.js project.
9
- */
10
- interface ScaffoldOptions {
11
- projectName: string;
12
- useTypescript: boolean;
13
- useEslint: boolean;
14
- useTailwind: boolean;
15
- useSrcDir: boolean;
16
- useTurbopack: boolean;
17
- useI18n: boolean;
18
- customAlias: boolean;
19
- importAlias: string;
20
- force?: boolean;
21
- }
22
-
23
- /**
24
- * Scaffold a new Next.js project based on provided options.
25
- *
26
- * - Copies the default template to the target directory
27
- * - Removes the target directory if it exists and --force is set
28
- * - Optionally customizes the structure in future (e.g. remove unused files)
29
- *
30
- * @param options ScaffoldOptions for the project
31
- */
32
- export async function scaffoldProject(options: ScaffoldOptions) {
33
- const targetPath = join(process.cwd(), options.projectName);
34
- const templatePath = join(
35
- import.meta.dir,
36
- "..",
37
- "templates",
38
- "Projects",
39
- "default",
40
- );
41
-
42
- // Check if target directory exists
43
- if (existsSync(targetPath)) {
44
- if (options.force) {
45
- console.warn("⚠️ Target directory already exists, removing...");
46
- await rm(targetPath, { recursive: true, force: true });
47
- } else {
48
- console.error(
49
- "❌ Target directory already exists. Use --force to overwrite.",
50
- );
51
- process.exit(1);
52
- }
53
- }
54
-
55
- try {
56
- console.log("📁 Creating project directory...");
57
- await mkdir(targetPath, { recursive: true });
58
-
59
- console.log("📦 Copying files from template...");
60
- await cp(templatePath, targetPath, { recursive: true });
61
-
62
- // Apply configuration: add dependencies or files based on prompt choices
63
- const pkgPath = join(targetPath, "package.json");
64
- if (existsSync(pkgPath)) {
65
- const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
66
- pkg.dependencies = pkg.dependencies || {};
67
- if (options.useI18n) {
68
- pkg.dependencies["next-intl"] =
69
- pkg.dependencies["next-intl"] || "^4.3.5";
70
- }
71
- await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
72
- }
73
-
74
- // Write CLI configuration to project root
75
- await writeFile(
76
- join(targetPath, "cnp.config.json"),
77
- JSON.stringify(options, null, 2),
78
- );
79
-
80
- console.log("✅ Project scaffolded successfully!");
81
- console.log(`➡️ cd ${options.projectName} && bun install && bun dev`);
82
- } catch (err) {
83
- console.error("❌ Error during scaffolding:", err);
84
- process.exit(1);
85
- }
86
- }
package/tsconfig.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2017",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "allowJs": true,
6
- "skipLibCheck": true,
7
- "strict": true,
8
- "noEmit": true,
9
- "esModuleInterop": true,
10
- "module": "esnext",
11
- "moduleResolution": "bundler",
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "jsx": "preserve",
15
- "incremental": true,
16
- "plugins": [
17
- {
18
- "name": "next"
19
- }
20
- ],
21
- "paths": {
22
- "@/*": ["./src/*"]
23
- }
24
- },
25
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
- "exclude": ["node_modules"]
27
- }