@go-labs-sg/event-tech 1.3.0 → 1.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.
Files changed (3) hide show
  1. package/README.md +102 -0
  2. package/dist/index.js +730 -151
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -85,8 +85,10 @@ Use `EVENT_TECH_API_URL` or `--api-url` to target another HTTPS deployment. Plai
85
85
  et --help
86
86
  et organization list
87
87
  et organization get --organization-id <id>
88
+ et organization create --name "Example Org" --owner-email owner@example.test --allowed-event-types LUCKY_DRAW
88
89
  et event list --organization-id <id>
89
90
  et event get --event-id <id>
91
+ et event create --organization-id <id> --name "Example Dinner" --type LUCKY_DRAW --location "Ballroom" --start 2026-11-20T18:00:00+08:00
90
92
  et event appearance get --event-id <id>
91
93
  et event appearance get --event-id <id> --public
92
94
  et event appearance update --event-id <id> --appearance-version <n> --appearance '<json>'
@@ -95,6 +97,10 @@ et event appearance upload --event-id <id> --file ./background.mp4 --media-type
95
97
  et event photo-wall display-items --event-id <id> --limit 30
96
98
  et lucky-draw participants replace --event-id <id> --participants '[{"luckyDrawId":"illustrative-001","name":"Example Person","email":"example@example.test"}]' --allow-state-change --target <id>
97
99
  et lucky-draw participants list --event-id <id> --page 1 --per-page 100
100
+ et lucky-draw config get --event-id <id>
101
+ et lucky-draw config update --event-id <id> --config '{"luckyDrawType":"SLOT"}'
102
+ et lucky-draw background upload --event-id <id> --file ./board.png --content-type image/png
103
+ et lucky-draw prizes list --event-id <id>
98
104
  ```
99
105
 
100
106
  Commands emit deterministic JSON envelopes. Authentication credentials are never included in successful output, and bearer credentials are redacted from errors.
@@ -231,6 +237,102 @@ bun run packages/event-tech/cli/src/index.ts help
231
237
 
232
238
  Building is not publication or permission to mutate live data. Use focused temporary business-behavior tests and the final pre-push gate from the shared workflow. Use this package's actual scripts; do not assume it has a command-artifact generator.
233
239
 
240
+ ## Lucky draw setup from scratch
241
+
242
+ An admin can set up a lucky draw without the browser. Each command uses the
243
+ same tRPC procedure and permission as the web flow: `organization create` is
244
+ global-admin only, `event create` needs organization owner/admin (or global
245
+ admin), and config, background and prize writes need `event.setup`.
246
+
247
+ ```sh
248
+ # 1. Organization (skip if it exists; check with `et organization get`).
249
+ et organization create --name "Example Org" \
250
+ --owner-email owner@example.test --allowed-event-types LUCKY_DRAW
251
+
252
+ # 2. LUCKY_DRAW event. The board row and default config are created with it.
253
+ et event create --organization-id <org-id> --name "Example Dinner" \
254
+ --type LUCKY_DRAW --location "Ballroom" \
255
+ --start 2026-11-20T18:00:00+08:00 --end 2026-11-20T23:00:00+08:00
256
+
257
+ # 3. 1920x1080 background (jpeg/png/webp/gif).
258
+ et lucky-draw background upload --event-id <event-id> \
259
+ --file ./board-1920x1080.png --content-type image/png
260
+
261
+ # 4. Slot mode plus theme. Preview first with --dry-run.
262
+ et lucky-draw config update --event-id <event-id> --config-file ./theme.json --dry-run
263
+ et lucky-draw config update --event-id <event-id> --config-file ./theme.json
264
+
265
+ # 5. Prizes, then the participant roster.
266
+ et lucky-draw prizes create --event-id <event-id> --prizes-file ./prizes.json
267
+ et lucky-draw participants replace --event-id <event-id> \
268
+ --participants-file ./participants.json --allow-state-change --target <event-id>
269
+
270
+ # 6. Read everything back.
271
+ et lucky-draw config get --event-id <event-id>
272
+ et lucky-draw prizes list --event-id <event-id>
273
+ ```
274
+
275
+ `organization create` also emails an owner invitation to `--owner-email`;
276
+ `data.deliveryStatus` reports `sent` or `failed`. The name must be unique among
277
+ active organizations (case-insensitive), otherwise the API returns `CONFLICT`.
278
+
279
+ `event create` requires `--location` and `--start` because the API does. Dates
280
+ are ISO 8601 date-times; include an offset so the time is unambiguous.
281
+
282
+ ### Config merge patch
283
+
284
+ `config update` reads the current config, applies only the keys you pass, and
285
+ saves the full result. Example `theme.json`:
286
+
287
+ ```json
288
+ {
289
+ "luckyDrawType": "SLOT",
290
+ "backgroundPreset": "#050d24",
291
+ "title": "Lucky Draw",
292
+ "titleColor": "#ffffff",
293
+ "titleFont": "font-serif",
294
+ "slotBorderColor": "#67ddf2",
295
+ "slotTextColor": "#ffffff",
296
+ "slotPositionX": 600,
297
+ "slotPositionY": 420
298
+ }
299
+ ```
300
+
301
+ Keys are validated with the API's `luckyDrawConfigSchema` before any request:
302
+ unknown keys, wrong types and invalid enum values (`luckyDrawType`:
303
+ `WHEEL|SLOT`; `segmentText`: `NAME|LUCKY_DRAW_ID|BOTH`) fail with a `USAGE`
304
+ error that names the field. Arrays such as `includeDomains` are replaced, not
305
+ appended. Fields missing from an older stored config fall back to the web
306
+ form's defaults. `--dry-run` prints `changedFields` and the merged config
307
+ without saving. It must be passed bare: `--dry-run=true` (or any
308
+ `--flag=value` form of a bare flag) is a `USAGE` error, never a write. Run `et --help` for the full key list; positions are pixels on
309
+ the 1920x1080 board.
310
+
311
+ ### Background
312
+
313
+ `background upload` uploads through the same presigned
314
+ `lucky-draw/backgrounds` flow as the preview page, then stores the key in
315
+ `customBackgroundImage`. It prints the `config get` readback, where
316
+ `customBackgroundImage` is a signed `/api/images/...` URL relative to the
317
+ Event Tech app. `background clear --allow-state-change --target <event-id>`
318
+ unsets it.
319
+
320
+ ### Prizes
321
+
322
+ `prizes create` appends after the last prize. Pass one prize with `--name`
323
+ (and optional `--description`) or many with `--prizes`/`--prizes-file`
324
+ (`[{"name":"Grand Prize","description":"Optional"}]`, up to 500).
325
+
326
+ `prizes update --prize-id <id> --name <text> [--description <text>]` renames a
327
+ prize. `prizes reorder --prize-ids <id,...>` must list every active prize ID
328
+ exactly once, in the new order.
329
+
330
+ `prizes delete --prize-ids <id,...> --allow-state-change --target <event-id>`
331
+ permanently deletes the prizes (the API hard-deletes them) and renumbers the
332
+ rest. A prize already assigned to a winner cannot be deleted (`CONFLICT`).
333
+ Before update, delete and reorder, the CLI checks that every prize ID belongs
334
+ to `--event-id`.
335
+
234
336
  ## Lucky draw participant source import
235
337
 
236
338
  The participant import is an API-first rollout: use a deployment that includes
package/dist/index.js CHANGED
@@ -1814,6 +1814,41 @@ var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst,
1814
1814
  });
1815
1815
  };
1816
1816
  });
1817
+ function handleCheckPropertyResult(result, payload, property) {
1818
+ if (result.issues.length) {
1819
+ payload.issues.push(...prefixIssues(property, result.issues));
1820
+ }
1821
+ }
1822
+ var $ZodCheckProperties = /* @__PURE__ */ $constructor("$ZodCheckProperties", (inst, def) => {
1823
+ $ZodCheck.init(inst, def);
1824
+ hide(inst, Symbol.iterator, function* () {
1825
+ yield inst;
1826
+ });
1827
+ let entries;
1828
+ inst._zod.check = (payload) => {
1829
+ if (payload.value == null) {
1830
+ payload.issues.push({ expected: "object", code: "invalid_type", input: payload.value, inst });
1831
+ return;
1832
+ }
1833
+ entries ?? (entries = Reflect.ownKeys(def.shape).map((key) => [key, def.shape[key]]));
1834
+ const input = payload.value;
1835
+ let proms;
1836
+ for (const [key, schema] of entries) {
1837
+ const result = schema._zod.run({ value: input[key], issues: [] }, {});
1838
+ if (result instanceof Promise) {
1839
+ proms ?? (proms = []);
1840
+ proms.push(result.then((result) => handleCheckPropertyResult(result, payload, key)));
1841
+ } else {
1842
+ handleCheckPropertyResult(result, payload, key);
1843
+ }
1844
+ }
1845
+ if (proms)
1846
+ return Promise.all(proms).then(() => {
1847
+ return;
1848
+ });
1849
+ return;
1850
+ };
1851
+ });
1817
1852
  var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (inst, def) => {
1818
1853
  $ZodCheck.init(inst, def);
1819
1854
  inst._zod.check = (payload) => {
@@ -4111,6 +4146,13 @@ function _endsWith(suffix, params) {
4111
4146
  suffix
4112
4147
  });
4113
4148
  }
4149
+ function _properties(shape, params) {
4150
+ return new $ZodCheckProperties({
4151
+ check: "properties",
4152
+ shape,
4153
+ ...normalizeParams(params)
4154
+ });
4155
+ }
4114
4156
  function _overwrite(tx) {
4115
4157
  return new $ZodCheckOverwrite({
4116
4158
  check: "overwrite",
@@ -6060,6 +6102,35 @@ function refine(fn, _params = {}) {
6060
6102
  function superRefine(fn, params) {
6061
6103
  return _superRefine(fn, params);
6062
6104
  }
6105
+ var ZodInstanceOf = /* @__PURE__ */ $constructor("ZodInstanceOf", (inst, def) => {
6106
+ ZodCustom.init(inst, def);
6107
+ }, {
6108
+ properties(shape, params) {
6109
+ return this.check(_properties(shape, params));
6110
+ }
6111
+ });
6112
+ function _instanceof(cls, params = {}) {
6113
+ const inst = new ZodInstanceOf({
6114
+ type: "custom",
6115
+ check: "custom",
6116
+ fn: (data) => data instanceof cls,
6117
+ abort: true,
6118
+ ...normalizeParams(params)
6119
+ });
6120
+ inst._zod.bag.Class = cls;
6121
+ inst._zod.check = (payload) => {
6122
+ if (!(payload.value instanceof cls)) {
6123
+ payload.issues.push({
6124
+ code: "invalid_type",
6125
+ expected: cls.name,
6126
+ input: payload.value,
6127
+ inst,
6128
+ path: [...inst._zod.def.path ?? []]
6129
+ });
6130
+ }
6131
+ };
6132
+ return inst;
6133
+ }
6063
6134
  // ../../cli-auth/src/protocol/schemas.ts
6064
6135
  var dateTimeString = string2().refine((value) => Number.isFinite(Date.parse(value)), "Invalid datetime");
6065
6136
  var base64Url2 = string2().min(43).max(128).regex(/^[A-Za-z0-9_-]+$/);
@@ -6754,8 +6825,30 @@ var logoutCliSession = async ({
6754
6825
  };
6755
6826
  // src/cli.ts
6756
6827
  import { readFileSync } from "fs";
6757
- import { readFile as readFile2 } from "fs/promises";
6758
- import { basename, resolve } from "path";
6828
+ import { readFile as readFile4 } from "fs/promises";
6829
+ import { basename as basename2, resolve } from "path";
6830
+
6831
+ // ../db/src/generated/prisma/enums.ts
6832
+ var EventType = {
6833
+ POST_IT_NOTE: "POST_IT_NOTE",
6834
+ PHOTO_WALL: "PHOTO_WALL",
6835
+ STORY_COLLECTION: "STORY_COLLECTION",
6836
+ LUCKY_DRAW: "LUCKY_DRAW",
6837
+ SCAVENGER_HUNT: "SCAVENGER_HUNT",
6838
+ HUMAN_BINGO: "HUMAN_BINGO",
6839
+ SIGNATURE_WALL: "SIGNATURE_WALL",
6840
+ GARDENS_BY_THE_BAY: "GARDENS_BY_THE_BAY",
6841
+ RATING_SYSTEM: "RATING_SYSTEM",
6842
+ EVENT_PASSPORT: "EVENT_PASSPORT"
6843
+ };
6844
+
6845
+ // ../validators/src/event-type.schema.ts
6846
+ var DEPRECATED_EVENT_TYPES = [
6847
+ EventType.SCAVENGER_HUNT,
6848
+ EventType.GARDENS_BY_THE_BAY
6849
+ ];
6850
+ var activeEventTypeSchema = _enum(EventType).exclude(DEPRECATED_EVENT_TYPES);
6851
+ var ACTIVE_EVENT_TYPES = activeEventTypeSchema.options;
6759
6852
 
6760
6853
  // src/api-client.ts
6761
6854
  import {
@@ -6841,6 +6934,193 @@ var api2 = new Proxy({}, {
6841
6934
  get: (_target, property, receiver) => Reflect.get(property === "public" ? testingClient ?? activePublicClient : testingClient ?? activeClient, property, receiver)
6842
6935
  });
6843
6936
 
6937
+ // src/arguments.ts
6938
+ import { readFile as readFile2 } from "fs/promises";
6939
+
6940
+ // src/runtime/index.ts
6941
+ var processRuntime = {
6942
+ stdout: process.stdout,
6943
+ stderr: process.stderr
6944
+ };
6945
+
6946
+ class CliUsageError extends Error {
6947
+ code = "USAGE";
6948
+ constructor(message) {
6949
+ super(message);
6950
+ this.name = "CliUsageError";
6951
+ }
6952
+ }
6953
+ var canonicalize = (value) => {
6954
+ if (Array.isArray(value))
6955
+ return value.map(canonicalize);
6956
+ if (value instanceof Date)
6957
+ return value.toJSON();
6958
+ if (typeof value === "object" && value !== null) {
6959
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, canonicalize(nested)]));
6960
+ }
6961
+ return value;
6962
+ };
6963
+ var serialize = (value) => `${JSON.stringify(canonicalize(value), null, 2)}
6964
+ `;
6965
+ var writeSuccess = (runtime, data) => {
6966
+ runtime.stdout.write(serialize({ ok: true, data }));
6967
+ };
6968
+ var sanitizeErrorMessage = (message) => message.replace(/\bBearer\s+\S+/giu, "Bearer [REDACTED]");
6969
+ var errorCode = (error) => {
6970
+ if (error instanceof CliUsageError)
6971
+ return error.code;
6972
+ if (error instanceof CliConfigurationError)
6973
+ return error.code;
6974
+ if (typeof error === "object" && error !== null && "data" in error) {
6975
+ const data = error.data;
6976
+ if (typeof data === "object" && data !== null && "code" in data && typeof data.code === "string") {
6977
+ return data.code;
6978
+ }
6979
+ }
6980
+ return "INTERNAL_ERROR";
6981
+ };
6982
+ var writeError = (runtime, error) => {
6983
+ const storageError = getCliStorageError(error);
6984
+ if (storageError) {
6985
+ runtime.stderr.write(serialize({ ok: false, error: storageError }));
6986
+ return;
6987
+ }
6988
+ const message = error instanceof Error ? sanitizeErrorMessage(error.message) : "Unknown error.";
6989
+ runtime.stderr.write(serialize({ ok: false, error: { code: errorCode(error), message } }));
6990
+ };
6991
+
6992
+ // src/arguments.ts
6993
+ var booleanFlags = new Set([
6994
+ "allow-delete",
6995
+ "allow-state-change",
6996
+ "dry-run",
6997
+ "help",
6998
+ "no-browser",
6999
+ "public",
7000
+ "version"
7001
+ ]);
7002
+ var parseArguments = (args) => {
7003
+ const positionals = [];
7004
+ const flags = new Map;
7005
+ for (let index = 0;index < args.length; index += 1) {
7006
+ const token = args[index];
7007
+ if (!token)
7008
+ continue;
7009
+ if (!token.startsWith("--")) {
7010
+ positionals.push(token);
7011
+ continue;
7012
+ }
7013
+ const [rawName, inlineValue] = token.slice(2).split("=", 2);
7014
+ if (!rawName)
7015
+ throw new CliUsageError("Flag names cannot be empty.");
7016
+ if (booleanFlags.has(rawName)) {
7017
+ if (inlineValue !== undefined) {
7018
+ throw new CliUsageError(`--${rawName} is a bare flag and does not take a value.`);
7019
+ }
7020
+ flags.set(rawName, true);
7021
+ continue;
7022
+ }
7023
+ if (inlineValue !== undefined) {
7024
+ flags.set(rawName, inlineValue);
7025
+ continue;
7026
+ }
7027
+ const nextValue = args[index + 1];
7028
+ if (nextValue && !nextValue.startsWith("--")) {
7029
+ flags.set(rawName, nextValue);
7030
+ index += 1;
7031
+ } else {
7032
+ flags.set(rawName, true);
7033
+ }
7034
+ }
7035
+ return { positionals, flags };
7036
+ };
7037
+ var getOptionalFlag = (parsed, name) => {
7038
+ const value = parsed.flags.get(name);
7039
+ if (value === undefined)
7040
+ return;
7041
+ if (value === true)
7042
+ throw new CliUsageError(`--${name} requires a value.`);
7043
+ return value;
7044
+ };
7045
+ var getRequiredFlag = (parsed, name) => {
7046
+ const value = getOptionalFlag(parsed, name);
7047
+ if (!value)
7048
+ throw new CliUsageError(`--${name} is required.`);
7049
+ return value;
7050
+ };
7051
+ var assertOnlyFlags = (parsed, allowedFlags) => {
7052
+ const allowed = new Set(["api-url", ...allowedFlags]);
7053
+ const unknown = [...parsed.flags.keys()].filter((name) => !allowed.has(name));
7054
+ if (unknown.length > 0) {
7055
+ throw new CliUsageError(`Unknown option${unknown.length === 1 ? "" : "s"}: ${unknown.map((name) => `--${name}`).join(", ")}.`);
7056
+ }
7057
+ };
7058
+ var assertPositionalCount = (parsed, expectedCount) => {
7059
+ if (parsed.positionals.length === expectedCount)
7060
+ return;
7061
+ throw new CliUsageError("Unexpected positional arguments.");
7062
+ };
7063
+ var assertStateChangeTarget = (parsed, eventId) => {
7064
+ if (parsed.flags.get("allow-state-change") !== true) {
7065
+ throw new CliUsageError("--allow-state-change is required as an explicit acknowledgement.");
7066
+ }
7067
+ if (getRequiredFlag(parsed, "target") !== eventId) {
7068
+ throw new CliUsageError("--target must exactly match --event-id.");
7069
+ }
7070
+ };
7071
+ var readInlineOrFile = async (parsed, inlineFlag, fileFlag) => {
7072
+ const inlineValue = getOptionalFlag(parsed, inlineFlag);
7073
+ const filePath = getOptionalFlag(parsed, fileFlag);
7074
+ if (inlineValue === undefined === (filePath === undefined)) {
7075
+ throw new CliUsageError(`Provide exactly one of --${inlineFlag} or --${fileFlag}.`);
7076
+ }
7077
+ if (inlineValue !== undefined)
7078
+ return inlineValue;
7079
+ try {
7080
+ return await readFile2(filePath, "utf8");
7081
+ } catch {
7082
+ throw new CliUsageError(`--${fileFlag} could not be read.`);
7083
+ }
7084
+ };
7085
+ var parseJsonValue = (value, name) => {
7086
+ try {
7087
+ return JSON.parse(value);
7088
+ } catch {
7089
+ throw new CliUsageError(`--${name} must be valid JSON.`);
7090
+ }
7091
+ };
7092
+ var parsePositiveInteger = (value, name) => {
7093
+ const parsed = Number(value);
7094
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
7095
+ throw new CliUsageError(`--${name} must be a positive integer.`);
7096
+ }
7097
+ return parsed;
7098
+ };
7099
+ var parseNonnegativeInteger = (value, name) => {
7100
+ const parsed = Number(value);
7101
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
7102
+ throw new CliUsageError(`--${name} must be a non-negative integer.`);
7103
+ }
7104
+ return parsed;
7105
+ };
7106
+ var parseBoundedInteger = (value, name, minimum, maximum) => {
7107
+ const parsed = parsePositiveInteger(value, name);
7108
+ if (parsed > maximum || parsed < minimum) {
7109
+ throw new CliUsageError(`--${name} must be an integer from ${minimum} to ${maximum}.`);
7110
+ }
7111
+ return parsed;
7112
+ };
7113
+ var parseIdList = (value, name) => {
7114
+ const ids = value.split(",").map((id) => id.trim());
7115
+ if (ids.some((id) => id.length === 0)) {
7116
+ throw new CliUsageError(`--${name} must not contain blank entries.`);
7117
+ }
7118
+ if (new Set(ids).size !== ids.length) {
7119
+ throw new CliUsageError(`--${name} must not contain duplicates.`);
7120
+ }
7121
+ return ids;
7122
+ };
7123
+
6844
7124
  // src/commands/index.ts
6845
7125
  var usage = `Event Tech CLI
6846
7126
 
@@ -6853,13 +7133,24 @@ Usage:
6853
7133
  et auth whoami
6854
7134
  et organization list
6855
7135
  et organization get --organization-id <id>
7136
+ et organization create --name <text> --owner-email <email> [--allowed-event-types <TYPE,...>]
6856
7137
  et event list --organization-id <id> [--page <n>] [--per-page <n>] [--name <text>] [--status <status>] [--archive-status <status>]
6857
7138
  et event get --event-id <id>
7139
+ et event create --organization-id <id> --name <text> --type <type> --location <text> --start <iso> [--end <iso>]
6858
7140
  et event appearance get --event-id <id> [--public]
6859
7141
  et event appearance update --event-id <id> --appearance-version <n> --appearance <json>
6860
7142
  et event appearance reset --event-id <id> --appearance-version <n>
6861
7143
  et event appearance upload --event-id <id> --file <path> --media-type <image|video> --content-type <mime>
6862
7144
  et event photo-wall display-items --event-id <id> [--limit <n>] [--cursor <json>] [--retained-ids <json>]
7145
+ et lucky-draw config get --event-id <id>
7146
+ et lucky-draw config update --event-id <id> (--config <json> | --config-file <path>) [--dry-run]
7147
+ et lucky-draw background upload --event-id <id> --file <path> --content-type <image-mime>
7148
+ et lucky-draw background clear --event-id <id> --allow-state-change --target <same-event-id>
7149
+ et lucky-draw prizes list --event-id <id>
7150
+ et lucky-draw prizes create --event-id <id> (--name <text> [--description <text>] | --prizes <json-array> | --prizes-file <path>)
7151
+ et lucky-draw prizes update --event-id <id> --prize-id <id> --name <text> [--description <text>]
7152
+ et lucky-draw prizes delete --event-id <id> --prize-ids <id,...> --allow-state-change --target <same-event-id>
7153
+ et lucky-draw prizes reorder --event-id <id> --prize-ids <id,...>
6863
7154
  et lucky-draw pre-draw --event-id <id> --prize-count <n>
6864
7155
  et lucky-draw participants replace --event-id <id> (--participants <json-array> | --participants-file <path>) --allow-state-change --target <same-event-id>
6865
7156
  et lucky-draw participants list --event-id <id> [--page <n>] [--per-page <n>]
@@ -6903,7 +7194,30 @@ Event Passport:
6903
7194
  Points are scores only; existing stamp-based Lucky Draw and prize thresholds still apply.
6904
7195
  Guest enrollment and account-linking require the browser; CLI uses existing device-approved auth.
6905
7196
 
7197
+ Organization and event setup:
7198
+ organization create is global-admin only. It provisions the org and emails an owner invitation
7199
+ to --owner-email; deliveryStatus reports whether that email was sent. Names must be unique.
7200
+ event create needs org owner/admin (or global admin). --type is an active event type such as
7201
+ LUCKY_DRAW; a LUCKY_DRAW event gets its board with default config automatically.
7202
+ --start/--end are ISO 8601 date-times with an offset, for example 2026-11-20T18:00:00+08:00.
7203
+
6906
7204
  Lucky draw:
7205
+ config, background and prize writes need event.setup, the same as the web board settings.
7206
+ config update is a merge patch: only the given keys change. Unknown keys and wrong value types
7207
+ are rejected before any request. Arrays (includeDomains etc.) are replaced, not appended.
7208
+ --dry-run prints the merged config without saving it.
7209
+ Keys: title, titleColor, isTitleVisible, titleFontSize, titleFont, prizeColor, prizeFontSize,
7210
+ isPrizeVisible, prizeFont, luckyDrawType (WHEEL|SLOT), slotBackgroundColor, slotBorderColor,
7211
+ slotTextColor, slotFontSize, slotTextFont, backgroundPreset, customBackgroundImage,
7212
+ numberOfSegments, segmentPreset, segmentText (NAME|LUCKY_DRAW_ID|BOTH), wheelSegmentFont,
7213
+ wheelSegmentFontSize, wheelSegmentColorsPreset, includeDomains, excludeDomains, includeEmails,
7214
+ excludeEmails, titlePositionX/Y, slotPositionX/Y, prizePositionX/Y, prizeDivWidth, prizeDivHeight.
7215
+ Positions are pixels on the 1920x1080 board.
7216
+ background upload stores the image (jpeg/png/webp/gif) and sets customBackgroundImage;
7217
+ the readback contains a signed /api/images URL. background clear unsets it.
7218
+ prizes create appends after the last prize; --prizes takes [{"name","description"?}].
7219
+ prizes delete permanently removes unassigned prizes and renumbers the rest.
7220
+ prizes reorder takes every active prize ID once, in the new order.
6907
7221
  pre-draw immediately saves winners for the next unassigned prizes in order.
6908
7222
  Uses the live draw random selection and participant filters in the backend.
6909
7223
  participants replace replaces the imported source roster; it is not an append.
@@ -6911,6 +7225,8 @@ Lucky draw:
6911
7225
  participants list defaults to page 1 with 100 results per page; inspect every page.
6912
7226
 
6913
7227
  Global options:
7228
+ Bare flags (--dry-run, --allow-state-change, --public, --no-browser) take no value;
7229
+ --flag=value spellings are rejected before any request.
6914
7230
  --api-url <url> Override EVENT_TECH_API_URL.
6915
7231
  --help Print this help as JSON.
6916
7232
  --version Print the CLI version as JSON.
@@ -6920,56 +7236,351 @@ Authentication:
6920
7236
  Credentials are stored exclusively through Bun.secrets.
6921
7237
  EVENT_TECH_API_URL defaults to ${DEFAULT_EVENT_TECH_API_BASE_URL}.`;
6922
7238
 
6923
- // src/runtime/index.ts
6924
- var processRuntime = {
6925
- stdout: process.stdout,
6926
- stderr: process.stderr
7239
+ // src/commands/lucky-draw-setup.ts
7240
+ import { readFile as readFile3 } from "fs/promises";
7241
+ import { basename } from "path";
7242
+
7243
+ // ../db/src/lucky-draw-config-json.ts
7244
+ var LuckyDrawSegmentText = {
7245
+ NAME: "NAME",
7246
+ LUCKY_DRAW_ID: "LUCKY_DRAW_ID",
7247
+ BOTH: "BOTH"
7248
+ };
7249
+ var LuckyDrawType = {
7250
+ WHEEL: "WHEEL",
7251
+ SLOT: "SLOT"
6927
7252
  };
6928
7253
 
6929
- class CliUsageError extends Error {
6930
- code = "USAGE";
6931
- constructor(message) {
6932
- super(message);
6933
- this.name = "CliUsageError";
7254
+ // ../validators/src/lucky-draw/lucky-draw-config.schema.ts
7255
+ var luckyDrawConfigSchema = object({
7256
+ title: string2(),
7257
+ titleColor: string2(),
7258
+ isTitleVisible: boolean2(),
7259
+ titleFontSize: number2(),
7260
+ prizeColor: string2(),
7261
+ prizeFontSize: number2(),
7262
+ isPrizeVisible: boolean2(),
7263
+ slotBackgroundColor: string2(),
7264
+ slotBorderColor: string2(),
7265
+ slotTextColor: string2(),
7266
+ slotFontSize: number2(),
7267
+ wheelSegmentColorsPreset: string2(),
7268
+ backgroundPreset: string2(),
7269
+ numberOfSegments: number2(),
7270
+ includeDomains: array(string2()),
7271
+ excludeDomains: array(string2()),
7272
+ includeEmails: array(string2()),
7273
+ excludeEmails: array(string2()),
7274
+ segmentPreset: string2(),
7275
+ segmentText: _enum([
7276
+ LuckyDrawSegmentText.NAME,
7277
+ LuckyDrawSegmentText.LUCKY_DRAW_ID,
7278
+ LuckyDrawSegmentText.BOTH
7279
+ ]),
7280
+ luckyDrawType: _enum([LuckyDrawType.WHEEL, LuckyDrawType.SLOT]),
7281
+ titleFont: string2(),
7282
+ prizeFont: string2(),
7283
+ wheelSegmentFont: string2(),
7284
+ wheelSegmentFontSize: number2(),
7285
+ slotTextFont: string2(),
7286
+ customBackgroundImage: string2().optional().nullable(),
7287
+ titlePositionX: number2(),
7288
+ titlePositionY: number2(),
7289
+ slotPositionX: number2(),
7290
+ slotPositionY: number2(),
7291
+ prizePositionX: number2(),
7292
+ prizePositionY: number2(),
7293
+ prizeDivWidth: number2(),
7294
+ prizeDivHeight: number2()
7295
+ });
7296
+
7297
+ // ../validators/src/lucky-draw/lucky-draw-config-input.schema.ts
7298
+ var luckyDrawConfigInputProcedureSchema = object({
7299
+ eventId: string2(),
7300
+ config: luckyDrawConfigSchema
7301
+ });
7302
+ var luckyDrawConfigInputSchema = object({
7303
+ ...luckyDrawConfigSchema.omit({ customBackgroundImage: true }).shape,
7304
+ customBackgroundImage: union([_instanceof(File), string2()]).optional().nullable()
7305
+ });
7306
+ var DEFAULT_LUCKY_DRAW_CONFIG_INPUT_VALUES = {
7307
+ title: "Lucky Draw Title",
7308
+ titleColor: "#111111",
7309
+ isTitleVisible: true,
7310
+ titleFontSize: 40,
7311
+ prizeColor: "#111111",
7312
+ prizeFontSize: 30,
7313
+ isPrizeVisible: true,
7314
+ slotBackgroundColor: "#ffffff",
7315
+ slotBorderColor: "#ffffff",
7316
+ slotTextColor: "#111111",
7317
+ slotFontSize: 5,
7318
+ backgroundPreset: "#ede9fe",
7319
+ numberOfSegments: 12,
7320
+ includeDomains: [],
7321
+ excludeDomains: [],
7322
+ includeEmails: [],
7323
+ excludeEmails: [],
7324
+ segmentPreset: "Vibrant",
7325
+ segmentText: LuckyDrawSegmentText.NAME,
7326
+ luckyDrawType: LuckyDrawType.WHEEL,
7327
+ titleFont: "font-sans",
7328
+ prizeFont: "font-sans",
7329
+ wheelSegmentFont: "font-sans",
7330
+ wheelSegmentFontSize: 12,
7331
+ slotTextFont: "font-sans",
7332
+ customBackgroundImage: null,
7333
+ titlePositionX: 750,
7334
+ titlePositionY: 100,
7335
+ slotPositionX: 600,
7336
+ slotPositionY: 300,
7337
+ prizePositionX: 800,
7338
+ prizePositionY: 200,
7339
+ prizeDivWidth: 500,
7340
+ prizeDivHeight: 100,
7341
+ wheelSegmentColorsPreset: "Vibrant"
7342
+ };
7343
+
7344
+ // ../validators/src/lucky-draw/lucky-draw-prize-input.schema.ts
7345
+ var luckyDrawPrizeInputSchema = object({
7346
+ name: string2().min(1),
7347
+ description: string2().optional()
7348
+ });
7349
+ var multipleLuckyDrawPrizeInputSchema = object({
7350
+ eventId: string2().min(1, "EventID is required"),
7351
+ prizes: array(luckyDrawPrizeInputSchema)
7352
+ });
7353
+
7354
+ // src/commands/lucky-draw-setup.ts
7355
+ var LUCKY_DRAW_BACKGROUND_FOLDER = "lucky-draw/backgrounds";
7356
+ var luckyDrawConfigPatchSchema = luckyDrawConfigSchema.partial().strict();
7357
+ var formatIssues = (label, issues) => `${label} is invalid: ${issues.map((issue) => {
7358
+ const path = issue.path.map(String).join(".");
7359
+ return path ? `${path}: ${issue.message}` : issue.message;
7360
+ }).join("; ")}.`;
7361
+ var parseLuckyDrawConfigPatch = (raw) => {
7362
+ const value = parseJsonValue(raw, "config");
7363
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
7364
+ throw new CliUsageError("--config must be a JSON object.");
7365
+ }
7366
+ const result = luckyDrawConfigPatchSchema.safeParse(value);
7367
+ if (!result.success) {
7368
+ throw new CliUsageError(formatIssues("--config", result.error.issues));
7369
+ }
7370
+ if (Object.keys(result.data).length === 0) {
7371
+ throw new CliUsageError("--config must change at least one field.");
7372
+ }
7373
+ return result.data;
7374
+ };
7375
+ var mergeLuckyDrawConfig = (current, patch) => {
7376
+ const merged = {
7377
+ ...DEFAULT_LUCKY_DRAW_CONFIG_INPUT_VALUES,
7378
+ ...current ?? {},
7379
+ ...patch
7380
+ };
7381
+ const result = luckyDrawConfigSchema.safeParse(merged);
7382
+ if (!result.success) {
7383
+ throw new CliUsageError(formatIssues("Merged lucky draw config", result.error.issues));
6934
7384
  }
6935
- }
6936
- var canonicalize = (value) => {
6937
- if (Array.isArray(value))
6938
- return value.map(canonicalize);
6939
- if (value instanceof Date)
6940
- return value.toJSON();
6941
- if (typeof value === "object" && value !== null) {
6942
- return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, canonicalize(nested)]));
7385
+ return result.data;
7386
+ };
7387
+ var parsePrizeList = (raw) => {
7388
+ const value = parseJsonValue(raw, "prizes");
7389
+ if (!Array.isArray(value) || value.length === 0) {
7390
+ throw new CliUsageError("--prizes must be a non-empty JSON array.");
6943
7391
  }
6944
- return value;
7392
+ if (value.length > 500) {
7393
+ throw new CliUsageError("--prizes must contain at most 500 prizes.");
7394
+ }
7395
+ return value.map((item, index) => {
7396
+ const result = luckyDrawPrizeInputSchema.strict().safeParse(item);
7397
+ if (!result.success) {
7398
+ throw new CliUsageError(formatIssues(`--prizes[${index}]`, result.error.issues));
7399
+ }
7400
+ return result.data;
7401
+ });
6945
7402
  };
6946
- var serialize = (value) => `${JSON.stringify(canonicalize(value), null, 2)}
6947
- `;
6948
- var writeSuccess = (runtime, data) => {
6949
- runtime.stdout.write(serialize({ ok: true, data }));
7403
+ var loadCurrentConfig = async (eventId) => (await api2.event.luckyDraw.getLuckyDrawByEventId.query({ eventId })).config;
7404
+ var loadPrizes = async (eventId) => (await api2.event.luckyDraw.getLuckyDrawPrizesByEventId.query({ eventId })).prizes;
7405
+ var assertPrizesBelongToEvent = (prizes, prizeIds, eventId) => {
7406
+ const known = new Set(prizes.map((prize) => prize.id));
7407
+ const missing = prizeIds.filter((id) => !known.has(id));
7408
+ if (missing.length > 0) {
7409
+ throw new CliUsageError(`Prize ID(s) not found on event ${eventId}: ${missing.join(", ")}.`);
7410
+ }
6950
7411
  };
6951
- var sanitizeErrorMessage = (message) => message.replace(/\bBearer\s+\S+/giu, "Bearer [REDACTED]");
6952
- var errorCode = (error) => {
6953
- if (error instanceof CliUsageError)
6954
- return error.code;
6955
- if (error instanceof CliConfigurationError)
6956
- return error.code;
6957
- if (typeof error === "object" && error !== null && "data" in error) {
6958
- const data = error.data;
6959
- if (typeof data === "object" && data !== null && "code" in data && typeof data.code === "string") {
6960
- return data.code;
7412
+ var saveConfig = (eventId, config) => api2.event.luckyDraw.updateLuckyDrawConfigJson.mutate({ eventId, config });
7413
+ var runLuckyDrawSetupCommand = async (input) => {
7414
+ const { parsed, runtime, authenticate } = input;
7415
+ const [, action, subAction] = parsed.positionals;
7416
+ if (action === "config" && subAction === "get") {
7417
+ assertPositionalCount(parsed, 3);
7418
+ assertOnlyFlags(parsed, ["event-id"]);
7419
+ const eventId = getRequiredFlag(parsed, "event-id");
7420
+ await authenticate();
7421
+ writeSuccess(runtime, await api2.event.luckyDraw.getLuckyDrawByEventId.query({ eventId }));
7422
+ return true;
7423
+ }
7424
+ if (action === "config" && subAction === "update") {
7425
+ assertPositionalCount(parsed, 3);
7426
+ assertOnlyFlags(parsed, ["event-id", "config", "config-file", "dry-run"]);
7427
+ const eventId = getRequiredFlag(parsed, "event-id");
7428
+ const patch = parseLuckyDrawConfigPatch(await readInlineOrFile(parsed, "config", "config-file"));
7429
+ await authenticate();
7430
+ const config = mergeLuckyDrawConfig(await loadCurrentConfig(eventId), patch);
7431
+ if (parsed.flags.get("dry-run") === true) {
7432
+ writeSuccess(runtime, {
7433
+ dryRun: true,
7434
+ changedFields: Object.keys(patch).sort(),
7435
+ config
7436
+ });
7437
+ return true;
6961
7438
  }
7439
+ writeSuccess(runtime, await saveConfig(eventId, config));
7440
+ return true;
6962
7441
  }
6963
- return "INTERNAL_ERROR";
6964
- };
6965
- var writeError = (runtime, error) => {
6966
- const storageError = getCliStorageError(error);
6967
- if (storageError) {
6968
- runtime.stderr.write(serialize({ ok: false, error: storageError }));
6969
- return;
7442
+ if (action === "background" && subAction === "upload") {
7443
+ assertPositionalCount(parsed, 3);
7444
+ assertOnlyFlags(parsed, ["event-id", "file", "content-type"]);
7445
+ const eventId = getRequiredFlag(parsed, "event-id");
7446
+ const filePath = getRequiredFlag(parsed, "file");
7447
+ const contentType = getRequiredFlag(parsed, "content-type");
7448
+ if (!contentType.startsWith("image/")) {
7449
+ throw new CliUsageError("--content-type must be an image MIME type.");
7450
+ }
7451
+ let file;
7452
+ try {
7453
+ file = await readFile3(filePath);
7454
+ } catch {
7455
+ throw new CliUsageError("--file could not be read.");
7456
+ }
7457
+ await authenticate();
7458
+ const signed = await api2.attachment.getPresignedUrlToUpload.mutate({
7459
+ folder: LUCKY_DRAW_BACKGROUND_FOLDER,
7460
+ fileName: basename(filePath),
7461
+ contentType,
7462
+ size: file.byteLength,
7463
+ eventId
7464
+ });
7465
+ const uploadResponse = await fetch(signed.uploadUrl, {
7466
+ method: "PUT",
7467
+ headers: { "content-type": contentType },
7468
+ body: file
7469
+ });
7470
+ if (!uploadResponse.ok) {
7471
+ throw new Error(`Lucky draw background upload failed with HTTP ${uploadResponse.status}.`);
7472
+ }
7473
+ const config = mergeLuckyDrawConfig(await loadCurrentConfig(eventId), {
7474
+ customBackgroundImage: signed.key
7475
+ });
7476
+ await saveConfig(eventId, config);
7477
+ writeSuccess(runtime, await api2.event.luckyDraw.getLuckyDrawByEventId.query({ eventId }));
7478
+ return true;
6970
7479
  }
6971
- const message = error instanceof Error ? sanitizeErrorMessage(error.message) : "Unknown error.";
6972
- runtime.stderr.write(serialize({ ok: false, error: { code: errorCode(error), message } }));
7480
+ if (action === "background" && subAction === "clear") {
7481
+ assertPositionalCount(parsed, 3);
7482
+ assertOnlyFlags(parsed, ["event-id", "allow-state-change", "target"]);
7483
+ const eventId = getRequiredFlag(parsed, "event-id");
7484
+ assertStateChangeTarget(parsed, eventId);
7485
+ await authenticate();
7486
+ const config = mergeLuckyDrawConfig(await loadCurrentConfig(eventId), {
7487
+ customBackgroundImage: null
7488
+ });
7489
+ writeSuccess(runtime, await saveConfig(eventId, config));
7490
+ return true;
7491
+ }
7492
+ if (action === "prizes" && subAction === "list") {
7493
+ assertPositionalCount(parsed, 3);
7494
+ assertOnlyFlags(parsed, ["event-id"]);
7495
+ const eventId = getRequiredFlag(parsed, "event-id");
7496
+ await authenticate();
7497
+ writeSuccess(runtime, await api2.event.luckyDraw.getLuckyDrawPrizesByEventId.query({ eventId }));
7498
+ return true;
7499
+ }
7500
+ if (action === "prizes" && subAction === "create") {
7501
+ assertPositionalCount(parsed, 3);
7502
+ assertOnlyFlags(parsed, [
7503
+ "event-id",
7504
+ "name",
7505
+ "description",
7506
+ "prizes",
7507
+ "prizes-file"
7508
+ ]);
7509
+ const eventId = getRequiredFlag(parsed, "event-id");
7510
+ const name = getOptionalFlag(parsed, "name");
7511
+ const description = getOptionalFlag(parsed, "description");
7512
+ const hasList = parsed.flags.has("prizes") || parsed.flags.has("prizes-file");
7513
+ if (hasList === (name !== undefined)) {
7514
+ throw new CliUsageError("Provide either --name [--description] or one of --prizes / --prizes-file.");
7515
+ }
7516
+ if (hasList && description !== undefined) {
7517
+ throw new CliUsageError("--description is only valid with --name; include descriptions in the --prizes JSON.");
7518
+ }
7519
+ const prizes = hasList ? parsePrizeList(await readInlineOrFile(parsed, "prizes", "prizes-file")) : parsePrizeList(JSON.stringify([
7520
+ { name, ...description === undefined ? {} : { description } }
7521
+ ]));
7522
+ await authenticate();
7523
+ writeSuccess(runtime, await api2.event.luckyDraw.createLuckyDrawPrize.mutate({
7524
+ eventId,
7525
+ prizes
7526
+ }));
7527
+ return true;
7528
+ }
7529
+ if (action === "prizes" && subAction === "update") {
7530
+ assertPositionalCount(parsed, 3);
7531
+ assertOnlyFlags(parsed, ["event-id", "prize-id", "name", "description"]);
7532
+ const eventId = getRequiredFlag(parsed, "event-id");
7533
+ const prizeId = getRequiredFlag(parsed, "prize-id");
7534
+ const name = getRequiredFlag(parsed, "name");
7535
+ const description = getOptionalFlag(parsed, "description");
7536
+ await authenticate();
7537
+ assertPrizesBelongToEvent(await loadPrizes(eventId), [prizeId], eventId);
7538
+ writeSuccess(runtime, await api2.event.luckyDraw.updateLuckyDrawPrize.mutate({
7539
+ id: prizeId,
7540
+ name,
7541
+ ...description === undefined ? {} : { description }
7542
+ }));
7543
+ return true;
7544
+ }
7545
+ if (action === "prizes" && subAction === "delete") {
7546
+ assertPositionalCount(parsed, 3);
7547
+ assertOnlyFlags(parsed, [
7548
+ "event-id",
7549
+ "prize-ids",
7550
+ "allow-state-change",
7551
+ "target"
7552
+ ]);
7553
+ const eventId = getRequiredFlag(parsed, "event-id");
7554
+ const prizeIds = parseIdList(getRequiredFlag(parsed, "prize-ids"), "prize-ids");
7555
+ assertStateChangeTarget(parsed, eventId);
7556
+ await authenticate();
7557
+ assertPrizesBelongToEvent(await loadPrizes(eventId), prizeIds, eventId);
7558
+ writeSuccess(runtime, await api2.event.luckyDraw.deleteLuckyDrawPrize.mutate({ prizeIds }));
7559
+ return true;
7560
+ }
7561
+ if (action === "prizes" && subAction === "reorder") {
7562
+ assertPositionalCount(parsed, 3);
7563
+ assertOnlyFlags(parsed, ["event-id", "prize-ids"]);
7564
+ const eventId = getRequiredFlag(parsed, "event-id");
7565
+ const prizeIds = parseIdList(getRequiredFlag(parsed, "prize-ids"), "prize-ids");
7566
+ await authenticate();
7567
+ const prizes = await loadPrizes(eventId);
7568
+ assertPrizesBelongToEvent(prizes, prizeIds, eventId);
7569
+ if (prizeIds.length !== prizes.length) {
7570
+ throw new CliUsageError(`--prize-ids must list all ${prizes.length} active prizes in the new order.`);
7571
+ }
7572
+ const namesById = new Map(prizes.map((prize) => [prize.id, prize.name]));
7573
+ writeSuccess(runtime, await api2.event.luckyDraw.updateLuckyDrawPrizesOrder.mutate({
7574
+ eventId,
7575
+ prizes: prizeIds.map((id, order) => ({
7576
+ id,
7577
+ order,
7578
+ name: namesById.get(id) ?? ""
7579
+ }))
7580
+ }));
7581
+ return true;
7582
+ }
7583
+ return false;
6973
7584
  };
6974
7585
 
6975
7586
  // src/commands/passport.ts
@@ -7309,93 +7920,6 @@ var getInstalledCliVersion = () => {
7309
7920
  return "unknown";
7310
7921
  }
7311
7922
  };
7312
- var booleanFlags = new Set([
7313
- "allow-delete",
7314
- "allow-state-change",
7315
- "help",
7316
- "no-browser",
7317
- "public",
7318
- "version"
7319
- ]);
7320
- var parseArguments = (args) => {
7321
- const positionals = [];
7322
- const flags = new Map;
7323
- for (let index = 0;index < args.length; index += 1) {
7324
- const token = args[index];
7325
- if (!token)
7326
- continue;
7327
- if (!token.startsWith("--")) {
7328
- positionals.push(token);
7329
- continue;
7330
- }
7331
- const [rawName, inlineValue] = token.slice(2).split("=", 2);
7332
- if (!rawName)
7333
- throw new CliUsageError("Flag names cannot be empty.");
7334
- if (inlineValue !== undefined) {
7335
- flags.set(rawName, inlineValue);
7336
- continue;
7337
- }
7338
- if (booleanFlags.has(rawName)) {
7339
- flags.set(rawName, true);
7340
- continue;
7341
- }
7342
- const nextValue = args[index + 1];
7343
- if (nextValue && !nextValue.startsWith("--")) {
7344
- flags.set(rawName, nextValue);
7345
- index += 1;
7346
- } else {
7347
- flags.set(rawName, true);
7348
- }
7349
- }
7350
- return { positionals, flags };
7351
- };
7352
- var getOptionalFlag = (parsed, name) => {
7353
- const value = parsed.flags.get(name);
7354
- if (value === undefined)
7355
- return;
7356
- if (value === true)
7357
- throw new CliUsageError(`--${name} requires a value.`);
7358
- return value;
7359
- };
7360
- var getRequiredFlag = (parsed, name) => {
7361
- const value = getOptionalFlag(parsed, name);
7362
- if (!value)
7363
- throw new CliUsageError(`--${name} is required.`);
7364
- return value;
7365
- };
7366
- var assertOnlyFlags = (parsed, allowedFlags) => {
7367
- const allowed = new Set(["api-url", ...allowedFlags]);
7368
- const unknown = [...parsed.flags.keys()].filter((name) => !allowed.has(name));
7369
- if (unknown.length > 0) {
7370
- throw new CliUsageError(`Unknown option${unknown.length === 1 ? "" : "s"}: ${unknown.map((name) => `--${name}`).join(", ")}.`);
7371
- }
7372
- };
7373
- var assertPositionalCount = (parsed, expectedCount) => {
7374
- if (parsed.positionals.length === expectedCount)
7375
- return;
7376
- throw new CliUsageError("Unexpected positional arguments.");
7377
- };
7378
- var parsePositiveInteger = (value, name) => {
7379
- const parsed = Number(value);
7380
- if (!Number.isSafeInteger(parsed) || parsed <= 0) {
7381
- throw new CliUsageError(`--${name} must be a positive integer.`);
7382
- }
7383
- return parsed;
7384
- };
7385
- var parseNonnegativeInteger = (value, name) => {
7386
- const parsed = Number(value);
7387
- if (!Number.isSafeInteger(parsed) || parsed < 0) {
7388
- throw new CliUsageError(`--${name} must be a non-negative integer.`);
7389
- }
7390
- return parsed;
7391
- };
7392
- var parseBoundedInteger = (value, name, minimum, maximum) => {
7393
- const parsed = parsePositiveInteger(value, name);
7394
- if (parsed > maximum || parsed < minimum) {
7395
- throw new CliUsageError(`--${name} must be an integer from ${minimum} to ${maximum}.`);
7396
- }
7397
- return parsed;
7398
- };
7399
7923
  var parseJsonObject = (value, name) => {
7400
7924
  let parsed;
7401
7925
  try {
@@ -7475,13 +7999,6 @@ var parseLuckyDrawParticipants = (value) => {
7475
7999
  };
7476
8000
  });
7477
8001
  };
7478
- var readParticipantsFile = async (path) => {
7479
- try {
7480
- return await readFile2(path, "utf8");
7481
- } catch {
7482
- throw new CliUsageError("--participants-file could not be read.");
7483
- }
7484
- };
7485
8002
  var parseAppearanceMediaType = (value) => {
7486
8003
  if (value === "image" || value === "video")
7487
8004
  return value;
@@ -7501,6 +8018,33 @@ var parseArchiveStatus = (value) => {
7501
8018
  }
7502
8019
  return value;
7503
8020
  };
8021
+ var parseCreatableEventType = (value) => {
8022
+ const result = activeEventTypeSchema.safeParse(value);
8023
+ if (!result.success) {
8024
+ throw new CliUsageError(`--type must be one of: ${activeEventTypeSchema.options.join(", ")}.`);
8025
+ }
8026
+ return result.data;
8027
+ };
8028
+ var parseEventTypeList = (value) => {
8029
+ const types = value.split(",").map((type) => type.trim());
8030
+ return [...new Set(types)].map((type) => {
8031
+ const result = activeEventTypeSchema.safeParse(type);
8032
+ if (!result.success) {
8033
+ throw new CliUsageError(`--allowed-event-types entries must be one of: ${activeEventTypeSchema.options.join(", ")}.`);
8034
+ }
8035
+ return result.data;
8036
+ });
8037
+ };
8038
+ var parseIsoDateTime = (value, name) => {
8039
+ if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d/.test(value)) {
8040
+ throw new CliUsageError(`--${name} must be an ISO 8601 date-time such as 2026-11-20T18:00:00+08:00.`);
8041
+ }
8042
+ const parsed = new Date(value);
8043
+ if (Number.isNaN(parsed.getTime())) {
8044
+ throw new CliUsageError(`--${name} must be a valid date-time.`);
8045
+ }
8046
+ return parsed;
8047
+ };
7504
8048
  var runCommand = async (parsed, runtime, credentialStore, apiBaseUrl, configureAccessToken) => {
7505
8049
  const [group, action, subAction] = parsed.positionals;
7506
8050
  const cliVersion = getInstalledCliVersion();
@@ -7602,6 +8146,44 @@ Waiting for approval\u2026
7602
8146
  }));
7603
8147
  return;
7604
8148
  }
8149
+ if ((group === "organization" || group === "org") && action === "create" && subAction === undefined) {
8150
+ assertPositionalCount(parsed, 2);
8151
+ assertOnlyFlags(parsed, ["name", "owner-email", "allowed-event-types"]);
8152
+ const allowedEventTypes = getOptionalFlag(parsed, "allowed-event-types");
8153
+ const input = {
8154
+ name: getRequiredFlag(parsed, "name"),
8155
+ ownerEmail: getRequiredFlag(parsed, "owner-email"),
8156
+ ...allowedEventTypes === undefined ? {} : { allowedEventTypes: parseEventTypeList(allowedEventTypes) }
8157
+ };
8158
+ await ensureAuthentication();
8159
+ writeSuccess(runtime, await api2.admin.organization.createOrganization.mutate(input));
8160
+ return;
8161
+ }
8162
+ if (group === "event" && action === "create" && subAction === undefined) {
8163
+ assertPositionalCount(parsed, 2);
8164
+ assertOnlyFlags(parsed, [
8165
+ "organization-id",
8166
+ "name",
8167
+ "type",
8168
+ "start",
8169
+ "end",
8170
+ "location"
8171
+ ]);
8172
+ const end = getOptionalFlag(parsed, "end");
8173
+ const input = {
8174
+ organizationId: getRequiredFlag(parsed, "organization-id"),
8175
+ name: getRequiredFlag(parsed, "name"),
8176
+ type: parseCreatableEventType(getRequiredFlag(parsed, "type")),
8177
+ location: getRequiredFlag(parsed, "location"),
8178
+ eventDateRange: {
8179
+ from: parseIsoDateTime(getRequiredFlag(parsed, "start"), "start"),
8180
+ ...end === undefined ? {} : { to: parseIsoDateTime(end, "end") }
8181
+ }
8182
+ };
8183
+ await ensureAuthentication();
8184
+ writeSuccess(runtime, await api2.event.createEvent.mutate(input));
8185
+ return;
8186
+ }
7605
8187
  if (group === "event" && action === "list" && subAction === undefined) {
7606
8188
  assertPositionalCount(parsed, 2);
7607
8189
  assertOnlyFlags(parsed, [
@@ -7679,11 +8261,11 @@ Waiting for approval\u2026
7679
8261
  const filePath = getRequiredFlag(parsed, "file");
7680
8262
  const contentType = getRequiredFlag(parsed, "content-type");
7681
8263
  const mediaType = parseAppearanceMediaType(getRequiredFlag(parsed, "media-type"));
7682
- const file = await readFile2(filePath);
8264
+ const file = await readFile4(filePath);
7683
8265
  await ensureAuthentication();
7684
8266
  const signed = await api2.attachment.getPresignedUrlToUpload.mutate({
7685
8267
  folder: `event-appearance/${mediaType === "image" ? "images" : "videos"}`,
7686
- fileName: basename(filePath),
8268
+ fileName: basename2(filePath),
7687
8269
  contentType,
7688
8270
  size: file.byteLength,
7689
8271
  eventId
@@ -7714,6 +8296,13 @@ Waiting for approval\u2026
7714
8296
  writeSuccess(runtime, await api2.event.photoWall.getPhotoWallDisplayItems.query(input));
7715
8297
  return;
7716
8298
  }
8299
+ if (group === "lucky-draw" && await runLuckyDrawSetupCommand({
8300
+ parsed,
8301
+ runtime,
8302
+ authenticate: ensureAuthentication
8303
+ })) {
8304
+ return;
8305
+ }
7717
8306
  if (group === "lucky-draw" && action === "pre-draw" && subAction === undefined) {
7718
8307
  assertPositionalCount(parsed, 2);
7719
8308
  assertOnlyFlags(parsed, ["event-id", "prize-count"]);
@@ -7734,21 +8323,11 @@ Waiting for approval\u2026
7734
8323
  "allow-state-change",
7735
8324
  "target"
7736
8325
  ]);
7737
- if (parsed.flags.get("allow-state-change") !== true) {
7738
- throw new CliUsageError("--allow-state-change is required as an explicit acknowledgement.");
7739
- }
7740
8326
  const eventId = getRequiredFlag(parsed, "event-id");
7741
- if (getRequiredFlag(parsed, "target") !== eventId) {
7742
- throw new CliUsageError("--target must exactly match --event-id.");
7743
- }
7744
- const participantsValue = getOptionalFlag(parsed, "participants");
7745
- const participantsFile = getOptionalFlag(parsed, "participants-file");
7746
- if (participantsValue === undefined === (participantsFile === undefined)) {
7747
- throw new CliUsageError("Provide exactly one of --participants or --participants-file.");
7748
- }
8327
+ assertStateChangeTarget(parsed, eventId);
7749
8328
  const input = {
7750
8329
  eventId,
7751
- participants: parseLuckyDrawParticipants(participantsValue ?? await readParticipantsFile(getRequiredFlag(parsed, "participants-file")))
8330
+ participants: parseLuckyDrawParticipants(await readInlineOrFile(parsed, "participants", "participants-file"))
7752
8331
  };
7753
8332
  await ensureAuthentication();
7754
8333
  writeSuccess(runtime, await api2.event.luckyDraw.createLuckyDrawParticipant.mutate(input));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/event-tech",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Event Tech CLI for managing Event Tech resources through the Go Labs API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",