@objectstack/runtime 6.5.1 → 6.7.0
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/index.cjs +292 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -8
- package/dist/index.d.ts +65 -8
- package/dist/index.js +295 -20
- package/dist/index.js.map +1 -1
- package/package.json +28 -19
package/dist/index.js
CHANGED
|
@@ -2107,7 +2107,16 @@ var Runtime = class {
|
|
|
2107
2107
|
init_load_artifact_bundle();
|
|
2108
2108
|
import { resolve as resolvePath2 } from "path";
|
|
2109
2109
|
import { mkdirSync } from "fs";
|
|
2110
|
+
import { homedir } from "os";
|
|
2110
2111
|
import { z } from "zod";
|
|
2112
|
+
function resolveObjectStackHome() {
|
|
2113
|
+
const raw = process.env.OS_HOME?.trim();
|
|
2114
|
+
if (raw && raw.length > 0) {
|
|
2115
|
+
if (raw.startsWith("~")) return resolvePath2(homedir(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
2116
|
+
return resolvePath2(raw);
|
|
2117
|
+
}
|
|
2118
|
+
return resolvePath2(homedir(), ".objectstack");
|
|
2119
|
+
}
|
|
2111
2120
|
var StandaloneStackConfigSchema = z.object({
|
|
2112
2121
|
databaseUrl: z.string().optional(),
|
|
2113
2122
|
databaseAuthToken: z.string().optional(),
|
|
@@ -2138,7 +2147,7 @@ async function createStandaloneStack(config) {
|
|
|
2138
2147
|
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2139
2148
|
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
|
|
2140
2149
|
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
|
|
2141
|
-
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? `file:${resolvePath2(
|
|
2150
|
+
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`;
|
|
2142
2151
|
const dbAuthToken = cfg.databaseAuthToken ?? process.env.OS_DATABASE_AUTH_TOKEN?.trim() ?? process.env.TURSO_AUTH_TOKEN?.trim();
|
|
2143
2152
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2144
2153
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
@@ -2147,7 +2156,14 @@ async function createStandaloneStack(config) {
|
|
|
2147
2156
|
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2148
2157
|
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2149
2158
|
} else if (dbDriver === "turso") {
|
|
2150
|
-
|
|
2159
|
+
let TursoDriver;
|
|
2160
|
+
try {
|
|
2161
|
+
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
2162
|
+
} catch (err) {
|
|
2163
|
+
throw new Error(
|
|
2164
|
+
`[StandaloneStack] libsql/turso URL detected ("${dbUrl}") but @objectstack/driver-turso is not installed. Install it with: npm install @objectstack/driver-turso (or use a file: URL to default to better-sqlite3). (${err?.message ?? err})`
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2151
2167
|
driverPlugin = new DriverPlugin2(
|
|
2152
2168
|
new TursoDriver({ url: dbUrl, authToken: dbAuthToken })
|
|
2153
2169
|
);
|
|
@@ -2248,7 +2264,7 @@ async function createStandaloneStack(config) {
|
|
|
2248
2264
|
|
|
2249
2265
|
// src/default-host.ts
|
|
2250
2266
|
import { resolve as resolvePath3 } from "path";
|
|
2251
|
-
import { existsSync } from "fs";
|
|
2267
|
+
import { existsSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
2252
2268
|
init_load_artifact_bundle();
|
|
2253
2269
|
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
2254
2270
|
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath3(cwd, "dist/objectstack.json");
|
|
@@ -2258,12 +2274,42 @@ function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
|
2258
2274
|
}
|
|
2259
2275
|
async function createDefaultHostConfig(options = {}) {
|
|
2260
2276
|
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
2261
|
-
|
|
2277
|
+
let resolvedArtifact = resolveDefaultArtifactPath(standaloneOpts.artifactPath);
|
|
2262
2278
|
if (!resolvedArtifact && requireArtifact) {
|
|
2263
2279
|
throw new Error(
|
|
2264
2280
|
"[createDefaultHostConfig] No artifact source available. Set OS_ARTIFACT_PATH (file path or http(s):// URL), place the artifact at <cwd>/dist/objectstack.json, or pass `{ artifactPath: ... }` explicitly. To boot an empty kernel anyway, pass `{ requireArtifact: false }`."
|
|
2265
2281
|
);
|
|
2266
2282
|
}
|
|
2283
|
+
if (!resolvedArtifact && !requireArtifact) {
|
|
2284
|
+
const home = resolveObjectStackHome();
|
|
2285
|
+
const stubPath = resolvePath3(home, "dist/objectstack.json");
|
|
2286
|
+
if (!existsSync(stubPath)) {
|
|
2287
|
+
mkdirSync2(resolvePath3(stubPath, ".."), { recursive: true });
|
|
2288
|
+
writeFileSync(
|
|
2289
|
+
stubPath,
|
|
2290
|
+
JSON.stringify(
|
|
2291
|
+
{
|
|
2292
|
+
manifest: {
|
|
2293
|
+
id: "com.objectstack.empty",
|
|
2294
|
+
name: "empty",
|
|
2295
|
+
version: "0.0.0",
|
|
2296
|
+
type: "app",
|
|
2297
|
+
description: "Empty starter kernel \u2014 install apps via the Studio marketplace."
|
|
2298
|
+
},
|
|
2299
|
+
objects: [],
|
|
2300
|
+
views: [],
|
|
2301
|
+
apps: [],
|
|
2302
|
+
flows: [],
|
|
2303
|
+
requires: []
|
|
2304
|
+
},
|
|
2305
|
+
null,
|
|
2306
|
+
2
|
|
2307
|
+
),
|
|
2308
|
+
"utf8"
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
resolvedArtifact = stubPath;
|
|
2312
|
+
}
|
|
2267
2313
|
return createStandaloneStack({
|
|
2268
2314
|
...standaloneOpts,
|
|
2269
2315
|
artifactPath: resolvedArtifact
|
|
@@ -6821,6 +6867,8 @@ var KernelManager = class {
|
|
|
6821
6867
|
this.maxSize = config.maxSize ?? 32;
|
|
6822
6868
|
this.ttlMs = config.ttlMs ?? 15 * 60 * 1e3;
|
|
6823
6869
|
this.logger = config.logger ?? console;
|
|
6870
|
+
this.freshnessProbe = config.freshnessProbe;
|
|
6871
|
+
this.staleCheckIntervalMs = config.staleCheckIntervalMs ?? 1e4;
|
|
6824
6872
|
}
|
|
6825
6873
|
/** Returns the currently cached environmentIds (ordered by insertion). */
|
|
6826
6874
|
keys() {
|
|
@@ -6843,8 +6891,31 @@ var KernelManager = class {
|
|
|
6843
6891
|
if (this.ttlMs > 0 && Date.now() - existing.lastAccess > this.ttlMs) {
|
|
6844
6892
|
await this.evict(environmentId);
|
|
6845
6893
|
} else {
|
|
6846
|
-
|
|
6847
|
-
|
|
6894
|
+
if (this.freshnessProbe) {
|
|
6895
|
+
const now = Date.now();
|
|
6896
|
+
if (now - existing.lastStaleCheckAt >= this.staleCheckIntervalMs) {
|
|
6897
|
+
existing.lastStaleCheckAt = now;
|
|
6898
|
+
let stale = false;
|
|
6899
|
+
try {
|
|
6900
|
+
stale = await this.freshnessProbe(environmentId, existing.createdAt);
|
|
6901
|
+
} catch (err) {
|
|
6902
|
+
this.logger.warn?.("[KernelManager] freshness probe failed", { environmentId, err });
|
|
6903
|
+
}
|
|
6904
|
+
if (stale) {
|
|
6905
|
+
this.logger.info?.("[KernelManager] kernel evicted by freshness probe", { environmentId });
|
|
6906
|
+
await this.evict(environmentId);
|
|
6907
|
+
} else {
|
|
6908
|
+
existing.lastAccess = Date.now();
|
|
6909
|
+
return existing.kernel;
|
|
6910
|
+
}
|
|
6911
|
+
} else {
|
|
6912
|
+
existing.lastAccess = Date.now();
|
|
6913
|
+
return existing.kernel;
|
|
6914
|
+
}
|
|
6915
|
+
} else {
|
|
6916
|
+
existing.lastAccess = Date.now();
|
|
6917
|
+
return existing.kernel;
|
|
6918
|
+
}
|
|
6848
6919
|
}
|
|
6849
6920
|
}
|
|
6850
6921
|
const inflight = this.pending.get(environmentId);
|
|
@@ -6852,7 +6923,7 @@ var KernelManager = class {
|
|
|
6852
6923
|
const promise = (async () => {
|
|
6853
6924
|
const kernel = await this.factory.create(environmentId);
|
|
6854
6925
|
const now = Date.now();
|
|
6855
|
-
this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now });
|
|
6926
|
+
this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now, lastStaleCheckAt: now });
|
|
6856
6927
|
await this.enforceMaxSize();
|
|
6857
6928
|
return kernel;
|
|
6858
6929
|
})();
|
|
@@ -7020,6 +7091,30 @@ var ArtifactApiClient = class {
|
|
|
7020
7091
|
if (!found?.headCommitId) return null;
|
|
7021
7092
|
return { commitId: String(found.headCommitId), publishedAt: found.headPublishedAt ?? null };
|
|
7022
7093
|
}
|
|
7094
|
+
/**
|
|
7095
|
+
* Cheap freshness probe — returns the env's `last_published_at`
|
|
7096
|
+
* (and best-effort current commit) without rebuilding the artifact.
|
|
7097
|
+
* Used by `KernelManager` on cache hits to detect when a per-env
|
|
7098
|
+
* kernel has been invalidated by an upstream change (marketplace
|
|
7099
|
+
* install/uninstall, artifact publish) so it can be rebuilt
|
|
7100
|
+
* without waiting for the 15-minute LRU TTL to expire.
|
|
7101
|
+
*
|
|
7102
|
+
* Returns `null` on definitive 404 / unknown env. Errors propagate
|
|
7103
|
+
* (caller decides whether to treat unreachable cloud as fresh or
|
|
7104
|
+
* stale — typically fresh, so a brief outage doesn't churn every
|
|
7105
|
+
* cached kernel).
|
|
7106
|
+
*/
|
|
7107
|
+
async getFreshness(environmentId) {
|
|
7108
|
+
const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/freshness`;
|
|
7109
|
+
const res = await this.request(url);
|
|
7110
|
+
if (res === null) return null;
|
|
7111
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
7112
|
+
if (!body || typeof body !== "object") return null;
|
|
7113
|
+
const envId = typeof body.environmentId === "string" ? body.environmentId : environmentId;
|
|
7114
|
+
const lastPublishedAt = typeof body.lastPublishedAt === "string" ? body.lastPublishedAt : null;
|
|
7115
|
+
const commitId = typeof body.commitId === "string" ? body.commitId : null;
|
|
7116
|
+
return { environmentId: envId, lastPublishedAt, commitId };
|
|
7117
|
+
}
|
|
7023
7118
|
/** Drop cached entries for a project (and any matching hostname). */
|
|
7024
7119
|
invalidate(environmentId) {
|
|
7025
7120
|
this.artifactCache.delete(environmentId);
|
|
@@ -7233,7 +7328,14 @@ async function createDriver(driverType, databaseUrl, authToken) {
|
|
|
7233
7328
|
}
|
|
7234
7329
|
case "libsql":
|
|
7235
7330
|
case "turso": {
|
|
7236
|
-
|
|
7331
|
+
let TursoDriver;
|
|
7332
|
+
try {
|
|
7333
|
+
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
7334
|
+
} catch (err) {
|
|
7335
|
+
throw new Error(
|
|
7336
|
+
`[ArtifactEnvironmentRegistry] libsql/turso driver requested but @objectstack/driver-turso is not installed. Install it with: npm install @objectstack/driver-turso. (${err?.message ?? err})`
|
|
7337
|
+
);
|
|
7338
|
+
}
|
|
7237
7339
|
return new TursoDriver({ url: databaseUrl, authToken });
|
|
7238
7340
|
}
|
|
7239
7341
|
case "postgres":
|
|
@@ -7771,7 +7873,30 @@ var ArtifactKernelFactory = class {
|
|
|
7771
7873
|
};
|
|
7772
7874
|
|
|
7773
7875
|
// src/cloud/auth-proxy-plugin.ts
|
|
7876
|
+
import { createHmac as createHmac3, randomUUID as randomUUID2 } from "crypto";
|
|
7774
7877
|
var AUTH_PREFIX = "/api/v1/auth";
|
|
7878
|
+
function signSessionCookieValue(rawToken, secret) {
|
|
7879
|
+
const signature = createHmac3("sha256", secret).update(rawToken).digest("base64");
|
|
7880
|
+
return encodeURIComponent(`${rawToken}.${signature}`);
|
|
7881
|
+
}
|
|
7882
|
+
function buildSetCookieHeader(name, encodedValue, attrs, maxAgeSec) {
|
|
7883
|
+
const parts = [`${name}=${encodedValue}`];
|
|
7884
|
+
const a = attrs ?? {};
|
|
7885
|
+
if (a.path) parts.push(`Path=${a.path}`);
|
|
7886
|
+
else parts.push("Path=/");
|
|
7887
|
+
if (Number.isFinite(maxAgeSec) && maxAgeSec > 0) parts.push(`Max-Age=${Math.floor(maxAgeSec)}`);
|
|
7888
|
+
if (a.domain) parts.push(`Domain=${a.domain}`);
|
|
7889
|
+
if (a.sameSite) {
|
|
7890
|
+
const ss = String(a.sameSite);
|
|
7891
|
+
parts.push(`SameSite=${ss.charAt(0).toUpperCase() + ss.slice(1)}`);
|
|
7892
|
+
} else {
|
|
7893
|
+
parts.push("SameSite=Lax");
|
|
7894
|
+
}
|
|
7895
|
+
if (a.secure) parts.push("Secure");
|
|
7896
|
+
if (a.httpOnly !== false) parts.push("HttpOnly");
|
|
7897
|
+
if (a.partitioned) parts.push("Partitioned");
|
|
7898
|
+
return parts.join("; ");
|
|
7899
|
+
}
|
|
7775
7900
|
function pickHandler(svc) {
|
|
7776
7901
|
if (!svc) return void 0;
|
|
7777
7902
|
if (typeof svc.handleRequest === "function") return svc.handleRequest.bind(svc);
|
|
@@ -7865,6 +7990,115 @@ var AuthProxyPlugin = class {
|
|
|
7865
7990
|
return c.json({ hasOwner: true });
|
|
7866
7991
|
}
|
|
7867
7992
|
}
|
|
7993
|
+
if (c.req.method === "POST" && subPath === "sso-handoff-issue") {
|
|
7994
|
+
try {
|
|
7995
|
+
const expected = (process.env.OS_CLOUD_API_KEY ?? "").trim();
|
|
7996
|
+
if (!expected) {
|
|
7997
|
+
return c.json({ error: "sso_handoff_disabled", reason: "OS_CLOUD_API_KEY unset on env runtime" }, 503);
|
|
7998
|
+
}
|
|
7999
|
+
const authz = c.req.header("authorization") ?? "";
|
|
8000
|
+
const provided = authz.toLowerCase().startsWith("bearer ") ? authz.slice(7).trim() : "";
|
|
8001
|
+
if (!provided || provided !== expected) {
|
|
8002
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
8003
|
+
}
|
|
8004
|
+
if (typeof authSvc?.getAuthContext !== "function") {
|
|
8005
|
+
return c.json({ error: "auth_service_unavailable" }, 503);
|
|
8006
|
+
}
|
|
8007
|
+
const handoffAuthCtx = await authSvc.getAuthContext();
|
|
8008
|
+
const internal = handoffAuthCtx?.internalAdapter;
|
|
8009
|
+
if (!internal?.createVerificationValue) {
|
|
8010
|
+
return c.json({ error: "verification_api_unavailable" }, 503);
|
|
8011
|
+
}
|
|
8012
|
+
let body = {};
|
|
8013
|
+
try {
|
|
8014
|
+
body = await c.req.json();
|
|
8015
|
+
} catch {
|
|
8016
|
+
body = {};
|
|
8017
|
+
}
|
|
8018
|
+
const email = String(body?.email ?? "").toLowerCase().trim();
|
|
8019
|
+
if (!email) return c.json({ error: "email_required" }, 400);
|
|
8020
|
+
const name = body?.name == null ? null : String(body.name);
|
|
8021
|
+
const by = body?.by == null ? "service" : String(body.by);
|
|
8022
|
+
const envIdInBody = body?.envId == null ? null : String(body.envId);
|
|
8023
|
+
const handoff = randomUUID2().replace(/-/g, "") + randomUUID2().replace(/-/g, "");
|
|
8024
|
+
const ttlSec = 60;
|
|
8025
|
+
const expiresAt = new Date(Date.now() + ttlSec * 1e3);
|
|
8026
|
+
await internal.createVerificationValue({
|
|
8027
|
+
identifier: `sso-handoff:${handoff}`,
|
|
8028
|
+
value: JSON.stringify({ email, name, by, envId: envIdInBody ?? environmentId }),
|
|
8029
|
+
expiresAt
|
|
8030
|
+
});
|
|
8031
|
+
return c.json({
|
|
8032
|
+
token: handoff,
|
|
8033
|
+
expiresAt: expiresAt.toISOString(),
|
|
8034
|
+
ttlSec
|
|
8035
|
+
});
|
|
8036
|
+
} catch (err) {
|
|
8037
|
+
ctx.logger?.error?.("[AuthProxyPlugin] sso-handoff-issue failed", err instanceof Error ? err : new Error(String(err)));
|
|
8038
|
+
return c.json({ error: "sso_handoff_issue_failed", message: String(err?.message ?? err) }, 500);
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
if (c.req.method === "GET" && subPath === "sso-exchange") {
|
|
8042
|
+
try {
|
|
8043
|
+
const token = (url.searchParams.get("token") ?? "").trim();
|
|
8044
|
+
const nextRaw = url.searchParams.get("next") ?? "/";
|
|
8045
|
+
const next = nextRaw.startsWith("/") ? nextRaw : "/";
|
|
8046
|
+
if (!token) return c.text("missing token", 400);
|
|
8047
|
+
if (typeof authSvc?.getAuthContext !== "function") {
|
|
8048
|
+
return c.text("auth service unavailable", 503);
|
|
8049
|
+
}
|
|
8050
|
+
const authCtx = await authSvc.getAuthContext();
|
|
8051
|
+
const internal = authCtx?.internalAdapter;
|
|
8052
|
+
if (!internal?.consumeVerificationValue) {
|
|
8053
|
+
return c.text("verification API unavailable", 503);
|
|
8054
|
+
}
|
|
8055
|
+
const consumed = await internal.consumeVerificationValue(`sso-handoff:${token}`);
|
|
8056
|
+
if (!consumed) return c.text("invalid or expired token", 401);
|
|
8057
|
+
const expiresAt = consumed?.expiresAt ? new Date(consumed.expiresAt).getTime() : 0;
|
|
8058
|
+
if (!expiresAt || expiresAt < Date.now()) return c.text("expired token", 401);
|
|
8059
|
+
let payload = {};
|
|
8060
|
+
try {
|
|
8061
|
+
payload = JSON.parse(String(consumed.value));
|
|
8062
|
+
} catch {
|
|
8063
|
+
payload = { email: String(consumed.value) };
|
|
8064
|
+
}
|
|
8065
|
+
const email = String(payload.email ?? "").toLowerCase().trim();
|
|
8066
|
+
if (!email) return c.text("handoff missing email", 400);
|
|
8067
|
+
const found = await internal.findUserByEmail(email, { includeAccounts: true });
|
|
8068
|
+
let userId = found?.user?.id;
|
|
8069
|
+
let hasCredentialAccount = (found?.accounts ?? []).some((a) => a.providerId === "credential" && a.password);
|
|
8070
|
+
if (!userId) {
|
|
8071
|
+
const created = await internal.createUser({
|
|
8072
|
+
email,
|
|
8073
|
+
name: payload.name ?? email,
|
|
8074
|
+
emailVerified: true
|
|
8075
|
+
});
|
|
8076
|
+
userId = created?.id;
|
|
8077
|
+
hasCredentialAccount = false;
|
|
8078
|
+
}
|
|
8079
|
+
if (!userId) return c.text("failed to provision user", 500);
|
|
8080
|
+
const session = await internal.createSession(userId, false);
|
|
8081
|
+
const rawToken = session?.token;
|
|
8082
|
+
const sessionExpiresAt = session?.expiresAt ? new Date(session.expiresAt) : new Date(Date.now() + 7 * 24 * 3600 * 1e3);
|
|
8083
|
+
if (!rawToken) return c.text("failed to mint session", 500);
|
|
8084
|
+
const secret = authCtx?.secret ?? "";
|
|
8085
|
+
if (!secret) return c.text("auth secret unavailable", 503);
|
|
8086
|
+
const cookieName = authCtx?.authCookies?.sessionToken?.name ?? "better-auth.session_token";
|
|
8087
|
+
const cookieAttrs = authCtx?.authCookies?.sessionToken?.attributes ?? {};
|
|
8088
|
+
const encoded = signSessionCookieValue(rawToken, secret);
|
|
8089
|
+
const maxAgeSec = Math.max(60, Math.floor((sessionExpiresAt.getTime() - Date.now()) / 1e3));
|
|
8090
|
+
const setCookie = buildSetCookieHeader(cookieName, encoded, cookieAttrs, maxAgeSec);
|
|
8091
|
+
const finalNext = hasCredentialAccount ? next : `/_console/system/profile?recovery_needed=true&next=${encodeURIComponent(next)}`;
|
|
8092
|
+
const headers = new Headers();
|
|
8093
|
+
headers.set("Set-Cookie", setCookie);
|
|
8094
|
+
headers.set("Location", finalNext);
|
|
8095
|
+
headers.set("Cache-Control", "no-store");
|
|
8096
|
+
return new Response(null, { status: 302, headers });
|
|
8097
|
+
} catch (err) {
|
|
8098
|
+
ctx.logger?.error?.("[AuthProxyPlugin] sso-exchange failed", err instanceof Error ? err : new Error(String(err)));
|
|
8099
|
+
return c.text(`sso-exchange failed: ${err?.message ?? String(err)}`, 500);
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
7868
8102
|
const fn = await resolveAuthHandler(authSvc);
|
|
7869
8103
|
if (!fn) {
|
|
7870
8104
|
return c.json({ error: "auth_service_unavailable", environmentId }, 503);
|
|
@@ -8047,20 +8281,46 @@ var RuntimeConfigPlugin = class {
|
|
|
8047
8281
|
return;
|
|
8048
8282
|
}
|
|
8049
8283
|
const rawApp = httpServer.getRawApp();
|
|
8050
|
-
const
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8284
|
+
const features = {
|
|
8285
|
+
installLocal: this.installLocal,
|
|
8286
|
+
marketplace: true
|
|
8287
|
+
};
|
|
8288
|
+
let envRegistry = null;
|
|
8289
|
+
try {
|
|
8290
|
+
envRegistry = ctx.getService("env-registry");
|
|
8291
|
+
} catch {
|
|
8292
|
+
}
|
|
8293
|
+
const handler = async (c) => {
|
|
8294
|
+
const rawHost = c.req.header("host") ?? "";
|
|
8295
|
+
const host = rawHost.split(":")[0].toLowerCase().trim();
|
|
8296
|
+
let defaultEnvironmentId;
|
|
8297
|
+
let defaultOrgId;
|
|
8298
|
+
let resolvedSingleEnv = this.singleEnvironment;
|
|
8299
|
+
if (envRegistry && host && typeof envRegistry.resolveHostname === "function") {
|
|
8300
|
+
try {
|
|
8301
|
+
const resolved = await envRegistry.resolveHostname(host);
|
|
8302
|
+
if (resolved?.environmentId) {
|
|
8303
|
+
defaultEnvironmentId = resolved.environmentId;
|
|
8304
|
+
if (resolved.organizationId) defaultOrgId = String(resolved.organizationId);
|
|
8305
|
+
resolvedSingleEnv = true;
|
|
8306
|
+
}
|
|
8307
|
+
} catch {
|
|
8308
|
+
}
|
|
8056
8309
|
}
|
|
8310
|
+
return c.json({
|
|
8311
|
+
cloudUrl: this.cloudUrl,
|
|
8312
|
+
singleEnvironment: resolvedSingleEnv,
|
|
8313
|
+
defaultOrgId,
|
|
8314
|
+
defaultEnvironmentId,
|
|
8315
|
+
features
|
|
8316
|
+
});
|
|
8057
8317
|
};
|
|
8058
|
-
const handler = (c) => c.json(payload);
|
|
8059
8318
|
rawApp.get("/api/v1/runtime/config", handler);
|
|
8060
8319
|
rawApp.get("/api/v1/studio/runtime-config", handler);
|
|
8061
8320
|
ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
|
|
8062
8321
|
cloudUrl: this.cloudUrl || "(empty)",
|
|
8063
|
-
installLocal: this.installLocal
|
|
8322
|
+
installLocal: this.installLocal,
|
|
8323
|
+
perHostEnvResolution: !!envRegistry
|
|
8064
8324
|
});
|
|
8065
8325
|
});
|
|
8066
8326
|
};
|
|
@@ -8261,7 +8521,21 @@ var ObjectOSEnvironmentPlugin = class {
|
|
|
8261
8521
|
factory,
|
|
8262
8522
|
maxSize: this.config.kernelCacheSize,
|
|
8263
8523
|
ttlMs: this.config.kernelTtlMs,
|
|
8264
|
-
logger: ctx.logger
|
|
8524
|
+
logger: ctx.logger,
|
|
8525
|
+
// Only the HTTP client exposes /freshness; file-mode (CLI dev)
|
|
8526
|
+
// has no upstream to probe.
|
|
8527
|
+
freshnessProbe: this.config.controlPlaneUrl === "file" ? void 0 : async (envId, builtAtMs) => {
|
|
8528
|
+
const fresh = await client.getFreshness(envId);
|
|
8529
|
+
if (!fresh) return false;
|
|
8530
|
+
const t = fresh.lastPublishedAt ? Date.parse(fresh.lastPublishedAt) : NaN;
|
|
8531
|
+
if (!Number.isFinite(t)) return false;
|
|
8532
|
+
if (t <= builtAtMs) return false;
|
|
8533
|
+
try {
|
|
8534
|
+
client.invalidate(envId);
|
|
8535
|
+
} catch {
|
|
8536
|
+
}
|
|
8537
|
+
return true;
|
|
8538
|
+
}
|
|
8265
8539
|
});
|
|
8266
8540
|
this.kernelManager = kernelManager;
|
|
8267
8541
|
ctx.registerService("env-registry", envRegistry);
|
|
@@ -8313,7 +8587,7 @@ async function createObjectOSStack(config) {
|
|
|
8313
8587
|
}
|
|
8314
8588
|
|
|
8315
8589
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
8316
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
8590
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
8317
8591
|
import { join, resolve } from "path";
|
|
8318
8592
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
8319
8593
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
@@ -8446,8 +8720,8 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8446
8720
|
installedBy: userId
|
|
8447
8721
|
};
|
|
8448
8722
|
try {
|
|
8449
|
-
|
|
8450
|
-
|
|
8723
|
+
mkdirSync3(this.storageDir, { recursive: true });
|
|
8724
|
+
writeFileSync2(join(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
8451
8725
|
} catch (err) {
|
|
8452
8726
|
return c.json({
|
|
8453
8727
|
success: false,
|
|
@@ -8866,6 +9140,7 @@ export {
|
|
|
8866
9140
|
resolveDefaultArtifactPath,
|
|
8867
9141
|
resolveErrorReporter,
|
|
8868
9142
|
resolveMetrics,
|
|
9143
|
+
resolveObjectStackHome,
|
|
8869
9144
|
resolveRequestId,
|
|
8870
9145
|
seedPlatformSsoClient
|
|
8871
9146
|
};
|