@localizeaso/cli 0.1.0-preview.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 +24 -0
- package/package.json +35 -0
- package/packages/asc-shared/dist/app-store-review.d.ts +610 -0
- package/packages/asc-shared/dist/app-store-review.d.ts.map +1 -0
- package/packages/asc-shared/dist/app-store-review.js +242 -0
- package/packages/asc-shared/dist/aso-keyword-map.d.ts +94 -0
- package/packages/asc-shared/dist/aso-keyword-map.d.ts.map +1 -0
- package/packages/asc-shared/dist/aso-keyword-map.js +292 -0
- package/packages/asc-shared/dist/constants.d.ts +15 -0
- package/packages/asc-shared/dist/constants.d.ts.map +1 -0
- package/packages/asc-shared/dist/constants.js +130 -0
- package/packages/asc-shared/dist/cross-localization.d.ts +29 -0
- package/packages/asc-shared/dist/cross-localization.d.ts.map +1 -0
- package/packages/asc-shared/dist/cross-localization.js +189 -0
- package/packages/asc-shared/dist/dedupe.d.ts +17 -0
- package/packages/asc-shared/dist/dedupe.d.ts.map +1 -0
- package/packages/asc-shared/dist/dedupe.js +104 -0
- package/packages/asc-shared/dist/design-tokens.d.ts +83 -0
- package/packages/asc-shared/dist/design-tokens.d.ts.map +1 -0
- package/packages/asc-shared/dist/design-tokens.js +73 -0
- package/packages/asc-shared/dist/index.d.ts +16 -0
- package/packages/asc-shared/dist/index.d.ts.map +1 -0
- package/packages/asc-shared/dist/index.js +16 -0
- package/packages/asc-shared/dist/keywords.d.ts +48 -0
- package/packages/asc-shared/dist/keywords.d.ts.map +1 -0
- package/packages/asc-shared/dist/keywords.js +376 -0
- package/packages/asc-shared/dist/limits.d.ts +11 -0
- package/packages/asc-shared/dist/limits.d.ts.map +1 -0
- package/packages/asc-shared/dist/limits.js +9 -0
- package/packages/asc-shared/dist/locales.d.ts +10 -0
- package/packages/asc-shared/dist/locales.d.ts.map +1 -0
- package/packages/asc-shared/dist/locales.js +314 -0
- package/packages/asc-shared/dist/monetization-boundary.d.ts +148 -0
- package/packages/asc-shared/dist/monetization-boundary.d.ts.map +1 -0
- package/packages/asc-shared/dist/monetization-boundary.js +365 -0
- package/packages/asc-shared/dist/post-approval-paths.d.ts +30 -0
- package/packages/asc-shared/dist/post-approval-paths.d.ts.map +1 -0
- package/packages/asc-shared/dist/post-approval-paths.js +25 -0
- package/packages/asc-shared/dist/review-gate-summary.d.ts +166 -0
- package/packages/asc-shared/dist/review-gate-summary.d.ts.map +1 -0
- package/packages/asc-shared/dist/review-gate-summary.js +354 -0
- package/packages/asc-shared/dist/reviewer-feedback.d.ts +19 -0
- package/packages/asc-shared/dist/reviewer-feedback.d.ts.map +1 -0
- package/packages/asc-shared/dist/reviewer-feedback.js +94 -0
- package/packages/asc-shared/dist/screenshot-review.d.ts +478 -0
- package/packages/asc-shared/dist/screenshot-review.d.ts.map +1 -0
- package/packages/asc-shared/dist/screenshot-review.js +17 -0
- package/packages/asc-shared/dist/supabase.types.d.ts +541 -0
- package/packages/asc-shared/dist/supabase.types.d.ts.map +1 -0
- package/packages/asc-shared/dist/supabase.types.js +5 -0
- package/packages/asc-shared/dist/validation.d.ts +42 -0
- package/packages/asc-shared/dist/validation.d.ts.map +1 -0
- package/packages/asc-shared/dist/validation.js +113 -0
- package/scripts/ensure-shared-build.mjs +76 -0
- package/scripts/export-astro-mcp-apps.mjs +841 -0
- package/scripts/localizeaso.mjs +2100 -0
- package/scripts/review-agent.mjs +9092 -0
- package/scripts/review-mcp.mjs +5931 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { FIELD_LIMITS } from './limits.js';
|
|
2
|
+
import { findDuplicateKeywords } from './dedupe.js';
|
|
3
|
+
const SCREENSHOT_APPLY_PLAN_FINGERPRINT_RE = /^screenshot-apply-plan-v1:[a-f0-9]{16}:\d+$/;
|
|
4
|
+
const FIELD_APPLY_PLAN_FINGERPRINT_RE = /^field-apply-plan-v1:[a-f0-9]{16}:\d+$/;
|
|
5
|
+
const EXPECTED_APPLY_PLAN_FINGERPRINT_RE = /\s--expected-apply-plan-fingerprint(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/;
|
|
6
|
+
export function normalizeScreenshotApplyPlanFingerprint(value) {
|
|
7
|
+
const fingerprint = typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
return SCREENSHOT_APPLY_PLAN_FINGERPRINT_RE.test(fingerprint) ? fingerprint : '';
|
|
9
|
+
}
|
|
10
|
+
export function normalizeFieldApplyPlanFingerprint(value) {
|
|
11
|
+
const fingerprint = typeof value === 'string' ? value.trim() : '';
|
|
12
|
+
return FIELD_APPLY_PLAN_FINGERPRINT_RE.test(fingerprint) ? fingerprint : '';
|
|
13
|
+
}
|
|
14
|
+
export function isScreenshotApplyPlanFingerprint(value) {
|
|
15
|
+
return normalizeScreenshotApplyPlanFingerprint(value).length > 0;
|
|
16
|
+
}
|
|
17
|
+
export function isFieldApplyPlanFingerprint(value) {
|
|
18
|
+
return normalizeFieldApplyPlanFingerprint(value).length > 0;
|
|
19
|
+
}
|
|
20
|
+
export function expectedApplyPlanFingerprintFromCommand(value) {
|
|
21
|
+
const command = typeof value === 'string' ? value.trim() : '';
|
|
22
|
+
const match = command.match(EXPECTED_APPLY_PLAN_FINGERPRINT_RE);
|
|
23
|
+
return match ? (match[1] || match[2] || match[3] || '').trim() : '';
|
|
24
|
+
}
|
|
25
|
+
export function hasValidExpectedApplyPlanFingerprint(value) {
|
|
26
|
+
const fingerprint = expectedApplyPlanFingerprintFromCommand(value);
|
|
27
|
+
return isFieldApplyPlanFingerprint(fingerprint) || isScreenshotApplyPlanFingerprint(fingerprint);
|
|
28
|
+
}
|
|
29
|
+
const FIELD_POST_APPROVAL_MUTATION_COMMAND_RE = /\b(?:field-apply-drafts|field-apply-keywords|field-metadata-files|field-pricing-payload|field-submit-metadata|field-submit-pricing)\b/;
|
|
30
|
+
const STATUS_COMMAND_RE = /\b(?:status|field-status)\b/;
|
|
31
|
+
const FIELD_STATUS_COMMAND_RE = /\bfield-status\b/;
|
|
32
|
+
const APPLIED_OR_SUBMITTED_STATUS_RE = /\s--status(?:=|\s+)(?:applied|submitted)\b/;
|
|
33
|
+
export function hasExpectedApplyPlanFingerprintForCommandFamily(value) {
|
|
34
|
+
const command = typeof value === 'string' ? value.trim() : '';
|
|
35
|
+
if (!command)
|
|
36
|
+
return false;
|
|
37
|
+
const fingerprint = expectedApplyPlanFingerprintFromCommand(command);
|
|
38
|
+
if (FIELD_POST_APPROVAL_MUTATION_COMMAND_RE.test(command)) {
|
|
39
|
+
return isFieldApplyPlanFingerprint(fingerprint);
|
|
40
|
+
}
|
|
41
|
+
if (FIELD_STATUS_COMMAND_RE.test(command) && APPLIED_OR_SUBMITTED_STATUS_RE.test(command)) {
|
|
42
|
+
return isFieldApplyPlanFingerprint(fingerprint);
|
|
43
|
+
}
|
|
44
|
+
if (STATUS_COMMAND_RE.test(command) && APPLIED_OR_SUBMITTED_STATUS_RE.test(command)) {
|
|
45
|
+
return isScreenshotApplyPlanFingerprint(fingerprint);
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_REQUIRED_FIELDS = [
|
|
50
|
+
'title',
|
|
51
|
+
'subtitle',
|
|
52
|
+
'keywords',
|
|
53
|
+
'description',
|
|
54
|
+
];
|
|
55
|
+
export function validateLocaleFields(locale, fields, options) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
const requiredFields = options?.requiredFields ?? DEFAULT_REQUIRED_FIELDS;
|
|
58
|
+
for (const field of requiredFields) {
|
|
59
|
+
const value = fields[field];
|
|
60
|
+
if (!value || value.trim().length === 0) {
|
|
61
|
+
errors.push({
|
|
62
|
+
locale,
|
|
63
|
+
field,
|
|
64
|
+
code: 'required',
|
|
65
|
+
severity: 'error',
|
|
66
|
+
message: `${field} is required`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [field, limit] of Object.entries(FIELD_LIMITS)) {
|
|
71
|
+
const typedField = field;
|
|
72
|
+
const value = fields[typedField];
|
|
73
|
+
if (!value)
|
|
74
|
+
continue;
|
|
75
|
+
const length = value.length;
|
|
76
|
+
if (length > limit) {
|
|
77
|
+
errors.push({
|
|
78
|
+
locale,
|
|
79
|
+
field: typedField,
|
|
80
|
+
code: 'limit',
|
|
81
|
+
severity: 'error',
|
|
82
|
+
message: `${field} exceeds ${limit} characters`,
|
|
83
|
+
limit,
|
|
84
|
+
length,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const duplicates = findDuplicateKeywords({
|
|
89
|
+
title: fields.title,
|
|
90
|
+
subtitle: fields.subtitle,
|
|
91
|
+
keywords: fields.keywords,
|
|
92
|
+
});
|
|
93
|
+
if (duplicates.length) {
|
|
94
|
+
errors.push({
|
|
95
|
+
locale,
|
|
96
|
+
field: 'keywords',
|
|
97
|
+
code: 'duplicate',
|
|
98
|
+
severity: 'warning',
|
|
99
|
+
message: 'Duplicate keywords across title/subtitle/keywords',
|
|
100
|
+
duplicates: duplicates.map((entry) => entry.keyword),
|
|
101
|
+
duplicateDetails: duplicates,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return { ok: errors.filter((e) => e.severity === 'error').length === 0, errors };
|
|
105
|
+
}
|
|
106
|
+
export function validateAllLocales(locales, options) {
|
|
107
|
+
const errors = [];
|
|
108
|
+
for (const [locale, fields] of Object.entries(locales)) {
|
|
109
|
+
const result = validateLocaleFields(locale, fields, options);
|
|
110
|
+
errors.push(...result.errors);
|
|
111
|
+
}
|
|
112
|
+
return { ok: errors.filter((error) => error.severity === 'error').length === 0, errors };
|
|
113
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const repoRoot = join(__dirname, '..');
|
|
10
|
+
const sharedRoot = join(repoRoot, 'packages', 'asc-shared');
|
|
11
|
+
|
|
12
|
+
async function newestMtimeMs(dir) {
|
|
13
|
+
let newest = 0;
|
|
14
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const filePath = join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
newest = Math.max(newest, await newestMtimeMs(filePath));
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const fileStat = await stat(filePath);
|
|
22
|
+
newest = Math.max(newest, fileStat.mtimeMs);
|
|
23
|
+
}
|
|
24
|
+
return newest;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function sharedBuildIsFresh() {
|
|
28
|
+
try {
|
|
29
|
+
const [jsStat, typesStat] = await Promise.all([
|
|
30
|
+
stat(join(sharedRoot, 'dist', 'index.js')),
|
|
31
|
+
stat(join(sharedRoot, 'dist', 'index.d.ts')),
|
|
32
|
+
]);
|
|
33
|
+
let sourceMtime = 0;
|
|
34
|
+
try {
|
|
35
|
+
sourceMtime = await newestMtimeMs(join(sharedRoot, 'src'));
|
|
36
|
+
} catch {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return jsStat.mtimeMs >= sourceMtime && typesStat.mtimeMs >= sourceMtime;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function ensureSharedBuild() {
|
|
46
|
+
if (await sharedBuildIsFresh()) return;
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const child = spawn('pnpm', ['--filter', '@aso/asc-shared', 'build'], {
|
|
49
|
+
cwd: repoRoot,
|
|
50
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let stderr = '';
|
|
54
|
+
child.stderr.on('data', (chunk) => {
|
|
55
|
+
stderr += chunk;
|
|
56
|
+
});
|
|
57
|
+
child.on('error', reject);
|
|
58
|
+
child.on('close', (code) => {
|
|
59
|
+
if (code === 0) {
|
|
60
|
+
resolve();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
reject(new Error(stderr.trim() || `@aso/asc-shared build failed with exit code ${code}.`));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
69
|
+
try {
|
|
70
|
+
await ensureSharedBuild();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
console.error(message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|