diff --git a/.storybook/main.ts b/.storybook/main.ts index 4bbea4fce3..ddca2df90c 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,6 +1,4 @@ import type { StorybookConfig } from '@storybook-vue/nuxt' -import { readFileSync } from 'node:fs' -import { resolve } from 'node:path' const config = { stories: [ @@ -23,115 +21,6 @@ const config = { async viteFinal(newConfig) { newConfig.plugins ??= [] - // Fix: Nuxt's generated #internal/nuxt/paths (/paths.mjs) calls - // getAppConfig() during module evaluation (#build/fetch.mjs → baseURL()). Without an - // SSR payload, Nuxt's client useRuntimeConfig shim returns undefined and `.app` - // throws, so fall back to an empty config. - newConfig.plugins.unshift({ - name: 'storybook-nuxt-paths-app-config', - enforce: 'pre', - transform(code, id) { - if (!/paths\.mjs(?:$|\?)/.test(id)) return - return code.replace( - 'const getAppConfig = () => useRuntimeConfig().app', - 'const getAppConfig = () => useRuntimeConfig()?.app ?? {}', - ) - }, - }) - - // Fix: nuxt:components:imports-alias relies on internal Nuxt state that is - // cleaned up after nuxt.close() in @storybook-vue/nuxt's loadNuxtViteConfig. - // When that state is gone, `import X from '#components'` is left unresolved - // and Vite 8 falls through to package-subpath resolution, which fails with - // "Missing '#components' specifier in 'nuxt' package". - // This plugin intercepts #components first and serves a virtual module built - // from the components.d.ts written during the same Nuxt boot. - // Resolve the Nuxt build dir from Vite's alias map, which can be either a - // plain-object (Record) or Vite's resolved array form - // (readonly Alias[] where find is string | RegExp). We must handle both - // without casting to Record, which would be unsound for the - // array form. - const aliases = newConfig.resolve?.alias - const buildDir = (() => { - if (!aliases) return undefined - if (Array.isArray(aliases)) { - const entry = aliases.find(a => a.find === '#build') - return typeof entry?.replacement === 'string' ? entry.replacement : undefined - } - const value = (aliases as Record)['#build'] - return typeof value === 'string' ? value : undefined - })() - newConfig.plugins.unshift({ - name: 'storybook-nuxt-components', - enforce: 'pre', - resolveId(id) { - if (id === '#components') return '\0virtual:#components' - return null - }, - load(id) { - if (id !== '\0virtual:#components') return - if (!buildDir) { - throw new Error('[storybook-nuxt-components] Could not resolve the `#build` alias.') - } - const dtsPath = resolve(buildDir, 'components.d.ts') - // Wire the generated declaration file into Vite's file-watch graph so - // that the virtual module is invalidated when Nuxt regenerates it. - this.addWatchFile(dtsPath) - const dts = readFileSync(dtsPath, 'utf-8') - const lines: string[] = [] - // Match only the direct `typeof import("…").default` form. - // Lazy/island wrappers (LazyComponent, IslandComponent) are - // excluded intentionally — Storybook only needs the concrete type. - // The format has been stable across all Nuxt 3 releases. - const re = - /^export const (\w+): typeof import\("([^"]+)"\)(?:\.default|\[['"]default['"]\])\s*;?$/gm - let match: RegExpExecArray | null - while ((match = re.exec(dts)) !== null) { - const [, name, rel] = match - if (!name || !rel) continue - const abs = resolve(buildDir, rel).replaceAll('\\', '/') - lines.push(`export { default as ${name} } from ${JSON.stringify(abs)}`) - } - if (lines.length === 0) { - throw new Error( - `[storybook-nuxt-components] No component exports were found in ${dtsPath}.`, - ) - } - return lines.join('\n') - }, - }) - - // Bridge compatibility between Storybook v10 core and v9 @storybook-vue/nuxt - // v10 expects module federation globals that v9 doesn't provide - newConfig.plugins.push({ - name: 'storybook-v10-compat', - transformIndexHtml: { - order: 'pre', - handler() { - return [ - { - tag: 'script', - injectTo: 'head-prepend' as const, - children: [ - '// Minimal shims for Storybook v10 module federation system', - '// These will be replaced when Storybook runtime loads', - 'window.__STORYBOOK_MODULE_GLOBAL__ = { global: window };', - 'window.__STORYBOOK_MODULE_CLIENT_LOGGER__ = {', - " deprecate: console.warn.bind(console, '[deprecated]'),", - ' once: console.log.bind(console),', - ' logger: console', - '};', - 'window.__STORYBOOK_MODULE_CHANNELS__ = {', - ' Channel: class { on() {} off() {} emit() {} once() {} },', - ' createBrowserChannel: () => new window.__STORYBOOK_MODULE_CHANNELS__.Channel()', - '};', - ].join('\n'), - }, - ] - }, - }, - }) - newConfig.plugins.push({ name: 'ignore-internals', transform(_, id) { diff --git a/app/components/Changelog/Skeleton.vue b/app/components/Changelog/Skeleton.vue index 1606140393..d14dc799f6 100644 --- a/app/components/Changelog/Skeleton.vue +++ b/app/components/Changelog/Skeleton.vue @@ -1,11 +1,13 @@ diff --git a/app/components/Chart/SplitSparkline.vue b/app/components/Chart/SplitSparkline.vue index 9cb6761efd..9420ea691f 100644 --- a/app/components/Chart/SplitSparkline.vue +++ b/app/components/Chart/SplitSparkline.vue @@ -18,7 +18,7 @@ const props = defineProps<{ dataset?: Array< VueUiXyDatasetItem & { color?: string - series: number[] + series: Array dashIndices?: number[] } > @@ -29,7 +29,6 @@ const props = defineProps<{ day: string } showLastDatapointEstimation: boolean - nullifyZeroValues?: boolean largePackageSeries?: boolean[] granularity: ChartTimeGranularity }>() @@ -94,12 +93,13 @@ function formatDataLabel(seriesIndex: number, value: number): string { } } + if (rawLastValue == null) return $t('package.trends.no_data_short') return numberFormatter.value.format(value) } // Hover | kbd interaction: show the actual value at the hovered index const rawValue = series[selectedIndex.value] - if (isLargeSeries(seriesIndex) && isMissingDownloadValue(rawValue)) { + if (rawValue == null || (isLargeSeries(seriesIndex) && isMissingDownloadValue(rawValue))) { return $t('package.trends.no_data_short') } @@ -124,14 +124,7 @@ const datasets = computed(() => { return { period, - value: - rawValue === 0 - ? props.nullifyZeroValues && hasNoDataTail - ? index === props.dates.length - 1 - ? 0 - : null - : 0 - : rawValue || (index === props.dates.length - 1 ? 0 : null), + value: rawValue ?? null, } }) }) @@ -234,9 +227,9 @@ const configs = computed(() => { strokeWidth: 1, dashIndices, dashArray: 3, - cutNullValues: false, + cutNullValues: true, nullDashes: { - show: true, + show: false, }, }, plot: { diff --git a/app/components/Package/DailyDownloadsHeatmap.vue b/app/components/Package/DailyDownloadsHeatmap.vue new file mode 100644 index 0000000000..719fdac178 --- /dev/null +++ b/app/components/Package/DailyDownloadsHeatmap.vue @@ -0,0 +1,794 @@ + + + diff --git a/app/components/Package/Header.vue b/app/components/Package/Header.vue index 1e0a0e813f..7fae62c643 100644 --- a/app/components/Package/Header.vue +++ b/app/components/Package/Header.vue @@ -80,10 +80,10 @@ const { copied: copiedPkgVersion, copy: copyPkgVersion } = useClipboard({ copiedDuring: 2000, }) -function hasProvenance(version: PackumentVersion | null): boolean { - if (!version?.dist) return false - return !!(version.dist as { attestations?: unknown }).attestations -} +const publishTrustStatus = computed( + () => + (props.resolvedVersion && props.pkg?.versions[props.resolvedVersion]?.trustStatus) || undefined, +) const { announce } = useCommandPalette() @@ -284,7 +284,23 @@ useShortcuts({ :tabindex="showScrollToTop ? 0 : -1" />
-