Commit Graph

4 Commits

Author SHA1 Message Date
fullex
c514dcc049 refactor(shared): move packages/shared to src/shared
packages/shared was never a real pnpm workspace package (no package.json); it was referenced only through the @shared TypeScript path alias. Relocate it under src/ via git mv (143 files, detected as pure renames).

Repoint the @shared alias and include globs to src/shared across electron.vite.config.ts, tsconfig.{json,node,web}.json and vitest.config.ts; update scripts/check-custom-exts.ts, scripts/update-languages.ts, the eslint.config.mjs generated-file globs, the data-classify generator output targets, .github/CODEOWNERS path rules, and CLAUDE.md/docs/source-comment references.

The @shared alias name is unchanged, so all 1403 @shared/* import sites resolve without modification. Verified with typecheck:node, typecheck:web and the full test suite (700 files, 9739 tests passing).
2026-05-28 21:02:49 -07:00
fullex
44b5edce54 feat(preboot): add core/preboot subsystem for userData location
Introduce a new `src/main/core/preboot/` subsystem that owns the
synchronous setup happening before `application.bootstrap()` runs. This
is the follow-up promised by c17dd033f to rewire v1's `initAppDataDir()`
to read from BootConfig instead of `~/.cherrystudio/config/config.json`.

## Startup phase vocabulary

The v2 main process now has three clearly-named startup phases, with the
definitions written into `src/main/core/README.md` as the authoritative
source:

  - preboot  — synchronous setup before the lifecycle container is
               built. BootConfig load, userData resolution, top-level
               Electron APIs. No DI, no services. Owned by core/preboot/.
  - bootstrap — the `application.bootstrap()` orchestration function.
                Freezes the path registry, runs Background/BeforeReady/
                WhenReady stages. NestJS/Spring-style term, the only
                meaning of "bootstrap" in v2 from this PR onward.
  - running  — steady state after bootstrap() returns.

v1's `src/main/bootstrap.ts` used "bootstrap" in the OS "early-boot
loader" sense, which clashes with the NestJS-style meaning v2's lifecycle
code has been using. "preboot" disambiguates without touching v2's
bootstrap terminology.

## userData location resolution

`resolveUserDataLocation()` is a single entry point that:

  1. Applies any pending relocation (see below).
  2. Reads `BootConfig.app.user_data_path[exe]` and calls setPath if
     valid.
  3. Falls back to portable-mode `<PORTABLE_EXECUTABLE_DIR>/data`.
  4. Otherwise leaves Electron's default userData.

Step 2 mirrors v1's `getAppDataPathFromConfig()` path-validity check
(existsSync + accessSync W_OK). Portable fallback mirrors v1's branch
character-for-character. Only the data source changes: BootConfig, not
v1 config.json. v1→v2 data migration is handled separately by
BootConfigMigrator (from c17dd033f).

## Pending relocation (new)

A new BootConfig field `temp.user_data_relocation` under a new top-level
`temp.*` namespace holds in-flight userData relocation state:

    { status: 'pending', from, to }
    { status: 'failed',  from, to, error, failedAt }
    null

The `temp.*` namespace is reserved for ephemeral runtime state that
should never be backed up or synced (restoring a stale temp.* entry
could re-execute a relocation that already happened).

When a pending request is present, `executePendingRelocation()`
synchronously copies the entire `from` tree to `to` with
`fs.cpSync({ recursive: true, force: true, verbatimSymlinks: true })`,
then atomically commits the new location to BootConfig
(`app.user_data_path[exe] = to` + clear `temp.user_data_relocation` +
flush via temp file + rename).

Pre-flight checks fail fast before any bytes are copied:
  - from !== to
  - to is not inside from (with path.sep guard against sibling-prefix
    false positives like /a vs /ab)
  - from exists
  - path.dirname(to) exists and is writable

On failure, the error is recorded as a `failed` state in BootConfig and
the function returns normally, letting Step 2 fall through to the
existing user_data_path. The `failed` record is the entry point for a
future renderer recovery UI (retry/abandon/investigate); this PR does
not implement the UI.

`failed` states are intentionally NOT auto-retried — that would turn a
one-off environmental failure into an infinite loop.

## v1 two-phase copy abandoned

v1's `copyOccupiedDirsInMainProcess()` worked around Windows file locks
on `occupiedDirs` (logs, Network, Partitions/webview/Network) by
splitting the copy into two phases (renderer copies the bulk, main
copies the locked dirs during the narrow "no renderer yet" window).

v2 abandons that distinction entirely: the previous process has fully
exited before preboot runs, so nothing is locked, and the entire
userData tree is copied as a single opaque unit. `occupiedDirs` has no
meaning in v2 and is deprecated in `packages/shared/config/constant.ts`
— it is kept on disk only because two v1-era call sites (bootstrap.ts
and BasicDataSettings.tsx) still reference it during the transition.

## Deprecations (code unchanged)

Two v1 files have their file-header @deprecated comments strengthened
but their code bodies untouched:

  - src/main/bootstrap.ts — no longer imported anywhere; the file stays
    on disk as reference for the follow-up cleanup PR.
  - src/main/utils/init.ts — initAppDataDir is dead code;
    updateAppDataConfig is still called by the v1 IPC handler at
    src/main/ipc.ts:300 (out of scope for this PR).

## Known limitation (pre-existing)

LoggerService opens its winston file transport at module-load time
pointing at `app.getPath('logs')`, which on Windows/Linux resolves to
`<userData>/logs`. This happens BEFORE preboot runs, so the winston
file handle is bound to the pre-setPath path. After preboot calls
setPath('userData', <new>), winston keeps writing to the old handle.
Post-relocation logs therefore appear at the old location until the
next restart. This is a pre-existing v2 issue (LoggerService doesn't
respond to setPath) and is documented in executePendingRelocation's
doc comment; a follow-up should either lazy-open winston transports
or teach LoggerService to rotate on userData change.

## Out of scope (follow-up cleanup PR)

  - App_SetAppDataPath IPC handler migration to write BootConfig
    pending_relocation instead of v1 argv protocol
  - BasicDataSettings.tsx renderer flow migration
  - src/main/bootstrap.ts and src/main/utils/init.ts deletion
  - occupiedDirs constant deletion
  - LoggerService setPath responsiveness

## Tests

25 unit tests in userDataLocation.test.ts covering:
  - getNormalizedExecutablePath (5 platform branches)
  - resolveUserDataLocation normal resolution (9 scenarios including
    AppImage/portable normalized key matching)
  - resolveUserDataLocation pending relocation (11 scenarios including
    pre-flight failure cases and the sibling-prefix regression guard)

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-08 01:03:45 -07:00
fullex
c17dd033fc feat(migration): migrate v1 ~/.cherrystudio/config/config.json into BootConfig
Add a 'configfile' source kind to BootConfigMigrator so the v1 home config
file's `appDataPath` is migrated into a new BootConfig key
`app.user_data_path: Record<string, string>`, keyed by executable path.

v1's `~/.cherrystudio/config/config.json` is conceptually "early-boot
configuration that must be readable before userData is decided" — the same
category as BootConfig. v1 `config/config.json` will eventually be replaced
entirely by BootConfig; this PR establishes the data migration path, and a
follow-up will rewire `initAppDataDir()` to read from BootConfig instead.

Key pieces:

- New `LegacyHomeConfigReader` — sync fs read, normalizes both the legacy
  string and the array-of-{executablePath,dataPath} v1 formats into a
  `Record<string, string>`. Returns `null` (not `{}`) for "no data" so the
  migrator's null-skip guard distinguishes "missing" from "empty record"
  and doesn't write a spurious empty entry.

- BootConfigMigrator: new 'configfile' branch in `prepare()`; inline
  `configFileMappings` local const with `targetKey: BootConfigKey` type
  annotation (regen safety net — schema loses `app.user_data_path` →
  compile error at the declaration site). `defaultValue: null` on
  config-file items so "no v1 file" skips the item rather than falling
  back to the schema default `{}`.

- Type tightening: `MigrationItem.targetKey` and `PreparedData.targetKey`
  narrowed from `string` to `BootConfigKey`; all 6 `as BootConfigKey`
  assertions deleted. This removes a silent "regen drops the key" failure
  mode where the assertions previously suppressed compile errors.

- Generator updates: `generate-boot-config.js` gains a
  `MANUAL_BOOT_CONFIG_ITEMS` list that flows through the same sort/emit
  pipeline as classification-derived items, keeping `bootConfigSchemas.ts`
  as a fully auto-generated single-interface file (no manual sections, no
  declaration merging, no preservation logic). `generate-migration.js`
  tightens the 3 BootConfig mapping arrays to `targetKey: BootConfigKey`.

- Tests: new `LegacyHomeConfigReader.test.ts` covers 9 scenarios (7 null
  returns + 2 populated records); new `BootConfigMigrator.test.ts` covers
  the configfile source end-to-end plus a redux-source regression.

- Docs: per-migrator `README-BootConfigMigrator.md` created per the
  convention in `v2-migration-guide.md`. `v2-migration-guide.md`,
  `boot-config-overview.md`, `boot-config-schema-guide.md`, and
  `src/main/data/migration/v2/README.md` updated to mention
  BootConfigMigrator, `LegacyHomeConfigReader`, and the 'configfile'
  source kind. The AppImage/Windows-portable `executablePath`
  normalization gap is documented as a known limitation that must be
  addressed in the follow-up read-side PR.

Explicitly out of scope:

- Rewiring `initAppDataDir()` / `updateAppDataConfig()` to read/write
  BootConfig — the v1 home config file keeps working unchanged.
- Adding a 'configfile' category to classification.json — the
  data-classify toolchain isn't modelled for file sources yet.
- Deleting the v1 home config file post-migration — left intact.

Verified end-to-end on a real v1 config.json: the migrator produced
`"app.user_data_path": { [executablePath]: dataPath }` in
`~/.cherrystudio/boot-config.json`, verbatim-preserving the v1 data.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-07 09:20:23 -07:00
fullex
c5544c8a6e feat: wire BootConfig across renderer, preload, migration and UI
Complete the BootConfig integration across all layers:
- Preload: update IPC bridge types to UnifiedPreferenceType
- Renderer PreferenceService: use getDefaultValue for BootConfig fallback
- usePreference hook: unified default value lookup
- GeneralSettings: consume hardware acceleration via usePreference
- BootConfigMigrator: migrate legacy Redux/ElectronStore values
- BootConfigMappings: define source-to-target field mappings
- Migration i18n: add migrated_boot_config progress text
- Code generator: add generate-boot-config.js script
- Update classification.json and preference schemas

Signed-off-by: fullex <0xfullex@gmail.com>
2026-03-25 09:36:03 -07:00