gen-flags: Args.required reads are flags too (edit --ops, privacy-manifest --ios, apple listing --version were refused as unknown options); play games client listAll (no false flags)

This commit is contained in:
CRTkafa 2026-10-21 05:28:02 +07:00
parent d427b5c4a4
commit 567d6ca181
3 changed files with 15 additions and 19 deletions

View file

@ -50,6 +50,7 @@
"upload" "upload"
], ],
"apple adhoc": [ "apple adhoc": [
"archive",
"csv", "csv",
"method", "method",
"name", "name",
@ -178,6 +179,7 @@
"version" "version"
], ],
"apple resubmit": [ "apple resubmit": [
"build",
"new-version", "new-version",
"no-submit", "no-submit",
"platform", "platform",
@ -212,6 +214,7 @@
"wait-review" "wait-review"
], ],
"apple submit": [ "apple submit": [
"build",
"new-version", "new-version",
"no-submit", "no-submit",
"platform", "platform",
@ -359,7 +362,6 @@
"write" "write"
], ],
"check": [ "check": [
"achievements",
"aso", "aso",
"capacitor", "capacitor",
"cordova", "cordova",
@ -369,7 +371,6 @@
"flutter", "flutter",
"godot", "godot",
"keychain", "keychain",
"leaderboards",
"name", "name",
"no-install", "no-install",
"react-native", "react-native",
@ -453,6 +454,7 @@
"dir" "dir"
], ],
"edit": [ "edit": [
"ops",
"show" "show"
], ],
"flow": [ "flow": [
@ -461,9 +463,7 @@
"resume" "resume"
], ],
"games status": [ "games status": [
"achievements",
"keychain", "keychain",
"leaderboards",
"no-install" "no-install"
], ],
"health": [ "health": [
@ -598,10 +598,7 @@
"csv", "csv",
"force" "force"
], ],
"play games": [ "play games": [],
"achievements",
"leaderboards"
],
"play images": [ "play images": [
"include-drafts", "include-drafts",
"not-sent-for-review", "not-sent-for-review",
@ -703,6 +700,7 @@
"with-automatic" "with-automatic"
], ],
"privacy-manifest": [ "privacy-manifest": [
"ios",
"propose", "propose",
"sdk-dirs", "sdk-dirs",
"tracking-domains" "tracking-domains"
@ -867,7 +865,6 @@
"play" "play"
], ],
"validate": [ "validate": [
"achievements",
"add-for-review", "add-for-review",
"allow-price-changes", "allow-price-changes",
"apple", "apple",
@ -884,7 +881,6 @@
"jobs", "jobs",
"keychain", "keychain",
"lang", "lang",
"leaderboards",
"no-health", "no-health",
"no-install", "no-install",
"no-local", "no-local",
@ -926,7 +922,6 @@
"ws": [ "ws": [
"aab", "aab",
"accounts", "accounts",
"achievements",
"add-for-review", "add-for-review",
"adgroup", "adgroup",
"against", "against",
@ -943,6 +938,7 @@
"apple", "apple",
"apply", "apply",
"apps", "apps",
"archive",
"aso", "aso",
"base", "base",
"base-url", "base-url",
@ -1028,7 +1024,6 @@
"landing", "landing",
"lang", "lang",
"languages", "languages",
"leaderboards",
"limit", "limit",
"listing-text", "listing-text",
"listings", "listings",
@ -1075,6 +1070,7 @@
"only-keywords", "only-keywords",
"only-missing", "only-missing",
"only-terms", "only-terms",
"ops",
"originality", "originality",
"out", "out",
"param", "param",

View file

@ -41,7 +41,7 @@ export function gamesConfigClient(root: string) {
return (t ? JSON.parse(t) : {}) as T; return (t ? JSON.parse(t) : {}) as T;
} }
} }
async function list(kind: 'achievements' | 'leaderboards', appId: string): Promise<PlayExisting[]> { async function listAll(kind: 'achievements' | 'leaderboards', appId: string): Promise<PlayExisting[]> {
const out: PlayExisting[] = []; const out: PlayExisting[] = [];
let page: string | undefined; let page: string | undefined;
do { do {
@ -51,13 +51,13 @@ export function gamesConfigClient(root: string) {
} while (page); } while (page);
return out; return out;
} }
return { call, list }; return { call, listAll };
} }
export const playDefaultLocale = (config: Ctx['loaded']['config']): string => config.play?.defaultLanguage ?? 'en-US'; export const playDefaultLocale = (config: Ctx['loaded']['config']): string => config.play?.defaultLanguage ?? 'en-US';
export async function readPlayGames(c: ReturnType<typeof gamesConfigClient>, appId: string): Promise<{ leaderboards: PlayExisting[]; achievements: PlayExisting[] }> { export async function readPlayGames(c: ReturnType<typeof gamesConfigClient>, appId: string): Promise<{ leaderboards: PlayExisting[]; achievements: PlayExisting[] }> {
return { leaderboards: await c.list('leaderboards', appId), achievements: await c.list('achievements', appId) }; return { leaderboards: await c.listAll('leaderboards', appId), achievements: await c.listAll('achievements', appId) };
} }
const pending = (plan: ItemPlan[]) => plan.filter(p => p.action !== 'keep'); const pending = (plan: ItemPlan[]) => plan.filter(p => p.action !== 'keep');
@ -92,7 +92,7 @@ export async function run(ctx: Ctx): Promise<void> {
if (p.action === 'create') { if (p.action === 'create') {
const r = await createOnce({ what: `${p.kind} ${p.id}`, tag: TAG, const r = await createOnce({ what: `${p.kind} ${p.id}`, tag: TAG,
create: async () => (await c.call<PlayExisting>('POST', `/applications/${appId}/${kind}`, p.attributes)).id, create: async () => (await c.call<PlayExisting>('POST', `/applications/${appId}/${kind}`, p.attributes)).id,
find: async () => (await c.list(kind, appId)).find(x => x.draft?.name?.translations.some(t => t.locale === locale && t.value === name))?.id ?? null }); find: async () => (await c.listAll(kind, appId)).find(x => x.draft?.name?.translations.some(t => t.locale === locale && t.value === name))?.id ?? null });
log(TAG, `${p.kind} ${p.id}: created as ${r.value} (set playId: "${r.value}" in the config to match it by id)`); log(TAG, `${p.kind} ${p.id}: created as ${r.value} (set playId: "${r.value}" in the config to match it by id)`);
} else { } else {
await c.call('PUT', `/${kind}/${encodeURIComponent(p.storeId!)}`, p.attributes); await c.call('PUT', `/${kind}/${encodeURIComponent(p.storeId!)}`, p.attributes);

View file

@ -2,7 +2,7 @@
// Writes cli/data/flags.json: the --flags every command accepts, so the dispatcher (cli/index.ts) can reject an // Writes cli/data/flags.json: the --flags every command accepts, so the dispatcher (cli/index.ts) can reject an
// unknown flag (a typo like --dyr) with the usage before anything runs, also in the compiled sidecar (no sources). // unknown flag (a typo like --dyr) with the usage before anything runs, also in the compiled sidecar (no sources).
// For each command in cli/commands.ts it follows the command module and every local module it imports (static and // For each command in cli/commands.ts it follows the command module and every local module it imports (static and
// dynamic), and collects the literal flag reads: <x>.has('name'), .get, .str, .num, .list, .many. Reads with a computed // dynamic), and collects the literal flag reads: <x>.has('name'), .get, .str, .num, .list, .many, .required. Reads with a computed
// name are listed in DYNAMIC below (the scan fails on a new one until it is added there). `ws` forwards its flags to the // name are listed in DYNAMIC below (the scan fails on a new one until it is added there). `ws` forwards its flags to the
// command it runs, so it accepts every flag. GLOBAL flags work on every command. The usage lines of each module's // command it runs, so it accepts every flag. GLOBAL flags work on every command. The usage lines of each module's
// header comment (`// storebound <command> ...`) go along, for `storebound <command> --help`. // header comment (`// storebound <command> ...`) go along, for `storebound <command> --help`.
@ -31,9 +31,9 @@ const DYNAMIC: Record<string, string[]> = {
const FORWARDS = new Set(['ws']); const FORWARDS = new Set(['ws']);
// the receiver is captured so HTTP headers and URL search params (res.headers.get('content-type')) are not flags // the receiver is captured so HTTP headers and URL search params (res.headers.get('content-type')) are not flags
const READ = /([\w$.)\]]*)\.(?:has|get|str|num|list|many)\(\s*(['"])([a-z][a-z0-9-]*)\2/g; const READ = /([\w$.)\]]*)\.(?:has|get|str|num|list|many|required)\(\s*(['"])([a-z][a-z0-9-]*)\2/g;
const NOT_ARGS = /(?:headers|searchParams|params|url|env)$/i; const NOT_ARGS = /(?:headers|searchParams|params|url|env)$/i;
const COMPUTED = /\b(?:args|a|ctx\.args)\.(?:has|get|str|num|list|many)\(\s*(?![\s'"])/g; const COMPUTED = /\b(?:args|a|ctx\.args)\.(?:has|get|str|num|list|many|required)\(\s*(?![\s'"])/g;
const IMPORT = /(?:\bfrom\s+|\bimport\s*\(\s*)'(\.{1,2}\/[^']+)'/g; const IMPORT = /(?:\bfrom\s+|\bimport\s*\(\s*)'(\.{1,2}\/[^']+)'/g;
function file(spec: string, from: string): string | null { function file(spec: string, from: string): string | null {