which-url 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Auto-detect your app's URL across hosting providers. Zero config.
4
4
 
5
+ This README documents `which-url@0.0.11`.
6
+
5
7
  ```bash
6
8
  npm install which-url
7
9
  ```
@@ -30,11 +32,12 @@ The default export gives you everything as an object:
30
32
  import appUrl from 'which-url'
31
33
 
32
34
  appUrl.origin // "https://myapp.com"
33
- appUrl.productionOrigin // "https://myapp.com"
35
+ appUrl.productionOrigin // "https://myapp.com" or undefined
34
36
  appUrl.hostname // "myapp.com"
35
37
  appUrl.protocol // "https:"
36
38
  appUrl.env // "production"
37
39
  appUrl.platform // "vercel"
40
+ appUrl.isResolved // true
38
41
  appUrl.isProduction // true
39
42
  ```
40
43
 
@@ -90,11 +93,11 @@ Reads environment variables that hosting providers set automatically:
90
93
 
91
94
  When you call `createUrl({ env })`, the passed object replaces `process.env` as the source for steps 1–2.
92
95
 
93
- If nothing is detected in production, the default singleton returns empty URL strings so imports stay safe in tests, client bundles, and build tools. Call `createUrl()` directly when a missing URL should throw.
96
+ If nothing is detected in production, the default singleton returns empty current-URL strings and `isResolved: false` so imports stay safe in tests, client bundles, and build tools. Call `createUrl()` directly when a missing current URL should throw.
94
97
 
95
98
  `origin` is the current environment origin. In a preview deployment, it should point at the preview. In production, it should point at production. Locally, it should point at local dev.
96
99
 
97
- `productionOrigin` is the canonical production origin when configured or detectable. Use it for things that should still point at the public production site from previews, such as canonical metadata, social cards, and "view live site" links.
100
+ `productionOrigin` is the canonical production origin when configured or detectable, otherwise `undefined`. Use it for things that should still point at the public production site from previews, such as canonical metadata, social cards, and "view live site" links. When you need a URL in all environments, use `productionOrigin ?? origin`.
98
101
 
99
102
  ## Strict mode with `createUrl()`
100
103
 
@@ -116,6 +119,18 @@ auth({ baseURL: appUrl.origin })
116
119
 
117
120
  `createUrl()` resolves fresh environment values when called. If no URL can be detected in production, it throws with instructions to set `APP_URL`.
118
121
 
122
+ If you intentionally use the import-safe singleton in production code, branch on `isResolved` before trusting URL strings:
123
+
124
+ ```typescript
125
+ import { isResolved, origin } from 'which-url'
126
+
127
+ if (!isResolved) {
128
+ throw new Error('APP_URL is required in production')
129
+ }
130
+
131
+ auth({ baseURL: origin })
132
+ ```
133
+
119
134
  For runtimes that pass env as an argument (e.g. Cloudflare Workers), call `createUrl({ env })` and the passed object replaces `process.env` for that resolution. See [Cloudflare Workers](#cloudflare-workers) below.
120
135
 
121
136
  ## Override with `APP_URL`
@@ -143,7 +158,7 @@ APP_PRODUCTION_URL=https://myapp.com
143
158
  import { origin, productionOrigin } from 'which-url'
144
159
 
145
160
  origin // current environment origin
146
- productionOrigin // canonical production origin, when available
161
+ productionOrigin // canonical production origin, or undefined
147
162
  ```
148
163
 
149
164
  On Vercel, `productionOrigin` is detected from `VERCEL_PROJECT_PRODUCTION_URL`, even in preview deployments.
@@ -152,6 +167,18 @@ In Next.js browser bundles on Vercel deployments, it is detected from `NEXT_PUBL
152
167
 
153
168
  **Client-side frameworks:** Framework prefixes are supported here too — `NEXT_PUBLIC_APP_PRODUCTION_URL`, `VITE_APP_PRODUCTION_URL`, `PUBLIC_APP_PRODUCTION_URL`, etc. These are build-time values in browser bundles.
154
169
 
170
+ ## Multi-tenant apps
171
+
172
+ For subdomain-based tenants, `hostname` is often enough when the current app host is also the tenant suffix:
173
+
174
+ ```typescript
175
+ import { hostname } from 'which-url'
176
+
177
+ const tenantSuffix = hostname
178
+ ```
179
+
180
+ If your app origin and tenant suffix differ — for example `app.foo.com` for the product UI and `*.foo.com` for tenants — keep that suffix in your own env var for now. `which-url` does not currently expose a separate tenant suffix.
181
+
155
182
  ## Platform support
156
183
 
157
184
  | Platform | Detection | URL source | Verified |
@@ -283,10 +310,11 @@ Strict resolver function. It resolves when called and throws if no URL can be de
283
310
  | `href` | `string` | Same as `origin` |
284
311
  | `protocol` | `string` | `"https:"` |
285
312
  | `port` | `string` | `""` or `"3000"` |
286
- | `productionOrigin` | `string` | `"https://myapp.com"` |
313
+ | `productionOrigin` | `string \| undefined` | `"https://myapp.com"` or `undefined` |
287
314
  | `env` | `AppEnv` | `"production"` \| `"preview"` \| `"local"` |
288
315
  | `platform` | `Platform` | `"vercel"` \| `"netlify"` \| ... \| `null` |
289
316
  | `debug`* | `string` | `"[provider:vercel] url=myapp.com \| env=production (vercel:production)"` |
317
+ | `isResolved` | `boolean` | `true` unless the import-safe fallback was used |
290
318
  | `isProduction` | `boolean` | |
291
319
  | `isPreview` | `boolean` | |
292
320
  | `isLocal` | `boolean` | |
package/dist/index.d.ts CHANGED
@@ -13,14 +13,16 @@ export declare const host: string;
13
13
  export declare const protocol: string;
14
14
  /** Port string — `""` for default ports, `"3000"` for custom */
15
15
  export declare const port: string;
16
- /** Canonical production origin when configured or detectable `"https://myapp.com"` */
17
- export declare const productionOrigin: string;
16
+ /** Canonical production origin when configured or detectable, otherwise `undefined`. */
17
+ export declare const productionOrigin: string | undefined;
18
18
  /** Current environment — `"production"`, `"preview"`, or `"local"` */
19
19
  export declare const env: AppEnv;
20
20
  /** Detected hosting platform — `"vercel"`, `"netlify"`, etc. or `null` */
21
21
  export declare const platform: Platform;
22
22
  /** Resolution debug string. */
23
23
  export declare const debug: string;
24
+ /** `true` when the current URL resolved successfully; `false` only on the import-safe fallback. */
25
+ export declare const isResolved: boolean;
24
26
  /** `true` when running in production */
25
27
  export declare const isProduction: boolean;
26
28
  /** `true` when running in a preview/staging deployment */
@@ -35,9 +37,10 @@ export declare const isLocal: boolean;
35
37
  * import appUrl from 'which-url'
36
38
  *
37
39
  * appUrl.origin // "https://myapp.com"
38
- * appUrl.productionOrigin // "https://myapp.com"
40
+ * appUrl.productionOrigin // "https://myapp.com" or undefined
39
41
  * appUrl.env // "production"
40
42
  * appUrl.platform // "vercel"
43
+ * appUrl.isResolved // true
41
44
  * appUrl.debug // "[provider:vercel] url=myapp.com | env=production (vercel:production)"
42
45
  * ```
43
46
  */
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var u=["VERCEL_ENV","VERCEL_URL","VERCEL_BRANCH_URL","VERCEL_PROJECT_PRODUCTION_URL","APP_URL","APP_PRODUCTION_URL","APP_ENV"];function D(E){switch(E){case"VERCEL_ENV":return process.env.VERCEL_ENV||process.env.NEXT_PUBLIC_VERCEL_ENV||process.env.NUXT_ENV_VERCEL_ENV||process.env.VITE_VERCEL_ENV||process.env.PUBLIC_VERCEL_ENV||process.env.REACT_APP_VERCEL_ENV||process.env.GATSBY_VERCEL_ENV||process.env.VUE_APP_VERCEL_ENV||process.env.REDWOOD_ENV_VERCEL_ENV||process.env.SANITY_STUDIO_VERCEL_ENV||void 0;case"VERCEL_URL":return process.env.VERCEL_URL||process.env.NEXT_PUBLIC_VERCEL_URL||process.env.NUXT_ENV_VERCEL_URL||process.env.VITE_VERCEL_URL||process.env.PUBLIC_VERCEL_URL||process.env.REACT_APP_VERCEL_URL||process.env.GATSBY_VERCEL_URL||process.env.VUE_APP_VERCEL_URL||process.env.REDWOOD_ENV_VERCEL_URL||process.env.SANITY_STUDIO_VERCEL_URL||void 0;case"VERCEL_BRANCH_URL":return process.env.VERCEL_BRANCH_URL||process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL||process.env.NUXT_ENV_VERCEL_BRANCH_URL||process.env.VITE_VERCEL_BRANCH_URL||process.env.PUBLIC_VERCEL_BRANCH_URL||process.env.REACT_APP_VERCEL_BRANCH_URL||process.env.GATSBY_VERCEL_BRANCH_URL||process.env.VUE_APP_VERCEL_BRANCH_URL||process.env.REDWOOD_ENV_VERCEL_BRANCH_URL||process.env.SANITY_STUDIO_VERCEL_BRANCH_URL||void 0;case"VERCEL_PROJECT_PRODUCTION_URL":return process.env.VERCEL_PROJECT_PRODUCTION_URL||process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL||process.env.NUXT_ENV_VERCEL_PROJECT_PRODUCTION_URL||process.env.VITE_VERCEL_PROJECT_PRODUCTION_URL||process.env.PUBLIC_VERCEL_PROJECT_PRODUCTION_URL||process.env.REACT_APP_VERCEL_PROJECT_PRODUCTION_URL||process.env.GATSBY_VERCEL_PROJECT_PRODUCTION_URL||process.env.VUE_APP_VERCEL_PROJECT_PRODUCTION_URL||process.env.REDWOOD_ENV_VERCEL_PROJECT_PRODUCTION_URL||process.env.SANITY_STUDIO_VERCEL_PROJECT_PRODUCTION_URL||void 0;case"APP_URL":return process.env.APP_URL||process.env.NEXT_PUBLIC_APP_URL||process.env.NUXT_ENV_APP_URL||process.env.VITE_APP_URL||process.env.PUBLIC_APP_URL||process.env.REACT_APP_APP_URL||process.env.GATSBY_APP_URL||process.env.VUE_APP_APP_URL||process.env.REDWOOD_ENV_APP_URL||process.env.SANITY_STUDIO_APP_URL||void 0;case"APP_PRODUCTION_URL":return process.env.APP_PRODUCTION_URL||process.env.NEXT_PUBLIC_APP_PRODUCTION_URL||process.env.NUXT_ENV_APP_PRODUCTION_URL||process.env.VITE_APP_PRODUCTION_URL||process.env.PUBLIC_APP_PRODUCTION_URL||process.env.REACT_APP_APP_PRODUCTION_URL||process.env.GATSBY_APP_PRODUCTION_URL||process.env.VUE_APP_APP_PRODUCTION_URL||process.env.REDWOOD_ENV_APP_PRODUCTION_URL||process.env.SANITY_STUDIO_APP_PRODUCTION_URL||void 0;case"APP_ENV":return process.env.APP_ENV||process.env.NEXT_PUBLIC_APP_ENV||process.env.NUXT_ENV_APP_ENV||process.env.VITE_APP_ENV||process.env.PUBLIC_APP_ENV||process.env.REACT_APP_APP_ENV||process.env.GATSBY_APP_ENV||process.env.VUE_APP_APP_ENV||process.env.REDWOOD_ENV_APP_ENV||process.env.SANITY_STUDIO_APP_ENV||void 0;default:return}}function C(E,P){if(typeof process<"u"&&process?.env&&E===process.env){if(u.includes(P)){let L=D(P);if(L)return L}}let A=["","NEXT_PUBLIC_","NUXT_ENV_","VITE_","PUBLIC_","REACT_APP_","GATSBY_","VUE_APP_","REDWOOD_ENV_","SANITY_STUDIO_"];for(let L of A){let _=E[L+P];if(_)return _}return}function t(E){if(E!==void 0)return G(E);if(typeof process<"u"&&process?.env)return process.env;return{}}function G(E){let P={};for(let[A,L]of Object.entries(E))if(typeof L==="string")P[A]=L;return P}var U=[{name:"vercel",detect:(E)=>!!E.VERCEL||!!C(E,"VERCEL_ENV"),resolveUrl:(E)=>{if(C(E,"VERCEL_ENV")==="production")return C(E,"VERCEL_PROJECT_PRODUCTION_URL")||C(E,"VERCEL_URL")||null;return C(E,"VERCEL_BRANCH_URL")||C(E,"VERCEL_URL")||null},resolveProductionUrl:(E)=>C(E,"VERCEL_PROJECT_PRODUCTION_URL")||null,resolveEnv:(E)=>{let P=C(E,"VERCEL_ENV");if(P==="production")return"production";if(P==="preview")return"preview";return"local"}},{name:"netlify",detect:(E)=>!!E.NETLIFY,resolveUrl:(E)=>{if(E.CONTEXT==="production")return E.URL||null;return E.DEPLOY_PRIME_URL||E.DEPLOY_URL||null},resolveProductionUrl:(E)=>E.URL||null,resolveEnv:(E)=>{if(E.CONTEXT==="production")return"production";if(E.CONTEXT==="deploy-preview"||E.CONTEXT==="branch-deploy")return"preview";return"local"}},{name:"cloudflare",detect:(E)=>!!E.CF_PAGES,resolveUrl:(E)=>E.CF_PAGES_URL||null,resolveEnv:(E)=>{if(E.CF_PAGES_BRANCH==="main"||E.CF_PAGES_BRANCH==="master")return"production";return"preview"}},{name:"railway",detect:(E)=>!!E.RAILWAY_PUBLIC_DOMAIN,resolveUrl:(E)=>E.RAILWAY_PUBLIC_DOMAIN||null,resolveProductionUrl:(E)=>E.RAILWAY_PUBLIC_DOMAIN||null,resolveEnv:(E)=>{if(E.RAILWAY_ENVIRONMENT==="production")return"production";return"production"}},{name:"fly",detect:(E)=>!!E.FLY_APP_NAME,resolveUrl:(E)=>E.FLY_APP_NAME?`${E.FLY_APP_NAME}.fly.dev`:null,resolveProductionUrl:(E)=>E.FLY_APP_NAME?`${E.FLY_APP_NAME}.fly.dev`:null,resolveEnv:()=>"production"},{name:"render",detect:(E)=>!!E.RENDER,resolveUrl:(E)=>E.RENDER_EXTERNAL_URL||null,resolveProductionUrl:(E)=>E.IS_PULL_REQUEST==="true"?null:E.RENDER_EXTERNAL_URL||null,resolveEnv:(E)=>{if(E.IS_PULL_REQUEST==="true")return"preview";return"production"}},{name:"digitalocean",detect:(E)=>!!E.DIGITALOCEAN_APP_PLATFORM,resolveUrl:(E)=>E.APP_URL||null,resolveProductionUrl:(E)=>E.APP_URL||null,resolveEnv:()=>"production"},{name:"heroku",detect:(E)=>!!E.HEROKU_APP_NAME,resolveUrl:(E)=>E.HEROKU_APP_NAME?`${E.HEROKU_APP_NAME}.herokuapp.com`:null,resolveProductionUrl:(E)=>E.HEROKU_APP_NAME?`${E.HEROKU_APP_NAME}.herokuapp.com`:null,resolveEnv:()=>"production"}];function T(E){let P=E.trim().replace(/\/+$/,"");if(P.startsWith("http://")||P.startsWith("https://"))return P;return`https://${P}`}function f(E){let P=t(E),A=C(P,"APP_URL");if(A)return{url:T(A),debugLabel:`[override] APP_URL=${A}`};if(P.PORTLESS_TAILSCALE_URL)return{url:P.PORTLESS_TAILSCALE_URL,debugLabel:`[portless:tailscale] PORTLESS_TAILSCALE_URL=${P.PORTLESS_TAILSCALE_URL}`};if(P.PORTLESS_URL)return{url:P.PORTLESS_URL,debugLabel:`[portless] PORTLESS_URL=${P.PORTLESS_URL}`};for(let _ of U)if(_.detect(P)){let R=_.resolveUrl(P);if(R)return{url:T(R),debugLabel:`[provider:${_.name}] url=${R}`}}if(typeof window<"u"&&window.location)return{url:window.location.origin,debugLabel:"[browser] window.location.origin"};if(P.NODE_ENV!=="production"){let _=P.PORT||"3000";return{url:`http://localhost:${_}`,debugLabel:`[fallback] PORT=${_}`}}throw Error("which-url: Cannot detect app URL. Set APP_URL environment variable.")}function F(E){let P=t(E),A=C(P,"APP_PRODUCTION_URL");if(A)return{url:T(A),debugLabel:`[production:override] APP_PRODUCTION_URL=${A}`};for(let _ of U)if(_.detect(P)){let R=_.resolveProductionUrl?.(P);if(R)return{url:T(R),debugLabel:`[production:provider:${_.name}] url=${R}`}}for(let _ of U)if(_.detect(P)&&_.resolveEnv(P)==="production"){let R=_.resolveUrl(P);if(R)return{url:T(R),debugLabel:`[production:current:${_.name}] url=${R}`}}let L=C(P,"APP_URL");if(L&&P.NODE_ENV==="production")return{url:T(L),debugLabel:`[production:current] APP_URL=${L}`};return null}function M(E){let P=t(E);for(let A of U)if(A.detect(P))return A.name;return null}var K=["production","preview","local"];function c(E){let P=t(E),A=C(P,"APP_ENV");if(A&&K.includes(A))return{env:A,debugLabel:`APP_ENV=${A}`};if(P.NODE_ENV==="development")return{env:"local",debugLabel:"NODE_ENV=development"};for(let L of U)if(L.detect(P)){let _=L.resolveEnv(P);return{env:_,debugLabel:`${L.name}:${_}`}}if(P.NODE_ENV==="production")return{env:"production",debugLabel:"NODE_ENV=production"};return{env:"local",debugLabel:"default"}}function H(E){return Object.defineProperty(E,"debug",{value:E.debug,enumerable:!1,configurable:!1}),E}function X(E){let P=E?.env,{url:A,debugLabel:L}=f(P),_=new URL(A),{env:R,debugLabel:O}=c(P),V=M(P),I=F(P),Y=I?new URL(I.url).origin:"",B=I?` | production=${Y} (${I.debugLabel})`:" | production=unresolved",W=`${L} | env=${R} (${O})${B}`,$={href:_.origin,origin:_.origin,hostname:_.hostname,host:_.host,protocol:_.protocol,port:_.port,productionOrigin:Y,env:R,platform:V,debug:W,isProduction:R==="production",isPreview:R==="preview",isLocal:R==="local"};return H($)}function S(E){let P=E instanceof Error?E.message:"resolution failed",A="local",L="default";try{let R=c();A=R.env,L=R.debugLabel}catch{}let _={href:"",origin:"",hostname:"",host:"",protocol:"",port:"",productionOrigin:"",env:A,platform:M(),debug:`[error] ${P} | env=${A} (${L})`,isProduction:A==="production",isPreview:A==="preview",isLocal:A==="local"};return H(_)}var N;try{N=X()}catch(E){N=S(E)}var{href:m,origin:g,hostname:i,host:o,protocol:s,port:d,productionOrigin:n,env:p,platform:a,debug:r,isProduction:v,isPreview:e,isLocal:EE}=N,_E=N;export{s as protocol,n as productionOrigin,d as port,a as platform,g as origin,v as isProduction,e as isPreview,EE as isLocal,m as href,i as hostname,o as host,p as env,_E as default,r as debug,X as createUrl};
1
+ var D=["VERCEL_ENV","VERCEL_URL","VERCEL_BRANCH_URL","VERCEL_PROJECT_PRODUCTION_URL","APP_URL","APP_PRODUCTION_URL","APP_ENV"];function G(E){switch(E){case"VERCEL_ENV":return process.env.VERCEL_ENV||process.env.NEXT_PUBLIC_VERCEL_ENV||process.env.NUXT_ENV_VERCEL_ENV||process.env.VITE_VERCEL_ENV||process.env.PUBLIC_VERCEL_ENV||process.env.REACT_APP_VERCEL_ENV||process.env.GATSBY_VERCEL_ENV||process.env.VUE_APP_VERCEL_ENV||process.env.REDWOOD_ENV_VERCEL_ENV||process.env.SANITY_STUDIO_VERCEL_ENV||void 0;case"VERCEL_URL":return process.env.VERCEL_URL||process.env.NEXT_PUBLIC_VERCEL_URL||process.env.NUXT_ENV_VERCEL_URL||process.env.VITE_VERCEL_URL||process.env.PUBLIC_VERCEL_URL||process.env.REACT_APP_VERCEL_URL||process.env.GATSBY_VERCEL_URL||process.env.VUE_APP_VERCEL_URL||process.env.REDWOOD_ENV_VERCEL_URL||process.env.SANITY_STUDIO_VERCEL_URL||void 0;case"VERCEL_BRANCH_URL":return process.env.VERCEL_BRANCH_URL||process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL||process.env.NUXT_ENV_VERCEL_BRANCH_URL||process.env.VITE_VERCEL_BRANCH_URL||process.env.PUBLIC_VERCEL_BRANCH_URL||process.env.REACT_APP_VERCEL_BRANCH_URL||process.env.GATSBY_VERCEL_BRANCH_URL||process.env.VUE_APP_VERCEL_BRANCH_URL||process.env.REDWOOD_ENV_VERCEL_BRANCH_URL||process.env.SANITY_STUDIO_VERCEL_BRANCH_URL||void 0;case"VERCEL_PROJECT_PRODUCTION_URL":return process.env.VERCEL_PROJECT_PRODUCTION_URL||process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL||process.env.NUXT_ENV_VERCEL_PROJECT_PRODUCTION_URL||process.env.VITE_VERCEL_PROJECT_PRODUCTION_URL||process.env.PUBLIC_VERCEL_PROJECT_PRODUCTION_URL||process.env.REACT_APP_VERCEL_PROJECT_PRODUCTION_URL||process.env.GATSBY_VERCEL_PROJECT_PRODUCTION_URL||process.env.VUE_APP_VERCEL_PROJECT_PRODUCTION_URL||process.env.REDWOOD_ENV_VERCEL_PROJECT_PRODUCTION_URL||process.env.SANITY_STUDIO_VERCEL_PROJECT_PRODUCTION_URL||void 0;case"APP_URL":return process.env.APP_URL||process.env.NEXT_PUBLIC_APP_URL||process.env.NUXT_ENV_APP_URL||process.env.VITE_APP_URL||process.env.PUBLIC_APP_URL||process.env.REACT_APP_APP_URL||process.env.GATSBY_APP_URL||process.env.VUE_APP_APP_URL||process.env.REDWOOD_ENV_APP_URL||process.env.SANITY_STUDIO_APP_URL||void 0;case"APP_PRODUCTION_URL":return process.env.APP_PRODUCTION_URL||process.env.NEXT_PUBLIC_APP_PRODUCTION_URL||process.env.NUXT_ENV_APP_PRODUCTION_URL||process.env.VITE_APP_PRODUCTION_URL||process.env.PUBLIC_APP_PRODUCTION_URL||process.env.REACT_APP_APP_PRODUCTION_URL||process.env.GATSBY_APP_PRODUCTION_URL||process.env.VUE_APP_APP_PRODUCTION_URL||process.env.REDWOOD_ENV_APP_PRODUCTION_URL||process.env.SANITY_STUDIO_APP_PRODUCTION_URL||void 0;case"APP_ENV":return process.env.APP_ENV||process.env.NEXT_PUBLIC_APP_ENV||process.env.NUXT_ENV_APP_ENV||process.env.VITE_APP_ENV||process.env.PUBLIC_APP_ENV||process.env.REACT_APP_APP_ENV||process.env.GATSBY_APP_ENV||process.env.VUE_APP_APP_ENV||process.env.REDWOOD_ENV_APP_ENV||process.env.SANITY_STUDIO_APP_ENV||void 0;default:return}}function C(E,P){if(typeof process<"u"&&process?.env&&E===process.env){if(D.includes(P)){let _=G(P);if(_)return _}}let L=["","NEXT_PUBLIC_","NUXT_ENV_","VITE_","PUBLIC_","REACT_APP_","GATSBY_","VUE_APP_","REDWOOD_ENV_","SANITY_STUDIO_"];for(let _ of L){let A=E[_+P];if(A)return A}return}function I(E){if(E!==void 0)return K(E);if(typeof process<"u"&&process?.env)return process.env;return{}}function K(E){let P={};for(let[L,_]of Object.entries(E))if(typeof _==="string")P[L]=_;return P}var U=[{name:"vercel",detect:(E)=>!!E.VERCEL||!!C(E,"VERCEL_ENV"),resolveUrl:(E)=>{if(C(E,"VERCEL_ENV")==="production")return C(E,"VERCEL_PROJECT_PRODUCTION_URL")||C(E,"VERCEL_URL")||null;return C(E,"VERCEL_BRANCH_URL")||C(E,"VERCEL_URL")||null},resolveProductionUrl:(E)=>C(E,"VERCEL_PROJECT_PRODUCTION_URL")||null,resolveEnv:(E)=>{let P=C(E,"VERCEL_ENV");if(P==="production")return"production";if(P==="preview")return"preview";return"local"}},{name:"netlify",detect:(E)=>!!E.NETLIFY,resolveUrl:(E)=>{if(E.CONTEXT==="production")return E.URL||null;return E.DEPLOY_PRIME_URL||E.DEPLOY_URL||null},resolveProductionUrl:(E)=>E.URL||null,resolveEnv:(E)=>{if(E.CONTEXT==="production")return"production";if(E.CONTEXT==="deploy-preview"||E.CONTEXT==="branch-deploy")return"preview";return"local"}},{name:"cloudflare",detect:(E)=>!!E.CF_PAGES,resolveUrl:(E)=>E.CF_PAGES_URL||null,resolveEnv:(E)=>{if(E.CF_PAGES_BRANCH==="main"||E.CF_PAGES_BRANCH==="master")return"production";return"preview"}},{name:"railway",detect:(E)=>!!E.RAILWAY_PUBLIC_DOMAIN,resolveUrl:(E)=>E.RAILWAY_PUBLIC_DOMAIN||null,resolveProductionUrl:(E)=>E.RAILWAY_PUBLIC_DOMAIN||null,resolveEnv:(E)=>{if(E.RAILWAY_ENVIRONMENT==="production")return"production";return"production"}},{name:"fly",detect:(E)=>!!E.FLY_APP_NAME,resolveUrl:(E)=>E.FLY_APP_NAME?`${E.FLY_APP_NAME}.fly.dev`:null,resolveProductionUrl:(E)=>E.FLY_APP_NAME?`${E.FLY_APP_NAME}.fly.dev`:null,resolveEnv:()=>"production"},{name:"render",detect:(E)=>!!E.RENDER,resolveUrl:(E)=>E.RENDER_EXTERNAL_URL||null,resolveProductionUrl:(E)=>E.IS_PULL_REQUEST==="true"?null:E.RENDER_EXTERNAL_URL||null,resolveEnv:(E)=>{if(E.IS_PULL_REQUEST==="true")return"preview";return"production"}},{name:"digitalocean",detect:(E)=>!!E.DIGITALOCEAN_APP_PLATFORM,resolveUrl:(E)=>E.APP_URL||null,resolveProductionUrl:(E)=>E.APP_URL||null,resolveEnv:()=>"production"},{name:"heroku",detect:(E)=>!!E.HEROKU_APP_NAME,resolveUrl:(E)=>E.HEROKU_APP_NAME?`${E.HEROKU_APP_NAME}.herokuapp.com`:null,resolveProductionUrl:(E)=>E.HEROKU_APP_NAME?`${E.HEROKU_APP_NAME}.herokuapp.com`:null,resolveEnv:()=>"production"}];function T(E){let P=E.trim().replace(/\/+$/,"");if(P.startsWith("http://")||P.startsWith("https://"))return P;return`https://${P}`}function F(E){let P=I(E),L=C(P,"APP_URL");if(L)return{url:T(L),debugLabel:`[override] APP_URL=${L}`};if(P.PORTLESS_TAILSCALE_URL)return{url:P.PORTLESS_TAILSCALE_URL,debugLabel:`[portless:tailscale] PORTLESS_TAILSCALE_URL=${P.PORTLESS_TAILSCALE_URL}`};if(P.PORTLESS_URL)return{url:P.PORTLESS_URL,debugLabel:`[portless] PORTLESS_URL=${P.PORTLESS_URL}`};for(let A of U)if(A.detect(P)){let R=A.resolveUrl(P);if(R)return{url:T(R),debugLabel:`[provider:${A.name}] url=${R}`}}if(typeof window<"u"&&window.location)return{url:window.location.origin,debugLabel:"[browser] window.location.origin"};if(P.NODE_ENV!=="production"){let A=P.PORT||"3000";return{url:`http://localhost:${A}`,debugLabel:`[fallback] PORT=${A}`}}throw Error("which-url: Cannot detect app URL. Set APP_URL environment variable.")}function H(E){let P=I(E),L=C(P,"APP_PRODUCTION_URL");if(L)return{url:T(L),debugLabel:`[production:override] APP_PRODUCTION_URL=${L}`};for(let A of U)if(A.detect(P)){let R=A.resolveProductionUrl?.(P);if(R)return{url:T(R),debugLabel:`[production:provider:${A.name}] url=${R}`}}for(let A of U)if(A.detect(P)&&A.resolveEnv(P)==="production"){let R=A.resolveUrl(P);if(R)return{url:T(R),debugLabel:`[production:current:${A.name}] url=${R}`}}let _=C(P,"APP_URL");if(_&&P.NODE_ENV==="production")return{url:T(_),debugLabel:`[production:current] APP_URL=${_}`};return null}function M(E){let P=I(E);for(let L of U)if(L.detect(P))return L.name;return null}var X=["production","preview","local"];function c(E){let P=I(E),L=C(P,"APP_ENV");if(L&&X.includes(L))return{env:L,debugLabel:`APP_ENV=${L}`};if(P.NODE_ENV==="development")return{env:"local",debugLabel:"NODE_ENV=development"};for(let _ of U)if(_.detect(P)){let A=_.resolveEnv(P);return{env:A,debugLabel:`${_.name}:${A}`}}if(P.NODE_ENV==="production")return{env:"production",debugLabel:"NODE_ENV=production"};return{env:"local",debugLabel:"default"}}function f(E){return Object.defineProperty(E,"debug",{value:E.debug,enumerable:!1,configurable:!1}),E}function S(E){let P=E?.env,{url:L,debugLabel:_}=F(P),A=new URL(L),{env:R,debugLabel:O}=c(P),V=M(P),t=H(P),Y=t?new URL(t.url).origin:void 0,B=t?` | production=${Y} (${t.debugLabel})`:" | production=unresolved",W=`${_} | env=${R} (${O})${B}`,$={href:A.origin,origin:A.origin,hostname:A.hostname,host:A.host,protocol:A.protocol,port:A.port,productionOrigin:Y,env:R,platform:V,debug:W,isResolved:!0,isProduction:R==="production",isPreview:R==="preview",isLocal:R==="local"};return f($)}function u(E){let P=E instanceof Error?E.message:"resolution failed",L="local",_="default";try{let R=c();L=R.env,_=R.debugLabel}catch{}let A={href:"",origin:"",hostname:"",host:"",protocol:"",port:"",productionOrigin:void 0,env:L,platform:M(),debug:`[error] ${P} | env=${L} (${_})`,isResolved:!1,isProduction:L==="production",isPreview:L==="preview",isLocal:L==="local"};return f(A)}var N;try{N=S()}catch(E){N=u(E)}var{href:l,origin:g,hostname:i,host:o,protocol:s,port:n,productionOrigin:d,env:a,platform:p,debug:r,isResolved:v,isProduction:e,isPreview:EE,isLocal:PE}=N,LE=N;export{s as protocol,d as productionOrigin,n as port,p as platform,g as origin,v as isResolved,e as isProduction,EE as isPreview,PE as isLocal,l as href,i as hostname,o as host,a as env,LE as default,r as debug,S as createUrl};
package/dist/types.d.ts CHANGED
@@ -14,12 +14,14 @@ export interface WhichUrl {
14
14
  readonly protocol: string;
15
15
  /** Port string — `""` for default ports, `"3000"` for custom */
16
16
  readonly port: string;
17
- /** Canonical production origin when configured or detectable `"https://myapp.com"` */
18
- readonly productionOrigin: string;
17
+ /** Canonical production origin when configured or detectable, otherwise `undefined`. */
18
+ readonly productionOrigin: string | undefined;
19
19
  /** Current environment — `"production"`, `"preview"`, or `"local"` */
20
20
  readonly env: AppEnv;
21
21
  /** Detected hosting platform — `"vercel"`, `"netlify"`, etc. or `null` */
22
22
  readonly platform: Platform;
23
+ /** `true` when the current URL resolved successfully; `false` only on the import-safe fallback. */
24
+ readonly isResolved: boolean;
23
25
  /** `true` when running in production */
24
26
  readonly isProduction: boolean;
25
27
  /** `true` when running in a preview/staging deployment */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "which-url",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Auto-detect your app's URL across hosting providers. Zero config.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,6 +31,8 @@
31
31
  "scripts": {
32
32
  "build": "bun build src/index.ts --outdir dist --format esm --minify && tsc --emitDeclarationOnly --outDir dist",
33
33
  "test": "bun test",
34
+ "release:check": "bun test && bun run build && git diff --quiet && git diff --cached --quiet && node -e \"const { execSync } = require('node:child_process'); const version = require('./package.json').version; const tag = execSync('git describe --tags --exact-match', { encoding: 'utf8' }).trim(); const expected = 'v' + version; if (tag !== expected) throw new Error('package.json version ' + version + ' must match checked-out tag ' + tag)\"",
35
+ "release:publish": "bun run release:check && git push && git push --tags && npm publish",
34
36
  "prepublishOnly": "bun run build"
35
37
  },
36
38
  "devDependencies": {