# npmglobalize: transitive workspace dep gap on standalone publish

## Symptom

A workspace member with `npmVisibility: "public"` (`@bobfrankston/mailx-imap`) gets
published as a standalone registry package. Its tarball declares
`@bobfrankston/mailx-settings: ^0.1.6` as a registry dep — but
`mailx-settings` is *also* a workspace member, with no `.globalize.json5`,
so it was never published. End user runs `npm install -g @bobfrankston/mailx`
on a fresh machine and gets:

```
404 Not Found - GET https://registry.npmjs.org/@bobfrankston%2fmailx-settings
```

Reproducer (mailx workspace, 2026-05-02):
- `app/packages/mailx-imap/.globalize.json5` → `npmVisibility: "public"`
- `app/packages/mailx-imap/package.json` → `dependencies: { "@bobfrankston/mailx-settings": "file:../mailx-settings" }`
- `app/packages/mailx-settings/` exists, no `.globalize.json5`
- `npmglobalize` from workspace root: builds everything, publishes mailx-imap and the parent mailx, exits with success
- `mailx-imap`'s published `package.json` rewrote `file:../mailx-settings` → `^0.1.6`
- `mailx-settings` was never published

## What npmglobalize does today

When publishing a standalone-public workspace member, file: deps that point
at *other workspace members* get rewritten to registry version constraints
(`^X.Y.Z`) — same as it'd do for any external file: dep. No check that the
referenced workspace member is itself public/published.

Result: a tarball whose declared deps 404 on a clean install. The publish
itself succeeds; the breakage shows up at install time on a remote machine.

## What npmglobalize should do

Pick one (preference order):

### 1. Fail loud at publish time (cheapest, most defensible)

Before publishing a workspace member as standalone-public, walk its file:
deps. For each that resolves to another workspace member:
- If that member is also public → fine, the registry constraint will resolve.
- If that member is **not** public → **refuse to publish** with a clear error:

  ```
  Refusing to publish @bobfrankston/mailx-imap as a standalone registry
  package: it depends on @bobfrankston/mailx-settings (workspace member,
  not public). Published tarball would 404 on install.

  Fixes:
   - Add .globalize.json5 with npmVisibility: "public" to mailx-settings
     (and any of its transitive workspace deps).
   - Or remove .globalize.json5 from mailx-imap so it stays bundled
     under the parent's tarball.
  ```

This is the behavior I'd ask for first. It's predictable, errors at the
site of the mistake, and doesn't silently change packaging.

### 2. Bundle transitive workspace deps in the standalone tarball

When publishing a standalone-public workspace member, copy any non-public
workspace deps **into the tarball** as bundled directories, and keep file:
paths instead of rewriting to registry constraints. The published tarball
becomes self-contained.

Trade-off: tarball gets bigger and pulls in copies on every standalone
publish. Multiple public members each carry their own copy of shared
internal deps — duplication on disk, but install-time correctness.

### 3. Auto-promote transitive workspace deps to public

If a public member depends on a non-public workspace member, automatically
promote the dep to public and publish it too. Cascading.

Trade-off: silently expands the publish surface area — one new public
package can flip the entire transitive closure to public. Surprising and
hard to undo. Less defensible than (1) or (2).

## Recommendation

Implement (1). It's a one-pass dependency walk before the publish step,
returning a clear error message. The other options can layer on later if
the workflow demands them — but the loud failure prevents the silent-bad-
publish from ever shipping in the first place.

## Edge cases (1) needs to handle

- **External file: deps** (not workspace members): `file:../../MailApps/iflow-direct`
  resolves outside the workspace. Today these get rewritten to registry
  versions and presumably *those* deps are published separately. Don't
  block on those — only on workspace-internal file: deps.
- **Public member depends on public member**: fine, registry resolves both.
- **Public member depends on a registry version that exists**: fine.
- **Public member depends on a workspace member with `noPublish: true`**:
  same gap, same fail. Treat noPublish=true workspace members as "not public".
- **Cyclic deps**: shouldn't apply here (workspace dep graphs are DAGs by
  npm's own constraint), but if the walk hits one, abort with a different
  message.

## Why this matters

Symptom is "Mac install fails" — but the root cause is npmglobalize
publishing a tarball it can't possibly install correctly. The user has
no signal at publish time; the breakage is invisible until someone tries
to install the package on a fresh machine. Fail-at-publish makes the
contract explicit: *if it publishes, it installs.*
