-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrelease.sh
More file actions
executable file
·277 lines (236 loc) · 9.6 KB
/
Copy pathrelease.sh
File metadata and controls
executable file
·277 lines (236 loc) · 9.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/env bash
# Frontguard release orchestrator.
#
# Usage:
# scripts/release.sh # full release (requires NPM_TOKEN)
# scripts/release.sh --dry-run # validate everything end-to-end, publish nothing
# scripts/release.sh --skip-build
# scripts/release.sh --only-npm # skip the marketplace checklist section
#
# This script is the single source of truth for what shipping a Frontguard
# release looks like. It is idempotent and safe to re-run. In --dry-run mode
# no external state is mutated — no npm publish, no git tag, no commits.
set -euo pipefail
# ----------------------------------------------------------------------------
# Config
# ----------------------------------------------------------------------------
DRY_RUN=0
SKIP_BUILD=0
ONLY_NPM=0
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERSION_FILE="$ROOT_DIR/VERSION"
# npm packages that we publish to the public registry.
# Format: "<workspace-path>:<expected-name>"
NPM_PACKAGES=(
"packages/cli:@frontguard/cli"
"packages/playwright:@frontguard/playwright"
"packages/mcp:@frontguard/mcp"
"packages/create-frontguard-plugin:create-frontguard-plugin"
"integrations/netlify:@frontguard/netlify-plugin"
)
# Marketplace targets we *cannot* publish from a script — they require human
# review or owner-only credentials. The script prints each one's submission
# URL and the artifact path the user needs to upload.
declare -a MARKETPLACES=(
"GitHub Marketplace|integrations/github-app/manifest.yml|https://github.com/marketplace/new"
"Vercel Marketplace|integrations/vercel/frontguard.config.ts|https://vercel.com/dashboard/integrations/console"
"Netlify Build Plugins|integrations/netlify/manifest.yml|https://app.netlify.com/integrations/build-plugins"
"Slack App Directory|integrations/slack-app/manifest.yml|https://api.slack.com/apps"
)
# ----------------------------------------------------------------------------
# Plumbing
# ----------------------------------------------------------------------------
color() {
# color <code> <text...>
local code=$1; shift
printf '\033[%sm%s\033[0m\n' "$code" "$*"
}
info() { color "0;36" "==> $*"; }
ok() { color "0;32" "OK $*"; }
warn() { color "0;33" "!! $*"; }
fail() { color "0;31" "ERR $*" >&2; exit 1; }
run() {
# run <cmd...> — echoes the command, then runs it. In --dry-run mode it
# only echoes (with a "[dry]" prefix) and does not execute.
if [ "$DRY_RUN" -eq 1 ]; then
printf ' [dry] %s\n' "$*"
return 0
fi
printf ' $ %s\n' "$*"
"$@"
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1"
}
# ----------------------------------------------------------------------------
# Arg parsing
# ----------------------------------------------------------------------------
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--skip-build) SKIP_BUILD=1 ;;
--only-npm) ONLY_NPM=1 ;;
-h|--help)
sed -n '2,12p' "$0"
exit 0
;;
*) fail "Unknown flag: $1" ;;
esac
shift
done
cd "$ROOT_DIR"
# ----------------------------------------------------------------------------
# Preflight
# ----------------------------------------------------------------------------
[ -f "$VERSION_FILE" ] || fail "VERSION file missing at $VERSION_FILE"
VERSION="$(tr -d '[:space:]' < "$VERSION_FILE")"
[ -n "$VERSION" ] || fail "VERSION file is empty"
info "Release version: $VERSION"
require_cmd node
require_cmd npm
require_cmd git
require_cmd jq
# Bail early if the tree is dirty unless we're in dry-run mode.
if [ "$DRY_RUN" -eq 0 ]; then
if ! git diff --quiet || ! git diff --cached --quiet; then
fail "Working tree is dirty — commit or stash before releasing."
fi
fi
# Check that each publishable package.json's version matches VERSION.
for entry in "${NPM_PACKAGES[@]}"; do
pkg_path="${entry%%:*}"
pkg_name="${entry##*:}"
pkg_json="$ROOT_DIR/$pkg_path/package.json"
[ -f "$pkg_json" ] || fail "Missing package.json at $pkg_json"
found_name="$(jq -r '.name' "$pkg_json")"
found_version="$(jq -r '.version' "$pkg_json")"
[ "$found_name" = "$pkg_name" ] || fail "$pkg_path: name '$found_name' != expected '$pkg_name'"
[ "$found_version" = "$VERSION" ] || fail "$pkg_path: version '$found_version' != VERSION '$VERSION'"
# Scoped packages must publish as public.
case "$pkg_name" in
@*/*)
access="$(jq -r '.publishConfig.access // "missing"' "$pkg_json")"
[ "$access" = "public" ] || fail "$pkg_path: publishConfig.access must be 'public' (got '$access')"
;;
esac
# Reject 'private: true' on publishable packages.
is_private="$(jq -r '.private // false' "$pkg_json")"
[ "$is_private" = "false" ] || fail "$pkg_path: 'private: true' would block npm publish"
ok "$pkg_name@$VERSION metadata OK"
done
# ----------------------------------------------------------------------------
# Build
# ----------------------------------------------------------------------------
if [ "$SKIP_BUILD" -eq 1 ]; then
warn "Skipping build (--skip-build)"
else
info "Building all workspaces"
run npm run build --workspaces --if-present
fi
# ----------------------------------------------------------------------------
# npm pack --dry-run (always) — catches bad files entries before publish.
# ----------------------------------------------------------------------------
info "Verifying tarball contents (npm pack --dry-run)"
for entry in "${NPM_PACKAGES[@]}"; do
pkg_path="${entry%%:*}"
pkg_name="${entry##*:}"
(
cd "$ROOT_DIR/$pkg_path"
npm pack --dry-run --json > /tmp/frontguard-pack.json 2>/dev/null || \
npm pack --dry-run 2>&1 | tail -20
)
ok "$pkg_name pack succeeded"
done
# ----------------------------------------------------------------------------
# npm publish
# ----------------------------------------------------------------------------
info "Publishing npm packages"
if [ "$DRY_RUN" -eq 0 ]; then
[ -n "${NPM_TOKEN:-}" ] || fail "NPM_TOKEN env var required (not set). Use --dry-run to validate without secrets."
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$HOME/.npmrc.frontguard-release"
export NPM_CONFIG_USERCONFIG="$HOME/.npmrc.frontguard-release"
trap 'rm -f "$HOME/.npmrc.frontguard-release"' EXIT
fi
# Provenance signing only works when the publish runs inside a recognised
# CI provider (GitHub Actions / GitLab CI / CircleCI / Buildkite). When run
# locally we must opt out explicitly, otherwise npm refuses to publish any
# package whose package.json declares `publishConfig.provenance: true`.
PUBLISH_FLAGS=(--access public)
if [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then
PUBLISH_FLAGS+=(--provenance=false)
fi
for entry in "${NPM_PACKAGES[@]}"; do
pkg_path="${entry%%:*}"
pkg_name="${entry##*:}"
# Skip if this exact version is already on the registry. Allows re-running
# the script after a partial failure without duplicate-publish errors.
if [ "$DRY_RUN" -eq 0 ]; then
if npm view "$pkg_name@$VERSION" version >/dev/null 2>&1; then
warn "$pkg_name@$VERSION already on registry — skipping"
continue
fi
fi
(
cd "$ROOT_DIR/$pkg_path"
run npm publish "${PUBLISH_FLAGS[@]}"
)
# `npm publish --access public` only flips access on the FIRST publish, and
# even there it can be silently overridden by the org's default if the org
# is configured to ship private-by-default. Force the access explicitly for
# scoped packages so the registry CDN exposes the tarball publicly.
# (Unscoped packages are always public — npm rejects `access set` on them.)
if [ "$DRY_RUN" -eq 0 ] && [[ "$pkg_name" == @*/* ]]; then
if ! run npm access set status=public "$pkg_name"; then
warn "$pkg_name: failed to set access=public — verify at https://www.npmjs.com/package/$pkg_name/access"
fi
fi
ok "$pkg_name@$VERSION published"
done
# ----------------------------------------------------------------------------
# Marketplace submission checklist
# ----------------------------------------------------------------------------
if [ "$ONLY_NPM" -eq 1 ]; then
ok "npm publish complete (--only-npm). Skipping marketplace checklist."
exit 0
fi
info "Marketplace submission checklist"
cat <<'EOF'
The following surfaces require manual review / owner credentials and cannot
be published from CI. For each, the script confirms the manifest exists and
prints the submission URL the release owner must visit.
EOF
for entry in "${MARKETPLACES[@]}"; do
IFS='|' read -r name manifest url <<< "$entry"
manifest_path="$ROOT_DIR/$manifest"
if [ -f "$manifest_path" ]; then
ok "$name — manifest at $manifest"
else
warn "$name — manifest missing at $manifest"
fi
printf ' submit: %s\n\n' "$url"
done
# ----------------------------------------------------------------------------
# CHANGELOG entry stub
# ----------------------------------------------------------------------------
info "Per-release CHANGELOG entry"
mkdir -p "$ROOT_DIR/.release-notes"
CHANGELOG_STUB="$ROOT_DIR/.release-notes/$VERSION.md"
{
printf '## [%s] - %s\n\n' "$VERSION" "$(date +%Y-%m-%d 2>/dev/null || echo 'TODO-DATE')"
printf '### npm\n\n'
for entry in "${NPM_PACKAGES[@]}"; do
pkg_name="${entry##*:}"
printf -- '- %s@%s\n' "$pkg_name" "$VERSION"
done
printf '\n### Marketplace listings\n\n'
for entry in "${MARKETPLACES[@]}"; do
IFS='|' read -r name _ url <<< "$entry"
printf -- '- %s — submission queue: %s\n' "$name" "$url"
done
} > "$CHANGELOG_STUB"
ok "Release notes draft: $CHANGELOG_STUB"
if [ "$DRY_RUN" -eq 1 ]; then
ok "Dry-run complete. No npm publish, no marketplace submission, no git tags."
else
ok "Release $VERSION published. Submit the marketplace listings above by hand."
fi