@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.
- package/dist/entry/index.d.ts +4 -0
- package/dist/entry/internal.d.ts +5 -0
- package/dist/entry/vite.d.ts +2 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +7 -0
- package/dist/internal.js +211 -0
- package/dist/internal.js.map +7 -0
- package/dist/lib/package-config/package-config-schema.d.ts +35 -0
- package/dist/lib/package-config/package-config.d.ts +75 -0
- package/dist/lib/release-slug/release-slug.d.ts +43 -0
- package/dist/lib/vendor/config-schema.d.ts +58 -0
- package/dist/lib/vite/vite.d.ts +201 -0
- package/dist/vite.js +367 -0
- package/dist/vite.js.map +7 -0
- package/package.json +70 -0
- package/readme.md +78 -0
|
@@ -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
|