react-native-intune 0.1.2 → 1.0.0-rc.1
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 +8 -4
- package/RNIntune.podspec +3 -3
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/reactnativeintune/RNIntuneResetJournal.kt +1 -1
- package/ios/RNIntunePlistGuard.m +1 -1
- package/ios/RNIntuneResetJournal.m +1 -1
- package/lib/module/api/tokenProvider.js +3 -0
- package/lib/module/api/tokenProvider.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal/decode.js +23 -2
- package/lib/module/internal/decode.js.map +1 -1
- package/lib/module/types.js +37 -6
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/api/tokenProvider.d.ts +3 -0
- package/lib/typescript/src/api/tokenProvider.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal/decode.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +61 -11
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +4 -6
- package/plugin/transforms.js +110 -3
- package/scripts/doctor.mjs +7 -3
- package/scripts/lib/checks.mjs +329 -28
- package/scripts/lib/edits.mjs +329 -0
- package/scripts/lib/project.mjs +13 -0
- package/scripts/setup.mjs +59 -9
- package/sdk-lock.json +9 -8
- package/sdk-versions.json +125 -28
- package/src/api/tokenProvider.ts +3 -0
- package/src/index.ts +1 -0
- package/src/internal/decode.ts +27 -8
- package/src/types.ts +66 -15
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text edits `setup` makes to iOS project files, as pure functions.
|
|
3
|
+
*
|
|
4
|
+
* Text, not a parse-and-serialise round trip, for plists and project.pbxproj alike.
|
|
5
|
+
* `plutil` would have been simpler and it drops comments: React Native's template keeps
|
|
6
|
+
* "Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection!" inside
|
|
7
|
+
* Info.plist, and a setup that silently deletes the template's warnings has made a change
|
|
8
|
+
* nobody asked for. Every function here inserts or replaces the smallest span it can and
|
|
9
|
+
* leaves the rest of the file byte for byte.
|
|
10
|
+
*
|
|
11
|
+
* Each returns the new text, or null when the file does not have the shape the edit
|
|
12
|
+
* needs. Null becomes "do this by hand" — the same answer as a check without an apply,
|
|
13
|
+
* which beats an edit landing somewhere it has no effect.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import crypto from 'node:crypto';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------- plist
|
|
19
|
+
|
|
20
|
+
/** The top-level dict's closing tag — the last `</dict>` before `</plist>`. */
|
|
21
|
+
function topLevelEnd(xml) {
|
|
22
|
+
const plist = xml.lastIndexOf('</plist>');
|
|
23
|
+
const at = plist > -1 ? xml.lastIndexOf('</dict>', plist) : -1;
|
|
24
|
+
return at > -1 ? at : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Adds `<key>…</key>` and its value at the end of the top-level dict. */
|
|
28
|
+
export function plistAddKey(xml, key, valueXml) {
|
|
29
|
+
const at = topLevelEnd(xml);
|
|
30
|
+
if (at === null) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const entry = `\t<key>${key}</key>\n${valueXml.replace(/^/gm, '\t')}\n`;
|
|
34
|
+
return `${xml.slice(0, at)}${entry}${xml.slice(at)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The span of the `<array>` that follows a top-level `<key>`, or null. */
|
|
38
|
+
function arrayAfterKey(xml, key) {
|
|
39
|
+
const m = new RegExp(`<key>${key}</key>\\s*<array>`).exec(xml);
|
|
40
|
+
if (!m) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const start = m.index + m[0].length;
|
|
44
|
+
const end = xml.indexOf('</array>', start);
|
|
45
|
+
return end > -1 ? { start, end } : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The indentation the existing items of an array use, or a sensible default. */
|
|
49
|
+
function itemIndent(xml, span, fallback) {
|
|
50
|
+
return xml.slice(span.start, span.end).match(/\n([ \t]*)</)?.[1] ?? fallback;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Makes `strings` present in the array under `key`, creating the key if it is absent.
|
|
55
|
+
* Existing entries are left where they are; missing ones are appended.
|
|
56
|
+
*/
|
|
57
|
+
export function plistEnsureStrings(xml, key, strings) {
|
|
58
|
+
const span = arrayAfterKey(xml, key);
|
|
59
|
+
if (!span) {
|
|
60
|
+
if (new RegExp(`<key>${key}</key>`).test(xml)) {
|
|
61
|
+
return null; // present, but not an array — not ours to reshape
|
|
62
|
+
}
|
|
63
|
+
return plistAddKey(
|
|
64
|
+
xml,
|
|
65
|
+
key,
|
|
66
|
+
`<array>\n${strings.map((s) => `\t<string>${s}</string>`).join('\n')}\n</array>`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const body = xml.slice(span.start, span.end);
|
|
70
|
+
const missing = strings.filter(
|
|
71
|
+
(s) => !body.includes(`<string>${s}</string>`)
|
|
72
|
+
);
|
|
73
|
+
if (missing.length === 0) {
|
|
74
|
+
return xml;
|
|
75
|
+
}
|
|
76
|
+
const indent = itemIndent(xml, span, '\t\t');
|
|
77
|
+
const closing = body.match(/\n([ \t]*)$/)?.[0] ?? '\n\t';
|
|
78
|
+
const trimmed = body.replace(/\s*$/, '');
|
|
79
|
+
const added = missing.map((s) => `\n${indent}<string>${s}</string>`).join('');
|
|
80
|
+
return `${xml.slice(0, span.start)}${trimmed}${added}${closing}${xml.slice(span.end)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Adds a URL type carrying `scheme` to CFBundleURLTypes, creating the key if needed.
|
|
85
|
+
* A scheme already present in any URL type is left alone.
|
|
86
|
+
*/
|
|
87
|
+
export function plistEnsureUrlScheme(xml, scheme) {
|
|
88
|
+
if (xml.includes(`<string>${scheme}</string>`)) {
|
|
89
|
+
return xml;
|
|
90
|
+
}
|
|
91
|
+
const type = `<dict>\n\t<key>CFBundleURLSchemes</key>\n\t<array>\n\t\t<string>${scheme}</string>\n\t</array>\n</dict>`;
|
|
92
|
+
const span = arrayAfterKey(xml, 'CFBundleURLTypes');
|
|
93
|
+
if (!span) {
|
|
94
|
+
if (/<key>CFBundleURLTypes<\/key>/.test(xml)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return plistAddKey(
|
|
98
|
+
xml,
|
|
99
|
+
'CFBundleURLTypes',
|
|
100
|
+
`<array>\n${type.replace(/^/gm, '\t')}\n</array>`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
// The URL types array holds dicts, and `arrayAfterKey` stops at the first </array> —
|
|
104
|
+
// the inner CFBundleURLSchemes one. Insert right after the opening tag instead, which
|
|
105
|
+
// needs no knowledge of what follows.
|
|
106
|
+
const indent = itemIndent(
|
|
107
|
+
xml,
|
|
108
|
+
{ start: span.start, end: span.start + 200 },
|
|
109
|
+
'\t\t'
|
|
110
|
+
);
|
|
111
|
+
const entry = `\n${type.replace(/^/gm, indent)}`;
|
|
112
|
+
return `${xml.slice(0, span.start)}${entry}${xml.slice(span.start)}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Removes `<key>K</key>` and the one value that follows it, wherever it appears. */
|
|
116
|
+
export function plistRemoveKeys(xml, keys) {
|
|
117
|
+
let next = xml;
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
next = next.replace(
|
|
120
|
+
new RegExp(
|
|
121
|
+
`\\n?[ \\t]*<key>${key}</key>\\s*<(string|integer|true|false)\\b[^>]*?(?:/>|>[\\s\\S]*?</\\1>)`,
|
|
122
|
+
'g'
|
|
123
|
+
),
|
|
124
|
+
''
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------- entitlements
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The keychain groups in the required order — the app's own first, which is where iOS
|
|
134
|
+
* writes items that name no group — followed by any the app already had.
|
|
135
|
+
*/
|
|
136
|
+
export function entitlementsWithKeychainGroups(xml, groups) {
|
|
137
|
+
const fresh = `<?xml version="1.0" encoding="UTF-8"?>
|
|
138
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
139
|
+
<plist version="1.0">
|
|
140
|
+
<dict>
|
|
141
|
+
</dict>
|
|
142
|
+
</plist>
|
|
143
|
+
`;
|
|
144
|
+
const source = xml || fresh;
|
|
145
|
+
const span = arrayAfterKey(source, 'keychain-access-groups');
|
|
146
|
+
const existing = span
|
|
147
|
+
? [
|
|
148
|
+
...source
|
|
149
|
+
.slice(span.start, span.end)
|
|
150
|
+
.matchAll(/<string>([^<]*)<\/string>/g),
|
|
151
|
+
].map((m) => m[1])
|
|
152
|
+
: [];
|
|
153
|
+
const ordered = [...groups, ...existing.filter((g) => !groups.includes(g))];
|
|
154
|
+
const array = `<array>\n${ordered.map((g) => `\t<string>${g}</string>`).join('\n')}\n</array>`;
|
|
155
|
+
if (!span) {
|
|
156
|
+
return plistAddKey(source, 'keychain-access-groups', array);
|
|
157
|
+
}
|
|
158
|
+
const keyAt = source.lastIndexOf(
|
|
159
|
+
'<key>keychain-access-groups</key>',
|
|
160
|
+
span.start
|
|
161
|
+
);
|
|
162
|
+
const lineStart = source.lastIndexOf('\n', keyAt) + 1;
|
|
163
|
+
const indent = source.slice(lineStart, keyAt);
|
|
164
|
+
const arrayEnd = span.end + '</array>'.length;
|
|
165
|
+
const replacement = `<key>keychain-access-groups</key>\n${array.replace(/^/gm, indent).trimStart()}`;
|
|
166
|
+
return `${source.slice(0, keyAt)}${replacement}${source.slice(arrayEnd)}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------- project.pbxproj
|
|
170
|
+
|
|
171
|
+
const object = (isa) =>
|
|
172
|
+
new RegExp(
|
|
173
|
+
`(\\w{24})\\s*(?:/\\*[^*]*\\*/)?\\s*=\\s*\\{\\s*isa\\s*=\\s*${isa};([\\s\\S]*?)\\n\\t\\t\\};`,
|
|
174
|
+
'g'
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
/** The XCBuildConfiguration ids of every application target, and those targets' ids. */
|
|
178
|
+
function applicationTargets(pbxproj) {
|
|
179
|
+
const lists = new Map();
|
|
180
|
+
for (const [, id, body] of pbxproj.matchAll(object('XCConfigurationList'))) {
|
|
181
|
+
const ids = body.match(/buildConfigurations\s*=\s*\(([^)]*)\)/)?.[1] ?? '';
|
|
182
|
+
lists.set(
|
|
183
|
+
id,
|
|
184
|
+
[...ids.matchAll(/\w{24}/g)].map((m) => m[0])
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const targets = [];
|
|
188
|
+
for (const [, id, body] of pbxproj.matchAll(object('PBXNativeTarget'))) {
|
|
189
|
+
if (
|
|
190
|
+
!/productType\s*=\s*"com\.apple\.product-type\.application"/.test(body)
|
|
191
|
+
) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const list = body.match(/buildConfigurationList\s*=\s*(\w{24})/)?.[1];
|
|
195
|
+
targets.push({ id, configs: lists.get(list) ?? [] });
|
|
196
|
+
}
|
|
197
|
+
return targets;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Applies `change` to the buildSettings body of each configuration id. */
|
|
201
|
+
function editBuildSettings(pbxproj, ids, change) {
|
|
202
|
+
let next = pbxproj;
|
|
203
|
+
for (const id of ids) {
|
|
204
|
+
const re = new RegExp(
|
|
205
|
+
`(${id}\\s*(?:/\\*[^*]*\\*/)?\\s*=\\s*\\{[\\s\\S]*?buildSettings\\s*=\\s*\\{)([\\s\\S]*?)(\\n\\t\\t\\t\\};)`
|
|
206
|
+
);
|
|
207
|
+
next = next.replace(
|
|
208
|
+
re,
|
|
209
|
+
(all, open, body, close) => `${open}${change(body)}${close}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return next;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Sets `key = value;` in a buildSettings body, replacing it or adding it. */
|
|
216
|
+
function setBuildSetting(body, key, value) {
|
|
217
|
+
const line = new RegExp(`(\\n\\t\\t\\t\\t${key}\\s*=\\s*)[^;]*;`);
|
|
218
|
+
if (line.test(body)) {
|
|
219
|
+
return body.replace(line, `$1${value};`);
|
|
220
|
+
}
|
|
221
|
+
return `${body}\n\t\t\t\t${key} = ${value};`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Raises IPHONEOS_DEPLOYMENT_TARGET on every configuration of every application target,
|
|
226
|
+
* setting it on the target rather than relying on the project level it would otherwise
|
|
227
|
+
* inherit — which is what Expo's prebuild does, and what doctor reads.
|
|
228
|
+
*/
|
|
229
|
+
export function pbxprojSetDeploymentTarget(pbxproj, version, atLeast) {
|
|
230
|
+
const targets = applicationTargets(pbxproj);
|
|
231
|
+
if (targets.length === 0) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const ids = targets.flatMap((t) => t.configs);
|
|
235
|
+
return editBuildSettings(pbxproj, ids, (body) => {
|
|
236
|
+
const current = body.match(
|
|
237
|
+
/IPHONEOS_DEPLOYMENT_TARGET\s*=\s*"?([\d.]+)"?;/
|
|
238
|
+
)?.[1];
|
|
239
|
+
return current && atLeast(current)
|
|
240
|
+
? body
|
|
241
|
+
: setBuildSetting(body, 'IPHONEOS_DEPLOYMENT_TARGET', version);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Points every application target's configurations at an entitlements file. */
|
|
246
|
+
export function pbxprojSetEntitlements(pbxproj, relativePath) {
|
|
247
|
+
const targets = applicationTargets(pbxproj);
|
|
248
|
+
if (targets.length === 0) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return editBuildSettings(
|
|
252
|
+
pbxproj,
|
|
253
|
+
targets.flatMap((t) => t.configs),
|
|
254
|
+
(body) =>
|
|
255
|
+
/CODE_SIGN_ENTITLEMENTS\s*=/.test(body)
|
|
256
|
+
? body
|
|
257
|
+
: setBuildSetting(body, 'CODE_SIGN_ENTITLEMENTS', relativePath)
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Adds a Run Script phase that runs Microsoft's IntuneMAMConfigurator, first in every
|
|
263
|
+
* application target — before Compile Sources, so the plist and entitlements it writes
|
|
264
|
+
* are in place for the build that follows.
|
|
265
|
+
*/
|
|
266
|
+
export function pbxprojAddConfiguratorPhase(
|
|
267
|
+
pbxproj,
|
|
268
|
+
script,
|
|
269
|
+
id = newObjectId(pbxproj)
|
|
270
|
+
) {
|
|
271
|
+
if (/IntuneMAMConfigurator/.test(pbxproj)) {
|
|
272
|
+
return pbxproj;
|
|
273
|
+
}
|
|
274
|
+
const targets = applicationTargets(pbxproj);
|
|
275
|
+
if (targets.length === 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const escaped = script
|
|
279
|
+
.replace(/\\/g, '\\\\')
|
|
280
|
+
.replace(/"/g, '\\"')
|
|
281
|
+
.replace(/\n/g, '\\n');
|
|
282
|
+
const phase =
|
|
283
|
+
`\t\t${id} /* Run IntuneMAMConfigurator */ = {\n` +
|
|
284
|
+
'\t\t\tisa = PBXShellScriptBuildPhase;\n' +
|
|
285
|
+
'\t\t\talwaysOutOfDate = 1;\n' +
|
|
286
|
+
'\t\t\tbuildActionMask = 2147483647;\n' +
|
|
287
|
+
'\t\t\tfiles = (\n\t\t\t);\n' +
|
|
288
|
+
'\t\t\tinputPaths = (\n\t\t\t);\n' +
|
|
289
|
+
'\t\t\tname = "Run IntuneMAMConfigurator";\n' +
|
|
290
|
+
'\t\t\toutputPaths = (\n\t\t\t);\n' +
|
|
291
|
+
'\t\t\trunOnlyForDeploymentPostprocessing = 0;\n' +
|
|
292
|
+
'\t\t\tshellPath = /bin/sh;\n' +
|
|
293
|
+
`\t\t\tshellScript = "${escaped}";\n` +
|
|
294
|
+
'\t\t};\n';
|
|
295
|
+
|
|
296
|
+
let next = pbxproj;
|
|
297
|
+
const section = '/* Begin PBXShellScriptBuildPhase section */\n';
|
|
298
|
+
if (next.includes(section)) {
|
|
299
|
+
next = next.replace(section, `${section}${phase}`);
|
|
300
|
+
} else {
|
|
301
|
+
const anchor = '/* Begin PBXSourcesBuildPhase section */';
|
|
302
|
+
if (!next.includes(anchor)) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
next = next.replace(
|
|
306
|
+
anchor,
|
|
307
|
+
`${section}${phase}/* End PBXShellScriptBuildPhase section */\n\n${anchor}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
for (const t of targets) {
|
|
311
|
+
next = next.replace(
|
|
312
|
+
new RegExp(
|
|
313
|
+
`(${t.id}\\s*(?:/\\*[^*]*\\*/)?\\s*=\\s*\\{[\\s\\S]*?buildPhases\\s*=\\s*\\(\\n)`
|
|
314
|
+
),
|
|
315
|
+
`$1\t\t\t\t${id} /* Run IntuneMAMConfigurator */,\n`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return next;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** A 24-hex-digit object id, the form Xcode uses, not already in the file. */
|
|
322
|
+
function newObjectId(pbxproj) {
|
|
323
|
+
for (;;) {
|
|
324
|
+
const id = crypto.randomBytes(12).toString('hex').toUpperCase();
|
|
325
|
+
if (!pbxproj.includes(id)) {
|
|
326
|
+
return id;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
package/scripts/lib/project.mjs
CHANGED
|
@@ -178,11 +178,24 @@ function walk(dir, onFile, depth = 0) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Edits `setup` has decided on but not yet written, by path.
|
|
183
|
+
*
|
|
184
|
+
* Several checks change the same file — three edit Info.plist, three project.pbxproj —
|
|
185
|
+
* and each computes its edit from what it reads. Reading through this means the second
|
|
186
|
+
* edit starts from the first one's result instead of from the original file, which it
|
|
187
|
+
* would otherwise silently overwrite.
|
|
188
|
+
*/
|
|
189
|
+
export const pending = new Map();
|
|
190
|
+
|
|
181
191
|
/** Reads a file, or returns an empty string. Callers treat absent and empty alike. */
|
|
182
192
|
export function read(file) {
|
|
183
193
|
if (!file) {
|
|
184
194
|
return '';
|
|
185
195
|
}
|
|
196
|
+
if (pending.has(file)) {
|
|
197
|
+
return pending.get(file);
|
|
198
|
+
}
|
|
186
199
|
try {
|
|
187
200
|
return fs.readFileSync(file, 'utf8');
|
|
188
201
|
} catch {
|
package/scripts/setup.mjs
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import readline from 'node:readline';
|
|
23
|
-
import
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { findProject, pending, rel } from './lib/project.mjs';
|
|
24
25
|
import { gitIsClean, runChecks, summarise } from './doctor.mjs';
|
|
25
26
|
|
|
26
27
|
const args = process.argv.slice(2);
|
|
@@ -37,7 +38,8 @@ async function main() {
|
|
|
37
38
|
const project = findProject();
|
|
38
39
|
const s = summarise(runChecks(project));
|
|
39
40
|
report(project, s);
|
|
40
|
-
process.
|
|
41
|
+
process.exitCode = s.blocking.length > 0 ? 1 : 0;
|
|
42
|
+
return;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
const project = findProject();
|
|
@@ -51,6 +53,26 @@ async function main() {
|
|
|
51
53
|
);
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
// Under Expo, android/ and ios/ are generated by prebuild and regenerated by the next
|
|
57
|
+
// one, so everything written here would vanish without a trace. The config plugin is
|
|
58
|
+
// the same edits, applied where they survive.
|
|
59
|
+
const pkg = (() => {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(fs.readFileSync(path.join(project.root, 'package.json'), 'utf8'));
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
if ((pkg.dependencies?.expo || pkg.devDependencies?.expo) && !flags.force) {
|
|
67
|
+
fail(
|
|
68
|
+
`This is an Expo project. Its android/ and ios/ directories are generated by\n` +
|
|
69
|
+
`\`expo prebuild\`, so the next prebuild would overwrite anything written here.\n\n` +
|
|
70
|
+
`Add the config plugin to app.json instead and run prebuild:\n\n` +
|
|
71
|
+
` "plugins": [["react-native-intune", { "androidSignatureHash": "…" }]]\n\n` +
|
|
72
|
+
`doctor still works here, for checking what prebuild produced.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
if (!project.android && !project.ios) {
|
|
55
77
|
fail(
|
|
56
78
|
`No native project found under ${project.root}.\n` +
|
|
@@ -75,16 +97,37 @@ async function main() {
|
|
|
75
97
|
const results = runChecks(project);
|
|
76
98
|
const s = summarise(results);
|
|
77
99
|
|
|
78
|
-
|
|
100
|
+
// Applied one after another through `pending`, so each edit reads the ones before it.
|
|
101
|
+
// One check may change several files — creating the entitlements file and pointing the
|
|
102
|
+
// Xcode target at it — and several checks may change the same one.
|
|
103
|
+
const originals = new Map();
|
|
104
|
+
const reasons = new Map();
|
|
79
105
|
for (const r of s.failing) {
|
|
80
106
|
if (typeof r.check.apply !== 'function') {
|
|
81
107
|
continue;
|
|
82
108
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
const result = r.check.apply(project);
|
|
110
|
+
for (const edit of [result].flat().filter(Boolean)) {
|
|
111
|
+
if (!originals.has(edit.file)) {
|
|
112
|
+
originals.set(
|
|
113
|
+
edit.file,
|
|
114
|
+
fs.existsSync(edit.file) ? fs.readFileSync(edit.file, 'utf8') : null
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
pending.set(edit.file, edit.contents);
|
|
118
|
+
reasons.set(edit.file, [...(reasons.get(edit.file) ?? []), r.check.title]);
|
|
86
119
|
}
|
|
87
120
|
}
|
|
121
|
+
const edits = [...originals]
|
|
122
|
+
.map(([file, before]) => ({
|
|
123
|
+
file,
|
|
124
|
+
before: before ?? '',
|
|
125
|
+
created: before === null,
|
|
126
|
+
contents: pending.get(file),
|
|
127
|
+
titles: [...new Set(reasons.get(file))],
|
|
128
|
+
}))
|
|
129
|
+
.filter((e) => e.created || e.contents !== e.before);
|
|
130
|
+
pending.clear();
|
|
88
131
|
|
|
89
132
|
if (edits.length === 0) {
|
|
90
133
|
process.stdout.write(
|
|
@@ -95,16 +138,20 @@ async function main() {
|
|
|
95
138
|
} else {
|
|
96
139
|
process.stdout.write(`\nThese files will change:\n\n`);
|
|
97
140
|
for (const e of edits) {
|
|
98
|
-
process.stdout.write(
|
|
141
|
+
process.stdout.write(
|
|
142
|
+
` ${rel(project, e.file)}${e.created ? ' (new)' : ''} — ${e.titles.join('; ')}\n`
|
|
143
|
+
);
|
|
99
144
|
process.stdout.write(diff(e.before, e.contents));
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
if (!flags.yes && !(await confirm())) {
|
|
103
148
|
process.stdout.write(`\nNothing written.\n\n`);
|
|
104
|
-
process.
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
return;
|
|
105
151
|
}
|
|
106
152
|
|
|
107
153
|
for (const e of edits) {
|
|
154
|
+
fs.mkdirSync(path.dirname(e.file), { recursive: true });
|
|
108
155
|
fs.writeFileSync(e.file, e.contents);
|
|
109
156
|
process.stdout.write(` wrote ${rel(project, e.file)}\n`);
|
|
110
157
|
}
|
|
@@ -115,7 +162,10 @@ async function main() {
|
|
|
115
162
|
// own apply is a bug worth surfacing here instead of at the next build.
|
|
116
163
|
const after = summarise(runChecks(findProject()));
|
|
117
164
|
report(project, after, { afterApply: true });
|
|
118
|
-
process.exit(
|
|
165
|
+
// An exit code, not process.exit(): on macOS a write to a pipe is asynchronous, and
|
|
166
|
+
// exiting straight after the diff cut it off — every file after the project.pbxproj
|
|
167
|
+
// diff, and every "wrote" line, silently missing whenever setup's output was piped.
|
|
168
|
+
process.exitCode = after.blocking.length > 0 ? 1 : 0;
|
|
119
169
|
}
|
|
120
170
|
|
|
121
171
|
function report(project, s, { afterApply = false } = {}) {
|
package/sdk-lock.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ios": {
|
|
3
3
|
"repo": "microsoftconnect/ms-intune-app-sdk-ios",
|
|
4
|
-
"tag": "21.
|
|
5
|
-
"fetchedAt": "2026-09-
|
|
4
|
+
"tag": "21.9.0",
|
|
5
|
+
"fetchedAt": "2026-09-30T10:58:03.813Z",
|
|
6
6
|
"files": {
|
|
7
|
-
"ios/IntuneMAMSwift.xcframework": "
|
|
8
|
-
"ios/IntuneMAMSwiftStub.xcframework": "
|
|
9
|
-
"ios/IntuneMAMTelemetry.xcframework": "
|
|
10
|
-
"ios/IntuneMAMResources.bundle": "
|
|
11
|
-
"ios/IntuneMAMConfigurator.dmg": "
|
|
7
|
+
"ios/IntuneMAMSwift.xcframework": "6100e987caed8d4286056d04c003b71ad7ea15d3523495ab3cf7a3aa7d39565d",
|
|
8
|
+
"ios/IntuneMAMSwiftStub.xcframework": "b6906db29374ffbd4e8b708975572bc0cb3bb02199e36c61f5b359573175deb8",
|
|
9
|
+
"ios/IntuneMAMTelemetry.xcframework": "e218e261dbc28a70a390bc249905884a97790560ab4bd0e57633fc548cdef1d9",
|
|
10
|
+
"ios/IntuneMAMResources.bundle": "3d71e06a0261c4904b8104ef7405bdf22114ed60ccf09b6ab4094cd56199b7dd",
|
|
11
|
+
"ios/IntuneMAMConfigurator.dmg": "753a563e336e40884d2f30cfaad80663f6bd63a81c5bc53c5a232c49bec6ab07",
|
|
12
|
+
"ios/IntuneMAMConfigurator": "b8c747c49b114ad6171820edcf7562bcd43831c1da4dbdfcb6993c81c5136c72",
|
|
12
13
|
"ios/REDIST.txt": "31c5b0e0c3ee48b60c6f12e56d5a8b6d47754f127750f27627800d2b44b79e56",
|
|
13
14
|
"ios/Microsoft License Terms Intune App SDK for iOS.pdf": "5b12b9a6ee3671a138b67884ff69c5bda17b6cb7bec0646f108cf072930c7394",
|
|
14
|
-
"ios/ThirdPartyNotices.txt": "
|
|
15
|
+
"ios/ThirdPartyNotices.txt": "f99b09285acd2944c0dde5773f68cfa1179e8a743e0e6ba913d1f688a79d439f"
|
|
15
16
|
}
|
|
16
17
|
},
|
|
17
18
|
"android": {
|