mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge branch 'main' into verbose-logging
This commit is contained in:
commit
be6a6dccd9
62 changed files with 1243 additions and 41 deletions
3
.github/workflows/test-on-pr.yml
vendored
3
.github/workflows/test-on-pr.yml
vendored
|
|
@ -31,6 +31,9 @@ jobs:
|
||||||
- name: Run linting
|
- name: Run linting
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Type check
|
||||||
|
run: npm run typecheck --workspace=packages/safe-chain
|
||||||
|
|
||||||
- name: Create package tarball
|
- name: Create package tarball
|
||||||
run: npm pack --workspace=packages/safe-chain
|
run: npm pack --workspace=packages/safe-chain
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"eslint/no-console": "error",
|
"eslint/no-console": "error",
|
||||||
"eslint/no-empty": "error"
|
"eslint/no-empty": "error",
|
||||||
|
"eslint/no-undef": "error"
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
372
package-lock.json
generated
372
package-lock.json
generated
|
|
@ -411,6 +411,94 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/make-fetch-happen": {
|
||||||
|
"version": "10.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/make-fetch-happen/-/make-fetch-happen-10.0.4.tgz",
|
||||||
|
"integrity": "sha512-jKzweQaEMMAi55ehvR1z0JF6aSVQm/h1BXBhPLOJriaeQBctjw5YbpIGs7zAx9dN0Sa2OO5bcXwCkrlgenoPEA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node-fetch": "*",
|
||||||
|
"@types/retry": "*",
|
||||||
|
"@types/ssri": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
|
||||||
|
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node-fetch": {
|
||||||
|
"version": "2.6.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
|
||||||
|
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^4.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node-forge": {
|
||||||
|
"version": "1.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz",
|
||||||
|
"integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/npm-package-arg": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/npm-registry-fetch": {
|
||||||
|
"version": "8.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.9.tgz",
|
||||||
|
"integrity": "sha512-7NxvodR5Yrop3pb6+n8jhJNyzwOX0+6F+iagNEoi9u1CGxruYAwZD8pvGc9prIkL0+FdX5Xp0p80J9QPrGUp/g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/node-fetch": "*",
|
||||||
|
"@types/npm-package-arg": "*",
|
||||||
|
"@types/npmlog": "*",
|
||||||
|
"@types/ssri": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/npmlog": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-hJWbrKFvxKyWwSUXjZMYTINsSOY6IclhvGOZ97M8ac2tmR9hMwmTnYaMdpGhvju9ctWLTPhCS+eLfQNluiEjQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/retry": {
|
||||||
|
"version": "0.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz",
|
||||||
|
"integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/semver": {
|
||||||
|
"version": "7.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
|
||||||
|
"integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/ssri": {
|
||||||
|
"version": "7.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.5.tgz",
|
||||||
|
"integrity": "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||||
|
|
@ -444,6 +532,12 @@
|
||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
|
@ -516,6 +610,19 @@
|
||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||||
|
|
@ -581,6 +688,18 @@
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|
@ -612,6 +731,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
|
@ -653,6 +795,51 @@
|
||||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
|
|
@ -669,6 +856,22 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-minipass": {
|
"node_modules/fs-minipass": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
|
||||||
|
|
@ -681,6 +884,15 @@
|
||||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-east-asian-width": {
|
"node_modules/get-east-asian-width": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
|
||||||
|
|
@ -693,6 +905,43 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
|
|
@ -728,6 +977,57 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hosted-git-info": {
|
"node_modules/hosted-git-info": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz",
|
||||||
|
|
@ -919,6 +1219,36 @@
|
||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-function": {
|
"node_modules/mimic-function": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||||
|
|
@ -1563,6 +1893,25 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/unique-filename": {
|
"node_modules/unique-filename": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz",
|
||||||
|
|
@ -1739,6 +2088,14 @@
|
||||||
"aikido-pnpx": "bin/aikido-pnpx.js",
|
"aikido-pnpx": "bin/aikido-pnpx.js",
|
||||||
"aikido-yarn": "bin/aikido-yarn.js",
|
"aikido-yarn": "bin/aikido-yarn.js",
|
||||||
"safe-chain": "bin/safe-chain.js"
|
"safe-chain": "bin/safe-chain.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/make-fetch-happen": "^10.0.4",
|
||||||
|
"@types/node": "^18.19.130",
|
||||||
|
"@types/node-forge": "^1.3.14",
|
||||||
|
"@types/npm-registry-fetch": "^8.0.9",
|
||||||
|
"@types/semver": "^7.7.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/safe-chain-bun": {
|
"packages/safe-chain-bun": {
|
||||||
|
|
@ -1752,6 +2109,21 @@
|
||||||
"bun": ">=1.2.21"
|
"bun": ">=1.2.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/safe-chain/node_modules/@types/node": {
|
||||||
|
"version": "18.19.130",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||||
|
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/safe-chain/node_modules/undici-types": {
|
||||||
|
"version": "5.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"test/e2e": {
|
"test/e2e": {
|
||||||
"name": "@aikidosec/safe-chain-e2e-tests",
|
"name": "@aikidosec/safe-chain-e2e-tests",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const scanner = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
console.warn(`Safe-Chain security scan failed: ${error.message}`);
|
console.warn(`Safe-Chain security scan failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "bun";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "bunx";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "npm";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "npx";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "pnpm";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "pnpx";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ const packageManagerName = "yarn";
|
||||||
initializePackageManager(packageManagerName);
|
initializePackageManager(packageManagerName);
|
||||||
var exitCode = await main(process.argv.slice(2));
|
var exitCode = await main(process.argv.slice(2));
|
||||||
|
|
||||||
|
// @ts-expect-error scanCommand can return an empty array in main
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test --experimental-test-module-mocks 'src/**/*.spec.js'",
|
"test": "node --test --experimental-test-module-mocks 'src/**/*.spec.js'",
|
||||||
"test:watch": "node --test --watch --experimental-test-module-mocks 'src/**/*.spec.js'",
|
"test:watch": "node --test --watch --experimental-test-module-mocks 'src/**/*.spec.js'",
|
||||||
"lint": "oxlint --deny-warnings"
|
"lint": "oxlint --deny-warnings",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"aikido-npm": "bin/aikido-npm.js",
|
"aikido-npm": "bin/aikido-npm.js",
|
||||||
|
|
@ -38,6 +39,14 @@
|
||||||
"ora": "8.2.0",
|
"ora": "8.2.0",
|
||||||
"semver": "7.7.2"
|
"semver": "7.7.2"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/make-fetch-happen": "^10.0.4",
|
||||||
|
"@types/node": "^18.19.130",
|
||||||
|
"@types/npm-registry-fetch": "^8.0.9",
|
||||||
|
"@types/semver": "^7.7.1",
|
||||||
|
"@types/node-forge": "^1.3.14",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/AikidoSec/safe-chain/issues"
|
"url": "https://github.com/AikidoSec/safe-chain/issues"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,16 @@ import fetch from "make-fetch-happen";
|
||||||
const malwareDatabaseUrl =
|
const malwareDatabaseUrl =
|
||||||
"https://malware-list.aikido.dev/malware_predictions.json";
|
"https://malware-list.aikido.dev/malware_predictions.json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} MalwarePackage
|
||||||
|
* @property {string} package_name
|
||||||
|
* @property {string} version
|
||||||
|
* @property {string} reason
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<{malwareDatabase: MalwarePackage[], version: string | undefined}>}
|
||||||
|
*/
|
||||||
export async function fetchMalwareDatabase() {
|
export async function fetchMalwareDatabase() {
|
||||||
const response = await fetch(malwareDatabaseUrl);
|
const response = await fetch(malwareDatabaseUrl);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -15,11 +25,14 @@ export async function fetchMalwareDatabase() {
|
||||||
malwareDatabase: malwareDatabase,
|
malwareDatabase: malwareDatabase,
|
||||||
version: response.headers.get("etag") || undefined,
|
version: response.headers.get("etag") || undefined,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(`Error parsing malware database: ${error.message}`);
|
throw new Error(`Error parsing malware database: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<string | undefined>}
|
||||||
|
*/
|
||||||
export async function fetchMalwareDatabaseVersion() {
|
export async function fetchMalwareDatabaseVersion() {
|
||||||
const response = await fetch(malwareDatabaseUrl, {
|
const response = await fetch(malwareDatabaseUrl, {
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import * as semver from "semver";
|
import * as semver from "semver";
|
||||||
import * as npmFetch from "npm-registry-fetch";
|
import * as npmFetch from "npm-registry-fetch";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} packageName
|
||||||
|
* @param {string | null} [versionRange]
|
||||||
|
* @returns {Promise<string | null>}
|
||||||
|
*/
|
||||||
export async function resolvePackageVersion(packageName, versionRange) {
|
export async function resolvePackageVersion(packageName, versionRange) {
|
||||||
if (!versionRange) {
|
if (!versionRange) {
|
||||||
versionRange = "latest";
|
versionRange = "latest";
|
||||||
|
|
@ -11,7 +16,10 @@ export async function resolvePackageVersion(packageName, versionRange) {
|
||||||
return versionRange;
|
return versionRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageInfo = await getPackageInfo(packageName);
|
const packageInfo = (
|
||||||
|
/** @type {{"dist-tags"?: Record<string, string>, versions?: Record<string, unknown>} | null} */
|
||||||
|
await getPackageInfo(packageName)
|
||||||
|
);
|
||||||
if (!packageInfo) {
|
if (!packageInfo) {
|
||||||
// It is possible that no version is found (could be a private package, or a package that doesn't exist)
|
// It is possible that no version is found (could be a private package, or a package that doesn't exist)
|
||||||
// In this case, we return null to indicate that we couldn't resolve the version
|
// In this case, we return null to indicate that we couldn't resolve the version
|
||||||
|
|
@ -19,7 +27,7 @@ export async function resolvePackageVersion(packageName, versionRange) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const distTags = packageInfo["dist-tags"];
|
const distTags = packageInfo["dist-tags"];
|
||||||
if (distTags && distTags[versionRange]) {
|
if (distTags && isDistTags(distTags) && distTags[versionRange]) {
|
||||||
// If the version range is a dist-tag, return the version associated with that tag
|
// If the version range is a dist-tag, return the version associated with that tag
|
||||||
// e.g., "latest", "next", etc.
|
// e.g., "latest", "next", etc.
|
||||||
return distTags[versionRange];
|
return distTags[versionRange];
|
||||||
|
|
@ -41,6 +49,19 @@ export async function resolvePackageVersion(packageName, versionRange) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {unknown} distTags
|
||||||
|
* @returns {distTags is Record<string, string>}
|
||||||
|
*/
|
||||||
|
function isDistTags(distTags) {
|
||||||
|
return typeof distTags === "object";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {Promise<Record<string, unknown> | null>}
|
||||||
|
*/
|
||||||
async function getPackageInfo(packageName) {
|
async function getPackageInfo(packageName) {
|
||||||
try {
|
try {
|
||||||
return await npmFetch.json(packageName);
|
return await npmFetch.json(packageName);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
|
/**
|
||||||
|
* @type {{loggingLevel: string | undefined}}
|
||||||
|
*/
|
||||||
const state = {
|
const state = {
|
||||||
loggingLevel: undefined,
|
loggingLevel: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
|
const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
export function initializeCliArguments(args) {
|
export function initializeCliArguments(args) {
|
||||||
// Reset state on each call
|
// Reset state on each call
|
||||||
state.loggingLevel = undefined;
|
state.loggingLevel = undefined;
|
||||||
|
|
@ -24,6 +31,11 @@ export function initializeCliArguments(args) {
|
||||||
return remainingArgs;
|
return remainingArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {string} prefix
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function getLastArgEqualsValue(args, prefix) {
|
function getLastArgEqualsValue(args, prefix) {
|
||||||
for (var i = args.length - 1; i >= 0; i--) {
|
for (var i = args.length - 1; i >= 0; i--) {
|
||||||
const arg = args[i];
|
const arg = args[i];
|
||||||
|
|
@ -35,6 +47,10 @@ function getLastArgEqualsValue(args, prefix) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function setLoggingLevel(args) {
|
function setLoggingLevel(args) {
|
||||||
const safeChainLoggingArg = SAFE_CHAIN_ARG_PREFIX + "logging=";
|
const safeChainLoggingArg = SAFE_CHAIN_ARG_PREFIX + "logging=";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,22 @@ import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
export function getScanTimeout() {
|
export function getScanTimeout() {
|
||||||
const config = readConfigFile();
|
const config = /** @type {{scanTimeout?: number}} */ (readConfigFile());
|
||||||
return (
|
|
||||||
parseInt(process.env.AIKIDO_SCAN_TIMEOUT_MS) || config.scanTimeout || 10000 // Default to 10 seconds
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
);
|
return parseInt(process.env.AIKIDO_SCAN_TIMEOUT_MS) || config.scanTimeout || 10000 // Default to 10 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../api/aikido.js").MalwarePackage[]} data
|
||||||
|
* @param {string | number} version
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
export function writeDatabaseToLocalCache(data, version) {
|
export function writeDatabaseToLocalCache(data, version) {
|
||||||
try {
|
try {
|
||||||
const databasePath = getDatabasePath();
|
const databasePath = getDatabasePath();
|
||||||
|
|
@ -24,6 +33,9 @@ export function writeDatabaseToLocalCache(data, version) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {{malwareDatabase: import("../api/aikido.js").MalwarePackage[] | null, version: string | null}}
|
||||||
|
*/
|
||||||
export function readDatabaseFromLocalCache() {
|
export function readDatabaseFromLocalCache() {
|
||||||
try {
|
try {
|
||||||
const databasePath = getDatabasePath();
|
const databasePath = getDatabasePath();
|
||||||
|
|
@ -55,6 +67,9 @@ export function readDatabaseFromLocalCache() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {unknown}
|
||||||
|
*/
|
||||||
function readConfigFile() {
|
function readConfigFile() {
|
||||||
const configFilePath = getConfigFilePath();
|
const configFilePath = getConfigFilePath();
|
||||||
|
|
||||||
|
|
@ -66,6 +81,9 @@ function readConfigFile() {
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getDatabasePath() {
|
function getDatabasePath() {
|
||||||
const aikidoDir = getAikidoDirectory();
|
const aikidoDir = getAikidoDirectory();
|
||||||
return path.join(aikidoDir, "malwareDatabase.json");
|
return path.join(aikidoDir, "malwareDatabase.json");
|
||||||
|
|
@ -76,10 +94,16 @@ function getDatabaseVersionPath() {
|
||||||
return path.join(aikidoDir, "version.txt");
|
return path.join(aikidoDir, "version.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getConfigFilePath() {
|
function getConfigFilePath() {
|
||||||
return path.join(getAikidoDirectory(), "config.json");
|
return path.join(getAikidoDirectory(), "config.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getAikidoDirectory() {
|
function getAikidoDirectory() {
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
const aikidoDir = path.join(homeDir, ".aikido");
|
const aikidoDir = path.join(homeDir, ".aikido");
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,22 @@ function emptyLine() {
|
||||||
writeInformation("");
|
writeInformation("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {...any} optionalParams
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function writeInformation(message, ...optionalParams) {
|
function writeInformation(message, ...optionalParams) {
|
||||||
if (isSilentMode()) return;
|
if (isSilentMode()) return;
|
||||||
|
|
||||||
writeOrBuffer(() => console.log(message, ...optionalParams));
|
writeOrBuffer(() => console.log(message, ...optionalParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {...any} optionalParams
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function writeWarning(message, ...optionalParams) {
|
function writeWarning(message, ...optionalParams) {
|
||||||
if (isSilentMode()) return;
|
if (isSilentMode()) return;
|
||||||
|
|
||||||
|
|
@ -42,6 +52,11 @@ function writeWarning(message, ...optionalParams) {
|
||||||
writeOrBuffer(() => console.warn(message, ...optionalParams));
|
writeOrBuffer(() => console.warn(message, ...optionalParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {...any} optionalParams
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function writeError(message, ...optionalParams) {
|
function writeError(message, ...optionalParams) {
|
||||||
if (!isCi()) {
|
if (!isCi()) {
|
||||||
message = chalk.red(message);
|
message = chalk.red(message);
|
||||||
|
|
@ -71,6 +86,19 @@ function writeOrBuffer(messageFunction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Spinner
|
||||||
|
* @property {(message: string) => void} succeed
|
||||||
|
* @property {(message: string) => void} fail
|
||||||
|
* @property {() => void} stop
|
||||||
|
* @property {(message: string) => void} setText
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
*
|
||||||
|
* @returns {Spinner}
|
||||||
|
*/
|
||||||
function startProcess(message) {
|
function startProcess(message) {
|
||||||
if (isSilentMode()) {
|
if (isSilentMode()) {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import { initializeCliArguments } from "./config/cliArguments.js";
|
||||||
import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
|
import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<number | never[]>}
|
||||||
|
*/
|
||||||
export async function main(args) {
|
export async function main(args) {
|
||||||
process.on("SIGINT", handleProcessTermination);
|
process.on("SIGINT", handleProcessTermination);
|
||||||
process.on("SIGTERM", handleProcessTermination);
|
process.on("SIGTERM", handleProcessTermination);
|
||||||
|
|
@ -14,6 +18,23 @@ export async function main(args) {
|
||||||
const proxy = createSafeChainProxy();
|
const proxy = createSafeChainProxy();
|
||||||
await proxy.startServer();
|
await proxy.startServer();
|
||||||
|
|
||||||
|
// Global error handlers to log unhandled errors
|
||||||
|
process.on("uncaughtException", (error) => {
|
||||||
|
ui.writeError(`Safe-chain: Uncaught exception: ${error.message}`);
|
||||||
|
// @ts-expect-error writeVerbose will be added in a future PR
|
||||||
|
ui.writeVerbose(`Stack trace: ${error.stack}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("unhandledRejection", (reason) => {
|
||||||
|
ui.writeError(`Safe-chain: Unhandled promise rejection: ${reason}`);
|
||||||
|
if (reason instanceof Error) {
|
||||||
|
// @ts-expect-error writeVerbose will be added in a future PR
|
||||||
|
ui.writeVerbose(`Stack trace: ${reason.stack}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// This parses all the --safe-chain arguments and removes them from the args array
|
// This parses all the --safe-chain arguments and removes them from the args array
|
||||||
args = initializeCliArguments(args);
|
args = initializeCliArguments(args);
|
||||||
|
|
@ -52,7 +73,7 @@ export async function main(args) {
|
||||||
// Returning the exit code back to the caller allows the promise
|
// Returning the exit code back to the caller allows the promise
|
||||||
// to be awaited in the bin files and return the correct exit code
|
// to be awaited in the bin files and return the correct exit code
|
||||||
return packageManagerResult.status;
|
return packageManagerResult.status;
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
ui.writeError("Failed to check for malicious packages:", error.message);
|
ui.writeError("Failed to check for malicious packages:", error.message);
|
||||||
|
|
||||||
// Returning the exit code back to the caller allows the promise
|
// Returning the exit code back to the caller allows the promise
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {...string} commandArgs
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
export function matchesCommand(args, ...commandArgs) {
|
export function matchesCommand(args, ...commandArgs) {
|
||||||
if (args.length < commandArgs.length) {
|
if (args.length < commandArgs.length) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createBunPackageManager() {
|
export function createBunPackageManager() {
|
||||||
return {
|
return {
|
||||||
runCommand: (args) => runBunCommand("bun", args),
|
runCommand: (args) => runBunCommand("bun", args),
|
||||||
|
|
@ -13,6 +16,9 @@ export function createBunPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createBunxPackageManager() {
|
export function createBunxPackageManager() {
|
||||||
return {
|
return {
|
||||||
runCommand: (args) => runBunCommand("bunx", args),
|
runCommand: (args) => runBunCommand("bunx", args),
|
||||||
|
|
@ -24,14 +30,20 @@ export function createBunxPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<{status: number}>}
|
||||||
|
*/
|
||||||
async function runBunCommand(command, args) {
|
async function runBunCommand(command, args) {
|
||||||
try {
|
try {
|
||||||
const result = await safeSpawn(command, args, {
|
const result = await safeSpawn(command, args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
return { status: error.status };
|
return { status: error.status };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,32 @@ import {
|
||||||
} from "./pnpm/createPackageManager.js";
|
} from "./pnpm/createPackageManager.js";
|
||||||
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {{packageManagerName: PackageManager | null}}
|
||||||
|
*/
|
||||||
const state = {
|
const state = {
|
||||||
packageManagerName: null,
|
packageManagerName: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} GetDependencyUpdatesResult
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} version
|
||||||
|
* @property {string} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PackageManager
|
||||||
|
* @property {(args: string[]) => Promise<{ status: number }>} runCommand
|
||||||
|
* @property {(args: string[]) => boolean} isSupportedCommand
|
||||||
|
* @property {(args: string[]) => Promise<GetDependencyUpdatesResult[]> | GetDependencyUpdatesResult[]} getDependencyUpdatesForCommand
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} packageManagerName
|
||||||
|
*
|
||||||
|
* @return {PackageManager}
|
||||||
|
*/
|
||||||
export function initializePackageManager(packageManagerName) {
|
export function initializePackageManager(packageManagerName) {
|
||||||
if (packageManagerName === "npm") {
|
if (packageManagerName === "npm") {
|
||||||
state.packageManagerName = createNpmPackageManager();
|
state.packageManagerName = createNpmPackageManager();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,15 @@ import {
|
||||||
npmExecCommand,
|
npmExecCommand,
|
||||||
} from "./utils/npmCommands.js";
|
} from "./utils/npmCommands.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createNpmPackageManager() {
|
export function createNpmPackageManager() {
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isSupportedCommand(args) {
|
function isSupportedCommand(args) {
|
||||||
const scanner = findDependencyScannerForCommand(
|
const scanner = findDependencyScannerForCommand(
|
||||||
commandScannerMapping,
|
commandScannerMapping,
|
||||||
|
|
@ -17,6 +25,11 @@ export function createNpmPackageManager() {
|
||||||
return scanner.shouldScan(args);
|
return scanner.shouldScan(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {ReturnType<import("../currentPackageManager.js").PackageManager["getDependencyUpdatesForCommand"]>}
|
||||||
|
*/
|
||||||
function getDependencyUpdatesForCommand(args) {
|
function getDependencyUpdatesForCommand(args) {
|
||||||
const scanner = findDependencyScannerForCommand(
|
const scanner = findDependencyScannerForCommand(
|
||||||
commandScannerMapping,
|
commandScannerMapping,
|
||||||
|
|
@ -32,12 +45,22 @@ export function createNpmPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Record<string, import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner>}
|
||||||
|
*/
|
||||||
const commandScannerMapping = {
|
const commandScannerMapping = {
|
||||||
[npmInstallCommand]: commandArgumentScanner(),
|
[npmInstallCommand]: commandArgumentScanner(),
|
||||||
[npmUpdateCommand]: commandArgumentScanner(),
|
[npmUpdateCommand]: commandArgumentScanner(),
|
||||||
[npmExecCommand]: commandArgumentScanner({ ignoreDryRun: true }), // exec command doesn't support dry-run
|
[npmExecCommand]: commandArgumentScanner({ ignoreDryRun: true }), // exec command doesn't support dry-run
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Record<string, import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner>} scanners
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
|
||||||
|
*/
|
||||||
function findDependencyScannerForCommand(scanners, args) {
|
function findDependencyScannerForCommand(scanners, args) {
|
||||||
const command = getNpmCommandForArgs(args);
|
const command = getNpmCommandForArgs(args);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,29 @@ import { resolvePackageVersion } from "../../../api/npmApi.js";
|
||||||
import { parsePackagesFromInstallArgs } from "../parsing/parsePackagesFromInstallArgs.js";
|
import { parsePackagesFromInstallArgs } from "../parsing/parsePackagesFromInstallArgs.js";
|
||||||
import { hasDryRunArg } from "../utils/npmCommands.js";
|
import { hasDryRunArg } from "../utils/npmCommands.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ScanResult
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} version
|
||||||
|
* @property {string} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ScannerOptions
|
||||||
|
* @property {boolean} [ignoreDryRun]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CommandArgumentScanner
|
||||||
|
* @property {(args: string[]) => Promise<ScanResult[]> | ScanResult[]} scan
|
||||||
|
* @property {(args: string[]) => boolean} shouldScan
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ScannerOptions} [opts]
|
||||||
|
*
|
||||||
|
* @returns {CommandArgumentScanner}
|
||||||
|
*/
|
||||||
export function commandArgumentScanner(opts) {
|
export function commandArgumentScanner(opts) {
|
||||||
const ignoreDryRun = opts?.ignoreDryRun ?? false;
|
const ignoreDryRun = opts?.ignoreDryRun ?? false;
|
||||||
|
|
||||||
|
|
@ -10,14 +33,28 @@ export function commandArgumentScanner(opts) {
|
||||||
shouldScan: (args) => shouldScanDependencies(args, ignoreDryRun),
|
shouldScan: (args) => shouldScanDependencies(args, ignoreDryRun),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<ScanResult[]>}
|
||||||
|
*/
|
||||||
function scanDependencies(args) {
|
function scanDependencies(args) {
|
||||||
return checkChangesFromArgs(args);
|
return checkChangesFromArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {boolean} ignoreDryRun
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function shouldScanDependencies(args, ignoreDryRun) {
|
function shouldScanDependencies(args, ignoreDryRun) {
|
||||||
return ignoreDryRun || !hasDryRunArg(args);
|
return ignoreDryRun || !hasDryRunArg(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<ScanResult[]>}
|
||||||
|
*/
|
||||||
export async function checkChangesFromArgs(args) {
|
export async function checkChangesFromArgs(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const packageUpdates = parsePackagesFromInstallArgs(args);
|
const packageUpdates = parsePackagesFromInstallArgs(args);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
/**
|
||||||
|
* @returns {import("./commandArgumentScanner.js").CommandArgumentScanner}
|
||||||
|
*/
|
||||||
export function nullScanner() {
|
export function nullScanner() {
|
||||||
return {
|
return {
|
||||||
scan: () => [],
|
scan: () => [],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PackageDetail
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} version
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} NpmOption
|
||||||
|
* @property {string} name
|
||||||
|
* @property {number} numberOfParameters
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {PackageDetail[]}
|
||||||
|
*/
|
||||||
export function parsePackagesFromInstallArgs(args) {
|
export function parsePackagesFromInstallArgs(args) {
|
||||||
const changes = [];
|
/** @type {{name: string, version: string | null}[]} */
|
||||||
|
const changes = [];
|
||||||
let defaultTag = "latest";
|
let defaultTag = "latest";
|
||||||
|
|
||||||
// Skip first argument (install command)
|
// Skip first argument (install command)
|
||||||
|
|
@ -32,9 +49,13 @@ export function parsePackagesFromInstallArgs(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
return /** @type {PackageDetail[]} */ (changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {NpmOption | undefined}
|
||||||
|
*/
|
||||||
function getNpmOption(arg) {
|
function getNpmOption(arg) {
|
||||||
if (isNpmOptionWithParameter(arg)) {
|
if (isNpmOptionWithParameter(arg)) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -54,6 +75,10 @@ function getNpmOption(arg) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isNpmOptionWithParameter(arg) {
|
function isNpmOptionWithParameter(arg) {
|
||||||
const optionsWithParameters = [
|
const optionsWithParameters = [
|
||||||
"--access",
|
"--access",
|
||||||
|
|
@ -81,6 +106,10 @@ function isNpmOptionWithParameter(arg) {
|
||||||
return optionsWithParameters.includes(arg);
|
return optionsWithParameters.includes(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {{name: string, version: string | null}}
|
||||||
|
*/
|
||||||
function parsePackagename(arg) {
|
function parsePackagename(arg) {
|
||||||
arg = removeAlias(arg);
|
arg = removeAlias(arg);
|
||||||
const lastAtIndex = arg.lastIndexOf("@");
|
const lastAtIndex = arg.lastIndexOf("@");
|
||||||
|
|
@ -102,6 +131,10 @@ function parsePackagename(arg) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function removeAlias(arg) {
|
function removeAlias(arg) {
|
||||||
const aliasIndex = arg.indexOf("@npm:");
|
const aliasIndex = arg.indexOf("@npm:");
|
||||||
if (aliasIndex !== -1) {
|
if (aliasIndex !== -1) {
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {Promise<{status: number}>}
|
||||||
|
*/
|
||||||
export async function runNpm(args) {
|
export async function runNpm(args) {
|
||||||
try {
|
try {
|
||||||
const result = await safeSpawn("npm", args, {
|
const result = await safeSpawn("npm", args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
return { status: error.status };
|
return { status: error.status };
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -19,6 +25,10 @@ export async function runNpm(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<{status: number, output?: string}>}
|
||||||
|
*/
|
||||||
export async function dryRunNpmCommandAndOutput(args) {
|
export async function dryRunNpmCommandAndOutput(args) {
|
||||||
try {
|
try {
|
||||||
const result = await safeSpawn(
|
const result = await safeSpawn(
|
||||||
|
|
@ -26,6 +36,7 @@ export async function dryRunNpmCommandAndOutput(args) {
|
||||||
[...args, "--ignore-scripts", "--dry-run"],
|
[...args, "--ignore-scripts", "--dry-run"],
|
||||||
{
|
{
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -33,7 +44,7 @@ export async function dryRunNpmCommandAndOutput(args) {
|
||||||
status: result.status,
|
status: result.status,
|
||||||
output: result.status === 0 ? result.stdout : result.stderr,
|
output: result.status === 0 ? result.stdout : result.stderr,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
const output =
|
const output =
|
||||||
error.stdout?.toString() ??
|
error.stdout?.toString() ??
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// This was ran with the abbrev package to generate the abbrevs object below
|
// This was ran with the abbrev package to generate the abbrevs object below
|
||||||
// console.log(abbrev(commands.concat(Object.keys(aliases))));
|
// console.log(abbrev(commands.concat(Object.keys(aliases))));
|
||||||
|
/** @type {Record<string, string>} */
|
||||||
export const abbrevs = {
|
export const abbrevs = {
|
||||||
ac: "access",
|
ac: "access",
|
||||||
acc: "access",
|
acc: "access",
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ const commands = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// These must resolve to an entry in commands
|
// These must resolve to an entry in commands
|
||||||
|
/** @type {Record<string, string>} */
|
||||||
const aliases = {
|
const aliases = {
|
||||||
// aliases
|
// aliases
|
||||||
author: "owner",
|
author: "owner",
|
||||||
|
|
@ -138,6 +139,10 @@ const aliases = {
|
||||||
"add-user": "adduser",
|
"add-user": "adduser",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} c
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
export function deref(c) {
|
export function deref(c) {
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { deref } from "./cmd-list.js";
|
import { deref } from "./cmd-list.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
export function getNpmCommandForArgs(args) {
|
export function getNpmCommandForArgs(args) {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -13,6 +17,10 @@ export function getNpmCommandForArgs(args) {
|
||||||
return argCommand;
|
return argCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
export function hasDryRunArg(args) {
|
export function hasDryRunArg(args) {
|
||||||
return args.some((arg) => arg === "--dry-run");
|
return args.some((arg) => arg === "--dry-run");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
||||||
import { runNpx } from "./runNpxCommand.js";
|
import { runNpx } from "./runNpxCommand.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createNpxPackageManager() {
|
export function createNpxPackageManager() {
|
||||||
const scanner = commandArgumentScanner();
|
const scanner = commandArgumentScanner();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
||||||
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../../npm/dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
|
||||||
|
*/
|
||||||
export function commandArgumentScanner() {
|
export function commandArgumentScanner() {
|
||||||
return {
|
return {
|
||||||
scan: (args) => scanDependencies(args),
|
scan: (args) => scanDependencies(args),
|
||||||
shouldScan: () => true, // all npx commands need to be scanned, npx doesn't have dry-run
|
shouldScan: () => true, // all npx commands need to be scanned, npx doesn't have dry-run
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<import("../../npm/dependencyScanner/commandArgumentScanner.js").ScanResult[]>}
|
||||||
|
*/
|
||||||
function scanDependencies(args) {
|
function scanDependencies(args) {
|
||||||
return checkChangesFromArgs(args);
|
return checkChangesFromArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<import("../../npm/dependencyScanner/commandArgumentScanner.js").ScanResult[]>}
|
||||||
|
*/
|
||||||
export async function checkChangesFromArgs(args) {
|
export async function checkChangesFromArgs(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const packageUpdates = parsePackagesFromArguments(args);
|
const packageUpdates = parsePackagesFromArguments(args);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {{name: string, version: string}[]}
|
||||||
|
*/
|
||||||
export function parsePackagesFromArguments(args) {
|
export function parsePackagesFromArguments(args) {
|
||||||
let defaultTag = "latest";
|
let defaultTag = "latest";
|
||||||
|
|
||||||
|
|
@ -21,6 +26,10 @@ export function parsePackagesFromArguments(args) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {{name: string, numberOfParameters: number} | undefined}
|
||||||
|
*/
|
||||||
function getOption(arg) {
|
function getOption(arg) {
|
||||||
if (isOptionWithParameter(arg)) {
|
if (isOptionWithParameter(arg)) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -41,6 +50,10 @@ function getOption(arg) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isOptionWithParameter(arg) {
|
function isOptionWithParameter(arg) {
|
||||||
const optionsWithParameters = [
|
const optionsWithParameters = [
|
||||||
"--access",
|
"--access",
|
||||||
|
|
@ -68,6 +81,11 @@ function isOptionWithParameter(arg) {
|
||||||
return optionsWithParameters.includes(arg);
|
return optionsWithParameters.includes(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @param {string} defaultTag
|
||||||
|
* @returns {{name: string, version: string}}
|
||||||
|
*/
|
||||||
function parsePackagename(arg, defaultTag) {
|
function parsePackagename(arg, defaultTag) {
|
||||||
// format can be --package=name@version
|
// format can be --package=name@version
|
||||||
// in that case, we need to remove the --package= part
|
// in that case, we need to remove the --package= part
|
||||||
|
|
@ -97,6 +115,10 @@ function parsePackagename(arg, defaultTag) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function removeAlias(arg) {
|
function removeAlias(arg) {
|
||||||
// removes the alias.
|
// removes the alias.
|
||||||
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {Promise<{status: number}>}
|
||||||
|
*/
|
||||||
export async function runNpx(args) {
|
export async function runNpx(args) {
|
||||||
try {
|
try {
|
||||||
const result = await safeSpawn("npx", args, {
|
const result = await safeSpawn("npx", args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
return { status: error.status };
|
return { status: error.status };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { runPnpmCommand } from "./runPnpmCommand.js";
|
||||||
|
|
||||||
const scanner = commandArgumentScanner();
|
const scanner = commandArgumentScanner();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createPnpmPackageManager() {
|
export function createPnpmPackageManager() {
|
||||||
return {
|
return {
|
||||||
runCommand: (args) => runPnpmCommand(args, "pnpm"),
|
runCommand: (args) => runPnpmCommand(args, "pnpm"),
|
||||||
|
|
@ -23,6 +26,9 @@ export function createPnpmPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createPnpxPackageManager() {
|
export function createPnpxPackageManager() {
|
||||||
return {
|
return {
|
||||||
runCommand: (args) => runPnpmCommand(args, "pnpx"),
|
runCommand: (args) => runPnpmCommand(args, "pnpx"),
|
||||||
|
|
@ -32,6 +38,11 @@ export function createPnpxPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {boolean} isPnpx
|
||||||
|
* @returns {ReturnType<import("../currentPackageManager.js").PackageManager["getDependencyUpdatesForCommand"]>}
|
||||||
|
*/
|
||||||
function getDependencyUpdatesForCommand(args, isPnpx) {
|
function getDependencyUpdatesForCommand(args, isPnpx) {
|
||||||
if (isPnpx) {
|
if (isPnpx) {
|
||||||
return scanner.scan(args);
|
return scanner.scan(args);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
||||||
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../../npm/dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
|
||||||
|
*/
|
||||||
export function commandArgumentScanner() {
|
export function commandArgumentScanner() {
|
||||||
return {
|
return {
|
||||||
scan: (args) => scanDependencies(args),
|
scan: (args) => scanDependencies(args),
|
||||||
|
|
@ -8,6 +11,10 @@ export function commandArgumentScanner() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<import("../../npm/dependencyScanner/commandArgumentScanner.js").ScanResult[]>}
|
||||||
|
*/
|
||||||
async function scanDependencies(args) {
|
async function scanDependencies(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const packageUpdates = parsePackagesFromArguments(args);
|
const packageUpdates = parsePackagesFromArguments(args);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {{name: string, version: string}[]}
|
||||||
|
*/
|
||||||
export function parsePackagesFromArguments(args) {
|
export function parsePackagesFromArguments(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
let defaultTag = "latest";
|
let defaultTag = "latest";
|
||||||
|
|
@ -22,6 +26,10 @@ export function parsePackagesFromArguments(args) {
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {{name: string, numberOfParameters: number} | undefined}
|
||||||
|
*/
|
||||||
function getOption(arg) {
|
function getOption(arg) {
|
||||||
if (isOptionWithParameter(arg)) {
|
if (isOptionWithParameter(arg)) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -42,12 +50,21 @@ function getOption(arg) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isOptionWithParameter(arg) {
|
function isOptionWithParameter(arg) {
|
||||||
const optionsWithParameters = ["--C", "--dir"];
|
const optionsWithParameters = ["--C", "--dir"];
|
||||||
|
|
||||||
return optionsWithParameters.includes(arg);
|
return optionsWithParameters.includes(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @param {string} defaultTag
|
||||||
|
* @returns {{name: string, version: string}}
|
||||||
|
*/
|
||||||
function parsePackagename(arg, defaultTag) {
|
function parsePackagename(arg, defaultTag) {
|
||||||
// format can be --package=name@version
|
// format can be --package=name@version
|
||||||
// in that case, we need to remove the --package= part
|
// in that case, we need to remove the --package= part
|
||||||
|
|
@ -77,6 +94,10 @@ function parsePackagename(arg, defaultTag) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function removeAlias(arg) {
|
function removeAlias(arg) {
|
||||||
// removes the alias.
|
// removes the alias.
|
||||||
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,24 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {string} [toolName]
|
||||||
|
* @returns {Promise<{status: number}>}
|
||||||
|
*/
|
||||||
export async function runPnpmCommand(args, toolName = "pnpm") {
|
export async function runPnpmCommand(args, toolName = "pnpm") {
|
||||||
try {
|
try {
|
||||||
let result;
|
let result;
|
||||||
if (toolName === "pnpm") {
|
if (toolName === "pnpm") {
|
||||||
result = await safeSpawn("pnpm", args, {
|
result = await safeSpawn("pnpm", args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
});
|
});
|
||||||
} else if (toolName === "pnpx") {
|
} else if (toolName === "pnpx") {
|
||||||
result = await safeSpawn("pnpx", args, {
|
result = await safeSpawn("pnpx", args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -20,7 +27,7 @@ export async function runPnpmCommand(args, toolName = "pnpm") {
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
return { status: error.status };
|
return { status: error.status };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import { runYarnCommand } from "./runYarnCommand.js";
|
||||||
|
|
||||||
const scanner = commandArgumentScanner();
|
const scanner = commandArgumentScanner();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
*/
|
||||||
export function createYarnPackageManager() {
|
export function createYarnPackageManager() {
|
||||||
return {
|
return {
|
||||||
runCommand: runYarnCommand,
|
runCommand: runYarnCommand,
|
||||||
|
|
@ -18,6 +21,11 @@ export function createYarnPackageManager() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {...string} commandArgs
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function matchesCommand(args, ...commandArgs) {
|
function matchesCommand(args, ...commandArgs) {
|
||||||
if (args.length < commandArgs.length) {
|
if (args.length < commandArgs.length) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
||||||
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import("../../npm/dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
|
||||||
|
*/
|
||||||
export function commandArgumentScanner() {
|
export function commandArgumentScanner() {
|
||||||
return {
|
return {
|
||||||
scan: (args) => scanDependencies(args),
|
scan: (args) => scanDependencies(args),
|
||||||
|
|
@ -8,6 +11,10 @@ export function commandArgumentScanner() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<import("../../npm/dependencyScanner/commandArgumentScanner.js").ScanResult[]>}
|
||||||
|
*/
|
||||||
async function scanDependencies(args) {
|
async function scanDependencies(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const packageUpdates = parsePackagesFromArguments(args);
|
const packageUpdates = parsePackagesFromArguments(args);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {{name: string, version: string}[]}
|
||||||
|
*/
|
||||||
export function parsePackagesFromArguments(args) {
|
export function parsePackagesFromArguments(args) {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
let defaultTag = "latest";
|
let defaultTag = "latest";
|
||||||
|
|
@ -22,6 +26,11 @@ export function parsePackagesFromArguments(args) {
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
*
|
||||||
|
* @returns {{name: string, numberOfParameters: number} | undefined}
|
||||||
|
*/
|
||||||
function getOption(arg) {
|
function getOption(arg) {
|
||||||
if (isOptionWithParameter(arg)) {
|
if (isOptionWithParameter(arg)) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -42,6 +51,11 @@ function getOption(arg) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isOptionWithParameter(arg) {
|
function isOptionWithParameter(arg) {
|
||||||
const optionsWithParameters = [
|
const optionsWithParameters = [
|
||||||
"--use-yarnrc",
|
"--use-yarnrc",
|
||||||
|
|
@ -64,6 +78,12 @@ function isOptionWithParameter(arg) {
|
||||||
return optionsWithParameters.includes(arg);
|
return optionsWithParameters.includes(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @param {string} defaultTag
|
||||||
|
*
|
||||||
|
* @returns {{name: string, version: string}}
|
||||||
|
*/
|
||||||
function parsePackagename(arg, defaultTag) {
|
function parsePackagename(arg, defaultTag) {
|
||||||
// format can be --package=name@version
|
// format can be --package=name@version
|
||||||
// in that case, we need to remove the --package= part
|
// in that case, we need to remove the --package= part
|
||||||
|
|
@ -93,6 +113,10 @@ function parsePackagename(arg, defaultTag) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function removeAlias(arg) {
|
function removeAlias(arg) {
|
||||||
// removes the alias.
|
// removes the alias.
|
||||||
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,14 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {Promise<{status: number}>}
|
||||||
|
*/
|
||||||
export async function runYarnCommand(args) {
|
export async function runYarnCommand(args) {
|
||||||
try {
|
try {
|
||||||
|
// @ts-expect-error values of process.env can be string | undefined
|
||||||
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
||||||
await fixYarnProxyEnvironmentVariables(env);
|
await fixYarnProxyEnvironmentVariables(env);
|
||||||
|
|
||||||
|
|
@ -12,7 +18,7 @@ export async function runYarnCommand(args) {
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
return { status: error.status };
|
return { status: error.status };
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -22,6 +28,11 @@ export async function runYarnCommand(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Record<string, string>} env
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
async function fixYarnProxyEnvironmentVariables(env) {
|
async function fixYarnProxyEnvironmentVariables(env) {
|
||||||
// Yarn ignores standard proxy environment variable HTTPS_PROXY
|
// Yarn ignores standard proxy environment variable HTTPS_PROXY
|
||||||
// It does respect NODE_EXTRA_CA_CERTS for custom CA certificates though.
|
// It does respect NODE_EXTRA_CA_CERTS for custom CA certificates though.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ export function getCaCertPath() {
|
||||||
return path.join(certFolder, "ca-cert.pem");
|
return path.join(certFolder, "ca-cert.pem");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} hostname
|
||||||
|
* @returns {{privateKey: string, certificate: string}}
|
||||||
|
*/
|
||||||
export function generateCertForHost(hostname) {
|
export function generateCertForHost(hostname) {
|
||||||
let existingCert = certCache.get(hostname);
|
let existingCert = certCache.get(hostname);
|
||||||
if (existingCert) {
|
if (existingCert) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@ import { generateCertForHost } from "./certUtils.js";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("net").Socket} clientSocket
|
||||||
|
* @param {(target: string) => Promise<boolean>} isAllowed
|
||||||
|
*/
|
||||||
export function mitmConnect(req, clientSocket, isAllowed) {
|
export function mitmConnect(req, clientSocket, isAllowed) {
|
||||||
ui.writeVerbose(`Safe-chain: Set up MITM tunnel for ${req.url}`);
|
ui.writeVerbose(`Safe-chain: Set up MITM tunnel for ${req.url}`);
|
||||||
const { hostname } = new URL(`http://${req.url}`);
|
const { hostname } = new URL(`http://${req.url}`);
|
||||||
|
|
@ -18,6 +23,16 @@ export function mitmConnect(req, clientSocket, isAllowed) {
|
||||||
|
|
||||||
const server = createHttpsServer(hostname, isAllowed);
|
const server = createHttpsServer(hostname, isAllowed);
|
||||||
|
|
||||||
|
server.on("error", (err) => {
|
||||||
|
ui.writeError(`Safe-chain: HTTPS server error: ${err.message}`);
|
||||||
|
// @ts-expect-error Property 'headersSent' does not exist on type 'Socket'
|
||||||
|
if (!clientSocket.headersSent) {
|
||||||
|
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||||
|
} else if (clientSocket.writable) {
|
||||||
|
clientSocket.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Establish the connection
|
// Establish the connection
|
||||||
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||||
|
|
||||||
|
|
@ -25,10 +40,22 @@ export function mitmConnect(req, clientSocket, isAllowed) {
|
||||||
server.emit("connection", clientSocket);
|
server.emit("connection", clientSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} hostname
|
||||||
|
* @param {(target: string) => Promise<boolean>} isAllowed
|
||||||
|
* @returns {import("https").Server}
|
||||||
|
*/
|
||||||
function createHttpsServer(hostname, isAllowed) {
|
function createHttpsServer(hostname, isAllowed) {
|
||||||
const cert = generateCertForHost(hostname);
|
const cert = generateCertForHost(hostname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("http").ServerResponse} res
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
async function handleRequest(req, res) {
|
async function handleRequest(req, res) {
|
||||||
|
// @ts-expect-error req.url might be undefined
|
||||||
const pathAndQuery = getRequestPathAndQuery(req.url);
|
const pathAndQuery = getRequestPathAndQuery(req.url);
|
||||||
const targetUrl = `https://${hostname}${pathAndQuery}`;
|
const targetUrl = `https://${hostname}${pathAndQuery}`;
|
||||||
|
|
||||||
|
|
@ -43,15 +70,21 @@ function createHttpsServer(hostname, isAllowed) {
|
||||||
forwardRequest(req, hostname, res);
|
forwardRequest(req, hostname, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return https.createServer(
|
const server = https.createServer(
|
||||||
{
|
{
|
||||||
key: cert.privateKey,
|
key: cert.privateKey,
|
||||||
cert: cert.certificate,
|
cert: cert.certificate,
|
||||||
},
|
},
|
||||||
handleRequest
|
handleRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getRequestPathAndQuery(url) {
|
function getRequestPathAndQuery(url) {
|
||||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
|
|
@ -60,6 +93,11 @@ function getRequestPathAndQuery(url) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {string} hostname
|
||||||
|
* @param {import("http").ServerResponse} res
|
||||||
|
*/
|
||||||
function forwardRequest(req, hostname, res) {
|
function forwardRequest(req, hostname, res) {
|
||||||
const proxyReq = createProxyRequest(hostname, req, res);
|
const proxyReq = createProxyRequest(hostname, req, res);
|
||||||
|
|
||||||
|
|
@ -71,6 +109,11 @@ function forwardRequest(req, hostname, res) {
|
||||||
res.end("Bad Gateway");
|
res.end("Bad Gateway");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
req.on("error", (err) => {
|
||||||
|
ui.writeError(`Safe-chain: Error reading client request: ${err.message}`);
|
||||||
|
proxyReq.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
req.on("data", (chunk) => {
|
req.on("data", (chunk) => {
|
||||||
proxyReq.write(chunk);
|
proxyReq.write(chunk);
|
||||||
});
|
});
|
||||||
|
|
@ -83,7 +126,15 @@ function forwardRequest(req, hostname, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} hostname
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("http").ServerResponse} res
|
||||||
|
*
|
||||||
|
* @returns {import("http").ClientRequest}
|
||||||
|
*/
|
||||||
function createProxyRequest(hostname, req, res) {
|
function createProxyRequest(hostname, req, res) {
|
||||||
|
/** @type {import("http").RequestOptions} */
|
||||||
const options = {
|
const options = {
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
port: 443,
|
port: 443,
|
||||||
|
|
@ -92,7 +143,9 @@ function createProxyRequest(hostname, req, res) {
|
||||||
headers: { ...req.headers },
|
headers: { ...req.headers },
|
||||||
};
|
};
|
||||||
|
|
||||||
delete options.headers.host;
|
if (options.headers && "host" in options.headers) {
|
||||||
|
delete options.headers.host;
|
||||||
|
}
|
||||||
|
|
||||||
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
||||||
if (httpsProxy) {
|
if (httpsProxy) {
|
||||||
|
|
@ -100,6 +153,17 @@ function createProxyRequest(hostname, req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyReq = https.request(options, (proxyRes) => {
|
const proxyReq = https.request(options, (proxyRes) => {
|
||||||
|
proxyRes.on("error", (err) => {
|
||||||
|
ui.writeError(
|
||||||
|
`Safe-chain: Error reading upstream response: ${err.message}`
|
||||||
|
);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.writeHead(502);
|
||||||
|
res.end("Bad Gateway");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error statusCode might be undefined
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||||
proxyRes.pipe(res);
|
proxyRes.pipe(res);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
export const knownRegistries = ["registry.npmjs.org", "registry.yarnpkg.com"];
|
export const knownRegistries = ["registry.npmjs.org", "registry.yarnpkg.com"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {{packageName: string | undefined, version: string | undefined}}
|
||||||
|
*/
|
||||||
export function parsePackageFromUrl(url) {
|
export function parsePackageFromUrl(url) {
|
||||||
let packageName, version, registry;
|
let packageName, version, registry;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import * as https from "https";
|
import * as https from "https";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("http").ServerResponse} res
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
export function handleHttpProxyRequest(req, res) {
|
export function handleHttpProxyRequest(req, res) {
|
||||||
|
// @ts-expect-error req.url might be undefined
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
|
|
||||||
// The protocol for the plainHttpProxy should usually only be http:
|
// The protocol for the plainHttpProxy should usually only be http:
|
||||||
|
|
@ -20,9 +27,11 @@ export function handleHttpProxyRequest(req, res) {
|
||||||
|
|
||||||
const proxyRequest = protocol
|
const proxyRequest = protocol
|
||||||
.request(
|
.request(
|
||||||
|
// @ts-expect-error req.url might be undefined
|
||||||
req.url,
|
req.url,
|
||||||
{ method: req.method, headers: req.headers },
|
{ method: req.method, headers: req.headers },
|
||||||
(proxyRes) => {
|
(proxyRes) => {
|
||||||
|
// @ts-expect-error statusCode might be undefined
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||||
proxyRes.pipe(res);
|
proxyRes.pipe(res);
|
||||||
|
|
||||||
|
|
@ -43,8 +52,13 @@ export function handleHttpProxyRequest(req, res) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.on("error", (err) => {
|
.on("error", (err) => {
|
||||||
res.writeHead(502);
|
if (!res.headersSent) {
|
||||||
res.end(`Bad Gateway: ${err.message}`);
|
res.writeHead(502);
|
||||||
|
res.end(`Bad Gateway: ${err.message}`);
|
||||||
|
} else {
|
||||||
|
// Headers already sent, just destroy the response
|
||||||
|
res.destroy();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on("error", () => {
|
req.on("error", () => {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import { ui } from "../environment/userInteraction.js";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
const SERVER_STOP_TIMEOUT_MS = 1000;
|
const SERVER_STOP_TIMEOUT_MS = 1000;
|
||||||
|
/**
|
||||||
|
* @type {{port: number | null, blockedRequests: {packageName: string, version: string, url: string}[]}}
|
||||||
|
*/
|
||||||
const state = {
|
const state = {
|
||||||
port: null,
|
port: null,
|
||||||
blockedRequests: [],
|
blockedRequests: [],
|
||||||
|
|
@ -24,6 +27,9 @@ export function createSafeChainProxy() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
function getSafeChainProxyEnvironmentVariables() {
|
function getSafeChainProxyEnvironmentVariables() {
|
||||||
if (!state.port) {
|
if (!state.port) {
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -36,6 +42,11 @@ function getSafeChainProxyEnvironmentVariables() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Record<string, string>} env
|
||||||
|
*
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
export function mergeSafeChainProxyEnvironmentVariables(env) {
|
export function mergeSafeChainProxyEnvironmentVariables(env) {
|
||||||
const proxyEnv = getSafeChainProxyEnvironmentVariables();
|
const proxyEnv = getSafeChainProxyEnvironmentVariables();
|
||||||
|
|
||||||
|
|
@ -67,6 +78,11 @@ function createProxyServer() {
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").Server} server
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
function startServer(server) {
|
function startServer(server) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Passing port 0 makes the OS assign an available port
|
// Passing port 0 makes the OS assign an available port
|
||||||
|
|
@ -86,6 +102,11 @@ function startServer(server) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").Server} server
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
function stopServer(server) {
|
function stopServer(server) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -99,10 +120,18 @@ function stopServer(server) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("net").Socket} clientSocket
|
||||||
|
* @param {Buffer} head
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function handleConnect(req, clientSocket, head) {
|
function handleConnect(req, clientSocket, head) {
|
||||||
// CONNECT method is used for HTTPS requests
|
// CONNECT method is used for HTTPS requests
|
||||||
// It establishes a tunnel to the server identified by the request URL
|
// It establishes a tunnel to the server identified by the request URL
|
||||||
|
|
||||||
|
// @ts-expect-error req.url might be undefined
|
||||||
if (knownRegistries.some((reg) => req.url.includes(reg))) {
|
if (knownRegistries.some((reg) => req.url.includes(reg))) {
|
||||||
// For npm and yarn registries, we want to intercept and inspect the traffic
|
// For npm and yarn registries, we want to intercept and inspect the traffic
|
||||||
// so we can block packages with malware
|
// so we can block packages with malware
|
||||||
|
|
@ -114,6 +143,10 @@ function handleConnect(req, clientSocket, head) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
async function isAllowedUrl(url) {
|
async function isAllowedUrl(url) {
|
||||||
const { packageName, version } = parsePackageFromUrl(url);
|
const { packageName, version } = parsePackageFromUrl(url);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
import * as net from "net";
|
import * as net from "net";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("net").Socket} clientSocket
|
||||||
|
* @param {Buffer} head
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
export function tunnelRequest(req, clientSocket, head) {
|
export function tunnelRequest(req, clientSocket, head) {
|
||||||
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
||||||
|
|
||||||
|
|
@ -21,15 +28,17 @@ export function tunnelRequest(req, clientSocket, head) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("net").Socket} clientSocket
|
||||||
|
* @param {Buffer} head
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function tunnelRequestToDestination(req, clientSocket, head) {
|
function tunnelRequestToDestination(req, clientSocket, head) {
|
||||||
const { port, hostname } = new URL(`http://${req.url}`);
|
const { port, hostname } = new URL(`http://${req.url}`);
|
||||||
|
|
||||||
clientSocket.on("error", () => {
|
// @ts-expect-error port from URL is a string but net.connect accepts number
|
||||||
// NO-OP
|
|
||||||
// This can happen if the client TCP socket sends RST instead of FIN.
|
|
||||||
// Not subscribing to 'close' event will cause node to throw and crash.
|
|
||||||
});
|
|
||||||
|
|
||||||
const serverSocket = net.connect(port || 443, hostname, () => {
|
const serverSocket = net.connect(port || 443, hostname, () => {
|
||||||
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||||
serverSocket.write(head);
|
serverSocket.write(head);
|
||||||
|
|
@ -37,6 +46,14 @@ function tunnelRequestToDestination(req, clientSocket, head) {
|
||||||
clientSocket.pipe(serverSocket);
|
clientSocket.pipe(serverSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clientSocket.on("error", () => {
|
||||||
|
// This can happen if the client TCP socket sends RST instead of FIN.
|
||||||
|
// Not subscribing to 'error' event will cause node to throw and crash.
|
||||||
|
if (serverSocket.writable) {
|
||||||
|
serverSocket.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
serverSocket.on("error", (err) => {
|
serverSocket.on("error", (err) => {
|
||||||
ui.writeError(
|
ui.writeError(
|
||||||
`Safe-chain: error connecting to ${hostname}:${port} - ${err.message}`
|
`Safe-chain: error connecting to ${hostname}:${port} - ${err.message}`
|
||||||
|
|
@ -47,11 +64,18 @@ function tunnelRequestToDestination(req, clientSocket, head) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("http").IncomingMessage} req
|
||||||
|
* @param {import("net").Socket} clientSocket
|
||||||
|
* @param {Buffer} head
|
||||||
|
* @param {string} proxyUrl
|
||||||
|
*/
|
||||||
function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) {
|
function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) {
|
||||||
const { port, hostname } = new URL(`http://${req.url}`);
|
const { port, hostname } = new URL(`http://${req.url}`);
|
||||||
const proxy = new URL(proxyUrl);
|
const proxy = new URL(proxyUrl);
|
||||||
|
|
||||||
// Connect to proxy server
|
// Connect to proxy server
|
||||||
|
// @ts-expect-error net.connect wants port as number but proxy.port is string
|
||||||
const proxySocket = net.connect({
|
const proxySocket = net.connect({
|
||||||
host: proxy.hostname,
|
host: proxy.hostname,
|
||||||
port: proxy.port,
|
port: proxy.port,
|
||||||
|
|
@ -103,6 +127,13 @@ function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) {
|
||||||
if (clientSocket.writable) {
|
if (clientSocket.writable) {
|
||||||
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ui.writeError(
|
||||||
|
`Safe-chain: proxy socket error after connection - ${err.message}`
|
||||||
|
);
|
||||||
|
if (clientSocket.writable) {
|
||||||
|
clientSocket.end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,25 @@ import {
|
||||||
openMalwareDatabase,
|
openMalwareDatabase,
|
||||||
} from "../malwareDatabase.js";
|
} from "../malwareDatabase.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PackageChange
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} version
|
||||||
|
* @property {string} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} AuditResult
|
||||||
|
* @property {PackageChange[]} allowedChanges
|
||||||
|
* @property {(PackageChange & {reason: string})[]} disallowedChanges
|
||||||
|
* @property {boolean} isAllowed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PackageChange[]} changes
|
||||||
|
*
|
||||||
|
* @returns {Promise<AuditResult>}
|
||||||
|
*/
|
||||||
export async function auditChanges(changes) {
|
export async function auditChanges(changes) {
|
||||||
const allowedChanges = [];
|
const allowedChanges = [];
|
||||||
const disallowedChanges = [];
|
const disallowedChanges = [];
|
||||||
|
|
@ -41,6 +60,10 @@ export async function auditChanges(changes) {
|
||||||
return auditResults;
|
return auditResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{name: string, version: string, type: string}[]} changes
|
||||||
|
* @returns {Promise<{name: string, version: string, status: string}[]>}
|
||||||
|
*/
|
||||||
async function getPackagesWithMalware(changes) {
|
async function getPackagesWithMalware(changes) {
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ import chalk from "chalk";
|
||||||
import { getPackageManager } from "../packagemanager/currentPackageManager.js";
|
import { getPackageManager } from "../packagemanager/currentPackageManager.js";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
export function shouldScanCommand(args) {
|
export function shouldScanCommand(args) {
|
||||||
if (!args || args.length === 0) {
|
if (!args || args.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -13,6 +18,11 @@ export function shouldScanCommand(args) {
|
||||||
return getPackageManager().isSupportedCommand(args);
|
return getPackageManager().isSupportedCommand(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {Promise<number | never[]>}
|
||||||
|
*/
|
||||||
export async function scanCommand(args) {
|
export async function scanCommand(args) {
|
||||||
if (!shouldScanCommand(args)) {
|
if (!shouldScanCommand(args)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -23,6 +33,7 @@ export async function scanCommand(args) {
|
||||||
const spinner = ui.startProcess(
|
const spinner = ui.startProcess(
|
||||||
"Safe-chain: Scanning for malicious packages..."
|
"Safe-chain: Scanning for malicious packages..."
|
||||||
);
|
);
|
||||||
|
/** @type {import("./audit/index.js").AuditResult | undefined} */
|
||||||
let audit;
|
let audit;
|
||||||
|
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
|
|
@ -44,7 +55,7 @@ export async function scanCommand(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
audit = await auditChanges(changes);
|
audit = await auditChanges(changes);
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
spinner.fail(`Safe-chain: Error while scanning.`);
|
spinner.fail(`Safe-chain: Error while scanning.`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +80,12 @@ export async function scanCommand(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("./audit/index.js").PackageChange[]} changes
|
||||||
|
* @param spinner {import("../environment/userInteraction.js").Spinner}
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
function printMaliciousChanges(changes, spinner) {
|
function printMaliciousChanges(changes, spinner) {
|
||||||
spinner.fail("Safe-chain: " + chalk.bold("Malicious changes detected:"));
|
spinner.fail("Safe-chain: " + chalk.bold("Malicious changes detected:"));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ import {
|
||||||
} from "../config/configFile.js";
|
} from "../config/configFile.js";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} MalwareDatabase
|
||||||
|
* @property {function(string, string): string} getPackageStatus
|
||||||
|
* @property {function(string, string): boolean} isMalware
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {MalwareDatabase | null} */
|
||||||
let cachedMalwareDatabase = null;
|
let cachedMalwareDatabase = null;
|
||||||
|
|
||||||
export async function openMalwareDatabase() {
|
export async function openMalwareDatabase() {
|
||||||
|
|
@ -17,6 +24,11 @@ export async function openMalwareDatabase() {
|
||||||
|
|
||||||
const malwareDatabase = await getMalwareDatabase();
|
const malwareDatabase = await getMalwareDatabase();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} version
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getPackageStatus(name, version) {
|
function getPackageStatus(name, version) {
|
||||||
const packageData = malwareDatabase.find(
|
const packageData = malwareDatabase.find(
|
||||||
(pkg) =>
|
(pkg) =>
|
||||||
|
|
@ -31,7 +43,7 @@ export async function openMalwareDatabase() {
|
||||||
return packageData.reason;
|
return packageData.reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This implicitely caches the malware database
|
// This implicitly caches the malware database
|
||||||
// that's closed over by the getPackageStatus function
|
// that's closed over by the getPackageStatus function
|
||||||
cachedMalwareDatabase = {
|
cachedMalwareDatabase = {
|
||||||
getPackageStatus,
|
getPackageStatus,
|
||||||
|
|
@ -43,6 +55,9 @@ export async function openMalwareDatabase() {
|
||||||
return cachedMalwareDatabase;
|
return cachedMalwareDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<import("../api/aikido.js").MalwarePackage[]>}
|
||||||
|
*/
|
||||||
async function getMalwareDatabase() {
|
async function getMalwareDatabase() {
|
||||||
const { malwareDatabase: cachedDatabase, version: cachedVersion } =
|
const { malwareDatabase: cachedDatabase, version: cachedVersion } =
|
||||||
readDatabaseFromLocalCache();
|
readDatabaseFromLocalCache();
|
||||||
|
|
@ -56,10 +71,11 @@ async function getMalwareDatabase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { malwareDatabase, version } = await fetchMalwareDatabase();
|
const { malwareDatabase, version } = await fetchMalwareDatabase();
|
||||||
|
// @ts-expect-error version can be undefined
|
||||||
writeDatabaseToLocalCache(malwareDatabase, version);
|
writeDatabaseToLocalCache(malwareDatabase, version);
|
||||||
|
|
||||||
return malwareDatabase;
|
return malwareDatabase;
|
||||||
} catch (error) {
|
} catch (/** @type any */ error) {
|
||||||
if (cachedDatabase) {
|
if (cachedDatabase) {
|
||||||
ui.writeWarning(
|
ui.writeWarning(
|
||||||
"Failed to fetch the latest malware database. Using cached version."
|
"Failed to fetch the latest malware database. Using cached version."
|
||||||
|
|
@ -70,6 +86,11 @@ async function getMalwareDatabase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} status
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function isMalwareStatus(status) {
|
function isMalwareStatus(status) {
|
||||||
let malwareStatus = status.toUpperCase();
|
let malwareStatus = status.toUpperCase();
|
||||||
return malwareStatus === MALWARE_STATUS_MALWARE;
|
return malwareStatus === MALWARE_STATUS_MALWARE;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,15 @@ import * as os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} AikidoTool
|
||||||
|
* @property {string} tool
|
||||||
|
* @property {string} aikidoCommand
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {AikidoTool[]}
|
||||||
|
*/
|
||||||
export const knownAikidoTools = [
|
export const knownAikidoTools = [
|
||||||
{ tool: "npm", aikidoCommand: "aikido-npm" },
|
{ tool: "npm", aikidoCommand: "aikido-npm" },
|
||||||
{ tool: "npx", aikidoCommand: "aikido-npx" },
|
{ tool: "npx", aikidoCommand: "aikido-npx" },
|
||||||
|
|
@ -30,6 +39,11 @@ export function getPackageManagerList() {
|
||||||
return `${tools.join(", ")}, and ${lastTool} commands`;
|
return `${tools.join(", ")}, and ${lastTool} commands`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} executableName
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
export function doesExecutableExistOnSystem(executableName) {
|
export function doesExecutableExistOnSystem(executableName) {
|
||||||
if (os.platform() === "win32") {
|
if (os.platform() === "win32") {
|
||||||
const result = spawnSync("where", [executableName], { stdio: "ignore" });
|
const result = spawnSync("where", [executableName], { stdio: "ignore" });
|
||||||
|
|
@ -40,6 +54,13 @@ export function doesExecutableExistOnSystem(executableName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {RegExp} pattern
|
||||||
|
* @param {string} [eol]
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
export function removeLinesMatchingPattern(filePath, pattern, eol) {
|
export function removeLinesMatchingPattern(filePath, pattern, eol) {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -54,6 +75,12 @@ export function removeLinesMatchingPattern(filePath, pattern, eol) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLineLength = 100;
|
const maxLineLength = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} line
|
||||||
|
* @param {RegExp} pattern
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function shouldRemoveLine(line, pattern) {
|
function shouldRemoveLine(line, pattern) {
|
||||||
const isPatternMatch = pattern.test(line);
|
const isPatternMatch = pattern.test(line);
|
||||||
|
|
||||||
|
|
@ -82,6 +109,13 @@ function shouldRemoveLine(line, pattern) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} line
|
||||||
|
* @param {string} [eol]
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
export function addLineToFile(filePath, line, eol) {
|
export function addLineToFile(filePath, line, eol) {
|
||||||
createFileIfNotExists(filePath);
|
createFileIfNotExists(filePath);
|
||||||
|
|
||||||
|
|
@ -92,6 +126,11 @@ export function addLineToFile(filePath, line, eol) {
|
||||||
fs.writeFileSync(filePath, updatedContent, "utf-8");
|
fs.writeFileSync(filePath, updatedContent, "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function createFileIfNotExists(filePath) {
|
function createFileIfNotExists(filePath) {
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,11 @@ export async function setupCi() {
|
||||||
ui.writeInformation(`Added shims directory to PATH for CI environments.`);
|
ui.writeInformation(`Added shims directory to PATH for CI environments.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} shimsDir
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function createUnixShims(shimsDir) {
|
function createUnixShims(shimsDir) {
|
||||||
// Read the template file
|
// Read the template file
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -64,6 +69,11 @@ function createUnixShims(shimsDir) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} shimsDir
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function createWindowsShims(shimsDir) {
|
function createWindowsShims(shimsDir) {
|
||||||
// Read the template file
|
// Read the template file
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -97,6 +107,11 @@ function createWindowsShims(shimsDir) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} shimsDir
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function createShims(shimsDir) {
|
function createShims(shimsDir) {
|
||||||
if (os.platform() === "win32") {
|
if (os.platform() === "win32") {
|
||||||
createWindowsShims(shimsDir);
|
createWindowsShims(shimsDir);
|
||||||
|
|
@ -105,6 +120,11 @@ function createShims(shimsDir) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} shimsDir
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
function modifyPathForCi(shimsDir) {
|
function modifyPathForCi(shimsDir) {
|
||||||
if (process.env.GITHUB_PATH) {
|
if (process.env.GITHUB_PATH) {
|
||||||
// In GitHub Actions, append the shims directory to GITHUB_PATH
|
// In GitHub Actions, append the shims directory to GITHUB_PATH
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export async function setup() {
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
ui.writeError(
|
ui.writeError(
|
||||||
`Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`
|
`Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`
|
||||||
);
|
);
|
||||||
|
|
@ -53,6 +53,7 @@ export async function setup() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the setup function for the given shell and reports the result.
|
* Calls the setup function for the given shell and reports the result.
|
||||||
|
* @param {import("./shellDetection.js").Shell} shell
|
||||||
*/
|
*/
|
||||||
function setupShell(shell) {
|
function setupShell(shell) {
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
@ -60,7 +61,7 @@ function setupShell(shell) {
|
||||||
try {
|
try {
|
||||||
shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases
|
shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases
|
||||||
success = shell.setup(knownAikidoTools);
|
success = shell.setup(knownAikidoTools);
|
||||||
} catch (err) {
|
} catch (/** @type {any} */ err) {
|
||||||
success = false;
|
success = false;
|
||||||
error = err;
|
error = err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,17 @@ import windowsPowershell from "./supported-shells/windowsPowershell.js";
|
||||||
import fish from "./supported-shells/fish.js";
|
import fish from "./supported-shells/fish.js";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Shell
|
||||||
|
* @property {string} name
|
||||||
|
* @property {() => boolean} isInstalled
|
||||||
|
* @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} setup
|
||||||
|
* @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} teardown
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Shell[]}
|
||||||
|
*/
|
||||||
export function detectShells() {
|
export function detectShells() {
|
||||||
let possibleShells = [zsh, bash, powershell, windowsPowershell, fish];
|
let possibleShells = [zsh, bash, powershell, windowsPowershell, fish];
|
||||||
let availableShells = [];
|
let availableShells = [];
|
||||||
|
|
@ -15,7 +26,7 @@ export function detectShells() {
|
||||||
availableShells.push(shell);
|
availableShells.push(shell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
ui.writeError(
|
ui.writeError(
|
||||||
`We were not able to detect which shells are installed on your system. Please check your shell configuration. Error: ${error.message}`
|
`We were not able to detect which shells are installed on your system. Please check your shell configuration. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ function isInstalled() {
|
||||||
return doesExecutableExistOnSystem(executableName);
|
return doesExecutableExistOnSystem(executableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../helpers.js").AikidoTool[]} tools
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function teardown(tools) {
|
function teardown(tools) {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
|
|
@ -57,13 +62,18 @@ function getStartupFile() {
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
return windowsFixPath(path);
|
return windowsFixPath(path);
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function windowsFixPath(path) {
|
function windowsFixPath(path) {
|
||||||
try {
|
try {
|
||||||
if (os.platform() !== "win32") {
|
if (os.platform() !== "win32") {
|
||||||
|
|
@ -93,6 +103,11 @@ function hasCygpath() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function cygpathw(path) {
|
function cygpathw(path) {
|
||||||
try {
|
try {
|
||||||
var result = spawnSync("cygpath", ["-w", path], {
|
var result = spawnSync("cygpath", ["-w", path], {
|
||||||
|
|
@ -108,6 +123,9 @@ function cygpathw(path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../shellDetection.js").Shell}
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: shellName,
|
name: shellName,
|
||||||
isInstalled,
|
isInstalled,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@ function isInstalled() {
|
||||||
return doesExecutableExistOnSystem(executableName);
|
return doesExecutableExistOnSystem(executableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../helpers.js").AikidoTool[]} tools
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function teardown(tools) {
|
function teardown(tools) {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
|
|
@ -54,13 +59,16 @@ function getStartupFile() {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
shell: executableName,
|
shell: executableName,
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../shellDetection.js").Shell}
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: shellName,
|
name: shellName,
|
||||||
isInstalled,
|
isInstalled,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ function isInstalled() {
|
||||||
return doesExecutableExistOnSystem(executableName);
|
return doesExecutableExistOnSystem(executableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../helpers.js").AikidoTool[]} tools
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function teardown(tools) {
|
function teardown(tools) {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
|
|
@ -50,13 +55,16 @@ function getStartupFile() {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
shell: executableName,
|
shell: executableName,
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../shellDetection.js").Shell}
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: shellName,
|
name: shellName,
|
||||||
isInstalled,
|
isInstalled,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ function isInstalled() {
|
||||||
return doesExecutableExistOnSystem(executableName);
|
return doesExecutableExistOnSystem(executableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../helpers.js").AikidoTool[]} tools
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function teardown(tools) {
|
function teardown(tools) {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
|
|
@ -50,13 +55,16 @@ function getStartupFile() {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
shell: executableName,
|
shell: executableName,
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../shellDetection.js").Shell}
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: shellName,
|
name: shellName,
|
||||||
isInstalled,
|
isInstalled,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@ function isInstalled() {
|
||||||
return doesExecutableExistOnSystem(executableName);
|
return doesExecutableExistOnSystem(executableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../helpers.js").AikidoTool[]} tools
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function teardown(tools) {
|
function teardown(tools) {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
|
|
@ -54,7 +59,7 @@ function getStartupFile() {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
shell: executableName,
|
shell: executableName,
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import { ui } from "../environment/userInteraction.js";
|
||||||
import { detectShells } from "./shellDetection.js";
|
import { detectShells } from "./shellDetection.js";
|
||||||
import { knownAikidoTools, getPackageManagerList } from "./helpers.js";
|
import { knownAikidoTools, getPackageManagerList } from "./helpers.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
export async function teardown() {
|
export async function teardown() {
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
chalk.bold("Removing shell aliases.") +
|
chalk.bold("Removing shell aliases.") +
|
||||||
|
|
@ -52,7 +55,7 @@ export async function teardown() {
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (/** @type {any} */ error) {
|
||||||
ui.writeError(
|
ui.writeError(
|
||||||
`Failed to remove shell aliases: ${error.message}. Please check your shell configuration.`
|
`Failed to remove shell aliases: ${error.message}. Please check your shell configuration.`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import { spawn, execSync } from "child_process";
|
import { spawn, execSync } from "child_process";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function sanitizeShellArgument(arg) {
|
function sanitizeShellArgument(arg) {
|
||||||
// If argument contains shell metacharacters, wrap in double quotes
|
// If argument contains shell metacharacters, wrap in double quotes
|
||||||
// and escape characters that are special even inside double quotes
|
// and escape characters that are special even inside double quotes
|
||||||
|
|
@ -11,6 +16,11 @@ function sanitizeShellArgument(arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function hasShellMetaChars(arg) {
|
function hasShellMetaChars(arg) {
|
||||||
// Shell metacharacters that need escaping
|
// Shell metacharacters that need escaping
|
||||||
// These characters have special meaning in shells and need to be quoted
|
// These characters have special meaning in shells and need to be quoted
|
||||||
|
|
@ -20,12 +30,23 @@ function hasShellMetaChars(arg) {
|
||||||
return shellMetaChars.test(arg);
|
return shellMetaChars.test(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} arg
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function escapeDoubleQuoteContent(arg) {
|
function escapeDoubleQuoteContent(arg) {
|
||||||
// Escape special characters for shell safety
|
// Escape special characters for shell safety
|
||||||
// This escapes ", $, `, and \ by prefixing them with a backslash
|
// This escapes ", $, `, and \ by prefixing them with a backslash
|
||||||
return arg.replace(/(["`$\\])/g, "\\$1");
|
return arg.replace(/(["`$\\])/g, "\\$1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
* @param {string[]} args
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function buildCommand(command, args) {
|
function buildCommand(command, args) {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
return command;
|
return command;
|
||||||
|
|
@ -36,11 +57,17 @@ function buildCommand(command, args) {
|
||||||
return `${command} ${escapedArgs.join(" ")}`;
|
return `${command} ${escapedArgs.join(" ")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function resolveCommandPath(command) {
|
function resolveCommandPath(command) {
|
||||||
// command will be "npm", "yarn", etc.
|
// command will be "npm", "yarn", etc.
|
||||||
// Use 'command -v' to find the full path
|
// Use 'command -v' to find the full path
|
||||||
const fullPath = execSync(`command -v ${command}`, {
|
const fullPath = execSync(`command -v ${command}`, {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
|
// @ts-expect-error shell is a string option
|
||||||
shell: true,
|
shell: true,
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
|
|
@ -51,6 +78,13 @@ function resolveCommandPath(command) {
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {import("child_process").SpawnOptions} options
|
||||||
|
*
|
||||||
|
* @returns {Promise<{status: number, stdout: string, stderr: string}>}
|
||||||
|
*/
|
||||||
export async function safeSpawn(command, args, options = {}) {
|
export async function safeSpawn(command, args, options = {}) {
|
||||||
// The command is always one of our supported package managers.
|
// The command is always one of our supported package managers.
|
||||||
// It should always be alphanumeric or _ or -
|
// It should always be alphanumeric or _ or -
|
||||||
|
|
@ -87,6 +121,7 @@ export async function safeSpawn(command, args, options = {}) {
|
||||||
|
|
||||||
child.on("close", (code) => {
|
child.on("close", (code) => {
|
||||||
resolve({
|
resolve({
|
||||||
|
// @ts-expect-error code can be null
|
||||||
status: code,
|
status: code,
|
||||||
stdout: stdout,
|
stdout: stdout,
|
||||||
stderr: stderr,
|
stderr: stderr,
|
||||||
|
|
|
||||||
21
packages/safe-chain/tsconfig.json
Normal file
21
packages/safe-chain/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2023"],
|
||||||
|
"module": "node16",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "node16",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.js",
|
||||||
|
"bin/**/*.js"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"src/**/*.spec.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue