sumor 1.3.0 → 2.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.
Files changed (179) hide show
  1. package/.eslintignore +6 -0
  2. package/.eslintrc.yml +4 -0
  3. package/.gitattributes +1 -0
  4. package/.github/workflows/audit.yml +22 -0
  5. package/.github/workflows/available.yml +22 -0
  6. package/.github/workflows/ci.yml +45 -0
  7. package/.github/workflows/coverage.yml +23 -0
  8. package/.github/workflows/publish.yml +31 -0
  9. package/.github/workflows/ut.yml +24 -0
  10. package/.husky/pre-commit +10 -0
  11. package/.husky/pre-push +10 -0
  12. package/.prettierrc.yml +7 -0
  13. package/LICENSE +21 -0
  14. package/demo/api/error.js +3 -0
  15. package/demo/api/file.js +4 -0
  16. package/demo/api/file.yaml +11 -0
  17. package/demo/api/hello.js +4 -0
  18. package/demo/api/hello.yaml +13 -0
  19. package/demo/api/login.js +4 -0
  20. package/demo/api/logout.js +4 -0
  21. package/demo/api/long.js +9 -0
  22. package/demo/api/long.yaml +2 -0
  23. package/demo/api/user/admin.js +12 -0
  24. package/demo/api/user/info.js +8 -0
  25. package/demo/config/config.yaml +2 -0
  26. package/demo/i18n/demo.yaml +1 -0
  27. package/demo/package.json +6 -0
  28. package/i18n/README.md +32 -0
  29. package/i18n/sumor_api.yaml +16 -0
  30. package/i18n/sumor_app.yaml +3 -0
  31. package/i18n/sumor_internal.yaml +1 -0
  32. package/i18n/sumor_token.yaml +2 -0
  33. package/i18nExt/sumor_api.de.yaml +3 -0
  34. package/i18nExt/sumor_api.en.yaml +3 -0
  35. package/i18nExt/sumor_api.es.yaml +3 -0
  36. package/i18nExt/sumor_api.fr.yaml +3 -0
  37. package/i18nExt/sumor_api.it.yaml +3 -0
  38. package/i18nExt/sumor_api.ja.yaml +3 -0
  39. package/i18nExt/sumor_api.ko.yaml +3 -0
  40. package/i18nExt/sumor_api.pt.yaml +3 -0
  41. package/i18nExt/sumor_api.zh-TW.yaml +3 -0
  42. package/i18nExt/sumor_api.zh.yaml +3 -0
  43. package/i18nExt/sumor_app.de.yaml +3 -0
  44. package/i18nExt/sumor_app.en.yaml +3 -0
  45. package/i18nExt/sumor_app.es.yaml +3 -0
  46. package/i18nExt/sumor_app.zh-TW.yaml +3 -0
  47. package/i18nExt/sumor_app.zh.yaml +3 -0
  48. package/i18nExt/sumor_internal.de.yaml +1 -0
  49. package/i18nExt/sumor_internal.en.yaml +1 -0
  50. package/i18nExt/sumor_internal.es.yaml +1 -0
  51. package/i18nExt/sumor_internal.zh-TW.yaml +1 -0
  52. package/i18nExt/sumor_internal.zh.yaml +1 -0
  53. package/i18nExt/sumor_token.de.yaml +2 -0
  54. package/i18nExt/sumor_token.es.yaml +2 -0
  55. package/i18nExt/sumor_token.fr.yaml +2 -0
  56. package/i18nExt/sumor_token.it.yaml +2 -0
  57. package/i18nExt/sumor_token.ja.yaml +2 -0
  58. package/i18nExt/sumor_token.ko.yaml +2 -0
  59. package/i18nExt/sumor_token.pt.yaml +2 -0
  60. package/i18nExt/sumor_token.ru.yaml +2 -0
  61. package/i18nExt/sumor_token.tr.yaml +2 -0
  62. package/i18nExt/sumor_token.zh-TW.yaml +2 -0
  63. package/i18nExt/sumor_token.zh.yaml +2 -0
  64. package/jest.config.json +26 -0
  65. package/modules/alertPage/html/template.html +162 -0
  66. package/modules/alertPage/html/undraw_access-denied.svg +52 -0
  67. package/modules/alertPage/html/undraw_alert.svg +1 -0
  68. package/modules/alertPage/html/undraw_celebration.svg +78 -0
  69. package/modules/alertPage/html/undraw_complete-form.svg +1 -0
  70. package/modules/alertPage/html/undraw_login.svg +1 -0
  71. package/modules/alertPage/index.js +46 -0
  72. package/modules/alertPage/index.test.js +12 -0
  73. package/modules/i18n/README.md +90 -0
  74. package/modules/i18n/convertI18nValue/README.md +31 -0
  75. package/modules/i18n/convertI18nValue/getI18nTemplate.js +26 -0
  76. package/modules/i18n/convertI18nValue/getI18nTemplate.test.js +40 -0
  77. package/modules/i18n/convertI18nValue/index.js +14 -0
  78. package/modules/i18n/convertI18nValue/index.test.js +55 -0
  79. package/modules/i18n/convertI18nValue/stringVariableReplace.js +13 -0
  80. package/modules/i18n/convertI18nValue/stringVariableReplace.test.js +39 -0
  81. package/modules/i18n/index.js +26 -0
  82. package/modules/i18n/index.test.js +13 -0
  83. package/modules/i18n/load/README.md +28 -0
  84. package/modules/i18n/load/load.js +31 -0
  85. package/modules/i18n/load/load.test.js +30 -0
  86. package/modules/i18n/registry.js +48 -0
  87. package/modules/i18n/registry.test.js +84 -0
  88. package/modules/logger/convert/parseFile.js +14 -0
  89. package/modules/logger/convert/parseFile.test.js +28 -0
  90. package/modules/logger/convert/stringifyCMD.js +48 -0
  91. package/modules/logger/convert/stringifyCMD.test.js +37 -0
  92. package/modules/logger/convert/stringifyFile.js +24 -0
  93. package/modules/logger/convert/stringifyFile.test.js +79 -0
  94. package/modules/logger/index.js +82 -0
  95. package/modules/logger/index.test.js +124 -0
  96. package/modules/logger/logFileOperator.js +50 -0
  97. package/modules/logger/logFileOperator.test.js +69 -0
  98. package/modules/middlewares/apiMiddleware/errorCatcher.js +9 -0
  99. package/modules/middlewares/apiMiddleware/exposeApis/index.js +82 -0
  100. package/modules/middlewares/apiMiddleware/index.js +111 -0
  101. package/modules/middlewares/apiMiddleware/index.test.js +145 -0
  102. package/modules/middlewares/apiMiddleware/load/index.js +35 -0
  103. package/modules/middlewares/apiMiddleware/load/index.test.js +30 -0
  104. package/modules/middlewares/apiMiddleware/metadataToSwagger.js +139 -0
  105. package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.js +26 -0
  106. package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.test.js +36 -0
  107. package/modules/middlewares/apiMiddleware/prepareData/format/convertType.js +48 -0
  108. package/modules/middlewares/apiMiddleware/prepareData/format/convertType.test.js +53 -0
  109. package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.js +31 -0
  110. package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.test.js +31 -0
  111. package/modules/middlewares/apiMiddleware/prepareData/format/index.js +18 -0
  112. package/modules/middlewares/apiMiddleware/prepareData/format/index.test.js +40 -0
  113. package/modules/middlewares/apiMiddleware/prepareData/format/precision.js +12 -0
  114. package/modules/middlewares/apiMiddleware/prepareData/format/precision.test.js +33 -0
  115. package/modules/middlewares/apiMiddleware/prepareData/format/trim.js +15 -0
  116. package/modules/middlewares/apiMiddleware/prepareData/format/trim.test.js +24 -0
  117. package/modules/middlewares/apiMiddleware/prepareData/index.js +29 -0
  118. package/modules/middlewares/apiMiddleware/prepareData/index.test.js +121 -0
  119. package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.js +26 -0
  120. package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.test.js +52 -0
  121. package/modules/middlewares/apiMiddleware/prepareData/validate/index.js +30 -0
  122. package/modules/middlewares/apiMiddleware/public/favicon.ico +0 -0
  123. package/modules/middlewares/apiMiddleware/response/sendError.js +57 -0
  124. package/modules/middlewares/apiMiddleware/response/sendError.test.js +251 -0
  125. package/modules/middlewares/apiMiddleware/response/sendNotFound.js +26 -0
  126. package/modules/middlewares/apiMiddleware/response/sendResponse.js +25 -0
  127. package/modules/middlewares/apiMiddleware/response/sendSuccess.js +30 -0
  128. package/modules/middlewares/bodyMiddleware/cleanupFiles.js +14 -0
  129. package/modules/middlewares/bodyMiddleware/cleanupFiles.test.js +54 -0
  130. package/modules/middlewares/bodyMiddleware/fileParser.js +69 -0
  131. package/modules/middlewares/bodyMiddleware/fileParser.test.js +163 -0
  132. package/modules/middlewares/bodyMiddleware/index.js +12 -0
  133. package/modules/middlewares/bodyMiddleware/index.test.js +64 -0
  134. package/modules/middlewares/bodyMiddleware/mergeData.js +4 -0
  135. package/modules/middlewares/bodyMiddleware/mergeData.test.js +38 -0
  136. package/modules/middlewares/i18nMiddleware/index.js +82 -0
  137. package/modules/middlewares/i18nMiddleware/index.test.js +75 -0
  138. package/modules/middlewares/tokenMiddleware/Token.js +115 -0
  139. package/modules/middlewares/tokenMiddleware/Token.test.js +67 -0
  140. package/modules/middlewares/tokenMiddleware/index.js +32 -0
  141. package/modules/middlewares/tokenMiddleware/index.test.js +115 -0
  142. package/modules/middlewares/tokenMiddleware/parseCookie.js +24 -0
  143. package/modules/middlewares/tokenMiddleware/parseCookie.test.js +49 -0
  144. package/modules/serve/formatConfig.js +7 -0
  145. package/modules/serve/formatConfig.test.js +28 -0
  146. package/modules/serve/index.js +30 -0
  147. package/modules/serve/index.test.js +69 -0
  148. package/modules/serve/listenApp.js +11 -0
  149. package/modules/system/getSystemLanguage.js +9 -0
  150. package/modules/system/getSystemLanguage.test.js +19 -0
  151. package/modules/utils/getError.js +56 -0
  152. package/modules/utils/getError.test.js +97 -0
  153. package/modules/utils/loadConfig.js +73 -0
  154. package/modules/utils/loadConfig.test.js +129 -0
  155. package/modules/utils/pathUtils.js +43 -0
  156. package/modules/utils/pathUtils.test.js +52 -0
  157. package/modules/utils/type.js +14 -0
  158. package/modules/utils/type.test.js +40 -0
  159. package/package.json +61 -1
  160. package/src/app.js +40 -0
  161. package/src/cli.js +28 -0
  162. package/src/index.js +3 -0
  163. package/test-utils/codeTest/README.md +4 -0
  164. package/test-utils/codeTest/custom.css +47 -0
  165. package/test-utils/codeTest/index.js +80 -0
  166. package/test-utils/codeTest/utils/calculateCoverage.js +55 -0
  167. package/test-utils/codeTest/utils/getTestFiles.js +20 -0
  168. package/test-utils/codeTest/utils/runTests.js +38 -0
  169. package/test-utils/codeTest/utils/startServer.js +23 -0
  170. package/test-utils/tmp.js +15 -0
  171. package/cli.js +0 -3
  172. package/index.es.js +0 -578
  173. package/template/web/AppWithFrame.vue +0 -20
  174. package/template/web/index.html +0 -19
  175. package/template/web/src/App.vue +0 -15
  176. package/template/web/src/entry-client.js +0 -9
  177. package/template/web/src/entry-server.js +0 -69
  178. package/template/web/src/main.js +0 -41
  179. package/template/web/src/style.scss +0 -389
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect } from '@jest/globals'
2
+ import loadConfig from './loadConfig.js'
3
+ import tmp from '../../test-utils/tmp.js'
4
+ import fse from 'fs-extra'
5
+ import path from 'path'
6
+
7
+ describe('loadConfig', () => {
8
+ const prepare = async () => {
9
+ const caseDir = await tmp('config')
10
+ return path.join(caseDir, 'main')
11
+ }
12
+
13
+ it('应该将 JSON 配置文件加载为对象', async () => {
14
+ const configPath = await prepare()
15
+ const configContent = { key: 'value' }
16
+ await fse.writeFile(`${configPath}.json`, JSON.stringify(configContent))
17
+ const config = await loadConfig(configPath)
18
+ expect(config).toEqual(configContent)
19
+ })
20
+
21
+ it('应该将 YAML 配置文件加载为对象', async () => {
22
+ const configPath = await prepare()
23
+ const configContent = { key: 'value' }
24
+ await fse.writeFile(`${configPath}.yaml`, 'key: value')
25
+ const config = await loadConfig(configPath)
26
+ expect(config).toEqual(configContent)
27
+ })
28
+
29
+ it('如果未找到配置文件,则应返回一个空对象', async () => {
30
+ const configPath = await prepare()
31
+ const config = await loadConfig(configPath)
32
+ expect(config).toEqual({})
33
+ })
34
+
35
+ it('应该按优先级加载配置文件: yaml > json > yml', async () => {
36
+ const configPath = await prepare()
37
+ const configContentJson = { key: 'json' }
38
+ const configContentYaml = { key: 'yaml' }
39
+ const configContentYml = { key: 'yml' }
40
+
41
+ let config
42
+ await fse.writeFile(`${configPath}.yml`, 'key: yml')
43
+ config = await loadConfig(configPath)
44
+ expect(config).toEqual(configContentYml)
45
+ await fse.writeFile(`${configPath}.json`, JSON.stringify(configContentJson))
46
+ config = await loadConfig(configPath)
47
+ expect(config).toEqual(configContentJson)
48
+ await fse.writeFile(`${configPath}.yml`, 'key: yml')
49
+ config = await loadConfig(configPath)
50
+ expect(config).toEqual(configContentJson)
51
+ await fse.writeFile(`${configPath}.yaml`, 'key: yaml')
52
+ config = await loadConfig(configPath)
53
+ expect(config).toEqual(configContentYaml)
54
+ })
55
+
56
+ it('应该支持带有多个点的文件名', async () => {
57
+ const configPath = await prepare()
58
+ const configContent = { key: 'value' }
59
+ await fse.writeFile(`${configPath}.zh-CN.json`, JSON.stringify(configContent))
60
+ let config = await loadConfig(`${configPath}.zh-CN`)
61
+ expect(config).toEqual(configContent)
62
+ await fse.writeFile(`${configPath}.zh-CN.yml`, 'key: value')
63
+ config = await loadConfig(`${configPath}.zh-CN`)
64
+ expect(config).toEqual(configContent)
65
+ })
66
+
67
+ it('应该支持路径中包含后缀名的情况 (yml)', async () => {
68
+ const configPath = await prepare()
69
+ const configContent = { key: 'value' }
70
+ await fse.writeFile(`${configPath}.yml`, 'key: value')
71
+ const config = await loadConfig(`${configPath}.yml`)
72
+ expect(config).toEqual(configContent)
73
+ })
74
+
75
+ it('应该支持路径中包含后缀名的情况 (json)', async () => {
76
+ const configPath = await prepare()
77
+ const configContent = { key: 'value' }
78
+ await fse.writeFile(`${configPath}.json`, JSON.stringify(configContent))
79
+ const config = await loadConfig(`${configPath}.json`)
80
+ expect(config).toEqual(configContent)
81
+ })
82
+
83
+ it('应该支持多个配置查询,条件为 *.yaml', async () => {
84
+ const rootDir = await tmp('config')
85
+ const yamlContent1 = { key: 'value1' }
86
+ const yamlContent2 = { key: 'value2' }
87
+
88
+ await fse.writeFile(path.join(rootDir, 'config1.yaml'), 'key: value1')
89
+ await fse.writeFile(path.join(rootDir, 'config2.yaml'), 'key: value2')
90
+
91
+ const configs = await loadConfig('*.yaml', rootDir)
92
+ expect(configs).toEqual({
93
+ config1: yamlContent1,
94
+ config2: yamlContent2
95
+ })
96
+ })
97
+
98
+ it('应该支持多个配置查询,条件为 *', async () => {
99
+ const rootDir = await tmp('config')
100
+ const yamlContent = { key: 'yamlValue' }
101
+ const jsonContent = { key: 'jsonValue' }
102
+
103
+ await fse.writeFile(path.join(rootDir, 'config.yaml'), 'key: yamlValue')
104
+ await fse.writeFile(path.join(rootDir, 'config.json'), JSON.stringify(jsonContent))
105
+
106
+ const configs = await loadConfig('*', rootDir)
107
+ expect(configs).toEqual({
108
+ config: yamlContent
109
+ })
110
+ })
111
+
112
+ it('应该支持多个配置查询,条件为 **/*', async () => {
113
+ const rootDir = await tmp('config')
114
+ const yamlContent = { key: 'yamlValue' }
115
+ const jsonContent = { key: 'jsonValue' }
116
+
117
+ const subDir = path.join(rootDir, 'subdir')
118
+ await fse.ensureDir(subDir)
119
+
120
+ await fse.writeFile(path.join(rootDir, 'config.yaml'), 'key: yamlValue')
121
+ await fse.writeFile(path.join(subDir, 'config.json'), JSON.stringify(jsonContent))
122
+
123
+ const configs = await loadConfig('**/*', rootDir)
124
+ expect(configs).toEqual({
125
+ config: yamlContent,
126
+ 'subdir/config': jsonContent
127
+ })
128
+ })
129
+ })
@@ -0,0 +1,43 @@
1
+ import path from 'path'
2
+
3
+ export function filterOneDot(files) {
4
+ return files.filter(file => {
5
+ const base = path.basename(file)
6
+ return /^[^.]+\.[^.]+$/.test(base)
7
+ })
8
+ }
9
+
10
+ export function filterDoubleDot(files) {
11
+ return files.filter(file => {
12
+ const base = path.basename(file)
13
+ return /^[^.]+\.[^.]+\.[^.]+$/.test(base)
14
+ })
15
+ }
16
+
17
+ export function getRelativePath(absDirectory, file) {
18
+ absDirectory = path.normalize(absDirectory)
19
+ file = path.normalize(file)
20
+
21
+ let relative = file.replace(absDirectory, '')
22
+ relative = relative.split('.')[0]
23
+ relative = relative.replace(/\\/g, '/')
24
+ relative = relative.replace(/^\//, '')
25
+ return relative
26
+ }
27
+
28
+ export function joinPath(...paths) {
29
+ if (paths.length === 0) return ''
30
+
31
+ const normalizedPaths = paths.map(p => path.normalize(p))
32
+ let result = normalizedPaths[0]
33
+
34
+ for (let i = 1; i < normalizedPaths.length; i++) {
35
+ if (path.isAbsolute(normalizedPaths[i])) {
36
+ result = normalizedPaths[i]
37
+ } else {
38
+ result = path.join(result, normalizedPaths[i])
39
+ }
40
+ }
41
+
42
+ return result
43
+ }
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect } from '@jest/globals'
2
+ import { filterOneDot, filterDoubleDot, getRelativePath, joinPath } from './pathUtils.js'
3
+
4
+ describe('Utility Functions', () => {
5
+ it('过滤出包含一个点的文件', () => {
6
+ const files = ['demo.yml', 'demo.zh-CN.yml', 'demo.en.json', 'demo.en.json.bak']
7
+ const filteredFiles = filterOneDot(files)
8
+ expect(filteredFiles.length).toBe(1)
9
+ expect(filteredFiles).toContain('demo.yml')
10
+ })
11
+
12
+ it('过滤出包含两个点的文件', () => {
13
+ const files = ['demo.yml', 'demo.zh-CN.yml', 'demo.en.json', 'demo.en.json.bak']
14
+ const filteredFiles = filterDoubleDot(files)
15
+ expect(filteredFiles.length).toBe(2)
16
+ expect(filteredFiles).toContain('demo.en.json')
17
+ expect(filteredFiles).toContain('demo.zh-CN.yml')
18
+ })
19
+
20
+ it('获取相对路径', () => {
21
+ const absDirectory = '/Users/demo'
22
+ const file = '/Users/demo/tmp/i18n/registry1/demo.yml'
23
+ const relativePath = getRelativePath(absDirectory, file)
24
+ expect(relativePath).toBe('tmp/i18n/registry1/demo')
25
+
26
+ // 支持windows
27
+ const absDirectoryWin = 'C:\\Users\\demo'
28
+ const fileWin = 'C:\\Users\\demo\\tmp\\i18n\\registry1\\demo.yml'
29
+ const relativePathWin = getRelativePath(absDirectoryWin, fileWin)
30
+ expect(relativePathWin).toBe('tmp/i18n/registry1/demo')
31
+ })
32
+
33
+ it('拼接路径 - 空路径数组', () => {
34
+ expect(joinPath()).toBe('')
35
+ })
36
+
37
+ it('拼接路径 - 多路径拼接', () => {
38
+ if (process.platform === 'darwin' || process.platform === 'linux') {
39
+ const path1 = '/Users/demo'
40
+ const path2 = 'tmp/i18n'
41
+ const path3 = 'registry1/demo.yml'
42
+ expect(joinPath(path1, path2, path3)).toBe('/Users/demo/tmp/i18n/registry1/demo.yml')
43
+ }
44
+
45
+ if (process.platform === 'win32') {
46
+ const path1 = 'C:\\Users\\demo'
47
+ const path2 = 'tmp\\i18n'
48
+ const path3 = 'registry1\\demo.yml'
49
+ expect(joinPath(path1, path2, path3)).toBe('C:\\Users\\demo\\tmp\\i18n\\registry1\\demo.yml')
50
+ }
51
+ })
52
+ })
@@ -0,0 +1,14 @@
1
+ export default value => {
2
+ let type = typeof value
3
+ if (value === null) {
4
+ type = 'null'
5
+ } else if (type === 'object') {
6
+ const objType = Object.prototype.toString.call(value)
7
+ if (objType === '[object Array]') {
8
+ type = 'array'
9
+ } else if (objType === '[object RegExp]') {
10
+ type = 'regexp'
11
+ }
12
+ }
13
+ return type
14
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, test, expect } from '@jest/globals'
2
+ import type from './type.js'
3
+
4
+ describe('type 函数', () => {
5
+ test('应该返回 "null" 对于 null', () => {
6
+ expect(type(null)).toBe('null')
7
+ })
8
+
9
+ test('应该返回 "array" 对于数组', () => {
10
+ expect(type([])).toBe('array')
11
+ })
12
+
13
+ test('应该返回 "regexp" 对于正则表达式', () => {
14
+ expect(type(/abc/)).toBe('regexp')
15
+ })
16
+
17
+ test('应该返回 "object" 对于对象', () => {
18
+ expect(type({})).toBe('object')
19
+ })
20
+
21
+ test('应该返回 "string" 对于字符串', () => {
22
+ expect(type('hello')).toBe('string')
23
+ })
24
+
25
+ test('应该返回 "number" 对于数字', () => {
26
+ expect(type(123)).toBe('number')
27
+ })
28
+
29
+ test('应该返回 "boolean" 对于布尔值', () => {
30
+ expect(type(true)).toBe('boolean')
31
+ })
32
+
33
+ test('应该返回 "undefined" 对于 undefined', () => {
34
+ expect(type(undefined)).toBe('undefined')
35
+ })
36
+
37
+ test('应该返回 "function" 对于函数', () => {
38
+ expect(type(() => {})).toBe('function')
39
+ })
40
+ })
package/package.json CHANGED
@@ -1 +1,61 @@
1
- {"name":"sumor","type":"module","bin":{"sumor":"cli.js","sr":"cli.js"},"main":"./index.es.js","module":"./index.es.js","exports":{".":{"import":"./index.es.js"}},"dependencies":{"@vitejs/plugin-vue":"^4.2.3","chalk":"^5.3.0","commander":"^11.0.0","esbuild":"^0.19.3","fs-extra":"^11.1.1","vite":"^4.4.9","axios":"^1.4.0","@alicloud/sms-sdk":"^1.1.6","ali-oss":"6.1.1","body-parser":"^1.20.1","compression":"^1.7.4","cookie-parser":"^1.4.6","express":"^4.18.2","knex":"^2.1.0","multer":"^1.4.2","mysql2":"^3.1.0","serve-static":"^1.14.1","spdy":"^4.0.2","uuid":"^3.3.2","glob":"^10.3.4","jsrsasign":"^10.8.6","yaml":"^2.3.2","chokidar":"^3.5.3","http-proxy-middleware":"^2.0.6","archiver":"^2.1.1","extract-zip":"^1.6.6","inquirer":"^8.2.5","node-ssh":"^6.0.0","os-utils":"0.0.14","vite-plugin-rewrite-all":"^1.0.1"},"version":"1.3.0"}
1
+ {
2
+ "name": "sumor",
3
+ "version": "2.0.0",
4
+ "main": "src/index.js",
5
+ "type": "module",
6
+ "bin": {
7
+ "sumor": "src/cli.js"
8
+ },
9
+ "scripts": {
10
+ "demo": "cd demo && npm run start",
11
+ "check": "npm run autofix && npm run coverage",
12
+ "codeTest": "node test-utils/codeTest/index.js",
13
+ "=============devops=============": "",
14
+ "lint": "eslint .",
15
+ "autofix": "eslint --fix . && prettier --write . && npm audit fix --force",
16
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch='**/*.test.js'",
17
+ "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --testMatch='**/*.test.js'",
18
+ "=============automation=============": "",
19
+ "prepare": "husky",
20
+ "pre-commit": "npm run autofix",
21
+ "pre-push": "npm run test",
22
+ "=============publish=============": "",
23
+ "push-tag": "git push && git push --tags",
24
+ "publish-beta": "npm version prerelease --preid beta && npm run push-tag",
25
+ "publish-release-patch": "npm version patch && npm run push-tag",
26
+ "publish-release-minor": "npm version minor && npm run push-tag",
27
+ "publish-release-major": "npm version major && npm run push-tag"
28
+ },
29
+ "keywords": [],
30
+ "author": "",
31
+ "license": "UNLICENSED",
32
+ "description": "",
33
+ "dependencies": {
34
+ "axios": "^1.8.4",
35
+ "body-parser": "^2.2.0",
36
+ "chalk": "^5.4.1",
37
+ "commander": "^13.1.0",
38
+ "express": "^4.21.2",
39
+ "fs-extra": "^11.3.0",
40
+ "glob": "^11.0.1",
41
+ "multer": "^1.4.5-lts.2",
42
+ "os-locale": "^6.0.2",
43
+ "serve-static": "^2.2.0",
44
+ "swagger-ui-express": "^5.0.1",
45
+ "yaml": "^2.7.1"
46
+ },
47
+ "devDependencies": {
48
+ "@jest/globals": "^29.7.0",
49
+ "eslint": "^8.57.0",
50
+ "eslint-config-prettier": "^9.1.0",
51
+ "eslint-config-standard": "^17.1.0",
52
+ "eslint-plugin-prettier": "^5.1.3",
53
+ "form-data": "^4.0.2",
54
+ "husky": "^9.0.11",
55
+ "inquirer": "^12.4.2",
56
+ "jest": "^29.7.0",
57
+ "jest-html-reporter": "^3.10.2",
58
+ "portfinder": "^1.0.33",
59
+ "prettier": "^3.2.5"
60
+ }
61
+ }
package/src/app.js ADDED
@@ -0,0 +1,40 @@
1
+ import loadConfig from '../modules/utils/loadConfig.js'
2
+ import serve from '../modules/serve/index.js'
3
+ import i18nMiddleware from '../modules/middlewares/i18nMiddleware/index.js'
4
+ import { cache } from '../modules/i18n/registry.js'
5
+ import apiMiddleware from '../modules/middlewares/apiMiddleware/index.js'
6
+ import tokenMiddleware from '../modules/middlewares/tokenMiddleware/index.js'
7
+
8
+ export default async configPath => {
9
+ configPath = configPath || `${process.cwd()}/config/config`
10
+ const userConfig = await loadConfig(configPath)
11
+
12
+ const initApp = app => {
13
+ const { logger } = app.namespace('sumor_app')
14
+ logger.info('APP_STARTING')
15
+
16
+ for (const namespace in cache) {
17
+ logger.debug('I18N_REGISTERED', { namespace: namespace.toUpperCase() })
18
+ }
19
+
20
+ // 记录HTTP请求
21
+ app.use(async (req, res, next) => {
22
+ const { logger } = req.namespace('SUMOR_HTTP')
23
+ const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || '0.0.0.0'
24
+ const agent = req.headers['user-agent'] || 'unknown agent'
25
+ logger.trace(`${req.method} ${req.originalUrl} IP/${ip} ${agent}`)
26
+ next()
27
+ })
28
+ }
29
+
30
+ const { app, config } = await serve(userConfig, [
31
+ i18nMiddleware,
32
+ tokenMiddleware,
33
+ initApp,
34
+ apiMiddleware(`${process.cwd()}/api`)
35
+ ])
36
+
37
+ const { logger } = app.namespace('sumor_app')
38
+
39
+ logger.info('APP_STARTED', { port: config.port })
40
+ }
package/src/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fse from 'fs-extra'
4
+ import path from 'path'
5
+ import { Command } from 'commander'
6
+ import { fileURLToPath } from 'url'
7
+
8
+ // modules
9
+ import app from './app.js'
10
+
11
+ const __filename = fileURLToPath(import.meta.url)
12
+ const __dirname = path.dirname(__filename)
13
+
14
+ const packageJsonPath = path.resolve(__dirname, '../package.json')
15
+ const packageJson = await fse.readJSON(packageJsonPath)
16
+
17
+ const program = new Command()
18
+
19
+ program.name('sumor-cli').description('轻呈云应用命令行工具').version(packageJson.version)
20
+
21
+ program
22
+ .command('start')
23
+ .description('启动轻呈云应用')
24
+ .action(() => {
25
+ app()
26
+ })
27
+
28
+ program.parse(process.argv)
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import config from '../modules/utils/loadConfig.js'
2
+
3
+ export { config }
@@ -0,0 +1,4 @@
1
+ # 代码测试器
2
+
3
+ 代码测试不指定文件的情况下,会自动测试当前目录下的所有代码文件。这消耗太多时间,而指定文件名又很麻烦。
4
+ 该工具将通过命令行交互,查找当前目录下的所有测试文件,让用户选择要测试的文件,然后执行测试。
@@ -0,0 +1,47 @@
1
+ /* 当浏览器设置为dark模式时生效 */
2
+ a {
3
+ color: #2196f3;
4
+ }
5
+
6
+ @media (prefers-color-scheme: dark) {
7
+ body {
8
+ background: #000;
9
+ color: #fff;
10
+ }
11
+ .quiet {
12
+ color: rgba(255, 255, 255, 0.5);
13
+ }
14
+ .low {
15
+ background: rgba(255, 87, 34, 0.5);
16
+ }
17
+ .high {
18
+ background: rgba(255, 255, 255, 0.1);
19
+ }
20
+ .medium {
21
+ background: rgba(255, 87, 34, 0.3);
22
+ }
23
+
24
+ .status-line.medium,
25
+ .medium .cover-fill {
26
+ background: rgb(255, 87, 34);
27
+ }
28
+ a {
29
+ color: #a9d9ff;
30
+ }
31
+ .coverage-summary th {
32
+ background: rgba(255, 255, 255, 0.3);
33
+ }
34
+ thead tr {
35
+ border: 1px solid #bbb;
36
+ }
37
+ pre {
38
+ background: rgba(255, 255, 255, 1);
39
+ color: #000;
40
+ .quiet {
41
+ color: rgba(0, 0, 0, 0.7);
42
+ }
43
+ }
44
+ pre a {
45
+ color: #2196f3;
46
+ }
47
+ }
@@ -0,0 +1,80 @@
1
+ import inquirer from 'inquirer'
2
+ import path from 'path'
3
+ import fse from 'fs-extra'
4
+ import getTestFiles from './utils/getTestFiles.js'
5
+ import runTests from './utils/runTests.js'
6
+ import startServer from './utils/startServer.js'
7
+ const LAST_TEST_FILE_PATH = path.join(process.cwd(), 'tmp', 'tools', 'codeTest', 'lastTestFile.txt')
8
+
9
+ // 主函数
10
+ const executeCodeTest = async () => {
11
+ const testFiles = getTestFiles(path.join(process.cwd(), 'modules'))
12
+ if (testFiles.length === 0) {
13
+ console.log('未找到测试文件。')
14
+ return
15
+ }
16
+
17
+ // const sortedTestFiles = testFiles.sort((a, b) => {
18
+ // if (a.endsWith('/index.test.js') && !b.endsWith('/index.test.js')) return -1
19
+ // if (!a.endsWith('/index.test.js') && b.endsWith('/index.test.js')) return 1
20
+ // return a.localeCompare(b)
21
+ // })
22
+
23
+ let displayTestFiles = testFiles.map(file => {
24
+ if (file.endsWith('/index.test.js'))
25
+ return {
26
+ name: file.replace('/index.test.js', ''),
27
+ value: file
28
+ }
29
+ return {
30
+ name: file.replace('.test.js', ''),
31
+ value: file
32
+ }
33
+ })
34
+ displayTestFiles = displayTestFiles.sort((a, b) => {
35
+ if (b.name > a.name) return -1
36
+ else return 1
37
+ })
38
+
39
+ let lastTestFile = ''
40
+ if (await fse.exists(LAST_TEST_FILE_PATH)) {
41
+ lastTestFile = await fse.readFile(LAST_TEST_FILE_PATH, 'utf-8')
42
+ }
43
+
44
+ const answers = await inquirer.prompt([
45
+ {
46
+ type: 'list',
47
+ name: 'file',
48
+ message: `选择要运行的测试文件:(共${displayTestFiles.length - 1}个)`,
49
+ choices: [{ name: `全部`, value: '' }, ...displayTestFiles],
50
+ default: lastTestFile
51
+ }
52
+ ])
53
+
54
+ let selectedFile = answers.file
55
+ selectedFile = selectedFile || ''
56
+ const report = await runTests(LAST_TEST_FILE_PATH, selectedFile)
57
+
58
+ const unitPath = path.join(report, 'unit')
59
+
60
+ const generatedUnitPath = path.join(process.cwd(), 'output', 'unit')
61
+ await fse.move(generatedUnitPath, path.join(process.cwd(), unitPath))
62
+
63
+ const unitPort = await startServer(path.join(process.cwd(), unitPath))
64
+ console.log(`单元测试报告请访问 http://localhost:${unitPort}`)
65
+ // console.log(` - 报告目录:${unitPath}`)
66
+
67
+ const coveragePath = path.join(report, 'coverage', 'lcov-report')
68
+
69
+ // 将custom.css的内容追加到base.css文件中
70
+ const customCssPath = path.join(new URL('.', import.meta.url).pathname, 'custom.css')
71
+ const baseCssPath = path.join(coveragePath, 'base.css')
72
+ const customCss = await fse.readFile(customCssPath, 'utf-8')
73
+ await fse.appendFile(baseCssPath, customCss)
74
+
75
+ const coveragePort = await startServer(path.join(process.cwd(), coveragePath))
76
+ console.log(`覆盖率报告请访问 http://localhost:${coveragePort}`)
77
+ // console.log(` - 报告目录:${coveragePath}`)
78
+ }
79
+
80
+ executeCodeTest()
@@ -0,0 +1,55 @@
1
+ export function calculateCoverage(coverageJSON) {
2
+ const results = {}
3
+ let totalStatements = 0
4
+ let totalFunctions = 0
5
+ let totalBranches = 0
6
+ let totalStatementCoverage = 0
7
+ let totalFunctionCoverage = 0
8
+ let totalBranchCoverage = 0
9
+
10
+ for (const filePath in coverageJSON) {
11
+ const fileCoverage = coverageJSON[filePath]
12
+ const statements = Object.keys(fileCoverage.s).length
13
+ const functions = Object.keys(fileCoverage.f).length
14
+ const branches = Object.keys(fileCoverage.b).length
15
+ const statementCoverage =
16
+ (Object.values(fileCoverage.s).filter(v => v > 0).length / statements) * 100
17
+ const functionCoverage =
18
+ (Object.values(fileCoverage.f).filter(v => v > 0).length / functions) * 100
19
+ const branchCoverage =
20
+ (Object.values(fileCoverage.b).filter(v => v.every(b => b > 0)).length / branches) * 100
21
+
22
+ results[filePath] = {
23
+ statements,
24
+ functions,
25
+ branches,
26
+ statementCoverage,
27
+ functionCoverage,
28
+ branchCoverage
29
+ }
30
+
31
+ totalStatements += statements
32
+ totalFunctions += functions
33
+ totalBranches += branches
34
+ if (statements > 0) {
35
+ totalStatementCoverage += statementCoverage * statements
36
+ }
37
+ if (functions > 0) {
38
+ totalFunctionCoverage += functionCoverage * functions
39
+ }
40
+ if (branches > 0) {
41
+ totalBranchCoverage += branchCoverage * branches
42
+ }
43
+ }
44
+
45
+ const total = {
46
+ statements: totalStatements,
47
+ functions: totalFunctions,
48
+ branches: totalBranches,
49
+ statementCoverage: totalStatementCoverage / totalStatements,
50
+ functionCoverage: totalFunctionCoverage / totalFunctions,
51
+ branchCoverage: totalBranchCoverage / totalBranches
52
+ }
53
+
54
+ return { results, total }
55
+ }
@@ -0,0 +1,20 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ // 获取当前目录下的所有测试文件的函数
5
+ const getTestFiles = testFolder => {
6
+ let testFiles = []
7
+ const files = fs.readdirSync(testFolder)
8
+ files.forEach(file => {
9
+ const filePath = path.join(testFolder, file)
10
+ const stat = fs.statSync(filePath)
11
+ if (stat.isDirectory()) {
12
+ testFiles = testFiles.concat(getTestFiles(filePath))
13
+ } else if (file.endsWith('.test.js')) {
14
+ testFiles.push(filePath.replace(process.cwd() + path.sep, ''))
15
+ }
16
+ })
17
+ return testFiles
18
+ }
19
+
20
+ export default getTestFiles
@@ -0,0 +1,38 @@
1
+ import { execSync } from 'child_process'
2
+ import fse from 'fs-extra'
3
+ import path from 'path'
4
+
5
+ // 替换 .test.ts 为 .test.js
6
+ const runTests = async (config, file) => {
7
+ await fse.remove(path.join(process.cwd(), 'tmp', 'test'))
8
+
9
+ await fse.ensureFile(config)
10
+ await fse.writeFile(config, file, 'utf-8')
11
+
12
+ let folder = 'all'
13
+ if (file !== '') {
14
+ folder = file.replace('.test.js', '').replace(/\/|\\/g, '.').replace('.index', '')
15
+ }
16
+ const root = `./output/test/${folder}`
17
+ await fse.remove(path.join(process.cwd(), root))
18
+
19
+ try {
20
+ const cmdArr = [
21
+ 'node --experimental-vm-modules node_modules/jest/bin/jest.js',
22
+ file.replace(/\\/g, '/'),
23
+ '--coverage',
24
+ '--coverageDirectory=' + root + '/coverage'
25
+ ]
26
+ console.log(cmdArr.join(' '))
27
+ execSync(cmdArr.join(' '), {
28
+ stdio: 'inherit'
29
+ })
30
+ } catch (e) {
31
+ if (e) {
32
+ console.log('\n单元测试失败,调试请查看上述日志')
33
+ }
34
+ }
35
+ return root
36
+ }
37
+
38
+ export default runTests