@lark-apaas/coding-template-nestjs-react-fullstack 0.1.0-alpha.1

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 (250) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +41 -0
  3. package/hooks/after-install.mjs +3 -0
  4. package/hooks/after-render.mjs +3 -0
  5. package/hooks/before-render.mjs +3 -0
  6. package/package.json +36 -0
  7. package/template/.codebase/pipelines/ci.yaml +34 -0
  8. package/template/.codebase/pipelines/coco-code-review.yaml +21 -0
  9. package/template/.prettierrc +8 -0
  10. package/template/.spark_project +16 -0
  11. package/template/.stylelintrc.js +12 -0
  12. package/template/_env.local +38 -0
  13. package/template/_gitignore +64 -0
  14. package/template/_npmrc +1 -0
  15. package/template/client/index.html +23 -0
  16. package/template/client/public/favicon.svg +40 -0
  17. package/template/client/src/api/index.ts +19 -0
  18. package/template/client/src/app.tsx +22 -0
  19. package/template/client/src/components/Layout.tsx +11 -0
  20. package/template/client/src/components/business-ui/README.md +519 -0
  21. package/template/client/src/components/business-ui/api/chats/queries.ts +39 -0
  22. package/template/client/src/components/business-ui/api/chats/service.ts +29 -0
  23. package/template/client/src/components/business-ui/api/departments/queries.ts +42 -0
  24. package/template/client/src/components/business-ui/api/departments/service.ts +18 -0
  25. package/template/client/src/components/business-ui/api/files/service.ts +28 -0
  26. package/template/client/src/components/business-ui/api/user-profiles/queries.ts +42 -0
  27. package/template/client/src/components/business-ui/api/user-profiles/service.ts +23 -0
  28. package/template/client/src/components/business-ui/api/users/queries.ts +34 -0
  29. package/template/client/src/components/business-ui/api/users/service.ts +40 -0
  30. package/template/client/src/components/business-ui/chat-select/README.md +128 -0
  31. package/template/client/src/components/business-ui/chat-select/chat-item.tsx +85 -0
  32. package/template/client/src/components/business-ui/chat-select/chat-select-tag.tsx +83 -0
  33. package/template/client/src/components/business-ui/chat-select/chat-select.tsx +197 -0
  34. package/template/client/src/components/business-ui/chat-select/index.tsx +12 -0
  35. package/template/client/src/components/business-ui/chat-select/types.ts +102 -0
  36. package/template/client/src/components/business-ui/chat-select/use-chat-value.ts +180 -0
  37. package/template/client/src/components/business-ui/chat-select/utils.ts +81 -0
  38. package/template/client/src/components/business-ui/department-select/department-item.tsx +55 -0
  39. package/template/client/src/components/business-ui/department-select/department-select-field.tsx +86 -0
  40. package/template/client/src/components/business-ui/department-select/department-select-tag.tsx +56 -0
  41. package/template/client/src/components/business-ui/department-select/department-select.tsx +168 -0
  42. package/template/client/src/components/business-ui/department-select/icon-department.tsx +22 -0
  43. package/template/client/src/components/business-ui/department-select/index.tsx +7 -0
  44. package/template/client/src/components/business-ui/department-select/types.ts +15 -0
  45. package/template/client/src/components/business-ui/department-select/utils.ts +17 -0
  46. package/template/client/src/components/business-ui/entity-combobox/base-combobox-content.tsx +35 -0
  47. package/template/client/src/components/business-ui/entity-combobox/base-combobox-empty.tsx +28 -0
  48. package/template/client/src/components/business-ui/entity-combobox/base-combobox-error.tsx +35 -0
  49. package/template/client/src/components/business-ui/entity-combobox/base-combobox-item.tsx +55 -0
  50. package/template/client/src/components/business-ui/entity-combobox/base-combobox-list.tsx +174 -0
  51. package/template/client/src/components/business-ui/entity-combobox/base-combobox-loading.tsx +19 -0
  52. package/template/client/src/components/business-ui/entity-combobox/base-combobox-search.tsx +31 -0
  53. package/template/client/src/components/business-ui/entity-combobox/base-combobox-trigger.tsx +281 -0
  54. package/template/client/src/components/business-ui/entity-combobox/base-combobox.tsx +132 -0
  55. package/template/client/src/components/business-ui/entity-combobox/context.tsx +19 -0
  56. package/template/client/src/components/business-ui/entity-combobox/entity-combobox.tsx +145 -0
  57. package/template/client/src/components/business-ui/entity-combobox/highlight-text.tsx +52 -0
  58. package/template/client/src/components/business-ui/entity-combobox/hooks.tsx +21 -0
  59. package/template/client/src/components/business-ui/entity-combobox/index.tsx +31 -0
  60. package/template/client/src/components/business-ui/entity-combobox/item-pill.tsx +179 -0
  61. package/template/client/src/components/business-ui/entity-combobox/popover-wrapper.tsx +149 -0
  62. package/template/client/src/components/business-ui/entity-combobox/search-trigger.tsx +262 -0
  63. package/template/client/src/components/business-ui/entity-combobox/shared-types.ts +296 -0
  64. package/template/client/src/components/business-ui/entity-combobox/size-variants.tsx +322 -0
  65. package/template/client/src/components/business-ui/entity-combobox/types.ts +246 -0
  66. package/template/client/src/components/business-ui/entity-combobox/use-fetch-data.tsx +81 -0
  67. package/template/client/src/components/business-ui/entity-combobox/use-infinite-scroll.tsx +99 -0
  68. package/template/client/src/components/business-ui/entity-combobox/use-popover-outside-click.tsx +32 -0
  69. package/template/client/src/components/business-ui/form/checkbox-field.tsx +83 -0
  70. package/template/client/src/components/business-ui/form/context.tsx +38 -0
  71. package/template/client/src/components/business-ui/form/field-layout.tsx +91 -0
  72. package/template/client/src/components/business-ui/form/form.tsx +54 -0
  73. package/template/client/src/components/business-ui/form/hooks/form-context.tsx +4 -0
  74. package/template/client/src/components/business-ui/form/hooks/form-utils.ts +23 -0
  75. package/template/client/src/components/business-ui/form/hooks/form.tsx +40 -0
  76. package/template/client/src/components/business-ui/form/index.tsx +4 -0
  77. package/template/client/src/components/business-ui/form/input-field.tsx +74 -0
  78. package/template/client/src/components/business-ui/form/radio-group-field.tsx +138 -0
  79. package/template/client/src/components/business-ui/form/select-field.tsx +155 -0
  80. package/template/client/src/components/business-ui/form/switch-field.tsx +66 -0
  81. package/template/client/src/components/business-ui/form/textarea-field.tsx +72 -0
  82. package/template/client/src/components/business-ui/form/types.ts +32 -0
  83. package/template/client/src/components/business-ui/tiptap-editor/README.md +324 -0
  84. package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +74 -0
  85. package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +41 -0
  86. package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +41 -0
  87. package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +141 -0
  88. package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +99 -0
  89. package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +39 -0
  90. package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +65 -0
  91. package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +127 -0
  92. package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +380 -0
  93. package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +96 -0
  94. package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +75 -0
  95. package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +118 -0
  96. package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +84 -0
  97. package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +54 -0
  98. package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +539 -0
  99. package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +236 -0
  100. package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +260 -0
  101. package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +456 -0
  102. package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +47 -0
  103. package/template/client/src/components/business-ui/tiptap-editor/index.ts +38 -0
  104. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +112 -0
  105. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +169 -0
  106. package/template/client/src/components/business-ui/types/user.ts +74 -0
  107. package/template/client/src/components/business-ui/user-display/index.tsx +2 -0
  108. package/template/client/src/components/business-ui/user-display/overflow-tooltip-text.tsx +90 -0
  109. package/template/client/src/components/business-ui/user-display/type.ts +27 -0
  110. package/template/client/src/components/business-ui/user-display/user-display.tsx +99 -0
  111. package/template/client/src/components/business-ui/user-display/user-with-avatar.tsx +158 -0
  112. package/template/client/src/components/business-ui/user-display/utils.ts +37 -0
  113. package/template/client/src/components/business-ui/user-profile/error-image.tsx +302 -0
  114. package/template/client/src/components/business-ui/user-profile/user-external-script.ts +73 -0
  115. package/template/client/src/components/business-ui/user-profile/user-profile.tsx +370 -0
  116. package/template/client/src/components/business-ui/user-select/index.tsx +28 -0
  117. package/template/client/src/components/business-ui/user-select/types.ts +109 -0
  118. package/template/client/src/components/business-ui/user-select/use-user-value.ts +160 -0
  119. package/template/client/src/components/business-ui/user-select/user-item.tsx +98 -0
  120. package/template/client/src/components/business-ui/user-select/user-pill.tsx +140 -0
  121. package/template/client/src/components/business-ui/user-select/user-select-tag.tsx +82 -0
  122. package/template/client/src/components/business-ui/user-select/user-select.tsx +359 -0
  123. package/template/client/src/components/business-ui/user-select/utils.tsx +188 -0
  124. package/template/client/src/components/business-ui/utils/user.ts +72 -0
  125. package/template/client/src/components/ui/README.md +134 -0
  126. package/template/client/src/components/ui/accordion.tsx +66 -0
  127. package/template/client/src/components/ui/alert-dialog.tsx +157 -0
  128. package/template/client/src/components/ui/alert.tsx +71 -0
  129. package/template/client/src/components/ui/aspect-ratio.tsx +11 -0
  130. package/template/client/src/components/ui/avatar.tsx +53 -0
  131. package/template/client/src/components/ui/badge.tsx +42 -0
  132. package/template/client/src/components/ui/breadcrumb.tsx +109 -0
  133. package/template/client/src/components/ui/button-group.tsx +83 -0
  134. package/template/client/src/components/ui/button.tsx +69 -0
  135. package/template/client/src/components/ui/calendar.tsx +213 -0
  136. package/template/client/src/components/ui/card.tsx +82 -0
  137. package/template/client/src/components/ui/carousel.tsx +241 -0
  138. package/template/client/src/components/ui/chart.tsx +357 -0
  139. package/template/client/src/components/ui/checkbox.tsx +32 -0
  140. package/template/client/src/components/ui/collapsible.tsx +33 -0
  141. package/template/client/src/components/ui/command.tsx +208 -0
  142. package/template/client/src/components/ui/context-menu.tsx +324 -0
  143. package/template/client/src/components/ui/dialog.tsx +143 -0
  144. package/template/client/src/components/ui/drawer.tsx +135 -0
  145. package/template/client/src/components/ui/dropdown-menu.tsx +329 -0
  146. package/template/client/src/components/ui/empty.tsx +104 -0
  147. package/template/client/src/components/ui/field.tsx +248 -0
  148. package/template/client/src/components/ui/form.tsx +167 -0
  149. package/template/client/src/components/ui/hover-card.tsx +44 -0
  150. package/template/client/src/components/ui/icons/file-ae-colorful-icon.tsx +21 -0
  151. package/template/client/src/components/ui/icons/file-ai-colorful-icon.tsx +36 -0
  152. package/template/client/src/components/ui/icons/file-android-colorful-icon.tsx +33 -0
  153. package/template/client/src/components/ui/icons/file-audio-colorful-icon.tsx +21 -0
  154. package/template/client/src/components/ui/icons/file-code-colorful-icon.tsx +28 -0
  155. package/template/client/src/components/ui/icons/file-csv-colorful-icon.tsx +21 -0
  156. package/template/client/src/components/ui/icons/file-eml-colorful-icon.tsx +29 -0
  157. package/template/client/src/components/ui/icons/file-ios-colorful-icon.tsx +25 -0
  158. package/template/client/src/components/ui/icons/file-keynote-colorful-icon.tsx +29 -0
  159. package/template/client/src/components/ui/icons/file-pages-colorful-icon.tsx +29 -0
  160. package/template/client/src/components/ui/icons/file-ps-colorful-icon.tsx +21 -0
  161. package/template/client/src/components/ui/icons/file-sketch-colorful-icon.tsx +21 -0
  162. package/template/client/src/components/ui/icons/file-slide-colorful-icon.tsx +21 -0
  163. package/template/client/src/components/ui/icons/file-vcf-colorful-icon.tsx +29 -0
  164. package/template/client/src/components/ui/icons/file-wiki-excel-colorful-icon.tsx +23 -0
  165. package/template/client/src/components/ui/icons/file-wiki-image-colorful-icon.tsx +27 -0
  166. package/template/client/src/components/ui/icons/file-wiki-pdf-colorful-icon.tsx +20 -0
  167. package/template/client/src/components/ui/icons/file-wiki-ppt-colorful-icon.tsx +21 -0
  168. package/template/client/src/components/ui/icons/file-wiki-text-colorful-icon.tsx +12 -0
  169. package/template/client/src/components/ui/icons/file-wiki-unknown-colorful-icon.tsx +14 -0
  170. package/template/client/src/components/ui/icons/file-wiki-video-colorful-icon.tsx +23 -0
  171. package/template/client/src/components/ui/icons/file-wiki-word-colorful-icon.tsx +38 -0
  172. package/template/client/src/components/ui/icons/file-wiki-zip-colorful-icon.tsx +21 -0
  173. package/template/client/src/components/ui/image.tsx +183 -0
  174. package/template/client/src/components/ui/input-group.tsx +166 -0
  175. package/template/client/src/components/ui/input-otp.tsx +77 -0
  176. package/template/client/src/components/ui/input.tsx +21 -0
  177. package/template/client/src/components/ui/item.tsx +193 -0
  178. package/template/client/src/components/ui/kbd.tsx +28 -0
  179. package/template/client/src/components/ui/label.tsx +24 -0
  180. package/template/client/src/components/ui/menubar.tsx +348 -0
  181. package/template/client/src/components/ui/native-select.tsx +48 -0
  182. package/template/client/src/components/ui/navigation-menu.tsx +168 -0
  183. package/template/client/src/components/ui/pagination.tsx +127 -0
  184. package/template/client/src/components/ui/popover.tsx +48 -0
  185. package/template/client/src/components/ui/progress.tsx +31 -0
  186. package/template/client/src/components/ui/radio-group.tsx +45 -0
  187. package/template/client/src/components/ui/resizable.tsx +56 -0
  188. package/template/client/src/components/ui/scroll-area.tsx +58 -0
  189. package/template/client/src/components/ui/select.tsx +243 -0
  190. package/template/client/src/components/ui/separator.tsx +28 -0
  191. package/template/client/src/components/ui/sheet.tsx +139 -0
  192. package/template/client/src/components/ui/sidebar.tsx +727 -0
  193. package/template/client/src/components/ui/skeleton.tsx +13 -0
  194. package/template/client/src/components/ui/slider.tsx +87 -0
  195. package/template/client/src/components/ui/sonner.tsx +67 -0
  196. package/template/client/src/components/ui/spinner.tsx +16 -0
  197. package/template/client/src/components/ui/streamdown.tsx +186 -0
  198. package/template/client/src/components/ui/switch.tsx +31 -0
  199. package/template/client/src/components/ui/table.tsx +116 -0
  200. package/template/client/src/components/ui/tabs.tsx +66 -0
  201. package/template/client/src/components/ui/textarea.tsx +18 -0
  202. package/template/client/src/components/ui/toggle-group.tsx +83 -0
  203. package/template/client/src/components/ui/toggle.tsx +47 -0
  204. package/template/client/src/components/ui/tooltip.tsx +61 -0
  205. package/template/client/src/hooks/use-example.ts +52 -0
  206. package/template/client/src/hooks/use-mobile.ts +19 -0
  207. package/template/client/src/index.css +7 -0
  208. package/template/client/src/index.tsx +36 -0
  209. package/template/client/src/lib/shiki.ts +92 -0
  210. package/template/client/src/lib/utils.ts +6 -0
  211. package/template/client/src/pages/ExamplePage/ExamplePage.tsx +37 -0
  212. package/template/client/src/pages/NotFound/NotFound.tsx +11 -0
  213. package/template/client/src/tailwind-theme.css +257 -0
  214. package/template/client/src/types/common.ts +34 -0
  215. package/template/client/src/types/global.d.ts +53 -0
  216. package/template/client/src/types/index.ts +4 -0
  217. package/template/client/src/typography.css +41 -0
  218. package/template/client/src/utils/img-resources/avatar-placeholders.ts +12 -0
  219. package/template/client/src/utils/img-resources/banner-placeholders.ts +29 -0
  220. package/template/client/src/utils/img-resources/cover-placeholders.ts +47 -0
  221. package/template/components.json +21 -0
  222. package/template/eslint.config.js +50 -0
  223. package/template/nest-cli.json +25 -0
  224. package/template/package-lock.json +29195 -0
  225. package/template/package.json +202 -0
  226. package/template/postcss.config.js +10 -0
  227. package/template/scripts/build.sh +120 -0
  228. package/template/scripts/dev.js +295 -0
  229. package/template/scripts/dev.sh +2 -0
  230. package/template/scripts/lint.js +150 -0
  231. package/template/scripts/prune-smart.js +281 -0
  232. package/template/scripts/run.sh +5 -0
  233. package/template/server/app.module.ts +27 -0
  234. package/template/server/common/constants/api_response_code.ts +54 -0
  235. package/template/server/common/filters/exception.filter.ts +78 -0
  236. package/template/server/common/interfaces/api_response.interface.ts +21 -0
  237. package/template/server/common/interfaces/exception.interface.ts +19 -0
  238. package/template/server/database/schema.ts +90 -0
  239. package/template/server/main.ts +31 -0
  240. package/template/server/modules/hello/hello.controller.ts +16 -0
  241. package/template/server/modules/hello/hello.module.ts +10 -0
  242. package/template/server/modules/hello/hello.service.ts +28 -0
  243. package/template/server/modules/view/view.controller.ts +17 -0
  244. package/template/server/modules/view/view.module.ts +8 -0
  245. package/template/shared/api.interface.ts +1 -0
  246. package/template/tailwind.config.ts +9 -0
  247. package/template/tsconfig.app.json +23 -0
  248. package/template/tsconfig.json +8 -0
  249. package/template/tsconfig.node.json +16 -0
  250. package/template/vite.config.ts +10 -0
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('node:path');
4
+ const { spawn } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+
7
+ const cwd = process.cwd();
8
+
9
+ function getBinName(name) {
10
+ return process.platform === 'win32' ? `${name}.cmd` : name;
11
+ }
12
+
13
+ function runCommand(command, args) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(command, args, {
16
+ cwd,
17
+ stdio: 'inherit',
18
+ shell: false,
19
+ });
20
+
21
+ child.on('close', (code) => resolve(code || 0));
22
+ child.on('error', () => resolve(1));
23
+ });
24
+ }
25
+
26
+ function normalizeProjectFile(filePath) {
27
+ const absolutePath = path.isAbsolute(filePath)
28
+ ? filePath
29
+ : path.resolve(cwd, filePath);
30
+
31
+ if (!fs.existsSync(absolutePath)) {
32
+ console.warn(`[lint] Skip missing file: ${filePath}`);
33
+ return null;
34
+ }
35
+
36
+ const relativePath = path.relative(cwd, absolutePath);
37
+ if (relativePath.startsWith('..')) {
38
+ console.warn(`[lint] Skip file outside project: ${filePath}`);
39
+ return null;
40
+ }
41
+
42
+ return relativePath.split(path.sep).join('/');
43
+ }
44
+
45
+ function parseFilesArg(argv) {
46
+ const filesIndex = argv.indexOf('--files');
47
+ if (filesIndex === -1) {
48
+ return null;
49
+ }
50
+
51
+ return argv.slice(filesIndex + 1).filter(Boolean);
52
+ }
53
+
54
+ function isEslintTarget(filePath) {
55
+ return /\.(c|m)?(j|t)sx?$/.test(filePath);
56
+ }
57
+
58
+ function isTypeCheckTarget(filePath) {
59
+ return /\.(ts|tsx|mts|cts)$/.test(filePath);
60
+ }
61
+
62
+ function isStylelintTarget(filePath) {
63
+ return filePath.endsWith('.css');
64
+ }
65
+
66
+ async function runDefaultLint() {
67
+ const code = await runCommand(getBinName('npx'), [
68
+ 'concurrently',
69
+ 'npm run eslint',
70
+ 'npm run type:check',
71
+ 'npm run stylelint',
72
+ ]);
73
+ process.exit(code);
74
+ }
75
+
76
+ async function runSelectiveLint(inputFiles) {
77
+ const normalizedFiles = Array.from(
78
+ new Set(inputFiles.map(normalizeProjectFile).filter(Boolean)),
79
+ );
80
+
81
+ if (normalizedFiles.length === 0) {
82
+ console.log('[lint] No supported project files found');
83
+ process.exit(0);
84
+ }
85
+
86
+ const eslintFiles = normalizedFiles.filter(isEslintTarget);
87
+ const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
88
+ const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
89
+
90
+ const clientTypeFiles = [];
91
+ const serverTypeFiles = [];
92
+
93
+ for (const filePath of typeCheckFiles) {
94
+ if (filePath.startsWith('client/')) {
95
+ clientTypeFiles.push(filePath);
96
+ } else if (filePath.startsWith('server/')) {
97
+ serverTypeFiles.push(filePath);
98
+ } else if (filePath.startsWith('shared/')) {
99
+ clientTypeFiles.push(filePath);
100
+ serverTypeFiles.push(filePath);
101
+ }
102
+ }
103
+
104
+ const tasks = [];
105
+
106
+ if (eslintFiles.length > 0) {
107
+ tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
108
+ }
109
+
110
+ if (stylelintFiles.length > 0) {
111
+ tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
112
+ }
113
+
114
+ if (clientTypeFiles.length > 0) {
115
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
116
+ }
117
+
118
+ if (serverTypeFiles.length > 0) {
119
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
120
+ }
121
+
122
+ if (tasks.length === 0) {
123
+ console.log('[lint] No supported files matched for lint');
124
+ process.exit(0);
125
+ }
126
+
127
+ const results = await Promise.all(tasks);
128
+ process.exit(results.some(code => code !== 0) ? 1 : 0);
129
+ }
130
+
131
+ async function main() {
132
+ const files = parseFilesArg(process.argv.slice(2));
133
+ if (files === null) {
134
+ await runDefaultLint();
135
+ return;
136
+ }
137
+
138
+ if (files.length === 0) {
139
+ console.error('[lint] --files requires at least one file path');
140
+ process.exit(1);
141
+ }
142
+
143
+ await runSelectiveLint(files);
144
+ }
145
+
146
+ main().catch((error) => {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ console.error(`[lint] Failed to run lint: ${message}`);
149
+ process.exit(1);
150
+ });
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ // This file is auto-generated by @lark-apaas/fullstack-cli
3
+
4
+ const { nodeFileTrace } = require('@vercel/nft');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const ROOT_DIR = path.resolve(__dirname, '..');
9
+ const DIST_SERVER_DIR = path.join(ROOT_DIR, 'dist/server');
10
+ const ROOT_PACKAGE_JSON = path.join(ROOT_DIR, 'package.json');
11
+ const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
12
+ const OUT_NODE_MODULES = path.join(DIST_SERVER_DIR, 'node_modules');
13
+ const OUT_PACKAGE_JSON = path.join(DIST_SERVER_DIR, 'package.json');
14
+
15
+ // Server 入口文件
16
+ const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
17
+
18
+ // Node.js 内置模块列表
19
+ const BUILTIN_MODULES = new Set([
20
+ 'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
21
+ 'events', 'fs', 'http', 'http2', 'https', 'net', 'os', 'path', 'stream',
22
+ 'string_decoder', 'timers', 'tls', 'url', 'util', 'v8', 'vm', 'zlib',
23
+ 'async_hooks', 'perf_hooks', 'worker_threads', 'inspector', 'trace_events'
24
+ ]);
25
+
26
+ /**
27
+ * 从完整路径中提取 npm 包名(优化版)
28
+ */
29
+ const nodeModulesSep = 'node_modules' + path.sep;
30
+ function extractPackageName(filePath) {
31
+ const idx = filePath.lastIndexOf(nodeModulesSep);
32
+ if (idx === -1) return null;
33
+
34
+ const afterNodeModules = filePath.slice(idx + nodeModulesSep.length);
35
+ const slashIdx = afterNodeModules.indexOf(path.sep);
36
+
37
+ // 处理 scoped package (@xxx/yyy)
38
+ if (afterNodeModules[0] === '@') {
39
+ const secondSlash = afterNodeModules.indexOf(path.sep, slashIdx + 1);
40
+ return secondSlash === -1
41
+ ? afterNodeModules
42
+ : afterNodeModules.slice(0, secondSlash);
43
+ }
44
+
45
+ return slashIdx === -1 ? afterNodeModules : afterNodeModules.slice(0, slashIdx);
46
+ }
47
+
48
+ /**
49
+ * 递归删除目录
50
+ */
51
+ function removeDir(dir) {
52
+ if (fs.existsSync(dir)) {
53
+ fs.rmSync(dir, { recursive: true, force: true });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 递归复制目录(优化版:优先使用硬链接)
59
+ */
60
+ function copyDir(src, dest, stats = { hardLinks: 0, copies: 0 }) {
61
+ if (!fs.existsSync(dest)) {
62
+ fs.mkdirSync(dest, { recursive: true });
63
+ }
64
+
65
+ const entries = fs.readdirSync(src, { withFileTypes: true });
66
+
67
+ for (const entry of entries) {
68
+ const srcPath = path.join(src, entry.name);
69
+ const destPath = path.join(dest, entry.name);
70
+
71
+ if (entry.isDirectory()) {
72
+ copyDir(srcPath, destPath, stats);
73
+ } else {
74
+ try {
75
+ // 优先使用硬链接(CI 环境中安全,速度快,节省空间)
76
+ fs.linkSync(srcPath, destPath);
77
+ stats.hardLinks++;
78
+ } catch {
79
+ // 硬链接失败时回退到复制(如跨文件系统)
80
+ fs.copyFileSync(srcPath, destPath);
81
+ stats.copies++;
82
+ }
83
+ }
84
+ }
85
+
86
+ return stats;
87
+ }
88
+
89
+ /**
90
+ * 选择性复制包
91
+ */
92
+ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
93
+ const copiedCount = { success: 0, failed: 0 };
94
+ const fileStats = { hardLinks: 0, copies: 0 };
95
+
96
+ // 确保输出目录存在
97
+ if (fs.existsSync(outNodeModules)) {
98
+ removeDir(outNodeModules);
99
+ }
100
+ fs.mkdirSync(outNodeModules, { recursive: true });
101
+
102
+ // 复制每个包
103
+ for (const pkg of packages) {
104
+ const srcPath = path.join(rootNodeModules, pkg);
105
+ const destPath = path.join(outNodeModules, pkg);
106
+
107
+ if (!fs.existsSync(srcPath)) {
108
+ copiedCount.failed++;
109
+ continue;
110
+ }
111
+
112
+ try {
113
+ // 确保父目录存在(处理 scoped packages)
114
+ const destDir = path.dirname(destPath);
115
+ if (!fs.existsSync(destDir)) {
116
+ fs.mkdirSync(destDir, { recursive: true });
117
+ }
118
+
119
+ // 复制包目录,收集统计信息
120
+ copyDir(srcPath, destPath, fileStats);
121
+ copiedCount.success++;
122
+ } catch (err) {
123
+ console.error(` ⚠️ 复制失败: ${pkg} - ${err.message}`);
124
+ copiedCount.failed++;
125
+ }
126
+ }
127
+
128
+ return { copiedCount, fileStats };
129
+ }
130
+
131
+ /**
132
+ * 主函数:智能裁剪
133
+ */
134
+ async function smartPrune() {
135
+ console.log('🔍 开始智能依赖分析...\n');
136
+
137
+ const totalStartTime = Date.now();
138
+
139
+ // 1. 检查入口文件是否存在
140
+ if (!fs.existsSync(SERVER_ENTRY)) {
141
+ console.error(`❌ 入口文件不存在: ${SERVER_ENTRY}`);
142
+ console.error(' 请先运行 npm run build:server');
143
+ process.exit(1);
144
+ }
145
+
146
+ console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
147
+
148
+ // 2. 使用 @vercel/nft 追踪依赖
149
+ console.log('🔎 追踪实际依赖...');
150
+ const analyzeStart = Date.now();
151
+
152
+ const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
153
+ base: ROOT_DIR,
154
+ processCwd: ROOT_DIR,
155
+ ts: false, // 禁用 TS 解析
156
+ conditions: ['node', 'production'], // 只解析 Node.js 生产环境的导出
157
+ exportsOnly: true, // 只使用 package.json 的 exports 字段
158
+ analysis: {
159
+ emitGlobs: false, // 禁用 glob 分析
160
+ computeFileReferences: false, // 禁用文件引用计算
161
+ evaluatePureExpressions: false, // 禁用纯表达式求值
162
+ },
163
+ });
164
+
165
+ const analyzeElapsed = Date.now() - analyzeStart;
166
+ console.log(` ✅ 分析完成,耗时: ${analyzeElapsed}ms\n`);
167
+
168
+ // 3. 提取 npm 包名(优化:减少字符串操作)
169
+ const requiredPackages = new Set();
170
+
171
+ for (const file of fileList) {
172
+ // 快速跳过非 node_modules 文件
173
+ if (!file.includes('node_modules')) continue;
174
+
175
+ const pkgName = extractPackageName(file);
176
+ if (pkgName && !BUILTIN_MODULES.has(pkgName)) {
177
+ requiredPackages.add(pkgName);
178
+ }
179
+ }
180
+
181
+ console.log(`📦 静态分析需要 ${requiredPackages.size} 个 npm 包`);
182
+
183
+ // 4. 处理 actionPlugins(动态加载的插件,无法被静态分析追踪)
184
+ const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
185
+ const actionPlugins = originalPackage.actionPlugins || {};
186
+ const actionPluginNames = Object.keys(actionPlugins);
187
+
188
+ if (actionPluginNames.length > 0) {
189
+ console.log(`🔌 发现 ${actionPluginNames.length} 个 Action 插件,添加到依赖列表...`);
190
+
191
+ for (const pluginName of actionPluginNames) {
192
+ requiredPackages.add(pluginName);
193
+
194
+ // 检查插件的 peerDependencies,也添加进去
195
+ const pluginPkgPath = path.join(ROOT_NODE_MODULES, pluginName, 'package.json');
196
+ if (fs.existsSync(pluginPkgPath)) {
197
+ try {
198
+ const pluginPkg = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf8'));
199
+ if (pluginPkg.peerDependencies) {
200
+ for (const peerDep of Object.keys(pluginPkg.peerDependencies)) {
201
+ requiredPackages.add(peerDep);
202
+ }
203
+ }
204
+ } catch {
205
+ // 忽略解析错误
206
+ }
207
+ }
208
+ }
209
+
210
+ console.log(` ✅ 插件: ${actionPluginNames.join(', ')}`);
211
+ }
212
+
213
+ console.log(`📦 总共需要 ${requiredPackages.size} 个 npm 包\n`);
214
+
215
+ // 5. 选择性复制包(只复制需要的)
216
+ console.log('📋 选择性复制 node_modules(仅复制需要的包)...');
217
+ const copyStart = Date.now();
218
+
219
+ const sortedPackages = Array.from(requiredPackages).sort();
220
+ const { copiedCount, fileStats } = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
221
+
222
+ const copyElapsed = Date.now() - copyStart;
223
+ console.log(` ✅ 复制完成,耗时: ${copyElapsed}ms`);
224
+ console.log(` 成功: ${copiedCount.success} 个包,失败: ${copiedCount.failed} 个`);
225
+ console.log(` 硬链接: ${fileStats.hardLinks} 个文件,复制: ${fileStats.copies} 个文件\n`);
226
+
227
+ // 6. 生成精简版 package.json
228
+ // 优化:直接构建 dependencies,避免多次对象展开
229
+ const prunedDependencies = {};
230
+ const allDeps = originalPackage.dependencies || {};
231
+ const allDevDeps = originalPackage.devDependencies || {};
232
+
233
+ for (const pkg of requiredPackages) {
234
+ // 优先从 dependencies/devDependencies 获取版本,其次从 actionPlugins 获取
235
+ const version = allDeps[pkg] || allDevDeps[pkg] || actionPlugins[pkg];
236
+ if (version) {
237
+ prunedDependencies[pkg] = version;
238
+ }
239
+ }
240
+
241
+ const prunedPackage = {
242
+ name: originalPackage.name,
243
+ version: originalPackage.version,
244
+ private: true,
245
+ dependencies: prunedDependencies,
246
+ scripts: {
247
+ start: originalPackage.scripts?.start || 'node main.js'
248
+ },
249
+ engines: originalPackage.engines
250
+ };
251
+
252
+ fs.writeFileSync(OUT_PACKAGE_JSON, JSON.stringify(prunedPackage, null, 2));
253
+
254
+ const totalElapsed = Date.now() - totalStartTime;
255
+
256
+ // 7. 输出统计信息
257
+ console.log('='.repeat(60));
258
+ console.log('📊 智能裁剪统计:');
259
+ console.log('='.repeat(60));
260
+ console.log(` 需要的包数量: ${requiredPackages.size}`);
261
+ if (actionPluginNames.length > 0) {
262
+ console.log(` Action 插件: ${actionPluginNames.length} 个`);
263
+ }
264
+ console.log(` 成功复制: ${copiedCount.success} 个包`);
265
+ console.log(` 失败: ${copiedCount.failed} 个包`);
266
+ console.log(` 硬链接文件: ${fileStats.hardLinks} 个`);
267
+ console.log(` 复制文件: ${fileStats.copies} 个`);
268
+ console.log(` 分析耗时: ${analyzeElapsed}ms`);
269
+ console.log(` 复制耗时: ${copyElapsed}ms`);
270
+ console.log(` 总耗时: ${totalElapsed}ms`);
271
+ console.log('='.repeat(60) + '\n');
272
+
273
+ console.log('✅ 智能裁剪完成!');
274
+ console.log(`📍 精简的 package.json 已保存到: ${OUT_PACKAGE_JSON}\n`);
275
+ }
276
+
277
+ // 运行智能裁剪
278
+ smartPrune().catch(err => {
279
+ console.error('❌ 智能裁剪失败:', err);
280
+ process.exit(1);
281
+ });
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # This file is auto-generated by @lark-apaas/fullstack-cli
3
+
4
+ # 生产环境下,启动服务
5
+ npm run start
@@ -0,0 +1,27 @@
1
+ import { APP_FILTER } from '@nestjs/core';
2
+ import { Module } from '@nestjs/common';
3
+ import { PlatformModule } from '@lark-apaas/fullstack-nestjs-core';
4
+
5
+ import { GlobalExceptionFilter } from './common/filters/exception.filter';
6
+ import { ViewModule } from './modules/view/view.module';
7
+
8
+ @Module({
9
+ imports: [
10
+ // 平台 Module,提供平台能力
11
+ PlatformModule.forRoot(),
12
+ // ====== @route-section: business-modules START ======
13
+ // Place all business modules here.Do NOT add fallback modules here.
14
+ // ====== @route-section: business-modules END ======
15
+
16
+ // ⚠️ @route-order: last
17
+ // ViewModule is the fallback route module, must be registered last.
18
+ ViewModule,
19
+ ],
20
+ providers: [
21
+ {
22
+ provide: APP_FILTER,
23
+ useClass: GlobalExceptionFilter,
24
+ },
25
+ ],
26
+ })
27
+ export class AppModule {}
@@ -0,0 +1,54 @@
1
+ import { HttpStatus } from '@nestjs/common';
2
+
3
+ // 业务状态码与HTTP状态码的映射
4
+ export enum ResponseCode {
5
+ // 成功状态
6
+ SUCCESS = 'SUCCESS',
7
+ CREATED = 'CREATED',
8
+ ACCEPTED = 'ACCEPTED',
9
+ NO_CONTENT = 'NO_CONTENT',
10
+
11
+ // 客户端错误
12
+ BAD_REQUEST = 'BAD_REQUEST',
13
+ UNAUTHORIZED = 'UNAUTHORIZED',
14
+ FORBIDDEN = 'FORBIDDEN',
15
+ NOT_FOUND = 'NOT_FOUND',
16
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
17
+ CONFLICT = 'CONFLICT',
18
+ TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS',
19
+
20
+ // 服务端错误
21
+ INTERNAL_ERROR = 'INTERNAL_ERROR',
22
+ BAD_GATEWAY = 'BAD_GATEWAY',
23
+ SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
24
+
25
+ // 业务错误
26
+ BUSINESS_ERROR = 'BUSINESS_ERROR',
27
+ }
28
+
29
+ // 状态码映射配置
30
+ export const RESPONSE_CODE_TO_HTTP_STATUS_MAP: Record<ResponseCode, number> = {
31
+ [ResponseCode.SUCCESS]: HttpStatus.OK,
32
+ [ResponseCode.CREATED]: HttpStatus.CREATED,
33
+ [ResponseCode.ACCEPTED]: HttpStatus.ACCEPTED,
34
+ [ResponseCode.NO_CONTENT]: HttpStatus.NO_CONTENT,
35
+
36
+ [ResponseCode.BAD_REQUEST]: HttpStatus.BAD_REQUEST,
37
+ [ResponseCode.UNAUTHORIZED]: HttpStatus.UNAUTHORIZED,
38
+ [ResponseCode.FORBIDDEN]: HttpStatus.FORBIDDEN,
39
+ [ResponseCode.NOT_FOUND]: HttpStatus.NOT_FOUND,
40
+ [ResponseCode.VALIDATION_ERROR]: HttpStatus.UNPROCESSABLE_ENTITY,
41
+ [ResponseCode.CONFLICT]: HttpStatus.CONFLICT,
42
+ [ResponseCode.TOO_MANY_REQUESTS]: HttpStatus.TOO_MANY_REQUESTS,
43
+
44
+ [ResponseCode.INTERNAL_ERROR]: HttpStatus.INTERNAL_SERVER_ERROR,
45
+ [ResponseCode.BAD_GATEWAY]: HttpStatus.BAD_GATEWAY,
46
+ [ResponseCode.SERVICE_UNAVAILABLE]: HttpStatus.SERVICE_UNAVAILABLE,
47
+
48
+ [ResponseCode.BUSINESS_ERROR]: HttpStatus.UNPROCESSABLE_ENTITY, // 通用业务错误状态码
49
+ };
50
+
51
+ // 自动生成的HTTP状态码映射
52
+ export const HTTP_STATUS_TO_RESPONSE_CODE_MAP: Record<number, ResponseCode> = Object.fromEntries(
53
+ Object.entries(RESPONSE_CODE_TO_HTTP_STATUS_MAP).map(([code, status]) => [status, code as ResponseCode]),
54
+ ) as Record<number, ResponseCode>;
@@ -0,0 +1,78 @@
1
+ import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
2
+ import type { Response } from 'express';
3
+ import { BusinessException } from '../interfaces/exception.interface';
4
+ import { HTTP_STATUS_TO_RESPONSE_CODE_MAP, ResponseCode } from '../constants/api_response_code';
5
+ import { ApiErrorResponse } from '../interfaces/api_response.interface';
6
+
7
+ // 全局异常过滤器,用于捕获所有未处理的异常
8
+ @Catch()
9
+ export class GlobalExceptionFilter implements ExceptionFilter {
10
+ catch(exception: unknown, host: ArgumentsHost) {
11
+ const ctx = host.switchToHttp();
12
+ const response = ctx.getResponse<Response>();
13
+
14
+ // 如果响应头已发送,则不处理
15
+ if (response.headersSent) {
16
+ return;
17
+ }
18
+
19
+ let errorResponse: Omit<ApiErrorResponse, 'httpStatus'>;
20
+ let httpStatus: HttpStatus;
21
+
22
+ if (exception instanceof BusinessException) {
23
+ // 业务异常
24
+ httpStatus = exception.httpStatus;
25
+ errorResponse = {
26
+ error: {
27
+ code: exception.code,
28
+ message: exception.message,
29
+ details: exception.details,
30
+ fieldErrors: exception.fieldErrors,
31
+ timestamp: Date.now(),
32
+ },
33
+ };
34
+ } else if (exception instanceof HttpException) {
35
+ // HTTP异常
36
+ httpStatus = exception.getStatus() as HttpStatus;
37
+ const exceptionResponse = exception.getResponse();
38
+
39
+ errorResponse = {
40
+ error: {
41
+ code: HTTP_STATUS_TO_RESPONSE_CODE_MAP[httpStatus],
42
+ message: typeof exceptionResponse === 'string' ? exceptionResponse : exception.message,
43
+ details: typeof exceptionResponse === 'object' ? JSON.stringify(exceptionResponse) : undefined,
44
+ timestamp: Date.now(),
45
+ },
46
+ };
47
+ } else if (
48
+ typeof exception === 'object' &&
49
+ exception !== null &&
50
+ (exception as { code?: unknown }).code === '22P02'
51
+ ) {
52
+ // Postgres invalid_text_representation:路径/查询参数与列类型不匹配(最常见是非法 UUID)
53
+ // 与「合法 UUID 但记录不存在」走同一条 not-found 语义,避免 500 噪声
54
+ httpStatus = HttpStatus.NOT_FOUND;
55
+ errorResponse = {
56
+ error: {
57
+ code: ResponseCode.NOT_FOUND,
58
+ message: '资源不存在',
59
+ timestamp: Date.now(),
60
+ },
61
+ };
62
+ } else {
63
+ // 未知异常
64
+ httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
65
+ errorResponse = {
66
+ error: {
67
+ code: ResponseCode.INTERNAL_ERROR,
68
+ message: '服务器内部错误',
69
+ stack: (exception as Error).stack,
70
+ cause: (exception as Error).cause as string,
71
+ timestamp: Date.now(),
72
+ },
73
+ };
74
+ }
75
+
76
+ response.status(httpStatus).json(errorResponse);
77
+ }
78
+ }
@@ -0,0 +1,21 @@
1
+ // 错误统一响应
2
+ export interface ApiErrorResponse {
3
+ /** 错误详情 */
4
+ error: {
5
+ /** 错误代码 */
6
+ code: string;
7
+ /** 错误消息 */
8
+ message: string;
9
+ /** 错误详情 */
10
+ details?: string;
11
+ /** 字段验证错误 */
12
+ fieldErrors?: Record<string, string[]>;
13
+ /** 调用栈(仅开发环境) */
14
+ stack?: string;
15
+ /** 错误原因 */
16
+ cause?: string;
17
+ /** 错误发生时间 */
18
+ timestamp?: number;
19
+ };
20
+ }
21
+
@@ -0,0 +1,19 @@
1
+ // 业务异常类
2
+ import { HttpStatus } from '@nestjs/common';
3
+ import { RESPONSE_CODE_TO_HTTP_STATUS_MAP, ResponseCode } from '../constants/api_response_code';
4
+
5
+ export class BusinessException extends Error {
6
+ constructor(
7
+ public readonly code: ResponseCode,
8
+ public readonly message: string,
9
+ public readonly httpStatus: HttpStatus = HttpStatus.BAD_REQUEST,
10
+ public readonly details?: string,
11
+ public readonly fieldErrors?: Record<string, string[]>,
12
+ ) {
13
+ super(message);
14
+ this.name = 'BusinessException';
15
+ }
16
+ getHttpStatus(): HttpStatus {
17
+ return RESPONSE_CODE_TO_HTTP_STATUS_MAP[this.code];
18
+ }
19
+ }