@slip-stream-kit/config 0.1.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,201 @@
1
+ import type { InfraKitDev, InfraKitDevProxy } from '../package-config/package-config';
2
+ import { slugifyRelease } from '../release-slug/release-slug';
3
+ /**
4
+ * Wire version of the dev-context fragment — the ONE contract between the `infra-kit` CLI (which writes
5
+ * fragments) and this helper (which reads them). They ship as SEPARATE npm packages on separate cadences,
6
+ * so this number is the only thing either side can trust about the other.
7
+ *
8
+ * `v >= 2` is a PROMISE that `origin` is present and authoritative.
9
+ *
10
+ * That promise is what makes the guard in {@link resolveLocalTarget} possible. Without it, a missing
11
+ * `origin` is ambiguous — it means BOTH "an old CLI wrote this, legacy mode is correct" AND "a current
12
+ * CLI wrote a broken fragment, legacy mode is catastrophic" — and the helper has to guess. It guesses
13
+ * legacy, rebuilds the target from a `templates.local` that says `http://`, and proxies plain HTTP into
14
+ * a TLS listener. Silently, because portless answers :80 with a 302 rather than refusing.
15
+ *
16
+ * A fragment with NO `v` is a pre-v2 (legacy) writer and must still be honoured: rejecting it would drop
17
+ * a running package to `cloud` instead of falling into legacy mode, and that rollback path is exactly why
18
+ * the schema above is lenient and un-`refine`d. Absent or unknown `v` therefore NEVER rejects.
19
+ */
20
+ export declare const DEV_CONTEXT_WIRE_VERSION = 2;
21
+ /**
22
+ * A single Vite `server.proxy` entry. `changeOrigin` is always set. `cookieDomainRewrite` is cloud-only
23
+ * (it keeps an HTTPS cloud BE's cookies usable from a local FE). `secure: false` appears on BOTH sources
24
+ * but for different reasons: a cloud target may serve a cert the local store rejects, and a local target
25
+ * is served by portless from its own private CA — set there only for a loopback host (see
26
+ * {@link isLoopbackTarget}). `headers` is present only when HTTP Basic Auth is injected (see
27
+ * {@link buildBasicAuthHeader}) — it carries the `Authorization` header applied
28
+ * uniformly to every route so upstream environments behind auth (e2e/staging)
29
+ * stay reachable.
30
+ */
31
+ export interface InfraKitViteProxyEntry {
32
+ target: string;
33
+ changeOrigin: true;
34
+ secure?: false;
35
+ cookieDomainRewrite?: 'localhost';
36
+ headers?: Record<string, string>;
37
+ }
38
+ /** A Vite `server.proxy`-shaped map: path-prefix → proxy entry. */
39
+ export type InfraKitViteProxy = Record<string, InfraKitViteProxyEntry>;
40
+ /** Explicit HTTP Basic Auth credentials, overriding the `E2E__BASIC_AUTH_*` env vars. */
41
+ export interface InfraKitBasicAuth {
42
+ username: string;
43
+ password: string;
44
+ }
45
+ export interface InfraKitDevOptions {
46
+ /** Package dir whose `infra-kit.config.ts` is loaded. Defaults to `process.cwd()`. */
47
+ cwd?: string;
48
+ /**
49
+ * Explicit HTTP Basic Auth credentials injected into every proxy route as an
50
+ * `Authorization` header. Takes precedence over the `E2E__BASIC_AUTH_USERNAME`
51
+ * / `E2E__BASIC_AUTH_PASSWORD` env vars. Omit to use the env-based default.
52
+ */
53
+ basicAuth?: InfraKitBasicAuth;
54
+ /**
55
+ * Vite's `command`. Pass it (`defineConfig(async ({ command }) => ({ server: await infraKitDev({ command }) }))`)
56
+ * so `build` is a no-op: `server` is irrelevant to a build and proxy resolution would otherwise
57
+ * fail-fast on a cloud route with no sourced env. Omit (or `'serve'`) for the dev-server config.
58
+ */
59
+ command?: 'build' | 'serve';
60
+ /**
61
+ * Explicit dev-server port. Omit for a **per-worktree dynamic** free port (a fresh OS-assigned
62
+ * port) so N simultaneous git worktrees never collide on Vite's default `5173`; Vite prints the
63
+ * chosen URL. Pass a fixed number only when an external contract pins the port.
64
+ */
65
+ port?: number;
66
+ /**
67
+ * Interface the dev server binds. Defaults to {@link LOOPBACK_V4} so a portless alias can reach it
68
+ * (Vite's own `localhost` default binds `[::1]` only, which the proxy cannot dial). Override when
69
+ * the dev server must be reachable off the loopback — `'0.0.0.0'` or `true` inside a container, on
70
+ * a LAN, or for an e2e runner on another host.
71
+ */
72
+ host?: string | boolean;
73
+ }
74
+ /**
75
+ * Re-exported from the shared, dependency-light `lib/release-slug` module so the
76
+ * published `infra-kit/vite` surface (`entry/vite.ts:7`) is unchanged while the
77
+ * dev-server's fragment writer derives `<release>` from the SAME implementation
78
+ * (no slug drift). See {@link slugifyRelease}.
79
+ */
80
+ export { slugifyRelease };
81
+ interface ResolveProxyArgs {
82
+ proxy: InfraKitDevProxy;
83
+ localSet: ReadonlySet<string>;
84
+ env: string | undefined;
85
+ getRelease: () => string;
86
+ /** Pre-computed `Authorization` header value applied uniformly to every route. */
87
+ authHeader?: string;
88
+ /** Per-package runtime data (recorded release/port) from the dev-context merge. */
89
+ localInfo?: ReadonlyMap<string, LocalPackageInfo>;
90
+ }
91
+ /**
92
+ * Pure resolver: turn a `dev.proxy` config + resolved inputs (local set, env,
93
+ * lazy release) into a Vite `server.proxy` map. Side-effect-free so the
94
+ * resolution shape is fully unit-testable. When `authHeader` is set, every route
95
+ * entry gets a matching `headers.Authorization`; otherwise no `headers` key.
96
+ */
97
+ export declare const resolveProxyConfig: ({ proxy, localSet, env, getRelease, authHeader, localInfo, }: ResolveProxyArgs) => InfraKitViteProxy;
98
+ /**
99
+ * Load a package's `infra-kit.config.ts` and return its `dev` block, or
100
+ * `undefined` when the config or the `dev` key is absent. The `.ts` config is
101
+ * evaluated via Node's native type stripping (Node >= 24) — the same mechanism
102
+ * the CLI's config loader uses. Cache-busted by mtime so repeated dev-server
103
+ * reloads pick up edits.
104
+ *
105
+ * Reached from two different processes, and only one of them is ours. Under the
106
+ * CLI (`infra-kit dev`, `audit`) the entry has installed
107
+ * {@link ../node-warnings.suppressTypelessPackageJsonWarning}, so a consumer package
108
+ * with no `"type"` loads quietly. Under `infraKitDev()` in a consumer's own
109
+ * `vite.config.ts` we are a library in their process and do NOT patch their globals,
110
+ * so such a package still prints Node's MODULE_TYPELESS_PACKAGE_JSON banner there.
111
+ * Harmless (it is a double-parse notice), and latent while consumer UI packages
112
+ * declare `"type": "module"`.
113
+ */
114
+ export declare const loadDev: (cwd: string) => Promise<InfraKitDev | undefined>;
115
+ /** Per-package runtime data merged from the dev-context fragment directory. */
116
+ export interface LocalPackageInfo {
117
+ /** The real bound port the runner recorded. Provenance only — the proxy target is the alias host. */
118
+ port: number;
119
+ /**
120
+ * The authoritative local target (`https://<release>.<packageName>.localhost`), published by the
121
+ * runner. Present → {@link resolveLocalTarget} uses it verbatim and consults no template. Absent →
122
+ * the fragment came from an old CLI and the legacy template path runs.
123
+ */
124
+ origin?: string;
125
+ /**
126
+ * The fragment's declared wire version (see {@link DEV_CONTEXT_WIRE_VERSION}). Absent → a pre-v2 writer,
127
+ * so legacy mode is the correct reading. Present → the writer PROMISED an `origin`, and a missing one is
128
+ * a broken fragment rather than an old one.
129
+ */
130
+ wire?: number;
131
+ /** LEGACY (no `origin`): the runner-recorded release slug, preferred over the helper's git derivation. */
132
+ release?: string;
133
+ /** LEGACY (no `origin`): `<release>.<packageName>.localhost`, present only when an alias was registered. */
134
+ alias?: string;
135
+ /** LEGACY (no `origin`): the port {@link alias} resolves on. `80` (the default) is implicit in the template. */
136
+ proxyPort?: number;
137
+ }
138
+ /**
139
+ * The locally-running package set plus a `package → { port, release }` map merged
140
+ * from the dev-context fragments. `packages` feeds `pickSource`; `info` lets a
141
+ * `local` route emit the per-package recorded release (see {@link resolveRoute}).
142
+ */
143
+ export interface LocalContext {
144
+ packages: ReadonlySet<string>;
145
+ info: ReadonlyMap<string, LocalPackageInfo>;
146
+ }
147
+ /**
148
+ * Read the locally-running package context, searched upward from `cwd`. Prefers
149
+ * the `.infra-kit/dev-context/` fragment DIRECTORY (merged via
150
+ * {@link readFragmentDir}); when it is absent, falls back to the legacy single
151
+ * `.infra-kit/dev-context.json` file (read as before — no per-package release);
152
+ * when neither exists → empty context (frontend-only, every route resolves cloud).
153
+ */
154
+ export declare const readLocalContext: (cwd: string) => LocalContext;
155
+ /**
156
+ * Read the set of locally-running packages (searched upward from `cwd`). Thin
157
+ * wrapper over {@link readLocalContext} preserving the historical `Set`-returning
158
+ * contract. Absent dev-context → empty set (frontend-only cloud resolution).
159
+ */
160
+ export declare const readLocalSet: (cwd: string) => ReadonlySet<string>;
161
+ /**
162
+ * Vite's `server.hmr` override, pointing the HMR client at the alias instead of at the raw dev-server port.
163
+ * The page is loaded over `https://<alias>`, so its websocket must be `wss://` on the same origin or the
164
+ * browser blocks it as mixed content; `clientPort` is the proxy's implicit `:443`, not vite's bound port.
165
+ */
166
+ export interface InfraKitViteHmr {
167
+ protocol: 'wss';
168
+ host: string;
169
+ clientPort: number;
170
+ }
171
+ /**
172
+ * Resolve a package's `dev` block into a ready-made Vite `server` config: a per-worktree dev-server
173
+ * `port` plus the `proxy` map. Loads the package's `infra-kit.config.ts`, merges the local dev set
174
+ * from the `.infra-kit/dev-context/` fragment directory, and interpolates the local/cloud templates.
175
+ * `<env>` comes from `INFRA_KIT_ENV`; `<release>` from each package's runner-recorded fragment when
176
+ * present, else the slugified git branch (computed lazily, only when a local route needs it).
177
+ *
178
+ * `port` defaults to a fresh OS-assigned free port so simultaneous git worktrees never collide on
179
+ * Vite's `5173` (override via `options.port`). `host` defaults to {@link LOOPBACK_V4} so the portless
180
+ * alias can actually reach it (override via `options.host` for containers/LAN). `hmr` is emitted only
181
+ * when `infra-kit dev` registered an alias for this UI, so a bare `vite dev` keeps vite's own HMR
182
+ * defaults. Pass `command` so `build` is a no-op (empty proxy, no port) — a build ignores `server` and
183
+ * proxy resolution would otherwise fail-fast on a cloud route with no sourced env.
184
+ *
185
+ * @example
186
+ * // vite.config.ts — the whole `server` field is the helper's output
187
+ * import { infraKitDev } from 'infra-kit/vite'
188
+ * export default defineConfig(async ({ command }) => ({ server: await infraKitDev({ command }) }))
189
+ */
190
+ export declare const infraKitDev: (options?: InfraKitDevOptions) => Promise<{
191
+ port?: number;
192
+ host?: string | boolean;
193
+ strictPort?: boolean;
194
+ hmr?: InfraKitViteHmr;
195
+ proxy: InfraKitViteProxy;
196
+ }>;
197
+ /**
198
+ * Convenience wrapper returning just the proxy map (for spreading into
199
+ * `server.proxy` directly). Equivalent to `(await infraKitDev(options)).proxy`.
200
+ */
201
+ export declare const infraKitProxy: (options?: InfraKitDevOptions) => Promise<InfraKitViteProxy>;
package/dist/vite.js ADDED
@@ -0,0 +1,367 @@
1
+ // src/lib/vite/vite.ts
2
+ import { Buffer } from "node:buffer";
3
+ import { execFileSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import net from "node:net";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { pathToFileURL } from "node:url";
9
+ import { z as z2 } from "zod";
10
+
11
+ // src/lib/package-config/package-config-schema.ts
12
+ import { z } from "zod";
13
+ var packageConfigSchema = z.strictObject({
14
+ requiredScripts: z.array(z.string().min(1)).optional(),
15
+ requiredFiles: z.array(z.string().min(1)).optional(),
16
+ turbo: z.strictObject({
17
+ requiredTasks: z.array(z.string().min(1)).optional()
18
+ }).optional(),
19
+ dev: z.strictObject({
20
+ proxy: z.strictObject({
21
+ templates: z.strictObject({
22
+ local: z.string().min(1),
23
+ cloud: z.string().min(1)
24
+ }),
25
+ routes: z.record(
26
+ z.string().min(1),
27
+ z.strictObject({
28
+ packageName: z.string().min(1),
29
+ from: z.array(z.enum(["local", "cloud"])).min(1),
30
+ default: z.enum(["local", "cloud"]).optional()
31
+ }).refine(
32
+ (route) => {
33
+ return route.from.length <= 1 || route.default !== void 0;
34
+ },
35
+ {
36
+ message: "default is required when `from` has more than one source"
37
+ }
38
+ ).refine(
39
+ (route) => {
40
+ return route.default === void 0 || route.from.includes(route.default);
41
+ },
42
+ {
43
+ message: "default must be listed in `from`"
44
+ }
45
+ )
46
+ )
47
+ }).optional()
48
+ }).optional()
49
+ });
50
+
51
+ // src/lib/release-slug/release-slug.ts
52
+ var slugifyHostLabel = (label) => {
53
+ const runs = label.toLowerCase().match(/[a-z0-9]+/g);
54
+ return runs ? runs.join("-") : "";
55
+ };
56
+ var slugifyRelease = (branch) => {
57
+ return slugifyHostLabel(branch.replace(/^(?:feature|feat|release|hotfix|bugfix|fix|chore)\//i, ""));
58
+ };
59
+ var DEFAULT_RELEASE_SLUG = "local";
60
+
61
+ // src/lib/vite/vite.ts
62
+ var INFRA_KIT_ENV = "INFRA_KIT_ENV";
63
+ var PACKAGE_CONFIG_FILE = "infra-kit.config.ts";
64
+ var DEV_CONTEXT_DIR = path.join(".infra-kit", "dev-context");
65
+ var DEV_CONTEXT_FILE = path.join(".infra-kit", "dev-context.json");
66
+ var devContextFragmentSchema = z2.object({
67
+ package: z2.string(),
68
+ port: z2.number(),
69
+ pid: z2.number().optional(),
70
+ writtenAt: z2.number().optional(),
71
+ release: z2.string().optional(),
72
+ alias: z2.string().optional(),
73
+ proxyPort: z2.number().optional(),
74
+ origin: z2.string().optional(),
75
+ v: z2.number().optional()
76
+ });
77
+ var DEV_CONTEXT_WIRE_VERSION = 2;
78
+ var buildBasicAuthHeader = (env, override) => {
79
+ const username = override?.username ?? env.E2E__BASIC_AUTH_USERNAME;
80
+ const password = override?.password ?? env.E2E__BASIC_AUTH_PASSWORD;
81
+ if (!username || !password) return void 0;
82
+ const encoded = Buffer.from(`${username}:${password}`).toString("base64");
83
+ return `Basic ${encoded}`;
84
+ };
85
+ var interpolate = (template, values) => {
86
+ return template.replaceAll("<release>", values.release).replaceAll("<packageName>", values.packageName).replaceAll("<env>", values.env);
87
+ };
88
+ var DEFAULT_HTTP_PORT = 80;
89
+ var LOOPBACK_V4 = "127.0.0.1";
90
+ var withProxyPort = (target, proxyPort) => {
91
+ if (proxyPort == null || proxyPort === DEFAULT_HTTP_PORT) return target;
92
+ let url;
93
+ try {
94
+ url = new URL(target);
95
+ } catch {
96
+ throw new Error(
97
+ `@slip-stream-kit/config/vite: dev.proxy templates.local resolved to "${target}", which is not a valid URL, so the proxy port ${proxyPort} cannot be applied.`
98
+ );
99
+ }
100
+ if (url.port !== "") return target;
101
+ url.port = String(proxyPort);
102
+ const grafted = url.toString();
103
+ return !target.endsWith("/") && grafted.endsWith("/") ? grafted.slice(0, -1) : grafted;
104
+ };
105
+ var LEGACY_SELF_SPAWNED_PORT_FLOOR = 1024;
106
+ var resolveLegacyTarget = (target, proxyPort) => {
107
+ if (proxyPort != null && proxyPort >= LEGACY_SELF_SPAWNED_PORT_FLOOR) {
108
+ return withProxyPort(target.replace(/^https:\/\//iu, "http://"), proxyPort);
109
+ }
110
+ return withProxyPort(target, proxyPort);
111
+ };
112
+ var resolveLocalTarget = (args) => {
113
+ const { route, templates, env, getRelease, info } = args;
114
+ if (info?.origin) return info.origin;
115
+ if (info?.wire != null) {
116
+ throw new Error(
117
+ `@slip-stream-kit/config/vite: the dev-context fragment for "${route.packageName}" declares wire v${info.wire} but carries no \`origin\`. A v${DEV_CONTEXT_WIRE_VERSION} writer must publish the exact origin it registered. Refusing to guess a target rather than silently proxy plain HTTP at a TLS listener \u2014 restart \`infra-kit dev\`, and if it persists the CLI and this helper are incompatible.`
118
+ );
119
+ }
120
+ const target = interpolate(templates.local, {
121
+ // Prefer THIS package's runner-recorded release slug (R4) over the single global git derivation,
122
+ // so cross-branch FE/BE pairings don't drift the emitted `<release>` from the segment the runner
123
+ // aliased. Fall back to the global git slug only when the fragment carried no release.
124
+ release: info?.release ?? getRelease(),
125
+ // The local template resolves to a `<release>.<packageName>.localhost` alias the dev-server
126
+ // registered with portless — which slugifies the package name to a legal DNS label. Slugify
127
+ // identically here or a scoped name (`@hulyo/client-ui`) emits a target that no alias backs.
128
+ packageName: slugifyHostLabel(route.packageName),
129
+ env: env ?? ""
130
+ });
131
+ return resolveLegacyTarget(target, info?.proxyPort);
132
+ };
133
+ var pickSource = (route, localSet) => {
134
+ const fallback = route.default ?? route.from[0];
135
+ return route.from.includes("local") && localSet.has(route.packageName) ? "local" : fallback;
136
+ };
137
+ var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
138
+ var isLoopbackTarget = (target) => {
139
+ let hostname;
140
+ try {
141
+ hostname = new URL(target).hostname.toLowerCase();
142
+ } catch {
143
+ return false;
144
+ }
145
+ const bare = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
146
+ return bare.endsWith(".localhost") || LOOPBACK_HOSTNAMES.has(bare);
147
+ };
148
+ var resolveRoute = ({
149
+ routePath,
150
+ route,
151
+ templates,
152
+ localSet,
153
+ env,
154
+ getRelease,
155
+ localInfo
156
+ }) => {
157
+ const source = pickSource(route, localSet);
158
+ if (source === "local") {
159
+ const target2 = resolveLocalTarget({ route, templates, env, getRelease, info: localInfo?.get(route.packageName) });
160
+ return isLoopbackTarget(target2) ? { target: target2, changeOrigin: true, secure: false } : { target: target2, changeOrigin: true };
161
+ }
162
+ if (!env) {
163
+ throw new Error(
164
+ `@slip-stream-kit/config/vite: proxy route "${routePath}" resolves to a cloud backend but ${INFRA_KIT_ENV} is not set. Source an environment from Doppler first (e.g. \`infra-kit env-load -c dev\`).`
165
+ );
166
+ }
167
+ const target = interpolate(templates.cloud, { release: "", packageName: route.packageName, env });
168
+ return { target, changeOrigin: true, secure: false, cookieDomainRewrite: "localhost" };
169
+ };
170
+ var resolveProxyConfig = ({
171
+ proxy,
172
+ localSet,
173
+ env,
174
+ getRelease,
175
+ authHeader,
176
+ localInfo
177
+ }) => {
178
+ const result = {};
179
+ const headers = authHeader ? { Authorization: authHeader } : void 0;
180
+ for (const [routePath, route] of Object.entries(proxy.routes)) {
181
+ const entry = resolveRoute({ routePath, route, templates: proxy.templates, localSet, env, getRelease, localInfo });
182
+ result[routePath] = headers ? { ...entry, headers } : entry;
183
+ }
184
+ return result;
185
+ };
186
+ var once = (fn) => {
187
+ let cached;
188
+ return () => {
189
+ cached ??= { value: fn() };
190
+ return cached.value;
191
+ };
192
+ };
193
+ var loadDev = async (cwd) => {
194
+ const configPath = path.join(cwd, PACKAGE_CONFIG_FILE);
195
+ if (!fs.existsSync(configPath)) return void 0;
196
+ const stat = fs.statSync(configPath);
197
+ const moduleUrl = `${pathToFileURL(configPath).href}?mtime=${Number(stat.mtimeMs)}`;
198
+ const imported = await import(moduleUrl);
199
+ const rawExport = imported.default;
200
+ if (rawExport === void 0) return void 0;
201
+ const resolved = typeof rawExport === "function" ? await rawExport() : rawExport;
202
+ const parsed = packageConfigSchema.safeParse(resolved);
203
+ if (!parsed.success) {
204
+ throw new Error(
205
+ `@slip-stream-kit/config/vite: invalid ${PACKAGE_CONFIG_FILE} at ${configPath}: ${z2.prettifyError(parsed.error)}`
206
+ );
207
+ }
208
+ return parsed.data.dev;
209
+ };
210
+ var extractPackages = (parsed) => {
211
+ if (Array.isArray(parsed)) {
212
+ return parsed.filter((v) => {
213
+ return typeof v === "string";
214
+ });
215
+ }
216
+ if (parsed !== null && typeof parsed === "object") {
217
+ const candidate = parsed.packages ?? parsed.localPackages;
218
+ if (Array.isArray(candidate)) {
219
+ return candidate.filter((v) => {
220
+ return typeof v === "string";
221
+ });
222
+ }
223
+ }
224
+ return [];
225
+ };
226
+ var findUp = (start, relative) => {
227
+ let dir = path.resolve(start);
228
+ for (; ; ) {
229
+ const candidate = path.join(dir, relative);
230
+ if (fs.existsSync(candidate)) return candidate;
231
+ const parent = path.dirname(dir);
232
+ if (parent === dir) return void 0;
233
+ dir = parent;
234
+ }
235
+ };
236
+ var emptyLocalContext = () => {
237
+ return { packages: /* @__PURE__ */ new Set(), info: /* @__PURE__ */ new Map() };
238
+ };
239
+ var isFragmentWriterAlive = (pid) => {
240
+ if (pid == null) return true;
241
+ try {
242
+ process.kill(pid, 0);
243
+ return true;
244
+ } catch (error) {
245
+ return error.code === "EPERM";
246
+ }
247
+ };
248
+ var readFragmentDir = (dir) => {
249
+ let entries;
250
+ try {
251
+ entries = fs.readdirSync(dir);
252
+ } catch {
253
+ return emptyLocalContext();
254
+ }
255
+ const packages = /* @__PURE__ */ new Set();
256
+ const info = /* @__PURE__ */ new Map();
257
+ for (const name of entries) {
258
+ if (!name.endsWith(".json")) continue;
259
+ try {
260
+ const parsed = devContextFragmentSchema.safeParse(JSON.parse(fs.readFileSync(path.join(dir, name), "utf-8")));
261
+ if (!parsed.success) continue;
262
+ if (!isFragmentWriterAlive(parsed.data.pid)) continue;
263
+ packages.add(parsed.data.package);
264
+ info.set(parsed.data.package, {
265
+ port: parsed.data.port,
266
+ origin: parsed.data.origin,
267
+ release: parsed.data.release,
268
+ alias: parsed.data.alias,
269
+ proxyPort: parsed.data.proxyPort,
270
+ wire: parsed.data.v
271
+ });
272
+ } catch {
273
+ continue;
274
+ }
275
+ }
276
+ return { packages, info };
277
+ };
278
+ var readLocalContext = (cwd) => {
279
+ const dir = findUp(cwd, DEV_CONTEXT_DIR);
280
+ if (dir) return readFragmentDir(dir);
281
+ const legacy = findUp(cwd, DEV_CONTEXT_FILE);
282
+ if (!legacy) return emptyLocalContext();
283
+ try {
284
+ return { packages: new Set(extractPackages(JSON.parse(fs.readFileSync(legacy, "utf-8")))), info: /* @__PURE__ */ new Map() };
285
+ } catch {
286
+ return emptyLocalContext();
287
+ }
288
+ };
289
+ var readGitBranch = (cwd) => {
290
+ return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf-8" }).trim();
291
+ };
292
+ var getFreePort = () => {
293
+ return new Promise((resolve, reject) => {
294
+ const srv = net.createServer();
295
+ srv.unref();
296
+ srv.on("error", reject);
297
+ srv.listen(0, "127.0.0.1", () => {
298
+ const address = srv.address();
299
+ const port = typeof address === "object" && address !== null ? address.port : 0;
300
+ srv.close(() => {
301
+ return resolve(port);
302
+ });
303
+ });
304
+ });
305
+ };
306
+ var UI_PORTS_ENV = "INFRA_KIT_UI_PORTS";
307
+ var readPackageName = (cwd) => {
308
+ try {
309
+ const parsed = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
310
+ return typeof parsed.name === "string" ? parsed.name : null;
311
+ } catch {
312
+ return null;
313
+ }
314
+ };
315
+ var resolveManagedUi = (cwd) => {
316
+ const raw = process.env[UI_PORTS_ENV];
317
+ if (raw == null || raw === "") return null;
318
+ const name = readPackageName(cwd);
319
+ if (name == null) return null;
320
+ try {
321
+ const entry = JSON.parse(raw)[name];
322
+ if (typeof entry === "number") return { port: entry };
323
+ if (entry === null || typeof entry !== "object") return null;
324
+ const { port, alias } = entry;
325
+ if (typeof port !== "number") return null;
326
+ return { port, alias: typeof alias === "string" ? alias : void 0 };
327
+ } catch {
328
+ return null;
329
+ }
330
+ };
331
+ var HTTPS_PORT = 443;
332
+ var infraKitDev = async (options = {}) => {
333
+ const cwd = options.cwd ?? process.cwd();
334
+ if (options.command === "build") return { proxy: {} };
335
+ const managed = options.port == null ? resolveManagedUi(cwd) : null;
336
+ const port = options.port ?? managed?.port ?? await getFreePort();
337
+ const strictPort = managed != null;
338
+ const host = options.host ?? LOOPBACK_V4;
339
+ const hmr = managed?.alias == null ? void 0 : { protocol: "wss", host: managed.alias, clientPort: HTTPS_PORT };
340
+ const server = { port, host, ...strictPort ? { strictPort } : {}, ...hmr ? { hmr } : {} };
341
+ const dev = await loadDev(cwd);
342
+ if (!dev?.proxy) return { ...server, proxy: {} };
343
+ const { packages: localSet, info: localInfo } = readLocalContext(cwd);
344
+ const env = process.env[INFRA_KIT_ENV];
345
+ const authHeader = buildBasicAuthHeader(process.env, options.basicAuth);
346
+ const getRelease = once(() => {
347
+ try {
348
+ return slugifyRelease(readGitBranch(cwd)) || DEFAULT_RELEASE_SLUG;
349
+ } catch {
350
+ return DEFAULT_RELEASE_SLUG;
351
+ }
352
+ });
353
+ return {
354
+ ...server,
355
+ proxy: resolveProxyConfig({ proxy: dev.proxy, localSet, env, getRelease, authHeader, localInfo })
356
+ };
357
+ };
358
+ var infraKitProxy = async (options = {}) => {
359
+ return (await infraKitDev(options)).proxy;
360
+ };
361
+ export {
362
+ infraKitDev,
363
+ infraKitProxy,
364
+ resolveProxyConfig,
365
+ slugifyRelease
366
+ };
367
+ //# sourceMappingURL=vite.js.map