Skip to content

Commit 5a86e64

Browse files
committed
feat: production setup
1 parent 8b70808 commit 5a86e64

32 files changed

Lines changed: 178 additions & 20 deletions

app/app.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ if (import.meta.client) {
4949
</script>
5050

5151
<template>
52+
<NuxtPwaAssets />
5253
<div class="min-h-screen flex flex-col bg-bg text-fg">
5354
<a href="#main-content" class="skip-link font-mono">{{ $t('common.skip_link') }}</a>
5455

app/components/AppHeader.vue

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const { isConnected, npmUser } = useConnector()
1616
1717
const router = useRouter()
1818
const route = useRoute()
19+
const buildInfo = useAppConfig().buildInfo
1920
2021
const searchQuery = ref('')
2122
const isSearchFocused = ref(false)
@@ -58,14 +59,18 @@ onKeyStroke(',', e => {
5859
>
5960
<!-- Start: Logo -->
6061
<div class="flex-shrink-0">
61-
<NuxtLink
62-
v-if="showLogo"
63-
to="/"
64-
:aria-label="$t('header.home')"
65-
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
66-
>
67-
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
68-
</NuxtLink>
62+
<div v-if="showLogo" class="">
63+
<NuxtLink
64+
to="/"
65+
:aria-label="$t('header.home')"
66+
dir="ltr"
67+
class="inline-flex items-center flex-gap1 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
68+
>
69+
<img :alt="$t('alt_logo')" src="/favicon.svg" width="24" height="24" />
70+
<span>npmx</span>
71+
<!-- <span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx-->
72+
</NuxtLink>
73+
</div>
6974
<!-- Spacer when logo is hidden -->
7075
<span v-else class="w-1" />
7176
</div>

app/pages/index.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ defineOgImageComponent('Default')
3232
<header class="flex-1 flex flex-col items-center justify-center text-center py-20">
3333
<!-- Animated title -->
3434
<h1
35-
class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 motion-safe:animate-fade-in motion-safe:animate-fill-both"
35+
dir="ltr"
36+
class="inline-flex items-center header-logo flex-gap1 font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 motion-safe:animate-fade-in motion-safe:animate-fill-both"
3637
>
37-
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
38+
<img :alt="$t('alt_logo')" src="/favicon.svg" width="72" height="72" class="mt-4" />
39+
<span>npmx</span>
3840
</h1>
3941

4042
<p

config/env.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Git from 'simple-git'
2+
import { isDevelopment } from 'std-env'
3+
import * as process from 'node:process'
4+
5+
export { version } from '../package.json'
6+
7+
/**
8+
* Environment variable `PULL_REQUEST` provided by Netlify.
9+
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
10+
*
11+
* Whether triggered by a GitHub PR
12+
*/
13+
export const isPR = process.env.PULL_REQUEST === 'true'
14+
15+
/**
16+
* Environment variable `BRANCH` provided by Netlify.
17+
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
18+
*
19+
* Git branch
20+
*/
21+
export const gitBranch = process.env.BRANCH
22+
23+
/**
24+
* Environment variable `CONTEXT` provided by Netlify.
25+
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#build-metadata}
26+
*
27+
* Whether triggered by PR, `deploy-preview` or `dev`.
28+
*/
29+
export const isPreview =
30+
isPR || process.env.CONTEXT === 'deploy-preview' || process.env.CONTEXT === 'dev'
31+
32+
const git = Git()
33+
export async function getGitInfo() {
34+
let branch
35+
try {
36+
branch = gitBranch || (await git.revparse(['--abbrev-ref', 'HEAD']))
37+
} catch {
38+
branch = 'unknown'
39+
}
40+
41+
let commit
42+
try {
43+
commit = await git.revparse(['HEAD'])
44+
} catch {
45+
commit = 'unknown'
46+
}
47+
48+
let shortCommit
49+
try {
50+
shortCommit = await git.revparse(['--short=7', 'HEAD'])
51+
} catch {
52+
shortCommit = 'unknown'
53+
}
54+
55+
return { branch, commit, shortCommit }
56+
}
57+
58+
export async function getEnv() {
59+
const { commit, shortCommit, branch } = await getGitInfo()
60+
const env = isDevelopment
61+
? 'dev'
62+
: isPreview
63+
? 'preview'
64+
: branch === 'main'
65+
? 'canary'
66+
: 'release'
67+
return { commit, shortCommit, branch, env } as const
68+
}

i18n/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"description": "A better browser for the npm registry. Search, browse, and explore packages with a modern interface."
66
}
77
},
8+
"version": "Version",
9+
"built_at": "Built {0}",
10+
"alt_logo": "npmx logo",
811
"tagline": "a better browser for the npm registry",
912
"non_affiliation_disclaimer": "not affiliated with npm, Inc.",
1013
"trademark_disclaimer": "npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.",

modules/build-env.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { BuildInfo } from '../shared/types'
2+
import { createResolver, defineNuxtModule } from 'nuxt/kit'
3+
import { isCI } from 'std-env'
4+
import { getEnv, version } from '../config/env'
5+
6+
const { resolve } = createResolver(import.meta.url)
7+
8+
export default defineNuxtModule({
9+
meta: {
10+
name: 'npmx:build-env',
11+
},
12+
async setup(_options, nuxt) {
13+
const { env, commit, shortCommit, branch } = await getEnv()
14+
const buildInfo: BuildInfo = {
15+
version,
16+
time: +Date.now(),
17+
commit,
18+
shortCommit,
19+
branch,
20+
env,
21+
}
22+
23+
nuxt.options.appConfig = nuxt.options.appConfig || {}
24+
nuxt.options.appConfig.env = env
25+
nuxt.options.appConfig.buildInfo = buildInfo
26+
27+
nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {}
28+
nuxt.options.nitro.virtual['#build-info'] = `export const env = ${JSON.stringify(env)}`
29+
30+
nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets || []
31+
if (env === 'dev') nuxt.options.nitro.publicAssets.unshift({ dir: resolve('../public-dev') })
32+
else if (env === 'canary' || env === 'preview' || !isCI)
33+
nuxt.options.nitro.publicAssets.unshift({ dir: resolve('../public-staging') })
34+
},
35+
})

nuxt.config.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1+
import type { BuildInfo } from './shared/types'
12
import { currentLocales } from './config/i18n'
23

34
export default defineNuxtConfig({
45
modules: [
5-
function (_, nuxt) {
6-
if (nuxt.options._prepare) {
7-
nuxt.options.pwa ||= {}
8-
nuxt.options.pwa.pwaAssets ||= {}
9-
nuxt.options.pwa.pwaAssets.disabled = true
10-
}
11-
},
126
// Workaround for Nuxt 4.3.0 regression: https://github.com/nuxt/nuxt/issues/34140
137
// shared-imports.d.ts pulls in app composables during type-checking of shared context,
148
// but the shared context doesn't have access to auto-import globals.
@@ -152,10 +146,10 @@ export default defineNuxtConfig({
152146
},
153147

154148
pwa: {
155-
// Disable service worker - only using for asset generation
149+
// Disable service worker
156150
disable: true,
157151
pwaAssets: {
158-
config: true,
152+
config: false,
159153
},
160154
manifest: {
161155
name: 'npmx',
@@ -187,3 +181,10 @@ export default defineNuxtConfig({
187181
langDir: 'locales',
188182
},
189183
})
184+
185+
declare module '@nuxt/schema' {
186+
interface AppConfig {
187+
env: BuildInfo['env']
188+
buildInfo: BuildInfo
189+
}
190+
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"license": "MIT",
44
"private": true,
55
"type": "module",
6+
"version": "0.0.0",
67
"author": {
78
"name": "Daniel Roe",
89
"email": "daniel@roe.dev",
@@ -20,6 +21,7 @@
2021
"lint:fix": "vite lint --fix && vite fmt",
2122
"generate": "nuxt generate",
2223
"npmx-connector": "pnpm --filter npmx-connector dev",
24+
"generate-pwa-icons": "pwa-assets-generator",
2325
"preview": "nuxt preview",
2426
"postinstall": "nuxt prepare && simple-git-hooks && pnpm generate:lexicons",
2527
"generate:lexicons": "lex build --lexicons lexicons --out shared/types/lexicons --clear",
@@ -55,6 +57,7 @@
5557
"sanitize-html": "^2.17.0",
5658
"semver": "^7.7.3",
5759
"shiki": "^3.21.0",
60+
"simple-git": "^3.30.0",
5861
"ufo": "^1.6.3",
5962
"valibot": "^1.2.0",
6063
"validate-npm-package-name": "^7.0.2",
@@ -70,6 +73,7 @@
7073
"@npm/types": "2.1.0",
7174
"@nuxt/test-utils": "https://pkg.pr.new/@nuxt/test-utils@1499a48",
7275
"@playwright/test": "1.57.0",
76+
"@types/node": "24.10.9",
7377
"@types/sanitize-html": "2.16.0",
7478
"@types/semver": "7.7.1",
7579
"@types/validate-npm-package-name": "4.0.2",

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
673 Bytes
Loading

0 commit comments

Comments
 (0)