ReviewOS

stacks/localtunnels

Hard `github:` dependency on `@stacksjs/ts-analytics` breaks installs for every `@stacksjs/tunnel` consumer

#32
Closed glennmichael123 opened this 9 days ago ·
9 days ago

localtunnels declares an optional, lazily-imported package as a hard dependency on a floating GitHub ref:

// packages/localtunnels/package.json
"dependencies": {
  "@stacksjs/ts-analytics": "github:stacksjs/ts-analytics",
  "bunfig": "^0.15.6"
}

Because the ref is unpinned, a package manager resolves it to whatever the default branch happened to be and freezes that commit into the consumer's lockfile. Every install from then on downloads a GitHub repo tarball rather than a registry package.

The failure

This surfaced as a CI failure in a downstream app that has never heard of ts-analytics:

bun install --frozen-lockfile
error: GET https://api.github.com/repos/stacksjs/ts-analytics/tarball/54013c6 - 504
Error: Process completed with exit code 1.

The chain that drags it in:

app -> @stacksjs/tunnel@0.72.8 -> localtunnels@^0.2.11 -> github:stacksjs/ts-analytics

So this reaches every consumer of @stacksjs/tunnel, which means every scaffolded Stacks app.

The tarball URL itself is healthy — I retried it three times and got 200 each time. The failure is throughput. api.github.com allows 60 requests/hour per IP unauthenticated (measured against /rate_limit, not assumed), and GitHub Actions runners share an IP pool, so under load the request returns 504 or 403. A registry dependency would not be subject to that limit at all.

0.2.12 is the current latest, so no downstream upgrade escapes it.

Why it should not be a dependency at all

@stacksjs/ts-analytics is only ever reached through a lazy await import() on the deploy --analytics path, already guarded by a try/catch that tells the user how to install it:

// packages/localtunnels/src/cloud/deploy-analytics.ts:69
const tsAnalytics = await import('@stacksjs/ts-analytics')
// ...:74
'@stacksjs/ts-analytics package is required for analytics deployment.\n'
  + 'Install it with: bun add @stacksjs/ts-analytics',

The codebase already agrees with this position elsewhere in the very same file. @stacksjs/ts-cloud is imported the same way, with the same guard and the same message:

// packages/localtunnels/src/cloud/deploy-analytics.ts:55
const tsCloud = await import('@stacksjs/ts-cloud')
// ...:61
'@stacksjs/ts-cloud package is required for analytics deployment.\n'
  + 'Install it with: bun add @stacksjs/ts-cloud',

…and @stacksjs/ts-cloud is not declared as a dependency. So the intended design is clearly "optional, install it if you use this path". ts-analytics is the inconsistent one.

Suggested fix

Drop it from dependencies and treat it like ts-cloud — the runtime guard already handles absence gracefully. If it should be declared for discoverability, an optional peer dep pointing at the published npm package rather than a git ref would do it:

"peerDependencies": { "@ts-analytics/tracking": "^0.1.13" },
"peerDependenciesMeta": { "@ts-analytics/tracking": { "optional": true } }

Note the rename: the package publishes to npm as @ts-analytics/tracking, and @stacksjs/ts-analytics is not on the registry under that name — which is likely why the git ref was reached for in the first place.

One wrinkle worth checking before switching: deploy-analytics.ts resolves a source file out of the package —

`${require.resolve('@stacksjs/ts-analytics').replace(/\/dist\/.*$|\/src\/.*$/, '')}/deploy/lambda-handler.ts`

— so whichever package it points at has to ship deploy/lambda-handler.ts in its published files, not just built output. That may be the actual reason for the repo tarball, in which case the fix is to add that path to the published package's files rather than to keep fetching the whole repo.

Workaround in the meantime

Downstream repos can set GITHUB_TOKEN on their install steps to lift the limit to 5,000/hour, which is what I've done. It makes the fetch reliable; it does not remove it.

9 days ago

Fixed in 3aabc4f.

One correction to the issue above. I wrote that @stacksjs/ts-analytics "is not on the registry under that name". That is wrong — it is published, at 0.1.6 (2026-07-08, the only version). So the bun add @stacksjs/ts-analytics in the error messages did resolve to something; it just resolved to a stale copy that cannot serve the deploy path.

That makes the "wrinkle" at the bottom of the issue the decisive constraint rather than a footnote. I checked both published tarballs:

packageships deploy/lambda-handler.ts?
@ts-analytics/tracking@0.1.13no — files: ["bin","dist"]
@stacksjs/ts-analytics@0.1.6no — 274 entries, zero under deploy/

deployAnalytics bundles that file straight out of the resolved package (deploy-analytics.ts:149), so no registry dependency can satisfy it. Swapping the git ref for an npm version would have moved the failure from install time to deploy time.

What shipped instead: the dep moved from dependencies to devDependencies, keeping the git ref.

  • Consumers stop fetching the tarball entirely — verified by packing the package and installing it into a clean project: the resolved manifest is {"bunfig": "^0.15.6"}, no @stacksjs/ts-analytics anywhere in the tree, zero github: refs in the lockfile.
  • deploy:analytics keeps working in this repo, because a devDependency is still installed here and still carries deploy/lambda-handler.ts.
  • It matches how @stacksjs/ts-cloud is already handled twelve lines earlier in the same function.
  • Typecheck clean; tests 49 pass / 34 skip / 0 fail, unchanged from before.

The two error messages now say bun add github:stacksjs/ts-analytics and explain why the registry copy will not do.

Still open, if anyone wants it: publishing deploy/ in the npm package would let this be a normal versioned dependency. Not needed for the install failure, so I left it alone.

Sign in to comment on this issue.