mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-27 23:13:03 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5a40a7e3d | |||
| 3ce6ccf7a6 | |||
| 8c6824b7f6 | |||
| e35583a752 | |||
| 866575add5 | |||
| 4f01e7f687 | |||
| 18e4d16c6f | |||
| da4b188b38 |
@@ -62,6 +62,7 @@ jobs:
|
||||
VERSION: ${{ inputs.version }}
|
||||
EXPECTED_HEAD_SHA: ${{ inputs.expected_head_sha }}
|
||||
PRERELEASE: ${{ inputs.prerelease }}
|
||||
PUBLISH_CLI: ${{ inputs.publish_cli }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ACTUAL_HEAD_SHA="$(git rev-parse HEAD)"
|
||||
@@ -73,6 +74,10 @@ jobs:
|
||||
echo "Version ${VERSION} is a pre-release version, but prerelease was not enabled." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${VERSION}" != *-* && "${PRERELEASE}" == "true" && "${PUBLISH_CLI}" == "true" ]]; then
|
||||
echo "A stable version staged as a pre-release must use publish_cli=false so that the CLI latest and major-minor image tags do not advance before BRAT validation." >&2
|
||||
exit 1
|
||||
fi
|
||||
node utils/release-notes.mjs validate "${VERSION}"
|
||||
|
||||
- name: Ensure and push release tags
|
||||
@@ -96,8 +101,15 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
PRERELEASE: ${{ inputs.prerelease }}
|
||||
PUBLISH_CLI: ${{ inputs.publish_cli }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${PUBLISH_CLI}" == "true" ]]; then
|
||||
gh workflow run cli-docker.yml \
|
||||
--ref "${VERSION}-cli" \
|
||||
--field dry_run=false \
|
||||
--field force=false
|
||||
fi
|
||||
gh workflow run release.yml \
|
||||
--ref "${VERSION}" \
|
||||
--field tag="${VERSION}" \
|
||||
@@ -113,15 +125,20 @@ jobs:
|
||||
{
|
||||
echo "Ensured the plug-in tag \`${VERSION}\` points to the reviewed release commit."
|
||||
if [[ "${PUBLISH_CLI}" == "true" ]]; then
|
||||
echo "The CLI tag \`${VERSION}-cli\` was also created; its tag event starts the container workflow."
|
||||
echo "The CLI tag \`${VERSION}-cli\` was also created, and finalisation explicitly dispatched the CLI container workflow."
|
||||
else
|
||||
echo "CLI publication was omitted."
|
||||
fi
|
||||
echo ""
|
||||
echo "Dispatched the plug-in release workflow for \`${VERSION}\`. After approval for the release environment, it creates a draft GitHub Release."
|
||||
echo ""
|
||||
if [[ "${PRERELEASE}" == "true" ]]; then
|
||||
echo "Publish the draft as a pre-release, keep the release pull request in draft, and merge only after BRAT validation succeeds."
|
||||
if [[ "${VERSION}" == *-* ]]; then
|
||||
echo "Publish the draft as a pre-release without replacing the latest stable release."
|
||||
echo "Keep the release pull request in draft and unmerged after BRAT validation; close it only through a separate maintainer action."
|
||||
elif [[ "${PRERELEASE}" == "true" ]]; then
|
||||
echo "Publish the draft initially as a pre-release without replacing the latest stable release."
|
||||
echo "After BRAT validation, remove the pre-release designation and make this exact release the latest stable release before merging the release pull request."
|
||||
echo "Create the stable CLI tag and publish its latest and major-minor image tags through a separate maintainer gate."
|
||||
else
|
||||
echo "Publish the draft as the latest stable release, keep the release pull request in draft, and merge only after BRAT validation succeeds."
|
||||
fi
|
||||
|
||||
@@ -244,7 +244,9 @@ export class ModuleExample extends AbstractObsidianModule {
|
||||
- Use SemVer beta identifiers such as `1.0.0-beta.0` for immutable integration previews. Increment the beta number when a published preview needs a correction. Reserve `1.0.0-rc.0` for the first feature- and contract-frozen release candidate. Historical `-patchedN` releases remain unchanged in the release history.
|
||||
- Publish a pre-release from an immutable reviewed tag, mark its GitHub Release as a pre-release, and do not replace the latest stable release.
|
||||
- A plug-in review release may omit the CLI image when the CLI artefact is not part of the required validation. When a pre-release CLI image is published, it receives immutable version and SHA-qualified tags only; it must not advance `latest` or a stable major-minor tag.
|
||||
- Keep the release pull request in draft until the exact published plug-in has passed BRAT validation. If validation fails, prepare the next pre-release version rather than moving the existing tag.
|
||||
- Keep a hyphenated pre-release's release pull request in draft and unmerged after BRAT validation. Reconcile the published version's metadata into its base branch through a reviewed metadata-only commit, then close the release pull request only through a separate maintainer action.
|
||||
- Stage a stable version for BRAT by publishing its exact `x.y.z` tag initially as a GitHub pre-release with `prerelease=true` and `publish_cli=false`. The stable manifest version would otherwise make the CLI workflow advance `latest` and the major-minor image tag before validation.
|
||||
- If validation fails, leave every published tag unchanged and prepare the next pre-release or patch version.
|
||||
|
||||
## Release Notes
|
||||
|
||||
@@ -262,15 +264,17 @@ The `Finalise Release Tags` and `Release Obsidian Plugin` workflows use the `rel
|
||||
|
||||
- Run the `Prepare Release PR` workflow with the target version and selected base branch. It creates the release branch, updates versions, confirms that Commonlib is locked to an immutable package version, moves the `## Unreleased` notes to the target version, commits the release preparation, pushes the branch, and opens a draft release PR. The base branch may already select the target development version; the workflow still runs the version lifecycle so that release-only metadata such as `versions.json` is recorded in the release commit.
|
||||
- Do not tag the release branch when the PR is first created. Polish the release PR first, especially `updates.md`.
|
||||
- Once the release PR head is fixed, run the `Finalise Release Tags` workflow with its full head commit SHA. It validates the release branch, ensures that the plug-in tag points to that commit, optionally creates the corresponding CLI tag, and dispatches the plug-in release workflow. A CLI tag starts its own container workflow. The finalisation workflow can be retried when existing tags already point to the reviewed commit, but stops if a selected tag points elsewhere.
|
||||
- The plug-in publishing workflow is intentionally dispatch-only. Pushing a plug-in tag directly does not publish a GitHub Release; use `Finalise Release Tags`, or dispatch `Release Obsidian Plugin` explicitly for recovery or a pre-release. The CLI Docker workflow retains its documented branch, tag, and manual triggers.
|
||||
- Approve the `Release Obsidian Plugin` workflow for the `release` environment, then inspect the generated draft GitHub Release. For a selected CLI publication, confirm the image tags appropriate to a stable or pre-release version.
|
||||
- Publish a stable draft as the latest release, or publish a pre-release draft without replacing the latest stable release. In either case, keep the release PR in draft and leave its base branch unchanged until BRAT validation succeeds. Record that state in the PR.
|
||||
- Once the release PR head is fixed, run the `Finalise Release Tags` workflow with its full head commit SHA. It validates the release branch, ensures that the plug-in tag points to that commit, optionally creates the corresponding CLI tag, and explicitly dispatches the selected plug-in and CLI release workflows. The finalisation workflow can be retried when existing tags already point to the reviewed commit, but stops if a selected tag points elsewhere.
|
||||
- The plug-in publishing workflow is intentionally dispatch-only. Pushing a plug-in tag directly does not publish a GitHub Release; use `Finalise Release Tags`, or dispatch `Release Obsidian Plugin` explicitly for recovery or a pre-release. When CLI publication is selected, finalisation dispatches the CLI Docker workflow against the reviewed CLI tag instead of relying on a tag created by `GITHUB_TOKEN` to start another workflow.
|
||||
- For a hyphenated pre-release, run finalisation with `prerelease=true`; CLI publication remains optional. For a stable version awaiting BRAT validation, use `prerelease=true` and `publish_cli=false`.
|
||||
- Approve the `Release Obsidian Plugin` workflow for the `release` environment, then inspect the generated draft GitHub Release. When a hyphenated pre-release includes the CLI, confirm that it received only its immutable version and SHA-qualified image tags.
|
||||
- Publish the draft as a GitHub pre-release without replacing the latest stable release. Keep its release pull request in draft and leave its base branch unchanged throughout BRAT validation. Record that state in the pull request.
|
||||
- Validate the published release through BRAT. Confirm start-up, ordinary bidirectional synchronisation, and any regression scenario relevant to the release.
|
||||
- After BRAT validation succeeds, mark the release PR ready and merge it into the selected base branch with a merge commit. This keeps the tagged release commit in that branch's history.
|
||||
- After a hyphenated pre-release passes, keep its release pull request unmerged. Add a reviewed metadata-only commit to the selected base branch which records the published version in `versions.json` and moves its exact tagged release notes out of `## Unreleased`, then close the release pull request only through a separate maintainer action.
|
||||
- After a stable version passes, remove its GitHub pre-release designation and make that exact release the latest stable release. Create the stable CLI tag and publish its `latest` and major-minor image tags, if selected, through a separate maintainer gate. Only then mark the stable release pull request ready and merge it into the selected base branch with a merge commit.
|
||||
- If BRAT validation fails, keep the release PR in draft and do not move published tags. Before preparing the next version, add a reviewed metadata-only commit to the selected base branch which records the published version in `versions.json` and moves its exact tagged release notes out of `## Unreleased`. Keep only changes made after that tag under `## Unreleased`. Compare the historical section with `git show <tag>:updates.md`; do not merge the failed release PR or describe it as validated. The next release PR can then rotate only the correction notes while preserving the immutable release history.
|
||||
- Prepare and publish the next patch or pre-release version from that reconciled base. Leave the failed release PR draft until it is deliberately closed as superseded under a separate maintainer action.
|
||||
- For a pre-release, set `prerelease=true` in `Finalise Release Tags`. A hyphenated version is rejected unless that input is enabled.
|
||||
- A hyphenated version is rejected unless `prerelease=true`. A stable version staged with `prerelease=true` is rejected unless `publish_cli=false`.
|
||||
|
||||
### Release Cheat Sheet
|
||||
|
||||
@@ -290,15 +294,16 @@ The `Finalise Release Tags` and `Release Obsidian Plugin` workflows use the `rel
|
||||
- `version`: the same target version.
|
||||
- `release_branch`: leave blank unless the release branch used a custom name.
|
||||
- `expected_head_sha`: the full head commit SHA reviewed in the release PR.
|
||||
- `prerelease`: enable for a version such as `1.0.0-rc.0`.
|
||||
- `publish_cli`: disable when the reviewed release is plug-in-only.
|
||||
- `prerelease`: enable for a version such as `1.0.0-rc.0`, and also when staging a stable version for BRAT.
|
||||
- `publish_cli`: optional for a hyphenated pre-release, but disable it when staging a stable version.
|
||||
5. Approve the `Release Obsidian Plugin` workflow for the `release` environment, then check the generated draft GitHub Release.
|
||||
6. If CLI publication was selected, confirm that the CLI tag event published the expected image tags.
|
||||
7. Publish the draft as a stable release or pre-release as selected, but keep the release PR in draft and leave its base branch unchanged.
|
||||
8. Update the PR state message to describe the published release and state that merging remains on hold until BRAT validation is complete.
|
||||
6. If a hyphenated pre-release includes the CLI, confirm that the explicitly dispatched CLI workflow published only immutable version and SHA-qualified image tags.
|
||||
7. Publish the draft as a GitHub pre-release without replacing the latest stable release, but keep the release PR in draft and leave its base branch unchanged.
|
||||
8. Update the PR state message to describe the published pre-release and state that merging remains on hold.
|
||||
9. Validate the published release through BRAT, including start-up, ordinary bidirectional synchronisation, and any release-specific regression scenario.
|
||||
10. After BRAT validation succeeds, mark the release PR ready and merge it into the selected base branch with a merge commit.
|
||||
11. If validation fails, leave the PR in draft and do not move the published tags. Reconcile the published version's `updates.md` section and `versions.json` entry into the base branch as metadata only, then prepare the next patch or pre-release version from the remaining `## Unreleased` entries.
|
||||
10. After a hyphenated pre-release passes, keep its release PR unmerged, reconcile its `versions.json` entry and exact release-note section into the selected base branch as metadata only, then close the PR through a separate maintainer action.
|
||||
11. After a stable version passes, remove its pre-release designation, make the exact release the latest stable release, publish the stable CLI tags through a separate maintainer gate if selected, then mark the release PR ready and merge it into the selected base branch.
|
||||
12. If validation fails, leave the PR in draft and do not move the published tags. Reconcile the published version's `updates.md` section and `versions.json` entry into the base branch as metadata only, then prepare the next patch or pre-release version from the remaining `## Unreleased` entries.
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ The `Hatch` recovery controls are ordered by escalation. Running **Recreate chun
|
||||
1. Stop editing the affected file, pause replication on the participating devices, and keep a separate copy of every readable version.
|
||||
2. If another device or backup has the intended content, preserve that copy before changing any revision.
|
||||
3. If the current Vault file is readable, select **Recreate current chunks**. This can restore only chunks derived from the current Vault contents; it cannot reconstruct unique bytes from an unavailable historical or conflict revision.
|
||||
4. Select **Inspect conflicts and file/database differences** → **Scan all files**.
|
||||
4. Select **Inspect conflicts and file/database differences** → **Begin inspection**.
|
||||
5. Review the database winner, every conflict revision, and any unavailable shared ancestor separately. Revision identifiers, `Δsize`, `Δtime`, and chunk availability are diagnostic evidence; they do not decide which content is correct.
|
||||
6. Use the wrench menu on the exact revision:
|
||||
- **Compare with Vault** opens a read-only comparison for readable text.
|
||||
|
||||
+1
-1
@@ -735,7 +735,7 @@ Recreate chunks from files currently present in the Vault. This can repair missi
|
||||
|
||||
Compare each Vault file with every current live revision in the local database. Each winner and conflict revision is shown separately with its exact revision identifier, local chunk availability, and relationship to the current Vault file. Unavailable shared ancestors are reported separately because they prevent conservative three-way merging but are not live revisions which can be discarded.
|
||||
|
||||
Select **Scan all files** to run the inspection. Each reported file and live revision has a wrench menu for read-only comparison, applying an exact database revision to the Vault, recording an exact byte match, preserving the Vault file as a child of a selected branch, retrying chunk retrieval, or explicitly discarding a branch. Destructive actions require confirmation. Follow [Recover a conflicted or mismatched file](recovery.md#recover-a-conflicted-or-mismatched-file) before changing revision history.
|
||||
Select **Begin inspection** to run the inspection. Each reported file and live revision has a wrench menu for read-only comparison, applying an exact database revision to the Vault, recording an exact byte match, preserving the Vault file as a child of a selected branch, retrying chunk retrieval, or explicitly discarding a branch. Destructive actions require confirmation. Follow [Recover a conflicted or mismatched file](recovery.md#recover-a-conflicted-or-mismatched-file) before changing revision history.
|
||||
|
||||
#### Resolve All conflicted files by the newer one
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "1.0.0-beta.0",
|
||||
"version": "1.0.0-rc.1",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.0-beta.0",
|
||||
"version": "1.0.0-rc.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.0-beta.0",
|
||||
"version": "1.0.0-rc.1",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"src/apps/cli",
|
||||
@@ -22,7 +22,7 @@
|
||||
"@smithy/querystring-builder": "^4.2.9",
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.0-rc.14",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.0",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fflate": "^0.8.2",
|
||||
@@ -4764,9 +4764,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vrtmrz/livesync-commonlib": {
|
||||
"version": "0.1.0-rc.14",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.0-rc.14.tgz",
|
||||
"integrity": "sha512-5aEy0x/aGNJjNdQYpQQ3VK386qlXZNInxq8pcsjf+NTV/X8jBQ+Q+omiRjYqn6BpS30xVWWeAZ/vdTeaiLsDVw==",
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.0.tgz",
|
||||
"integrity": "sha512-rdzEubzLStioanE67pps2XSGZ2UGyMIyeEoKsdPztQLLW8wM05PWApOPbJujIydHcDr2hFanbDFe89Lk7Mn4XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -15913,7 +15913,7 @@
|
||||
},
|
||||
"src/apps/cli": {
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"version": "1.0.0-beta.0-cli",
|
||||
"version": "1.0.0-rc.1-cli",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
@@ -15938,7 +15938,7 @@
|
||||
},
|
||||
"src/apps/webapp": {
|
||||
"name": "livesync-webapp",
|
||||
"version": "1.0.0-beta.0-webapp",
|
||||
"version": "1.0.0-rc.1-webapp",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.51"
|
||||
},
|
||||
@@ -15950,7 +15950,7 @@
|
||||
}
|
||||
},
|
||||
"src/apps/webpeer": {
|
||||
"version": "1.0.0-beta.0-webpeer",
|
||||
"version": "1.0.0-rc.1-webpeer",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.51"
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.0-beta.0",
|
||||
"version": "1.0.0-rc.1",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
@@ -165,7 +165,7 @@
|
||||
"@smithy/querystring-builder": "^4.2.9",
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.0-rc.14",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.0",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fflate": "^0.8.2",
|
||||
|
||||
@@ -101,9 +101,9 @@ COPY --from=runtime-deps /deps/node_modules ./node_modules
|
||||
# Copy the built CLI bundle from builder stage
|
||||
COPY --from=builder /build/src/apps/cli/dist ./dist
|
||||
|
||||
# Install entrypoint wrapper
|
||||
COPY src/apps/cli/docker-entrypoint.sh /usr/local/bin/livesync-cli
|
||||
RUN chmod +x /usr/local/bin/livesync-cli
|
||||
# Install the entrypoint wrapper with a deterministic mode, regardless of
|
||||
# source checkout permissions.
|
||||
COPY --chmod=755 src/apps/cli/docker-entrypoint.sh /usr/local/bin/livesync-cli
|
||||
|
||||
# Mount your vault / local database directory here
|
||||
VOLUME ["/data"]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dockerfile = readFileSync(new URL("./Dockerfile", import.meta.url), "utf8");
|
||||
|
||||
describe("CLI Docker image", () => {
|
||||
it("sets a deterministic readable and executable entrypoint mode", () => {
|
||||
expect(dockerfile).toContain("COPY --chmod=755 src/apps/cli/docker-entrypoint.sh /usr/local/bin/livesync-cli");
|
||||
expect(dockerfile).not.toContain("RUN chmod +x /usr/local/bin/livesync-cli");
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"private": true,
|
||||
"version": "1.0.0-beta.0-cli",
|
||||
"version": "1.0.0-rc.1-cli",
|
||||
"main": "dist/index.cjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const setupPutCatHelper = readFileSync(new URL("./test/test-setup-put-cat-linux.sh", import.meta.url), "utf8");
|
||||
|
||||
describe("CLI setup URI E2E helper", () => {
|
||||
it("evaluates Commonlib package imports as ESM", () => {
|
||||
expect(setupPutCatHelper).toContain("node --input-type=module -e");
|
||||
expect(setupPutCatHelper).not.toContain("npx tsx -e");
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ cli_test_init_settings_file "$SETTINGS_FILE"
|
||||
|
||||
echo "[INFO] creating setup URI from settings"
|
||||
SETUP_URI="$(
|
||||
SETTINGS_FILE="$SETTINGS_FILE" SETUP_PASSPHRASE="$SETUP_PASSPHRASE" npx tsx -e '
|
||||
SETTINGS_FILE="$SETTINGS_FILE" SETUP_PASSPHRASE="$SETUP_PASSPHRASE" node --input-type=module -e '
|
||||
import { fs } from "@vrtmrz/livesync-commonlib/node";
|
||||
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
(async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "livesync-webapp",
|
||||
"private": true,
|
||||
"version": "1.0.0-beta.0-webapp",
|
||||
"version": "1.0.0-rc.1-webapp",
|
||||
"type": "module",
|
||||
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webpeer",
|
||||
"private": true,
|
||||
"version": "1.0.0-beta.0-webpeer",
|
||||
"version": "1.0.0-rc.1-webpeer",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -144,7 +144,7 @@ export const liveSyncProvisionalEnglishMessages = {
|
||||
"Inspect conflicts and file/database differences",
|
||||
"Scan every Vault file and live local-database revision for conflicts, missing chunks, and differences. Each result provides actions for the exact revision.":
|
||||
"Scan every Vault file and live local-database revision for conflicts, missing chunks, and differences. Each result provides actions for the exact revision.",
|
||||
"Scan all files": "Scan all files",
|
||||
"Begin inspection": "Begin inspection",
|
||||
"Connection settings": "Connection settings",
|
||||
"Saved connections": "Saved connections",
|
||||
} as const;
|
||||
|
||||
@@ -832,7 +832,7 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
||||
)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText($msg("Scan all files"))
|
||||
.setButtonText($msg("Begin inspection"))
|
||||
.setDisabled(false)
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
|
||||
@@ -151,7 +151,7 @@ LIVESYNC_CLI_COMMAND="docker run --rm --network host --user $(id -u):$(id -g) --
|
||||
|
||||
`test:e2e:obsidian:p2p-setup-uri-workflow` runs two concurrent isolated real Obsidian sessions against the local Compose Nostr relay fixture. The first device imports a generated initial Setup URI and completes its signalling test with zero peers, creates a Setup URI for the second device through the registered command, and remains online while the second device imports it. The second device must select the expected online source before Fetch can rebuild its local database. The workflow accepts each connection request visibly on the receiving device, verifies the initial A-to-B fetch, checks that the menu for the three persistent per-peer actions remains within the viewport, reconnects both P2P sessions in join order, and verifies the B-to-A return journey. Every started session remains tracked until teardown completes.
|
||||
|
||||
`test:e2e:obsidian:startup-scan` configures a temporary CouchDB database, stops Obsidian, writes a note directly into the vault, restarts Obsidian, and verifies from CouchDB that the boot-time scan picked up the offline file.
|
||||
`test:e2e:obsidian:startup-scan` starts from a CouchDB fixture using current settings with its device-local compatibility marker already acknowledged, stops Obsidian, writes a note directly into the Vault, restarts the same isolated Vault and profile without rewriting its plug-in data, and verifies from CouchDB that the start-up scan picked up the offline file. Onboarding remains covered by `onboarding-invitation`; this scenario owns the ordinary configured restart and start-up scan.
|
||||
|
||||
`test:e2e:obsidian:setup-uri-workflow` runs the repository's public Commonlib-backed CouchDB provisioning and Setup URI tools against the local CouchDB fixture. It configures a new, empty Vault in the first real Obsidian session through the visible onboarding wizard and uses Rebuild. After that device is working, it generates a new Setup URI through the registered command; the second real Obsidian Vault uses that URI for Fetch instead of reusing the initial Setup URI produced by the provisioning tool. The workflow verifies ordinary notes from the first device to the second and back again, independently enables Hidden File Sync on each device, and verifies a snippet. The retained Setup URI screenshots show only encrypted URIs and visually masked Setup URI passphrases; plaintext credentials are not captured. Files prefixed with `guide-` capture the relevant dialogue, settings panel, or workspace leaf without transient Notices. Public documentation copies selected images only after visual inspection; the E2E run does not overwrite repository documentation assets.
|
||||
|
||||
|
||||
@@ -797,7 +797,7 @@ async function verifyHatchSurfacesAndSafeActions(): Promise<string> {
|
||||
state: "visible",
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
await liveSyncSettings.getByRole("button", { name: "Scan all files", exact: true }).waitFor({
|
||||
await liveSyncSettings.getByRole("button", { name: "Begin inspection", exact: true }).waitFor({
|
||||
state: "visible",
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
@@ -918,7 +918,7 @@ async function verifyHatchSurfacesAndSafeActions(): Promise<string> {
|
||||
await page
|
||||
.locator(".sls-setting:visible")
|
||||
.last()
|
||||
.getByRole("button", { name: "Scan all files", exact: true })
|
||||
.getByRole("button", { name: "Begin inspection", exact: true })
|
||||
.click({
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
|
||||
@@ -373,7 +373,7 @@ async function main(): Promise<void> {
|
||||
exact: true,
|
||||
}),
|
||||
});
|
||||
await verifySetting.getByRole("button", { name: "Scan all files", exact: true }).click({
|
||||
await verifySetting.getByRole("button", { name: "Begin inspection", exact: true }).click({
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
const card = repairCard(settings);
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Proves that a configured LiveSync Vault scans files created while Obsidian
|
||||
* was stopped. The first launch receives a CouchDB profile using current
|
||||
* settings and its acknowledged device-local compatibility marker before the
|
||||
* plug-in loads.
|
||||
*
|
||||
* The second launch reuses the same Vault, profile, local database, and
|
||||
* settings without rewriting plug-in data, so the assertion covers an
|
||||
* ordinary configured restart rather than the separate onboarding flow.
|
||||
*/
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import {
|
||||
@@ -11,7 +21,8 @@ import {
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import {
|
||||
assertEqual,
|
||||
configureCouchDb,
|
||||
createE2eCouchDbPluginData,
|
||||
createE2eObsidianDeviceLocalState,
|
||||
prepareRemote,
|
||||
pushLocalChanges,
|
||||
waitForLiveSyncCoreReady,
|
||||
@@ -47,6 +58,12 @@ async function main(): Promise<void> {
|
||||
|
||||
const couchDb = await loadCouchDbConfig();
|
||||
const dbName = makeUniqueDatabaseName(couchDb.dbPrefix, "startup-scan");
|
||||
const couchDbSettings = {
|
||||
uri: couchDb.uri,
|
||||
username: couchDb.username,
|
||||
password: couchDb.password,
|
||||
dbName,
|
||||
};
|
||||
const vault = await createTemporaryVault();
|
||||
let session: ObsidianLiveSyncSession | undefined;
|
||||
|
||||
@@ -63,15 +80,11 @@ async function main(): Promise<void> {
|
||||
cliBinary: cli.binary,
|
||||
vault,
|
||||
startupGraceMs: Number(process.env.E2E_OBSIDIAN_STARTUP_GRACE_MS ?? 1000),
|
||||
pluginData: createE2eCouchDbPluginData(couchDbSettings),
|
||||
localStorageEntries: createE2eObsidianDeviceLocalState(vault.name),
|
||||
});
|
||||
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
const configured = await configureCouchDb(cli.binary, session.cliEnv, {
|
||||
uri: couchDb.uri,
|
||||
username: couchDb.username,
|
||||
password: couchDb.password,
|
||||
dbName,
|
||||
});
|
||||
assertEqual(configured.isConfigured, true, "Self-hosted LiveSync was not configured.");
|
||||
const initialReadiness = await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
assertEqual(initialReadiness.configured, true, "Self-hosted LiveSync did not start configured.");
|
||||
await prepareRemote(cli.binary, session.cliEnv);
|
||||
await session.app.stop();
|
||||
session = undefined;
|
||||
@@ -84,7 +97,8 @@ async function main(): Promise<void> {
|
||||
vault,
|
||||
startupGraceMs: Number(process.env.E2E_OBSIDIAN_STARTUP_GRACE_MS ?? 1000),
|
||||
});
|
||||
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
const restartedReadiness = await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
assertEqual(restartedReadiness.configured, true, "Self-hosted LiveSync lost its configuration on restart.");
|
||||
|
||||
const localEntry = await waitForLocalDatabaseEntry(cli.binary, session.cliEnv, notePath);
|
||||
await pushLocalChanges(cli.binary, session.cliEnv);
|
||||
|
||||
+96
-1
@@ -12,9 +12,104 @@ Earlier releases remain available in the 0.25 release history and the legacy rel
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.0.0-rc.1
|
||||
|
||||
27th July, 2026
|
||||
|
||||
The work towards 1.0 has become so substantial that I have written [an article about it](https://fancy-syncing.vrtmrz.net/blog/0036-livesync-1_0_0-en.html) (linked again here).
|
||||
|
||||
### Important
|
||||
|
||||
- This candidate retains the plug-in behaviour prepared for rc.0. The version was advanced because release tags are immutable; rc.0 was stopped during CLI validation before a plug-in release was published.
|
||||
- This remains an opt-in pre-release for BRAT validation and does not replace the latest stable release. The exact rc.1 plug-in and CLI artefacts will be validated separately after publication.
|
||||
|
||||
### CLI and release validation
|
||||
|
||||
- CLI release validation now generates Setup URIs through the supported ESM package interface, allowing the Docker test to reach the CLI container instead of stopping during test preparation.
|
||||
- The CLI Docker image now assigns its entrypoint permissions explicitly, so non-root execution does not depend on permissions inherited from the source checkout.
|
||||
- Release finalisation now explicitly dispatches the CLI container workflow when CLI publication is selected, rather than relying on a workflow-created tag to start another workflow.
|
||||
- Focused regression tests guard the ESM execution mode and deterministic container entrypoint permissions, while the existing release-workflow tests now require explicit CLI dispatch with non-dry-run, immutable-tag inputs.
|
||||
|
||||
### Testing
|
||||
|
||||
- The native CLI setup, put, cat, list, information, deletion, conflict-resolution, and revision-retrieval scenario completed with the packaged Commonlib dependency.
|
||||
- The same scenario completed through the rebuilt non-root Docker image.
|
||||
- The focused CLI and release-workflow unit tests passed after first demonstrating all three regressions against the unmodified implementation.
|
||||
|
||||
## 1.0.0-rc.0
|
||||
|
||||
27th July, 2026
|
||||
|
||||
The work towards 1.0 has become so substantial that I have written [an article about it](https://fancy-syncing.vrtmrz.net/blog/0036-livesync-1_0_0-en.html) (linked again here).
|
||||
|
||||
### Important
|
||||
|
||||
- This is the first 1.0 release candidate. It remains an opt-in pre-release for BRAT validation and does not replace the latest stable release. Update every participating device before resuming synchronisation, and continue to use a current backup while testing with an existing Vault.
|
||||
- An upgraded, copied, or restored Vault may pause replication for an explicit compatibility review. Existing automatic synchronisation choices are preserved and resume only after the decision has been saved.
|
||||
|
||||
### Changes consolidated from beta.0 through beta.5
|
||||
|
||||
#### Setup and compatibility
|
||||
|
||||
- An unconfigured Vault now waits for the user to start setup. Onboarding is offered through a persistent Notice and remains available from **Self-hosted LiveSync settings** → **Setup**.
|
||||
- Setup now creates named CouchDB, Object Storage, and P2P connections. Setup URIs preserve their connection names and selections, and reserve Fetch or Rebuild before the ordinary start-up scan begins.
|
||||
- Existing Vaults retain their effective legacy settings, including the case-insensitive file-name fallback used when an older release had no explicit case setting.
|
||||
- Manual CouchDB setup distinguishes creating the first database from connecting another device. Onboarding requires a successful connection, while Settings can explicitly save an unverified connection and offers each server-setting correction separately.
|
||||
- Compatible differences limited to the chunk hash algorithm, chunk size, or splitter version are aligned automatically by default. Existing chunks remain readable, an explicit opt-out remains available, and differences involving incompatible settings still require review.
|
||||
|
||||
#### Conflict handling and recovery
|
||||
|
||||
- Automatic text and structured-data merge now uses the nearest revision actually shared by both branches. A resolution received from another device no longer recreates the same conflict merely because the Vault still contains the exact content of the removed branch.
|
||||
- Edits, logical deletions, and renames made while a file remains conflicted extend the revision displayed on that device. When the relationship cannot be proved, LiveSync preserves the branches for review.
|
||||
- **Not now** postpones repeated automatic merge dialogues while retaining the unresolved-conflict warning. Three or more live revisions are reviewed one reproducible pair at a time, completed pairs remain resolved across restart, and explicit commands can reopen a postponed conflict.
|
||||
- **Inspect conflicts and file/database differences** compares the Vault with the database winner and every live conflict revision. Compact indicators show missing chunks, `Δsize`, `Δtime`, whether the Vault matches the winner, and whether conflicts remain.
|
||||
- Each reported file and live revision has a compact wrench menu for comparison, applying an exact readable revision, recording an exact byte match, storing the Vault content as a child of a selected branch, retrying missing chunks without changing the tree, or explicitly discarding one selected live branch.
|
||||
- Unreadable live revisions are preserved during automatic handling. An absent Vault file and a winning logical deletion are treated as agreement unless another live branch still requires attention.
|
||||
- Garbage Collection V3 is limited to CouchDB and now protects every live conflict branch, required shared ancestry, and shared chunks. It stops when device progress cannot be verified and reports compaction failure without a contradictory success message.
|
||||
|
||||
#### P2P and optional synchronisation features
|
||||
|
||||
- P2P and Hidden File Sync remain supported opt-in features. Customisation Sync remains a supported Advanced workflow, while Data Compression remains available but disabled by default.
|
||||
- P2P controls remain outside the ordinary CouchDB experience until P2P is configured. The current status pane distinguishes announcing changes, following a peer, and persistent per-device actions.
|
||||
- First-device P2P setup can complete its signalling test without another peer online. Fetch on an additional device still requires an available source peer and a completed P2P Rebuild.
|
||||
- P2P setup and guidance now distinguish the required signalling relay from optional TURN, describe the replaceable public relay's privacy and availability limits, and reliably close and recreate relay connections across settings changes and database resets.
|
||||
- Enabling Hidden File Sync opens one progress Notice before saving the setting and reuses it until the initial scan has finished instead of stacking phase, reload, and restart messages.
|
||||
- Broadening selectors, ignore rules, size or modification-time limits, or file-name case handling now rechecks previously received files without requiring another remote update.
|
||||
|
||||
#### Interface and operations
|
||||
|
||||
- Command-palette actions now use clearer names and appear only when their feature and current context make them usable. Renamed commands retain their identifiers so that existing hotkeys continue to work.
|
||||
- Setup and review dialogue text can be selected for copying or translation. Applying an available interface translation no longer holds start-up behind an unsolicited dialogue; a persistent Notice opens the existing details on demand.
|
||||
- Action buttons are arranged for narrow mobile screens, long dialogues keep their controls reachable, and persistent Notices no longer cover close controls.
|
||||
- Remote-size warnings use persistent clickable Notices. Initial uploads and Rebuild no longer ask to send every chunk in advance; ordinary replication completes the transfer.
|
||||
- Obsolete controls for the plug-in trash setting and fixed chunk revisions were removed. The Change Log remains available but no longer opens automatically or tracks an unread count.
|
||||
|
||||
#### Other fixes and security
|
||||
|
||||
- The optional Custom HTTP Handler used by Object Storage sends the correct byte range from binary request bodies and reports unsupported body types instead of silently sending an empty request.
|
||||
- Fly.io setup generates CouchDB and Vault encryption secrets with cryptographically secure randomness. Dependency updates address excessive CPU use from crafted path patterns and `mailto:` links, and the CLI rejects detected path traversal and symbolic-link components before Vault operations.
|
||||
- Self-hosted LiveSync now owns its translation catalogue. Commonlib supplies canonical English to other consumers, while translation contributions can be made in the main Self-hosted LiveSync repository.
|
||||
|
||||
### Changes since beta.5
|
||||
|
||||
- The Hatch action for **Inspect conflicts and file/database differences** is now labelled **Begin inspection** so that its purpose is clear without repeating the setting name.
|
||||
- Start-up and full-inspection scans now omit built-in legacy LiveSync log files and recovery flag files before comparing Vault and local-database state. Existing ignored database records remain untouched, and user-configured ignore behaviour is unchanged.
|
||||
|
||||
### Testing
|
||||
|
||||
- Expanded automated Real Obsidian coverage for upgrades, two-device synchronisation, CouchDB, Object Storage, P2P, Hidden File Sync, mobile dialogues, conflict and revision recovery, failure diagnostics, and strict clean-up.
|
||||
- Real CouchDB integration coverage verifies logical deletion, shared and conflict chunk retention, compaction, downstream replication, and recreation of content-addressed chunks.
|
||||
- An encrypted Real Obsidian reconnect scenario replaces the remote Security Seed while one client retains the previous value, verifies that synchronisation adopts the replacement without restoring the old value, and proves a bidirectional encrypted round-trip.
|
||||
- The beta series was exercised through BRAT on macOS, iOS, and Android, including upgrade from 0.25.83, bidirectional synchronisation, P2P setup, conflict handling, recovery controls, mobile layouts, and start-up with existing configurations. The exact RC artefact will be validated separately after publication.
|
||||
|
||||
## 1.0.0-beta.5
|
||||
|
||||
26th July, 2026
|
||||
|
||||
### Improved
|
||||
|
||||
- **Inspect conflicts and file/database differences** now compares the current Vault file with every live database revision. Compact, mobile-friendly indicators show missing chunks, `Δsize`, `Δtime`, whether the Vault matches the winner, and whether conflict branches remain. Each revision's wrench menu can compare readable text, reflect that exact revision to the Vault, record an exact byte match, store the Vault content as a child of that branch, or discard only that branch.
|
||||
- **Inspect conflicts and file/database differences** now compares the current Vault file with the database winner and every live conflict revision. Compact, mobile-friendly indicators show missing chunks, `Δsize`, `Δtime`, whether the Vault matches the winner, and whether conflict branches remain.
|
||||
- Each reported file and live revision now has a compact wrench menu. Its available actions can compare readable text, apply the selected revision to the Vault, record an exact byte match, store the Vault content as a child of the selected branch, retry retrieving missing chunks without changing the revision tree, or discard only the selected live branch after confirmation.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ function inlineCode(value) {
|
||||
/**
|
||||
* Render the reader-facing checklist for a draft release pull request.
|
||||
*
|
||||
* The version decides whether publication is stable or pre-release. The base
|
||||
* branch is included explicitly because integration previews can target a
|
||||
* reviewed integration branch rather than `main`.
|
||||
* The version decides whether the release commit is an immutable SemVer
|
||||
* pre-release or a stable version staged through a GitHub pre-release. The
|
||||
* base branch is included explicitly because integration previews can target
|
||||
* a reviewed integration branch rather than `main`.
|
||||
*
|
||||
* @param {string} version
|
||||
* @param {string} baseBranch
|
||||
@@ -39,13 +40,28 @@ export function renderReleasePrBody(version, baseBranch) {
|
||||
const isPrerelease = selectedVersion.includes("-");
|
||||
const purpose = isPrerelease
|
||||
? `an immutable pre-release for BRAT validation without replacing the latest stable release`
|
||||
: `the next stable release`;
|
||||
: `a stable version which will first be staged as a GitHub pre-release for BRAT validation`;
|
||||
const finaliseInstruction = isPrerelease
|
||||
? "Run the finalise release workflow with this PR's fixed head SHA and `prerelease=true`"
|
||||
: "Run the finalise release workflow with this PR's fixed head SHA and `prerelease=false`";
|
||||
: "Run the finalise release workflow with this PR's fixed head SHA, `prerelease=true`, and `publish_cli=false`";
|
||||
const publicationInstruction = isPrerelease
|
||||
? "Publish the GitHub Release as a pre-release without replacing the latest stable release, while keeping this pull request in draft"
|
||||
: "Publish the GitHub Release as the latest stable release while keeping this pull request in draft";
|
||||
: "Publish the GitHub Release initially as a pre-release without replacing the latest stable release, while keeping this pull request in draft";
|
||||
const assetInstruction = isPrerelease
|
||||
? "Confirm the draft GitHub Release assets and the published CLI image, if selected"
|
||||
: "Confirm the draft GitHub Release assets; keep stable CLI publication deferred until BRAT validation passes";
|
||||
const holdInstruction = isPrerelease
|
||||
? `Publishing and validating this pre-release does not unblock this pull request. Keep it in draft and unmerged, and leave ${baseBranchCode} unchanged.`
|
||||
: `Publishing the GitHub pre-release does not unblock this pull request. Keep it in draft, and leave ${baseBranchCode} unchanged, until the exact published build has passed BRAT validation and has been promoted to the latest stable release.`;
|
||||
const completionInstructions = isPrerelease
|
||||
? [
|
||||
"- [ ] Keep this pre-release pull request unmerged; close it only through a separate maintainer action",
|
||||
]
|
||||
: [
|
||||
"- [ ] Remove the pre-release designation and make this exact release the latest stable release",
|
||||
"- [ ] Create the stable CLI tag and publish its `latest` and major-minor image tags, if selected, through a separate maintainer gate",
|
||||
`- [ ] Mark this pull request ready and merge it into ${baseBranchCode} with a merge commit`,
|
||||
];
|
||||
|
||||
return [
|
||||
`This release pull request prepares Self-hosted LiveSync ${versionCode} from ${baseBranchCode} as ${purpose}.`,
|
||||
@@ -53,7 +69,7 @@ export function renderReleasePrBody(version, baseBranch) {
|
||||
"> [!IMPORTANT]",
|
||||
"> **Merge intentionally on hold**",
|
||||
">",
|
||||
`> Publishing the GitHub Release does not unblock this pull request. Keep this pull request in draft, and leave ${baseBranchCode} unchanged, until the exact published build has passed BRAT validation.`,
|
||||
`> ${holdInstruction}`,
|
||||
"",
|
||||
"## Release checklist",
|
||||
"",
|
||||
@@ -62,10 +78,10 @@ export function renderReleasePrBody(version, baseBranch) {
|
||||
"- [ ] Confirm `manifest.json`, `versions.json`, workspace package versions, and the locked Commonlib package version",
|
||||
"- [ ] Confirm CI has passed",
|
||||
`- [ ] ${finaliseInstruction}`,
|
||||
"- [ ] Confirm the draft GitHub Release assets and the published CLI image, if selected",
|
||||
`- [ ] ${assetInstruction}`,
|
||||
`- [ ] ${publicationInstruction}`,
|
||||
"- [ ] Validate the exact published release with BRAT",
|
||||
`- [ ] Mark this pull request ready and merge it into ${baseBranchCode} with a merge commit`,
|
||||
...completionInstructions,
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { renderReleasePrBody } from "./release-pr-body.mjs";
|
||||
import { ensureTags } from "./release-tags.mjs";
|
||||
|
||||
const releaseNotesScript = fileURLToPath(new URL("./release-notes.mjs", import.meta.url));
|
||||
const releasePrBodyScript = fileURLToPath(new URL("./release-pr-body.mjs", import.meta.url));
|
||||
const versionBumpScript =
|
||||
process.env.VERSION_BUMP_SCRIPT || fileURLToPath(new URL("../version-bump.mjs", import.meta.url));
|
||||
const workspaceUpdateScript = fileURLToPath(new URL("../update-workspaces.mjs", import.meta.url));
|
||||
@@ -160,13 +160,12 @@ describe("release notes", () => {
|
||||
describe("release workflow", () => {
|
||||
it("uses the locked Commonlib package instead of generated fallback declarations", () => {
|
||||
const workflow = readFileSync(prepareReleaseWorkflow, "utf8");
|
||||
const body = runNode(releasePrBodyScript, ["1.0.0-beta.0", "integration"], makeTemporaryDirectory());
|
||||
const body = renderReleasePrBody("1.0.0-beta.0", "integration");
|
||||
|
||||
expect(workflow).not.toContain("npm run build:lib:types");
|
||||
expect(workflow).not.toMatch(/git add[^\n]*_types/);
|
||||
expect(workflow).toMatch(/git add[^\n]*package-lock\.json/);
|
||||
expect(body.status, body.stderr).toBe(0);
|
||||
expect(body.stdout).toContain("locked Commonlib package version");
|
||||
expect(body).toContain("locked Commonlib package version");
|
||||
});
|
||||
|
||||
it("reruns the version lifecycle when the integration branch already selects the release version", () => {
|
||||
@@ -183,48 +182,73 @@ describe("release workflow", () => {
|
||||
expect(workflow).not.toContain("latest stable release");
|
||||
});
|
||||
|
||||
it("keeps the release PR in draft until BRAT validation", () => {
|
||||
const prerelease = runNode(
|
||||
releasePrBodyScript,
|
||||
["1.0.0-beta.0", "common-library-package-boundary"],
|
||||
makeTemporaryDirectory()
|
||||
);
|
||||
it("keeps an immutable pre-release out of its base branch after BRAT validation", () => {
|
||||
const prerelease = renderReleasePrBody("1.0.0-rc.0", "common-library-package-boundary");
|
||||
|
||||
expect(prerelease.status, prerelease.stderr).toBe(0);
|
||||
expect(prerelease.stdout).toContain("Merge intentionally on hold");
|
||||
expect(prerelease.stdout).toContain("Self-hosted LiveSync `1.0.0-beta.0`");
|
||||
expect(prerelease.stdout).toContain("leave `common-library-package-boundary` unchanged");
|
||||
expect(prerelease.stdout).toContain("prerelease=true");
|
||||
expect(prerelease.stdout).toContain(
|
||||
expect(prerelease).toContain("Merge intentionally on hold");
|
||||
expect(prerelease).toContain("Self-hosted LiveSync `1.0.0-rc.0`");
|
||||
expect(prerelease).toContain("leave `common-library-package-boundary` unchanged");
|
||||
expect(prerelease).toContain("prerelease=true");
|
||||
expect(prerelease).toContain(
|
||||
"Publish the GitHub Release as a pre-release without replacing the latest stable release"
|
||||
);
|
||||
expect(prerelease.stdout).toContain("Validate the exact published release with BRAT");
|
||||
expect(prerelease.stdout).toContain(
|
||||
"Mark this pull request ready and merge it into `common-library-package-boundary` with a merge commit"
|
||||
expect(prerelease).toContain("Validate the exact published release with BRAT");
|
||||
expect(prerelease).toContain("Keep this pre-release pull request unmerged");
|
||||
expect(prerelease).toContain("close it only through a separate maintainer action");
|
||||
expect(prerelease).not.toContain("Mark this pull request ready and merge it");
|
||||
});
|
||||
|
||||
it("publishes a stable version initially as a GitHub pre-release for BRAT validation", () => {
|
||||
const stable = renderReleasePrBody("1.0.0", "main");
|
||||
|
||||
expect(stable).toContain("prerelease=true");
|
||||
expect(stable).toContain("publish_cli=false");
|
||||
expect(stable).toContain(
|
||||
"Publish the GitHub Release initially as a pre-release without replacing the latest stable release"
|
||||
);
|
||||
expect(stable).toContain(
|
||||
"Remove the pre-release designation and make this exact release the latest stable release"
|
||||
);
|
||||
expect(stable).toContain(
|
||||
"Create the stable CLI tag and publish its `latest` and major-minor image tags, if selected, through a separate maintainer gate"
|
||||
);
|
||||
expect(stable).toContain("Mark this pull request ready and merge it into `main` with a merge commit");
|
||||
expect(stable).not.toContain("prerelease=false");
|
||||
});
|
||||
|
||||
it("summarises immutable pre-releases separately from stable versions awaiting promotion", () => {
|
||||
const workflow = readFileSync(finaliseReleaseWorkflow, "utf8");
|
||||
|
||||
expect(workflow).toContain('if [[ "${VERSION}" == *-* ]]; then');
|
||||
expect(workflow).toContain(
|
||||
"Keep the release pull request in draft and unmerged after BRAT validation; close it only through a separate maintainer action."
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"After BRAT validation, remove the pre-release designation and make this exact release the latest stable release before merging the release pull request."
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
'if [[ "${VERSION}" != *-* && "${PRERELEASE}" == "true" && "${PUBLISH_CLI}" == "true" ]]; then'
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"A stable version staged as a pre-release must use publish_cli=false so that the CLI latest and major-minor image tags do not advance before BRAT validation."
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps stable release instructions distinct from pre-release instructions", () => {
|
||||
const stable = runNode(releasePrBodyScript, ["1.0.0", "main"], makeTemporaryDirectory());
|
||||
|
||||
expect(stable.status, stable.stderr).toBe(0);
|
||||
expect(stable.stdout).toContain("prerelease=false");
|
||||
expect(stable.stdout).toContain(
|
||||
"Publish the GitHub Release as the latest stable release while keeping this pull request in draft"
|
||||
);
|
||||
expect(stable.stdout).not.toContain("as a pre-release without replacing");
|
||||
});
|
||||
|
||||
it("dispatches the plug-in workflow and lets the CLI tag trigger its own workflow", () => {
|
||||
it("dispatches the selected plug-in and CLI workflows explicitly", () => {
|
||||
const workflow = readFileSync(finaliseReleaseWorkflow, "utf8");
|
||||
|
||||
expect(workflow).toContain("actions: write");
|
||||
expect(workflow).toContain('node utils/release-tags.mjs ensure "${VERSION}" "${EXPECTED_HEAD_SHA}"');
|
||||
expect(workflow).toContain('git push --atomic origin "refs/tags/${VERSION}" "refs/tags/${VERSION}-cli"');
|
||||
expect(workflow).not.toContain("Tag already exists");
|
||||
expect(workflow).toContain("PUBLISH_CLI: ${{ inputs.publish_cli }}");
|
||||
expect(workflow).toContain('if [[ "${PUBLISH_CLI}" == "true" ]]; then');
|
||||
expect(workflow).toContain("gh workflow run cli-docker.yml");
|
||||
expect(workflow).toContain('--ref "${VERSION}-cli"');
|
||||
expect(workflow).toContain("--field dry_run=false");
|
||||
expect(workflow).toContain("--field force=false");
|
||||
expect(workflow).toContain("gh workflow run release.yml");
|
||||
expect(workflow).not.toContain("gh workflow run cli-docker.yml");
|
||||
expect(workflow).toContain("its tag event starts the container workflow");
|
||||
expect(workflow).toContain("explicitly dispatched the CLI container workflow");
|
||||
});
|
||||
|
||||
it("publishes only by explicit dispatch and validates the selected release", () => {
|
||||
|
||||
+4
-1
@@ -10,5 +10,8 @@
|
||||
"1.0.0-beta.1": "1.7.2",
|
||||
"1.0.0-beta.2": "1.7.2",
|
||||
"1.0.0-beta.3": "1.7.2",
|
||||
"1.0.0-beta.4": "1.7.2"
|
||||
"1.0.0-beta.4": "1.7.2",
|
||||
"1.0.0-beta.5": "1.7.2",
|
||||
"1.0.0-rc.0": "1.7.2",
|
||||
"1.0.0-rc.1": "1.7.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user