diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..ab2e16195 --- /dev/null +++ b/.env.example @@ -0,0 +1,142 @@ +# corvus/.env.example +# +# Canonical list of environment variables used across this repository. +# - DO NOT store real secrets here. Use CI secrets or your local secret manager (direnv, .env.local, etc.). +# - This file is a documentation + example file. Copy it to .env for local development and fill in real values where required. +# - Frontend/public envs: the shared getEnv helper accepts keys with no prefix, PUBLIC_ or VITE_. + +######################################## +# Global / environment +######################################## +# NODE_ENV - common runtime mode. optional. values: development|production +NODE_ENV=development + +# Generic site fallback used by resolveSiteUrl() when provider-specific keys are missing +# Example: https://example.com or http://localhost:4321 +SITE_URL=http://localhost:9988 + +######################################## +# Local dev ports (sane defaults used by shared env helper) +######################################## +# These are the PORTS mapping used across the web packages. Change only if you have port conflicts. +CHAT_PORT=4323 +DOCS_PORT=4321 +MARKETING_PORT=9988 +PLUGINS_PORT=9990 + +######################################## +# Web apps / Static sites +######################################## +# Docs site +DOCS_URL=http://localhost:${DOCS_PORT} +# Marketing site - provider-aware. Local default uses MARKETING_PORT above. +MARKETING_URL=http://localhost:${MARKETING_PORT} + +# Hosting provider fallbacks (set by provider automatically in their build environments) +# These are read by resolveSiteUrl({ providerKeys: { cloudflare: 'CF_PAGES_URL', vercel: 'VERCEL_URL', netlify: 'URL' }}) +CF_PAGES_URL= +URL= +VERCEL_URL= + +######################################## +# Frontend public keys (safe for client; mark explicitly if client-side) +######################################## +# Public keys that are intentionally exposed to browser code should use PUBLIC_ or VITE_ prefix +# Example (marketing analytics; safe to be public): +PUBLIC_AHREFS_KEY=your_ahrefs_public_key_here + +######################################## +# Agent runtime / backend (Corvus) +######################################## +# CORVUS_API_KEY - API key for internal agent auth (keep secret) +CORVUS_API_KEY=your_corvus_api_key_here +CORVUS_GATEWAY_HOST=127.0.0.1 +CORVUS_GATEWAY_PORT=4000 +CORVUS_OPEN_SKILLS_ENABLED=false +CORVUS_WORKSPACE=default + +######################################## +# SurrealDB (primary app DB for agent runtime) +######################################## +# Use a local dev SurrealDB for development. Production should be set via secrets. +CORVUS_SURREALDB_DATABASE=corvus_db +CORVUS_SURREALDB_NAMESPACE=corvus +CORVUS_SURREALDB_PASSWORD=your_local_dev_db_password_here +CORVUS_SURREALDB_TOKEN= +CORVUS_SURREALDB_URL=http://127.0.0.1:8000 +CORVUS_SURREALDB_USERNAME=root + +# Test-specific SurrealDB (used by CI/tests when present) +CORVUS_TEST_SURREALDB_DATABASE=corvus_test_db +CORVUS_TEST_SURREALDB_NAMESPACE=corvus_test +CORVUS_TEST_SURREALDB_URL=http://127.0.0.1:8001 + +######################################## +# Third-party provider keys (examples/placeholders) +######################################## +# Google APIs (if used) +GOOGLE_API_KEY=your_google_api_key_here + +# Anthropic / OpenAI / LLM providers +ANTHROPIC_API_KEY=your_anthropic_api_key_here +OPENAI_API_KEY=your_openai_api_key_here + +# GitHub / CI tokens +GH_TOKEN=your_gh_token_here +GITHUB_TOKEN=your_github_token_here + +######################################## +# CI, release & publishing secrets (store in GitHub Secrets / CI provider) +######################################## +# GPG / signing used in release pipelines (example placeholders only) +SIGNING_IN_MEMORY_KEY=your_signing_key_base64_here +SIGNING_IN_MEMORY_KEY_PASSWORD=your_signing_key_password_here + +# Maven Central +MAVEN_CENTRAL_USERNAME=your_maven_username +MAVEN_CENTRAL_PASSWORD=your_maven_password + +# Cargo / crates.io +CARGO_REGISTRY_TOKEN=your_crates_io_token_here + +# NPM / packages +NPM_TOKEN=your_npm_token_here + +# DockerHub +DOCKERHUB_USERNAME=your_dockerhub_username_here +DOCKERHUB_TOKEN=your_dockerhub_token_here + +# Code quality / telemetry +SONAR_TOKEN= +CODECOV_TOKEN= + +######################################## +# Gradle / build / repo config +######################################## +# Project coordinates and build cache credentials (optional) +GROUP=com.profiletailors +VERSION=0.1.0 +BUILD_CACHE_USER= +BUILD_CACHE_PWD= +PRIREPO_URL= +PRIREPO_USERNAME= +PRIREPO_PASSWORD= +ENABLE_LOCAL_CONFIG=true + +######################################## +# Optional / feature toggles & local helpers +######################################## +# If you need a shorter local override for SITE_URL per app, set the specific one above (MARKETING_URL, DOCS_URL, ...) + +# Example debug flags +DEBUG=true + +######################################## +# Notes +######################################## +# - Copy this file to .env and fill in the secret values before running local services. +# - For CI and releases: add all SECRET values (signing keys, repository tokens, package tokens) to your +# repository's GitHub Secrets or your CI provider's secret store. Never commit real credentials to the repo. +# - Frontend/public variables: prefix with PUBLIC_ or VITE_ if they are safe to expose to the browser. +# - The repo's shared env helper will prefer provider-specific envs (CF_PAGES_URL, VERCEL_URL, URL) when present +# and fall back to the primary key (MARKETING_URL / DOCS_URL / SITE_URL) for site resolution. diff --git a/.github/config/changelog.json b/.github/config/changelog.json old mode 100755 new mode 100644 index 1906b4a91..d9edd6f0e --- a/.github/config/changelog.json +++ b/.github/config/changelog.json @@ -1,8 +1,7 @@ { - "$schema": "https://raw.githubusercontent.com/mikepenz/release-changelog-builder-action/main/configs/json-schema.json", - "template": "## 📢 What's Changed\n\n#{{CHANGELOG}}\n
\n🔐 Uncategorized Changes\n\n#{{UNCATEGORIZED}}\n
\n\n## Contributors:\n- #{{CONTRIBUTORS}}", - "commit_template": "- #{{TITLE}} #{{MERGE_SHA}}", - "pr_template": "- #{{TITLE}} - #{{MERGE_SHA}} - ##{{NUMBER}}", + "template": "## 📢 What's Changed\n\n{{CHANGELOG}}\n\n
\n🔐 Uncategorized Changes\n\n{{UNCATEGORIZED}}\n
\n\n## Contributors:\n- {{CONTRIBUTORS}}", + "commit_template": "- {{TITLE}} ({{MERGE_SHA}})", + "pr_template": "- {{TITLE}} — #{{NUMBER}} (merge {{MERGE_SHA}})", "empty_template": "- No changes in this release", "exclude_merge_branches": ["hotfix/temp-*", "owner/qa", "owner/test"], "tag_resolver": { @@ -73,13 +72,13 @@ "on_property": "title", "method": "regexr", "pattern": "!!\\s*$", - "target": "$'pr|breaking-change" + "target": "pr|breaking-change" }, { "on_property": "title", "method": "regexr", - "pattern": "\\.\\.\\s*$", - "target": "$'pr|changelog-ignore" + "pattern": "\\.\\.\\.\\s*$", + "target": "pr|changelog-ignore" }, { "on_property": "title", @@ -89,7 +88,7 @@ } ], "sort": { - "order": "ASC", + "order": "DESC", "on_property": "mergedAt" }, "max_tags_to_fetch": 200, diff --git a/clients/web/apps/docs/astro.config.mjs b/clients/web/apps/docs/astro.config.mjs index 4043febc7..dbf9fbd1f 100644 --- a/clients/web/apps/docs/astro.config.mjs +++ b/clients/web/apps/docs/astro.config.mjs @@ -1,10 +1,55 @@ import starlight from "@astrojs/starlight"; import { defineConfig } from "astro/config"; import { viewTransitions } from "astro-vtbot/starlight-view-transitions"; +import { getPortFromUrl, PORTS, resolveSiteUrl } from "@corvus/shared/env"; +import { loadEnv } from "vite"; + +const DEFAULT_DEV_URL = `http://localhost:${PORTS.DOCS}`; +const DEFAULT_PROD_URL = "https://docs.profiletailors.com"; + +const mode = process.env.NODE_ENV || "production"; +const env = loadEnv(mode, process.cwd(), ""); +const docsUrl = resolveSiteUrl({ + env, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { + cloudflare: "CF_PAGES_URL", + vercel: "VERCEL_URL", + netlify: "URL", + }, + isProdLike: mode === "production", +}); +const resolvedPort = getPortFromUrl(docsUrl, PORTS.DOCS); + +function computeBaseFromUrl(url) { + if (!url) return "/"; + try { + const pathname = new URL(url).pathname || "/"; + // If root, keep '/' + if (pathname === "/") return "/"; + // Remove trailing slash for non-root paths + return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname; + } catch (e) { + return "/"; + } +} + +const base = computeBaseFromUrl(docsUrl); export default defineConfig({ - site: "https://dallay.github.io", - base: "/corvus", + site: docsUrl, + base, // computed from the provider/site URL so subpath deployments work + server: { + host: true, + port: resolvedPort, + }, + preview: { + host: true, + port: resolvedPort, + }, integrations: [ starlight({ title: "Corvus", diff --git a/clients/web/packages/shared/test/env.test.mjs b/clients/web/packages/shared/test/env.test.mjs new file mode 100644 index 000000000..4a151f878 --- /dev/null +++ b/clients/web/packages/shared/test/env.test.mjs @@ -0,0 +1,115 @@ +import { describe, it, expect } from "vitest"; +import { resolveSiteUrl, getPortFromUrl, PORTS } from "../env.mjs"; + +const DEFAULT_DEV_URL = `http://localhost:${PORTS.DOCS}`; +const DEFAULT_PROD_URL = "https://docs.profiletailors.com"; + +describe("resolveSiteUrl and getPortFromUrl matrix", () => { + it("resolveSiteUrl_prefers_DOCS_URL_over_providers_and_falls_back_to_SITE_URL", () => { + // Explicit DOCS_URL should win + const explicit = resolveSiteUrl({ + env: { DOCS_URL: "http://explicit.example/path" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(explicit).toBe("http://explicit.example/path"); + + // Cloudflare provider should be selected when DOCS_URL missing + const cloudflare = resolveSiteUrl({ + env: { CF_PAGES_URL: "docs.cloudflare.test/sub" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(cloudflare).toBe("https://docs.cloudflare.test/sub"); + + // Vercel provider + const vercel = resolveSiteUrl({ + env: { VERCEL_URL: "my-vercel.app/docs" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(vercel).toBe("https://my-vercel.app/docs"); + + // Netlify provider (URL) + const netlify = resolveSiteUrl({ + env: { URL: "https://netlify.example/base/" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(netlify).toBe("https://netlify.example/base"); + + // Generic SITE_URL fallback + const generic = resolveSiteUrl({ + env: { SITE_URL: "http://fallback.test:9001/fpath" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(generic).toBe("http://fallback.test:9001/fpath"); + + // No envs -> dev fallback + const fallbackDev = resolveSiteUrl({ + env: {}, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(fallbackDev).toBe(DEFAULT_DEV_URL); + + // No envs -> prod fallback when isProdLike + const fallbackProd = resolveSiteUrl({ + env: {}, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: true, + }); + expect(fallbackProd).toBe(DEFAULT_PROD_URL); + }); + + it("getPortFromUrl_derives_port_from_docsUrl", () => { + // URL with explicit port + const withPort = "http://example.com:8080/path"; + expect(getPortFromUrl(withPort, PORTS.DOCS)).toBe(8080); + + // URL without port -> fallback + const withoutPort = "https://example.com/path"; + expect(getPortFromUrl(withoutPort, PORTS.DOCS)).toBe(PORTS.DOCS); + + // Derived from resolveSiteUrl result + const docsUrl = resolveSiteUrl({ + env: { DOCS_URL: "http://localhost:12345/docs" }, + primaryKey: "DOCS_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" }, + isProdLike: false, + }); + expect(getPortFromUrl(docsUrl, PORTS.DOCS)).toBe(12345); + }); +});