@lark-apaas/coding-template-nestjs-react-fullstack 0.1.26 → 0.1.27-alpha.20260821031300

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-template-nestjs-react-fullstack",
3
- "version": "0.1.26",
3
+ "version": "0.1.27-alpha.20260821031300",
4
4
  "description": "NestJS + React fullstack template for miaoda coding",
5
5
  "type": "module",
6
6
  "files": [
@@ -37,9 +37,9 @@
37
37
  "@headlessui/react": "^2.2.9",
38
38
  "@heroicons/react": "^2.2.0",
39
39
  "@hookform/resolvers": "^5.2.2",
40
- "@lark-apaas/client-toolkit": "^1.2.64",
40
+ "@lark-apaas/client-toolkit": "^1.2.63",
41
41
  "@lark-apaas/fullstack-presets": "^1.1.0",
42
- "@lark-apaas/fullstack-vite-preset": "^1.0.35",
42
+ "@lark-apaas/fullstack-vite-preset": "^1.0.34",
43
43
  "@nestjs/cli": "^10.4.9",
44
44
  "@nestjs/testing": "^10.4.20",
45
45
  "@radix-ui/react-accordion": "^1.2.12",
@@ -3100,9 +3100,9 @@
3100
3100
  }
3101
3101
  },
3102
3102
  "node_modules/@lark-apaas/client-toolkit": {
3103
- "version": "1.2.64",
3104
- "resolved": "https://registry.npmmirror.com/@lark-apaas/client-toolkit/-/client-toolkit-1.2.64.tgz",
3105
- "integrity": "sha512-9NKm2VKQzOcKZzcQoK9sTEkQ5tMnTyZil9KLoR30E5jtgFrHzvQPQ5yu0Sj3GgiUsnLUYU3Jx5NoBXdkFrX/gA==",
3103
+ "version": "1.2.63",
3104
+ "resolved": "https://registry.npmmirror.com/@lark-apaas/client-toolkit/-/client-toolkit-1.2.63.tgz",
3105
+ "integrity": "sha512-/ZtOLOb72bWob1rV950B30C9E9Msda8BdwAFQeMWs7khaVjHBJq/k2vdJksWip14frlsUAPBPcuGe3XafBIcPw==",
3106
3106
  "dev": true,
3107
3107
  "dependencies": {
3108
3108
  "@ant-design/colors": "^7.2.1",
@@ -3755,9 +3755,9 @@
3755
3755
  }
3756
3756
  },
3757
3757
  "node_modules/@lark-apaas/fullstack-vite-preset": {
3758
- "version": "1.0.35",
3759
- "resolved": "https://registry.npmmirror.com/@lark-apaas/fullstack-vite-preset/-/fullstack-vite-preset-1.0.35.tgz",
3760
- "integrity": "sha512-91nSumvDvH53Iu6Q5XvkCxB2lyL0tJf7NzJwS5y2jYDGanoGXGgnb4vcT2KI1SOa/1Cdp6mr1xxp0fwTp2VzRQ==",
3758
+ "version": "1.0.34",
3759
+ "resolved": "https://registry.npmmirror.com/@lark-apaas/fullstack-vite-preset/-/fullstack-vite-preset-1.0.34.tgz",
3760
+ "integrity": "sha512-E1eZxA52JbIZA7LZMUqUJpSU8Xg7A9mlsMtkBSel/7F4Y6s51uBLusy2LpG2IQmWp8omhMQRljdnmhw+RTlC8A==",
3761
3761
  "dev": true,
3762
3762
  "license": "MIT",
3763
3763
  "dependencies": {
@@ -35,8 +35,8 @@
35
35
  "dependencies": {
36
36
  "@lark-apaas/fullstack-nestjs-core": "^1.1.59",
37
37
  "@nestjs/axios": "^4.0.1",
38
- "@nestjs/common": "^10.4.20",
39
38
  "@nestjs/cache-manager": "^3.1.3",
39
+ "@nestjs/common": "^10.4.20",
40
40
  "@nestjs/config": "^3.3.0",
41
41
  "@nestjs/core": "^10.4.20",
42
42
  "@nestjs/platform-express": "^10.4.20",
@@ -62,9 +62,9 @@
62
62
  "@headlessui/react": "^2.2.9",
63
63
  "@heroicons/react": "^2.2.0",
64
64
  "@hookform/resolvers": "^5.2.2",
65
- "@lark-apaas/client-toolkit": "^1.2.64",
65
+ "@lark-apaas/client-toolkit": "^1.2.63",
66
+ "@lark-apaas/coding-preset-vite-react": "1.0.18-alpha.20260819103004",
66
67
  "@lark-apaas/fullstack-presets": "^1.1.0",
67
- "@lark-apaas/fullstack-vite-preset": "^1.0.35",
68
68
  "@nestjs/cli": "^10.4.9",
69
69
  "@nestjs/testing": "^10.4.20",
70
70
  "@radix-ui/react-accordion": "^1.2.12",
@@ -181,7 +181,7 @@
181
181
  "typescript-eslint": "^8.44.0",
182
182
  "uuid": "11.0.5",
183
183
  "vaul": "^1.1.2",
184
- "vite": "^7.3.1",
184
+ "vite": "^8",
185
185
  "xlsx": "^0.18.5",
186
186
  "xlsx-js-style": "^1.2.0",
187
187
  "zod": "^3.25.76",
@@ -89,6 +89,18 @@ cp "$ROOT_DIR/dist/client/"*.html "$OUT_DIR/dist/client/" || true
89
89
  # 拷贝 run.sh 文件
90
90
  cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
91
91
 
92
+ # 拷贝 shared/static → OUT_DIR/shared/static
93
+ # Nest server 生产从 process.cwd() + shared/static 读 (nestjs-core static.controller.ts:100),
94
+ # 生产 cwd = $OUT_DIR (dist/server), 所以目标是 dist/server/shared/static。
95
+ # coding-preset 的 static-assets plugin 之前 closeBundle 拷贝, 现在删除后由 build.sh 显式做。
96
+ # 排除 .ts/.tsx/.js/.jsx: 避免 shared/static/ 里若有开发脚本被误传到生产。
97
+ if [ -d "$ROOT_DIR/shared/static" ]; then
98
+ mkdir -p "$OUT_DIR/shared/static"
99
+ rsync -a --exclude='*.ts' --exclude='*.tsx' --exclude='*.js' --exclude='*.jsx' \
100
+ "$ROOT_DIR/shared/static/" "$OUT_DIR/shared/static/"
101
+ echo " Static → dist/server/shared/static/"
102
+ fi
103
+
92
104
  # 清理无用文件
93
105
  rm -rf "$ROOT_DIR/dist/scripts"
94
106
  rm -rf "$ROOT_DIR/dist/tsconfig.node.tsbuildinfo"
@@ -59,10 +59,23 @@ function timestamp() {
59
59
  );
60
60
  }
61
61
 
62
- /** Write to terminal + dev.std.log */
62
+ // Cap pending async stdout writes so a stalled pty consumer can't grow memory unbounded.
63
+ // Each pending fs.write retains ~1.5 KB (msg + libuv/V8 wrappers); 1000 ≈ ~1.5 MB ceiling.
64
+ let _stdoutInFlight = 0;
65
+ const STDOUT_MAX_INFLIGHT = 1000;
66
+
67
+ /** Write to dev.std.log (sync, guaranteed) + mirror to terminal (async, non-blocking) */
63
68
  function writeOutput(msg) {
64
- try { process.stdout.write(msg); } catch {}
69
+ // File first and synchronously — read-logs reads this file; it must never be gated
70
+ // by the terminal consumer.
65
71
  try { fs.writeSync(devStdLogFd, msg); } catch {}
72
+ // stdout mirror via async fs.write: if process.stdout is a pty/pipe whose consumer
73
+ // stalls, the block happens on the libuv threadpool, NOT the event loop — so the
74
+ // synchronous log-FILE writes above (and subsequent readline callbacks) keep running.
75
+ // Drop overflow when too many writes are already pending (best-effort mirror).
76
+ if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) { return; }
77
+ _stdoutInFlight++;
78
+ try { fs.write(1, msg, () => { _stdoutInFlight--; }); } catch { _stdoutInFlight--; }
66
79
  }
67
80
 
68
81
  /** Structured event log → terminal + dev.std.log + dev.log */
@@ -239,20 +252,23 @@ process.on('SIGTERM', cleanup);
239
252
  process.on('SIGINT', cleanup);
240
253
  process.on('SIGHUP', cleanup);
241
254
 
242
- // Stale dist makes nest --watch skip missing files; watcher won't self-heal.
243
- function cleanStaleDist() {
244
- const distPath = path.join(PROJECT_ROOT, 'dist');
245
- if (fs.existsSync(distPath)) {
246
- fs.rmSync(distPath, { recursive: true, force: true });
247
- logEvent('INFO', 'main', 'Cleaned dist/ to force full rebuild');
248
- }
249
- }
255
+ // 定向保留 dist/ 里能复用的部分, 替代之前无脑 rm -rf dist/。
256
+ // 收益: 保留 dist/server 让 nest --watch 从增量编译起点起 (省 8-13s 冷启动),
257
+ // routes JSON 也保留 (build 会覆盖, 无 stale 风险)。dist/client/index.html 按
258
+ // preset 打的 <meta name="miaoda-html-mode"> marker 判定:
259
+ // marker=dev → 保留 dist/client/ 整个 (dev cache hit)
260
+ // marker=prod agent 跑过 build:client 污染, 定向清 index.html + assets/
261
+ // 具体逻辑抽到 lib/preserve-dev-cache.cjs 便于单测。
262
+ // 降级 escape hatch: FORCE_CLEAN_DIST=true 走原全清路径 (marker 逻辑出 bug 时用)。
263
+ const { preserveDevCache } = require('./lib/preserve-dev-cache.cjs');
250
264
 
251
265
  // ── Main ──────────────────────────────────────────────────────────────────────
252
266
  async function main() {
253
267
  logEvent('INFO', 'main', '========== Dev session started ==========');
254
268
 
255
- cleanStaleDist();
269
+ preserveDevCache(PROJECT_ROOT, {
270
+ log: (level, msg) => logEvent(level, 'main', msg),
271
+ });
256
272
 
257
273
  // Initialize action plugins
258
274
  writeOutput('\n🔌 Initializing action plugins...\n');
@@ -1,18 +1,14 @@
1
1
  #!/usr/bin/env bash
2
- # `npm run dev` 入口;按 MIAODA_DEP_CACHE_DIR 是否非空判断运行环境(与 miaoda-cli
3
- # isSandboxEnv() 同口径):
4
- # - 非空(沙箱平台注入的依赖缓存目录)→ 直接跑 dev.js
2
+ # `npm run dev` 入口;按 SANDBOX_ID 是否非空判断运行环境:
3
+ # - SANDBOX_ID 非空(沙箱平台注入应用所属沙箱 ID)→ 直接跑 dev.js
5
4
  # (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
6
5
  # dev 入口不再额外 `npm run upgrade`。
7
- # - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.js:纯 stdout、崩了就崩、Agent 友好。
8
- # 显式想跑本地路径可用 `npm run dev:local`(绕过本判断)。
9
- #
10
- # 不用 SANDBOX_ID:它由 sandbox_console 在绑定沙箱时才写入 .force/environment/env,预热池实例
11
- # 在绑定前拿不到,会被误判成本地。保留为附加条件只为兼容,不影响判定结果。
6
+ # - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.sh:纯 stdout、崩了就崩、Agent 友好。
7
+ # 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
12
8
  set -euo pipefail
13
9
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
14
10
 
15
- if [ -n "${MIAODA_DEP_CACHE_DIR:-}" ] || [ -n "${SANDBOX_ID:-}" ]; then
11
+ if [ -n "${SANDBOX_ID:-}" ]; then
16
12
  exec node "$SCRIPT_DIR/dev.js" "$@"
17
13
  fi
18
14
 
@@ -22,7 +18,7 @@ if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
22
18
  fi
23
19
 
24
20
  # 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
25
- # latest + 迁移老 npm scripts。沙箱不走这里(上面的沙箱分支已经 exec return)。
21
+ # latest + 迁移老 npm scripts。沙箱不走这里(SANDBOX_ID 分支已经 exec return)。
26
22
  npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
27
23
 
28
24
  exec node "$SCRIPT_DIR/dev-local.js" "$@"
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ // dev.js 启动时替代无脑 rm -rf dist/ 的 lib: 保留 dist/server (nest --watch 增量编译起点)
4
+ // + 按 HTML mode marker 决定 dist/client 是保留还是清污染。
5
+ // 跟 preset 侧 html-output plugin 的 injectHtmlModeMarker / detectHtmlMode 配套:
6
+ // preset 写盘时打 marker (dev 版 vs prod 污染版), 这里读 marker 定去留。
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * 从 HTML 里回读 <meta name="miaoda-html-mode" content="dev|prod"> marker。
13
+ * 与 packages/tools/fullstack-vite-preset/src/vite-plugins/html-output-plugin.ts
14
+ * 的 detectHtmlMode 同源, 为了让 dev.js 模板脚本零依赖跑起来这里重复一份。
15
+ */
16
+ function detectHtmlMode(htmlContent) {
17
+ const match = htmlContent.match(
18
+ /<meta\s+name=["']miaoda-html-mode["']\s+content=["'](dev|prod)["'][^>]*>/i
19
+ );
20
+ return (match && match[1]) || 'unknown';
21
+ }
22
+
23
+ /**
24
+ * 定向清理 dist/ 的可复用副本, 替代 dev.js 之前无脑 `rm -rf dist/` 的 cleanStaleDist。
25
+ *
26
+ * 决策规则:
27
+ * - FORCE_CLEAN_DIST=true → 走 escape hatch 全清 (preserveDevCache 逻辑 / 保留的
28
+ * dist/server 出 stale 时兜底)
29
+ * - dist/ 不存在 → 无动作
30
+ * - dist/client/index.html 存在且 marker=dev → 整个 dist/client/ 保留 (dev cache hit)
31
+ * - 存在但 marker=prod 或缺失 → 只清 dist/client/index.html + dist/client/assets/
32
+ * (build 污染部分), dist/server/ 与 routes JSON 保留
33
+ * - 不存在 → 无动作, htmlOutputPlugin 稍后会写入
34
+ *
35
+ * @param {string} projectRoot 项目根目录
36
+ * @param {object} [options]
37
+ * @param {(level: string, msg: string) => void} [options.log] 日志回调, 默认 console.log
38
+ * @param {boolean} [options.forceClean] 覆盖 FORCE_CLEAN_DIST env var 判断 (给测试用)
39
+ * @returns {{ action: 'no_dist' | 'force_cleaned' | 'no_index' | 'preserved' | 'cleaned_polluted' | 'read_failed_cleaned' | 'cleanup_failed', mode: string }}
40
+ */
41
+ function preserveDevCache(projectRoot, options) {
42
+ const log = (options && options.log) || defaultLogger;
43
+ const forceClean =
44
+ options && typeof options.forceClean === 'boolean'
45
+ ? options.forceClean
46
+ : process.env.FORCE_CLEAN_DIST === 'true';
47
+
48
+ const distPath = path.join(projectRoot, 'dist');
49
+ if (!fs.existsSync(distPath)) {
50
+ log('INFO', 'preserveDevCache: no dist/, will be created by vite/nest');
51
+ return { action: 'no_dist', mode: 'unknown' };
52
+ }
53
+
54
+ if (forceClean) {
55
+ fs.rmSync(distPath, { recursive: true, force: true });
56
+ log(
57
+ 'INFO',
58
+ 'preserveDevCache: FORCE_CLEAN_DIST=true, cleaned entire dist/'
59
+ );
60
+ return { action: 'force_cleaned', mode: 'unknown' };
61
+ }
62
+
63
+ const clientDir = path.join(distPath, 'client');
64
+ const indexHtml = path.join(clientDir, 'index.html');
65
+
66
+ if (!fs.existsSync(indexHtml)) {
67
+ log(
68
+ 'INFO',
69
+ 'preserveDevCache: no dist/client/index.html, other artifacts preserved'
70
+ );
71
+ return { action: 'no_index', mode: 'unknown' };
72
+ }
73
+
74
+ let mode = 'unknown';
75
+ let readFailed = false;
76
+ try {
77
+ const content = fs.readFileSync(indexHtml, 'utf-8');
78
+ mode = detectHtmlMode(content);
79
+ } catch (e) {
80
+ log(
81
+ 'WARN',
82
+ `preserveDevCache: read dist/client/index.html failed: ${e.message}, treating as polluted`
83
+ );
84
+ readFailed = true;
85
+ mode = 'unknown';
86
+ }
87
+
88
+ if (mode === 'dev') {
89
+ log('INFO', 'preserveDevCache: dist/client preserved (dev cache hit)');
90
+ return { action: 'preserved', mode: 'dev' };
91
+ }
92
+
93
+ // marker=prod 或 unknown → build:client 污染 (或人为放的产物), 定向清 index.html + assets/
94
+ // dist/server/, dist/*.json 保留
95
+ try {
96
+ fs.rmSync(indexHtml, { force: true });
97
+ const assetsDir = path.join(clientDir, 'assets');
98
+ if (fs.existsSync(assetsDir)) {
99
+ fs.rmSync(assetsDir, { recursive: true, force: true });
100
+ }
101
+ log(
102
+ 'INFO',
103
+ `preserveDevCache: dist/client/index.html was ${mode}, cleaned index.html + assets/ (dist/server preserved)`
104
+ );
105
+ return {
106
+ action: readFailed ? 'read_failed_cleaned' : 'cleaned_polluted',
107
+ mode,
108
+ };
109
+ } catch (e) {
110
+ log(
111
+ 'WARN',
112
+ `preserveDevCache: cleanup failed: ${e.message}, continuing anyway (index.html / assets may still be polluted)`
113
+ );
114
+ return { action: 'cleanup_failed', mode };
115
+ }
116
+ }
117
+
118
+ function defaultLogger(level, msg) {
119
+ // eslint-disable-next-line no-console
120
+ console.log(`[${level}] [main] ${msg}`);
121
+ }
122
+
123
+ module.exports = { detectHtmlMode, preserveDevCache };
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- import { defineConfig } from '@lark-apaas/fullstack-vite-preset';
2
+ import { defineConfig } from '@lark-apaas/coding-preset-vite-react';
3
3
 
4
4
  export default defineConfig({
5
5
  resolve: {