@neat.is/core 0.9.20-dev.20260922 → 0.9.21-dev.20260923
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KIV7OB2K.js → chunk-NFJ4W5T6.js} +168 -7
- package/dist/chunk-NFJ4W5T6.js.map +1 -0
- package/dist/{chunk-F7I3G4KC.js → chunk-SK2FEJKB.js} +39 -2
- package/dist/chunk-SK2FEJKB.js.map +1 -0
- package/dist/cli.cjs +260 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +89 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +200 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +200 -6
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +155 -7
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-F7I3G4KC.js.map +0 -1
- package/dist/chunk-KIV7OB2K.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -188,6 +188,15 @@ interface ObservedSignal {
|
|
|
188
188
|
readyReplicas?: number;
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A per-tick credential source (docs/contracts/connector-config.md §9, ADR-230) — the shape the poll loop's
|
|
193
|
+
* `refreshCredentials` seam (index.ts) already consumes for the hosted profile. A credential that mints a
|
|
194
|
+
* short-lived token (a GCP service-account key → a ~1h Google access token) resolves to one of these instead
|
|
195
|
+
* of a fixed value: the loop calls it each tick to get that tick's credentials, and the source caches and
|
|
196
|
+
* re-mints behind its own expiry logic. `poll()` is unchanged — the connector still receives an opaque,
|
|
197
|
+
* already-minted `credentials` record and performs no auth handshake of its own (connectors.md §3).
|
|
198
|
+
*/
|
|
199
|
+
type CredentialSource = () => Promise<Record<string, unknown>>;
|
|
191
200
|
|
|
192
201
|
/**
|
|
193
202
|
* What a provider's target-resolution step hands back for one signal. The
|
|
@@ -250,6 +259,7 @@ interface ConnectorRegistration {
|
|
|
250
259
|
credentials: Record<string, unknown>;
|
|
251
260
|
resolveTarget: ResolveConnectorTarget;
|
|
252
261
|
intervalMs?: number;
|
|
262
|
+
refreshCredentials?: CredentialSource;
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
interface BuildApiOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -188,6 +188,15 @@ interface ObservedSignal {
|
|
|
188
188
|
readyReplicas?: number;
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A per-tick credential source (docs/contracts/connector-config.md §9, ADR-230) — the shape the poll loop's
|
|
193
|
+
* `refreshCredentials` seam (index.ts) already consumes for the hosted profile. A credential that mints a
|
|
194
|
+
* short-lived token (a GCP service-account key → a ~1h Google access token) resolves to one of these instead
|
|
195
|
+
* of a fixed value: the loop calls it each tick to get that tick's credentials, and the source caches and
|
|
196
|
+
* re-mints behind its own expiry logic. `poll()` is unchanged — the connector still receives an opaque,
|
|
197
|
+
* already-minted `credentials` record and performs no auth handshake of its own (connectors.md §3).
|
|
198
|
+
*/
|
|
199
|
+
type CredentialSource = () => Promise<Record<string, unknown>>;
|
|
191
200
|
|
|
192
201
|
/**
|
|
193
202
|
* What a provider's target-resolution step hands back for one signal. The
|
|
@@ -250,6 +259,7 @@ interface ConnectorRegistration {
|
|
|
250
259
|
credentials: Record<string, unknown>;
|
|
251
260
|
resolveTarget: ResolveConnectorTarget;
|
|
252
261
|
intervalMs?: number;
|
|
262
|
+
refreshCredentials?: CredentialSource;
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
interface BuildApiOptions {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SK2FEJKB.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-NFJ4W5T6.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-ERE47MCR.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -6577,9 +6577,30 @@ function withRecoveredCodeAttrs(attrs, locus) {
|
|
|
6577
6577
|
if (locus.codeLineno !== void 0) attrs[CODE_LINENO_ATTR] = locus.codeLineno;
|
|
6578
6578
|
return attrs;
|
|
6579
6579
|
}
|
|
6580
|
+
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
6581
|
+
"authorization",
|
|
6582
|
+
"proxy-authorization",
|
|
6583
|
+
"cookie",
|
|
6584
|
+
"set-cookie",
|
|
6585
|
+
"x-api-key",
|
|
6586
|
+
"api-key",
|
|
6587
|
+
"x-auth-token",
|
|
6588
|
+
"x-amz-security-token",
|
|
6589
|
+
"x-csrf-token"
|
|
6590
|
+
]);
|
|
6591
|
+
function attrHeaderName(key) {
|
|
6592
|
+
const lower = key.toLowerCase();
|
|
6593
|
+
const marker = ".header.";
|
|
6594
|
+
const at = lower.lastIndexOf(marker);
|
|
6595
|
+
return at >= 0 ? lower.slice(at + marker.length) : lower;
|
|
6596
|
+
}
|
|
6597
|
+
function isSensitiveAttrKey(key) {
|
|
6598
|
+
return SENSITIVE_HEADER_NAMES.has(attrHeaderName(key));
|
|
6599
|
+
}
|
|
6580
6600
|
function sanitizeAttributes(attrs) {
|
|
6581
6601
|
const out = {};
|
|
6582
6602
|
for (const [k, v] of Object.entries(attrs)) {
|
|
6603
|
+
if (isSensitiveAttrKey(k)) continue;
|
|
6583
6604
|
if (typeof v === "bigint") out[k] = v.toString();
|
|
6584
6605
|
else out[k] = v;
|
|
6585
6606
|
}
|
|
@@ -7167,6 +7188,14 @@ async function readErrorFileTail(errorsPath, maxBytes) {
|
|
|
7167
7188
|
await handle.close();
|
|
7168
7189
|
}
|
|
7169
7190
|
}
|
|
7191
|
+
function redactPersistedAttributes(ev) {
|
|
7192
|
+
if (!ev.attributes) return ev;
|
|
7193
|
+
const scrubbed = sanitizeAttributes(ev.attributes);
|
|
7194
|
+
if (Object.keys(scrubbed).length === Object.keys(ev.attributes).length) return ev;
|
|
7195
|
+
if (Object.keys(scrubbed).length > 0) return { ...ev, attributes: scrubbed };
|
|
7196
|
+
const { attributes: _dropped, ...rest } = ev;
|
|
7197
|
+
return rest;
|
|
7198
|
+
}
|
|
7170
7199
|
async function readErrorEvents(errorsPath, opts) {
|
|
7171
7200
|
let raw;
|
|
7172
7201
|
try {
|
|
@@ -7175,7 +7204,7 @@ async function readErrorEvents(errorsPath, opts) {
|
|
|
7175
7204
|
if (err.code === "ENOENT") return [];
|
|
7176
7205
|
throw err;
|
|
7177
7206
|
}
|
|
7178
|
-
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
7207
|
+
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => redactPersistedAttributes(JSON.parse(line)));
|
|
7179
7208
|
const deduped = dedupeIncidents(events);
|
|
7180
7209
|
const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
|
|
7181
7210
|
return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
|
|
@@ -19097,6 +19126,10 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
19097
19126
|
// src/connectors/registry.ts
|
|
19098
19127
|
init_cjs_shims();
|
|
19099
19128
|
|
|
19129
|
+
// src/connectors/gcp-auth.ts
|
|
19130
|
+
init_cjs_shims();
|
|
19131
|
+
var import_node_crypto3 = require("crypto");
|
|
19132
|
+
|
|
19100
19133
|
// src/connectors/junction.ts
|
|
19101
19134
|
init_cjs_shims();
|
|
19102
19135
|
var buckets = /* @__PURE__ */ new Map();
|
|
@@ -19358,6 +19391,108 @@ async function dbJunction(run, policy) {
|
|
|
19358
19391
|
}
|
|
19359
19392
|
}
|
|
19360
19393
|
|
|
19394
|
+
// src/connectors/gcp-auth.ts
|
|
19395
|
+
var DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
|
|
19396
|
+
var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
19397
|
+
var ASSERTION_LIFETIME_S = 3600;
|
|
19398
|
+
var DEFAULT_REFRESH_SKEW_MS = 6e4;
|
|
19399
|
+
var GCP_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
|
|
19400
|
+
function parseServiceAccountKey(json) {
|
|
19401
|
+
let raw;
|
|
19402
|
+
try {
|
|
19403
|
+
raw = JSON.parse(json);
|
|
19404
|
+
} catch {
|
|
19405
|
+
throw new Error("gcp service-account key: not valid JSON");
|
|
19406
|
+
}
|
|
19407
|
+
if (!raw || typeof raw !== "object") {
|
|
19408
|
+
throw new Error("gcp service-account key: expected a JSON object");
|
|
19409
|
+
}
|
|
19410
|
+
const obj = raw;
|
|
19411
|
+
const clientEmail = obj["client_email"];
|
|
19412
|
+
const privateKey = obj["private_key"];
|
|
19413
|
+
const projectId = obj["project_id"];
|
|
19414
|
+
if (typeof clientEmail !== "string" || clientEmail.length === 0) {
|
|
19415
|
+
throw new Error("gcp service-account key: missing client_email");
|
|
19416
|
+
}
|
|
19417
|
+
if (typeof privateKey !== "string" || privateKey.length === 0) {
|
|
19418
|
+
throw new Error("gcp service-account key: missing private_key");
|
|
19419
|
+
}
|
|
19420
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
19421
|
+
throw new Error("gcp service-account key: missing project_id");
|
|
19422
|
+
}
|
|
19423
|
+
const key = { client_email: clientEmail, private_key: privateKey, project_id: projectId };
|
|
19424
|
+
if (typeof obj["token_uri"] === "string" && obj["token_uri"].length > 0) {
|
|
19425
|
+
key.token_uri = obj["token_uri"];
|
|
19426
|
+
}
|
|
19427
|
+
return key;
|
|
19428
|
+
}
|
|
19429
|
+
function base64url(input) {
|
|
19430
|
+
return Buffer.from(input).toString("base64url");
|
|
19431
|
+
}
|
|
19432
|
+
function buildServiceAccountAssertion(key, scope, tokenUri, nowMs) {
|
|
19433
|
+
const iat = Math.floor(nowMs / 1e3);
|
|
19434
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
19435
|
+
const claims = {
|
|
19436
|
+
iss: key.client_email,
|
|
19437
|
+
scope,
|
|
19438
|
+
aud: tokenUri,
|
|
19439
|
+
iat,
|
|
19440
|
+
exp: iat + ASSERTION_LIFETIME_S
|
|
19441
|
+
};
|
|
19442
|
+
const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
|
|
19443
|
+
const signature = (0, import_node_crypto3.createSign)("RSA-SHA256").update(signingInput).end().sign(key.private_key);
|
|
19444
|
+
return `${signingInput}.${base64url(signature)}`;
|
|
19445
|
+
}
|
|
19446
|
+
async function mintGcpAccessToken(key, scope, opts = {}) {
|
|
19447
|
+
const now = opts.now ?? Date.now;
|
|
19448
|
+
const tokenUri = opts.tokenUri ?? key.token_uri ?? DEFAULT_TOKEN_URI;
|
|
19449
|
+
const mintedAtMs = now();
|
|
19450
|
+
const assertion = buildServiceAccountAssertion(key, scope, tokenUri, mintedAtMs);
|
|
19451
|
+
const body = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString();
|
|
19452
|
+
const res = await junctionFetch(
|
|
19453
|
+
tokenUri,
|
|
19454
|
+
{
|
|
19455
|
+
method: "POST",
|
|
19456
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
19457
|
+
body
|
|
19458
|
+
},
|
|
19459
|
+
// accountKey: the GCP project id — an identifier, safe to log, the same
|
|
19460
|
+
// per-(provider, accountKey) rate-limit bucket key the GCP connectors use.
|
|
19461
|
+
{ provider: "gcp-auth", accountKey: key.project_id, ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {} }
|
|
19462
|
+
);
|
|
19463
|
+
if (!res.ok) {
|
|
19464
|
+
throw new Error(`gcp token mint failed: ${res.status} ${res.statusText}`);
|
|
19465
|
+
}
|
|
19466
|
+
const json = await res.json();
|
|
19467
|
+
const accessToken = json.access_token;
|
|
19468
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
19469
|
+
throw new Error("gcp token mint returned no access_token");
|
|
19470
|
+
}
|
|
19471
|
+
const expiresInS = typeof json.expires_in === "number" && json.expires_in > 0 ? json.expires_in : ASSERTION_LIFETIME_S;
|
|
19472
|
+
return { accessToken, expiresAtMs: mintedAtMs + expiresInS * 1e3 };
|
|
19473
|
+
}
|
|
19474
|
+
function createGcpTokenSource(key, scope, opts = {}) {
|
|
19475
|
+
const now = opts.now ?? Date.now;
|
|
19476
|
+
const skewMs = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
|
|
19477
|
+
let cached = null;
|
|
19478
|
+
let inFlight = null;
|
|
19479
|
+
const fresh = () => cached !== null && now() < cached.expiresAtMs - skewMs;
|
|
19480
|
+
return async () => {
|
|
19481
|
+
if (!fresh()) {
|
|
19482
|
+
if (!inFlight) {
|
|
19483
|
+
inFlight = mintGcpAccessToken(key, scope, opts).then((minted) => {
|
|
19484
|
+
cached = minted;
|
|
19485
|
+
return minted;
|
|
19486
|
+
}).finally(() => {
|
|
19487
|
+
inFlight = null;
|
|
19488
|
+
});
|
|
19489
|
+
}
|
|
19490
|
+
await inFlight;
|
|
19491
|
+
}
|
|
19492
|
+
return { projectId: key.project_id, accessToken: cached.accessToken };
|
|
19493
|
+
};
|
|
19494
|
+
}
|
|
19495
|
+
|
|
19361
19496
|
// src/connectors/supabase/index.ts
|
|
19362
19497
|
init_cjs_shims();
|
|
19363
19498
|
|
|
@@ -20224,7 +20359,7 @@ var import_types73 = require("@neat.is/types");
|
|
|
20224
20359
|
|
|
20225
20360
|
// src/connectors/cloudflare/client.ts
|
|
20226
20361
|
init_cjs_shims();
|
|
20227
|
-
var
|
|
20362
|
+
var import_node_crypto4 = require("crypto");
|
|
20228
20363
|
var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
20229
20364
|
var DEFAULT_EVENT_LIMIT = 1e3;
|
|
20230
20365
|
async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
|
|
@@ -20237,7 +20372,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
|
|
|
20237
20372
|
const body = {
|
|
20238
20373
|
// Cloudflare's schema requires an identifier per query even for an
|
|
20239
20374
|
// ad-hoc, unsaved one — a fresh id per tick, never reused.
|
|
20240
|
-
queryId: `neat-connector-${(0,
|
|
20375
|
+
queryId: `neat-connector-${(0, import_node_crypto4.randomUUID)()}`,
|
|
20241
20376
|
timeframe: { from: window.fromMs, to: window.toMs },
|
|
20242
20377
|
view: "events",
|
|
20243
20378
|
limit: config.eventLimit ?? DEFAULT_EVENT_LIMIT,
|
|
@@ -22239,7 +22374,8 @@ var PUSH_PROVIDER_DISPATCH = {
|
|
|
22239
22374
|
function isPushProvider(provider) {
|
|
22240
22375
|
return provider in PUSH_PROVIDER_DISPATCH;
|
|
22241
22376
|
}
|
|
22242
|
-
|
|
22377
|
+
var GCP_SERVICE_ACCOUNT_PROVIDERS = /* @__PURE__ */ new Set(["cloud-run", "gcp-lb", "firebase"]);
|
|
22378
|
+
function resolveEntryCredentials(dispatch, entry2, env, fetchImpl) {
|
|
22243
22379
|
let credentials;
|
|
22244
22380
|
try {
|
|
22245
22381
|
const resolved = resolveCredential(entry2.credential, env);
|
|
@@ -22248,6 +22384,15 @@ function resolveEntryCredentials(dispatch, entry2, env) {
|
|
|
22248
22384
|
if (err instanceof EnvRefUnsetError) return { ok: false, kind: "unset-env", reason: err.message };
|
|
22249
22385
|
return { ok: false, kind: "error", reason: err.message };
|
|
22250
22386
|
}
|
|
22387
|
+
if (GCP_SERVICE_ACCOUNT_PROVIDERS.has(entry2.provider) && typeof credentials.serviceAccountKey === "string" && credentials.serviceAccountKey.length > 0) {
|
|
22388
|
+
try {
|
|
22389
|
+
const key = parseServiceAccountKey(credentials.serviceAccountKey);
|
|
22390
|
+
const source = createGcpTokenSource(key, GCP_LOGGING_READ_SCOPE, fetchImpl ? { fetchImpl } : {});
|
|
22391
|
+
return { ok: true, credentials: { projectId: key.project_id }, refreshCredentials: source };
|
|
22392
|
+
} catch (err) {
|
|
22393
|
+
return { ok: false, kind: "error", reason: err.message };
|
|
22394
|
+
}
|
|
22395
|
+
}
|
|
22251
22396
|
const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k]);
|
|
22252
22397
|
if (missingCreds.length > 0) {
|
|
22253
22398
|
return {
|
|
@@ -22297,7 +22442,10 @@ function buildRegistration(entry2, graph, env = process.env) {
|
|
|
22297
22442
|
connector: built.connector,
|
|
22298
22443
|
credentials,
|
|
22299
22444
|
resolveTarget: built.resolveTarget,
|
|
22300
|
-
...intervalMs !== void 0 ? { intervalMs } : {}
|
|
22445
|
+
...intervalMs !== void 0 ? { intervalMs } : {},
|
|
22446
|
+
// A refreshable credential (ADR-230) resolves to a per-tick token source; carry it so the daemon wires
|
|
22447
|
+
// it to the poll loop's `refreshCredentials`. Absent for a static credential.
|
|
22448
|
+
...creds.refreshCredentials ? { refreshCredentials: creds.refreshCredentials } : {}
|
|
22301
22449
|
}
|
|
22302
22450
|
};
|
|
22303
22451
|
}
|
|
@@ -22341,7 +22489,13 @@ async function startConnectorPolling(input) {
|
|
|
22341
22489
|
},
|
|
22342
22490
|
input.graph,
|
|
22343
22491
|
registration.resolveTarget,
|
|
22344
|
-
{
|
|
22492
|
+
{
|
|
22493
|
+
intervalMs: registration.intervalMs,
|
|
22494
|
+
connectorId: registration.id,
|
|
22495
|
+
// A refreshable credential (ADR-230) mints a fresh token each tick; the static path leaves this unset
|
|
22496
|
+
// and the loop reuses ctx.credentials, exactly as before.
|
|
22497
|
+
...registration.refreshCredentials ? { refreshCredentials: registration.refreshCredentials } : {}
|
|
22498
|
+
}
|
|
22345
22499
|
)
|
|
22346
22500
|
);
|
|
22347
22501
|
return () => {
|
|
@@ -23299,6 +23453,37 @@ async function reconcileFrontierSurfaces(graph, errorsPath) {
|
|
|
23299
23453
|
|
|
23300
23454
|
// src/connectors/hosted.ts
|
|
23301
23455
|
init_cjs_shims();
|
|
23456
|
+
|
|
23457
|
+
// src/connectors/railway/target-ref.ts
|
|
23458
|
+
init_cjs_shims();
|
|
23459
|
+
function nonEmptyString(v) {
|
|
23460
|
+
return typeof v === "string" && v.length > 0;
|
|
23461
|
+
}
|
|
23462
|
+
function decodeRailwayTargetRef(ref) {
|
|
23463
|
+
if (typeof ref !== "string" || ref.length === 0) return null;
|
|
23464
|
+
let json;
|
|
23465
|
+
try {
|
|
23466
|
+
json = Buffer.from(ref, "base64url").toString("utf8");
|
|
23467
|
+
} catch {
|
|
23468
|
+
return null;
|
|
23469
|
+
}
|
|
23470
|
+
let parsed;
|
|
23471
|
+
try {
|
|
23472
|
+
parsed = JSON.parse(json);
|
|
23473
|
+
} catch {
|
|
23474
|
+
return null;
|
|
23475
|
+
}
|
|
23476
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
23477
|
+
const obj = parsed;
|
|
23478
|
+
if (!nonEmptyString(obj.environmentId) || !nonEmptyString(obj.serviceId)) return null;
|
|
23479
|
+
return {
|
|
23480
|
+
environmentId: obj.environmentId,
|
|
23481
|
+
serviceId: obj.serviceId,
|
|
23482
|
+
...nonEmptyString(obj.serviceName) ? { serviceName: obj.serviceName } : {}
|
|
23483
|
+
};
|
|
23484
|
+
}
|
|
23485
|
+
|
|
23486
|
+
// src/connectors/hosted.ts
|
|
23302
23487
|
var CREDENTIAL_REFRESH_SKEW_MS = 6e4;
|
|
23303
23488
|
var CP_REQUEST_TIMEOUT_MS = 1e4;
|
|
23304
23489
|
async function cpGet(path81, deps) {
|
|
@@ -23325,6 +23510,15 @@ function hostedOptions(provider, summary, serviceName) {
|
|
|
23325
23510
|
if (!ref) return null;
|
|
23326
23511
|
return { apiProjectRef: ref, nodeRef: `${ref}.supabase.co`, serviceName };
|
|
23327
23512
|
}
|
|
23513
|
+
case "railway": {
|
|
23514
|
+
const target = summary.projectRef ? decodeRailwayTargetRef(summary.projectRef) : null;
|
|
23515
|
+
if (!target) return null;
|
|
23516
|
+
return {
|
|
23517
|
+
environmentId: target.environmentId,
|
|
23518
|
+
serviceId: target.serviceId,
|
|
23519
|
+
serviceNameById: { [target.serviceId]: serviceName }
|
|
23520
|
+
};
|
|
23521
|
+
}
|
|
23328
23522
|
default:
|
|
23329
23523
|
return null;
|
|
23330
23524
|
}
|