@@ -4,6 +4,7 @@ import { pathToFileURL } from "url"
44import { isPathPluginSpec , parsePluginSpecifier , resolvePathPluginTarget } from "@/plugin/shared"
55import { zod } from "@/util/effect-zod"
66import { withStatics } from "@/util/schema"
7+ import os from "os"
78import path from "path"
89
910export const Options = Schema . Record ( Schema . String , Schema . Unknown ) . pipe ( withStatics ( ( s ) => ( { zod : zod ( s ) } ) ) )
@@ -49,11 +50,52 @@ export function pluginOptions(plugin: Spec): Options | undefined {
4950 return Array . isArray ( plugin ) ? plugin [ 1 ] : undefined
5051}
5152
53+ function expandPathVariablePrefix ( spec : string ) {
54+ if ( spec === "~" ) return os . homedir ( )
55+ if ( spec . startsWith ( "~/" ) || spec . startsWith ( "~\\" ) ) return path . join ( os . homedir ( ) , spec . slice ( 2 ) )
56+
57+ const posix = spec . match ( / ^ \$ ( [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * ) (? = $ | [ \\ / ] ) / )
58+ if ( posix ) {
59+ const value = process . env [ posix [ 1 ] ]
60+ if ( value ) return value + spec . slice ( posix [ 0 ] . length )
61+ }
62+
63+ const braced = spec . match ( / ^ \$ \{ ( [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * ) \} (? = $ | [ \\ / ] ) / )
64+ if ( braced ) {
65+ const value = process . env [ braced [ 1 ] ]
66+ if ( value ) return value + spec . slice ( braced [ 0 ] . length )
67+ }
68+
69+ const windows = spec . match ( / ^ % ( [ ^ % ] + ) % (? = $ | [ \\ / ] ) / )
70+ if ( windows ) {
71+ const value = process . env [ windows [ 1 ] ]
72+ if ( value ) return value + spec . slice ( windows [ 0 ] . length )
73+ }
74+
75+ return spec
76+ }
77+
78+ function hasPathVariablePrefix ( spec : string ) {
79+ if ( spec === "~" || spec . startsWith ( "~/" ) || spec . startsWith ( "~\\" ) ) return true
80+
81+ const posix = spec . match ( / ^ \$ ( [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * ) (? = $ | [ \\ / ] ) / )
82+ if ( posix ) return ! ! process . env [ posix [ 1 ] ]
83+
84+ const braced = spec . match ( / ^ \$ \{ ( [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * ) \} (? = $ | [ \\ / ] ) / )
85+ if ( braced ) return ! ! process . env [ braced [ 1 ] ]
86+
87+ const windows = spec . match ( / ^ % ( [ ^ % ] + ) % (? = $ | [ \\ / ] ) / )
88+ if ( windows ) return ! ! process . env [ windows [ 1 ] ]
89+
90+ return false
91+ }
92+
5293// Path-like specs are resolved relative to the config file that declared them so merges later on do not
5394// accidentally reinterpret `./plugin.ts` relative to some other directory.
5495export async function resolvePluginSpec ( plugin : Spec , configFilepath : string ) : Promise < Spec > {
55- const spec = pluginSpecifier ( plugin )
56- if ( ! isPathPluginSpec ( spec ) ) return plugin
96+ const raw = pluginSpecifier ( plugin )
97+ const spec = expandPathVariablePrefix ( raw )
98+ if ( ! isPathPluginSpec ( raw ) && ! hasPathVariablePrefix ( raw ) && ! isPathPluginSpec ( spec ) ) return plugin
5799
58100 const base = path . dirname ( configFilepath )
59101 const file = ( ( ) => {
0 commit comments