Add Windows x64 and x86 installers and refresh release screenshots
This commit is contained in:
1 parent
f3b4205ee1
commit
739eb522ec
18 files changed
+266
-42
No files matched your search
@@ -158,3 +158,9 @@ NEW TOKEN breakpoints — 560px / 768px / 900px
|
|||||||
`src/lib/clean.js` contains one literal hex, `JPEG_MATTE = "#FFFFFF"`. That is
|
`src/lib/clean.js` contains one literal hex, `JPEG_MATTE = "#FFFFFF"`. That is
|
||||||
image data written to a canvas, not interface styling, so it is intentionally
|
image data written to a canvas, not interface styling, so it is intentionally
|
||||||
not a token.
|
not a token.
|
||||||
|
|
||||||
|
### Windows distribution (1.7.0, 2026-10-25)
|
||||||
|
|
||||||
|
`npm run release:windows` builds separate x64/x86 installer and portable packages. `scripts/build-windows.mjs` pins Electron 44.3.0 for x64 and 43.8.3 for x86 because 44 removed ia32 binaries. Electron 43 support ends in January 2027; do not silently remove that support limitation from the docs. Installed settings use `%APPDATA%\Untrace Desktop`; portable launcher settings remain in the existing adjacent `Untrace-data` folder. `desktop/data-path.cjs` centralizes this distinction. Preserve settings on uninstall. Run installer checks sequentially and separately from app tests, because the installer closes other processes with the application executable name.
|
||||||
|
|
||||||
|
Both remotes default to `main`. See `docs/UPSTREAM-REVIEW.md` before importing upstream website changes. Regenerate real-workspace screenshots with `npm run screenshots` using only the generated sample artwork.
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.7.0 - 2026-10-25
|
||||||
|
|
||||||
|
- Added per-user Windows installers and portable launchers for x64 and x86.
|
||||||
|
- Store installed-app preferences in the user's application-data directory; preserve portable settings beside the launcher.
|
||||||
|
- Use Electron 43.8.3 for x86 because Electron 44 no longer ships 32-bit binaries; keep Electron 44.3.0 for x64.
|
||||||
|
- Added reproducible full-workspace screenshots with generated sample artwork.
|
||||||
|
- Reviewed upstream through 6d5e92b; no outstanding image-engine fixes require importing the web interface.
|
||||||
|
- Set main as the default branch on both hosting services.
|
||||||
|
|
||||||
|
|
||||||
## 1.6.1 - 2026-09-10
|
## 1.6.1 - 2026-09-10
|
||||||
|
|
||||||
- Filter folder selections, multiple-file selections and drag-and-drop before opening a batch: only supported images enter the queue. Non-image files no longer appear as skipped rows.
|
- Filter folder selections, multiple-file selections and drag-and-drop before opening a batch: only supported images enter the queue. Non-image files no longer appear as skipped rows.
|
||||||
|
|||||||
+20
-14
@@ -8,7 +8,7 @@ The desktop edition is maintained on `main`. The pre-existing GitHub web edition
|
|||||||
- `gitea`: https://git.ptbox.org/dasp/Untrace.git
|
- `gitea`: https://git.ptbox.org/dasp/Untrace.git
|
||||||
- `upstream`: https://github.com/developersharif/untrace.git (reference only)
|
- `upstream`: https://github.com/developersharif/untrace.git (reference only)
|
||||||
|
|
||||||
Create empty repositories at the two publishing destinations before pushing. Do not initialize them with a README or license. Retain the MIT license and upstream attribution when distributing source or binaries.
|
Both repositories already exist and default to `main`. Retain the MIT license and upstream attribution when distributing source or binaries.
|
||||||
|
|
||||||
## Verify and package
|
## Verify and package
|
||||||
|
|
||||||
@@ -17,8 +17,9 @@ On Windows x64 with Node.js 22.13 or newer:
|
|||||||
```powershell
|
```powershell
|
||||||
npm ci
|
npm ci
|
||||||
npm test
|
npm test
|
||||||
npm run portable
|
npm run release:windows
|
||||||
node scripts/smoke-portable.mjs
|
node scripts/smoke-portable.mjs x64
|
||||||
|
node scripts/smoke-portable.mjs x86
|
||||||
```
|
```
|
||||||
|
|
||||||
Review `git status --short` and `git diff` before committing. Keep dependencies, generated builds, local settings, secrets and test outputs out of Git. Inspect all newly added files as well as tracked changes. Commit the reviewed source and publishing documentation before tagging a release.
|
Review `git status --short` and `git diff` before committing. Keep dependencies, generated builds, local settings, secrets and test outputs out of Git. Inspect all newly added files as well as tracked changes. Commit the reviewed source and publishing documentation before tagging a release.
|
||||||
@@ -36,19 +37,24 @@ git push gitea $branch
|
|||||||
After validating the built release and ensuring the tag does not already exist:
|
After validating the built release and ensuring the tag does not already exist:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
git tag -a v1.6.1 -m 'Untrace Desktop 1.6.1'
|
git tag -a v1.7.0 -m 'Untrace Desktop 1.7.0'
|
||||||
git push origin v1.6.1
|
git push origin v1.7.0
|
||||||
git push gitea v1.6.1
|
git push gitea v1.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a release on each host for the same tag. Upload `release/Untrace-Desktop-1.6.1-portable-x64.exe`, a SHA-256 checksum file, and matching source. Include the MIT license, theme credits, version highlights from CHANGELOG.md, Windows x64 requirements, and the unsigned-executable notice. Do not upload `Untrace-data` or test output.
|
Create a release on each host for the same tag. Upload exactly these six files:
|
||||||
|
|
||||||
Generate the checksum after building:
|
- `release/x64/Untrace-Desktop-1.7.0-setup-x64.exe`
|
||||||
|
- `release/x64/Untrace-Desktop-1.7.0-portable-x64.exe`
|
||||||
|
- `release/x86/Untrace-Desktop-1.7.0-setup-x86.exe`
|
||||||
|
- `release/x86/Untrace-Desktop-1.7.0-portable-x86.exe`
|
||||||
|
- `release/Untrace-Desktop-Source-v1.7.0.zip`, made with `git archive` from the release tag
|
||||||
|
- `release/SHA256SUMS.txt`, containing hashes for all five files above
|
||||||
|
|
||||||
```powershell
|
Test installed-app startup and uninstall for both architectures, preserving user preferences. Run the full packaged test suite on x86 because it uses a different Electron major version. The x64 runtime is 44.3.0; x86 uses 43.8.3 (the last supported 32-bit series, reaching end of support in January 2027). Do not change the runtime pins without repeating package tests.
|
||||||
$asset = 'release/Untrace-Desktop-1.6.1-portable-x64.exe'
|
|
||||||
$hash = (Get-FileHash -LiteralPath $asset -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
||||||
"$hash $(Split-Path -Leaf $asset)" | Set-Content -Encoding ascii release/SHA256SUMS.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
README.md links to the latest releases on both hosts. Publish identical assets and source on both hosts.
|
Include MIT attribution, theme credits, the Windows requirements, and the unsigned-executable notice. Do not upload user data, intermediate archives, blockmaps, or test outputs. Validate the six remote assets before publishing each draft. When resuming an upload, locate drafts through the release list as tag lookup may omit drafts.
|
||||||
|
|
||||||
|
README.md links to the latest releases on both hosts. See [upstream review](docs/UPSTREAM-REVIEW.md) before importing web-edition changes. Regenerate the real application screenshots with `npm run screenshots`; the scenic image is generated test artwork.
|
||||||
|
|
||||||
|
Run installer checks sequentially, with no other Untrace processes running: NSIS uninstall closes running copies by executable name. Use scripts/test-installers.ps1 -Architecture x64, then x86, and only then run packaged regression tests. The x86 packages were verified under WOW64 on a 64-bit Windows host; native 32-bit Windows was not separately tested.
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
An offline image metadata cleaner for Windows, with batch processing and fourteen themes.
|
An offline image metadata cleaner for Windows, with batch processing and fourteen themes.
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](#run)
|
[](#run)
|
||||||
[](CHANGELOG.md)
|
[](CHANGELOG.md)
|
||||||
|
|
||||||
[Download — GitHub](https://github.com/donishki/Untrace/releases/latest) · [Download — Gitea](https://git.ptbox.org/dasp/Untrace/releases/latest) · [Build and verify](#build-and-verify) · [Changelog](CHANGELOG.md) · [Publishing guide](PUBLISHING.md)
|
[Download — GitHub](https://github.com/donishki/Untrace/releases/latest) · [Download — Gitea](https://git.ptbox.org/dasp/Untrace/releases/latest) · [Build and verify](#build-and-verify) · [Changelog](CHANGELOG.md) · [Publishing guide](PUBLISHING.md)
|
||||||
|
|
||||||
@@ -18,18 +18,36 @@ An offline desktop edition of [developersharif/untrace](https://github.com/devel
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
Metadata comparison using a generated test image (captured in version 1.6.1).
|
Real screenshots of version 1.7.0 using generated landscape artwork, without personal photos or paths.
|
||||||
|
|
||||||

|
### A focused workspace
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Light appearance
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Batch processing
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
1. Put `Untrace-Desktop-1.6.1-portable-x64.exe` in a writable folder or USB drive and double-click it.
|
Download from [GitHub Releases](https://github.com/donishki/Untrace/releases/latest) or [Gitea Releases](https://git.ptbox.org/dasp/Untrace/releases/latest).
|
||||||
2. Choose **Open image**, drop a file anywhere in the workspace, or paste an image.
|
|
||||||
3. Review the **Inspector**, choose an export format, and select **Clean image**.
|
|
||||||
4. Compare **Original** and **Cleaned copy**, then choose **Save clean image**.
|
|
||||||
|
|
||||||
Requires 64-bit Windows 10 or 11. No installation, administrator rights, Node.js, browser installation or internet is required. The EXE is unsigned; Windows may show an unknown-publisher or SmartScreen prompt. Initial launch extracts the bundled runtime to a temporary folder.
|
| Package | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `Untrace-Desktop-1.7.0-setup-x64.exe` | Install on 64-bit Windows. Recommended for most PCs. |
|
||||||
|
| `Untrace-Desktop-1.7.0-setup-x86.exe` | Install on 32-bit Windows. Also runs on 64-bit Windows. |
|
||||||
|
| `Untrace-Desktop-1.7.0-portable-x64.exe` | Run the 64-bit app from a writable folder or USB drive. |
|
||||||
|
| `Untrace-Desktop-1.7.0-portable-x86.exe` | Run the 32-bit portable app. |
|
||||||
|
|
||||||
|
Installers work for your Windows account, offer an installation folder and shortcuts, and can be removed through Windows Settings > Apps. No administrator rights are required. Settings survive upgrades and uninstall. Install one architecture at a time; x64 and x86 installed editions share preferences.
|
||||||
|
|
||||||
|
Requires Windows 10 or 11 for x64, or Windows 10 for x86. The x86 edition uses Electron 43.8.3, the last Electron series supporting 32-bit Windows; its support ends in January 2027. The x64 edition uses Electron 44.3.0. No Node.js, browser installation, or internet is required to run either edition. Executables are unsigned; Windows may show an unknown-publisher or SmartScreen prompt. Portable launchers extract the runtime to a temporary folder.
|
||||||
|
|
||||||
|
Open or drop an image, review the Inspector, choose an export format, select **Clean image**, and save the separate copy. You can also select several images or a folder for a batch.
|
||||||
|
|
||||||
The window has an image workspace, a scrolling metadata inspector, export settings, before/after viewing, fit/100% zoom, fourteen themes and Help. The bottom status bar shows progress. There are no promotional sections or external pages required to use the app.
|
The window has an image workspace, a scrolling metadata inspector, export settings, before/after viewing, fit/100% zoom, fourteen themes and Help. The bottom status bar shows progress. There are no promotional sections or external pages required to use the app.
|
||||||
|
|
||||||
@@ -84,7 +102,7 @@ Untrace Desktop is a derivative of developersharif's Untrace, not an official up
|
|||||||
|
|
||||||
## Portable data and privacy
|
## Portable data and privacy
|
||||||
|
|
||||||
Preferences and Chromium session files live in `Untrace-data` beside the executable. Keep it with the EXE to retain preferences. Close Untrace Desktop before deleting that folder to reset it. The runtime also uses the Windows temporary folder, so portable does not mean forensic trace-free.
|
Portable preferences and Chromium session files live in `Untrace-data` beside the launcher. Installed and source-run editions use `%APPDATA%\Untrace Desktop`, separate from portable settings. Keep it with the EXE to retain preferences. Close Untrace Desktop before deleting that folder to reset it. The runtime also uses the Windows temporary folder, so portable does not mean forensic trace-free.
|
||||||
|
|
||||||
Images are processed locally and originals are not automatically overwritten. The application blocks HTTP/WebSocket requests, denies permission requests, and has no telemetry or updater. The source link in Help opens your default browser when clicked. The renderer is sandboxed, with Node integration disabled and a restrictive content security policy. A narrow preload bridge opens the native output-folder chooser and saves encoded bytes only inside that selected destination, using exclusive file creation. Image scanning and encoding run in terminable background workers.
|
Images are processed locally and originals are not automatically overwritten. The application blocks HTTP/WebSocket requests, denies permission requests, and has no telemetry or updater. The source link in Help opens your default browser when clicked. The renderer is sandboxed, with Node integration disabled and a restrictive content security policy. A narrow preload bridge opens the native output-folder chooser and saves encoded bytes only inside that selected destination, using exclusive file creation. Image scanning and encoding run in terminable background workers.
|
||||||
|
|
||||||
@@ -98,21 +116,22 @@ References: [Electron overview](https://www.electronjs.org/docs/latest/), [Elect
|
|||||||
|
|
||||||
## Build and verify
|
## Build and verify
|
||||||
|
|
||||||
Use Node.js 22.13 or newer and npm on Windows x64. Dependency downloads require internet access.
|
Use Node.js 22.13 or newer and npm on Windows x64 / x86. Dependency downloads require internet access.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm ci
|
npm ci
|
||||||
npm test
|
npm test
|
||||||
npm run portable
|
npm run release:windows
|
||||||
node scripts/smoke-portable.mjs
|
node scripts/smoke-portable.mjs x64
|
||||||
|
node scripts/smoke-portable.mjs x86
|
||||||
```
|
```
|
||||||
|
|
||||||
The EXE is written to `release/`. `release/win-unpacked/Untrace Desktop.exe` is the alternative folder-based build; keep all its files together. `npm run desktop` launches from source, and `npm run dev` runs the same workspace in a development browser. Desktop packaging removes remote font imports from generated assets without modifying vendored tokens; system fonts are used offline.
|
Installers and portable EXEs are written to `release/x64/` and `release/x86/`. The unpacked apps are in `release/x64/win-unpacked/` and `release/x86/win-ia32-unpacked/`; keep all their files together. `npm run portable` builds both portable launchers; `npm run installer` builds both installers. `npm run desktop` launches from source, and `npm run dev` runs the same workspace in a development browser. Desktop packaging removes remote font imports from generated assets without modifying vendored tokens; system fonts are used offline.
|
||||||
|
|
||||||
To run the full suite against the packaged application:
|
To run the full suite against the packaged application:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:UNTRACE_TEST_EXE = (Resolve-Path 'release/win-unpacked/Untrace Desktop.exe').Path
|
$env:UNTRACE_TEST_EXE = (Resolve-Path 'release/x64/win-unpacked/Untrace Desktop.exe').Path
|
||||||
npx playwright test
|
npx playwright test
|
||||||
Remove-Item Env:UNTRACE_TEST_EXE
|
Remove-Item Env:UNTRACE_TEST_EXE
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
function dataPath({ env, appData }) {
|
||||||
|
if (env.UNTRACE_DATA_DIR) return env.UNTRACE_DATA_DIR;
|
||||||
|
if (env.PORTABLE_EXECUTABLE_DIR) return path.join(env.PORTABLE_EXECUTABLE_DIR, 'Untrace-data');
|
||||||
|
return path.join(appData, 'Untrace Desktop');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { dataPath };
|
||||||
+2
-3
@@ -6,9 +6,8 @@ const { name: productName, appId } = require('./brand.json');
|
|||||||
app.setName(productName);
|
app.setName(productName);
|
||||||
app.setAppUserModelId(appId);
|
app.setAppUserModelId(appId);
|
||||||
|
|
||||||
// Keep preferences alongside the portable executable, including on USB drives.
|
// Portable settings travel with the launcher. Installed settings survive upgrades/uninstall.
|
||||||
const home = process.env.PORTABLE_EXECUTABLE_DIR || path.dirname(app.getPath('exe'));
|
const data = require('./data-path.cjs').dataPath({ env: process.env, appData: app.getPath('appData') });
|
||||||
const data = process.env.UNTRACE_DATA_DIR || path.join(home, 'Untrace-data');
|
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(data, { recursive: true });
|
fs.mkdirSync(data, { recursive: true });
|
||||||
app.setPath('userData', data);
|
app.setPath('userData', data);
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Upstream review — 2026-10-25
|
||||||
|
|
||||||
|
Reviewed [developersharif/untrace](https://github.com/developersharif/untrace) through commit `6d5e92b14d371ebe6133e5613259a858a79d7a0a` on upstream/main.
|
||||||
|
|
||||||
|
`git log main..upstream/main` is empty: all current upstream commits are already ancestors of this desktop edition. The earlier history-preserving merge intentionally kept the desktop working tree; ancestry alone does not mean every web feature was adopted.
|
||||||
|
|
||||||
|
The four web-edition commits after the original desktop baseline (`99b4b22`) add landing-page branding/animation, privacy guides and SEO/social assets, web batch review/ZIP downloads, and CNAME handling. They do not change `src/lib/clean.js`, `src/lib/metadata.js`, or `package.json` relative to that baseline. No scanner/cleaner fix or dependency update needs importing from these commits.
|
||||||
|
|
||||||
|
The desktop edition already implements sequential worker-based batches, cancellation, metadata comparison, and restricted native-folder saving. Importing the browser Batch component or website App/Vite configuration would replace or complicate those workflows without a required bug fix. No upstream commit was cherry-picked for 1.7.0.
|
||||||
|
|
||||||
|
Keep the original web implementation on `web-before-desktop` for reference. Both GitHub and Gitea should default to `main`. Gitea originally selected the first pushed branch, `web-before-desktop`; its repository default was corrected to `main`.
|
||||||
|
|
||||||
|
For future reviews, fetch upstream, inspect changes to shared processing modules separately from website changes, and test any selected fix against offline desktop, cancellation, batch saving, and both supported architectures before publishing.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 165 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "untrace-desktop",
|
"name": "untrace-desktop",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "untrace-desktop",
|
"name": "untrace-desktop",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"exifr": "^7.1.3"
|
"exifr": "^7.1.3"
|
||||||
|
|||||||
+21
-5
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "untrace-desktop",
|
"name": "untrace-desktop",
|
||||||
"description": "Offline image metadata cleaner for Windows with a dedicated desktop workspace.",
|
"description": "Offline image metadata cleaner for Windows with a dedicated desktop workspace.",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -10,8 +10,11 @@
|
|||||||
"deploy": "npm run build && gh-pages -d dist",
|
"deploy": "npm run build && gh-pages -d dist",
|
||||||
"build:desktop": "npm run build && node scripts/prepare-desktop.mjs",
|
"build:desktop": "npm run build && node scripts/prepare-desktop.mjs",
|
||||||
"desktop": "npm run build:desktop && electron .",
|
"desktop": "npm run build:desktop && electron .",
|
||||||
"portable": "npm run build:desktop && electron-builder --win portable --x64",
|
"portable": "npm run build:desktop && node scripts/build-windows.mjs portable",
|
||||||
"test": "npm run build:desktop && playwright test"
|
"test": "npm run build:desktop && playwright test",
|
||||||
|
"installer": "npm run build:desktop && node scripts/build-windows.mjs nsis",
|
||||||
|
"release:windows": "npm run build:desktop && node scripts/build-windows.mjs",
|
||||||
|
"screenshots": "npm run build:desktop && node scripts/screenshots.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.63.0",
|
"@playwright/test": "^1.63.0",
|
||||||
@@ -50,7 +53,7 @@
|
|||||||
"icon": "desktop/icon.ico"
|
"icon": "desktop/icon.ico"
|
||||||
},
|
},
|
||||||
"portable": {
|
"portable": {
|
||||||
"artifactName": "Untrace-Desktop-${version}-portable-x64.exe"
|
"artifactName": "Untrace-Desktop-${version}-portable-${arch}.exe"
|
||||||
},
|
},
|
||||||
"extraFiles": [
|
"extraFiles": [
|
||||||
{
|
{
|
||||||
@@ -69,7 +72,20 @@
|
|||||||
"from": "THEME-CREDITS.md",
|
"from": "THEME-CREDITS.md",
|
||||||
"to": "THEME-CREDITS.md"
|
"to": "THEME-CREDITS.md"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"perMachine": false,
|
||||||
|
"allowElevation": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"runAfterFinish": false,
|
||||||
|
"deleteAppDataOnUninstall": false,
|
||||||
|
"artifactName": "Untrace-Desktop-${version}-setup-${arch}.exe",
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true,
|
||||||
|
"license": "LICENSE"
|
||||||
|
},
|
||||||
|
"compression": "normal"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { build, Platform, Arch } from 'electron-builder';
|
||||||
|
|
||||||
|
const requested = process.argv[2];
|
||||||
|
if (requested && !['portable', 'nsis'].includes(requested)) throw new Error('Expected portable or nsis.');
|
||||||
|
const targets = requested ? [requested] : ['portable', 'nsis'];
|
||||||
|
// Electron 43 is the last series with Windows ia32 binaries.
|
||||||
|
for (const [label, arch, electronVersion] of [['x64', Arch.x64, '44.3.0'], ['x86', Arch.ia32, '43.8.3']]) {
|
||||||
|
await build({
|
||||||
|
targets: Platform.WINDOWS.createTarget(targets, arch),
|
||||||
|
publish: 'never',
|
||||||
|
config: {
|
||||||
|
electronVersion,
|
||||||
|
directories: { output: `release/${label}` },
|
||||||
|
portable: { artifactName: `Untrace-Desktop-\${version}-portable-${label}.exe` },
|
||||||
|
nsis: { artifactName: `Untrace-Desktop-\${version}-setup-${label}.exe` },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { _electron as electron, expect } from '@playwright/test';
|
||||||
|
import { mkdtemp, rm, mkdir } from 'node:fs/promises';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const data = await mkdtemp(path.join(os.tmpdir(), 'untrace-screenshots-'));
|
||||||
|
const env = { ...process.env, UNTRACE_TEST: '1', UNTRACE_DATA_DIR: data };
|
||||||
|
delete env.ELECTRON_RUN_AS_NODE;
|
||||||
|
const app = await electron.launch({ args: ['.', '--disable-features=CalculateNativeWinOcclusion', '--disable-renderer-backgrounding', '--disable-backgrounding-occluded-windows'], env });
|
||||||
|
await mkdir('docs/screenshots', { recursive: true });
|
||||||
|
try {
|
||||||
|
const page = await app.firstWindow();
|
||||||
|
await app.evaluate(({ BrowserWindow }) => { const win = BrowserWindow.getAllWindows()[0]; win.setContentSize(1440, 960); win.webContents.setBackgroundThrottling(false); });
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
|
await page.evaluate(() => localStorage.setItem('untrace-theme', 'graphite'));
|
||||||
|
await page.reload();
|
||||||
|
// Original generated artwork, not a private photo. Only sample pixels enter the app.
|
||||||
|
const bytes = Buffer.from(await page.evaluate(() => {
|
||||||
|
const canvas = document.createElement('canvas'); canvas.width = 1800; canvas.height = 1200;
|
||||||
|
const c = canvas.getContext('2d');
|
||||||
|
const sky = c.createLinearGradient(0, 0, 0, 750); sky.addColorStop(0, '#254e6b'); sky.addColorStop(.65, '#9bb7b2'); sky.addColorStop(1, '#f6d5aa'); c.fillStyle = sky; c.fillRect(0, 0, 1800, 1200);
|
||||||
|
c.fillStyle = '#f6e7bd'; c.beginPath(); c.arc(1300, 270, 68, 0, Math.PI * 2); c.fill();
|
||||||
|
function mountain(points, color) { c.fillStyle = color; c.beginPath(); c.moveTo(0, 760); for (const [x,y] of points) c.lineTo(x,y); c.lineTo(1800,760); c.closePath(); c.fill(); }
|
||||||
|
mountain([[0,520],[240,300],[420,460],[680,180],[950,480],[1220,260],[1500,430],[1800,220]], '#637e8b');
|
||||||
|
mountain([[0,680],[340,430],[600,680],[890,360],[1170,610],[1430,390],[1800,640]], '#345867');
|
||||||
|
c.fillStyle = '#dce5df'; c.beginPath(); c.moveTo(510,395); c.lineTo(680,180); c.lineTo(800,323); c.lineTo(710,292); c.lineTo(670,335); c.lineTo(625,307); c.fill();
|
||||||
|
const lake = c.createLinearGradient(0, 740, 0, 1200); lake.addColorStop(0,'#9bb5b0'); lake.addColorStop(1,'#274d5d'); c.fillStyle = lake; c.fillRect(0,740,1800,460);
|
||||||
|
for(let i=0;i<60;i++){ const y=770+i*7; c.fillStyle=i%3===0?'#99b0aa66':'#c9d5bd33'; c.fillRect((i*137)%1800,y,80+(i*31)%340,2); }
|
||||||
|
for(let i=0;i<42;i++){const x=i*47, h=65+(i*43)%130; c.fillStyle='#193f47'; c.beginPath(); c.moveTo(x,760-h); c.lineTo(x-24,770); c.lineTo(x+24,770); c.fill();}
|
||||||
|
c.fillStyle='#183c45'; c.beginPath(); c.moveTo(0,970); c.quadraticCurveTo(260,880,470,1200); c.lineTo(0,1200); c.fill();
|
||||||
|
return canvas.toDataURL('image/jpeg', .95).split(',')[1];
|
||||||
|
}), 'base64');
|
||||||
|
const sample = { name: 'Alpine-lake.jpg', mimeType: 'image/jpeg', buffer: bytes };
|
||||||
|
await page.getByLabel('Open image file', { exact: true }).setInputFiles(sample);
|
||||||
|
await page.getByLabel('File format', { exact: true }).selectOption('image/jpeg');
|
||||||
|
await page.getByRole('button', { name: 'Clean image', exact: true }).click();
|
||||||
|
await expect(page.getByRole('link', { name: 'Save clean image' })).toBeVisible();
|
||||||
|
await page.screenshot({ path: 'docs/screenshots/workspace-dark.png' });
|
||||||
|
await page.evaluate(() => localStorage.setItem('untrace-theme', 'light'));
|
||||||
|
await page.reload();
|
||||||
|
await page.getByLabel('Open image file', { exact: true }).setInputFiles(sample);
|
||||||
|
await expect(page.getByRole('button', { name: 'Clean image', exact: true })).toBeEnabled();
|
||||||
|
await page.screenshot({ path: 'docs/screenshots/workspace-light.png' });
|
||||||
|
await page.getByLabel('Open image file', { exact: true }).setInputFiles(['Alpine-lake.jpg','Mountain-reflections.jpg','Morning-light.jpg','Forest-shore.jpg'].map(name => ({ ...sample, name })));
|
||||||
|
await expect(page.getByRole('button', { name: 'Clean batch', exact: true })).toBeEnabled();
|
||||||
|
await page.getByRole('button', { name: 'Clean batch', exact: true }).click();
|
||||||
|
await expect(page.getByText('Cleaned', { exact: true })).toHaveCount(4);
|
||||||
|
await page.screenshot({ path: 'docs/screenshots/batch-light.png' });
|
||||||
|
console.log('Captured three real application screenshots using generated sample artwork.');
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
await rm(data, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { _electron as electron, expect } from '@playwright/test';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const [executablePath, architecture] = process.argv.slice(2);
|
||||||
|
if (!executablePath || !['x64', 'ia32'].includes(architecture)) throw new Error('Pass installed executable path and x64 or ia32.');
|
||||||
|
const env = { ...process.env, UNTRACE_TEST: '1' };
|
||||||
|
delete env.ELECTRON_RUN_AS_NODE;
|
||||||
|
delete env.UNTRACE_DATA_DIR;
|
||||||
|
delete env.PORTABLE_EXECUTABLE_DIR;
|
||||||
|
const app = await electron.launch({ executablePath, env });
|
||||||
|
try {
|
||||||
|
const page = await app.firstWindow();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Inspector', exact: true })).toBeVisible();
|
||||||
|
const actual = await app.evaluate(({ app }) => ({ architecture: process.arch, data: app.getPath('userData'), appData: app.getPath('appData'), version: app.getVersion() }));
|
||||||
|
const { version } = JSON.parse(await readFile('package.json', 'utf8'));
|
||||||
|
if (actual.architecture !== architecture || actual.version !== version) throw new Error('Installed architecture/version mismatch.');
|
||||||
|
if (actual.data !== path.join(actual.appData, 'Untrace Desktop')) throw new Error('Installed settings path mismatch.');
|
||||||
|
if (await page.evaluate(() => typeof window.require) !== 'undefined') throw new Error('Renderer has Node access.');
|
||||||
|
if (await page.evaluate(() => fetch('https://example.com').then(() => true).catch(() => false))) throw new Error('Unexpected network access.');
|
||||||
|
console.log(`PASS: installed ${architecture} ${version} launches offline with isolated renderer and per-user settings.`);
|
||||||
|
} finally { await app.close(); }
|
||||||
@@ -8,11 +8,16 @@ const data = await mkdtemp(path.join(os.tmpdir(), 'untrace-portable-'));
|
|||||||
const env = { ...process.env, UNTRACE_TEST: '1', UNTRACE_DATA_DIR: data };
|
const env = { ...process.env, UNTRACE_TEST: '1', UNTRACE_DATA_DIR: data };
|
||||||
delete env.ELECTRON_RUN_AS_NODE;
|
delete env.ELECTRON_RUN_AS_NODE;
|
||||||
const { version, build } = JSON.parse(await readFile('package.json', 'utf8'));
|
const { version, build } = JSON.parse(await readFile('package.json', 'utf8'));
|
||||||
const artifact = build.portable.artifactName.replace('${version}', version);
|
const architecture = process.argv[2] || 'x64';
|
||||||
const processHandle = spawn(path.resolve('release', artifact), ['--remote-debugging-port=19384'], { env, windowsHide: true, stdio: 'ignore' });
|
if (!['x64', 'x86'].includes(architecture)) throw new Error('Expected x64 or x86.');
|
||||||
|
const artifact = `Untrace-Desktop-${version}-portable-${architecture}.exe`;
|
||||||
|
const processHandle = spawn(path.resolve('release', architecture, artifact), ['--remote-debugging-port=19384'], { env, windowsHide: true, stdio: 'ignore' });
|
||||||
|
let launchError;
|
||||||
|
processHandle.on('error', error => { launchError = error; });
|
||||||
let browser;
|
let browser;
|
||||||
try {
|
try {
|
||||||
for (let attempt = 0; attempt < 120; attempt++) {
|
for (let attempt = 0; attempt < 120; attempt++) {
|
||||||
|
if (launchError) throw launchError;
|
||||||
try { browser = await chromium.connectOverCDP('http://127.0.0.1:19384', { timeout: 1000 }); break; }
|
try { browser = await chromium.connectOverCDP('http://127.0.0.1:19384', { timeout: 1000 }); break; }
|
||||||
catch { await new Promise(resolve => setTimeout(resolve, 500)); }
|
catch { await new Promise(resolve => setTimeout(resolve, 500)); }
|
||||||
}
|
}
|
||||||
@@ -21,7 +26,7 @@ try {
|
|||||||
await page.waitForURL('untrace://app/');
|
await page.waitForURL('untrace://app/');
|
||||||
await page.getByLabel('Open image file', { exact: true }).waitFor({ state: 'attached' });
|
await page.getByLabel('Open image file', { exact: true }).waitFor({ state: 'attached' });
|
||||||
if (await page.title() !== build.productName) throw new Error('Unexpected window');
|
if (await page.title() !== build.productName) throw new Error('Unexpected window');
|
||||||
console.log('PASS: single-file portable extracted and loaded the offline app.');
|
console.log(`PASS: ${architecture} single-file portable extracted and loaded the offline app.`);
|
||||||
const cdp = await browser.newBrowserCDPSession();
|
const cdp = await browser.newBrowserCDPSession();
|
||||||
await Promise.race([cdp.send('Browser.close').catch(() => {}), new Promise(resolve => setTimeout(resolve, 1000))]);
|
await Promise.race([cdp.send('Browser.close').catch(() => {}), new Promise(resolve => setTimeout(resolve, 1000))]);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
param([ValidateSet('x64','x86')][string]$Architecture)
|
||||||
|
$ErrorActionPreference='Stop'
|
||||||
|
$root=Split-Path $PSScriptRoot -Parent
|
||||||
|
$version=(Get-Content -LiteralPath (Join-Path $root 'package.json') -Raw | ConvertFrom-Json).version
|
||||||
|
$existing=@(Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object DisplayName -like 'Untrace*')
|
||||||
|
if($existing.Count){throw 'An Untrace installation already exists. Run installer verification in a clean Windows account.'}
|
||||||
|
$installer=Join-Path $root "release/$Architecture/Untrace-Desktop-$version-setup-$Architecture.exe"
|
||||||
|
$target=Join-Path ([IO.Path]::GetTempPath()) ('Untrace-install-check-'+$Architecture+'-'+[guid]::NewGuid().ToString('N'))
|
||||||
|
$profile=Join-Path $env:APPDATA 'Untrace Desktop'
|
||||||
|
New-Item -ItemType Directory -Path $profile -Force | Out-Null
|
||||||
|
$sentinel=Join-Path $profile ('.install-check-'+[guid]::NewGuid().ToString('N'))
|
||||||
|
Set-Content -LiteralPath $sentinel -Value 'preserve-settings'
|
||||||
|
Push-Location $root
|
||||||
|
try {
|
||||||
|
$process=Start-Process -FilePath $installer -ArgumentList "/S /currentuser /D=$target" -PassThru -Wait -WindowStyle Hidden
|
||||||
|
if($process.ExitCode -ne 0){throw "Installer failed: $($process.ExitCode)"}
|
||||||
|
$exe=Join-Path $target 'Untrace Desktop.exe'
|
||||||
|
if(-not (Test-Path -LiteralPath $exe)){throw 'Installed executable is missing'}
|
||||||
|
$arch=if($Architecture -eq 'x86'){'ia32'}else{'x64'}
|
||||||
|
node scripts/smoke-installed.mjs $exe $arch
|
||||||
|
if($LASTEXITCODE -ne 0){throw 'Installed smoke test failed'}
|
||||||
|
$uninstaller=Get-ChildItem -LiteralPath $target -Filter 'Uninstall*.exe' | Select-Object -First 1
|
||||||
|
if(-not $uninstaller){throw 'Uninstaller is missing'}
|
||||||
|
$process=Start-Process -FilePath $uninstaller.FullName -ArgumentList '/S /currentuser' -PassThru -Wait -WindowStyle Hidden
|
||||||
|
if($process.ExitCode -ne 0){throw "Uninstaller failed: $($process.ExitCode)"}
|
||||||
|
for($attempt=0;$attempt -lt 30 -and (Test-Path -LiteralPath $exe);$attempt++){Start-Sleep -Milliseconds 500}
|
||||||
|
if(Test-Path -LiteralPath $exe){throw 'Uninstall did not remove the installed executable'}
|
||||||
|
if((Get-Content -LiteralPath $sentinel -Raw).Trim() -ne 'preserve-settings'){throw 'Uninstall removed settings'}
|
||||||
|
$left=@(Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object DisplayName -like 'Untrace*')
|
||||||
|
if($left.Count){throw 'Uninstall registration remains'}
|
||||||
|
Write-Output "PASS: $Architecture install, installed startup, uninstall, and settings preservation."
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
if(Test-Path -LiteralPath $sentinel){Remove-Item -LiteralPath $sentinel}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import path from 'node:path';
|
||||||
|
const { dataPath } = createRequire(import.meta.url)('../desktop/data-path.cjs');
|
||||||
|
|
||||||
|
test('installed settings stay outside program files and portable settings travel with the launcher', () => {
|
||||||
|
const appData = path.resolve('test-profile');
|
||||||
|
const portable = path.resolve('usb');
|
||||||
|
expect(dataPath({ env: {}, appData })).toBe(path.join(appData, 'Untrace Desktop'));
|
||||||
|
expect(dataPath({ env: { PORTABLE_EXECUTABLE_DIR: portable }, appData })).toBe(path.join(portable, 'Untrace-data'));
|
||||||
|
expect(dataPath({ env: { PORTABLE_EXECUTABLE_DIR: portable, UNTRACE_DATA_DIR: 'test-override' }, appData })).toBe('test-override');
|
||||||
|
});
|
||||||
Reference in new issue
Block a user