@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12
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 +112 -10
- package/dist/archive/archive.d.ts +129 -0
- package/dist/archive/archive.d.ts.map +1 -0
- package/dist/archive/archive.js +223 -0
- package/dist/archive/archive.js.map +1 -0
- package/dist/archive/entries.d.ts +132 -0
- package/dist/archive/entries.d.ts.map +1 -0
- package/dist/archive/entries.js +223 -0
- package/dist/archive/entries.js.map +1 -0
- package/dist/archive/limits.d.ts +39 -0
- package/dist/archive/limits.d.ts.map +1 -0
- package/dist/archive/limits.js +59 -0
- package/dist/archive/limits.js.map +1 -0
- package/dist/archive/records.d.ts +14 -0
- package/dist/archive/records.d.ts.map +1 -0
- package/dist/archive/records.js +3 -0
- package/dist/archive/records.js.map +1 -0
- package/dist/archive/tar-reader.d.ts +18 -0
- package/dist/archive/tar-reader.d.ts.map +1 -0
- package/dist/archive/tar-reader.js +173 -0
- package/dist/archive/tar-reader.js.map +1 -0
- package/dist/archive/zip-reader.d.ts +76 -0
- package/dist/archive/zip-reader.d.ts.map +1 -0
- package/dist/archive/zip-reader.js +376 -0
- package/dist/archive/zip-reader.js.map +1 -0
- package/dist/archive.d.ts +14 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +12 -0
- package/dist/archive.js.map +1 -0
- package/dist/asset-reference.d.ts +17 -1
- package/dist/asset-reference.d.ts.map +1 -1
- package/dist/asset-reference.js +31 -7
- package/dist/asset-reference.js.map +1 -1
- package/dist/dirent-kind.d.ts +13 -40
- package/dist/dirent-kind.d.ts.map +1 -1
- package/dist/dirent-kind.js +21 -69
- package/dist/dirent-kind.js.map +1 -1
- package/dist/entrypoint.d.ts +2 -2
- package/dist/entrypoint.d.ts.map +1 -1
- package/dist/entrypoint.js +4 -3
- package/dist/entrypoint.js.map +1 -1
- package/dist/env-flag.d.ts +13 -5
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +13 -5
- package/dist/env-flag.js.map +1 -1
- package/dist/errors/confirmed-absent.d.ts +41 -0
- package/dist/errors/confirmed-absent.d.ts.map +1 -0
- package/dist/errors/confirmed-absent.js +43 -0
- package/dist/errors/confirmed-absent.js.map +1 -0
- package/dist/errors/errno-table.d.ts +177 -0
- package/dist/errors/errno-table.d.ts.map +1 -0
- package/dist/errors/errno-table.js +343 -0
- package/dist/errors/errno-table.js.map +1 -0
- package/dist/errors/fs-boundary.d.ts +36 -0
- package/dist/errors/fs-boundary.d.ts.map +1 -0
- package/dist/errors/fs-boundary.js +121 -0
- package/dist/errors/fs-boundary.js.map +1 -0
- package/dist/errors/fs-fault.d.ts +176 -0
- package/dist/errors/fs-fault.d.ts.map +1 -0
- package/dist/errors/fs-fault.js +215 -0
- package/dist/errors/fs-fault.js.map +1 -0
- package/dist/errors/git-errno.d.ts +13 -0
- package/dist/errors/git-errno.d.ts.map +1 -0
- package/dist/errors/git-errno.js +45 -0
- package/dist/errors/git-errno.js.map +1 -0
- package/dist/errors/path-present.d.ts +72 -0
- package/dist/errors/path-present.d.ts.map +1 -0
- package/dist/errors/path-present.js +110 -0
- package/dist/errors/path-present.js.map +1 -0
- package/dist/errors/rag-index-empty.d.ts +16 -0
- package/dist/errors/rag-index-empty.d.ts.map +1 -0
- package/dist/errors/rag-index-empty.js +16 -0
- package/dist/errors/rag-index-empty.js.map +1 -0
- package/dist/errors/suppressed-faults.d.ts +34 -0
- package/dist/errors/suppressed-faults.d.ts.map +1 -0
- package/dist/errors/suppressed-faults.js +66 -0
- package/dist/errors/suppressed-faults.js.map +1 -0
- package/dist/file-crawler.d.ts +13 -18
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +140 -24
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +75 -50
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +148 -158
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -4
- package/dist/fs.js.map +1 -1
- package/dist/git-snapshot.d.ts +25 -17
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +65 -11
- package/dist/git-snapshot.js.map +1 -1
- package/dist/git-tracker.d.ts +11 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +29 -6
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +7 -35
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +5 -4
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/in-order.d.ts +93 -0
- package/dist/in-order.d.ts.map +1 -0
- package/dist/in-order.js +165 -0
- package/dist/in-order.js.map +1 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/kill-process-tree.d.ts +43 -0
- package/dist/kill-process-tree.d.ts.map +1 -0
- package/dist/kill-process-tree.js +48 -0
- package/dist/kill-process-tree.js.map +1 -0
- package/dist/link-following.d.ts +115 -0
- package/dist/link-following.d.ts.map +1 -0
- package/dist/link-following.js +283 -0
- package/dist/link-following.js.map +1 -0
- package/dist/listing-refusal.d.ts +44 -0
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +79 -2
- package/dist/listing-refusal.js.map +1 -1
- package/dist/name-fold.d.ts +25 -0
- package/dist/name-fold.d.ts.map +1 -0
- package/dist/name-fold.js +40 -0
- package/dist/name-fold.js.map +1 -0
- package/dist/path-containment.d.ts +17 -0
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +28 -5
- package/dist/path-containment.js.map +1 -1
- package/dist/path-utils.d.ts +14 -17
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +37 -55
- package/dist/path-utils.js.map +1 -1
- package/dist/process.d.ts +2 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +2 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +8 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +6 -2
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +1 -1
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts.map +1 -1
- package/dist/skill-targets.js +3 -1
- package/dist/skill-targets.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +0 -15
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +11 -50
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/special-file.d.ts +18 -0
- package/dist/special-file.d.ts.map +1 -0
- package/dist/special-file.js +21 -0
- package/dist/special-file.js.map +1 -0
- package/dist/terminating-signals.d.ts +129 -0
- package/dist/terminating-signals.d.ts.map +1 -0
- package/dist/terminating-signals.js +210 -0
- package/dist/terminating-signals.js.map +1 -0
- package/dist/test-helpers.d.ts +9 -5
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +18 -21
- package/dist/test-helpers.js.map +1 -1
- package/dist/test-scratch-guard.d.ts +34 -0
- package/dist/test-scratch-guard.d.ts.map +1 -0
- package/dist/test-scratch-guard.js +45 -0
- package/dist/test-scratch-guard.js.map +1 -0
- package/dist/testing/archive-fixtures.d.ts +147 -0
- package/dist/testing/archive-fixtures.d.ts.map +1 -0
- package/dist/testing/archive-fixtures.js +449 -0
- package/dist/testing/archive-fixtures.js.map +1 -0
- package/dist/testing/executables.d.ts +6 -0
- package/dist/testing/executables.d.ts.map +1 -1
- package/dist/testing/executables.js +20 -9
- package/dist/testing/executables.js.map +1 -1
- package/dist/testing/fault-fs-preload.d.ts +16 -0
- package/dist/testing/fault-fs-preload.d.ts.map +1 -0
- package/dist/testing/fault-fs-preload.js +22 -0
- package/dist/testing/fault-fs-preload.js.map +1 -0
- package/dist/testing/fault-fs.d.ts +115 -0
- package/dist/testing/fault-fs.d.ts.map +1 -0
- package/dist/testing/fault-fs.js +411 -0
- package/dist/testing/fault-fs.js.map +1 -0
- package/dist/testing/fault-spec.d.ts +83 -0
- package/dist/testing/fault-spec.d.ts.map +1 -0
- package/dist/testing/fault-spec.js +132 -0
- package/dist/testing/fault-spec.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -1
- package/dist/testing/hostile-tree.js +3 -4
- package/dist/testing/hostile-tree.js.map +1 -1
- package/dist/testing/platform-gates.d.ts +8 -0
- package/dist/testing/platform-gates.d.ts.map +1 -1
- package/dist/testing/platform-gates.js +24 -0
- package/dist/testing/platform-gates.js.map +1 -1
- package/dist/testing/temp-dir.d.ts +31 -0
- package/dist/testing/temp-dir.d.ts.map +1 -1
- package/dist/testing/temp-dir.js +63 -0
- package/dist/testing/temp-dir.js.map +1 -1
- package/dist/testing/tier-timeout.d.ts +34 -0
- package/dist/testing/tier-timeout.d.ts.map +1 -0
- package/dist/testing/tier-timeout.js +45 -0
- package/dist/testing/tier-timeout.js.map +1 -0
- package/dist/testing/tree-snapshot.d.ts +71 -0
- package/dist/testing/tree-snapshot.d.ts.map +1 -0
- package/dist/testing/tree-snapshot.js +163 -0
- package/dist/testing/tree-snapshot.js.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +8 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +29 -0
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +64 -0
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts +58 -6
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +111 -11
- package/dist/text-file.js.map +1 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +1 -1
- package/dist/text.js.map +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +2 -10
- package/dist/timing-dump.js.map +1 -1
- package/dist/tree-change/apply-decisions.d.ts +101 -0
- package/dist/tree-change/apply-decisions.d.ts.map +1 -0
- package/dist/tree-change/apply-decisions.js +139 -0
- package/dist/tree-change/apply-decisions.js.map +1 -0
- package/dist/tree-change/apply.d.ts +98 -0
- package/dist/tree-change/apply.d.ts.map +1 -0
- package/dist/tree-change/apply.js +364 -0
- package/dist/tree-change/apply.js.map +1 -0
- package/dist/tree-change/copy-decisions.d.ts +58 -0
- package/dist/tree-change/copy-decisions.d.ts.map +1 -0
- package/dist/tree-change/copy-decisions.js +66 -0
- package/dist/tree-change/copy-decisions.js.map +1 -0
- package/dist/tree-change/copy-tree.d.ts +125 -0
- package/dist/tree-change/copy-tree.d.ts.map +1 -0
- package/dist/tree-change/copy-tree.js +279 -0
- package/dist/tree-change/copy-tree.js.map +1 -0
- package/dist/tree-change/files.d.ts +167 -0
- package/dist/tree-change/files.d.ts.map +1 -0
- package/dist/tree-change/files.js +423 -0
- package/dist/tree-change/files.js.map +1 -0
- package/dist/tree-change/followed-walk.d.ts +54 -0
- package/dist/tree-change/followed-walk.d.ts.map +1 -0
- package/dist/tree-change/followed-walk.js +81 -0
- package/dist/tree-change/followed-walk.js.map +1 -0
- package/dist/tree-change/host-chain.d.ts +48 -0
- package/dist/tree-change/host-chain.d.ts.map +1 -0
- package/dist/tree-change/host-chain.js +85 -0
- package/dist/tree-change/host-chain.js.map +1 -0
- package/dist/tree-change/identity-compare.d.ts +61 -0
- package/dist/tree-change/identity-compare.d.ts.map +1 -0
- package/dist/tree-change/identity-compare.js +115 -0
- package/dist/tree-change/identity-compare.js.map +1 -0
- package/dist/tree-change/identity.d.ts +62 -0
- package/dist/tree-change/identity.d.ts.map +1 -0
- package/dist/tree-change/identity.js +152 -0
- package/dist/tree-change/identity.js.map +1 -0
- package/dist/tree-change/index.d.ts +24 -0
- package/dist/tree-change/index.d.ts.map +1 -0
- package/dist/tree-change/index.js +14 -0
- package/dist/tree-change/index.js.map +1 -0
- package/dist/tree-change/link-chain.d.ts +180 -0
- package/dist/tree-change/link-chain.d.ts.map +1 -0
- package/dist/tree-change/link-chain.js +377 -0
- package/dist/tree-change/link-chain.js.map +1 -0
- package/dist/tree-change/link-containment.d.ts +105 -0
- package/dist/tree-change/link-containment.d.ts.map +1 -0
- package/dist/tree-change/link-containment.js +196 -0
- package/dist/tree-change/link-containment.js.map +1 -0
- package/dist/tree-change/link-walk.d.ts +49 -0
- package/dist/tree-change/link-walk.d.ts.map +1 -0
- package/dist/tree-change/link-walk.js +69 -0
- package/dist/tree-change/link-walk.js.map +1 -0
- package/dist/tree-change/plan.d.ts +206 -0
- package/dist/tree-change/plan.d.ts.map +1 -0
- package/dist/tree-change/plan.js +320 -0
- package/dist/tree-change/plan.js.map +1 -0
- package/dist/tree-change/readable-tree.d.ts +44 -0
- package/dist/tree-change/readable-tree.d.ts.map +1 -0
- package/dist/tree-change/readable-tree.js +52 -0
- package/dist/tree-change/readable-tree.js.map +1 -0
- package/dist/tree-change/rollback-error.d.ts +29 -0
- package/dist/tree-change/rollback-error.d.ts.map +1 -0
- package/dist/tree-change/rollback-error.js +31 -0
- package/dist/tree-change/rollback-error.js.map +1 -0
- package/dist/tree-change/staging-names.d.ts +27 -0
- package/dist/tree-change/staging-names.d.ts.map +1 -0
- package/dist/tree-change/staging-names.js +40 -0
- package/dist/tree-change/staging-names.js.map +1 -0
- package/dist/tree-change/tree-walk.d.ts +81 -0
- package/dist/tree-change/tree-walk.d.ts.map +1 -0
- package/dist/tree-change/tree-walk.js +212 -0
- package/dist/tree-change/tree-walk.js.map +1 -0
- package/dist/win32-namespace.d.ts +24 -0
- package/dist/win32-namespace.d.ts.map +1 -0
- package/dist/win32-namespace.js +31 -0
- package/dist/win32-namespace.js.map +1 -0
- package/dist/yaml/surgical-yaml.d.ts +12 -3
- package/dist/yaml/surgical-yaml.d.ts.map +1 -1
- package/dist/yaml/surgical-yaml.js +16 -6
- package/dist/yaml/surgical-yaml.js.map +1 -1
- package/eslint/README.md +12 -4
- package/eslint/rules/fs-binding-scan.cjs +278 -0
- package/eslint/rules/no-adhoc-errno.cjs +364 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
- package/eslint/rules/no-blind-catch.cjs +1 -1
- package/eslint/rules/no-destructive-fs.cjs +359 -0
- package/eslint/rules/no-existssync.cjs +70 -0
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
- package/eslint/rules/no-link-following-fs.cjs +68 -0
- package/eslint/rules/no-literal-process-exit.cjs +96 -8
- package/eslint/rules/no-literal-test-timeout.cjs +214 -0
- package/eslint/rules/no-raw-signal-listener.cjs +224 -0
- package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
- package/package.json +10 -2
- package/dist/errors/errno.d.ts +0 -51
- package/dist/errors/errno.d.ts.map +0 -1
- package/dist/errors/errno.js +0 -112
- package/dist/errors/errno.js.map +0 -1
- package/eslint/rules/no-fs-promises-cp.cjs +0 -43
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-targets.js","sourceRoot":"","sources":["../src/skill-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"skill-targets.js","sourceRoot":"","sources":["../src/skill-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ;IACR,OAAO;IACP,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;CACA,CAAC;AAIX,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,SAAS,CAAU,CAAC;AAW9D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,aAAa,GAAoD;IAC5E,MAAM,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE;IACnE,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,kBAAkB,EAAE;IACtE,OAAO,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,gBAAgB,EAAE;IACrE,MAAM,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE;IACnE,MAAM,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE;IACnE,QAAQ,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE,UAAU,EAAE,kBAAkB,EAAE;IACjF,MAAM,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,kBAAkB,EAAE;CACxE,CAAC;AAEF,SAAS,aAAa,CAAC,KAAa;IAClC,OAAQ,kBAAwC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAmB,EACnB,KAAiB,EACjB,GAAW;IAEX,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,mBAAmB,MAAM,CAAC,MAAM,CAAC,qBAAqB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACtF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,kBAAkB,MAAM,CAAC,KAAK,CAAC,oBAAoB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,kGAAkG;IAClG,MAAM,IAAI,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,qCAAqC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACpH,MAAM,GAAG,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;IAChE,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -46,21 +46,6 @@ export interface SpawnResult {
|
|
|
46
46
|
timedOut: boolean;
|
|
47
47
|
stalled: boolean;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* SIGKILL the child AND every process in its group. The child is spawned
|
|
51
|
-
* `detached` (POSIX) so it leads a new process group; killing the negated pid
|
|
52
|
-
* reaps backgrounded grandchildren (e.g. a skill that runs `nohup … &`) and
|
|
53
|
-
* orphaned MCP subprocesses that a direct `child.kill()` would leave running.
|
|
54
|
-
*
|
|
55
|
-
* - Guards an undefined pid (child never spawned — nothing to kill).
|
|
56
|
-
* - Absorbs the throw that means the group is already gone (see
|
|
57
|
-
* {@link isProcessGroupGone}); anything else stays loud.
|
|
58
|
-
* - Windows has no POSIX process groups: fall back to `taskkill /T /F`, which
|
|
59
|
-
* terminates the whole process tree.
|
|
60
|
-
*/
|
|
61
|
-
export declare function killProcessTree(child: {
|
|
62
|
-
pid?: number | undefined;
|
|
63
|
-
}): void;
|
|
64
49
|
/**
|
|
65
50
|
* SIGKILL every currently in-flight `claude` child (and its process tree, via
|
|
66
51
|
* {@link killProcessTree}) and clear the registry. Idempotent — safe to call
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAoBlE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AA4BD;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CA8G1F"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { killProcessTree } from '../kill-process-tree.js';
|
|
3
3
|
import { spawnHardened } from '../spawn-hardened.js';
|
|
4
4
|
/**
|
|
5
5
|
* Pure assembly of the headless `claude -p` argv (spec §9). The prompt is never
|
|
@@ -61,51 +61,12 @@ export function assembleClaudeArgs(opts) {
|
|
|
61
61
|
return args;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* - Guards an undefined pid (child never spawned — nothing to kill).
|
|
70
|
-
* - Absorbs the throw that means the group is already gone (see
|
|
71
|
-
* {@link isProcessGroupGone}); anything else stays loud.
|
|
72
|
-
* - Windows has no POSIX process groups: fall back to `taskkill /T /F`, which
|
|
73
|
-
* terminates the whole process tree.
|
|
74
|
-
*/
|
|
75
|
-
export function killProcessTree(child) {
|
|
76
|
-
const { pid } = child;
|
|
77
|
-
if (pid === undefined)
|
|
78
|
-
return;
|
|
79
|
-
try {
|
|
80
|
-
if (process.platform === 'win32') {
|
|
81
|
-
// eslint-disable-next-line sonarjs/no-os-command-from-path -- taskkill is a fixed Windows system command (no PATH-injection surface); it is the only POSIX-process-group-free way to terminate the child's tree.
|
|
82
|
-
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F']);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// Negated pid → deliver the signal to the whole process group.
|
|
86
|
-
process.kill(-pid, 'SIGKILL');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
// A reaper that says nothing about a tree it did not reap is the
|
|
91
|
-
// orphan-billing defect the registry exists to prevent, so only "already
|
|
92
|
-
// gone" is absorbed. A bug here — the one other thing `process.kill` on our
|
|
93
|
-
// own child's group can throw — stays loud.
|
|
94
|
-
if (!isProcessGroupGone(error))
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Whether `process.kill(-pid)` failed because the group is already gone.
|
|
100
|
-
*
|
|
101
|
-
* `ESRCH`: no such process group. `EPERM`: the group id has been recycled to a
|
|
102
|
-
* process this uid may not signal — which, for a group WE created and would
|
|
103
|
-
* have permission over, also means every process of ours in it has exited.
|
|
104
|
-
* Both are the outcome the kill was after.
|
|
64
|
+
* How a `claude` child is killed: with every process in its group. It is spawned `detached` (POSIX),
|
|
65
|
+
* so it leads a new process group, and the group kill reaps backgrounded grandchildren (a skill that
|
|
66
|
+
* runs `nohup … &`) and orphaned MCP subprocesses that a direct `child.kill()` would leave running;
|
|
67
|
+
* on Windows `taskkill /T /F` takes the tree.
|
|
105
68
|
*/
|
|
106
|
-
|
|
107
|
-
return error instanceof Error && 'code' in error && (error.code === 'ESRCH' || error.code === 'EPERM');
|
|
108
|
-
}
|
|
69
|
+
const CLAUDE_TREE = { groupLeader: true, throughChild: false };
|
|
109
70
|
/**
|
|
110
71
|
* Registry of currently in-flight `claude` child processes, keyed by the
|
|
111
72
|
* `ChildProcess` object itself. A child is registered immediately after
|
|
@@ -131,7 +92,7 @@ const activeClaudeChildren = new Set();
|
|
|
131
92
|
*/
|
|
132
93
|
export function killAllActiveClaudeChildren() {
|
|
133
94
|
for (const child of activeClaudeChildren) {
|
|
134
|
-
killProcessTree(child);
|
|
95
|
+
killProcessTree(child, CLAUDE_TREE);
|
|
135
96
|
}
|
|
136
97
|
activeClaudeChildren.clear();
|
|
137
98
|
}
|
|
@@ -193,7 +154,7 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
193
154
|
const promptStream = Readable.from([opts.prompt], { objectMode: false });
|
|
194
155
|
const wallTimer = setTimeout(() => {
|
|
195
156
|
timedOut = true;
|
|
196
|
-
killProcessTree(child);
|
|
157
|
+
killProcessTree(child, CLAUDE_TREE);
|
|
197
158
|
}, opts.timeoutMs);
|
|
198
159
|
// Stall watchdog: reset on every stdout/stderr chunk; fire if silent for stallMs.
|
|
199
160
|
let stallTimer;
|
|
@@ -204,7 +165,7 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
204
165
|
clearTimeout(stallTimer);
|
|
205
166
|
stallTimer = setTimeout(() => {
|
|
206
167
|
stalled = true;
|
|
207
|
-
killProcessTree(child);
|
|
168
|
+
killProcessTree(child, CLAUDE_TREE);
|
|
208
169
|
}, stallMs);
|
|
209
170
|
};
|
|
210
171
|
resetStallTimer();
|
|
@@ -226,7 +187,7 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
226
187
|
};
|
|
227
188
|
promptStream.on('error', err => {
|
|
228
189
|
clearAllTimers();
|
|
229
|
-
killProcessTree(child);
|
|
190
|
+
killProcessTree(child, CLAUDE_TREE);
|
|
230
191
|
reject(err);
|
|
231
192
|
});
|
|
232
193
|
// Swallow EPIPE: if the child exits before consuming all stdin, the write
|
|
@@ -242,7 +203,7 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
242
203
|
// ever fires while the process is actually alive we would otherwise drop it
|
|
243
204
|
// from the reap set without killing it (the exact orphan this file guards
|
|
244
205
|
// against). killProcessTree no-ops on an undefined pid (never-spawned case).
|
|
245
|
-
child.on('error', err => { clearAllTimers(); killProcessTree(child); reject(err); });
|
|
206
|
+
child.on('error', err => { clearAllTimers(); killProcessTree(child, CLAUDE_TREE); reject(err); });
|
|
246
207
|
child.on('close', code => { clearAllTimers(); resolve({ status: code ?? -1, timedOut, stalled }); });
|
|
247
208
|
});
|
|
248
209
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one special-file policy: what a read does with an entry that is not a
|
|
3
|
+
* regular file. Internal, and off the barrel: `openForReading` and the
|
|
4
|
+
* tree-change walk both judge by it, so a named pipe is refused the same way
|
|
5
|
+
* whether one file is read or a whole tree is.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Refuse, before reading a byte, something that is not a regular file: a named
|
|
9
|
+
* pipe, socket or device has no content to read, only a stream that may never
|
|
10
|
+
* end. It is an `EFTYPE` errno ("inappropriate file type"), so every caller's
|
|
11
|
+
* own environmental-refusal convention codes it. A directory is left to the
|
|
12
|
+
* read, which raises the OS's own `EISDIR`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function refuseSpecialFile(stats: {
|
|
15
|
+
isFile(): boolean;
|
|
16
|
+
isDirectory(): boolean;
|
|
17
|
+
}, filePath: string): void;
|
|
18
|
+
//# sourceMappingURL=special-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special-file.d.ts","sourceRoot":"","sources":["../src/special-file.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,MAAM,IAAI,OAAO,CAAC;IAAC,WAAW,IAAI,OAAO,CAAA;CAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM9G"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one special-file policy: what a read does with an entry that is not a
|
|
3
|
+
* regular file. Internal, and off the barrel: `openForReading` and the
|
|
4
|
+
* tree-change walk both judge by it, so a named pipe is refused the same way
|
|
5
|
+
* whether one file is read or a whole tree is.
|
|
6
|
+
*/
|
|
7
|
+
/** An OS errno (BSD's "inappropriate file type"), not a VAT code: the shape every errno predicate reads. */
|
|
8
|
+
const NOT_A_REGULAR_FILE_ERRNO = 'EFTYPE';
|
|
9
|
+
/**
|
|
10
|
+
* Refuse, before reading a byte, something that is not a regular file: a named
|
|
11
|
+
* pipe, socket or device has no content to read, only a stream that may never
|
|
12
|
+
* end. It is an `EFTYPE` errno ("inappropriate file type"), so every caller's
|
|
13
|
+
* own environmental-refusal convention codes it. A directory is left to the
|
|
14
|
+
* read, which raises the OS's own `EISDIR`.
|
|
15
|
+
*/
|
|
16
|
+
export function refuseSpecialFile(stats, filePath) {
|
|
17
|
+
if (stats.isFile() || stats.isDirectory())
|
|
18
|
+
return;
|
|
19
|
+
throw Object.assign(new Error(`${NOT_A_REGULAR_FILE_ERRNO}: not a regular file (a named pipe, socket or device), refused unread: ${filePath}`), { code: NOT_A_REGULAR_FILE_ERRNO, path: filePath, syscall: 'open' });
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=special-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special-file.js","sourceRoot":"","sources":["../src/special-file.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4GAA4G;AAC5G,MAAM,wBAAwB,GAAG,QAAQ,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAoD,EAAE,QAAgB;IACtG,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE;QAAE,OAAO;IAClD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,GAAG,wBAAwB,0EAA0E,QAAQ,EAAE,CAAC,EAC1H,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CACpE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signals a process must not die of before it has cleaned up, the ONE way a handler for them is
|
|
3
|
+
* installed ({@link handleTerminatingSignals}), and how the process then dies of the signal itself.
|
|
4
|
+
*
|
|
5
|
+
* 🚨 Every terminating-signal handler in VAT's product code goes through
|
|
6
|
+
* {@link handleTerminatingSignals}; `local/no-raw-signal-listener` refuses any other way of adding
|
|
7
|
+
* or removing a listener for one (its README lists every shape it catches). The class it closes: a handler
|
|
8
|
+
* that removed itself on the first signal and started an ASYNCHRONOUS cleanup, so a second signal —
|
|
9
|
+
* which a terminal Ctrl-C under the `vat` wrapper delivers (the terminal signals the whole
|
|
10
|
+
* foreground group, and the wrapper forwards) — met the default disposition and killed the process
|
|
11
|
+
* half-way through that cleanup, leaking the clone or the lock it was removing.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The signals that end a process by default disposition — `kill <pid>`, `kill -INT <pid>`,
|
|
15
|
+
* `timeout(1)`, a CI runner stopping or cancelling a step, a closed terminal, Ctrl-C, `Ctrl-\`,
|
|
16
|
+
* Ctrl-Break.
|
|
17
|
+
*
|
|
18
|
+
* SIGQUIT is here because its default disposition terminates exactly as the others do (plus a core
|
|
19
|
+
* dump where the system allows one). SIGBREAK is Windows' Ctrl-Break, which Node delivers there and
|
|
20
|
+
* which ends a process with no handler. Each exists on one platform only: a listener for the other
|
|
21
|
+
* platform's registers an ordinary event that never fires, which is harmless — so ONE list serves
|
|
22
|
+
* both. SIGKILL is not here because nothing can handle it.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ On Windows only SIGINT, SIGBREAK and SIGHUP are ever DELIVERED, and only as console events
|
|
25
|
+
* (Ctrl-C, Ctrl-Break, the console closing) — which the console sends to EVERY process attached to
|
|
26
|
+
* it. An outside kill there (`taskkill /F`, `process.kill(pid, <any>)`, `child.kill()`) is
|
|
27
|
+
* `TerminateProcess`: no handler runs at all.
|
|
28
|
+
*/
|
|
29
|
+
export declare const TERMINATING_SIGNALS: readonly ["SIGTERM", "SIGHUP", "SIGINT", "SIGQUIT", "SIGBREAK"];
|
|
30
|
+
/** One of {@link TERMINATING_SIGNALS}. */
|
|
31
|
+
export type TerminatingSignal = (typeof TERMINATING_SIGNALS)[number];
|
|
32
|
+
/**
|
|
33
|
+
* Whether `signal` is one of {@link TERMINATING_SIGNALS}.
|
|
34
|
+
*
|
|
35
|
+
* @param signal - A signal name, as a child's `exit` reports it
|
|
36
|
+
* @returns True for a terminating signal
|
|
37
|
+
*/
|
|
38
|
+
export declare function isTerminatingSignal(signal: string): signal is TerminatingSignal;
|
|
39
|
+
/**
|
|
40
|
+
* How long after the first terminating signal a repeat is still the SAME interruption, and is
|
|
41
|
+
* swallowed rather than acted on.
|
|
42
|
+
*
|
|
43
|
+
* A terminal's Ctrl-C under the `vat` wrapper arrives twice — the foreground group's, and the
|
|
44
|
+
* wrapper's forward — milliseconds apart. A repeat later than this is an operator (or a supervisor)
|
|
45
|
+
* asking again because the first did not end the process soon enough: it ends the process at once,
|
|
46
|
+
* whatever cleanup is still running.
|
|
47
|
+
*/
|
|
48
|
+
export declare const REPEAT_SIGNAL_WINDOW_MS = 500;
|
|
49
|
+
/**
|
|
50
|
+
* End this process OF `signal`, as its default disposition would have — so a shell and a CI runner
|
|
51
|
+
* read the status they expect (128 + n) — with an exit-code fallback.
|
|
52
|
+
*
|
|
53
|
+
* The {@link handleTerminatingSignals} listener is removed first, so the raise reaches the default
|
|
54
|
+
* disposition rather than a guard still swallowing repeats. Only that listener: nothing else in VAT
|
|
55
|
+
* installs one (`local/no-raw-signal-listener`), and a host's own — a test runner's — is not this
|
|
56
|
+
* module's to strip. A process one of those keeps alive takes the fallback below.
|
|
57
|
+
*
|
|
58
|
+
* The wait is synchronous so nothing else runs meanwhile: output published after the operator asked
|
|
59
|
+
* this process to end would read as the run's verdict. A process still here after
|
|
60
|
+
* {@link RERAISE_GRACE_MS} survived its own raise (PID 1) and exits `fallbackExitCode`. Windows is
|
|
61
|
+
* never sent the raise: it cannot SEND most of these signals, and the ones it can terminate without
|
|
62
|
+
* a signal status to preserve — so on Windows this always exits `fallbackExitCode`.
|
|
63
|
+
*
|
|
64
|
+
* @param signal - The signal this process received
|
|
65
|
+
* @param fallbackExitCode - The exit code when the raise cannot end this process
|
|
66
|
+
*/
|
|
67
|
+
export declare function reRaiseSignal(signal: NodeJS.Signals, fallbackExitCode: number): never;
|
|
68
|
+
/**
|
|
69
|
+
* What a guard undoes before the process ends: given the signal, and an `AbortSignal` that aborts
|
|
70
|
+
* — synchronously, just before the process ends — when a later repeat forces the ending while this
|
|
71
|
+
* cleanup is still running. Most cleanups have nothing to do then; one that started something which
|
|
72
|
+
* must not outlive this process (the `vat` wrapper's CLI) kills it there.
|
|
73
|
+
*/
|
|
74
|
+
export type TerminatingSignalCleanup = (signal: TerminatingSignal, forced: AbortSignal) => unknown;
|
|
75
|
+
/** What {@link handleTerminatingSignals} hands back. */
|
|
76
|
+
export interface TerminatingSignalGuard {
|
|
77
|
+
/**
|
|
78
|
+
* The guarded work is over without a signal: stop guarding it.
|
|
79
|
+
*
|
|
80
|
+
* Before any signal arrived, this removes the guard — and the process's listeners with the last
|
|
81
|
+
* one. Once a signal HAS arrived it changes nothing: the process still ends of that signal when
|
|
82
|
+
* every cleanup has settled (or a later repeat forces it). A caller that must end the process its
|
|
83
|
+
* own way meanwhile does so with {@link reRaiseSignal} or `process.exit`, both of which pass every
|
|
84
|
+
* guard by.
|
|
85
|
+
*
|
|
86
|
+
* @returns True when the guard was removed; false when a signal is already ending the process
|
|
87
|
+
*/
|
|
88
|
+
release(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* {@link release} for work that would go on to report or exit its own way: resolves once the
|
|
91
|
+
* guard is removed — and NEVER settles when a signal is already ending the process, so that work
|
|
92
|
+
* neither publishes nor exits under it. Call it only once this guard's own cleanup can no longer
|
|
93
|
+
* be in progress (the caller has run or awaited it), so the ending it yields to is not waiting
|
|
94
|
+
* on the caller.
|
|
95
|
+
*
|
|
96
|
+
* @returns A promise that resolves once the guard is removed
|
|
97
|
+
*/
|
|
98
|
+
releaseOrAwaitEnding(): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Guard this process's terminating signals while `cleanup` has something to undo: on the FIRST
|
|
102
|
+
* {@link TERMINATING_SIGNALS} member, run it — and every other live guard's — then die of that
|
|
103
|
+
* signal.
|
|
104
|
+
*
|
|
105
|
+
* - **A repeat within {@link REPEAT_SIGNAL_WINDOW_MS} is swallowed.** A terminal Ctrl-C under the
|
|
106
|
+
* `vat` wrapper arrives twice (the group's, and the wrapper's forward); the second must not meet
|
|
107
|
+
* the default disposition half-way through an asynchronous cleanup.
|
|
108
|
+
* - **A repeat after it ends the process at once**, of the repeat, the cleanups abandoned (each
|
|
109
|
+
* cleanup's `forced` signal aborts first): a second Ctrl-C or kill still ends a cleanup that hung.
|
|
110
|
+
* - **Synchronous or asynchronous.** A `cleanup` that returns a promise is awaited, whichever way it
|
|
111
|
+
* settles — it reports its own failures — and a ref'd timer holds the process open meanwhile, so
|
|
112
|
+
* a promise that holds nothing open cannot let it exit 0. When none returned one, the process
|
|
113
|
+
* ends synchronously, inside the signal's own turn.
|
|
114
|
+
* - **The ending** is {@link reRaiseSignal}: on POSIX the process dies of the signal (128 + n); on
|
|
115
|
+
* Windows, or where the raise cannot kill it (PID 1), it exits `fallbackExitCode`.
|
|
116
|
+
* - **Guards compose.** One listener serves every live guard, so two guards in one process both
|
|
117
|
+
* clean up before it ends; the first one installed decides `fallbackExitCode`. A guard installed
|
|
118
|
+
* while an ending is in progress runs its cleanup at once, and the ending waits for it too.
|
|
119
|
+
*
|
|
120
|
+
* @param options - The guard
|
|
121
|
+
* @param options.cleanup - What to undo before the process ends
|
|
122
|
+
* @param options.fallbackExitCode - The exit code where the signal cannot end the process
|
|
123
|
+
* @returns The guard, to release once the guarded work is over
|
|
124
|
+
*/
|
|
125
|
+
export declare function handleTerminatingSignals(options: {
|
|
126
|
+
cleanup: TerminatingSignalCleanup;
|
|
127
|
+
fallbackExitCode: number;
|
|
128
|
+
}): TerminatingSignalGuard;
|
|
129
|
+
//# sourceMappingURL=terminating-signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminating-signals.d.ts","sourceRoot":"","sources":["../src/terminating-signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,iEAAkE,CAAC;AAEnG,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,iBAAiB,CAE/E;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAa3C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,GAAG,KAAK,CAOrF;AAED;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC;AAwGnG,wDAAwD;AACxD,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;OAUG;IACH,OAAO,IAAI,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,wBAAwB,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC;CAC1B,GAAG,sBAAsB,CAiBzB"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signals a process must not die of before it has cleaned up, the ONE way a handler for them is
|
|
3
|
+
* installed ({@link handleTerminatingSignals}), and how the process then dies of the signal itself.
|
|
4
|
+
*
|
|
5
|
+
* 🚨 Every terminating-signal handler in VAT's product code goes through
|
|
6
|
+
* {@link handleTerminatingSignals}; `local/no-raw-signal-listener` refuses any other way of adding
|
|
7
|
+
* or removing a listener for one (its README lists every shape it catches). The class it closes: a handler
|
|
8
|
+
* that removed itself on the first signal and started an ASYNCHRONOUS cleanup, so a second signal —
|
|
9
|
+
* which a terminal Ctrl-C under the `vat` wrapper delivers (the terminal signals the whole
|
|
10
|
+
* foreground group, and the wrapper forwards) — met the default disposition and killed the process
|
|
11
|
+
* half-way through that cleanup, leaking the clone or the lock it was removing.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The signals that end a process by default disposition — `kill <pid>`, `kill -INT <pid>`,
|
|
15
|
+
* `timeout(1)`, a CI runner stopping or cancelling a step, a closed terminal, Ctrl-C, `Ctrl-\`,
|
|
16
|
+
* Ctrl-Break.
|
|
17
|
+
*
|
|
18
|
+
* SIGQUIT is here because its default disposition terminates exactly as the others do (plus a core
|
|
19
|
+
* dump where the system allows one). SIGBREAK is Windows' Ctrl-Break, which Node delivers there and
|
|
20
|
+
* which ends a process with no handler. Each exists on one platform only: a listener for the other
|
|
21
|
+
* platform's registers an ordinary event that never fires, which is harmless — so ONE list serves
|
|
22
|
+
* both. SIGKILL is not here because nothing can handle it.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ On Windows only SIGINT, SIGBREAK and SIGHUP are ever DELIVERED, and only as console events
|
|
25
|
+
* (Ctrl-C, Ctrl-Break, the console closing) — which the console sends to EVERY process attached to
|
|
26
|
+
* it. An outside kill there (`taskkill /F`, `process.kill(pid, <any>)`, `child.kill()`) is
|
|
27
|
+
* `TerminateProcess`: no handler runs at all.
|
|
28
|
+
*/
|
|
29
|
+
export const TERMINATING_SIGNALS = ['SIGTERM', 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGBREAK'];
|
|
30
|
+
/**
|
|
31
|
+
* Whether `signal` is one of {@link TERMINATING_SIGNALS}.
|
|
32
|
+
*
|
|
33
|
+
* @param signal - A signal name, as a child's `exit` reports it
|
|
34
|
+
* @returns True for a terminating signal
|
|
35
|
+
*/
|
|
36
|
+
export function isTerminatingSignal(signal) {
|
|
37
|
+
return TERMINATING_SIGNALS.includes(signal);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* How long after the first terminating signal a repeat is still the SAME interruption, and is
|
|
41
|
+
* swallowed rather than acted on.
|
|
42
|
+
*
|
|
43
|
+
* A terminal's Ctrl-C under the `vat` wrapper arrives twice — the foreground group's, and the
|
|
44
|
+
* wrapper's forward — milliseconds apart. A repeat later than this is an operator (or a supervisor)
|
|
45
|
+
* asking again because the first did not end the process soon enough: it ends the process at once,
|
|
46
|
+
* whatever cleanup is still running.
|
|
47
|
+
*/
|
|
48
|
+
export const REPEAT_SIGNAL_WINDOW_MS = 500;
|
|
49
|
+
/**
|
|
50
|
+
* How long a re-raised signal is given to end this process before it ends itself instead.
|
|
51
|
+
*
|
|
52
|
+
* A process that SURVIVES its own re-raise is one whose default disposition for the signal does
|
|
53
|
+
* nothing — PID 1 in a container is the common case — and it must still end.
|
|
54
|
+
*/
|
|
55
|
+
const RERAISE_GRACE_MS = 1000;
|
|
56
|
+
/** The longest interval a Node timer accepts: the hold below never fires, it only holds. */
|
|
57
|
+
const HOLD_INTERVAL_MS = 2 ** 31 - 1;
|
|
58
|
+
/**
|
|
59
|
+
* End this process OF `signal`, as its default disposition would have — so a shell and a CI runner
|
|
60
|
+
* read the status they expect (128 + n) — with an exit-code fallback.
|
|
61
|
+
*
|
|
62
|
+
* The {@link handleTerminatingSignals} listener is removed first, so the raise reaches the default
|
|
63
|
+
* disposition rather than a guard still swallowing repeats. Only that listener: nothing else in VAT
|
|
64
|
+
* installs one (`local/no-raw-signal-listener`), and a host's own — a test runner's — is not this
|
|
65
|
+
* module's to strip. A process one of those keeps alive takes the fallback below.
|
|
66
|
+
*
|
|
67
|
+
* The wait is synchronous so nothing else runs meanwhile: output published after the operator asked
|
|
68
|
+
* this process to end would read as the run's verdict. A process still here after
|
|
69
|
+
* {@link RERAISE_GRACE_MS} survived its own raise (PID 1) and exits `fallbackExitCode`. Windows is
|
|
70
|
+
* never sent the raise: it cannot SEND most of these signals, and the ones it can terminate without
|
|
71
|
+
* a signal status to preserve — so on Windows this always exits `fallbackExitCode`.
|
|
72
|
+
*
|
|
73
|
+
* @param signal - The signal this process received
|
|
74
|
+
* @param fallbackExitCode - The exit code when the raise cannot end this process
|
|
75
|
+
*/
|
|
76
|
+
export function reRaiseSignal(signal, fallbackExitCode) {
|
|
77
|
+
unlisten();
|
|
78
|
+
if (process.platform !== 'win32') {
|
|
79
|
+
process.kill(process.pid, signal);
|
|
80
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, RERAISE_GRACE_MS);
|
|
81
|
+
}
|
|
82
|
+
process.exit(fallbackExitCode);
|
|
83
|
+
}
|
|
84
|
+
/** Every guard not yet released, in installation order. */
|
|
85
|
+
const registrations = new Set();
|
|
86
|
+
/** The ending in progress, if a signal has arrived — and the reason a repeat is not a new one. */
|
|
87
|
+
let ending;
|
|
88
|
+
/** Whether `value` is a promise (or any thenable) to wait for. */
|
|
89
|
+
function isThenable(value) {
|
|
90
|
+
return typeof value?.then === 'function';
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run one cleanup inside `current`. A synchronous throw becomes a rejection, so it cannot stop the
|
|
94
|
+
* others — or the ending: a cleanup reports its own failures, and this only makes sure the process
|
|
95
|
+
* still ends. A promise it returns is waited for, whichever way it settles: the ending finishes
|
|
96
|
+
* once the last one has.
|
|
97
|
+
*/
|
|
98
|
+
function joinEnding(current, cleanup) {
|
|
99
|
+
let outcome;
|
|
100
|
+
try {
|
|
101
|
+
outcome = cleanup(current.signal, current.forced.signal);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
outcome = Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
105
|
+
}
|
|
106
|
+
if (!isThenable(outcome))
|
|
107
|
+
return;
|
|
108
|
+
current.pending += 1;
|
|
109
|
+
const settled = () => {
|
|
110
|
+
current.pending -= 1;
|
|
111
|
+
if (current.pending === 0 && ending === current)
|
|
112
|
+
finishEnding(current, current.signal);
|
|
113
|
+
};
|
|
114
|
+
outcome.then(settled, settled);
|
|
115
|
+
}
|
|
116
|
+
/** End `current`'s ending: let go of everything this module holds, then die of `signal`. */
|
|
117
|
+
function finishEnding(current, signal) {
|
|
118
|
+
clearInterval(current.hold);
|
|
119
|
+
registrations.clear();
|
|
120
|
+
ending = undefined;
|
|
121
|
+
reRaiseSignal(signal, current.fallbackExitCode);
|
|
122
|
+
}
|
|
123
|
+
/** The one listener every guard shares, on every {@link TERMINATING_SIGNALS} member. */
|
|
124
|
+
function onTerminatingSignal(signal) {
|
|
125
|
+
const current = ending;
|
|
126
|
+
if (current !== undefined) {
|
|
127
|
+
// The same interruption, delivered twice (a group Ctrl-C and the wrapper's forward): swallowed,
|
|
128
|
+
// so it cannot cut the cleanups short.
|
|
129
|
+
if (performance.now() - current.at < REPEAT_SIGNAL_WINDOW_MS)
|
|
130
|
+
return;
|
|
131
|
+
// Asked again: the cleanups are abandoned, and the process ends now, of the repeat.
|
|
132
|
+
current.forced.abort();
|
|
133
|
+
finishEnding(current, signal);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const [first] = registrations;
|
|
137
|
+
if (first === undefined)
|
|
138
|
+
return;
|
|
139
|
+
const started = {
|
|
140
|
+
signal,
|
|
141
|
+
at: performance.now(),
|
|
142
|
+
fallbackExitCode: first.fallbackExitCode,
|
|
143
|
+
forced: new AbortController(),
|
|
144
|
+
hold: setInterval(() => undefined, HOLD_INTERVAL_MS),
|
|
145
|
+
pending: 0,
|
|
146
|
+
};
|
|
147
|
+
ending = started;
|
|
148
|
+
for (const registration of registrations)
|
|
149
|
+
joinEnding(started, registration.cleanup);
|
|
150
|
+
// Synchronous cleanups end the process synchronously: nothing else runs meanwhile, so nothing can
|
|
151
|
+
// publish a verdict after the operator asked this process to stop.
|
|
152
|
+
if (started.pending === 0)
|
|
153
|
+
finishEnding(started, signal);
|
|
154
|
+
}
|
|
155
|
+
function listen() {
|
|
156
|
+
for (const signal of TERMINATING_SIGNALS)
|
|
157
|
+
process.on(signal, onTerminatingSignal);
|
|
158
|
+
}
|
|
159
|
+
function unlisten() {
|
|
160
|
+
for (const signal of TERMINATING_SIGNALS)
|
|
161
|
+
process.off(signal, onTerminatingSignal);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Guard this process's terminating signals while `cleanup` has something to undo: on the FIRST
|
|
165
|
+
* {@link TERMINATING_SIGNALS} member, run it — and every other live guard's — then die of that
|
|
166
|
+
* signal.
|
|
167
|
+
*
|
|
168
|
+
* - **A repeat within {@link REPEAT_SIGNAL_WINDOW_MS} is swallowed.** A terminal Ctrl-C under the
|
|
169
|
+
* `vat` wrapper arrives twice (the group's, and the wrapper's forward); the second must not meet
|
|
170
|
+
* the default disposition half-way through an asynchronous cleanup.
|
|
171
|
+
* - **A repeat after it ends the process at once**, of the repeat, the cleanups abandoned (each
|
|
172
|
+
* cleanup's `forced` signal aborts first): a second Ctrl-C or kill still ends a cleanup that hung.
|
|
173
|
+
* - **Synchronous or asynchronous.** A `cleanup` that returns a promise is awaited, whichever way it
|
|
174
|
+
* settles — it reports its own failures — and a ref'd timer holds the process open meanwhile, so
|
|
175
|
+
* a promise that holds nothing open cannot let it exit 0. When none returned one, the process
|
|
176
|
+
* ends synchronously, inside the signal's own turn.
|
|
177
|
+
* - **The ending** is {@link reRaiseSignal}: on POSIX the process dies of the signal (128 + n); on
|
|
178
|
+
* Windows, or where the raise cannot kill it (PID 1), it exits `fallbackExitCode`.
|
|
179
|
+
* - **Guards compose.** One listener serves every live guard, so two guards in one process both
|
|
180
|
+
* clean up before it ends; the first one installed decides `fallbackExitCode`. A guard installed
|
|
181
|
+
* while an ending is in progress runs its cleanup at once, and the ending waits for it too.
|
|
182
|
+
*
|
|
183
|
+
* @param options - The guard
|
|
184
|
+
* @param options.cleanup - What to undo before the process ends
|
|
185
|
+
* @param options.fallbackExitCode - The exit code where the signal cannot end the process
|
|
186
|
+
* @returns The guard, to release once the guarded work is over
|
|
187
|
+
*/
|
|
188
|
+
export function handleTerminatingSignals(options) {
|
|
189
|
+
const registration = { cleanup: options.cleanup, fallbackExitCode: options.fallbackExitCode };
|
|
190
|
+
if (ending === undefined) {
|
|
191
|
+
if (registrations.size === 0)
|
|
192
|
+
listen();
|
|
193
|
+
registrations.add(registration);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
joinEnding(ending, registration.cleanup);
|
|
197
|
+
}
|
|
198
|
+
const release = () => {
|
|
199
|
+
if (ending !== undefined)
|
|
200
|
+
return false;
|
|
201
|
+
if (registrations.delete(registration) && registrations.size === 0)
|
|
202
|
+
unlisten();
|
|
203
|
+
return true;
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
release,
|
|
207
|
+
releaseOrAwaitEnding: () => (release() ? Promise.resolve() : new Promise(() => { })),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=terminating-signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminating-signals.js","sourceRoot":"","sources":["../src/terminating-signals.ts"],"names":[],"mappings":"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"}
|
package/dist/test-helpers.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type SymlinkCapability = {
|
|
|
26
26
|
*
|
|
27
27
|
* Because the answer is memoized for the whole process, what reads as "no"
|
|
28
28
|
* matters more than usual: a `null` here silently `skip()`s every symlink test
|
|
29
|
-
* for the rest of the run. So ONLY {@link
|
|
29
|
+
* for the rest of the run. So ONLY {@link isSymlinkUnsupportedError} is a
|
|
30
30
|
* no. A tmpdir that is unwritable or missing, or a bug, is not an answer about
|
|
31
31
|
* symlinks at all and stays loud rather than becoming a process-wide skip for
|
|
32
32
|
* a reason nothing reported.
|
|
@@ -106,13 +106,14 @@ export declare const INHERITED_GIT_ENV: readonly ["GIT_ALTERNATE_OBJECT_DIRECTOR
|
|
|
106
106
|
export declare function detachGitEnv(): () => void;
|
|
107
107
|
/**
|
|
108
108
|
* The errno-shaped error a refused `fs` call throws: a message, the `code`,
|
|
109
|
-
* and the `
|
|
109
|
+
* the `syscall` and the `path`, exactly as Node shapes one — a caller that names
|
|
110
|
+
* the refused file from `path` (a tree probe naming the nested file) reads it.
|
|
110
111
|
*/
|
|
111
112
|
export declare function errnoError(code: string, syscall: string, target: string): NodeJS.ErrnoException;
|
|
112
113
|
/** The sync `node:fs` calls a refusal can be injected into. */
|
|
113
|
-
export type RefusableSyncFsMethod = 'readdirSync' | 'readFileSync' | 'statSync' | 'lstatSync' | 'realpathSync' | 'renameSync' | 'rmSync' | 'unlinkSync';
|
|
114
|
+
export type RefusableSyncFsMethod = 'cpSync' | 'openSync' | 'readdirSync' | 'readFileSync' | 'statSync' | 'lstatSync' | 'realpathSync' | 'renameSync' | 'rmSync' | 'unlinkSync' | 'writeFileSync';
|
|
114
115
|
/** The `node:fs/promises` calls a refusal can be injected into. */
|
|
115
|
-
export type RefusableAsyncFsMethod = 'readdir' | 'readFile' | 'stat' | 'lstat' | 'access';
|
|
116
|
+
export type RefusableAsyncFsMethod = 'readdir' | 'readFile' | 'stat' | 'lstat' | 'access' | 'copyFile' | 'mkdir' | 'writeFile';
|
|
116
117
|
/**
|
|
117
118
|
* Make `fs[method]` throw `code` for exactly `targetPath` until the returned
|
|
118
119
|
* restore is called; every other path, and every other method, stays real.
|
|
@@ -131,8 +132,11 @@ export declare function refuseSyncFs(method: RefusableSyncFsMethod, targetPath:
|
|
|
131
132
|
/**
|
|
132
133
|
* `fs/promises[method]` rejects with `code` for exactly `targetPath` until the
|
|
133
134
|
* returned restore is called; every other path, and every other method, is real.
|
|
135
|
+
* `beforeRefusing`, when given, runs first on each refused call — a write that
|
|
136
|
+
* fails partway (a full disk) leaves a truncated file behind, and a test of the
|
|
137
|
+
* cleanup needs that file to exist.
|
|
134
138
|
*/
|
|
135
|
-
export declare function refuseAsyncFs(method: RefusableAsyncFsMethod, targetPath: string, code: string): () => void;
|
|
139
|
+
export declare function refuseAsyncFs(method: RefusableAsyncFsMethod, targetPath: string, code: string, beforeRefusing?: () => void): () => void;
|
|
136
140
|
/**
|
|
137
141
|
* Run `body` while `fs[method]` throws `code` for exactly `targetPath`; the
|
|
138
142
|
* patch is lifted however `body` exits. See {@link refuseSyncFs}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAQA,OAAO,CAAC,MAAM,sBAAsB,EAAE,OAAO,MAAM,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAQA,OAAO,CAAC,MAAM,sBAAsB,EAAE,OAAO,MAAM,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAI5E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,GAAG,IAAI,CAwB5D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GACjC,IAAI,CAEN;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GACjC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,yUAgBpB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,IAAI,MAAM,IAAI,CAoBzC;AAGD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,cAAc,CAE/F;AAED,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,UAAU,GACV,aAAa,GACb,cAAc,GACd,UAAU,GACV,WAAW,GACX,cAAc,GACd,YAAY,GACZ,QAAQ,GACR,YAAY,GACZ,eAAe,CAAC;AAEpB,mEAAmE;AACnE,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,CAAC;AAiB/H;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,IAAI,CAQxG;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,MAAM,IAAI,GAC1B,MAAM,IAAI,CAWZ;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAEZ"}
|