diff --git a/packages/node-modules-inspector/src/app/registry/index.ts b/packages/node-modules-inspector/src/app/registry/index.ts index a0f5fc8b..db43d36f 100644 --- a/packages/node-modules-inspector/src/app/registry/index.ts +++ b/packages/node-modules-inspector/src/app/registry/index.ts @@ -68,6 +68,24 @@ export function createRegistryBackend( }) registryWarnings.value = warnings + // If the user asked for top-level packages but none of them could be + // resolved, treat it as a hard failure. Throwing here propagates to the + // landing page's error box (via `fetchData(..., propagateError)`), + // instead of dropping the user into an empty graph with only a toast. + const requestedCount = Object.keys(dependencies).length + const root = [...result.packages.values()].find(pkg => pkg.workspace) + const resolvedTopLevel = root?.dependencies.size ?? 0 + if (requestedCount && !resolvedTopLevel) { + const reasons = warnings + .filter(warning => !warning.dependent) + .map(warning => warning.message) + throw new Error( + reasons.length + ? reasons.join('\n') + : 'None of the requested packages could be resolved from the npm registry.', + ) + } + return { hash: getHash([...result.packages.keys()].sort()), timestamp: Date.now(), diff --git a/packages/node-modules-inspector/src/app/web/Landing.vue b/packages/node-modules-inspector/src/app/web/Landing.vue index f2125176..ac92e21a 100644 --- a/packages/node-modules-inspector/src/app/web/Landing.vue +++ b/packages/node-modules-inspector/src/app/web/Landing.vue @@ -2,13 +2,14 @@ import type { InstallExcludeSpec } from 'node-modules-tools/registry' import { parseInstallSpecs } from 'node-modules-tools/registry' import { computed, defineAsyncComponent, onMounted, ref, shallowRef } from 'vue' +import { useRouter } from '#app/composables/router' import { backend } from '../backends' import RegistryWarnings from '../components/registry/Warnings.vue' import UiCredits from '../components/ui/Credits.vue' import UiTitle from '../components/ui/Title.vue' import MainEntry from '../entries/main.vue' import { createRegistryBackend, registryProgress } from '../registry' -import { fetchData, rawPayload } from '../state/data' +import { fetchData } from '../state/data' import { query } from '../state/query' import { openTerminal, showTerminal } from '../state/terminal' @@ -18,6 +19,11 @@ const LazyPanelTerminal = defineAsyncComponent(() => import('../components/panel type WebMode = 'instant' | 'sandbox' +const router = useRouter() +// `Landing` renders outside ``, where `useRoute()` returns a stale +// snapshot — `router.currentRoute` is the reactive source that tracks pushes. +const isLanding = computed(() => router.currentRoute.value.path === '/') + // The WebContainer SDK (`@webcontainer/api`) is heavy and only needed for // "Sandbox Install" mode. Load `./container` lazily so the default Instant // (npm-registry) mode never pulls the SDK into the initial bundle. @@ -147,6 +153,13 @@ async function run() { await runSandbox(deps) else await runInstant(deps) + + // Navigate into the inspector with a real history push (not a replace) so + // the browser Back button returns here to the landing. The landing lives + // at `/`; once we're on an inspector route the payload renders MainEntry. + // Only move on a clean success — stay put if anything went wrong. + if (isLanding.value && !error.value) + await router.push({ path: '/grid/depth', hash: location.hash }) } catch (e) { console.error(e) @@ -187,7 +200,12 @@ async function runSandbox(deps: Record) {