Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/assets/logos/sponsors/bluesky-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions app/assets/logos/sponsors/bluesky.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions app/assets/logos/sponsors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import LogoVlt from './vlt.svg'
import LogoVltLight from './vlt-light.svg'
import LogoNetlify from './netlify.svg'
import LogoNetlifyLight from './netlify-light.svg'
import LogoBluesky from './bluesky.svg'
import LogoBlueskyLight from './bluesky-light.svg'

// The list is used on the about page. To add, simply upload the logos nearby and add an entry here. Prefer SVGs.
// For logo src, specify a string or object with the light and dark theme variants.
Expand Down Expand Up @@ -51,4 +53,13 @@ export const SPONSORS = [
normalisingIndent: '0.125rem',
url: 'https://netlify.com/',
},
{
name: 'Bluesky',
logo: {
dark: LogoBluesky,
light: LogoBlueskyLight,
},
normalisingIndent: '0.625rem',
url: 'https://bsky.app/',
},
]
4 changes: 3 additions & 1 deletion app/components/BlogPostListCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ defineProps<{
<!-- Text Content -->
<div class="flex-1 min-w-0 text-start gap-2">
<div class="flex items-center gap-2">
<span class="text-xs text-fg-muted font-mono">{{ published }}</span>
<span class="text-xs text-fg-muted font-mono">
<DateTime :datetime="published" year="numeric" month="short" day="numeric" />
</span>
<span
v-if="draft"
class="text-xs px-1.5 py-0.5 rounded badge-orange font-sans font-medium"
Expand Down
2 changes: 1 addition & 1 deletion app/components/BuildEnvironment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const buildTime = computed(() => new Date(buildInfo.value.time))
<span>&middot;</span>
<LinkBase
v-if="buildInfo.env === 'release'"
:to="`https://github.com/npmx-dev/npmx.dev/tag/v${buildInfo.version}`"
:to="`https://github.com/npmx-dev/npmx.dev/releases/tag/v${buildInfo.version}`"
>
v{{ buildInfo.version }}
</LinkBase>
Expand Down
4 changes: 2 additions & 2 deletions app/components/Header/ConnectorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ function handleDisconnect() {
dir="ltr"
>
<span class="text-fg-subtle">$</span>
<span class="text-fg-subtle ms-2">pnpm npmx-connector</span>
<span class="text-fg-subtle ms-2">npx npmx-connector</span>
<ButtonBase
:aria-label="copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')"
@click="copy('pnpm npmx-connector')"
@click="copy('npx npmx-connector')"
class="ms-auto"
:classicon="copied ? 'i-lucide:check text-green-500' : 'i-lucide:copy'"
/>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/blog/alpha-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ headline="Read more from the community"
description: 'Getting involved in open source doesn\'t have to be scary! Understand how to find a great project and make your first contribution in this guide from Salma.'
},
{
url: 'https://roe.dev/blog/a-virtuous-cycle',
url: 'https://roe.dev/blog/virtuous-circle',
title: 'A Virtuous Circle',
authorHandle: 'danielroe.dev',
description: 'There\'s a reason why building npmx has been such a blast so far, and it\'s one of the most powerful patterns in open source software development. It\'s also why \'the 10x developer\' is an incredibly dangerous myth.'
Expand Down
34 changes: 27 additions & 7 deletions config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import Git from 'simple-git'
import * as process from 'node:process'

export { version } from '../package.json'
import { version as packageVersion } from '../package.json'

export { packageVersion as version }

/**
* Environment variable `PULL_REQUEST` provided by Netlify.
Expand Down Expand Up @@ -41,22 +43,24 @@ export const gitBranch = process.env.BRANCH || process.env.VERCEL_GIT_COMMIT_REF
/**
* Whether this is the canary environment (main.npmx.dev).
*
* Detected via the custom Vercel environment (`VERCEL_ENV === 'canary'`),
* or as a fallback, a production deploy from the `main` branch.
* Detected as any non-PR Vercel deploy from the `main` branch
* (which may receive `VERCEL_ENV === 'production'` or `'preview'`
* depending on the project's production branch configuration).
*
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_ENV}
*/
export const isCanary =
process.env.VERCEL_ENV === 'canary' ||
(process.env.VERCEL_ENV === 'production' && gitBranch === 'main')
(process.env.VERCEL_ENV === 'production' || process.env.VERCEL_ENV === 'preview') &&
gitBranch === 'main' &&
!isPR

/**
* Environment variable `CONTEXT` provided by Netlify.
* `dev`, `production`, `deploy-preview`, `branch-deploy`, `preview-server`, or a branch name
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#build-metadata}
*
* Environment variable `VERCEL_ENV` provided by Vercel.
* `production`, `preview`, `development`, or a custom environment name (e.g. `canary`).
* `production`, `preview`, or `development`.
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_ENV}
*
* Whether this is some sort of preview environment.
Expand Down Expand Up @@ -152,12 +156,28 @@ export async function getFileLastUpdated(path: string) {
}
}

/**
* Resolves the current version from git tags, falling back to `package.json`.
*
* Uses `git describe --tags --abbrev=0 --match 'v*'` to find the most recent
* reachable release tag (e.g. `v0.1.0` -> `0.1.0`).
*/
export async function getVersion() {
try {
const tag = (await git.raw(['describe', '--tags', '--abbrev=0', '--match', 'v*'])).trim()
return tag.replace(/^v/, '')
} catch {
return packageVersion
}
}

export async function getEnv(isDevelopment: boolean) {
const { commit, shortCommit, branch } = await getGitInfo()
const [{ commit, shortCommit, branch }, version] = await Promise.all([getGitInfo(), getVersion()])
const env = isDevelopment ? 'dev' : isCanary ? 'canary' : isPreview ? 'preview' : 'release'
const previewUrl = getPreviewUrl()
const productionUrl = getProductionUrl()
return {
version,
commit,
shortCommit,
branch,
Expand Down
4 changes: 2 additions & 2 deletions modules/build-env.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BuildInfo, EnvType } from '../shared/types'
import { createResolver, defineNuxtModule } from 'nuxt/kit'
import { isCI } from 'std-env'
import { getEnv, getFileLastUpdated, version } from '../config/env'
import { getEnv, getFileLastUpdated } from '../config/env'

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

Expand All @@ -26,7 +26,7 @@ export default defineNuxtModule({
prNumber: null,
} satisfies BuildInfo
} else {
const [{ env: useEnv, commit, shortCommit, branch, prNumber }, privacyPolicyDate] =
const [{ env: useEnv, version, commit, shortCommit, branch, prNumber }, privacyPolicyDate] =
await Promise.all([getEnv(nuxt.options.dev), getFileLastUpdated('app/pages/privacy.vue')])
env = useEnv
nuxt.options.appConfig.env = useEnv
Expand Down
10 changes: 9 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default defineNuxtConfig({
},
},

devtools: { enabled: !true },
devtools: { enabled: true },

devServer: {
// Used with atproto oauth
Expand Down Expand Up @@ -136,6 +136,14 @@ export default defineNuxtConfig({
// never cache
'/api/auth/**': { isr: false, cache: false },
'/api/social/**': { isr: false, cache: false },
'/api/atproto/bluesky-comments': {
isr: {
expiration: 60 * 60 /* one hour */,
passQuery: true,
allowQuery: ['uri'],
},
cache: { maxAge: 3600 },
},
'/api/atproto/bluesky-author-profiles': {
isr: {
expiration: 60 * 60 /* one hour */,
Expand Down
10 changes: 1 addition & 9 deletions public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# todo remove
User-agent: *
Allow: /$
Allow: /about$
Allow: /privacy$
Allow: /__og-image__/*
Disallow: /

# User-agent: *
# Allow: /
Allow: /

# Search pages: infinite query-param combinations
Disallow: /search
Expand Down
1 change: 0 additions & 1 deletion test/nuxt/components/HeaderConnectorModal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,6 @@ describe('HeaderConnectorModal', () => {

it('shows the CLI command to run', async () => {
const dialog = await mountAndOpen()
// The command is now "pnpm npmx-connector"
expect(dialog?.textContent).toContain('npmx-connector')
})

Expand Down
59 changes: 38 additions & 21 deletions test/unit/config/env.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,34 @@ describe('isCanary', () => {
vi.unstubAllEnvs()
})

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

expect(isCanary).toBe(true)
})

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

expect(isCanary).toBe(true)
})

it('returns false when VERCEL_ENV is "preview", branch is "main", but is a PR', async () => {
vi.stubEnv('VERCEL_ENV', 'preview')
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '123')
const { isCanary } = await import('../../../config/env')

expect(isCanary).toBe(false)
})

it.each([
['production (non-main branch)', 'production', 'v1.0.0'],
['preview', 'preview', undefined],
['preview (non-main branch)', 'preview', 'feat/foo'],
['development', 'development', undefined],
['unset', undefined, undefined],
])('returns false when VERCEL_ENV is %s', async (_label, value, branch) => {
Expand Down Expand Up @@ -78,19 +88,19 @@ describe('getEnv', () => {
expect(result.env).toBe('dev')
})

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

expect(result.env).toBe('canary')
})

it('returns "preview" for Vercel preview deploys', async () => {
it('returns "preview" for Vercel preview PR deploys', async () => {
vi.stubEnv('VERCEL_ENV', 'preview')
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '123')
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'feat/foo')
const { getEnv } = await import('../../../config/env')
const result = await getEnv(false)

Expand Down Expand Up @@ -125,18 +135,9 @@ describe('getEnv', () => {
expect(result.env).toBe('release')
})

it('prioritises "canary" over "preview" when VERCEL_ENV is "canary" and PR is open', async () => {
vi.stubEnv('VERCEL_ENV', 'canary')
vi.stubEnv('VERCEL_GIT_PULL_REQUEST_ID', '789')
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
const { getEnv } = await import('../../../config/env')
const result = await getEnv(false)

expect(result.env).toBe('canary')
})

it('prioritises "dev" over "canary" in development mode', async () => {
vi.stubEnv('VERCEL_ENV', 'canary')
vi.stubEnv('VERCEL_ENV', 'preview')
vi.stubEnv('VERCEL_GIT_COMMIT_REF', 'main')
const { getEnv } = await import('../../../config/env')
const result = await getEnv(true)

Expand Down Expand Up @@ -169,7 +170,6 @@ describe('getPreviewUrl', () => {
it.each([
['Netlify production', { CONTEXT: 'production', URL: 'https://prod.example.com' }],
['Vercel production', { VERCEL_ENV: 'production', NUXT_ENV_VERCEL_URL: 'prod.example.com' }],
['Vercel canary', { VERCEL_ENV: 'canary', NUXT_ENV_VERCEL_URL: 'main.example.com' }],
])('%s environment returns `undefined`', async (_name, envVars) => {
for (const [key, value] of Object.entries(envVars)) {
vi.stubEnv(key, value)
Expand Down Expand Up @@ -308,3 +308,20 @@ describe('getProductionUrl', () => {
expect(getProductionUrl()).toBe(expectedUrl)
})
})

describe('getVersion', () => {
it('returns package.json version when no git tags are reachable', async () => {
const { getVersion, version } = await import('../../../config/env')
const result = await getVersion()

// In test environments without reachable tags, falls back to package.json
expect(result).toBe(version)
})

it('strips the leading "v" prefix from the tag', async () => {
const { getVersion } = await import('../../../config/env')
const result = await getVersion()

expect(result).not.toMatch(/^v/)
})
})
Loading