Skip to content

Commit c1764fc

Browse files
DylanMerigaudclaude
andcommitted
Make the BambooHR seed script tolerate a fresh trial account's metadata
Seeding a brand-new trial (vs the previously-used account) 400'd twice on strict Zod parses that the trial's data violates: - GET /meta/lists returns many list fields with no `alias` (e.g. "Termination Type") and some with no `options`; the strict `alias: string` aborted the whole parse before the script could reach the division field (which does have an alias). - The account's demo employees carry `division: null`; the read-back's `division: z.string().optional()` rejected null (optional allows undefined, not null), aborting the seed-scope scan. Both are now nullish. We look up the division by alias and filter employees to SEED_DIVISION, so aliasless lists and null-division employees are simply out of scope rather than a hard parse failure. Verified: `pnpm hris:seed` now seeds the 13-person org into "LedgerLoop Demo" on the trial, and discovery returns 13. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f9fa8cf commit c1764fc

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

scripts/seed-bamboo.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,16 @@ const ListOption = z.object({
8282
});
8383
type ListOption = z.infer<typeof ListOption>;
8484

85-
/** Shape of GET /meta/lists we rely on (extra fields ignored). */
85+
/** Shape of GET /meta/lists we rely on (extra fields ignored). Many list fields
86+
carry NO `alias` (e.g. "Termination Type") and some no `options`, depending on
87+
the account, so both are nullish; we only care about the one aliased "division",
88+
which does have both. A strict `alias: string` here 400s the whole parse on any
89+
aliasless list, which aborts the seed before it can touch the division. */
8690
const MetaLists = z.array(
8791
z.object({
88-
alias: z.string(),
92+
alias: z.string().nullish(),
8993
fieldId: z.number(),
90-
options: z.array(ListOption),
94+
options: z.array(ListOption).nullish(),
9195
}),
9296
);
9397

@@ -105,14 +109,15 @@ const ensureDivision = async (c: Creds): Promise<void> => {
105109
const lists = MetaLists.parse(await res.json());
106110
const division = lists.find((l) => l.alias === "division");
107111
if (!division) throw new Error("no 'division' list field on this account");
112+
const existingOptions = division.options ?? [];
108113

109-
if (division.options.some((o) => o.name === SEED_DIVISION)) {
114+
if (existingOptions.some((o) => o.name === SEED_DIVISION)) {
110115
console.log(`Division "${SEED_DIVISION}" already exists.`);
111116
return;
112117
}
113118

114119
// Echo existing options by id (so the replace-PUT doesn't drop them), append ours.
115-
const options: Record<string, unknown>[] = division.options
120+
const options: Record<string, unknown>[] = existingOptions
116121
.filter((o) => o.name !== null) // skip any prior junk/blank options
117122
.map((o) => ({ id: o.id, value: o.name, archived: o.archived }));
118123
options.push({ value: SEED_DIVISION });
@@ -207,8 +212,11 @@ const seededEmployees = async (
207212
employees: z.array(
208213
z.object({
209214
id: z.string(),
210-
displayName: z.string().optional(),
211-
division: z.string().optional(),
215+
displayName: z.string().nullish(),
216+
// The account's demo employees can carry `division: null`; nullish (not
217+
// just optional) so the read-back doesn't 400 on them. We filter to
218+
// SEED_DIVISION next, so a null division is simply out of scope.
219+
division: z.string().nullish(),
212220
}),
213221
),
214222
})

0 commit comments

Comments
 (0)