@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,260 @@
1
+ // Reads the app targets of an Xcode project from project.pbxproj (an old-style "OpenStep"
2
+ // property list): their names, product types and the build settings that decide which Info.plist
3
+ // they use and which platform they build for.
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { NOT_READ, readProjectFile } from '../core/files.js';
6
+ export class XcodeProjectError extends Error {
7
+ name = 'XcodeProjectError';
8
+ }
9
+ /** Parses an old-style property list such as project.pbxproj. */
10
+ export function parseOpenStep(text) {
11
+ let index = 0;
12
+ const fail = (message) => {
13
+ const line = text.slice(0, index).split('\n').length;
14
+ throw new XcodeProjectError(`${message} (line ${line})`);
15
+ };
16
+ const skip = () => {
17
+ for (;;) {
18
+ const char = text[index];
19
+ if (char === undefined)
20
+ return;
21
+ if (/\s/.test(char))
22
+ index++;
23
+ else if (text.startsWith('//', index)) {
24
+ const end = text.indexOf('\n', index);
25
+ index = end === -1 ? text.length : end + 1;
26
+ }
27
+ else if (text.startsWith('/*', index)) {
28
+ const end = text.indexOf('*/', index + 2);
29
+ if (end === -1)
30
+ fail('Unclosed comment');
31
+ index = end + 2;
32
+ }
33
+ else
34
+ return;
35
+ }
36
+ };
37
+ const quoted = () => {
38
+ index++;
39
+ let result = '';
40
+ for (;;) {
41
+ const char = text[index++];
42
+ if (char === undefined)
43
+ fail('Unclosed string');
44
+ if (char === '"')
45
+ return result;
46
+ if (char !== '\\') {
47
+ result += char;
48
+ continue;
49
+ }
50
+ const escaped = text[index++];
51
+ if (escaped === 'n')
52
+ result += '\n';
53
+ else if (escaped === 't')
54
+ result += '\t';
55
+ else if (escaped === 'r')
56
+ result += '\r';
57
+ else if (escaped === 'U' || escaped === 'u') {
58
+ const hex = text.slice(index, index + 4);
59
+ if (!/^[0-9a-fA-F]{4}$/.test(hex))
60
+ fail('Bad unicode escape');
61
+ result += String.fromCharCode(Number.parseInt(hex, 16));
62
+ index += 4;
63
+ }
64
+ else if (escaped !== undefined)
65
+ result += escaped;
66
+ }
67
+ };
68
+ const bare = () => {
69
+ const match = /^[A-Za-z0-9_$+./:-]+/.exec(text.slice(index, index + 4096));
70
+ if (!match)
71
+ return fail(`Unexpected character "${text[index] ?? 'end of file'}"`);
72
+ index += match[0].length;
73
+ return match[0];
74
+ };
75
+ const value = () => {
76
+ skip();
77
+ const char = text[index];
78
+ if (char === '{') {
79
+ index++;
80
+ const dict = {};
81
+ for (;;) {
82
+ skip();
83
+ if (text[index] === '}') {
84
+ index++;
85
+ return dict;
86
+ }
87
+ const key = text[index] === '"' ? quoted() : bare();
88
+ skip();
89
+ if (text[index] !== '=')
90
+ fail('Expected "="');
91
+ index++;
92
+ dict[key] = value();
93
+ skip();
94
+ if (text[index] !== ';')
95
+ fail('Expected ";"');
96
+ index++;
97
+ }
98
+ }
99
+ if (char === '(') {
100
+ index++;
101
+ const array = [];
102
+ for (;;) {
103
+ skip();
104
+ if (text[index] === ')') {
105
+ index++;
106
+ return array;
107
+ }
108
+ array.push(value());
109
+ skip();
110
+ if (text[index] === ',')
111
+ index++;
112
+ else if (text[index] !== ')')
113
+ fail('Expected "," or ")"');
114
+ }
115
+ }
116
+ if (char === '"')
117
+ return quoted();
118
+ if (char === '<') {
119
+ const end = text.indexOf('>', index);
120
+ if (end === -1)
121
+ fail('Unclosed data');
122
+ const data = text.slice(index + 1, end).replace(/\s+/g, '');
123
+ index = end + 1;
124
+ return data;
125
+ }
126
+ return bare();
127
+ };
128
+ const result = value();
129
+ skip();
130
+ if (index < text.length)
131
+ fail('Unexpected text after the property list');
132
+ return result;
133
+ }
134
+ const asDict = (value) => typeof value === 'object' && !Array.isArray(value) ? value : undefined;
135
+ const asString = (value) => typeof value === 'string' ? value : undefined;
136
+ const asArray = (value) => Array.isArray(value) ? value : [];
137
+ function settingsOf(dict) {
138
+ const result = {};
139
+ for (const [key, value] of Object.entries(dict ?? {})) {
140
+ if (typeof value === 'string')
141
+ result[key] = value;
142
+ else if (Array.isArray(value))
143
+ result[key] = value.filter((item) => typeof item === 'string');
144
+ }
145
+ return result;
146
+ }
147
+ function configurationsOf(objects, listId) {
148
+ const result = new Map();
149
+ const list = asDict(objects[listId ?? '']);
150
+ for (const id of asArray(list?.buildConfigurations)) {
151
+ const configuration = asDict(objects[asString(id) ?? '']);
152
+ const name = asString(configuration?.name);
153
+ if (configuration && name)
154
+ result.set(name, settingsOf(asDict(configuration.buildSettings)));
155
+ }
156
+ return result;
157
+ }
158
+ /** Reads the native targets of a parsed project.pbxproj. */
159
+ export function readTargets(pbxproj) {
160
+ const objects = asDict(asDict(pbxproj)?.objects);
161
+ const rootId = asString(asDict(pbxproj)?.rootObject);
162
+ if (!objects || !rootId)
163
+ throw new XcodeProjectError('project.pbxproj has no objects.');
164
+ const project = asDict(objects[rootId]);
165
+ if (!project)
166
+ throw new XcodeProjectError('project.pbxproj has no root object.');
167
+ const projectConfigurations = configurationsOf(objects, asString(project.buildConfigurationList));
168
+ const targets = [];
169
+ for (const id of asArray(project.targets)) {
170
+ const target = asDict(objects[asString(id) ?? '']);
171
+ if (asString(target?.isa) !== 'PBXNativeTarget' || !target)
172
+ continue;
173
+ const configurations = {};
174
+ for (const [name, settings] of configurationsOf(objects, asString(target.buildConfigurationList))) {
175
+ configurations[name] = { ...projectConfigurations.get(name), ...settings };
176
+ }
177
+ targets.push({
178
+ name: asString(target.name) ?? asString(target.productName) ?? 'unnamed',
179
+ productType: asString(target.productType) ?? '',
180
+ configurations,
181
+ });
182
+ }
183
+ return targets;
184
+ }
185
+ /**
186
+ * Reads `<path>.xcodeproj/project.pbxproj` when it is a regular file inside `root` (by default
187
+ * the .xcodeproj folder itself).
188
+ */
189
+ export async function readXcodeProject(path, root = path) {
190
+ const file = join(path, 'project.pbxproj');
191
+ const read = await readProjectFile(file, root);
192
+ if (!read.found)
193
+ throw new XcodeProjectError(`${file} ${NOT_READ[read.reason]}`);
194
+ const text = read.bytes.toString('utf8');
195
+ return { path, sourceRoot: dirname(resolve(path)), targets: readTargets(parseOpenStep(text)) };
196
+ }
197
+ const APP_PRODUCT_TYPES = new Set([
198
+ 'com.apple.product-type.application',
199
+ 'com.apple.product-type.application.on-demand-install-capable', // App Clip
200
+ ]);
201
+ const setting = (settings, key) => {
202
+ const value = settings[key];
203
+ return typeof value === 'string' ? value : value?.join(' ');
204
+ };
205
+ /** Whether the target builds with the iOS SDK (SDKROOT iphoneos, or `auto` listing iphoneos). */
206
+ export function buildsForIOS(settings) {
207
+ const sdk = setting(settings, 'SDKROOT');
208
+ if (sdk === undefined)
209
+ return false;
210
+ if (sdk.startsWith('iphoneos'))
211
+ return true;
212
+ if (sdk === 'auto')
213
+ return /\biphoneos\b/.test(setting(settings, 'SUPPORTED_PLATFORMS') ?? '');
214
+ return false;
215
+ }
216
+ /** iOS app targets (apps and App Clips): the ones UIKit's launch check applies to. */
217
+ export function iosAppTargets(project) {
218
+ return project.targets.filter((target) => APP_PRODUCT_TYPES.has(target.productType) &&
219
+ Object.values(target.configurations).some(buildsForIOS));
220
+ }
221
+ const VARIABLE = /\$\(([A-Za-z0-9_]+)\)|\$\{([A-Za-z0-9_]+)\}/g;
222
+ /**
223
+ * The Info.plist files a target's configurations name in INFOPLIST_FILE, as absolute paths.
224
+ * $(SRCROOT), $(PROJECT_DIR), $(TARGET_NAME) and $(PRODUCT_NAME) are expanded; a path that still
225
+ * contains an unknown variable is returned in `unresolved`.
226
+ */
227
+ export function infoPlistFiles(project, target) {
228
+ const files = new Set();
229
+ const unresolved = new Set();
230
+ for (const settings of Object.values(target.configurations)) {
231
+ const raw = setting(settings, 'INFOPLIST_FILE');
232
+ if (!raw)
233
+ continue;
234
+ const known = {
235
+ SRCROOT: project.sourceRoot,
236
+ PROJECT_DIR: project.sourceRoot,
237
+ TARGET_NAME: target.name,
238
+ PRODUCT_NAME: setting(settings, 'PRODUCT_NAME')?.replace(VARIABLE, target.name) ?? target.name,
239
+ };
240
+ let missing = false;
241
+ const expanded = raw.replace(VARIABLE, (whole, round, curly) => {
242
+ const value = known[round ?? curly ?? ''];
243
+ if (value === undefined)
244
+ missing = true;
245
+ return value ?? whole;
246
+ });
247
+ if (missing)
248
+ unresolved.add(raw);
249
+ else
250
+ files.add(resolve(project.sourceRoot, expanded));
251
+ }
252
+ return { files: [...files], unresolved: [...unresolved] };
253
+ }
254
+ /** Whether every configuration of the target has Xcode generate (part of) its Info.plist. */
255
+ export function generatesInfoPlist(target) {
256
+ const values = Object.values(target.configurations);
257
+ return (values.length > 0 &&
258
+ values.every((settings) => setting(settings, 'GENERATE_INFOPLIST_FILE') === 'YES'));
259
+ }
260
+ //# sourceMappingURL=pbxproj.js.map
@@ -0,0 +1,2 @@
1
+ import { type PlistValue } from './plist-value.js';
2
+ export declare function parseBinaryPlist(bytes: Uint8Array): PlistValue;
@@ -0,0 +1,123 @@
1
+ // Binary property lists (bplist00), the format found in built apps:
2
+ // https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c
3
+ import { BINARY_MAGIC, PlistError } from './plist-value.js';
4
+ const APPLE_EPOCH_MS = Date.UTC(2001, 0, 1);
5
+ export function parseBinaryPlist(bytes) {
6
+ if (bytes.length < BINARY_MAGIC.length + 32)
7
+ throw new PlistError('Binary plist is too short.');
8
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9
+ const trailer = bytes.length - 32;
10
+ const offsetSize = view.getUint8(trailer + 6);
11
+ const refSize = view.getUint8(trailer + 7);
12
+ const objectCount = Number(view.getBigUint64(trailer + 8));
13
+ const topObject = Number(view.getBigUint64(trailer + 16));
14
+ const tableOffset = Number(view.getBigUint64(trailer + 24));
15
+ if (offsetSize < 1 || offsetSize > 8 || refSize < 1 || refSize > 8) {
16
+ throw new PlistError('Binary plist trailer is not valid.');
17
+ }
18
+ if (tableOffset + objectCount * offsetSize > trailer || topObject >= objectCount) {
19
+ throw new PlistError('Binary plist offset table is out of range.');
20
+ }
21
+ const readUint = (at, size) => {
22
+ if (at + size > bytes.length)
23
+ throw new PlistError('Binary plist reads past its end.');
24
+ let value = 0;
25
+ for (let index = 0; index < size; index++)
26
+ value = value * 256 + (bytes[at + index] ?? 0);
27
+ return value;
28
+ };
29
+ const offsetOf = (ref) => readUint(tableOffset + ref * offsetSize, offsetSize);
30
+ const visiting = new Set();
31
+ const lengthAt = (at, info) => {
32
+ if (info !== 0xf)
33
+ return [info, at + 1];
34
+ const marker = bytes[at + 1] ?? 0;
35
+ if (marker >> 4 !== 0x1)
36
+ throw new PlistError('Binary plist length is not an integer.');
37
+ const size = 1 << (marker & 0xf);
38
+ return [readUint(at + 2, size), at + 2 + size];
39
+ };
40
+ const read = (ref) => {
41
+ if (ref >= objectCount)
42
+ throw new PlistError('Binary plist object reference is out of range.');
43
+ if (visiting.has(ref))
44
+ throw new PlistError('Binary plist contains a reference cycle.');
45
+ visiting.add(ref);
46
+ try {
47
+ return readObject(offsetOf(ref));
48
+ }
49
+ finally {
50
+ visiting.delete(ref);
51
+ }
52
+ };
53
+ const readObject = (at) => {
54
+ const marker = bytes[at];
55
+ if (marker === undefined)
56
+ throw new PlistError('Binary plist object is out of range.');
57
+ const type = marker >> 4;
58
+ const info = marker & 0xf;
59
+ switch (type) {
60
+ case 0x0:
61
+ if (info === 0x8)
62
+ return false;
63
+ if (info === 0x9)
64
+ return true;
65
+ throw new PlistError(`Unsupported binary plist marker 0x${marker.toString(16)}.`);
66
+ case 0x1: {
67
+ const size = 1 << info;
68
+ if (size === 16)
69
+ return view.getBigInt64(at + 9);
70
+ if (size === 8) {
71
+ const big = view.getBigInt64(at + 1);
72
+ return Number.isSafeInteger(Number(big)) ? Number(big) : big;
73
+ }
74
+ return readUint(at + 1, size);
75
+ }
76
+ case 0x2:
77
+ return info === 2 ? view.getFloat32(at + 1) : view.getFloat64(at + 1);
78
+ case 0x3:
79
+ return new Date(APPLE_EPOCH_MS + view.getFloat64(at + 1) * 1000);
80
+ case 0x4: {
81
+ const [length, start] = lengthAt(at, info);
82
+ return Uint8Array.from(bytes.subarray(start, start + length));
83
+ }
84
+ case 0x5: {
85
+ const [length, start] = lengthAt(at, info);
86
+ return new TextDecoder('latin1').decode(bytes.subarray(start, start + length));
87
+ }
88
+ case 0x6: {
89
+ const [length, start] = lengthAt(at, info);
90
+ let text = '';
91
+ for (let index = 0; index < length; index++) {
92
+ text += String.fromCharCode(readUint(start + index * 2, 2));
93
+ }
94
+ return text;
95
+ }
96
+ case 0x8:
97
+ return readUint(at + 1, info + 1);
98
+ case 0xa: {
99
+ const [length, start] = lengthAt(at, info);
100
+ const items = [];
101
+ for (let index = 0; index < length; index++) {
102
+ items.push(read(readUint(start + index * refSize, refSize)));
103
+ }
104
+ return items;
105
+ }
106
+ case 0xd: {
107
+ const [length, start] = lengthAt(at, info);
108
+ const dict = {};
109
+ for (let index = 0; index < length; index++) {
110
+ const key = read(readUint(start + index * refSize, refSize));
111
+ if (typeof key !== 'string')
112
+ throw new PlistError('Binary plist dictionary key is not a string.');
113
+ dict[key] = read(readUint(start + (length + index) * refSize, refSize));
114
+ }
115
+ return dict;
116
+ }
117
+ default:
118
+ throw new PlistError(`Unsupported binary plist marker 0x${marker.toString(16)}.`);
119
+ }
120
+ };
121
+ return read(topObject);
122
+ }
123
+ //# sourceMappingURL=plist-binary.js.map
@@ -0,0 +1,15 @@
1
+ export type PlistValue = string | number | bigint | boolean | Date | Uint8Array | PlistValue[] | PlistDict;
2
+ export interface PlistDict {
3
+ [key: string]: PlistValue;
4
+ }
5
+ export interface ParsedPlist {
6
+ value: PlistValue;
7
+ /** 1-based line of each top-level `<key>` in an XML plist; empty for binary plists. */
8
+ keyLines: ReadonlyMap<string, number>;
9
+ }
10
+ export declare class PlistError extends Error {
11
+ name: string;
12
+ }
13
+ /** The first bytes of a binary property list. */
14
+ export declare const BINARY_MAGIC = "bplist00";
15
+ export declare function isDict(value: PlistValue | undefined): value is PlistDict;
@@ -0,0 +1,14 @@
1
+ // The values a property list holds and the error the plist readers throw.
2
+ export class PlistError extends Error {
3
+ name = 'PlistError';
4
+ }
5
+ /** The first bytes of a binary property list. */
6
+ export const BINARY_MAGIC = 'bplist00';
7
+ export function isDict(value) {
8
+ return (typeof value === 'object' &&
9
+ value !== null &&
10
+ !Array.isArray(value) &&
11
+ !(value instanceof Date) &&
12
+ !(value instanceof Uint8Array));
13
+ }
14
+ //# sourceMappingURL=plist-value.js.map
@@ -0,0 +1,2 @@
1
+ import { type ParsedPlist } from './plist-value.js';
2
+ export declare function parseXmlPlist(xml: string): ParsedPlist;
@@ -0,0 +1,195 @@
1
+ // XML property lists, the format Xcode writes into projects. Only what Info.plist files use.
2
+ import { lineIndex } from '../core/lines.js';
3
+ import { PlistError } from './plist-value.js';
4
+ const ENTITIES = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'" };
5
+ function decodeEntities(text) {
6
+ return text.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-z]+);/g, (whole, entity) => {
7
+ if (entity.startsWith('#x'))
8
+ return String.fromCodePoint(Number.parseInt(entity.slice(2), 16));
9
+ if (entity.startsWith('#'))
10
+ return String.fromCodePoint(Number.parseInt(entity.slice(1), 10));
11
+ return ENTITIES[entity] ?? whole;
12
+ });
13
+ }
14
+ function tokenize(xml) {
15
+ const tokens = [];
16
+ const tag = /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!DOCTYPE[^>]*>|<!\[CDATA\[([\s\S]*?)\]\]>|<(\/?)([A-Za-z_][\w.-]*)[^>]*?(\/?)>/g;
17
+ let last = 0;
18
+ for (let match = tag.exec(xml); match; match = tag.exec(xml)) {
19
+ if (match.index > last) {
20
+ tokens.push({
21
+ kind: 'text',
22
+ name: '',
23
+ text: decodeEntities(xml.slice(last, match.index)),
24
+ offset: last,
25
+ });
26
+ }
27
+ last = tag.lastIndex;
28
+ if (match[1] !== undefined) {
29
+ tokens.push({ kind: 'text', name: '', text: match[1], offset: match.index });
30
+ }
31
+ else if (match[3]) {
32
+ const kind = match[2] ? 'close' : match[4] ? 'empty' : 'open';
33
+ tokens.push({ kind, name: match[3], text: '', offset: match.index });
34
+ }
35
+ }
36
+ if (last < xml.length) {
37
+ tokens.push({ kind: 'text', name: '', text: decodeEntities(xml.slice(last)), offset: last });
38
+ }
39
+ // Text between elements is whitespace except inside <key>, <string> and friends; merging
40
+ // adjacent text (CDATA next to plain text) keeps one text token per element.
41
+ const merged = [];
42
+ for (const token of tokens) {
43
+ const previous = merged.at(-1);
44
+ if (token.kind === 'text' && previous?.kind === 'text')
45
+ previous.text += token.text;
46
+ else
47
+ merged.push(token);
48
+ }
49
+ return merged;
50
+ }
51
+ class XmlReader {
52
+ tokens;
53
+ lineAt;
54
+ index = 0;
55
+ keyLines = new Map();
56
+ constructor(tokens, lineAt) {
57
+ this.tokens = tokens;
58
+ this.lineAt = lineAt;
59
+ }
60
+ next() {
61
+ while (this.index < this.tokens.length) {
62
+ const token = this.tokens[this.index++];
63
+ if (token && !(token.kind === 'text' && token.text.trim() === ''))
64
+ return token;
65
+ }
66
+ return undefined;
67
+ }
68
+ peek() {
69
+ const saved = this.index;
70
+ const token = this.next();
71
+ this.index = saved;
72
+ return token;
73
+ }
74
+ textUntil(name) {
75
+ let text = '';
76
+ for (let token = this.tokens[this.index++]; token; token = this.tokens[this.index++]) {
77
+ if (token.kind === 'text')
78
+ text += token.text;
79
+ else if (token.kind === 'close' && token.name === name)
80
+ return text;
81
+ else
82
+ throw new PlistError(`Unexpected <${token.name}> inside <${name}>.`);
83
+ }
84
+ throw new PlistError(`<${name}> is not closed.`);
85
+ }
86
+ document() {
87
+ const root = this.next();
88
+ if (root?.name !== 'plist' || root.kind !== 'open') {
89
+ throw new PlistError('Not a property list: no <plist> element.');
90
+ }
91
+ const value = this.value(true);
92
+ const end = this.next();
93
+ if (end?.kind !== 'close' || end.name !== 'plist') {
94
+ throw new PlistError('Not a property list: expected </plist>.');
95
+ }
96
+ return value;
97
+ }
98
+ value(topLevel = false) {
99
+ const token = this.next();
100
+ if (!token || token.kind === 'text' || token.kind === 'close') {
101
+ throw new PlistError('Expected a value element.');
102
+ }
103
+ if (token.kind === 'empty')
104
+ return this.emptyValue(token.name);
105
+ switch (token.name) {
106
+ case 'dict':
107
+ return this.dict(topLevel);
108
+ case 'array':
109
+ return this.array();
110
+ case 'string':
111
+ return this.textUntil('string');
112
+ case 'integer': {
113
+ const text = this.textUntil('integer').trim();
114
+ if (!/^[+-]?\d+$/.test(text))
115
+ throw new PlistError(`Not an integer: ${text}`);
116
+ const big = BigInt(text);
117
+ return Number.isSafeInteger(Number(big)) ? Number(big) : big;
118
+ }
119
+ case 'real': {
120
+ const number = Number(this.textUntil('real').trim());
121
+ if (Number.isNaN(number))
122
+ throw new PlistError('Not a real number.');
123
+ return number;
124
+ }
125
+ case 'true':
126
+ case 'false':
127
+ this.textUntil(token.name);
128
+ return token.name === 'true';
129
+ case 'date':
130
+ return new Date(this.textUntil('date').trim());
131
+ case 'data':
132
+ return Uint8Array.from(Buffer.from(this.textUntil('data').replace(/\s+/g, ''), 'base64'));
133
+ default:
134
+ throw new PlistError(`Unknown element <${token.name}>.`);
135
+ }
136
+ }
137
+ emptyValue(name) {
138
+ switch (name) {
139
+ case 'true':
140
+ return true;
141
+ case 'false':
142
+ return false;
143
+ case 'dict':
144
+ return {};
145
+ case 'array':
146
+ return [];
147
+ case 'string':
148
+ return '';
149
+ case 'data':
150
+ return new Uint8Array();
151
+ default:
152
+ throw new PlistError(`Unknown element <${name}/>.`);
153
+ }
154
+ }
155
+ dict(topLevel) {
156
+ const result = {};
157
+ for (;;) {
158
+ const token = this.peek();
159
+ if (!token)
160
+ throw new PlistError('<dict> is not closed.');
161
+ if (token.kind === 'close' && token.name === 'dict') {
162
+ this.next();
163
+ return result;
164
+ }
165
+ const keyToken = this.next();
166
+ if (keyToken?.name !== 'key')
167
+ throw new PlistError('Expected <key> in <dict>.');
168
+ const key = keyToken.kind === 'empty' ? '' : this.textUntil('key');
169
+ if (topLevel)
170
+ this.keyLines.set(key, this.lineAt(keyToken.offset));
171
+ result[key] = this.value();
172
+ }
173
+ }
174
+ array() {
175
+ const result = [];
176
+ for (;;) {
177
+ const token = this.peek();
178
+ if (!token)
179
+ throw new PlistError('<array> is not closed.');
180
+ if (token.kind === 'close' && token.name === 'array') {
181
+ this.next();
182
+ return result;
183
+ }
184
+ result.push(this.value());
185
+ }
186
+ }
187
+ }
188
+ export function parseXmlPlist(xml) {
189
+ const text = xml.replace(/^/, '');
190
+ if (!/<plist[\s>]/.test(text))
191
+ throw new PlistError('Not a property list: no <plist> element.');
192
+ const reader = new XmlReader(tokenize(text), lineIndex(text));
193
+ return { value: reader.document(), keyLines: reader.keyLines };
194
+ }
195
+ //# sourceMappingURL=plist-xml.js.map
@@ -0,0 +1,6 @@
1
+ import { type ParsedPlist } from './plist-value.js';
2
+ export { parseBinaryPlist } from './plist-binary.js';
3
+ export { isDict, type ParsedPlist, type PlistDict, PlistError, type PlistValue, } from './plist-value.js';
4
+ export { parseXmlPlist } from './plist-xml.js';
5
+ /** Parses an XML or binary property list. Throws PlistError for anything else. */
6
+ export declare function parsePlist(data: Uint8Array | string): ParsedPlist;
@@ -0,0 +1,19 @@
1
+ // Property list reader for Info.plist files: the XML format Xcode writes into projects and the
2
+ // binary format (bplist00) found in built apps. No dependencies; only what Info.plist files use.
3
+ import { parseBinaryPlist } from './plist-binary.js';
4
+ import { BINARY_MAGIC } from './plist-value.js';
5
+ import { parseXmlPlist } from './plist-xml.js';
6
+ export { parseBinaryPlist } from './plist-binary.js';
7
+ export { isDict, PlistError, } from './plist-value.js';
8
+ export { parseXmlPlist } from './plist-xml.js';
9
+ /** Parses an XML or binary property list. Throws PlistError for anything else. */
10
+ export function parsePlist(data) {
11
+ if (typeof data !== 'string') {
12
+ const head = new TextDecoder('latin1').decode(data.subarray(0, BINARY_MAGIC.length));
13
+ if (head === BINARY_MAGIC)
14
+ return { value: parseBinaryPlist(data), keyLines: new Map() };
15
+ return parseXmlPlist(new TextDecoder().decode(data));
16
+ }
17
+ return parseXmlPlist(data);
18
+ }
19
+ //# sourceMappingURL=plist.js.map
@@ -0,0 +1,9 @@
1
+ import type { SourceFile, SourceIndex, TypeRegion } from './sources.js';
2
+ export declare function swiftRegions(file: SourceFile): TypeRegion[];
3
+ export declare function objcRegions(file: SourceFile): TypeRegion[];
4
+ /** Classes that derive, directly or through other classes in the app, from one of `bases`. */
5
+ export declare function derivedFrom(regions: TypeRegion[], bases: readonly string[]): Set<string>;
6
+ /** The innermost type region of `file` that contains `offset`. */
7
+ export declare function regionAt(index: SourceIndex, file: SourceFile, offset: number): TypeRegion | undefined;
8
+ /** Whether `offset` in `file` is inside the body of a type in `names` (class or extension). */
9
+ export declare function insideTypeNamed(index: SourceIndex, file: SourceFile, offset: number, names: ReadonlySet<string>): boolean;