@gjsify/cli 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.gjs.mjs +539 -493
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +37 -1
- package/lib/bundler-pick.js +108 -17
- package/lib/cli-app.js +7 -2
- package/lib/commands/affected-classify.d.ts +7 -1
- package/lib/commands/affected-classify.js +66 -9
- package/lib/commands/affected-script-refs.d.ts +20 -0
- package/lib/commands/affected-script-refs.js +218 -0
- package/lib/commands/affected.js +24 -2
- package/lib/commands/env.d.ts +31 -0
- package/lib/commands/env.js +114 -0
- package/lib/commands/exec.d.ts +10 -0
- package/lib/commands/exec.js +208 -0
- package/lib/commands/flatpak/diff.js +8 -2
- package/lib/commands/flatpak/init.js +4 -1
- package/lib/commands/flatpak/sources.js +4 -3
- package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
- package/lib/commands/flatpak/sync-flathub.js +149 -7
- package/lib/commands/flatpak/utils.d.ts +10 -0
- package/lib/commands/flatpak/utils.js +10 -0
- package/lib/commands/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +81 -5
- package/lib/commands/link.d.ts +13 -0
- package/lib/commands/link.js +243 -0
- package/lib/commands/onboard.js +18 -2
- package/lib/commands/publish.d.ts +34 -0
- package/lib/commands/publish.js +153 -8
- package/lib/commands/self-update.js +2 -1
- package/lib/commands/ship.js +19 -1
- package/lib/commands/test.d.ts +18 -9
- package/lib/commands/test.js +56 -11
- package/lib/commands/tsc.js +4 -4
- package/lib/commands/upgrade.d.ts +15 -0
- package/lib/commands/upgrade.js +76 -2
- package/lib/commands/webext/index.d.ts +2 -0
- package/lib/commands/webext/index.js +15 -0
- package/lib/commands/webext/subcommands.d.ts +16 -0
- package/lib/commands/webext/subcommands.js +204 -0
- package/lib/types/config-data.d.ts +2 -2
- package/lib/utils/bin-shim.d.ts +8 -2
- package/lib/utils/bin-shim.js +6 -5
- package/lib/utils/bundle-inputs.d.ts +84 -0
- package/lib/utils/bundle-inputs.js +268 -0
- package/lib/utils/check-system-deps.d.ts +5 -19
- package/lib/utils/check-system-deps.js +31 -94
- package/lib/utils/dev-link.d.ts +225 -0
- package/lib/utils/dev-link.js +684 -0
- package/lib/utils/exec-bin.d.ts +137 -0
- package/lib/utils/exec-bin.js +321 -0
- package/lib/utils/gi-runtime-paths.js +3 -2
- package/lib/utils/gitignore.d.ts +15 -0
- package/lib/utils/gitignore.js +178 -0
- package/lib/utils/gjsify-shim.d.ts +47 -0
- package/lib/utils/gjsify-shim.js +97 -8
- package/lib/utils/hang-watchdog.d.ts +95 -0
- package/lib/utils/hang-watchdog.js +194 -0
- package/lib/utils/install-backend-native.d.ts +33 -1
- package/lib/utils/install-backend-native.js +184 -20
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- package/lib/utils/native-skip-scan.d.ts +20 -0
- package/lib/utils/native-skip-scan.js +343 -0
- package/lib/utils/node-script.js +1 -1
- package/lib/utils/normalize-bundler-options.js +11 -0
- package/lib/utils/oxc-resolve.js +42 -3
- package/lib/utils/process-table.d.ts +22 -0
- package/lib/utils/process-table.js +188 -0
- package/lib/utils/prune-prefix.js +1 -1
- package/lib/utils/publish-pins.d.ts +38 -0
- package/lib/utils/publish-pins.js +47 -0
- package/lib/utils/publish-readback.d.ts +103 -17
- package/lib/utils/publish-readback.js +326 -47
- package/lib/utils/run-gjs.d.ts +15 -1
- package/lib/utils/run-gjs.js +98 -13
- package/lib/utils/run-node.d.ts +2 -0
- package/lib/utils/run-node.js +2 -1
- package/lib/utils/runtimes.js +14 -13
- package/lib/utils/ship/gzip.js +1 -1
- package/lib/utils/ship/icns.d.ts +14 -0
- package/lib/utils/ship/icns.js +83 -0
- package/lib/utils/ship/ico.d.ts +25 -0
- package/lib/utils/ship/ico.js +120 -0
- package/lib/utils/ship/icons.d.ts +197 -0
- package/lib/utils/ship/icons.js +399 -0
- package/lib/utils/ship/launcher.js +16 -1
- package/lib/utils/ship/layout.d.ts +32 -0
- package/lib/utils/ship/layout.js +112 -38
- package/lib/utils/ship/msi.d.ts +1 -1
- package/lib/utils/ship/msi.js +30 -5
- package/lib/utils/ship/pe-launcher.d.ts +18 -0
- package/lib/utils/ship/pe-launcher.js +125 -4
- package/lib/utils/ship/plan.d.ts +1 -34
- package/lib/utils/ship/plan.js +7 -56
- package/lib/utils/ship/plist.d.ts +20 -10
- package/lib/utils/ship/plist.js +29 -12
- package/lib/utils/ship/settings.js +3 -2
- package/lib/utils/spawn.d.ts +13 -0
- package/lib/utils/spawn.js +10 -5
- package/lib/utils/watch-loop.d.ts +15 -1
- package/lib/utils/watch-loop.js +9 -5
- package/lib/utils/webext/build.d.ts +42 -0
- package/lib/utils/webext/build.js +120 -0
- package/lib/utils/webext/config.d.ts +72 -0
- package/lib/utils/webext/config.js +234 -0
- package/lib/utils/webext/icons.d.ts +24 -0
- package/lib/utils/webext/icons.js +83 -0
- package/lib/utils/webext/launch.d.ts +32 -0
- package/lib/utils/webext/launch.js +77 -0
- package/lib/utils/webext/manifest.d.ts +53 -0
- package/lib/utils/webext/manifest.js +219 -0
- package/lib/utils/webext/pages.d.ts +26 -0
- package/lib/utils/webext/pages.js +61 -0
- package/lib/utils/webext/targets.d.ts +13 -0
- package/lib/utils/webext/targets.js +36 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- package/package.json +20 -20
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read a cached tarball by SRI integrity — raw bytes on a
|
|
3
|
-
* read failure (e.g. a partial write from an interrupted run) is a MISS,
|
|
4
|
-
* file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
2
|
+
* Read a cached tarball by SRI integrity — raw bytes on a VERIFIED hit, `null`
|
|
3
|
+
* otherwise. A read failure (e.g. a partial write from an interrupted run) is a MISS,
|
|
4
|
+
* and the file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
5
|
+
*
|
|
6
|
+
* The bytes are re-hashed against the integrity that named them, through the SAME
|
|
7
|
+
* {@link verifyIntegrity} the download path uses — so both paths agree by
|
|
8
|
+
* construction instead of by comment.
|
|
9
|
+
*
|
|
10
|
+
* This used to trust the path: "content-addressed, so a hash hit is byte-identical to
|
|
11
|
+
* what the registry would return". That is a claim ABOUT the bytes, and nothing read
|
|
12
|
+
* them to check it. It held while the store was a local directory this machine had
|
|
13
|
+
* written itself. It stops holding the moment the store is a restorable CI artifact —
|
|
14
|
+
* a GitHub Actions cache entry that is truncated, corrupted, or written by a workflow
|
|
15
|
+
* on another branch would have been consumed as authentic, because the only thing
|
|
16
|
+
* asserting its contents was its filename. `gjsify-setup` now restores exactly such an
|
|
17
|
+
* artifact, so the check is no longer optional. Yarn re-hashes on every install for
|
|
18
|
+
* this reason (refs/yarn-berry Cache.ts, "we check the file hashes during each install
|
|
19
|
+
* anyway"); measured here, it costs ~0.5 s over this repo's ~420 MB closure, against
|
|
20
|
+
* an extract phase of minutes.
|
|
21
|
+
*
|
|
22
|
+
* A mismatch DELETES the entry rather than merely skipping it. That is not the prune
|
|
23
|
+
* this repo refuses elsewhere — the name asserts the content and the content
|
|
24
|
+
* disproves the name, so the file is garbage by its own definition. It has to go:
|
|
25
|
+
* {@link putCachedTarball} is idempotent on existence, so a corrupt blob left in place
|
|
26
|
+
* would never be replaced and every future install would re-download it forever.
|
|
5
27
|
*/
|
|
6
|
-
export declare function getCachedTarball(integrity: string | undefined): Uint8Array | null
|
|
28
|
+
export declare function getCachedTarball(integrity: string | undefined): Promise<Uint8Array | null>;
|
|
7
29
|
/**
|
|
8
30
|
* Persist a tarball to the cache, via a `<path>.tmp.<pid>` sibling and an atomic
|
|
9
31
|
* rename so concurrent installs never observe a half-written entry. No-op when
|
|
@@ -17,9 +39,15 @@ export declare function cacheRootForLogging(): string;
|
|
|
17
39
|
export declare function isCacheHit(integrity: string | undefined): boolean;
|
|
18
40
|
/**
|
|
19
41
|
* Read a tarball from npm's cacache content store by SRI integrity — raw `.tgz`
|
|
20
|
-
* bytes on a
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
42
|
+
* bytes on a VERIFIED hit, `null` on a MISS, disabled interop or read failure.
|
|
43
|
+
*
|
|
44
|
+
* Re-hashed like {@link getCachedTarball}, and with more reason: these bytes come
|
|
45
|
+
* from ANOTHER tool's store, on a path this code does not control and cannot assume
|
|
46
|
+
* was written by a cacache that verified anything. The old note argued cacache
|
|
47
|
+
* verified on write and a corrupt tarball would fail loudly in the extractor — but
|
|
48
|
+
* "the next stage will probably notice" is not verification, and the caller
|
|
49
|
+
* write-throughs a foreign hit into OUR store, which would launder an unchecked blob
|
|
50
|
+
* into a first-class entry. A mismatch is left alone here rather than deleted: this
|
|
51
|
+
* store belongs to npm, and removing another tool's files is not this code's call.
|
|
24
52
|
*/
|
|
25
|
-
export declare function getForeignCachedTarball(integrity: string | undefined): Uint8Array | null
|
|
53
|
+
export declare function getForeignCachedTarball(integrity: string | undefined): Promise<Uint8Array | null>;
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
// on too. Tarballs without an integrity hash (older registries) fall through to a
|
|
26
26
|
// no-op cache and download every time.
|
|
27
27
|
import { Buffer } from 'node:buffer';
|
|
28
|
-
import { existsSync, statSync } from 'node:fs';
|
|
28
|
+
import { existsSync, statSync, unlinkSync } from 'node:fs';
|
|
29
29
|
import { homedir } from 'node:os';
|
|
30
30
|
import { join } from 'node:path';
|
|
31
|
+
import { verifyIntegrity } from '@gjsify/npm-registry';
|
|
31
32
|
import { atomicWrite, gjsifyCacheRoot, readCacheFile } from './install-cache-fs.js';
|
|
32
33
|
const CACHE_LAYOUT_VERSION = 'v1';
|
|
33
34
|
/** Root of the tarball cache: `$XDG_CACHE_HOME/gjsify/tarballs/v1`. */
|
|
@@ -68,15 +69,50 @@ function pathFor(integrity) {
|
|
|
68
69
|
return join(cacheRoot(), sri.algorithm, shard, `${sri.hex}.tgz`);
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
|
-
* Read a cached tarball by SRI integrity — raw bytes on a
|
|
72
|
-
* read failure (e.g. a partial write from an interrupted run) is a MISS,
|
|
73
|
-
* file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
72
|
+
* Read a cached tarball by SRI integrity — raw bytes on a VERIFIED hit, `null`
|
|
73
|
+
* otherwise. A read failure (e.g. a partial write from an interrupted run) is a MISS,
|
|
74
|
+
* and the file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
75
|
+
*
|
|
76
|
+
* The bytes are re-hashed against the integrity that named them, through the SAME
|
|
77
|
+
* {@link verifyIntegrity} the download path uses — so both paths agree by
|
|
78
|
+
* construction instead of by comment.
|
|
79
|
+
*
|
|
80
|
+
* This used to trust the path: "content-addressed, so a hash hit is byte-identical to
|
|
81
|
+
* what the registry would return". That is a claim ABOUT the bytes, and nothing read
|
|
82
|
+
* them to check it. It held while the store was a local directory this machine had
|
|
83
|
+
* written itself. It stops holding the moment the store is a restorable CI artifact —
|
|
84
|
+
* a GitHub Actions cache entry that is truncated, corrupted, or written by a workflow
|
|
85
|
+
* on another branch would have been consumed as authentic, because the only thing
|
|
86
|
+
* asserting its contents was its filename. `gjsify-setup` now restores exactly such an
|
|
87
|
+
* artifact, so the check is no longer optional. Yarn re-hashes on every install for
|
|
88
|
+
* this reason (refs/yarn-berry Cache.ts, "we check the file hashes during each install
|
|
89
|
+
* anyway"); measured here, it costs ~0.5 s over this repo's ~420 MB closure, against
|
|
90
|
+
* an extract phase of minutes.
|
|
91
|
+
*
|
|
92
|
+
* A mismatch DELETES the entry rather than merely skipping it. That is not the prune
|
|
93
|
+
* this repo refuses elsewhere — the name asserts the content and the content
|
|
94
|
+
* disproves the name, so the file is garbage by its own definition. It has to go:
|
|
95
|
+
* {@link putCachedTarball} is idempotent on existence, so a corrupt blob left in place
|
|
96
|
+
* would never be replaced and every future install would re-download it forever.
|
|
74
97
|
*/
|
|
75
|
-
export function getCachedTarball(integrity) {
|
|
98
|
+
export async function getCachedTarball(integrity) {
|
|
76
99
|
const path = pathFor(integrity);
|
|
77
|
-
if (!path)
|
|
100
|
+
if (!path || !integrity)
|
|
101
|
+
return null;
|
|
102
|
+
const bytes = readCacheFile(path);
|
|
103
|
+
if (!bytes)
|
|
78
104
|
return null;
|
|
79
|
-
|
|
105
|
+
if (await verifyIntegrity(bytes, integrity))
|
|
106
|
+
return bytes;
|
|
107
|
+
// Best-effort: a read-only cache volume must not break the install, and the
|
|
108
|
+
// caller falls through to a verifying download either way.
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(path);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* keep going — the download path replaces the value we return as a MISS */
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
80
116
|
}
|
|
81
117
|
/**
|
|
82
118
|
* Persist a tarball to the cache, via a `<path>.tmp.<pid>` sibling and an atomic
|
|
@@ -158,14 +194,23 @@ function npmCachePathFor(integrity) {
|
|
|
158
194
|
}
|
|
159
195
|
/**
|
|
160
196
|
* Read a tarball from npm's cacache content store by SRI integrity — raw `.tgz`
|
|
161
|
-
* bytes on a
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
197
|
+
* bytes on a VERIFIED hit, `null` on a MISS, disabled interop or read failure.
|
|
198
|
+
*
|
|
199
|
+
* Re-hashed like {@link getCachedTarball}, and with more reason: these bytes come
|
|
200
|
+
* from ANOTHER tool's store, on a path this code does not control and cannot assume
|
|
201
|
+
* was written by a cacache that verified anything. The old note argued cacache
|
|
202
|
+
* verified on write and a corrupt tarball would fail loudly in the extractor — but
|
|
203
|
+
* "the next stage will probably notice" is not verification, and the caller
|
|
204
|
+
* write-throughs a foreign hit into OUR store, which would launder an unchecked blob
|
|
205
|
+
* into a first-class entry. A mismatch is left alone here rather than deleted: this
|
|
206
|
+
* store belongs to npm, and removing another tool's files is not this code's call.
|
|
165
207
|
*/
|
|
166
|
-
export function getForeignCachedTarball(integrity) {
|
|
208
|
+
export async function getForeignCachedTarball(integrity) {
|
|
167
209
|
const path = npmCachePathFor(integrity);
|
|
168
|
-
if (!path)
|
|
210
|
+
if (!path || !integrity)
|
|
211
|
+
return null;
|
|
212
|
+
const bytes = readCacheFile(path);
|
|
213
|
+
if (!bytes)
|
|
169
214
|
return null;
|
|
170
|
-
return
|
|
215
|
+
return (await verifyIntegrity(bytes, integrity)) ? bytes : null;
|
|
171
216
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Prettier parser napi oxfmt would format `fileName` with, or `null` when
|
|
3
|
+
* the native build formats it itself or no oxfmt formats it. `svelte` counts
|
|
4
|
+
* only when the config enables it, as in oxfmt.
|
|
5
|
+
*/
|
|
6
|
+
export declare function externalParserFor(fileName: string, svelteEnabled?: boolean): string | null;
|
|
7
|
+
export interface NativeSkipScanOptions {
|
|
8
|
+
/** The `--config` handed to oxfmt. Absent → discovery, as in oxfmt. */
|
|
9
|
+
configPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NativeSkipScanResult {
|
|
12
|
+
/** Files napi oxfmt would format that the native build skips. */
|
|
13
|
+
skipped: string[];
|
|
14
|
+
/** Files the walk hands to oxfmt's classifier. */
|
|
15
|
+
total: number;
|
|
16
|
+
}
|
|
17
|
+
/** Walk `paths` the way oxfmt does and report what the native build skips. */
|
|
18
|
+
export declare function scanForNativeSkips(paths: string[], cwd?: string, options?: NativeSkipScanOptions): NativeSkipScanResult;
|
|
19
|
+
/** JSON with comments and trailing commas; string contents are never touched. */
|
|
20
|
+
export declare function parseJsonc(text: string): unknown;
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// Scan for files the Node oxfmt would format but the native one (GJS) skips.
|
|
2
|
+
//
|
|
3
|
+
// `@gjsify/oxfmt-native` is oxfmt built WITHOUT its `napi` feature. That build
|
|
4
|
+
// formats JS/TS, JSON, TOML, GraphQL and CSS/SCSS/Less itself, but the formats
|
|
5
|
+
// oxfmt hands to its Prettier host (Markdown, HTML, Vue, YAML, …) are not even
|
|
6
|
+
// classified: oxfmt's walker drops them without a word. `gjsify format --check`
|
|
7
|
+
// under GJS would then exit 0 on a tree that the Node CLI finds unformatted.
|
|
8
|
+
//
|
|
9
|
+
// The answer is only honest when it is asked about the SAME file set oxfmt
|
|
10
|
+
// walks. The first version of this scan walked everything, `node_modules/`
|
|
11
|
+
// included, read no ignore file and flagged CSS the native build formats; on
|
|
12
|
+
// this repository `format --check` exited 1 on dependency READMEs without ever
|
|
13
|
+
// starting oxfmt. So the walk mirrors oxfmt's (refs/oxc
|
|
14
|
+
// `apps/oxfmt/src/cli/{walk,resolve}.rs`, `crates/oxc_config/src/walk.rs`):
|
|
15
|
+
//
|
|
16
|
+
// - VCS directories and `node_modules` are never entered;
|
|
17
|
+
// - `.prettierignore` in cwd and `!`-prefixed CLI paths block everywhere;
|
|
18
|
+
// - `.gitignore` (nested, plus parents up to the repository root) and
|
|
19
|
+
// `.git/info/exclude` apply to walked entries — only inside a repository
|
|
20
|
+
// when every walk root is in one, everywhere when not;
|
|
21
|
+
// - the config's `ignorePatterns` apply to every file, relative to the
|
|
22
|
+
// config's directory; with no explicit config a nested `.oxfmtrc.json(c)`
|
|
23
|
+
// scopes its subtree, like oxfmt's nested-config discovery;
|
|
24
|
+
// - a file named directly is not held to `.gitignore`, as in oxfmt;
|
|
25
|
+
// - symlinks are not followed.
|
|
26
|
+
//
|
|
27
|
+
// Only a file that survives all of that AND that napi oxfmt would route to its
|
|
28
|
+
// external formatter is reported.
|
|
29
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
30
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path';
|
|
31
|
+
import { compileGitignore, globToRegexSource, readGitignoreFile } from './gitignore.js';
|
|
32
|
+
const parsers = (parser, keys) => Object.fromEntries(keys.map((k) => [k, parser]));
|
|
33
|
+
// oxfmt `core/support.rs`: the `#[cfg(feature = "napi")]` sets behind
|
|
34
|
+
// `get_external_parser_name`. Matched case-sensitively, as oxfmt does.
|
|
35
|
+
const EXTERNAL_BY_EXTENSION = {
|
|
36
|
+
...parsers('yaml', ['yml', 'mir', 'reek', 'rviz', 'sublime-syntax', 'syntax', 'yaml', 'yaml-tmlanguage']),
|
|
37
|
+
...parsers('markdown', [
|
|
38
|
+
'md',
|
|
39
|
+
'livemd',
|
|
40
|
+
'markdown',
|
|
41
|
+
'mdown',
|
|
42
|
+
'mdwn',
|
|
43
|
+
'mkd',
|
|
44
|
+
'mkdn',
|
|
45
|
+
'mkdown',
|
|
46
|
+
'ronn',
|
|
47
|
+
'scd',
|
|
48
|
+
'workbook',
|
|
49
|
+
]),
|
|
50
|
+
...parsers('html', ['html', 'hta', 'htm', 'inc', 'xht', 'xhtml']),
|
|
51
|
+
...parsers('glimmer', ['handlebars', 'hbs']),
|
|
52
|
+
mdx: 'mdx',
|
|
53
|
+
vue: 'vue',
|
|
54
|
+
svelte: 'svelte',
|
|
55
|
+
mjml: 'mjml',
|
|
56
|
+
};
|
|
57
|
+
const EXTERNAL_BY_FILENAME = {
|
|
58
|
+
...parsers('yaml', ['.clang-format', '.clang-tidy', '.clangd', '.gemrc', 'CITATION.cff', 'glide.lock']),
|
|
59
|
+
...parsers('yaml', ['pixi.lock', '.prettierrc', '.stylelintrc', '.lintstagedrc']),
|
|
60
|
+
...parsers('markdown', ['contents.lr', 'README']),
|
|
61
|
+
};
|
|
62
|
+
// Machine-written files oxfmt refuses before classifying — `pnpm-lock.yaml`
|
|
63
|
+
// would otherwise look like YAML the native build skipped.
|
|
64
|
+
const EXCLUDE_FILENAMES = new Set([
|
|
65
|
+
'package-lock.json',
|
|
66
|
+
'pnpm-lock.yaml',
|
|
67
|
+
'yarn.lock',
|
|
68
|
+
'MODULE.bazel.lock',
|
|
69
|
+
'bun.lock',
|
|
70
|
+
'deno.lock',
|
|
71
|
+
'composer.lock',
|
|
72
|
+
'Package.resolved',
|
|
73
|
+
'Pipfile.lock',
|
|
74
|
+
'flake.lock',
|
|
75
|
+
'mcmod.info',
|
|
76
|
+
'Cargo.lock',
|
|
77
|
+
'Gopkg.lock',
|
|
78
|
+
'pdm.lock',
|
|
79
|
+
'poetry.lock',
|
|
80
|
+
'uv.lock',
|
|
81
|
+
]);
|
|
82
|
+
const VCS_DIRS = new Set(['.git', '.jj', '.sl', '.svn', '.hg']);
|
|
83
|
+
const CONFIG_NAMES = ['.oxfmtrc.json', '.oxfmtrc.jsonc'];
|
|
84
|
+
/**
|
|
85
|
+
* The Prettier parser napi oxfmt would format `fileName` with, or `null` when
|
|
86
|
+
* the native build formats it itself or no oxfmt formats it. `svelte` counts
|
|
87
|
+
* only when the config enables it, as in oxfmt.
|
|
88
|
+
*/
|
|
89
|
+
export function externalParserFor(fileName, svelteEnabled = false) {
|
|
90
|
+
if (EXCLUDE_FILENAMES.has(fileName))
|
|
91
|
+
return null;
|
|
92
|
+
const ext = extname(fileName).slice(1);
|
|
93
|
+
const parser = EXTERNAL_BY_FILENAME[fileName] ?? (ext ? EXTERNAL_BY_EXTENSION[ext] : undefined) ?? null;
|
|
94
|
+
if (parser === 'svelte' && !svelteEnabled)
|
|
95
|
+
return null;
|
|
96
|
+
return parser;
|
|
97
|
+
}
|
|
98
|
+
/** Walk `paths` the way oxfmt does and report what the native build skips. */
|
|
99
|
+
export function scanForNativeSkips(paths, cwd = process.cwd(), options = {}) {
|
|
100
|
+
const skipped = [];
|
|
101
|
+
let total = 0;
|
|
102
|
+
const excludes = paths.filter((p) => p.startsWith('!')).map((p) => p.slice(1));
|
|
103
|
+
const globals = [];
|
|
104
|
+
const prettierignore = readGitignoreFile(join(cwd, '.prettierignore'));
|
|
105
|
+
if (prettierignore)
|
|
106
|
+
globals.push(prettierignore);
|
|
107
|
+
if (excludes.length)
|
|
108
|
+
globals.push(compileGitignore(excludes, cwd));
|
|
109
|
+
const globallyIgnored = (p, isDir, anyParent) => globals.some((g) => (anyParent ? g.matchedPathOrAnyParents(p, isDir) : g.matched(p, isDir)) === 'ignore');
|
|
110
|
+
const explicitConfig = options.configPath ? resolve(cwd, options.configPath) : null;
|
|
111
|
+
const rootScope = loadScope(explicitConfig ?? discoverConfig(cwd));
|
|
112
|
+
const nested = explicitConfig === null;
|
|
113
|
+
const scopeFor = (dir) => (nested ? nearestScope(dir, rootScope) : rootScope);
|
|
114
|
+
// oxfmt matches CLI globs with fast_glob::glob_match, which expands `{a,b}`
|
|
115
|
+
// brace groups (nested too); globToRegexSource — the gitignore translator —
|
|
116
|
+
// escapes the braces instead, so a brace glob would match nothing. Expand
|
|
117
|
+
// them into alternatives first. Gitignore patterns never carry braces, so
|
|
118
|
+
// this stays a CLI-glob-only translation.
|
|
119
|
+
const expandBraces = (glob) => {
|
|
120
|
+
const m = /\{([^{}]*)\}/.exec(glob);
|
|
121
|
+
if (!m)
|
|
122
|
+
return [glob];
|
|
123
|
+
return m[1]
|
|
124
|
+
.split(',')
|
|
125
|
+
.flatMap((alt) => expandBraces(glob.slice(0, m.index) + alt + glob.slice(m.index + m[0].length)));
|
|
126
|
+
};
|
|
127
|
+
// An argument that names nothing on disk but carries glob characters is a
|
|
128
|
+
// pattern: oxfmt then walks cwd and keeps the files it matches.
|
|
129
|
+
const globs = [];
|
|
130
|
+
const targets = new Set();
|
|
131
|
+
for (const p of paths) {
|
|
132
|
+
if (p.startsWith('!'))
|
|
133
|
+
continue;
|
|
134
|
+
const abs = resolve(cwd, p);
|
|
135
|
+
if (/[*?[{]/.test(p) && !existsSync(abs)) {
|
|
136
|
+
for (const alt of expandBraces(p)) {
|
|
137
|
+
globs.push(new RegExp(`^${globToRegexSource(alt.includes('/') ? alt : `**/${alt}`)}$`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
targets.add(abs);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (globs.length || targets.size === 0)
|
|
145
|
+
targets.add(resolve(cwd));
|
|
146
|
+
const record = (file, scope) => {
|
|
147
|
+
if (scope?.ignore?.matchedPathOrAnyParents(file, false) === 'ignore')
|
|
148
|
+
return;
|
|
149
|
+
total++;
|
|
150
|
+
if (externalParserFor(basename(file), scope?.svelte))
|
|
151
|
+
skipped.push(file);
|
|
152
|
+
};
|
|
153
|
+
const walkRoots = [];
|
|
154
|
+
for (const target of targets) {
|
|
155
|
+
let st;
|
|
156
|
+
try {
|
|
157
|
+
st = statSync(target);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (globallyIgnored(target, st.isDirectory(), true))
|
|
163
|
+
continue;
|
|
164
|
+
if (st.isDirectory())
|
|
165
|
+
walkRoots.push(target);
|
|
166
|
+
else if (st.isFile())
|
|
167
|
+
record(target, scopeFor(dirname(target)));
|
|
168
|
+
}
|
|
169
|
+
const matchesGlob = (file) => {
|
|
170
|
+
if (!globs.length || targets.has(file))
|
|
171
|
+
return true;
|
|
172
|
+
const rel = relative(cwd, file).split(sep).join('/');
|
|
173
|
+
return globs.some((g) => g.test(rel));
|
|
174
|
+
};
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
const walk = (dir, inherited, scope) => {
|
|
177
|
+
if (seen.has(dir))
|
|
178
|
+
return;
|
|
179
|
+
seen.add(dir);
|
|
180
|
+
const own = readGitignoreFile(join(dir, '.gitignore'));
|
|
181
|
+
const stack = own ? [own, ...inherited] : inherited;
|
|
182
|
+
if (nested && dir !== scope?.dir)
|
|
183
|
+
scope = scopeAt(dir) ?? scope;
|
|
184
|
+
let entries;
|
|
185
|
+
try {
|
|
186
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
const full = join(dir, entry.name);
|
|
193
|
+
const isDir = entry.isDirectory();
|
|
194
|
+
if (!isDir && !entry.isFile())
|
|
195
|
+
continue;
|
|
196
|
+
if (isDir && (VCS_DIRS.has(entry.name) || entry.name === 'node_modules'))
|
|
197
|
+
continue;
|
|
198
|
+
if (globallyIgnored(full, isDir, false) || gitIgnored(stack, full, isDir))
|
|
199
|
+
continue;
|
|
200
|
+
if (isDir)
|
|
201
|
+
walk(full, stack, scope);
|
|
202
|
+
else if (matchesGlob(full))
|
|
203
|
+
record(full, scope);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const requireGit = walkRoots.every((r) => repoRootOf(r) !== null);
|
|
207
|
+
for (const root of walkRoots) {
|
|
208
|
+
walk(root, parentGitignores(root, requireGit ? repoRootOf(root) : null, requireGit), scopeFor(root));
|
|
209
|
+
}
|
|
210
|
+
return { skipped, total };
|
|
211
|
+
}
|
|
212
|
+
/** Nearest-first: the first matcher that decides wins, as in the `ignore` crate. */
|
|
213
|
+
function gitIgnored(stack, path, isDir) {
|
|
214
|
+
for (const m of stack) {
|
|
215
|
+
const hit = m.matched(path, isDir);
|
|
216
|
+
if (hit !== null)
|
|
217
|
+
return hit === 'ignore';
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The `.gitignore` files ABOVE a walk root (the walk reads the root's own),
|
|
223
|
+
* nearest first, then `.git/info/exclude`, which ranks below them. Inside a
|
|
224
|
+
* repository the lookup stops at its root, outside one it runs to `/`.
|
|
225
|
+
*/
|
|
226
|
+
function parentGitignores(root, repo, requireGit) {
|
|
227
|
+
if (requireGit && repo === null)
|
|
228
|
+
return [];
|
|
229
|
+
const out = [];
|
|
230
|
+
for (let dir = root; dir !== repo;) {
|
|
231
|
+
const parent = dirname(dir);
|
|
232
|
+
if (parent === dir)
|
|
233
|
+
break;
|
|
234
|
+
dir = parent;
|
|
235
|
+
const m = readGitignoreFile(join(dir, '.gitignore'));
|
|
236
|
+
if (m)
|
|
237
|
+
out.push(m);
|
|
238
|
+
}
|
|
239
|
+
const exclude = repo ? readGitignoreFile(join(repo, '.git', 'info', 'exclude'), repo) : null;
|
|
240
|
+
if (exclude)
|
|
241
|
+
out.push(exclude);
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
function repoRootOf(start) {
|
|
245
|
+
for (let dir = start;; dir = dirname(dir)) {
|
|
246
|
+
if (existsSync(join(dir, '.git')) || existsSync(join(dir, '.jj')))
|
|
247
|
+
return dir;
|
|
248
|
+
if (dirname(dir) === dir)
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function configIn(dir) {
|
|
253
|
+
for (const name of CONFIG_NAMES) {
|
|
254
|
+
const p = join(dir, name);
|
|
255
|
+
if (existsSync(p))
|
|
256
|
+
return p;
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
function discoverConfig(cwd) {
|
|
261
|
+
for (let dir = resolve(cwd);; dir = dirname(dir)) {
|
|
262
|
+
const found = configIn(dir);
|
|
263
|
+
if (found)
|
|
264
|
+
return found;
|
|
265
|
+
if (dirname(dir) === dir)
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function scopeAt(dir) {
|
|
270
|
+
const cfg = configIn(dir);
|
|
271
|
+
return cfg ? loadScope(cfg) : null;
|
|
272
|
+
}
|
|
273
|
+
/** The scope of the nearest config at or above `dir`. */
|
|
274
|
+
function nearestScope(dir, fallback) {
|
|
275
|
+
for (;; dir = dirname(dir)) {
|
|
276
|
+
if (fallback && dir === fallback.dir)
|
|
277
|
+
return fallback;
|
|
278
|
+
const scope = scopeAt(dir);
|
|
279
|
+
if (scope)
|
|
280
|
+
return scope;
|
|
281
|
+
if (dirname(dir) === dir)
|
|
282
|
+
return fallback;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* A config's `ignorePatterns`, and whether it enables Svelte. A config that
|
|
287
|
+
* does not parse contributes no patterns — more files reported, never fewer;
|
|
288
|
+
* oxfmt rejects that config anyway.
|
|
289
|
+
*/
|
|
290
|
+
function loadScope(configPath) {
|
|
291
|
+
if (!configPath)
|
|
292
|
+
return null;
|
|
293
|
+
const dir = dirname(configPath);
|
|
294
|
+
let raw = {};
|
|
295
|
+
try {
|
|
296
|
+
raw = parseJsonc(readFileSync(configPath, 'utf-8'));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
raw = {};
|
|
300
|
+
}
|
|
301
|
+
const patterns = Array.isArray(raw.ignorePatterns)
|
|
302
|
+
? raw.ignorePatterns.filter((p) => typeof p === 'string')
|
|
303
|
+
: [];
|
|
304
|
+
return {
|
|
305
|
+
dir,
|
|
306
|
+
ignore: patterns.length ? compileGitignore(patterns, dir) : null,
|
|
307
|
+
svelte: raw.svelte !== undefined && raw.svelte !== null && raw.svelte !== false,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** JSON with comments and trailing commas; string contents are never touched. */
|
|
311
|
+
export function parseJsonc(text) {
|
|
312
|
+
const pass = (src, drop) => {
|
|
313
|
+
let out = '';
|
|
314
|
+
for (let i = 0; i < src.length;) {
|
|
315
|
+
if (src[i] === '"') {
|
|
316
|
+
let j = i + 1;
|
|
317
|
+
while (j < src.length && src[j] !== '"')
|
|
318
|
+
j += src[j] === '\\' ? 2 : 1;
|
|
319
|
+
out += src.slice(i, j + 1);
|
|
320
|
+
i = j + 1;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const skip = drop(src, i);
|
|
324
|
+
if (skip > 0)
|
|
325
|
+
i += skip;
|
|
326
|
+
else
|
|
327
|
+
out += src[i++];
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
};
|
|
331
|
+
const uncommented = pass(text, (src, i) => {
|
|
332
|
+
if (src.startsWith('//', i)) {
|
|
333
|
+
const nl = src.indexOf('\n', i);
|
|
334
|
+
return (nl === -1 ? src.length : nl) - i;
|
|
335
|
+
}
|
|
336
|
+
if (src.startsWith('/*', i)) {
|
|
337
|
+
const end = src.indexOf('*/', i + 2);
|
|
338
|
+
return (end === -1 ? src.length : end + 2) - i;
|
|
339
|
+
}
|
|
340
|
+
return 0;
|
|
341
|
+
});
|
|
342
|
+
return JSON.parse(pass(uncommented, (src, i) => (src[i] === ',' && /^\s*[}\]]/.test(src.slice(i + 1)) ? 1 : 0)));
|
|
343
|
+
}
|
package/lib/utils/node-script.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// has no `node:` URI scheme and no node_modules walker, so `gjs -m <script>.mjs` dies on the
|
|
7
7
|
// first import. That gap is why a Node-less host could install gjsify, resolve every native
|
|
8
8
|
// prebuild, and then fail `build:infra` at `node scripts/process-template.mjs` (see
|
|
9
|
-
// `status/open-todos.md`).
|
|
9
|
+
// `status/open-todos/README.md`).
|
|
10
10
|
//
|
|
11
11
|
// Rejected alternative: commit a `dist/*.gjs.mjs` per script — one more artifact each under
|
|
12
12
|
// the committed-bundle freshness gate, scaling with the number of scripts. This is ONE
|
|
@@ -130,6 +130,17 @@ export function mergeBundlerOptions(base, overrides) {
|
|
|
130
130
|
if (base.transform?.define || overrides.transform?.define) {
|
|
131
131
|
out.transform.define = { ...base.transform?.define, ...overrides.transform?.define };
|
|
132
132
|
}
|
|
133
|
+
// `inject` is a map like `define` and merges the same way. Replacing it wholesale
|
|
134
|
+
// meant a user who injected ONE identifier silently lost the orchestrator's
|
|
135
|
+
// `console` → `@gjsify/rolldown-plugin-gjsify/shims/console-gjs` rewrite, which
|
|
136
|
+
// `--app gjs` cannot express any other way: GJS defines `globalThis.console`
|
|
137
|
+
// non-writable and non-configurable, so assigning to it from a register module
|
|
138
|
+
// no-ops. It also has to stay in lockstep with `mergeTransformOptions` in the
|
|
139
|
+
// plugin's auto-globals analyser — when the two merge the same options
|
|
140
|
+
// differently, the detector measures a bundle that is not the one that ships.
|
|
141
|
+
if (base.transform?.inject || overrides.transform?.inject) {
|
|
142
|
+
out.transform.inject = { ...base.transform?.inject, ...overrides.transform?.inject };
|
|
143
|
+
}
|
|
133
144
|
}
|
|
134
145
|
if (base.resolve || overrides.resolve) {
|
|
135
146
|
out.resolve = { ...base.resolve, ...overrides.resolve };
|
package/lib/utils/oxc-resolve.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
// `gjsify/register-class-order` rule wired via `.oxlintrc.json` jsPlugins)
|
|
46
46
|
// lives in the Node launcher, so a native oxlint bridge could only run the
|
|
47
47
|
// Rust rule subset. Lint stays Node-spawned until that trade-off is decided
|
|
48
|
-
// (tracked in status/open-todos.md).
|
|
48
|
+
// (tracked in status/open-todos/README.md).
|
|
49
49
|
import { existsSync, readFileSync } from 'node:fs';
|
|
50
50
|
import { join, resolve } from 'node:path';
|
|
51
51
|
import { createRequire } from 'node:module';
|
|
@@ -233,6 +233,8 @@ function findConfigFile(cwd, names) {
|
|
|
233
233
|
return null;
|
|
234
234
|
}
|
|
235
235
|
let _nativeOxfmtProbe = null;
|
|
236
|
+
/** Why the bridge's library would not open, when `tryLoadNativeOxfmt()` measured it. */
|
|
237
|
+
let _nativeOxfmtLoadError = null;
|
|
236
238
|
/**
|
|
237
239
|
* Try to load `@gjsify/oxfmt-native` (GJS only). Same multi-anchor
|
|
238
240
|
* resolution as `bundler-pick.ts`'s `tryLoadNative()` — GJS's native ESM
|
|
@@ -257,6 +259,34 @@ async function tryLoadNativeOxfmt() {
|
|
|
257
259
|
const mod = (await import(/* @vite-ignore */ target));
|
|
258
260
|
if (!mod.hasNativeOxfmt())
|
|
259
261
|
return null;
|
|
262
|
+
// The typelib resolved; its library opens at the first class access.
|
|
263
|
+
// Open it now, beside that typelib — the wrapper cannot, for the reason
|
|
264
|
+
// `bundler-pick.ts`'s `tryLoadNative()` gives — so a library that will
|
|
265
|
+
// not load names itself instead of failing inside `runOxfmt()`.
|
|
266
|
+
// Resolved and imported, not statically imported — the reasoning is
|
|
267
|
+
// at the top of this file. By the time a formatter asks for the
|
|
268
|
+
// native backend, utils' `lib/esm` is built. `./native-library`
|
|
269
|
+
// rather than `./core`: `core` re-exports `main-loop`, whose
|
|
270
|
+
// module-level singleton would then exist twice in a process that
|
|
271
|
+
// already carries it inlined in the GJS bundle.
|
|
272
|
+
//
|
|
273
|
+
// Its own `try` because the outer one cannot tell this apart from
|
|
274
|
+
// "there is no native backend", and `_nativeOxfmtLoadError` is the
|
|
275
|
+
// one place the npm fallback reports why it took over.
|
|
276
|
+
let openNativeLibrary;
|
|
277
|
+
try {
|
|
278
|
+
const utilsResolved = resolveNpmPackage('@gjsify/utils/native-library', { bundleUrl: import.meta.url }) ??
|
|
279
|
+
createRequire(import.meta.url).resolve('@gjsify/utils/native-library');
|
|
280
|
+
const utilsHref = pathToFileURL(utilsResolved).href;
|
|
281
|
+
({ openNativeLibrary } = (await import(/* @vite-ignore */ utilsHref)));
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
_nativeOxfmtLoadError = err instanceof Error ? err : new Error(String(err));
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
_nativeOxfmtLoadError = openNativeLibrary('GjsifyOxfmt');
|
|
288
|
+
if (_nativeOxfmtLoadError)
|
|
289
|
+
return null;
|
|
260
290
|
return mod;
|
|
261
291
|
}
|
|
262
292
|
catch {
|
|
@@ -281,13 +311,22 @@ export async function shouldUseNativeOxfmt() {
|
|
|
281
311
|
if (choice === 'native') {
|
|
282
312
|
const native = await tryLoadNativeOxfmt();
|
|
283
313
|
if (!native) {
|
|
284
|
-
throw new Error('GJSIFY_OXFMT=native but @gjsify/oxfmt-native is not loadable (no prebuild for this architecture, or not running under GJS).'
|
|
314
|
+
throw new Error('GJSIFY_OXFMT=native but @gjsify/oxfmt-native is not loadable (no prebuild for this architecture, or not running under GJS).' +
|
|
315
|
+
(_nativeOxfmtLoadError ? `\n${_nativeOxfmtLoadError.message}` : ''));
|
|
285
316
|
}
|
|
286
317
|
return true;
|
|
287
318
|
}
|
|
288
319
|
if (!isGjs())
|
|
289
320
|
return false;
|
|
290
|
-
|
|
321
|
+
const native = (await tryLoadNativeOxfmt()) !== null;
|
|
322
|
+
// Falling back to the npm launcher is not an error, so the default path is
|
|
323
|
+
// quiet — but a MEASURED reason why the native bridge would not load is the
|
|
324
|
+
// only place the user hears it, and it is the whole point of measuring.
|
|
325
|
+
// `GJSIFY_OXFMT=native` above throws with this text; the default earns the
|
|
326
|
+
// same answer for free.
|
|
327
|
+
if (!native && _nativeOxfmtLoadError)
|
|
328
|
+
console.debug(_nativeOxfmtLoadError.message);
|
|
329
|
+
return native;
|
|
291
330
|
}
|
|
292
331
|
/**
|
|
293
332
|
* Run an oxc tool (oxlint / oxfmt).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is `pid` a live process on this machine?
|
|
3
|
+
*
|
|
4
|
+
* `/proc/<pid>` where procfs exists (one stat, no subprocess, identical under
|
|
5
|
+
* Node and GJS); elsewhere Node's signal-0 probe — `ESRCH` dead, `EPERM`
|
|
6
|
+
* alive-but-foreign. Any OTHER outcome counts as ALIVE: callers use this to
|
|
7
|
+
* decide whether something may be taken away from its owner, and "cannot tell"
|
|
8
|
+
* must never read as "gone".
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Parse `ps -A -o pid=,ppid=` output (two integer columns, blank headers) into
|
|
13
|
+
* ppid → children. Exported for the unit test; the format is POSIX, so procps
|
|
14
|
+
* on Linux and the BSD `ps` on macOS print the same shape.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parsePsPidPpid(output: string): Map<number, number[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Every transitive descendant of `pid`, breadth-first (reverse the list for
|
|
19
|
+
* deepest-first signalling). A snapshot — processes spawned after it are not in
|
|
20
|
+
* it, which is why `foreach` re-walks before escalating to SIGKILL.
|
|
21
|
+
*/
|
|
22
|
+
export declare function collectDescendants(pid: number): number[];
|