vydanne 0.8.0 → 0.9.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/README.md +1 -1
- package/bin/vydanne.mjs +4 -0
- package/package.json +1 -1
- package/src/commands/preflight.mjs +38 -0
package/README.md
CHANGED
|
@@ -224,7 +224,7 @@ Two details worth knowing:
|
|
|
224
224
|
|
|
225
225
|
| Command | In plain English |
|
|
226
226
|
|---|---|
|
|
227
|
-
| `preflight` | **Run this first.** Checks the listing is complete, nothing is over a character limit, no locale mentions the other app store — and that the screenshots on the store are your *current* ones, not a stale set. Green means submittable. |
|
|
227
|
+
| `preflight` | **Run this first.** Checks the listing is complete, nothing is over a character limit, no locale mentions the other app store — and that the screenshots on the store are your *current* ones, not a stale set. It also asserts the fields no local file drives and that no diff can therefore see: copyright, the App Review contact, the age rating, the primary category and the content-rights declaration. Those are each set by their own command, so a listing that never ran one had them EMPTY while this printed green. Green means submittable. |
|
|
228
228
|
| `prepare` | Starts the next release: creates the App Store version you're preparing and attaches your newest build to it. **Needed before `fill` on an app that already has a version on sale** — until a draft exists there is nothing for the listing text to go into. Reuses the draft if it's already there, so it's safe to re-run. It does *not* submit. |
|
|
229
229
|
| `push` | **The whole release, one command**: runs `prepare` → `fill` → `previews` → `age-rating` → `review-contact` → `accessibility` → `preflight`, in that order, stopping at the first failure. Dry run without `--apply`, like everything else. Ends at a green preflight — it never submits. |
|
|
230
230
|
| `diff` | Shows exactly what's different between your files and what's live. Nothing is changed — a safe preview. |
|
package/bin/vydanne.mjs
CHANGED
|
@@ -128,6 +128,10 @@ usage: vydanne <command> [--apply] [--config vydanne.config.mjs]
|
|
|
128
128
|
skipped); every skip is reported again at the end, so green still means green.
|
|
129
129
|
Ends at a green preflight — Add to Review + Submit stay yours, in the web UI.
|
|
130
130
|
✎ fill metadata + screenshots + previews (native; iOS & macOS separate)
|
|
131
|
+
✎ appinfo App Store category + the content-rights answer — the two app-level facts that
|
|
132
|
+
block Add for Review and belong to no single release. 'categories' takes Apple's
|
|
133
|
+
ids (GAMES, GAMES_PUZZLE — not display names); 'contentRights: false' for an app
|
|
134
|
+
showing no third-party content. Set once; re-running is a no-op.
|
|
131
135
|
✎ age-rating set the age rating (AppInfo declaration)
|
|
132
136
|
✎ review-contact App Review contact from the gitignored files
|
|
133
137
|
✎ accessibility Accessibility Nutrition Labels (draft; VYDANNE_A11Y_PUBLISH=1 to publish once live)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vydanne",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "App Store Connect + Google Play submission prep — the companion to zdymak (media). Native Node (no fastlane/Ruby/Python): localized listings, screenshot/preview/icon upload, ratings, review contact, accessibility & privacy labels, IAP, export docs, build upload to TestFlight / a Play closed track, a diff of local-vs-store, and a preflight verifier that encodes the store gotchas. Never submits for review. iOS/macOS via the ASC REST API; Android via the Play Developer Edits API (--store google).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app-store-connect",
|
|
@@ -16,6 +16,27 @@ export async function run(config, client) {
|
|
|
16
16
|
// the README listed them among the limits "checked before upload". Read-only, so `allowLive` is
|
|
17
17
|
// right: on an app with nothing in preparation the live record is the only one to measure.
|
|
18
18
|
const info = await client.appInfo({ allowLive: true });
|
|
19
|
+
|
|
20
|
+
// ── The fields no LOCAL file drives ─────────────────────────────────────────────────────────────
|
|
21
|
+
//
|
|
22
|
+
// Everything else here compares local metadata against the store, which is the right question for
|
|
23
|
+
// anything `fill` uploads and the wrong one for the half-dozen fields set by their own standalone
|
|
24
|
+
// command. Those are invisible to a local-vs-remote diff: there is nothing local to differ from, so
|
|
25
|
+
// a field that was NEVER SET reads exactly like one that matches.
|
|
26
|
+
//
|
|
27
|
+
// Reported from the field: a listing reached PREPARE_FOR_SUBMISSION with an empty copyright and no
|
|
28
|
+
// App Review contact at all, while this command printed "no blockers". `prepare` sets the first and
|
|
29
|
+
// `review-contact` the second; neither is part of `fill`, and neither had ever been run. Preflight
|
|
30
|
+
// was not wrong so much as asked the wrong question — so it now also asks whether the listing is
|
|
31
|
+
// SUBMITTABLE, which only the store can answer.
|
|
32
|
+
const rights = client.app?.attributes?.contentRightsDeclaration;
|
|
33
|
+
if (!rights) problems.push("content rights not declared — `vydanne appinfo --apply`");
|
|
34
|
+
if (info) {
|
|
35
|
+
if (!info.attributes.appStoreAgeRating) problems.push("age rating not declared — `vydanne age-rating --apply`");
|
|
36
|
+
const primary = (await client.get(`/v1/appInfos/${info.id}/primaryCategory`)).json?.data?.id;
|
|
37
|
+
if (!primary) problems.push("primary category not set — `vydanne appinfo --apply`");
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
if (info) {
|
|
20
41
|
const infoLocs = (await client.get(`/v1/appInfos/${info.id}/appInfoLocalizations?limit=200`)).json.data || [];
|
|
21
42
|
for (const il of infoLocs) {
|
|
@@ -37,6 +58,23 @@ export async function run(config, client) {
|
|
|
37
58
|
continue;
|
|
38
59
|
}
|
|
39
60
|
console.log(` ${platform}: version ${v.attributes.versionString} (${v.attributes.appStoreState})`);
|
|
61
|
+
|
|
62
|
+
// Required, and set by `prepare` from <metadataDir>/copyright.txt — not by `fill`.
|
|
63
|
+
if (!v.attributes.copyright) {
|
|
64
|
+
problems.push(`${platform}: copyright EMPTY — set ${config.metadataDir}/copyright.txt, then \`vydanne prepare --apply\``);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The App Review contact. Apple will not take a submission without one, and it is per-VERSION, so
|
|
68
|
+
// a contact set on a previous version does not carry forward on its own.
|
|
69
|
+
const detail = (await client.get(`/v1/appStoreVersions/${v.id}/appStoreReviewDetail`)).json?.data?.attributes;
|
|
70
|
+
const CONTACT = ["contactFirstName", "contactLastName", "contactPhone", "contactEmail"];
|
|
71
|
+
const missing = detail ? CONTACT.filter((f) => !detail[f]) : CONTACT;
|
|
72
|
+
if (missing.length) {
|
|
73
|
+
problems.push(`${platform}: App Review contact ${detail ? `missing ${missing.join(", ")}` : "not set"} — \`vydanne review-contact --apply\``);
|
|
74
|
+
} else if (detail.demoAccountRequired && !detail.demoAccountName) {
|
|
75
|
+
// The rejection `review-contact` exists to avoid, arriving by the door of nobody running it.
|
|
76
|
+
problems.push(`${platform}: demoAccountRequired is true but no demo account is set — Apple rejects this`);
|
|
77
|
+
}
|
|
40
78
|
const locs = await client.versionLocalizations(v.id);
|
|
41
79
|
const ascLocales = [...new Set([config.primaryLocale, ...Object.values(res.supported)])];
|
|
42
80
|
|