Add orchestrator gen-0 scaffold
This commit is contained in:
4
.envrc
Normal file
4
.envrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by br-orchestrate render
|
||||||
|
export ORCHESTRA_VERSION=0.1.0
|
||||||
|
export ORCHESTRA_PACKS=education,infra-devops,creator-studio,finance,legal,research-lab
|
||||||
|
export ORCHESTRA_ENVIRONMENTS=dev,prod
|
||||||
18
.eslintrc.json
Normal file
18
.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2021,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"ignorePatterns": ["public/**/*.json"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
.github/workflows/ci.yml
vendored
Normal file
29
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["*"]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'pnpm'
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint
|
||||||
|
- name: Test
|
||||||
|
run: pnpm test
|
||||||
|
- name: Type Check
|
||||||
|
run: pnpm build
|
||||||
|
- name: Render artifacts
|
||||||
|
run: pnpm br-orchestrate render
|
||||||
39
.matrix.json
Normal file
39
.matrix.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"repos": {
|
||||||
|
"core": "BlackRoad-OS/blackroad-os-core",
|
||||||
|
"api": "BlackRoad-OS/blackroad-os-api",
|
||||||
|
"gateway": "BlackRoad-OS/blackroad-os-api-gateway",
|
||||||
|
"operator": "BlackRoad-OS/blackroad-os-operator",
|
||||||
|
"prism": "BlackRoad-OS/blackroad-os-prism-console",
|
||||||
|
"web": "BlackRoad-OS/blackroad-os-web"
|
||||||
|
},
|
||||||
|
"packs": [
|
||||||
|
"education",
|
||||||
|
"infra-devops",
|
||||||
|
"creator-studio",
|
||||||
|
"finance",
|
||||||
|
"legal",
|
||||||
|
"research-lab"
|
||||||
|
],
|
||||||
|
"environments": {
|
||||||
|
"dev": {
|
||||||
|
"domain_root": "dev.blackroad.io"
|
||||||
|
},
|
||||||
|
"prod": {
|
||||||
|
"domain_root": "blackroad.io"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "core-web",
|
||||||
|
"repo": "core",
|
||||||
|
"env": "prod",
|
||||||
|
"url": "https://web.blackroad.io",
|
||||||
|
"depends": [
|
||||||
|
"gateway",
|
||||||
|
"operator"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
19
README.md
19
README.md
@@ -1 +1,18 @@
|
|||||||
# blackroad-os
|
# BlackRoad OS · Orchestrator
|
||||||
|
|
||||||
|
Welcome to the meta-orchestration layer for the BlackRoad ecosystem. This repository
|
||||||
|
describes the constellation of services, packs, and environments that make up the platform.
|
||||||
|
|
||||||
|
Run `pnpm br-orchestrate render` to regenerate this README based on `orchestra.yml`.
|
||||||
|
|
||||||
|
## Service Matrix
|
||||||
|
| Service | Env | Repo | URL | Health | Depends |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| core-web | prod | core | https://web.blackroad.io | /api/health | gateway, operator |
|
||||||
|
|
||||||
|
## Topology
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
core-web --> gateway
|
||||||
|
core-web --> operator
|
||||||
|
```
|
||||||
|
|||||||
6
README.stub.md
Normal file
6
README.stub.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# BlackRoad OS · Orchestrator
|
||||||
|
|
||||||
|
Welcome to the meta-orchestration layer for the BlackRoad ecosystem. This repository
|
||||||
|
describes the constellation of services, packs, and environments that make up the platform.
|
||||||
|
|
||||||
|
Run `pnpm br-orchestrate render` to regenerate this README based on `orchestra.yml`.
|
||||||
29
orchestra.yml
Normal file
29
orchestra.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
version: 0.1.0
|
||||||
|
repos:
|
||||||
|
core: BlackRoad-OS/blackroad-os-core
|
||||||
|
api: BlackRoad-OS/blackroad-os-api
|
||||||
|
gateway: BlackRoad-OS/blackroad-os-api-gateway
|
||||||
|
operator: BlackRoad-OS/blackroad-os-operator
|
||||||
|
prism: BlackRoad-OS/blackroad-os-prism-console
|
||||||
|
web: BlackRoad-OS/blackroad-os-web
|
||||||
|
services:
|
||||||
|
core-web:
|
||||||
|
repo: core
|
||||||
|
env: prod
|
||||||
|
url: https://web.blackroad.io
|
||||||
|
health: /api/health
|
||||||
|
depends:
|
||||||
|
- gateway
|
||||||
|
- operator
|
||||||
|
packs:
|
||||||
|
- education
|
||||||
|
- infra-devops
|
||||||
|
- creator-studio
|
||||||
|
- finance
|
||||||
|
- legal
|
||||||
|
- research-lab
|
||||||
|
environments:
|
||||||
|
dev:
|
||||||
|
domain_root: dev.blackroad.io
|
||||||
|
prod:
|
||||||
|
domain_root: blackroad.io
|
||||||
3
orchestrator.env.example
Normal file
3
orchestrator.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Sample environment overrides for br-orchestrate
|
||||||
|
ORCHESTRA_VERSION=0.1.0
|
||||||
|
DEFAULT_ENV=dev
|
||||||
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "blackroad-os",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"br-orchestrate": "tsx src/cli.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "pnpm run lint:ts && pnpm run lint:yaml && pnpm run format:check",
|
||||||
|
"lint:ts": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs",
|
||||||
|
"lint:yaml": "yaml-lint orchestra.yml",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"build": "tsc --noEmit",
|
||||||
|
"postbuild": "tsx scripts/postbuild.ts",
|
||||||
|
"br-orchestrate": "tsx src/cli.ts"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vitest": "^1.0.4",
|
||||||
|
"yaml-lint": "^1.2.4",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
2616
pnpm-lock.yaml
generated
Normal file
2616
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
public/sig.beacon.json
Normal file
4
public/sig.beacon.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"agent": "Orchestrator-Gen-0",
|
||||||
|
"ts": 0
|
||||||
|
}
|
||||||
11
scripts/postbuild.ts
Normal file
11
scripts/postbuild.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const beacon = {
|
||||||
|
agent: 'Orchestrator-Gen-0',
|
||||||
|
ts: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = path.join(process.cwd(), 'public', 'sig.beacon.json')
|
||||||
|
fs.mkdirSync(path.dirname(target), { recursive: true })
|
||||||
|
fs.writeFileSync(target, JSON.stringify(beacon, null, 2))
|
||||||
52
src/cli.ts
Normal file
52
src/cli.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { loadManifest, withContext } from './schema.js'
|
||||||
|
import { render } from './render.js'
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
return `br-orchestrate <command>\n\nCommands:\n lint Validate orchestra.yml with the schema\n render Emit .matrix.json, .envrc, README.md\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
function lint(manifestPath?: string) {
|
||||||
|
const manifest = withContext(loadManifest(manifestPath))
|
||||||
|
const summary = [
|
||||||
|
`version: ${manifest.version}`,
|
||||||
|
`repos: ${Object.keys(manifest.repos).length}`,
|
||||||
|
`services: ${Object.keys(manifest.services).length}`,
|
||||||
|
`packs: ${manifest.packs.length}`,
|
||||||
|
`envs: ${Object.keys(manifest.environments).length}`,
|
||||||
|
]
|
||||||
|
process.stdout.write(`orchestra.yml is valid\n${summary.join('\n')}\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runRender(manifestPath?: string) {
|
||||||
|
const manifest = withContext(loadManifest(manifestPath))
|
||||||
|
render(manifest)
|
||||||
|
process.stdout.write('Rendered .matrix.json, .envrc, and README.md\n')
|
||||||
|
// TODO(orchestrator-next): add drift detection and multi-tenant shards
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const [, , command, manifestPath] = process.argv
|
||||||
|
if (!command || command === '--help' || command === '-h') {
|
||||||
|
process.stdout.write(usage())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), 'orchestra.yml'))) {
|
||||||
|
throw new Error('orchestra.yml not found in current directory')
|
||||||
|
}
|
||||||
|
if (command === 'lint') {
|
||||||
|
lint(manifestPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (command === 'render') {
|
||||||
|
runRender(manifestPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
process.stderr.write(`Unknown command: ${command}\n`)
|
||||||
|
process.stderr.write(usage())
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
58
src/render.ts
Normal file
58
src/render.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { Manifest } from './schema.js'
|
||||||
|
|
||||||
|
function matrix(manifest: Manifest) {
|
||||||
|
const services = Object.entries(manifest.services).map(([name, svc]) => ({
|
||||||
|
name,
|
||||||
|
repo: svc.repo,
|
||||||
|
env: svc.env,
|
||||||
|
url: svc.url,
|
||||||
|
depends: svc.depends,
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
version: manifest.version,
|
||||||
|
repos: manifest.repos,
|
||||||
|
packs: manifest.packs,
|
||||||
|
environments: manifest.environments,
|
||||||
|
services,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function envrc(manifest: Manifest) {
|
||||||
|
const lines = [
|
||||||
|
'# Generated by br-orchestrate render',
|
||||||
|
`export ORCHESTRA_VERSION=${manifest.version}`,
|
||||||
|
`export ORCHESTRA_PACKS=${manifest.packs.join(',')}`,
|
||||||
|
`export ORCHESTRA_ENVIRONMENTS=${Object.keys(manifest.environments).join(',')}`,
|
||||||
|
]
|
||||||
|
return lines.join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
function readme(manifest: Manifest) {
|
||||||
|
const rows = Object.entries(manifest.services)
|
||||||
|
.map(([name, svc]) => {
|
||||||
|
const depends = svc.depends?.length ? svc.depends.join(', ') : '—'
|
||||||
|
const health = svc.health ?? '—'
|
||||||
|
return `| ${name} | ${svc.env} | ${svc.repo} | ${svc.url} | ${health} | ${depends} |`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
const edges = Object.entries(manifest.services).flatMap(([name, svc]) =>
|
||||||
|
(svc.depends || []).map((dep) => `${name} --> ${dep}`)
|
||||||
|
)
|
||||||
|
const mermaid = ['```mermaid', 'graph LR', ...(edges.length ? edges : ['%% no deps']), '```'].join(
|
||||||
|
'\n'
|
||||||
|
)
|
||||||
|
const header = fs.readFileSync(path.join(process.cwd(), 'README.stub.md'), 'utf-8')
|
||||||
|
const table = `| Service | Env | Repo | URL | Health | Depends |\n| --- | --- | --- | --- | --- | --- |\n${rows}`
|
||||||
|
return [header.trim(), '', '## Service Matrix', table, '', '## Topology', mermaid, ''].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function render(manifest: Manifest, cwd = process.cwd()) {
|
||||||
|
const matrixPath = path.join(cwd, '.matrix.json')
|
||||||
|
const envrcPath = path.join(cwd, '.envrc')
|
||||||
|
const readmePath = path.join(cwd, 'README.md')
|
||||||
|
fs.writeFileSync(matrixPath, JSON.stringify(matrix(manifest), null, 2))
|
||||||
|
fs.writeFileSync(envrcPath, envrc(manifest))
|
||||||
|
fs.writeFileSync(readmePath, readme(manifest))
|
||||||
|
}
|
||||||
50
src/schema.ts
Normal file
50
src/schema.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
|
||||||
|
const repoId = z.string().min(1, 'repo id required')
|
||||||
|
|
||||||
|
export const EnvironmentSchema = z.object({
|
||||||
|
domain_root: z.string().min(1, 'domain_root required'),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ServiceSchema = z.object({
|
||||||
|
repo: repoId,
|
||||||
|
env: z.string().min(1, 'env required'),
|
||||||
|
url: z.string().url('url must be valid'),
|
||||||
|
health: z.string().optional(),
|
||||||
|
depends: z.array(z.string()).default([]),
|
||||||
|
notes: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ManifestSchema = z.object({
|
||||||
|
version: z.string().min(1),
|
||||||
|
repos: z.record(repoId),
|
||||||
|
services: z.record(ServiceSchema),
|
||||||
|
packs: z.array(z.string()),
|
||||||
|
environments: z.record(EnvironmentSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type Manifest = z.infer<typeof ManifestSchema>
|
||||||
|
|
||||||
|
export const MANIFEST_PATH = path.join(process.cwd(), 'orchestra.yml')
|
||||||
|
|
||||||
|
export function loadManifest(customPath?: string): Manifest {
|
||||||
|
const manifestPath = customPath ? path.resolve(customPath) : MANIFEST_PATH
|
||||||
|
const raw = fs.readFileSync(manifestPath, 'utf-8')
|
||||||
|
const data = yaml.load(raw)
|
||||||
|
const parsed = ManifestSchema.safeParse(data)
|
||||||
|
if (!parsed.success) {
|
||||||
|
const formatted = parsed.error.errors.map((err) => `${err.path.join('.')}: ${err.message}`)
|
||||||
|
throw new Error(`Invalid orchestra.yml\n${formatted.join('\n')}`)
|
||||||
|
}
|
||||||
|
return parsed.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withContext(manifest: Manifest): Manifest {
|
||||||
|
const resolved = { ...manifest }
|
||||||
|
// TODO(orchestrator-next): enrich with GitOps sync targets and drift detection
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
30
tests/schema.test.ts
Normal file
30
tests/schema.test.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { ManifestSchema } from '../src/schema.js'
|
||||||
|
|
||||||
|
const sample = {
|
||||||
|
version: '0.1.0',
|
||||||
|
repos: { core: 'BlackRoad-OS/blackroad-os-core' },
|
||||||
|
services: {
|
||||||
|
'core-web': {
|
||||||
|
repo: 'core',
|
||||||
|
env: 'prod',
|
||||||
|
url: 'https://web.blackroad.io',
|
||||||
|
health: '/api/health',
|
||||||
|
depends: ['gateway'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packs: ['education'],
|
||||||
|
environments: { prod: { domain_root: 'blackroad.io' } },
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ManifestSchema', () => {
|
||||||
|
it('accepts valid manifest', () => {
|
||||||
|
const parsed = ManifestSchema.safeParse(sample)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects invalid manifest', () => {
|
||||||
|
const parsed = ManifestSchema.safeParse({ ...sample, version: '' })
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2021",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src", "tests", "scripts"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user