jax-hono 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,5 +13,5 @@ helpers/ 框架辅助函数
13
13
  utils/ 纯工具函数
14
14
  errors/ 统一错误类型
15
15
  types/ 框架公共类型
16
- generated/ 自动生成的注册表
16
+ generated/ 调用项目中自动生成的注册表,包内同名目录会链接到这里
17
17
  ```
package/bin/jax.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const cliPath = path.join(__dirname, "jax.ts");
11
+
12
+ function getBunCommand() {
13
+ const candidates = [];
14
+
15
+ if (process.env.BUN_INSTALL) {
16
+ candidates.push(
17
+ path.join(
18
+ process.env.BUN_INSTALL,
19
+ "bin",
20
+ process.platform === "win32" ? "bun.exe" : "bun",
21
+ ),
22
+ );
23
+ }
24
+
25
+ if (process.platform === "win32") {
26
+ const nodeDir = path.dirname(process.execPath);
27
+ candidates.push(
28
+ path.join(nodeDir, "bun.exe"),
29
+ path.join(nodeDir, "node_modules", "bun", "bin", "bun.exe"),
30
+ );
31
+ }
32
+
33
+ for (const candidate of candidates) {
34
+ if (existsSync(candidate)) {
35
+ return candidate;
36
+ }
37
+ }
38
+
39
+ return "bun";
40
+ }
41
+
42
+ const child = spawn(getBunCommand(), [cliPath, ...process.argv.slice(2)], {
43
+ stdio: "inherit",
44
+ windowsHide: true,
45
+ });
46
+
47
+ child.on("error", (error) => {
48
+ if (error.code === "ENOENT") {
49
+ console.error(
50
+ "未找到 Bun。请从 https://bun.sh 安装 Bun,或确认 bun 已加入 PATH。",
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ console.error(error.message);
56
+ process.exit(1);
57
+ });
58
+
59
+ child.on("exit", (code, signal) => {
60
+ if (signal) {
61
+ process.kill(process.pid, signal);
62
+ return;
63
+ }
64
+
65
+ process.exit(code ?? 0);
66
+ });
package/bin/jax.ts CHANGED
@@ -4,18 +4,40 @@ import { cac } from "cac";
4
4
  import { $ } from "bun";
5
5
  import path from "node:path";
6
6
  import { existsSync } from "node:fs";
7
- import { mkdir } from "node:fs/promises";
7
+ import { lstat, mkdir, rm } from "node:fs/promises";
8
8
 
9
9
  const cli = cac("jax");
10
10
 
11
+ async function removeExistingTarget(
12
+ targetDir: string,
13
+ allowRegularDirectory: boolean,
14
+ ) {
15
+ if (!existsSync(targetDir)) {
16
+ return;
17
+ }
18
+
19
+ const stats = await lstat(targetDir);
20
+ if (stats.isSymbolicLink() || allowRegularDirectory) {
21
+ await rm(targetDir, { recursive: true, force: true });
22
+ return;
23
+ }
24
+
25
+ console.error(
26
+ `目标已存在且不是符号链接:${targetDir}\n请手动删除后再执行 jax link。`,
27
+ );
28
+ process.exit(1);
29
+ }
30
+
11
31
  cli
12
- .command(
13
- "link",
14
- "Create a symlink to the jax-hono installation directory",
15
- )
32
+ .command("link", "创建指向 jax-hono 安装目录的链接")
16
33
  .action(async () => {
17
34
  const cwd = process.cwd();
18
35
  const sourceDir = path.resolve(import.meta.dir, "..");
36
+ if (path.resolve(cwd) === sourceDir) {
37
+ console.error("不能在 jax-hono 安装目录内执行 jax link。");
38
+ process.exit(1);
39
+ }
40
+
19
41
  const shouldUseNodeModules =
20
42
  existsSync(path.join(cwd, "package.json")) ||
21
43
  existsSync(path.join(cwd, "node_modules"));
@@ -29,54 +51,90 @@ cli
29
51
  await mkdir(targetRoot, { recursive: true });
30
52
  }
31
53
 
54
+ await removeExistingTarget(targetDir, shouldUseNodeModules);
55
+
32
56
  if (process.platform === "win32") {
33
57
  const result =
34
- await $`cmd /c mklink /D ${targetDir} ${sourceDir}`.nothrow();
58
+ await $`cmd /c mklink /J ${targetDir} ${sourceDir}`.nothrow().quiet();
35
59
  if (result.exitCode !== 0) {
36
60
  console.error(
37
- "Failed to create symlink. Make sure you run as Administrator.",
61
+ `创建目录联接失败:${result.stderr.toString()}`,
38
62
  );
39
63
  process.exit(1);
40
64
  }
41
- console.log(`Symlink created: ${targetDir} <<===>> ${sourceDir}`);
65
+ console.log(`目录联接已创建:${targetDir} <<===>> ${sourceDir}`);
42
66
  } else {
43
67
  const relativeSource = path.relative(path.dirname(targetDir), sourceDir);
44
- const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow();
68
+ const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow().quiet();
45
69
  if (result.exitCode !== 0) {
46
- console.error(`Failed to create symlink: ${result.stderr.toString()}`);
70
+ console.error(`创建符号链接失败:${result.stderr.toString()}`);
47
71
  process.exit(1);
48
72
  }
49
- console.log(`Symlink created: ${targetDir} -> ${relativeSource}`);
73
+ console.log(`符号链接已创建:${targetDir} -> ${relativeSource}`);
50
74
  }
51
75
  });
52
76
 
53
77
  cli
54
- .command("open", "Open jax-hono installation directory in file manager")
78
+ .command("open", "在文件管理器中打开 jax-hono 安装目录")
55
79
  .action(async () => {
56
80
  const sourceDir = path.resolve(import.meta.dir, "..");
57
81
 
58
- let command: string[];
59
- if (process.platform === "darwin") {
60
- command = ["open", sourceDir];
61
- } else if (process.platform === "win32") {
62
- command = ["explorer", sourceDir];
63
- } else {
64
- command = ["xdg-open", sourceDir];
82
+ if (process.platform === "win32") {
83
+ const escapedSourceDir = sourceDir.replace(/'/g, "''");
84
+ const proc = Bun.spawn([
85
+ "powershell",
86
+ "-NoProfile",
87
+ "-Command",
88
+ `Start-Process -FilePath explorer.exe -ArgumentList '${escapedSourceDir}'`,
89
+ ], {
90
+ stdout: "pipe",
91
+ stderr: "pipe",
92
+ windowsHide: true,
93
+ });
94
+ const exitCode = await proc.exited;
95
+ const stderr = await new Response(proc.stderr).text();
96
+
97
+ if (exitCode !== 0) {
98
+ console.error(`打开目录失败:${stderr || "未知错误"}`);
99
+ process.exit(1);
100
+ }
101
+
102
+ console.log(`已打开:${sourceDir}`);
103
+ return;
65
104
  }
66
105
 
106
+ const command =
107
+ process.platform === "darwin" ? ["open", sourceDir] : ["xdg-open", sourceDir];
67
108
  const [cmd, ...args] = command;
68
- const result = await $`${cmd} ${args}`.nothrow();
109
+ const result = await $`${cmd} ${args}`.nothrow().quiet();
69
110
  if (result.exitCode !== 0) {
70
- console.error(`Failed to open directory: ${result.stderr.toString()}`);
111
+ console.error(`打开目录失败:${result.stderr.toString() || "未知错误"}`);
71
112
  process.exit(1);
72
113
  }
73
- console.log(`Opened: ${sourceDir}`);
114
+ console.log(`已打开:${sourceDir}`);
74
115
  });
75
116
 
76
- cli.help();
117
+ cli.help((sections) =>
118
+ sections.map((section) => {
119
+ const titleMap: Record<string, string> = {
120
+ Usage: "用法",
121
+ Commands: "命令",
122
+ "For more info, run any command with the `--help` flag":
123
+ "查看更多信息,请在任意命令后添加 `--help`",
124
+ Options: "选项",
125
+ Examples: "示例",
126
+ };
127
+
128
+ return {
129
+ ...section,
130
+ title: section.title ? (titleMap[section.title] ?? section.title) : section.title,
131
+ body: section.body.replace("Display this message", "显示帮助信息"),
132
+ };
133
+ }),
134
+ );
77
135
  const parsed = cli.parse();
78
136
 
79
- // Show help when no command is matched (e.g. bare `jax` or unknown command)
137
+ // 未匹配到命令时显示帮助,例如直接执行 `jax` 或输入未知命令。
80
138
  if (!cli.matchedCommand && !parsed.options.help) {
81
139
  cli.outputHelp();
82
140
  }
@@ -1,12 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
2
2
  import { cwd } from 'node:process'
3
3
  import { join, parse, relative } from 'node:path'
4
+ import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
4
5
 
5
- const packageDir = join(import.meta.dir, '..')
6
6
  const srcDir = join(cwd(), 'src')
7
7
  const configDir = join(srcDir, 'config')
8
8
  const typesFile = join(srcDir, 'types/index.ts')
9
- const generatedDir = join(packageDir, 'generated')
10
9
  const outputFile = join(generatedDir, 'config.ts')
11
10
  const envStages = new Set([
12
11
  'default',
@@ -102,14 +101,14 @@ ${projectConfigType}
102
101
  export default configModules
103
102
  `
104
103
 
105
- mkdirSync(generatedDir, { recursive: true })
104
+ ensureGeneratedDir()
106
105
 
107
106
  if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
108
107
  return
109
108
  }
110
109
 
111
110
  writeFileSync(outputFile, content)
112
- console.log(`Generated ${relative(srcDir, outputFile)} with ${files.length} config file(s).`)
111
+ console.log(`Generated ${relative(projectDir, outputFile)} with ${files.length} config file(s).`)
113
112
  }
114
113
 
115
114
  if (import.meta.main) {
@@ -1,10 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
2
2
  import { cwd } from 'node:process'
3
3
  import { basename, join, parse, relative } from 'node:path'
4
+ import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
4
5
 
5
- const packageDir = join(import.meta.dir, '..')
6
6
  const srcDir = join(cwd(), 'src')
7
- const generatedDir = join(packageDir, 'generated')
8
7
  const moduleExtensions = new Set(['.ts', '.js', '.mjs', '.cjs'])
9
8
  const generatedHeader = `// This file is generated by jax-hono/build/generate-registry.ts.
10
9
  // Do not edit it directly.
@@ -66,14 +65,14 @@ function walkFiles(dir: string): string[] {
66
65
  function writeGeneratedFile(fileName: string, content: string) {
67
66
  const outputFile = join(generatedDir, fileName)
68
67
 
69
- mkdirSync(generatedDir, { recursive: true })
68
+ ensureGeneratedDir()
70
69
 
71
70
  if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
72
71
  return
73
72
  }
74
73
 
75
74
  writeFileSync(outputFile, content)
76
- console.log(`Generated ${relative(srcDir, outputFile)}.`)
75
+ console.log(`Generated ${relative(projectDir, outputFile)}.`)
77
76
  }
78
77
 
79
78
  function toImportPath(filePath: string) {
@@ -0,0 +1,57 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ mkdirSync,
5
+ realpathSync,
6
+ rmSync,
7
+ symlinkSync,
8
+ } from 'node:fs'
9
+ import { cwd } from 'node:process'
10
+ import { join, resolve } from 'node:path'
11
+
12
+ export const packageDir = join(import.meta.dir, '..')
13
+ export const projectDir = cwd()
14
+
15
+ export function getGeneratedDir() {
16
+ const configuredDir = Bun.env.JAX_HONO_GENERATED_DIR
17
+
18
+ return configuredDir
19
+ ? resolve(projectDir, configuredDir)
20
+ : join(projectDir, 'generated')
21
+ }
22
+
23
+ export const generatedDir = getGeneratedDir()
24
+
25
+ export function ensureGeneratedDir() {
26
+ mkdirSync(generatedDir, { recursive: true })
27
+ ensurePackageGeneratedLink()
28
+ }
29
+
30
+ function ensurePackageGeneratedLink() {
31
+ const packageGeneratedDir = join(packageDir, 'generated')
32
+
33
+ if (resolve(packageGeneratedDir) === resolve(generatedDir)) {
34
+ if (existsSync(packageGeneratedDir) && lstatSync(packageGeneratedDir).isSymbolicLink()) {
35
+ rmSync(packageGeneratedDir, { recursive: true, force: true })
36
+ mkdirSync(generatedDir, { recursive: true })
37
+ }
38
+
39
+ return
40
+ }
41
+
42
+ if (existsSync(packageGeneratedDir)) {
43
+ const stats = lstatSync(packageGeneratedDir)
44
+
45
+ if (stats.isSymbolicLink()) {
46
+ const currentTarget = realpathSync(packageGeneratedDir)
47
+
48
+ if (resolve(currentTarget) === resolve(generatedDir)) {
49
+ return
50
+ }
51
+ }
52
+
53
+ rmSync(packageGeneratedDir, { recursive: true, force: true })
54
+ }
55
+
56
+ symlinkSync(generatedDir, packageGeneratedDir, 'junction')
57
+ }
package/docs/jax.md CHANGED
@@ -80,7 +80,7 @@ export default definePlugin({
80
80
  });
81
81
  ```
82
82
 
83
- 未启用的插件不会写入 `src/jax/generated/plugins.generated.ts`,因此 `bun build` 不会解析它的运行时依赖。
83
+ 未启用的插件不会写入调用项目的 `generated/plugins.generated.ts`,因此 `bun build` 不会解析它的运行时依赖。
84
84
 
85
85
  ### `build`
86
86
 
@@ -88,12 +88,12 @@ export default definePlugin({
88
88
 
89
89
  当前 `bun run setup` 会生成:
90
90
 
91
- - `src/jax/generated/config.ts`
92
- - `src/jax/generated/controllers.generated.ts`
93
- - `src/jax/generated/middlewares.generated.ts`
94
- - `src/jax/generated/models.generated.ts`
95
- - `src/jax/generated/plugins.generated.ts`
96
- - `src/jax/generated/services.generated.ts`
91
+ - `generated/config.ts`
92
+ - `generated/controllers.generated.ts`
93
+ - `generated/middlewares.generated.ts`
94
+ - `generated/models.generated.ts`
95
+ - `generated/plugins.generated.ts`
96
+ - `generated/services.generated.ts`
97
97
 
98
98
  业务代码不要手动编辑 `generated` 目录。
99
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jax-hono",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Lightweight framework layer on top of Hono, built for Bun",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -60,7 +60,7 @@
60
60
  }
61
61
  },
62
62
  "bin": {
63
- "jax": "./bin/jax.ts"
63
+ "jax": "./bin/jax.js"
64
64
  },
65
65
  "files": [
66
66
  "index.ts",
@@ -94,6 +94,7 @@
94
94
  "mongoose": "*"
95
95
  },
96
96
  "devDependencies": {
97
+ "@types/bun": "^1.3.14",
97
98
  "dayjs": "^1.11.21",
98
99
  "deepmerge": "^4.3.1",
99
100
  "dotenv": "^17.4.2",
@@ -6,7 +6,7 @@ import mongoose, {
6
6
  type Model,
7
7
  type SchemaOptions,
8
8
  } from "mongoose";
9
- import config from "@/config";
9
+ import config from "../../config";
10
10
  import { rankSetter } from "./schema";
11
11
  import dayjs from "dayjs";
12
12