Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* text eol=lf
* text=auto eol=lf
1 change: 1 addition & 0 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ if (import.meta.client) {

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

<AppHeader :show-logo="!isHomepage" />
Expand Down
29 changes: 21 additions & 8 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ withDefaults(
const { isConnected, npmUser } = useConnector()

const router = useRouter()
const route = useRoute()
const buildInfo = useAppConfig().buildInfo

const searchQuery = ref('')
const isSearchFocused = ref(false)

const showFullSearch = ref(false)

Expand All @@ -36,14 +41,22 @@ onKeyStroke(',', e => {
>
<!-- Start: Logo -->
<div :class="{ 'hidden sm:block': showFullSearch }" class="flex-shrink-0">
<NuxtLink
v-if="showLogo"
to="/"
:aria-label="$t('header.home')"
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
>
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
</NuxtLink>
<div v-if="showLogo">
<NuxtLink
to="/"
:aria-label="$t('header.home')"
dir="ltr"
class="inline-flex items-center gap-2 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
>
<img
aria-hidden="true"
:alt="$t('alt_logo')"
src="/logo.svg"
class="w-8 h-8 rounded-lg"
/>
<span>npmx</span>
</NuxtLink>
</div>
<!-- Spacer when logo is hidden -->
<span v-else class="w-1" />
</div>
Expand Down
42 changes: 40 additions & 2 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { debounce } from 'perfect-debounce'

const router = useRouter()
const buildInfo = useAppConfig().buildInfo

const searchQuery = ref('')
const searchInputRef = useTemplateRef('searchInputRef')
const { focused: isSearchFocused } = useFocus(searchInputRef)
Expand Down Expand Up @@ -34,11 +36,47 @@ defineOgImageComponent('Default')
<header class="flex-1 flex flex-col items-center justify-center text-center py-20">
<!-- Animated title -->
<h1
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"
dir="ltr"
class="flex items-center justify-center gap-2 header-logo 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"
>
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
<img
aria-hidden="true"
:alt="$t('alt_logo')"
src="/logo.svg"
class="w-12 h-12 sm:w-20 sm:h-20 md:w-24 md:h-24 rounded-2xl sm:rounded-3xl"
/>
<span class="pb-4">npmx</span>
</h1>

<!-- Build info badge (moved below title) -->
<div
class="mb-8 font-mono text-xs text-fg-muted flex items-center justify-center gap-2 motion-safe:animate-fade-in motion-safe:animate-fill-both"
style="animation-delay: 0.05s"
>
<NuxtLink
v-if="buildInfo.env === 'release'"
external
:href="`https://github.com/npmx-dev/npmx.dev/tag/v${buildInfo.version}`"
target="_blank"
class="hover:text-fg transition-colors"
>
v{{ buildInfo.version }}
</NuxtLink>
<span v-else class="uppercase tracking-wider">{{ buildInfo.env }}</span>

<template v-if="buildInfo.commit && buildInfo.branch !== 'release'">
<span>&middot;</span>
<NuxtLink
external
:href="`https://github.com/npmx-dev/npmx.dev/commit/${buildInfo.commit}`"
target="_blank"
class="hover:text-fg transition-colors"
>
{{ buildInfo.shortCommit }}
</NuxtLink>
</template>
</div>

<p
class="text-fg-muted text-lg sm:text-xl max-w-md mb-12 motion-safe:animate-slide-up motion-safe:animate-fill-both"
style="animation-delay: 0.1s"
Expand Down
86 changes: 86 additions & 0 deletions config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Git from 'simple-git'
import * as process from 'node:process'

export { version } from '../package.json'

/**
* Environment variable `PULL_REQUEST` provided by Netlify.
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
*
* Environment variable `VERCEL_GIT_PULL_REQUEST_ID` provided by Vercel.
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_GIT_PULL_REQUEST_ID}
*
* Whether triggered by a GitHub PR
*/
export const isPR = process.env.PULL_REQUEST === 'true' || !!process.env.VERCEL_GIT_PULL_REQUEST_ID

/**
* Environment variable `BRANCH` provided by Netlify.
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
*
* Environment variable `VERCEL_GIT_COMMIT_REF` provided by Vercel.
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_GIT_COMMIT_REF}
*
* Git branch
*/
export const gitBranch = process.env.BRANCH || process.env.VERCEL_GIT_COMMIT_REF

/**
* Environment variable `CONTEXT` provided by Netlify.
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#build-metadata}
*
* Environment variable `VERCEL_ENV` provided by Vercel.
* @see {@link https://vercel.com/docs/environment-variables/system-environment-variables#VERCEL_ENV}
*
* Whether triggered by PR, `deploy-preview` or `dev`.
*/
export const isPreview =
isPR ||
process.env.CONTEXT === 'deploy-preview' ||
process.env.CONTEXT === 'dev' ||
process.env.VERCEL_ENV === 'preview' ||
process.env.VERCEL_ENV === 'development'

const git = Git()
export async function getGitInfo() {
let branch
try {
branch = gitBranch || (await git.revparse(['--abbrev-ref', 'HEAD']))
} catch {
branch = 'unknown'
}

let commit
try {
// Netlify: COMMIT_REF, Vercel: VERCEL_GIT_COMMIT_SHA
commit =
process.env.COMMIT_REF || process.env.VERCEL_GIT_COMMIT_SHA || (await git.revparse(['HEAD']))
} catch {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I cleanup netlify entries?

commit = 'unknown'
}

let shortCommit
try {
if (commit && commit !== 'unknown') {
shortCommit = commit.slice(0, 7)
} else {
shortCommit = await git.revparse(['--short=7', 'HEAD'])
}
} catch {
shortCommit = 'unknown'
}

return { branch, commit, shortCommit }
}

export async function getEnv(isDevelopment: boolean) {
const { commit, shortCommit, branch } = await getGitInfo()
const env = isDevelopment
? 'dev'
: isPreview
? 'preview'
: branch === 'main'
? 'canary'
: 'release'
return { commit, shortCommit, branch, env } as const
}
3 changes: 3 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"description": "A better browser for the npm registry. Search, browse, and explore packages with a modern interface."
}
},
"version": "Version",
"built_at": "Built {0}",
"alt_logo": "npmx logo",
"tagline": "a better browser for the npm registry",
"non_affiliation_disclaimer": "not affiliated with npm, Inc.",
"trademark_disclaimer": "npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.",
Expand Down
3 changes: 3 additions & 0 deletions lunaria/files/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"description": "A better browser for the npm registry. Search, browse, and explore packages with a modern interface."
}
},
"version": "Version",
"built_at": "Built {0}",
"alt_logo": "npmx logo",
"tagline": "a better browser for the npm registry",
"non_affiliation_disclaimer": "not affiliated with npm, Inc.",
"trademark_disclaimer": "npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.",
Expand Down
35 changes: 35 additions & 0 deletions modules/build-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BuildInfo } from '../shared/types'
import { createResolver, defineNuxtModule } from 'nuxt/kit'
import { isCI } from 'std-env'
import { getEnv, version } from '../config/env'

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

export default defineNuxtModule({
meta: {
name: 'npmx:build-env',
},
async setup(_options, nuxt) {
const { env, commit, shortCommit, branch } = await getEnv(nuxt.options.dev)
const buildInfo: BuildInfo = {
version,
time: +Date.now(),
commit,
shortCommit,
branch,
env,
}

nuxt.options.appConfig = nuxt.options.appConfig || {}
nuxt.options.appConfig.env = env
nuxt.options.appConfig.buildInfo = buildInfo

nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {}
nuxt.options.nitro.virtual['#build-info'] = `export const env = ${JSON.stringify(env)}`

nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets || []
if (env === 'dev') nuxt.options.nitro.publicAssets.unshift({ dir: resolve('../public-dev') })
else if (env === 'canary' || env === 'preview' || !isCI)
nuxt.options.nitro.publicAssets.unshift({ dir: resolve('../public-staging') })
},
})
43 changes: 34 additions & 9 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import type { BuildInfo } from './shared/types'
import { currentLocales } from './config/i18n'

export default defineNuxtConfig({
modules: [
function (_, nuxt) {
if (nuxt.options._prepare) {
nuxt.options.pwa ||= {}
nuxt.options.pwa.pwaAssets ||= {}
nuxt.options.pwa.pwaAssets.disabled = true
}
},
// Workaround for Nuxt 4.3.0 regression: https://github.com/nuxt/nuxt/issues/34140
// shared-imports.d.ts pulls in app composables during type-checking of shared context,
// but the shared context doesn't have access to auto-import globals.
Expand Down Expand Up @@ -161,17 +155,41 @@ export default defineNuxtConfig({
},

pwa: {
// Disable service worker - only using for asset generation
// Disable service worker
disable: true,
pwaAssets: {
config: true,
config: false,
},
manifest: {
name: 'npmx',
short_name: 'npmx',
description: 'A fast, modern browser for the npm registry',
theme_color: '#0a0a0a',
background_color: '#0a0a0a',
icons: [
{
src: 'pwa-64x64.png',
sizes: '64x64',
type: 'image/png',
},
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any',
},
{
src: 'maskable-icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
},

Expand All @@ -196,3 +214,10 @@ export default defineNuxtConfig({
langDir: 'locales',
},
})

declare module '@nuxt/schema' {
interface AppConfig {
env: BuildInfo['env']
buildInfo: BuildInfo
}
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"license": "MIT",
"private": true,
"type": "module",
"version": "0.0.0",
"author": {
"name": "Daniel Roe",
"email": "daniel@roe.dev",
Expand All @@ -20,6 +21,7 @@
"lint:fix": "vite lint --fix && vite fmt",
"generate": "nuxt generate",
"npmx-connector": "pnpm --filter npmx-connector dev",
"generate-pwa-icons": "pwa-assets-generator",
"preview": "nuxt preview",
"postinstall": "nuxt prepare && simple-git-hooks && pnpm generate:lexicons",
"generate:lexicons": "lex build --lexicons lexicons --out shared/types/lexicons --clear",
Expand Down Expand Up @@ -57,6 +59,7 @@
"sanitize-html": "2.17.0",
"semver": "7.7.3",
"shiki": "3.21.0",
"simple-git": "3.30.0",
"ufo": "1.6.3",
"valibot": "1.2.0",
"validate-npm-package-name": "7.0.2",
Expand All @@ -72,6 +75,7 @@
"@npm/types": "2.1.0",
"@nuxt/test-utils": "https://pkg.pr.new/@nuxt/test-utils@1499a48",
"@playwright/test": "1.58.0",
"@types/node": "24.10.9",
"@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1",
"@types/validate-npm-package-name": "4.0.2",
Expand All @@ -95,6 +99,7 @@
"typescript": "5.9.3",
"unocss": "66.6.0",
"unplugin-vue-router": "0.19.2",
"vite-plugin-pwa": "1.2.0",
"vite-plus": "0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
"vitest": "npm:@voidzero-dev/vite-plus-test@0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
"vitest-environment-nuxt": "1.0.1",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public-dev/apple-touch-icon-180x180.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public-dev/favicon.ico
Binary file not shown.
Loading