From 673922e941d72015531d44704d67440441655381 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 12:27:40 +0000 Subject: [PATCH 1/8] fix: update connector command --- app/components/Header/ConnectorModal.vue | 4 ++-- test/nuxt/components/HeaderConnectorModal.spec.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/components/Header/ConnectorModal.vue b/app/components/Header/ConnectorModal.vue index 445161dbad..b22414df47 100644 --- a/app/components/Header/ConnectorModal.vue +++ b/app/components/Header/ConnectorModal.vue @@ -116,10 +116,10 @@ function handleDisconnect() { dir="ltr" > $ - pnpm npmx-connector + npx npmx-connector diff --git a/test/nuxt/components/HeaderConnectorModal.spec.ts b/test/nuxt/components/HeaderConnectorModal.spec.ts index bf1450998a..b2d0ce5971 100644 --- a/test/nuxt/components/HeaderConnectorModal.spec.ts +++ b/test/nuxt/components/HeaderConnectorModal.spec.ts @@ -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') }) From 64a3cf2752f2a1714e528cfbff5719ac87208d58 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 12:47:36 +0000 Subject: [PATCH 2/8] chore: update blog post link --- app/pages/blog/alpha-release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 24780f367c..56e3e3aca8 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -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.' From 12f81a9ce6061e19724ab5c62b6c4b767f033e84 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 14:17:45 +0000 Subject: [PATCH 3/8] fix: detect canary status from main branch only --- config/env.ts | 34 ++++++++++++++++----- modules/build-env.ts | 4 +-- test/unit/config/env.spec.ts | 59 +++++++++++++++++++++++------------- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/config/env.ts b/config/env.ts index 85a5fa6019..cbd852d406 100644 --- a/config/env.ts +++ b/config/env.ts @@ -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. @@ -41,14 +43,16 @@ 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. @@ -56,7 +60,7 @@ export const isCanary = * @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. @@ -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, diff --git a/modules/build-env.ts b/modules/build-env.ts index 05c35bb811..cc21d90f14 100644 --- a/modules/build-env.ts +++ b/modules/build-env.ts @@ -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) @@ -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 diff --git a/test/unit/config/env.spec.ts b/test/unit/config/env.spec.ts index 1aa4f97cf3..48be8159b8 100644 --- a/test/unit/config/env.spec.ts +++ b/test/unit/config/env.spec.ts @@ -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) => { @@ -78,8 +88,8 @@ 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) @@ -87,10 +97,10 @@ describe('getEnv', () => { 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) @@ -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) @@ -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) @@ -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/) + }) +}) From e56fdef9cb3d5c11bdc5e5c7f66d7a3f2d6fdce2 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 14:43:58 +0000 Subject: [PATCH 4/8] feat: allow crawlers to access pages --- public/robots.txt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/public/robots.txt b/public/robots.txt index 3d6069d1cc..bdd4add700 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -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 From f8dff443b213cc1bfb2c6e4edb7d21f776307abe Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 3 Mar 2026 22:48:09 +0800 Subject: [PATCH 5/8] fix: correct homepage git tag page url (#1884) --- app/components/BuildEnvironment.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/BuildEnvironment.vue b/app/components/BuildEnvironment.vue index 53eed13cfa..3f35a82b7a 100644 --- a/app/components/BuildEnvironment.vue +++ b/app/components/BuildEnvironment.vue @@ -23,7 +23,7 @@ const buildTime = computed(() => new Date(buildInfo.value.time)) · v{{ buildInfo.version }} From a7840004dfeb0e5a3711d4b6028a36331730bbb7 Mon Sep 17 00:00:00 2001 From: Alex Savelyev <91429106+alexdln@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:29:24 +0000 Subject: [PATCH 6/8] feat: add bsky to about page (#1893) --- app/assets/logos/sponsors/bluesky-light.svg | 17 +++++++++++++++++ app/assets/logos/sponsors/bluesky.svg | 17 +++++++++++++++++ app/assets/logos/sponsors/index.ts | 11 +++++++++++ 3 files changed, 45 insertions(+) create mode 100644 app/assets/logos/sponsors/bluesky-light.svg create mode 100644 app/assets/logos/sponsors/bluesky.svg diff --git a/app/assets/logos/sponsors/bluesky-light.svg b/app/assets/logos/sponsors/bluesky-light.svg new file mode 100644 index 0000000000..5fa1b8009f --- /dev/null +++ b/app/assets/logos/sponsors/bluesky-light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/logos/sponsors/bluesky.svg b/app/assets/logos/sponsors/bluesky.svg new file mode 100644 index 0000000000..9d1b84422d --- /dev/null +++ b/app/assets/logos/sponsors/bluesky.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/logos/sponsors/index.ts b/app/assets/logos/sponsors/index.ts index acec2f9af2..3ab7d951ae 100644 --- a/app/assets/logos/sponsors/index.ts +++ b/app/assets/logos/sponsors/index.ts @@ -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. @@ -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/', + }, ] From de2805196d5ec9063b01eeae84ade75dd6fa4831 Mon Sep 17 00:00:00 2001 From: Max Chang <36927158+maxchang3@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:29:44 +0800 Subject: [PATCH 7/8] fix(blog): format published date in blog post list card (#1891) --- app/components/BlogPostListCard.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/BlogPostListCard.vue b/app/components/BlogPostListCard.vue index f620cb9b7f..5fd17dc743 100644 --- a/app/components/BlogPostListCard.vue +++ b/app/components/BlogPostListCard.vue @@ -33,7 +33,9 @@ defineProps<{
- {{ published }} + + + Date: Tue, 3 Mar 2026 15:42:31 +0000 Subject: [PATCH 8/8] fix: pass uri query for bluesky comments --- nuxt.config.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 4031a9828e..2687d0ea45 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -55,7 +55,7 @@ export default defineNuxtConfig({ }, }, - devtools: { enabled: !true }, + devtools: { enabled: true }, devServer: { // Used with atproto oauth @@ -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 */,