everything-dev 2.0.0-rc.6 → 2.0.0-rc.8

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.
Files changed (148) hide show
  1. package/README.md +2 -2
  2. package/dist/api/auth-middleware.cjs +14 -1
  3. package/dist/api/auth-middleware.cjs.map +1 -1
  4. package/dist/api/auth-middleware.d.cts +9 -0
  5. package/dist/api/auth-middleware.d.mts +9 -0
  6. package/dist/api/auth-middleware.mjs +14 -1
  7. package/dist/api/auth-middleware.mjs.map +1 -1
  8. package/dist/build.cjs +1 -1
  9. package/dist/build.mjs +1 -1
  10. package/dist/bundle-cache.cjs +1 -1
  11. package/dist/bundle-cache.cjs.map +1 -1
  12. package/dist/bundle-cache.mjs +1 -1
  13. package/dist/bundle-cache.mjs.map +1 -1
  14. package/dist/cli/init.cjs +22 -10
  15. package/dist/cli/init.cjs.map +1 -1
  16. package/dist/cli/init.mjs +22 -10
  17. package/dist/cli/init.mjs.map +1 -1
  18. package/dist/cli.cjs +6 -5
  19. package/dist/cli.cjs.map +1 -1
  20. package/dist/cli.mjs +5 -4
  21. package/dist/cli.mjs.map +1 -1
  22. package/dist/commands/deploy.cjs +111 -1
  23. package/dist/commands/deploy.cjs.map +1 -1
  24. package/dist/commands/deploy.mjs +112 -2
  25. package/dist/commands/deploy.mjs.map +1 -1
  26. package/dist/commands/keys.cjs +1 -1
  27. package/dist/commands/keys.cjs.map +1 -1
  28. package/dist/commands/keys.mjs +1 -1
  29. package/dist/commands/keys.mjs.map +1 -1
  30. package/dist/commands/smoke.cjs +41 -0
  31. package/dist/commands/smoke.cjs.map +1 -0
  32. package/dist/commands/smoke.mjs +41 -0
  33. package/dist/commands/smoke.mjs.map +1 -0
  34. package/dist/commands/upgrade.cjs +2 -2
  35. package/dist/commands/upgrade.cjs.map +1 -1
  36. package/dist/commands/upgrade.mjs +1 -1
  37. package/dist/commands/upgrade.mjs.map +1 -1
  38. package/dist/contract.cjs +27 -1
  39. package/dist/contract.cjs.map +1 -1
  40. package/dist/contract.d.cts +65 -1
  41. package/dist/contract.d.mts +65 -1
  42. package/dist/contract.meta.cjs +14 -2
  43. package/dist/contract.meta.cjs.map +1 -1
  44. package/dist/contract.meta.d.cts +24 -2
  45. package/dist/contract.meta.d.mts +24 -2
  46. package/dist/contract.meta.mjs +14 -2
  47. package/dist/contract.meta.mjs.map +1 -1
  48. package/dist/contract.mjs +26 -2
  49. package/dist/contract.mjs.map +1 -1
  50. package/dist/db/core.cjs +22 -4
  51. package/dist/db/core.cjs.map +1 -1
  52. package/dist/db/core.mjs +22 -4
  53. package/dist/db/core.mjs.map +1 -1
  54. package/dist/dev-log-pipeline.cjs +6 -0
  55. package/dist/dev-log-pipeline.cjs.map +1 -1
  56. package/dist/dev-log-pipeline.mjs +6 -0
  57. package/dist/dev-log-pipeline.mjs.map +1 -1
  58. package/dist/dev-program.cjs +20 -3
  59. package/dist/dev-program.cjs.map +1 -1
  60. package/dist/dev-program.mjs +20 -4
  61. package/dist/dev-program.mjs.map +1 -1
  62. package/dist/dev-session.cjs +1 -0
  63. package/dist/dev-session.cjs.map +1 -1
  64. package/dist/dev-session.mjs +1 -0
  65. package/dist/dev-session.mjs.map +1 -1
  66. package/dist/fastkv.cjs +21 -3
  67. package/dist/fastkv.cjs.map +1 -1
  68. package/dist/fastkv.d.cts +14 -1
  69. package/dist/fastkv.d.mts +14 -1
  70. package/dist/fastkv.mjs +21 -5
  71. package/dist/fastkv.mjs.map +1 -1
  72. package/dist/{mf.cjs → federation.cjs} +6 -13
  73. package/dist/federation.cjs.map +1 -0
  74. package/dist/{mf.d.cts → federation.d.cts} +2 -2
  75. package/dist/{mf.d.mts → federation.d.mts} +2 -2
  76. package/dist/{mf.mjs → federation.mjs} +6 -13
  77. package/dist/federation.mjs.map +1 -0
  78. package/dist/http-client.cjs +84 -57
  79. package/dist/http-client.cjs.map +1 -1
  80. package/dist/http-client.mjs +84 -58
  81. package/dist/http-client.mjs.map +1 -1
  82. package/dist/image-deploy.cjs +9 -0
  83. package/dist/image-deploy.cjs.map +1 -1
  84. package/dist/image-deploy.mjs +9 -1
  85. package/dist/image-deploy.mjs.map +1 -1
  86. package/dist/index.cjs +4 -0
  87. package/dist/index.d.cts +3 -3
  88. package/dist/index.d.mts +3 -3
  89. package/dist/index.mjs +3 -3
  90. package/dist/infra/planner.cjs +6 -2
  91. package/dist/infra/planner.cjs.map +1 -1
  92. package/dist/infra/planner.mjs +6 -2
  93. package/dist/infra/planner.mjs.map +1 -1
  94. package/dist/orchestrator.cjs +39 -35
  95. package/dist/orchestrator.cjs.map +1 -1
  96. package/dist/orchestrator.mjs +39 -35
  97. package/dist/orchestrator.mjs.map +1 -1
  98. package/dist/plugin.cjs +2 -0
  99. package/dist/plugin.cjs.map +1 -1
  100. package/dist/plugin.d.cts +24 -0
  101. package/dist/plugin.d.mts +24 -0
  102. package/dist/plugin.mjs +2 -0
  103. package/dist/plugin.mjs.map +1 -1
  104. package/dist/process-registry.cjs +1 -1
  105. package/dist/process-registry.mjs +1 -1
  106. package/dist/publish.cjs +3 -3
  107. package/dist/publish.cjs.map +1 -1
  108. package/dist/publish.mjs +4 -4
  109. package/dist/publish.mjs.map +1 -1
  110. package/dist/resolver.cjs +293 -0
  111. package/dist/resolver.cjs.map +1 -0
  112. package/dist/resolver.d.cts +99 -0
  113. package/dist/resolver.d.mts +99 -0
  114. package/dist/resolver.mjs +284 -0
  115. package/dist/resolver.mjs.map +1 -0
  116. package/dist/smoke.cjs +262 -0
  117. package/dist/smoke.cjs.map +1 -0
  118. package/dist/smoke.mjs +260 -0
  119. package/dist/smoke.mjs.map +1 -0
  120. package/dist/ui/auth.d.cts +217 -123
  121. package/dist/ui/auth.d.mts +217 -123
  122. package/dist/ui/router-client.cjs +2 -1
  123. package/dist/ui/router-client.cjs.map +1 -1
  124. package/dist/ui/router-client.mjs +2 -1
  125. package/dist/ui/router-client.mjs.map +1 -1
  126. package/dist/version-manifest-resolve.cjs +3 -3
  127. package/dist/version-manifest-resolve.cjs.map +1 -1
  128. package/dist/version-manifest-resolve.mjs +1 -1
  129. package/dist/version-manifest-resolve.mjs.map +1 -1
  130. package/dist/workspace.cjs +1 -1
  131. package/dist/workspace.mjs +1 -1
  132. package/package.json +18 -18
  133. package/skills/api-and-auth/SKILL.md +12 -9
  134. package/skills/api-and-auth/references/middleware.md +38 -0
  135. package/skills/cli-reference/SKILL.md +22 -6
  136. package/skills/talk-to-the-app/SKILL.md +32 -5
  137. package/dist/bundle-fs-resolve.cjs +0 -235
  138. package/dist/bundle-fs-resolve.cjs.map +0 -1
  139. package/dist/bundle-fs-resolve.d.cts +0 -82
  140. package/dist/bundle-fs-resolve.d.mts +0 -82
  141. package/dist/bundle-fs-resolve.mjs +0 -228
  142. package/dist/bundle-fs-resolve.mjs.map +0 -1
  143. package/dist/bundle-path.cjs +0 -48
  144. package/dist/bundle-path.cjs.map +0 -1
  145. package/dist/bundle-path.mjs +0 -45
  146. package/dist/bundle-path.mjs.map +0 -1
  147. package/dist/mf.cjs.map +0 -1
  148. package/dist/mf.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.cjs","names":["Data","Duration","Effect","Schedule"],"sources":["../src/http-client.ts"],"sourcesContent":["import { Data, Duration, Effect, Schedule } from \"effect\";\n\n// --- Tagged errors ---\n\nexport class FetchNetworkError extends Data.TaggedError(\"FetchNetworkError\")<{\n url: string;\n cause: unknown;\n}> {}\n\nexport class FetchTimeoutError extends Data.TaggedError(\"FetchTimeoutError\")<{\n url: string;\n}> {}\n\nexport class FetchHttpError extends Data.TaggedError(\"FetchHttpError\")<{\n url: string;\n status: number;\n statusText: string;\n}> {}\n\nexport type FetchError = FetchNetworkError | FetchTimeoutError | FetchHttpError;\n\n// --- Options ---\n\nexport interface FetchOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: BodyInit;\n redirect?: RequestRedirect;\n timeout?: Duration.Input;\n}\n\nexport interface FetchWithRetryOptions extends FetchOptions {\n retries?: number;\n}\n\n// --- Defaults ---\n\nconst DEFAULT_TIMEOUT = \"10 seconds\";\nconst DEFAULT_RETRIES = 3;\n\nconst EXPONENTIAL_BASE = Duration.seconds(1);\nconst EXPONENTIAL_CAP = Duration.seconds(15);\n\n// --- Helpers ---\n\nconst isRetryable = (error: FetchError): boolean => {\n if (error instanceof FetchNetworkError) return true;\n if (error instanceof FetchTimeoutError) return true;\n if (error instanceof FetchHttpError) return error.status >= 500;\n return false;\n};\n\n// --- Low-level: just timeout + error classification, returns Response regardless of HTTP status ---\n\nconst runFetch = (url: string, init?: RequestInit): Promise<Response> => fetch(url, init);\n\nconst fetchRawEff = (\n url: string,\n options?: FetchOptions,\n): Effect.Effect<Response, FetchNetworkError | FetchTimeoutError> =>\n Effect.tryPromise({\n try: async () => {\n const timeoutMs = Duration.toMillis(options?.timeout ?? DEFAULT_TIMEOUT);\n try {\n return await runFetch(url, {\n method: options?.method ?? \"GET\",\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (\n error instanceof Error &&\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n ) {\n throw new FetchTimeoutError({ url });\n }\n throw new FetchNetworkError({ url, cause: error });\n }\n },\n catch: (error) => {\n if (error instanceof FetchNetworkError || error instanceof FetchTimeoutError) {\n return error;\n }\n return new FetchNetworkError({ url, cause: error });\n },\n });\n\n// --- Adds HTTP status checking: non-2xx becomes FetchHttpError ---\n\nconst fetchEff = (url: string, options?: FetchOptions): Effect.Effect<Response, FetchError> =>\n fetchRawEff(url, options).pipe(\n Effect.filterOrFail(\n (response) => response.ok,\n (response) =>\n new FetchHttpError({ url, status: response.status, statusText: response.statusText }),\n ),\n );\n\n// --- With retry ---\n\nconst retrySchedule = Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: DEFAULT_RETRIES }),\n);\n\nexport const fetchWithRetryEff = (\n url: string,\n options?: FetchWithRetryOptions,\n): Effect.Effect<Response, FetchError> => {\n const retries = options?.retries ?? DEFAULT_RETRIES;\n\n if (retries <= 0) return fetchEff(url, options);\n\n const schedule =\n options?.retries !== undefined\n ? Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: retries }),\n )\n : retrySchedule;\n\n return fetchEff(url, options).pipe(\n Effect.retry({\n schedule,\n while: isRetryable,\n }),\n );\n};\n\n// --- In-memory GET cache (per-process, prevents duplicate round trips) ---\n\nconst getCache = new Map<string, { data: unknown; expiresAt: number }>();\nconst GET_CACHE_TTL_MS = 30_000;\n\nexport function clearHttpCache(): void {\n getCache.clear();\n}\n\nfunction isCacheable(_url: string, options?: FetchWithRetryOptions): boolean {\n if (options?.method && options.method !== \"GET\") return false;\n if (options?.body) return false;\n return true;\n}\n\n// --- Promise wrappers (bridge for non-Effect code) ---\n\nexport const fetchResponse = (url: string, options?: FetchOptions): Promise<Response> =>\n Effect.runPromise(fetchRawEff(url, options));\n\nexport const fetchJsonOrNull = async <T>(\n url: string,\n options?: FetchWithRetryOptions,\n): Promise<T | null> => {\n if (isCacheable(url, options)) {\n const cached = getCache.get(url);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.data as T;\n }\n }\n\n const retries = options?.retries ?? DEFAULT_RETRIES;\n const eff =\n retries > 0 ? fetchWithRetryEff(url, { ...options, retries }) : fetchEff(url, options);\n\n try {\n const res = await Effect.runPromise(eff);\n const data = (await res.json()) as T;\n if (isCacheable(url, options)) {\n getCache.set(url, { data, expiresAt: Date.now() + GET_CACHE_TTL_MS });\n }\n return data;\n } catch (error) {\n const msg =\n error instanceof FetchNetworkError\n ? `[http] Network error: ${error.url} — ${String(error.cause)}`\n : error instanceof FetchTimeoutError\n ? `[http] Timeout: ${error.url}`\n : error instanceof FetchHttpError\n ? `[http] HTTP ${error.status}: ${error.url}`\n : `[http] Unknown error while fetching ${url}`;\n console.error(msg);\n return null;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-client.cjs","names":["Data","Duration","Effect","Schedule","Context","Layer"],"sources":["../src/http-client.ts"],"sourcesContent":["import { Context, Data, Duration, Effect, Layer, Schedule } from \"effect\";\n\n// --- Tagged errors ---\n\nexport class FetchNetworkError extends Data.TaggedError(\"FetchNetworkError\")<{\n url: string;\n cause: unknown;\n}> {}\n\nexport class FetchTimeoutError extends Data.TaggedError(\"FetchTimeoutError\")<{\n url: string;\n}> {}\n\nexport class FetchHttpError extends Data.TaggedError(\"FetchHttpError\")<{\n url: string;\n status: number;\n statusText: string;\n}> {}\n\nexport type FetchError = FetchNetworkError | FetchTimeoutError | FetchHttpError;\n\n// --- Options ---\n\nexport interface FetchOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: BodyInit;\n redirect?: RequestRedirect;\n timeout?: Duration.Input;\n}\n\nexport interface FetchWithRetryOptions extends FetchOptions {\n retries?: number;\n}\n\n// --- Defaults ---\n\nconst DEFAULT_TIMEOUT = \"10 seconds\";\nconst DEFAULT_RETRIES = 3;\n\nconst EXPONENTIAL_BASE = Duration.seconds(1);\nconst EXPONENTIAL_CAP = Duration.seconds(15);\n\ntype FetchImpl = typeof globalThis.fetch;\n\n// --- Helpers ---\n\nconst isRetryable = (error: FetchError): boolean => {\n if (error instanceof FetchNetworkError) return true;\n if (error instanceof FetchTimeoutError) return true;\n if (error instanceof FetchHttpError) return error.status >= 500;\n return false;\n};\n\nexport interface HttpFetchShape {\n /** One network attempt: no status assertion, no internal deadline. The\n * fiber's signal is composed with any caller-provided signal, so an\n * `Effect.timeout` around the call aborts the in-flight request, and a\n * caller-initiated cancellation still wins. */\n readonly attempt: (\n input: string | URL | Request,\n init?: RequestInit,\n ) => Effect.Effect<Response, FetchNetworkError>;\n /** attempt + deadline + ok assertion (non-2xx → `FetchHttpError`) + capped\n * exponential jittered retry (5xx/network/timeout retried, 4xx not). */\n readonly get: (\n url: string,\n options?: FetchWithRetryOptions,\n ) => Effect.Effect<Response, FetchError>;\n}\n\nconst makeHttpFetch = (\n base: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,\n): HttpFetchShape => {\n const urlOf = (input: string | URL | Request): string =>\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n const attempt = (\n input: string | URL | Request,\n init?: RequestInit,\n ): Effect.Effect<Response, FetchNetworkError> =>\n Effect.tryPromise({\n try: (signal) =>\n base(input, {\n ...init,\n signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal,\n }),\n catch: (cause) => new FetchNetworkError({ url: urlOf(input), cause }),\n });\n\n const get = (\n url: string,\n options?: FetchWithRetryOptions,\n ): Effect.Effect<Response, FetchError> => {\n const timeoutMs = Duration.toMillis(options?.timeout ?? DEFAULT_TIMEOUT);\n const retries = options?.retries ?? DEFAULT_RETRIES;\n const once = attempt(url, {\n method: options?.method ?? \"GET\",\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n }).pipe(\n Effect.timeoutOrElse({\n duration: timeoutMs,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n Effect.filterOrFail(\n (response) => response.ok,\n (response) =>\n new FetchHttpError({ url, status: response.status, statusText: response.statusText }),\n ),\n );\n if (retries <= 0) return once;\n return once.pipe(\n Effect.retry({\n schedule: Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: retries }),\n Schedule.jittered,\n ),\n while: isRetryable,\n }),\n );\n };\n\n return { attempt, get };\n};\n\nconst defaultHttpFetch = makeHttpFetch((url, init) => globalThis.fetch(url, init));\n\n/**\n * The HTTP seam for boot-path consumers (bundle resolution, federation\n * loading, orchestrator deadlines): one resilience policy — interruption-\n * backed deadlines, capped exponential jittered retry, typed errors — with\n * the base fetch injectable so the bundle fetch interceptor can point it at\n * the pre-patch `fetch` and not re-enter itself.\n */\nexport class HttpFetch extends Context.Service<HttpFetch, HttpFetchShape>()(\n \"everything-dev/http-client/HttpFetch\",\n) {\n static layerWith(options: { fetchImpl: FetchImpl }): Layer.Layer<HttpFetch> {\n return Layer.succeed(HttpFetch, makeHttpFetch(options.fetchImpl));\n }\n\n static readonly layer: Layer.Layer<HttpFetch> = Layer.succeed(HttpFetch, defaultHttpFetch);\n}\n\n// --- In-memory GET cache (per-process, prevents duplicate round trips) ---\n\nconst getCache = new Map<string, { data: unknown; expiresAt: number }>();\nconst GET_CACHE_TTL_MS = 30_000;\nconst GET_CACHE_MAX_ENTRIES = 128;\n\nexport function clearHttpCache(): void {\n getCache.clear();\n}\n\nexport function httpCacheSize(): number {\n return getCache.size;\n}\n\nfunction setCached(url: string, data: unknown): void {\n const now = Date.now();\n for (const [key, entry] of getCache) {\n if (entry.expiresAt <= now) getCache.delete(key);\n }\n if (getCache.size >= GET_CACHE_MAX_ENTRIES) {\n for (const key of getCache.keys()) {\n getCache.delete(key);\n if (getCache.size < GET_CACHE_MAX_ENTRIES) break;\n }\n }\n getCache.set(url, { data, expiresAt: now + GET_CACHE_TTL_MS });\n}\n\nfunction isCacheable(_url: string, options?: FetchWithRetryOptions): boolean {\n if (options?.method && options.method !== \"GET\") return false;\n if (options?.body) return false;\n return true;\n}\n\n// --- Promise wrappers (bridge for non-Effect code) — the ambient call-time\n// lookup means a boot fetch crossing the bundle interceptor still gains its\n// retry/deadline policy; the fiber-signal composition aborts the in-flight\n// request at the deadline. ---\n\n/** One raw fetch attempt with a deadline: returns the Response regardless of\n * status; rejects on network failure or deadline expiry. */\nexport const fetchRaw = (\n url: string,\n init?: RequestInit,\n timeout: Duration.Input = DEFAULT_TIMEOUT,\n): Promise<Response> =>\n Effect.runPromise(\n defaultHttpFetch.attempt(url, init).pipe(\n Effect.timeoutOrElse({\n duration: timeout,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n ),\n );\n\nexport const fetchResponse = (url: string, options?: FetchOptions): Promise<Response> =>\n Effect.runPromise(\n defaultHttpFetch\n .attempt(url, {\n method: options?.method,\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n })\n .pipe(\n Effect.timeoutOrElse({\n duration: options?.timeout ?? DEFAULT_TIMEOUT,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n ),\n );\n\nexport const fetchJsonOrNull = async <T>(\n url: string,\n options?: FetchWithRetryOptions,\n): Promise<T | null> => {\n if (isCacheable(url, options)) {\n const cached = getCache.get(url);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.data as T;\n }\n }\n\n try {\n const res = await Effect.runPromise(defaultHttpFetch.get(url, options));\n const data = (await res.json()) as T;\n if (isCacheable(url, options)) {\n setCached(url, data);\n }\n return data;\n } catch (error) {\n const msg =\n error instanceof FetchNetworkError\n ? `[http] Network error: ${error.url} — ${String(error.cause)}`\n : error instanceof FetchTimeoutError\n ? `[http] Timeout: ${error.url}`\n : error instanceof FetchHttpError\n ? `[http] HTTP ${error.status}: ${error.url}`\n : `[http] Unknown error while fetching ${url}`;\n console.error(msg);\n return null;\n }\n};\n\nexport const fetchWithRetryEff = (\n url: string,\n options?: FetchWithRetryOptions,\n): Effect.Effect<Response, FetchError> => defaultHttpFetch.get(url, options);\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Data, Duration, Effect, Schedule } from "effect";
1
+ import { Context, Data, Duration, Effect, Layer, Schedule } from "effect";
2
2
 
3
3
  //#region src/http-client.ts
4
4
  var FetchNetworkError = class extends Data.TaggedError("FetchNetworkError") {};
@@ -14,79 +14,105 @@ const isRetryable = (error) => {
14
14
  if (error instanceof FetchHttpError) return error.status >= 500;
15
15
  return false;
16
16
  };
17
- const runFetch = (url, init) => fetch(url, init);
18
- const fetchRawEff = (url, options) => Effect.tryPromise({
19
- try: async () => {
17
+ const makeHttpFetch = (base) => {
18
+ const urlOf = (input) => typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
19
+ const attempt = (input, init) => Effect.tryPromise({
20
+ try: (signal) => base(input, {
21
+ ...init,
22
+ signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal
23
+ }),
24
+ catch: (cause) => new FetchNetworkError({
25
+ url: urlOf(input),
26
+ cause
27
+ })
28
+ });
29
+ const get = (url, options) => {
20
30
  const timeoutMs = Duration.toMillis(options?.timeout ?? DEFAULT_TIMEOUT);
21
- try {
22
- return await runFetch(url, {
23
- method: options?.method ?? "GET",
24
- headers: options?.headers,
25
- body: options?.body,
26
- redirect: options?.redirect,
27
- signal: AbortSignal.timeout(timeoutMs)
28
- });
29
- } catch (error) {
30
- if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) throw new FetchTimeoutError({ url });
31
- throw new FetchNetworkError({
32
- url,
33
- cause: error
34
- });
35
- }
36
- },
37
- catch: (error) => {
38
- if (error instanceof FetchNetworkError || error instanceof FetchTimeoutError) return error;
39
- return new FetchNetworkError({
31
+ const retries = options?.retries ?? DEFAULT_RETRIES;
32
+ const once = attempt(url, {
33
+ method: options?.method ?? "GET",
34
+ headers: options?.headers,
35
+ body: options?.body,
36
+ redirect: options?.redirect
37
+ }).pipe(Effect.timeoutOrElse({
38
+ duration: timeoutMs,
39
+ orElse: () => Effect.fail(new FetchTimeoutError({ url }))
40
+ }), Effect.filterOrFail((response) => response.ok, (response) => new FetchHttpError({
40
41
  url,
41
- cause: error
42
- });
42
+ status: response.status,
43
+ statusText: response.statusText
44
+ })));
45
+ if (retries <= 0) return once;
46
+ return once.pipe(Effect.retry({
47
+ schedule: Schedule.exponential(EXPONENTIAL_BASE).pipe(Schedule.upTo({
48
+ duration: EXPONENTIAL_CAP,
49
+ times: retries
50
+ }), Schedule.jittered),
51
+ while: isRetryable
52
+ }));
53
+ };
54
+ return {
55
+ attempt,
56
+ get
57
+ };
58
+ };
59
+ const defaultHttpFetch = makeHttpFetch((url, init) => globalThis.fetch(url, init));
60
+ /**
61
+ * The HTTP seam for boot-path consumers (bundle resolution, federation
62
+ * loading, orchestrator deadlines): one resilience policy — interruption-
63
+ * backed deadlines, capped exponential jittered retry, typed errors — with
64
+ * the base fetch injectable so the bundle fetch interceptor can point it at
65
+ * the pre-patch `fetch` and not re-enter itself.
66
+ */
67
+ var HttpFetch = class HttpFetch extends Context.Service()("everything-dev/http-client/HttpFetch") {
68
+ static layerWith(options) {
69
+ return Layer.succeed(HttpFetch, makeHttpFetch(options.fetchImpl));
43
70
  }
44
- });
45
- const fetchEff = (url, options) => fetchRawEff(url, options).pipe(Effect.filterOrFail((response) => response.ok, (response) => new FetchHttpError({
46
- url,
47
- status: response.status,
48
- statusText: response.statusText
49
- })));
50
- const retrySchedule = Schedule.exponential(EXPONENTIAL_BASE).pipe(Schedule.upTo({
51
- duration: EXPONENTIAL_CAP,
52
- times: DEFAULT_RETRIES
53
- }));
54
- const fetchWithRetryEff = (url, options) => {
55
- const retries = options?.retries ?? DEFAULT_RETRIES;
56
- if (retries <= 0) return fetchEff(url, options);
57
- const schedule = options?.retries !== void 0 ? Schedule.exponential(EXPONENTIAL_BASE).pipe(Schedule.upTo({
58
- duration: EXPONENTIAL_CAP,
59
- times: retries
60
- })) : retrySchedule;
61
- return fetchEff(url, options).pipe(Effect.retry({
62
- schedule,
63
- while: isRetryable
64
- }));
71
+ static layer = Layer.succeed(HttpFetch, defaultHttpFetch);
65
72
  };
66
73
  const getCache = /* @__PURE__ */ new Map();
67
74
  const GET_CACHE_TTL_MS = 3e4;
75
+ const GET_CACHE_MAX_ENTRIES = 128;
76
+ function setCached(url, data) {
77
+ const now = Date.now();
78
+ for (const [key, entry] of getCache) if (entry.expiresAt <= now) getCache.delete(key);
79
+ if (getCache.size >= GET_CACHE_MAX_ENTRIES) for (const key of getCache.keys()) {
80
+ getCache.delete(key);
81
+ if (getCache.size < GET_CACHE_MAX_ENTRIES) break;
82
+ }
83
+ getCache.set(url, {
84
+ data,
85
+ expiresAt: now + GET_CACHE_TTL_MS
86
+ });
87
+ }
68
88
  function isCacheable(_url, options) {
69
89
  if (options?.method && options.method !== "GET") return false;
70
90
  if (options?.body) return false;
71
91
  return true;
72
92
  }
73
- const fetchResponse = (url, options) => Effect.runPromise(fetchRawEff(url, options));
93
+ /** One raw fetch attempt with a deadline: returns the Response regardless of
94
+ * status; rejects on network failure or deadline expiry. */
95
+ const fetchRaw = (url, init, timeout = DEFAULT_TIMEOUT) => Effect.runPromise(defaultHttpFetch.attempt(url, init).pipe(Effect.timeoutOrElse({
96
+ duration: timeout,
97
+ orElse: () => Effect.fail(new FetchTimeoutError({ url }))
98
+ })));
99
+ const fetchResponse = (url, options) => Effect.runPromise(defaultHttpFetch.attempt(url, {
100
+ method: options?.method,
101
+ headers: options?.headers,
102
+ body: options?.body,
103
+ redirect: options?.redirect
104
+ }).pipe(Effect.timeoutOrElse({
105
+ duration: options?.timeout ?? DEFAULT_TIMEOUT,
106
+ orElse: () => Effect.fail(new FetchTimeoutError({ url }))
107
+ })));
74
108
  const fetchJsonOrNull = async (url, options) => {
75
109
  if (isCacheable(url, options)) {
76
110
  const cached = getCache.get(url);
77
111
  if (cached && cached.expiresAt > Date.now()) return cached.data;
78
112
  }
79
- const retries = options?.retries ?? DEFAULT_RETRIES;
80
- const eff = retries > 0 ? fetchWithRetryEff(url, {
81
- ...options,
82
- retries
83
- }) : fetchEff(url, options);
84
113
  try {
85
- const data = await (await Effect.runPromise(eff)).json();
86
- if (isCacheable(url, options)) getCache.set(url, {
87
- data,
88
- expiresAt: Date.now() + GET_CACHE_TTL_MS
89
- });
114
+ const data = await (await Effect.runPromise(defaultHttpFetch.get(url, options))).json();
115
+ if (isCacheable(url, options)) setCached(url, data);
90
116
  return data;
91
117
  } catch (error) {
92
118
  const msg = error instanceof FetchNetworkError ? `[http] Network error: ${error.url} — ${String(error.cause)}` : error instanceof FetchTimeoutError ? `[http] Timeout: ${error.url}` : error instanceof FetchHttpError ? `[http] HTTP ${error.status}: ${error.url}` : `[http] Unknown error while fetching ${url}`;
@@ -96,5 +122,5 @@ const fetchJsonOrNull = async (url, options) => {
96
122
  };
97
123
 
98
124
  //#endregion
99
- export { FetchHttpError, FetchNetworkError, FetchTimeoutError, fetchJsonOrNull, fetchResponse, fetchWithRetryEff };
125
+ export { FetchHttpError, FetchNetworkError, FetchTimeoutError, HttpFetch, fetchJsonOrNull, fetchRaw, fetchResponse };
100
126
  //# sourceMappingURL=http-client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.mjs","names":[],"sources":["../src/http-client.ts"],"sourcesContent":["import { Data, Duration, Effect, Schedule } from \"effect\";\n\n// --- Tagged errors ---\n\nexport class FetchNetworkError extends Data.TaggedError(\"FetchNetworkError\")<{\n url: string;\n cause: unknown;\n}> {}\n\nexport class FetchTimeoutError extends Data.TaggedError(\"FetchTimeoutError\")<{\n url: string;\n}> {}\n\nexport class FetchHttpError extends Data.TaggedError(\"FetchHttpError\")<{\n url: string;\n status: number;\n statusText: string;\n}> {}\n\nexport type FetchError = FetchNetworkError | FetchTimeoutError | FetchHttpError;\n\n// --- Options ---\n\nexport interface FetchOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: BodyInit;\n redirect?: RequestRedirect;\n timeout?: Duration.Input;\n}\n\nexport interface FetchWithRetryOptions extends FetchOptions {\n retries?: number;\n}\n\n// --- Defaults ---\n\nconst DEFAULT_TIMEOUT = \"10 seconds\";\nconst DEFAULT_RETRIES = 3;\n\nconst EXPONENTIAL_BASE = Duration.seconds(1);\nconst EXPONENTIAL_CAP = Duration.seconds(15);\n\n// --- Helpers ---\n\nconst isRetryable = (error: FetchError): boolean => {\n if (error instanceof FetchNetworkError) return true;\n if (error instanceof FetchTimeoutError) return true;\n if (error instanceof FetchHttpError) return error.status >= 500;\n return false;\n};\n\n// --- Low-level: just timeout + error classification, returns Response regardless of HTTP status ---\n\nconst runFetch = (url: string, init?: RequestInit): Promise<Response> => fetch(url, init);\n\nconst fetchRawEff = (\n url: string,\n options?: FetchOptions,\n): Effect.Effect<Response, FetchNetworkError | FetchTimeoutError> =>\n Effect.tryPromise({\n try: async () => {\n const timeoutMs = Duration.toMillis(options?.timeout ?? DEFAULT_TIMEOUT);\n try {\n return await runFetch(url, {\n method: options?.method ?? \"GET\",\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (\n error instanceof Error &&\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n ) {\n throw new FetchTimeoutError({ url });\n }\n throw new FetchNetworkError({ url, cause: error });\n }\n },\n catch: (error) => {\n if (error instanceof FetchNetworkError || error instanceof FetchTimeoutError) {\n return error;\n }\n return new FetchNetworkError({ url, cause: error });\n },\n });\n\n// --- Adds HTTP status checking: non-2xx becomes FetchHttpError ---\n\nconst fetchEff = (url: string, options?: FetchOptions): Effect.Effect<Response, FetchError> =>\n fetchRawEff(url, options).pipe(\n Effect.filterOrFail(\n (response) => response.ok,\n (response) =>\n new FetchHttpError({ url, status: response.status, statusText: response.statusText }),\n ),\n );\n\n// --- With retry ---\n\nconst retrySchedule = Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: DEFAULT_RETRIES }),\n);\n\nexport const fetchWithRetryEff = (\n url: string,\n options?: FetchWithRetryOptions,\n): Effect.Effect<Response, FetchError> => {\n const retries = options?.retries ?? DEFAULT_RETRIES;\n\n if (retries <= 0) return fetchEff(url, options);\n\n const schedule =\n options?.retries !== undefined\n ? Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: retries }),\n )\n : retrySchedule;\n\n return fetchEff(url, options).pipe(\n Effect.retry({\n schedule,\n while: isRetryable,\n }),\n );\n};\n\n// --- In-memory GET cache (per-process, prevents duplicate round trips) ---\n\nconst getCache = new Map<string, { data: unknown; expiresAt: number }>();\nconst GET_CACHE_TTL_MS = 30_000;\n\nexport function clearHttpCache(): void {\n getCache.clear();\n}\n\nfunction isCacheable(_url: string, options?: FetchWithRetryOptions): boolean {\n if (options?.method && options.method !== \"GET\") return false;\n if (options?.body) return false;\n return true;\n}\n\n// --- Promise wrappers (bridge for non-Effect code) ---\n\nexport const fetchResponse = (url: string, options?: FetchOptions): Promise<Response> =>\n Effect.runPromise(fetchRawEff(url, options));\n\nexport const fetchJsonOrNull = async <T>(\n url: string,\n options?: FetchWithRetryOptions,\n): Promise<T | null> => {\n if (isCacheable(url, options)) {\n const cached = getCache.get(url);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.data as T;\n }\n }\n\n const retries = options?.retries ?? DEFAULT_RETRIES;\n const eff =\n retries > 0 ? fetchWithRetryEff(url, { ...options, retries }) : fetchEff(url, options);\n\n try {\n const res = await Effect.runPromise(eff);\n const data = (await res.json()) as T;\n if (isCacheable(url, options)) {\n getCache.set(url, { data, expiresAt: Date.now() + GET_CACHE_TTL_MS });\n }\n return data;\n } catch (error) {\n const msg =\n error instanceof FetchNetworkError\n ? `[http] Network error: ${error.url} — ${String(error.cause)}`\n : error instanceof FetchTimeoutError\n ? `[http] Timeout: ${error.url}`\n : error instanceof FetchHttpError\n ? `[http] HTTP ${error.status}: ${error.url}`\n : `[http] Unknown error while fetching ${url}`;\n console.error(msg);\n return null;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-client.mjs","names":[],"sources":["../src/http-client.ts"],"sourcesContent":["import { Context, Data, Duration, Effect, Layer, Schedule } from \"effect\";\n\n// --- Tagged errors ---\n\nexport class FetchNetworkError extends Data.TaggedError(\"FetchNetworkError\")<{\n url: string;\n cause: unknown;\n}> {}\n\nexport class FetchTimeoutError extends Data.TaggedError(\"FetchTimeoutError\")<{\n url: string;\n}> {}\n\nexport class FetchHttpError extends Data.TaggedError(\"FetchHttpError\")<{\n url: string;\n status: number;\n statusText: string;\n}> {}\n\nexport type FetchError = FetchNetworkError | FetchTimeoutError | FetchHttpError;\n\n// --- Options ---\n\nexport interface FetchOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: BodyInit;\n redirect?: RequestRedirect;\n timeout?: Duration.Input;\n}\n\nexport interface FetchWithRetryOptions extends FetchOptions {\n retries?: number;\n}\n\n// --- Defaults ---\n\nconst DEFAULT_TIMEOUT = \"10 seconds\";\nconst DEFAULT_RETRIES = 3;\n\nconst EXPONENTIAL_BASE = Duration.seconds(1);\nconst EXPONENTIAL_CAP = Duration.seconds(15);\n\ntype FetchImpl = typeof globalThis.fetch;\n\n// --- Helpers ---\n\nconst isRetryable = (error: FetchError): boolean => {\n if (error instanceof FetchNetworkError) return true;\n if (error instanceof FetchTimeoutError) return true;\n if (error instanceof FetchHttpError) return error.status >= 500;\n return false;\n};\n\nexport interface HttpFetchShape {\n /** One network attempt: no status assertion, no internal deadline. The\n * fiber's signal is composed with any caller-provided signal, so an\n * `Effect.timeout` around the call aborts the in-flight request, and a\n * caller-initiated cancellation still wins. */\n readonly attempt: (\n input: string | URL | Request,\n init?: RequestInit,\n ) => Effect.Effect<Response, FetchNetworkError>;\n /** attempt + deadline + ok assertion (non-2xx → `FetchHttpError`) + capped\n * exponential jittered retry (5xx/network/timeout retried, 4xx not). */\n readonly get: (\n url: string,\n options?: FetchWithRetryOptions,\n ) => Effect.Effect<Response, FetchError>;\n}\n\nconst makeHttpFetch = (\n base: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,\n): HttpFetchShape => {\n const urlOf = (input: string | URL | Request): string =>\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n const attempt = (\n input: string | URL | Request,\n init?: RequestInit,\n ): Effect.Effect<Response, FetchNetworkError> =>\n Effect.tryPromise({\n try: (signal) =>\n base(input, {\n ...init,\n signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal,\n }),\n catch: (cause) => new FetchNetworkError({ url: urlOf(input), cause }),\n });\n\n const get = (\n url: string,\n options?: FetchWithRetryOptions,\n ): Effect.Effect<Response, FetchError> => {\n const timeoutMs = Duration.toMillis(options?.timeout ?? DEFAULT_TIMEOUT);\n const retries = options?.retries ?? DEFAULT_RETRIES;\n const once = attempt(url, {\n method: options?.method ?? \"GET\",\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n }).pipe(\n Effect.timeoutOrElse({\n duration: timeoutMs,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n Effect.filterOrFail(\n (response) => response.ok,\n (response) =>\n new FetchHttpError({ url, status: response.status, statusText: response.statusText }),\n ),\n );\n if (retries <= 0) return once;\n return once.pipe(\n Effect.retry({\n schedule: Schedule.exponential(EXPONENTIAL_BASE).pipe(\n Schedule.upTo({ duration: EXPONENTIAL_CAP, times: retries }),\n Schedule.jittered,\n ),\n while: isRetryable,\n }),\n );\n };\n\n return { attempt, get };\n};\n\nconst defaultHttpFetch = makeHttpFetch((url, init) => globalThis.fetch(url, init));\n\n/**\n * The HTTP seam for boot-path consumers (bundle resolution, federation\n * loading, orchestrator deadlines): one resilience policy — interruption-\n * backed deadlines, capped exponential jittered retry, typed errors — with\n * the base fetch injectable so the bundle fetch interceptor can point it at\n * the pre-patch `fetch` and not re-enter itself.\n */\nexport class HttpFetch extends Context.Service<HttpFetch, HttpFetchShape>()(\n \"everything-dev/http-client/HttpFetch\",\n) {\n static layerWith(options: { fetchImpl: FetchImpl }): Layer.Layer<HttpFetch> {\n return Layer.succeed(HttpFetch, makeHttpFetch(options.fetchImpl));\n }\n\n static readonly layer: Layer.Layer<HttpFetch> = Layer.succeed(HttpFetch, defaultHttpFetch);\n}\n\n// --- In-memory GET cache (per-process, prevents duplicate round trips) ---\n\nconst getCache = new Map<string, { data: unknown; expiresAt: number }>();\nconst GET_CACHE_TTL_MS = 30_000;\nconst GET_CACHE_MAX_ENTRIES = 128;\n\nexport function clearHttpCache(): void {\n getCache.clear();\n}\n\nexport function httpCacheSize(): number {\n return getCache.size;\n}\n\nfunction setCached(url: string, data: unknown): void {\n const now = Date.now();\n for (const [key, entry] of getCache) {\n if (entry.expiresAt <= now) getCache.delete(key);\n }\n if (getCache.size >= GET_CACHE_MAX_ENTRIES) {\n for (const key of getCache.keys()) {\n getCache.delete(key);\n if (getCache.size < GET_CACHE_MAX_ENTRIES) break;\n }\n }\n getCache.set(url, { data, expiresAt: now + GET_CACHE_TTL_MS });\n}\n\nfunction isCacheable(_url: string, options?: FetchWithRetryOptions): boolean {\n if (options?.method && options.method !== \"GET\") return false;\n if (options?.body) return false;\n return true;\n}\n\n// --- Promise wrappers (bridge for non-Effect code) — the ambient call-time\n// lookup means a boot fetch crossing the bundle interceptor still gains its\n// retry/deadline policy; the fiber-signal composition aborts the in-flight\n// request at the deadline. ---\n\n/** One raw fetch attempt with a deadline: returns the Response regardless of\n * status; rejects on network failure or deadline expiry. */\nexport const fetchRaw = (\n url: string,\n init?: RequestInit,\n timeout: Duration.Input = DEFAULT_TIMEOUT,\n): Promise<Response> =>\n Effect.runPromise(\n defaultHttpFetch.attempt(url, init).pipe(\n Effect.timeoutOrElse({\n duration: timeout,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n ),\n );\n\nexport const fetchResponse = (url: string, options?: FetchOptions): Promise<Response> =>\n Effect.runPromise(\n defaultHttpFetch\n .attempt(url, {\n method: options?.method,\n headers: options?.headers,\n body: options?.body,\n redirect: options?.redirect,\n })\n .pipe(\n Effect.timeoutOrElse({\n duration: options?.timeout ?? DEFAULT_TIMEOUT,\n orElse: () => Effect.fail(new FetchTimeoutError({ url })),\n }),\n ),\n );\n\nexport const fetchJsonOrNull = async <T>(\n url: string,\n options?: FetchWithRetryOptions,\n): Promise<T | null> => {\n if (isCacheable(url, options)) {\n const cached = getCache.get(url);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.data as T;\n }\n }\n\n try {\n const res = await Effect.runPromise(defaultHttpFetch.get(url, options));\n const data = (await res.json()) as T;\n if (isCacheable(url, options)) {\n setCached(url, data);\n }\n return data;\n } catch (error) {\n const msg =\n error instanceof FetchNetworkError\n ? `[http] Network error: ${error.url} — ${String(error.cause)}`\n : error instanceof FetchTimeoutError\n ? `[http] Timeout: ${error.url}`\n : error instanceof FetchHttpError\n ? `[http] HTTP ${error.status}: ${error.url}`\n : `[http] Unknown error while fetching ${url}`;\n console.error(msg);\n return null;\n }\n};\n\nexport const fetchWithRetryEff = (\n url: string,\n options?: FetchWithRetryOptions,\n): Effect.Effect<Response, FetchError> => defaultHttpFetch.get(url, options);\n"],"mappings":"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"}
@@ -25,6 +25,14 @@ function resolveImageRef(input) {
25
25
  source: "repository"
26
26
  };
27
27
  }
28
+ /** Compose the runnable image ref: `image@sha256:…` when a digest exists,
29
+ * `image` alone otherwise. Never docker-run the bare ref in a deploy flow —
30
+ * the implicit `:latest` is held to the last stable release during
31
+ * prereleases (ADR 0021), which boots a different framework build than the
32
+ * one this train pinned. */
33
+ function imageRefWithDigest(image, digest) {
34
+ return digest ? `${image}@${digest}` : image;
35
+ }
28
36
  async function hasDocker() {
29
37
  try {
30
38
  return (await require_run.run("docker", [
@@ -252,6 +260,7 @@ exports.buildAndPushImage = buildAndPushImage;
252
260
  exports.computeImageTags = computeImageTags;
253
261
  exports.deployImageToRailway = deployImageToRailway;
254
262
  exports.hasDocker = hasDocker;
263
+ exports.imageRefWithDigest = imageRefWithDigest;
255
264
  exports.isValidImageDigest = isValidImageDigest;
256
265
  exports.resolveDeployImagePlan = resolveDeployImagePlan;
257
266
  exports.resolveImageRef = resolveImageRef;
@@ -1 +1 @@
1
- {"version":3,"file":"image-deploy.cjs","names":["run","readFileSync","join","colors","icons"],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021, amended): image-backed deploy. The service's source\n * is a Docker image ref (connected once in the dashboard — `:rc` during the\n * prerelease train), and `redeploy --from-source` pulls and deploys whatever\n * that ref points at. The image leg pushes `:rc` (and the `sha-<short>` /\n * version tags) immediately before this step, so the pulled bytes are exactly\n * what this train validated. Railway skips the build entirely for\n * Docker-image sources — nothing is uploaded, nothing is built on Railway,\n * and the digest-pin Dockerfile mechanism this replaces is gone (the CLI v5\n * dropped RAILWAY_DOCKERFILE_PATH, its snapshot respects .gitignore so\n * generated Dockerfiles never shipped, and its Metal builder rejected the\n * repo Dockerfile's cache-mount ids).\n */\nexport async function deployImageToRailway(input: {\n service: string;\n configDir: string;\n}): Promise<void> {\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (image-backed)...`);\n // The probe is authoritative: runners don't ship the CLI, and a spawn\n // failure resolves (reject:false) as exitCode 1 with empty output — the\n // 2026-10-13 deploy surfaced exactly that as a silent instant failure.\n let version: string | undefined;\n try {\n version = (await run(\"railway\", [\"--version\"], { cwd: input.configDir, capture: true }))\n ?.stdout;\n } catch {\n version = undefined;\n }\n if (!version?.trim()) {\n throw new Error(\"Railway CLI not found. Install it: npm i -g @railway/cli\");\n }\n console.log(` ${colors.dim(`railway ${version.trim()}`)}`);\n\n // Stream the CLI's output live — a silent exit (empty stdout+stderr, seen\n // on the 2026-10-13 deploy) must still be diagnosable from the workflow log.\n const collected = { stdout: \"\", stderr: \"\" };\n const result = await run(\n \"railway\",\n [\"redeploy\", \"--service\", input.service, \"--from-source\", \"--yes\"],\n {\n cwd: input.configDir,\n capture: true,\n onChunk: (stream, chunk) => {\n collected[stream] += chunk.toString(\"utf-8\");\n for (const line of chunk.toString(\"utf-8\").split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n },\n },\n );\n if (!result || result.exitCode !== 0) {\n const exitCode = result?.exitCode ?? \"unknown\";\n const tail = (stream: string, text: string) =>\n text.trim()\n ? `\\nrailway ${stream} (tail):\\n${text.trim().split(\"\\n\").slice(-20).join(\"\\n\")}`\n : \"\";\n throw new Error(\n `railway redeploy failed with exit code ${exitCode}` +\n tail(\"stdout\", collected.stdout) +\n tail(\"stderr\", collected.stderr),\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-deploy.cjs","names":["run","readFileSync","join","colors","icons"],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\n/** Compose the runnable image ref: `image@sha256:…` when a digest exists,\n * `image` alone otherwise. Never docker-run the bare ref in a deploy flow —\n * the implicit `:latest` is held to the last stable release during\n * prereleases (ADR 0021), which boots a different framework build than the\n * one this train pinned. */\nexport function imageRefWithDigest(image: string, digest?: string): string {\n return digest ? `${image}@${digest}` : image;\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021, amended): image-backed deploy. The service's source\n * is a Docker image ref (connected once in the dashboard — `:rc` during the\n * prerelease train), and `redeploy --from-source` pulls and deploys whatever\n * that ref points at. The image leg pushes `:rc` (and the `sha-<short>` /\n * version tags) immediately before this step, so the pulled bytes are exactly\n * what this train validated. Railway skips the build entirely for\n * Docker-image sources — nothing is uploaded, nothing is built on Railway,\n * and the digest-pin Dockerfile mechanism this replaces is gone (the CLI v5\n * dropped RAILWAY_DOCKERFILE_PATH, its snapshot respects .gitignore so\n * generated Dockerfiles never shipped, and its Metal builder rejected the\n * repo Dockerfile's cache-mount ids).\n */\nexport async function deployImageToRailway(input: {\n service: string;\n configDir: string;\n}): Promise<void> {\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (image-backed)...`);\n // The probe is authoritative: runners don't ship the CLI, and a spawn\n // failure resolves (reject:false) as exitCode 1 with empty output — the\n // 2026-10-13 deploy surfaced exactly that as a silent instant failure.\n let version: string | undefined;\n try {\n version = (await run(\"railway\", [\"--version\"], { cwd: input.configDir, capture: true }))\n ?.stdout;\n } catch {\n version = undefined;\n }\n if (!version?.trim()) {\n throw new Error(\"Railway CLI not found. Install it: npm i -g @railway/cli\");\n }\n console.log(` ${colors.dim(`railway ${version.trim()}`)}`);\n\n // Stream the CLI's output live — a silent exit (empty stdout+stderr, seen\n // on the 2026-10-13 deploy) must still be diagnosable from the workflow log.\n const collected = { stdout: \"\", stderr: \"\" };\n const result = await run(\n \"railway\",\n [\"redeploy\", \"--service\", input.service, \"--from-source\", \"--yes\"],\n {\n cwd: input.configDir,\n capture: true,\n onChunk: (stream, chunk) => {\n collected[stream] += chunk.toString(\"utf-8\");\n for (const line of chunk.toString(\"utf-8\").split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n },\n },\n );\n if (!result || result.exitCode !== 0) {\n const exitCode = result?.exitCode ?? \"unknown\";\n const tail = (stream: string, text: string) =>\n text.trim()\n ? `\\nrailway ${stream} (tail):\\n${text.trim().split(\"\\n\").slice(-20).join(\"\\n\")}`\n : \"\";\n throw new Error(\n `railway redeploy failed with exit code ${exitCode}` +\n tail(\"stdout\", collected.stdout) +\n tail(\"stderr\", collected.stderr),\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
@@ -25,6 +25,14 @@ function resolveImageRef(input) {
25
25
  source: "repository"
26
26
  };
27
27
  }
28
+ /** Compose the runnable image ref: `image@sha256:…` when a digest exists,
29
+ * `image` alone otherwise. Never docker-run the bare ref in a deploy flow —
30
+ * the implicit `:latest` is held to the last stable release during
31
+ * prereleases (ADR 0021), which boots a different framework build than the
32
+ * one this train pinned. */
33
+ function imageRefWithDigest(image, digest) {
34
+ return digest ? `${image}@${digest}` : image;
35
+ }
28
36
  async function hasDocker() {
29
37
  try {
30
38
  return (await run("docker", [
@@ -248,5 +256,5 @@ async function deployImageToRailway(input) {
248
256
  }
249
257
 
250
258
  //#endregion
251
- export { buildAndPushImage, computeImageTags, deployImageToRailway, hasDocker, isValidImageDigest, resolveDeployImagePlan, resolveImageRef };
259
+ export { buildAndPushImage, computeImageTags, deployImageToRailway, hasDocker, imageRefWithDigest, isValidImageDigest, resolveDeployImagePlan, resolveImageRef };
252
260
  //# sourceMappingURL=image-deploy.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021, amended): image-backed deploy. The service's source\n * is a Docker image ref (connected once in the dashboard — `:rc` during the\n * prerelease train), and `redeploy --from-source` pulls and deploys whatever\n * that ref points at. The image leg pushes `:rc` (and the `sha-<short>` /\n * version tags) immediately before this step, so the pulled bytes are exactly\n * what this train validated. Railway skips the build entirely for\n * Docker-image sources — nothing is uploaded, nothing is built on Railway,\n * and the digest-pin Dockerfile mechanism this replaces is gone (the CLI v5\n * dropped RAILWAY_DOCKERFILE_PATH, its snapshot respects .gitignore so\n * generated Dockerfiles never shipped, and its Metal builder rejected the\n * repo Dockerfile's cache-mount ids).\n */\nexport async function deployImageToRailway(input: {\n service: string;\n configDir: string;\n}): Promise<void> {\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (image-backed)...`);\n // The probe is authoritative: runners don't ship the CLI, and a spawn\n // failure resolves (reject:false) as exitCode 1 with empty output — the\n // 2026-10-13 deploy surfaced exactly that as a silent instant failure.\n let version: string | undefined;\n try {\n version = (await run(\"railway\", [\"--version\"], { cwd: input.configDir, capture: true }))\n ?.stdout;\n } catch {\n version = undefined;\n }\n if (!version?.trim()) {\n throw new Error(\"Railway CLI not found. Install it: npm i -g @railway/cli\");\n }\n console.log(` ${colors.dim(`railway ${version.trim()}`)}`);\n\n // Stream the CLI's output live — a silent exit (empty stdout+stderr, seen\n // on the 2026-10-13 deploy) must still be diagnosable from the workflow log.\n const collected = { stdout: \"\", stderr: \"\" };\n const result = await run(\n \"railway\",\n [\"redeploy\", \"--service\", input.service, \"--from-source\", \"--yes\"],\n {\n cwd: input.configDir,\n capture: true,\n onChunk: (stream, chunk) => {\n collected[stream] += chunk.toString(\"utf-8\");\n for (const line of chunk.toString(\"utf-8\").split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n },\n },\n );\n if (!result || result.exitCode !== 0) {\n const exitCode = result?.exitCode ?? \"unknown\";\n const tail = (stream: string, text: string) =>\n text.trim()\n ? `\\nrailway ${stream} (tail):\\n${text.trim().split(\"\\n\").slice(-20).join(\"\\n\")}`\n : \"\";\n throw new Error(\n `railway redeploy failed with exit code ${exitCode}` +\n tail(\"stdout\", collected.stdout) +\n tail(\"stderr\", collected.stderr),\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\n/** Compose the runnable image ref: `image@sha256:…` when a digest exists,\n * `image` alone otherwise. Never docker-run the bare ref in a deploy flow —\n * the implicit `:latest` is held to the last stable release during\n * prereleases (ADR 0021), which boots a different framework build than the\n * one this train pinned. */\nexport function imageRefWithDigest(image: string, digest?: string): string {\n return digest ? `${image}@${digest}` : image;\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021, amended): image-backed deploy. The service's source\n * is a Docker image ref (connected once in the dashboard — `:rc` during the\n * prerelease train), and `redeploy --from-source` pulls and deploys whatever\n * that ref points at. The image leg pushes `:rc` (and the `sha-<short>` /\n * version tags) immediately before this step, so the pulled bytes are exactly\n * what this train validated. Railway skips the build entirely for\n * Docker-image sources — nothing is uploaded, nothing is built on Railway,\n * and the digest-pin Dockerfile mechanism this replaces is gone (the CLI v5\n * dropped RAILWAY_DOCKERFILE_PATH, its snapshot respects .gitignore so\n * generated Dockerfiles never shipped, and its Metal builder rejected the\n * repo Dockerfile's cache-mount ids).\n */\nexport async function deployImageToRailway(input: {\n service: string;\n configDir: string;\n}): Promise<void> {\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (image-backed)...`);\n // The probe is authoritative: runners don't ship the CLI, and a spawn\n // failure resolves (reject:false) as exitCode 1 with empty output — the\n // 2026-10-13 deploy surfaced exactly that as a silent instant failure.\n let version: string | undefined;\n try {\n version = (await run(\"railway\", [\"--version\"], { cwd: input.configDir, capture: true }))\n ?.stdout;\n } catch {\n version = undefined;\n }\n if (!version?.trim()) {\n throw new Error(\"Railway CLI not found. Install it: npm i -g @railway/cli\");\n }\n console.log(` ${colors.dim(`railway ${version.trim()}`)}`);\n\n // Stream the CLI's output live — a silent exit (empty stdout+stderr, seen\n // on the 2026-10-13 deploy) must still be diagnosable from the workflow log.\n const collected = { stdout: \"\", stderr: \"\" };\n const result = await run(\n \"railway\",\n [\"redeploy\", \"--service\", input.service, \"--from-source\", \"--yes\"],\n {\n cwd: input.configDir,\n capture: true,\n onChunk: (stream, chunk) => {\n collected[stream] += chunk.toString(\"utf-8\");\n for (const line of chunk.toString(\"utf-8\").split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n },\n },\n );\n if (!result || result.exitCode !== 0) {\n const exitCode = result?.exitCode ?? \"unknown\";\n const tail = (stream: string, text: string) =>\n text.trim()\n ? `\\nrailway ${stream} (tail):\\n${text.trim().split(\"\\n\").slice(-20).join(\"\\n\")}`\n : \"\";\n throw new Error(\n `railway redeploy failed with exit code ${exitCode}` +\n tail(\"stdout\", collected.stdout) +\n tail(\"stderr\", collected.stderr),\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -135,6 +135,8 @@ exports.SharedConfigSchema = require_types.SharedConfigSchema;
135
135
  exports.SharedDepConfigSchema = require_types.SharedDepConfigSchema;
136
136
  exports.SharedDepMapSchema = require_types.SharedDepMapSchema;
137
137
  exports.SlotPinSchema = require_types.SlotPinSchema;
138
+ exports.SmokeOptionsSchema = require_contract.SmokeOptionsSchema;
139
+ exports.SmokeResultSchema = require_contract.SmokeResultSchema;
138
140
  exports.SourceModeSchema = require_types.SourceModeSchema;
139
141
  exports.StartOptionsSchema = require_contract.StartOptionsSchema;
140
142
  exports.StartResultSchema = require_contract.StartResultSchema;
@@ -176,6 +178,7 @@ exports.defaultConfigEnv = require_config.defaultConfigEnv;
176
178
  exports.fetchBosConfigFromFastKv = require_fastkv.fetchBosConfigFromFastKv;
177
179
  exports.fetchConfigHistory = require_fastkv.fetchConfigHistory;
178
180
  exports.fetchDeployManifests = require_fastkv.fetchDeployManifests;
181
+ exports.fetchPublishedConfigFromFastKv = require_fastkv.fetchPublishedConfigFromFastKv;
179
182
  exports.fetchRemotePluginManifest = require_fastkv.fetchRemotePluginManifest;
180
183
  exports.findConfigPath = require_config.findConfigPath;
181
184
  exports.getConfigBaseDir = require_config.getConfigBaseDir;
@@ -202,6 +205,7 @@ exports.parseAppDescriptorModule = require_config.parseAppDescriptorModule;
202
205
  exports.parseBosUrl = require_fastkv.parseBosUrl;
203
206
  exports.parseDevOverlayModule = require_config.parseDevOverlayModule;
204
207
  exports.parseExtendsTarget = require_config.parseExtendsTarget;
208
+ exports.parseFastKvConfigValue = require_fastkv.parseFastKvConfigValue;
205
209
  exports.parsePort = require_config.parsePort;
206
210
  exports.parseRuntimeOverrideTargets = require_config.parseRuntimeOverrideTargets;
207
211
  exports.readAuthoredConfigInput = require_config.readAuthoredConfigInput;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ApiPluginConfig, ApiPluginConfigSchema, BosConfig, BosConfigInput, BosConfigInputAppEntry, BosConfigInputSchema, BosConfigSchema, BosPluginRef, BosPluginRefSchema, BosStaging, BosStagingSchema, CdnConfig, CdnConfigSchema, CiConfig, CiConfigSchema, ClientRuntimeConfig, ClientRuntimeConfigSchema, ClientRuntimeInfo, ClientRuntimeInfoSchema, ComposableAppEntry, ComposableAppEntrySchema, DependencyNodeKind, DependencyNodeKindSchema, Extends, ExtendsConfig, ExtendsSchema, FederationEntry, FederationEntrySchema, HostConfig, HostConfigSchema, JsonObject, JsonObjectSchema, JsonPrimitive, JsonValue, JsonValueSchema, ParentStarterConfig, ParentStarterConfigSchema, PluginEntries, PluginEntryValue, PluginRuntimeUi, PluginUiConfig, PluginUiConfigSchema, PublishAuth, PublishAuthSchema, PublishConfig, PublishConfigSchema, RailwayCi, RailwayCiSchema, RuntimeConfig, RuntimeConfigSchema, RuntimeDependencyNode, RuntimeDependencyNodeSchema, RuntimeLineage, RuntimeLineageSchema, RuntimePluginConfig, RuntimePluginConfigSchema, SharedConfig, SharedConfigSchema, SharedDepConfig, SharedDepConfigSchema, SharedDepMapSchema, SlotPin, SlotPinSchema, SourceMode, SourceModeSchema, StarterLevel, StarterLevelConfig, StarterLevelConfigSchema, StarterLevelSchema, UiConfig, UiConfigSchema } from "./types.cjs";
2
- import { BosConfigResult, BuildOptions, BuildOptionsSchema, BuildResultSchema, ConfigOptionsSchema, ConfigResultSchema, DbDoctorOptions, DbDoctorOptionsSchema, DbDoctorResult, DbDoctorResultSchema, DbRepairOptions, DbRepairOptionsSchema, DbRepairResult, DbRepairResultSchema, DbStudioOptions, DbStudioOptionsSchema, DbStudioResult, DbStudioResultSchema, DeployManifestListEntrySchema, DeployOptions, DeployOptionsSchema, DeployResult, DeployResultSchema, DeployedVersionStatusSchema, DevOptions, DevOptionsSchema, DevResult, DevResultSchema, InfraExportOptions, InfraExportOptionsSchema, InfraExportResult, InfraExportResultSchema, InfraExportService, InfraExportServiceSchema, InitOptions, InitOptionsSchema, InitResult, InitResultSchema, KeyPublishOptions, KeyPublishOptionsSchema, KeyPublishResult, KeyPublishResultSchema, KillOptions, KillOptionsSchema, KillResult, KillResultSchema, LoginOptions, LoginOptionsSchema, LoginResult, LoginResultSchema, LogoutOptions, LogoutOptionsSchema, LogoutResult, LogoutResultSchema, LogsOptions, LogsOptionsSchema, LogsResult, LogsResultSchema, MfCheckOptions, MfCheckOptionsSchema, MfCheckRemoteResultSchema, MfCheckResult, MfCheckResultSchema, OverrideSection, OverrideSectionSchema, PhaseTiming, PhaseTimingSchema, PidEntry, PidEntrySchema, PluginAddOptions, PluginAddOptionsSchema, PluginAddResult, PluginAddResultSchema, PluginListResult, PluginListResultSchema, PluginPublishOptions, PluginPublishOptionsSchema, PluginPublishResult, PluginPublishResultSchema, PluginRemoveOptions, PluginRemoveOptionsSchema, PluginRemoveResult, PluginRemoveResultSchema, ProcessRole, ProcessRoleSchema, PsResult, PsResultSchema, PublishOptions, PublishOptionsSchema, PublishResult, PublishResultSchema, RegistryUseOptionsSchema, RegistryUseResultSchema, RollbackHistoryEntrySchema, RollbackOptionsSchema, RollbackResultSchema, RollbackSlotCheckSchema, RuntimeOverrideTarget, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, StartOptions, StartOptionsSchema, StartResult, StartResultSchema, StatusResult, StatusResultSchema, SyncOptions, SyncOptionsSchema, SyncResult, SyncResultSchema, TypecheckOptions, TypecheckOptionsSchema, TypecheckResult, TypecheckResultSchema, TypecheckWorkspaceResult, TypecheckWorkspaceResultSchema, TypesGenOptions, TypesGenOptionsSchema, TypesGenResult, TypesGenResultSchema, UpgradeOptions, UpgradeOptionsSchema, UpgradeResult, UpgradeResultSchema, WorkspaceDeployResult, WorkspaceDeployResultSchema, bosContract, commandOptionSchemas } from "./contract.cjs";
2
+ import { BosConfigResult, BuildOptions, BuildOptionsSchema, BuildResultSchema, ConfigOptionsSchema, ConfigResultSchema, DbDoctorOptions, DbDoctorOptionsSchema, DbDoctorResult, DbDoctorResultSchema, DbRepairOptions, DbRepairOptionsSchema, DbRepairResult, DbRepairResultSchema, DbStudioOptions, DbStudioOptionsSchema, DbStudioResult, DbStudioResultSchema, DeployManifestListEntrySchema, DeployOptions, DeployOptionsSchema, DeployResult, DeployResultSchema, DeployedVersionStatusSchema, DevOptions, DevOptionsSchema, DevResult, DevResultSchema, InfraExportOptions, InfraExportOptionsSchema, InfraExportResult, InfraExportResultSchema, InfraExportService, InfraExportServiceSchema, InitOptions, InitOptionsSchema, InitResult, InitResultSchema, KeyPublishOptions, KeyPublishOptionsSchema, KeyPublishResult, KeyPublishResultSchema, KillOptions, KillOptionsSchema, KillResult, KillResultSchema, LoginOptions, LoginOptionsSchema, LoginResult, LoginResultSchema, LogoutOptions, LogoutOptionsSchema, LogoutResult, LogoutResultSchema, LogsOptions, LogsOptionsSchema, LogsResult, LogsResultSchema, MfCheckOptions, MfCheckOptionsSchema, MfCheckRemoteResultSchema, MfCheckResult, MfCheckResultSchema, OverrideSection, OverrideSectionSchema, PhaseTiming, PhaseTimingSchema, PidEntry, PidEntrySchema, PluginAddOptions, PluginAddOptionsSchema, PluginAddResult, PluginAddResultSchema, PluginListResult, PluginListResultSchema, PluginPublishOptions, PluginPublishOptionsSchema, PluginPublishResult, PluginPublishResultSchema, PluginRemoveOptions, PluginRemoveOptionsSchema, PluginRemoveResult, PluginRemoveResultSchema, ProcessRole, ProcessRoleSchema, PsResult, PsResultSchema, PublishOptions, PublishOptionsSchema, PublishResult, PublishResultSchema, RegistryUseOptionsSchema, RegistryUseResultSchema, RollbackHistoryEntrySchema, RollbackOptionsSchema, RollbackResultSchema, RollbackSlotCheckSchema, RuntimeOverrideTarget, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, SmokeOptionsSchema, SmokeResultSchema, StartOptions, StartOptionsSchema, StartResult, StartResultSchema, StatusResult, StatusResultSchema, SyncOptions, SyncOptionsSchema, SyncResult, SyncResultSchema, TypecheckOptions, TypecheckOptionsSchema, TypecheckResult, TypecheckResultSchema, TypecheckWorkspaceResult, TypecheckWorkspaceResultSchema, TypesGenOptions, TypesGenOptionsSchema, TypesGenResult, TypesGenResultSchema, UpgradeOptions, UpgradeOptionsSchema, UpgradeResult, UpgradeResultSchema, WorkspaceDeployResult, WorkspaceDeployResultSchema, bosContract, commandOptionSchemas } from "./contract.cjs";
3
3
  import { CliCommandMeta, cliCommandMeta } from "./contract.meta.cjs";
4
- import { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl } from "./fastkv.cjs";
4
+ import { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchPublishedConfigFromFastKv, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl, parseFastKvConfigValue } from "./fastkv.cjs";
5
5
  import { AnySchema, Context, Effect, Layer, LoadedPluginWithBinding, Plugin, PluginRuntime, RouterContract, Scope, createPlugin, createPluginRuntime, oc, z } from "./sdk.cjs";
6
6
  import { StartConfigSource, isRegistryStart, resolveStartConfigSource } from "./start-config-source.cjs";
7
7
  import { BOS_CONFIG_ORDER, BosEnv, mergeBosConfigWithExtends, rebuildOrderedConfig, resolveExtendsRef } from "./merge.cjs";
8
8
  import { BuildRuntimeConfigOptions, CircularExtendsError, ConfigExtendsError, ConfigLoadError, ConfigNotfoundError, ConfigVersionManifestError, DEV_OVERLAY_FILENAME, DevOverlayError, MISSING_CONFIG_MESSAGE, ResolvedComposableReference, buildRuntimeConfig, buildRuntimeConfigEffect, buildRuntimePluginsForConfig, defaultConfigEnv, findConfigPath, getConfigBaseDir, getHostDevelopmentPort, getResolvedConfigPath, isAppDescriptorPath, isLocalDevelopmentTarget, isRuntimeOverrideAllowed, loadAppDescriptorConfig, loadGeneratedResolvedConfig, localConfigEntryPath, parseAppDescriptorModule, parseDevOverlayModule, parseExtendsTarget, parsePort, parseRuntimeOverrideTargets, readAuthoredConfigInput, readBosConfigForBuild, readLocalAuthoredConfigInput, resetConfigPathCache, resolveBosConfigPath, resolveComposableReference, resolveConfigComposableEntries, resolveDevelopmentHostUrl, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resolveUiRuntimeName, runWithConfigWarningSink, writeResolvedConfig } from "./config.cjs";
9
- export { type AnySchema, ApiPluginConfig, ApiPluginConfigSchema, BOS_CONFIG_ORDER, BosConfig, BosConfigInput, BosConfigInputAppEntry, BosConfigInputSchema, BosConfigResult, BosConfigSchema, type BosEnv, BosPluginRef, BosPluginRefSchema, BosStaging, BosStagingSchema, BuildOptions, BuildOptionsSchema, BuildResultSchema, BuildRuntimeConfigOptions, CdnConfig, CdnConfigSchema, CiConfig, CiConfigSchema, CircularExtendsError, CliCommandMeta, ClientRuntimeConfig, ClientRuntimeConfigSchema, ClientRuntimeInfo, ClientRuntimeInfoSchema, ComposableAppEntry, ComposableAppEntrySchema, ConfigExtendsError, ConfigHistoryEntry, ConfigLoadError, ConfigNotfoundError, ConfigOptionsSchema, ConfigResultSchema, ConfigVersionManifestError, Context, DEV_OVERLAY_FILENAME, DbDoctorOptions, DbDoctorOptionsSchema, DbDoctorResult, DbDoctorResultSchema, DbRepairOptions, DbRepairOptionsSchema, DbRepairResult, DbRepairResultSchema, DbStudioOptions, DbStudioOptionsSchema, DbStudioResult, DbStudioResultSchema, DependencyNodeKind, DependencyNodeKindSchema, DeployManifestEntry, DeployManifestListEntrySchema, DeployOptions, DeployOptionsSchema, DeployResult, DeployResultSchema, DeployedVersionStatusSchema, DevOptions, DevOptionsSchema, DevOverlayError, DevResult, DevResultSchema, Effect, Extends, ExtendsConfig, ExtendsSchema, FederationEntry, FederationEntrySchema, HostConfig, HostConfigSchema, InfraExportOptions, InfraExportOptionsSchema, InfraExportResult, InfraExportResultSchema, InfraExportService, InfraExportServiceSchema, InitOptions, InitOptionsSchema, InitResult, InitResultSchema, JsonObject, JsonObjectSchema, JsonPrimitive, JsonValue, JsonValueSchema, KeyPublishOptions, KeyPublishOptionsSchema, KeyPublishResult, KeyPublishResultSchema, KillOptions, KillOptionsSchema, KillResult, KillResultSchema, Layer, type LoadedPluginWithBinding, LoginOptions, LoginOptionsSchema, LoginResult, LoginResultSchema, LogoutOptions, LogoutOptionsSchema, LogoutResult, LogoutResultSchema, LogsOptions, LogsOptionsSchema, LogsResult, LogsResultSchema, MISSING_CONFIG_MESSAGE, MfCheckOptions, MfCheckOptionsSchema, MfCheckRemoteResultSchema, MfCheckResult, MfCheckResultSchema, NetworkId, OverrideSection, OverrideSectionSchema, ParentStarterConfig, ParentStarterConfigSchema, PhaseTiming, PhaseTimingSchema, PidEntry, PidEntrySchema, type Plugin, PluginAddOptions, PluginAddOptionsSchema, PluginAddResult, PluginAddResultSchema, PluginEntries, PluginEntryValue, PluginListResult, PluginListResultSchema, PluginManifest, PluginPublishOptions, PluginPublishOptionsSchema, PluginPublishResult, PluginPublishResultSchema, PluginRemoveOptions, PluginRemoveOptionsSchema, PluginRemoveResult, PluginRemoveResultSchema, PluginRuntime, PluginRuntimeUi, PluginUiConfig, PluginUiConfigSchema, ProcessRole, ProcessRoleSchema, PsResult, PsResultSchema, PublishAuth, PublishAuthSchema, PublishConfig, PublishConfigSchema, PublishOptions, PublishOptionsSchema, PublishResult, PublishResultSchema, RailwayCi, RailwayCiSchema, RegistryUseOptionsSchema, RegistryUseResultSchema, ResolvedComposableReference, RollbackHistoryEntrySchema, RollbackOptionsSchema, RollbackResultSchema, RollbackSlotCheckSchema, type RouterContract, RuntimeConfig, RuntimeConfigSchema, RuntimeDependencyNode, RuntimeDependencyNodeSchema, RuntimeLineage, RuntimeLineageSchema, RuntimeOverrideTarget, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, RuntimePluginConfig, RuntimePluginConfigSchema, Scope, SharedConfig, SharedConfigSchema, SharedDepConfig, SharedDepConfigSchema, SharedDepMapSchema, SlotPin, SlotPinSchema, SourceMode, SourceModeSchema, StartConfigSource, StartOptions, StartOptionsSchema, StartResult, StartResultSchema, StarterLevel, StarterLevelConfig, StarterLevelConfigSchema, StarterLevelSchema, StatusResult, StatusResultSchema, SyncOptions, SyncOptionsSchema, SyncResult, SyncResultSchema, TypecheckOptions, TypecheckOptionsSchema, TypecheckResult, TypecheckResultSchema, TypecheckWorkspaceResult, TypecheckWorkspaceResultSchema, TypesGenOptions, TypesGenOptionsSchema, TypesGenResult, TypesGenResultSchema, UiConfig, UiConfigSchema, UpgradeOptions, UpgradeOptionsSchema, UpgradeResult, UpgradeResultSchema, WorkspaceDeployResult, WorkspaceDeployResultSchema, bosContract, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, buildRuntimeConfig, buildRuntimeConfigEffect, buildRuntimePluginsForConfig, cliCommandMeta, commandOptionSchemas, createPlugin, createPluginRuntime, defaultConfigEnv, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchRemotePluginManifest, findConfigPath, getConfigBaseDir, getFastKvBaseUrlForNetwork, getHostDevelopmentPort, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, getResolvedConfigPath, isAppDescriptorPath, isLocalDevelopmentTarget, isRegistryStart, isRuntimeOverrideAllowed, loadAppDescriptorConfig, loadGeneratedResolvedConfig, localConfigEntryPath, mergeBosConfigWithExtends, oc, parseAppDescriptorModule, parseBosUrl, parseDevOverlayModule, parseExtendsTarget, parsePort, parseRuntimeOverrideTargets, readAuthoredConfigInput, readBosConfigForBuild, readLocalAuthoredConfigInput, rebuildOrderedConfig, resetConfigPathCache, resolveBosConfigPath, resolveComposableReference, resolveConfigComposableEntries, resolveDevelopmentHostUrl, resolveExtendsRef, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resolveStartConfigSource, resolveUiRuntimeName, runWithConfigWarningSink, writeResolvedConfig, z };
9
+ export { type AnySchema, ApiPluginConfig, ApiPluginConfigSchema, BOS_CONFIG_ORDER, BosConfig, BosConfigInput, BosConfigInputAppEntry, BosConfigInputSchema, BosConfigResult, BosConfigSchema, type BosEnv, BosPluginRef, BosPluginRefSchema, BosStaging, BosStagingSchema, BuildOptions, BuildOptionsSchema, BuildResultSchema, BuildRuntimeConfigOptions, CdnConfig, CdnConfigSchema, CiConfig, CiConfigSchema, CircularExtendsError, CliCommandMeta, ClientRuntimeConfig, ClientRuntimeConfigSchema, ClientRuntimeInfo, ClientRuntimeInfoSchema, ComposableAppEntry, ComposableAppEntrySchema, ConfigExtendsError, ConfigHistoryEntry, ConfigLoadError, ConfigNotfoundError, ConfigOptionsSchema, ConfigResultSchema, ConfigVersionManifestError, Context, DEV_OVERLAY_FILENAME, DbDoctorOptions, DbDoctorOptionsSchema, DbDoctorResult, DbDoctorResultSchema, DbRepairOptions, DbRepairOptionsSchema, DbRepairResult, DbRepairResultSchema, DbStudioOptions, DbStudioOptionsSchema, DbStudioResult, DbStudioResultSchema, DependencyNodeKind, DependencyNodeKindSchema, DeployManifestEntry, DeployManifestListEntrySchema, DeployOptions, DeployOptionsSchema, DeployResult, DeployResultSchema, DeployedVersionStatusSchema, DevOptions, DevOptionsSchema, DevOverlayError, DevResult, DevResultSchema, Effect, Extends, ExtendsConfig, ExtendsSchema, FederationEntry, FederationEntrySchema, HostConfig, HostConfigSchema, InfraExportOptions, InfraExportOptionsSchema, InfraExportResult, InfraExportResultSchema, InfraExportService, InfraExportServiceSchema, InitOptions, InitOptionsSchema, InitResult, InitResultSchema, JsonObject, JsonObjectSchema, JsonPrimitive, JsonValue, JsonValueSchema, KeyPublishOptions, KeyPublishOptionsSchema, KeyPublishResult, KeyPublishResultSchema, KillOptions, KillOptionsSchema, KillResult, KillResultSchema, Layer, type LoadedPluginWithBinding, LoginOptions, LoginOptionsSchema, LoginResult, LoginResultSchema, LogoutOptions, LogoutOptionsSchema, LogoutResult, LogoutResultSchema, LogsOptions, LogsOptionsSchema, LogsResult, LogsResultSchema, MISSING_CONFIG_MESSAGE, MfCheckOptions, MfCheckOptionsSchema, MfCheckRemoteResultSchema, MfCheckResult, MfCheckResultSchema, NetworkId, OverrideSection, OverrideSectionSchema, ParentStarterConfig, ParentStarterConfigSchema, PhaseTiming, PhaseTimingSchema, PidEntry, PidEntrySchema, type Plugin, PluginAddOptions, PluginAddOptionsSchema, PluginAddResult, PluginAddResultSchema, PluginEntries, PluginEntryValue, PluginListResult, PluginListResultSchema, PluginManifest, PluginPublishOptions, PluginPublishOptionsSchema, PluginPublishResult, PluginPublishResultSchema, PluginRemoveOptions, PluginRemoveOptionsSchema, PluginRemoveResult, PluginRemoveResultSchema, PluginRuntime, PluginRuntimeUi, PluginUiConfig, PluginUiConfigSchema, ProcessRole, ProcessRoleSchema, PsResult, PsResultSchema, PublishAuth, PublishAuthSchema, PublishConfig, PublishConfigSchema, PublishOptions, PublishOptionsSchema, PublishResult, PublishResultSchema, RailwayCi, RailwayCiSchema, RegistryUseOptionsSchema, RegistryUseResultSchema, ResolvedComposableReference, RollbackHistoryEntrySchema, RollbackOptionsSchema, RollbackResultSchema, RollbackSlotCheckSchema, type RouterContract, RuntimeConfig, RuntimeConfigSchema, RuntimeDependencyNode, RuntimeDependencyNodeSchema, RuntimeLineage, RuntimeLineageSchema, RuntimeOverrideTarget, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, RuntimePluginConfig, RuntimePluginConfigSchema, Scope, SharedConfig, SharedConfigSchema, SharedDepConfig, SharedDepConfigSchema, SharedDepMapSchema, SlotPin, SlotPinSchema, SmokeOptionsSchema, SmokeResultSchema, SourceMode, SourceModeSchema, StartConfigSource, StartOptions, StartOptionsSchema, StartResult, StartResultSchema, StarterLevel, StarterLevelConfig, StarterLevelConfigSchema, StarterLevelSchema, StatusResult, StatusResultSchema, SyncOptions, SyncOptionsSchema, SyncResult, SyncResultSchema, TypecheckOptions, TypecheckOptionsSchema, TypecheckResult, TypecheckResultSchema, TypecheckWorkspaceResult, TypecheckWorkspaceResultSchema, TypesGenOptions, TypesGenOptionsSchema, TypesGenResult, TypesGenResultSchema, UiConfig, UiConfigSchema, UpgradeOptions, UpgradeOptionsSchema, UpgradeResult, UpgradeResultSchema, WorkspaceDeployResult, WorkspaceDeployResultSchema, bosContract, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, buildRuntimeConfig, buildRuntimeConfigEffect, buildRuntimePluginsForConfig, cliCommandMeta, commandOptionSchemas, createPlugin, createPluginRuntime, defaultConfigEnv, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchPublishedConfigFromFastKv, fetchRemotePluginManifest, findConfigPath, getConfigBaseDir, getFastKvBaseUrlForNetwork, getHostDevelopmentPort, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, getResolvedConfigPath, isAppDescriptorPath, isLocalDevelopmentTarget, isRegistryStart, isRuntimeOverrideAllowed, loadAppDescriptorConfig, loadGeneratedResolvedConfig, localConfigEntryPath, mergeBosConfigWithExtends, oc, parseAppDescriptorModule, parseBosUrl, parseDevOverlayModule, parseExtendsTarget, parseFastKvConfigValue, parsePort, parseRuntimeOverrideTargets, readAuthoredConfigInput, readBosConfigForBuild, readLocalAuthoredConfigInput, rebuildOrderedConfig, resetConfigPathCache, resolveBosConfigPath, resolveComposableReference, resolveConfigComposableEntries, resolveDevelopmentHostUrl, resolveExtendsRef, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resolveStartConfigSource, resolveUiRuntimeName, runWithConfigWarningSink, writeResolvedConfig, z };