Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "packages/svelte-core"]
path = packages/svelte-core
url = git@github.com:OpenShock/svelte-core.git
url = https://github.com/OpenShock/svelte-core.git
35 changes: 10 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,15 @@ This is the frontend for the OpenShock project that interfaces with the OpenShoc
- `pnpm run dev` - Start the development server
- `pnpm run build` - Build the project
- `pnpm run preview` - Preview the built project
- `pnpx openapi-generator-cli generate` - Generate the API client
- `pnpm run regen-api` - Generate the API client

## Setup

To get started with development, you will need to have **Node.js** and **pnpm** installed.

We recommend using a version manager like [Volta](https://docs.volta.sh/guide/getting-started) to manage your Node.js versions.

### Install Node.js and pnpm

Install on Linux:

```bash
curl https://get.volta.sh | bash
```

Install on Windows:
To clone the project you may need to rewrite git HTTPS urls to SSH for the submodule checkout to work on your machine.
You can do this with `git config --global url."git@github.com:".insteadOf "https://github.com/"`

```pwsh
winget install Volta.Volta
```

Then you can install the required **Node.js** and **pnpm** version by running a single command in the repository root directory:

```bash
volta install node
volta install pnpm
```
To get started with development, you will need to have **Node.js** and **pnpm** installed.
We recommend using a node version manager of your choice.

### Install dependencies and build project

Expand Down Expand Up @@ -154,7 +135,6 @@ src/ # Application source code
- `vite.config.ts` — Vite bundler config.
- `svelte.config.js` — SvelteKit configuration.
- `components.json` — Component metadata for shadcn.
- `openapitools.json` — Codegen config from OpenAPI spec.
- `playwright.config.ts` — End-to-end testing configuration (Playwright).

### 🌐 Environment Variables
Expand All @@ -178,6 +158,11 @@ Any file ending with `.local` are meant for setting local development variables
- `Dockerfile` — Container definition for deployment or local dev.
- `.dockerignore` — Files to exclude from Docker build context.

## Selfhosting

A guide to selfhost can be found [on the wiki.](https://wiki.openshock.org/guides/selfhosting)
Alternatively you can deploy it to cloudflare workers using wrangler (or forking it).

## Support

You can support the openshock dev team here: [Sponsor OpenShock](https://github.com/sponsors/OpenShock)
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
"@internationalized/date": "^3.12.2",
"@lucide/svelte": "^1.28.0",
"@microsoft/signalr": "^10.0.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/sdk-trace-base": "^2.10.0",
"@playwright/test": "^1.62.1",
"@sveltejs/adapter-cloudflare": "^7.2.9",
"@sveltejs/adapter-node": "^5.5.7",
Expand Down Expand Up @@ -70,17 +68,20 @@
"vaul-svelte": "1.0.0-next.7",
"vite-plugin-devtools-json": "^1.1.0",
"vite-plugin-mkcert": "^2.1.0",
"vitest": "^4.1.10"
"vitest": "^4.1.10",
"wrangler": "^4.117.0"
},
"dependencies": {
"@openshock/svelte-core": "workspace:*",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.221.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
"@opentelemetry/instrumentation": "^0.221.0",
"@opentelemetry/instrumentation-fetch": "^0.221.0",
"@opentelemetry/resources": "^2.10.0",
"@opentelemetry/sdk-logs": "^0.221.0",
"@opentelemetry/sdk-trace-node": "^2.10.0",
"@opentelemetry/sdk-trace-web": "^2.10.0",
"core": "workspace:@openshock/svelte-core@*",
"driver.js": "^1.8.0",
Expand Down
38 changes: 32 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 11 additions & 55 deletions src/instrumentation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,15 @@ import {
PUBLIC_SIGNOZ_LOGS_URL,
PUBLIC_SIGNOZ_TRACES_URL,
} from '$env/static/public';
import {
ROOT_CONTEXT,
context as otelContext,
trace as otelTrace,
type Context,
type ContextManager,
} from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { BasicTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { AsyncLocalStorage } from 'node:async_hooks';
import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace-node';

// Collector for SvelteKit's experimental server-side spans (handle/load/form actions,
// enabled via kit.experimental.tracing in vite.config.ts). Runs before any application
// code is imported, so it must stay self-contained: no $lib imports — the client
// telemetry module ($lib/telemetry/common) pulls in consent state and other
// browser-oriented code that has no business in the server bootstrap.
//
// Uses BasicTracerProvider + a hand-rolled AsyncLocalStorage context manager instead of
// NodeTracerProvider: sdk-trace-node pulls in @opentelemetry/context-async-hooks, whose
// bare `require("events")`/`require("async_hooks")` Cloudflare's Pages Functions bundler
// cannot resolve. The `node:`-prefixed import below bundles fine and resolves at runtime
// on workerd (nodejs_compat/nodejs_als) and Node alike.

class AlsContextManager implements ContextManager {
#als = new AsyncLocalStorage<Context>();

active(): Context {
return this.#als.getStore() ?? ROOT_CONTEXT;
}

with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
return this.#als.run(context, () => fn.apply(thisArg as ThisParameterType<F>, args));
}

bind<T>(context: Context, target: T): T {
if (typeof target !== 'function') return target;
// eslint-disable-next-line @typescript-eslint/no-this-alias -- the wrapper must forward the caller's dynamic `this`, so the manager is captured under another name
const manager = this;
return function (this: unknown, ...args: unknown[]) {
return manager.with(context, () =>
(target as (...a: unknown[]) => unknown).apply(this, args)
);
} as T;
}

enable(): this {
return this;
}

disable(): this {
this.#als.disable();
return this;
}
}

const DEFAULT_COLLECTOR_ORIGIN = 'https://signoz.openshock.app';

Expand All @@ -78,14 +27,22 @@ function originOf(url: string, fallback: string): string {

// Server spans describe our infrastructure, not user behavior, so they are gated only by
// the deployment kill-switch — the per-user consent flow in $lib/telemetry does not apply.
//
// PUBLIC_SIGNOZ_LOGS_ENABLED is deliberately one switch for every telemetry signal
// (client logs, client traces, and these server spans) despite the `LOGS` in its name;
// PUBLIC_SIGNOZ_TRACES_URL likewise falls back to the logs collector's origin below.
//
// It is a `$env/static/public` import, so this whole block is dead-code eliminated at
// build time when the flag is off — the default in `.env`. A deploy that wants server
// traces must set it in the build environment, not just at runtime.
const enabled = PUBLIC_SIGNOZ_LOGS_ENABLED === 'true' || PUBLIC_SIGNOZ_LOGS_ENABLED === '1';

if (enabled) {
try {
const collectorOrigin = originOf(PUBLIC_SIGNOZ_LOGS_URL, DEFAULT_COLLECTOR_ORIGIN);
const tracesUrl = PUBLIC_SIGNOZ_TRACES_URL || `${collectorOrigin}/v1/traces`;

const provider = new BasicTracerProvider({
const provider = new NodeTracerProvider({
// Mirrors $lib/telemetry/common buildResource() so client and server signals
// correlate as one service in SigNoz; `service.version` (the git SHA) drives
// SigNoz deployment markers.
Expand All @@ -99,8 +56,7 @@ if (enabled) {
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({ url: tracesUrl }))],
});

otelContext.setGlobalContextManager(new AlsContextManager().enable());
otelTrace.setGlobalTracerProvider(provider);
provider.register();
} catch (error) {
// Tracing is best-effort — a broken collector setup must never take the server down.
console.error('Failed to initialize server-side tracing:', error);
Expand Down
35 changes: 35 additions & 0 deletions src/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types --include-runtime=false ./src/worker-configuration.d.ts` (hash: be37b830cd0749e98366116ff4e4d29e)
interface __BaseEnv_Env {
ASSETS: Fetcher;
NODE_PATH: string;
PUBLIC_SITE_NAME: string;
PUBLIC_SITE_DESCRIPTION: string;
PUBLIC_GITHUB_PROJECT_URL: string;
PUBLIC_DISCORD_INVITE_URL: string;
PUBLIC_TURNSTILE_DEV_BYPASS_VALUE: string;
PUBLIC_DEVELOPMENT_BANNER: string;
PUBLIC_DISABLE_ONBOARDING: string;
PUBLIC_DISABLE_SHOCKER_MAP: string;
PUBLIC_SITE_URL: string;
PUBLIC_SITE_SHORT_URL: string;
PUBLIC_BACKEND_API_URL: string;
PUBLIC_GATEWAY_CSP_WILDCARD: string;
PRIVATE_BACKEND_TLS_INSECURE: string;
PUBLIC_SIGNOZ_LOGS_ENABLED: string;
PUBLIC_SIGNOZ_LOGS_URL: string;
PUBLIC_SIGNOZ_TRACES_URL: string;
PUBLIC_SIGNOZ_TRACE_PROPAGATION: string;
PUBLIC_SIGNOZ_DEPLOYMENT_ENVIRONMENT: string;
PUBLIC_SIGNOZ_RESOURCE_ATTRIBUTES: string;
}
declare namespace Cloudflare {
interface Env extends __BaseEnv_Env {}
}
interface Env extends __BaseEnv_Env {}
type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "NODE_PATH" | "PUBLIC_SITE_NAME" | "PUBLIC_SITE_DESCRIPTION" | "PUBLIC_GITHUB_PROJECT_URL" | "PUBLIC_DISCORD_INVITE_URL" | "PUBLIC_TURNSTILE_DEV_BYPASS_VALUE" | "PUBLIC_DEVELOPMENT_BANNER" | "PUBLIC_DISABLE_ONBOARDING" | "PUBLIC_DISABLE_SHOCKER_MAP" | "PUBLIC_SITE_URL" | "PUBLIC_SITE_SHORT_URL" | "PUBLIC_BACKEND_API_URL" | "PUBLIC_GATEWAY_CSP_WILDCARD" | "PRIVATE_BACKEND_TLS_INSECURE" | "PUBLIC_SIGNOZ_LOGS_ENABLED" | "PUBLIC_SIGNOZ_LOGS_URL" | "PUBLIC_SIGNOZ_TRACES_URL" | "PUBLIC_SIGNOZ_TRACE_PROPAGATION" | "PUBLIC_SIGNOZ_DEPLOYMENT_ENVIRONMENT" | "PUBLIC_SIGNOZ_RESOURCE_ATTRIBUTES">> {}
}
15 changes: 6 additions & 9 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,10 @@ const isTruthy = (value?: string) => value === 'true' || value === '1';
// detection, adapter selection, and the CSP in a single, type-checked file.
// ---------------------------------------------------------------------------

// Determine if we are running on Cloudflare (Pages git integration or Workers Builds)
const isGithubActions = env.GITHUB_ACTIONS === 'true';
const isWorkersCI = env.WORKERS_CI === '1';
const isCloudflare = env.CF_PAGES === '1' || isWorkersCI;
const isDocker = env.DOCKER === 'true';
// Determine if we are running on Cloudflare Workers Builds
const isGithubActions = isTruthy(env.GITHUB_ACTIONS);
const isCloudflare = isTruthy(env.WORKERS_CI);
const isDocker = isTruthy(env.DOCKER);
// Don't trust NODE_ENV — tools like svelte-check load this file mid-process and
// can have NODE_ENV='production' set by transitively-imported plugins (vite,
// vite-plugin-svelte) even though no production build is actually happening.
Expand All @@ -76,9 +75,7 @@ const dotenv = { ...env, ...loadEnv(buildMode, process.cwd(), 'PUBLIC_') };

function getGitHash(): string | undefined {
if (isGithubActions) return env.GITHUB_SHA;
if (isWorkersCI) return env.WORKERS_CI_COMMIT_SHA;
// Cloudflare Pages prefixes its build vars with CF_PAGES_
if (isCloudflare) return env.CF_PAGES_COMMIT_SHA;
if (isCloudflare) return env.WORKERS_CI_COMMIT_SHA;
if (isDocker) return env.GIT_COMMIT_SHA;

return child_process.execSync('git rev-parse HEAD').toString().trim();
Expand Down Expand Up @@ -237,7 +234,7 @@ function resolveServerConfig(useLocalRedirect: boolean): LocalServer | undefined

export default defineConfig(({ command, mode, isPreview }) => {
const isLocalServe = command === 'serve' || isPreview === true;
const isProduction = mode === 'production' && (isTruthy(env.DOCKER) || isTruthy(env.CF_PAGES));
const isProduction = mode === 'production' && (isTruthy(env.DOCKER) || isTruthy(env.WORKERS_CI));
// Vitest resolves this config with command 'serve', unit tests must never trigger mkcert or the hosts/port checks.
const isTest = mode === 'test' || isTruthy(env.VITEST);

Expand Down
Loading