()
| 113 | }; |
| 114 | |
| 115 | const loadVirtualSource = async (): Promise<string> => { |
| 116 | if (cachedSource !== null) return cachedSource; |
| 117 | |
| 118 | const configPath = resolveConfigPath(); |
| 119 | const jsoncPath = resolveJsoncPath(); |
| 120 | const fromDir = configPath ? dirname(configPath) : projectRoot; |
| 121 | |
| 122 | // Collect plugin entries in priority order: static config first, |
| 123 | // jsonc second. De-duplicate by package name — if both list the |
| 124 | // same plugin, only one import is emitted. (Static wins for the |
| 125 | // ordering of `plugins` array, which matters for nav order.) |
| 126 | // |
| 127 | // `clientConfig` only flows in via the static config path: it |
| 128 | // lives on the runtime `PluginSpec` returned by |
| 129 | // `definePlugin(...)`, which is only available when we jiti-load |
| 130 | // the TS module. The jsonc path carries package names only. |
| 131 | const entries: Array<{ readonly pkg: string; readonly clientConfig?: unknown }> = []; |
| 132 | const seen = new Set<string>(); |
| 133 | |
| 134 | if (configPath) { |
| 135 | // jiti is a dev dep of consumers; importing dynamically lets the |
| 136 | // plugin be lazy-loaded and avoids a hard requirement when the |
| 137 | // host doesn't actually use plugins yet. |
| 138 | const { createJiti } = await import("jiti"); |
| 139 | const jiti = createJiti(pathToFileURL(configPath).href, { |
| 140 | interopDefault: true, |
| 141 | moduleCache: false, |
| 142 | }); |
| 143 | const mod = (await jiti.import(configPath)) as |
| 144 | | { default?: ExecutorCliConfig } |
| 145 | | ExecutorCliConfig; |
| 146 | const config = ("default" in mod && mod.default ? mod.default : mod) as ExecutorCliConfig; |
| 147 | for (const spec of config.plugins()) { |
| 148 | if (!spec.packageName) continue; |
| 149 | if (seen.has(spec.packageName)) continue; |
| 150 | seen.add(spec.packageName); |
| 151 | const clientConfig = (spec as { clientConfig?: unknown }).clientConfig; |
| 152 | entries.push({ |
| 153 | pkg: spec.packageName, |
| 154 | ...(clientConfig !== undefined ? { clientConfig } : {}), |
| 155 | }); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | if (jsoncPath) { |
| 160 | for (const pkg of readJsoncPlugins(jsoncPath)) { |
| 161 | if (seen.has(pkg)) continue; |
| 162 | seen.add(pkg); |
| 163 | entries.push({ pkg }); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | const lines: string[] = []; |
| 168 | const exportExpressions: string[] = []; |
| 169 | |
| 170 | for (const entry of entries) { |
| 171 | const resolved = tryResolveClient(entry.pkg, fromDir); |
| 172 | if (!resolved) { |
no test coverage detected