Skip to content

Commit 12f81a9

Browse files
committed
fix: detect canary status from main branch only
1 parent 64a3cf2 commit 12f81a9

File tree

3 files changed

+67
-30
lines changed

3 files changed

+67
-30
lines changed

config/env.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import Git from 'simple-git'
44
import * as process from 'node:process'
55

6-
export { version } from '../package.json'
6+
import { version as packageVersion } from '../package.json'
7+
8+
export { packageVersion as version }
79

810
/**
911
* Environment variable `PULL_REQUEST` provided by Netlify.
@@ -41,22 +43,24 @@ export const gitBranch = process.env.BRANCH || process.env.VERCEL_GIT_COMMIT_REF
4143
/**
4244
* Whether this is the canary environment (main.npmx.dev).
4345
*
44-
* Detected via the custom Vercel environment (`VERCEL_ENV === 'canary'`),
45-
* or as a fallback, a production deploy from the `main` branch.
46+
* Detected as any non-PR Vercel deploy from the `main` branch
47+
* (which may receive `VERCEL_ENV === 'production'` or `'preview'`
48+
* depending on the project's production branch configuration).
4649
*
4750
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_ENV}
4851
*/
4952
export const isCanary =
50-
process.env.VERCEL_ENV === 'canary' ||
51-
(process.env.VERCEL_ENV === 'production' && gitBranch === 'main')
53+
(process.env.VERCEL_ENV === 'production' || process.env.VERCEL_ENV === 'preview') &&
54+
gitBranch === 'main' &&
55+
!isPR
5256

5357
/**
5458
* Environment variable `CONTEXT` provided by Netlify.
5559
* `dev`, `production`, `deploy-preview`, `branch-deploy`, `preview-server`, or a branch name
5660
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#build-metadata}
5761
*
5862
* Environment variable `VERCEL_ENV` provided by Vercel.
59-
* `production`, `preview`, `development`, or a custom environment name (e.g. `canary`).
63+
* `production`, `preview`, or `development`.
6064
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_ENV}
6165
*
6266
* Whether this is some sort of preview environment.
@@ -152,12 +156,28 @@ export async function getFileLastUpdated(path: string) {
152156
}
153157
}
154158

159+
/**
160+
* Resolves the current version from git tags, falling back to `package.json`.
161+
*
162+
* Uses `git describe --tags --abbrev=0 --match 'v*'` to find the most recent
163+
* reachable release tag (e.g. `v0.1.0` -> `0.1.0`).
164+
*/
165+
export async function getVersion() {
166+
try {
167+
const tag = (await git.raw(['describe', '--tags', '--abbrev=0', '--match', 'v*'])).trim()
168+
return tag.replace(/^v/, '')
169+
} catch {
170+
return packageVersion
171+
}
172+
}
173+
155174
export async function getEnv(isDevelopment: boolean) {
156-
const { commit, shortCommit, branch } = await getGitInfo()
175+
const [{ commit, shortCommit, branch }, version] = await Promise.all([getGitInfo(), getVersion()])
157176
const env = isDevelopment ? 'dev' : isCanary ? 'canary' : isPreview ? 'preview' : 'release'
158177
const previewUrl = getPreviewUrl()
159178
const productionUrl = getProductionUrl()
160179
return {
180+
version,
161181
commit,
162182
shortCommit,
163183
branch,

modules/build-env.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { BuildInfo, EnvType } from '../shared/types'
22
import { createResolver, defineNuxtModule } from 'nuxt/kit'
33
import { isCI } from 'std-env'
4-
import { getEnv, getFileLastUpdated, version } from '../config/env'
4+
import { getEnv, getFileLastUpdated } from '../config/env'
55

66
const { resolve } = createResolver(import.meta.url)
77

@@ -26,7 +26,7 @@ export default defineNuxtModule({
2626
prNumber: null,
2727
} satisfies BuildInfo
2828
} else {
29-
const [{ env: useEnv, commit, shortCommit, branch, prNumber }, privacyPolicyDate] =
29+
const [{ env: useEnv, version, commit, shortCommit, branch, prNumber }, privacyPolicyDate] =
3030
await Promise.all([getEnv(nuxt.options.dev), getFileLastUpdated('app/pages/privacy.vue')])
3131
env = useEnv
3232
nuxt.options.appConfig.env = useEnv

test/unit/config/env.spec.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,34 @@ describe('isCanary', () => {
2727
vi.unstubAllEnvs()
2828
})
2929

30-
it('returns true when VERCEL_ENV is "canary"', async () => {
31-
vi.stubEnv('VERCEL_ENV', 'canary')
30+
it('returns true when VERCEL_ENV is "production" and branch is "main"', async () => {
31+
vi.stubEnv('VERCEL_ENV', 'production')
32+
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
3233
const { isCanary } = await import('../../../config/env')
3334

3435
expect(isCanary).toBe(true)
3536
})
3637

37-
it('returns true when VERCEL_ENV is "production" and branch is "main"', async () => {
38-
vi.stubEnv('VERCEL_ENV', 'production')
38+
it('returns true when VERCEL_ENV is "preview" and branch is "main" (non-PR)', async () => {
39+
vi.stubEnv('VERCEL_ENV', 'preview')
3940
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
4041
const { isCanary } = await import('../../../config/env')
4142

4243
expect(isCanary).toBe(true)
4344
})
4445

46+
it('returns false when VERCEL_ENV is "preview", branch is "main", but is a PR', async () => {
47+
vi.stubEnv('VERCEL_ENV', 'preview')
48+
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
49+
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '123')
50+
const { isCanary } = await import('../../../config/env')
51+
52+
expect(isCanary).toBe(false)
53+
})
54+
4555
it.each([
4656
['production (non-main branch)', 'production', 'v1.0.0'],
47-
['preview', 'preview', undefined],
57+
['preview (non-main branch)', 'preview', 'feat/foo'],
4858
['development', 'development', undefined],
4959
['unset', undefined, undefined],
5060
])('returns false when VERCEL_ENV is %s', async (_label, value, branch) => {
@@ -78,19 +88,19 @@ describe('getEnv', () => {
7888
expect(result.env).toBe('dev')
7989
})
8090

81-
it('returns "canary" when VERCEL_ENV is "canary"', async () => {
82-
vi.stubEnv('VERCEL_ENV', 'canary')
91+
it('returns "canary" for Vercel preview deploys from main branch (non-PR)', async () => {
92+
vi.stubEnv('VERCEL_ENV', 'preview')
8393
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
8494
const { getEnv } = await import('../../../config/env')
8595
const result = await getEnv(false)
8696

8797
expect(result.env).toBe('canary')
8898
})
8999

90-
it('returns "preview" for Vercel preview deploys', async () => {
100+
it('returns "preview" for Vercel preview PR deploys', async () => {
91101
vi.stubEnv('VERCEL_ENV', 'preview')
92102
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '123')
93-
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
103+
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'feat/foo')
94104
const { getEnv } = await import('../../../config/env')
95105
const result = await getEnv(false)
96106

@@ -125,18 +135,9 @@ describe('getEnv', () => {
125135
expect(result.env).toBe('release')
126136
})
127137

128-
it('prioritises "canary" over "preview" when VERCEL_ENV is "canary" and PR is open', async () => {
129-
vi.stubEnv('VERCEL_ENV', 'canary')
130-
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '789')
131-
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
132-
const { getEnv } = await import('../../../config/env')
133-
const result = await getEnv(false)
134-
135-
expect(result.env).toBe('canary')
136-
})
137-
138138
it('prioritises "dev" over "canary" in development mode', async () => {
139-
vi.stubEnv('VERCEL_ENV', 'canary')
139+
vi.stubEnv('VERCEL_ENV', 'preview')
140+
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
140141
const { getEnv } = await import('../../../config/env')
141142
const result = await getEnv(true)
142143

@@ -169,7 +170,6 @@ describe('getPreviewUrl', () => {
169170
it.each([
170171
['Netlify production', { CONTEXT: 'production', URL: 'https://prod.example.com' }],
171172
['Vercel production', { VERCEL_ENV: 'production', NUXT_ENV_VERCEL_URL: 'prod.example.com' }],
172-
['Vercel canary', { VERCEL_ENV: 'canary', NUXT_ENV_VERCEL_URL: 'main.example.com' }],
173173
])('%s environment returns `undefined`', async (_name, envVars) => {
174174
for (const [key, value] of Object.entries(envVars)) {
175175
vi.stubEnv(key, value)
@@ -308,3 +308,20 @@ describe('getProductionUrl', () => {
308308
expect(getProductionUrl()).toBe(expectedUrl)
309309
})
310310
})
311+
312+
describe('getVersion', () => {
313+
it('returns package.json version when no git tags are reachable', async () => {
314+
const { getVersion, version } = await import('../../../config/env')
315+
const result = await getVersion()
316+
317+
// In test environments without reachable tags, falls back to package.json
318+
expect(result).toBe(version)
319+
})
320+
321+
it('strips the leading "v" prefix from the tag', async () => {
322+
const { getVersion } = await import('../../../config/env')
323+
const result = await getVersion()
324+
325+
expect(result).not.toMatch(/^v/)
326+
})
327+
})

0 commit comments

Comments
 (0)