Skip to content

Commit 3549718

Browse files
committed
link install scripts into code tab
1 parent 38d53e1 commit 3549718

File tree

4 files changed

+75
-4
lines changed

4 files changed

+75
-4
lines changed

app/components/PackageInstallScripts.vue

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
<script setup lang="ts">
2+
import { getInstallScriptFilePath } from '~/utils/install-scripts'
3+
24
const props = defineProps<{
35
packageName: string
6+
version: string
47
installScripts: {
58
scripts: ('preinstall' | 'install' | 'postinstall')[]
69
content?: Record<string, string>
710
npxDependencies: Record<string, string>
811
}
912
}>()
1013
14+
function getCodeLink(scriptContent: string): string {
15+
const filePath = getInstallScriptFilePath(scriptContent)
16+
return `/code/${props.packageName}/v/${props.version}/${filePath}`
17+
}
18+
1119
const outdatedNpxDeps = useOutdatedDependencies(() => props.installScripts.npxDependencies)
1220
const hasNpxDeps = computed(() => Object.keys(props.installScripts.npxDependencies).length > 0)
1321
const sortedNpxDeps = computed(() => {
@@ -32,11 +40,17 @@ const isExpanded = shallowRef(false)
3240
<div v-for="scriptName in installScripts.scripts" :key="scriptName">
3341
<dt class="font-mono text-xs text-fg-muted">{{ scriptName }}</dt>
3442
<dd
35-
tabindex="0"
36-
class="font-mono text-sm text-fg-subtle m-0 truncate focus:whitespace-normal focus:overflow-visible cursor-help rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
43+
class="font-mono text-sm text-fg-subtle m-0 truncate rounded"
3744
:title="installScripts.content?.[scriptName]"
3845
>
39-
{{ installScripts.content?.[scriptName] || $t('package.install_scripts.script_label') }}
46+
<NuxtLink
47+
v-if="installScripts.content?.[scriptName]"
48+
:to="getCodeLink(installScripts.content[scriptName])"
49+
class="hover:text-fg transition-colors duration-200 focus:whitespace-normal focus:overflow-visible focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
50+
>
51+
{{ installScripts.content[scriptName] }}
52+
</NuxtLink>
53+
<span v-else>{{ $t('package.install_scripts.script_label') }}</span>
4054
</dd>
4155
</div>
4256
</dl>

app/pages/[...package].vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ function handleClick(event: MouseEvent) {
10471047
<PackageInstallScripts
10481048
v-if="displayVersion?.installScripts"
10491049
:package-name="pkg.name"
1050+
:version="displayVersion.version"
10501051
:install-scripts="displayVersion.installScripts"
10511052
/>
10521053

app/utils/install-scripts.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,37 @@ export function extractInstallScriptsInfo(
114114
npxDependencies: extractNpxDependencies(scripts),
115115
}
116116
}
117+
118+
/**
119+
* Pattern to match scripts that are just `node <file-path>`
120+
* Captures the file path (relative paths with alphanumeric chars, dots, hyphens, underscores, and slashes)
121+
*/
122+
const NODE_SCRIPT_PATTERN = /^node\s+([\w./-]+)$/
123+
124+
/**
125+
* Get the file path for an install script link.
126+
* - If the script is `node <file-path>`, returns that file path
127+
* - Otherwise, returns 'package.json'
128+
*
129+
* @param scriptContent - The content of the script
130+
* @returns The file path to link to in the code tab
131+
*/
132+
export function getInstallScriptFilePath(scriptContent: string): string {
133+
const match = NODE_SCRIPT_PATTERN.exec(scriptContent)
134+
135+
if (match?.[1]) {
136+
// Script is `node <file-path>`, link to that file
137+
// Normalize path: strip leading ./
138+
const filePath = match[1].replace(/^\.\//, '')
139+
140+
// Fall back to package.json if path contains navigational elements (the client-side routing can't handle these well)
141+
if (filePath.includes('../') || filePath.includes('./')) {
142+
return 'package.json'
143+
}
144+
145+
return filePath
146+
}
147+
148+
// Default: link to package.json
149+
return 'package.json'
150+
}

test/unit/app/utils/install-scripts.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { describe, expect, it } from 'vitest'
2-
import { extractInstallScriptsInfo } from '../../../../app/utils/install-scripts'
2+
import {
3+
extractInstallScriptsInfo,
4+
getInstallScriptFilePath,
5+
} from '../../../../app/utils/install-scripts'
36

47
describe('extractInstallScriptsInfo', () => {
58
it('returns null when no install scripts exist', () => {
@@ -75,3 +78,22 @@ describe('extractInstallScriptsInfo', () => {
7578
})
7679
})
7780
})
81+
82+
describe('getInstallScriptFilePath', () => {
83+
it('returns file path when script is `node <file-path>`', () => {
84+
expect(getInstallScriptFilePath('node scripts/postinstall.js')).toBe('scripts/postinstall.js')
85+
})
86+
87+
it('returns package.json when script is not a simple node command', () => {
88+
expect(getInstallScriptFilePath('npx prisma generate')).toBe('package.json')
89+
})
90+
91+
it('strips leading ./ from relative paths', () => {
92+
expect(getInstallScriptFilePath('node ./scripts/setup.js')).toBe('scripts/setup.js')
93+
})
94+
95+
it('falls back to package.json for parent directory references', () => {
96+
expect(getInstallScriptFilePath('node ../scripts/setup.js')).toBe('package.json')
97+
expect(getInstallScriptFilePath('node ./scripts/../lib/setup.js')).toBe('package.json')
98+
})
99+
})

0 commit comments

Comments
 (0)