@appforge-ci/core 0.1.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/LICENSE +21 -0
- package/dist/agent-readiness.d.ts +163 -0
- package/dist/agent-readiness.d.ts.map +1 -0
- package/dist/agent-readiness.js +179 -0
- package/dist/agent-readiness.js.map +1 -0
- package/dist/agent-readiness.test.d.ts +2 -0
- package/dist/agent-readiness.test.d.ts.map +1 -0
- package/dist/agent-readiness.test.js +177 -0
- package/dist/agent-readiness.test.js.map +1 -0
- package/dist/api-client.d.ts +529 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +551 -0
- package/dist/api-client.js.map +1 -0
- package/dist/api-client.test.d.ts +2 -0
- package/dist/api-client.test.d.ts.map +1 -0
- package/dist/api-client.test.js +305 -0
- package/dist/api-client.test.js.map +1 -0
- package/dist/build-cost.test.d.ts +2 -0
- package/dist/build-cost.test.d.ts.map +1 -0
- package/dist/build-cost.test.js +21 -0
- package/dist/build-cost.test.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/ota-artifact.d.ts +41 -0
- package/dist/ota-artifact.d.ts.map +1 -0
- package/dist/ota-artifact.js +43 -0
- package/dist/ota-artifact.js.map +1 -0
- package/dist/ota-artifact.test.d.ts +2 -0
- package/dist/ota-artifact.test.d.ts.map +1 -0
- package/dist/ota-artifact.test.js +44 -0
- package/dist/ota-artifact.test.js.map +1 -0
- package/dist/ota-bundle-hash.d.ts +38 -0
- package/dist/ota-bundle-hash.d.ts.map +1 -0
- package/dist/ota-bundle-hash.js +40 -0
- package/dist/ota-bundle-hash.js.map +1 -0
- package/dist/ota-bundle-hash.test.d.ts +2 -0
- package/dist/ota-bundle-hash.test.d.ts.map +1 -0
- package/dist/ota-bundle-hash.test.js +28 -0
- package/dist/ota-bundle-hash.test.js.map +1 -0
- package/dist/ota-rollout.d.ts +38 -0
- package/dist/ota-rollout.d.ts.map +1 -0
- package/dist/ota-rollout.js +53 -0
- package/dist/ota-rollout.js.map +1 -0
- package/dist/ota-rollout.test.d.ts +2 -0
- package/dist/ota-rollout.test.d.ts.map +1 -0
- package/dist/ota-rollout.test.js +48 -0
- package/dist/ota-rollout.test.js.map +1 -0
- package/dist/schemas.d.ts +776 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +538 -0
- package/dist/schemas.js.map +1 -0
- package/dist/token-packs.test.d.ts +2 -0
- package/dist/token-packs.test.d.ts.map +1 -0
- package/dist/token-packs.test.js +34 -0
- package/dist/token-packs.test.js.map +1 -0
- package/dist/types.d.ts +445 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +80 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc,0DAAwD,CAAC;AAEpF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAwBjC;;;sEAGkE;;IAElE,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAO7E,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAf/B;;;0EAGkE;;QAElE,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe7E,CAAC;AAgCH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;EAKvB,CAAC;AAEH,eAAO,MAAM,WAAW;;;IAGtB;;;uEAGmE;;IAEnE;;yCAEqC;;;;;;;;;;;;EAErC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH;;;;;;;;;;;;uEAYuE;AACvE,eAAO,MAAM,iBAAiB;;;;;;;;;EAG5B,CAAC;AAEH;;;4CAG4C;AAC5C,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH;;;;;;;;;;;;;;;4CAe4C;AAC5C,eAAO,MAAM,6BAA6B;;;;;;EAExC,CAAC;AAEH;;;;;wDAKwD;AACxD,eAAO,MAAM,oBAAoB;;;;;;;;;EAG/B,CAAC;AAEH;;;;;gEAKgE;AAChE,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAC;AAEH;;;4BAG4B;AAC5B,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAC;AAuCH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;EAK1B,CAAC;AAKH,eAAO,MAAM,eAAe;;;;;;;;;;;;EAI1B,CAAC;AAQH,eAAO,MAAM,eAAe;;;;;;;;;EAU1B,CAAC;AA0EH,eAAO,MAAM,kBAAkB;;;;;;;;;EAiB7B,CAAC;AAiBH,eAAO,MAAM,kBAAkB,0EAA2E,CAAC;AAE3G,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAQlE,eAAO,MAAM,oBAAoB;;;;;;;;;EAe/B,CAAC;AAeH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;EAS7B,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;EAEtB,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;IA4B/B,iDAAiD;;IAEjD;;2EAEuE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEvE,CAAC;AAgBH,eAAO,MAAM,eAAe;;;;;;;;;EAG1B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B9B,CAAC;AASH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;EAKlC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAOH,eAAO,MAAM,6BAA6B;;;;;;EAExC,CAAC;AAEH;;;;;oBAKoB;AACpB,eAAO,MAAM,2BAA2B;;;;;;EAEtC,CAAC;AAEH;;;;;;oDAMoD;AACpD,eAAO,MAAM,qBAAqB;;;;;;EAEhC,CAAC"}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const platformSchema = z.enum(["ios", "android", "flutter", "react-native"]);
|
|
3
|
+
export const appforgeWorkflowSchema = z.object({
|
|
4
|
+
name: z.string().optional(),
|
|
5
|
+
environment: z
|
|
6
|
+
.object({
|
|
7
|
+
node: z.string().optional(),
|
|
8
|
+
flutter: z.string().optional(),
|
|
9
|
+
xcode: z.string().optional(),
|
|
10
|
+
vars: z.record(z.string()).optional(),
|
|
11
|
+
})
|
|
12
|
+
.optional(),
|
|
13
|
+
triggers: z
|
|
14
|
+
.object({
|
|
15
|
+
push: z.object({ branch: z.array(z.string()) }).optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional(),
|
|
18
|
+
scripts: z
|
|
19
|
+
.array(z.object({
|
|
20
|
+
name: z.string().optional(),
|
|
21
|
+
script: z.string().min(1),
|
|
22
|
+
}))
|
|
23
|
+
.min(1),
|
|
24
|
+
artifacts: z.array(z.string()).optional(),
|
|
25
|
+
/** Opt-in post-build smoke test (#9 v1 — installs the built .apk or
|
|
26
|
+
* zipped Simulator .app, launches it, screenshots it) — see
|
|
27
|
+
* packages/agent/src/lib/android-smoke-test.ts and ios-smoke-test.ts.
|
|
28
|
+
* Off by default; never changes existing workflows' behavior. */
|
|
29
|
+
smokeTest: z.boolean().optional(),
|
|
30
|
+
/** Opt-in OTA (over-the-air) code-push update publishing. Off by default. */
|
|
31
|
+
ota: z
|
|
32
|
+
.object({
|
|
33
|
+
channel: z.string().default("production"),
|
|
34
|
+
mandatory: z.boolean().default(false),
|
|
35
|
+
})
|
|
36
|
+
.optional(),
|
|
37
|
+
});
|
|
38
|
+
/** Validates a parsed appforge.yaml document. */
|
|
39
|
+
export const appforgeConfigSchema = z.object({
|
|
40
|
+
platform: platformSchema,
|
|
41
|
+
workflows: z.record(appforgeWorkflowSchema).refine((w) => Object.keys(w).length > 0, {
|
|
42
|
+
message: "at least one workflow is required",
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
// `.trim().toLowerCase()` before `.email()` — security-relevant, not just
|
|
46
|
+
// tidiness. `apps/api/src/lib/middleware.ts`'s `isPlatformAdminEmail`
|
|
47
|
+
// already lowercases the email it's checking against
|
|
48
|
+
// `PLATFORM_ADMIN_EMAILS` (also stored lowercased), but nothing upstream
|
|
49
|
+
// of it — this schema, `createUser`/`getUserByEmail` (packages/db/src/
|
|
50
|
+
// users.ts), or the `User.email` column itself (`TEXT NOT NULL UNIQUE`,
|
|
51
|
+
// SQLite's default case-sensitive BINARY collation) — ever normalized
|
|
52
|
+
// case before this fix. That let a case-variant of a real platform
|
|
53
|
+
// admin's email (`Admin@Company.com` vs. the allowlisted
|
|
54
|
+
// `admin@company.com`) register as a *genuinely distinct* `User` row
|
|
55
|
+
// (the case-sensitive UNIQUE index doesn't dedupe them) that still
|
|
56
|
+
// passed `isPlatformAdminEmail`'s own lowercased check — full
|
|
57
|
+
// `requirePlatformAdmin` access (fleet enroll/drain/reboot/cancel-build/
|
|
58
|
+
// remote-control, every route in `apps/admin-api`) to anyone who knew an
|
|
59
|
+
// admin's email address, which `docs/self-hosting.md` tells operators to
|
|
60
|
+
// configure and which is often no secret at all. Normalizing here closes
|
|
61
|
+
// it for every future signup/login unconditionally; see
|
|
62
|
+
// `migrations/0026_lowercase_user_email.sql` for backfilling any
|
|
63
|
+
// already-mixed-case row.
|
|
64
|
+
const emailSchema = z.string().trim().toLowerCase().email();
|
|
65
|
+
// `name`/`orgName` had no upper bound at all until this fix — signup is
|
|
66
|
+
// unauthenticated and free (no payment method needed, see #19's free
|
|
67
|
+
// signup credit), so any caller could submit an arbitrarily large string
|
|
68
|
+
// here (megabytes+), which then gets stored in D1, echoed back in every
|
|
69
|
+
// `/orgs`/`/me`-shaped API response, and rendered directly in the
|
|
70
|
+
// dashboard. 255 matches the bound this same fix applies to the
|
|
71
|
+
// equivalent `App`/`Org` name fields below (`createAppSchema`/
|
|
72
|
+
// `updateAppSchema`/`updateOrgSchema`) — plenty for a real display name,
|
|
73
|
+
// nowhere near enough to matter as a storage/response-size abuse vector.
|
|
74
|
+
export const signupSchema = z.object({
|
|
75
|
+
email: emailSchema,
|
|
76
|
+
password: z.string().min(10, "password must be at least 10 characters"),
|
|
77
|
+
name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
|
|
78
|
+
orgName: z.string().min(1).max(255, "must be at most 255 characters"),
|
|
79
|
+
});
|
|
80
|
+
export const loginSchema = z.object({
|
|
81
|
+
email: emailSchema,
|
|
82
|
+
password: z.string().min(1),
|
|
83
|
+
/** Only required when the account has TOTP 2FA enabled — see
|
|
84
|
+
* routes/auth.ts's login handler. Mutually exclusive with
|
|
85
|
+
* `recoveryCode` in practice (the handler tries `recoveryCode` first
|
|
86
|
+
* when both are somehow present), never both required at once. */
|
|
87
|
+
totpCode: z.string().optional(),
|
|
88
|
+
/** Alternate to `totpCode` for a lost authenticator — one of
|
|
89
|
+
* `TotpRecoveryCode`'s single-use codes (migrations/
|
|
90
|
+
* 0008_totp_recovery_codes.sql). */
|
|
91
|
+
recoveryCode: z.string().optional(),
|
|
92
|
+
});
|
|
93
|
+
export const verifyEmailSchema = z.object({
|
|
94
|
+
token: z.string().min(1),
|
|
95
|
+
});
|
|
96
|
+
/** `POST /auth/2fa/confirm` requires re-proving the account password —
|
|
97
|
+
* the same "a stolen session token alone isn't enough to mutate this"
|
|
98
|
+
* reasoning as `disableTotpSchema` below, applied to the opposite
|
|
99
|
+
* (enable) side. Without this, a stolen/leaked session token (JWT or
|
|
100
|
+
* API token — either satisfies `requireUser`) alone was enough to
|
|
101
|
+
* enroll and confirm 2FA with an attacker-controlled secret: `confirm`
|
|
102
|
+
* is the call that actually flips `User.totpEnabledAt` and hands back
|
|
103
|
+
* the only copy of the recovery codes, and once it's flipped,
|
|
104
|
+
* `POST /auth/login` refuses the real account owner (who has neither
|
|
105
|
+
* the attacker's authenticator nor their recovery codes) — a
|
|
106
|
+
* self-inflicted, unrecoverable lockout worse than the already-guarded
|
|
107
|
+
* "stolen token disables 2FA" case, since this codebase has no
|
|
108
|
+
* account-recovery flow. See docs/security.md's "2FA (TOTP)" note. */
|
|
109
|
+
export const confirmTotpSchema = z.object({
|
|
110
|
+
code: z.string().min(6).max(6),
|
|
111
|
+
password: z.string().min(1),
|
|
112
|
+
});
|
|
113
|
+
/** `POST /auth/2fa/disable` requires re-proving the account password —
|
|
114
|
+
* without this, a stolen/leaked session token (JWT or API token) alone
|
|
115
|
+
* was enough to turn off 2FA, defeating the point of having it. See
|
|
116
|
+
* docs/security.md's "2FA (TOTP)" note. */
|
|
117
|
+
export const disableTotpSchema = z.object({
|
|
118
|
+
password: z.string().min(1),
|
|
119
|
+
});
|
|
120
|
+
/** `POST /auth/2fa/recovery-codes/regenerate` requires re-proving the
|
|
121
|
+
* account password — the one mutating 2FA route in `two-factor.ts`
|
|
122
|
+
* that didn't get this treatment when `confirmTotpSchema`/
|
|
123
|
+
* `disableTotpSchema` above did. Without it, a stolen/leaked session
|
|
124
|
+
* token alone (JWT or API token — either satisfies `requireUser`, and
|
|
125
|
+
* an API token "never expires on its own") was enough to silently
|
|
126
|
+
* invalidate every one of the real owner's existing recovery codes and
|
|
127
|
+
* have the fresh set handed back to the attacker instead — two
|
|
128
|
+
* independent harms in one call: it silently destroys the owner's own
|
|
129
|
+
* recovery path (this codebase has no account-recovery flow, so a
|
|
130
|
+
* lost authenticator plus a now-invalid saved code is an unrecoverable
|
|
131
|
+
* lockout, with zero signal anywhere that this happened), and it hands
|
|
132
|
+
* the attacker 10 valid recovery codes that let them satisfy the 2FA
|
|
133
|
+
* challenge at `POST /auth/login` later, using nothing but the
|
|
134
|
+
* password itself — defeating the entire point of 2FA. See
|
|
135
|
+
* docs/security.md's "2FA (TOTP)" note. */
|
|
136
|
+
export const regenerateRecoveryCodesSchema = z.object({
|
|
137
|
+
password: z.string().min(1),
|
|
138
|
+
});
|
|
139
|
+
/** `POST /auth/change-password` — same "re-prove the account password
|
|
140
|
+
* before mutating it" reasoning as `disableTotpSchema` above: a stolen
|
|
141
|
+
* session token alone must not be enough to rotate the one credential
|
|
142
|
+
* every other recovery path (2FA disable, this route itself) treats as
|
|
143
|
+
* the account's ultimate fallback. `newPassword` uses the same
|
|
144
|
+
* `min(10)` rule `signupSchema` enforces at signup. */
|
|
145
|
+
export const changePasswordSchema = z.object({
|
|
146
|
+
currentPassword: z.string().min(1),
|
|
147
|
+
newPassword: z.string().min(10, "password must be at least 10 characters"),
|
|
148
|
+
});
|
|
149
|
+
/** `POST /auth/forgot-password` — deliberately just an email, no
|
|
150
|
+
* password: this is the unauthenticated "I forgot it" entry point
|
|
151
|
+
* (issue-and-email-a-token side of the pair below), same shape as
|
|
152
|
+
* `verifyEmailSchema`'s issuing sibling `resend-verification` (which
|
|
153
|
+
* takes no body at all, since it's already `requireUser`-gated; this
|
|
154
|
+
* route isn't, so it needs the email to know who to email). */
|
|
155
|
+
export const forgotPasswordSchema = z.object({
|
|
156
|
+
email: emailSchema,
|
|
157
|
+
});
|
|
158
|
+
/** `POST /auth/reset-password` — redeems the token minted by
|
|
159
|
+
* `forgot-password` above. `newPassword` uses the same `min(10)` rule
|
|
160
|
+
* `changePasswordSchema`/`signupSchema` enforce everywhere else a
|
|
161
|
+
* password is ever set. */
|
|
162
|
+
export const resetPasswordSchema = z.object({
|
|
163
|
+
token: z.string().min(1),
|
|
164
|
+
newPassword: z.string().min(10, "password must be at least 10 characters"),
|
|
165
|
+
});
|
|
166
|
+
// `.url()` alone only checks that the string parses via the WHATWG `URL`
|
|
167
|
+
// constructor — it does NOT restrict the scheme. `file:`, `ext:`,
|
|
168
|
+
// `ssh:`, and `git:` all parse as valid URLs just as readily as
|
|
169
|
+
// `https:` does. `repoUrl` is stored verbatim (`packages/db/src/
|
|
170
|
+
// apps.ts`) and, once a build is leased, handed straight to a real
|
|
171
|
+
// `git clone` on a shared Mac-mini build slot
|
|
172
|
+
// (`packages/agent/src/lib/builder.ts`'s `runScript`/`simpleGit().clone`)
|
|
173
|
+
// — real `git` honors whatever transport the URL names: `file://`
|
|
174
|
+
// clones a local path on that shared host outright, and `git://`/
|
|
175
|
+
// `ssh://` attempt a real network connection to wherever the URL
|
|
176
|
+
// points — an SSRF vector against any host that Mac mini can otherwise
|
|
177
|
+
// reach (verified directly against a real `git` binary: an
|
|
178
|
+
// unrestricted `git://` clone genuinely attempts a DNS lookup/
|
|
179
|
+
// connection before failing). Git's own `ext::<command>` transport is
|
|
180
|
+
// worse still — it runs an arbitrary shell command as the "clone"
|
|
181
|
+
// itself, real RCE — though verified that a current `git` already
|
|
182
|
+
// refuses it by default; not every Mac mini in the fleet is guaranteed
|
|
183
|
+
// to run a git new enough to have that hardening, so this schema still
|
|
184
|
+
// closes it off rather than depending on git's own version-dependent
|
|
185
|
+
// default. Any signed-up org (no payment method needed — see #19's
|
|
186
|
+
// free signup credit) can set an app's `repoUrl` and trigger a build,
|
|
187
|
+
// so this schema is the actual authorization boundary for what
|
|
188
|
+
// transport ever reaches a real `git clone` on shared, multi-tenant
|
|
189
|
+
// infrastructure (`agents.ts`'s own docblock: "the fleet is a shared
|
|
190
|
+
// pool... not one Mac mini per customer"). This product only ever
|
|
191
|
+
// integrates public GitHub repos over HTTPS (no SSH-deploy-key feature
|
|
192
|
+
// exists anywhere in this codebase), so restricting to exactly `https:`
|
|
193
|
+
// isn't just "safe enough," it's the only scheme any legitimate caller
|
|
194
|
+
// ever needs. See `packages/agent/src/lib/builder.ts`'s own `runScript`
|
|
195
|
+
// git-clone call for the matching defense-in-depth restriction applied
|
|
196
|
+
// at the point of the actual clone, in case a repoUrl ever reaches the
|
|
197
|
+
// agent by some path other than this schema.
|
|
198
|
+
const repoUrlSchema = z
|
|
199
|
+
.string()
|
|
200
|
+
.url()
|
|
201
|
+
.refine((v) => new URL(v).protocol === "https:", "must be an https:// URL — no other transport (file:, ext:, ssh:, git:, ...) is supported");
|
|
202
|
+
export const createAppSchema = z.object({
|
|
203
|
+
name: z.string().min(1).max(255, "must be at most 255 characters"),
|
|
204
|
+
platform: platformSchema,
|
|
205
|
+
repoUrl: repoUrlSchema,
|
|
206
|
+
defaultBranch: z.string().min(1).default("main"),
|
|
207
|
+
});
|
|
208
|
+
// `platform` is deliberately excluded — see updateApp's own docblock in
|
|
209
|
+
// packages/db/src/apps.ts. Every field optional; the route only applies
|
|
210
|
+
// whichever ones the caller actually sent.
|
|
211
|
+
export const updateAppSchema = z.object({
|
|
212
|
+
name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
|
|
213
|
+
repoUrl: repoUrlSchema.optional(),
|
|
214
|
+
defaultBranch: z.string().min(1).optional(),
|
|
215
|
+
});
|
|
216
|
+
// Deliberately narrower than `OrgRow` (packages/db) — `updateOrg`'s own
|
|
217
|
+
// signature takes `Partial<OrgRow>` and applies whatever keys it's
|
|
218
|
+
// given, so this schema (not that function) is what actually keeps a
|
|
219
|
+
// `PATCH /orgs/:orgId` request from touching `tokenBalance`,
|
|
220
|
+
// `lhvCardToken`, or any other billing-internal column, the same way
|
|
221
|
+
// `updateAppSchema` deliberately excludes `platform`.
|
|
222
|
+
export const updateOrgSchema = z.object({
|
|
223
|
+
name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
|
|
224
|
+
// Explicit `null` clears it (disables notifications); omitted leaves
|
|
225
|
+
// whatever's already set unchanged — same "undefined vs. null" update
|
|
226
|
+
// convention this schema's own generic `updateOrg` (packages/db)
|
|
227
|
+
// partial-patch mechanism already relies on elsewhere. `.url()`
|
|
228
|
+
// deliberately doesn't restrict to https-only: a self-hosted operator
|
|
229
|
+
// testing against a local catcher (http://localhost:...) is a
|
|
230
|
+
// legitimate use before pointing this at a real endpoint.
|
|
231
|
+
buildWebhookUrl: z.string().url().nullable().optional(),
|
|
232
|
+
});
|
|
233
|
+
// Build-secrets v1 (ROADMAP.md, split off from #322) — a secret's `name`
|
|
234
|
+
// is injected verbatim as an env var name into every build for the app
|
|
235
|
+
// it's set on (apps/admin-api's /agents/lease-job route,
|
|
236
|
+
// packages/agent's builder.ts), so it's restricted to the same charset
|
|
237
|
+
// a real shell env var name allows (POSIX: starts with a letter/
|
|
238
|
+
// underscore, then letters/digits/underscores) — anything else could
|
|
239
|
+
// never actually work as `$NAME` in a workflow script, so rejecting it
|
|
240
|
+
// up front is a better failure mode than accepting it and silently
|
|
241
|
+
// never being usable. `value` is otherwise unrestricted content (a
|
|
242
|
+
// keystore password, an API token, anything) except for two things:
|
|
243
|
+
// no embedded newline, and a minimum length of 4.
|
|
244
|
+
//
|
|
245
|
+
// No newline: `packages/agent/src/lib/builder.ts`'s log-redaction fix
|
|
246
|
+
// (the `createLineBuffer` this cycle's own fix added) buffers a build
|
|
247
|
+
// step's raw stdout/stderr to `\n`-terminated lines before ever
|
|
248
|
+
// running `redactSecrets` on them, on the explicit assumption that "a
|
|
249
|
+
// secret's own value doesn't itself contain a newline" — true only if
|
|
250
|
+
// this schema actually enforces it. Without this, a multi-line secret
|
|
251
|
+
// (a PEM signing key, a base64-free P12/keystore export, a raw JSON
|
|
252
|
+
// service-account credential — all realistic values for a mobile-CI
|
|
253
|
+
// signing workflow) that gets echoed by a build step and happens to
|
|
254
|
+
// land split across two stdout chunks at one of its own embedded
|
|
255
|
+
// newlines would leak in full: each half, redacted independently,
|
|
256
|
+
// never matches the whole value, reassembling into the plaintext
|
|
257
|
+
// secret once the log is read back — the exact failure shape this
|
|
258
|
+
// cycle's own log-redaction fixes exist to close, reopened through a
|
|
259
|
+
// different trigger. Base64-encode a multi-line secret before storing
|
|
260
|
+
// it if you need one (decode it back inside the workflow script
|
|
261
|
+
// itself).
|
|
262
|
+
//
|
|
263
|
+
// Minimum 4 characters: `redactSecrets` (`builder.ts`) itself skips
|
|
264
|
+
// any secret value under 4 characters entirely (`if (value.length < 4)
|
|
265
|
+
// continue`) — a deliberate choice there, since redacting a one/two-
|
|
266
|
+
// character "secret" would blot out ordinary log text on pure
|
|
267
|
+
// coincidence far too often to be worth it, and offers essentially no
|
|
268
|
+
// confidentiality anyway. But nothing tied that skip threshold to
|
|
269
|
+
// what's actually *storable* as a secret until now: with only
|
|
270
|
+
// `.min(1)` here, an org could set e.g. `{ name: "PIN", value: "123"
|
|
271
|
+
// }`, have it injected into every build's environment exactly like any
|
|
272
|
+
// other secret, and have it never redacted from the persisted log if a
|
|
273
|
+
// step ever echoed it — a real, silent path from "operator sets a
|
|
274
|
+
// secret" to "cleartext in a log any org member can read," defeating
|
|
275
|
+
// the same "no route anywhere returns a secret's decrypted value"
|
|
276
|
+
// invariant the newline restriction above protects. Matching the
|
|
277
|
+
// redaction function's own skip threshold here closes that at the one
|
|
278
|
+
// place a short value could ever enter the system.
|
|
279
|
+
// Upper bound on `value`, added alongside the min-length/no-newline
|
|
280
|
+
// checks above: this field had no ceiling at all, unlike every other
|
|
281
|
+
// free-text field this codebase's build-input schemas validate (e.g.
|
|
282
|
+
// `appendLogSchema.chunk`'s own `.max()`, same file). `value` is
|
|
283
|
+
// injected verbatim as an env var value into every build for the app
|
|
284
|
+
// it's set on — `apps/admin-api/src/routes/agents.ts`'s `/agents/
|
|
285
|
+
// lease-job` route decrypts and inlines it into the job response,
|
|
286
|
+
// `packages/agent/src/lib/builder.ts`'s `runBuild` spreads it straight
|
|
287
|
+
// into the spawned build process's env (`spawn(..., { env: {
|
|
288
|
+
// ...process.env, ...vars } })`). A sufficiently large single env var
|
|
289
|
+
// can blow past the real OS `execve`/env-size ceiling (macOS's
|
|
290
|
+
// `ARG_MAX`), which doesn't fail cleanly at set-time — it makes
|
|
291
|
+
// `spawn`'s own `'error'` event fire on *every future build* for that
|
|
292
|
+
// app, which builder.ts classifies as `failureKind: "infra"` (the
|
|
293
|
+
// shell itself never launched) and auto-refunds. That's a genuine,
|
|
294
|
+
// silent, self-inflicted denial of service: one oversized secret,
|
|
295
|
+
// accepted without complaint through the dashboard's plain
|
|
296
|
+
// `<input type="password">`, permanently breaks every subsequent build
|
|
297
|
+
// for the app with an opaque native spawn failure instead of the clear
|
|
298
|
+
// 400 this schema should have given at the moment the secret was
|
|
299
|
+
// actually set. 64 KiB is comfortably larger than any realistic
|
|
300
|
+
// keystore password, API token, PEM signing key, or JSON service-
|
|
301
|
+
// account credential (even base64-inflated) while staying far under
|
|
302
|
+
// any OS env-var ceiling — generous, not a tight fit, same "reject the
|
|
303
|
+
// pathological case, not the normal one" reasoning `appendLogSchema.
|
|
304
|
+
// chunk`'s own `.max()` already documents.
|
|
305
|
+
export const setAppSecretSchema = z.object({
|
|
306
|
+
// `.max(255)` is defense in depth against one oversized name — same
|
|
307
|
+
// convention `createAppSchema.name`/`signupSchema.orgName` use — not
|
|
308
|
+
// the fix for unbounded distinct-secret *count* per app (see
|
|
309
|
+
// `MAX_APP_SECRETS_PER_APP`, apps/api/src/routes/orgs.ts, and its
|
|
310
|
+
// route's own count check for that; unlike #574/#577's list routes,
|
|
311
|
+
// `/agents/lease-job` needs every secret for a build, so bounding the
|
|
312
|
+
// read side alone wouldn't protect that hot path).
|
|
313
|
+
name: z
|
|
314
|
+
.string()
|
|
315
|
+
.max(255, "must be at most 255 characters")
|
|
316
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid env var name (letters, digits, underscore; not starting with a digit)"),
|
|
317
|
+
value: z
|
|
318
|
+
.string()
|
|
319
|
+
.min(4, "must be at least 4 characters — a shorter value can never be redacted from build logs (see redactSecrets in packages/agent/src/lib/builder.ts)")
|
|
320
|
+
.max(64 * 1024, "must be at most 64KB")
|
|
321
|
+
.refine((v) => !v.includes("\n"), "secret values can't contain a newline — base64-encode a multi-line secret (a PEM key, a keystore export, a JSON credential) before storing it"),
|
|
322
|
+
});
|
|
323
|
+
// Fleet-secrets v1 (issue #392). Unlike `AppSecret`'s free-form env var
|
|
324
|
+
// names, a `FleetSecret` name is drawn from a closed allowlist, because
|
|
325
|
+
// the two have very different blast radii: an `AppSecret` reaches one
|
|
326
|
+
// app's own builds, whereas a `FleetSecret` is handed to the agent
|
|
327
|
+
// daemon on *every* Mac mini enrolled in the org and baked into
|
|
328
|
+
// `apps/device-stream`'s launchd plist. Constraining the set of storable
|
|
329
|
+
// names at the one write boundary (apps/api's
|
|
330
|
+
// `POST /orgs/:orgId/fleet-secrets`) keeps that broadcast channel from
|
|
331
|
+
// quietly becoming a general-purpose "push arbitrary values onto every
|
|
332
|
+
// machine in the fleet" mechanism — which is what it would be if any
|
|
333
|
+
// name were storable. These are exactly the three values
|
|
334
|
+
// `apps/device-stream` reads at startup (see its own index.ts) and the
|
|
335
|
+
// three issue #392 names; adding a fourth is a deliberate one-line
|
|
336
|
+
// change here plus a matching consumer, not something an API caller can
|
|
337
|
+
// do on their own.
|
|
338
|
+
export const FLEET_SECRET_NAMES = ["JWT_SECRET", "JWT_SECRET_PREVIOUS", "INTERNAL_SERVICE_TOKEN"];
|
|
339
|
+
// `value` carries the same min-length/no-newline rules
|
|
340
|
+
// `setAppSecretSchema` documents above. The newline rule matters
|
|
341
|
+
// independently here: a fleet secret is written into an XML plist
|
|
342
|
+
// (`renderDeviceStreamPlist`), where an embedded newline inside a
|
|
343
|
+
// `<string>` would silently produce a value launchd hands the service
|
|
344
|
+
// with a stray line break in it.
|
|
345
|
+
export const setFleetSecretSchema = z.object({
|
|
346
|
+
name: z.enum(FLEET_SECRET_NAMES),
|
|
347
|
+
value: z
|
|
348
|
+
.string()
|
|
349
|
+
.min(4, "must be at least 4 characters — a shorter value can never be redacted from build logs (see redactSecrets in packages/agent/src/lib/builder.ts)")
|
|
350
|
+
// Same 64KB ceiling `setAppSecretSchema.value` has (see its own
|
|
351
|
+
// docblock) — this schema was added later (#392) and reintroduced
|
|
352
|
+
// the identical unbounded-value gap the earlier fix closed there.
|
|
353
|
+
// Nothing downstream (encryptSecret, the FleetSecret table's plain
|
|
354
|
+
// TEXT columns) rejects an oversized value either, and this one is
|
|
355
|
+
// broadcast to every agent in the org (GET /agents/fleet-secrets)
|
|
356
|
+
// and baked verbatim into a launchd plist <string> — an oversized
|
|
357
|
+
// value fails silently at rotation time instead of cleanly here.
|
|
358
|
+
.max(64 * 1024, "must be at most 64KB")
|
|
359
|
+
.refine((v) => !v.includes("\n"), "secret values can't contain a newline — they're written into a launchd plist <string> as-is"),
|
|
360
|
+
});
|
|
361
|
+
// No ASCII control characters (0x00-0x1F, 0x7F) — covers both the
|
|
362
|
+
// newline-forges-a-fake-log-line case setFleetSecretSchema/
|
|
363
|
+
// setAppSecretSchema's own `.refine` already guards against for a
|
|
364
|
+
// different downstream (a launchd plist <string>), and ANSI escape
|
|
365
|
+
// codes (\x1b), which would corrupt an operator's terminal instead.
|
|
366
|
+
// See issue #614: an org-member-controlled `workflow`/`branch` value
|
|
367
|
+
// is logged raw (packages/agent/src/index.ts's `Leased build ...` line)
|
|
368
|
+
// to a Mac mini fleet shared across every org, before appforge.yaml's
|
|
369
|
+
// own workflow-name validation ever runs.
|
|
370
|
+
const NO_CONTROL_CHARS_RE = /^[^\x00-\x1f\x7f]*$/;
|
|
371
|
+
const noControlChars = (v) => NO_CONTROL_CHARS_RE.test(v);
|
|
372
|
+
const NO_CONTROL_CHARS_MESSAGE = "can't contain control characters (including newlines)";
|
|
373
|
+
export const triggerBuildSchema = z.object({
|
|
374
|
+
appId: z.string().min(1),
|
|
375
|
+
workflow: z.string().min(1).max(255, "must be at most 255 characters").refine(noControlChars, NO_CONTROL_CHARS_MESSAGE),
|
|
376
|
+
branch: z
|
|
377
|
+
.string()
|
|
378
|
+
.min(1)
|
|
379
|
+
.max(255, "must be at most 255 characters")
|
|
380
|
+
.refine(noControlChars, NO_CONTROL_CHARS_MESSAGE)
|
|
381
|
+
.optional(),
|
|
382
|
+
});
|
|
383
|
+
export const topUpSchema = z.object({
|
|
384
|
+
packId: z.string().min(1),
|
|
385
|
+
});
|
|
386
|
+
export const setBillingModeSchema = z.object({
|
|
387
|
+
mode: z.enum(["prepaid", "postpaid_monthly"]),
|
|
388
|
+
});
|
|
389
|
+
export const agentEnrollSchema = z.object({
|
|
390
|
+
enrollmentToken: z.string().min(1),
|
|
391
|
+
name: z.string().min(1),
|
|
392
|
+
// The Mac's hardware UUID — the stable key the control plane dedupes
|
|
393
|
+
// enrollment on (see @appforge-ci/db's upsertAgent). Optional/nullable:
|
|
394
|
+
// absent from agents built before this field existed, and null on a
|
|
395
|
+
// non-Mac host, where the server falls back to matching on hostname.
|
|
396
|
+
machineId: z.string().min(1).nullable().optional(),
|
|
397
|
+
hostname: z.string().min(1),
|
|
398
|
+
macOsVersion: z.string().min(1),
|
|
399
|
+
xcodeVersion: z.string().nullable().optional(),
|
|
400
|
+
flutterVersion: z.string().nullable().optional(),
|
|
401
|
+
javaVersion: z.string().nullable().optional(),
|
|
402
|
+
androidSdkVersion: z.string().nullable().optional(),
|
|
403
|
+
cocoapodsVersion: z.string().nullable().optional(),
|
|
404
|
+
cpuModel: z.string().min(1),
|
|
405
|
+
// Optional/nullable for the same reason as machineId above: absent from
|
|
406
|
+
// an agent binary built before this field existed. A current binary
|
|
407
|
+
// always sends its own real AGENT_VERSION (packages/agent/src/lib/
|
|
408
|
+
// version.ts) — see @appforge-ci/core's isAgentVersionCurrent.
|
|
409
|
+
agentVersion: z.string().min(1).nullable().optional(),
|
|
410
|
+
// Optional/nullable for the same reason: absent from an agent binary
|
|
411
|
+
// built before this field existed. See packages/agent/src/lib/
|
|
412
|
+
// system-info.ts's screenSharingEnabled().
|
|
413
|
+
screenSharingEnabled: z.boolean().nullable().optional(),
|
|
414
|
+
});
|
|
415
|
+
export const agentHeartbeatSchema = z.object({
|
|
416
|
+
status: z.enum(["online", "draining", "building"]),
|
|
417
|
+
cpuUsagePercent: z.number().min(0).max(100),
|
|
418
|
+
memUsagePercent: z.number().min(0).max(100),
|
|
419
|
+
diskFreeGb: z.number().min(0),
|
|
420
|
+
currentBuildId: z.string().nullable().optional(),
|
|
421
|
+
tailscaleHostname: z.string().nullable().optional(),
|
|
422
|
+
// Always present (the agent caches its last-detected values and sends
|
|
423
|
+
// them every tick — see updateAgentHeartbeat's docblock in
|
|
424
|
+
// @appforge/db for why these are a direct overwrite, not optional).
|
|
425
|
+
xcodeVersion: z.string().nullable(),
|
|
426
|
+
flutterVersion: z.string().nullable(),
|
|
427
|
+
javaVersion: z.string().nullable(),
|
|
428
|
+
androidSdkVersion: z.string().nullable(),
|
|
429
|
+
cocoapodsVersion: z.string().nullable(),
|
|
430
|
+
// Nullable and optional, unlike the toolchain fields just above —
|
|
431
|
+
// omitted entirely by a not-yet-upgraded agent binary (updateAgentHeartbeat
|
|
432
|
+
// then COALESCEs, keeping whatever version this row last reported)
|
|
433
|
+
// rather than always resent, since a real build's own version is a
|
|
434
|
+
// constant, not a live-detected value that can legitimately disappear.
|
|
435
|
+
agentVersion: z.string().min(1).nullable().optional(),
|
|
436
|
+
// Same COALESCE-on-omission reasoning as agentVersion just above.
|
|
437
|
+
screenSharingEnabled: z.boolean().nullable().optional(),
|
|
438
|
+
// Concurrent builds (see @appforge-ci/agent's ConcurrentBuildRunner) —
|
|
439
|
+
// both optional/omittable for the same "not-yet-upgraded agent binary"
|
|
440
|
+
// reason as agentVersion/screenSharingEnabled just above; once an agent
|
|
441
|
+
// does send them, apps/admin-api's heartbeat route overwrites directly,
|
|
442
|
+
// never merges (see migrations/0017_agent_concurrency.sql's docblock).
|
|
443
|
+
/** How many builds this Mac will run at once. */
|
|
444
|
+
capacity: z.number().int().min(1).optional(),
|
|
445
|
+
/** Build ids this agent is running right now — its length is always
|
|
446
|
+
* <= capacity. Empty array means idle, distinct from omitting the
|
|
447
|
+
* field entirely (an old agent binary that's never heard of this). */
|
|
448
|
+
activeBuildIds: z.array(z.string()).optional(),
|
|
449
|
+
});
|
|
450
|
+
// `POST /builds/:buildId/logs` (apps/admin-api/src/routes/agents.ts) —
|
|
451
|
+
// one call per flushed line/burst from `packages/agent/src/lib/
|
|
452
|
+
// builder.ts`'s `createLineBuffer`, which itself caps a single
|
|
453
|
+
// *unterminated* line at 1 MiB before flushing it as its own call, and
|
|
454
|
+
// routinely flushes smaller multi-line bursts well under that. `.max()`
|
|
455
|
+
// here is a generous ceiling above that normal range (rejecting outright
|
|
456
|
+
// a single pathological chunk, 400) rather than a tight fit around it —
|
|
457
|
+
// the real cumulative-growth guard is the per-build byte cap
|
|
458
|
+
// `Build.logBytes` enforces route-side (migrations/
|
|
459
|
+
// 0028_build_log_bytes_cap.sql), not this per-chunk bound; this just
|
|
460
|
+
// closes the "one absurdly large chunk in a single call" gap that cap
|
|
461
|
+
// alone wouldn't catch quickly (it only rejects the *next* call once the
|
|
462
|
+
// cumulative total is already over, not the one call that single-
|
|
463
|
+
// handedly blows past it).
|
|
464
|
+
export const appendLogSchema = z.object({
|
|
465
|
+
chunk: z.string().max(4 * 1024 * 1024, "log chunk too large"),
|
|
466
|
+
seq: z.number().int().min(0),
|
|
467
|
+
});
|
|
468
|
+
export const completeBuildSchema = z.object({
|
|
469
|
+
status: z.enum(["succeeded", "failed"]),
|
|
470
|
+
artifacts: z
|
|
471
|
+
.array(z.object({
|
|
472
|
+
fileName: z.string(),
|
|
473
|
+
sizeBytes: z.number().int().min(0),
|
|
474
|
+
storageKey: z.string(),
|
|
475
|
+
}))
|
|
476
|
+
.default([]),
|
|
477
|
+
// Present only for builds run with an OTA-enabled workflow (Flutter/React
|
|
478
|
+
// Native code-push) — see packages/agent/src/lib/builder.ts's otaInfo and
|
|
479
|
+
// @appforge-ci/agent's ApiClient.completeBuild.
|
|
480
|
+
ota: z
|
|
481
|
+
.object({
|
|
482
|
+
channel: z.string().min(1).optional(),
|
|
483
|
+
mandatory: z.boolean().optional(),
|
|
484
|
+
bundleHash: z.string().min(1).optional(),
|
|
485
|
+
})
|
|
486
|
+
.optional(),
|
|
487
|
+
// Only meaningful when status is "failed" — see BuildResult.failureKind's
|
|
488
|
+
// docblock in packages/agent/src/lib/builder.ts for exactly what "infra"
|
|
489
|
+
// covers (deliberately narrow) and apps/admin-api/src/routes/agents.ts's
|
|
490
|
+
// /builds/:buildId/complete for what it drives (an automatic refund).
|
|
491
|
+
// Absent/undefined on a succeeded build, or on an older agent version
|
|
492
|
+
// that predates this field.
|
|
493
|
+
failureKind: z.enum(["user", "infra"]).optional(),
|
|
494
|
+
});
|
|
495
|
+
// `channel` had no upper bound at all — same `.max(255)` convention as
|
|
496
|
+
// `createAppSchema`'s `name`. Defense in depth alongside the pagination
|
|
497
|
+
// fix for `listOtaChannelsForApp`/`GET /apps/:appId/ota/channels`
|
|
498
|
+
// (packages/db/src/ota.ts): this caps how large a single abusive channel
|
|
499
|
+
// string can be, though the actual unbounded-request-cost fix is
|
|
500
|
+
// pagination — this schema has no way to cap how many *distinct*
|
|
501
|
+
// channels an org member creates, only each one's own length.
|
|
502
|
+
export const promoteOtaReleaseSchema = z.object({
|
|
503
|
+
buildId: z.string().min(1),
|
|
504
|
+
channel: z.string().min(1).max(255, "must be at most 255 characters").default("production"),
|
|
505
|
+
mandatory: z.boolean().optional().default(false),
|
|
506
|
+
rollout: z.number().int().min(0).max(100).optional().default(100),
|
|
507
|
+
});
|
|
508
|
+
export const setActiveOtaReleaseSchema = z.object({
|
|
509
|
+
releaseId: z.string().min(1).nullable(),
|
|
510
|
+
});
|
|
511
|
+
// Same 0-100 bound as `promoteOtaReleaseSchema`'s own `rollout` field —
|
|
512
|
+
// this is the fix that lets an existing release's rollout be progressed
|
|
513
|
+
// in place (10% -> 50% -> 100%) without minting a new release id, which
|
|
514
|
+
// would otherwise reshuffle `isInStagedRollout`'s per-install bucketing
|
|
515
|
+
// (packages/core/src/ota-rollout.ts) since it hashes on `releaseId`.
|
|
516
|
+
export const updateOtaReleaseRolloutSchema = z.object({
|
|
517
|
+
rollout: z.number().int().min(0).max(100),
|
|
518
|
+
});
|
|
519
|
+
/** `PATCH /orgs/:orgId/agents/:agentId/remote-control` (apps/admin-api) —
|
|
520
|
+
* the only way `Agent.remoteControlEnabled` ever changes (see
|
|
521
|
+
* @appforge/db's migrations/0024_agent_remote_control.sql). Deliberately
|
|
522
|
+
* not threaded through agentEnrollSchema/agentHeartbeatSchema above:
|
|
523
|
+
* this is an operator policy decision, not something the agent
|
|
524
|
+
* self-reports. */
|
|
525
|
+
export const setAgentRemoteControlSchema = z.object({
|
|
526
|
+
enabled: z.boolean(),
|
|
527
|
+
});
|
|
528
|
+
/** `POST /orgs/:orgId/agents/:agentId/vnc-session` (apps/admin-api)'s
|
|
529
|
+
* request body — optional and defaults to "view" (via `.optional()`,
|
|
530
|
+
* left for the route to interpret as the no-body case), matching every
|
|
531
|
+
* existing caller that sends no body at all today. `mode: "control"` is
|
|
532
|
+
* a *request*, not a grant: the route decides the actually-issued mode
|
|
533
|
+
* server-side from `Agent.remoteControlEnabled` and echoes it back in
|
|
534
|
+
* the response — see that route's own docblock. */
|
|
535
|
+
export const startVncSessionSchema = z.object({
|
|
536
|
+
mode: z.enum(["view", "control"]).optional(),
|
|
537
|
+
});
|
|
538
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-packs.test.d.ts","sourceRoot":"","sources":["../src/token-packs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { tokenPackDisplay, findTokenPack, TOKEN_PACKS, BUILD_COST_TOKENS } from "./types.js";
|
|
4
|
+
test("tokenPackDisplay: starter pack is the list-price baseline (0% discount)", () => {
|
|
5
|
+
const starter = TOKEN_PACKS.find((p) => p.id === "starter");
|
|
6
|
+
const display = tokenPackDisplay(starter);
|
|
7
|
+
assert.equal(display.priceEur, 10);
|
|
8
|
+
assert.equal(display.pricePerTokenCents, 1);
|
|
9
|
+
assert.equal(display.buildsIncluded, Math.floor(starter.tokens / BUILD_COST_TOKENS));
|
|
10
|
+
assert.equal(display.discountPercent, 0);
|
|
11
|
+
});
|
|
12
|
+
test("tokenPackDisplay: growth/scale/studio discounts match TOKEN_PACKS' own documented rates", () => {
|
|
13
|
+
// Matches the "// 10% off" / "20% off" / "30% off" comments next to
|
|
14
|
+
// TOKEN_PACKS' own definition — this confirms existing intent, not new behavior.
|
|
15
|
+
const expected = { growth: 10, scale: 20, studio: 30 };
|
|
16
|
+
for (const [id, discountPercent] of Object.entries(expected)) {
|
|
17
|
+
const pack = TOKEN_PACKS.find((p) => p.id === id);
|
|
18
|
+
assert.equal(tokenPackDisplay(pack).discountPercent, discountPercent, `${id} pack`);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
test("tokenPackDisplay: buildsIncluded is tokens / BUILD_COST_TOKENS, floored", () => {
|
|
22
|
+
for (const pack of TOKEN_PACKS) {
|
|
23
|
+
assert.equal(tokenPackDisplay(pack).buildsIncluded, Math.floor(pack.tokens / BUILD_COST_TOKENS));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
test("findTokenPack returns the matching pack for a known id", () => {
|
|
27
|
+
const pack = findTokenPack("scale");
|
|
28
|
+
assert.equal(pack?.id, "scale");
|
|
29
|
+
assert.equal(pack?.tokens, 30_000);
|
|
30
|
+
});
|
|
31
|
+
test("findTokenPack returns undefined for an unknown id", () => {
|
|
32
|
+
assert.equal(findTokenPack("does-not-exist"), undefined);
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=token-packs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-packs.test.js","sourceRoot":"","sources":["../src/token-packs.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE7F,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACrF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yFAAyF,EAAE,GAAG,EAAE;IACnG,oEAAoE;IACpE,iFAAiF;IACjF,MAAM,QAAQ,GAA2B,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC/E,KAAK,MAAM,CAAC,EAAE,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAE,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IACtF,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACnG,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wDAAwD,EAAE,GAAG,EAAE;IAClE,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}
|