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

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.
@@ -1 +1 @@
1
- {"version":3,"file":"compile-log.d.ts","sourceRoot":"","sources":["../src/compile-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsBH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO7D"}
1
+ {"version":3,"file":"compile-log.d.ts","sourceRoot":"","sources":["../src/compile-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgCH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO7D"}
@@ -25,6 +25,16 @@ const DROP_RULES = [
25
25
  // Stack-trace frames (esbuild / Node internals) — the message line above
26
26
  // them is kept; frames are noise for a compile panel.
27
27
  /^\s+at /,
28
+ // Node process-level DeprecationWarnings + the `--trace-deprecation` hint
29
+ // Node prints after the first one. The developer's code never runs in the
30
+ // compile worker, so these are never actionable in a compile panel. Packaged
31
+ // Electron apps ALWAYS emit one: the asar fs shim's asarStatsToFsStats uses
32
+ // the deprecated fs.Stats constructor (electron/electron#47390), so the
33
+ // first stat of an in-asar file prints the DEP0180 pair to stderr. Only the
34
+ // DeprecationWarning form is dropped — other `(node:pid)` warnings
35
+ // (MaxListenersExceeded, Experimental) can carry real signal and are kept.
36
+ /^\(node:\d+\) (?:\[DEP\d+\] )?DeprecationWarning: /,
37
+ /^\(Use `.+--trace-deprecation .*` to show where the warning was created\)/,
28
38
  ];
29
39
  /**
30
40
  * Strip ANSI escapes, then decide keep/drop. Returns the cleaned line, or
@@ -116,6 +116,50 @@ describe('filterDmccLogLine — KEEP rules (real dmcc signal from the spike arch
116
116
  }
117
117
  });
118
118
  });
119
+ describe('filterDmccLogLine — DROP rules (packaged-app Node DeprecationWarning noise, electron/electron#47390)', () => {
120
+ // Electron's asar fs shim (`asarStatsToFsStats`) uses the deprecated
121
+ // `fs.Stats` constructor, so Node prints a process-level
122
+ // DeprecationWarning pair on the compile worker's stderr the first time a
123
+ // packaged app stats a file inside app.asar. The developer's own code
124
+ // never runs in the compile worker, so these lines are never actionable
125
+ // and must be dropped.
126
+ it('drops the Node process-level DeprecationWarning line, with or without a DEP code', () => {
127
+ const filter = getFilter();
128
+ expect(filter('(node:4984) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.')).toBeNull();
129
+ expect(filter('(node:90165) DeprecationWarning: Buffer() is deprecated due to security and usability issues.')).toBeNull();
130
+ });
131
+ it('drops the trace-deprecation hint line that follows, regardless of the binary name', () => {
132
+ const filter = getFilter();
133
+ expect(filter('(Use `千岛开发者工具 Helper --trace-deprecation ...` to show where the warning was created)')).toBeNull();
134
+ expect(filter('(Use `node --trace-deprecation ...` to show where the warning was created)')).toBeNull();
135
+ });
136
+ it('drops ANSI-colored variants of both lines', () => {
137
+ const filter = getFilter();
138
+ expect(filter('(node:4984) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.')).toBeNull();
139
+ expect(filter('(Use `node --trace-deprecation ...` to show where the warning was created)')).toBeNull();
140
+ });
141
+ });
142
+ describe('filterDmccLogLine — KEEP rules (guards against over-filtering Node warnings)', () => {
143
+ it('keeps other Node process-level warnings that are not DeprecationWarnings', () => {
144
+ const filter = getFilter();
145
+ // These can carry real signal (leak / experimental-API notices) and are
146
+ // not the asar-shim noise this filter targets.
147
+ const lines = [
148
+ '(node:123) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.',
149
+ '(node:123) ExperimentalWarning: VM Modules is an experimental feature.',
150
+ ];
151
+ for (const line of lines) {
152
+ expect(filter(line), `non-deprecation Node warning must be kept: ${JSON.stringify(line)}`).toBe(line);
153
+ }
154
+ });
155
+ it('keeps a compiler diagnostic that merely mentions DeprecationWarning mid-sentence', () => {
156
+ const filter = getFilter();
157
+ // Does not start with the `(node:<pid>)` prefix, so it is not the
158
+ // Node process-level warning form — default-keep applies.
159
+ const line = '[logic] esbuild 转换失败 app.js: DeprecationWarning something';
160
+ expect(filter(line)).toBe(line);
161
+ });
162
+ });
119
163
  describe('filterDmccLogLine — ANSI stripping happens BEFORE rule matching', () => {
120
164
  it('strips ANSI from a kept line and returns the cleaned text', () => {
121
165
  const filter = getFilter();
@@ -0,0 +1,38 @@
1
+ /**
2
+ * ESBUILD_BINARY_PATH redirect for Electron app.asar hosts.
3
+ *
4
+ * esbuild's JS lib computes its native binary path from its own module
5
+ * location. Packaged via electron-builder that location sits inside app.asar,
6
+ * and `child_process.spawn` (unlike execFile) is not patched by Electron's
7
+ * asar shim — spawning an in-asar path always fails ENOENT even though
8
+ * asarUnpack put the real binary in app.asar.unpacked. esbuild honors the
9
+ * ESBUILD_BINARY_PATH env var, so devkit points it at the unpacked binary.
10
+ *
11
+ * The platform packages lay their binary out differently (esbuild's own
12
+ * pkgAndSubpathForCurrentPlatform): win32 ships `esbuild.exe` at the package
13
+ * ROOT — there is no `bin/` directory — while every unix-like platform ships
14
+ * `bin/esbuild`. Resolving the unix shape on win32 throws, and a swallowed
15
+ * throw leaves the env var unset, which is exactly the packaged-Windows
16
+ * failure mode this module guards against.
17
+ *
18
+ * All I/O is dependency-injected so the branching is unit-testable; index.ts
19
+ * wires the real process/require/fs at module load.
20
+ */
21
+ export interface ApplyEsbuildBinaryPathOptions {
22
+ /** Caller module's directory — an `app.asar` segment means "packaged". */
23
+ dirname: string;
24
+ /** process.env-like map, mutated in place. */
25
+ env: Record<string, string | undefined>;
26
+ /** process.platform value. */
27
+ platform: string;
28
+ /** process.arch value. */
29
+ arch: string;
30
+ /** require.resolve-like; throws when the id cannot be resolved. */
31
+ resolve: (id: string) => string;
32
+ /** fs.existsSync-like, probed against the rewritten unpacked path. */
33
+ exists: (p: string) => boolean;
34
+ /** Diagnostics sink — every failure path reports here instead of going silent. */
35
+ warn: (msg: string) => void;
36
+ }
37
+ export declare function applyEsbuildBinaryPath(opts: ApplyEsbuildBinaryPathOptions): void;
38
+ //# sourceMappingURL=esbuild-binary-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild-binary-path.d.ts","sourceRoot":"","sources":["../src/esbuild-binary-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,6BAA6B;IAC7C,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;IACf,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACvC,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,mEAAmE;IACnE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B,sEAAsE;IACtE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9B,kFAAkF;IAClF,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,6BAA6B,GAAG,IAAI,CA0BhF"}
@@ -0,0 +1,29 @@
1
+ export function applyEsbuildBinaryPath(opts) {
2
+ const { dirname, env, platform, arch, resolve, exists, warn } = opts;
3
+ if (env.ESBUILD_BINARY_PATH)
4
+ return;
5
+ if (!dirname.includes('app.asar'))
6
+ return;
7
+ const subpath = platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild';
8
+ const id = `@esbuild/${platform}-${arch}/${subpath}`;
9
+ let resolved;
10
+ try {
11
+ resolved = resolve(id);
12
+ }
13
+ catch {
14
+ warn(`[devkit] could not resolve ${id} — esbuild's platform binary package is missing `
15
+ + 'from the packaged app, so the compiler cannot spawn esbuild. Ship the package '
16
+ + '(electron-builder dependency collection must include it) or set ESBUILD_BINARY_PATH.');
17
+ return;
18
+ }
19
+ // asarUnpack mirrors the archive's layout on disk — only the first app.asar
20
+ // segment moves to app.asar.unpacked. A path already outside the archive
21
+ // (or already unpacked) has no `app.asar/` segment and passes through as-is.
22
+ const unpacked = resolved.replace(/app\.asar([\\/])/, 'app.asar.unpacked$1');
23
+ if (!exists(unpacked)) {
24
+ warn(`[devkit] esbuild binary not found at ${unpacked} — the Electron host's packaging must `
25
+ + "asarUnpack '**/node_modules/esbuild/**' and '**/node_modules/@esbuild/**' so the native "
26
+ + 'binary exists outside app.asar (spawn cannot execute from inside the archive).');
27
+ }
28
+ env.ESBUILD_BINARY_PATH = unpacked;
29
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=esbuild-binary-path.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild-binary-path.test.d.ts","sourceRoot":"","sources":["../src/esbuild-binary-path.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { applyEsbuildBinaryPath } from './esbuild-binary-path.js';
3
+ /**
4
+ * Contract for `applyEsbuildBinaryPath` — the ESBUILD_BINARY_PATH redirect
5
+ * that lets esbuild spawn its native binary from app.asar.unpacked when
6
+ * devkit runs packaged inside an Electron app.asar.
7
+ *
8
+ * The platform package layout differs by OS:
9
+ * - darwin/linux: `@esbuild/<platform>-<arch>/bin/esbuild`
10
+ * - win32: `@esbuild/win32-<arch>/esbuild.exe` at the package ROOT, no
11
+ * `bin/` dir. Resolving the darwin/linux shape on win32 throws, and a
12
+ * swallowed throw means ESBUILD_BINARY_PATH is silently never set —
13
+ * packaged Windows apps then fail with
14
+ * `spawn ...app.asar\node_modules\@esbuild\win32-x64\esbuild.exe ENOENT`.
15
+ *
16
+ * All I/O (require.resolve, fs.existsSync, console.warn, process.env) is
17
+ * dependency-injected so the tests exercise pure branching logic.
18
+ */
19
+ function makeOpts(overrides = {}) {
20
+ return {
21
+ dirname: 'C:\\Users\\x\\AppData\\Local\\Programs\\myapp\\resources\\app.asar\\packages\\devkit\\dist',
22
+ env: {},
23
+ platform: 'win32',
24
+ arch: 'x64',
25
+ resolve: vi.fn(() => {
26
+ throw new Error('resolve not stubbed for this test');
27
+ }),
28
+ exists: vi.fn(() => true),
29
+ warn: vi.fn(),
30
+ ...overrides,
31
+ };
32
+ }
33
+ describe('applyEsbuildBinaryPath — no-op guards', () => {
34
+ it('does nothing when ESBUILD_BINARY_PATH is already set', () => {
35
+ const env = { ESBUILD_BINARY_PATH: '/already/set/esbuild' };
36
+ const resolve = vi.fn();
37
+ const exists = vi.fn();
38
+ const warn = vi.fn();
39
+ applyEsbuildBinaryPath(makeOpts({ env, resolve, exists, warn }));
40
+ expect(env.ESBUILD_BINARY_PATH).toBe('/already/set/esbuild');
41
+ expect(resolve).not.toHaveBeenCalled();
42
+ expect(exists).not.toHaveBeenCalled();
43
+ expect(warn).not.toHaveBeenCalled();
44
+ });
45
+ it('does nothing when dirname is not inside an app.asar (unpackaged dev run)', () => {
46
+ const env = {};
47
+ const resolve = vi.fn();
48
+ const exists = vi.fn();
49
+ const warn = vi.fn();
50
+ applyEsbuildBinaryPath(makeOpts({
51
+ dirname: '/Users/dev/code/dimina-kit/packages/devkit/dist',
52
+ env,
53
+ resolve,
54
+ exists,
55
+ warn,
56
+ }));
57
+ expect(env.ESBUILD_BINARY_PATH).toBeUndefined();
58
+ expect(resolve).not.toHaveBeenCalled();
59
+ expect(exists).not.toHaveBeenCalled();
60
+ expect(warn).not.toHaveBeenCalled();
61
+ });
62
+ });
63
+ describe('applyEsbuildBinaryPath — platform-correct binary subpath', () => {
64
+ it('resolves the win32 package root exe, not bin/esbuild (the Windows bug)', () => {
65
+ const resolve = vi.fn(() => 'C:\\app\\app.asar\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
66
+ applyEsbuildBinaryPath(makeOpts({ platform: 'win32', arch: 'x64', resolve }));
67
+ expect(resolve).toHaveBeenCalledWith('@esbuild/win32-x64/esbuild.exe');
68
+ expect(resolve).not.toHaveBeenCalledWith('@esbuild/win32-x64/bin/esbuild');
69
+ });
70
+ it('resolves the darwin bin/esbuild subpath', () => {
71
+ const resolve = vi.fn(() => '/app/app.asar/node_modules/@esbuild/darwin-arm64/bin/esbuild');
72
+ applyEsbuildBinaryPath(makeOpts({ platform: 'darwin', arch: 'arm64', resolve }));
73
+ expect(resolve).toHaveBeenCalledWith('@esbuild/darwin-arm64/bin/esbuild');
74
+ });
75
+ it('resolves the linux bin/esbuild subpath', () => {
76
+ const resolve = vi.fn(() => '/app/app.asar/node_modules/@esbuild/linux-x64/bin/esbuild');
77
+ applyEsbuildBinaryPath(makeOpts({ platform: 'linux', arch: 'x64', resolve }));
78
+ expect(resolve).toHaveBeenCalledWith('@esbuild/linux-x64/bin/esbuild');
79
+ });
80
+ });
81
+ describe('applyEsbuildBinaryPath — app.asar → app.asar.unpacked rewrite', () => {
82
+ it('rewrites the first app.asar\\ segment to app.asar.unpacked\\ on a Windows backslash path', () => {
83
+ const env = {};
84
+ const resolve = vi.fn(() => 'C:\\Users\\x\\AppData\\app.asar\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
85
+ applyEsbuildBinaryPath(makeOpts({ platform: 'win32', arch: 'x64', env, resolve }));
86
+ expect(env.ESBUILD_BINARY_PATH).toBe('C:\\Users\\x\\AppData\\app.asar.unpacked\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
87
+ });
88
+ it('rewrites the first app.asar/ segment to app.asar.unpacked/ on a POSIX forward-slash path', () => {
89
+ const env = {};
90
+ const resolve = vi.fn(() => '/Applications/MyApp.app/Contents/Resources/app.asar/node_modules/@esbuild/darwin-arm64/bin/esbuild');
91
+ applyEsbuildBinaryPath(makeOpts({ platform: 'darwin', arch: 'arm64', dirname: '/Applications/MyApp.app/Contents/Resources/app.asar/packages/devkit/dist', env, resolve }));
92
+ expect(env.ESBUILD_BINARY_PATH).toBe('/Applications/MyApp.app/Contents/Resources/app.asar.unpacked/node_modules/@esbuild/darwin-arm64/bin/esbuild');
93
+ });
94
+ it('does not double-rewrite a resolved path that already contains app.asar.unpacked', () => {
95
+ const env = {};
96
+ const resolve = vi.fn(() => '/Applications/MyApp.app/Contents/Resources/app.asar.unpacked/node_modules/@esbuild/darwin-arm64/bin/esbuild');
97
+ applyEsbuildBinaryPath(makeOpts({ platform: 'darwin', arch: 'arm64', dirname: '/Applications/MyApp.app/Contents/Resources/app.asar/packages/devkit/dist', env, resolve }));
98
+ expect(env.ESBUILD_BINARY_PATH).toBe('/Applications/MyApp.app/Contents/Resources/app.asar.unpacked/node_modules/@esbuild/darwin-arm64/bin/esbuild');
99
+ });
100
+ });
101
+ describe('applyEsbuildBinaryPath — resolve failure (the swallowed-throw half of the bug)', () => {
102
+ it('does not throw, leaves env unset, and warns once naming the platform package when resolve throws', () => {
103
+ const env = {};
104
+ const resolve = vi.fn(() => {
105
+ throw new Error("Cannot find module '@esbuild/win32-x64/esbuild.exe'");
106
+ });
107
+ const warn = vi.fn();
108
+ expect(() => {
109
+ applyEsbuildBinaryPath(makeOpts({ platform: 'win32', arch: 'x64', env, resolve, warn }));
110
+ }).not.toThrow();
111
+ expect(env.ESBUILD_BINARY_PATH).toBeUndefined();
112
+ expect(warn).toHaveBeenCalledTimes(1);
113
+ expect(warn.mock.calls[0]?.[0]).toEqual(expect.stringContaining('@esbuild/win32-x64'));
114
+ });
115
+ });
116
+ describe('applyEsbuildBinaryPath — unpacked-path existence check', () => {
117
+ it('still sets ESBUILD_BINARY_PATH and warns once with an asarUnpack hint when the unpacked path does not exist', () => {
118
+ const env = {};
119
+ const resolve = vi.fn(() => 'C:\\app\\app.asar\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
120
+ const exists = vi.fn(() => false);
121
+ const warn = vi.fn();
122
+ applyEsbuildBinaryPath(makeOpts({ platform: 'win32', arch: 'x64', env, resolve, exists, warn }));
123
+ expect(env.ESBUILD_BINARY_PATH).toBe('C:\\app\\app.asar.unpacked\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
124
+ expect(warn).toHaveBeenCalledTimes(1);
125
+ expect(warn.mock.calls[0]?.[0]).toEqual(expect.stringContaining('asarUnpack'));
126
+ });
127
+ it('never warns when the unpacked path exists', () => {
128
+ const resolve = vi.fn(() => 'C:\\app\\app.asar\\node_modules\\@esbuild\\win32-x64\\esbuild.exe');
129
+ const exists = vi.fn(() => true);
130
+ const warn = vi.fn();
131
+ applyEsbuildBinaryPath(makeOpts({ platform: 'win32', arch: 'x64', resolve, exists, warn }));
132
+ expect(warn).not.toHaveBeenCalled();
133
+ });
134
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,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;AAqDD,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,CA2DtD"}
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"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ 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 { applyEsbuildBinaryPath } from './esbuild-binary-path.js';
9
10
  import { createRebuildScheduler } from './rebuild-scheduler.js';
10
11
  import { createCompileWorker } from './compile-worker.js';
11
12
  import { createCompileWorkerStandby } from './compile-worker-standby.js';
@@ -50,21 +51,20 @@ function refillStandby() {
50
51
  }
51
52
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
52
53
  const require = createRequire(import.meta.url);
53
- // esbuild's native binary is shipped inside node_modules, but when packaged
54
- // via electron-builder it lives in app.asar. asarUnpack puts the real binary
55
- // in app.asar.unpacked but esbuild computes its binary path from __dirname,
56
- // which still points inside app.asar. Redirect it explicitly via require.resolve
57
- // so we don't hard-code the hoisting depth (pnpm vs npm layouts differ).
58
- if (!process.env.ESBUILD_BINARY_PATH && __dirname.includes('app.asar')) {
59
- const platform = `${process.platform}-${process.arch}`;
60
- try {
61
- const resolved = require.resolve(`@esbuild/${platform}/bin/esbuild`);
62
- process.env.ESBUILD_BINARY_PATH = resolved.replace(/app\.asar([\\/])/, 'app.asar.unpacked$1');
63
- }
64
- catch {
65
- // fall through — esbuild will surface a clearer error if the binary is truly missing
66
- }
67
- }
54
+ // Packaged inside app.asar, esbuild's native binary must be spawned from
55
+ // app.asar.unpacked resolved via require.resolve so the hoisting depth
56
+ // (pnpm vs npm layouts) is never hard-coded. The per-platform binary layout
57
+ // and every failure diagnostic live in esbuild-binary-path.ts. The env var is
58
+ // set before any compile worker forks, and forks inherit it.
59
+ applyEsbuildBinaryPath({
60
+ dirname: __dirname,
61
+ env: process.env,
62
+ platform: process.platform,
63
+ arch: process.arch,
64
+ resolve: require.resolve,
65
+ exists: fs.existsSync,
66
+ warn: console.warn,
67
+ });
68
68
  function getRandomPort() {
69
69
  return new Promise((resolve, reject) => {
70
70
  const srv = createServer();
@@ -309,7 +309,13 @@ export function createProjectWatcher(projectPath, onChange, onWatcherError) {
309
309
  // (rel starts with '..') must never be ignored.
310
310
  if (!rel || rel.startsWith('..'))
311
311
  return false;
312
- return rel.split('/').some(seg => seg.startsWith('.'));
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');
313
319
  },
314
320
  persistent: true,
315
321
  ignoreInitial: true,
@@ -186,4 +186,72 @@ describe('createProjectWatcher', () => {
186
186
  await settle(500);
187
187
  expect(onChange).not.toHaveBeenCalled();
188
188
  });
189
+ it('stays quiet when a file inside node_modules changes', async () => {
190
+ // Why this matters: a project with 87 dependencies puts chokidar under
191
+ // watch for 1232+ directories inside node_modules, making close() take
192
+ // seconds and letting dependency-internal file churn (postinstall
193
+ // scripts, lockfile-driven reinstalls) trigger spurious rebuilds.
194
+ // node_modules anywhere under the project — not just at the root — must
195
+ // be excluded from the watch entirely.
196
+ const nodeModulesDir = path.join(tempDir, 'node_modules', 'lodash');
197
+ fs.mkdirSync(nodeModulesDir, { recursive: true });
198
+ const target = path.join(nodeModulesDir, 'index.js');
199
+ fs.writeFileSync(target, '// vendored');
200
+ const onChange = vi.fn();
201
+ handle = createProjectWatcher(tempDir, onChange);
202
+ await handle.ready;
203
+ fs.writeFileSync(target, '// vendored, changed');
204
+ await settle(500);
205
+ expect(onChange).not.toHaveBeenCalled();
206
+ });
207
+ it('stays quiet when a file inside a nested package node_modules changes', async () => {
208
+ // Why this matters: node_modules can recur at any depth (npm/pnpm
209
+ // dependency trees, workspace packages with their own node_modules).
210
+ // A watcher that only ignores the top-level node_modules segment would
211
+ // still get flooded by these nested trees.
212
+ const nestedNodeModules = path.join(tempDir, 'packages', 'a', 'node_modules', 'b');
213
+ fs.mkdirSync(nestedNodeModules, { recursive: true });
214
+ const target = path.join(nestedNodeModules, 'x.js');
215
+ fs.writeFileSync(target, '// nested vendored');
216
+ const onChange = vi.fn();
217
+ handle = createProjectWatcher(tempDir, onChange);
218
+ await handle.ready;
219
+ fs.writeFileSync(target, '// nested vendored, changed');
220
+ await settle(500);
221
+ expect(onChange).not.toHaveBeenCalled();
222
+ });
223
+ it('fires onChange for changes inside miniprogram_npm', async () => {
224
+ // Why this matters: miniprogram_npm is the mini-app compiler's real
225
+ // input — the built npm output that the compiler reads directly, not a
226
+ // vendor tree to hide. A node_modules-ignoring rule that over-matches
227
+ // (e.g. any path segment containing "node_modules" as a substring, or a
228
+ // blanket "*npm*" pattern) would wrongly silence this directory too.
229
+ const npmDir = path.join(tempDir, 'miniprogram_npm', 'some-pkg');
230
+ fs.mkdirSync(npmDir, { recursive: true });
231
+ const target = path.join(npmDir, 'index.js');
232
+ fs.writeFileSync(target, '// built npm output');
233
+ const onChange = vi.fn();
234
+ handle = createProjectWatcher(tempDir, onChange);
235
+ await settle();
236
+ fs.writeFileSync(target, '// built npm output, changed');
237
+ await vi.waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 2000 });
238
+ });
239
+ it('fires onChange for source edits when the project itself sits under an ancestor node_modules directory', async () => {
240
+ // Why this matters: same shape as the dotted-ancestor regression above —
241
+ // a project can legitimately live inside a node_modules directory (e.g.
242
+ // a demo/fixture project bundled inside a pnpm-installed package). Only
243
+ // node_modules segments *under* projectPath should be ignored; an
244
+ // ancestor node_modules segment must not disable the watch for the
245
+ // project's own source.
246
+ const ancestorNodeModules = path.join(tempDir, 'node_modules', 'some-demo-pkg');
247
+ const projectRoot = path.join(ancestorNodeModules, 'demo-app');
248
+ fs.mkdirSync(projectRoot, { recursive: true });
249
+ const target = path.join(projectRoot, 'app.json');
250
+ fs.writeFileSync(target, '{}');
251
+ const onChange = vi.fn();
252
+ handle = createProjectWatcher(projectRoot, onChange);
253
+ await settle();
254
+ fs.writeFileSync(target, '{"a":1}');
255
+ await vi.waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 2000 });
256
+ });
189
257
  });