vydanne 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/types/index.d.ts CHANGED
@@ -5,7 +5,25 @@ export type Platform = 'IOS' | 'MAC_OS';
5
5
 
6
6
  /** Every vydanne CLI command (kept in sync with src/registry.mjs by scripts/check-types.mjs). */
7
7
  export type CommandName =
8
+ /**
9
+ * Create (or reuse) the App Store version being prepared and attach the newest build to it.
10
+ * Lands in PREPARE_FOR_SUBMISSION with `releaseType: MANUAL` — it does not submit for review.
11
+ * Required first on an app with a version already on sale: only then does a draft exist for
12
+ * `fill` to write into. Override the version with VYDANNE_VERSION.
13
+ */
14
+ | 'prepare'
15
+ /**
16
+ * The whole release pipeline in its one working order: prepare → fill → previews → age-rating →
17
+ * review-contact → accessibility → preflight. Dry-run without --apply; stops at the first failure;
18
+ * never submits — Add to Review and Submit stay manual.
19
+ */
20
+ | 'push'
8
21
  | 'fill'
22
+ /**
23
+ * The two app-level facts that block **Add for Review** and belong to no single release: the App
24
+ * Store category, and whether the app shows third-party content. Set once; re-running is a no-op.
25
+ */
26
+ | 'appinfo'
9
27
  | 'age-rating'
10
28
  | 'review-contact'
11
29
  | 'accessibility'
@@ -13,6 +31,12 @@ export type CommandName =
13
31
  | 'previews'
14
32
  | 'iap'
15
33
  | 'compliance'
34
+ /**
35
+ * Map zdymak's output (`store-assets/<locale>/<target>/NN-name.png`) onto the folders `fill`
36
+ * uploads from — ASC locale codes, device-slot filename prefixes, and the Play read paths.
37
+ * Local files only; `--dry-run` previews. Run after `zdymak screenshots`/`build`, before `fill`.
38
+ */
39
+ | 'bridge'
16
40
  | 'inspect'
17
41
  | 'diff'
18
42
  | 'preflight'
@@ -49,6 +73,19 @@ export interface PreviewSpec {
49
73
  locales?: string[];
50
74
  }
51
75
 
76
+ /**
77
+ * Apple's category ids, not display names: upper snake case, and game subcategories are `GAMES_*`
78
+ * ('GAMES_PUZZLE', never 'Puzzle'). Apple takes up to two subcategories per category.
79
+ */
80
+ export interface CategoriesConfig {
81
+ primary: string;
82
+ primarySubcategoryOne?: string;
83
+ primarySubcategoryTwo?: string;
84
+ secondary?: string;
85
+ secondarySubcategoryOne?: string;
86
+ secondarySubcategoryTwo?: string;
87
+ }
88
+
52
89
  export interface PrivacyConfig {
53
90
  /** ASC data-type ids, e.g. ['CRASH_DATA', 'PERFORMANCE_DATA']. */
54
91
  collected: string[];
@@ -97,10 +134,103 @@ export interface GoogleConfig {
97
134
  defaultLocale?: string;
98
135
  /** `.aab` for `prerelease` — a file, or a directory whose NEWEST .aab is taken. Override: VYDANNE_AAB. */
99
136
  aab?: string;
100
- /** Testing track for `prerelease`: 'internal' (default) | 'alpha' | 'beta'. 'production' is refused. */
101
- track?: "internal" | "alpha" | "beta";
137
+ /**
138
+ * Testing track for `prerelease`. 'internal' (default), 'alpha', 'beta', or the name of any closed
139
+ * track you created in Play Console. Only 'production' is refused — that release is a human's.
140
+ */
141
+ track?: string;
142
+ /**
143
+ * Play image type -> local source path. Merged over the defaults, so declare only what differs.
144
+ * A type whose source does not exist is skipped; a missing local set never deletes the live one.
145
+ */
146
+ images?: Partial<Record<PlayImageType, string>>;
147
+ /**
148
+ * Locales to upload graphics to. Play holds images per language; the default is one set at
149
+ * `defaultLocale`. Pass a list, or '*' for every local listing folder. A `<source>/<lang>`
150
+ * subdirectory, when present, overrides the shared source for that language.
151
+ */
152
+ imageLocales?: string[] | "*";
153
+ }
154
+
155
+ /** Play listing image slots. */
156
+ export type PlayImageType =
157
+ | "icon"
158
+ | "featureGraphic"
159
+ | "tvBanner"
160
+ | "phoneScreenshots"
161
+ | "sevenInchScreenshots"
162
+ | "tenInchScreenshots"
163
+ | "wearScreenshots"
164
+ | "tvScreenshots";
165
+
166
+ /** Where `bridge` reads zdymak's output from, and which directory feeds which store slot. */
167
+ export interface BridgeConfig {
168
+ /** zdymak's output root. Read from zdymak.config.mjs when absent; './store-assets' otherwise. */
169
+ out?: string;
170
+ /**
171
+ * Screenshot slot token -> the zdymak output directory it comes from (a name, or names in
172
+ * preference order). Defaults cover zdymak's own target names; override when `dir:` in
173
+ * zdymak.config.mjs makes the directory name differ from the target name.
174
+ */
175
+ apple?: Record<string, string | string[]>;
176
+ /** Play image type -> the zdymak output directory it comes from. Same shape and reason. */
177
+ play?: Partial<Record<PlayImageType, string | string[]>>;
178
+ }
179
+
180
+ /** Overrides for the App Review contact. The PII itself stays in gitignored .txt files. */
181
+ export interface ReviewContactConfig {
182
+ /**
183
+ * Whether App Review needs a demo account. Inferred from the presence of
184
+ * `<metadataDir>/review_information/demo_user.txt` when omitted — set it only to disagree.
185
+ */
186
+ demoAccountRequired?: boolean;
187
+ }
188
+
189
+ /** `push` defaults. `--skip` adds to this per invocation. */
190
+ export interface PushConfig {
191
+ /** Steps this app never runs. 'prepare' and 'preflight' cannot be skipped. */
192
+ skip?: CommandName[];
102
193
  }
103
194
 
195
+ /**
196
+ * Age-rating content descriptors, for any rating other than '4+'.
197
+ *
198
+ * Merged over an all-NONE base, so declare only what applies. Apple computes the rating band from
199
+ * these — you describe the content, it decides the number. Enum keys take
200
+ * 'NONE' | 'INFREQUENT_OR_MILD' | 'FREQUENT_OR_INTENSE'; the rest are booleans.
201
+ */
202
+ export interface AgeRatingConfig {
203
+ advertising?: boolean;
204
+ alcoholTobaccoOrDrugUseOrReferences?: AgeRatingLevel;
205
+ contests?: AgeRatingLevel;
206
+ gambling?: boolean;
207
+ gamblingSimulated?: AgeRatingLevel;
208
+ gunsOrOtherWeapons?: AgeRatingLevel;
209
+ healthOrWellnessTopics?: boolean;
210
+ kidsAgeBand?: "FIVE_AND_UNDER" | "SIX_TO_EIGHT" | "NINE_TO_ELEVEN" | null;
211
+ lootBox?: boolean;
212
+ medicalOrTreatmentInformation?: AgeRatingLevel;
213
+ messagingAndChat?: boolean;
214
+ parentalControls?: boolean;
215
+ profanityOrCrudeHumor?: AgeRatingLevel;
216
+ ageAssurance?: boolean;
217
+ sexualContentGraphicAndNudity?: AgeRatingLevel;
218
+ sexualContentOrNudity?: AgeRatingLevel;
219
+ socialMedia?: boolean;
220
+ socialMediaAgeRestricted?: boolean;
221
+ horrorOrFearThemes?: AgeRatingLevel;
222
+ matureOrSuggestiveThemes?: AgeRatingLevel;
223
+ unrestrictedWebAccess?: boolean;
224
+ userGeneratedContent?: boolean;
225
+ violenceCartoonOrFantasy?: AgeRatingLevel;
226
+ violenceRealisticProlongedGraphicOrSadistic?: AgeRatingLevel;
227
+ violenceRealistic?: AgeRatingLevel;
228
+ ageRatingOverrideV2?: AgeRatingLevel;
229
+ koreaAgeRatingOverride?: AgeRatingLevel;
230
+ }
231
+
232
+ export type AgeRatingLevel = "NONE" | "INFREQUENT_OR_MILD" | "FREQUENT_OR_INTENSE";
233
+
104
234
  export interface ExportConfig {
105
235
  /** 'standard' → self-classify (ECCN 5D002, ENC 740.17(b)(1)); else no compliance doc is generated. */
106
236
  encryption: string;
@@ -109,6 +239,19 @@ export interface ExportConfig {
109
239
  appName?: string;
110
240
  version?: string;
111
241
  teamId?: string;
242
+ /**
243
+ * The app's cryptography inventory: [purpose, algorithm, keySize] per row. REQUIRED when
244
+ * `encryption` is 'standard' — `compliance` will not invent one, because the PDF it generates makes
245
+ * factual claims to a US export authority.
246
+ */
247
+ algorithms?: Array<[string, string, string?]>;
248
+ /** The statement paragraph, in your own words. REQUIRED when `encryption` is 'standard'. */
249
+ statement?: string;
250
+ /**
251
+ * Set true only once the report has ACTUALLY been emailed to BIS and the NSA. When false (default)
252
+ * the generated PDF does not claim it was submitted.
253
+ */
254
+ filed?: boolean;
112
255
  }
113
256
 
114
257
  /** The `vydanne.config.mjs` default export. */
@@ -126,9 +269,34 @@ export interface VydanneConfig {
126
269
  platforms?: Platform[];
127
270
  /** App UI locales; mapped to ASC codes (unsupported ones fall back to primary). */
128
271
  uiLocales?: string[];
272
+ /**
273
+ * App locale code -> App Store locale code, for codes Apple spells differently or does not know.
274
+ * Merged over the built-in table, so declare only your exceptions. An override naming a code Apple
275
+ * does not have is reported by `locales` and `preflight` rather than silently ignored.
276
+ */
277
+ localeMap?: Record<string, string>;
129
278
  metadataDir?: string;
130
- /** e.g. '4+'. */
279
+ /**
280
+ * Where each platform's screenshots live. Defaults to fastlane's supply convention
281
+ * ('fastlane/screenshots' and 'fastlane/screenshots-macos'). Read by `fill`, `diff`, `preflight`
282
+ * and written by `bridge`.
283
+ */
284
+ screenshots?: { IOS?: string; MAC_OS?: string };
285
+ /** e.g. '4+'. Anything else needs `ageRating` to say what makes it that. */
131
286
  rating?: string;
287
+ /** Content descriptors for a rating above '4+'. Merged over an all-NONE base. */
288
+ ageRating?: AgeRatingConfig;
289
+ /**
290
+ * App Store category, written by `appinfo`. Apple blocks Add for Review without a primary one, and
291
+ * subcategories are valid only under `'GAMES'`.
292
+ */
293
+ categories?: CategoriesConfig;
294
+ /**
295
+ * Does the app contain, show or access THIRD-PARTY content? Also blocks Add for Review. Left
296
+ * undefined it is not written at all, so an answer already given in App Store Connect is never
297
+ * overwritten by a default nobody chose — this is a declaration to Apple, not a preference.
298
+ */
299
+ contentRights?: boolean;
132
300
  privacy?: PrivacyConfig;
133
301
  iaps?: IapConfig[];
134
302
  previews?: PreviewSpec[];
@@ -139,6 +307,12 @@ export interface VydanneConfig {
139
307
  ios?: IosConfig;
140
308
  /** Google Play (`--store google`): listings, screenshots, feature graphic via the Edits API. */
141
309
  google?: GoogleConfig;
310
+ /** Where `bridge` reads zdymak's output from, and which directory feeds which store slot. */
311
+ bridge?: BridgeConfig;
312
+ /** `push` defaults — steps this app never runs. */
313
+ push?: PushConfig;
314
+ /** App Review contact overrides (the PII itself stays in gitignored .txt files). */
315
+ reviewContact?: ReviewContactConfig;
142
316
  /**
143
317
  * Terms the cross-store check must not flag for this app.
144
318
  *
@@ -165,20 +339,90 @@ export declare class Client {
165
339
  post(path: string, body: unknown): Promise<{ status: number; json: any }>;
166
340
  patch(path: string, body: unknown): Promise<{ status: number; json: any }>;
167
341
  del(path: string): Promise<{ status: number; json: any }>;
168
- editVersion(platform: Platform): Promise<any>;
169
- appInfo(): Promise<any>;
342
+ /**
343
+ * The version being prepared, or null when none exists. `allowLive` (read-only commands only) falls
344
+ * back to the version on sale; writes must never target it.
345
+ */
346
+ editVersion(platform: Platform, opts?: { allowLive?: boolean }): Promise<any>;
347
+ /**
348
+ * The app-info being prepared (name/subtitle, age rating), or null when none exists. `allowLive`
349
+ * (read-only commands only) falls back to the live record; writes must never target it.
350
+ */
351
+ appInfo(opts?: { allowLive?: boolean }): Promise<any>;
170
352
  versionLocalizations(versionId: string): Promise<any[]>;
171
353
  localization(id: string, kind?: string): Promise<Record<string, unknown>>;
172
354
  }
173
355
 
174
- export declare function loadConfig(path?: string): Promise<VydanneConfig & { resolvedLocales: { supported: Record<string, string>; unsupported: string[] } }>;
356
+ /** Play Developer Edits API client. Every mutation happens inside an Edit; an uncommitted Edit is a no-op. */
357
+ export declare class PlayClient {
358
+ static create(opts: { keyPath: string; packageName: string; dryRun?: boolean }): Promise<PlayClient>;
359
+ dryRun: boolean;
360
+ newEdit(): Promise<string>;
361
+ validate(editId: string): Promise<{ status: number; json: any }>;
362
+ commit(editId: string): Promise<{ status: number; json: any }>;
363
+ deleteEdit(editId: string): Promise<unknown>;
364
+ getListings(editId: string): Promise<{ status: number; json: any }>;
365
+ putListing(editId: string, lang: string, body: Record<string, unknown>): Promise<{ status: number; json: any }>;
366
+ listImages(editId: string, lang: string, type: string): Promise<{ status: number; json: any }>;
367
+ deleteAllImages(editId: string, lang: string, type: string): Promise<unknown>;
368
+ uploadImage(editId: string, lang: string, type: string, file: string): Promise<unknown>;
369
+ }
370
+
371
+ export type Store = "apple" | "google";
372
+
373
+ /**
374
+ * Run one command, the way the CLI runs it — minus argv parsing and process.exit.
375
+ *
376
+ * `apply` defaults to FALSE, the same safety gate `--apply` drives: a caller that forgets it gets a
377
+ * dry run, never a write. `planned` is the machine-readable form of the writes a dry run withheld.
378
+ */
379
+ export declare function runCommand(
380
+ name: string,
381
+ opts?: {
382
+ config?: VydanneConfig;
383
+ configPath?: string;
384
+ store?: Store;
385
+ apply?: boolean;
386
+ },
387
+ ): Promise<{ ok: boolean; planned: Array<{ method: string; path: string; attributes: Record<string, unknown> }> }>;
388
+
389
+ export declare function loadConfig(path?: string): Promise<ResolvedConfig>;
175
390
  export declare function makeToken(opts: { keyId: string; issuerId: string; keyPath?: string }): string;
176
- export declare function resolveLocales(uiCodes: string[]): { supported: Record<string, string>; unsupported: string[] };
177
- export declare function toAsc(code: string): string | null;
391
+ export declare function resolveLocales(uiCodes: string[], extra?: Record<string, string>): ResolvedLocales;
392
+ export declare function toAsc(code: string, extra?: Record<string, string>): string | null;
178
393
  export declare const VALID: Set<string>;
394
+ export declare const UI_TO_ASC: Record<string, string>;
179
395
  export declare const CONFIG_KEYS: readonly string[];
180
396
  export declare const COMMAND_NAMES: readonly CommandName[];
181
- /** `writes` marks a command that mutates the STORE — those are dry-run unless the CLI gets `--apply`. */
182
- export declare const COMMANDS: Record<string, { mod: string; client: boolean; writes?: boolean }>;
183
397
 
398
+ /** Screenshot filename prefix -> ASC display type, per platform. */
399
+ export declare const IOS_DEVICE: Record<string, string>;
400
+ export declare const MAC_DEVICE: Record<string, string>;
401
+ export declare const DEFAULT_SCREENSHOT_BASE: { IOS: string; MAC_OS: string };
402
+ /** Where this platform's screenshots live: `screenshots` in the config, else the supply convention. */
403
+ export declare function screenshotBase(platform: Platform, config?: VydanneConfig): string;
404
+
405
+ export declare const DEFAULT_PLAY_IMAGES: Partial<Record<PlayImageType, string>>;
406
+ export declare const PLAY_IMAGE_KIND: Record<PlayImageType, "file" | "dir">;
407
+ /** The resolved Play image table for one app: [type, localSource, kind][]. */
408
+ export declare function playImages(config: VydanneConfig): Array<[string, string, "file" | "dir"]>;
409
+
410
+ /** `writes` marks a command that mutates the STORE — those are dry-run unless the CLI gets `--apply`.
411
+ * `credentials` marks the one command (prerelease) whose altool child authenticates itself. */
412
+ export declare const COMMANDS: Record<string, { mod: string; client?: boolean; credentials?: boolean; writes?: boolean }>;
413
+ /** The same, for `--store google`. Same names, different backend. */
414
+ export declare const PLAY_COMMANDS: Record<string, { mod: string; writes?: boolean }>;
415
+
416
+ export interface ResolvedLocales {
417
+ supported: Record<string, string>;
418
+ /** Codes with no App Store language — they fall back to the primary listing. */
419
+ unsupported: string[];
420
+ /** `localeMap` entries pointing at a code Apple does not have. A config mistake, not a missing language. */
421
+ invalid: string[];
422
+ }
423
+
424
+ export type ResolvedConfig = VydanneConfig & { resolvedLocales: ResolvedLocales };
425
+
426
+ // NOTE: this is a TYPE-only default export — `import type cfg from "vydanne"`. There is no runtime
427
+ // default export, so `import cfg from "vydanne"` in JS gets undefined. Import the named values.
184
428
  export default VydanneConfig;
@@ -12,9 +12,23 @@ export default {
12
12
  // The app's UI locales. vydanne maps each to its ASC App Store code (de -> de-DE, ar -> ar-SA …) and
13
13
  // flags any with no App Store language (e.g. Belarusian `be`) so you never abort an upload on a bad name.
14
14
  uiLocales: ["en", "de", "es", "fr", "ja", "zh-Hans"],
15
+ // Only for codes the built-in table doesn't cover — e.g. if your resource folders use Android's
16
+ // spellings. Merged over the defaults, and an entry naming a code Apple doesn't have is reported.
17
+ // localeMap: { nb: "no", iw: "he" },
15
18
  metadataDir: "fastlane/metadata",
19
+ // Defaults to fastlane's supply convention; set it if your repo puts them somewhere else.
20
+ // screenshots: { IOS: "fastlane/screenshots", MAC_OS: "fastlane/screenshots-macos" },
16
21
 
17
22
  rating: "4+",
23
+ // Only needed when `rating` is above 4+. Describe the CONTENT — Apple computes the band from it.
24
+ // Merged over an all-NONE base, so declare just what applies. Enum keys take
25
+ // NONE | INFREQUENT_OR_MILD | FREQUENT_OR_INTENSE; the rest are booleans.
26
+ // ageRating: { violenceCartoonOrFantasy: "INFREQUENT_OR_MILD", userGeneratedContent: false },
27
+
28
+ // App Review contact. The PII stays in the gitignored fastlane/metadata/review_information/*.txt —
29
+ // including demo_user.txt / demo_password.txt, which is how vydanne decides whether a demo account is
30
+ // required. Set this only to disagree with what's on disk.
31
+ // reviewContact: { demoAccountRequired: false },
18
32
 
19
33
  // Honest, minimal privacy — "accesses" is not "collects"; E2EE content the developer can't read is not
20
34
  // collected. Declare only what actually leaves the device to you.
@@ -36,7 +50,38 @@ export default {
36
50
  { platform: "IOS", type: "IPHONE_67", file: "marketing/out/appstore-preview.mp4", poster: "00:00:05:00", locales: ["en-GB", "en-US"] },
37
51
  ],
38
52
 
39
- export: { encryption: "standard", france: true, appName: "Example", version: "1.0", teamId: "ABCDE12345" },
53
+ // Export compliance. `algorithms` and `statement` are REQUIRED when encryption is "standard" — this
54
+ // generates a US export-compliance PDF that makes factual claims, so it will not guess them for you.
55
+ // `filed` stays false until you have actually emailed the report to BIS and the NSA; while it is
56
+ // false the PDF does not claim it was submitted.
57
+ export: {
58
+ encryption: "standard",
59
+ france: true,
60
+ appName: "Example",
61
+ version: "1.0",
62
+ teamId: "ABCDE12345",
63
+ algorithms: [
64
+ ["Transport", "TLS 1.2 / 1.3", "standard"],
65
+ ],
66
+ statement:
67
+ "The product uses TLS for network transport only. It is a mass-market consumer application " +
68
+ "distributed through public app stores, uses only standard published algorithms, and qualifies " +
69
+ "for export under License Exception ENC, EAR 740.17(b)(1), ECCN 5D002.",
70
+ filed: false,
71
+ },
72
+
73
+ // `bridge` maps zdymak's output onto the folders `fill` reads. Defaults cover zdymak's own directory
74
+ // names; override only when a `dir:` in zdymak.config.mjs makes the folder name differ from the
75
+ // target name (e.g. Play's 7" slot, which can only exist as `{ target: 'play-tablet', dir: … }`).
76
+ // bridge: {
77
+ // out: "./store-assets",
78
+ // apple: { iphone69: "appstore-iphone-6.9" },
79
+ // play: { sevenInchScreenshots: "play-tablet7-plain" },
80
+ // },
81
+
82
+ // Pipeline steps this app never runs. `--skip` adds to it per invocation; either way every skip is
83
+ // reported at the end, so a green `push` never overstates what it checked.
84
+ // push: { skip: ["accessibility"] },
40
85
 
41
86
  // Google Play (optional). Add this block + set PLAY_JSON_KEY_FILE to the service-account JSON, then run
42
87
  // any command with `--store google`. Package-SCOPED: vydanne only ever touches THIS packageName — a
@@ -52,5 +97,13 @@ export default {
52
97
  packageName: "com.example.app",
53
98
  metadataDir: "fastlane/metadata/android",
54
99
  defaultLocale: "en-GB",
100
+ // 'internal' (default), 'alpha', 'beta', or the name of any closed track you made in Play Console.
101
+ // Only 'production' is refused.
102
+ // track: "internal",
103
+ // Play image type -> local source. Merged over the defaults, so override only what differs.
104
+ // images: { icon: "brand/icons/play/icon-512.png", phoneScreenshots: "marketing/out/play-phone-plain" },
105
+ // Play holds graphics PER LANGUAGE. Default is one set at `defaultLocale`; list locales (or "*")
106
+ // to localize them, and put per-language files in `<source>/<lang>/`.
107
+ // imageLocales: ["en-GB", "de-DE"],
55
108
  },
56
109
  };