@mcamdali/sdkbump 0.0.0-stage → 0.1.3

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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.de.md +251 -0
  3. package/README.es.md +252 -0
  4. package/README.fr.md +252 -0
  5. package/README.ja.md +250 -0
  6. package/README.ko.md +250 -0
  7. package/README.md +322 -2
  8. package/README.pt-BR.md +252 -0
  9. package/README.ru.md +252 -0
  10. package/README.tr.md +338 -0
  11. package/README.zh-CN.md +250 -0
  12. package/dist/cli/main.d.ts +2 -0
  13. package/dist/cli/main.js +11 -0
  14. package/dist/cli/run.d.ts +29 -0
  15. package/dist/cli/run.js +111 -0
  16. package/dist/collect/app.d.ts +48 -0
  17. package/dist/collect/app.js +117 -0
  18. package/dist/collect/blank.d.ts +6 -0
  19. package/dist/collect/blank.js +75 -0
  20. package/dist/collect/pbxproj.d.ts +45 -0
  21. package/dist/collect/pbxproj.js +260 -0
  22. package/dist/collect/plist-binary.d.ts +2 -0
  23. package/dist/collect/plist-binary.js +123 -0
  24. package/dist/collect/plist-value.d.ts +15 -0
  25. package/dist/collect/plist-value.js +14 -0
  26. package/dist/collect/plist-xml.d.ts +2 -0
  27. package/dist/collect/plist-xml.js +195 -0
  28. package/dist/collect/plist.d.ts +6 -0
  29. package/dist/collect/plist.js +19 -0
  30. package/dist/collect/regions.d.ts +9 -0
  31. package/dist/collect/regions.js +108 -0
  32. package/dist/collect/sources.d.ts +45 -0
  33. package/dist/collect/sources.js +99 -0
  34. package/dist/core/files.d.ts +22 -0
  35. package/dist/core/files.js +47 -0
  36. package/dist/core/lines.d.ts +2 -0
  37. package/dist/core/lines.js +21 -0
  38. package/dist/core/versions.d.ts +18 -0
  39. package/dist/core/versions.js +63 -0
  40. package/dist/detect/project.d.ts +46 -0
  41. package/dist/detect/project.js +220 -0
  42. package/dist/index.d.ts +26 -0
  43. package/dist/index.js +27 -0
  44. package/dist/model.d.ts +93 -0
  45. package/dist/model.js +2 -0
  46. package/dist/report/index.d.ts +12 -0
  47. package/dist/report/index.js +22 -0
  48. package/dist/report/markdown.d.ts +2 -0
  49. package/dist/report/markdown.js +113 -0
  50. package/dist/report/text.d.ts +27 -0
  51. package/dist/report/text.js +63 -0
  52. package/dist/report/tty.d.ts +7 -0
  53. package/dist/report/tty.js +112 -0
  54. package/dist/rules/check.d.ts +11 -0
  55. package/dist/rules/check.js +253 -0
  56. package/dist/rules/data.d.ts +217 -0
  57. package/dist/rules/data.js +141 -0
  58. package/dist/rules/fixes.d.ts +28 -0
  59. package/dist/rules/fixes.js +161 -0
  60. package/dist/rules/patterns.d.ts +29 -0
  61. package/dist/rules/patterns.js +109 -0
  62. package/dist/rules/query.d.ts +16 -0
  63. package/dist/rules/query.js +50 -0
  64. package/dist/scan.d.ts +10 -0
  65. package/dist/scan.js +115 -0
  66. package/dist/version.d.ts +2 -0
  67. package/dist/version.js +7 -0
  68. package/package.json +69 -4
  69. package/rules/ios27.json +613 -0
  70. package/rules/schema.json +449 -0
@@ -0,0 +1,112 @@
1
+ import { ECOSYSTEM_LABELS, GUIDES, INFO_PLIST_LABELS, LIFECYCLE_LABELS, plural, REPOSITORY_URL, wrap, } from './text.js';
2
+ const CODES = {
3
+ bold: [1, 22],
4
+ dim: [2, 22],
5
+ red: [31, 39],
6
+ yellow: [33, 39],
7
+ green: [32, 39],
8
+ cyan: [36, 39],
9
+ };
10
+ const LABEL_WIDTH = 9;
11
+ const INDENT = ' '.repeat(LABEL_WIDTH);
12
+ const SEVERITY_STYLE = { error: 'red', warning: 'yellow', info: 'cyan' };
13
+ const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
14
+ function painter(color) {
15
+ return (style, text) => {
16
+ if (!color)
17
+ return text;
18
+ const [open, close] = CODES[style];
19
+ return `\u001b[${open}m${text}\u001b[${close}m`;
20
+ };
21
+ }
22
+ /** Each distinct fix of the report with its letter, in order of first appearance. */
23
+ function letterFixes(findings) {
24
+ const result = new Map();
25
+ for (const item of findings) {
26
+ for (const fix of [item.fix, ...item.alternatives]) {
27
+ if (!fix || result.has(fix.id))
28
+ continue;
29
+ result.set(fix.id, { letter: LETTERS[result.size] ?? '?', fix });
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ function header(report, paint, width) {
35
+ const { project, rules } = report;
36
+ const dim = (text) => wrap(text, width).map((line) => paint('dim', line));
37
+ const [name = '', ...rest] = wrap(`${project.name} · ${ECOSYSTEM_LABELS[project.ecosystem]} · ${project.path}`, width);
38
+ const nameLength = project.name.length;
39
+ const lines = [
40
+ ...dim(`sdkbump ${report.tool.version} · rules ${rules.ruleSet}@${rules.dataVersion} · ${rules.sdk}`),
41
+ '',
42
+ `${paint('bold', name.slice(0, nameLength))}${name.slice(nameLength)}`,
43
+ ...rest,
44
+ ];
45
+ for (const version of project.versions) {
46
+ lines.push(...dim(`${version.name} ${version.version ?? '(unknown)'} (${version.source})`));
47
+ }
48
+ for (const target of project.targets) {
49
+ const plists = target.infoPlistSource === 'file'
50
+ ? target.infoPlists.join(', ')
51
+ : INFO_PLIST_LABELS[target.infoPlistSource];
52
+ lines.push(...dim(`target ${target.name}: ${LIFECYCLE_LABELS[target.lifecycle]} (${plists})`));
53
+ }
54
+ const style = report.verdict.code === 'fails-to-launch' || report.verdict.code === 'black-screen'
55
+ ? 'red'
56
+ : report.verdict.code === 'no-findings'
57
+ ? 'green'
58
+ : 'yellow';
59
+ lines.push('', ...wrap(report.verdict.text, width).map((line) => paint('bold', paint(style, line))), '');
60
+ return lines;
61
+ }
62
+ export function renderTty(report, options) {
63
+ const paint = painter(options.color);
64
+ const width = Math.max(60, options.width ?? 100);
65
+ const body = (text, style) => wrap(text, width - LABEL_WIDTH).map((line) => `${INDENT}${style ? paint(style, line) : line}`);
66
+ const fixes = letterFixes(report.findings);
67
+ const out = header(report, paint, width);
68
+ for (const item of report.findings) {
69
+ const label = (item.mitigation ? 'HANDLED' : item.severity.toUpperCase()).padEnd(LABEL_WIDTH);
70
+ const style = item.mitigation ? 'green' : SEVERITY_STYLE[item.severity];
71
+ const [titleFirst = '', ...titleRest] = wrap(`${item.rule} ${item.title}`, width - LABEL_WIDTH);
72
+ out.push(`${paint('bold', paint(style, label))}${paint('bold', titleFirst)}`, ...titleRest.map((line) => `${INDENT}${paint('bold', line)}`), `${INDENT}${paint('cyan', `${item.file}${item.line ? `:${item.line}` : ''}`)}`, ...body(item.message));
73
+ if (item.mitigation) {
74
+ const { mitigation } = item;
75
+ const version = mitigation.version ? ` ${mitigation.version}` : '';
76
+ out.push(...body(`Handled at run time by ${mitigation.package}${version}. Limits: ${mitigation.limits.join(' ')}`, 'dim'));
77
+ }
78
+ const named = [item.fix, ...item.alternatives]
79
+ .filter((fix) => fix !== null)
80
+ .map((fix) => `[${fixes.get(fix.id)?.letter}] ${fix.title}`);
81
+ if (named.length)
82
+ out.push(...body(`Fix: ${named.join('; or ')}`, 'bold'));
83
+ out.push('');
84
+ }
85
+ if (fixes.size > 0) {
86
+ out.push(paint('bold', 'How to fix'), '');
87
+ for (const { letter, fix } of fixes.values()) {
88
+ out.push(`${paint('bold', `[${letter}]`.padEnd(LABEL_WIDTH))}${paint('bold', fix.title)}`);
89
+ fix.steps.forEach((step, index) => {
90
+ const marker = `${index + 1}. `;
91
+ const [first = '', ...rest] = wrap(step, width - LABEL_WIDTH - marker.length);
92
+ out.push(`${INDENT}${marker}${first}`, ...rest.map((line) => `${INDENT}${' '.repeat(marker.length)}${line}`));
93
+ });
94
+ out.push(`${INDENT}${paint('dim', fix.guide)}`, '');
95
+ }
96
+ }
97
+ for (const note of report.notes)
98
+ out.push(...wrap(`Note: ${note}`, width).map((line) => paint('dim', line)));
99
+ if (report.notes.length)
100
+ out.push('');
101
+ const { errors, warnings, info, mitigated } = report.summary;
102
+ const counts = [plural(errors, 'error'), plural(warnings, 'warning')];
103
+ if (info)
104
+ counts.push(`${info} info`);
105
+ if (mitigated)
106
+ counts.push(`${mitigated} handled by a workaround`);
107
+ out.push(paint('bold', counts.join(', ')), `Guide: ${GUIDES[report.project.ecosystem]}`);
108
+ if (report.findings.length === 0)
109
+ out.push(`What is checked: ${REPOSITORY_URL}#what-sdkbump-checks`);
110
+ return `${out.join('\n')}\n`;
111
+ }
112
+ //# sourceMappingURL=tty.js.map
@@ -0,0 +1,11 @@
1
+ import type { AppModel } from '../collect/app.js';
2
+ import type { Finding } from '../model.js';
3
+ import type { RuleData } from './data.js';
4
+ export { adoptsScenes } from './query.js';
5
+ export { GUIDE_BASE } from './fixes.js';
6
+ /** A finding before paths are made relative to the checked folder. */
7
+ export type RawFinding = Omit<Finding, 'file'> & {
8
+ path: string;
9
+ };
10
+ /** Runs every rule over the app. Findings are ordered by rule, then file and line. */
11
+ export declare function checkApp(app: AppModel, data: RuleData): Promise<RawFinding[]>;
@@ -0,0 +1,253 @@
1
+ import { sceneDelegateClassNames, sceneManifest } from '../collect/app.js';
2
+ import { atLeast, majorOf } from '../core/versions.js';
3
+ import { adoptScenesFix, bridgeAlternative, fixById, flutterMigration, hookFix, installedWorkaround, } from './fixes.js';
4
+ import { adoptsScenes, appDelegateName, inAppDelegate, isSwiftUIApp, matches, sceneDelegateText, } from './query.js';
5
+ import { hookPattern, REPLACEMENTS, WINDOW_CREATION, WINDOW_LOOKUPS } from './patterns.js';
6
+ export { adoptsScenes } from './query.js';
7
+ export { GUIDE_BASE } from './fixes.js';
8
+ function ruleOf(data, id) {
9
+ const rule = data.rules.find((candidate) => candidate.id === id);
10
+ if (!rule)
11
+ throw new Error(`Rule data has no rule ${id}.`);
12
+ return rule;
13
+ }
14
+ function finding(context, rule, values) {
15
+ const definition = ruleOf(context.data, rule);
16
+ return {
17
+ rule,
18
+ severity: values.severity ?? definition.severity,
19
+ title: values.title ?? definition.title,
20
+ message: values.message,
21
+ path: values.path,
22
+ line: values.line,
23
+ target: values.target ?? null,
24
+ owner: 'app',
25
+ fix: values.fix,
26
+ alternatives: values.alternatives ?? [],
27
+ mitigation: installedWorkaround(context, rule),
28
+ sources: values.sources ?? definition.sources,
29
+ };
30
+ }
31
+ // ---------------------------------------------------------------------------------------------
32
+ // Rules
33
+ function scn001Expo(context) {
34
+ const { app, data } = context;
35
+ const { project } = app;
36
+ const expo = project.packages.get('expo');
37
+ const version = expo?.version ?? null;
38
+ const major = version ? majorOf(version) : null;
39
+ const config = project.expoConfig;
40
+ const file = config?.file ?? 'package.json';
41
+ const path = `${project.root}/${file}`;
42
+ const byDefault = majorOf(data.milestones.expoScenesByDefault) ?? 58;
43
+ const supported = majorOf(data.milestones.expoSceneSupport) ?? 57;
44
+ if (major !== null && major >= byDefault)
45
+ return [];
46
+ const prefix = 'There is no ios/ folder, so this comes from the Expo configuration:';
47
+ if (major === supported) {
48
+ const missing = [];
49
+ if (version && !atLeast(version, data.milestones.expoSceneSupport)) {
50
+ missing.push(`expo is ${version}; scene support needs ${data.milestones.expoSceneSupport} or later`);
51
+ }
52
+ const buildProperties = project.packages.get('expo-build-properties');
53
+ if (!buildProperties)
54
+ missing.push('expo-build-properties is not a dependency');
55
+ else if (buildProperties.version &&
56
+ !atLeast(buildProperties.version, data.milestones.expoBuildProperties)) {
57
+ missing.push(`expo-build-properties is ${buildProperties.version}; ${data.milestones.expoBuildProperties} or later is needed`);
58
+ }
59
+ if (config?.enableSceneSupport === null) {
60
+ return [
61
+ finding(context, 'SCN001', {
62
+ severity: 'warning',
63
+ message: `${prefix} ${file} is code, which sdkbump does not run. Built with Expo SDK ${supported} and the iOS 27 SDK, the app launches only if that config sets expo-build-properties ios.enableSceneSupport to true.${missing.length ? ` Also: ${missing.join('; ')}.` : ''}`,
64
+ path,
65
+ line: null,
66
+ fix: fixById(data, 'expo-57-scene-support'),
67
+ alternatives: bridgeAlternative(context),
68
+ }),
69
+ ];
70
+ }
71
+ if (config?.enableSceneSupport !== true) {
72
+ missing.unshift(`${file} does not set expo-build-properties ios.enableSceneSupport to true`);
73
+ }
74
+ if (missing.length === 0)
75
+ return [];
76
+ return [
77
+ finding(context, 'SCN001', {
78
+ message: `${prefix} Expo SDK ${supported} uses the scene life cycle only when you opt in, and ${missing.join('; ')}. Built with the iOS 27 SDK, the app stops at launch.`,
79
+ path,
80
+ line: null,
81
+ fix: fixById(data, 'expo-57-scene-support'),
82
+ alternatives: bridgeAlternative(context),
83
+ }),
84
+ ];
85
+ }
86
+ return [
87
+ finding(context, 'SCN001', {
88
+ message: `${prefix} expo ${version ?? expo?.declared ?? '(unknown version)'} has no scene support. Built with the iOS 27 SDK, the app stops at launch.`,
89
+ path: `${project.root}/package.json`,
90
+ line: null,
91
+ fix: fixById(data, 'expo-upgrade'),
92
+ alternatives: bridgeAlternative(context),
93
+ }),
94
+ ];
95
+ }
96
+ /**
97
+ * The target's Info.plist could not be read, so whether it declares a scene is not known. An
98
+ * error here would be a guess: the file may well have a scene manifest.
99
+ */
100
+ function scn001NotRead(context, target) {
101
+ const { app } = context;
102
+ return finding(context, 'SCN001', {
103
+ severity: 'info',
104
+ title: 'Scene configuration not checked',
105
+ message: `sdkbump could not read the Info.plist of ${target.name} (see the notes), so it cannot tell whether the app declares a scene. ${appDelegateName(app)} does not implement application(_:configurationForConnecting:options:), so built with the iOS 27 SDK the app launches only if that Info.plist has a UIApplicationSceneManifest with a scene configuration.`,
106
+ path: `${target.xcodeProject}/project.pbxproj`,
107
+ line: null,
108
+ target: target.name,
109
+ fix: null,
110
+ });
111
+ }
112
+ function scn001(context) {
113
+ const { app } = context;
114
+ if (app.project.ecosystem === 'expo' && !app.project.iosDir)
115
+ return scn001Expo(context);
116
+ const results = [];
117
+ for (const target of app.targets) {
118
+ if (adoptsScenes(app, target))
119
+ continue;
120
+ if (target.infoPlistSource === 'not-read') {
121
+ results.push(scn001NotRead(context, target));
122
+ continue;
123
+ }
124
+ const plist = target.infoPlists[0];
125
+ const manifest = plist ? sceneManifest(plist) : { present: false, configured: false };
126
+ const where = plist ? 'Info.plist' : 'The Info.plist Xcode generates';
127
+ const declared = manifest.present
128
+ ? `${where} has UIApplicationSceneManifest but no scene configuration`
129
+ : `${where} has no UIApplicationSceneManifest`;
130
+ let message = `${declared}, and ${appDelegateName(app)} does not implement application(_:configurationForConnecting:options:). Built with the iOS 27 SDK, the app stops at launch: "UIScene life cycle is required for apps built with this SDK".`;
131
+ if (app.project.ecosystem === 'flutter' && context.flutter && !context.flutter.eligible) {
132
+ message += ` Flutter will not migrate it automatically: ${context.flutter.reasons.join(' ')}`;
133
+ }
134
+ if (app.project.ecosystem === 'expo' && app.project.expoConfig?.enableSceneSupport) {
135
+ message += ` ${app.project.expoConfig.file} turns scene support on, but ios/ was generated before.`;
136
+ }
137
+ const { fix, alternatives } = adoptScenesFix(context);
138
+ results.push(finding(context, 'SCN001', {
139
+ message,
140
+ path: plist?.path ?? `${target.xcodeProject}/project.pbxproj`,
141
+ line: plist?.keyLines.get('UIApplicationSceneManifest') ?? null,
142
+ target: target.name,
143
+ fix,
144
+ alternatives,
145
+ }));
146
+ }
147
+ return results;
148
+ }
149
+ function scn002(context) {
150
+ const { app, data } = context;
151
+ if (isSwiftUIApp(app))
152
+ return [];
153
+ const results = [];
154
+ const nativeLike = app.project.ecosystem === 'native' || app.project.ecosystem === 'flutter';
155
+ for (const match of matches(app.sources.files, (file) => WINDOW_CREATION[file.language])) {
156
+ if (!inAppDelegate(app, match))
157
+ continue;
158
+ const { fix, alternatives } = nativeLike
159
+ ? { fix: fixById(data, 'native-window-in-scene'), alternatives: [] }
160
+ : adoptScenesFix(context);
161
+ results.push(finding(context, 'SCN002', {
162
+ message: `${appDelegateName(app)} creates the window with ${match.file.language === 'swift' ? 'UIWindow(frame:)' : '[[UIWindow alloc] initWithFrame:]'}. With a scene, UIKit does not show a window that belongs to no scene, and the app opens to a black screen.`,
163
+ path: match.file.path,
164
+ line: match.file.lineAt(match.offset),
165
+ fix,
166
+ alternatives,
167
+ }));
168
+ }
169
+ return results;
170
+ }
171
+ /**
172
+ * Whether the app's scene delegate passes scene events on to the app delegate: a scene delegate
173
+ * class in the sources that derives from a forwarder, or a forwarder named directly in Info.plist
174
+ * (Expo SDK 57's opt-in writes EXExpoAppSceneDelegate there and adds no class of its own).
175
+ */
176
+ function usesForwardingSceneDelegate(app, forwarders) {
177
+ const derived = app.sources.regions.some((region) => app.sources.sceneDelegates.has(region.name) &&
178
+ region.inherits.some((parent) => forwarders.includes(parent)));
179
+ if (derived)
180
+ return true;
181
+ return app.targets.some((target) => target.infoPlists.some((plist) => sceneDelegateClassNames(plist).some((name) => forwarders.includes(name))));
182
+ }
183
+ function scn003(context) {
184
+ const { app, data } = context;
185
+ const forwarding = usesForwardingSceneDelegate(app, data.sceneForwarders);
186
+ if (forwarding)
187
+ return [];
188
+ const swiftUI = isSwiftUIApp(app);
189
+ const sceneCode = sceneDelegateText(app);
190
+ const allCode = app.sources.files.map((file) => file.code).join('\n');
191
+ const results = [];
192
+ for (const hook of data.hooks) {
193
+ const replacement = REPLACEMENTS[hook.id];
194
+ if (replacement?.uikit.test(sceneCode))
195
+ continue;
196
+ if (swiftUI && replacement?.swiftUI?.test(allCode))
197
+ continue;
198
+ for (const match of matches(app.sources.files, (file) => hookPattern(hook, file.language))) {
199
+ if (!inAppDelegate(app, match))
200
+ continue;
201
+ const name = match.file.language === 'swift' ? hook.swift : hook.objc;
202
+ const what = hook.kind === 'method'
203
+ ? 'UIKit does not call this method'
204
+ : 'UIKit does not put this key in the launch options';
205
+ const message = swiftUI
206
+ ? `A SwiftUI App runs on scenes, so ${what}${hook.kind === 'method' ? ' on the adaptor delegate' : ''}. Use ${hook.swiftUI ?? hook.replacement} instead.`
207
+ : `${what} once the app uses scenes. Use ${hook.replacement}.`;
208
+ results.push(finding(context, 'SCN003', {
209
+ title: hook.kind === 'method'
210
+ ? `${name} is not called with scenes`
211
+ : `${name} is empty with scenes`,
212
+ message,
213
+ path: match.file.path,
214
+ line: match.file.lineAt(match.offset),
215
+ fix: hookFix(context, hook, swiftUI),
216
+ sources: hook.sources,
217
+ }));
218
+ }
219
+ }
220
+ return results;
221
+ }
222
+ function scn004(context) {
223
+ const { app, data } = context;
224
+ const results = [];
225
+ for (const lookup of data.windowLookups) {
226
+ const pattern = WINDOW_LOOKUPS[lookup.id];
227
+ if (!pattern)
228
+ continue;
229
+ for (const match of matches(app.sources.files, (file) => pattern[file.language])) {
230
+ if (pattern.inAppDelegateOnly && !inAppDelegate(app, match))
231
+ continue;
232
+ const flutterChannel = app.project.ecosystem === 'flutter' && lookup.id === 'app-delegate-window';
233
+ results.push(finding(context, 'SCN004', {
234
+ title: lookup.title,
235
+ message: lookup.message,
236
+ path: match.file.path,
237
+ line: match.file.lineAt(match.offset),
238
+ fix: fixById(data, flutterChannel ? 'flutter-engine-bridge' : 'window-from-scene'),
239
+ sources: lookup.sources,
240
+ }));
241
+ }
242
+ }
243
+ return results;
244
+ }
245
+ /** Runs every rule over the app. Findings are ordered by rule, then file and line. */
246
+ export async function checkApp(app, data) {
247
+ const context = { app, data };
248
+ if (app.project.ecosystem === 'flutter')
249
+ context.flutter = await flutterMigration(app, data);
250
+ const all = [...scn001(context), ...scn002(context), ...scn003(context), ...scn004(context)];
251
+ return all.sort((a, b) => a.rule.localeCompare(b.rule) || a.path.localeCompare(b.path) || (a.line ?? 0) - (b.line ?? 0));
252
+ }
253
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1,217 @@
1
+ import { z } from 'zod';
2
+ declare const Source: z.ZodObject<{
3
+ title: z.ZodString;
4
+ url: z.ZodURL;
5
+ }, z.core.$strict>;
6
+ declare const Ecosystem: z.ZodEnum<{
7
+ expo: "expo";
8
+ flutter: "flutter";
9
+ native: "native";
10
+ "react-native": "react-native";
11
+ }>;
12
+ /** The rules of this rule set. */
13
+ export declare const RULE_IDS: readonly ['SCN001', 'SCN002', 'SCN003', 'SCN004'];
14
+ export type RuleId = (typeof RULE_IDS)[number];
15
+ /** Severities, highest first. */
16
+ export declare const SEVERITIES: readonly ['error', 'warning', 'info'];
17
+ export type Severity = (typeof SEVERITIES)[number];
18
+ declare const Rule: z.ZodObject<{
19
+ id: z.ZodEnum<{
20
+ SCN001: "SCN001";
21
+ SCN002: "SCN002";
22
+ SCN003: "SCN003";
23
+ SCN004: "SCN004";
24
+ }>;
25
+ severity: z.ZodEnum<{
26
+ error: "error";
27
+ info: "info";
28
+ warning: "warning";
29
+ }>;
30
+ title: z.ZodString;
31
+ summary: z.ZodString;
32
+ sources: z.ZodArray<z.ZodObject<{
33
+ title: z.ZodString;
34
+ url: z.ZodURL;
35
+ }, z.core.$strict>>;
36
+ }, z.core.$strict>;
37
+ declare const Hook: z.ZodObject<{
38
+ id: z.ZodString;
39
+ kind: z.ZodEnum<{
40
+ "launch-option": "launch-option";
41
+ method: "method";
42
+ }>;
43
+ swift: z.ZodString;
44
+ objc: z.ZodString;
45
+ replacement: z.ZodString;
46
+ swiftUI: z.ZodNullable<z.ZodString>;
47
+ sources: z.ZodArray<z.ZodObject<{
48
+ title: z.ZodString;
49
+ url: z.ZodURL;
50
+ }, z.core.$strict>>;
51
+ }, z.core.$strict>;
52
+ declare const WindowLookup: z.ZodObject<{
53
+ id: z.ZodString;
54
+ title: z.ZodString;
55
+ api: z.ZodString;
56
+ message: z.ZodString;
57
+ sources: z.ZodArray<z.ZodObject<{
58
+ title: z.ZodString;
59
+ url: z.ZodURL;
60
+ }, z.core.$strict>>;
61
+ }, z.core.$strict>;
62
+ declare const Fix: z.ZodObject<{
63
+ id: z.ZodString;
64
+ ecosystems: z.ZodArray<z.ZodEnum<{
65
+ expo: "expo";
66
+ flutter: "flutter";
67
+ native: "native";
68
+ "react-native": "react-native";
69
+ }>>;
70
+ title: z.ZodString;
71
+ steps: z.ZodArray<z.ZodString>;
72
+ guide: z.ZodString;
73
+ sources: z.ZodArray<z.ZodObject<{
74
+ title: z.ZodString;
75
+ url: z.ZodURL;
76
+ }, z.core.$strict>>;
77
+ }, z.core.$strict>;
78
+ declare const Workaround: z.ZodObject<{
79
+ package: z.ZodString;
80
+ ecosystems: z.ZodArray<z.ZodEnum<{
81
+ expo: "expo";
82
+ flutter: "flutter";
83
+ native: "native";
84
+ "react-native": "react-native";
85
+ }>>;
86
+ mitigates: z.ZodArray<z.ZodEnum<{
87
+ SCN001: "SCN001";
88
+ SCN002: "SCN002";
89
+ SCN003: "SCN003";
90
+ SCN004: "SCN004";
91
+ }>>;
92
+ summary: z.ZodString;
93
+ limits: z.ZodArray<z.ZodString>;
94
+ sources: z.ZodArray<z.ZodObject<{
95
+ title: z.ZodString;
96
+ url: z.ZodURL;
97
+ }, z.core.$strict>>;
98
+ }, z.core.$strict>;
99
+ export declare const RuleDataSchema: z.ZodObject<{
100
+ $schema: z.ZodOptional<z.ZodString>;
101
+ ruleSet: z.ZodString;
102
+ dataVersion: z.ZodString;
103
+ sdk: z.ZodString;
104
+ rules: z.ZodArray<z.ZodObject<{
105
+ id: z.ZodEnum<{
106
+ SCN001: "SCN001";
107
+ SCN002: "SCN002";
108
+ SCN003: "SCN003";
109
+ SCN004: "SCN004";
110
+ }>;
111
+ severity: z.ZodEnum<{
112
+ error: "error";
113
+ info: "info";
114
+ warning: "warning";
115
+ }>;
116
+ title: z.ZodString;
117
+ summary: z.ZodString;
118
+ sources: z.ZodArray<z.ZodObject<{
119
+ title: z.ZodString;
120
+ url: z.ZodURL;
121
+ }, z.core.$strict>>;
122
+ }, z.core.$strict>>;
123
+ hooks: z.ZodArray<z.ZodObject<{
124
+ id: z.ZodString;
125
+ kind: z.ZodEnum<{
126
+ "launch-option": "launch-option";
127
+ method: "method";
128
+ }>;
129
+ swift: z.ZodString;
130
+ objc: z.ZodString;
131
+ replacement: z.ZodString;
132
+ swiftUI: z.ZodNullable<z.ZodString>;
133
+ sources: z.ZodArray<z.ZodObject<{
134
+ title: z.ZodString;
135
+ url: z.ZodURL;
136
+ }, z.core.$strict>>;
137
+ }, z.core.$strict>>;
138
+ windowLookups: z.ZodArray<z.ZodObject<{
139
+ id: z.ZodString;
140
+ title: z.ZodString;
141
+ api: z.ZodString;
142
+ message: z.ZodString;
143
+ sources: z.ZodArray<z.ZodObject<{
144
+ title: z.ZodString;
145
+ url: z.ZodURL;
146
+ }, z.core.$strict>>;
147
+ }, z.core.$strict>>;
148
+ fixes: z.ZodArray<z.ZodObject<{
149
+ id: z.ZodString;
150
+ ecosystems: z.ZodArray<z.ZodEnum<{
151
+ expo: "expo";
152
+ flutter: "flutter";
153
+ native: "native";
154
+ "react-native": "react-native";
155
+ }>>;
156
+ title: z.ZodString;
157
+ steps: z.ZodArray<z.ZodString>;
158
+ guide: z.ZodString;
159
+ sources: z.ZodArray<z.ZodObject<{
160
+ title: z.ZodString;
161
+ url: z.ZodURL;
162
+ }, z.core.$strict>>;
163
+ }, z.core.$strict>>;
164
+ workarounds: z.ZodArray<z.ZodObject<{
165
+ package: z.ZodString;
166
+ ecosystems: z.ZodArray<z.ZodEnum<{
167
+ expo: "expo";
168
+ flutter: "flutter";
169
+ native: "native";
170
+ "react-native": "react-native";
171
+ }>>;
172
+ mitigates: z.ZodArray<z.ZodEnum<{
173
+ SCN001: "SCN001";
174
+ SCN002: "SCN002";
175
+ SCN003: "SCN003";
176
+ SCN004: "SCN004";
177
+ }>>;
178
+ summary: z.ZodString;
179
+ limits: z.ZodArray<z.ZodString>;
180
+ sources: z.ZodArray<z.ZodObject<{
181
+ title: z.ZodString;
182
+ url: z.ZodURL;
183
+ }, z.core.$strict>>;
184
+ }, z.core.$strict>>;
185
+ milestones: z.ZodObject<{
186
+ reactNativeScenes: z.ZodString;
187
+ expoSceneSupport: z.ZodString;
188
+ expoBuildProperties: z.ZodString;
189
+ expoScenesByDefault: z.ZodString;
190
+ flutterAutoMigration: z.ZodString;
191
+ }, z.core.$strict>;
192
+ sceneForwarders: z.ZodArray<z.ZodString>;
193
+ flutterTemplates: z.ZodObject<{
194
+ source: z.ZodURL;
195
+ swift: z.ZodArray<z.ZodString>;
196
+ objcHeader: z.ZodString;
197
+ objcImplementation: z.ZodString;
198
+ }, z.core.$strict>;
199
+ }, z.core.$strict>;
200
+ export type RuleData = z.infer<typeof RuleDataSchema>;
201
+ export type Ecosystem = z.infer<typeof Ecosystem>;
202
+ export type RuleSource = z.infer<typeof Source>;
203
+ export type RuleDefinition = z.infer<typeof Rule>;
204
+ export type HookDefinition = z.infer<typeof Hook>;
205
+ export type WindowLookupDefinition = z.infer<typeof WindowLookup>;
206
+ export type FixDefinition = z.infer<typeof Fix>;
207
+ export type WorkaroundDefinition = z.infer<typeof Workaround>;
208
+ export declare class RuleDataError extends Error {
209
+ name: string;
210
+ }
211
+ /** Validates rule data; throws RuleDataError listing every problem. */
212
+ export declare function parseRuleData(value: unknown): RuleData;
213
+ /** The bundled rules/ios27.json (one level above src/ and dist/). */
214
+ export declare function loadRuleData(): RuleData;
215
+ /** JSON schema of the rule data, written to rules/schema.json. */
216
+ export declare function ruleDataJsonSchema(): unknown;
217
+ export {};