@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,108 @@
1
+ function matchingBrace(code, open) {
2
+ let depth = 0;
3
+ for (let index = open; index < code.length; index++) {
4
+ if (code[index] === '{')
5
+ depth++;
6
+ else if (code[index] === '}' && --depth === 0)
7
+ return index;
8
+ }
9
+ return code.length;
10
+ }
11
+ const KEYWORDS = new Set([
12
+ 'func',
13
+ 'var',
14
+ 'let',
15
+ 'subscript',
16
+ 'init',
17
+ 'deinit',
18
+ 'static',
19
+ 'override',
20
+ ]);
21
+ const baseName = (type) => type
22
+ .replace(/<[^>]*>/g, '')
23
+ .replace(/^(?:any|some|@\w+)\s+/g, '')
24
+ .trim()
25
+ .split('.')
26
+ .at(-1) ?? '';
27
+ export function swiftRegions(file) {
28
+ const regions = [];
29
+ const declaration = /\b(class|struct|enum|actor|extension)\s+([A-Za-z_][\w.]*)\s*(?:<[^>{]*>)?\s*(?::\s*([^{]*?))?\s*(?:\bwhere\b[^{]*)?\{/g;
30
+ for (let match = declaration.exec(file.code); match; match = declaration.exec(file.code)) {
31
+ const [, kind, name = '', inherits] = match;
32
+ if (KEYWORDS.has(name))
33
+ continue;
34
+ const open = match.index + match[0].length - 1;
35
+ const before = file.code.slice(Math.max(0, match.index - 200), match.index);
36
+ const attributes = before.slice(Math.max(before.lastIndexOf('}'), before.lastIndexOf(';')) + 1);
37
+ regions.push({
38
+ file,
39
+ kind: kind,
40
+ name: baseName(name),
41
+ inherits: (inherits ?? '').split(',').map(baseName).filter(Boolean),
42
+ start: open,
43
+ end: matchingBrace(file.code, open),
44
+ line: file.lineAt(match.index),
45
+ main: /@(?:main|UIApplicationMain)\b/.test(attributes),
46
+ });
47
+ }
48
+ return regions;
49
+ }
50
+ export function objcRegions(file) {
51
+ const regions = [];
52
+ const declaration = /@(interface|implementation)\s+(\w+)\s*(?::\s*(\w+))?\s*(?:\(\s*\w*\s*\))?\s*(?:<([^>]*)>)?/g;
53
+ for (let match = declaration.exec(file.code); match; match = declaration.exec(file.code)) {
54
+ const [, kind, name = '', superclass, protocols] = match;
55
+ const endIndex = file.code.indexOf('@end', match.index);
56
+ regions.push({
57
+ file,
58
+ kind: kind,
59
+ name,
60
+ inherits: [superclass, ...(protocols ?? '').split(',')]
61
+ .map((item) => item?.trim() ?? '')
62
+ .filter(Boolean),
63
+ start: match.index,
64
+ end: endIndex === -1 ? file.code.length : endIndex,
65
+ line: file.lineAt(match.index),
66
+ main: false,
67
+ });
68
+ }
69
+ return regions;
70
+ }
71
+ /** Classes that derive, directly or through other classes in the app, from one of `bases`. */
72
+ export function derivedFrom(regions, bases) {
73
+ const names = new Set(bases);
74
+ const found = new Set();
75
+ let changed = true;
76
+ while (changed) {
77
+ changed = false;
78
+ for (const region of regions) {
79
+ if (region.kind === 'struct' || found.has(region.name))
80
+ continue;
81
+ if (region.inherits.some((parent) => names.has(parent))) {
82
+ found.add(region.name);
83
+ names.add(region.name);
84
+ changed = true;
85
+ }
86
+ }
87
+ }
88
+ return found;
89
+ }
90
+ /** The innermost type region of `file` that contains `offset`. */
91
+ export function regionAt(index, file, offset) {
92
+ let best;
93
+ for (const region of index.regions) {
94
+ if (region.file !== file || offset < region.start || offset > region.end)
95
+ continue;
96
+ if (!best || region.start >= best.start)
97
+ best = region;
98
+ }
99
+ return best;
100
+ }
101
+ /** Whether `offset` in `file` is inside the body of a type in `names` (class or extension). */
102
+ export function insideTypeNamed(index, file, offset, names) {
103
+ return index.regions.some((region) => region.file === file &&
104
+ offset >= region.start &&
105
+ offset <= region.end &&
106
+ names.has(region.name));
107
+ }
108
+ //# sourceMappingURL=regions.js.map
@@ -0,0 +1,45 @@
1
+ export { blankCommentsAndStrings } from './blank.js';
2
+ export { insideTypeNamed, regionAt } from './regions.js';
3
+ export type Language = 'swift' | 'objc';
4
+ export interface SourceFile {
5
+ path: string;
6
+ language: Language;
7
+ /** The file as written. */
8
+ text: string;
9
+ /** The same text with comments and string contents replaced by spaces; offsets and lines match. */
10
+ code: string;
11
+ /** 1-based line of an offset. */
12
+ lineAt(offset: number): number;
13
+ }
14
+ export interface TypeRegion {
15
+ file: SourceFile;
16
+ kind: 'class' | 'struct' | 'enum' | 'actor' | 'extension' | 'interface' | 'implementation';
17
+ name: string;
18
+ /** Superclass and protocols as written, without module prefixes or generic arguments. */
19
+ inherits: string[];
20
+ /** Offsets of the body: from `{` (or the declaration for Objective-C) to its end. */
21
+ start: number;
22
+ end: number;
23
+ line: number;
24
+ /** Swift: the declaration carries `@main` or `@UIApplicationMain`. */
25
+ main: boolean;
26
+ }
27
+ export interface SourceIndex {
28
+ files: SourceFile[];
29
+ regions: TypeRegion[];
30
+ appDelegates: ReadonlySet<string>;
31
+ sceneDelegates: ReadonlySet<string>;
32
+ /** Structs that conform to SwiftUI's App. */
33
+ swiftUIApps: TypeRegion[];
34
+ }
35
+ /** Superclasses and protocols that make a class an app delegate. */
36
+ export declare const APP_DELEGATE_BASES: readonly ['UIApplicationDelegate', 'FlutterAppDelegate', 'ExpoAppDelegate', 'RCTAppDelegate', 'EXAppDelegateWrapper'];
37
+ /** Superclasses and protocols that make a class a scene delegate. */
38
+ export declare const SCENE_DELEGATE_BASES: readonly ['UIWindowSceneDelegate', 'UISceneDelegate', 'FlutterSceneDelegate', 'ExpoAppSceneDelegate'];
39
+ /** Every Swift and Objective-C file under `folder`, skipping dependencies, builds and tests. */
40
+ export declare function listSourceFiles(folder: string): Promise<string[]>;
41
+ export declare function sourceFile(path: string, text: string): SourceFile;
42
+ /** Builds the index for the given files (already read), e.g. from listSourceFiles. */
43
+ export declare function indexFiles(files: SourceFile[]): SourceIndex;
44
+ /** Reads and indexes every source file under the given folders. */
45
+ export declare function indexSources(folders: readonly string[]): Promise<SourceIndex>;
@@ -0,0 +1,99 @@
1
+ // Swift and Objective-C sources of an app: comments and string literals blanked out (so a pattern
2
+ // never matches inside them), the classes, structs and extensions they declare, and which of
3
+ // those are app delegates, scene delegates or a SwiftUI App.
4
+ import { readdir, readFile } from 'node:fs/promises';
5
+ import { extname, join } from 'node:path';
6
+ import { lineIndex } from '../core/lines.js';
7
+ import { blankCommentsAndStrings } from './blank.js';
8
+ import { derivedFrom, objcRegions, swiftRegions } from './regions.js';
9
+ export { blankCommentsAndStrings } from './blank.js';
10
+ export { insideTypeNamed, regionAt } from './regions.js';
11
+ /** Superclasses and protocols that make a class an app delegate. */
12
+ export const APP_DELEGATE_BASES = [
13
+ 'UIApplicationDelegate',
14
+ 'FlutterAppDelegate',
15
+ 'ExpoAppDelegate',
16
+ 'RCTAppDelegate',
17
+ 'EXAppDelegateWrapper',
18
+ ];
19
+ /** Superclasses and protocols that make a class a scene delegate. */
20
+ export const SCENE_DELEGATE_BASES = [
21
+ 'UIWindowSceneDelegate',
22
+ 'UISceneDelegate',
23
+ 'FlutterSceneDelegate',
24
+ 'ExpoAppSceneDelegate',
25
+ ];
26
+ const SOURCE_EXTENSIONS = {
27
+ '.swift': 'swift',
28
+ '.m': 'objc',
29
+ '.mm': 'objc',
30
+ '.h': 'objc',
31
+ };
32
+ /** Folders that hold dependencies, build output or tests rather than the app's own code. */
33
+ const SKIPPED = /^(Pods|node_modules|build|DerivedData|\.build|Carthage|\.git|\.symlinks|Flutter|.*Tests|.*\.xcodeproj|.*\.xcworkspace|.*\.xcassets|.*\.framework|.*\.xcframework)$/;
34
+ /** Every Swift and Objective-C file under `folder`, skipping dependencies, builds and tests. */
35
+ export async function listSourceFiles(folder) {
36
+ const found = [];
37
+ const walk = async (dir, depth) => {
38
+ if (depth > 12)
39
+ return;
40
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
41
+ for (const entry of entries) {
42
+ if (entry.isDirectory()) {
43
+ if (!SKIPPED.test(entry.name))
44
+ await walk(join(dir, entry.name), depth + 1);
45
+ }
46
+ else if (entry.isFile() && SOURCE_EXTENSIONS[extname(entry.name)]) {
47
+ found.push(join(dir, entry.name));
48
+ }
49
+ }
50
+ };
51
+ await walk(folder, 0);
52
+ return found.sort();
53
+ }
54
+ /** App delegates named outside their declaration: SwiftUI's adaptor and UIApplicationMain. */
55
+ function namedAppDelegates(files) {
56
+ const names = [];
57
+ for (const file of files) {
58
+ for (const match of file.code.matchAll(/@UIApplicationDelegateAdaptor\s*\(\s*(\w+)\.self/g)) {
59
+ if (match[1])
60
+ names.push(match[1]);
61
+ }
62
+ for (const match of file.code.matchAll(/UIApplicationMain\s*\([^;]*NSStringFromClass\s*\(\s*\[\s*(\w+)\s+class\s*\]\s*\)/g)) {
63
+ if (match[1])
64
+ names.push(match[1]);
65
+ }
66
+ }
67
+ return names;
68
+ }
69
+ export function sourceFile(path, text) {
70
+ const language = SOURCE_EXTENSIONS[extname(path)] ?? 'swift';
71
+ return {
72
+ path,
73
+ language,
74
+ text,
75
+ code: blankCommentsAndStrings(text, language),
76
+ lineAt: lineIndex(text),
77
+ };
78
+ }
79
+ /** Builds the index for the given files (already read), e.g. from listSourceFiles. */
80
+ export function indexFiles(files) {
81
+ const regions = files.flatMap((file) => file.language === 'swift' ? swiftRegions(file) : objcRegions(file));
82
+ const appDelegates = derivedFrom(regions, APP_DELEGATE_BASES);
83
+ for (const name of namedAppDelegates(files))
84
+ appDelegates.add(name);
85
+ return {
86
+ files,
87
+ regions,
88
+ appDelegates,
89
+ sceneDelegates: derivedFrom(regions, SCENE_DELEGATE_BASES),
90
+ swiftUIApps: regions.filter((region) => region.kind === 'struct' && region.inherits.includes('App')),
91
+ };
92
+ }
93
+ /** Reads and indexes every source file under the given folders. */
94
+ export async function indexSources(folders) {
95
+ const paths = [...new Set((await Promise.all(folders.map(listSourceFiles))).flat())].sort();
96
+ const files = await Promise.all(paths.map(async (path) => sourceFile(path, await readFile(path, 'utf8'))));
97
+ return indexFiles(files);
98
+ }
99
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1,22 @@
1
+ export type ProjectFile = {
2
+ found: true;
3
+ bytes: Buffer;
4
+ } | {
5
+ found: false;
6
+ reason: 'missing' | 'outside' | 'not-file';
7
+ };
8
+ /** How a report says why a file was not read, after the file's name. */
9
+ export declare const NOT_READ: {
10
+ readonly missing: 'does not exist.';
11
+ readonly outside: 'is outside the project; it was not read.';
12
+ readonly 'not-file': 'is not a file; it was not read.';
13
+ };
14
+ /** Whether `path` is `folder` or inside it (both already resolved with realpath). */
15
+ export declare function isInside(path: string, folder: string): boolean;
16
+ /**
17
+ * Reads `path` when it is a regular file inside `root`. With `root` null only the regular-file
18
+ * check applies, for files a package manager may link from outside the project (node_modules).
19
+ */
20
+ export declare function readProjectFile(path: string, root: string | null): Promise<ProjectFile>;
21
+ /** The text of `path` (see readProjectFile), or null when it is not read. */
22
+ export declare function readProjectText(path: string, root: string | null): Promise<string | null>;
@@ -0,0 +1,47 @@
1
+ // Reads files of the checked project. Their names and symbolic links come from the project, so a
2
+ // file is read only when it is a regular file and, unless the caller says otherwise, inside the
3
+ // project folder: a link to /dev/zero or a FIFO would never end, and a link out of the project
4
+ // would read a file the user did not ask to check.
5
+ import { readFile, realpath, stat } from 'node:fs/promises';
6
+ import { isAbsolute, relative, sep } from 'node:path';
7
+ /** How a report says why a file was not read, after the file's name. */
8
+ export const NOT_READ = {
9
+ missing: 'does not exist.',
10
+ outside: 'is outside the project; it was not read.',
11
+ 'not-file': 'is not a file; it was not read.',
12
+ };
13
+ /** Whether `path` is `folder` or inside it (both already resolved with realpath). */
14
+ export function isInside(path, folder) {
15
+ const rest = relative(folder, path);
16
+ return rest !== '..' && !rest.startsWith(`..${sep}`) && !isAbsolute(rest);
17
+ }
18
+ /**
19
+ * Reads `path` when it is a regular file inside `root`. With `root` null only the regular-file
20
+ * check applies, for files a package manager may link from outside the project (node_modules).
21
+ */
22
+ export async function readProjectFile(path, root) {
23
+ let real;
24
+ try {
25
+ real = await realpath(path);
26
+ }
27
+ catch {
28
+ return { found: false, reason: 'missing' };
29
+ }
30
+ if (root !== null && !isInside(real, await realpath(root).catch(() => root))) {
31
+ return { found: false, reason: 'outside' };
32
+ }
33
+ try {
34
+ if (!(await stat(real)).isFile())
35
+ return { found: false, reason: 'not-file' };
36
+ return { found: true, bytes: await readFile(real) };
37
+ }
38
+ catch {
39
+ return { found: false, reason: 'missing' };
40
+ }
41
+ }
42
+ /** The text of `path` (see readProjectFile), or null when it is not read. */
43
+ export async function readProjectText(path, root) {
44
+ const file = await readProjectFile(path, root);
45
+ return file.found ? file.bytes.toString('utf8') : null;
46
+ }
47
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,2 @@
1
+ /** Returns a function from a 0-based offset in `text` to its 1-based line. */
2
+ export declare function lineIndex(text: string): (offset: number) => number;
@@ -0,0 +1,21 @@
1
+ // Line numbers for offsets into a text, for reports that point at a line of a file.
2
+ /** Returns a function from a 0-based offset in `text` to its 1-based line. */
3
+ export function lineIndex(text) {
4
+ const starts = [0];
5
+ for (let index = text.indexOf('\n'); index !== -1; index = text.indexOf('\n', index + 1)) {
6
+ starts.push(index + 1);
7
+ }
8
+ return (offset) => {
9
+ let low = 0;
10
+ let high = starts.length - 1;
11
+ while (low < high) {
12
+ const middle = (low + high + 1) >> 1;
13
+ if ((starts[middle] ?? 0) <= offset)
14
+ low = middle;
15
+ else
16
+ high = middle - 1;
17
+ }
18
+ return low + 1;
19
+ };
20
+ }
21
+ //# sourceMappingURL=lines.js.map
@@ -0,0 +1,18 @@
1
+ export interface Version {
2
+ major: number;
3
+ minor: number;
4
+ patch: number;
5
+ prerelease: string[];
6
+ }
7
+ export declare function parseVersion(text: string): Version | null;
8
+ /** Semantic version order; a prerelease sorts before its release. Unparsable text sorts first. */
9
+ export declare function compareVersions(left: string, right: string): number;
10
+ export declare const atLeast: (version: string, minimum: string) => boolean;
11
+ /**
12
+ * The lowest version a declared npm range allows, when that can be read without the registry:
13
+ * "0.87.1", "^0.87.1", "~57.0.29", ">=0.88.0-rc.1", "57.x". Returns null for tags ("latest"),
14
+ * "*", URLs, workspace and file references, and ranges with "||" or "<".
15
+ */
16
+ export declare function lowestAllowed(range: string): string | null;
17
+ /** The major version, e.g. 57 for "57.0.29". */
18
+ export declare function majorOf(version: string): number | null;
@@ -0,0 +1,63 @@
1
+ // Version numbers as npm and Flutter write them: compare them and find the lowest version a
2
+ // declared range (^1.2.3, ~57.0.29, >=0.88.0-rc.1) allows.
3
+ const VERSION = /^v?(\d+)\.(\d+)(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
4
+ export function parseVersion(text) {
5
+ const match = VERSION.exec(text.trim());
6
+ if (!match)
7
+ return null;
8
+ return {
9
+ major: Number(match[1]),
10
+ minor: Number(match[2]),
11
+ patch: Number(match[3] ?? 0),
12
+ prerelease: match[4] ? match[4].split('.') : [],
13
+ };
14
+ }
15
+ function comparePrerelease(left, right) {
16
+ if (left.length === 0 || right.length === 0)
17
+ return Math.sign(right.length - left.length);
18
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
19
+ const a = left[index];
20
+ const b = right[index];
21
+ if (a === undefined)
22
+ return -1;
23
+ if (b === undefined)
24
+ return 1;
25
+ const numeric = /^\d+$/.test(a) && /^\d+$/.test(b);
26
+ const order = numeric ? Number(a) - Number(b) : a < b ? -1 : a > b ? 1 : 0;
27
+ if (order !== 0)
28
+ return Math.sign(order);
29
+ }
30
+ return 0;
31
+ }
32
+ /** Semantic version order; a prerelease sorts before its release. Unparsable text sorts first. */
33
+ export function compareVersions(left, right) {
34
+ const a = parseVersion(left);
35
+ const b = parseVersion(right);
36
+ if (!a || !b)
37
+ return a ? 1 : b ? -1 : 0;
38
+ return (Math.sign(a.major - b.major) ||
39
+ Math.sign(a.minor - b.minor) ||
40
+ Math.sign(a.patch - b.patch) ||
41
+ comparePrerelease(a.prerelease, b.prerelease));
42
+ }
43
+ export const atLeast = (version, minimum) => compareVersions(version, minimum) >= 0;
44
+ /**
45
+ * The lowest version a declared npm range allows, when that can be read without the registry:
46
+ * "0.87.1", "^0.87.1", "~57.0.29", ">=0.88.0-rc.1", "57.x". Returns null for tags ("latest"),
47
+ * "*", URLs, workspace and file references, and ranges with "||" or "<".
48
+ */
49
+ export function lowestAllowed(range) {
50
+ const text = range.trim();
51
+ if (text.includes('||') || text.includes('<') || /[:/]/.test(text))
52
+ return null;
53
+ const match = /^(?:\^|~|>=|=)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(-[0-9A-Za-z.-]+)?$/.exec(text);
54
+ if (!match)
55
+ return null;
56
+ const part = (value) => value === undefined || /[x*]/.test(value) ? '0' : value;
57
+ return `${match[1]}.${part(match[2])}.${part(match[3])}${match[4] ?? ''}`;
58
+ }
59
+ /** The major version, e.g. 57 for "57.0.29". */
60
+ export function majorOf(version) {
61
+ return parseVersion(version)?.major ?? null;
62
+ }
63
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1,46 @@
1
+ export type Ecosystem = 'native' | 'flutter' | 'react-native' | 'expo';
2
+ export interface PackageVersion {
3
+ name: string;
4
+ /** Exact version when installed or locked, otherwise the lowest the declared range allows. */
5
+ version: string | null;
6
+ /** Where `version` came from. */
7
+ source: 'installed' | 'lockfile' | 'declared';
8
+ /** The range in package.json. */
9
+ declared: string;
10
+ }
11
+ export interface FlutterInfo {
12
+ /** From .fvmrc or .fvm/fvm_config.json; null when the project does not pin one. */
13
+ version: string | null;
14
+ /** pubspec.yaml sets `enable-uiscene-migration: false`. */
15
+ migrationDisabled: boolean;
16
+ plugin: boolean;
17
+ }
18
+ export interface ExpoConfig {
19
+ /** app.json (read) or app.config.js/ts (not evaluated). */
20
+ file: string;
21
+ /** expo-build-properties `ios.enableSceneSupport`; null when the config cannot be read. */
22
+ enableSceneSupport: boolean | null;
23
+ }
24
+ export interface Project {
25
+ root: string;
26
+ name: string;
27
+ ecosystem: Ecosystem;
28
+ /** The folder with the iOS app's Xcode project; null for an Expo app without ios/. */
29
+ iosDir: string | null;
30
+ /** Absolute paths of the .xcodeproj folders of the app. */
31
+ xcodeProjects: string[];
32
+ /** npm packages that decide the fix (react-native, expo, workarounds), when present. */
33
+ packages: ReadonlyMap<string, PackageVersion>;
34
+ flutter: FlutterInfo | null;
35
+ expoConfig: ExpoConfig | null;
36
+ }
37
+ export declare class ProjectError extends Error {
38
+ name: string;
39
+ }
40
+ /** npm packages whose version or presence changes the advice. */
41
+ export declare const WATCHED_PACKAGES: readonly ['react-native', 'expo', 'expo-build-properties', 'react-native-uiscene-bridge', 'react-native-xcode27'];
42
+ /**
43
+ * Detects the project at `path`: a project folder, or an .xcodeproj folder for a native app.
44
+ * Throws ProjectError when there is nothing iOS to check.
45
+ */
46
+ export declare function detectProject(path: string): Promise<Project>;