mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
fix(compat): support Obsidian down to 1.11.0
Release 1.2.0 raised minAppVersion to 1.13.0, pushing users still on Obsidian 1.11/1.12 back to the older 1.1.2 build via versions.json. Only three 1.13-era APIs were in use; two were already runtime-guarded, but ButtonComponent.setDestructive() would throw on older Obsidian. - Guard setDestructive() behind applyDestructiveStyle() (no-ops on < 1.13). - Declare SettingDefinitionItem locally and read update() via a cast so the forward-compatible settings code type-checks against 1.11 typings. - Ship 1.2.1 with minAppVersion 1.11.0 (manifest.json + versions.json). - Add tsconfig.compat.json and `npm run typecheck:compat` to type-check src against the 1.11 API, plus regression tests for applyDestructiveStyle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SSikT9ZAfQF8K4SE3DatD3
This commit is contained in:
parent
0a4cff5a46
commit
d896015765
8 changed files with 106 additions and 43 deletions
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "git-file-sync",
|
||||
"name": "Git File Sync",
|
||||
"version": "1.2.0",
|
||||
"minAppVersion": "1.13.0",
|
||||
"version": "1.2.1",
|
||||
"minAppVersion": "1.11.0",
|
||||
"description": "Selectively sync individual notes with GitLab or GitHub. Push, pull, diff, and resolve conflicts — file by file, on mobile and desktop.",
|
||||
"author": "ClaudiaFang",
|
||||
"authorUrl": "https://firstsun.heavenfortress.com/en/",
|
||||
|
|
|
|||
51
package-lock.json
generated
51
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "git-file-sync",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "git-file-sync",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ignore": "^7.0.5",
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
"husky": "^9.1.7",
|
||||
"jiti": "2.6.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"obsidian-1_11": "npm:obsidian@^1.11.0",
|
||||
"semantic-release": "^25.0.5",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
|
@ -1602,9 +1603,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1622,9 +1620,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1642,9 +1637,6 @@
|
|||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1662,9 +1654,6 @@
|
|||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1682,9 +1671,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1702,9 +1688,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -7172,9 +7155,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -7196,9 +7176,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -7220,9 +7197,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -7244,9 +7218,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9887,6 +9858,22 @@
|
|||
"@codemirror/view": "6.38.6"
|
||||
}
|
||||
},
|
||||
"node_modules/obsidian-1_11": {
|
||||
"name": "obsidian",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.11.0.tgz",
|
||||
"integrity": "sha512-lVqN9AmDWHzhNATi2tDnjqVgI6WUYKeT+lIsAycAyLt4XCC6zRsWzb+tFCiB7Rn3PpttefjoovilhYwvS4Iqxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "5.60.8",
|
||||
"moment": "2.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/state": "6.5.0",
|
||||
"@codemirror/view": "6.38.6"
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "git-file-sync",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "Selectively sync individual notes with GitLab or GitHub. Push, pull, diff, and resolve conflicts — file by file, on mobile and desktop.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"typecheck:compat": "tsc -p tsconfig.compat.json",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
|
|
@ -40,6 +41,7 @@
|
|||
"husky": "^9.1.7",
|
||||
"jiti": "2.6.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"obsidian-1_11": "npm:obsidian@^1.11.0",
|
||||
"semantic-release": "^25.0.5",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
import {App, PluginSettingTab, Setting, Notice, TextComponent, SettingDefinitionItem} from 'obsidian';
|
||||
import {App, PluginSettingTab, Setting, Notice, TextComponent} from 'obsidian';
|
||||
import GitLabFilesPush from "./main";
|
||||
|
||||
// Minimal shape of Obsidian >= 1.13's SettingDefinitionItem. Declared locally so
|
||||
// the plugin still type-checks against older Obsidian typings (minAppVersion
|
||||
// 1.11.0), where this type does not exist. Obsidian only calls
|
||||
// getSettingDefinitions() on versions that understand it.
|
||||
interface SettingDefinitionItem {
|
||||
name: string;
|
||||
render: (setting: unknown, group: { listEl: HTMLElement }) => void;
|
||||
}
|
||||
|
||||
export interface SyncMetadata {
|
||||
lastSyncedSha: string;
|
||||
lastSyncedAt: number;
|
||||
|
|
@ -83,8 +92,9 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
|||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
// Kept as a fallback for Obsidian < 1.13.0 (this plugin's minAppVersion),
|
||||
// which don't know about getSettingDefinitions() and always call display().
|
||||
// Kept as a fallback for Obsidian < 1.13.0 (older than 1.13, down to
|
||||
// minAppVersion 1.11.0), which don't know about getSettingDefinitions()
|
||||
// and always call display().
|
||||
display(): void {
|
||||
this.renderSettings(this.containerEl);
|
||||
}
|
||||
|
|
@ -99,8 +109,12 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private refresh(): void {
|
||||
if (typeof this.update === 'function') {
|
||||
this.update();
|
||||
// update() only exists on Obsidian >= 1.13. On older versions (down to
|
||||
// minAppVersion 1.11.0) re-render manually instead. Accessed via a cast
|
||||
// so this compiles against the 1.11 typings, which lack update().
|
||||
const maybeUpdate = (this as { update?: () => void }).update;
|
||||
if (typeof maybeUpdate === 'function') {
|
||||
maybeUpdate.call(this);
|
||||
} else {
|
||||
this.renderSettings(this.containerEl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
import { App, Modal, Setting } from 'obsidian';
|
||||
|
||||
/**
|
||||
* Apply the "destructive" button style, but only when the running Obsidian
|
||||
* supports it. ButtonComponent.setDestructive() was added in Obsidian 1.13; on
|
||||
* older versions (down to this plugin's minAppVersion, 1.11.0) the method is
|
||||
* absent, so we skip it instead of throwing "setDestructive is not a function".
|
||||
* Returns the same button so it can be chained.
|
||||
*/
|
||||
export function applyDestructiveStyle<T extends object>(btn: T): T {
|
||||
const setDestructive = (btn as { setDestructive?: () => unknown }).setDestructive;
|
||||
if (typeof setDestructive === 'function') {
|
||||
setDestructive.call(btn);
|
||||
}
|
||||
return btn;
|
||||
}
|
||||
|
||||
export class SyncConflictModal extends Modal {
|
||||
private readonly fileName: string;
|
||||
private readonly localContent: string;
|
||||
|
|
@ -52,10 +67,9 @@ export class SyncConflictModal extends Modal {
|
|||
this.onChoose('local');
|
||||
this.close();
|
||||
}))
|
||||
.addButton(btn => btn
|
||||
.addButton(btn => applyDestructiveStyle(btn)
|
||||
.setButtonText('Keep remote')
|
||||
.setTooltip('Overwrite local with remote content')
|
||||
.setDestructive()
|
||||
.onClick(() => {
|
||||
this.onChoose('remote');
|
||||
this.close();
|
||||
|
|
|
|||
28
tests/ui/SyncConflictModal.test.ts
Normal file
28
tests/ui/SyncConflictModal.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { applyDestructiveStyle } from '../../src/ui/SyncConflictModal';
|
||||
|
||||
// Guards the backward-compatibility fix that lets the plugin run on Obsidian
|
||||
// down to minAppVersion 1.11.0. ButtonComponent.setDestructive() only exists on
|
||||
// Obsidian >= 1.13; calling it unconditionally would throw on older versions.
|
||||
describe('applyDestructiveStyle (Obsidian version compatibility)', () => {
|
||||
it('applies the destructive style on Obsidian >= 1.13 (method present)', () => {
|
||||
const setDestructive = vi.fn();
|
||||
const btn = { setDestructive };
|
||||
|
||||
expect(applyDestructiveStyle(btn)).toBe(btn);
|
||||
expect(setDestructive).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('degrades gracefully on Obsidian < 1.13 (method absent) without throwing', () => {
|
||||
const btn = {}; // an older ButtonComponent has no setDestructive()
|
||||
|
||||
expect(() => applyDestructiveStyle(btn)).not.toThrow();
|
||||
expect(applyDestructiveStyle(btn)).toBe(btn);
|
||||
});
|
||||
|
||||
it('ignores a non-function setDestructive rather than crashing', () => {
|
||||
const btn = { setDestructive: 'nope' as unknown as () => unknown };
|
||||
|
||||
expect(() => applyDestructiveStyle(btn)).not.toThrow();
|
||||
});
|
||||
});
|
||||
17
tsconfig.compat.json
Normal file
17
tsconfig.compat.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// Type-checks src against the Obsidian 1.11 API typings (installed as the
|
||||
// "obsidian-1_11" alias) instead of the latest ones. Because the plugin
|
||||
// declares minAppVersion 1.11.0, any use of an API newer than 1.11 that is
|
||||
// not runtime-guarded becomes a compile error here. Run via `npm run
|
||||
// typecheck:compat`. Keep this passing whenever minAppVersion stays at 1.11.
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"obsidian": ["node_modules/obsidian-1_11/obsidian"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
|
@ -5,5 +5,6 @@
|
|||
"1.0.6": "1.12.7",
|
||||
"1.1.1": "1.12.7",
|
||||
"1.1.2": "1.12.7",
|
||||
"1.2.0": "1.13.0"
|
||||
"1.2.0": "1.13.0",
|
||||
"1.2.1": "1.11.0"
|
||||
}
|
||||
Loading…
Reference in a new issue