mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-22 00:31:17 +00:00
feat: add validation job to GitHub Actions for desktop packaging
- Introduced a new job `validate` in the GitHub Actions workflow to validate the desktop package. - Added steps for checking out the repository, setting up Node.js, installing dependencies, type checking, and running unit tests. - Updated the `package` job to depend on the `validate` job. chore: update package-lock.json and package.json for vitest integration - Added `vitest` as a development dependency in `package.json`. - Updated `typecheck` script to include a new TypeScript configuration for vitest. - Updated `package-lock.json` to reflect the new dependencies. refactor: modularize runtime path handling in main process - Created a new module `runtime-paths.ts` to handle static path normalization and request handling. - Refactored `main.ts` to utilize the new `normalizeStaticPath`, `getRequestedPath`, and `getPackagedRendererEntry` functions. test: add unit tests for runtime path utilities - Implemented tests for `normalizeStaticPath`, `getRequestedPath`, and `getPackagedRendererEntry` using Vitest. - Ensured coverage for directory traversal attempts and null-byte paths. chore: configure Vitest for testing - Added `vitest.config.ts` for Vitest configuration. - Created `tsconfig.vitest.json` to include types for Vitest in TypeScript compilation. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
1274b4bc99
commit
610a102397
11 changed files with 602 additions and 48 deletions
34
.github/workflows/desktop-packaging.yml
vendored
34
.github/workflows/desktop-packaging.yml
vendored
|
|
@ -14,10 +14,37 @@ concurrency:
|
|||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate desktop package
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: gitnexus-desktop/package-lock.json
|
||||
|
||||
- name: Install desktop dependencies
|
||||
run: npm ci
|
||||
working-directory: gitnexus-desktop
|
||||
|
||||
- name: Typecheck desktop package
|
||||
run: npm run typecheck
|
||||
working-directory: gitnexus-desktop
|
||||
|
||||
- name: Run desktop unit tests
|
||||
run: npm test
|
||||
working-directory: gitnexus-desktop
|
||||
|
||||
package:
|
||||
name: Build ${{ matrix.label }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
needs: validate
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
|
|
@ -85,8 +112,13 @@ jobs:
|
|||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||
|
||||
- name: Start Xvfb
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
Xvfb :99 -screen 0 1024x768x24 &
|
||||
echo "DISPLAY=:99" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Smoke test unpacked desktop bundle
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: node scripts/smoke-unpacked.mjs
|
||||
working-directory: gitnexus-desktop
|
||||
|
||||
|
|
|
|||
361
gitnexus-desktop/package-lock.json
generated
361
gitnexus-desktop/package-lock.json
generated
|
|
@ -17,7 +17,8 @@
|
|||
"electron-builder": "26.8.1",
|
||||
"electron-vite": "^5.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.0.0"
|
||||
"vite": "^7.0.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
|
|
@ -1791,6 +1792,13 @@
|
|||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"dev": true,
|
||||
|
|
@ -1850,6 +1858,17 @@
|
|||
"@types/responselike": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.13",
|
||||
"dev": true,
|
||||
|
|
@ -1858,6 +1877,13 @@
|
|||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"dev": true,
|
||||
|
|
@ -1969,6 +1995,119 @@
|
|||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
|
||||
"integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz",
|
||||
"integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.5",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz",
|
||||
"integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz",
|
||||
"integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.5",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz",
|
||||
"integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz",
|
||||
"integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz",
|
||||
"integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.13",
|
||||
"dev": true,
|
||||
|
|
@ -2207,6 +2346,16 @@
|
|||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/astral-regex": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
||||
|
|
@ -2590,6 +2739,16 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"dev": true,
|
||||
|
|
@ -3371,6 +3530,13 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
||||
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
|
|
@ -3462,6 +3628,26 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exponential-backoff": {
|
||||
"version": "3.1.3",
|
||||
"dev": true,
|
||||
|
|
@ -4650,6 +4836,17 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
|
|
@ -4768,6 +4965,13 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pe-library": {
|
||||
"version": "0.4.1",
|
||||
"dev": true,
|
||||
|
|
@ -5186,6 +5390,13 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"dev": true,
|
||||
|
|
@ -5306,6 +5517,13 @@
|
|||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stat-mode": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -5314,6 +5532,13 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
|
||||
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"dev": true,
|
||||
|
|
@ -5487,6 +5712,23 @@
|
|||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
|
||||
"integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"dev": true,
|
||||
|
|
@ -5502,6 +5744,16 @@
|
|||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
|
||||
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"dev": true,
|
||||
|
|
@ -6201,6 +6453,96 @@
|
|||
"@esbuild/win32-x64": "0.27.7"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz",
|
||||
"integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.5",
|
||||
"@vitest/mocker": "4.1.5",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/runner": "4.1.5",
|
||||
"@vitest/snapshot": "4.1.5",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"obug": "^2.1.1",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^4.0.0-rc.1",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^1.0.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.5",
|
||||
"@vitest/browser-preview": "4.1.5",
|
||||
"@vitest/browser-webdriverio": "4.1.5",
|
||||
"@vitest/coverage-istanbul": "4.1.5",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"@vitest/ui": "4.1.5",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-playwright": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-preview": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-istanbul": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-v8": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
|
|
@ -6223,6 +6565,23 @@
|
|||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"dev": true,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
"build:mac": "node scripts/package.mjs --mac dmg",
|
||||
"build:linux": "node scripts/package.mjs --linux AppImage",
|
||||
"build:all": "node scripts/package.mjs --win nsis --mac dmg --linux AppImage",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.renderer.json"
|
||||
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.renderer.json && tsc --noEmit -p tsconfig.vitest.json",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0",
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
"electron-builder": "26.8.1",
|
||||
"electron-vite": "^5.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.0.0"
|
||||
"vite": "^7.0.0",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@ const gitnexusDesktopRuntimeDependencyNames = [
|
|||
'pandemonium',
|
||||
'uuid',
|
||||
];
|
||||
const unmanagedGitNexusRuntimeDependencyNames = Object.keys(gitnexusPackageJson.dependencies ?? {})
|
||||
.filter((packageName) => !gitnexusDesktopRuntimeDependencyNames.includes(packageName))
|
||||
.sort();
|
||||
|
||||
const gitnexusDesktopBuildDependencyNames = ['@types/node', 'gitnexus-shared', 'typescript'];
|
||||
|
||||
|
|
@ -300,13 +303,14 @@ const overlayDependencyPackages = (sourceDirectory, destinationDirectory, depend
|
|||
const isLockedPathError =
|
||||
(errorCode === 'EPIPE' || errorCode === 'EBUSY' || errorCode === 'EPERM') &&
|
||||
existsSync(destinationPath);
|
||||
const isExistingPathError = errorCode === 'EEXIST' && existsSync(destinationPath);
|
||||
|
||||
if (!isLockedPathError) {
|
||||
if (!isLockedPathError && !isExistingPathError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[gitnexus-desktop] Skipping locked dependency path during repair: ${destinationPath}`,
|
||||
`[gitnexus-desktop] Skipping already-present dependency path during repair: ${destinationPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -526,6 +530,13 @@ if (
|
|||
runNpm(['ci'], sharedRoot);
|
||||
}
|
||||
|
||||
if (unmanagedGitNexusRuntimeDependencyNames.length > 0) {
|
||||
console.warn(
|
||||
'[gitnexus-desktop] Runtime repair does not yet manage these direct GitNexus dependencies:\n' +
|
||||
unmanagedGitNexusRuntimeDependencyNames.map((packageName) => ` - ${packageName}`).join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
if (isGitNexusRuntimeInstallStale()) {
|
||||
repairGitNexusPackages(gitnexusDesktopRuntimeDependencies, 'Repairing GitNexus runtime packages');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ const electronBuilderVersion =
|
|||
desktopPackageJson.devDependencies?.['electron-builder']?.replace(/^[^\d]*/, '') ?? '26.8.1';
|
||||
// electron-rebuild v4 requires Node 22+, but desktop packaging currently runs on Node 20.
|
||||
const electronRebuildVersion = '3.7.2';
|
||||
const bundledNodeExecutableName = process.platform === 'win32' ? 'node.exe' : 'node';
|
||||
const electronBuilderCliPath = path.join(packageRoot, 'node_modules', 'electron-builder', 'cli.js');
|
||||
const builderUtilRequire = createRequire(
|
||||
path.join(packageRoot, 'node_modules', 'builder-util', 'out', 'util.js'),
|
||||
|
|
@ -141,8 +140,6 @@ const builderEnvironment = {
|
|||
),
|
||||
GITNEXUS_DESKTOP_GITNEXUS_SKILLS: toBuilderRelativePath(path.join(gitnexusRoot, 'skills')),
|
||||
GITNEXUS_DESKTOP_GITNEXUS_VENDOR: toBuilderRelativePath(path.join(gitnexusRoot, 'vendor')),
|
||||
GITNEXUS_DESKTOP_NODE_EXECUTABLE: process.execPath,
|
||||
GITNEXUS_DESKTOP_NODE_RESOURCE_PATH: `gitnexus-node/${bundledNodeExecutableName}`,
|
||||
GITNEXUS_DESKTOP_WEB_DIST: toBuilderRelativePath(path.join(gitnexusWebRoot, 'dist')),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { spawn } from 'node:child_process';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -106,6 +106,26 @@ const resolveLatestExecutable = () => {
|
|||
};
|
||||
};
|
||||
|
||||
const prepareExecutableForSmokeTest = (executablePath) => {
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = spawnSync('xattr', ['-cr', executablePath], { stdio: 'inherit' });
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
if ((result.status ?? 0) !== 0) {
|
||||
throw new Error(`Failed to clear macOS quarantine attributes for ${executablePath}.`);
|
||||
}
|
||||
};
|
||||
|
||||
const getSmokeTestArguments = () => {
|
||||
return process.platform === 'linux' ? ['--no-sandbox'] : [];
|
||||
};
|
||||
|
||||
const runSmokeTest = async () => {
|
||||
const { executablePath, releaseDir: latestReleaseDir } = resolveLatestExecutable();
|
||||
|
||||
|
|
@ -119,10 +139,11 @@ const runSmokeTest = async () => {
|
|||
throw new Error(`Unable to locate the unpacked desktop executable under ${latestReleaseDir}.`);
|
||||
}
|
||||
|
||||
prepareExecutableForSmokeTest(executablePath);
|
||||
console.info(`[gitnexus-desktop] Smoke testing unpacked app: ${executablePath}`);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(executablePath, [], {
|
||||
const childProcess = spawn(executablePath, getSmokeTestArguments(), {
|
||||
cwd: path.dirname(executablePath),
|
||||
env: {
|
||||
...process.env,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse }
|
|||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
getPackagedRendererEntry,
|
||||
getRequestedPath,
|
||||
normalizeStaticPath,
|
||||
} from './runtime-paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const DESKTOP_APP_NAME = 'GitNexus Desktop';
|
||||
|
|
@ -251,6 +257,7 @@ const getNodeProcessEnvironment = (overrides: NodeJS.ProcessEnv = {}): NodeJS.Pr
|
|||
};
|
||||
|
||||
if (app.isPackaged) {
|
||||
// Packaged mode reuses Electron's embedded Node runtime via process.execPath.
|
||||
environment.ELECTRON_RUN_AS_NODE = '1';
|
||||
}
|
||||
|
||||
|
|
@ -367,55 +374,56 @@ const ensureWebDevServerStarted = async (): Promise<void> => {
|
|||
);
|
||||
};
|
||||
|
||||
const normalizeStaticPath = (rootDir: string, requestPath: string): string | null => {
|
||||
const normalizedRoot = path.resolve(rootDir);
|
||||
const requestedFile = requestPath === '/' ? '/index.html' : requestPath;
|
||||
const resolvedPath = path.resolve(normalizedRoot, `.${requestedFile}`);
|
||||
const isInsideRoot =
|
||||
resolvedPath === normalizedRoot || resolvedPath.startsWith(`${normalizedRoot}${path.sep}`);
|
||||
|
||||
if (!isInsideRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (existsSync(resolvedPath) && statSync(resolvedPath).isFile()) {
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
if (requestPath === '/' || path.extname(requestedFile) === '') {
|
||||
return path.join(normalizedRoot, 'index.html');
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
};
|
||||
|
||||
const sendStaticResponse = (assetPath: string | null, response: ServerResponse): void => {
|
||||
export const sendStaticResponse = (assetPath: string | null, response: ServerResponse): void => {
|
||||
if (!assetPath) {
|
||||
response.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
response.end('Forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existsSync(assetPath) || !statSync(assetPath).isFile()) {
|
||||
try {
|
||||
if (!existsSync(assetPath) || !statSync(assetPath).isFile()) {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
response.end('Not found');
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
response.end('Not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = MIME_TYPES[path.extname(assetPath)] ?? 'application/octet-stream';
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'no-store',
|
||||
'Content-Type': contentType,
|
||||
});
|
||||
createReadStream(assetPath).pipe(response);
|
||||
};
|
||||
const stream = createReadStream(assetPath);
|
||||
|
||||
const getRequestedPath = (requestUrl: string): string | null => {
|
||||
try {
|
||||
return decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
stream.once('error', () => {
|
||||
if (!response.headersSent) {
|
||||
response.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
response.end('Internal server error');
|
||||
return;
|
||||
}
|
||||
|
||||
response.destroy();
|
||||
});
|
||||
|
||||
response.once('close', () => {
|
||||
if (!stream.destroyed) {
|
||||
stream.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
stream.once('open', () => {
|
||||
if (response.destroyed) {
|
||||
stream.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'no-store',
|
||||
'Content-Type': contentType,
|
||||
});
|
||||
stream.pipe(response);
|
||||
});
|
||||
};
|
||||
|
||||
const handlePackagedWebRequest = (request: IncomingMessage, response: ServerResponse): void => {
|
||||
|
|
@ -565,6 +573,7 @@ const updateContentViewBounds = (window: BrowserWindow): void => {
|
|||
};
|
||||
|
||||
const createEmbeddedContentView = (contentUrl: string): BrowserView => {
|
||||
// BrowserView is deprecated in Electron; keep this isolated until we migrate to WebContentsView.
|
||||
const contentView = new BrowserView({
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
|
|
@ -603,7 +612,7 @@ const loadShellRenderer = async (window: BrowserWindow): Promise<void> => {
|
|||
return;
|
||||
}
|
||||
|
||||
const rendererEntry = path.join(__dirname, '../renderer/index.html');
|
||||
const rendererEntry = getPackagedRendererEntry(__dirname);
|
||||
console.info(`[gitnexus-desktop] Loading shell renderer file: ${rendererEntry}`);
|
||||
await window.loadFile(rendererEntry);
|
||||
};
|
||||
|
|
|
|||
46
gitnexus-desktop/src/main/runtime-paths.ts
Normal file
46
gitnexus-desktop/src/main/runtime-paths.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { existsSync, statSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export const normalizeStaticPath = (rootDir: string, requestPath: string): string | null => {
|
||||
if (requestPath.includes('\0')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedRoot = path.resolve(rootDir);
|
||||
const requestedFile = requestPath === '/' ? '/index.html' : requestPath;
|
||||
const resolvedPath = path.resolve(normalizedRoot, `.${requestedFile}`);
|
||||
const isInsideRoot =
|
||||
resolvedPath === normalizedRoot || resolvedPath.startsWith(`${normalizedRoot}${path.sep}`);
|
||||
|
||||
if (!isInsideRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync(resolvedPath) && statSync(resolvedPath).isFile()) {
|
||||
return resolvedPath;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (requestPath === '/' || path.extname(requestedFile) === '') {
|
||||
return path.join(normalizedRoot, 'index.html');
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
};
|
||||
|
||||
export const getRequestedPath = (requestUrl: string): string | null => {
|
||||
try {
|
||||
const requestedPath = decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
|
||||
|
||||
return requestedPath.includes('\0') ? null : requestedPath;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPackagedRendererEntry = (currentDir: string): string => {
|
||||
return path.join(currentDir, '../renderer/index.html');
|
||||
};
|
||||
61
gitnexus-desktop/test/main-utils.test.ts
Normal file
61
gitnexus-desktop/test/main-utils.test.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getPackagedRendererEntry,
|
||||
getRequestedPath,
|
||||
normalizeStaticPath,
|
||||
} from '../src/main/runtime-paths.js';
|
||||
|
||||
describe('normalizeStaticPath', () => {
|
||||
let rootDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
rootDir = mkdtempSync(path.join(os.tmpdir(), 'gitnexus-desktop-static-'));
|
||||
writeFileSync(path.join(rootDir, 'index.html'), '<html></html>');
|
||||
writeFileSync(path.join(rootDir, 'asset.js'), 'console.log("ok");');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(rootDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it('rejects directory traversal attempts', () => {
|
||||
expect(normalizeStaticPath(rootDir, '/../../etc/passwd')).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects null-byte paths', () => {
|
||||
expect(normalizeStaticPath(rootDir, '/\0asset.js')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns a real asset path when the file exists', () => {
|
||||
expect(normalizeStaticPath(rootDir, '/asset.js')).toBe(path.join(rootDir, 'asset.js'));
|
||||
});
|
||||
|
||||
it('falls back to index.html for extensionless routes', () => {
|
||||
expect(normalizeStaticPath(rootDir, '/desktop')).toBe(path.join(rootDir, 'index.html'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequestedPath', () => {
|
||||
it('returns null for malformed percent-encoding', () => {
|
||||
expect(getRequestedPath('/%GG')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for decoded null bytes', () => {
|
||||
expect(getRequestedPath('/%00')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPackagedRendererEntry', () => {
|
||||
it('resolves the packaged renderer relative to dist/main', () => {
|
||||
const rendererEntry = getPackagedRendererEntry(path.join('dist', 'main'));
|
||||
|
||||
expect(path.normalize(rendererEntry)).toBe(
|
||||
path.normalize(path.join('dist', 'renderer', 'index.html')),
|
||||
);
|
||||
});
|
||||
});
|
||||
7
gitnexus-desktop/tsconfig.vitest.json
Normal file
7
gitnexus-desktop/tsconfig.vitest.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "vitest/globals"]
|
||||
},
|
||||
"include": ["vitest.config.ts", "test/**/*.ts", "src/main/runtime-paths.ts"]
|
||||
}
|
||||
9
gitnexus-desktop/vitest.config.ts
Normal file
9
gitnexus-desktop/vitest.config.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
globals: true,
|
||||
include: ['test/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue