@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.
- package/LICENSE +21 -0
- package/README.de.md +251 -0
- package/README.es.md +252 -0
- package/README.fr.md +252 -0
- package/README.ja.md +250 -0
- package/README.ko.md +250 -0
- package/README.md +322 -2
- package/README.pt-BR.md +252 -0
- package/README.ru.md +252 -0
- package/README.tr.md +338 -0
- package/README.zh-CN.md +250 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +11 -0
- package/dist/cli/run.d.ts +29 -0
- package/dist/cli/run.js +111 -0
- package/dist/collect/app.d.ts +48 -0
- package/dist/collect/app.js +117 -0
- package/dist/collect/blank.d.ts +6 -0
- package/dist/collect/blank.js +75 -0
- package/dist/collect/pbxproj.d.ts +45 -0
- package/dist/collect/pbxproj.js +260 -0
- package/dist/collect/plist-binary.d.ts +2 -0
- package/dist/collect/plist-binary.js +123 -0
- package/dist/collect/plist-value.d.ts +15 -0
- package/dist/collect/plist-value.js +14 -0
- package/dist/collect/plist-xml.d.ts +2 -0
- package/dist/collect/plist-xml.js +195 -0
- package/dist/collect/plist.d.ts +6 -0
- package/dist/collect/plist.js +19 -0
- package/dist/collect/regions.d.ts +9 -0
- package/dist/collect/regions.js +108 -0
- package/dist/collect/sources.d.ts +45 -0
- package/dist/collect/sources.js +99 -0
- package/dist/core/files.d.ts +22 -0
- package/dist/core/files.js +47 -0
- package/dist/core/lines.d.ts +2 -0
- package/dist/core/lines.js +21 -0
- package/dist/core/versions.d.ts +18 -0
- package/dist/core/versions.js +63 -0
- package/dist/detect/project.d.ts +46 -0
- package/dist/detect/project.js +220 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +27 -0
- package/dist/model.d.ts +93 -0
- package/dist/model.js +2 -0
- package/dist/report/index.d.ts +12 -0
- package/dist/report/index.js +22 -0
- package/dist/report/markdown.d.ts +2 -0
- package/dist/report/markdown.js +113 -0
- package/dist/report/text.d.ts +27 -0
- package/dist/report/text.js +63 -0
- package/dist/report/tty.d.ts +7 -0
- package/dist/report/tty.js +112 -0
- package/dist/rules/check.d.ts +11 -0
- package/dist/rules/check.js +253 -0
- package/dist/rules/data.d.ts +217 -0
- package/dist/rules/data.js +141 -0
- package/dist/rules/fixes.d.ts +28 -0
- package/dist/rules/fixes.js +161 -0
- package/dist/rules/patterns.d.ts +29 -0
- package/dist/rules/patterns.js +109 -0
- package/dist/rules/query.d.ts +16 -0
- package/dist/rules/query.js +50 -0
- package/dist/scan.d.ts +10 -0
- package/dist/scan.js +115 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +7 -0
- package/package.json +69 -4
- package/rules/ios27.json +613 -0
- package/rules/schema.json +449 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Report } from '../model.js';
|
|
2
|
+
/** Exit codes; documented in the README and `--help`. */
|
|
3
|
+
export declare const EXIT: {
|
|
4
|
+
readonly ok: 0;
|
|
5
|
+
readonly findings: 1;
|
|
6
|
+
readonly error: 2;
|
|
7
|
+
};
|
|
8
|
+
export declare const FAIL_ON: readonly ['error', 'warning', 'info', 'none'];
|
|
9
|
+
type FailOn = (typeof FAIL_ON)[number];
|
|
10
|
+
/** Everything the CLI touches outside its arguments, so tests can run it in-process. */
|
|
11
|
+
export interface CliEnvironment {
|
|
12
|
+
cwd: string;
|
|
13
|
+
env: Record<string, string | undefined>;
|
|
14
|
+
stdout: (text: string) => void;
|
|
15
|
+
stderr: (text: string) => void;
|
|
16
|
+
/** Whether stdout is a terminal; turns colour on for the tty format. */
|
|
17
|
+
stdoutIsTty: boolean;
|
|
18
|
+
/** Terminal width, when stdout is a terminal. */
|
|
19
|
+
columns?: number | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** The npm package; the command it installs is `sdkbump`. */
|
|
22
|
+
export declare const PACKAGE_NAME = "@mcamdali/sdkbump";
|
|
23
|
+
/** The tarball the GitHub release of this version carries (the same file as on npm). */
|
|
24
|
+
export declare const RELEASE_TARBALL_URL: string;
|
|
25
|
+
/** Whether the report has an unhandled finding at or above `failOn`. */
|
|
26
|
+
export declare function failsOn(report: Report, failOn: FailOn): boolean;
|
|
27
|
+
/** Runs the CLI with the given arguments (without `node` and the script) and returns the exit code. */
|
|
28
|
+
export declare function run(argv: readonly string[], environment: CliEnvironment): Promise<number>;
|
|
29
|
+
export {};
|
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Command, CommanderError, Option } from 'commander';
|
|
4
|
+
import { ProjectError } from '../detect/project.js';
|
|
5
|
+
import { FORMATS, render } from '../report/index.js';
|
|
6
|
+
import { clean, REPOSITORY_URL } from '../report/text.js';
|
|
7
|
+
import { scan } from '../scan.js';
|
|
8
|
+
import { VERSION } from '../version.js';
|
|
9
|
+
/** Exit codes; documented in the README and `--help`. */
|
|
10
|
+
export const EXIT = { ok: 0, findings: 1, error: 2 };
|
|
11
|
+
export const FAIL_ON = ['error', 'warning', 'info', 'none'];
|
|
12
|
+
/** The npm package; the command it installs is `sdkbump`. */
|
|
13
|
+
export const PACKAGE_NAME = '@mcamdali/sdkbump';
|
|
14
|
+
/** The tarball the GitHub release of this version carries (the same file as on npm). */
|
|
15
|
+
export const RELEASE_TARBALL_URL = `${REPOSITORY_URL}/releases/download/v${VERSION}/sdkbump-${VERSION}.tgz`;
|
|
16
|
+
const HELP_AFTER = `
|
|
17
|
+
Exit codes:
|
|
18
|
+
0 no findings at or above --fail-on (findings a workaround handles do not count)
|
|
19
|
+
1 findings at or above --fail-on
|
|
20
|
+
2 bad arguments, no iOS project found, or an unexpected error
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
sdkbump checks the project in the current folder
|
|
24
|
+
sdkbump path/to/app --format markdown --output sdkbump.md
|
|
25
|
+
sdkbump --format json > sdkbump.json
|
|
26
|
+
sdkbump --fail-on warning also fails on warnings, e.g. in CI
|
|
27
|
+
|
|
28
|
+
The npm package is ${PACKAGE_NAME}; the command is sdkbump:
|
|
29
|
+
npx ${PACKAGE_NAME} runs the latest version without installing it
|
|
30
|
+
npm i -g ${PACKAGE_NAME} installs the sdkbump command
|
|
31
|
+
npx ${RELEASE_TARBALL_URL}
|
|
32
|
+
runs this release from GitHub
|
|
33
|
+
|
|
34
|
+
Docs: ${REPOSITORY_URL}#readme`;
|
|
35
|
+
function program(io) {
|
|
36
|
+
return new Command('sdkbump')
|
|
37
|
+
.description('Checks a native iOS, Flutter or React Native/Expo project for what the iOS 27 SDK changes, starting with the UIScene life cycle, and says which fix applies to each finding.')
|
|
38
|
+
.version(VERSION, '-v, --version')
|
|
39
|
+
.argument('[path]', 'project folder or .xcodeproj (default: current folder)')
|
|
40
|
+
.addOption(new Option('-f, --format <format>', 'output format').choices(FORMATS).default('tty'))
|
|
41
|
+
.option('-o, --output <file>', 'write the report to a file instead of stdout')
|
|
42
|
+
.addOption(new Option('--fail-on <severity>', 'lowest severity that makes the exit code 1')
|
|
43
|
+
.choices(FAIL_ON)
|
|
44
|
+
.default('error'))
|
|
45
|
+
.option('--no-color', 'plain text even in a terminal')
|
|
46
|
+
.addHelpText('after', HELP_AFTER)
|
|
47
|
+
.showHelpAfterError('(add --help for usage)')
|
|
48
|
+
.exitOverride()
|
|
49
|
+
.configureOutput({
|
|
50
|
+
writeOut: io.stdout,
|
|
51
|
+
writeErr: io.stderr,
|
|
52
|
+
outputError: (text, write) => write(text),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function useColor(options, io) {
|
|
56
|
+
if (options.format !== 'tty' || options.output || !options.color)
|
|
57
|
+
return false;
|
|
58
|
+
if (io.env.NO_COLOR)
|
|
59
|
+
return false;
|
|
60
|
+
if (io.env.FORCE_COLOR && io.env.FORCE_COLOR !== '0')
|
|
61
|
+
return true;
|
|
62
|
+
return io.stdoutIsTty;
|
|
63
|
+
}
|
|
64
|
+
const RANK = { error: 3, warning: 2, info: 1 };
|
|
65
|
+
/** Whether the report has an unhandled finding at or above `failOn`. */
|
|
66
|
+
export function failsOn(report, failOn) {
|
|
67
|
+
if (failOn === 'none')
|
|
68
|
+
return false;
|
|
69
|
+
return report.findings.some((item) => !item.mitigation && RANK[item.severity] >= RANK[failOn]);
|
|
70
|
+
}
|
|
71
|
+
/** Runs the CLI with the given arguments (without `node` and the script) and returns the exit code. */
|
|
72
|
+
export async function run(argv, environment) {
|
|
73
|
+
// Messages name folders, files and targets of the checked project, and the verdict after
|
|
74
|
+
// --output names a target. Every write to stderr is cleaned here, so none of them can carry an
|
|
75
|
+
// escape sequence to the terminal.
|
|
76
|
+
const io = { ...environment, stderr: (text) => environment.stderr(clean(text)) };
|
|
77
|
+
const command = program(io);
|
|
78
|
+
try {
|
|
79
|
+
await command.parseAsync([...argv], { from: 'user' });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof CommanderError)
|
|
83
|
+
return error.exitCode === 0 ? EXIT.ok : EXIT.error;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const options = command.opts();
|
|
87
|
+
const path = command.processedArgs[0] ?? '.';
|
|
88
|
+
try {
|
|
89
|
+
const report = await scan(resolve(io.cwd, path));
|
|
90
|
+
report.project.path = path;
|
|
91
|
+
const width = io.stdoutIsTty && io.columns ? Math.min(io.columns, 100) : 100;
|
|
92
|
+
const output = render(report, options.format, { color: useColor(options, io), width });
|
|
93
|
+
if (options.output) {
|
|
94
|
+
await writeFile(resolve(io.cwd, options.output), output);
|
|
95
|
+
io.stderr(`Wrote ${options.output}: ${report.verdict.text}\n`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
io.stdout(output);
|
|
99
|
+
}
|
|
100
|
+
return failsOn(report, options.failOn) ? EXIT.findings : EXIT.ok;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
104
|
+
io.stderr(`sdkbump: ${message}\n`);
|
|
105
|
+
if (!(error instanceof ProjectError)) {
|
|
106
|
+
io.stderr(`This looks like a bug in sdkbump; please report it at ${REPOSITORY_URL}/issues\n`);
|
|
107
|
+
}
|
|
108
|
+
return EXIT.error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Project } from '../detect/project.js';
|
|
2
|
+
import type { InfoPlistSource } from '../model.js';
|
|
3
|
+
import { type PlistDict } from './plist.js';
|
|
4
|
+
import { type SourceIndex } from './sources.js';
|
|
5
|
+
export interface InfoPlistFile {
|
|
6
|
+
path: string;
|
|
7
|
+
dict: PlistDict;
|
|
8
|
+
keyLines: ReadonlyMap<string, number>;
|
|
9
|
+
}
|
|
10
|
+
export interface AppTarget {
|
|
11
|
+
name: string;
|
|
12
|
+
/** The .xcodeproj the target belongs to. */
|
|
13
|
+
xcodeProject: string;
|
|
14
|
+
infoPlists: InfoPlistFile[];
|
|
15
|
+
/** Xcode generates (part of) the Info.plist: GENERATE_INFOPLIST_FILE = YES. */
|
|
16
|
+
generatesInfoPlist: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Where the rules get the Info.plist from: a file sdkbump read, the one Xcode generates from
|
|
19
|
+
* build settings, or none (not-read) when the file could not be read or the project file does
|
|
20
|
+
* not say which one it is.
|
|
21
|
+
*/
|
|
22
|
+
infoPlistSource: InfoPlistSource;
|
|
23
|
+
}
|
|
24
|
+
export interface AppModel {
|
|
25
|
+
project: Project;
|
|
26
|
+
targets: AppTarget[];
|
|
27
|
+
sources: SourceIndex;
|
|
28
|
+
/** Problems reading the project that the report should mention. */
|
|
29
|
+
notes: string[];
|
|
30
|
+
}
|
|
31
|
+
/** Reads the app targets and sources of a detected project. */
|
|
32
|
+
export declare function collectApp(project: Project): Promise<AppModel>;
|
|
33
|
+
/** The scene manifest of an Info.plist and whether it lists at least one configuration. */
|
|
34
|
+
export declare function sceneManifest(plist: InfoPlistFile): {
|
|
35
|
+
present: boolean;
|
|
36
|
+
configured: boolean;
|
|
37
|
+
};
|
|
38
|
+
/** Path of the Flutter Runner's AppDelegate files, for the auto-migration check. */
|
|
39
|
+
export declare const flutterAppDelegatePaths: (iosDir: string) => {
|
|
40
|
+
swift: string;
|
|
41
|
+
objcHeader: string;
|
|
42
|
+
objcImplementation: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The scene delegate classes an Info.plist names in its scene configurations, without a module
|
|
46
|
+
* prefix: "$(PRODUCT_MODULE_NAME).SceneDelegate" and "App.SceneDelegate" both give "SceneDelegate".
|
|
47
|
+
*/
|
|
48
|
+
export declare function sceneDelegateClassNames(plist: InfoPlistFile): string[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Everything the rules look at for one project: its iOS app targets with their Info.plist, and
|
|
2
|
+
// the index of the app's own Swift and Objective-C sources.
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { NOT_READ, readProjectFile } from '../core/files.js';
|
|
5
|
+
import { generatesInfoPlist, infoPlistFiles, iosAppTargets, readXcodeProject, } from './pbxproj.js';
|
|
6
|
+
import { isDict, parsePlist } from './plist.js';
|
|
7
|
+
import { indexSources } from './sources.js';
|
|
8
|
+
/**
|
|
9
|
+
* Reads the Info.plist a target names. INFOPLIST_FILE comes from the checked project, so a path
|
|
10
|
+
* that leads (through `..`, an absolute path or a symbolic link) outside the project folder is
|
|
11
|
+
* not read, and neither is anything but a regular file: /dev/zero would never end.
|
|
12
|
+
*/
|
|
13
|
+
async function readInfoPlist(path, projectRoot, notes) {
|
|
14
|
+
const file = await readProjectFile(path, projectRoot);
|
|
15
|
+
if (!file.found) {
|
|
16
|
+
notes.push(`${path} is named in INFOPLIST_FILE but ${NOT_READ[file.reason]}`);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const { value, keyLines } = parsePlist(file.bytes);
|
|
21
|
+
if (!isDict(value))
|
|
22
|
+
throw new Error('the top level is not a dictionary');
|
|
23
|
+
return { path, dict: value, keyLines };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
notes.push(`${path} could not be read as a property list: ${error.message}`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reads one app target's Info.plist. A target that names no INFOPLIST_FILE and does not have
|
|
32
|
+
* Xcode generate one most likely sets it in an .xcconfig file, which sdkbump does not read.
|
|
33
|
+
*/
|
|
34
|
+
async function readTarget(xcode, target, projectRoot, notes) {
|
|
35
|
+
const { files, unresolved } = infoPlistFiles(xcode, target);
|
|
36
|
+
for (const raw of unresolved) {
|
|
37
|
+
notes.push(`${target.name}: INFOPLIST_FILE ${raw} uses a build setting sdkbump does not expand.`);
|
|
38
|
+
}
|
|
39
|
+
const infoPlists = [];
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const plist = await readInfoPlist(file, projectRoot, notes);
|
|
42
|
+
if (plist)
|
|
43
|
+
infoPlists.push(plist);
|
|
44
|
+
}
|
|
45
|
+
const generates = generatesInfoPlist(target);
|
|
46
|
+
const named = files.length + unresolved.length > 0;
|
|
47
|
+
if (!named && !generates) {
|
|
48
|
+
notes.push(`${target.name}: the Xcode project sets neither INFOPLIST_FILE nor GENERATE_INFOPLIST_FILE = YES; they may be set in an .xcconfig file, which sdkbump does not read.`);
|
|
49
|
+
}
|
|
50
|
+
let infoPlistSource = 'not-read';
|
|
51
|
+
if (infoPlists.length > 0)
|
|
52
|
+
infoPlistSource = 'file';
|
|
53
|
+
else if (!named && generates)
|
|
54
|
+
infoPlistSource = 'generated';
|
|
55
|
+
return {
|
|
56
|
+
name: target.name,
|
|
57
|
+
xcodeProject: xcode.path,
|
|
58
|
+
infoPlists,
|
|
59
|
+
generatesInfoPlist: generates,
|
|
60
|
+
infoPlistSource,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Reads the app targets and sources of a detected project. */
|
|
64
|
+
export async function collectApp(project) {
|
|
65
|
+
const notes = [];
|
|
66
|
+
const targets = [];
|
|
67
|
+
for (const path of project.xcodeProjects) {
|
|
68
|
+
let xcode;
|
|
69
|
+
try {
|
|
70
|
+
xcode = await readXcodeProject(path, project.root);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
notes.push(`${path}: ${error.message}`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const target of iosAppTargets(xcode)) {
|
|
77
|
+
targets.push(await readTarget(xcode, target, project.root, notes));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (project.iosDir && project.xcodeProjects.length > 0 && targets.length === 0) {
|
|
81
|
+
notes.push('No iOS app target was found in the Xcode project.');
|
|
82
|
+
}
|
|
83
|
+
const sources = await indexSources(project.iosDir ? [project.iosDir] : []);
|
|
84
|
+
return { project, targets, sources, notes };
|
|
85
|
+
}
|
|
86
|
+
/** The scene manifest of an Info.plist and whether it lists at least one configuration. */
|
|
87
|
+
export function sceneManifest(plist) {
|
|
88
|
+
const manifest = plist.dict.UIApplicationSceneManifest;
|
|
89
|
+
if (!isDict(manifest))
|
|
90
|
+
return { present: false, configured: false };
|
|
91
|
+
const configurations = manifest.UISceneConfigurations;
|
|
92
|
+
const configured = isDict(configurations) &&
|
|
93
|
+
Object.values(configurations).some((roles) => Array.isArray(roles) && roles.length > 0);
|
|
94
|
+
return { present: true, configured };
|
|
95
|
+
}
|
|
96
|
+
/** Path of the Flutter Runner's AppDelegate files, for the auto-migration check. */
|
|
97
|
+
export const flutterAppDelegatePaths = (iosDir) => ({
|
|
98
|
+
swift: join(iosDir, 'Runner', 'AppDelegate.swift'),
|
|
99
|
+
objcHeader: join(iosDir, 'Runner', 'AppDelegate.h'),
|
|
100
|
+
objcImplementation: join(iosDir, 'Runner', 'AppDelegate.m'),
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* The scene delegate classes an Info.plist names in its scene configurations, without a module
|
|
104
|
+
* prefix: "$(PRODUCT_MODULE_NAME).SceneDelegate" and "App.SceneDelegate" both give "SceneDelegate".
|
|
105
|
+
*/
|
|
106
|
+
export function sceneDelegateClassNames(plist) {
|
|
107
|
+
const configurations = plist.dict.UIApplicationSceneManifest
|
|
108
|
+
?.UISceneConfigurations;
|
|
109
|
+
if (!isDict(configurations))
|
|
110
|
+
return [];
|
|
111
|
+
return Object.values(configurations)
|
|
112
|
+
.flatMap((roles) => (Array.isArray(roles) ? roles : []))
|
|
113
|
+
.map((entry) => (isDict(entry) ? entry.UISceneDelegateClassName : undefined))
|
|
114
|
+
.filter((name) => typeof name === 'string')
|
|
115
|
+
.map((name) => name.split('.').at(-1) ?? name);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Language } from './sources.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces comments and the contents of string and character literals with spaces, keeping
|
|
4
|
+
* newlines and quote characters, so offsets and line numbers stay the same.
|
|
5
|
+
*/
|
|
6
|
+
export declare function blankCommentsAndStrings(text: string, language: Language): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces comments and the contents of string and character literals with spaces, keeping
|
|
3
|
+
* newlines and quote characters, so offsets and line numbers stay the same.
|
|
4
|
+
*/
|
|
5
|
+
export function blankCommentsAndStrings(text, language) {
|
|
6
|
+
const out = text.split('');
|
|
7
|
+
const blank = (from, to) => {
|
|
8
|
+
for (let index = from; index < to; index++)
|
|
9
|
+
if (out[index] !== '\n')
|
|
10
|
+
out[index] = ' ';
|
|
11
|
+
};
|
|
12
|
+
let index = 0;
|
|
13
|
+
while (index < text.length) {
|
|
14
|
+
const char = text[index];
|
|
15
|
+
const next = text[index + 1];
|
|
16
|
+
if (char === '/' && next === '/') {
|
|
17
|
+
const end = text.indexOf('\n', index);
|
|
18
|
+
const stop = end === -1 ? text.length : end;
|
|
19
|
+
blank(index, stop);
|
|
20
|
+
index = stop;
|
|
21
|
+
}
|
|
22
|
+
else if (char === '/' && next === '*') {
|
|
23
|
+
// Swift block comments nest; Objective-C ones do not.
|
|
24
|
+
let depth = 1;
|
|
25
|
+
let cursor = index + 2;
|
|
26
|
+
while (cursor < text.length && depth > 0) {
|
|
27
|
+
if (text.startsWith('*/', cursor)) {
|
|
28
|
+
depth--;
|
|
29
|
+
cursor += 2;
|
|
30
|
+
}
|
|
31
|
+
else if (language === 'swift' && text.startsWith('/*', cursor)) {
|
|
32
|
+
depth++;
|
|
33
|
+
cursor += 2;
|
|
34
|
+
}
|
|
35
|
+
else
|
|
36
|
+
cursor++;
|
|
37
|
+
}
|
|
38
|
+
blank(index, cursor);
|
|
39
|
+
index = cursor;
|
|
40
|
+
}
|
|
41
|
+
else if (language === 'swift' &&
|
|
42
|
+
(char === '"' || (char === '#' && /^#+"/.test(text.slice(index, index + 8))))) {
|
|
43
|
+
const hashes = /^#*/.exec(text.slice(index))?.[0].length ?? 0;
|
|
44
|
+
const open = index + hashes;
|
|
45
|
+
const multiline = text.startsWith('"""', open);
|
|
46
|
+
const closing = `${multiline ? '"""' : '"'}${'#'.repeat(hashes)}`;
|
|
47
|
+
let cursor = open + (multiline ? 3 : 1);
|
|
48
|
+
while (cursor < text.length) {
|
|
49
|
+
if (hashes === 0 && text[cursor] === '\\')
|
|
50
|
+
cursor += 2;
|
|
51
|
+
else if (text.startsWith(closing, cursor))
|
|
52
|
+
break;
|
|
53
|
+
else if (!multiline && text[cursor] === '\n')
|
|
54
|
+
break;
|
|
55
|
+
else
|
|
56
|
+
cursor++;
|
|
57
|
+
}
|
|
58
|
+
const contentEnd = Math.min(cursor, text.length);
|
|
59
|
+
blank(open + (multiline ? 3 : 1), contentEnd);
|
|
60
|
+
index = contentEnd + (text.startsWith(closing, contentEnd) ? closing.length : 0);
|
|
61
|
+
}
|
|
62
|
+
else if (language === 'objc' && (char === '"' || char === "'")) {
|
|
63
|
+
let cursor = index + 1;
|
|
64
|
+
while (cursor < text.length && text[cursor] !== char && text[cursor] !== '\n') {
|
|
65
|
+
cursor += text[cursor] === '\\' ? 2 : 1;
|
|
66
|
+
}
|
|
67
|
+
blank(index + 1, Math.min(cursor, text.length));
|
|
68
|
+
index = cursor + 1;
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
index++;
|
|
72
|
+
}
|
|
73
|
+
return out.join('');
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=blank.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type OpenStepValue = string | OpenStepValue[] | OpenStepDict;
|
|
2
|
+
export interface OpenStepDict {
|
|
3
|
+
[key: string]: OpenStepValue;
|
|
4
|
+
}
|
|
5
|
+
export declare class XcodeProjectError extends Error {
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
/** Parses an old-style property list such as project.pbxproj. */
|
|
9
|
+
export declare function parseOpenStep(text: string): OpenStepValue;
|
|
10
|
+
export type BuildSettings = Readonly<Record<string, string | readonly string[]>>;
|
|
11
|
+
export interface XcodeTarget {
|
|
12
|
+
name: string;
|
|
13
|
+
productType: string;
|
|
14
|
+
/** Build settings per configuration (Debug, Release, …), project-level values included. */
|
|
15
|
+
configurations: Readonly<Record<string, BuildSettings>>;
|
|
16
|
+
}
|
|
17
|
+
export interface XcodeProject {
|
|
18
|
+
/** Path of the .xcodeproj folder. */
|
|
19
|
+
path: string;
|
|
20
|
+
/** The folder that contains the .xcodeproj, which $(SRCROOT) refers to. */
|
|
21
|
+
sourceRoot: string;
|
|
22
|
+
targets: XcodeTarget[];
|
|
23
|
+
}
|
|
24
|
+
/** Reads the native targets of a parsed project.pbxproj. */
|
|
25
|
+
export declare function readTargets(pbxproj: OpenStepValue): XcodeTarget[];
|
|
26
|
+
/**
|
|
27
|
+
* Reads `<path>.xcodeproj/project.pbxproj` when it is a regular file inside `root` (by default
|
|
28
|
+
* the .xcodeproj folder itself).
|
|
29
|
+
*/
|
|
30
|
+
export declare function readXcodeProject(path: string, root?: string): Promise<XcodeProject>;
|
|
31
|
+
/** Whether the target builds with the iOS SDK (SDKROOT iphoneos, or `auto` listing iphoneos). */
|
|
32
|
+
export declare function buildsForIOS(settings: BuildSettings): boolean;
|
|
33
|
+
/** iOS app targets (apps and App Clips): the ones UIKit's launch check applies to. */
|
|
34
|
+
export declare function iosAppTargets(project: XcodeProject): XcodeTarget[];
|
|
35
|
+
/**
|
|
36
|
+
* The Info.plist files a target's configurations name in INFOPLIST_FILE, as absolute paths.
|
|
37
|
+
* $(SRCROOT), $(PROJECT_DIR), $(TARGET_NAME) and $(PRODUCT_NAME) are expanded; a path that still
|
|
38
|
+
* contains an unknown variable is returned in `unresolved`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function infoPlistFiles(project: XcodeProject, target: XcodeTarget): {
|
|
41
|
+
files: string[];
|
|
42
|
+
unresolved: string[];
|
|
43
|
+
};
|
|
44
|
+
/** Whether every configuration of the target has Xcode generate (part of) its Info.plist. */
|
|
45
|
+
export declare function generatesInfoPlist(target: XcodeTarget): boolean;
|