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-analyticsSo 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.