From d5a40a7e3d2bafd1da0c5df28d5eac0cb84f63b7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 27 Jul 2026 03:37:46 +0000 Subject: [PATCH] release: prepare 1.0.0-rc.1 --- .github/workflows/finalise-release.yml | 9 ++++++- devs.md | 6 ++--- manifest.json | 2 +- package-lock.json | 10 ++++---- package.json | 2 +- src/apps/cli/Dockerfile | 6 ++--- src/apps/cli/docker-image.unit.spec.ts | 11 +++++++++ src/apps/cli/package.json | 2 +- .../cli/setup-uri-e2e-helper.unit.spec.ts | 11 +++++++++ src/apps/cli/test/test-setup-put-cat-linux.sh | 2 +- src/apps/webapp/package.json | 2 +- src/apps/webpeer/package.json | 2 +- updates.md | 24 +++++++++++++++++++ utils/release-process.unit.spec.ts | 11 ++++++--- versions.json | 3 ++- 15 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 src/apps/cli/docker-image.unit.spec.ts create mode 100644 src/apps/cli/setup-uri-e2e-helper.unit.spec.ts diff --git a/.github/workflows/finalise-release.yml b/.github/workflows/finalise-release.yml index 620c0915..9451fc7e 100644 --- a/.github/workflows/finalise-release.yml +++ b/.github/workflows/finalise-release.yml @@ -101,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}" \ @@ -118,7 +125,7 @@ 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 diff --git a/devs.md b/devs.md index 2951e8e5..d80a0b1c 100644 --- a/devs.md +++ b/devs.md @@ -264,8 +264,8 @@ 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. +- 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. @@ -297,7 +297,7 @@ The `Finalise Release Tags` and `Release Obsidian Plugin` workflows use the `rel - `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 a hyphenated pre-release includes the CLI, confirm that the CLI tag event published only immutable version and SHA-qualified image tags. +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. diff --git a/manifest.json b/manifest.json index 39908b15..def3b652 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "1.0.0-rc.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", diff --git a/package-lock.json b/package-lock.json index aadd050b..bcfc013d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "1.0.0-rc.0", + "version": "1.0.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "1.0.0-rc.0", + "version": "1.0.0-rc.1", "license": "MIT", "workspaces": [ "src/apps/cli", @@ -15913,7 +15913,7 @@ }, "src/apps/cli": { "name": "self-hosted-livesync-cli", - "version": "1.0.0-rc.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-rc.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-rc.0-webpeer", + "version": "1.0.0-rc.1-webpeer", "dependencies": { "octagonal-wheels": "^0.1.51" }, diff --git a/package.json b/package.json index 9e9da536..d391fd91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "1.0.0-rc.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", diff --git a/src/apps/cli/Dockerfile b/src/apps/cli/Dockerfile index bf6b9bf7..152710ae 100644 --- a/src/apps/cli/Dockerfile +++ b/src/apps/cli/Dockerfile @@ -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"] diff --git a/src/apps/cli/docker-image.unit.spec.ts b/src/apps/cli/docker-image.unit.spec.ts new file mode 100644 index 00000000..8834ebd6 --- /dev/null +++ b/src/apps/cli/docker-image.unit.spec.ts @@ -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"); + }); +}); diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 3be2ca45..481ae59a 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "self-hosted-livesync-cli", "private": true, - "version": "1.0.0-rc.0-cli", + "version": "1.0.0-rc.1-cli", "main": "dist/index.cjs", "type": "module", "scripts": { diff --git a/src/apps/cli/setup-uri-e2e-helper.unit.spec.ts b/src/apps/cli/setup-uri-e2e-helper.unit.spec.ts new file mode 100644 index 00000000..fc7d38b4 --- /dev/null +++ b/src/apps/cli/setup-uri-e2e-helper.unit.spec.ts @@ -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"); + }); +}); diff --git a/src/apps/cli/test/test-setup-put-cat-linux.sh b/src/apps/cli/test/test-setup-put-cat-linux.sh index 12c781a4..773833d9 100644 --- a/src/apps/cli/test/test-setup-put-cat-linux.sh +++ b/src/apps/cli/test/test-setup-put-cat-linux.sh @@ -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 () => { diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json index eac857df..bd5cbcb1 100644 --- a/src/apps/webapp/package.json +++ b/src/apps/webapp/package.json @@ -1,7 +1,7 @@ { "name": "livesync-webapp", "private": true, - "version": "1.0.0-rc.0-webapp", + "version": "1.0.0-rc.1-webapp", "type": "module", "description": "Browser-based Self-hosted LiveSync using FileSystem API", "scripts": { diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json index f49a942b..28a08597 100644 --- a/src/apps/webpeer/package.json +++ b/src/apps/webpeer/package.json @@ -1,7 +1,7 @@ { "name": "webpeer", "private": true, - "version": "1.0.0-rc.0-webpeer", + "version": "1.0.0-rc.1-webpeer", "type": "module", "scripts": { "dev": "vite", diff --git a/updates.md b/updates.md index a4b5de0c..0a7c5f95 100644 --- a/updates.md +++ b/updates.md @@ -12,6 +12,30 @@ 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 diff --git a/utils/release-process.unit.spec.ts b/utils/release-process.unit.spec.ts index 62338eb0..c9595e1b 100644 --- a/utils/release-process.unit.spec.ts +++ b/utils/release-process.unit.spec.ts @@ -234,16 +234,21 @@ describe("release workflow", () => { ); }); - 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", () => { diff --git a/versions.json b/versions.json index d176d6b5..ac318380 100644 --- a/versions.json +++ b/versions.json @@ -12,5 +12,6 @@ "1.0.0-beta.3": "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.0": "1.7.2", + "1.0.0-rc.1": "1.7.2" }