create-next-pro-cli 0.1.25 → 0.1.27

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.
Files changed (107) hide show
  1. package/README.md +205 -292
  2. package/create-next-pro-completion.sh +8 -38
  3. package/create-next-pro-completion.zsh +13 -0
  4. package/dist/bin.bun.js +1127 -628
  5. package/dist/bin.node.js +1170 -617
  6. package/dist/bin.node.js.map +1 -1
  7. package/dist/create-next-pro +35 -6
  8. package/package.json +49 -27
  9. package/templates/Projects/default/.env.example +17 -0
  10. package/templates/Projects/default/.github/workflows/quality.yml +24 -0
  11. package/templates/Projects/default/.gitignore.template +47 -0
  12. package/templates/Projects/default/.prettierignore +3 -0
  13. package/templates/Projects/default/README.md +66 -21
  14. package/templates/Projects/default/bun.lock +1152 -0
  15. package/templates/Projects/default/eslint.config.mjs +27 -11
  16. package/templates/Projects/default/messages/en/_global_ui.json +23 -10
  17. package/templates/Projects/default/messages/en.ts +17 -2
  18. package/templates/Projects/default/messages/fr/_global_ui.json +23 -10
  19. package/templates/Projects/default/messages/fr.ts +17 -2
  20. package/templates/Projects/default/next.config.ts +43 -3
  21. package/templates/Projects/default/package.json +42 -24
  22. package/templates/Projects/default/playwright.config.ts +26 -0
  23. package/templates/Projects/default/pnpm-workspace.yaml +5 -0
  24. package/templates/Projects/default/public/{cnp-logo.svg → logo.svg} +1 -1
  25. package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +8 -1
  26. package/templates/Projects/default/src/app/[locale]/(public)/login/page.tsx +8 -0
  27. package/templates/Projects/default/src/app/[locale]/(public)/register/page.tsx +8 -0
  28. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/error.tsx +25 -0
  29. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/loading.tsx +5 -0
  30. package/templates/Projects/default/src/app/[locale]/(user)/{Dashboard → dashboard}/page.tsx +1 -1
  31. package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +24 -2
  32. package/templates/Projects/default/src/app/[locale]/(user)/settings/loading.tsx +5 -0
  33. package/templates/Projects/default/src/app/[locale]/(user)/{Settings → settings}/page.tsx +1 -2
  34. package/templates/Projects/default/src/app/[locale]/(user)/userInfo/loading.tsx +5 -0
  35. package/templates/Projects/default/src/app/[locale]/(user)/{UserInfo → userInfo}/page.tsx +1 -2
  36. package/templates/Projects/default/src/app/[locale]/layout.tsx +34 -10
  37. package/templates/Projects/default/src/app/[locale]/loading.tsx +0 -9
  38. package/templates/Projects/default/src/app/[locale]/not-found.tsx +6 -15
  39. package/templates/Projects/default/src/app/[locale]/page.tsx +10 -1
  40. package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +8 -60
  41. package/templates/Projects/default/src/app/not-found.tsx +1 -1
  42. package/templates/Projects/default/src/app/sitemap.ts +2 -2
  43. package/templates/Projects/default/src/app/styles/globals.css +166 -113
  44. package/templates/Projects/default/src/auth.ts +20 -0
  45. package/templates/Projects/default/src/config.ts +3 -3
  46. package/templates/Projects/default/src/env.ts +65 -0
  47. package/templates/Projects/default/src/lib/i18n/messages.ts +8 -0
  48. package/templates/Projects/default/src/lib/i18n/request.ts +2 -16
  49. package/templates/Projects/default/src/lib/security/csp.ts +16 -0
  50. package/templates/Projects/default/src/lib/utils.ts +2 -1
  51. package/templates/Projects/default/src/proxy.ts +13 -0
  52. package/templates/Projects/default/src/ui/_global/BackButton.tsx +4 -2
  53. package/templates/Projects/default/src/ui/_global/Button.tsx +3 -8
  54. package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +10 -28
  55. package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +1 -1
  56. package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +9 -10
  57. package/templates/Projects/default/src/ui/_global/PublicNav.tsx +51 -17
  58. package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +45 -39
  59. package/templates/Projects/default/src/ui/_global/UserNav.tsx +6 -6
  60. package/templates/Projects/default/src/ui/_home/page-ui.tsx +5 -7
  61. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/LogoutButton.tsx +9 -7
  62. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/StatsCard.tsx +4 -4
  63. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/page-ui.tsx +7 -8
  64. package/templates/Projects/default/src/ui/login/page-ui.tsx +36 -0
  65. package/templates/Projects/default/src/ui/register/page-ui.tsx +38 -0
  66. package/templates/Projects/default/src/ui/settings/page-ui.tsx +15 -0
  67. package/templates/Projects/default/src/ui/userInfo/page-ui.tsx +15 -0
  68. package/templates/Projects/default/tailwind.config.ts +81 -1
  69. package/templates/Projects/default/tests/consumer/validate-template.ts +66 -0
  70. package/templates/Projects/default/tests/e2e/template-remediation.playwright.ts +106 -0
  71. package/templates/Projects/default/tests/rendering/verify-rendering.ts +56 -0
  72. package/templates/Projects/default/tests/unit/csp.test.ts +19 -0
  73. package/templates/Projects/default/tests/unit/env.test.ts +76 -0
  74. package/templates/Projects/default/tsconfig.json +6 -6
  75. package/templates/Projects/default/messages/getMergedMessages.ts +0 -31
  76. package/templates/Projects/default/middleware.ts +0 -11
  77. package/templates/Projects/default/src/app/[locale]/(public)/Login/page.tsx +0 -6
  78. package/templates/Projects/default/src/app/[locale]/(public)/Register/page.tsx +0 -6
  79. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/error.tsx +0 -38
  80. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/loading.tsx +0 -10
  81. package/templates/Projects/default/src/app/[locale]/(user)/Settings/loading.tsx +0 -17
  82. package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/loading.tsx +0 -17
  83. package/templates/Projects/default/src/app/api/auth/post-login/route.ts +0 -26
  84. package/templates/Projects/default/src/app/api/hello/route.ts +0 -5
  85. package/templates/Projects/default/src/app/layout.tsx +0 -11
  86. package/templates/Projects/default/src/app/page.tsx +0 -6
  87. package/templates/Projects/default/src/auth.config.ts +0 -0
  88. package/templates/Projects/default/src/lib/auth/disconnect.ts +0 -11
  89. package/templates/Projects/default/src/lib/auth/isConnected.ts +0 -18
  90. package/templates/Projects/default/src/lib/sample/example.ts +0 -3
  91. package/templates/Projects/default/src/lib/sample/index.ts +0 -3
  92. package/templates/Projects/default/src/ui/Login/page-ui.tsx +0 -22
  93. package/templates/Projects/default/src/ui/Register/page-ui.tsx +0 -26
  94. package/templates/Projects/default/src/ui/Settings/page-ui.tsx +0 -17
  95. package/templates/Projects/default/src/ui/UserInfo/page-ui.tsx +0 -17
  96. /package/templates/Projects/default/messages/en/{Dashboard.json → dashboard.json} +0 -0
  97. /package/templates/Projects/default/messages/en/{Login.json → login.json} +0 -0
  98. /package/templates/Projects/default/messages/en/{Register.json → register.json} +0 -0
  99. /package/templates/Projects/default/messages/en/{Settings.json → settings.json} +0 -0
  100. /package/templates/Projects/default/messages/en/{UserInfo.json → userInfo.json} +0 -0
  101. /package/templates/Projects/default/messages/fr/{Dashboard.json → dashboard.json} +0 -0
  102. /package/templates/Projects/default/messages/fr/{Login.json → login.json} +0 -0
  103. /package/templates/Projects/default/messages/fr/{Register.json → register.json} +0 -0
  104. /package/templates/Projects/default/messages/fr/{Settings.json → settings.json} +0 -0
  105. /package/templates/Projects/default/messages/fr/{UserInfo.json → userInfo.json} +0 -0
  106. /package/templates/Projects/default/public/{cnp-logo.png → logo.png} +0 -0
  107. /package/templates/Projects/default/src/ui/{Dashboard → dashboard}/WelcomeCard.tsx +0 -0
package/dist/bin.bun.js CHANGED
@@ -5,21 +5,35 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
34
  var __require = import.meta.require;
21
35
 
22
- // node_modules/prompts/node_modules/kleur/index.js
36
+ // node_modules/kleur/index.js
23
37
  var require_kleur = __commonJS((exports, module) => {
24
38
  var { FORCE_COLOR, NODE_DISABLE_COLORS, TERM } = process.env;
25
39
  var $ = {
@@ -243,7 +257,7 @@ var require_clear = __commonJS((exports, module) => {
243
257
  if (it)
244
258
  o = it;
245
259
  var i = 0;
246
- var F = function F() {};
260
+ var F = function F2() {};
247
261
  return { s: F, n: function n() {
248
262
  if (i >= o.length)
249
263
  return { done: true };
@@ -2575,7 +2589,7 @@ var require_dist = __commonJS((exports, module) => {
2575
2589
  if (it)
2576
2590
  o = it;
2577
2591
  var i = 0;
2578
- var F = function F() {};
2592
+ var F = function F2() {};
2579
2593
  return { s: F, n: function n() {
2580
2594
  if (i >= o.length)
2581
2595
  return { done: true };
@@ -2678,7 +2692,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
2678
2692
  }
2679
2693
  return question2.format ? yield question2.format(answer2, answers) : answer2;
2680
2694
  });
2681
- return function getFormattedAnswer(_x, _x2) {
2695
+ return function getFormattedAnswer2(_x, _x2) {
2682
2696
  return _ref.apply(this, arguments);
2683
2697
  };
2684
2698
  }();
@@ -4937,17 +4951,211 @@ var require_prompts3 = __commonJS((exports, module) => {
4937
4951
  });
4938
4952
 
4939
4953
  // src/index.ts
4940
- var import_prompts8 = __toESM(require_prompts3(), 1);
4941
- import fs from "fs";
4942
- import os from "os";
4954
+ import path8 from "path";
4955
+
4956
+ // src/cli/onboarding.ts
4943
4957
  import path from "path";
4944
- import { fileURLToPath as fileURLToPath2 } from "url";
4945
- import { dirname, resolve } from "path";
4958
+ function configDirectory(context) {
4959
+ return context.env.XDG_CONFIG_HOME ? path.join(context.env.XDG_CONFIG_HOME, "create-next-pro") : path.join(context.homeDir, ".config", "create-next-pro");
4960
+ }
4961
+ function configFile(context) {
4962
+ return path.join(configDirectory(context), "config.json");
4963
+ }
4964
+ async function readConfig(context) {
4965
+ try {
4966
+ return JSON.parse(await context.fs.readText(configFile(context)));
4967
+ } catch {
4968
+ return null;
4969
+ }
4970
+ }
4971
+ async function ensureLineInRc(context, target, line) {
4972
+ try {
4973
+ const current = context.fs.exists(target) ? await context.fs.readText(target) : "";
4974
+ if (!current.includes(line))
4975
+ await context.fs.appendText(target, `
4976
+ ${line}
4977
+ `);
4978
+ } catch {}
4979
+ }
4980
+ async function installCompletion(context, shell) {
4981
+ const directory = configDirectory(context);
4982
+ const source = path.join(context.packageRoot, shell === "zsh" ? "create-next-pro-completion.zsh" : "create-next-pro-completion.sh");
4983
+ const target = path.join(directory, `completion.${shell === "zsh" ? "zsh" : "sh"}`);
4984
+ await context.fs.mkdir(directory);
4985
+ await context.fs.copyFile(source, target);
4986
+ const rcFile = path.join(context.homeDir, shell === "zsh" ? ".zshrc" : ".bashrc");
4987
+ await ensureLineInRc(context, rcFile, `source "${target}"`);
4988
+ }
4989
+ async function onboarding(context, version) {
4990
+ context.terminal.log(`\uD83D\uDE80 Welcome to create-next-pro v${version}
4991
+ `);
4992
+ const response = await context.prompt([
4993
+ {
4994
+ type: "select",
4995
+ name: "shell",
4996
+ message: "Which shell do you use?",
4997
+ choices: [
4998
+ { title: "zsh", value: "zsh" },
4999
+ { title: "bash", value: "bash" }
5000
+ ],
5001
+ initial: context.env.SHELL?.includes("zsh") ? 0 : 1
5002
+ },
5003
+ {
5004
+ type: "toggle",
5005
+ name: "completion",
5006
+ message: "Install autocompletion?",
5007
+ initial: true,
5008
+ active: "Yes",
5009
+ inactive: "No"
5010
+ }
5011
+ ], { onCancel: () => false });
5012
+ if (response.shell !== "bash" && response.shell !== "zsh") {
5013
+ throw new Error("Configuration cancelled.");
5014
+ }
5015
+ const now = new Date().toISOString();
5016
+ const config = {
5017
+ version: 1,
5018
+ shell: response.shell,
5019
+ completionInstalled: Boolean(response.completion),
5020
+ createdAt: now,
5021
+ updatedAt: now
5022
+ };
5023
+ if (config.completionInstalled)
5024
+ await installCompletion(context, config.shell);
5025
+ await context.fs.mkdir(configDirectory(context));
5026
+ await context.fs.writeText(configFile(context), JSON.stringify(config, null, 2));
5027
+ context.terminal.log(`
5028
+ \u2705 Configuration saved.`);
5029
+ context.terminal.log("you can now use the CLI ! ex : ");
5030
+ context.terminal.log(" Without prompt (will change in future) :");
5031
+ context.terminal.log(" create-next-pro my-next-project");
5032
+ context.terminal.log(" With prompt :");
5033
+ context.terminal.log(" create-next-pro");
5034
+ context.terminal.log("For more information, visit: https://github.com/Rising-Corporation/create-next-pro-cli");
5035
+ context.terminal.log("Happy coding! \uD83C\uDF89");
5036
+ return config;
5037
+ }
4946
5038
 
4947
- // src/lib/addComponent.ts
4948
- var import_prompts = __toESM(require_prompts3(), 1);
5039
+ // src/cli/completion.ts
5040
+ import { readdir as readdir2 } from "fs/promises";
5041
+ import path3 from "path";
5042
+
5043
+ // src/core/page-catalog.ts
5044
+ import { readdir } from "fs/promises";
5045
+ import path2 from "path";
5046
+ function isRouteGroup(segment) {
5047
+ return segment.startsWith("(") && segment.endsWith(")");
5048
+ }
5049
+ async function discoverPages(projectRoot) {
5050
+ const appRoot = path2.join(projectRoot, "src", "app", "[locale]");
5051
+ const candidates = [];
5052
+ async function visit(directory, relative = []) {
5053
+ let entries;
5054
+ try {
5055
+ entries = await readdir(directory, { withFileTypes: true });
5056
+ } catch {
5057
+ return;
5058
+ }
5059
+ if (entries.some((entry) => entry.isFile() && entry.name === "page.tsx")) {
5060
+ const routeSegments = relative.filter((segment) => !isRouteGroup(segment));
5061
+ if (routeSegments.length > 0 && !routeSegments.some((segment) => segment.startsWith("_") || segment.startsWith("["))) {
5062
+ const logicalName = routeSegments.join(".");
5063
+ candidates.push({
5064
+ logicalName,
5065
+ routeSegments,
5066
+ routeDirectory: directory,
5067
+ uiDirectory: path2.join(projectRoot, "src", "ui", ...routeSegments),
5068
+ messageFile: path2.join(projectRoot, "messages", "{locale}", `${routeSegments[0]}.json`),
5069
+ messageKey: routeSegments.length > 1 ? routeSegments.at(-1) : undefined
5070
+ });
5071
+ }
5072
+ }
5073
+ for (const entry of entries) {
5074
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
5075
+ await visit(path2.join(directory, entry.name), [
5076
+ ...relative,
5077
+ entry.name
5078
+ ]);
5079
+ }
5080
+ }
5081
+ }
5082
+ await visit(appRoot);
5083
+ return candidates.sort((left, right) => left.logicalName.localeCompare(right.logicalName));
5084
+ }
5085
+
5086
+ // src/cli/completion.ts
5087
+ var PUBLIC_COMMANDS = [
5088
+ "addpage",
5089
+ "addcomponent",
5090
+ "addlib",
5091
+ "addapi",
5092
+ "addlanguage",
5093
+ "addtext",
5094
+ "rmpage",
5095
+ "--help",
5096
+ "--version",
5097
+ "--reconfigure"
5098
+ ];
5099
+ var OPTIONS = {
5100
+ addpage: [
5101
+ "--layout",
5102
+ "--page",
5103
+ "--loading",
5104
+ "--not-found",
5105
+ "--error",
5106
+ "--global-error",
5107
+ "--route",
5108
+ "--template",
5109
+ "--default"
5110
+ ],
5111
+ addcomponent: ["--page", "-P"]
5112
+ };
5113
+ async function directories(root) {
5114
+ try {
5115
+ return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name).sort();
5116
+ } catch {
5117
+ return [];
5118
+ }
5119
+ }
5120
+ async function completionCandidates(command, context) {
5121
+ if (!command)
5122
+ return [...PUBLIC_COMMANDS];
5123
+ if (command === "rmpage") {
5124
+ return (await discoverPages(context.cwd)).map((candidate) => candidate.logicalName);
5125
+ }
5126
+ if (command === "addcomponent" || command === "addpage") {
5127
+ return [
5128
+ ...OPTIONS[command] ?? [],
5129
+ ...await directories(path3.join(context.cwd, "src", "ui"))
5130
+ ];
5131
+ }
5132
+ if (command === "addlanguage")
5133
+ return ["de", "en", "es", "fr", "it", "ja", "pt"];
5134
+ return OPTIONS[command] ?? [];
5135
+ }
5136
+ async function printCompletions(args, context) {
5137
+ for (const candidate of await completionCandidates(args[1], context)) {
5138
+ context.terminal.log(candidate);
5139
+ }
5140
+ }
5141
+
5142
+ // src/core/contracts.ts
5143
+ var success = () => ({ exitCode: 0 });
5144
+
5145
+ class CliError extends Error {
5146
+ exitCode;
5147
+ constructor(message, exitCode = 1) {
5148
+ super(message);
5149
+ this.exitCode = exitCode;
5150
+ this.name = "CliError";
5151
+ }
5152
+ }
5153
+
5154
+ // src/lib/addApi.ts
5155
+ var import_prompts2 = __toESM(require_prompts3(), 1);
4949
5156
  import { join as join2 } from "path";
4950
- import { mkdir, readFile as readFile2, writeFile, readdir } from "fs/promises";
5157
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
5158
+ import { existsSync as existsSync3 } from "fs";
4951
5159
 
4952
5160
  // src/lib/utils.ts
4953
5161
  import { readFile } from "fs/promises";
@@ -4956,8 +5164,12 @@ import { join } from "path";
4956
5164
  function capitalize(str) {
4957
5165
  return str.charAt(0).toUpperCase() + str.slice(1);
4958
5166
  }
4959
- async function loadConfig() {
4960
- const configPath = join(process.cwd(), "cnp.config.json");
5167
+ function configuredAliasPrefix(config) {
5168
+ const alias = config.importAlias ?? "@/*";
5169
+ return alias.endsWith("/*") ? alias.slice(0, -2) : "@";
5170
+ }
5171
+ async function loadConfig(cwd = process.cwd()) {
5172
+ const configPath = join(cwd, "cnp.config.json");
4961
5173
  if (!existsSync(configPath))
4962
5174
  return null;
4963
5175
  try {
@@ -4992,21 +5204,193 @@ function toFileName(key) {
4992
5204
  }
4993
5205
  }
4994
5206
 
5207
+ // src/runtime/node-context.ts
5208
+ var import_prompts = __toESM(require_prompts3(), 1);
5209
+ import { existsSync as existsSync2 } from "fs";
5210
+ import {
5211
+ appendFile,
5212
+ copyFile,
5213
+ mkdir,
5214
+ readFile as readFile2,
5215
+ writeFile
5216
+ } from "fs/promises";
5217
+ import os from "os";
5218
+ import path4 from "path";
5219
+ import { fileURLToPath } from "url";
5220
+ function findPackageRoot(start) {
5221
+ let current = start;
5222
+ while (true) {
5223
+ const packagePath = path4.join(current, "package.json");
5224
+ if (existsSync2(packagePath))
5225
+ return current;
5226
+ const parent = path4.dirname(current);
5227
+ if (parent === current) {
5228
+ throw new Error(`Unable to locate package.json from ${start}`);
5229
+ }
5230
+ current = parent;
5231
+ }
5232
+ }
5233
+ function resolvePackageRoot(metaUrl = import.meta.url) {
5234
+ return findPackageRoot(path4.dirname(fileURLToPath(metaUrl)));
5235
+ }
5236
+ function createNodeContext(overrides = {}) {
5237
+ return {
5238
+ argv: process.argv.slice(2),
5239
+ cwd: process.cwd(),
5240
+ env: process.env,
5241
+ homeDir: os.homedir(),
5242
+ packageRoot: resolvePackageRoot(),
5243
+ terminal: console,
5244
+ prompt: import_prompts.default,
5245
+ fs: {
5246
+ exists: existsSync2,
5247
+ readText: (target) => readFile2(target, "utf8"),
5248
+ writeText: async (target, content) => {
5249
+ await writeFile(target, content);
5250
+ },
5251
+ mkdir: async (target) => {
5252
+ await mkdir(target, { recursive: true });
5253
+ },
5254
+ copyFile: async (source, target) => {
5255
+ await copyFile(source, target);
5256
+ },
5257
+ appendText: async (target, content) => {
5258
+ await appendFile(target, content);
5259
+ }
5260
+ },
5261
+ ...overrides
5262
+ };
5263
+ }
5264
+
5265
+ // src/core/project-paths.ts
5266
+ import path5 from "path";
5267
+ import { lstat } from "fs/promises";
5268
+ var SAFE_SEGMENT = /^[A-Za-z][A-Za-z0-9_-]*$/;
5269
+ function hasControlCharacters(value) {
5270
+ return [...value].some((character) => {
5271
+ const code = character.charCodeAt(0);
5272
+ return code <= 31 || code === 127;
5273
+ });
5274
+ }
5275
+ function parseLogicalName(value, label = "name") {
5276
+ if (!value || hasControlCharacters(value)) {
5277
+ throw new CliError(`Invalid ${label}: a non-empty printable value is required.`);
5278
+ }
5279
+ if (path5.isAbsolute(value) || value.includes("/") || value.includes("\\")) {
5280
+ throw new CliError(`Invalid ${label}: paths and separators are not allowed.`);
5281
+ }
5282
+ const segments = value.split(".");
5283
+ if (segments.some((segment) => !SAFE_SEGMENT.test(segment))) {
5284
+ throw new CliError(`Invalid ${label}: use dot-separated alphanumeric segments beginning with a letter.`);
5285
+ }
5286
+ return segments;
5287
+ }
5288
+ function validateProjectName(value) {
5289
+ if (!value || hasControlCharacters(value) || path5.isAbsolute(value) || value === "." || value === ".." || value.includes("/") || value.includes("\\") || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) {
5290
+ throw new CliError("Invalid project name: use letters, numbers, dots, dashes or underscores without path separators.");
5291
+ }
5292
+ return value;
5293
+ }
5294
+ function resolveInside(root, ...segments) {
5295
+ const absoluteRoot = path5.resolve(root);
5296
+ const target = path5.resolve(absoluteRoot, ...segments);
5297
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${path5.sep}`)) {
5298
+ throw new CliError(`Refusing to access a path outside the project: ${target}`);
5299
+ }
5300
+ return target;
5301
+ }
5302
+ async function assertSafeTarget(root, target) {
5303
+ const safeTarget = resolveInside(root, path5.relative(root, target));
5304
+ const relativeSegments = path5.relative(path5.resolve(root), safeTarget).split(path5.sep);
5305
+ let current = path5.resolve(root);
5306
+ for (const segment of relativeSegments) {
5307
+ if (!segment)
5308
+ continue;
5309
+ current = path5.join(current, segment);
5310
+ try {
5311
+ if ((await lstat(current)).isSymbolicLink()) {
5312
+ throw new CliError(`Symbolic links are forbidden in project paths: ${current}`);
5313
+ }
5314
+ } catch (error) {
5315
+ if (error instanceof CliError)
5316
+ throw error;
5317
+ if (error.code !== "ENOENT")
5318
+ throw error;
5319
+ }
5320
+ }
5321
+ return safeTarget;
5322
+ }
5323
+ function normalizeImportAlias(value) {
5324
+ if (!/^[A-Za-z@~][A-Za-z0-9@~_-]*\/\*$/.test(value)) {
5325
+ throw new CliError('Invalid import alias: expected a prefix followed by "/*" (for example "@/*" or "@core/*").');
5326
+ }
5327
+ return value;
5328
+ }
5329
+
5330
+ // src/lib/addApi.ts
5331
+ async function addApi(args, cwd = process.cwd()) {
5332
+ let apiName = args[1];
5333
+ if (!apiName || apiName.startsWith("-")) {
5334
+ const response = await import_prompts2.default.prompt({
5335
+ type: "text",
5336
+ name: "apiName",
5337
+ message: "\uD83D\uDD0C API route name to add:",
5338
+ validate: (name) => name ? true : "API route name is required"
5339
+ });
5340
+ apiName = response.apiName;
5341
+ }
5342
+ const apiSegments = parseLogicalName(apiName, "API route name");
5343
+ const config = await loadConfig(cwd);
5344
+ if (!config) {
5345
+ console.error("\u274C Configuration file cnp.config.json not found. Run this command from the project root.");
5346
+ return;
5347
+ }
5348
+ const apiDir = join2(cwd, "src", "app", "api", ...apiSegments);
5349
+ await assertSafeTarget(cwd, apiDir);
5350
+ if (!existsSync3(apiDir)) {
5351
+ await mkdir2(apiDir, { recursive: true });
5352
+ }
5353
+ const templateDir = join2(resolvePackageRoot(import.meta.url), "templates", "Api");
5354
+ const routeTemplate = join2(templateDir, "route.ts");
5355
+ const routePath = join2(apiDir, "route.ts");
5356
+ if (!existsSync3(routePath)) {
5357
+ if (existsSync3(routeTemplate)) {
5358
+ let content = await readFile3(routeTemplate, "utf-8");
5359
+ content = content.replace(/template/g, apiName);
5360
+ await writeFile2(routePath, content);
5361
+ } else {
5362
+ await writeFile2(routePath, `import { NextResponse } from "next/server";
5363
+
5364
+ export async function GET() {
5365
+ return NextResponse.json({ message: "Hello from ${apiName}" });
5366
+ }
5367
+ `);
5368
+ }
5369
+ console.log(`\uD83D\uDCC4 File created: ${routePath}`);
5370
+ } else {
5371
+ console.log(`\u2139\uFE0F File already exists: ${routePath}`);
5372
+ }
5373
+ console.log(`\u2705 API route "${apiName}" added.`);
5374
+ }
5375
+
4995
5376
  // src/lib/addComponent.ts
4996
- import { existsSync as existsSync2, statSync } from "fs";
4997
- async function addComponent(args) {
5377
+ var import_prompts3 = __toESM(require_prompts3(), 1);
5378
+ import { join as join3 } from "path";
5379
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3, readdir as readdir3 } from "fs/promises";
5380
+ import { existsSync as existsSync4, statSync } from "fs";
5381
+ async function addComponent(args, cwd = process.cwd()) {
4998
5382
  let componentName = args[1];
4999
5383
  let pageScope = null;
5000
- let pageIndex = args.findIndex((arg) => arg === "-P" || arg === "--page");
5384
+ const pageIndex = args.findIndex((arg) => arg === "-P" || arg === "--page");
5001
5385
  if (pageIndex !== -1 && args[pageIndex + 1]) {
5002
5386
  pageScope = args[pageIndex + 1];
5003
5387
  }
5004
5388
  let nestedPath = null;
5005
5389
  if (pageScope && pageScope.includes(".")) {
5006
- nestedPath = join2(...pageScope.split("."));
5390
+ nestedPath = join3(...pageScope.split("."));
5007
5391
  }
5008
5392
  if (!componentName || componentName.startsWith("-")) {
5009
- const response = await import_prompts.default.prompt({
5393
+ const response = await import_prompts3.default.prompt({
5010
5394
  type: "text",
5011
5395
  name: "componentName",
5012
5396
  message: "\uD83E\uDDE9 Component name to add:",
@@ -5014,18 +5398,21 @@ async function addComponent(args) {
5014
5398
  });
5015
5399
  componentName = response.componentName;
5016
5400
  }
5017
- const config = await loadConfig();
5401
+ parseLogicalName(componentName, "component name");
5402
+ if (pageScope)
5403
+ parseLogicalName(pageScope, "page name");
5404
+ const config = await loadConfig(cwd);
5018
5405
  if (!config) {
5019
5406
  console.error("\u274C Configuration file cnp.config.json not found. Run this command from the project root.");
5020
5407
  return;
5021
5408
  }
5022
5409
  const useI18n = !!config.useI18n;
5023
5410
  const componentNameUpper = capitalize(componentName);
5024
- const templatePath = join2(new URL("..", import.meta.url).pathname, "templates", "Component");
5411
+ const templatePath = join3(resolvePackageRoot(import.meta.url), "templates", "Component");
5025
5412
  let messagesPath = null;
5026
5413
  if (useI18n) {
5027
- messagesPath = join2(process.cwd(), "messages");
5028
- if (!existsSync2(messagesPath)) {
5414
+ messagesPath = join3(cwd, "messages");
5415
+ if (!existsSync4(messagesPath)) {
5029
5416
  console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5030
5417
  return;
5031
5418
  }
@@ -5034,56 +5421,65 @@ async function addComponent(args) {
5034
5421
  let translationKey;
5035
5422
  if (pageScope) {
5036
5423
  if (nestedPath) {
5037
- componentTargetPath = join2(process.cwd(), "src", "ui", nestedPath);
5424
+ componentTargetPath = join3(cwd, "src", "ui", nestedPath);
5038
5425
  translationKey = pageScope;
5039
5426
  } else {
5040
- componentTargetPath = join2(process.cwd(), "src", "ui", pageScope);
5427
+ componentTargetPath = join3(cwd, "src", "ui", pageScope);
5041
5428
  translationKey = pageScope;
5042
5429
  }
5043
5430
  } else {
5044
- componentTargetPath = join2(process.cwd(), "src", "ui", "_global");
5431
+ componentTargetPath = join3(cwd, "src", "ui", "_global");
5045
5432
  translationKey = "_global_ui";
5046
5433
  }
5047
- if (!existsSync2(componentTargetPath)) {
5048
- await mkdir(componentTargetPath, { recursive: true });
5434
+ await assertSafeTarget(cwd, componentTargetPath);
5435
+ if (!existsSync4(componentTargetPath)) {
5436
+ await mkdir3(componentTargetPath, { recursive: true });
5049
5437
  }
5050
- const componentFile = join2(componentTargetPath, `${componentNameUpper}.tsx`);
5051
- const templateComponentPath = join2(templatePath, "Component.tsx");
5052
- if (existsSync2(templateComponentPath)) {
5053
- let content = await readFile2(templateComponentPath, "utf-8");
5438
+ const componentFile = join3(componentTargetPath, `${componentNameUpper}.tsx`);
5439
+ const templateComponentPath = join3(templatePath, "Component.tsx");
5440
+ if (existsSync4(templateComponentPath)) {
5441
+ let content = await readFile4(templateComponentPath, "utf-8");
5054
5442
  content = content.replace(/Component/g, componentNameUpper).replace(/componentPage/g, translationKey);
5055
- await writeFile(componentFile, content);
5443
+ await writeFile3(componentFile, content);
5056
5444
  console.log(`\uD83D\uDCC4 File created: ${componentFile}`);
5057
5445
  } else {
5058
5446
  console.error("\u274C Template Component.tsx introuvable :", templateComponentPath);
5059
5447
  }
5060
5448
  if (useI18n && messagesPath) {
5061
- const entries = await readdir(messagesPath, { withFileTypes: true });
5449
+ const entries = await readdir3(messagesPath, { withFileTypes: true });
5062
5450
  const langDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5063
- const jsonTemplate = join2(templatePath, "component.json");
5064
- if (!existsSync2(jsonTemplate)) {
5451
+ const jsonTemplate = join3(templatePath, "component.json");
5452
+ if (!existsSync4(jsonTemplate)) {
5065
5453
  console.error("\u274C Template component.json not found:", jsonTemplate);
5066
5454
  return;
5067
5455
  }
5068
- const jsonContent = await readFile2(jsonTemplate, "utf-8");
5456
+ const jsonContent = await readFile4(jsonTemplate, "utf-8");
5069
5457
  const parsed = JSON.parse(jsonContent);
5070
5458
  for (const locale of langDirs) {
5071
- const localeDir = join2(messagesPath, locale);
5072
- if (!existsSync2(localeDir) || !statSync(localeDir).isDirectory())
5459
+ const localeDir = join3(messagesPath, locale);
5460
+ if (!existsSync4(localeDir) || !statSync(localeDir).isDirectory())
5073
5461
  continue;
5074
5462
  let jsonTarget;
5075
5463
  if (pageScope) {
5076
- jsonTarget = join2(messagesPath, locale, `${pageScope}.json`);
5464
+ const [messageFile] = pageScope.split(".");
5465
+ jsonTarget = join3(messagesPath, locale, `${messageFile}.json`);
5077
5466
  } else {
5078
- jsonTarget = join2(messagesPath, locale, `_global_ui.json`);
5467
+ jsonTarget = join3(messagesPath, locale, `_global_ui.json`);
5079
5468
  }
5080
5469
  let current = {};
5081
- if (existsSync2(jsonTarget)) {
5082
- const jsonFile = await readFile2(jsonTarget, "utf-8");
5470
+ if (existsSync4(jsonTarget)) {
5471
+ const jsonFile = await readFile4(jsonTarget, "utf-8");
5083
5472
  current = JSON.parse(jsonFile);
5084
5473
  }
5085
- current[componentNameUpper] = parsed;
5086
- await writeFile(jsonTarget, JSON.stringify(current, null, 2));
5474
+ if (pageScope?.includes(".")) {
5475
+ const child = pageScope.split(".")[1];
5476
+ const childMessages = current[child] && typeof current[child] === "object" ? current[child] : {};
5477
+ childMessages[componentNameUpper] = parsed;
5478
+ current[child] = childMessages;
5479
+ } else {
5480
+ current[componentNameUpper] = parsed;
5481
+ }
5482
+ await writeFile3(jsonTarget, JSON.stringify(current, null, 2));
5087
5483
  console.log(`\uD83D\uDCC4 File updated: ${jsonTarget}`);
5088
5484
  }
5089
5485
  } else {
@@ -5092,230 +5488,129 @@ async function addComponent(args) {
5092
5488
  console.log(`\u2705 Component "${componentNameUpper}" added ${pageScope ? `to page ${pageScope}` : "globally"}${useI18n ? " with localized messages" : ""}.`);
5093
5489
  }
5094
5490
 
5095
- // src/lib/addPage.ts
5096
- var import_prompts2 = __toESM(require_prompts3(), 1);
5097
- import { join as join3 } from "path";
5098
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
5099
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
5100
- async function addPage(args) {
5101
- let pageName = args[1];
5102
- if (!pageName || pageName.startsWith("-")) {
5103
- const response = await import_prompts2.default.prompt({
5104
- type: "text",
5105
- name: "pageName",
5106
- message: "\uD83D\uDCDD Page name to add:",
5107
- validate: (name) => name ? true : "Page name is required"
5108
- });
5109
- pageName = response.pageName;
5110
- }
5111
- let parentName = null;
5112
- let childName = null;
5113
- if (pageName.includes(".")) {
5114
- [parentName, childName] = pageName.split(".");
5115
- }
5116
- let shortFlags = args.find((arg) => /^-[A-Za-z]+$/.test(arg));
5117
- let longFlags = new Set(args.filter((a) => a.startsWith("--")));
5118
- const flags = new Set;
5119
- if (!shortFlags && Array.from(longFlags).length === 0) {
5120
- shortFlags = "-LPl";
5121
- }
5122
- if (shortFlags) {
5123
- for (const char of shortFlags.slice(1)) {
5124
- switch (char) {
5125
- case "L":
5126
- flags.add("layout");
5127
- break;
5128
- case "P":
5129
- flags.add("page");
5130
- break;
5131
- case "l":
5132
- flags.add("loading");
5133
- break;
5134
- case "n":
5135
- flags.add("not-found");
5136
- break;
5137
- case "e":
5138
- flags.add("error");
5139
- break;
5140
- case "g":
5141
- flags.add("global-error");
5142
- break;
5143
- case "r":
5144
- flags.add("route");
5145
- break;
5146
- case "t":
5147
- flags.add("template");
5148
- break;
5149
- case "d":
5150
- flags.add("default");
5151
- break;
5152
- }
5491
+ // src/lib/addLanguage.ts
5492
+ var import_prompts4 = __toESM(require_prompts3(), 1);
5493
+ import { join as join4 } from "path";
5494
+ import { existsSync as existsSync5 } from "fs";
5495
+ import { cp, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
5496
+ function generateLocales() {
5497
+ const dn = new Intl.DisplayNames(["en"], { type: "language" });
5498
+ const locales = [];
5499
+ for (let i = 0;i < 26; i++) {
5500
+ for (let j = 0;j < 26; j++) {
5501
+ const code = String.fromCharCode(97 + i) + String.fromCharCode(97 + j);
5502
+ try {
5503
+ const name = dn.of(code);
5504
+ if (name && name.toLowerCase() !== code) {
5505
+ locales.push(code);
5506
+ }
5507
+ } catch {}
5153
5508
  }
5154
5509
  }
5155
- for (const flag of [
5156
- "layout",
5157
- "page",
5158
- "loading",
5159
- "not-found",
5160
- "error",
5161
- "global-error",
5162
- "route",
5163
- "template",
5164
- "default"
5165
- ]) {
5166
- if (longFlags.has("--" + flag))
5167
- flags.add(flag);
5168
- }
5169
- const config = await loadConfig();
5170
- if (!config) {
5171
- console.error("\u274C Configuration file cnp.config.json not found. Run this command from the project root.");
5510
+ return locales.sort();
5511
+ }
5512
+ async function addLanguage(args, cwd = process.cwd()) {
5513
+ const config = await loadConfig(cwd);
5514
+ if (!config?.useI18n) {
5515
+ console.error("\u274C i18n is not enabled in this project.");
5172
5516
  return;
5173
5517
  }
5174
- const useI18n = !!config.useI18n;
5175
- const srcSegments = ["src", "app"];
5176
- if (useI18n)
5177
- srcSegments.push("[locale]");
5178
- const srcPath = join3(process.cwd(), ...srcSegments);
5179
- if (!existsSync3(srcPath)) {
5180
- console.error(`\u274C Expected directory not found: ${srcPath}`);
5518
+ const messagesPath = join4(cwd, "messages");
5519
+ if (!existsSync5(messagesPath)) {
5520
+ console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5181
5521
  return;
5182
5522
  }
5183
- let messagesPath = null;
5184
- let locales = [];
5185
- if (useI18n) {
5186
- messagesPath = join3(process.cwd(), "messages");
5187
- if (!existsSync3(messagesPath)) {
5188
- console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5189
- return;
5190
- }
5191
- const entries = await readdir2(messagesPath, { withFileTypes: true });
5192
- locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5523
+ const available = generateLocales();
5524
+ let locale = args[1];
5525
+ if (!locale || !available.includes(locale)) {
5526
+ const response = await import_prompts4.default({
5527
+ type: "autocomplete",
5528
+ name: "locale",
5529
+ message: "\uD83C\uDF10 Locale to add:",
5530
+ choices: available.map((l) => ({ title: l, value: l }))
5531
+ });
5532
+ locale = response.locale;
5193
5533
  }
5194
- const templatePath = join3(new URL("..", import.meta.url).pathname, "templates", "Page");
5195
- let uiPageDir, localePagePath, jsonFileName;
5196
- if (parentName && childName) {
5197
- uiPageDir = join3(process.cwd(), "src", "ui", parentName, childName);
5198
- localePagePath = join3(srcPath, parentName, childName);
5199
- jsonFileName = parentName;
5200
- } else {
5201
- uiPageDir = join3(process.cwd(), "src", "ui", pageName);
5202
- localePagePath = join3(srcPath, pageName);
5203
- jsonFileName = pageName;
5534
+ if (!locale)
5535
+ return;
5536
+ parseLogicalName(locale, "locale");
5537
+ await assertSafeTarget(cwd, messagesPath);
5538
+ if (existsSync5(join4(messagesPath, locale))) {
5539
+ throw new Error(`Locale ${locale} already exists.`);
5204
5540
  }
5205
- if (!existsSync3(uiPageDir)) {
5206
- await mkdir2(uiPageDir, { recursive: true });
5541
+ const routingFile = join4(cwd, "src", "lib", "i18n", "routing.ts");
5542
+ const messagesRegistryFile = join4(cwd, "src", "lib", "i18n", "messages.ts");
5543
+ if (!existsSync5(routingFile)) {
5544
+ throw new Error("routing.ts not found. Are you in project root?");
5207
5545
  }
5208
- const uiPageFile = join3(uiPageDir, "page-ui.tsx");
5209
- const uiPageTemplate = join3(templatePath, "page-ui.tsx");
5210
- if (existsSync3(uiPageTemplate)) {
5211
- let uiContent = await readFile3(uiPageTemplate, "utf-8");
5212
- uiContent = uiContent.replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5213
- await writeFile2(uiPageFile, uiContent);
5214
- console.log(`\uD83D\uDCC4 File created: ${uiPageFile}`);
5215
- } else {
5216
- console.warn("\u26A0\uFE0F Missing template file: page-ui.tsx at path:", uiPageTemplate);
5546
+ if (!existsSync5(messagesRegistryFile)) {
5547
+ throw new Error("messages.ts not found. Are you in project root?");
5217
5548
  }
5218
- if (!existsSync3(localePagePath)) {
5219
- await mkdir2(localePagePath, { recursive: true });
5549
+ const routingContent = await readFile5(routingFile, "utf-8");
5550
+ const defaultMatch = routingContent.match(/defaultLocale:\s*"([^"]+)"/);
5551
+ const defaultLocale = defaultMatch ? defaultMatch[1] : null;
5552
+ if (!defaultLocale || !existsSync5(join4(messagesPath, defaultLocale))) {
5553
+ throw new Error("Default locale not found.");
5220
5554
  }
5221
- for (const flag of flags) {
5222
- const filename = toFileName(flag);
5223
- const src = join3(templatePath, filename);
5224
- const dst = join3(localePagePath, filename);
5225
- if (!existsSync3(src)) {
5226
- console.warn(`\u26A0\uFE0F Missing template file: ${filename} at path: ${src}`);
5227
- continue;
5228
- }
5229
- const content = await readFile3(src, "utf-8");
5230
- const replaced = content.replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5231
- await writeFile2(dst, replaced);
5232
- console.log(`\uD83D\uDCC4 File created: ${dst}`);
5555
+ const defaultAggregatorFile = join4(messagesPath, `${defaultLocale}.ts`);
5556
+ if (!existsSync5(defaultAggregatorFile)) {
5557
+ throw new Error(`Default locale aggregator not found: ${defaultLocale}.ts`);
5233
5558
  }
5234
- if (useI18n && messagesPath) {
5235
- const jsonTemplate = join3(templatePath, "page.json");
5236
- if (!existsSync3(jsonTemplate)) {
5237
- console.warn("\u26A0\uFE0F Missing template: page.json at path:", jsonTemplate);
5238
- } else {
5239
- const content = await readFile3(jsonTemplate, "utf-8");
5240
- const replaced = content.replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5241
- for (const locale of locales) {
5242
- const localeDir = join3(messagesPath, locale);
5243
- if (!existsSync3(localeDir) || !statSync2(localeDir).isDirectory())
5244
- continue;
5245
- const jsonTarget = join3(messagesPath, locale, `${jsonFileName}.json`);
5246
- let current = {};
5247
- if (existsSync3(jsonTarget)) {
5248
- const jsonFile = await readFile3(jsonTarget, "utf-8");
5249
- try {
5250
- current = JSON.parse(jsonFile);
5251
- } catch {
5252
- current = {};
5253
- }
5254
- }
5255
- if (parentName && childName) {
5256
- current[childName] = JSON.parse(replaced);
5257
- } else {
5258
- current = JSON.parse(replaced);
5259
- }
5260
- await writeFile2(jsonTarget, JSON.stringify(current, null, 2));
5261
- console.log(`\uD83D\uDCC4 File created: ${jsonTarget}`);
5262
- }
5263
- }
5264
- } else {
5265
- console.log("\u2139\uFE0F Skipping translation templates; next-intl not enabled.");
5266
- }
5267
- console.log(`\u2705 Page "${pageName}" with templates added${useI18n ? " for each locale" : ""}.`);
5268
- }
5559
+ const localesMatch = routingContent.match(/locales:\s*\[([^\]]*)\]/);
5560
+ if (!localesMatch) {
5561
+ throw new Error("Unable to locate routing locales.");
5562
+ }
5563
+ const localesArr = localesMatch[1].split(",").map((s) => s.trim().replace(/["']/g, "")).filter(Boolean);
5564
+ if (localesArr.includes(locale)) {
5565
+ throw new Error(`Locale ${locale} already exists in routing.`);
5566
+ }
5567
+ const newLocales = `locales: [${[...localesArr, locale].map((item) => `"${item}"`).join(", ")}]`;
5568
+ const nextRoutingContent = routingContent.replace(/locales:\s*\[[^\]]*\]/, newLocales);
5569
+ const defaultAggregatorContent = await readFile5(defaultAggregatorFile, "utf-8");
5570
+ const defaultImportPrefix = `./${defaultLocale}/`;
5571
+ if (!defaultAggregatorContent.includes(defaultImportPrefix)) {
5572
+ throw new Error(`Default locale aggregator does not import from ${defaultImportPrefix}`);
5573
+ }
5574
+ const nextAggregatorContent = defaultAggregatorContent.replaceAll(defaultImportPrefix, `./${locale}/`);
5575
+ const messagesRegistryContent = await readFile5(messagesRegistryFile, "utf-8");
5576
+ const registryMatch = messagesRegistryContent.match(/const messages = \{([^}]*)\} as const;/);
5577
+ if (!registryMatch) {
5578
+ throw new Error("Unable to locate the typed messages registry.");
5579
+ }
5580
+ const registeredLocales = registryMatch[1].split(",").map((item) => item.trim()).filter(Boolean);
5581
+ if (registeredLocales.includes(locale)) {
5582
+ throw new Error(`Locale ${locale} already exists in messages registry.`);
5583
+ }
5584
+ const registryDeclaration = `const messages = { ${[
5585
+ ...registeredLocales,
5586
+ locale
5587
+ ].join(", ")} } as const;`;
5588
+ const declarationIndex = messagesRegistryContent.indexOf("const messages =");
5589
+ const nextMessagesRegistryContent = messagesRegistryContent.slice(0, declarationIndex) + `import ${locale} from "../../../messages/${locale}";
5269
5590
 
5270
- // src/lib/rmPage.ts
5271
- var import_prompts3 = __toESM(require_prompts3(), 1);
5272
- import { join as join4 } from "path";
5273
- import { writeFile as writeFile3, readdir as readdir3 } from "fs/promises";
5274
- import { existsSync as existsSync4 } from "fs";
5275
- async function rmPage(args) {
5276
- let pageName = args[1];
5277
- if (!pageName || pageName.startsWith("-")) {
5278
- const response = await import_prompts3.default.prompt({
5279
- type: "text",
5280
- name: "pageName",
5281
- message: "\uD83D\uDDD1\uFE0F Page name to remove:",
5282
- validate: (name) => name ? true : "Page name is required"
5283
- });
5284
- pageName = response.pageName;
5285
- }
5286
- const messagesPath = join4(process.cwd(), "messages");
5287
- const entries = await readdir3(messagesPath, { withFileTypes: true });
5288
- const langDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5289
- for (const locale of langDirs) {
5290
- const jsonTarget = join4(messagesPath, locale, `${pageName}.json`);
5291
- if (existsSync4(jsonTarget)) {
5292
- await writeFile3(jsonTarget, "");
5293
- await import("child_process").then((cp) => cp.execSync(`rm -f '${jsonTarget}'`));
5294
- console.log(`\uD83D\uDDD1\uFE0F Deleted: ${jsonTarget}`);
5295
- }
5296
- }
5297
- const uiPageDir = join4(process.cwd(), "src", "ui", pageName);
5298
- if (existsSync4(uiPageDir)) {
5299
- await import("child_process").then((cp) => cp.execSync(`rm -rf '${uiPageDir}'`));
5300
- console.log(`\uD83D\uDDD1\uFE0F Deleted: ${uiPageDir}`);
5301
- }
5302
- const appLocaleDir = join4(process.cwd(), "src", "app", "[locale]", pageName);
5303
- if (existsSync4(appLocaleDir)) {
5304
- await import("child_process").then((cp) => cp.execSync(`rm -rf '${appLocaleDir}'`));
5305
- console.log(`\uD83D\uDDD1\uFE0F Deleted: ${appLocaleDir}`);
5306
- }
5307
- console.log(`\u2705 Page "${pageName}" deleted.`);
5591
+ ` + messagesRegistryContent.slice(declarationIndex).replace(/const messages = \{[^}]*\} as const;/, registryDeclaration);
5592
+ await cp(join4(messagesPath, defaultLocale), join4(messagesPath, locale), {
5593
+ recursive: true
5594
+ });
5595
+ console.log(`\uD83D\uDCC4 Directory created: ${join4(messagesPath, locale)}`);
5596
+ await writeFile4(join4(messagesPath, `${locale}.ts`), nextAggregatorContent);
5597
+ console.log(`\uD83D\uDCC4 File created: ${join4(messagesPath, `${locale}.ts`)}`);
5598
+ await writeFile4(messagesRegistryFile, nextMessagesRegistryContent);
5599
+ console.log(`\uD83D\uDCC4 File updated: ${messagesRegistryFile}`);
5600
+ await writeFile4(routingFile, nextRoutingContent);
5601
+ console.log(`\uD83D\uDCC4 File updated: ${routingFile}`);
5602
+ console.log(`\u2705 Locale "${locale}" added and copied from default locale "${defaultLocale}".`);
5308
5603
  }
5309
5604
 
5310
5605
  // src/lib/addLib.ts
5311
- var import_prompts4 = __toESM(require_prompts3(), 1);
5606
+ var import_prompts5 = __toESM(require_prompts3(), 1);
5312
5607
  import { join as join5 } from "path";
5313
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
5314
- import { existsSync as existsSync5 } from "fs";
5315
- async function addLib(args) {
5608
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
5609
+ import { existsSync as existsSync6 } from "fs";
5610
+ async function addLib(args, cwd = process.cwd()) {
5316
5611
  let libArg = args[1];
5317
5612
  if (!libArg || libArg.startsWith("-")) {
5318
- const response = await import_prompts4.default.prompt({
5613
+ const response = await import_prompts5.default.prompt({
5319
5614
  type: "text",
5320
5615
  name: "libArg",
5321
5616
  message: "\uD83D\uDCE6 Lib name to add:",
@@ -5323,50 +5618,54 @@ async function addLib(args) {
5323
5618
  });
5324
5619
  libArg = response.libArg;
5325
5620
  }
5621
+ const libSegments = parseLogicalName(libArg, "library name");
5326
5622
  let libName = libArg;
5327
5623
  let fileName = null;
5328
- if (libArg.includes(".")) {
5329
- [libName, fileName] = libArg.split(".");
5624
+ if (libSegments.length === 2) {
5625
+ [libName, fileName] = libSegments;
5626
+ } else if (libSegments.length > 2) {
5627
+ throw new Error("Libraries currently support exactly library.module.");
5330
5628
  }
5331
- const config = await loadConfig();
5629
+ const config = await loadConfig(cwd);
5332
5630
  if (!config) {
5333
5631
  console.error("\u274C Configuration file cnp.config.json not found. Run this command from the project root.");
5334
5632
  return;
5335
5633
  }
5336
- const libDir = join5(process.cwd(), "src", "lib", libName);
5337
- if (!existsSync5(libDir)) {
5338
- await mkdir3(libDir, { recursive: true });
5634
+ const libDir = join5(cwd, "src", "lib", libName);
5635
+ await assertSafeTarget(cwd, libDir);
5636
+ if (!existsSync6(libDir)) {
5637
+ await mkdir4(libDir, { recursive: true });
5339
5638
  }
5340
- const templateDir = join5(new URL("..", import.meta.url).pathname, "templates", "Lib");
5639
+ const templateDir = join5(resolvePackageRoot(import.meta.url), "templates", "Lib");
5341
5640
  const indexTemplate = join5(templateDir, "index.ts");
5342
5641
  const fileTemplate = join5(templateDir, "item.ts");
5343
5642
  const indexPath = join5(libDir, "index.ts");
5344
- if (!existsSync5(indexPath)) {
5345
- if (existsSync5(indexTemplate)) {
5346
- const content = await readFile4(indexTemplate, "utf-8");
5347
- await writeFile4(indexPath, content);
5643
+ if (!existsSync6(indexPath)) {
5644
+ if (existsSync6(indexTemplate)) {
5645
+ const content = await readFile6(indexTemplate, "utf-8");
5646
+ await writeFile5(indexPath, content);
5348
5647
  } else {
5349
- await writeFile4(indexPath, `export {}
5648
+ await writeFile5(indexPath, `export {}
5350
5649
  `);
5351
5650
  }
5352
5651
  console.log(`\uD83D\uDCC4 File created: ${indexPath}`);
5353
5652
  }
5354
5653
  if (fileName) {
5355
5654
  const filePath = join5(libDir, `${fileName}.ts`);
5356
- if (!existsSync5(filePath)) {
5357
- if (existsSync5(fileTemplate)) {
5358
- let content = await readFile4(fileTemplate, "utf-8");
5655
+ if (!existsSync6(filePath)) {
5656
+ if (existsSync6(fileTemplate)) {
5657
+ let content = await readFile6(fileTemplate, "utf-8");
5359
5658
  content = content.replace(/template/g, fileName).replace(/Template/g, capitalize(fileName));
5360
- await writeFile4(filePath, content);
5659
+ await writeFile5(filePath, content);
5361
5660
  } else {
5362
- await writeFile4(filePath, `export function ${fileName}() {
5661
+ await writeFile5(filePath, `export function ${fileName}() {
5363
5662
  // TODO: implement
5364
5663
  }
5365
5664
  `);
5366
5665
  }
5367
5666
  console.log(`\uD83D\uDCC4 File created: ${filePath}`);
5368
5667
  }
5369
- let indexContent = await readFile4(indexPath, "utf-8");
5668
+ let indexContent = await readFile6(indexPath, "utf-8");
5370
5669
  const importLine = `import { ${fileName} } from "./${fileName}";`;
5371
5670
  const importRegex = new RegExp(`import\\s*{\\s*${fileName}\\s*}\\s*from\\s*"\\./${fileName}";`);
5372
5671
  const exportRegex = /export\s*{([^}]*)}/m;
@@ -5394,65 +5693,382 @@ async function addLib(args) {
5394
5693
 
5395
5694
  export { ` + exportsSet.join(", ") + ` };
5396
5695
  `;
5397
- await writeFile4(indexPath, indexContent);
5696
+ await writeFile5(indexPath, indexContent);
5398
5697
  console.log(`\u270F\uFE0F Updated index: ${indexPath}`);
5399
5698
  }
5400
5699
  console.log(`\u2705 Lib "${libName}"${fileName ? ` with module ${fileName}` : ""} added.`);
5401
5700
  }
5402
5701
 
5403
- // src/lib/addApi.ts
5404
- var import_prompts5 = __toESM(require_prompts3(), 1);
5702
+ // src/lib/addPage.ts
5703
+ var import_prompts6 = __toESM(require_prompts3(), 1);
5405
5704
  import { join as join6 } from "path";
5406
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5407
- import { existsSync as existsSync6 } from "fs";
5408
- async function addApi(args) {
5409
- let apiName = args[1];
5410
- if (!apiName || apiName.startsWith("-")) {
5411
- const response = await import_prompts5.default.prompt({
5705
+ import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile6, readdir as readdir4 } from "fs/promises";
5706
+ import { existsSync as existsSync7, statSync as statSync2 } from "fs";
5707
+ function registerMessagesFile(content, locale, fileName) {
5708
+ const importPath = `./${locale}/${fileName}.json`;
5709
+ if (content.includes(importPath))
5710
+ return content;
5711
+ const declarationIndex = content.indexOf("const messages =");
5712
+ const registryMatch = content.match(/const messages = \{([\s\S]*?)\n\};/);
5713
+ if (declarationIndex === -1 || !registryMatch) {
5714
+ throw new Error(`Unable to register ${fileName}.json in messages/${locale}.ts`);
5715
+ }
5716
+ const importStatement = `import ${fileName} from "${importPath}";
5717
+ `;
5718
+ const nextRegistry = registryMatch[0].replace(/\n\};$/, `
5719
+ ${fileName},
5720
+ };`);
5721
+ return content.slice(0, declarationIndex) + importStatement + content.slice(declarationIndex).replace(registryMatch[0], nextRegistry);
5722
+ }
5723
+ async function addPage(args, cwd = process.cwd()) {
5724
+ let pageName = args[1];
5725
+ if (!pageName || pageName.startsWith("-")) {
5726
+ const response = await import_prompts6.default.prompt({
5412
5727
  type: "text",
5413
- name: "apiName",
5414
- message: "\uD83D\uDD0C API route name to add:",
5415
- validate: (name) => name ? true : "API route name is required"
5728
+ name: "pageName",
5729
+ message: "\uD83D\uDCDD Page name to add:",
5730
+ validate: (name) => name ? true : "Page name is required"
5416
5731
  });
5417
- apiName = response.apiName;
5732
+ pageName = response.pageName;
5733
+ }
5734
+ const pageSegments = parseLogicalName(pageName, "page name");
5735
+ let parentName = null;
5736
+ let childName = null;
5737
+ if (pageSegments.length === 2) {
5738
+ [parentName, childName] = pageSegments;
5739
+ } else if (pageSegments.length > 2) {
5740
+ throw new Error("Nested pages currently support exactly Parent.Child.");
5741
+ }
5742
+ let shortFlags = args.find((arg) => /^-[A-Za-z]+$/.test(arg));
5743
+ const longFlags = new Set(args.filter((a) => a.startsWith("--")));
5744
+ const flags = new Set;
5745
+ if (!shortFlags && Array.from(longFlags).length === 0) {
5746
+ shortFlags = "-LPl";
5418
5747
  }
5419
- const config = await loadConfig();
5748
+ if (shortFlags) {
5749
+ for (const char of shortFlags.slice(1)) {
5750
+ switch (char) {
5751
+ case "L":
5752
+ flags.add("layout");
5753
+ break;
5754
+ case "P":
5755
+ flags.add("page");
5756
+ break;
5757
+ case "l":
5758
+ flags.add("loading");
5759
+ break;
5760
+ case "n":
5761
+ flags.add("not-found");
5762
+ break;
5763
+ case "e":
5764
+ flags.add("error");
5765
+ break;
5766
+ case "g":
5767
+ flags.add("global-error");
5768
+ break;
5769
+ case "r":
5770
+ flags.add("route");
5771
+ break;
5772
+ case "t":
5773
+ flags.add("template");
5774
+ break;
5775
+ case "d":
5776
+ flags.add("default");
5777
+ break;
5778
+ }
5779
+ }
5780
+ }
5781
+ for (const flag of [
5782
+ "layout",
5783
+ "page",
5784
+ "loading",
5785
+ "not-found",
5786
+ "error",
5787
+ "global-error",
5788
+ "route",
5789
+ "template",
5790
+ "default"
5791
+ ]) {
5792
+ if (longFlags.has("--" + flag))
5793
+ flags.add(flag);
5794
+ }
5795
+ const config = await loadConfig(cwd);
5420
5796
  if (!config) {
5421
5797
  console.error("\u274C Configuration file cnp.config.json not found. Run this command from the project root.");
5422
5798
  return;
5423
5799
  }
5424
- const apiDir = join6(process.cwd(), "src", "app", "api", apiName);
5425
- if (!existsSync6(apiDir)) {
5426
- await mkdir4(apiDir, { recursive: true });
5800
+ const useI18n = !!config.useI18n;
5801
+ const aliasPrefix = configuredAliasPrefix(config);
5802
+ const srcSegments = ["src", "app"];
5803
+ if (useI18n)
5804
+ srcSegments.push("[locale]");
5805
+ const srcPath = join6(cwd, ...srcSegments);
5806
+ if (!existsSync7(srcPath)) {
5807
+ console.error(`\u274C Expected directory not found: ${srcPath}`);
5808
+ return;
5809
+ }
5810
+ let messagesPath = null;
5811
+ let locales = [];
5812
+ if (useI18n) {
5813
+ messagesPath = join6(cwd, "messages");
5814
+ if (!existsSync7(messagesPath)) {
5815
+ console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5816
+ return;
5817
+ }
5818
+ const entries = await readdir4(messagesPath, { withFileTypes: true });
5819
+ locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5427
5820
  }
5428
- const templateDir = join6(new URL("..", import.meta.url).pathname, "templates", "Api");
5429
- const routeTemplate = join6(templateDir, "route.ts");
5430
- const routePath = join6(apiDir, "route.ts");
5431
- if (!existsSync6(routePath)) {
5432
- if (existsSync6(routeTemplate)) {
5433
- let content = await readFile5(routeTemplate, "utf-8");
5434
- content = content.replace(/template/g, apiName);
5435
- await writeFile5(routePath, content);
5821
+ const templatePath = join6(resolvePackageRoot(import.meta.url), "templates", "Page");
5822
+ let uiPageDir, localePagePath, jsonFileName;
5823
+ if (parentName && childName) {
5824
+ uiPageDir = join6(cwd, "src", "ui", parentName, childName);
5825
+ localePagePath = join6(srcPath, parentName, childName);
5826
+ jsonFileName = parentName;
5827
+ } else {
5828
+ uiPageDir = join6(cwd, "src", "ui", pageName);
5829
+ localePagePath = join6(srcPath, pageName);
5830
+ jsonFileName = pageName;
5831
+ }
5832
+ await assertSafeTarget(cwd, uiPageDir);
5833
+ await assertSafeTarget(cwd, localePagePath);
5834
+ if (!existsSync7(uiPageDir)) {
5835
+ await mkdir5(uiPageDir, { recursive: true });
5836
+ }
5837
+ const uiPageFile = join6(uiPageDir, "page-ui.tsx");
5838
+ const uiPageTemplate = join6(templatePath, "page-ui.tsx");
5839
+ if (existsSync7(uiPageTemplate)) {
5840
+ let uiContent = await readFile7(uiPageTemplate, "utf-8");
5841
+ const translationNamespace = parentName && childName ? `${parentName}.${childName}` : pageName;
5842
+ uiContent = uiContent.replace('useTranslations("template")', `useTranslations("${translationNamespace}")`);
5843
+ uiContent = uiContent.replaceAll('from "@/', `from "${aliasPrefix}/`).replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5844
+ await writeFile6(uiPageFile, uiContent);
5845
+ console.log(`\uD83D\uDCC4 File created: ${uiPageFile}`);
5846
+ } else {
5847
+ console.warn("\u26A0\uFE0F Missing template file: page-ui.tsx at path:", uiPageTemplate);
5848
+ }
5849
+ if (!existsSync7(localePagePath)) {
5850
+ await mkdir5(localePagePath, { recursive: true });
5851
+ }
5852
+ for (const flag of flags) {
5853
+ const filename = toFileName(flag);
5854
+ const src = join6(templatePath, filename);
5855
+ const dst = join6(localePagePath, filename);
5856
+ if (!existsSync7(src)) {
5857
+ console.warn(`\u26A0\uFE0F Missing template file: ${filename} at path: ${src}`);
5858
+ continue;
5859
+ }
5860
+ const content = await readFile7(src, "utf-8");
5861
+ const uiImportPath = parentName && childName ? `${parentName}/${childName}` : pageName;
5862
+ const replaced = content.replace("@/ui/template/", `@/ui/${uiImportPath}/`).replaceAll('from "@/', `from "${aliasPrefix}/`).replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5863
+ await writeFile6(dst, replaced);
5864
+ console.log(`\uD83D\uDCC4 File created: ${dst}`);
5865
+ }
5866
+ if (useI18n && messagesPath) {
5867
+ const jsonTemplate = join6(templatePath, "page.json");
5868
+ if (!existsSync7(jsonTemplate)) {
5869
+ console.warn("\u26A0\uFE0F Missing template: page.json at path:", jsonTemplate);
5436
5870
  } else {
5437
- await writeFile5(routePath, `import { NextResponse } from "next/server";
5871
+ const content = await readFile7(jsonTemplate, "utf-8");
5872
+ const replaced = content.replace(/template/g, childName || pageName).replace(/Template/g, capitalize(childName || pageName));
5873
+ for (const locale of locales) {
5874
+ const localeDir = join6(messagesPath, locale);
5875
+ if (!existsSync7(localeDir) || !statSync2(localeDir).isDirectory())
5876
+ continue;
5877
+ const jsonTarget = join6(messagesPath, locale, `${jsonFileName}.json`);
5878
+ let current = {};
5879
+ if (existsSync7(jsonTarget)) {
5880
+ const jsonFile = await readFile7(jsonTarget, "utf-8");
5881
+ try {
5882
+ current = JSON.parse(jsonFile);
5883
+ } catch {
5884
+ current = {};
5885
+ }
5886
+ }
5887
+ if (parentName && childName) {
5888
+ current[childName] = JSON.parse(replaced);
5889
+ } else {
5890
+ current = JSON.parse(replaced);
5891
+ }
5892
+ await writeFile6(jsonTarget, `${JSON.stringify(current, null, 2)}
5893
+ `);
5894
+ console.log(`\uD83D\uDCC4 File created: ${jsonTarget}`);
5895
+ const localeAggregator = join6(messagesPath, `${locale}.ts`);
5896
+ if (!existsSync7(localeAggregator)) {
5897
+ throw new Error(`Locale aggregator not found: messages/${locale}.ts`);
5898
+ }
5899
+ const aggregatorContent = await readFile7(localeAggregator, "utf-8");
5900
+ const nextAggregatorContent = registerMessagesFile(aggregatorContent, locale, jsonFileName);
5901
+ if (nextAggregatorContent !== aggregatorContent) {
5902
+ await writeFile6(localeAggregator, nextAggregatorContent);
5903
+ console.log(`\uD83D\uDCC4 File updated: ${localeAggregator}`);
5904
+ }
5905
+ }
5906
+ }
5907
+ } else {
5908
+ console.log("\u2139\uFE0F Skipping translation templates; next-intl not enabled.");
5909
+ }
5910
+ console.log(`\u2705 Page "${pageName}" with templates added${useI18n ? " for each locale" : ""}.`);
5911
+ }
5438
5912
 
5439
- export async function GET() {
5440
- return NextResponse.json({ message: "Hello from ${apiName}" });
5913
+ // src/lib/addText.ts
5914
+ import { join as join7 } from "path";
5915
+ import { existsSync as existsSync8 } from "fs";
5916
+ import { readFile as readFile8, writeFile as writeFile7, readdir as readdir5 } from "fs/promises";
5917
+ function defaultText(key) {
5918
+ return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5919
+ }
5920
+ async function addText(args, cwd = process.cwd()) {
5921
+ const pathArg = args[1];
5922
+ if (!pathArg) {
5923
+ console.error("\u274C Dot path parameter is required.");
5924
+ return;
5925
+ }
5926
+ const providedText = args.slice(2).join(" ");
5927
+ parseLogicalName(pathArg, "translation path");
5928
+ const config = await loadConfig(cwd);
5929
+ if (!config?.useI18n) {
5930
+ console.error("\u274C i18n is not enabled in this project.");
5931
+ return;
5932
+ }
5933
+ const messagesPath = join7(cwd, "messages");
5934
+ if (!existsSync8(messagesPath)) {
5935
+ console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5936
+ return;
5937
+ }
5938
+ const entries = await readdir5(messagesPath, { withFileTypes: true });
5939
+ const locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5940
+ const [fileName, ...segments] = pathArg.split(".");
5941
+ if (!fileName || segments.length === 0) {
5942
+ console.error("\u274C Invalid dot path provided.");
5943
+ return;
5944
+ }
5945
+ const finalKey = segments[segments.length - 1];
5946
+ const text = providedText || defaultText(finalKey);
5947
+ for (const locale of locales) {
5948
+ const filePath = join7(messagesPath, locale, `${fileName}.json`);
5949
+ await assertSafeTarget(cwd, filePath);
5950
+ let data = {};
5951
+ if (existsSync8(filePath)) {
5952
+ const raw = await readFile8(filePath, "utf-8");
5953
+ try {
5954
+ data = JSON.parse(raw);
5955
+ } catch {}
5956
+ }
5957
+ let cursor = data;
5958
+ for (let i = 0;i < segments.length - 1; i++) {
5959
+ const seg = segments[i];
5960
+ if (!cursor[seg])
5961
+ cursor[seg] = {};
5962
+ cursor = cursor[seg];
5963
+ }
5964
+ cursor[finalKey] = text;
5965
+ await writeFile7(filePath, JSON.stringify(data, null, 2));
5966
+ console.log(`\uD83D\uDCC4 File updated: ${filePath}`);
5967
+ }
5968
+ console.log(`\u2705 Text added at path "${pathArg}" with value "${text}".`);
5441
5969
  }
5970
+
5971
+ // src/lib/rmPage.ts
5972
+ import { existsSync as existsSync9 } from "fs";
5973
+ import { readFile as readFile9, readdir as readdir6, rm, writeFile as writeFile8 } from "fs/promises";
5974
+ import path6 from "path";
5975
+ async function removeMessages(projectRoot, candidate, terminal) {
5976
+ const messagesRoot = resolveInside(projectRoot, "messages");
5977
+ let locales;
5978
+ try {
5979
+ locales = (await readdir6(messagesRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
5980
+ } catch {
5981
+ return;
5982
+ }
5983
+ for (const locale of locales) {
5984
+ const target = resolveInside(projectRoot, "messages", locale, `${candidate.routeSegments[0]}.json`);
5985
+ if (!existsSync9(target))
5986
+ continue;
5987
+ if (candidate.messageKey) {
5988
+ const data = JSON.parse(await readFile9(target, "utf8"));
5989
+ delete data[candidate.messageKey];
5990
+ await writeFile8(target, `${JSON.stringify(data, null, 2)}
5442
5991
  `);
5992
+ } else {
5993
+ await rm(target);
5443
5994
  }
5444
- console.log(`\uD83D\uDCC4 File created: ${routePath}`);
5445
- } else {
5446
- console.log(`\u2139\uFE0F File already exists: ${routePath}`);
5995
+ terminal.log(`\uD83D\uDDD1\uFE0F Deleted messages for ${candidate.logicalName}: ${target}`);
5447
5996
  }
5448
- console.log(`\u2705 API route "${apiName}" added.`);
5997
+ }
5998
+ async function rmPage(args, cwd = process.cwd(), prompt, terminal = console) {
5999
+ const candidates = await discoverPages(cwd);
6000
+ let logicalName = args[1];
6001
+ if (!logicalName || logicalName.startsWith("-")) {
6002
+ if (!prompt)
6003
+ throw new CliError("A page name is required in non-interactive mode.");
6004
+ const selected = await prompt([
6005
+ {
6006
+ type: "autocomplete",
6007
+ name: "page",
6008
+ message: "\uD83D\uDDD1\uFE0F Page to remove:",
6009
+ choices: candidates.map((candidate2) => ({
6010
+ title: candidate2.logicalName.replaceAll(".", " \u203A "),
6011
+ value: candidate2.logicalName
6012
+ }))
6013
+ },
6014
+ {
6015
+ type: (value) => value ? "confirm" : null,
6016
+ name: "confirm",
6017
+ message: "Confirm page deletion?",
6018
+ initial: false
6019
+ }
6020
+ ]);
6021
+ if (!selected.confirm) {
6022
+ terminal.log("Page deletion cancelled.");
6023
+ return;
6024
+ }
6025
+ logicalName = String(selected.page ?? "");
6026
+ }
6027
+ parseLogicalName(logicalName, "page name");
6028
+ const candidate = candidates.find((entry) => entry.logicalName === logicalName);
6029
+ if (!candidate)
6030
+ throw new CliError(`Page not found: ${logicalName}`);
6031
+ await removeMessages(cwd, candidate, terminal);
6032
+ for (const target of [candidate.uiDirectory, candidate.routeDirectory]) {
6033
+ const safeTarget = resolveInside(cwd, path6.relative(cwd, target));
6034
+ if (existsSync9(safeTarget)) {
6035
+ await rm(safeTarget, { recursive: true, force: false });
6036
+ terminal.log(`\uD83D\uDDD1\uFE0F Deleted: ${safeTarget}`);
6037
+ }
6038
+ }
6039
+ terminal.log(`\u2705 Page "${logicalName}" deleted.`);
6040
+ }
6041
+
6042
+ // src/cli/registry.ts
6043
+ function legacyHandler(command) {
6044
+ return async (args, context) => {
6045
+ await command(args, context.cwd);
6046
+ return success();
6047
+ };
6048
+ }
6049
+ function createCommandRegistry() {
6050
+ return new Map([
6051
+ ["addcomponent", legacyHandler(addComponent)],
6052
+ ["addpage", legacyHandler(addPage)],
6053
+ ["addlib", legacyHandler(addLib)],
6054
+ ["addapi", legacyHandler(addApi)],
6055
+ ["addlanguage", legacyHandler(addLanguage)],
6056
+ ["addtext", legacyHandler(addText)],
6057
+ [
6058
+ "rmpage",
6059
+ async (args, context) => {
6060
+ await rmPage(args, context.cwd, context.prompt, context.terminal);
6061
+ return success();
6062
+ }
6063
+ ]
6064
+ ]);
5449
6065
  }
5450
6066
 
5451
6067
  // src/scaffold.ts
5452
- import { cp, mkdir as mkdir5, rm, writeFile as writeFile6, readFile as readFile6 } from "fs/promises";
5453
- import { join as join7 } from "path";
5454
- import { existsSync as existsSync7 } from "fs";
5455
- import { fileURLToPath } from "url";
6068
+ import { mkdir as mkdir7, rm as rm2, writeFile as writeFile10 } from "fs/promises";
6069
+ import { join as join8, resolve } from "path";
6070
+ import { existsSync as existsSync10 } from "fs";
6071
+ import { fileURLToPath as fileURLToPath2 } from "url";
5456
6072
 
5457
6073
  // src/lib/helper/consoleColor.ts
5458
6074
  var RED = "\x1B[31m";
@@ -5469,56 +6085,159 @@ function cyan(text) {
5469
6085
  return CYAN + text + RESET;
5470
6086
  }
5471
6087
 
6088
+ // src/core/template-manifest.ts
6089
+ import {
6090
+ lstat as lstat2,
6091
+ mkdir as mkdir6,
6092
+ readdir as readdir7,
6093
+ readFile as readFile10,
6094
+ writeFile as writeFile9,
6095
+ copyFile as copyFile2
6096
+ } from "fs/promises";
6097
+ import path7 from "path";
6098
+ var TEMPLATE_DENY_NAMES = new Set([
6099
+ ".env",
6100
+ ".git",
6101
+ ".agent",
6102
+ ".cursor",
6103
+ ".next",
6104
+ "node_modules",
6105
+ "artifacts",
6106
+ "coverage",
6107
+ "playwright-report",
6108
+ "test-results"
6109
+ ]);
6110
+ function isDistributableTemplatePath(relativePath) {
6111
+ const segments = relativePath.split(path7.sep);
6112
+ return !segments.some((segment) => TEMPLATE_DENY_NAMES.has(segment) || segment.endsWith(".tsbuildinfo") || segment === ".DS_Store");
6113
+ }
6114
+ async function templateManifest(root) {
6115
+ const files = [];
6116
+ async function visit(directory, relativeDirectory = "") {
6117
+ const entries = await readdir7(directory, { withFileTypes: true });
6118
+ entries.sort((left, right) => left.name.localeCompare(right.name));
6119
+ for (const entry of entries) {
6120
+ const relative = path7.join(relativeDirectory, entry.name);
6121
+ if (!isDistributableTemplatePath(relative))
6122
+ continue;
6123
+ const source = path7.join(directory, entry.name);
6124
+ const stats = await lstat2(source);
6125
+ if (stats.isSymbolicLink()) {
6126
+ throw new CliError(`Symbolic links are forbidden in templates: ${relative}`);
6127
+ }
6128
+ if (stats.isDirectory())
6129
+ await visit(source, relative);
6130
+ else if (stats.isFile())
6131
+ files.push(relative);
6132
+ else
6133
+ throw new CliError(`Unsupported template entry: ${relative}`);
6134
+ }
6135
+ }
6136
+ await visit(root);
6137
+ return files;
6138
+ }
6139
+ async function copyTemplate(root, target) {
6140
+ const manifest = await templateManifest(root);
6141
+ await mkdir6(target, { recursive: true });
6142
+ for (const relative of manifest) {
6143
+ const destination = path7.join(target, relative === ".gitignore.template" ? ".gitignore" : relative);
6144
+ await mkdir6(path7.dirname(destination), { recursive: true });
6145
+ await copyFile2(path7.join(root, relative), destination);
6146
+ }
6147
+ return manifest;
6148
+ }
6149
+ async function customizeGeneratedProject(target, projectName, importAlias) {
6150
+ const packagePath = path7.join(target, "package.json");
6151
+ const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
6152
+ packageJson.name = projectName.toLowerCase();
6153
+ delete packageJson.packageManager;
6154
+ await writeFile9(packagePath, `${JSON.stringify(packageJson, null, 2)}
6155
+ `);
6156
+ const tsconfigPath = path7.join(target, "tsconfig.json");
6157
+ const tsconfigContent = await readFile10(tsconfigPath, "utf8");
6158
+ const tsconfig = JSON.parse(tsconfigContent);
6159
+ if (!tsconfig.compilerOptions?.paths?.["@/*"]) {
6160
+ throw new CliError('The template tsconfig must define the default "@/*" alias.');
6161
+ }
6162
+ await writeFile9(tsconfigPath, tsconfigContent.replace('"@/*"', `"${importAlias}"`));
6163
+ if (importAlias !== "@/*") {
6164
+ const prefix = importAlias.slice(0, -2);
6165
+ for (const relative of await templateManifest(target)) {
6166
+ if (!/\.[cm]?[jt]sx?$/.test(relative))
6167
+ continue;
6168
+ const file = path7.join(target, relative);
6169
+ const content = await readFile10(file, "utf8");
6170
+ const next = content.replaceAll('from "@/', `from "${prefix}/`).replaceAll("from '@/", `from '${prefix}/`);
6171
+ if (next !== content)
6172
+ await writeFile9(file, next);
6173
+ }
6174
+ }
6175
+ }
6176
+
5472
6177
  // src/scaffold.ts
5473
- async function scaffoldProject(options) {
5474
- const targetPath = join7(process.cwd(), options.projectName);
6178
+ async function scaffoldProject(options, runtime = {}) {
6179
+ const requiredFeatures = [
6180
+ "useTypescript",
6181
+ "useEslint",
6182
+ "useTailwind",
6183
+ "useSrcDir",
6184
+ "useTurbopack",
6185
+ "useI18n"
6186
+ ];
6187
+ const unsupported = requiredFeatures.filter((feature) => options[feature] !== true);
6188
+ if (unsupported.length > 0) {
6189
+ throw new CliError(`The default Next.js 16 template requires: ${unsupported.join(", ")}.`);
6190
+ }
6191
+ const cwd = runtime.cwd ?? process.cwd();
6192
+ const terminal = runtime.terminal ?? console;
6193
+ const projectName = validateProjectName(options.projectName);
6194
+ const importAlias = normalizeImportAlias(options.customAlias === false ? "@/*" : options.importAlias || "@/*");
6195
+ const targetPath = join8(cwd, projectName);
5475
6196
  const __dirname2 = new URL(".", import.meta.url);
5476
- const templatePath = join7(fileURLToPath(__dirname2), "..", "templates", "Projects", "default");
5477
- if (existsSync7(targetPath)) {
6197
+ const templatePath = runtime.templatePath ?? join8(fileURLToPath2(__dirname2), "..", "templates", "Projects", "default");
6198
+ const resolvedCwd = resolve(cwd);
6199
+ const resolvedTarget = resolve(targetPath);
6200
+ if (!resolvedTarget.startsWith(`${resolvedCwd}/`)) {
6201
+ throw new CliError("The project destination must be a child of the current directory.");
6202
+ }
6203
+ if (existsSync10(targetPath)) {
5478
6204
  if (options.force) {
5479
- console.warn("\u26A0\uFE0F Target directory already exists, removing...");
5480
- await rm(targetPath, { recursive: true, force: true });
6205
+ terminal.warn("\u26A0\uFE0F Target directory already exists, removing...");
6206
+ await rm2(targetPath, { recursive: true, force: true });
5481
6207
  } else {
5482
- console.error(red("[X] Target directory already exists. Use --force to overwrite."));
5483
- process.exit(1);
6208
+ terminal.error(red("[X] Target directory already exists. Use --force to overwrite."));
6209
+ throw new CliError("[X] Target directory already exists. Use --force to overwrite.");
5484
6210
  }
5485
6211
  }
5486
6212
  try {
5487
- console.log("Creating project directory...");
5488
- await mkdir5(targetPath, { recursive: true });
5489
- console.log("Copying files from template...");
5490
- await cp(templatePath, targetPath, { recursive: true });
5491
- const pkgPath = join7(targetPath, "package.json");
5492
- if (existsSync7(pkgPath)) {
5493
- const pkg = JSON.parse(await readFile6(pkgPath, "utf-8"));
5494
- pkg.dependencies = pkg.dependencies || {};
5495
- if (options.useI18n) {
5496
- pkg.dependencies["next-intl"] = pkg.dependencies["next-intl"] || "^4.3.5";
5497
- }
5498
- await writeFile6(pkgPath, JSON.stringify(pkg, null, 2));
5499
- }
5500
- await writeFile6(join7(targetPath, "cnp.config.json"), JSON.stringify(options, null, 2));
5501
- console.log("Project setup complete!");
5502
- console.log("");
5503
- console.log("To get started:");
5504
- console.log(" " + green(`cd ${options.projectName}`));
5505
- console.log("");
5506
- console.log("Then install dependencies and launch the dev server with your preferred tool:");
5507
- console.log(" " + green(`bun install && bun dev`));
5508
- console.log(" " + green(`npm install && npm run dev`));
5509
- console.log(" " + green(`pnpm install && pnpm run dev`));
5510
- console.log("");
5511
- console.log("Documentation and examples can be found at:");
5512
- console.log(" " + cyan("https://github.com/Rising-Corporation/create-next-pro-cli"));
5513
- console.log("_-`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`-_");
6213
+ terminal.log("Creating project directory...");
6214
+ await mkdir7(targetPath, { recursive: true });
6215
+ terminal.log("Copying files from template...");
6216
+ await copyTemplate(templatePath, targetPath);
6217
+ await customizeGeneratedProject(targetPath, projectName, importAlias);
6218
+ await writeFile10(join8(targetPath, "cnp.config.json"), `${JSON.stringify({ ...options, projectName, importAlias }, null, 2)}
6219
+ `);
6220
+ terminal.log("Project setup complete!");
6221
+ terminal.log("");
6222
+ terminal.log("To get started:");
6223
+ terminal.log(" " + green(`cd ${options.projectName}`));
6224
+ terminal.log("");
6225
+ terminal.log("Then install dependencies and launch the dev server with your preferred tool:");
6226
+ terminal.log(" " + green(`bun install && bun dev`));
6227
+ terminal.log(" " + green(`npm install && npm run dev`));
6228
+ terminal.log(" " + green(`pnpm install && pnpm run dev`));
6229
+ terminal.log("");
6230
+ terminal.log("Documentation and examples can be found at:");
6231
+ terminal.log(" " + cyan("https://github.com/Rising-Corporation/create-next-pro-cli"));
6232
+ terminal.log("_-`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`_`'-_-'`-_");
5514
6233
  } catch (err) {
5515
- console.error(red("[X] Error during project creation:"), err);
5516
- process.exit(1);
6234
+ terminal.error(red("[X] Error during project creation:"), err);
6235
+ throw new CliError("[X] Error during project creation:");
5517
6236
  }
5518
6237
  }
5519
6238
 
5520
6239
  // src/lib/createProject.ts
5521
- async function createProject(nameArg, force) {
6240
+ async function createProject(nameArg, force, context) {
5522
6241
  const response = {
5523
6242
  projectName: nameArg,
5524
6243
  useTypescript: true,
@@ -5531,14 +6250,17 @@ async function createProject(nameArg, force) {
5531
6250
  importAlias: "@/*",
5532
6251
  force
5533
6252
  };
5534
- console.log(`Creating project "${response.projectName}"...`);
5535
- await scaffoldProject(response);
6253
+ const terminal = context?.terminal ?? console;
6254
+ terminal.log(`Creating project "${response.projectName}"...`);
6255
+ await scaffoldProject(response, {
6256
+ cwd: context?.cwd ?? process.cwd(),
6257
+ terminal
6258
+ });
5536
6259
  }
5537
6260
 
5538
6261
  // src/lib/createProjectWithPrompt.ts
5539
- var import_prompts6 = __toESM(require_prompts3(), 1);
5540
- async function createProjectWithPrompt() {
5541
- const response = await import_prompts6.default.prompt([
6262
+ async function createProjectWithPrompt(context) {
6263
+ const response = await context.prompt([
5542
6264
  {
5543
6265
  type: "text",
5544
6266
  name: "projectName",
@@ -5608,232 +6330,18 @@ async function createProjectWithPrompt() {
5608
6330
  initial: "@core/*"
5609
6331
  }
5610
6332
  ]);
5611
- console.log(`
6333
+ const options = response;
6334
+ context.terminal.log(`
5612
6335
  Your choices:`);
5613
- console.log(response);
5614
- await scaffoldProject(response);
5615
- }
5616
-
5617
- // src/lib/addLanguage.ts
5618
- var import_prompts7 = __toESM(require_prompts3(), 1);
5619
- import { join as join8 } from "path";
5620
- import { existsSync as existsSync8 } from "fs";
5621
- import { cp as cp2, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
5622
- function generateLocales() {
5623
- const dn = new Intl.DisplayNames(["en"], { type: "language" });
5624
- const locales = [];
5625
- for (let i = 0;i < 26; i++) {
5626
- for (let j = 0;j < 26; j++) {
5627
- const code = String.fromCharCode(97 + i) + String.fromCharCode(97 + j);
5628
- try {
5629
- const name = dn.of(code);
5630
- if (name && name.toLowerCase() !== code) {
5631
- locales.push(code);
5632
- }
5633
- } catch {}
5634
- }
5635
- }
5636
- return locales.sort();
5637
- }
5638
- async function addLanguage(args) {
5639
- const config = await loadConfig();
5640
- if (!config?.useI18n) {
5641
- console.error("\u274C i18n is not enabled in this project.");
5642
- return;
5643
- }
5644
- const messagesPath = join8(process.cwd(), "messages");
5645
- if (!existsSync8(messagesPath)) {
5646
- console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5647
- return;
5648
- }
5649
- const available = generateLocales();
5650
- let locale = args[1];
5651
- if (!locale || !available.includes(locale)) {
5652
- const response = await import_prompts7.default({
5653
- type: "autocomplete",
5654
- name: "locale",
5655
- message: "\uD83C\uDF10 Locale to add:",
5656
- choices: available.map((l) => ({ title: l, value: l }))
5657
- });
5658
- locale = response.locale;
5659
- }
5660
- if (!locale)
5661
- return;
5662
- if (existsSync8(join8(messagesPath, locale))) {
5663
- console.error(`\u274C Locale ${locale} already exists.`);
5664
- return;
5665
- }
5666
- const routingFile = join8(process.cwd(), "src", "lib", "i18n", "routing.ts");
5667
- if (!existsSync8(routingFile)) {
5668
- console.error("\u274C routing.ts not found. Are you in project root?");
5669
- return;
5670
- }
5671
- const routingContent = await readFile7(routingFile, "utf-8");
5672
- const defaultMatch = routingContent.match(/defaultLocale:\s*"([^"]+)"/);
5673
- const defaultLocale = defaultMatch ? defaultMatch[1] : null;
5674
- if (!defaultLocale || !existsSync8(join8(messagesPath, defaultLocale))) {
5675
- console.error("\u274C Default locale not found.");
5676
- return;
5677
- }
5678
- await cp2(join8(messagesPath, defaultLocale), join8(messagesPath, locale), {
5679
- recursive: true
6336
+ context.terminal.log(options);
6337
+ await scaffoldProject(options, {
6338
+ cwd: context.cwd,
6339
+ terminal: context.terminal
5680
6340
  });
5681
- console.log(`\uD83D\uDCC4 Directory created: ${join8(messagesPath, locale)}`);
5682
- const localesMatch = routingContent.match(/locales:\s*\[([^\]]*)\]/);
5683
- if (localesMatch) {
5684
- const localesArr = localesMatch[1].split(",").map((s) => s.trim().replace(/["']/g, "")).filter(Boolean);
5685
- if (!localesArr.includes(locale)) {
5686
- localesArr.push(locale);
5687
- const newLocales = `locales: [${localesArr.map((l) => `"${l}"`).join(", ")}]`;
5688
- const newContent = routingContent.replace(/locales:\s*\[[^\]]*\]/, newLocales);
5689
- await writeFile7(routingFile, newContent);
5690
- console.log(`\uD83D\uDCC4 File updated: ${routingFile}`);
5691
- }
5692
- }
5693
- console.log(`\u2705 Locale "${locale}" added and copied from default locale "${defaultLocale}".`);
5694
- }
5695
-
5696
- // src/lib/addText.ts
5697
- import { join as join9 } from "path";
5698
- import { existsSync as existsSync9 } from "fs";
5699
- import { readFile as readFile8, writeFile as writeFile8, readdir as readdir4 } from "fs/promises";
5700
- function defaultText(key) {
5701
- return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5702
- }
5703
- async function addText(args) {
5704
- const pathArg = args[1];
5705
- if (!pathArg) {
5706
- console.error("\u274C Dot path parameter is required.");
5707
- return;
5708
- }
5709
- const providedText = args.slice(2).join(" ");
5710
- const config = await loadConfig();
5711
- if (!config?.useI18n) {
5712
- console.error("\u274C i18n is not enabled in this project.");
5713
- return;
5714
- }
5715
- const messagesPath = join9(process.cwd(), "messages");
5716
- if (!existsSync9(messagesPath)) {
5717
- console.error("\u274C Messages directory missing. Ensure i18n was configured.");
5718
- return;
5719
- }
5720
- const entries = await readdir4(messagesPath, { withFileTypes: true });
5721
- const locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
5722
- const [fileName, ...segments] = pathArg.split(".");
5723
- if (!fileName || segments.length === 0) {
5724
- console.error("\u274C Invalid dot path provided.");
5725
- return;
5726
- }
5727
- const finalKey = segments[segments.length - 1];
5728
- const text = providedText || defaultText(finalKey);
5729
- for (const locale of locales) {
5730
- const filePath = join9(messagesPath, locale, `${fileName}.json`);
5731
- let data = {};
5732
- if (existsSync9(filePath)) {
5733
- const raw = await readFile8(filePath, "utf-8");
5734
- try {
5735
- data = JSON.parse(raw);
5736
- } catch {}
5737
- }
5738
- let cursor = data;
5739
- for (let i = 0;i < segments.length - 1; i++) {
5740
- const seg = segments[i];
5741
- if (!cursor[seg])
5742
- cursor[seg] = {};
5743
- cursor = cursor[seg];
5744
- }
5745
- cursor[finalKey] = text;
5746
- await writeFile8(filePath, JSON.stringify(data, null, 2));
5747
- console.log(`\uD83D\uDCC4 File updated: ${filePath}`);
5748
- }
5749
- console.log(`\u2705 Text added at path "${pathArg}" with value "${text}".`);
5750
6341
  }
5751
6342
 
5752
6343
  // src/index.ts
5753
- var CONFIG_DIR = process.env.XDG_CONFIG_HOME ? path.join(process.env.XDG_CONFIG_HOME, "create-next-pro") : path.join(os.homedir(), ".config", "create-next-pro");
5754
- var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
5755
- function readCfg() {
5756
- try {
5757
- return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
5758
- } catch {
5759
- return null;
5760
- }
5761
- }
5762
- function writeCfg(cfg) {
5763
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
5764
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
5765
- }
5766
- function rcFile(shell) {
5767
- return path.join(os.homedir(), shell === "zsh" ? ".zshrc" : ".bashrc");
5768
- }
5769
- function ensureLineInRc(file, line) {
5770
- try {
5771
- const cur = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
5772
- if (!cur.includes(line))
5773
- fs.appendFileSync(file, `
5774
- ${line}
5775
- `);
5776
- } catch {}
5777
- }
5778
- async function installCompletion(shell) {
5779
- const __dirname2 = path.dirname(fileURLToPath2(import.meta.url));
5780
- const completionSrc = path.resolve(__dirname2, "../create-next-pro-completion.sh");
5781
- const completionDst = path.join(CONFIG_DIR, "completion.sh");
5782
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
5783
- fs.copyFileSync(completionSrc, completionDst);
5784
- ensureLineInRc(rcFile(shell), `source "${completionDst}"`);
5785
- }
5786
- var __filename2 = fileURLToPath2(import.meta.url);
5787
- var __dirname2 = dirname(__filename2);
5788
- var packageJsonPath = resolve(__dirname2, "../package.json");
5789
- var packageJson = fs.readFileSync(packageJsonPath, "utf8");
5790
- async function onboarding() {
5791
- const pkg = JSON.parse(packageJson);
5792
- console.log(`\uD83D\uDE80 Welcome to create-next-pro v${pkg.version}
5793
- `);
5794
- const res = await import_prompts8.default([
5795
- {
5796
- type: "select",
5797
- name: "shell",
5798
- message: "Which shell do you use?",
5799
- choices: [
5800
- { title: "zsh", value: "zsh" },
5801
- { title: "bash", value: "bash" }
5802
- ],
5803
- initial: (os.userInfo().shell || "").includes("zsh") ? 0 : 1
5804
- },
5805
- {
5806
- type: "toggle",
5807
- name: "completion",
5808
- message: "Install autocompletion?",
5809
- initial: true,
5810
- active: "Yes",
5811
- inactive: "No"
5812
- }
5813
- ], { onCancel: () => process.exit(1) });
5814
- const cfg = {
5815
- version: 1,
5816
- shell: res.shell,
5817
- completionInstalled: !!res.completion,
5818
- createdAt: new Date().toISOString(),
5819
- updatedAt: new Date().toISOString()
5820
- };
5821
- if (cfg.completionInstalled)
5822
- await installCompletion(cfg.shell);
5823
- writeCfg(cfg);
5824
- console.log(`
5825
- \u2705 Configuration saved.`);
5826
- console.log("you can now use the CLI ! ex : ");
5827
- console.log(" Without prompt (will change in future) :");
5828
- console.log(" create-next-pro my-next-project");
5829
- console.log(" With prompt :");
5830
- console.log(" create-next-pro");
5831
- console.log("For more information, visit: https://github.com/Rising-Corporation/create-next-pro-cli");
5832
- console.log("Happy coding! \uD83C\uDF89");
5833
- return cfg;
5834
- }
5835
- function showHelp() {
5836
- console.log(`create-next-pro
6344
+ var HELP_TEXT = `create-next-pro
5837
6345
 
5838
6346
  Usage:
5839
6347
  create-next-pro <project-name> [--force]
@@ -5847,69 +6355,60 @@ Usage:
5847
6355
 
5848
6356
  Options:
5849
6357
  --help Show this help message
6358
+ --version Show the CLI version
5850
6359
  --reconfigure Run the configuration assistant again
5851
- `);
6360
+ `;
6361
+ function parseOptions(args) {
6362
+ return {
6363
+ force: args.includes("--force"),
6364
+ help: args.includes("--help"),
6365
+ version: args.includes("--version") || args.includes("-v"),
6366
+ reconfigure: args.includes("--reconfigure")
6367
+ };
5852
6368
  }
5853
- function showVersion() {
5854
- const pkg = JSON.parse(packageJson);
5855
- console.log(`v${pkg.version}`);
6369
+ async function packageVersion(context) {
6370
+ const packageJson = JSON.parse(await context.fs.readText(path8.join(context.packageRoot, "package.json")));
6371
+ return packageJson.version;
5856
6372
  }
5857
- async function main() {
5858
- let args;
5859
- if (typeof Bun !== "undefined") {
5860
- args = Bun.argv.slice(2);
5861
- } else if (typeof process !== "undefined" && process.argv) {
5862
- args = process.argv.slice(2);
5863
- } else {
5864
- args = [];
5865
- }
5866
- if (args.includes("--help"))
5867
- return showHelp();
5868
- if (args.includes("--version") || args.includes("-v"))
5869
- return showVersion();
5870
- if (args.includes("--reconfigure") || !readCfg()) {
5871
- await onboarding();
5872
- return;
5873
- }
5874
- const force = args.includes("--force");
5875
- if (args[0] === "addpage" && args.length === 1) {
5876
- args.push("-LPl");
5877
- }
5878
- if (args[0] === "addcomponent") {
5879
- addComponent(args);
5880
- return;
5881
- }
5882
- if (args[0] === "addpage") {
5883
- addPage(args);
5884
- return;
5885
- }
5886
- if (args[0] === "addlib") {
5887
- addLib(args);
5888
- return;
5889
- }
5890
- if (args[0] === "addapi") {
5891
- addApi(args);
5892
- return;
5893
- }
5894
- if (args[0] === "addlanguage") {
5895
- await addLanguage(args);
5896
- return;
5897
- }
5898
- if (args[0] === "addtext") {
5899
- await addText(args);
5900
- return;
5901
- }
5902
- if (args[0] === "rmpage") {
5903
- rmPage(args);
5904
- return;
5905
- }
5906
- const nameArg = args.find((arg) => !arg.startsWith("--"));
5907
- if (nameArg) {
5908
- createProject(nameArg, force);
5909
- return;
6373
+ async function main(context = createNodeContext()) {
6374
+ try {
6375
+ const args = [...context.argv];
6376
+ const options = parseOptions(args);
6377
+ const version = await packageVersion(context);
6378
+ if (args[0] === "__complete") {
6379
+ await printCompletions(args, context);
6380
+ return 0;
6381
+ }
6382
+ if (options.help) {
6383
+ context.terminal.log(HELP_TEXT);
6384
+ return 0;
6385
+ }
6386
+ if (options.version) {
6387
+ context.terminal.log(`v${version}`);
6388
+ return 0;
6389
+ }
6390
+ if (options.reconfigure || !await readConfig(context)) {
6391
+ await onboarding(context, version);
6392
+ return 0;
6393
+ }
6394
+ if (args[0] === "addpage" && args.length === 1)
6395
+ args.push("-LPl");
6396
+ const handler = args[0] ? createCommandRegistry().get(args[0]) : undefined;
6397
+ if (handler)
6398
+ return (await handler(args, context)).exitCode;
6399
+ const nameArg = args.find((arg) => !arg.startsWith("--"));
6400
+ if (nameArg) {
6401
+ await createProject(nameArg, options.force, context);
6402
+ return 0;
6403
+ }
6404
+ await createProjectWithPrompt(context);
6405
+ return 0;
6406
+ } catch (error) {
6407
+ const message = error instanceof Error ? error.message : String(error);
6408
+ context.terminal.error(message);
6409
+ return error instanceof CliError ? error.exitCode : 1;
5910
6410
  }
5911
- await createProjectWithPrompt();
5912
6411
  }
5913
6412
 
5914
6413
  // bin.bun.ts
5915
- main();
6414
+ process.exitCode = await main();