@sitepong/fastnode 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 (162) hide show
  1. package/README.md +81 -0
  2. package/bin/fastnode.js +13 -0
  3. package/bin/fastnode.ts +5 -0
  4. package/dist/actions/index.cjs +7405 -0
  5. package/dist/actions/index.cjs.map +1 -0
  6. package/dist/actions/index.d.cts +15 -0
  7. package/dist/actions/index.d.ts +15 -0
  8. package/dist/actions/index.mjs +7377 -0
  9. package/dist/actions/index.mjs.map +1 -0
  10. package/dist/cert/index.cjs +219 -0
  11. package/dist/cert/index.cjs.map +1 -0
  12. package/dist/cert/index.d.cts +4 -0
  13. package/dist/cert/index.d.ts +4 -0
  14. package/dist/cert/index.mjs +191 -0
  15. package/dist/cert/index.mjs.map +1 -0
  16. package/dist/cert-BmJGtYtb.d.cts +27 -0
  17. package/dist/cert-owkOFNW8.d.ts +27 -0
  18. package/dist/cli/index.cjs +7998 -0
  19. package/dist/cli/index.cjs.map +1 -0
  20. package/dist/cli/index.d.cts +9 -0
  21. package/dist/cli/index.d.ts +9 -0
  22. package/dist/cli/index.mjs +7972 -0
  23. package/dist/cli/index.mjs.map +1 -0
  24. package/dist/client/index.cjs +2592 -0
  25. package/dist/client/index.cjs.map +1 -0
  26. package/dist/client/index.d.cts +2 -0
  27. package/dist/client/index.d.ts +2 -0
  28. package/dist/client/index.mjs +2582 -0
  29. package/dist/client/index.mjs.map +1 -0
  30. package/dist/context-CymLkfrS.d.cts +13 -0
  31. package/dist/context-CymLkfrS.d.ts +13 -0
  32. package/dist/core/index.cjs +281 -0
  33. package/dist/core/index.cjs.map +1 -0
  34. package/dist/core/index.d.cts +1 -0
  35. package/dist/core/index.d.ts +1 -0
  36. package/dist/core/index.mjs +254 -0
  37. package/dist/core/index.mjs.map +1 -0
  38. package/dist/deliver/index.cjs +328 -0
  39. package/dist/deliver/index.cjs.map +1 -0
  40. package/dist/deliver/index.d.cts +3 -0
  41. package/dist/deliver/index.d.ts +3 -0
  42. package/dist/deliver/index.mjs +322 -0
  43. package/dist/deliver/index.mjs.map +1 -0
  44. package/dist/enums-B609j2rb.d.cts +20 -0
  45. package/dist/enums-B609j2rb.d.ts +20 -0
  46. package/dist/frameit/index.cjs +247 -0
  47. package/dist/frameit/index.cjs.map +1 -0
  48. package/dist/frameit/index.d.cts +1 -0
  49. package/dist/frameit/index.d.ts +1 -0
  50. package/dist/frameit/index.mjs +242 -0
  51. package/dist/frameit/index.mjs.map +1 -0
  52. package/dist/gym/index.cjs +356 -0
  53. package/dist/gym/index.cjs.map +1 -0
  54. package/dist/gym/index.d.cts +2 -0
  55. package/dist/gym/index.d.ts +2 -0
  56. package/dist/gym/index.mjs +346 -0
  57. package/dist/gym/index.mjs.map +1 -0
  58. package/dist/index-1Vu2cRQ2.d.ts +99 -0
  59. package/dist/index-A5MimeQk.d.ts +72 -0
  60. package/dist/index-B0yhu_LZ.d.ts +76 -0
  61. package/dist/index-B9yXicrQ.d.ts +62 -0
  62. package/dist/index-BFdNOmPJ.d.cts +53 -0
  63. package/dist/index-BLubSlgK.d.cts +1513 -0
  64. package/dist/index-BN0ubxjZ.d.ts +70 -0
  65. package/dist/index-BQJIJlK7.d.cts +70 -0
  66. package/dist/index-BYonklf3.d.cts +84 -0
  67. package/dist/index-BYonklf3.d.ts +84 -0
  68. package/dist/index-BimqCmY8.d.cts +78 -0
  69. package/dist/index-BimqCmY8.d.ts +78 -0
  70. package/dist/index-BoU8qt8r.d.cts +59 -0
  71. package/dist/index-BoU8qt8r.d.ts +59 -0
  72. package/dist/index-C2yG3wXA.d.cts +95 -0
  73. package/dist/index-C2yG3wXA.d.ts +95 -0
  74. package/dist/index-CR3BFPIz.d.cts +77 -0
  75. package/dist/index-CR3BFPIz.d.ts +77 -0
  76. package/dist/index-CWNLnSOL.d.cts +99 -0
  77. package/dist/index-Cp6cjtd-.d.cts +65 -0
  78. package/dist/index-Cp6cjtd-.d.ts +65 -0
  79. package/dist/index-D1Izm7qt.d.ts +1513 -0
  80. package/dist/index-D5eihzwM.d.cts +76 -0
  81. package/dist/index-DImN9PWc.d.ts +155 -0
  82. package/dist/index-DfCUVJX1.d.cts +62 -0
  83. package/dist/index-DfirmFbC.d.ts +32 -0
  84. package/dist/index-DkvinY7_.d.ts +54 -0
  85. package/dist/index-DrUpRXRG.d.cts +155 -0
  86. package/dist/index-EcVvViW8.d.cts +72 -0
  87. package/dist/index-HRiaaX-Q.d.cts +54 -0
  88. package/dist/index-TfXqwuac.d.ts +53 -0
  89. package/dist/index-lt819Jgj.d.cts +32 -0
  90. package/dist/index.cjs +8464 -0
  91. package/dist/index.cjs.map +1 -0
  92. package/dist/index.d.cts +23 -0
  93. package/dist/index.d.ts +23 -0
  94. package/dist/index.mjs +8410 -0
  95. package/dist/index.mjs.map +1 -0
  96. package/dist/lane/index.cjs +7641 -0
  97. package/dist/lane/index.cjs.map +1 -0
  98. package/dist/lane/index.d.cts +51 -0
  99. package/dist/lane/index.d.ts +51 -0
  100. package/dist/lane/index.mjs +7603 -0
  101. package/dist/lane/index.mjs.map +1 -0
  102. package/dist/match/index.cjs +942 -0
  103. package/dist/match/index.cjs.map +1 -0
  104. package/dist/match/index.d.cts +5 -0
  105. package/dist/match/index.d.ts +5 -0
  106. package/dist/match/index.mjs +913 -0
  107. package/dist/match/index.mjs.map +1 -0
  108. package/dist/pem/index.cjs +195 -0
  109. package/dist/pem/index.cjs.map +1 -0
  110. package/dist/pem/index.d.cts +3 -0
  111. package/dist/pem/index.d.ts +3 -0
  112. package/dist/pem/index.mjs +170 -0
  113. package/dist/pem/index.mjs.map +1 -0
  114. package/dist/pilot/index.cjs +206 -0
  115. package/dist/pilot/index.cjs.map +1 -0
  116. package/dist/pilot/index.d.cts +3 -0
  117. package/dist/pilot/index.d.ts +3 -0
  118. package/dist/pilot/index.mjs +196 -0
  119. package/dist/pilot/index.mjs.map +1 -0
  120. package/dist/precheck/index.cjs +277 -0
  121. package/dist/precheck/index.cjs.map +1 -0
  122. package/dist/precheck/index.d.cts +3 -0
  123. package/dist/precheck/index.d.ts +3 -0
  124. package/dist/precheck/index.mjs +274 -0
  125. package/dist/precheck/index.mjs.map +1 -0
  126. package/dist/produce/index.cjs +200 -0
  127. package/dist/produce/index.cjs.map +1 -0
  128. package/dist/produce/index.d.cts +3 -0
  129. package/dist/produce/index.d.ts +3 -0
  130. package/dist/produce/index.mjs +191 -0
  131. package/dist/produce/index.mjs.map +1 -0
  132. package/dist/scan/index.cjs +187 -0
  133. package/dist/scan/index.cjs.map +1 -0
  134. package/dist/scan/index.d.cts +1 -0
  135. package/dist/scan/index.d.ts +1 -0
  136. package/dist/scan/index.mjs +183 -0
  137. package/dist/scan/index.mjs.map +1 -0
  138. package/dist/sigh/index.cjs +203 -0
  139. package/dist/sigh/index.cjs.map +1 -0
  140. package/dist/sigh/index.d.cts +3 -0
  141. package/dist/sigh/index.d.ts +3 -0
  142. package/dist/sigh/index.mjs +198 -0
  143. package/dist/sigh/index.mjs.map +1 -0
  144. package/dist/snapshot/index.cjs +448 -0
  145. package/dist/snapshot/index.cjs.map +1 -0
  146. package/dist/snapshot/index.d.cts +1 -0
  147. package/dist/snapshot/index.d.ts +1 -0
  148. package/dist/snapshot/index.mjs +438 -0
  149. package/dist/snapshot/index.mjs.map +1 -0
  150. package/dist/trainer/index.cjs +395 -0
  151. package/dist/trainer/index.cjs.map +1 -0
  152. package/dist/trainer/index.d.cts +1 -0
  153. package/dist/trainer/index.d.ts +1 -0
  154. package/dist/trainer/index.mjs +372 -0
  155. package/dist/trainer/index.mjs.map +1 -0
  156. package/dist/xcode/index.cjs +188 -0
  157. package/dist/xcode/index.cjs.map +1 -0
  158. package/dist/xcode/index.d.cts +1 -0
  159. package/dist/xcode/index.d.ts +1 -0
  160. package/dist/xcode/index.mjs +176 -0
  161. package/dist/xcode/index.mjs.map +1 -0
  162. package/package.json +223 -0
@@ -0,0 +1,277 @@
1
+ 'use strict';
2
+
3
+ // src/precheck/rules.ts
4
+ function defineRule(metadata, fn) {
5
+ const rule = fn;
6
+ rule.metadata = metadata;
7
+ return rule;
8
+ }
9
+ function createWordMatchRule(metadata, words) {
10
+ const lower = words.map((w) => w.toLowerCase());
11
+ return defineRule(metadata, (text) => {
12
+ const textLower = text.toLowerCase();
13
+ for (const word of lower) {
14
+ if (textLower.includes(word)) {
15
+ return {
16
+ passed: false,
17
+ message: `${metadata.friendlyName}: found "${word}"`
18
+ };
19
+ }
20
+ }
21
+ return { passed: true, message: "" };
22
+ });
23
+ }
24
+ var negativeAppleSentiment = createWordMatchRule(
25
+ {
26
+ key: "negative_apple_sentiment",
27
+ envName: "RULE_NEGATIVE_APPLE_SENTIMENT",
28
+ friendlyName: "No negative sentiment about Apple",
29
+ description: "using text that could be negative sentiment about Apple",
30
+ supportedFields: null
31
+ },
32
+ ["suck", "bug", "broken", "crash", "hate", "terrible", "horrible", "worst", "awful"]
33
+ );
34
+ var placeholderText = createWordMatchRule(
35
+ {
36
+ key: "placeholder_words",
37
+ envName: "RULE_PLACEHOLDER_WORDS",
38
+ friendlyName: "No placeholder words",
39
+ description: "using placeholder text",
40
+ supportedFields: null
41
+ },
42
+ [
43
+ "lorem ipsum",
44
+ "placeholder",
45
+ "todo",
46
+ "tbd",
47
+ "coming soon",
48
+ "insert",
49
+ "your text here"
50
+ ]
51
+ );
52
+ var otherPlatforms = createWordMatchRule(
53
+ {
54
+ key: "other_platforms",
55
+ envName: "RULE_OTHER_PLATFORMS",
56
+ friendlyName: "No references to other platforms",
57
+ description: "mentioning other platforms",
58
+ supportedFields: null
59
+ },
60
+ ["android", "google play", "samsung", "windows phone", "blackberry", "kindle"]
61
+ );
62
+ var futureFunctionality = createWordMatchRule(
63
+ {
64
+ key: "future_functionality",
65
+ envName: "RULE_FUTURE_FUNCTIONALITY",
66
+ friendlyName: "No references to future functionality",
67
+ description: "referencing future functionality",
68
+ supportedFields: null
69
+ },
70
+ [
71
+ "will be available",
72
+ "coming soon",
73
+ "in a future update",
74
+ "planned for",
75
+ "we plan to",
76
+ "will include",
77
+ "will support"
78
+ ]
79
+ );
80
+ var testWords = createWordMatchRule(
81
+ {
82
+ key: "test_words",
83
+ envName: "RULE_TEST_WORDS",
84
+ friendlyName: "No references to test or demo",
85
+ description: "using test or demo words",
86
+ supportedFields: null
87
+ },
88
+ ["test", "demo", "beta version", "debug"]
89
+ );
90
+ var curseWords = defineRule(
91
+ {
92
+ key: "curse_words",
93
+ envName: "RULE_CURSE_WORDS",
94
+ friendlyName: "No curse words",
95
+ description: "using curse words",
96
+ supportedFields: null
97
+ },
98
+ (text) => {
99
+ const cursePatterns = [
100
+ /\bf+u+c+k+/i,
101
+ /\bs+h+i+t+/i,
102
+ /\ba+s+s+h+o+l+e+/i,
103
+ /\bb+i+t+c+h+/i,
104
+ /\bd+a+m+n+/i,
105
+ /\bh+e+l+l+\b/i,
106
+ /\bc+r+a+p+/i,
107
+ /\bd+i+c+k+/i,
108
+ /\bp+i+s+s+/i
109
+ ];
110
+ for (const pattern of cursePatterns) {
111
+ if (pattern.test(text)) {
112
+ return {
113
+ passed: false,
114
+ message: "Curse words: inappropriate language detected"
115
+ };
116
+ }
117
+ }
118
+ return { passed: true, message: "" };
119
+ }
120
+ );
121
+ var unreachableUrls = defineRule(
122
+ {
123
+ key: "unreachable_urls",
124
+ envName: "RULE_UNREACHABLE_URLS",
125
+ friendlyName: "No unreachable URLs",
126
+ description: "using URLs that cannot be reached",
127
+ supportedFields: ["supportUrl", "marketingUrl", "privacyUrl"]
128
+ },
129
+ (text) => {
130
+ if (!text.startsWith("http://") && !text.startsWith("https://")) {
131
+ return { passed: true, message: "" };
132
+ }
133
+ return { passed: true, message: "" };
134
+ }
135
+ );
136
+ var copyrightDate = defineRule(
137
+ {
138
+ key: "copyright_date",
139
+ envName: "RULE_COPYRIGHT_DATE",
140
+ friendlyName: "No out-of-date copyright",
141
+ description: "using a copyright date that is not this year",
142
+ supportedFields: ["copyright"]
143
+ },
144
+ (text) => {
145
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
146
+ const yearMatch = text.match(/©?\s*(\d{4})/);
147
+ if (yearMatch) {
148
+ const year = parseInt(yearMatch[1], 10);
149
+ if (year < currentYear - 1) {
150
+ return {
151
+ passed: false,
152
+ message: `Copyright date: year ${year} may be outdated (current: ${currentYear})`
153
+ };
154
+ }
155
+ }
156
+ return { passed: true, message: "" };
157
+ }
158
+ );
159
+ var noPriceInTitle = defineRule(
160
+ {
161
+ key: "no_price_in_title",
162
+ envName: "RULE_NO_PRICE_IN_TITLE",
163
+ friendlyName: "No price in title",
164
+ description: "mentioning price in the app title",
165
+ supportedFields: ["name", "subtitle"]
166
+ },
167
+ (text) => {
168
+ if (/\$\d+|\d+\.\d{2}|free|€\d+|£\d+/i.test(text)) {
169
+ return { passed: false, message: "No price in title: price reference found" };
170
+ }
171
+ return { passed: true, message: "" };
172
+ }
173
+ );
174
+ var freeStuffIap = createWordMatchRule(
175
+ {
176
+ key: "free_stuff_in_iap",
177
+ envName: "RULE_FREE_STUFF_IN_IAP",
178
+ friendlyName: "No words indicating your IAP is free",
179
+ description: "using text indicating that your IAP is free",
180
+ supportedFields: ["in_app_purchase"]
181
+ },
182
+ ["free"]
183
+ );
184
+ var ALL_RULES = {
185
+ negativeAppleSentiment,
186
+ placeholderText,
187
+ otherPlatforms,
188
+ futureFunctionality,
189
+ testWords,
190
+ curseWords,
191
+ unreachableUrls,
192
+ copyrightDate,
193
+ noPriceInTitle,
194
+ freeStuffIap
195
+ };
196
+
197
+ // src/precheck/index.ts
198
+ async function validateMetadata(options) {
199
+ const errors = [];
200
+ const warnings = [];
201
+ const app = await options.client.apps.findByBundleId(options.bundleIdentifier);
202
+ if (!app) throw new Error(`App not found: ${options.bundleIdentifier}`);
203
+ const versions = await options.client.appStoreVersions.listForApp(app.id, {
204
+ filter: { platform: "IOS" }
205
+ });
206
+ if (versions.length === 0) {
207
+ throw new Error("No app store version found");
208
+ }
209
+ const version = versions[0];
210
+ const localizations = await options.client.appStoreVersions.getLocalizations(version.id);
211
+ const ruleConfig = options.rules ?? Object.keys(ALL_RULES).map((name) => ({
212
+ name,
213
+ level: "error"
214
+ }));
215
+ for (const loc of localizations) {
216
+ const locale = loc.attributes.locale;
217
+ const fields = {
218
+ description: loc.attributes.description,
219
+ keywords: loc.attributes.keywords,
220
+ whatsNew: loc.attributes.whatsNew,
221
+ supportUrl: loc.attributes.supportUrl,
222
+ marketingUrl: loc.attributes.marketingUrl,
223
+ promotionalText: loc.attributes.promotionalText
224
+ };
225
+ for (const [field, value] of Object.entries(fields)) {
226
+ if (!value) continue;
227
+ for (const ruleConfig_ of ruleConfig) {
228
+ const rule = ALL_RULES[ruleConfig_.name];
229
+ if (!rule) continue;
230
+ const supported = rule.metadata?.supportedFields;
231
+ if (supported && !supported.includes(field)) continue;
232
+ const result = rule(value);
233
+ if (!result.passed) {
234
+ const issue = {
235
+ rule: ruleConfig_.name,
236
+ message: result.message,
237
+ field,
238
+ locale
239
+ };
240
+ if (ruleConfig_.level === "error") {
241
+ errors.push(issue);
242
+ } else {
243
+ warnings.push(issue);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+ if (version.attributes.copyright) {
250
+ for (const ruleConfig_ of ruleConfig) {
251
+ const rule = ALL_RULES[ruleConfig_.name];
252
+ if (!rule) continue;
253
+ const supported = rule.metadata?.supportedFields;
254
+ if (supported && !supported.includes("copyright")) continue;
255
+ const result = rule(version.attributes.copyright);
256
+ if (!result.passed) {
257
+ const issue = {
258
+ rule: ruleConfig_.name,
259
+ message: result.message,
260
+ field: "copyright"
261
+ };
262
+ if (ruleConfig_.level === "error") errors.push(issue);
263
+ else warnings.push(issue);
264
+ }
265
+ }
266
+ }
267
+ return {
268
+ passed: errors.length === 0,
269
+ errors,
270
+ warnings
271
+ };
272
+ }
273
+
274
+ exports.ALL_RULES = ALL_RULES;
275
+ exports.validateMetadata = validateMetadata;
276
+ //# sourceMappingURL=index.cjs.map
277
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/precheck/rules.ts","../../src/precheck/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export interface RuleResult {\n passed: boolean;\n message: string;\n}\n\n/**\n * Rule metadata attached as properties of each rule function. Mirrors\n * Fastlane precheck's `Rule` class attributes (rule.rb, line ~5+).\n *\n * `supportedFields` lists metadata field names this rule applies to. A\n * `null` value means the rule applies to every field (Fastlane's default\n * `supported_fields_symbol_set` return of `nil`).\n */\nexport interface RuleMetadata {\n key: string;\n envName: string;\n friendlyName: string;\n description: string;\n supportedFields: readonly string[] | null;\n}\n\nexport type Rule = ((text: string) => RuleResult) & { metadata: RuleMetadata };\n\nfunction defineRule(\n metadata: RuleMetadata,\n fn: (text: string) => RuleResult\n): Rule {\n const rule = fn as Rule;\n rule.metadata = metadata;\n return rule;\n}\n\nfunction createWordMatchRule(\n metadata: RuleMetadata,\n words: string[]\n): Rule {\n const lower = words.map((w) => w.toLowerCase());\n return defineRule(metadata, (text: string): RuleResult => {\n const textLower = text.toLowerCase();\n for (const word of lower) {\n if (textLower.includes(word)) {\n return {\n passed: false,\n message: `${metadata.friendlyName}: found \"${word}\"`,\n };\n }\n }\n return { passed: true, message: \"\" };\n });\n}\n\nexport const negativeAppleSentiment = createWordMatchRule(\n {\n key: \"negative_apple_sentiment\",\n envName: \"RULE_NEGATIVE_APPLE_SENTIMENT\",\n friendlyName: \"No negative sentiment about Apple\",\n description: \"using text that could be negative sentiment about Apple\",\n supportedFields: null,\n },\n [\"suck\", \"bug\", \"broken\", \"crash\", \"hate\", \"terrible\", \"horrible\", \"worst\", \"awful\"]\n);\n\nexport const placeholderText = createWordMatchRule(\n {\n key: \"placeholder_words\",\n envName: \"RULE_PLACEHOLDER_WORDS\",\n friendlyName: \"No placeholder words\",\n description: \"using placeholder text\",\n supportedFields: null,\n },\n [\n \"lorem ipsum\",\n \"placeholder\",\n \"todo\",\n \"tbd\",\n \"coming soon\",\n \"insert\",\n \"your text here\",\n ]\n);\n\nexport const otherPlatforms = createWordMatchRule(\n {\n key: \"other_platforms\",\n envName: \"RULE_OTHER_PLATFORMS\",\n friendlyName: \"No references to other platforms\",\n description: \"mentioning other platforms\",\n supportedFields: null,\n },\n [\"android\", \"google play\", \"samsung\", \"windows phone\", \"blackberry\", \"kindle\"]\n);\n\nexport const futureFunctionality = createWordMatchRule(\n {\n key: \"future_functionality\",\n envName: \"RULE_FUTURE_FUNCTIONALITY\",\n friendlyName: \"No references to future functionality\",\n description: \"referencing future functionality\",\n supportedFields: null,\n },\n [\n \"will be available\",\n \"coming soon\",\n \"in a future update\",\n \"planned for\",\n \"we plan to\",\n \"will include\",\n \"will support\",\n ]\n);\n\nexport const testWords = createWordMatchRule(\n {\n key: \"test_words\",\n envName: \"RULE_TEST_WORDS\",\n friendlyName: \"No references to test or demo\",\n description: \"using test or demo words\",\n supportedFields: null,\n },\n [\"test\", \"demo\", \"beta version\", \"debug\"]\n);\n\nexport const curseWords: Rule = defineRule(\n {\n key: \"curse_words\",\n envName: \"RULE_CURSE_WORDS\",\n friendlyName: \"No curse words\",\n description: \"using curse words\",\n supportedFields: null,\n },\n (text: string): RuleResult => {\n // Simplified curse word detection\n const cursePatterns = [\n /\\bf+u+c+k+/i,\n /\\bs+h+i+t+/i,\n /\\ba+s+s+h+o+l+e+/i,\n /\\bb+i+t+c+h+/i,\n /\\bd+a+m+n+/i,\n /\\bh+e+l+l+\\b/i,\n /\\bc+r+a+p+/i,\n /\\bd+i+c+k+/i,\n /\\bp+i+s+s+/i,\n ];\n for (const pattern of cursePatterns) {\n if (pattern.test(text)) {\n return {\n passed: false,\n message: \"Curse words: inappropriate language detected\",\n };\n }\n }\n return { passed: true, message: \"\" };\n }\n);\n\nexport const unreachableUrls: Rule = defineRule(\n {\n key: \"unreachable_urls\",\n envName: \"RULE_UNREACHABLE_URLS\",\n friendlyName: \"No unreachable URLs\",\n description: \"using URLs that cannot be reached\",\n supportedFields: [\"supportUrl\", \"marketingUrl\", \"privacyUrl\"],\n },\n (text: string): RuleResult => {\n // This is meant to check URLs — skip if not a URL\n if (!text.startsWith(\"http://\") && !text.startsWith(\"https://\")) {\n return { passed: true, message: \"\" };\n }\n // URL reachability check would be async — this is a sync placeholder\n return { passed: true, message: \"\" };\n }\n);\n\nexport const copyrightDate: Rule = defineRule(\n {\n key: \"copyright_date\",\n envName: \"RULE_COPYRIGHT_DATE\",\n friendlyName: \"No out-of-date copyright\",\n description: \"using a copyright date that is not this year\",\n supportedFields: [\"copyright\"],\n },\n (text: string): RuleResult => {\n const currentYear = new Date().getFullYear();\n const yearMatch = text.match(/©?\\s*(\\d{4})/);\n if (yearMatch) {\n const year = parseInt(yearMatch[1], 10);\n if (year < currentYear - 1) {\n return {\n passed: false,\n message: `Copyright date: year ${year} may be outdated (current: ${currentYear})`,\n };\n }\n }\n return { passed: true, message: \"\" };\n }\n);\n\nexport const noPriceInTitle: Rule = defineRule(\n {\n key: \"no_price_in_title\",\n envName: \"RULE_NO_PRICE_IN_TITLE\",\n friendlyName: \"No price in title\",\n description: \"mentioning price in the app title\",\n supportedFields: [\"name\", \"subtitle\"],\n },\n (text: string): RuleResult => {\n if (/\\$\\d+|\\d+\\.\\d{2}|free|€\\d+|£\\d+/i.test(text)) {\n return { passed: false, message: \"No price in title: price reference found\" };\n }\n return { passed: true, message: \"\" };\n }\n);\n\n/**\n * `freeStuffIap`: flag IAP descriptions that mention \"free\", matching\n * Fastlane's `FreeStuffIAPRule` (free_stuff_iap_rule.rb).\n */\nexport const freeStuffIap = createWordMatchRule(\n {\n key: \"free_stuff_in_iap\",\n envName: \"RULE_FREE_STUFF_IN_IAP\",\n friendlyName: \"No words indicating your IAP is free\",\n description: \"using text indicating that your IAP is free\",\n supportedFields: [\"in_app_purchase\"],\n },\n [\"free\"]\n);\n\nexport function customText(words: string[]): Rule {\n return createWordMatchRule(\n {\n key: \"custom_text\",\n envName: \"RULE_CUSTOM_TEXT\",\n friendlyName: \"No custom text\",\n description: \"using custom disallowed text\",\n supportedFields: null,\n },\n words\n );\n}\n\nexport const ALL_RULES: Record<string, Rule> = {\n negativeAppleSentiment,\n placeholderText,\n otherPlatforms,\n futureFunctionality,\n testWords,\n curseWords,\n unreachableUrls,\n copyrightDate,\n noPriceInTitle,\n freeStuffIap,\n};\n","import type { AppStoreConnectClient } from \"../client/index.js\";\nimport { ALL_RULES, type Rule, type RuleResult } from \"./rules.js\";\n\nexport { ALL_RULES } from \"./rules.js\";\nexport type { Rule, RuleResult } from \"./rules.js\";\n\nexport interface PrecheckOptions {\n client: AppStoreConnectClient;\n bundleIdentifier: string;\n includeInAppPurchases?: boolean;\n rules?: PrecheckRule[];\n}\n\nexport interface PrecheckRule {\n name: string;\n level: \"error\" | \"warning\";\n}\n\nexport interface ValidationResult {\n passed: boolean;\n errors: ValidationIssue[];\n warnings: ValidationIssue[];\n}\n\nexport interface ValidationIssue {\n rule: string;\n message: string;\n field?: string;\n locale?: string;\n}\n\nexport async function validateMetadata(\n options: PrecheckOptions\n): Promise<ValidationResult> {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n\n // Find app\n const app = await options.client.apps.findByBundleId(options.bundleIdentifier);\n if (!app) throw new Error(`App not found: ${options.bundleIdentifier}`);\n\n // Get latest version\n const versions = await options.client.appStoreVersions.listForApp(app.id, {\n filter: { platform: \"IOS\" },\n });\n\n if (versions.length === 0) {\n throw new Error(\"No app store version found\");\n }\n\n const version = versions[0];\n const localizations = await options.client.appStoreVersions.getLocalizations(version.id);\n\n // Determine which rules to run\n const ruleConfig = options.rules ?? Object.keys(ALL_RULES).map((name) => ({\n name,\n level: \"error\" as const,\n }));\n\n // Run rules against each localization\n for (const loc of localizations) {\n const locale = loc.attributes.locale;\n const fields: Record<string, string | null> = {\n description: loc.attributes.description,\n keywords: loc.attributes.keywords,\n whatsNew: loc.attributes.whatsNew,\n supportUrl: loc.attributes.supportUrl,\n marketingUrl: loc.attributes.marketingUrl,\n promotionalText: loc.attributes.promotionalText,\n };\n\n for (const [field, value] of Object.entries(fields)) {\n if (!value) continue;\n\n for (const ruleConfig_ of ruleConfig) {\n const rule = ALL_RULES[ruleConfig_.name];\n if (!rule) continue;\n\n // Respect the rule's supportedFields list (nil/null = applies to all).\n // Mirrors Fastlane precheck's `Rule#item_field_supported?`.\n const supported = rule.metadata?.supportedFields;\n if (supported && !supported.includes(field)) continue;\n\n const result = rule(value);\n if (!result.passed) {\n const issue: ValidationIssue = {\n rule: ruleConfig_.name,\n message: result.message,\n field,\n locale,\n };\n if (ruleConfig_.level === \"error\") {\n errors.push(issue);\n } else {\n warnings.push(issue);\n }\n }\n }\n }\n }\n\n // Check copyright on the version — only run copyright-supporting rules.\n if (version.attributes.copyright) {\n for (const ruleConfig_ of ruleConfig) {\n const rule = ALL_RULES[ruleConfig_.name];\n if (!rule) continue;\n const supported = rule.metadata?.supportedFields;\n if (supported && !supported.includes(\"copyright\")) continue;\n const result = rule(version.attributes.copyright);\n if (!result.passed) {\n const issue: ValidationIssue = {\n rule: ruleConfig_.name,\n message: result.message,\n field: \"copyright\",\n };\n if (ruleConfig_.level === \"error\") errors.push(issue);\n else warnings.push(issue);\n }\n }\n }\n\n return {\n passed: errors.length === 0,\n errors,\n warnings,\n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import '../index-BLubSlgK.cjs';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-DfCUVJX1.cjs';
3
+ import '../enums-B609j2rb.cjs';
@@ -0,0 +1,3 @@
1
+ import '../index-D1Izm7qt.js';
2
+ export { A as ALL_RULES, P as PrecheckOptions, a as PrecheckRule, R as Rule, c as RuleResult, b as ValidationIssue, V as ValidationResult, v as validateMetadata } from '../index-B9yXicrQ.js';
3
+ import '../enums-B609j2rb.js';
@@ -0,0 +1,274 @@
1
+ // src/precheck/rules.ts
2
+ function defineRule(metadata, fn) {
3
+ const rule = fn;
4
+ rule.metadata = metadata;
5
+ return rule;
6
+ }
7
+ function createWordMatchRule(metadata, words) {
8
+ const lower = words.map((w) => w.toLowerCase());
9
+ return defineRule(metadata, (text) => {
10
+ const textLower = text.toLowerCase();
11
+ for (const word of lower) {
12
+ if (textLower.includes(word)) {
13
+ return {
14
+ passed: false,
15
+ message: `${metadata.friendlyName}: found "${word}"`
16
+ };
17
+ }
18
+ }
19
+ return { passed: true, message: "" };
20
+ });
21
+ }
22
+ var negativeAppleSentiment = createWordMatchRule(
23
+ {
24
+ key: "negative_apple_sentiment",
25
+ envName: "RULE_NEGATIVE_APPLE_SENTIMENT",
26
+ friendlyName: "No negative sentiment about Apple",
27
+ description: "using text that could be negative sentiment about Apple",
28
+ supportedFields: null
29
+ },
30
+ ["suck", "bug", "broken", "crash", "hate", "terrible", "horrible", "worst", "awful"]
31
+ );
32
+ var placeholderText = createWordMatchRule(
33
+ {
34
+ key: "placeholder_words",
35
+ envName: "RULE_PLACEHOLDER_WORDS",
36
+ friendlyName: "No placeholder words",
37
+ description: "using placeholder text",
38
+ supportedFields: null
39
+ },
40
+ [
41
+ "lorem ipsum",
42
+ "placeholder",
43
+ "todo",
44
+ "tbd",
45
+ "coming soon",
46
+ "insert",
47
+ "your text here"
48
+ ]
49
+ );
50
+ var otherPlatforms = createWordMatchRule(
51
+ {
52
+ key: "other_platforms",
53
+ envName: "RULE_OTHER_PLATFORMS",
54
+ friendlyName: "No references to other platforms",
55
+ description: "mentioning other platforms",
56
+ supportedFields: null
57
+ },
58
+ ["android", "google play", "samsung", "windows phone", "blackberry", "kindle"]
59
+ );
60
+ var futureFunctionality = createWordMatchRule(
61
+ {
62
+ key: "future_functionality",
63
+ envName: "RULE_FUTURE_FUNCTIONALITY",
64
+ friendlyName: "No references to future functionality",
65
+ description: "referencing future functionality",
66
+ supportedFields: null
67
+ },
68
+ [
69
+ "will be available",
70
+ "coming soon",
71
+ "in a future update",
72
+ "planned for",
73
+ "we plan to",
74
+ "will include",
75
+ "will support"
76
+ ]
77
+ );
78
+ var testWords = createWordMatchRule(
79
+ {
80
+ key: "test_words",
81
+ envName: "RULE_TEST_WORDS",
82
+ friendlyName: "No references to test or demo",
83
+ description: "using test or demo words",
84
+ supportedFields: null
85
+ },
86
+ ["test", "demo", "beta version", "debug"]
87
+ );
88
+ var curseWords = defineRule(
89
+ {
90
+ key: "curse_words",
91
+ envName: "RULE_CURSE_WORDS",
92
+ friendlyName: "No curse words",
93
+ description: "using curse words",
94
+ supportedFields: null
95
+ },
96
+ (text) => {
97
+ const cursePatterns = [
98
+ /\bf+u+c+k+/i,
99
+ /\bs+h+i+t+/i,
100
+ /\ba+s+s+h+o+l+e+/i,
101
+ /\bb+i+t+c+h+/i,
102
+ /\bd+a+m+n+/i,
103
+ /\bh+e+l+l+\b/i,
104
+ /\bc+r+a+p+/i,
105
+ /\bd+i+c+k+/i,
106
+ /\bp+i+s+s+/i
107
+ ];
108
+ for (const pattern of cursePatterns) {
109
+ if (pattern.test(text)) {
110
+ return {
111
+ passed: false,
112
+ message: "Curse words: inappropriate language detected"
113
+ };
114
+ }
115
+ }
116
+ return { passed: true, message: "" };
117
+ }
118
+ );
119
+ var unreachableUrls = defineRule(
120
+ {
121
+ key: "unreachable_urls",
122
+ envName: "RULE_UNREACHABLE_URLS",
123
+ friendlyName: "No unreachable URLs",
124
+ description: "using URLs that cannot be reached",
125
+ supportedFields: ["supportUrl", "marketingUrl", "privacyUrl"]
126
+ },
127
+ (text) => {
128
+ if (!text.startsWith("http://") && !text.startsWith("https://")) {
129
+ return { passed: true, message: "" };
130
+ }
131
+ return { passed: true, message: "" };
132
+ }
133
+ );
134
+ var copyrightDate = defineRule(
135
+ {
136
+ key: "copyright_date",
137
+ envName: "RULE_COPYRIGHT_DATE",
138
+ friendlyName: "No out-of-date copyright",
139
+ description: "using a copyright date that is not this year",
140
+ supportedFields: ["copyright"]
141
+ },
142
+ (text) => {
143
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
144
+ const yearMatch = text.match(/©?\s*(\d{4})/);
145
+ if (yearMatch) {
146
+ const year = parseInt(yearMatch[1], 10);
147
+ if (year < currentYear - 1) {
148
+ return {
149
+ passed: false,
150
+ message: `Copyright date: year ${year} may be outdated (current: ${currentYear})`
151
+ };
152
+ }
153
+ }
154
+ return { passed: true, message: "" };
155
+ }
156
+ );
157
+ var noPriceInTitle = defineRule(
158
+ {
159
+ key: "no_price_in_title",
160
+ envName: "RULE_NO_PRICE_IN_TITLE",
161
+ friendlyName: "No price in title",
162
+ description: "mentioning price in the app title",
163
+ supportedFields: ["name", "subtitle"]
164
+ },
165
+ (text) => {
166
+ if (/\$\d+|\d+\.\d{2}|free|€\d+|£\d+/i.test(text)) {
167
+ return { passed: false, message: "No price in title: price reference found" };
168
+ }
169
+ return { passed: true, message: "" };
170
+ }
171
+ );
172
+ var freeStuffIap = createWordMatchRule(
173
+ {
174
+ key: "free_stuff_in_iap",
175
+ envName: "RULE_FREE_STUFF_IN_IAP",
176
+ friendlyName: "No words indicating your IAP is free",
177
+ description: "using text indicating that your IAP is free",
178
+ supportedFields: ["in_app_purchase"]
179
+ },
180
+ ["free"]
181
+ );
182
+ var ALL_RULES = {
183
+ negativeAppleSentiment,
184
+ placeholderText,
185
+ otherPlatforms,
186
+ futureFunctionality,
187
+ testWords,
188
+ curseWords,
189
+ unreachableUrls,
190
+ copyrightDate,
191
+ noPriceInTitle,
192
+ freeStuffIap
193
+ };
194
+
195
+ // src/precheck/index.ts
196
+ async function validateMetadata(options) {
197
+ const errors = [];
198
+ const warnings = [];
199
+ const app = await options.client.apps.findByBundleId(options.bundleIdentifier);
200
+ if (!app) throw new Error(`App not found: ${options.bundleIdentifier}`);
201
+ const versions = await options.client.appStoreVersions.listForApp(app.id, {
202
+ filter: { platform: "IOS" }
203
+ });
204
+ if (versions.length === 0) {
205
+ throw new Error("No app store version found");
206
+ }
207
+ const version = versions[0];
208
+ const localizations = await options.client.appStoreVersions.getLocalizations(version.id);
209
+ const ruleConfig = options.rules ?? Object.keys(ALL_RULES).map((name) => ({
210
+ name,
211
+ level: "error"
212
+ }));
213
+ for (const loc of localizations) {
214
+ const locale = loc.attributes.locale;
215
+ const fields = {
216
+ description: loc.attributes.description,
217
+ keywords: loc.attributes.keywords,
218
+ whatsNew: loc.attributes.whatsNew,
219
+ supportUrl: loc.attributes.supportUrl,
220
+ marketingUrl: loc.attributes.marketingUrl,
221
+ promotionalText: loc.attributes.promotionalText
222
+ };
223
+ for (const [field, value] of Object.entries(fields)) {
224
+ if (!value) continue;
225
+ for (const ruleConfig_ of ruleConfig) {
226
+ const rule = ALL_RULES[ruleConfig_.name];
227
+ if (!rule) continue;
228
+ const supported = rule.metadata?.supportedFields;
229
+ if (supported && !supported.includes(field)) continue;
230
+ const result = rule(value);
231
+ if (!result.passed) {
232
+ const issue = {
233
+ rule: ruleConfig_.name,
234
+ message: result.message,
235
+ field,
236
+ locale
237
+ };
238
+ if (ruleConfig_.level === "error") {
239
+ errors.push(issue);
240
+ } else {
241
+ warnings.push(issue);
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ if (version.attributes.copyright) {
248
+ for (const ruleConfig_ of ruleConfig) {
249
+ const rule = ALL_RULES[ruleConfig_.name];
250
+ if (!rule) continue;
251
+ const supported = rule.metadata?.supportedFields;
252
+ if (supported && !supported.includes("copyright")) continue;
253
+ const result = rule(version.attributes.copyright);
254
+ if (!result.passed) {
255
+ const issue = {
256
+ rule: ruleConfig_.name,
257
+ message: result.message,
258
+ field: "copyright"
259
+ };
260
+ if (ruleConfig_.level === "error") errors.push(issue);
261
+ else warnings.push(issue);
262
+ }
263
+ }
264
+ }
265
+ return {
266
+ passed: errors.length === 0,
267
+ errors,
268
+ warnings
269
+ };
270
+ }
271
+
272
+ export { ALL_RULES, validateMetadata };
273
+ //# sourceMappingURL=index.mjs.map
274
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/precheck/rules.ts","../../src/precheck/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export interface RuleResult {\n passed: boolean;\n message: string;\n}\n\n/**\n * Rule metadata attached as properties of each rule function. Mirrors\n * Fastlane precheck's `Rule` class attributes (rule.rb, line ~5+).\n *\n * `supportedFields` lists metadata field names this rule applies to. A\n * `null` value means the rule applies to every field (Fastlane's default\n * `supported_fields_symbol_set` return of `nil`).\n */\nexport interface RuleMetadata {\n key: string;\n envName: string;\n friendlyName: string;\n description: string;\n supportedFields: readonly string[] | null;\n}\n\nexport type Rule = ((text: string) => RuleResult) & { metadata: RuleMetadata };\n\nfunction defineRule(\n metadata: RuleMetadata,\n fn: (text: string) => RuleResult\n): Rule {\n const rule = fn as Rule;\n rule.metadata = metadata;\n return rule;\n}\n\nfunction createWordMatchRule(\n metadata: RuleMetadata,\n words: string[]\n): Rule {\n const lower = words.map((w) => w.toLowerCase());\n return defineRule(metadata, (text: string): RuleResult => {\n const textLower = text.toLowerCase();\n for (const word of lower) {\n if (textLower.includes(word)) {\n return {\n passed: false,\n message: `${metadata.friendlyName}: found \"${word}\"`,\n };\n }\n }\n return { passed: true, message: \"\" };\n });\n}\n\nexport const negativeAppleSentiment = createWordMatchRule(\n {\n key: \"negative_apple_sentiment\",\n envName: \"RULE_NEGATIVE_APPLE_SENTIMENT\",\n friendlyName: \"No negative sentiment about Apple\",\n description: \"using text that could be negative sentiment about Apple\",\n supportedFields: null,\n },\n [\"suck\", \"bug\", \"broken\", \"crash\", \"hate\", \"terrible\", \"horrible\", \"worst\", \"awful\"]\n);\n\nexport const placeholderText = createWordMatchRule(\n {\n key: \"placeholder_words\",\n envName: \"RULE_PLACEHOLDER_WORDS\",\n friendlyName: \"No placeholder words\",\n description: \"using placeholder text\",\n supportedFields: null,\n },\n [\n \"lorem ipsum\",\n \"placeholder\",\n \"todo\",\n \"tbd\",\n \"coming soon\",\n \"insert\",\n \"your text here\",\n ]\n);\n\nexport const otherPlatforms = createWordMatchRule(\n {\n key: \"other_platforms\",\n envName: \"RULE_OTHER_PLATFORMS\",\n friendlyName: \"No references to other platforms\",\n description: \"mentioning other platforms\",\n supportedFields: null,\n },\n [\"android\", \"google play\", \"samsung\", \"windows phone\", \"blackberry\", \"kindle\"]\n);\n\nexport const futureFunctionality = createWordMatchRule(\n {\n key: \"future_functionality\",\n envName: \"RULE_FUTURE_FUNCTIONALITY\",\n friendlyName: \"No references to future functionality\",\n description: \"referencing future functionality\",\n supportedFields: null,\n },\n [\n \"will be available\",\n \"coming soon\",\n \"in a future update\",\n \"planned for\",\n \"we plan to\",\n \"will include\",\n \"will support\",\n ]\n);\n\nexport const testWords = createWordMatchRule(\n {\n key: \"test_words\",\n envName: \"RULE_TEST_WORDS\",\n friendlyName: \"No references to test or demo\",\n description: \"using test or demo words\",\n supportedFields: null,\n },\n [\"test\", \"demo\", \"beta version\", \"debug\"]\n);\n\nexport const curseWords: Rule = defineRule(\n {\n key: \"curse_words\",\n envName: \"RULE_CURSE_WORDS\",\n friendlyName: \"No curse words\",\n description: \"using curse words\",\n supportedFields: null,\n },\n (text: string): RuleResult => {\n // Simplified curse word detection\n const cursePatterns = [\n /\\bf+u+c+k+/i,\n /\\bs+h+i+t+/i,\n /\\ba+s+s+h+o+l+e+/i,\n /\\bb+i+t+c+h+/i,\n /\\bd+a+m+n+/i,\n /\\bh+e+l+l+\\b/i,\n /\\bc+r+a+p+/i,\n /\\bd+i+c+k+/i,\n /\\bp+i+s+s+/i,\n ];\n for (const pattern of cursePatterns) {\n if (pattern.test(text)) {\n return {\n passed: false,\n message: \"Curse words: inappropriate language detected\",\n };\n }\n }\n return { passed: true, message: \"\" };\n }\n);\n\nexport const unreachableUrls: Rule = defineRule(\n {\n key: \"unreachable_urls\",\n envName: \"RULE_UNREACHABLE_URLS\",\n friendlyName: \"No unreachable URLs\",\n description: \"using URLs that cannot be reached\",\n supportedFields: [\"supportUrl\", \"marketingUrl\", \"privacyUrl\"],\n },\n (text: string): RuleResult => {\n // This is meant to check URLs — skip if not a URL\n if (!text.startsWith(\"http://\") && !text.startsWith(\"https://\")) {\n return { passed: true, message: \"\" };\n }\n // URL reachability check would be async — this is a sync placeholder\n return { passed: true, message: \"\" };\n }\n);\n\nexport const copyrightDate: Rule = defineRule(\n {\n key: \"copyright_date\",\n envName: \"RULE_COPYRIGHT_DATE\",\n friendlyName: \"No out-of-date copyright\",\n description: \"using a copyright date that is not this year\",\n supportedFields: [\"copyright\"],\n },\n (text: string): RuleResult => {\n const currentYear = new Date().getFullYear();\n const yearMatch = text.match(/©?\\s*(\\d{4})/);\n if (yearMatch) {\n const year = parseInt(yearMatch[1], 10);\n if (year < currentYear - 1) {\n return {\n passed: false,\n message: `Copyright date: year ${year} may be outdated (current: ${currentYear})`,\n };\n }\n }\n return { passed: true, message: \"\" };\n }\n);\n\nexport const noPriceInTitle: Rule = defineRule(\n {\n key: \"no_price_in_title\",\n envName: \"RULE_NO_PRICE_IN_TITLE\",\n friendlyName: \"No price in title\",\n description: \"mentioning price in the app title\",\n supportedFields: [\"name\", \"subtitle\"],\n },\n (text: string): RuleResult => {\n if (/\\$\\d+|\\d+\\.\\d{2}|free|€\\d+|£\\d+/i.test(text)) {\n return { passed: false, message: \"No price in title: price reference found\" };\n }\n return { passed: true, message: \"\" };\n }\n);\n\n/**\n * `freeStuffIap`: flag IAP descriptions that mention \"free\", matching\n * Fastlane's `FreeStuffIAPRule` (free_stuff_iap_rule.rb).\n */\nexport const freeStuffIap = createWordMatchRule(\n {\n key: \"free_stuff_in_iap\",\n envName: \"RULE_FREE_STUFF_IN_IAP\",\n friendlyName: \"No words indicating your IAP is free\",\n description: \"using text indicating that your IAP is free\",\n supportedFields: [\"in_app_purchase\"],\n },\n [\"free\"]\n);\n\nexport function customText(words: string[]): Rule {\n return createWordMatchRule(\n {\n key: \"custom_text\",\n envName: \"RULE_CUSTOM_TEXT\",\n friendlyName: \"No custom text\",\n description: \"using custom disallowed text\",\n supportedFields: null,\n },\n words\n );\n}\n\nexport const ALL_RULES: Record<string, Rule> = {\n negativeAppleSentiment,\n placeholderText,\n otherPlatforms,\n futureFunctionality,\n testWords,\n curseWords,\n unreachableUrls,\n copyrightDate,\n noPriceInTitle,\n freeStuffIap,\n};\n","import type { AppStoreConnectClient } from \"../client/index.js\";\nimport { ALL_RULES, type Rule, type RuleResult } from \"./rules.js\";\n\nexport { ALL_RULES } from \"./rules.js\";\nexport type { Rule, RuleResult } from \"./rules.js\";\n\nexport interface PrecheckOptions {\n client: AppStoreConnectClient;\n bundleIdentifier: string;\n includeInAppPurchases?: boolean;\n rules?: PrecheckRule[];\n}\n\nexport interface PrecheckRule {\n name: string;\n level: \"error\" | \"warning\";\n}\n\nexport interface ValidationResult {\n passed: boolean;\n errors: ValidationIssue[];\n warnings: ValidationIssue[];\n}\n\nexport interface ValidationIssue {\n rule: string;\n message: string;\n field?: string;\n locale?: string;\n}\n\nexport async function validateMetadata(\n options: PrecheckOptions\n): Promise<ValidationResult> {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n\n // Find app\n const app = await options.client.apps.findByBundleId(options.bundleIdentifier);\n if (!app) throw new Error(`App not found: ${options.bundleIdentifier}`);\n\n // Get latest version\n const versions = await options.client.appStoreVersions.listForApp(app.id, {\n filter: { platform: \"IOS\" },\n });\n\n if (versions.length === 0) {\n throw new Error(\"No app store version found\");\n }\n\n const version = versions[0];\n const localizations = await options.client.appStoreVersions.getLocalizations(version.id);\n\n // Determine which rules to run\n const ruleConfig = options.rules ?? Object.keys(ALL_RULES).map((name) => ({\n name,\n level: \"error\" as const,\n }));\n\n // Run rules against each localization\n for (const loc of localizations) {\n const locale = loc.attributes.locale;\n const fields: Record<string, string | null> = {\n description: loc.attributes.description,\n keywords: loc.attributes.keywords,\n whatsNew: loc.attributes.whatsNew,\n supportUrl: loc.attributes.supportUrl,\n marketingUrl: loc.attributes.marketingUrl,\n promotionalText: loc.attributes.promotionalText,\n };\n\n for (const [field, value] of Object.entries(fields)) {\n if (!value) continue;\n\n for (const ruleConfig_ of ruleConfig) {\n const rule = ALL_RULES[ruleConfig_.name];\n if (!rule) continue;\n\n // Respect the rule's supportedFields list (nil/null = applies to all).\n // Mirrors Fastlane precheck's `Rule#item_field_supported?`.\n const supported = rule.metadata?.supportedFields;\n if (supported && !supported.includes(field)) continue;\n\n const result = rule(value);\n if (!result.passed) {\n const issue: ValidationIssue = {\n rule: ruleConfig_.name,\n message: result.message,\n field,\n locale,\n };\n if (ruleConfig_.level === \"error\") {\n errors.push(issue);\n } else {\n warnings.push(issue);\n }\n }\n }\n }\n }\n\n // Check copyright on the version — only run copyright-supporting rules.\n if (version.attributes.copyright) {\n for (const ruleConfig_ of ruleConfig) {\n const rule = ALL_RULES[ruleConfig_.name];\n if (!rule) continue;\n const supported = rule.metadata?.supportedFields;\n if (supported && !supported.includes(\"copyright\")) continue;\n const result = rule(version.attributes.copyright);\n if (!result.passed) {\n const issue: ValidationIssue = {\n rule: ruleConfig_.name,\n message: result.message,\n field: \"copyright\",\n };\n if (ruleConfig_.level === \"error\") errors.push(issue);\n else warnings.push(issue);\n }\n }\n }\n\n return {\n passed: errors.length === 0,\n errors,\n warnings,\n };\n}\n"]}