diff --git a/app/components/Package/InstallScripts.vue b/app/components/Package/InstallScripts.vue index dde493782..6aababe2f 100644 --- a/app/components/Package/InstallScripts.vue +++ b/app/components/Package/InstallScripts.vue @@ -3,6 +3,7 @@ import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-depend const props = defineProps<{ packageName: string + version: string installScripts: { scripts: ('preinstall' | 'install' | 'postinstall')[] content?: Record @@ -10,6 +11,11 @@ const props = defineProps<{ } }>() +function getCodeLink(scriptContent: string): string { + const filePath = getInstallScriptFilePath(scriptContent) + return `/code/${props.packageName}/v/${props.version}/${filePath}` +} + const outdatedNpxDeps = useOutdatedDependencies(() => props.installScripts.npxDependencies) const hasNpxDeps = computed(() => Object.keys(props.installScripts.npxDependencies).length > 0) const sortedNpxDeps = computed(() => { @@ -34,7 +40,14 @@ const isExpanded = shallowRef(false) class="font-mono text-sm text-fg-subtle m-0 truncate focus:whitespace-normal focus:overflow-visible cursor-help rounded focus-visible:(outline-2 outline-accent outline-offset-2)" :title="installScripts.content?.[scriptName]" > - {{ installScripts.content?.[scriptName] || $t('package.install_scripts.script_label') }} + + {{ installScripts.content[scriptName] }} + + {{ $t('package.install_scripts.script_label') }} diff --git a/app/pages/package/[...package].vue b/app/pages/package/[...package].vue index f9d3aef46..a6913b71e 100644 --- a/app/pages/package/[...package].vue +++ b/app/pages/package/[...package].vue @@ -1215,6 +1215,7 @@ onKeyStroke( diff --git a/app/utils/install-scripts.ts b/app/utils/install-scripts.ts index bc67d80b5..ce5b8c9c4 100644 --- a/app/utils/install-scripts.ts +++ b/app/utils/install-scripts.ts @@ -114,3 +114,37 @@ export function extractInstallScriptsInfo( npxDependencies: extractNpxDependencies(scripts), } } + +/** + * Pattern to match scripts that are just `node ` + * Captures the file path (relative paths with alphanumeric chars, dots, hyphens, underscores, and slashes) + */ +const NODE_SCRIPT_PATTERN = /^node\s+([\w./-]+)$/ + +/** + * Get the file path for an install script link. + * - If the script is `node `, returns that file path + * - Otherwise, returns 'package.json' + * + * @param scriptContent - The content of the script + * @returns The file path to link to in the code tab + */ +export function getInstallScriptFilePath(scriptContent: string): string { + const match = NODE_SCRIPT_PATTERN.exec(scriptContent) + + if (match?.[1]) { + // Script is `node `, link to that file + // Normalize path: strip leading ./ + const filePath = match[1].replace(/^\.\//, '') + + // Fall back to package.json if path contains navigational elements (the client-side routing can't handle these well) + if (filePath.includes('../') || filePath.includes('./')) { + return 'package.json' + } + + return filePath + } + + // Default: link to package.json + return 'package.json' +} diff --git a/test/unit/app/utils/install-scripts.spec.ts b/test/unit/app/utils/install-scripts.spec.ts index d5f1b49e3..1d0020ba3 100644 --- a/test/unit/app/utils/install-scripts.spec.ts +++ b/test/unit/app/utils/install-scripts.spec.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from 'vitest' -import { extractInstallScriptsInfo } from '../../../../app/utils/install-scripts' +import { + extractInstallScriptsInfo, + getInstallScriptFilePath, +} from '../../../../app/utils/install-scripts' describe('extractInstallScriptsInfo', () => { it('returns null when no install scripts exist', () => { @@ -75,3 +78,22 @@ describe('extractInstallScriptsInfo', () => { }) }) }) + +describe('getInstallScriptFilePath', () => { + it('returns file path when script is `node `', () => { + expect(getInstallScriptFilePath('node scripts/postinstall.js')).toBe('scripts/postinstall.js') + }) + + it('returns package.json when script is not a simple node command', () => { + expect(getInstallScriptFilePath('npx prisma generate')).toBe('package.json') + }) + + it('strips leading ./ from relative paths', () => { + expect(getInstallScriptFilePath('node ./scripts/setup.js')).toBe('scripts/setup.js') + }) + + it('falls back to package.json for parent directory references', () => { + expect(getInstallScriptFilePath('node ../scripts/setup.js')).toBe('package.json') + expect(getInstallScriptFilePath('node ./scripts/../lib/setup.js')).toBe('package.json') + }) +})