@dimina-kit/devkit 0.1.2-dev.20260707070110 → 0.1.2-dev.20260710085051

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,7 @@ await session.close()
47
47
  | `containerDir` | `string` | -- | H5 容器静态资源目录;不传则使用内置 `dimina-fe-container` |
48
48
  | `outputDir` | `string` | -- | 编译产物输出目录,默认 `<os.tmpdir()>/dimina-kit/<projectPath 哈希前 12 位>`(每个项目独立) |
49
49
  | `watch` | `boolean` | `true` | 为 `false` 时跳过 chokidar 文件监听 / 自动重编译循环 |
50
+ | `autoReload` | `boolean` | `true` | 为 `false` 时,watch 重编译完成后**不**触发预览 live-reload(`onRebuild` 仍照常回调)——保留当前页面栈 / 表单状态,仅手动刷新。与 `watch` 独立 |
50
51
  | `onRebuild` | `() => void` | -- | 文件变更触发重新编译后的回调 |
51
52
  | `onBuildError` | `(err: unknown) => void` | -- | watch 重编译出错时的回调;**初次编译失败不走这里,而是直接 reject `openProject` 本身**(见下) |
52
53
  | `onLog` | `(entry) => void` | -- | 逐行编译日志回调;`entry = { stream: 'stdout' \| 'stderr', text }`,已经过内置噪音过滤(`filterDmccLogLine`) |
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-completed-subscriber.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-completed-subscriber.test.d.ts","sourceRoot":"","sources":["../src/build-completed-subscriber.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { composeBuildCompleted } from './index.js';
3
+ /**
4
+ * Behavior tests for composeBuildCompleted.
5
+ *
6
+ * The contract decouples two independent user toggles that both react to a
7
+ * finished build: live-reloading the simulator, and the caller's own
8
+ * onRebuild bookkeeping (e.g. compile-log entries, UI badges). A developer
9
+ * must be able to keep auto-compile ON while turning simulator auto-reload
10
+ * OFF, so page-stack/form state survives a save. If the two were coupled,
11
+ * disabling reload would also silence onRebuild (or vice versa).
12
+ */
13
+ describe('composeBuildCompleted', () => {
14
+ it('invokes reload before onRebuild when autoReload is true and a reload function is available', () => {
15
+ const callOrder = [];
16
+ const reload = vi.fn(() => { callOrder.push('reload'); });
17
+ const getReload = vi.fn(() => reload);
18
+ const onRebuild = vi.fn(() => { callOrder.push('onRebuild'); });
19
+ const subscriber = composeBuildCompleted({ autoReload: true, getReload, onRebuild });
20
+ subscriber();
21
+ expect(reload).toHaveBeenCalledTimes(1);
22
+ expect(onRebuild).toHaveBeenCalledTimes(1);
23
+ expect(callOrder).toEqual(['reload', 'onRebuild']);
24
+ });
25
+ it('does not invoke reload when autoReload is false, but still calls onRebuild', () => {
26
+ const reload = vi.fn();
27
+ const getReload = vi.fn(() => reload);
28
+ const onRebuild = vi.fn();
29
+ const subscriber = composeBuildCompleted({ autoReload: false, getReload, onRebuild });
30
+ subscriber();
31
+ expect(reload).not.toHaveBeenCalled();
32
+ expect(onRebuild).toHaveBeenCalledTimes(1);
33
+ });
34
+ it('does not even call getReload when autoReload is false', () => {
35
+ // Why this matters: getReload may have side effects or preconditions
36
+ // (e.g. asserting a live webContents exists) that are only valid to
37
+ // evaluate when a reload is actually intended.
38
+ const getReload = vi.fn(() => vi.fn());
39
+ const onRebuild = vi.fn();
40
+ const subscriber = composeBuildCompleted({ autoReload: false, getReload, onRebuild });
41
+ subscriber();
42
+ expect(getReload).not.toHaveBeenCalled();
43
+ expect(onRebuild).toHaveBeenCalledTimes(1);
44
+ });
45
+ it('does not throw when autoReload is true but getReload returns undefined, and still calls onRebuild', () => {
46
+ // Why this matters: the simulator's reload function may not be wired up
47
+ // yet (e.g. build finished before the webview attached). A not-ready
48
+ // reload must not crash the build-completed pipeline.
49
+ const getReload = vi.fn(() => undefined);
50
+ const onRebuild = vi.fn();
51
+ const subscriber = composeBuildCompleted({ autoReload: true, getReload, onRebuild });
52
+ expect(() => subscriber()).not.toThrow();
53
+ expect(getReload).toHaveBeenCalledTimes(1);
54
+ expect(onRebuild).toHaveBeenCalledTimes(1);
55
+ });
56
+ it('does not throw when onRebuild is omitted, with autoReload true', () => {
57
+ const reload = vi.fn();
58
+ const getReload = vi.fn(() => reload);
59
+ const subscriber = composeBuildCompleted({ autoReload: true, getReload });
60
+ expect(() => subscriber()).not.toThrow();
61
+ expect(reload).toHaveBeenCalledTimes(1);
62
+ });
63
+ it('does not throw when onRebuild is omitted, with autoReload false', () => {
64
+ const getReload = vi.fn(() => vi.fn());
65
+ const subscriber = composeBuildCompleted({ autoReload: false, getReload });
66
+ expect(() => subscriber()).not.toThrow();
67
+ });
68
+ });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { CompileLogEntry } from './compile-worker.js';
2
2
  import type { CompileWorkerStandby, CompileWorkerStandbyOptions } from './compile-worker-standby.js';
3
+ export { WATCH_IGNORE_DIRS } from './watch-ignore.js';
3
4
  export { createRebuildScheduler } from './rebuild-scheduler.js';
4
5
  export type { RebuildScheduler } from './rebuild-scheduler.js';
5
6
  export { filterDmccLogLine } from './compile-log.js';
@@ -45,6 +46,13 @@ export interface OpenProjectOptions {
45
46
  outputDir?: string;
46
47
  /** When false, skip the chokidar file-watcher / auto-rebuild loop. Default true. */
47
48
  watch?: boolean;
49
+ /**
50
+ * When false, a completed watcher rebuild does NOT live-reload the preview
51
+ * (the SSE `reload` → container `window.location.reload()`); `onRebuild`
52
+ * still fires so the host learns the build finished, but the page stack /
53
+ * form state survives. Independent of `watch`. Default true.
54
+ */
55
+ autoReload?: boolean;
48
56
  onRebuild?: () => void;
49
57
  onBuildError?: (err: unknown) => void;
50
58
  /**
@@ -62,6 +70,19 @@ export interface OpenProjectOptions {
62
70
  */
63
71
  onWatcherError?: (err: unknown) => void;
64
72
  }
73
+ /**
74
+ * Compose the "build completed" subscriber — the single place deciding which of
75
+ * two INDEPENDENT reactions run: live-reloading the preview, and the caller's
76
+ * `onRebuild`. Reload is attached only when `autoReload` is on (so auto-compile
77
+ * can stay ON while reload is OFF, preserving page/form state); when off,
78
+ * `getReload` is not even called. `getReload` is read at call time because the
79
+ * dev server's `reload` is assigned after this subscriber is wired.
80
+ */
81
+ export declare function composeBuildCompleted(opts: {
82
+ autoReload: boolean;
83
+ getReload: () => (() => void) | undefined;
84
+ onRebuild?: () => void;
85
+ }): () => void;
65
86
  export declare function openProject(opts: OpenProjectOptions): Promise<ProjectSession>;
66
87
  /**
67
88
  * Watch a project directory and invoke `onChange` whenever a source file is
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAgB,eAAe,EAAiB,MAAM,qBAAqB,CAAA;AAEvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAEpG,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AACxE,YAAY,EACX,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,EACZ,YAAY,GACZ,MAAM,6BAA6B,CAAA;AAYpC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,CAAC,EAAE,2BAA2B,GAChC,oBAAoB,CAKtB;AAoDD,MAAM,WAAW,OAAO;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAuFD,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0JnF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GACrC;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAiEtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -6,10 +6,12 @@ import { fileURLToPath } from 'node:url';
6
6
  import { createServer } from 'node:http';
7
7
  import { createRequire } from 'node:module';
8
8
  import chokidar from 'chokidar';
9
+ import { WATCH_IGNORE_DIRS } from './watch-ignore.js';
9
10
  import { applyEsbuildBinaryPath } from './esbuild-binary-path.js';
10
11
  import { createRebuildScheduler } from './rebuild-scheduler.js';
11
12
  import { createCompileWorker } from './compile-worker.js';
12
13
  import { createCompileWorkerStandby } from './compile-worker-standby.js';
14
+ export { WATCH_IGNORE_DIRS } from './watch-ignore.js';
13
15
  export { createRebuildScheduler } from './rebuild-scheduler.js';
14
16
  export { filterDmccLogLine } from './compile-log.js';
15
17
  export { createCompileWorker } from './compile-worker.js';
@@ -108,20 +110,31 @@ function upsertSessionApp(sessionApps, rebuilt) {
108
110
  sessionApps[idx] = rebuilt;
109
111
  }
110
112
  /**
111
- * Run one watcher-triggered rebuild. `getReload` is read at call time (not
112
- * captured eagerly) because `reload` is only assigned once the dev server has
113
- * started, after this runner is wired into the rebuild scheduler.
113
+ * Compose the "build completed" subscriber the single place deciding which of
114
+ * two INDEPENDENT reactions run: live-reloading the preview, and the caller's
115
+ * `onRebuild`. Reload is attached only when `autoReload` is on (so auto-compile
116
+ * can stay ON while reload is OFF, preserving page/form state); when off,
117
+ * `getReload` is not even called. `getReload` is read at call time because the
118
+ * dev server's `reload` is assigned after this subscriber is wired.
114
119
  */
115
- async function runRebuild(compileWorker, buildRequest, sessionApps, getReload, onRebuild, onBuildError) {
120
+ export function composeBuildCompleted(opts) {
121
+ return () => {
122
+ if (opts.autoReload)
123
+ opts.getReload()?.();
124
+ opts.onRebuild?.();
125
+ };
126
+ }
127
+ /** Run one watcher-triggered rebuild, then fan out to the build-completed
128
+ * subscriber (reload + onRebuild); a build failure routes to onBuildFailed. */
129
+ async function runRebuild(compileWorker, buildRequest, sessionApps, onBuildCompleted, onBuildFailed) {
116
130
  try {
117
131
  const rebuilt = (await compileWorker.build(buildRequest));
118
132
  if (rebuilt)
119
133
  upsertSessionApp(sessionApps, rebuilt);
120
- getReload()?.();
121
- onRebuild?.();
134
+ onBuildCompleted();
122
135
  }
123
136
  catch (e) {
124
- onBuildError?.(e);
137
+ onBuildFailed(e);
125
138
  }
126
139
  }
127
140
  /**
@@ -151,7 +164,7 @@ async function cleanupFailedOpen(watcher, compileWorker, server) {
151
164
  }
152
165
  }
153
166
  export async function openProject(opts) {
154
- const { projectPath: rawProjectPath, port = 0, sourcemap = false, fileTypes, simulatorDir, containerDir: overrideContainerDir, outputDir, watch = true, onRebuild, onBuildError, onLog, onWatcherError, } = opts;
167
+ const { projectPath: rawProjectPath, port = 0, sourcemap = false, fileTypes, simulatorDir, containerDir: overrideContainerDir, outputDir, watch = true, autoReload = true, onRebuild, onBuildError, onLog, onWatcherError, } = opts;
155
168
  const projectPath = path.resolve(rawProjectPath);
156
169
  const buildOptions = { sourcemap, fileTypes };
157
170
  const resolvedPort = port === 0 ? await getRandomPort() : port;
@@ -243,7 +256,12 @@ export async function openProject(opts) {
243
256
  // Watcher events are routed through the scheduler so a save landing while
244
257
  // a build is in flight is never dropped: it coalesces into exactly one
245
258
  // trailing rebuild once the current run settles.
246
- const rebuildScheduler = createRebuildScheduler(() => runRebuild(compileWorker, buildRequest, sessionApps, () => reload, onRebuild, onBuildError));
259
+ const onBuildCompleted = composeBuildCompleted({
260
+ autoReload,
261
+ getReload: () => reload,
262
+ onRebuild,
263
+ });
264
+ const rebuildScheduler = createRebuildScheduler(() => runRebuild(compileWorker, buildRequest, sessionApps, onBuildCompleted, err => onBuildError?.(err)));
247
265
  watcher = watch
248
266
  ? createProjectWatcher(projectPath, () => rebuildScheduler.schedule(), onWatcherError)
249
267
  : null;
@@ -309,13 +327,17 @@ export function createProjectWatcher(projectPath, onChange, onWatcherError) {
309
327
  // (rel starts with '..') must never be ignored.
310
328
  if (!rel || rel.startsWith('..'))
311
329
  return false;
312
- // `node_modules` (any depth) is excluded like vite/webpack exclude
313
- // it: the compiler never reads it directly (its real npm input is
314
- // the built `miniprogram_npm/`, which stays watched), while watching
315
- // it makes chokidar hold thousands of per-directory fs.watch handles
316
- // a multi-second `watcher.close()` on session close, a slow
317
- // initial scan, and spurious rebuilds on dependency churn.
318
- return rel.split('/').some(seg => seg.startsWith('.') || seg === 'node_modules');
330
+ // Ignore all dotfiles/dot-dirs plus the shared never-source core
331
+ // (`WATCH_IGNORE_DIRS`: node_modules + VCS), kept in one place so the
332
+ // devtools editor mirror can't drift from this on the load-bearing
333
+ // `node_modules` omission. NOT build-output dirs like `dist`/`build`
334
+ // (app.json may put pages there the compiler must see those edits).
335
+ // Watching `node_modules` makes chokidar hold thousands of
336
+ // per-directory handles — a multi-second `watcher.close()`, a slow
337
+ // initial scan, and spurious rebuilds on dependency churn; the
338
+ // compiler's real npm input is the built `miniprogram_npm/`, which
339
+ // stays watched.
340
+ return rel.split('/').some(seg => seg.startsWith('.') || WATCH_IGNORE_DIRS.has(seg));
319
341
  },
320
342
  persistent: true,
321
343
  ignoreInitial: true,
@@ -236,6 +236,24 @@ describe('createProjectWatcher', () => {
236
236
  fs.writeFileSync(target, '// built npm output, changed');
237
237
  await vi.waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 2000 });
238
238
  });
239
+ it('fires onChange for edits inside a `dist` directory (a mini-app may legitimately name a source dir dist/build)', async () => {
240
+ // Why this matters: the compile watcher must NOT blanket-ignore build-tool
241
+ // names like `dist`/`build` — app.json can declare pages/components under
242
+ // arbitrary paths (e.g. `pages/build/index`), and the compiler reads them
243
+ // as source. Only the perf-critical, never-source dirs (node_modules, VCS)
244
+ // are ignored here; hiding `dist`/`build` is the devtools *editor mirror*'s
245
+ // concern, not the recompile trigger's. The compiler's own output lands in
246
+ // os.tmpdir(), outside the watched project, so there is no self-loop to fear.
247
+ const distDir = path.join(tempDir, 'dist');
248
+ fs.mkdirSync(distDir, { recursive: true });
249
+ const target = path.join(distDir, 'index.js');
250
+ fs.writeFileSync(target, '// page source');
251
+ const onChange = vi.fn();
252
+ handle = createProjectWatcher(tempDir, onChange);
253
+ await handle.ready;
254
+ fs.writeFileSync(target, '// page source, changed');
255
+ await vi.waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 2000 });
256
+ });
239
257
  it('fires onChange for source edits when the project itself sits under an ancestor node_modules directory', async () => {
240
258
  // Why this matters: same shape as the dotted-ancestor regression above —
241
259
  // a project can legitimately live inside a node_modules directory (e.g.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=watch-ignore-dirs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-ignore-dirs.test.d.ts","sourceRoot":"","sources":["../src/watch-ignore-dirs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { WATCH_IGNORE_DIRS as indexExport } from './index.js';
3
+ import { WATCH_IGNORE_DIRS as leafExport } from './watch-ignore.js';
4
+ /**
5
+ * Pins down WATCH_IGNORE_DIRS as the single source of truth for the directory
6
+ * names that are NEVER mini-app source and must be skipped by every project
7
+ * watcher — the drift-proof core shared by the devkit recompile watcher and
8
+ * the devtools editor mirror. Deliberately minimal: build-tool output names
9
+ * like `dist`/`build` are NOT here, because app.json may declare pages under
10
+ * such paths and the compiler must still see edits to them (hiding build output
11
+ * from the editor is the devtools mirror's own concern, layered on top). Both
12
+ * the leaf module and the package index must expose the identical set object —
13
+ * two equal-membership sets would still let the call sites drift over time.
14
+ */
15
+ const EXPECTED_MEMBERS = [
16
+ 'node_modules',
17
+ '.git',
18
+ '.svn',
19
+ '.hg',
20
+ ];
21
+ describe('WATCH_IGNORE_DIRS', () => {
22
+ it('is re-exported from index.ts as the exact same Set instance as the leaf module', () => {
23
+ expect(indexExport).toBe(leafExport);
24
+ });
25
+ it.each(EXPECTED_MEMBERS)('contains %s', (member) => {
26
+ expect(leafExport.has(member)).toBe(true);
27
+ });
28
+ it('contains exactly the expected members, no more and no fewer', () => {
29
+ expect(new Set(leafExport)).toEqual(new Set(EXPECTED_MEMBERS));
30
+ });
31
+ it.each(['src', 'pages', 'miniprogram_npm', 'dist', 'build'])('does not contain %s', (nonMember) => {
32
+ expect(leafExport.has(nonMember)).toBe(false);
33
+ });
34
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Directory segment names that are NEVER mini-app source and must be skipped by
3
+ * every project-tree scan, at any depth. The drift-proof CORE shared by the
4
+ * devkit recompile watcher (createProjectWatcher) and the devtools editor
5
+ * mirror (project-fs `SKIP_DIRS`, which derives its set from this one): the
6
+ * `node_modules` omission that once made `watcher.close()` take 2.6s can never
7
+ * regress in just one of them.
8
+ *
9
+ * Deliberately minimal — build-tool OUTPUT names (`dist`, `build`, `.next`,
10
+ * tool caches …) are intentionally absent: app.json may declare pages under
11
+ * such paths (e.g. `pages/build/index`), so the recompile watcher must still
12
+ * see edits to them. Hiding build output is the devtools editor mirror's own
13
+ * concern, layered ON TOP of this core (see project-fs `SKIP_DIRS`). Each side
14
+ * also keeps its domain rule: the devkit watcher additionally drops all
15
+ * dotfiles (they never affect compilation).
16
+ */
17
+ export declare const WATCH_IGNORE_DIRS: ReadonlySet<string>;
18
+ //# sourceMappingURL=watch-ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-ignore.d.ts","sourceRoot":"","sources":["../src/watch-ignore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAEhD,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Directory segment names that are NEVER mini-app source and must be skipped by
3
+ * every project-tree scan, at any depth. The drift-proof CORE shared by the
4
+ * devkit recompile watcher (createProjectWatcher) and the devtools editor
5
+ * mirror (project-fs `SKIP_DIRS`, which derives its set from this one): the
6
+ * `node_modules` omission that once made `watcher.close()` take 2.6s can never
7
+ * regress in just one of them.
8
+ *
9
+ * Deliberately minimal — build-tool OUTPUT names (`dist`, `build`, `.next`,
10
+ * tool caches …) are intentionally absent: app.json may declare pages under
11
+ * such paths (e.g. `pages/build/index`), so the recompile watcher must still
12
+ * see edits to them. Hiding build output is the devtools editor mirror's own
13
+ * concern, layered ON TOP of this core (see project-fs `SKIP_DIRS`). Each side
14
+ * also keeps its domain rule: the devkit watcher additionally drops all
15
+ * dotfiles (they never affect compilation).
16
+ */
17
+ export const WATCH_IGNORE_DIRS = new Set([
18
+ 'node_modules', '.git', '.svn', '.hg',
19
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimina-kit/devkit",
3
- "version": "0.1.2-dev.20260707070110",
3
+ "version": "0.1.2-dev.20260710085051",
4
4
  "description": "Development toolkit for Dimina mini-apps with H5 container preview",
5
5
  "keywords": [
6
6
  "dimina",
@@ -33,6 +33,10 @@
33
33
  ".": {
34
34
  "types": "./dist/index.d.ts",
35
35
  "default": "./dist/index.js"
36
+ },
37
+ "./watch-ignore": {
38
+ "types": "./dist/watch-ignore.d.ts",
39
+ "default": "./dist/watch-ignore.js"
36
40
  }
37
41
  },
38
42
  "files": [
@@ -62,7 +66,7 @@
62
66
  "cors": "^2.8.6",
63
67
  "express": "^5.2.1",
64
68
  "open": "^11.0.0",
65
- "@dimina-kit/compiler": "0.0.1-dev.20260707070110"
69
+ "@dimina-kit/compiler": "0.0.2-dev.20260710085051"
66
70
  },
67
71
  "publishConfig": {
68
72
  "access": "public"