Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@
title: Roadmap and Changelog
description: Changelog and feature roadmap for Quartz Syncer.
created: 2025-05-16T12:59:31Z+0200
modified: 2026-06-02T19:09:30Z+0200
modified: 2026-06-08T15:17:47Z+0200
publish: true
---

## Upcoming

## Releases

### Version 1.18.0

- Significantly improved Quartz upgrade reliability with smart conflict resolution.
- Upgrades now automatically succeed when the user has only modified dedicated user files (`quartz.config.yaml`, `quartz.lock.json`, `quartz.ts`, `quartz/styles/custom.scss`, `content/`, `.github/`, `quartz/static/`, and `quartz/styles/syncer/`).
- Framework files (everything else) automatically accept upstream changes during upgrade.
- User-owned files are preserved across upgrades via snapshot and restore, even if upstream cleanly modified them.
- If the user has modified framework files, the upgrade fails with a specific list of which files were modified, instead of a generic merge conflict error.
- `quartz.config.default.yaml` is automatically accepted from upstream without blocking the upgrade.
- Removed unnecessary post-merge commits when user files were not changed by upstream.
- Updated upgrade notification to reference the in-app Upgrade button instead of `npx quartz upgrade`.
- `npx quartz upgrade` is now only recommended as a fallback when the in-app upgrade fails.

### Version 1.17.3

- Refactored buffer git interaction.
Expand Down
8 changes: 5 additions & 3 deletions docs/Guides/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: CLI
description: Automate Quartz Syncer workflows from the terminal using the Obsidian CLI.
created: 2026-04-01T00:00:00Z+0200
modified: 2026-04-01T17:15:10Z+0200
modified: 2026-06-08T15:22:53Z+0200
publish: true
tags: [guides]
---
Expand Down Expand Up @@ -168,9 +168,11 @@ obsidian quartz-syncer:upgrade dry-run format=json
| `dry-run` | Check for available updates without applying them. |
| `format` | Output format: `json` or `text` (default). |

> [!WARNING] Merge conflicts
> [!INFO] Smart conflict resolution
>
> If the upgrade encounters merge conflicts (other than `quartz.lock.json`, which is auto-resolved), the command will fail and list the conflicting files. Resolve them manually in your repository.
> Quartz Syncer automatically resolves most upgrade conflicts. User files (`quartz.config.yaml`, `quartz.lock.json`, `quartz.ts`, `quartz/styles/custom.scss`, `content/`, `.github/`, `quartz/static/`, and `quartz/styles/syncer/`) are preserved, while framework files accept upstream changes.
>
> If you have modified framework files (e.g., `package.json`, files in `quartz/components/`), the upgrade will fail and list which files were modified. In that case, run `npx quartz upgrade` in your repository to resolve conflicts manually.

### `quartz-syncer:version`

Expand Down
28 changes: 27 additions & 1 deletion docs/Troubleshooting/Quartz.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Quartz
description: Troubleshooting issues related to Quartz.
created: 2025-05-05T00:00:00Z+0200
modified: 2026-04-01T17:15:09Z+0200
modified: 2026-06-08T15:23:03Z+0200
publish: true
tags: [quartz]
---
Expand Down Expand Up @@ -63,6 +63,32 @@ The same flag works for `plugin add`:
npx quartz plugin add github:quartz-community/some-plugin -c 1
```

## Upgrade fails with "Cannot auto-upgrade: you have modified framework files"

This means you've changed files that are part of the Quartz framework itself (e.g., `package.json`, files in `quartz/components/`, `quartz/plugins/`). Quartz Syncer cannot safely merge upstream changes into these files because your modifications would be overwritten.

**What to do:**

1. The error message lists exactly which files were modified. Review the list to confirm they are intentional changes.
2. Run `npx quartz upgrade` in your Quartz repository to resolve the conflicts manually using git's standard merge tools.

**What are "framework files"?**

Quartz Syncer distinguishes between files you are expected to customize and files that belong to the Quartz framework:

| Your files (preserved during upgrade) | Framework files (updated from upstream) |
|---|---|
| `quartz.config.yaml` | `package.json` |
| `quartz.lock.json` | `tsconfig.json` |
| `quartz.ts` | `quartz/components/` |
| `quartz/styles/custom.scss` | `quartz/plugins/` |
| `quartz/styles/syncer/` | `quartz/cli/` |
| `quartz/static/` (icon, OG image) | `quartz/styles/` (except `custom.scss` and `syncer/`) |
| `content/` | `quartz/processors/` |
| `.github/` | Everything else in `quartz/` |

If you need to modify framework files for your setup, use `npx quartz upgrade` instead of the in-app upgrade to handle merge conflicts manually.

## I have a different issue not listed here

Please raise an [issue on GitHub](https://github.com/saberzero1/quartz-syncer/issues).
37 changes: 37 additions & 0 deletions src/cli/handlers/upgradeHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,41 @@ describe("upgradeHandler", () => {
expect(result).toBe("Error: Git remote URL is not configured.");
expect(RepositoryConnection).not.toHaveBeenCalled();
});

it("surfaces framework modification error from upgrade", async () => {
mockUpgradeFromUpstream.mockRejectedValue(
new Error(
"Cannot auto-upgrade: you have modified framework files that would " +
"conflict with upstream changes:\n - package.json\n" +
"Run `npx quartz upgrade` manually to resolve these conflicts.",
),
);

const result = await handler({
force: "true",
format: "json",
} as CliData);

const parsed = JSON.parse(result);

expect(parsed.ok).toBe(false);
expect(parsed.error).toContain("Cannot auto-upgrade");
expect(parsed.error).toContain("package.json");
});

it("surfaces merge conflict error from upgrade", async () => {
mockUpgradeFromUpstream.mockRejectedValue(
new Error("Merge conflicts in: quartz/build.ts, quartz/cli.ts"),
);

const result = await handler({
force: "true",
format: "json",
} as CliData);

const parsed = JSON.parse(result);

expect(parsed.ok).toBe(false);
expect(parsed.error).toContain("Merge conflicts in:");
});
});
87 changes: 87 additions & 0 deletions src/quartz/QuartzUpgradeService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,90 @@ describe("QuartzUpgradeService", () => {
assert.strictEqual(status.latestUpstreamSha, "abc1234");
});
});

describe("QuartzUpgradeService.performUpgrade", () => {
it("returns success on clean merge", async () => {
const mockRepo = {
hasCommitInHistory: async () => true,
upgradeFromUpstream: async () => ({
oid: "abc123",
alreadyMerged: false,
}),
} as unknown as RepositoryConnection;

const service = new QuartzUpgradeService(mockRepo);
const result = await service.performUpgrade();

assert.strictEqual(result.success, true);
assert.strictEqual(result.oid, "abc123");
assert.strictEqual(result.alreadyMerged, false);
});

it("returns success when already merged", async () => {
const mockRepo = {
hasCommitInHistory: async () => true,
upgradeFromUpstream: async () => ({
oid: "abc123",
alreadyMerged: true,
}),
} as unknown as RepositoryConnection;

const service = new QuartzUpgradeService(mockRepo);
const result = await service.performUpgrade();

assert.strictEqual(result.success, true);
assert.strictEqual(result.alreadyMerged, true);
});

it("detects 'Cannot auto-upgrade' as conflict error", async () => {
const mockRepo = {
hasCommitInHistory: async () => true,
upgradeFromUpstream: async () => {
throw new Error(
"Cannot auto-upgrade: you have modified framework files",
);
},
} as unknown as RepositoryConnection;

const service = new QuartzUpgradeService(mockRepo);
const result = await service.performUpgrade();

assert.strictEqual(result.success, false);
assert.ok(result.error?.includes("Cannot auto-upgrade"));
assert.ok(result.error?.includes("npx quartz upgrade"));
});

it("detects 'Merge conflicts in' as conflict error", async () => {
const mockRepo = {
hasCommitInHistory: async () => true,
upgradeFromUpstream: async () => {
throw new Error(
"Merge conflicts in: package.json, tsconfig.json",
);
},
} as unknown as RepositoryConnection;

const service = new QuartzUpgradeService(mockRepo);
const result = await service.performUpgrade();

assert.strictEqual(result.success, false);
assert.ok(result.error?.includes("Merge conflicts in:"));
assert.ok(result.error?.includes("npx quartz upgrade"));
});

it("treats non-conflict errors as generic failures", async () => {
const mockRepo = {
hasCommitInHistory: async () => true,
upgradeFromUpstream: async () => {
throw new Error("Network timeout");
},
} as unknown as RepositoryConnection;

const service = new QuartzUpgradeService(mockRepo);
const result = await service.performUpgrade();

assert.strictEqual(result.success, false);
assert.ok(result.error?.includes("Network timeout"));
assert.ok(!result.error?.includes("npx quartz upgrade"));
});
});
1 change: 1 addition & 0 deletions src/quartz/QuartzUpgradeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export class QuartzUpgradeService {

const isConflict =
message.includes("Merge conflicts in:") ||
message.includes("Cannot auto-upgrade:") ||
message.includes("MergeNotSupportedError") ||
message.includes("MergeConflictError") ||
message.includes("Merges with conflicts");
Expand Down
Loading
Loading