mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-07-26 14:51:43 +00:00
Eliminates the fragile "delete the config folder before upgrading" tester
instruction, which was wrong-by-OS because the userData folder name was
derived inconsistently per platform (fee[dB]ack on macOS, slopsmith-desktop
on Linux/Windows).
A. Deterministic paths + migration framework
- Pin the userData name on every OS via app.setName('feedback-desktop') +
build.extraMetadata.name; brand (productName 'fee[dB]ack') unchanged.
- One-time userData migration copies a legacy folder into the new one so
upgraded users don't start fresh (atomic copy-then-rename, fail-soft).
Runs before the single-instance lock / crashReporter, which would otherwise
create userData and defeat the "new dir doesn't exist" gate.
- config-migrations.ts: versioned, ordered, idempotent, fail-soft migration
runner stamped in CONFIG_DIR/config_version.json; logs the active CONFIG_DIR
at startup (closes the Linux ~/.local/share/slopsmith shared-config gap).
B. In-app "Reset / repair configuration" (Settings panel)
- Granular options: reset app settings & caches, clear plugin state & cached
Python deps, and full reset with default-OFF opt-ins for installed plugins /
song library / ML caches.
- config-paths.ts is the single source of truth for per-OS path enumeration;
the song library, installed plugins and ML caches are structurally confined
to optInExtras and never wiped by the safe/full categories.
- Reset stops the backend, deletes immediate paths, includes SQLite WAL/SHM
sidecars + the migration stamp on full reset, and defers Chromium/Crashpad
state to next launch (consumed before any window reopens it). ML caches honor
TORCH_HOME/HF_HOME. Empty selection is a no-op (backend left running).
- SECURITY: destructive resets require a native main-process confirmation
dialog — the renderer bridge is reachable by plugin scripts, so a
renderer-only confirm is not a sufficient gate.
Tests: node:test suites for path enumeration (per-OS + library/plugins
preserved), migration idempotency/fail-soft, reset delete pipeline guarantees,
userData migration, and deferred-deletion schedule/consume. `npm test` green
(adds a test script). codex review --base origin/main clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.7 KiB
JavaScript
67 lines
2.7 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { loadTs } = require('./_load-ts');
|
|
|
|
const {
|
|
runConfigMigrations,
|
|
readSchemaVersion,
|
|
CURRENT_SCHEMA_VERSION,
|
|
} = loadTs('src/main/config-migrations.ts');
|
|
|
|
function tmpConfigDir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'feedback-cfgmig-'));
|
|
}
|
|
|
|
test('first run stamps the config to CURRENT_SCHEMA_VERSION and is idempotent', () => {
|
|
const dir = tmpConfigDir();
|
|
assert.equal(readSchemaVersion(dir), 0);
|
|
|
|
const first = runConfigMigrations(dir, '1.2.3', '2026-06-26T00:00:00.000Z');
|
|
assert.equal(first.from, 0);
|
|
assert.equal(first.to, CURRENT_SCHEMA_VERSION);
|
|
assert.equal(readSchemaVersion(dir), CURRENT_SCHEMA_VERSION);
|
|
|
|
const stamp = JSON.parse(fs.readFileSync(path.join(dir, 'config_version.json'), 'utf8'));
|
|
assert.equal(stamp.schemaVersion, CURRENT_SCHEMA_VERSION);
|
|
assert.equal(stamp.appVersion, '1.2.3');
|
|
assert.equal(stamp.updatedAt, '2026-06-26T00:00:00.000Z');
|
|
|
|
// Second run is a no-op: nothing to migrate, stamp unchanged.
|
|
const second = runConfigMigrations(dir, '1.2.3', '2026-06-26T01:00:00.000Z');
|
|
assert.equal(second.from, CURRENT_SCHEMA_VERSION);
|
|
assert.equal(second.to, CURRENT_SCHEMA_VERSION);
|
|
assert.deepEqual(second.ran, []);
|
|
});
|
|
|
|
test('fail-soft: a throwing migration is logged and skipped; later migrations still run', () => {
|
|
const dir = tmpConfigDir();
|
|
const okMarker = path.join(dir, 'ok-ran.txt');
|
|
|
|
// Two version-1 migrations (both in range for a fresh dir). The first throws;
|
|
// the runner must not abort — the second must still execute.
|
|
const registry = [
|
|
{ version: 1, name: 'boom', run: () => { throw new Error('boom'); } },
|
|
{ version: 1, name: 'ok', run: () => fs.writeFileSync(okMarker, 'ran') },
|
|
];
|
|
|
|
const res = runConfigMigrations(dir, '9.9.9', '2026-06-26T00:00:00.000Z', registry);
|
|
|
|
assert.equal(res.ran.length, 2);
|
|
assert.equal(res.ran[0].ok, false);
|
|
assert.match(res.ran[0].error, /boom/);
|
|
assert.equal(res.ran[1].ok, true);
|
|
assert.ok(fs.existsSync(okMarker), 'the second migration ran despite the first throwing');
|
|
// The stamp still advances so a persistently-failing migration cannot wedge startup.
|
|
assert.equal(readSchemaVersion(dir), CURRENT_SCHEMA_VERSION);
|
|
});
|
|
|
|
test('readSchemaVersion treats a missing/corrupt stamp as version 0', () => {
|
|
const dir = tmpConfigDir();
|
|
assert.equal(readSchemaVersion(dir), 0);
|
|
fs.writeFileSync(path.join(dir, 'config_version.json'), 'not json {');
|
|
assert.equal(readSchemaVersion(dir), 0);
|
|
});
|