mirror of
https://github.com/aitingtingya/mv-obcc.git
synced 2026-07-22 07:47:28 +00:00
260 lines
7.8 KiB
Bash
Executable file
260 lines
7.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SOURCE_ROOT="${SOURCE_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
CLEAN_ROOT="${CLEAN_ROOT:-/Users/gingerman/obsidian/git-learn-md/mv-obcc_clean}"
|
|
REPO="aitingtingya/mv-obcc"
|
|
REPO_URL="https://github.com/${REPO}.git"
|
|
VERSION="${VERSION:-$(SOURCE_ROOT="$SOURCE_ROOT" node -p "require(process.env.SOURCE_ROOT + '/package.json').version")}"
|
|
SUCCESS=0
|
|
|
|
cleanup() {
|
|
if [[ "$SUCCESS" == "1" ]]; then
|
|
rm -rf "$CLEAN_ROOT"
|
|
else
|
|
echo "Release failed; keeping temporary repository for debugging: $CLEAN_ROOT" >&2
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
get_github_token() {
|
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
printf "%s" "$GITHUB_TOKEN"
|
|
return 0
|
|
fi
|
|
|
|
local credential token
|
|
credential="$(
|
|
printf "protocol=https\nhost=github.com\nusername=x-access-token\n\n" \
|
|
| GIT_TERMINAL_PROMPT=0 git credential fill 2>/dev/null || true
|
|
)"
|
|
token="$(printf "%s\n" "$credential" | sed -n "s/^password=//p" | head -n 1)"
|
|
if [[ -n "$token" ]]; then
|
|
printf "%s" "$token"
|
|
return 0
|
|
fi
|
|
|
|
cat >&2 <<'EOF'
|
|
GitHub token not found.
|
|
|
|
Provide it by either:
|
|
1. exporting GITHUB_TOKEN for this command, or
|
|
2. storing it once in git credential helper / macOS Keychain:
|
|
|
|
git config --global credential.helper osxkeychain
|
|
printf 'protocol=https\nhost=github.com\nusername=x-access-token\npassword=YOUR_TOKEN\n\n' | git credential-osxkeychain store
|
|
|
|
Do not write the real token into repository files.
|
|
EOF
|
|
return 1
|
|
}
|
|
|
|
GITHUB_TOKEN_RESOLVED="$(get_github_token)"
|
|
if [[ ! "$GITHUB_TOKEN_RESOLVED" =~ ^(ghp_|github_pat_) ]]; then
|
|
echo "Resolved GitHub token does not look like a GitHub PAT. Abort." >&2
|
|
exit 1
|
|
fi
|
|
BASIC_AUTH="$(printf "x-access-token:%s" "$GITHUB_TOKEN_RESOLVED" | base64 | tr -d "\n")"
|
|
GIT_AUTH_HEADER="Authorization: Basic ${BASIC_AUTH}"
|
|
|
|
echo "Preparing clean release ${VERSION} from ${SOURCE_ROOT}"
|
|
rm -rf "$CLEAN_ROOT"
|
|
git clone "$REPO_URL" "$CLEAN_ROOT"
|
|
|
|
find "$CLEAN_ROOT" -mindepth 1 -maxdepth 1 ! -name ".git" -exec rm -rf {} +
|
|
|
|
allowlist=(
|
|
"src"
|
|
"scripts"
|
|
".github"
|
|
".gitignore"
|
|
"main.ts"
|
|
"manifest.json"
|
|
"styles.css"
|
|
"esbuild.config.mjs"
|
|
"eslint.config.mjs"
|
|
"tsconfig.json"
|
|
"package.json"
|
|
"package-lock.json"
|
|
"README.md"
|
|
"DEVELOPMENT-GUIDELINES.md"
|
|
"LATEX-SUITE-UPGRADE.md"
|
|
"RELEASE-PROCESS.md"
|
|
"THIRD_PARTY_NOTICES.md"
|
|
"LICENSE"
|
|
"TEST-REPORT.md"
|
|
"WINDOWS-VALIDATION.md"
|
|
"versions.json"
|
|
)
|
|
|
|
for item in "${allowlist[@]}"; do
|
|
if [[ -e "$SOURCE_ROOT/$item" ]]; then
|
|
mkdir -p "$(dirname "$CLEAN_ROOT/$item")"
|
|
cp -R "$SOURCE_ROOT/$item" "$CLEAN_ROOT/$item"
|
|
fi
|
|
done
|
|
|
|
cd "$CLEAN_ROOT"
|
|
rm -rf tests vitest.config.ts node_modules dist release .obsidian .DS_Store
|
|
|
|
node <<'NODE'
|
|
const fs = require("node:fs");
|
|
|
|
const packagePath = "package.json";
|
|
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
pkg.scripts = pkg.scripts || {};
|
|
delete pkg.scripts.test;
|
|
delete pkg.scripts["test:watch"];
|
|
pkg.scripts.verify = "npm run lint && npm run typecheck && npm run build";
|
|
for (const dep of ["vitest", "jsdom", "@types/jsdom"]) {
|
|
delete pkg.devDependencies?.[dep];
|
|
}
|
|
fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
|
|
const tsconfigPath = "tsconfig.json";
|
|
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, "utf8"));
|
|
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
if (Array.isArray(tsconfig.compilerOptions.types)) {
|
|
tsconfig.compilerOptions.types = tsconfig.compilerOptions.types.filter(
|
|
(type) => type !== "vitest/globals",
|
|
);
|
|
}
|
|
if (Array.isArray(tsconfig.include)) {
|
|
tsconfig.include = tsconfig.include.filter((entry) => entry !== "tests/**/*.ts");
|
|
}
|
|
fs.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
NODE
|
|
|
|
run_logged() {
|
|
local name="$1"
|
|
shift
|
|
echo "Running ${name}..."
|
|
"$@" > ".release-${name}.log" 2>&1 || {
|
|
tail -n 120 ".release-${name}.log" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
run_logged npm-install npm install --package-lock-only
|
|
run_logged npm-ci npm ci
|
|
run_logged verify npm run verify
|
|
run_logged package npm run package
|
|
|
|
test ! -d tests
|
|
test ! -f vitest.config.ts
|
|
node <<'NODE'
|
|
const lock = JSON.parse(require("node:fs").readFileSync("package-lock.json", "utf8"));
|
|
for (const name of [
|
|
"node_modules/vite",
|
|
"node_modules/rolldown",
|
|
"node_modules/@napi-rs/wasm-runtime",
|
|
"node_modules/@emnapi/core",
|
|
"node_modules/@emnapi/runtime",
|
|
]) {
|
|
if (lock.packages?.[name]) throw new Error(`Unexpected test dependency in lockfile: ${name}`);
|
|
}
|
|
NODE
|
|
|
|
if grep -RInE 'ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]+|x-access-token:(ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]+)' \
|
|
--exclude-dir=.git \
|
|
--exclude="RELEASE-PROCESS.md" \
|
|
.; then
|
|
echo "Potential GitHub token found in release tree. Abort." >&2
|
|
exit 1
|
|
fi
|
|
|
|
stage_items=()
|
|
for item in \
|
|
.gitignore \
|
|
src \
|
|
scripts \
|
|
.github \
|
|
main.ts \
|
|
manifest.json \
|
|
styles.css \
|
|
esbuild.config.mjs \
|
|
eslint.config.mjs \
|
|
tsconfig.json \
|
|
package.json \
|
|
package-lock.json \
|
|
README.md \
|
|
DEVELOPMENT-GUIDELINES.md \
|
|
LATEX-SUITE-UPGRADE.md \
|
|
RELEASE-PROCESS.md \
|
|
THIRD_PARTY_NOTICES.md \
|
|
LICENSE \
|
|
TEST-REPORT.md \
|
|
WINDOWS-VALIDATION.md \
|
|
versions.json; do
|
|
[[ -e "$item" ]] && stage_items+=("$item")
|
|
done
|
|
git add -- "${stage_items[@]}"
|
|
|
|
if git diff --cached --name-only | grep -E '^(node_modules|dist|release|tests|\.obsidian|vitest\.config\.ts)'; then
|
|
echo "Forbidden development/test path staged. Abort." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if git diff --cached --quiet; then
|
|
echo "No file changes to commit."
|
|
else
|
|
git commit -m "release: ${VERSION}"
|
|
fi
|
|
|
|
echo "Pushing main..."
|
|
git -c http.extraHeader="$GIT_AUTH_HEADER" push "$REPO_URL" main
|
|
|
|
echo "Publishing tag ${VERSION}..."
|
|
git tag -f "$VERSION"
|
|
git -c http.extraHeader="$GIT_AUTH_HEADER" push "$REPO_URL" "refs/tags/${VERSION}" --force
|
|
|
|
set +e
|
|
release_lookup="$(
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $GITHUB_TOKEN_RESOLVED" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
"https://api.github.com/repos/${REPO}/releases/tags/${VERSION}" 2>/dev/null
|
|
)"
|
|
lookup_status=$?
|
|
set -e
|
|
if [[ $lookup_status -eq 0 && -n "$release_lookup" ]]; then
|
|
release_id="$(node -e 'const r=JSON.parse(process.argv[1]); if (r.id) process.stdout.write(String(r.id));' "$release_lookup")"
|
|
if [[ -n "$release_id" ]]; then
|
|
echo "Deleting existing release ${VERSION}..."
|
|
curl -fsS -X DELETE \
|
|
-H "Authorization: Bearer $GITHUB_TOKEN_RESOLVED" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
"https://api.github.com/repos/${REPO}/releases/${release_id}" >/dev/null
|
|
fi
|
|
fi
|
|
|
|
release_payload="$(node -e 'const version = process.argv[1]; process.stdout.write(JSON.stringify({ tag_name: version, target_commitish: "main", name: version, body: `mv-SenceAI ${version}`, draft: false, prerelease: false }));' "$VERSION")"
|
|
release_json="$(
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $GITHUB_TOKEN_RESOLVED" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
-d "$release_payload" \
|
|
"https://api.github.com/repos/${REPO}/releases"
|
|
)"
|
|
|
|
upload_url="$(node -e 'const r = JSON.parse(process.argv[1]); process.stdout.write(r.upload_url.replace(/\{.*\}$/, ""));' "$release_json")"
|
|
html_url="$(node -e 'const r = JSON.parse(process.argv[1]); process.stdout.write(r.html_url);' "$release_json")"
|
|
for asset in "dist/main.js:main.js" "manifest.json:manifest.json" "styles.css:styles.css"; do
|
|
src="${asset%%:*}"
|
|
name="${asset##*:}"
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $GITHUB_TOKEN_RESOLVED" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@$src" \
|
|
"${upload_url}?name=$name" >/dev/null
|
|
echo "Uploaded ${name}"
|
|
done
|
|
|
|
unset GITHUB_TOKEN_RESOLVED BASIC_AUTH GIT_AUTH_HEADER
|
|
SUCCESS=1
|
|
echo "Published ${VERSION}: ${html_url}"
|