@shubh90/app-runtime 0.4.0 → 0.4.1
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/README.md +13 -4
- package/dist/platform/index.d.ts +2 -1
- package/dist/server/build-marker.d.ts +10 -0
- package/dist/server/build-marker.js +51 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/sentry-tunnel.d.ts +6 -0
- package/dist/server/sentry-tunnel.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,11 +29,20 @@ and autosave never fights a kit-dirtied tree. Apps pin a version; a breaking
|
|
|
29
29
|
Plaza contract change is a semver major with a deprecation window, not a flag
|
|
30
30
|
day.
|
|
31
31
|
|
|
32
|
-
## Publishing (
|
|
32
|
+
## Publishing (npmjs, public)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
Every released version (0.1.0 →) lives on the PUBLIC npm registry — that is
|
|
35
|
+
where app installs resolve, with no auth to provision in any build
|
|
36
|
+
environment, which is the point. Publish from a machine logged in as
|
|
37
|
+
`shubh90`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# from packages/app-runtime, after npm run check && npm run build:
|
|
41
|
+
npm publish --registry=https://registry.npmjs.org
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `app-runtime-v*` tag workflow publishes a mirror copy to GitHub Packages;
|
|
45
|
+
nothing installs from there today, so treat npmjs as the source of truth.
|
|
37
46
|
|
|
38
47
|
```bash
|
|
39
48
|
# maintainers, from packages/app-runtime:
|
package/dist/platform/index.d.ts
CHANGED
|
@@ -51,7 +51,8 @@ export type MiiAnalyticsPolicy = {
|
|
|
51
51
|
/** posthog-js init options: `posthog.init("in-house", options)`. */
|
|
52
52
|
readonly options: {
|
|
53
53
|
readonly api_host: string;
|
|
54
|
-
|
|
54
|
+
/** posthog-js "defaults era" — a date literal its ConfigDefaults union accepts. */
|
|
55
|
+
readonly defaults: "2026-06-25";
|
|
55
56
|
readonly person_profiles: "identified_only";
|
|
56
57
|
readonly advanced_disable_flags: true;
|
|
57
58
|
readonly disable_external_dependency_loading: true;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type BuildMarkerOptions = {
|
|
2
|
+
/** Defaults to VERCEL_DEPLOYMENT_ID / VERCEL_GIT_COMMIT_SHA, read per request. */
|
|
3
|
+
readonly deploymentId?: () => string | undefined;
|
|
4
|
+
readonly commitSha?: () => string | undefined;
|
|
5
|
+
};
|
|
6
|
+
/** GET + OPTIONS for /api/mii-build; mount both from one factory. */
|
|
7
|
+
export declare function createBuildMarkerHandler(options?: BuildMarkerOptions): {
|
|
8
|
+
GET(request: Request): Response;
|
|
9
|
+
OPTIONS(request: Request): Response;
|
|
10
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Which build of this app is currently serving.
|
|
2
|
+
//
|
|
3
|
+
// Plaza's App pane is an iframe of the deployment. When an agent ships, the
|
|
4
|
+
// URL does not move, so nothing tells the pane the page underneath it is now
|
|
5
|
+
// stale — the pane polls this and reloads the frame when the answer changes.
|
|
6
|
+
// Plaza's server asks the same question for agents (deployedCommitOf), which
|
|
7
|
+
// is why this stays a route on the app: "the domain answers with this commit"
|
|
8
|
+
// is a stronger claim than any deploy provider's READY, and it needs no
|
|
9
|
+
// provider token to work.
|
|
10
|
+
//
|
|
11
|
+
// Deliberately touches nothing: no database, no imports beyond the origin
|
|
12
|
+
// list. It is polled every few seconds by every open pane, so it has to be
|
|
13
|
+
// the cheapest route in the app — the health check next door opens a Postgres
|
|
14
|
+
// connection and would wake the database on a timer forever.
|
|
15
|
+
import { PLAZA_FRAME_ORIGINS } from "../platform/index.js";
|
|
16
|
+
function corsHeaders(origin) {
|
|
17
|
+
// Echo the caller's origin only when it is a Plaza we know. `*` would let
|
|
18
|
+
// any page on the internet read this; harmless today, but this is the
|
|
19
|
+
// pattern the next endpoint copies, and the next one may not be harmless.
|
|
20
|
+
return origin !== null && PLAZA_FRAME_ORIGINS.includes(origin)
|
|
21
|
+
? {
|
|
22
|
+
"Access-Control-Allow-Origin": origin,
|
|
23
|
+
Vary: "Origin",
|
|
24
|
+
"Cache-Control": "no-store"
|
|
25
|
+
}
|
|
26
|
+
: { "Cache-Control": "no-store" };
|
|
27
|
+
}
|
|
28
|
+
/** GET + OPTIONS for /api/mii-build; mount both from one factory. */
|
|
29
|
+
export function createBuildMarkerHandler(options = {}) {
|
|
30
|
+
const deploymentId = options.deploymentId ?? (() => process.env.VERCEL_DEPLOYMENT_ID);
|
|
31
|
+
const commitSha = options.commitSha ?? (() => process.env.VERCEL_GIT_COMMIT_SHA);
|
|
32
|
+
return {
|
|
33
|
+
GET(request) {
|
|
34
|
+
// The deployment id changes on every deploy, including a redeploy of the
|
|
35
|
+
// same commit — which is exactly the question being asked. The commit
|
|
36
|
+
// rides along because it is the half a human can act on.
|
|
37
|
+
const deployment = deploymentId() ?? null;
|
|
38
|
+
const commit = commitSha() ?? null;
|
|
39
|
+
return Response.json({ build: deployment ?? commit, commit }, { headers: corsHeaders(request.headers.get("origin")) });
|
|
40
|
+
},
|
|
41
|
+
OPTIONS(request) {
|
|
42
|
+
return new Response(null, {
|
|
43
|
+
status: 204,
|
|
44
|
+
headers: {
|
|
45
|
+
...corsHeaders(request.headers.get("origin")),
|
|
46
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type DbRetryOptions } from "./db-retry.js";
|
|
2
2
|
export { withDbRetry, isTransientConnectionError, DB_RETRY_ATTEMPTS } from "./db-retry.js";
|
|
3
3
|
export type { DbRetryOptions } from "./db-retry.js";
|
|
4
|
+
export { createBuildMarkerHandler, type BuildMarkerOptions } from "./build-marker.js";
|
|
5
|
+
export { createSentryTunnelHandler, type SentryTunnelOptions } from "./sentry-tunnel.js";
|
|
4
6
|
export type HealthHandlerOptions = {
|
|
5
7
|
/** Defaults to process.env.DATABASE_URL, read per request. */
|
|
6
8
|
readonly databaseUrl?: () => string | undefined;
|
package/dist/server/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import postgres from "postgres";
|
|
7
7
|
import { withDbRetry } from "./db-retry.js";
|
|
8
8
|
export { withDbRetry, isTransientConnectionError, DB_RETRY_ATTEMPTS } from "./db-retry.js";
|
|
9
|
+
export { createBuildMarkerHandler } from "./build-marker.js";
|
|
10
|
+
export { createSentryTunnelHandler } from "./sentry-tunnel.js";
|
|
9
11
|
/**
|
|
10
12
|
* GET /api/health — the readiness gate. The sandbox refuses to report an
|
|
11
13
|
* instance as up, and Plaza refuses to wake one, until this answers ok. It
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type SentryTunnelOptions = {
|
|
2
|
+
/** Test hook; defaults to global fetch. */
|
|
3
|
+
readonly fetch?: typeof fetch;
|
|
4
|
+
};
|
|
5
|
+
/** POST for /monitoring: forward the envelope, answer what Sentry answered. */
|
|
6
|
+
export declare function createSentryTunnelHandler(options?: SentryTunnelOptions): (request: Request) => Promise<Response>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The Sentry tunnel: browsers post error envelopes to this app's own
|
|
2
|
+
// /monitoring route, which forwards them to Sentry — because ad-blockers
|
|
3
|
+
// block *.sentry.io from the browser, and a platform whose error reporting
|
|
4
|
+
// dies to a browser extension is blind exactly where it matters.
|
|
5
|
+
//
|
|
6
|
+
// The envelope's own header names the DSN, so the route needs no
|
|
7
|
+
// configuration; it forwards only to hosts that are plainly Sentry's, so an
|
|
8
|
+
// app cannot be turned into an open relay by a crafted envelope.
|
|
9
|
+
const ALLOWED_HOST = /(^|\.)(sentry\.io|ingest\.sentry\.io|ingest\.[a-z0-9-]+\.sentry\.io)$/;
|
|
10
|
+
/** POST for /monitoring: forward the envelope, answer what Sentry answered. */
|
|
11
|
+
export function createSentryTunnelHandler(options = {}) {
|
|
12
|
+
const doFetch = options.fetch ?? fetch;
|
|
13
|
+
return async function tunnel(request) {
|
|
14
|
+
const envelope = await request.text();
|
|
15
|
+
const newline = envelope.indexOf("\n");
|
|
16
|
+
const headerLine = newline === -1 ? envelope : envelope.slice(0, newline);
|
|
17
|
+
let dsn;
|
|
18
|
+
try {
|
|
19
|
+
const header = JSON.parse(headerLine);
|
|
20
|
+
if (typeof header.dsn !== "string") {
|
|
21
|
+
return Response.json({ error: "Envelope names no DSN." }, { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
dsn = new URL(header.dsn);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return Response.json({ error: "Not a Sentry envelope." }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
if (!ALLOWED_HOST.test(dsn.hostname)) {
|
|
29
|
+
// A DSN pointing anywhere else would make this route an open relay.
|
|
30
|
+
return Response.json({ error: "DSN host is not Sentry." }, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
const projectId = dsn.pathname.replace(/^\/+/, "");
|
|
33
|
+
if (!/^\d+$/.test(projectId)) {
|
|
34
|
+
return Response.json({ error: "DSN names no project." }, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
const upstream = await doFetch(`https://${dsn.hostname}/api/${projectId}/envelope/`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/x-sentry-envelope" },
|
|
39
|
+
body: envelope
|
|
40
|
+
});
|
|
41
|
+
return new Response(null, { status: upstream.status });
|
|
42
|
+
};
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shubh90/app-runtime",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The platform contract every mii org app depends on — sign-in, and the Next.js config an app must not diverge from. A versioned package, not files copied into each repo.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|