Skip to content

Add script to publish built JS packages to dist repo#2767

Merged
AbanoubGhadban merged 3 commits intoupcoming-v16.3.0from
build-and-publish-script
Mar 19, 2026
Merged

Add script to publish built JS packages to dist repo#2767
AbanoubGhadban merged 3 commits intoupcoming-v16.3.0from
build-and-publish-script

Conversation

@AbanoubGhadban
Copy link
Copy Markdown
Collaborator

Summary

  • Adds scripts/publish-to-builds-repo.sh that builds all JS packages and pushes the compiled output to shakacode/react-on-rails-builds
  • The builds repo contains pre-built package.json + lib/ for each package, ready for consumption as pnpm git dependencies — no TypeScript compilation needed during install
  • The script defaults to pushing to a branch matching the current react_on_rails branch name (overridable with --branch)

What the script does

  1. Runs pnpm build for all packages
  2. Copies package.json + lib/ to the builds repo
  3. Replaces workspace:* references with the actual version
  4. Strips devDependencies and build-related scripts
  5. Commits, pushes, and tags (e.g., v16.4.0)

Usage

./scripts/publish-to-builds-repo.sh              # build + push + tag
./scripts/publish-to-builds-repo.sh --dry-run     # preview without pushing
./scripts/publish-to-builds-repo.sh --tag v1.2.3  # custom tag
./scripts/publish-to-builds-repo.sh --branch main # override branch

Consumer usage (pnpm)

{
  "react-on-rails": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails",
  "react-on-rails-pro": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro",
  "react-on-rails-pro-node-renderer": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro-node-renderer"
}

Test plan

  • Dry run completes successfully
  • Real run pushes to shakacode/react-on-rails-builds with correct structure
  • Verified workspace:* replaced with actual version in dist package.json
  • Verified devDependencies and build scripts stripped
  • Verified consumer pnpm install resolves correct packages in ~30s

🤖 Generated with Claude Code

AbanoubGhadban and others added 2 commits March 19, 2026 14:21
Script builds all packages, copies package.json + lib/ to a separate
builds repo (shakacode/react-on-rails-builds), replaces workspace:*
references with actual versions, strips devDependencies and build
scripts, commits and pushes with a version tag.

Usage: ./scripts/publish-to-builds-repo.sh [--dry-run]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of defaulting to 'main', the script now pushes to a branch
matching the current react_on_rails branch name. --branch flag still
available for override.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81624257-3e6a-4ecc-8270-29ebcad69718

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch build-and-publish-script
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

)

# Read version from react-on-rails package.json
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: relative path breaks when script is not run from the monorepo root.

require('./packages/react-on-rails/package.json') resolves relative to the current working directory at script invocation time, not $MONOREPO_ROOT. Running the script from any other directory (e.g. bash scripts/publish-to-builds-repo.sh from /tmp) will silently fail, hit the 2>/dev/null suppression, and exit with the version error.

Suggested change
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
VERSION=$(node -e "console.log(require('$MONOREPO_ROOT/packages/react-on-rails/package.json').version)")

cp -r "$PKG_SRC/lib" "$PKG_DEST/lib"
else
echo " WARNING: $PKG_SRC/lib does not exist! Was the build successful?" >&2
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing lib/ should abort, not warn-and-continue.

If the build silently failed to produce lib/, the script continues and publishes an empty package to the dist repo. Consumers would get a broken install with no clear error message.

Suggested change
fi
echo "ERROR: $PKG_SRC/lib does not exist — build failed for $pkg." >&2
exit 1

Comment on lines +152 to +157
PKG_JSON="$DIST_DIR/repo/$pkg/package.json"
if grep -q '"workspace:\*"' "$PKG_JSON" 2>/dev/null; then
sed -i "s/\"workspace:\*\"/\"$VERSION\"/g" "$PKG_JSON"
echo " Fixed $pkg/package.json"
fi
done
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues with the workspace replacement:

  1. Incomplete pattern: Only replaces "workspace:*" but not other pnpm workspace ranges like "workspace:^", "workspace:~", or pinned versions like "workspace:1.2.3". If any of those appear in a package.json they'll be published verbatim and cause install failures.

  2. Fragile tool: Since step 5 already uses node to rewrite package.json, it's cleaner and safer to do the workspace replacement there too — avoiding the separate sed pass and the need to grep/check first:

// inside the node -e script in step 5:
function replaceWorkspace(deps) {
  if (!deps) return;
  for (const [k, v] of Object.entries(deps)) {
    if (typeof v === 'string' && v.startsWith('workspace:')) deps[k] = VERSION;
  }
}
replaceWorkspace(pkg.dependencies);
replaceWorkspace(pkg.peerDependencies);
replaceWorkspace(pkg.optionalDependencies);

This handles all workspace range formats and keeps the JSON rewriting in one place.

Comment on lines +166 to +181
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('$PKG_JSON', 'utf8'));
if (pkg.scripts) {
delete pkg.scripts.build;
delete pkg.scripts['build-watch'];
delete pkg.scripts.clean;
delete pkg.scripts.prepare;
delete pkg.scripts.prepublishOnly;
delete pkg.scripts['type-check'];
if (Object.keys(pkg.scripts).length === 0) delete pkg.scripts;
}
delete pkg.devDependencies;
fs.writeFileSync('$PKG_JSON', JSON.stringify(pkg, null, 2) + '\n');
"
echo " Cleaned $pkg/package.json"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell variable interpolated inside a single-quoted JS string — path injection risk.

'$PKG_JSON' is expanded by bash before Node sees it. If $PKG_JSON ever contained a single quote (e.g., from an unusual mktemp or a future change to the path construction), it would break the JS syntax. The safer pattern is to pass the path as a CLI argument and read it via process.argv:

Suggested change
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('$PKG_JSON', 'utf8'));
if (pkg.scripts) {
delete pkg.scripts.build;
delete pkg.scripts['build-watch'];
delete pkg.scripts.clean;
delete pkg.scripts.prepare;
delete pkg.scripts.prepublishOnly;
delete pkg.scripts['type-check'];
if (Object.keys(pkg.scripts).length === 0) delete pkg.scripts;
}
delete pkg.devDependencies;
fs.writeFileSync('$PKG_JSON', JSON.stringify(pkg, null, 2) + '\n');
"
echo " Cleaned $pkg/package.json"
node - "$PKG_JSON" << 'NODEEOF'
const fs = require('fs');
const pkgPath = process.argv[1];
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
if (pkg.scripts) {
delete pkg.scripts.build;
delete pkg.scripts['build-watch'];
delete pkg.scripts.clean;
delete pkg.scripts.prepare;
delete pkg.scripts.prepublishOnly;
delete pkg.scripts['type-check'];
if (Object.keys(pkg.scripts).length === 0) delete pkg.scripts;
}
delete pkg.devDependencies;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
NODEEOF

Comment thread scripts/publish-to-builds-repo.sh Outdated
find . -not -path './.git/*' -not -path './.git' | sort
else
echo "==> Pushing to $DIST_REPO branch $DIST_BRANCH..."
git push -u origin "$DIST_BRANCH"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Push will fail on re-run without guidance.

If the script is re-run for the same version (e.g., to fix a build artifact after a partial publish), the remote branch will have diverged and this push fails with a cryptic "rejected" message. Consider documenting the recovery path in the failure output:

Suggested change
git push -u origin "$DIST_BRANCH"
git push -u origin "$DIST_BRANCH" || {
echo "" >&2
echo "ERROR: Push rejected. If you need to overwrite a previous publish of the same version, run:" >&2
echo " git push --force-with-lease origin $DIST_BRANCH" >&2
echo "from $DIST_DIR/repo (before it is cleaned up), or delete the remote branch and retry." >&2
exit 1
}

Comment on lines +91 to +106
if git ls-remote "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then
git clone --branch "$DIST_BRANCH" --depth 1 "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || \
git clone "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || {
# Fresh repo with no commits yet
git init "$DIST_DIR/repo"
cd "$DIST_DIR/repo"
git remote add origin "$DIST_REPO"
}
else
# Branch doesn't exist or repo is empty
git clone "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || {
git init "$DIST_DIR/repo"
cd "$DIST_DIR/repo"
git remote add origin "$DIST_REPO"
}
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clone fallback logic is hard to follow and silences real errors.

The triple-fallback ls-remote → clone-with-branch → clone-without-branch → init with &>/dev/null and 2>/dev/null suppression makes it difficult to distinguish auth failures from genuinely empty repos. A few concerns:

  • If git ls-remote fails due to missing SSH key / no network access, the output is silenced and the error only surfaces later (confusingly) when git push fails.
  • The cd "$DIST_DIR/repo" inside the {...} fallback blocks changes the working directory inside a subshell-like group but actually runs inline — the outer cd "$DIST_DIR/repo" on line 108 is redundant in those paths, and harmless, but it obscures control flow.

Consider splitting this into explicit cases with clear error messages, and only suppressing output where it's truly noise (e.g., git checkout when the branch doesn't exist yet).

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 19, 2026

Greptile Summary

This PR adds scripts/publish-to-builds-repo.sh, a new shell script that builds all JS packages and pushes compiled output to the shakacode/react-on-rails-builds git repo so consumers can reference pre-built packages as pnpm git dependencies without needing a TypeScript compilation step. The approach is sound and well-structured, but several logic bugs would cause failures in common scenarios.

Key issues found:

  • git ls-remote missing --exit-code (line 91): Without this flag, git ls-remote exits 0 regardless of whether the target branch exists, making the else block (lines 99–106) unreachable dead code whenever the remote is accessible. The intent to branch on "branch exists vs. doesn't exist" is not achieved.
  • Relative require() path in node -e (line 52): require('./packages/react-on-rails/package.json') resolves against the caller's working directory, not the monorepo root. Running the script from any other directory silently fails version detection.
  • sed -i macOS incompatibility (line 154): BSD sed (macOS) requires an explicit (possibly empty) backup-extension argument; omitting it causes the script to error on macOS developer machines.
  • Tag existence check unreliable in shallow clone (line 242): git clone --depth 1 does not fetch all remote tags. git rev-parse "$DIST_TAG" will miss tags pointing to older commits, causing git push origin "$DIST_TAG" to fail with "tag already exists" on re-runs, aborting the script via set -euo pipefail.
  • No git identity configured (line 229): git commit will fail in CI environments that lack a global user.name / user.email configuration.

Confidence Score: 2/5

  • Not safe to merge — multiple logic bugs will cause failures in real usage and CI environments.
  • Four P1 logic bugs are present: broken branch-existence detection, a path that breaks when script is not run from the repo root, a macOS sed incompatibility that aborts the script entirely, and a shallow-clone tag check that causes hard failures on re-runs. These issues will surface immediately in practice and need to be resolved before the script is reliable.
  • scripts/publish-to-builds-repo.sh requires attention across lines 52, 91, 154, 229, and 242.

Important Files Changed

Filename Overview
scripts/publish-to-builds-repo.sh New shell script that builds JS packages and publishes them to a dist repo; contains four logic bugs: git ls-remote missing --exit-code, relative path in node -e, sed -i macOS incompatibility, and tag existence check broken in shallow clones

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant Script as publish-to-builds-repo.sh
    participant Monorepo as react_on_rails (local)
    participant DistRepo as react-on-rails-builds (remote)

    Dev->>Script: ./scripts/publish-to-builds-repo.sh [options]
    Script->>Monorepo: pnpm build (all packages)
    Monorepo-->>Script: lib/ output per package

    Script->>Script: mktemp -d (create temp DIST_DIR)
    Script->>DistRepo: git ls-remote (check branch)
    DistRepo-->>Script: refs (or empty)

    alt branch exists
        Script->>DistRepo: git clone --branch DIST_BRANCH --depth 1
    else branch missing / remote empty
        Script->>DistRepo: git clone (full) or git init
    end

    Script->>Script: git checkout -b DIST_BRANCH (if needed)
    Script->>Script: Copy package.json + lib/ + README.md per package
    Script->>Script: sed replace workspace:* → VERSION
    Script->>Script: node — strip devDependencies & build scripts
    Script->>Script: Write top-level README.md
    Script->>Script: git add -A && git commit

    alt DRY_RUN=false
        Script->>DistRepo: git push -u origin DIST_BRANCH
        Script->>Script: Check tag existence (git rev-parse)
        Script->>DistRepo: git push origin TAG
    else DRY_RUN=true
        Script->>Script: Print contents (find .)
    end

    Script->>Script: trap EXIT → rm -rf DIST_DIR
Loading

Last reviewed commit: "Default builds repo ..."

trap 'rm -rf "$DIST_DIR"' EXIT

echo "==> Cloning builds repo into $DIST_DIR..."
if git ls-remote "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 git ls-remote doesn't check branch existence without --exit-code

git ls-remote exits with code 0 even when no refs match — it only fails on network/auth errors. This means the if branch is always taken when the remote is reachable, and the else block on line 99 is essentially dead code.

In practice the fallback inside the if branch (git clone --branchgit clone without --branch) accidentally handles the missing-branch case, but the intent is hidden and error output is silenced. The fix is to add --exit-code:

Suggested change
if git ls-remote "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then
if git ls-remote --exit-code "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then

With --exit-code, git ls-remote returns 2 when no matching refs are found, making the condition correctly reflect whether the branch actually exists.

)

# Read version from react-on-rails package.json
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Relative path in node -e depends on the caller's working directory

require('./packages/react-on-rails/package.json') resolves relative to the process's current working directory at call time — not relative to the script. If the script is invoked from any directory other than the monorepo root (e.g. bash scripts/publish-to-builds-repo.sh from ~/), node will silently fail (stderr is redirected), $VERSION will be empty, and the script will exit with an error. Use $MONOREPO_ROOT for an absolute path:

Suggested change
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
VERSION=$(node -e "console.log(require('$MONOREPO_ROOT/packages/react-on-rails/package.json').version)" 2>/dev/null)

for pkg in "${PACKAGES[@]}"; do
PKG_JSON="$DIST_DIR/repo/$pkg/package.json"
if grep -q '"workspace:\*"' "$PKG_JSON" 2>/dev/null; then
sed -i "s/\"workspace:\*\"/\"$VERSION\"/g" "$PKG_JSON"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 sed -i without an empty-string argument fails on macOS (BSD sed)

GNU sed (Linux) accepts sed -i "s/...", but BSD sed (macOS) requires the backup-extension argument immediately after -i, even if it is empty: sed -i '' "s/...". Without it, BSD sed treats the substitute expression as the backup extension and errors out.

A portable one-liner that works on both platforms:

Suggested change
sed -i "s/\"workspace:\*\"/\"$VERSION\"/g" "$PKG_JSON"
sed -i.bak "s/\"workspace:\*\"/\"$VERSION\"/g" "$PKG_JSON" && rm -f "${PKG_JSON}.bak"

Alternatively, since node is already used below, the replacement could be folded into the step-5 node script to avoid sed entirely.

Comment thread scripts/publish-to-builds-repo.sh Outdated
Comment on lines +242 to +248
if git rev-parse "$DIST_TAG" &>/dev/null; then
echo "==> Tag $DIST_TAG already exists, skipping tag creation."
else
git tag "$DIST_TAG"
git push origin "$DIST_TAG"
echo "==> Tag $DIST_TAG pushed."
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Tag existence check is unreliable with a shallow clone

git clone --depth 1 only fetches commits (and tags reachable from those commits) within depth 1. Tags created in a previous run and pointing to older commits won't be present locally, so git rev-parse "$DIST_TAG" will report that the tag doesn't exist even when it already exists on the remote. The subsequent git push origin "$DIST_TAG" will then fail with "tag already exists", and because of set -euo pipefail the script aborts.

Check the remote directly instead:

Suggested change
if git rev-parse "$DIST_TAG" &>/dev/null; then
echo "==> Tag $DIST_TAG already exists, skipping tag creation."
else
git tag "$DIST_TAG"
git push origin "$DIST_TAG"
echo "==> Tag $DIST_TAG pushed."
fi
if git ls-remote --exit-code origin "refs/tags/$DIST_TAG" &>/dev/null; then
echo "==> Tag $DIST_TAG already exists on remote, skipping tag creation."
else
git tag "$DIST_TAG"
git push origin "$DIST_TAG"
echo "==> Tag $DIST_TAG pushed."
fi

SOURCE_SHA=$(cd "$MONOREPO_ROOT" && git rev-parse --short HEAD)
SOURCE_BRANCH=$(cd "$MONOREPO_ROOT" && git rev-parse --abbrev-ref HEAD)

git commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Git user identity not configured — will fail in fresh CI environments

git commit requires user.name and user.email to be configured. In a CI environment that doesn't have a global git config (e.g. a freshly-provisioned runner), this commit will fail with "Please tell me who you are."

Add identity configuration before the commit, or at least guard with a clear error:

Suggested change
git commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"
git -c user.name="github-actions[bot]" -c user.email="github-actions[bot]@users.noreply.github.com" \
commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 19, 2026

Review Summary

The overall approach is solid — using a dist repo as a pre-built git dependency target is a clean pattern. The script is well-structured and the dry-run flag is a nice touch. A few issues worth addressing before merging:

Bugs

  • Line 52 — relative path breaks outside monorepo root: require('./packages/react-on-rails/package.json') resolves from the caller's CWD, not $MONOREPO_ROOT. Running the script from any other directory silently fails and exits with a misleading version error. (see inline comment for fix)

  • Line 138 — missing lib/ warns but continues: A failed build that produces no lib/ causes the script to publish an empty package. This should be a hard error. (see inline comment)

Correctness

  • Lines 152–157 — incomplete workspace: replacement: The sed replacement only matches "workspace:*" exactly, missing workspace:^, workspace:~, and pinned ranges. Consolidating into the node rewrite in step 5 handles all formats cleanly. (see inline comment)

  • Line 239 — push fails with no guidance on re-run: Re-running for the same version (e.g. after fixing a build artifact) will be rejected with no recovery instructions. (see inline comment)

Code quality

  • Lines 91–106 — clone fallback logic suppresses real errors: The triple-fallback chain with &>/dev/null / 2>/dev/null makes auth failures and network errors invisible until much later. (see inline comment)

  • Lines 166–181 — shell variable in single-quoted JS string: '$PKG_JSON' is expanded by bash into the JS source. Using process.argv is the idiomatic and safe alternative. (see inline comment)

Minor

  • No prerequisite checks for node, pnpm, git at script start — a missing tool gives a confusing error mid-script.
  • --no-tags on git clone --depth 1 would skip fetching tags from the builds repo, saving network traffic.

Previously the script skipped the push entirely when there were no file
changes, which meant a new branch name would never be created in the
builds repo if the content matched an existing branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
trap 'rm -rf "$DIST_DIR"' EXIT

echo "==> Cloning builds repo into $DIST_DIR..."
if git ls-remote "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: git ls-remote always exits 0 when the remote is reachable, even when the branch doesn't exist. This means the else branch (lines 99–106) is dead code whenever the remote is accessible — the script will always take the if path and attempt git clone --branch, which then fails if the branch doesn't exist.

Fix with --exit-code:

Suggested change
if git ls-remote "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then
if git ls-remote --exit-code "$DIST_REPO" "refs/heads/$DIST_BRANCH" &>/dev/null; then

Or pipe to grep:

if git ls-remote "$DIST_REPO" 2>/dev/null | grep -q "refs/heads/$DIST_BRANCH"; then

)

# Read version from react-on-rails package.json
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: relative path depends on the caller's CWD, not $MONOREPO_ROOT.

If the script is invoked from any directory other than the monorepo root (e.g. bash scripts/publish-to-builds-repo.sh from a subdirectory), require('./packages/…') will fail. The error is silently swallowed by 2>/dev/null, yielding an empty $VERSION and the generic "Could not read version" message.

Suggested change
VERSION=$(node -e "console.log(require('./packages/react-on-rails/package.json').version)" 2>/dev/null)
VERSION=$(node -e "console.log(require('$MONOREPO_ROOT/packages/react-on-rails/package.json').version)" 2>/dev/null)

for pkg in "${PACKAGES[@]}"; do
PKG_JSON="$DIST_DIR/repo/$pkg/package.json"
if grep -q '"workspace:\*"' "$PKG_JSON" 2>/dev/null; then
sed -i "s/\"workspace:\*\"/\"$VERSION\"/g" "$PKG_JSON"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues here:

1. sed -i is not portable to macOS (BSD sed). GNU sed accepts sed -i "..." but BSD sed requires an explicit backup suffix: sed -i '' "...". This will silently fail or error on developer machines.

2. Only workspace:* is handled. pnpm also supports workspace:^ and workspace:~. If any package ever uses those specifiers, they'll be left unreplaced in the dist package.json.

Consider handling all workspace protocols at once with a more robust replacement, or use the node -e approach already used in step 5 (which avoids the sed portability issue entirely).

Also, if $VERSION ever contains sed metacharacters (/, &, \), the substitution will break. A safer delimiter would be |.

# Copy lib/ directory
if [[ -d "$PKG_SRC/lib" ]]; then
cp -r "$PKG_SRC/lib" "$PKG_DEST/lib"
else
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing lib/ only warns but allows the script to continue. The dist package would be pushed with a package.json but no compiled output — silently broken for consumers.

This should be a hard failure:

Suggested change
else
if [[ -d "$PKG_SRC/lib" ]]; then
cp -r "$PKG_SRC/lib" "$PKG_DEST/lib"
else
echo "ERROR: $PKG_SRC/lib does not exist — build may have failed. Aborting." >&2
exit 1
fi

if git diff --cached --quiet; then
echo "==> No file changes to commit."
else
git commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git commit will fail in CI environments where user.name/user.email are not configured.

Add a guard or inline config before the commit:

Suggested change
git commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"
git -c user.name="publish-to-builds-repo" -c user.email="noreply@shakacode.com" \
commit -m "Build packages $VERSION from $SOURCE_BRANCH ($SOURCE_SHA)"

Or set them explicitly before the git add:

git config user.email "noreply@shakacode.com"
git config user.name "publish-to-builds-repo"

git push -u origin "$DIST_BRANCH"

# Create and push tag
if git rev-parse "$DIST_TAG" &>/dev/null; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git rev-parse "$DIST_TAG" only checks the local clone — not the remote.

With --depth 1, remote tags are not fetched by default. This means the check will always say "tag doesn't exist" and attempt to push, potentially failing with a "tag already exists on remote" error if the script is re-run for the same version.

Fetch remote tags first, or check the remote directly:

if git ls-remote --exit-code --tags origin "$DIST_TAG" &>/dev/null; then
  echo "==> Tag $DIST_TAG already exists on remote, skipping tag creation."
else
  git tag "$DIST_TAG"
  git push origin "$DIST_TAG"
  echo "==> Tag $DIST_TAG pushed."
fi

Comment on lines +92 to +106
git clone --branch "$DIST_BRANCH" --depth 1 "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || \
git clone "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || {
# Fresh repo with no commits yet
git init "$DIST_DIR/repo"
cd "$DIST_DIR/repo"
git remote add origin "$DIST_REPO"
}
else
# Branch doesn't exist or repo is empty
git clone "$DIST_REPO" "$DIST_DIR/repo" 2>/dev/null || {
git init "$DIST_DIR/repo"
cd "$DIST_DIR/repo"
git remote add origin "$DIST_REPO"
}
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent fallback to git init masks authentication failures.

If git clone fails because of an SSH key problem, wrong permissions, or a network issue, it silently falls through to git init — creating a brand-new local repo. The subsequent git push will then fail with a cryptic "refusing to update checked out branch" or "remote rejected" error, far from the real cause.

Consider removing the 2>/dev/null on clone failures, or at minimum checking whether the failure was "repo not found" vs. another error:

git clone "$DIST_REPO" "$DIST_DIR/repo" || {
  if git ls-remote "$DIST_REPO" &>/dev/null; then
    # Remote is accessible but empty — init locally
    git init "$DIST_DIR/repo"
    cd "$DIST_DIR/repo"
    git remote add origin "$DIST_REPO"
  else
    echo "Error: Cannot access $DIST_REPO — check credentials and URL" >&2
    exit 1
  fi
}

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 19, 2026

Good concept. A dedicated publish script for a pre-built dist repo is a clean solution for the no-TypeScript-on-install goal. The structure and dry-run mode are well thought out. See inline comments for specific issues; summary below.

Bugs

  • Line 91: git ls-remote exits 0 even when no matching refs exist, so the else branch (lines 99-106) is dead code whenever the remote is reachable. Fix: add --exit-code flag.
  • Line 52: node require path is relative to the callers CWD, not MONOREPO_ROOT. Silently fails when the script is run from a subdirectory.
  • Line 243: git rev-parse only checks local tags. With a depth-1 clone, remote tags are not fetched, so the duplicate-tag guard never triggers. Use git ls-remote --exit-code --tags instead.

Reliability

  • Lines 92-106: any git clone failure (SSH key, permissions, network) silently falls through to git init. The real error only surfaces later at push.
  • Lines 136-138: missing lib/ is a warning not a hard failure. A package with no compiled output would be pushed to consumers. Should exit 1.
  • Line 229: git commit fails in clean CI environments without user.name/user.email configured.

Portability

  • Line 154: sed -i is not portable to macOS (BSD sed needs sed -i with an empty-string argument). Also only handles workspace:* not workspace:^ or workspace:~. Consolidating into the node -e pass in step 5 would fix both issues.

@AbanoubGhadban AbanoubGhadban merged commit da37b46 into upcoming-v16.3.0 Mar 19, 2026
41 of 43 checks passed
@AbanoubGhadban AbanoubGhadban deleted the build-and-publish-script branch March 19, 2026 13:19
AbanoubGhadban added a commit that referenced this pull request Apr 1, 2026
## Summary
- Adds `scripts/publish-to-builds-repo.sh` that builds all JS packages
and pushes the compiled output to
[shakacode/react-on-rails-builds](https://github.com/shakacode/react-on-rails-builds)
- The builds repo contains pre-built `package.json` + `lib/` for each
package, ready for consumption as pnpm git dependencies — no TypeScript
compilation needed during install
- The script defaults to pushing to a branch matching the current
react_on_rails branch name (overridable with `--branch`)

### What the script does
1. Runs `pnpm build` for all packages
2. Copies `package.json` + `lib/` to the builds repo
3. Replaces `workspace:*` references with the actual version
4. Strips `devDependencies` and build-related scripts
5. Commits, pushes, and tags (e.g., `v16.4.0`)

### Usage
```bash
./scripts/publish-to-builds-repo.sh              # build + push + tag
./scripts/publish-to-builds-repo.sh --dry-run     # preview without pushing
./scripts/publish-to-builds-repo.sh --tag v1.2.3  # custom tag
./scripts/publish-to-builds-repo.sh --branch main # override branch
```

### Consumer usage (pnpm)
```json
{
  "react-on-rails": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails",
  "react-on-rails-pro": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro",
  "react-on-rails-pro-node-renderer": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro-node-renderer"
}
```

## Test plan
- [x] Dry run completes successfully
- [x] Real run pushes to `shakacode/react-on-rails-builds` with correct
structure
- [x] Verified `workspace:*` replaced with actual version in dist
`package.json`
- [x] Verified `devDependencies` and build scripts stripped
- [x] Verified consumer `pnpm install` resolves correct packages in ~30s

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
justin808 pushed a commit that referenced this pull request Apr 12, 2026
## Summary
- Adds `scripts/publish-to-builds-repo.sh` that builds all JS packages
and pushes the compiled output to
[shakacode/react-on-rails-builds](https://github.com/shakacode/react-on-rails-builds)
- The builds repo contains pre-built `package.json` + `lib/` for each
package, ready for consumption as pnpm git dependencies — no TypeScript
compilation needed during install
- The script defaults to pushing to a branch matching the current
react_on_rails branch name (overridable with `--branch`)

### What the script does
1. Runs `pnpm build` for all packages
2. Copies `package.json` + `lib/` to the builds repo
3. Replaces `workspace:*` references with the actual version
4. Strips `devDependencies` and build-related scripts
5. Commits, pushes, and tags (e.g., `v16.4.0`)

### Usage
```bash
./scripts/publish-to-builds-repo.sh              # build + push + tag
./scripts/publish-to-builds-repo.sh --dry-run     # preview without pushing
./scripts/publish-to-builds-repo.sh --tag v1.2.3  # custom tag
./scripts/publish-to-builds-repo.sh --branch main # override branch
```

### Consumer usage (pnpm)
```json
{
  "react-on-rails": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails",
  "react-on-rails-pro": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro",
  "react-on-rails-pro-node-renderer": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro-node-renderer"
}
```

## Test plan
- [x] Dry run completes successfully
- [x] Real run pushes to `shakacode/react-on-rails-builds` with correct
structure
- [x] Verified `workspace:*` replaced with actual version in dist
`package.json`
- [x] Verified `devDependencies` and build scripts stripped
- [x] Verified consumer `pnpm install` resolves correct packages in ~30s

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
justin808 pushed a commit that referenced this pull request Apr 12, 2026
## Summary
- Adds `scripts/publish-to-builds-repo.sh` that builds all JS packages
and pushes the compiled output to
[shakacode/react-on-rails-builds](https://github.com/shakacode/react-on-rails-builds)
- The builds repo contains pre-built `package.json` + `lib/` for each
package, ready for consumption as pnpm git dependencies — no TypeScript
compilation needed during install
- The script defaults to pushing to a branch matching the current
react_on_rails branch name (overridable with `--branch`)

### What the script does
1. Runs `pnpm build` for all packages
2. Copies `package.json` + `lib/` to the builds repo
3. Replaces `workspace:*` references with the actual version
4. Strips `devDependencies` and build-related scripts
5. Commits, pushes, and tags (e.g., `v16.4.0`)

### Usage
```bash
./scripts/publish-to-builds-repo.sh              # build + push + tag
./scripts/publish-to-builds-repo.sh --dry-run     # preview without pushing
./scripts/publish-to-builds-repo.sh --tag v1.2.3  # custom tag
./scripts/publish-to-builds-repo.sh --branch main # override branch
```

### Consumer usage (pnpm)
```json
{
  "react-on-rails": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails",
  "react-on-rails-pro": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro",
  "react-on-rails-pro-node-renderer": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro-node-renderer"
}
```

## Test plan
- [x] Dry run completes successfully
- [x] Real run pushes to `shakacode/react-on-rails-builds` with correct
structure
- [x] Verified `workspace:*` replaced with actual version in dist
`package.json`
- [x] Verified `devDependencies` and build scripts stripped
- [x] Verified consumer `pnpm install` resolves correct packages in ~30s

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AbanoubGhadban added a commit that referenced this pull request May 3, 2026
## Summary
- Adds `scripts/publish-to-builds-repo.sh` that builds all JS packages
and pushes the compiled output to
[shakacode/react-on-rails-builds](https://github.com/shakacode/react-on-rails-builds)
- The builds repo contains pre-built `package.json` + `lib/` for each
package, ready for consumption as pnpm git dependencies — no TypeScript
compilation needed during install
- The script defaults to pushing to a branch matching the current
react_on_rails branch name (overridable with `--branch`)

### What the script does
1. Runs `pnpm build` for all packages
2. Copies `package.json` + `lib/` to the builds repo
3. Replaces `workspace:*` references with the actual version
4. Strips `devDependencies` and build-related scripts
5. Commits, pushes, and tags (e.g., `v16.4.0`)

### Usage
```bash
./scripts/publish-to-builds-repo.sh              # build + push + tag
./scripts/publish-to-builds-repo.sh --dry-run     # preview without pushing
./scripts/publish-to-builds-repo.sh --tag v1.2.3  # custom tag
./scripts/publish-to-builds-repo.sh --branch main # override branch
```

### Consumer usage (pnpm)
```json
{
  "react-on-rails": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails",
  "react-on-rails-pro": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro",
  "react-on-rails-pro-node-renderer": "github:shakacode/react-on-rails-builds#v16.4.0&path:react-on-rails-pro-node-renderer"
}
```

## Test plan
- [x] Dry run completes successfully
- [x] Real run pushes to `shakacode/react-on-rails-builds` with correct
structure
- [x] Verified `workspace:*` replaced with actual version in dist
`package.json`
- [x] Verified `devDependencies` and build scripts stripped
- [x] Verified consumer `pnpm install` resolves correct packages in ~30s

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant