jax-hono 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Jax-hono
2
+
3
+ Jax 是本项目的轻量框架层。完整说明见 [docs/jax.md](docs/jax.md)。
4
+
5
+ 维护 Jax 内部代码时按目录职责放置文件:
6
+
7
+ ```txt
8
+ core/ Hono runtime、生命周期、Controller/Service
9
+ middleware/ 框架中间件
10
+ plugins/ 插件定义、内置插件配置、内置插件实现
11
+ build/ 代码生成/扫描注册
12
+ helpers/ 框架辅助函数
13
+ utils/ 纯工具函数
14
+ errors/ 统一错误类型
15
+ types/ 框架公共类型
16
+ generated/ 自动生成的注册表
17
+ ```
package/bin/jax.ts ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { cac } from "cac";
4
+ import { $ } from "bun";
5
+ import path from "node:path";
6
+
7
+ const cli = cac("jax");
8
+
9
+ cli
10
+ .command(
11
+ "link",
12
+ "Create a symlink (./jax -> jax-hono installation directory)",
13
+ )
14
+ .action(async () => {
15
+ const sourceDir = path.resolve(import.meta.dir, "..");
16
+ const targetDir = path.resolve(process.cwd(), "jax");
17
+
18
+ if (process.platform === "win32") {
19
+ const result =
20
+ await $`cmd /c mklink /D ${targetDir} ${sourceDir}`.nothrow();
21
+ if (result.exitCode !== 0) {
22
+ console.error(
23
+ "Failed to create symlink. Make sure you run as Administrator.",
24
+ );
25
+ process.exit(1);
26
+ }
27
+ console.log(`Symlink created: ${targetDir} <<===>> ${sourceDir}`);
28
+ } else {
29
+ const relativeSource = path.relative(path.dirname(targetDir), sourceDir);
30
+ const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow();
31
+ if (result.exitCode !== 0) {
32
+ console.error(`Failed to create symlink: ${result.stderr.toString()}`);
33
+ process.exit(1);
34
+ }
35
+ console.log(`Symlink created: ${targetDir} -> ${relativeSource}`);
36
+ }
37
+ });
38
+
39
+ cli
40
+ .command("open", "Open jax-hono installation directory in file manager")
41
+ .action(async () => {
42
+ const sourceDir = path.resolve(import.meta.dir, "..");
43
+
44
+ let command: string[];
45
+ if (process.platform === "darwin") {
46
+ command = ["open", sourceDir];
47
+ } else if (process.platform === "win32") {
48
+ command = ["explorer", sourceDir];
49
+ } else {
50
+ command = ["xdg-open", sourceDir];
51
+ }
52
+
53
+ const [cmd, ...args] = command;
54
+ const result = await $`${cmd} ${args}`.nothrow();
55
+ if (result.exitCode !== 0) {
56
+ console.error(`Failed to open directory: ${result.stderr.toString()}`);
57
+ process.exit(1);
58
+ }
59
+ console.log(`Opened: ${sourceDir}`);
60
+ });
61
+
62
+ cli.help();
63
+ const parsed = cli.parse();
64
+
65
+ // Show help when no command is matched (e.g. bare `jax` or unknown command)
66
+ if (!cli.matchedCommand && !parsed.options.help) {
67
+ cli.outputHelp();
68
+ }
package/build/build.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { setupJax } from '../setup'
2
+
3
+ setupJax()
4
+
5
+ const result = await Bun.build({
6
+ entrypoints: ['src/index.ts'],
7
+ outdir: 'dist',
8
+ target: 'bun',
9
+ })
10
+
11
+ if (!result.success) {
12
+ for (const log of result.logs) {
13
+ console.error(log)
14
+ }
15
+
16
+ process.exit(1)
17
+ }
18
+
19
+ console.log('Build completed.')
@@ -0,0 +1,115 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
2
+ import { join, parse, relative } from 'node:path'
3
+
4
+ const srcDir = join(import.meta.dir, '../..')
5
+ const configDir = join(srcDir, 'config')
6
+ const typesFile = join(srcDir, 'types/index.ts')
7
+ const generatedDir = join(srcDir, 'jax/generated')
8
+ const outputFile = join(generatedDir, 'config.ts')
9
+ const envStages = new Set([
10
+ 'default',
11
+ 'local',
12
+ 'dev',
13
+ 'development',
14
+ 'test',
15
+ 'staging',
16
+ 'pre',
17
+ 'uat',
18
+ 'prod',
19
+ 'production',
20
+ ])
21
+
22
+ function toImportName(fileName: string, index: number) {
23
+ return `config${index}_${parse(fileName).name.replace(/[^a-zA-Z0-9_$]/g, '_')}`
24
+ }
25
+
26
+ function getStage(fileName: string) {
27
+ const stage = parse(fileName).name.split('.').at(-1)
28
+
29
+ if (stage && envStages.has(stage)) {
30
+ return stage
31
+ }
32
+
33
+ return 'base'
34
+ }
35
+
36
+ function getImportPath(fileName: string) {
37
+ const { dir, name } = parse(fileName)
38
+ const path = join('../../config', dir, name).replaceAll('\\', '/')
39
+
40
+ return path.startsWith('.') ? path : `./${path}`
41
+ }
42
+
43
+ function getConfigFiles() {
44
+ if (!existsSync(configDir)) {
45
+ return []
46
+ }
47
+
48
+ return readdirSync(configDir)
49
+ .filter((fileName) => /^config\.[^.]+\.ts$/.test(fileName))
50
+ .sort((left, right) => {
51
+ const stageWeight: Record<string, number> = {
52
+ base: 0,
53
+ default: 1,
54
+ }
55
+
56
+ const diff = (stageWeight[getStage(left)] ?? 2) - (stageWeight[getStage(right)] ?? 2)
57
+
58
+ if (diff !== 0) {
59
+ return diff
60
+ }
61
+
62
+ return left.localeCompare(right)
63
+ })
64
+ }
65
+
66
+ function hasProjectConfigType() {
67
+ if (!existsSync(typesFile)) {
68
+ return false
69
+ }
70
+
71
+ const source = readFileSync(typesFile, 'utf8')
72
+
73
+ return /\bexport\s+(?:type|interface)\s+ProjectConfig\b/.test(source)
74
+ }
75
+
76
+ export function generateConfig() {
77
+ const files = getConfigFiles()
78
+ const defaultConfigIndex = files.findIndex((fileName) => getStage(fileName) === 'default')
79
+ const imports = files.map((fileName, index) => {
80
+ return `import ${toImportName(fileName, index)} from '${getImportPath(fileName)}'`
81
+ })
82
+ const modules = files.map((fileName, index) => {
83
+ return ` { name: '${fileName}', stage: '${getStage(fileName)}', config: ${toImportName(fileName, index)} },`
84
+ })
85
+ const hasProjectTypes = hasProjectConfigType()
86
+ const typeImports = hasProjectTypes ? [`import type { ProjectConfig } from '../../types'`] : []
87
+ const projectConfigType = hasProjectTypes
88
+ ? 'export type { ProjectConfig }'
89
+ : defaultConfigIndex === -1
90
+ ? 'export type ProjectConfig = Record<string, unknown>'
91
+ : `export type ProjectConfig = typeof ${toImportName(files[defaultConfigIndex], defaultConfigIndex)}`
92
+ const content = `${[...typeImports, ...imports].join('\n')}
93
+
94
+ export const configModules = [
95
+ ${modules.join('\n')}
96
+ ] as const
97
+
98
+ ${projectConfigType}
99
+
100
+ export default configModules
101
+ `
102
+
103
+ mkdirSync(generatedDir, { recursive: true })
104
+
105
+ if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
106
+ return
107
+ }
108
+
109
+ writeFileSync(outputFile, content)
110
+ console.log(`Generated ${relative(srcDir, outputFile)} with ${files.length} config file(s).`)
111
+ }
112
+
113
+ if (import.meta.main) {
114
+ generateConfig()
115
+ }