local-review 0.2.0 → 0.4.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/CHANGELOG.md +23 -0
- package/dist/demo/lib/gitworld.js +224 -0
- package/dist/demo/world/api.js +2436 -0
- package/dist/demo/world/infra.js +189 -0
- package/dist/demo/world/people.js +16 -0
- package/dist/demo/world/review.js +578 -0
- package/dist/demo/world/sdk.js +376 -0
- package/dist/demo/world/web.js +657 -0
- package/dist/server/cli.js +28 -0
- package/dist/server/config.js +3 -0
- package/dist/server/examples.js +204 -0
- package/dist/server/index.js +17 -2
- package/dist/server/projects.js +2 -0
- package/dist/web/assets/{angular-html-DJ4eInGE.js → angular-html-F7zRgWCZ.js} +1 -1
- package/dist/web/assets/{angular-ts-DBhgg2a7.js → angular-ts-BpEpytbw.js} +1 -1
- package/dist/web/assets/{apl-TCQAu201.js → apl-CXKG97CP.js} +1 -1
- package/dist/web/assets/{astro-DVtFuwC2.js → astro-Bbu7K9fw.js} +1 -1
- package/dist/web/assets/{blade-CMp-sZYg.js → blade-BIHnATAa.js} +1 -1
- package/dist/web/assets/{c-DgqspIM2.js → c-C-acQYm6.js} +1 -1
- package/dist/web/assets/{chapel-NTLt7i8E.js → chapel-CO4oLJ27.js} +1 -1
- package/dist/web/assets/{cobol-CJiWQrT4.js → cobol-DwOqj9Jg.js} +1 -1
- package/dist/web/assets/{coffee-Bv_hE6wf.js → coffee-D4-tkAkt.js} +1 -1
- package/dist/web/assets/{cpp-CuUsyAvc.js → cpp-B5AO2QHP.js} +1 -1
- package/dist/web/assets/{crystal-BeLopiD_.js → crystal-Da5vXEdt.js} +1 -1
- package/dist/web/assets/{css-OngwBV3K.js → css-BPZthOhS.js} +1 -1
- package/dist/web/assets/{edge-BFLildoE.js → edge-Dax-tr1_.js} +1 -1
- package/dist/web/assets/{elixir-1Fs6FjVs.js → elixir-Bn9yrJM6.js} +1 -1
- package/dist/web/assets/{elm-piMM67vF.js → elm-BOsm44l2.js} +1 -1
- package/dist/web/assets/{erb-JJ0sF8GL.js → erb-Gt3RYukx.js} +1 -1
- package/dist/web/assets/{git-rebase-CtqXywWe.js → git-rebase-PkordWam.js} +1 -1
- package/dist/web/assets/{glimmer-js-BPewWxGC.js → glimmer-js-B5msfXVo.js} +1 -1
- package/dist/web/assets/{glimmer-ts-DyUQWB47.js → glimmer-ts-CR148_Kt.js} +1 -1
- package/dist/web/assets/{glsl-D6tJoMaT.js → glsl-qur0cRcN.js} +1 -1
- package/dist/web/assets/{graphql-E6u2hkWE.js → graphql-C42K488Q.js} +1 -1
- package/dist/web/assets/{hack-Djq191-P.js → hack-NLFouBH4.js} +1 -1
- package/dist/web/assets/{haml-C0LqmZG3.js → haml-DKn_-I-I.js} +1 -1
- package/dist/web/assets/{handlebars-DwdxF6uN.js → handlebars-CQReOdSJ.js} +1 -1
- package/dist/web/assets/{html-derivative-DTVrLAw3.js → html-derivative-DoqJMb8S.js} +1 -1
- package/dist/web/assets/{html-B5Myt0fy.js → html-iUF_eP_4.js} +1 -1
- package/dist/web/assets/{http-CFoh3cQf.js → http-BJrCx5B5.js} +1 -1
- package/dist/web/assets/{hurl-B_38naAo.js → hurl-DlDfbRcL.js} +1 -1
- package/dist/web/assets/{index-CfxIojO0.js → index-CH6ceWie.js} +52 -52
- package/dist/web/assets/index-DU33o_Lu.css +1 -0
- package/dist/web/assets/{java-odnexk6r.js → java-DdxicsS9.js} +1 -1
- package/dist/web/assets/{javascript-DvFCJKws.js → javascript-Dw8rInq-.js} +1 -1
- package/dist/web/assets/{jinja-UeLt2DYu.js → jinja-ztBbs9jc.js} +1 -1
- package/dist/web/assets/{jison-HNaDYeqE.js → jison-DyOjkTQj.js} +1 -1
- package/dist/web/assets/{json-D57CnrjR.js → json-C6t0u9rP.js} +1 -1
- package/dist/web/assets/{jsx-o_5ZAydi.js → jsx-BpIc6zqG.js} +1 -1
- package/dist/web/assets/{julia-20UebbrD.js → julia-CvA9RPfz.js} +1 -1
- package/dist/web/assets/{just-DqnPCg-Q.js → just-CiH5trEM.js} +1 -1
- package/dist/web/assets/{latex-CZhIB-LV.js → latex-BbpuYBUm.js} +1 -1
- package/dist/web/assets/{liquid-BzouPsN0.js → liquid-Dy7K9saE.js} +1 -1
- package/dist/web/assets/{lua-BXclv8Fm.js → lua-CLN5DayT.js} +1 -1
- package/dist/web/assets/{marko-M78_INlG.js → marko-Be70_WI8.js} +1 -1
- package/dist/web/assets/{mdc-CBluOiY7.js → mdc-RgJIMLnK.js} +1 -1
- package/dist/web/assets/{nginx-CZFFQFbT.js → nginx-GhsGoiRj.js} +1 -1
- package/dist/web/assets/{nim-17kKsGqk.js → nim-BaIl7YzC.js} +1 -1
- package/dist/web/assets/{org-BZABHRQH.js → org-ksusUjYr.js} +1 -1
- package/dist/web/assets/{perl-CMrAQ9Je.js → perl-KX9noYTv.js} +1 -1
- package/dist/web/assets/{php-DwA3kETk.js → php-C-szMz57.js} +1 -1
- package/dist/web/assets/{pug-D4fM85kA.js → pug-nbUZSc0V.js} +1 -1
- package/dist/web/assets/{qml-C3893RHi.js → qml-BD2H6imu.js} +1 -1
- package/dist/web/assets/{r-DwJhLW77.js → r-CbR0TzPr.js} +1 -1
- package/dist/web/assets/{razor-_KkCNZ8V.js → razor-D_SYu_ob.js} +1 -1
- package/dist/web/assets/{regexp-BZ9NHg3b.js → regexp-BIEFfqXG.js} +1 -1
- package/dist/web/assets/{rst-gdK-1c4t.js → rst-D21NyilJ.js} +1 -1
- package/dist/web/assets/{ruby-DsmqYtlH.js → ruby-CCk7F-X_.js} +1 -1
- package/dist/web/assets/{sas-BNGv1ScF.js → sas-BasHIiX3.js} +1 -1
- package/dist/web/assets/{scss-DjYUXT73.js → scss-DL8S6L92.js} +1 -1
- package/dist/web/assets/{shellscript-DBtV-eli.js → shellscript-D-B4NAUX.js} +1 -1
- package/dist/web/assets/{shellsession-BKEV8Fnx.js → shellsession-LwE3hLIg.js} +1 -1
- package/dist/web/assets/{soy-DUapMxos.js → soy-DaUNAouD.js} +1 -1
- package/dist/web/assets/{sql-B-UyqKkd.js → sql-Drw6fxyA.js} +1 -1
- package/dist/web/assets/{stata-Xq3iZiMK.js → stata-Bk-VgNHo.js} +1 -1
- package/dist/web/assets/{surrealql-CzXacK8i.js → surrealql-BfOZjDkH.js} +1 -1
- package/dist/web/assets/{svelte-CZaBEmPP.js → svelte-BP8BXHT0.js} +1 -1
- package/dist/web/assets/{templ-CxkGuUpr.js → templ-BgcZlshd.js} +1 -1
- package/dist/web/assets/{tex-DP74JH6g.js → tex-BQczXiA6.js} +1 -1
- package/dist/web/assets/{ts-tags-BAcrCE2Y.js → ts-tags-CVFTjDsr.js} +1 -1
- package/dist/web/assets/{tsx-Dw11YP6z.js → tsx-j3JIiZaT.js} +1 -1
- package/dist/web/assets/{twig-BMVjBxD6.js → twig-D7saNHJy.js} +1 -1
- package/dist/web/assets/{typescript-DoBLlVdC.js → typescript-D62o9E27.js} +1 -1
- package/dist/web/assets/{typst-w2u_vl4_.js → typst-DQOeX7Fq.js} +1 -1
- package/dist/web/assets/{vue-BmuHTv5p.js → vue-BpdrgvY0.js} +1 -1
- package/dist/web/assets/{vue-html-Dt0zSF4I.js → vue-html-Cwi6ng_G.js} +1 -1
- package/dist/web/assets/{vue-vine-wvJ1BTex.js → vue-vine-BO8zdtYP.js} +1 -1
- package/dist/web/assets/{xml-CXftIBCQ.js → xml-DgdKyO3D.js} +1 -1
- package/dist/web/assets/{xsl-BoHSOMz1.js → xsl-CLXFRDPl.js} +1 -1
- package/dist/web/assets/{yaml-dGreldtD.js → yaml-BYa9ePnt.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- package/dist/web/assets/index-QywYYqTw.css +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// orchard-infra: Terraform for the cloud pieces, Kubernetes manifests for the API, and the feature flags file.
|
|
2
|
+
import { RepoBuilder, after, replace } from '../lib/gitworld.js';
|
|
3
|
+
import { at, june, maya, remote, sam, theo } from './people.js';
|
|
4
|
+
const append = (s) => (old) => old + s;
|
|
5
|
+
export function buildInfra() {
|
|
6
|
+
const r = new RepoBuilder('orchard-infra', remote('orchard-infra'));
|
|
7
|
+
const i1 = r.commit(null, 'Terraform for the database and network', {
|
|
8
|
+
'README.md': `# orchard-infra
|
|
9
|
+
|
|
10
|
+
Everything Orchard runs on. Terraform (\`terraform/\`) owns the cloud resources; the API's Kubernetes
|
|
11
|
+
manifests live in \`k8s/\`; \`flags/flags.yaml\` is read by the API at startup.
|
|
12
|
+
|
|
13
|
+
Changes go through a PR like any code: CI runs \`terraform plan\` and posts it on the PR, and merging
|
|
14
|
+
to \`main\` applies it.
|
|
15
|
+
`,
|
|
16
|
+
'terraform/main.tf': `terraform {
|
|
17
|
+
required_version = ">= 1.8"
|
|
18
|
+
required_providers {
|
|
19
|
+
aws = {
|
|
20
|
+
source = "hashicorp/aws"
|
|
21
|
+
version = "~> 5.60"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
backend "s3" {
|
|
25
|
+
bucket = "orchard-terraform-state"
|
|
26
|
+
key = "prod/terraform.tfstate"
|
|
27
|
+
region = "eu-west-2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
provider "aws" {
|
|
32
|
+
region = var.region
|
|
33
|
+
default_tags {
|
|
34
|
+
tags = { project = "orchard", env = var.env }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`,
|
|
38
|
+
'terraform/variables.tf': `variable "region" {
|
|
39
|
+
type = string
|
|
40
|
+
default = "eu-west-2"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
variable "env" {
|
|
44
|
+
type = string
|
|
45
|
+
default = "prod"
|
|
46
|
+
}
|
|
47
|
+
`,
|
|
48
|
+
'terraform/database.tf': `resource "aws_db_instance" "main" {
|
|
49
|
+
identifier = "orchard-\${var.env}"
|
|
50
|
+
engine = "postgres"
|
|
51
|
+
engine_version = "16.3"
|
|
52
|
+
instance_class = "db.t4g.medium"
|
|
53
|
+
allocated_storage = 50
|
|
54
|
+
max_allocated_storage = 200
|
|
55
|
+
db_name = "orchard"
|
|
56
|
+
username = "orchard"
|
|
57
|
+
manage_master_user_password = true
|
|
58
|
+
backup_retention_period = 14
|
|
59
|
+
deletion_protection = true
|
|
60
|
+
skip_final_snapshot = false
|
|
61
|
+
}
|
|
62
|
+
`,
|
|
63
|
+
}, { author: theo, at: at('2026-05-06 10:00') });
|
|
64
|
+
const i2 = r.commit(i1, 'Deploy the API to the cluster', {
|
|
65
|
+
'k8s/api/deployment.yaml': `apiVersion: apps/v1
|
|
66
|
+
kind: Deployment
|
|
67
|
+
metadata:
|
|
68
|
+
name: orchard-api
|
|
69
|
+
labels: { app: orchard-api }
|
|
70
|
+
spec:
|
|
71
|
+
replicas: 3
|
|
72
|
+
selector:
|
|
73
|
+
matchLabels: { app: orchard-api }
|
|
74
|
+
template:
|
|
75
|
+
metadata:
|
|
76
|
+
labels: { app: orchard-api }
|
|
77
|
+
spec:
|
|
78
|
+
containers:
|
|
79
|
+
- name: api
|
|
80
|
+
image: registry.orchard.example/orchard-api:latest
|
|
81
|
+
ports:
|
|
82
|
+
- containerPort: 8080
|
|
83
|
+
envFrom:
|
|
84
|
+
- configMapRef: { name: orchard-api }
|
|
85
|
+
- secretRef: { name: orchard-api }
|
|
86
|
+
readinessProbe:
|
|
87
|
+
httpGet: { path: /healthz, port: 8080 }
|
|
88
|
+
resources:
|
|
89
|
+
requests: { cpu: 100m, memory: 128Mi }
|
|
90
|
+
limits: { memory: 256Mi }
|
|
91
|
+
`,
|
|
92
|
+
'k8s/api/configmap.yaml': `apiVersion: v1
|
|
93
|
+
kind: ConfigMap
|
|
94
|
+
metadata:
|
|
95
|
+
name: orchard-api
|
|
96
|
+
data:
|
|
97
|
+
PORT: "8080"
|
|
98
|
+
SMTP_ADDR: "smtp.orchard.example:587"
|
|
99
|
+
MAIL_FROM: "bookings@orchard.example"
|
|
100
|
+
`,
|
|
101
|
+
}, { author: theo, at: at('2026-05-29 15:40') });
|
|
102
|
+
const i3 = r.commit(i2, 'Add the feature flags file', {
|
|
103
|
+
'flags/flags.yaml': `# Read by orchard-api at startup. A flag is off unless listed here.
|
|
104
|
+
# studios: [ids] turns a flag on for those studios only; all: true for everyone.
|
|
105
|
+
flags:
|
|
106
|
+
class-spots-left:
|
|
107
|
+
all: true
|
|
108
|
+
`,
|
|
109
|
+
'k8s/api/configmap.yaml': append(' FLAGS_FILE: "/etc/orchard/flags.yaml"\n'),
|
|
110
|
+
}, { author: june, at: at('2026-07-09 11:15') });
|
|
111
|
+
const i4 = r.commit(i3, 'Alert when webhook deliveries pile up', {
|
|
112
|
+
'k8s/api/alerts.yaml': `apiVersion: monitoring.coreos.com/v1
|
|
113
|
+
kind: PrometheusRule
|
|
114
|
+
metadata:
|
|
115
|
+
name: orchard-api
|
|
116
|
+
spec:
|
|
117
|
+
groups:
|
|
118
|
+
- name: payments
|
|
119
|
+
rules:
|
|
120
|
+
- alert: WebhookDeadLetters
|
|
121
|
+
expr: orchard_webhook_dead_letters > 0
|
|
122
|
+
for: 10m
|
|
123
|
+
labels: { severity: page }
|
|
124
|
+
annotations:
|
|
125
|
+
summary: Payment webhooks are failing after every retry; members may be missing credits.
|
|
126
|
+
`,
|
|
127
|
+
}, { author: sam, at: at('2026-08-07 14:30') });
|
|
128
|
+
const i5 = r.commit(i4, 'Raise the API memory limit to 384Mi', {
|
|
129
|
+
'k8s/api/deployment.yaml': replace('limits: { memory: 256Mi }', 'limits: { memory: 384Mi }'),
|
|
130
|
+
}, { author: theo, at: at('2026-08-21 09:05') });
|
|
131
|
+
// ---------------------------------------------------------------- Maya's waitlists branch
|
|
132
|
+
const f1 = r.commit(i5, 'Run the hold-expiry sweep every five minutes', {
|
|
133
|
+
'k8s/api/cronjobs.yaml': `apiVersion: batch/v1
|
|
134
|
+
kind: CronJob
|
|
135
|
+
metadata:
|
|
136
|
+
name: orchard-sweep-holds
|
|
137
|
+
spec:
|
|
138
|
+
# Expires waitlist offers nobody took within the hold window and offers the spot to the next
|
|
139
|
+
# person in line (orchard-api: \`orchard-api sweep-holds\`). Holds are two hours, so a few minutes
|
|
140
|
+
# of lateness is fine; Forbid stops a slow run from overlapping the next.
|
|
141
|
+
schedule: "*/5 * * * *"
|
|
142
|
+
concurrencyPolicy: Forbid
|
|
143
|
+
successfulJobsHistoryLimit: 1
|
|
144
|
+
failedJobsHistoryLimit: 3
|
|
145
|
+
jobTemplate:
|
|
146
|
+
spec:
|
|
147
|
+
backoffLimit: 0
|
|
148
|
+
activeDeadlineSeconds: 240
|
|
149
|
+
template:
|
|
150
|
+
spec:
|
|
151
|
+
restartPolicy: Never
|
|
152
|
+
containers:
|
|
153
|
+
- name: sweep
|
|
154
|
+
image: registry.orchard.example/orchard-api:latest
|
|
155
|
+
args: ["sweep-holds"]
|
|
156
|
+
envFrom:
|
|
157
|
+
- configMapRef: { name: orchard-api }
|
|
158
|
+
- secretRef: { name: orchard-api }
|
|
159
|
+
resources:
|
|
160
|
+
requests: { cpu: 50m, memory: 64Mi }
|
|
161
|
+
limits: { memory: 128Mi }
|
|
162
|
+
`,
|
|
163
|
+
}, { author: maya, at: at('2026-09-11 17:05'), key: 'i1' });
|
|
164
|
+
const f2 = r.commit(f1, 'Alert when promotions stop going out', {
|
|
165
|
+
'k8s/api/alerts.yaml': append(` - name: waitlists
|
|
166
|
+
rules:
|
|
167
|
+
- alert: HoldSweepFailing
|
|
168
|
+
expr: time() - kube_cronjob_status_last_successful_time{cronjob="orchard-sweep-holds"} > 1800
|
|
169
|
+
labels: { severity: ticket }
|
|
170
|
+
annotations:
|
|
171
|
+
summary: >-
|
|
172
|
+
The hold-expiry sweep hasn't succeeded for 30 minutes. Expired offers aren't being
|
|
173
|
+
passed on, so people further down waitlists aren't getting spots.
|
|
174
|
+
`),
|
|
175
|
+
}, { author: maya, at: at('2026-09-12 09:40'), key: 'i2' });
|
|
176
|
+
const f3 = r.commit(f2, 'Turn on waitlists for the beta studios', {
|
|
177
|
+
'flags/flags.yaml': after(' class-spots-left:\n all: true\n', ` waitlists:
|
|
178
|
+
# the three studios that asked for it; everyone once we've watched a few weeks of promotions
|
|
179
|
+
studios: [4, 11, 17]
|
|
180
|
+
`),
|
|
181
|
+
}, { author: maya, at: at('2026-09-12 15:30'), key: 'i3' });
|
|
182
|
+
r.ref('main', i5);
|
|
183
|
+
r.ref('refs/remotes/origin/main', i5);
|
|
184
|
+
r.ref('refs/remotes/origin/HEAD', i5);
|
|
185
|
+
r.ref('maya/waitlists', f3);
|
|
186
|
+
r.ref('maya/infra-sweep-cron', f1);
|
|
187
|
+
r.ref('maya/infra-sweep-alert', f2);
|
|
188
|
+
return r;
|
|
189
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const maya = { name: 'Maya Okafor', email: 'maya@orchard.example' };
|
|
2
|
+
export const theo = { name: 'Theo Lindqvist', email: 'theo@orchard.example' };
|
|
3
|
+
export const sam = { name: 'Sam Reyes', email: 'sam@orchard.example' };
|
|
4
|
+
export const june = { name: 'June Park', email: 'june@orchard.example' };
|
|
5
|
+
/** The Local Review user the demo runs as (LOCAL_REVIEW_USER): the reviewer's comments are `author: maya`. */
|
|
6
|
+
export const REVIEWER = 'maya';
|
|
7
|
+
export const ORG = 'orchard-studio-demo';
|
|
8
|
+
export const remote = (repo) => `git@github.com:${ORG}/${repo}.git`;
|
|
9
|
+
export const prUrl = (repo, n) => `https://github.com/${ORG}/${repo}/pull/${n}`;
|
|
10
|
+
/** "2026-09-14 10:02" → an ISO UTC timestamp. */
|
|
11
|
+
export const at = (s) => `${s.replace(' ', 'T')}:00Z`;
|
|
12
|
+
/**
|
|
13
|
+
* "Now" for the demo: the capture rig freezes the browser clock here, so relative times ("2 days ago") are the same
|
|
14
|
+
* in every screenshot, and the seed sets file times relative to it.
|
|
15
|
+
*/
|
|
16
|
+
export const DEMO_NOW = '2026-09-18T16:30:00Z';
|