Skip to content

Commit 30b6ba6

Browse files
committed
feat: ship self-contained DSCODE terminal packages
Bundle a private pinned runtime and native dependencies, isolate user data from existing DSH installations, and replace whole packages during updates. Preserve native settings in a separate user layer and retain runtime leases until descendants exit. Switch terminal release CI to verified ARM64 and Intel GitHub assets, generate a matching Homebrew formula, and document curl installation and legacy migration. Validated full repository checks, focused data/update regressions, and both packaged architectures locally (Intel via Rosetta).
1 parent 1a21336 commit 30b6ba6

56 files changed

Lines changed: 1501 additions & 630 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/release.yml‎

Lines changed: 57 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88
inputs:
99
publish:
10-
description: Publish the verified packages to npm and the Hub
10+
description: Publish verified self-contained packages and the Desktop plugin
1111
type: boolean
1212
default: false
1313
verify_credentials:
@@ -42,25 +42,66 @@ jobs:
4242
# `npm run setup` writes into node_modules, and nothing else applies it.
4343
- run: npm run setup
4444
- run: npm run check
45-
- name: Build the release candidates
46-
env:
47-
DSCODE_REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
45+
- name: Build the self-contained release candidates
4846
run: |
49-
npm run build:packages
50-
npm run release:hub
5147
npm run dist
52-
- run: npm run verify:hub
48+
node scripts/homebrew-formula.mjs
49+
- run: node scripts/verify-standalone.mjs
5350
# One tarball keeps the exact layout: upload-artifact roots the archive at the
5451
# least common ancestor of its paths, which silently dropped the `artifacts/`
55-
# prefix and left the publish job without bundle-pack.json.
52+
# prefix and left the publish job without the expected artifact paths.
5653
- name: Pack the verified artifacts for the publish job
57-
run: tar -czf release-candidates.tar.gz artifacts/npm artifacts/local/hub-verification.json artifacts/dscode-*.tar.gz
54+
run: tar -czf release-candidates.tar.gz artifacts/dscode-*.tar.gz artifacts/dscode-*.sha256 artifacts/dscode.rb artifacts/local/standalone-*.json
5855
- uses: actions/upload-artifact@v4
5956
with:
6057
name: release-candidates
6158
if-no-files-found: error
6259
path: release-candidates.tar.gz
6360

61+
verify-intel:
62+
needs: build
63+
runs-on: macos-15-intel
64+
steps:
65+
- uses: actions/checkout@v4
66+
- uses: actions/setup-node@v4
67+
with:
68+
node-version: 24
69+
- uses: actions/download-artifact@v4
70+
with:
71+
name: release-candidates
72+
- run: tar -xzf release-candidates.tar.gz
73+
- run: node scripts/verify-standalone.mjs
74+
- uses: actions/upload-artifact@v4
75+
with:
76+
name: standalone-intel-verification
77+
path: artifacts/local/standalone-darwin-x64.json
78+
79+
publish-terminal:
80+
needs: [build, verify-intel]
81+
if: github.ref_type == 'tag' || inputs.publish
82+
runs-on: macos-14
83+
environment: release
84+
permissions:
85+
contents: write
86+
steps:
87+
- uses: actions/checkout@v4
88+
- uses: actions/download-artifact@v4
89+
with:
90+
name: release-candidates
91+
- run: tar -xzf release-candidates.tar.gz
92+
- name: Publish the same complete packages used by installers and Homebrew
93+
env:
94+
GH_TOKEN: ${{ github.token }}
95+
run: |
96+
version=$(node -p "require('./package.json').version")
97+
test -f "docs/releases/$version.md"
98+
set -- "artifacts/dscode-$version-darwin-arm64-standalone.tar.gz" "artifacts/dscode-$version-darwin-x64-standalone.tar.gz" artifacts/dscode-*.sha256 artifacts/dscode.rb
99+
if gh release view "v$version" >/dev/null 2>&1; then
100+
gh release upload "v$version" "$@" --clobber
101+
else
102+
gh release create "v$version" "$@" --title "DSCODE $version" --notes-file "docs/releases/$version.md"
103+
fi
104+
64105
desktop:
65106
runs-on: macos-14
66107
timeout-minutes: 20
@@ -85,9 +126,9 @@ jobs:
85126
if-no-files-found: error
86127
path: desktop-candidate.tar.gz
87128

88-
publish:
89-
needs: [build, desktop]
90-
if: github.ref_type == 'tag' || inputs.publish || inputs.verify_credentials
129+
publish-desktop:
130+
needs: [desktop, publish-terminal]
131+
if: always() && needs.desktop.result == 'success' && (needs.publish-terminal.result == 'success' || inputs.verify_credentials)
91132
runs-on: macos-14
92133
timeout-minutes: 60
93134
environment: release
@@ -100,46 +141,16 @@ jobs:
100141
node-version: 24
101142
cache: npm
102143
- run: npm ci --ignore-scripts
103-
- uses: actions/download-artifact@v4
104-
with:
105-
name: release-candidates
106144
- uses: actions/download-artifact@v4
107145
with:
108146
name: desktop-candidate
109-
- name: Restore the verified artifacts
110-
run: |
111-
tar -xzf release-candidates.tar.gz
112-
tar -xzf desktop-candidate.tar.gz
113-
test -f artifacts/npm/bundle-pack.json || { echo "::error::the release candidate archive is incomplete"; exit 1; }
114-
test -f artifacts/local/hub-verification.json || { echo "::error::the hub verification record is missing"; exit 1; }
115-
# Read-only: both tokens authenticate and this version is still free. A manual
116-
# dispatch with verify_credentials stops here.
117-
- name: Check the publishing credentials
147+
- name: Restore and check the Desktop candidate
118148
env:
119149
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
120150
DSH_HUB_TOKEN: ${{ secrets.DSH_HUB_TOKEN }}
121151
run: |
122-
test -n "$NPM_PUBLISH_TOKEN" || { echo "::error::add the NPM_PUBLISH_TOKEN secret (granular npm token with bypass 2FA for the launcher, bundle and Desktop package)"; exit 1; }
123-
test -n "$DSH_HUB_TOKEN" || { echo "::error::add the DSH_HUB_TOKEN secret (Hub CI token)"; exit 1; }
124-
node scripts/check-publish-credentials.mjs
152+
tar -xzf desktop-candidate.tar.gz
125153
npm run publish:desktop -- credentials
126-
# Never publish the launcher before its Hub release resolves: the three
127-
# phases each re-check the artifact hashes they verified above.
128-
- name: Publish the bundle to npm
129-
if: github.ref_type == 'tag' || inputs.publish
130-
env:
131-
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
132-
run: npm run publish:hub -- bundle
133-
- name: Sync npm and publish the Hub profile
134-
if: github.ref_type == 'tag' || inputs.publish
135-
env:
136-
DSH_HUB_TOKEN: ${{ secrets.DSH_HUB_TOKEN }}
137-
run: npm run publish:hub -- profile
138-
- name: Publish the launcher to npm
139-
if: github.ref_type == 'tag' || inputs.publish
140-
env:
141-
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
142-
run: npm run publish:hub -- launcher
143154
- name: Publish the qualified Desktop plugin and verify public discovery
144155
if: github.ref_type == 'tag' || inputs.publish
145156
env:
@@ -156,20 +167,10 @@ jobs:
156167
name: desktop-public-verification
157168
if-no-files-found: error
158169
path: artifacts/local/desktop-public-install.json
159-
# The curl installer, `dscode update` and the README all fetch these tarballs from
160-
# the release: the prebuilt ones install without npm, the source one is the fallback.
161-
- name: Attach the release tarballs to the GitHub release
170+
- name: Attach Desktop assets to the existing release
162171
if: github.ref_type == 'tag' || inputs.publish
163172
env:
164173
GH_TOKEN: ${{ github.token }}
165174
run: |
166175
version=$(node -p "require('./package.json').version")
167-
set -- "artifacts/dscode-$version.tar.gz" "artifacts/dscode-$version-darwin-arm64.tar.gz" "artifacts/dscode-$version-darwin-x64.tar.gz" artifacts/desktop/release/*.tgz artifacts/desktop/release/*.sha256 artifacts/desktop/release/*-README.md artifacts/desktop/release/*-release-notes.md
168-
for tarball in "$@"; do
169-
test -f "$tarball" || { echo "::error::missing $tarball"; exit 1; }
170-
done
171-
if gh release view "v$version" >/dev/null 2>&1; then
172-
gh release upload "v$version" "$@" --clobber
173-
else
174-
gh release create "v$version" "$@" --title "DSCODE $version" --notes-file "docs/releases/$version.md"
175-
fi
176+
gh release upload "v$version" artifacts/desktop/release/*.tgz artifacts/desktop/release/*.sha256 artifacts/desktop/release/*-README.md artifacts/desktop/release/*-release-notes.md --clobber

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ For agents working in this repository. The human contribution flow lives in [CON
66

77
**Rule: a release is not just a version bump.** These four items ship in the same change as the code; if any one is missing, the tree is not releasable.
88

9-
1. **Release notes** `docs/releases/<version>.md` must exist. `make attach` refuses outright while the GitHub release does not exist yet: `docs/releases/<version>.md is missing; write the release notes first`. Follow the recent files (for example `docs/releases/0.7.21.md`): a one-sentence summary, then per change what changed, why, and where the behaviour stops, ending with which checks actually ran and which paths have no automated case.
9+
1. **Release notes** `docs/releases/<version>.md` must exist. `make attach` refuses publication without them: `write the release notes first`. Follow the recent files (for example `docs/releases/0.7.21.md`): a one-sentence summary, then per change what changed, why, and where the behaviour stops, ending with which checks actually ran and which paths have no automated case.
1010
2. **`package.json` `version`** must match the git tag; `make version-check` rejects a mismatch.
1111
3. **`docs/CHANGELOG.md`** gets this version's entry, newest first.
1212
4. **Both READMEs stay in step**: `README.md` and `README.zh-CN.md` are two parallel texts, so a change to a feature description, a command, an install step or an index table on one side needs the other side.
1313

1414
## Cutting a release
1515

16-
Follow the [release checklist](docs/hub-distribution.md). Two rules break most runs: the tag must equal `v<package.json version>` exactly, and a version already on npm from a different build cannot be re-tagged, so bump `package.json` and cut a new tag instead. After pushing the tag, confirm the run with `gh run list --workflow=release.yml` and read a failure with `gh run view --job=<job-id> --log-failed`; a publish job that dies in about 30 seconds at the credential check is usually that version conflict, not a missing secret. `make release`, `make verify` and `npm run doctor` cannot run inside a dscode session because the nested sandbox is refused — run them from a normal terminal.
16+
Follow the [self-contained package release checklist](docs/distribution.md#build-and-release). Terminal packages no longer publish through npm/Hub; the [legacy checklist](docs/hub-distribution.md) applies only when maintaining those older channels. Two rules break most runs: the tag must equal `v<package.json version>` exactly, and a version already on npm from a different build cannot be re-tagged, so bump `package.json` and cut a new tag instead. After pushing the tag, confirm the run with `gh run list --workflow=release.yml` and read a failure with `gh run view --job=<job-id> --log-failed`; a publish job that dies in about 30 seconds at the credential check is usually that version conflict, not a missing secret. `make release`, `make verify` and `npm run doctor` cannot run inside a dscode session because the nested sandbox is refused — run them from a normal terminal.
1717

1818
## Language
1919

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ History uses a descriptive subject line plus a body explaining the situation, th
4747

4848
## Releases
4949

50-
Releases are cut by the maintainer with `make release` and published with `make publish`; see [docs/hub-distribution.md](docs/hub-distribution.md). You do not need to bump versions in a pull request.
50+
Releases are cut by the maintainer with `make release` and published with `make publish`; see [docs/distribution.md](docs/distribution.md). You do not need to bump versions in a pull request.
5151

5252
## Security
5353

‎Makefile‎

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
#
33
# Every target mirrors a step of .github/workflows/release.yml, so the sequence can be
44
# rehearsed on this machine before a tag is pushed. The workflow remains the
5-
# only path that publishes from CI; a local `make publish` uses the npm token in the Keychain
6-
# (scripts/npm-token.mjs) and the Hub token exported in your shell.
5+
# only path that publishes from CI; local terminal publication uses gh authentication.
76
#
87
# make release the build job: check, build the candidates, verify and pack them
9-
# make publish the publish job: credentials, terminal and Desktop publication, release assets
8+
# make publish attach the verified terminal packages; Desktop remains separate
109
#
11-
# Publishing reads NPM_PUBLISH_TOKEN, DSH_HUB_TOKEN and, for the release asset, GH_TOKEN.
10+
# Terminal publication uses GH_TOKEN or gh login. Legacy/Desktop targets use npm and Hub credentials.
1211

1312
SHELL := /bin/sh
1413
# Every phase consumes the artifacts of the one before it, so parallel prerequisites would
@@ -17,24 +16,23 @@ SHELL := /bin/sh
1716
.DEFAULT_GOAL := help
1817

1918
VERSION := $(shell node -p "require('./package.json').version" 2>/dev/null)
20-
REPOSITORY ?= https://github.com/qiz029/dscode
2119
TAG ?= v$(VERSION)
2220
DESKTOP_RUNTIME ?= .research/desktop-release-runtime
2321
DESKTOP_ASSETS = artifacts/desktop/release/*.tgz artifacts/desktop/release/*.sha256 artifacts/desktop/release/*-README.md artifacts/desktop/release/*-release-notes.md
24-
# The source tarball plus one prebuilt tarball per platform (scripts/distribute.mjs).
25-
TARBALLS := artifacts/dscode-$(VERSION).tar.gz artifacts/dscode-$(VERSION)-darwin-arm64.tar.gz artifacts/dscode-$(VERSION)-darwin-x64.tar.gz
22+
# One self-contained application per platform, plus checksums and a matching tap formula.
23+
TARBALLS := artifacts/dscode-$(VERSION)-darwin-arm64-standalone.tar.gz artifacts/dscode-$(VERSION)-darwin-x64-standalone.tar.gz artifacts/dscode-$(VERSION)-darwin-arm64-standalone.tar.gz.sha256 artifacts/dscode-$(VERSION)-darwin-x64-standalone.tar.gz.sha256 artifacts/dscode.rb
2624

2725
.PHONY: help install check version-check build verify candidates unpack release credentials publish publish-bundle publish-profile publish-launcher desktop publish-desktop attach
2826

2927
help:
3028
@echo 'DSCODE release targets (VERSION=$(VERSION)):'
31-
@echo ' make release version-check, check, build, verify, desktop, candidates'
32-
@echo ' make publish credentials, bundle, profile, launcher, desktop, attach'
29+
@echo ' make release version-check, check, build, verify, candidates'
30+
@echo ' make publish attach the verified self-contained terminal packages'
3331
@echo ' make install npm ci --ignore-scripts'
3432
@echo ' make check npm run check (lint, coverage, integration, package, eval)'
3533
@echo ' make version-check TAG must match the manifest version (TAG defaults to v$(VERSION))'
36-
@echo ' make build build:packages, release:hub, dist'
37-
@echo ' make verify verify:hub against the candidates in artifacts/'
34+
@echo ' make build dist and the matching Homebrew formula'
35+
@echo ' make verify verify the self-contained candidate for this machine'
3836
@echo ' make desktop prepare and verify Desktop (requires DSCODE_TEST_CHROME)'
3937
@echo ' make publish-desktop publish Desktop, sync Hub and verify public installation'
4038
@echo ' make candidates pack release-candidates.tar.gz for a publish job'
@@ -43,7 +41,7 @@ help:
4341
@echo ' make publish-bundle publish the bundle to npm'
4442
@echo ' make publish-profile sync the Hub to npm, then publish the dscode profile'
4543
@echo ' make publish-launcher publish the launcher to npm'
46-
@echo ' make attach attach the source and prebuilt tarballs to the GitHub release'
44+
@echo ' make attach attach the self-contained packages to the GitHub release'
4745

4846
# The workflow installs with --ignore-scripts: the exact dependency set the release is
4947
# verified against.
@@ -58,33 +56,29 @@ check:
5856
version-check:
5957
@version=$$(node -p "require('./package.json').version"); test "$(TAG)" = "v$$version" || { echo "tag $(TAG) does not match version $$version" >&2; exit 1; }; echo "tag $(TAG) matches version $$version"
6058

61-
# The workflow exports this for the whole build step, so a fork names itself in everything
62-
# the step generates instead of publishing the upstream URL.
6359
build:
64-
DSCODE_REPOSITORY="$(REPOSITORY)" npm run build:packages
65-
DSCODE_REPOSITORY="$(REPOSITORY)" npm run release:hub
66-
DSCODE_REPOSITORY="$(REPOSITORY)" npm run dist
60+
npm run dist
61+
node scripts/homebrew-formula.mjs
6762

6863
verify:
69-
npm run verify:hub
64+
node scripts/verify-standalone.mjs
7065

7166
desktop:
7267
npm run prepare:desktop -- "$(DESKTOP_RUNTIME)"
7368
npm run release:desktop -- "$(DESKTOP_RUNTIME)"
7469

7570
# One tarball keeps the exact layout the publish job restores, artifacts/ prefix included.
7671
candidates:
77-
tar -czf release-candidates.tar.gz artifacts/npm artifacts/local/hub-verification.json artifacts/desktop/release $(TARBALLS)
72+
tar -czf release-candidates.tar.gz artifacts/local/standalone-*.json $(TARBALLS)
7873

7974
# The publish job restores the candidates it downloaded and refuses an incomplete archive;
8075
# locally this is also how a candidate tarball from another machine enters the tree.
8176
unpack:
8277
@test -f release-candidates.tar.gz || { echo "release-candidates.tar.gz is missing; run make candidates" >&2; exit 1; }
8378
tar -xzf release-candidates.tar.gz
84-
@test -f artifacts/npm/bundle-pack.json || { echo "the release candidate archive is incomplete" >&2; exit 1; }
85-
@test -f artifacts/local/hub-verification.json || { echo "the hub verification record is missing" >&2; exit 1; }
79+
@test -f artifacts/dscode.rb || { echo "the release candidate archive is incomplete" >&2; exit 1; }
8680

87-
release: version-check check build verify desktop candidates
81+
release: version-check check build verify candidates
8882

8983
# Read-only: the npm token authenticates as the publishing user — NPM_PUBLISH_TOKEN, or the
9084
# Keychain token scripts/npm-token.mjs stores for a login-free local release — the Hub token
@@ -114,9 +108,10 @@ publish-desktop:
114108

115109
# The tag always comes from the manifest version — the build job refuses a tag that does not
116110
# name it — and the curl installer, `dscode update` and the README fetch these assets: the
117-
# prebuilt tarballs install without npm, the source one is the fallback.
111+
# self-contained archives include Node and dependencies; there is no source fallback.
118112
attach:
119-
@for tarball in $(TARBALLS) $(DESKTOP_ASSETS); do test -f "$$tarball" || { echo "$$tarball is missing; run make build" >&2; exit 1; }; done
120-
@version=$(VERSION); if gh release view "v$$version" >/dev/null 2>&1; then gh release upload "v$$version" $(TARBALLS) $(DESKTOP_ASSETS) --clobber; else test -f "docs/releases/$$version.md" || { echo "docs/releases/$$version.md is missing; write the release notes first" >&2; exit 1; }; gh release create "v$$version" $(TARBALLS) $(DESKTOP_ASSETS) --title "DSCODE $$version" --notes-file "docs/releases/$$version.md"; fi
113+
@test -f "docs/releases/$(VERSION).md" || { echo "write the release notes first" >&2; exit 1; }
114+
@for tarball in $(TARBALLS); do test -f "$$tarball" || { echo "$$tarball is missing; run make build" >&2; exit 1; }; done
115+
@version=$(VERSION); if gh release view "v$$version" >/dev/null 2>&1; then gh release upload "v$$version" $(TARBALLS) --clobber; else test -f "docs/releases/$$version.md" || { echo "docs/releases/$$version.md is missing; write the release notes first" >&2; exit 1; }; gh release create "v$$version" $(TARBALLS) --title "DSCODE $$version" --notes-file "docs/releases/$$version.md"; fi
121116

122-
publish: credentials publish-bundle publish-profile publish-launcher publish-desktop attach
117+
publish: attach

0 commit comments

Comments
 (0)