rork-xcode 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -2
- package/dist/index.d.ts +153 -1
- package/dist/index.js +547 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/rorkai/rork-xcode/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/rork-xcode)
|
|
5
5
|
|
|
6
|
-
The [fastest](#performance) zero-dependency Xcode project (`project.pbxproj`) parser, builder, and object model for any JavaScript runtime: browsers, Node.js, Bun, Electron, Cloudflare Workers, and React Native.
|
|
6
|
+
The [fastest](#performance) zero-dependency Xcode project (`project.pbxproj`) parser, builder, and object model for any JavaScript runtime: browsers, Node.js, Bun, Electron, Cloudflare Workers, and React Native. [Scheme files](#schemes) (`.xcscheme`) are covered with the same round-trip guarantees.
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
9
|
import { parsePbxproj, buildPbxproj } from "rork-xcode";
|
|
@@ -229,6 +229,39 @@ for (const [id, object] of project.objects()) {
|
|
|
229
229
|
- **Soft reads, loud writes.** Real-world projects can be malformed, so lookups return `undefined` where a document could omit something. Operations that cannot proceed without structure (no root project object, an unknown product type, a view whose object was deleted) throw `XcodeModelError`.
|
|
230
230
|
- **Identity-mapped views.** Two lookups of the same id return the same instance, so views compare with `===`.
|
|
231
231
|
|
|
232
|
+
## Schemes
|
|
233
|
+
|
|
234
|
+
`.xcscheme` files describe how Xcode builds, runs, tests, and archives a target. They are not property lists but a small XML dialect of their own, and `parseXcscheme` and `buildXcscheme` cover it with the same contract as the pbxproj functions: an Xcode-written scheme rebuilds byte for byte, any other input reaches Xcode's canonical layout in one build, and malformed input fails with a typed error carrying line and column.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { buildXcscheme, parseXcscheme, xcschemeElements } from "rork-xcode";
|
|
238
|
+
|
|
239
|
+
const scheme = parseXcscheme(xcschemeText);
|
|
240
|
+
|
|
241
|
+
// The tree is plain data: elements with ordered attributes and children.
|
|
242
|
+
for (const reference of xcschemeElements(scheme.root, "BuildableReference")) {
|
|
243
|
+
reference.attributes["BlueprintName"] = "RenamedApp";
|
|
244
|
+
reference.attributes["BuildableName"] = "RenamedApp.app";
|
|
245
|
+
reference.attributes["ReferencedContainer"] = "container:RenamedApp.xcodeproj";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const text = buildXcscheme(scheme);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`createXcscheme` produces the scheme Xcode's own "New Scheme" action writes for an application target, wired to the target's object id from the project document:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { createXcscheme } from "rork-xcode";
|
|
255
|
+
|
|
256
|
+
const scheme = createXcscheme({
|
|
257
|
+
appName: "DemoApp",
|
|
258
|
+
blueprintIdentifier: app.id,
|
|
259
|
+
});
|
|
260
|
+
const text = buildXcscheme(scheme);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Attribute order is preserved and meaningful: the writer emits attributes in insertion order, which is how byte-identical round-trips fall out. Comments are kept, attribute values resolve the character references Xcode writes (`"`, `&`, ` ` and friends), and the writer re-escapes them identically.
|
|
264
|
+
|
|
232
265
|
## Performance
|
|
233
266
|
|
|
234
267
|
`rork-xcode` is measured against the pbxproj parsers on npm, [`@bacons/xcode`](https://www.npmjs.com/package/@bacons/xcode) (its `/json` parse/build entry point) and [`xcode`](https://www.npmjs.com/package/xcode) (the long-standing package used by native build tooling), on three documents: two real Xcode-written projects from the test suite and a deterministically generated five-target app with 800 source files. It is the fastest at both operations on every document, with zero dependencies.
|
|
@@ -260,7 +293,7 @@ Measured on an Apple M5 Max, Node.js 24, single thread, with `@bacons/xcode` 1.0
|
|
|
260
293
|
- The committed fixture corpus spans project generations from Xcode 3 to Xcode 16, captured from real projects with identifiers neutralized: synchronized folders with both exception-set kinds, classic groups, variant groups, aggregate and legacy targets, reference proxies, build rules, Swift packages, and a ~100 KiB multiplatform framework project.
|
|
261
294
|
- Documents already in current Xcode's layout must round-trip byte for byte; documents from other tool generations must normalize to a byte-stable fixed point with unchanged values.
|
|
262
295
|
- On macOS, the suite cross-validates every fixture and its rebuilt form with `plutil`, Apple's own property list parser and the empirical ground truth for what Apple tooling accepts.
|
|
263
|
-
- A corpus sweep (`pnpm corpus`) walks every Xcode project on the machine, verifies each one parses and reaches a byte-stable fixed point, exercises the object model against
|
|
296
|
+
- A corpus sweep (`pnpm corpus`) walks every Xcode project and scheme on the machine, verifies each one parses and reaches a byte-stable fixed point, exercises the object model against every project, and cross-validates a sample against plutil's own reading.
|
|
264
297
|
- CI runs the full gate on Linux and macOS, and executes the built artifact on the oldest supported Node to enforce the `engines` floor.
|
|
265
298
|
|
|
266
299
|
## Releasing
|
package/dist/index.d.ts
CHANGED
|
@@ -114,6 +114,42 @@ declare class PbxprojParseError extends Error {
|
|
|
114
114
|
*/
|
|
115
115
|
constructor(message: string, source: string, offset: number);
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Thrown when the source text is not a well-formed scheme document (the
|
|
119
|
+
* XML dialect of `.xcscheme` files).
|
|
120
|
+
*
|
|
121
|
+
* The message always embeds the line and column of the failure, and the
|
|
122
|
+
* same information is available in structured form on {@link position}
|
|
123
|
+
* for programmatic use.
|
|
124
|
+
*/
|
|
125
|
+
declare class XcschemeParseError extends Error {
|
|
126
|
+
/** Where in the source text parsing failed. */
|
|
127
|
+
readonly position: PbxprojErrorPosition;
|
|
128
|
+
/**
|
|
129
|
+
* @param message Failure description without location; the location is
|
|
130
|
+
* appended automatically.
|
|
131
|
+
* @param source Full source text, used to compute the position.
|
|
132
|
+
* @param offset Character offset of the failure inside `source`.
|
|
133
|
+
*/
|
|
134
|
+
constructor(message: string, source: string, offset: number);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Thrown when a scheme element cannot be written as XML.
|
|
138
|
+
*
|
|
139
|
+
* Raised for element and attribute names that are not valid XML names and
|
|
140
|
+
* for attribute values carrying control characters XML 1.0 cannot encode.
|
|
141
|
+
* The {@link path} pinpoints the offending element inside the tree.
|
|
142
|
+
*/
|
|
143
|
+
declare class XcschemeBuildError extends Error {
|
|
144
|
+
/** Path to the offending element from the root, e.g. `Scheme.BuildAction[0]`. */
|
|
145
|
+
readonly path: string;
|
|
146
|
+
/**
|
|
147
|
+
* @param message Failure description without location; the element path
|
|
148
|
+
* is appended automatically.
|
|
149
|
+
* @param path Path to the offending element from the root.
|
|
150
|
+
*/
|
|
151
|
+
constructor(message: string, path: string);
|
|
152
|
+
}
|
|
117
153
|
/**
|
|
118
154
|
* Thrown by the object model when an operation cannot proceed: the
|
|
119
155
|
* document lacks the structure the operation needs (no objects dictionary,
|
|
@@ -1245,6 +1281,122 @@ declare class XcodeObject<Properties extends PbxprojObject = PbxprojObject> {
|
|
|
1245
1281
|
*/
|
|
1246
1282
|
declare function parsePbxproj(text: string): PbxprojValue;
|
|
1247
1283
|
//#endregion
|
|
1284
|
+
//#region src/scheme/types.d.ts
|
|
1285
|
+
/**
|
|
1286
|
+
* The node model of a scheme document.
|
|
1287
|
+
*
|
|
1288
|
+
* A parsed `.xcscheme` is a tree of plain objects: elements with ordered
|
|
1289
|
+
* attributes and child nodes, plus the occasional comment. All state lives
|
|
1290
|
+
* in this tree. There is no wrapper class to keep in sync, so callers
|
|
1291
|
+
* mutate nodes directly and serialize whatever the tree currently says.
|
|
1292
|
+
*
|
|
1293
|
+
* @module
|
|
1294
|
+
*/
|
|
1295
|
+
/**
|
|
1296
|
+
* An XML element of a scheme document.
|
|
1297
|
+
*
|
|
1298
|
+
* Attribute order is preserved and meaningful: the writer emits attributes
|
|
1299
|
+
* in insertion order, which is how byte-identical round-trips of
|
|
1300
|
+
* Xcode-written files fall out. Assigning an existing key keeps its
|
|
1301
|
+
* position, and adding a new key appends it.
|
|
1302
|
+
*/
|
|
1303
|
+
interface XcschemeElement {
|
|
1304
|
+
/** Tag name, for example `Scheme` or `BuildableReference`. */
|
|
1305
|
+
name: string;
|
|
1306
|
+
/** Attribute values by name, in document order. */
|
|
1307
|
+
attributes: Record<string, string>;
|
|
1308
|
+
/** Child elements and comments, in document order. */
|
|
1309
|
+
children: XcschemeNode[];
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* A comment inside a scheme document. Xcode never writes comments, but
|
|
1313
|
+
* hand-edited and tool-generated schemes can carry them, and dropping
|
|
1314
|
+
* them would make round-trips lossy.
|
|
1315
|
+
*/
|
|
1316
|
+
interface XcschemeComment {
|
|
1317
|
+
/** The comment text between `<!--` and `-->`, verbatim. */
|
|
1318
|
+
comment: string;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Any node a scheme element can contain.
|
|
1322
|
+
*/
|
|
1323
|
+
type XcschemeNode = XcschemeElement | XcschemeComment;
|
|
1324
|
+
/**
|
|
1325
|
+
* A parsed scheme file: its root element plus any comments sitting
|
|
1326
|
+
* outside it. Xcode writes only the root, so the comment lists are almost
|
|
1327
|
+
* always empty, but files that carry them round-trip without loss.
|
|
1328
|
+
*/
|
|
1329
|
+
interface XcschemeDocument {
|
|
1330
|
+
/** Comments before the root element. */
|
|
1331
|
+
leading: XcschemeComment[];
|
|
1332
|
+
/** The `Scheme` element. */
|
|
1333
|
+
root: XcschemeElement;
|
|
1334
|
+
/** Comments after the root element. */
|
|
1335
|
+
trailing: XcschemeComment[];
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Whether a node is an element rather than a comment.
|
|
1339
|
+
*/
|
|
1340
|
+
declare function isXcschemeElement(node: XcschemeNode): node is XcschemeElement;
|
|
1341
|
+
//#endregion
|
|
1342
|
+
//#region src/scheme/build.d.ts
|
|
1343
|
+
/**
|
|
1344
|
+
* Serializes a scheme document to the text of a `.xcscheme` file.
|
|
1345
|
+
*
|
|
1346
|
+
* @throws XcschemeBuildError for element or attribute names that are not
|
|
1347
|
+
* XML names and for attribute values carrying unencodable control
|
|
1348
|
+
* characters, with the path of the offending node.
|
|
1349
|
+
*/
|
|
1350
|
+
declare function buildXcscheme(document: XcschemeDocument): string;
|
|
1351
|
+
//#endregion
|
|
1352
|
+
//#region src/scheme/model.d.ts
|
|
1353
|
+
/**
|
|
1354
|
+
* Collects elements of the given name anywhere in the tree, in document
|
|
1355
|
+
* order, the subtree root included. Passing no name collects every
|
|
1356
|
+
* element.
|
|
1357
|
+
*
|
|
1358
|
+
* ```ts
|
|
1359
|
+
* for (const reference of xcschemeElements(scheme.root, "BuildableReference")) {
|
|
1360
|
+
* reference.attributes["BlueprintName"] = "RenamedApp";
|
|
1361
|
+
* }
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
declare function xcschemeElements(root: XcschemeElement, name?: string): XcschemeElement[];
|
|
1365
|
+
/**
|
|
1366
|
+
* Options for {@link createXcscheme}.
|
|
1367
|
+
*/
|
|
1368
|
+
interface CreateXcschemeOptions {
|
|
1369
|
+
/** The application target's name, which also names the scheme. */
|
|
1370
|
+
appName: string;
|
|
1371
|
+
/**
|
|
1372
|
+
* The `.xcodeproj` directory name the buildable references point at.
|
|
1373
|
+
* Defaults to `<appName>.xcodeproj`.
|
|
1374
|
+
*/
|
|
1375
|
+
xcodeprojName?: string;
|
|
1376
|
+
/**
|
|
1377
|
+
* The application target's object id in the project document. Xcode
|
|
1378
|
+
* repairs an empty identifier on first open, so omitting it still
|
|
1379
|
+
* produces a working scheme.
|
|
1380
|
+
*/
|
|
1381
|
+
blueprintIdentifier?: string;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Creates the scheme Xcode writes for an application target: build,
|
|
1385
|
+
* launch, profile, analyze, and archive actions wired to the app product,
|
|
1386
|
+
* with Xcode's default configuration choices (Debug for development
|
|
1387
|
+
* actions, Release for profiling and archiving).
|
|
1388
|
+
*/
|
|
1389
|
+
declare function createXcscheme(options: CreateXcschemeOptions): XcschemeDocument;
|
|
1390
|
+
//#endregion
|
|
1391
|
+
//#region src/scheme/parse.d.ts
|
|
1392
|
+
/**
|
|
1393
|
+
* Parses the text of a `.xcscheme` file into its node tree.
|
|
1394
|
+
*
|
|
1395
|
+
* @throws XcschemeParseError when the text is not a well-formed scheme
|
|
1396
|
+
* document, with the line and column of the failure.
|
|
1397
|
+
*/
|
|
1398
|
+
declare function parseXcscheme(text: string): XcschemeDocument;
|
|
1399
|
+
//#endregion
|
|
1248
1400
|
//#region src/uuid.d.ts
|
|
1249
1401
|
/**
|
|
1250
1402
|
* Deterministic object identifiers for generated pbxproj objects.
|
|
@@ -1272,4 +1424,4 @@ declare function parsePbxproj(text: string): PbxprojValue;
|
|
|
1272
1424
|
*/
|
|
1273
1425
|
declare function generateObjectId(seed: string, existing: ReadonlySet<string>): string;
|
|
1274
1426
|
//#endregion
|
|
1275
|
-
export { type AddNativeTargetOptions, AggregateTarget, type ApplePlatform, type BuildConfigurationProperties, type BuildFileProperties, BuildPhase, type BuildPhaseProperties, BuildRule, type BuildRuleProperties, type BuildSettings, type ConfigurationListProperties, type ContainerItemProxyProperties, CopyFilesDestination, type ExceptionSetProperties, type FileReferenceProperties, Group, type GroupProperties, Isa, LegacyTarget, type LegacyTargetProperties, NativeTarget, type NativeTargetProperties, type PbxprojArray, PbxprojBuildError, type PbxprojErrorPosition, type PbxprojObject, PbxprojParseError, type PbxprojValue, ProductType, type ProjectIssue, type ProjectIssueKind, ReferenceProxy, type ReferenceProxyProperties, RootProject, type RootProjectProperties, type SwiftPackageProductDependencyProperties, type SwiftPackageReferenceProperties, SyncRootGroup, type SyncRootGroupProperties, Target, type TargetDependencyProperties, type TargetProperties, VersionGroup, type VersionGroupProperties, XcodeModelError, XcodeObject, XcodeProject, buildPbxproj, generateObjectId, parsePbxproj };
|
|
1427
|
+
export { type AddNativeTargetOptions, AggregateTarget, type ApplePlatform, type BuildConfigurationProperties, type BuildFileProperties, BuildPhase, type BuildPhaseProperties, BuildRule, type BuildRuleProperties, type BuildSettings, type ConfigurationListProperties, type ContainerItemProxyProperties, CopyFilesDestination, type CreateXcschemeOptions, type ExceptionSetProperties, type FileReferenceProperties, Group, type GroupProperties, Isa, LegacyTarget, type LegacyTargetProperties, NativeTarget, type NativeTargetProperties, type PbxprojArray, PbxprojBuildError, type PbxprojErrorPosition, type PbxprojObject, PbxprojParseError, type PbxprojValue, ProductType, type ProjectIssue, type ProjectIssueKind, ReferenceProxy, type ReferenceProxyProperties, RootProject, type RootProjectProperties, type SwiftPackageProductDependencyProperties, type SwiftPackageReferenceProperties, SyncRootGroup, type SyncRootGroupProperties, Target, type TargetDependencyProperties, type TargetProperties, VersionGroup, type VersionGroupProperties, XcodeModelError, XcodeObject, XcodeProject, XcschemeBuildError, type XcschemeComment, type XcschemeDocument, type XcschemeElement, type XcschemeNode, XcschemeParseError, buildPbxproj, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parsePbxproj, parseXcscheme, xcschemeElements };
|
package/dist/index.js
CHANGED
|
@@ -259,6 +259,51 @@ var PbxprojParseError = class extends Error {
|
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
261
|
/**
|
|
262
|
+
* Thrown when the source text is not a well-formed scheme document (the
|
|
263
|
+
* XML dialect of `.xcscheme` files).
|
|
264
|
+
*
|
|
265
|
+
* The message always embeds the line and column of the failure, and the
|
|
266
|
+
* same information is available in structured form on {@link position}
|
|
267
|
+
* for programmatic use.
|
|
268
|
+
*/
|
|
269
|
+
var XcschemeParseError = class extends Error {
|
|
270
|
+
/** Where in the source text parsing failed. */
|
|
271
|
+
position;
|
|
272
|
+
/**
|
|
273
|
+
* @param message Failure description without location; the location is
|
|
274
|
+
* appended automatically.
|
|
275
|
+
* @param source Full source text, used to compute the position.
|
|
276
|
+
* @param offset Character offset of the failure inside `source`.
|
|
277
|
+
*/
|
|
278
|
+
constructor(message, source, offset) {
|
|
279
|
+
const position = positionAt(source, offset);
|
|
280
|
+
super(`${message} (line ${position.line}, column ${position.column})`);
|
|
281
|
+
this.name = "XcschemeParseError";
|
|
282
|
+
this.position = position;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Thrown when a scheme element cannot be written as XML.
|
|
287
|
+
*
|
|
288
|
+
* Raised for element and attribute names that are not valid XML names and
|
|
289
|
+
* for attribute values carrying control characters XML 1.0 cannot encode.
|
|
290
|
+
* The {@link path} pinpoints the offending element inside the tree.
|
|
291
|
+
*/
|
|
292
|
+
var XcschemeBuildError = class extends Error {
|
|
293
|
+
/** Path to the offending element from the root, e.g. `Scheme.BuildAction[0]`. */
|
|
294
|
+
path;
|
|
295
|
+
/**
|
|
296
|
+
* @param message Failure description without location; the element path
|
|
297
|
+
* is appended automatically.
|
|
298
|
+
* @param path Path to the offending element from the root.
|
|
299
|
+
*/
|
|
300
|
+
constructor(message, path) {
|
|
301
|
+
super(`${message} (at ${path})`);
|
|
302
|
+
this.name = "XcschemeBuildError";
|
|
303
|
+
this.path = path;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
262
307
|
* Thrown by the object model when an operation cannot proceed: the
|
|
263
308
|
* document lacks the structure the operation needs (no objects dictionary,
|
|
264
309
|
* no root project object), a view's object was removed from the document,
|
|
@@ -1595,11 +1640,11 @@ const IS_LITERAL_CHAR = (() => {
|
|
|
1595
1640
|
for (const ch of "_$/:.-") table[ch.charCodeAt(0)] = 1;
|
|
1596
1641
|
return table;
|
|
1597
1642
|
})();
|
|
1598
|
-
const CODE_TAB = 9;
|
|
1599
|
-
const CODE_LINE_FEED = 10;
|
|
1600
|
-
const CODE_CARRIAGE_RETURN = 13;
|
|
1601
|
-
const CODE_SPACE = 32;
|
|
1602
|
-
const CODE_QUOTE = 34;
|
|
1643
|
+
const CODE_TAB$1 = 9;
|
|
1644
|
+
const CODE_LINE_FEED$1 = 10;
|
|
1645
|
+
const CODE_CARRIAGE_RETURN$1 = 13;
|
|
1646
|
+
const CODE_SPACE$1 = 32;
|
|
1647
|
+
const CODE_QUOTE$1 = 34;
|
|
1603
1648
|
const CODE_SINGLE_QUOTE = 39;
|
|
1604
1649
|
const CODE_OPEN_PAREN = 40;
|
|
1605
1650
|
const CODE_CLOSE_PAREN = 41;
|
|
@@ -1607,13 +1652,13 @@ const CODE_ASTERISK = 42;
|
|
|
1607
1652
|
const CODE_COMMA = 44;
|
|
1608
1653
|
const CODE_MINUS = 45;
|
|
1609
1654
|
const CODE_DOT = 46;
|
|
1610
|
-
const CODE_SLASH = 47;
|
|
1655
|
+
const CODE_SLASH$1 = 47;
|
|
1611
1656
|
const CODE_ZERO = 48;
|
|
1612
1657
|
const CODE_NINE = 57;
|
|
1613
1658
|
const CODE_SEMICOLON = 59;
|
|
1614
|
-
const CODE_LESS_THAN = 60;
|
|
1615
|
-
const CODE_EQUALS = 61;
|
|
1616
|
-
const CODE_GREATER_THAN = 62;
|
|
1659
|
+
const CODE_LESS_THAN$1 = 60;
|
|
1660
|
+
const CODE_EQUALS$1 = 61;
|
|
1661
|
+
const CODE_GREATER_THAN$1 = 62;
|
|
1617
1662
|
const CODE_BACKSLASH = 92;
|
|
1618
1663
|
const CODE_OPEN_BRACE = 123;
|
|
1619
1664
|
const CODE_CLOSE_BRACE = 125;
|
|
@@ -1625,10 +1670,10 @@ const CODE_CLOSE_BRACE = 125;
|
|
|
1625
1670
|
*/
|
|
1626
1671
|
const IS_WHITESPACE = (() => {
|
|
1627
1672
|
const table = /* @__PURE__ */ new Uint8Array(256);
|
|
1628
|
-
table[CODE_SPACE] = 1;
|
|
1629
|
-
table[CODE_TAB] = 1;
|
|
1630
|
-
table[CODE_CARRIAGE_RETURN] = 1;
|
|
1631
|
-
table[CODE_LINE_FEED] = 1;
|
|
1673
|
+
table[CODE_SPACE$1] = 1;
|
|
1674
|
+
table[CODE_TAB$1] = 1;
|
|
1675
|
+
table[CODE_CARRIAGE_RETURN$1] = 1;
|
|
1676
|
+
table[CODE_LINE_FEED$1] = 1;
|
|
1632
1677
|
return table;
|
|
1633
1678
|
})();
|
|
1634
1679
|
/**
|
|
@@ -1644,7 +1689,7 @@ function isDigit(code) {
|
|
|
1644
1689
|
* through the `read*` and `parse*` methods; there is no separate tokenizer
|
|
1645
1690
|
* stage and no token objects.
|
|
1646
1691
|
*/
|
|
1647
|
-
var Parser = class {
|
|
1692
|
+
var Parser$1 = class {
|
|
1648
1693
|
/** Source text of the document being parsed. */
|
|
1649
1694
|
input;
|
|
1650
1695
|
/** Cursor position as a UTF-16 code unit offset into {@link input}. */
|
|
@@ -1691,7 +1736,7 @@ var Parser = class {
|
|
|
1691
1736
|
const length = input.length;
|
|
1692
1737
|
let pos = this.pos;
|
|
1693
1738
|
while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
|
|
1694
|
-
if (pos < length && input.charCodeAt(pos) === CODE_SLASH) pos = this.skipCommentedTrivia(pos);
|
|
1739
|
+
if (pos < length && input.charCodeAt(pos) === CODE_SLASH$1) pos = this.skipCommentedTrivia(pos);
|
|
1695
1740
|
this.pos = pos;
|
|
1696
1741
|
}
|
|
1697
1742
|
/**
|
|
@@ -1711,7 +1756,7 @@ var Parser = class {
|
|
|
1711
1756
|
const length = input.length;
|
|
1712
1757
|
for (;;) {
|
|
1713
1758
|
const next = input.charCodeAt(pos + 1);
|
|
1714
|
-
if (next === CODE_SLASH) {
|
|
1759
|
+
if (next === CODE_SLASH$1) {
|
|
1715
1760
|
const lineEnd = input.indexOf("\n", pos + 2);
|
|
1716
1761
|
pos = lineEnd === -1 ? length : lineEnd;
|
|
1717
1762
|
} else if (next === CODE_ASTERISK) {
|
|
@@ -1723,7 +1768,7 @@ var Parser = class {
|
|
|
1723
1768
|
pos = commentEnd + 2;
|
|
1724
1769
|
} else return pos;
|
|
1725
1770
|
while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
|
|
1726
|
-
if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH) return pos;
|
|
1771
|
+
if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH$1) return pos;
|
|
1727
1772
|
}
|
|
1728
1773
|
}
|
|
1729
1774
|
/**
|
|
@@ -1806,7 +1851,7 @@ var Parser = class {
|
|
|
1806
1851
|
const input = this.input;
|
|
1807
1852
|
const length = input.length;
|
|
1808
1853
|
const start = ++this.pos;
|
|
1809
|
-
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.pos++;
|
|
1854
|
+
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN$1) this.pos++;
|
|
1810
1855
|
if (this.pos >= length) this.fail("Unterminated data run", start - 1);
|
|
1811
1856
|
let hex = "";
|
|
1812
1857
|
for (let i = start; i < this.pos; i++) {
|
|
@@ -1849,10 +1894,10 @@ var Parser = class {
|
|
|
1849
1894
|
return result;
|
|
1850
1895
|
}
|
|
1851
1896
|
let key;
|
|
1852
|
-
if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
|
|
1897
|
+
if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
|
|
1853
1898
|
else if (IS_LITERAL_CHAR[code] === 1) key = this.readLiteral();
|
|
1854
1899
|
else this.fail(`Expected a key but found '${input[this.pos]}'`);
|
|
1855
|
-
this.expect(CODE_EQUALS, "=");
|
|
1900
|
+
this.expect(CODE_EQUALS$1, "=");
|
|
1856
1901
|
const value = this.parseValue();
|
|
1857
1902
|
this.expect(CODE_SEMICOLON, ";");
|
|
1858
1903
|
if (key === "__proto__") Object.defineProperty(result, key, {
|
|
@@ -1905,9 +1950,9 @@ var Parser = class {
|
|
|
1905
1950
|
const code = this.input.charCodeAt(this.pos);
|
|
1906
1951
|
if (code === CODE_OPEN_BRACE) return this.parseObject();
|
|
1907
1952
|
if (code === CODE_OPEN_PAREN) return this.parseArray();
|
|
1908
|
-
if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
|
|
1953
|
+
if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
|
|
1909
1954
|
if (IS_LITERAL_CHAR[code] === 1) return interpretLiteral(this.readLiteral());
|
|
1910
|
-
if (code === CODE_LESS_THAN) return this.readData();
|
|
1955
|
+
if (code === CODE_LESS_THAN$1) return this.readData();
|
|
1911
1956
|
this.fail(`Expected a value but found '${this.input[this.pos]}'`);
|
|
1912
1957
|
}
|
|
1913
1958
|
};
|
|
@@ -1961,7 +2006,7 @@ function interpretLiteral(literal) {
|
|
|
1961
2006
|
* carries the line and column of the failure.
|
|
1962
2007
|
*/
|
|
1963
2008
|
function parsePbxproj(text) {
|
|
1964
|
-
return new Parser(text).parseDocument();
|
|
2009
|
+
return new Parser$1(text).parseDocument();
|
|
1965
2010
|
}
|
|
1966
2011
|
//#endregion
|
|
1967
2012
|
//#region src/md5.ts
|
|
@@ -3397,4 +3442,482 @@ function stripReferences(properties, id) {
|
|
|
3397
3442
|
}
|
|
3398
3443
|
}
|
|
3399
3444
|
//#endregion
|
|
3400
|
-
|
|
3445
|
+
//#region src/scheme/types.ts
|
|
3446
|
+
/**
|
|
3447
|
+
* Whether a node is an element rather than a comment.
|
|
3448
|
+
*/
|
|
3449
|
+
function isXcschemeElement(node) {
|
|
3450
|
+
return "name" in node;
|
|
3451
|
+
}
|
|
3452
|
+
//#endregion
|
|
3453
|
+
//#region src/scheme/build.ts
|
|
3454
|
+
/**
|
|
3455
|
+
* Serializer for the `.xcscheme` XML dialect.
|
|
3456
|
+
*
|
|
3457
|
+
* The output reproduces Xcode's own layout byte for byte: the UTF-8
|
|
3458
|
+
* declaration, three-space indentation, one attribute per line with
|
|
3459
|
+
* spaces around the equals sign, the closing angle bracket glued to the
|
|
3460
|
+
* last attribute, and an explicit close tag on every element. Parsing an
|
|
3461
|
+
* Xcode-written scheme and building it back yields the identical file,
|
|
3462
|
+
* and any input reaches that canonical form in one build.
|
|
3463
|
+
*
|
|
3464
|
+
* @module
|
|
3465
|
+
*/
|
|
3466
|
+
const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
3467
|
+
/** One indentation step of Xcode's scheme writer. */
|
|
3468
|
+
const INDENT = " ";
|
|
3469
|
+
/**
|
|
3470
|
+
* Matches the names Xcode's scheme vocabulary uses; a stray non-name
|
|
3471
|
+
* (an empty string, spaces, XML syntax) must fail rather than produce a
|
|
3472
|
+
* document no parser accepts.
|
|
3473
|
+
*/
|
|
3474
|
+
const NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/u;
|
|
3475
|
+
/**
|
|
3476
|
+
* Matches characters that cannot appear in an attribute value: XML 1.0
|
|
3477
|
+
* has no representation for control characters other than tab, line
|
|
3478
|
+
* feed, and carriage return.
|
|
3479
|
+
*/
|
|
3480
|
+
const UNENCODABLE_PATTERN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]/u;
|
|
3481
|
+
/**
|
|
3482
|
+
* Serializes a scheme document to the text of a `.xcscheme` file.
|
|
3483
|
+
*
|
|
3484
|
+
* @throws XcschemeBuildError for element or attribute names that are not
|
|
3485
|
+
* XML names and for attribute values carrying unencodable control
|
|
3486
|
+
* characters, with the path of the offending node.
|
|
3487
|
+
*/
|
|
3488
|
+
function buildXcscheme(document) {
|
|
3489
|
+
let output = XML_DECLARATION;
|
|
3490
|
+
for (const comment of document.leading) output += `<!--${comment.comment}-->\n`;
|
|
3491
|
+
output += renderElement(document.root, 0, document.root.name);
|
|
3492
|
+
for (const comment of document.trailing) output += `<!--${comment.comment}-->\n`;
|
|
3493
|
+
return output;
|
|
3494
|
+
}
|
|
3495
|
+
/**
|
|
3496
|
+
* Renders one element with Xcode's layout: attributes each on their own
|
|
3497
|
+
* line indented one step past the tag, `>` glued to the last attribute,
|
|
3498
|
+
* children indented one step, and always an explicit close tag.
|
|
3499
|
+
*/
|
|
3500
|
+
function renderElement(element, depth, path) {
|
|
3501
|
+
if (!NAME_PATTERN.test(element.name)) throw new XcschemeBuildError(`Element name ${JSON.stringify(element.name)} is not a valid XML name`, path);
|
|
3502
|
+
const indent = INDENT.repeat(depth);
|
|
3503
|
+
const names = Object.keys(element.attributes);
|
|
3504
|
+
let output;
|
|
3505
|
+
if (names.length === 0) output = `${indent}<${element.name}>\n`;
|
|
3506
|
+
else {
|
|
3507
|
+
output = `${indent}<${element.name}\n`;
|
|
3508
|
+
const attributeIndent = indent + INDENT;
|
|
3509
|
+
for (let i = 0; i < names.length; i++) {
|
|
3510
|
+
const name = names[i];
|
|
3511
|
+
if (!NAME_PATTERN.test(name)) throw new XcschemeBuildError(`Attribute name ${JSON.stringify(name)} is not a valid XML name`, path);
|
|
3512
|
+
const value = escapeAttribute(element.attributes[name], path, name);
|
|
3513
|
+
const terminator = i === names.length - 1 ? ">" : "";
|
|
3514
|
+
output += `${attributeIndent}${name} = "${value}"${terminator}\n`;
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
output += renderChildren(element.children, depth + 1, path);
|
|
3518
|
+
output += `${indent}</${element.name}>\n`;
|
|
3519
|
+
return output;
|
|
3520
|
+
}
|
|
3521
|
+
/**
|
|
3522
|
+
* Renders an element's children in document order, numbering repeated
|
|
3523
|
+
* element names in the error path so a failure points at one node.
|
|
3524
|
+
*/
|
|
3525
|
+
function renderChildren(children, depth, path) {
|
|
3526
|
+
let output = "";
|
|
3527
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3528
|
+
for (const child of children) if (isXcschemeElement(child)) {
|
|
3529
|
+
const index = seen.get(child.name) ?? 0;
|
|
3530
|
+
seen.set(child.name, index + 1);
|
|
3531
|
+
output += renderElement(child, depth, `${path}.${child.name}[${index}]`);
|
|
3532
|
+
} else output += `${INDENT.repeat(depth)}<!--${child.comment}-->\n`;
|
|
3533
|
+
return output;
|
|
3534
|
+
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Escapes an attribute value the way Xcode's writer does: the five named
|
|
3537
|
+
* entities for XML syntax characters, and character references for tab,
|
|
3538
|
+
* line feed, and carriage return so they survive attribute-value
|
|
3539
|
+
* normalization on the next parse.
|
|
3540
|
+
*/
|
|
3541
|
+
function escapeAttribute(value, path, attributeName) {
|
|
3542
|
+
const unencodable = UNENCODABLE_PATTERN.exec(value);
|
|
3543
|
+
if (unencodable != null) throw new XcschemeBuildError(`Attribute ${attributeName} contains the control character U+${unencodable[0].charCodeAt(0).toString(16).padStart(4, "0").toUpperCase()}, which XML cannot encode`, path);
|
|
3544
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'").replaceAll(" ", "	").replaceAll("\n", " ").replaceAll("\r", " ");
|
|
3545
|
+
}
|
|
3546
|
+
//#endregion
|
|
3547
|
+
//#region src/scheme/model.ts
|
|
3548
|
+
/**
|
|
3549
|
+
* Helpers over the scheme node tree: element queries and the default
|
|
3550
|
+
* scheme factory.
|
|
3551
|
+
*
|
|
3552
|
+
* The tree itself is plain data, so most edits are direct property
|
|
3553
|
+
* writes. What this module adds is the recursive query that editing
|
|
3554
|
+
* flows start from and a factory producing the scheme Xcode's own "New
|
|
3555
|
+
* Scheme" action writes for an application target.
|
|
3556
|
+
*
|
|
3557
|
+
* @module
|
|
3558
|
+
*/
|
|
3559
|
+
/**
|
|
3560
|
+
* Collects elements of the given name anywhere in the tree, in document
|
|
3561
|
+
* order, the subtree root included. Passing no name collects every
|
|
3562
|
+
* element.
|
|
3563
|
+
*
|
|
3564
|
+
* ```ts
|
|
3565
|
+
* for (const reference of xcschemeElements(scheme.root, "BuildableReference")) {
|
|
3566
|
+
* reference.attributes["BlueprintName"] = "RenamedApp";
|
|
3567
|
+
* }
|
|
3568
|
+
* ```
|
|
3569
|
+
*/
|
|
3570
|
+
function xcschemeElements(root, name) {
|
|
3571
|
+
const found = [];
|
|
3572
|
+
const visit = (element) => {
|
|
3573
|
+
if (name == null || element.name === name) found.push(element);
|
|
3574
|
+
for (const child of element.children) if (isXcschemeElement(child)) visit(child);
|
|
3575
|
+
};
|
|
3576
|
+
visit(root);
|
|
3577
|
+
return found;
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* Creates the scheme Xcode writes for an application target: build,
|
|
3581
|
+
* launch, profile, analyze, and archive actions wired to the app product,
|
|
3582
|
+
* with Xcode's default configuration choices (Debug for development
|
|
3583
|
+
* actions, Release for profiling and archiving).
|
|
3584
|
+
*/
|
|
3585
|
+
function createXcscheme(options) {
|
|
3586
|
+
const xcodeprojName = options.xcodeprojName ?? `${options.appName}.xcodeproj`;
|
|
3587
|
+
/**
|
|
3588
|
+
* Buildable references appear once per action; each site gets its own
|
|
3589
|
+
* element so a mutation through one action cannot alias into another.
|
|
3590
|
+
*/
|
|
3591
|
+
const buildableReference = () => ({
|
|
3592
|
+
name: "BuildableReference",
|
|
3593
|
+
attributes: {
|
|
3594
|
+
BuildableIdentifier: "primary",
|
|
3595
|
+
BlueprintIdentifier: options.blueprintIdentifier ?? "",
|
|
3596
|
+
BuildableName: `${options.appName}.app`,
|
|
3597
|
+
BlueprintName: options.appName,
|
|
3598
|
+
ReferencedContainer: `container:${xcodeprojName}`
|
|
3599
|
+
},
|
|
3600
|
+
children: []
|
|
3601
|
+
});
|
|
3602
|
+
return {
|
|
3603
|
+
leading: [],
|
|
3604
|
+
root: {
|
|
3605
|
+
name: "Scheme",
|
|
3606
|
+
attributes: { version: "1.7" },
|
|
3607
|
+
children: [
|
|
3608
|
+
{
|
|
3609
|
+
name: "BuildAction",
|
|
3610
|
+
attributes: {
|
|
3611
|
+
parallelizeBuildables: "YES",
|
|
3612
|
+
buildImplicitDependencies: "YES"
|
|
3613
|
+
},
|
|
3614
|
+
children: [{
|
|
3615
|
+
name: "BuildActionEntries",
|
|
3616
|
+
attributes: {},
|
|
3617
|
+
children: [{
|
|
3618
|
+
name: "BuildActionEntry",
|
|
3619
|
+
attributes: {
|
|
3620
|
+
buildForTesting: "YES",
|
|
3621
|
+
buildForRunning: "YES",
|
|
3622
|
+
buildForProfiling: "YES",
|
|
3623
|
+
buildForArchiving: "YES",
|
|
3624
|
+
buildForAnalyzing: "YES"
|
|
3625
|
+
},
|
|
3626
|
+
children: [buildableReference()]
|
|
3627
|
+
}]
|
|
3628
|
+
}]
|
|
3629
|
+
},
|
|
3630
|
+
{
|
|
3631
|
+
name: "LaunchAction",
|
|
3632
|
+
attributes: {
|
|
3633
|
+
buildConfiguration: "Debug",
|
|
3634
|
+
selectedDebuggerIdentifier: "Xcode.DebuggerFoundation.Debugger.LLDB",
|
|
3635
|
+
selectedLauncherIdentifier: "Xcode.DebuggerFoundation.Launcher.LLDB",
|
|
3636
|
+
launchStyle: "0",
|
|
3637
|
+
useCustomWorkingDirectory: "NO",
|
|
3638
|
+
ignoresPersistentStateOnLaunch: "NO",
|
|
3639
|
+
debugDocumentVersioning: "YES",
|
|
3640
|
+
debugServiceExtension: "internal",
|
|
3641
|
+
allowLocationSimulation: "YES"
|
|
3642
|
+
},
|
|
3643
|
+
children: [{
|
|
3644
|
+
name: "BuildableProductRunnable",
|
|
3645
|
+
attributes: { runnableDebuggingMode: "0" },
|
|
3646
|
+
children: [buildableReference()]
|
|
3647
|
+
}]
|
|
3648
|
+
},
|
|
3649
|
+
{
|
|
3650
|
+
name: "ProfileAction",
|
|
3651
|
+
attributes: {
|
|
3652
|
+
buildConfiguration: "Release",
|
|
3653
|
+
shouldUseLaunchSchemeArgsEnv: "YES",
|
|
3654
|
+
savedToolIdentifier: "",
|
|
3655
|
+
useCustomWorkingDirectory: "NO",
|
|
3656
|
+
debugDocumentVersioning: "YES"
|
|
3657
|
+
},
|
|
3658
|
+
children: [{
|
|
3659
|
+
name: "BuildableProductRunnable",
|
|
3660
|
+
attributes: { runnableDebuggingMode: "0" },
|
|
3661
|
+
children: [buildableReference()]
|
|
3662
|
+
}]
|
|
3663
|
+
},
|
|
3664
|
+
{
|
|
3665
|
+
name: "AnalyzeAction",
|
|
3666
|
+
attributes: { buildConfiguration: "Debug" },
|
|
3667
|
+
children: []
|
|
3668
|
+
},
|
|
3669
|
+
{
|
|
3670
|
+
name: "ArchiveAction",
|
|
3671
|
+
attributes: {
|
|
3672
|
+
buildConfiguration: "Release",
|
|
3673
|
+
revealArchiveInOrganizer: "YES"
|
|
3674
|
+
},
|
|
3675
|
+
children: []
|
|
3676
|
+
}
|
|
3677
|
+
]
|
|
3678
|
+
},
|
|
3679
|
+
trailing: []
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
//#endregion
|
|
3683
|
+
//#region src/scheme/parse.ts
|
|
3684
|
+
/**
|
|
3685
|
+
* Parser for the `.xcscheme` XML dialect.
|
|
3686
|
+
*
|
|
3687
|
+
* Scheme files are XML with a narrow shape: elements with attributes and
|
|
3688
|
+
* child elements, no text content, no namespaces, no DOCTYPE. The parser
|
|
3689
|
+
* accepts that shape from any writer (attribute order, quoting style, and
|
|
3690
|
+
* whitespace vary across tools), resolves character and entity references
|
|
3691
|
+
* in attribute values, and preserves comments. Anything outside the shape
|
|
3692
|
+
* fails loudly with a position, in line with the pbxproj parser.
|
|
3693
|
+
*
|
|
3694
|
+
* @module
|
|
3695
|
+
*/
|
|
3696
|
+
const CODE_TAB = 9;
|
|
3697
|
+
const CODE_LINE_FEED = 10;
|
|
3698
|
+
const CODE_CARRIAGE_RETURN = 13;
|
|
3699
|
+
const CODE_SPACE = 32;
|
|
3700
|
+
const CODE_BANG = 33;
|
|
3701
|
+
const CODE_QUOTE = 34;
|
|
3702
|
+
const CODE_AMPERSAND = 38;
|
|
3703
|
+
const CODE_APOSTROPHE = 39;
|
|
3704
|
+
const CODE_HYPHEN = 45;
|
|
3705
|
+
const CODE_SLASH = 47;
|
|
3706
|
+
const CODE_LESS_THAN = 60;
|
|
3707
|
+
const CODE_EQUALS = 61;
|
|
3708
|
+
const CODE_GREATER_THAN = 62;
|
|
3709
|
+
const CODE_QUESTION = 63;
|
|
3710
|
+
const CODE_HASH = 35;
|
|
3711
|
+
const CODE_BOM = 65279;
|
|
3712
|
+
/**
|
|
3713
|
+
* Whether a code unit is XML whitespace (space, tab, line feed, or
|
|
3714
|
+
* carriage return).
|
|
3715
|
+
*/
|
|
3716
|
+
function isWhitespace(code) {
|
|
3717
|
+
return code === CODE_SPACE || code === CODE_TAB || code === CODE_LINE_FEED || code === CODE_CARRIAGE_RETURN;
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Whether a code unit can start an XML name. The scheme vocabulary is
|
|
3721
|
+
* ASCII, so the accepted alphabet is letters, underscore, and colon.
|
|
3722
|
+
*/
|
|
3723
|
+
function isNameStart(code) {
|
|
3724
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 58;
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Whether a code unit can continue an XML name.
|
|
3728
|
+
*/
|
|
3729
|
+
function isNameChar(code) {
|
|
3730
|
+
return isNameStart(code) || code >= 48 && code <= 57 || code === CODE_HYPHEN || code === 46;
|
|
3731
|
+
}
|
|
3732
|
+
/** The named character entities XML 1.0 predefines. */
|
|
3733
|
+
const NAMED_ENTITIES = {
|
|
3734
|
+
amp: "&",
|
|
3735
|
+
apos: "'",
|
|
3736
|
+
gt: ">",
|
|
3737
|
+
lt: "<",
|
|
3738
|
+
quot: "\""
|
|
3739
|
+
};
|
|
3740
|
+
/**
|
|
3741
|
+
* Parses the text of a `.xcscheme` file into its node tree.
|
|
3742
|
+
*
|
|
3743
|
+
* @throws XcschemeParseError when the text is not a well-formed scheme
|
|
3744
|
+
* document, with the line and column of the failure.
|
|
3745
|
+
*/
|
|
3746
|
+
function parseXcscheme(text) {
|
|
3747
|
+
return new Parser(text).parseDocument();
|
|
3748
|
+
}
|
|
3749
|
+
var Parser = class {
|
|
3750
|
+
input;
|
|
3751
|
+
pos = 0;
|
|
3752
|
+
constructor(input) {
|
|
3753
|
+
this.input = input;
|
|
3754
|
+
if (input.charCodeAt(0) === CODE_BOM) this.pos = 1;
|
|
3755
|
+
}
|
|
3756
|
+
parseDocument() {
|
|
3757
|
+
this.skipWhitespace();
|
|
3758
|
+
this.skipDeclaration();
|
|
3759
|
+
const leading = [];
|
|
3760
|
+
const trailing = [];
|
|
3761
|
+
this.skipWhitespace();
|
|
3762
|
+
while (this.peekIsCommentStart()) {
|
|
3763
|
+
leading.push(this.parseComment());
|
|
3764
|
+
this.skipWhitespace();
|
|
3765
|
+
}
|
|
3766
|
+
const root = this.parseElement();
|
|
3767
|
+
this.skipWhitespace();
|
|
3768
|
+
while (this.peekIsCommentStart()) {
|
|
3769
|
+
trailing.push(this.parseComment());
|
|
3770
|
+
this.skipWhitespace();
|
|
3771
|
+
}
|
|
3772
|
+
if (this.pos < this.input.length) this.fail("Expected end of document after the root element");
|
|
3773
|
+
return {
|
|
3774
|
+
leading,
|
|
3775
|
+
root,
|
|
3776
|
+
trailing
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* Skips the `<?xml ... ?>` declaration when present. Its attributes are
|
|
3781
|
+
* not retained: the writer always emits the canonical UTF-8 declaration.
|
|
3782
|
+
*/
|
|
3783
|
+
skipDeclaration() {
|
|
3784
|
+
if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN || this.input.charCodeAt(this.pos + 1) !== CODE_QUESTION) return;
|
|
3785
|
+
const end = this.input.indexOf("?>", this.pos + 2);
|
|
3786
|
+
if (end === -1) this.fail("Unterminated XML declaration");
|
|
3787
|
+
this.pos = end + 2;
|
|
3788
|
+
}
|
|
3789
|
+
parseElement() {
|
|
3790
|
+
if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN) this.fail("Expected an element");
|
|
3791
|
+
this.pos++;
|
|
3792
|
+
const name = this.parseName("element name");
|
|
3793
|
+
const attributes = Object.create(null);
|
|
3794
|
+
for (;;) {
|
|
3795
|
+
const hadWhitespace = this.skipWhitespace();
|
|
3796
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3797
|
+
if (code === CODE_GREATER_THAN) {
|
|
3798
|
+
this.pos++;
|
|
3799
|
+
break;
|
|
3800
|
+
}
|
|
3801
|
+
if (code === CODE_SLASH && this.input.charCodeAt(this.pos + 1) === CODE_GREATER_THAN) {
|
|
3802
|
+
this.pos += 2;
|
|
3803
|
+
return {
|
|
3804
|
+
name,
|
|
3805
|
+
attributes,
|
|
3806
|
+
children: []
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
if (Number.isNaN(code)) this.fail(`Unterminated <${name}> tag`);
|
|
3810
|
+
if (!hadWhitespace) this.fail("Expected whitespace before an attribute");
|
|
3811
|
+
const attributeName = this.parseName("attribute name");
|
|
3812
|
+
this.skipWhitespace();
|
|
3813
|
+
if (this.input.charCodeAt(this.pos) !== CODE_EQUALS) this.fail(`Expected = after attribute ${attributeName}`);
|
|
3814
|
+
this.pos++;
|
|
3815
|
+
this.skipWhitespace();
|
|
3816
|
+
if (attributeName in attributes) this.fail(`Duplicate attribute ${attributeName} on <${name}>`);
|
|
3817
|
+
attributes[attributeName] = this.parseAttributeValue();
|
|
3818
|
+
}
|
|
3819
|
+
const children = [];
|
|
3820
|
+
for (;;) {
|
|
3821
|
+
this.skipWhitespace();
|
|
3822
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3823
|
+
if (Number.isNaN(code)) this.fail(`Missing </${name}> close tag`);
|
|
3824
|
+
if (code !== CODE_LESS_THAN) this.fail("Unexpected text content inside an element");
|
|
3825
|
+
if (this.input.charCodeAt(this.pos + 1) === CODE_SLASH) {
|
|
3826
|
+
this.pos += 2;
|
|
3827
|
+
const closeName = this.parseName("close tag name");
|
|
3828
|
+
if (closeName !== name) this.fail(`Expected </${name}> but found </${closeName}>`);
|
|
3829
|
+
this.skipWhitespace();
|
|
3830
|
+
if (this.input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.fail(`Malformed </${closeName}> close tag`);
|
|
3831
|
+
this.pos++;
|
|
3832
|
+
return {
|
|
3833
|
+
name,
|
|
3834
|
+
attributes,
|
|
3835
|
+
children
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
if (this.peekIsCommentStart()) children.push(this.parseComment());
|
|
3839
|
+
else children.push(this.parseElement());
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
peekIsCommentStart() {
|
|
3843
|
+
return this.input.charCodeAt(this.pos) === CODE_LESS_THAN && this.input.charCodeAt(this.pos + 1) === CODE_BANG && this.input.charCodeAt(this.pos + 2) === CODE_HYPHEN && this.input.charCodeAt(this.pos + 3) === CODE_HYPHEN;
|
|
3844
|
+
}
|
|
3845
|
+
parseComment() {
|
|
3846
|
+
const end = this.input.indexOf("-->", this.pos + 4);
|
|
3847
|
+
if (end === -1) this.fail("Unterminated comment");
|
|
3848
|
+
const comment = this.input.slice(this.pos + 4, end);
|
|
3849
|
+
this.pos = end + 3;
|
|
3850
|
+
return { comment };
|
|
3851
|
+
}
|
|
3852
|
+
parseName(what) {
|
|
3853
|
+
const start = this.pos;
|
|
3854
|
+
if (!isNameStart(this.input.charCodeAt(this.pos))) this.fail(`Expected ${what}`);
|
|
3855
|
+
this.pos++;
|
|
3856
|
+
while (isNameChar(this.input.charCodeAt(this.pos))) this.pos++;
|
|
3857
|
+
return this.input.slice(start, this.pos);
|
|
3858
|
+
}
|
|
3859
|
+
parseAttributeValue() {
|
|
3860
|
+
const quote = this.input.charCodeAt(this.pos);
|
|
3861
|
+
if (quote !== CODE_QUOTE && quote !== CODE_APOSTROPHE) this.fail("Expected a quoted attribute value");
|
|
3862
|
+
this.pos++;
|
|
3863
|
+
let value = "";
|
|
3864
|
+
let runStart = this.pos;
|
|
3865
|
+
for (;;) {
|
|
3866
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3867
|
+
if (Number.isNaN(code)) this.fail("Unterminated attribute value");
|
|
3868
|
+
if (code === quote) {
|
|
3869
|
+
value += this.input.slice(runStart, this.pos);
|
|
3870
|
+
this.pos++;
|
|
3871
|
+
return value;
|
|
3872
|
+
}
|
|
3873
|
+
if (code === CODE_LESS_THAN) this.fail("Attribute values cannot contain a raw <");
|
|
3874
|
+
if (code === CODE_AMPERSAND) {
|
|
3875
|
+
value += this.input.slice(runStart, this.pos);
|
|
3876
|
+
value += this.parseReference();
|
|
3877
|
+
runStart = this.pos;
|
|
3878
|
+
} else this.pos++;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Resolves one `&...;` reference with the cursor on the ampersand. The
|
|
3883
|
+
* accepted forms are the five XML named entities and decimal or hex
|
|
3884
|
+
* character references, which covers everything observed in
|
|
3885
|
+
* Xcode-written schemes (`"`, `&`, `'`, `<`, `>`,
|
|
3886
|
+
* and ` `-style whitespace).
|
|
3887
|
+
*/
|
|
3888
|
+
parseReference() {
|
|
3889
|
+
const start = this.pos;
|
|
3890
|
+
const end = this.input.indexOf(";", start + 1);
|
|
3891
|
+
if (end === -1 || end - start > 12) this.fail("Unterminated character reference");
|
|
3892
|
+
const body = this.input.slice(start + 1, end);
|
|
3893
|
+
this.pos = end + 1;
|
|
3894
|
+
if (body.charCodeAt(0) === CODE_HASH) {
|
|
3895
|
+
const isHex = body.charCodeAt(1) === 120 || body.charCodeAt(1) === 88;
|
|
3896
|
+
const digits = body.slice(isHex ? 2 : 1);
|
|
3897
|
+
const radix = isHex ? 16 : 10;
|
|
3898
|
+
const codePoint = Number.parseInt(digits, radix);
|
|
3899
|
+
if (digits.length === 0 || Number.isNaN(codePoint) || codePoint > 1114111) this.failAt(`Invalid character reference &${body};`, start);
|
|
3900
|
+
return String.fromCodePoint(codePoint);
|
|
3901
|
+
}
|
|
3902
|
+
const named = Object.hasOwn(NAMED_ENTITIES, body) ? NAMED_ENTITIES[body] : void 0;
|
|
3903
|
+
if (named == null) this.failAt(`Unknown entity &${body};`, start);
|
|
3904
|
+
return named;
|
|
3905
|
+
}
|
|
3906
|
+
/**
|
|
3907
|
+
* Skips whitespace and reports whether any was consumed, which attribute
|
|
3908
|
+
* parsing uses to require separation between attributes.
|
|
3909
|
+
*/
|
|
3910
|
+
skipWhitespace() {
|
|
3911
|
+
const start = this.pos;
|
|
3912
|
+
while (isWhitespace(this.input.charCodeAt(this.pos))) this.pos++;
|
|
3913
|
+
return this.pos > start;
|
|
3914
|
+
}
|
|
3915
|
+
fail(message) {
|
|
3916
|
+
this.failAt(message, this.pos);
|
|
3917
|
+
}
|
|
3918
|
+
failAt(message, offset) {
|
|
3919
|
+
throw new XcschemeParseError(message, this.input, offset);
|
|
3920
|
+
}
|
|
3921
|
+
};
|
|
3922
|
+
//#endregion
|
|
3923
|
+
export { AggregateTarget, BuildPhase, BuildRule, CopyFilesDestination, Group, Isa, LegacyTarget, NativeTarget, PbxprojBuildError, PbxprojParseError, ProductType, ReferenceProxy, RootProject, SyncRootGroup, Target, VersionGroup, XcodeModelError, XcodeObject, XcodeProject, XcschemeBuildError, XcschemeParseError, buildPbxproj, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parsePbxproj, parseXcscheme, xcschemeElements };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rork-xcode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-dependency Xcode project (pbxproj) parser and
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Zero-dependency Xcode project (pbxproj) and scheme (xcscheme) parser, builder, and object model that runs identically in every JavaScript runtime",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apple",
|
|
7
7
|
"builder",
|