cli-meta-ads 0.1.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 (106) hide show
  1. package/AGENTS.md +188 -0
  2. package/AI_CONTEXT.md +144 -0
  3. package/CLAUDE.md +183 -0
  4. package/README.md +590 -0
  5. package/REQUIREMENTS.md +148 -0
  6. package/dist/auth/constants.d.ts +1 -0
  7. package/dist/auth/constants.js +1 -0
  8. package/dist/auth/guards.d.ts +5 -0
  9. package/dist/auth/guards.js +16 -0
  10. package/dist/auth/login.d.ts +28 -0
  11. package/dist/auth/login.js +222 -0
  12. package/dist/cli/action.d.ts +11 -0
  13. package/dist/cli/action.js +77 -0
  14. package/dist/cli/build-cli.d.ts +2 -0
  15. package/dist/cli/build-cli.js +110 -0
  16. package/dist/cli/context.d.ts +24 -0
  17. package/dist/cli/context.js +19 -0
  18. package/dist/client/meta-api-client.d.ts +50 -0
  19. package/dist/client/meta-api-client.js +258 -0
  20. package/dist/client/meta-discovery.d.ts +13 -0
  21. package/dist/client/meta-discovery.js +88 -0
  22. package/dist/commands/accounts.d.ts +4 -0
  23. package/dist/commands/accounts.js +42 -0
  24. package/dist/commands/ads.d.ts +4 -0
  25. package/dist/commands/ads.js +148 -0
  26. package/dist/commands/adsets.d.ts +4 -0
  27. package/dist/commands/adsets.js +49 -0
  28. package/dist/commands/anomalies.d.ts +4 -0
  29. package/dist/commands/anomalies.js +44 -0
  30. package/dist/commands/assets.d.ts +4 -0
  31. package/dist/commands/assets.js +116 -0
  32. package/dist/commands/audiences.d.ts +4 -0
  33. package/dist/commands/audiences.js +40 -0
  34. package/dist/commands/auth.d.ts +4 -0
  35. package/dist/commands/auth.js +139 -0
  36. package/dist/commands/campaigns.d.ts +4 -0
  37. package/dist/commands/campaigns.js +273 -0
  38. package/dist/commands/capi.d.ts +4 -0
  39. package/dist/commands/capi.js +64 -0
  40. package/dist/commands/creatives.d.ts +4 -0
  41. package/dist/commands/creatives.js +49 -0
  42. package/dist/commands/diagnostics.d.ts +4 -0
  43. package/dist/commands/diagnostics.js +88 -0
  44. package/dist/commands/helpers.d.ts +13 -0
  45. package/dist/commands/helpers.js +50 -0
  46. package/dist/commands/launch.d.ts +4 -0
  47. package/dist/commands/launch.js +109 -0
  48. package/dist/commands/performance.d.ts +4 -0
  49. package/dist/commands/performance.js +55 -0
  50. package/dist/commands/pixel.d.ts +4 -0
  51. package/dist/commands/pixel.js +68 -0
  52. package/dist/commands/report.d.ts +4 -0
  53. package/dist/commands/report.js +30 -0
  54. package/dist/config/file-config.d.ts +6 -0
  55. package/dist/config/file-config.js +174 -0
  56. package/dist/config/types.d.ts +32 -0
  57. package/dist/config/types.js +1 -0
  58. package/dist/domain/account-scope.d.ts +7 -0
  59. package/dist/domain/account-scope.js +28 -0
  60. package/dist/domain/analytics.d.ts +52 -0
  61. package/dist/domain/analytics.js +125 -0
  62. package/dist/domain/approval-service.d.ts +10 -0
  63. package/dist/domain/approval-service.js +48 -0
  64. package/dist/domain/asset-feed-compiler.d.ts +43 -0
  65. package/dist/domain/asset-feed-compiler.js +104 -0
  66. package/dist/domain/launch-service.d.ts +200 -0
  67. package/dist/domain/launch-service.js +558 -0
  68. package/dist/domain/meta-ads-service.d.ts +620 -0
  69. package/dist/domain/meta-ads-service.js +841 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +9 -0
  72. package/dist/output/render.d.ts +3 -0
  73. package/dist/output/render.js +103 -0
  74. package/dist/types.d.ts +42 -0
  75. package/dist/types.js +1 -0
  76. package/dist/utils/currency.d.ts +4 -0
  77. package/dist/utils/currency.js +40 -0
  78. package/dist/utils/date-range.d.ts +20 -0
  79. package/dist/utils/date-range.js +115 -0
  80. package/dist/utils/errors.d.ts +35 -0
  81. package/dist/utils/errors.js +68 -0
  82. package/dist/utils/ids.d.ts +4 -0
  83. package/dist/utils/ids.js +23 -0
  84. package/dist/utils/meta-placement-assets.d.ts +44 -0
  85. package/dist/utils/meta-placement-assets.js +315 -0
  86. package/dist/utils/security.d.ts +5 -0
  87. package/dist/utils/security.js +104 -0
  88. package/dist/validators/common.d.ts +10 -0
  89. package/dist/validators/common.js +56 -0
  90. package/dist/validators/create-spec.d.ts +373 -0
  91. package/dist/validators/create-spec.js +394 -0
  92. package/dist/validators/launch-spec.d.ts +229 -0
  93. package/dist/validators/launch-spec.js +371 -0
  94. package/docs/TECHNICAL.md +480 -0
  95. package/examples/README.md +29 -0
  96. package/examples/launch/assets/feed4x5.png +0 -0
  97. package/examples/launch/assets/story9x16.png +0 -0
  98. package/examples/launch/multi-format-launch.json +90 -0
  99. package/examples/single-object/ad.json +6 -0
  100. package/examples/single-object/adset.json +30 -0
  101. package/examples/single-object/campaign.json +6 -0
  102. package/examples/single-object/creative.json +19 -0
  103. package/package.json +62 -0
  104. package/skills/meta-cli-operator/SKILL.md +105 -0
  105. package/skills/meta-cli-operator/agents/openai.yaml +4 -0
  106. package/skills/meta-cli-operator/references/update-matrix.md +117 -0
@@ -0,0 +1,394 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { AppError, ExitCode } from "../utils/errors.js";
5
+ import { normalizeObjectId } from "../utils/ids.js";
6
+ import { hasPlacementFormats, launchCreativeFormatKeys } from "../utils/meta-placement-assets.js";
7
+ export const pausedStatusSchema = z.literal("PAUSED");
8
+ export const nonEmptyStringSchema = z.string().trim().min(1);
9
+ const metaIdSchema = (label) => z.string().trim().min(1).transform((value) => normalizeObjectId(value, label));
10
+ export const campaignObjectiveValues = [
11
+ "APP_INSTALLS",
12
+ "BRAND_AWARENESS",
13
+ "CONVERSIONS",
14
+ "EVENT_RESPONSES",
15
+ "LEAD_GENERATION",
16
+ "LINK_CLICKS",
17
+ "LOCAL_AWARENESS",
18
+ "MESSAGES",
19
+ "OFFER_CLAIMS",
20
+ "OUTCOME_APP_PROMOTION",
21
+ "OUTCOME_AWARENESS",
22
+ "OUTCOME_ENGAGEMENT",
23
+ "OUTCOME_LEADS",
24
+ "OUTCOME_SALES",
25
+ "OUTCOME_TRAFFIC",
26
+ "PAGE_LIKES",
27
+ "POST_ENGAGEMENT",
28
+ "PRODUCT_CATALOG_SALES",
29
+ "REACH",
30
+ "STORE_VISITS",
31
+ "VIDEO_VIEWS"
32
+ ];
33
+ export const bidStrategyValues = [
34
+ "LOWEST_COST_WITHOUT_CAP",
35
+ "LOWEST_COST_WITH_BID_CAP",
36
+ "COST_CAP"
37
+ ];
38
+ export const optimizationGoalValues = [
39
+ "APP_INSTALLS",
40
+ "APP_INSTALLS_AND_OFFSITE_CONVERSIONS",
41
+ "CONVERSATIONS",
42
+ "ENGAGED_PAGE_VIEWS",
43
+ "ENGAGED_USERS",
44
+ "EVENT_RESPONSES",
45
+ "IMPRESSIONS",
46
+ "LANDING_PAGE_VIEWS",
47
+ "LEAD_GENERATION",
48
+ "LINK_CLICKS",
49
+ "OFFSITE_CONVERSIONS",
50
+ "PAGE_LIKES",
51
+ "POST_ENGAGEMENT",
52
+ "QUALITY_CALL",
53
+ "QUALITY_LEAD",
54
+ "REACH",
55
+ "THRUPLAY",
56
+ "VALUE",
57
+ "VISIT_INSTAGRAM_PROFILE"
58
+ ];
59
+ export const campaignObjectiveSchema = z.enum(campaignObjectiveValues);
60
+ export const bidStrategySchema = z.enum(bidStrategyValues);
61
+ export const optimizationGoalSchema = z.enum(optimizationGoalValues);
62
+ export const callToActionSchema = z.enum(["SHOP_NOW", "LEARN_MORE", "SIGN_UP"]);
63
+ const automationToggleSchema = z.union([z.literal(0), z.literal(1)]);
64
+ function hasCampaignBudget(value) {
65
+ return value.dailyBudget !== undefined || value.lifetimeBudget !== undefined;
66
+ }
67
+ function normalizeAdvantageAudienceTargeting(value) {
68
+ if (value.targeting_automation?.advantage_audience !== 1) {
69
+ return value;
70
+ }
71
+ return {
72
+ ...value,
73
+ age_max: value.age_max !== undefined && value.age_max < 65 ? 65 : value.age_max,
74
+ age_min: value.age_min !== undefined && value.age_min > 25 ? 25 : value.age_min
75
+ };
76
+ }
77
+ const targetingAutomationSchema = z.object({
78
+ advantage_audience: automationToggleSchema.optional(),
79
+ individual_setting: z.record(z.string(), automationToggleSchema).optional()
80
+ }).catchall(z.unknown());
81
+ const rawTargetingSpecSchema = z.object({
82
+ age_max: z.number().int().positive().optional(),
83
+ age_min: z.number().int().positive().optional(),
84
+ targeting_automation: targetingAutomationSchema.optional()
85
+ }).catchall(z.unknown()).refine((value) => Object.keys(value).length > 0, "targeting must include at least one key.");
86
+ export const targetingSpecSchema = rawTargetingSpecSchema
87
+ .transform((value) => normalizeAdvantageAudienceTargeting(value))
88
+ .superRefine((value, ctx) => {
89
+ if (value.age_min !== undefined
90
+ && value.age_max !== undefined
91
+ && value.age_min > value.age_max) {
92
+ ctx.addIssue({
93
+ code: z.ZodIssueCode.custom,
94
+ message: "targeting.age_min cannot be greater than targeting.age_max."
95
+ });
96
+ }
97
+ });
98
+ export const promotedObjectSchema = z.object({
99
+ applicationId: metaIdSchema("application id").optional(),
100
+ customConversionId: metaIdSchema("custom conversion id").optional(),
101
+ customEventType: nonEmptyStringSchema.optional(),
102
+ eventId: metaIdSchema("event id").optional(),
103
+ instagramActorId: metaIdSchema("instagram actor id").optional(),
104
+ objectStoreUrl: z.string().trim().url().optional(),
105
+ offlineConversionDataSetId: metaIdSchema("offline conversion data set id").optional(),
106
+ pageId: metaIdSchema("page id").optional(),
107
+ pixelId: metaIdSchema("pixel id").optional(),
108
+ productSetId: metaIdSchema("product set id").optional(),
109
+ whatsappPhoneNumber: nonEmptyStringSchema.optional()
110
+ }).strict().superRefine((value, ctx) => {
111
+ if (Object.keys(value).length === 0) {
112
+ ctx.addIssue({
113
+ code: z.ZodIssueCode.custom,
114
+ message: "promotedObject must include at least one supported field."
115
+ });
116
+ }
117
+ });
118
+ const imageCropValuesSchema = z.array(z.number());
119
+ const imageCropsSchema = z.record(z.string(), imageCropValuesSchema);
120
+ export const creativePlatformCustomizationOverrideSchema = z.object({
121
+ captionIds: z.array(nonEmptyStringSchema).optional(),
122
+ imageCrops: imageCropsSchema.optional(),
123
+ imageHash: nonEmptyStringSchema.optional(),
124
+ imageUrl: z.string().trim().url().optional(),
125
+ videoId: metaIdSchema("video id").optional()
126
+ }).strict().superRefine((value, ctx) => {
127
+ if (!value.imageHash && !value.imageUrl && !value.videoId) {
128
+ ctx.addIssue({
129
+ code: z.ZodIssueCode.custom,
130
+ message: "Platform customization must include imageHash, imageUrl, or videoId."
131
+ });
132
+ }
133
+ });
134
+ export const creativeFormatOverrideSchema = z.object({
135
+ captionIds: z.array(nonEmptyStringSchema).optional(),
136
+ imageCrops: imageCropsSchema.optional(),
137
+ imageHash: nonEmptyStringSchema.optional(),
138
+ imageUrl: z.string().trim().url().optional(),
139
+ videoId: metaIdSchema("video id").optional()
140
+ }).strict().superRefine((value, ctx) => {
141
+ if (!value.imageHash && !value.imageUrl && !value.videoId) {
142
+ ctx.addIssue({
143
+ code: z.ZodIssueCode.custom,
144
+ message: "Format override must include imageHash, imageUrl, or videoId."
145
+ });
146
+ }
147
+ });
148
+ export const creativeFormatsSchema = z.object({
149
+ feed4x5: creativeFormatOverrideSchema.optional(),
150
+ square1x1: creativeFormatOverrideSchema.optional(),
151
+ story9x16: creativeFormatOverrideSchema.optional()
152
+ }).strict().superRefine((value, ctx) => {
153
+ if (!launchCreativeFormatKeys.some((key) => value[key])) {
154
+ ctx.addIssue({
155
+ code: z.ZodIssueCode.custom,
156
+ message: "formats must include at least one supported format override."
157
+ });
158
+ }
159
+ });
160
+ export const creativePlatformCustomizationsSchema = z.object({
161
+ instagram: creativePlatformCustomizationOverrideSchema.optional()
162
+ }).strict().superRefine((value, ctx) => {
163
+ if (!value.instagram) {
164
+ ctx.addIssue({
165
+ code: z.ZodIssueCode.custom,
166
+ message: "platformCustomizations must include at least one supported platform override."
167
+ });
168
+ }
169
+ });
170
+ export const campaignCreateSpecSchema = z.object({
171
+ bidStrategy: bidStrategySchema.optional(),
172
+ dailyBudget: z.number().int().positive().optional(),
173
+ lifetimeBudget: z.number().int().positive().optional(),
174
+ name: nonEmptyStringSchema,
175
+ objective: campaignObjectiveSchema,
176
+ specialAdCategories: z.array(nonEmptyStringSchema).default([]),
177
+ status: pausedStatusSchema.optional()
178
+ }).strict().superRefine((value, ctx) => {
179
+ if (value.dailyBudget !== undefined && value.lifetimeBudget !== undefined) {
180
+ ctx.addIssue({
181
+ code: z.ZodIssueCode.custom,
182
+ message: "Provide either dailyBudget or lifetimeBudget for campaign budget optimization, not both."
183
+ });
184
+ }
185
+ if (value.bidStrategy !== undefined && !hasCampaignBudget(value)) {
186
+ ctx.addIssue({
187
+ code: z.ZodIssueCode.custom,
188
+ message: "Campaign bidStrategy requires dailyBudget or lifetimeBudget."
189
+ });
190
+ }
191
+ });
192
+ export const adSetCreateSpecSchema = z.object({
193
+ bidAmount: z.number().int().positive().optional(),
194
+ bidStrategy: bidStrategySchema.optional(),
195
+ billingEvent: z.enum(["IMPRESSIONS", "CLICKS"]),
196
+ campaignId: metaIdSchema("campaign id"),
197
+ dailyBudget: z.number().int().positive().optional(),
198
+ endTime: nonEmptyStringSchema.optional(),
199
+ lifetimeBudget: z.number().int().positive().optional(),
200
+ name: nonEmptyStringSchema,
201
+ optimizationGoal: optimizationGoalSchema,
202
+ promotedObject: promotedObjectSchema,
203
+ startTime: nonEmptyStringSchema.optional(),
204
+ status: pausedStatusSchema.optional(),
205
+ targeting: targetingSpecSchema
206
+ }).strict().superRefine((value, ctx) => {
207
+ if (value.dailyBudget === undefined && value.lifetimeBudget === undefined) {
208
+ ctx.addIssue({
209
+ code: z.ZodIssueCode.custom,
210
+ message: "Either dailyBudget or lifetimeBudget is required."
211
+ });
212
+ }
213
+ if (value.dailyBudget !== undefined && value.lifetimeBudget !== undefined) {
214
+ ctx.addIssue({
215
+ code: z.ZodIssueCode.custom,
216
+ message: "Provide either dailyBudget or lifetimeBudget, not both."
217
+ });
218
+ }
219
+ if (value.bidStrategy === "LOWEST_COST_WITHOUT_CAP" && value.bidAmount !== undefined) {
220
+ ctx.addIssue({
221
+ code: z.ZodIssueCode.custom,
222
+ message: "bidAmount cannot be combined with LOWEST_COST_WITHOUT_CAP."
223
+ });
224
+ }
225
+ if ((value.bidStrategy === "LOWEST_COST_WITH_BID_CAP" || value.bidStrategy === "COST_CAP")
226
+ && value.bidAmount === undefined) {
227
+ ctx.addIssue({
228
+ code: z.ZodIssueCode.custom,
229
+ message: `${value.bidStrategy} requires bidAmount.`
230
+ });
231
+ }
232
+ if ((value.optimizationGoal === "OFFSITE_CONVERSIONS" || value.optimizationGoal === "VALUE")
233
+ && (!value.promotedObject.pixelId || !value.promotedObject.customEventType)) {
234
+ ctx.addIssue({
235
+ code: z.ZodIssueCode.custom,
236
+ message: `${value.optimizationGoal} requires promotedObject.pixelId and promotedObject.customEventType.`
237
+ });
238
+ }
239
+ if ((value.optimizationGoal === "CONVERSATIONS"
240
+ || value.optimizationGoal === "LEAD_GENERATION"
241
+ || value.optimizationGoal === "QUALITY_CALL"
242
+ || value.optimizationGoal === "QUALITY_LEAD")
243
+ && !value.promotedObject.pageId) {
244
+ ctx.addIssue({
245
+ code: z.ZodIssueCode.custom,
246
+ message: `${value.optimizationGoal} requires promotedObject.pageId.`
247
+ });
248
+ }
249
+ });
250
+ const imageLinkCreativeSpecSchema = z.object({
251
+ formats: creativeFormatsSchema.optional(),
252
+ imageHash: nonEmptyStringSchema.optional(),
253
+ kind: z.literal("link-image"),
254
+ linkData: z.object({
255
+ callToAction: callToActionSchema.optional(),
256
+ description: nonEmptyStringSchema.optional(),
257
+ headline: nonEmptyStringSchema.optional(),
258
+ link: z.string().trim().url(),
259
+ message: nonEmptyStringSchema
260
+ }).strict(),
261
+ name: nonEmptyStringSchema.optional(),
262
+ pageId: metaIdSchema("page id"),
263
+ platformCustomizations: creativePlatformCustomizationsSchema.optional(),
264
+ status: pausedStatusSchema.optional()
265
+ }).strict().superRefine((value, ctx) => {
266
+ if (!value.imageHash && !hasPlacementFormats(value.formats)) {
267
+ ctx.addIssue({
268
+ code: z.ZodIssueCode.custom,
269
+ message: "Creative must include imageHash or formats."
270
+ });
271
+ }
272
+ if (value.platformCustomizations && value.formats) {
273
+ ctx.addIssue({
274
+ code: z.ZodIssueCode.custom,
275
+ message: "formats cannot be combined with legacy platformCustomizations."
276
+ });
277
+ }
278
+ if (value.platformCustomizations?.instagram?.videoId) {
279
+ ctx.addIssue({
280
+ code: z.ZodIssueCode.custom,
281
+ message: "platformCustomizations.instagram cannot use videoId for link-image creatives."
282
+ });
283
+ }
284
+ launchCreativeFormatKeys.forEach((formatKey) => {
285
+ const format = value.formats?.[formatKey];
286
+ if (format?.videoId) {
287
+ ctx.addIssue({
288
+ code: z.ZodIssueCode.custom,
289
+ message: `formats.${formatKey} cannot use videoId for link-image creatives.`
290
+ });
291
+ }
292
+ });
293
+ });
294
+ const videoLinkCreativeSpecSchema = z.object({
295
+ formats: creativeFormatsSchema.optional(),
296
+ kind: z.literal("video-link"),
297
+ name: nonEmptyStringSchema.optional(),
298
+ pageId: metaIdSchema("page id"),
299
+ platformCustomizations: creativePlatformCustomizationsSchema.optional(),
300
+ status: pausedStatusSchema.optional(),
301
+ videoData: z.object({
302
+ callToAction: callToActionSchema.optional(),
303
+ description: nonEmptyStringSchema.optional(),
304
+ link: z.string().trim().url(),
305
+ message: nonEmptyStringSchema,
306
+ title: nonEmptyStringSchema.optional()
307
+ }).strict(),
308
+ videoId: metaIdSchema("video id").optional()
309
+ }).strict().superRefine((value, ctx) => {
310
+ if (!value.videoId && !hasPlacementFormats(value.formats)) {
311
+ ctx.addIssue({
312
+ code: z.ZodIssueCode.custom,
313
+ message: "Creative must include videoId or formats."
314
+ });
315
+ }
316
+ if (value.platformCustomizations && value.formats) {
317
+ ctx.addIssue({
318
+ code: z.ZodIssueCode.custom,
319
+ message: "formats cannot be combined with legacy platformCustomizations."
320
+ });
321
+ }
322
+ if (value.platformCustomizations?.instagram
323
+ && (value.platformCustomizations.instagram.imageHash
324
+ || value.platformCustomizations.instagram.imageUrl
325
+ || value.platformCustomizations.instagram.imageCrops)) {
326
+ ctx.addIssue({
327
+ code: z.ZodIssueCode.custom,
328
+ message: "platformCustomizations.instagram cannot use imageHash, imageUrl, or imageCrops for video-link creatives."
329
+ });
330
+ }
331
+ launchCreativeFormatKeys.forEach((formatKey) => {
332
+ const format = value.formats?.[formatKey];
333
+ if (format && (format.imageHash || format.imageUrl || format.imageCrops)) {
334
+ ctx.addIssue({
335
+ code: z.ZodIssueCode.custom,
336
+ message: `formats.${formatKey} cannot use imageHash, imageUrl, or imageCrops for video-link creatives.`
337
+ });
338
+ }
339
+ });
340
+ });
341
+ export const creativeCreateSpecSchema = z.discriminatedUnion("kind", [
342
+ imageLinkCreativeSpecSchema,
343
+ videoLinkCreativeSpecSchema
344
+ ]);
345
+ export const adCreateSpecSchema = z.object({
346
+ adSetId: metaIdSchema("ad set id"),
347
+ creativeId: metaIdSchema("creative id"),
348
+ name: nonEmptyStringSchema,
349
+ status: pausedStatusSchema.optional()
350
+ }).strict();
351
+ async function ensureFileExists(filePath, label) {
352
+ const absolutePath = path.resolve(filePath);
353
+ try {
354
+ await access(absolutePath);
355
+ return absolutePath;
356
+ }
357
+ catch {
358
+ throw new AppError(`${label} does not exist: ${filePath}`, ExitCode.Usage, {
359
+ filePath: absolutePath
360
+ });
361
+ }
362
+ }
363
+ export async function resolveExistingFilePath(filePath, label, baseDir) {
364
+ if (!filePath.trim()) {
365
+ throw new AppError(`${label} is required.`, ExitCode.Usage);
366
+ }
367
+ return ensureFileExists(path.resolve(baseDir ?? process.cwd(), filePath), label);
368
+ }
369
+ export async function readSpecFile(specPath, schema, label) {
370
+ const absolutePath = await ensureFileExists(specPath, `${label} spec`);
371
+ let parsed;
372
+ try {
373
+ parsed = JSON.parse(await readFile(absolutePath, "utf8"));
374
+ }
375
+ catch (error) {
376
+ if (error instanceof SyntaxError) {
377
+ throw new AppError(`${label} spec must be valid JSON.`, ExitCode.Usage, {
378
+ specPath: absolutePath
379
+ });
380
+ }
381
+ throw error;
382
+ }
383
+ const result = schema.safeParse(parsed);
384
+ if (!result.success) {
385
+ throw new AppError(`${label} spec is invalid.`, ExitCode.Usage, {
386
+ issues: result.error.issues,
387
+ specPath: absolutePath
388
+ });
389
+ }
390
+ return {
391
+ spec: result.data,
392
+ specPath: absolutePath
393
+ };
394
+ }
@@ -0,0 +1,229 @@
1
+ import { z } from "zod";
2
+ export declare const launchSpecSchema: z.ZodObject<{
3
+ version: z.ZodLiteral<1>;
4
+ campaign: z.ZodObject<{
5
+ bidStrategy: z.ZodOptional<z.ZodEnum<{
6
+ LOWEST_COST_WITHOUT_CAP: "LOWEST_COST_WITHOUT_CAP";
7
+ LOWEST_COST_WITH_BID_CAP: "LOWEST_COST_WITH_BID_CAP";
8
+ COST_CAP: "COST_CAP";
9
+ }>>;
10
+ dailyBudget: z.ZodOptional<z.ZodNumber>;
11
+ lifetimeBudget: z.ZodOptional<z.ZodNumber>;
12
+ ref: z.ZodString;
13
+ name: z.ZodString;
14
+ objective: z.ZodEnum<{
15
+ APP_INSTALLS: "APP_INSTALLS";
16
+ BRAND_AWARENESS: "BRAND_AWARENESS";
17
+ CONVERSIONS: "CONVERSIONS";
18
+ EVENT_RESPONSES: "EVENT_RESPONSES";
19
+ LEAD_GENERATION: "LEAD_GENERATION";
20
+ LINK_CLICKS: "LINK_CLICKS";
21
+ LOCAL_AWARENESS: "LOCAL_AWARENESS";
22
+ MESSAGES: "MESSAGES";
23
+ OFFER_CLAIMS: "OFFER_CLAIMS";
24
+ OUTCOME_APP_PROMOTION: "OUTCOME_APP_PROMOTION";
25
+ OUTCOME_AWARENESS: "OUTCOME_AWARENESS";
26
+ OUTCOME_ENGAGEMENT: "OUTCOME_ENGAGEMENT";
27
+ OUTCOME_LEADS: "OUTCOME_LEADS";
28
+ OUTCOME_SALES: "OUTCOME_SALES";
29
+ OUTCOME_TRAFFIC: "OUTCOME_TRAFFIC";
30
+ PAGE_LIKES: "PAGE_LIKES";
31
+ POST_ENGAGEMENT: "POST_ENGAGEMENT";
32
+ PRODUCT_CATALOG_SALES: "PRODUCT_CATALOG_SALES";
33
+ REACH: "REACH";
34
+ STORE_VISITS: "STORE_VISITS";
35
+ VIDEO_VIEWS: "VIDEO_VIEWS";
36
+ }>;
37
+ specialAdCategories: z.ZodDefault<z.ZodArray<z.ZodString>>;
38
+ status: z.ZodOptional<z.ZodLiteral<"PAUSED">>;
39
+ }, z.core.$strict>;
40
+ adSets: z.ZodDefault<z.ZodArray<z.ZodObject<{
41
+ ref: z.ZodString;
42
+ campaignRef: z.ZodString;
43
+ bidAmount: z.ZodOptional<z.ZodNumber>;
44
+ bidStrategy: z.ZodOptional<z.ZodEnum<{
45
+ LOWEST_COST_WITHOUT_CAP: "LOWEST_COST_WITHOUT_CAP";
46
+ LOWEST_COST_WITH_BID_CAP: "LOWEST_COST_WITH_BID_CAP";
47
+ COST_CAP: "COST_CAP";
48
+ }>>;
49
+ billingEvent: z.ZodEnum<{
50
+ IMPRESSIONS: "IMPRESSIONS";
51
+ CLICKS: "CLICKS";
52
+ }>;
53
+ dailyBudget: z.ZodOptional<z.ZodNumber>;
54
+ endTime: z.ZodOptional<z.ZodString>;
55
+ lifetimeBudget: z.ZodOptional<z.ZodNumber>;
56
+ name: z.ZodString;
57
+ optimizationGoal: z.ZodEnum<{
58
+ APP_INSTALLS: "APP_INSTALLS";
59
+ EVENT_RESPONSES: "EVENT_RESPONSES";
60
+ LEAD_GENERATION: "LEAD_GENERATION";
61
+ LINK_CLICKS: "LINK_CLICKS";
62
+ PAGE_LIKES: "PAGE_LIKES";
63
+ POST_ENGAGEMENT: "POST_ENGAGEMENT";
64
+ REACH: "REACH";
65
+ APP_INSTALLS_AND_OFFSITE_CONVERSIONS: "APP_INSTALLS_AND_OFFSITE_CONVERSIONS";
66
+ CONVERSATIONS: "CONVERSATIONS";
67
+ ENGAGED_PAGE_VIEWS: "ENGAGED_PAGE_VIEWS";
68
+ ENGAGED_USERS: "ENGAGED_USERS";
69
+ IMPRESSIONS: "IMPRESSIONS";
70
+ LANDING_PAGE_VIEWS: "LANDING_PAGE_VIEWS";
71
+ OFFSITE_CONVERSIONS: "OFFSITE_CONVERSIONS";
72
+ QUALITY_CALL: "QUALITY_CALL";
73
+ QUALITY_LEAD: "QUALITY_LEAD";
74
+ THRUPLAY: "THRUPLAY";
75
+ VALUE: "VALUE";
76
+ VISIT_INSTAGRAM_PROFILE: "VISIT_INSTAGRAM_PROFILE";
77
+ }>;
78
+ promotedObject: z.ZodObject<{
79
+ applicationId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
80
+ customConversionId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
81
+ customEventType: z.ZodOptional<z.ZodString>;
82
+ eventId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
83
+ instagramActorId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
84
+ objectStoreUrl: z.ZodOptional<z.ZodString>;
85
+ offlineConversionDataSetId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
86
+ pageId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
87
+ pixelId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
88
+ productSetId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
89
+ whatsappPhoneNumber: z.ZodOptional<z.ZodString>;
90
+ }, z.core.$strict>;
91
+ startTime: z.ZodOptional<z.ZodString>;
92
+ status: z.ZodOptional<z.ZodLiteral<"PAUSED">>;
93
+ targeting: z.ZodPipe<z.ZodObject<{
94
+ age_max: z.ZodOptional<z.ZodNumber>;
95
+ age_min: z.ZodOptional<z.ZodNumber>;
96
+ targeting_automation: z.ZodOptional<z.ZodObject<{
97
+ advantage_audience: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>>;
98
+ individual_setting: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>>>;
99
+ }, z.core.$catchall<z.ZodUnknown>>>;
100
+ }, z.core.$catchall<z.ZodUnknown>>, z.ZodTransform<{
101
+ [x: string]: unknown;
102
+ age_max?: number | undefined;
103
+ age_min?: number | undefined;
104
+ targeting_automation?: {
105
+ [x: string]: unknown;
106
+ advantage_audience?: 0 | 1 | undefined;
107
+ individual_setting?: Record<string, 0 | 1> | undefined;
108
+ } | undefined;
109
+ }, {
110
+ [x: string]: unknown;
111
+ age_max?: number | undefined;
112
+ age_min?: number | undefined;
113
+ targeting_automation?: {
114
+ [x: string]: unknown;
115
+ advantage_audience?: 0 | 1 | undefined;
116
+ individual_setting?: Record<string, 0 | 1> | undefined;
117
+ } | undefined;
118
+ }>>;
119
+ }, z.core.$strict>>>;
120
+ assets: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
121
+ ref: z.ZodString;
122
+ kind: z.ZodLiteral<"image">;
123
+ file: z.ZodString;
124
+ name: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strict>, z.ZodObject<{
126
+ ref: z.ZodString;
127
+ kind: z.ZodLiteral<"video">;
128
+ file: z.ZodString;
129
+ name: z.ZodOptional<z.ZodString>;
130
+ }, z.core.$strict>], "kind">>>;
131
+ creatives: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
132
+ ref: z.ZodString;
133
+ kind: z.ZodLiteral<"link-image">;
134
+ assetRef: z.ZodOptional<z.ZodString>;
135
+ formats: z.ZodOptional<z.ZodObject<{
136
+ feed4x5: z.ZodOptional<z.ZodObject<{
137
+ assetRef: z.ZodString;
138
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
139
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
140
+ }, z.core.$strict>>;
141
+ square1x1: z.ZodOptional<z.ZodObject<{
142
+ assetRef: z.ZodString;
143
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
144
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
145
+ }, z.core.$strict>>;
146
+ story9x16: z.ZodOptional<z.ZodObject<{
147
+ assetRef: z.ZodString;
148
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
149
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
150
+ }, z.core.$strict>>;
151
+ }, z.core.$strict>>;
152
+ linkData: z.ZodObject<{
153
+ callToAction: z.ZodOptional<z.ZodEnum<{
154
+ SHOP_NOW: "SHOP_NOW";
155
+ LEARN_MORE: "LEARN_MORE";
156
+ SIGN_UP: "SIGN_UP";
157
+ }>>;
158
+ description: z.ZodOptional<z.ZodString>;
159
+ headline: z.ZodOptional<z.ZodString>;
160
+ link: z.ZodString;
161
+ message: z.ZodString;
162
+ }, z.core.$strict>;
163
+ name: z.ZodOptional<z.ZodString>;
164
+ pageId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
165
+ platformCustomizations: z.ZodOptional<z.ZodObject<{
166
+ instagram: z.ZodOptional<z.ZodObject<{
167
+ assetRef: z.ZodString;
168
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
169
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
170
+ }, z.core.$strict>>;
171
+ }, z.core.$strict>>;
172
+ status: z.ZodOptional<z.ZodLiteral<"PAUSED">>;
173
+ }, z.core.$strict>, z.ZodObject<{
174
+ ref: z.ZodString;
175
+ kind: z.ZodLiteral<"video-link">;
176
+ assetRef: z.ZodOptional<z.ZodString>;
177
+ formats: z.ZodOptional<z.ZodObject<{
178
+ feed4x5: z.ZodOptional<z.ZodObject<{
179
+ assetRef: z.ZodString;
180
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
181
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
182
+ }, z.core.$strict>>;
183
+ square1x1: z.ZodOptional<z.ZodObject<{
184
+ assetRef: z.ZodString;
185
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
186
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
187
+ }, z.core.$strict>>;
188
+ story9x16: z.ZodOptional<z.ZodObject<{
189
+ assetRef: z.ZodString;
190
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
191
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
192
+ }, z.core.$strict>>;
193
+ }, z.core.$strict>>;
194
+ name: z.ZodOptional<z.ZodString>;
195
+ pageId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
196
+ platformCustomizations: z.ZodOptional<z.ZodObject<{
197
+ instagram: z.ZodOptional<z.ZodObject<{
198
+ assetRef: z.ZodString;
199
+ captionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
200
+ imageCrops: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodNumber>>>;
201
+ }, z.core.$strict>>;
202
+ }, z.core.$strict>>;
203
+ status: z.ZodOptional<z.ZodLiteral<"PAUSED">>;
204
+ videoData: z.ZodObject<{
205
+ callToAction: z.ZodOptional<z.ZodEnum<{
206
+ SHOP_NOW: "SHOP_NOW";
207
+ LEARN_MORE: "LEARN_MORE";
208
+ SIGN_UP: "SIGN_UP";
209
+ }>>;
210
+ description: z.ZodOptional<z.ZodString>;
211
+ link: z.ZodString;
212
+ message: z.ZodString;
213
+ title: z.ZodOptional<z.ZodString>;
214
+ }, z.core.$strict>;
215
+ }, z.core.$strict>], "kind">>>;
216
+ ads: z.ZodDefault<z.ZodArray<z.ZodObject<{
217
+ ref: z.ZodString;
218
+ name: z.ZodString;
219
+ adSetRef: z.ZodString;
220
+ creativeRef: z.ZodString;
221
+ status: z.ZodOptional<z.ZodLiteral<"PAUSED">>;
222
+ }, z.core.$strict>>>;
223
+ }, z.core.$strict>;
224
+ export type LaunchSpec = z.infer<typeof launchSpecSchema>;
225
+ export declare function readLaunchSpecFile(specPath: string): Promise<{
226
+ spec: LaunchSpec;
227
+ specPath: string;
228
+ }>;
229
+ export declare function requireReceiptPath(value: string | undefined): string;