mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
feat(yomitan): add read-only external profile support for shared dictionaries (#18)
This commit is contained in:
@@ -65,7 +65,7 @@ test('ensureDefaultConfigBootstrap creates config dir and default jsonc only whe
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureDefaultConfigBootstrap does not seed default config into an existing config directory', () => {
|
||||
test('ensureDefaultConfigBootstrap seeds default config into an existing config directory when missing', () => {
|
||||
withTempDir((root) => {
|
||||
const configDir = path.join(root, 'SubMiner');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
@@ -74,10 +74,13 @@ test('ensureDefaultConfigBootstrap does not seed default config into an existing
|
||||
ensureDefaultConfigBootstrap({
|
||||
configDir,
|
||||
configFilePaths: getDefaultConfigFilePaths(configDir),
|
||||
generateTemplate: () => 'should-not-write',
|
||||
generateTemplate: () => '{\n "logging": {}\n}\n',
|
||||
});
|
||||
|
||||
assert.equal(fs.existsSync(path.join(configDir, 'config.jsonc')), false);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(configDir, 'config.jsonc'), 'utf8'),
|
||||
'{\n "logging": {}\n}\n',
|
||||
);
|
||||
assert.equal(fs.readFileSync(path.join(configDir, 'existing-user-file.txt'), 'utf8'), 'keep\n');
|
||||
});
|
||||
});
|
||||
@@ -91,6 +94,7 @@ test('readSetupState ignores invalid files and round-trips valid state', () => {
|
||||
const state = createDefaultSetupState();
|
||||
state.status = 'completed';
|
||||
state.completionSource = 'user';
|
||||
state.yomitanSetupMode = 'internal';
|
||||
state.lastSeenYomitanDictionaryCount = 2;
|
||||
writeSetupState(statePath, state);
|
||||
|
||||
@@ -98,7 +102,7 @@ test('readSetupState ignores invalid files and round-trips valid state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('readSetupState migrates v1 state to v2 windows shortcut defaults', () => {
|
||||
test('readSetupState migrates v1 state to v3 windows shortcut defaults', () => {
|
||||
withTempDir((root) => {
|
||||
const statePath = getSetupStatePath(root);
|
||||
fs.writeFileSync(
|
||||
@@ -115,10 +119,11 @@ test('readSetupState migrates v1 state to v2 windows shortcut defaults', () => {
|
||||
);
|
||||
|
||||
assert.deepEqual(readSetupState(statePath), {
|
||||
version: 2,
|
||||
version: 3,
|
||||
status: 'incomplete',
|
||||
completedAt: null,
|
||||
completionSource: null,
|
||||
yomitanSetupMode: null,
|
||||
lastSeenYomitanDictionaryCount: 0,
|
||||
pluginInstallStatus: 'unknown',
|
||||
pluginInstallPathSummary: null,
|
||||
@@ -131,6 +136,45 @@ test('readSetupState migrates v1 state to v2 windows shortcut defaults', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('readSetupState migrates completed v2 state to internal yomitan setup mode', () => {
|
||||
withTempDir((root) => {
|
||||
const statePath = getSetupStatePath(root);
|
||||
fs.writeFileSync(
|
||||
statePath,
|
||||
JSON.stringify({
|
||||
version: 2,
|
||||
status: 'completed',
|
||||
completedAt: '2026-03-12T00:00:00.000Z',
|
||||
completionSource: 'user',
|
||||
lastSeenYomitanDictionaryCount: 1,
|
||||
pluginInstallStatus: 'unknown',
|
||||
pluginInstallPathSummary: null,
|
||||
windowsMpvShortcutPreferences: {
|
||||
startMenuEnabled: true,
|
||||
desktopEnabled: true,
|
||||
},
|
||||
windowsMpvShortcutLastStatus: 'unknown',
|
||||
}),
|
||||
);
|
||||
|
||||
assert.deepEqual(readSetupState(statePath), {
|
||||
version: 3,
|
||||
status: 'completed',
|
||||
completedAt: '2026-03-12T00:00:00.000Z',
|
||||
completionSource: 'user',
|
||||
yomitanSetupMode: 'internal',
|
||||
lastSeenYomitanDictionaryCount: 1,
|
||||
pluginInstallStatus: 'unknown',
|
||||
pluginInstallPathSummary: null,
|
||||
windowsMpvShortcutPreferences: {
|
||||
startMenuEnabled: true,
|
||||
desktopEnabled: true,
|
||||
},
|
||||
windowsMpvShortcutLastStatus: 'unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveDefaultMpvInstallPaths resolves linux, macOS, and Windows defaults', () => {
|
||||
const linuxHomeDir = path.join(path.sep, 'tmp', 'home');
|
||||
const xdgConfigHome = path.join(path.sep, 'tmp', 'xdg');
|
||||
|
||||
@@ -5,6 +5,7 @@ import { resolveConfigDir } from '../config/path-resolution';
|
||||
|
||||
export type SetupStateStatus = 'incomplete' | 'in_progress' | 'completed' | 'cancelled';
|
||||
export type SetupCompletionSource = 'user' | 'legacy_auto_detected' | null;
|
||||
export type SetupYomitanMode = 'internal' | 'external' | null;
|
||||
export type SetupPluginInstallStatus = 'unknown' | 'installed' | 'skipped' | 'failed';
|
||||
export type SetupWindowsMpvShortcutInstallStatus = 'unknown' | 'installed' | 'skipped' | 'failed';
|
||||
|
||||
@@ -14,10 +15,11 @@ export interface SetupWindowsMpvShortcutPreferences {
|
||||
}
|
||||
|
||||
export interface SetupState {
|
||||
version: 2;
|
||||
version: 3;
|
||||
status: SetupStateStatus;
|
||||
completedAt: string | null;
|
||||
completionSource: SetupCompletionSource;
|
||||
yomitanSetupMode: SetupYomitanMode;
|
||||
lastSeenYomitanDictionaryCount: number;
|
||||
pluginInstallStatus: SetupPluginInstallStatus;
|
||||
pluginInstallPathSummary: string | null;
|
||||
@@ -52,10 +54,11 @@ function asObject(value: unknown): Record<string, unknown> | null {
|
||||
|
||||
export function createDefaultSetupState(): SetupState {
|
||||
return {
|
||||
version: 2,
|
||||
version: 3,
|
||||
status: 'incomplete',
|
||||
completedAt: null,
|
||||
completionSource: null,
|
||||
yomitanSetupMode: null,
|
||||
lastSeenYomitanDictionaryCount: 0,
|
||||
pluginInstallStatus: 'unknown',
|
||||
pluginInstallPathSummary: null,
|
||||
@@ -74,11 +77,12 @@ export function normalizeSetupState(value: unknown): SetupState | null {
|
||||
const status = record.status;
|
||||
const pluginInstallStatus = record.pluginInstallStatus;
|
||||
const completionSource = record.completionSource;
|
||||
const yomitanSetupMode = record.yomitanSetupMode;
|
||||
const windowsPrefs = asObject(record.windowsMpvShortcutPreferences);
|
||||
const windowsMpvShortcutLastStatus = record.windowsMpvShortcutLastStatus;
|
||||
|
||||
if (
|
||||
(version !== 1 && version !== 2) ||
|
||||
(version !== 1 && version !== 2 && version !== 3) ||
|
||||
(status !== 'incomplete' &&
|
||||
status !== 'in_progress' &&
|
||||
status !== 'completed' &&
|
||||
@@ -94,16 +98,26 @@ export function normalizeSetupState(value: unknown): SetupState | null {
|
||||
windowsMpvShortcutLastStatus !== 'failed') ||
|
||||
(completionSource !== null &&
|
||||
completionSource !== 'user' &&
|
||||
completionSource !== 'legacy_auto_detected')
|
||||
completionSource !== 'legacy_auto_detected') ||
|
||||
(version === 3 &&
|
||||
yomitanSetupMode !== null &&
|
||||
yomitanSetupMode !== 'internal' &&
|
||||
yomitanSetupMode !== 'external')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
version: 2,
|
||||
version: 3,
|
||||
status,
|
||||
completedAt: typeof record.completedAt === 'string' ? record.completedAt : null,
|
||||
completionSource,
|
||||
yomitanSetupMode:
|
||||
version === 3 && (yomitanSetupMode === 'internal' || yomitanSetupMode === 'external')
|
||||
? yomitanSetupMode
|
||||
: status === 'completed'
|
||||
? 'internal'
|
||||
: null,
|
||||
lastSeenYomitanDictionaryCount:
|
||||
typeof record.lastSeenYomitanDictionaryCount === 'number' &&
|
||||
Number.isFinite(record.lastSeenYomitanDictionaryCount) &&
|
||||
@@ -208,13 +222,8 @@ export function ensureDefaultConfigBootstrap(options: {
|
||||
const existsSync = options.existsSync ?? fs.existsSync;
|
||||
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
||||
const writeFileSync = options.writeFileSync ?? fs.writeFileSync;
|
||||
const configDirExists = existsSync(options.configDir);
|
||||
|
||||
if (
|
||||
existsSync(options.configFilePaths.jsoncPath) ||
|
||||
existsSync(options.configFilePaths.jsonPath) ||
|
||||
configDirExists
|
||||
) {
|
||||
if (existsSync(options.configFilePaths.jsoncPath) || existsSync(options.configFilePaths.jsonPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user