react-native-3rddigital-appupdate 1.0.18 → 1.0.20
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/package.json +1 -1
- package/scripts/bundle.js +250 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-3rddigital-appupdate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "A React Native library for seamless over-the-air (OTA) updates with version checks, automatic bundle download, and customizable user prompts for iOS and Android.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/scripts/bundle.js
CHANGED
|
@@ -169,6 +169,54 @@ function findFirstXcodeProj(dir) {
|
|
|
169
169
|
return null;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
function findFirstXcworkspace(dir) {
|
|
173
|
+
const files = fs.readdirSync(dir);
|
|
174
|
+
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const fullPath = path.join(dir, file);
|
|
177
|
+
const stat = fs.statSync(fullPath);
|
|
178
|
+
|
|
179
|
+
if (stat.isDirectory()) {
|
|
180
|
+
if (file.endsWith('.xcworkspace') && file !== 'Pods.xcworkspace') {
|
|
181
|
+
return fullPath;
|
|
182
|
+
}
|
|
183
|
+
const nested = findFirstXcworkspace(fullPath);
|
|
184
|
+
if (nested) return nested;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function quoteShellArg(value) {
|
|
192
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function runAndCapture(command, cwd = process.cwd()) {
|
|
196
|
+
try {
|
|
197
|
+
return execSync(command, {
|
|
198
|
+
cwd,
|
|
199
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
200
|
+
})
|
|
201
|
+
.toString()
|
|
202
|
+
.trim();
|
|
203
|
+
} catch (_error) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function extractJsonObject(text) {
|
|
209
|
+
if (!text) return null;
|
|
210
|
+
|
|
211
|
+
const start = text.indexOf('{');
|
|
212
|
+
const end = text.lastIndexOf('}');
|
|
213
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return text.slice(start, end + 1);
|
|
218
|
+
}
|
|
219
|
+
|
|
172
220
|
function extractBracedBlock(content, startIndex) {
|
|
173
221
|
const openIndex = content.indexOf('{', startIndex);
|
|
174
222
|
if (openIndex === -1) return null;
|
|
@@ -349,6 +397,14 @@ function choosePreferredBuildConfig(buildConfigs, preferredNames = []) {
|
|
|
349
397
|
return buildConfigs[0] ?? null;
|
|
350
398
|
}
|
|
351
399
|
|
|
400
|
+
function buildIosEntryLabel({ targetName, buildConfiguration, appId }) {
|
|
401
|
+
const buildConfigurationLabel = buildConfiguration
|
|
402
|
+
? ` [${buildConfiguration}]`
|
|
403
|
+
: '';
|
|
404
|
+
const appIdLabel = appId ? ` (${appId})` : '';
|
|
405
|
+
return `${targetName}${buildConfigurationLabel}${appIdLabel}`;
|
|
406
|
+
}
|
|
407
|
+
|
|
352
408
|
function getAndroidBuildGradlePath(projectRoot) {
|
|
353
409
|
return findFirstExistingPath([
|
|
354
410
|
path.join(projectRoot, 'android', 'app', 'build.gradle'),
|
|
@@ -464,19 +520,134 @@ function getIosProjectFiles() {
|
|
|
464
520
|
return null;
|
|
465
521
|
}
|
|
466
522
|
|
|
523
|
+
const xcworkspacePath = findFirstXcworkspace(iosDir);
|
|
524
|
+
|
|
467
525
|
const pbxprojPath = path.join(xcodeProjPath, 'project.pbxproj');
|
|
468
526
|
if (!fs.existsSync(pbxprojPath)) {
|
|
469
527
|
console.warn('⚠️ project.pbxproj not found.');
|
|
470
528
|
return null;
|
|
471
529
|
}
|
|
472
530
|
|
|
473
|
-
return { iosDir, xcodeProjPath, pbxprojPath };
|
|
531
|
+
return { iosDir, xcodeProjPath, xcworkspacePath, pbxprojPath };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function getIosBuildContainerArgs(projectFiles) {
|
|
535
|
+
if (projectFiles.xcworkspacePath) {
|
|
536
|
+
return `-workspace ${quoteShellArg(projectFiles.xcworkspacePath)}`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return `-project ${quoteShellArg(projectFiles.xcodeProjPath)}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function parseXcodebuildSettings(output) {
|
|
543
|
+
const settings = {};
|
|
544
|
+
|
|
545
|
+
for (const line of output.split('\n')) {
|
|
546
|
+
const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.+)$/);
|
|
547
|
+
if (!match) continue;
|
|
548
|
+
settings[match[1]] = cleanPbxString(match[2]);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return settings;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function isResolvedBuildSetting(value) {
|
|
555
|
+
if (!value) return false;
|
|
556
|
+
|
|
557
|
+
return !/[()$]/.test(value);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function getIosSchemeMetadataFromXcodebuild(projectFiles) {
|
|
561
|
+
const projectRoot = getProjectRoot();
|
|
562
|
+
const buildContainerArgs = getIosBuildContainerArgs(projectFiles);
|
|
563
|
+
const listOutput = runAndCapture(
|
|
564
|
+
`xcodebuild -list -json ${buildContainerArgs}`,
|
|
565
|
+
projectRoot
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
if (!listOutput) return null;
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
const jsonOutput = extractJsonObject(listOutput);
|
|
572
|
+
if (!jsonOutput) return null;
|
|
573
|
+
|
|
574
|
+
const parsed = JSON.parse(jsonOutput);
|
|
575
|
+
const schemes =
|
|
576
|
+
parsed.project?.schemes ??
|
|
577
|
+
parsed.workspace?.schemes ??
|
|
578
|
+
parsed.project?.targets ??
|
|
579
|
+
[];
|
|
580
|
+
|
|
581
|
+
const uniqueSchemes = [...new Set(schemes)].filter(Boolean);
|
|
582
|
+
if (!uniqueSchemes.length) return null;
|
|
583
|
+
|
|
584
|
+
const entries = uniqueSchemes
|
|
585
|
+
.map((schemeName) => {
|
|
586
|
+
const buildConfigurations = ['Release', 'Profile', 'Debug'];
|
|
587
|
+
const buildSettingsOutput = buildConfigurations
|
|
588
|
+
.map((configuration) => ({
|
|
589
|
+
configuration,
|
|
590
|
+
output: runAndCapture(
|
|
591
|
+
`xcodebuild -showBuildSettings ${buildContainerArgs} -scheme ${quoteShellArg(
|
|
592
|
+
schemeName
|
|
593
|
+
)} -configuration ${quoteShellArg(configuration)}`,
|
|
594
|
+
projectRoot
|
|
595
|
+
),
|
|
596
|
+
}))
|
|
597
|
+
.find((item) => item.output)?.output;
|
|
598
|
+
|
|
599
|
+
if (!buildSettingsOutput) return null;
|
|
600
|
+
|
|
601
|
+
const settings = parseXcodebuildSettings(buildSettingsOutput);
|
|
602
|
+
const appId = isResolvedBuildSetting(settings.PRODUCT_BUNDLE_IDENTIFIER)
|
|
603
|
+
? settings.PRODUCT_BUNDLE_IDENTIFIER
|
|
604
|
+
: null;
|
|
605
|
+
const version = isResolvedBuildSetting(settings.MARKETING_VERSION)
|
|
606
|
+
? settings.MARKETING_VERSION
|
|
607
|
+
: null;
|
|
608
|
+
const targetName =
|
|
609
|
+
settings.TARGET_NAME || settings.PRODUCT_NAME || schemeName;
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
name: schemeName,
|
|
613
|
+
targetName,
|
|
614
|
+
label: schemeName,
|
|
615
|
+
appId,
|
|
616
|
+
version,
|
|
617
|
+
productName: settings.PRODUCT_NAME || targetName,
|
|
618
|
+
buildConfiguration: settings.CONFIGURATION || 'Release',
|
|
619
|
+
};
|
|
620
|
+
})
|
|
621
|
+
.filter(Boolean);
|
|
622
|
+
|
|
623
|
+
if (!entries.length) return null;
|
|
624
|
+
|
|
625
|
+
const dedupedEntries = entries.filter(
|
|
626
|
+
(entry, index, allEntries) =>
|
|
627
|
+
allEntries.findIndex(
|
|
628
|
+
(candidate) =>
|
|
629
|
+
candidate.name === entry.name && candidate.appId === entry.appId
|
|
630
|
+
) === index
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
return {
|
|
634
|
+
defaultConfig: dedupedEntries[0] ?? null,
|
|
635
|
+
targets: dedupedEntries,
|
|
636
|
+
};
|
|
637
|
+
} catch (_error) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
474
640
|
}
|
|
475
641
|
|
|
476
642
|
function getIosTargetMetadata() {
|
|
477
643
|
const projectFiles = getIosProjectFiles();
|
|
478
644
|
if (!projectFiles) return null;
|
|
479
645
|
|
|
646
|
+
const xcodebuildMetadata = getIosSchemeMetadataFromXcodebuild(projectFiles);
|
|
647
|
+
if (xcodebuildMetadata?.targets?.length) {
|
|
648
|
+
return xcodebuildMetadata;
|
|
649
|
+
}
|
|
650
|
+
|
|
480
651
|
const pbxprojContent = fs.readFileSync(projectFiles.pbxprojPath, 'utf8');
|
|
481
652
|
const configObjects = parsePbxprojObjectsByIsa(
|
|
482
653
|
pbxprojContent,
|
|
@@ -516,6 +687,18 @@ function getIosTargetMetadata() {
|
|
|
516
687
|
])
|
|
517
688
|
);
|
|
518
689
|
|
|
690
|
+
const preferredConfigNames = ['Release', 'Profile', 'Debug'];
|
|
691
|
+
const fallbackConfigGroups = new Map();
|
|
692
|
+
|
|
693
|
+
for (const config of configMap.values()) {
|
|
694
|
+
const appIdKey = config.appId ?? `no-app-id::${config.id}`;
|
|
695
|
+
if (!fallbackConfigGroups.has(appIdKey)) {
|
|
696
|
+
fallbackConfigGroups.set(appIdKey, []);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
fallbackConfigGroups.get(appIdKey).push(config);
|
|
700
|
+
}
|
|
701
|
+
|
|
519
702
|
const targetObjects = parsePbxprojObjectsByIsa(
|
|
520
703
|
pbxprojContent,
|
|
521
704
|
'PBXNativeTarget'
|
|
@@ -560,7 +743,7 @@ function getIosTargetMetadata() {
|
|
|
560
743
|
configsByAppId.get(appIdKey).push(buildConfig);
|
|
561
744
|
}
|
|
562
745
|
|
|
563
|
-
const
|
|
746
|
+
const targetPreferredConfigNames = [
|
|
564
747
|
configList?.defaultName,
|
|
565
748
|
'Release',
|
|
566
749
|
'Profile',
|
|
@@ -574,28 +757,27 @@ function getIosTargetMetadata() {
|
|
|
574
757
|
buildConfigsWithAppId.length
|
|
575
758
|
? buildConfigsWithAppId
|
|
576
759
|
: buildConfigs,
|
|
577
|
-
|
|
760
|
+
targetPreferredConfigNames
|
|
578
761
|
),
|
|
579
762
|
].filter(Boolean)
|
|
580
763
|
: Array.from(configsByAppId.values())
|
|
581
764
|
.map((configGroup) =>
|
|
582
|
-
choosePreferredBuildConfig(
|
|
765
|
+
choosePreferredBuildConfig(
|
|
766
|
+
configGroup,
|
|
767
|
+
targetPreferredConfigNames
|
|
768
|
+
)
|
|
583
769
|
)
|
|
584
770
|
.filter(Boolean);
|
|
585
771
|
|
|
586
772
|
return distinctConfigs.map((selectedConfig) => {
|
|
587
|
-
const buildConfigurationLabel = selectedConfig.name
|
|
588
|
-
? ` [${selectedConfig.name}]`
|
|
589
|
-
: '';
|
|
590
|
-
|
|
591
|
-
const appIdLabel = selectedConfig.appId
|
|
592
|
-
? ` (${selectedConfig.appId})`
|
|
593
|
-
: '';
|
|
594
|
-
|
|
595
773
|
return {
|
|
596
774
|
name: `${targetName}::${selectedConfig.appId ?? selectedConfig.id ?? 'no-app-id'}`,
|
|
597
775
|
targetName,
|
|
598
|
-
label:
|
|
776
|
+
label: buildIosEntryLabel({
|
|
777
|
+
targetName,
|
|
778
|
+
buildConfiguration: selectedConfig.name ?? null,
|
|
779
|
+
appId: selectedConfig.appId ?? null,
|
|
780
|
+
}),
|
|
599
781
|
appId: selectedConfig.appId ?? null,
|
|
600
782
|
version: selectedConfig.version ?? null,
|
|
601
783
|
productName: selectedConfig.productName ?? targetName,
|
|
@@ -616,9 +798,62 @@ function getIosTargetMetadata() {
|
|
|
616
798
|
) === index
|
|
617
799
|
);
|
|
618
800
|
|
|
801
|
+
const existingAppIds = new Set(
|
|
802
|
+
uniqueTargets.map((target) => target.appId).filter(Boolean)
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const fallbackEntries = Array.from(fallbackConfigGroups.entries())
|
|
806
|
+
.filter(([appId]) => !existingAppIds.has(appId))
|
|
807
|
+
.map(([appId, configs]) => {
|
|
808
|
+
const selectedConfig = choosePreferredBuildConfig(
|
|
809
|
+
configs,
|
|
810
|
+
preferredConfigNames
|
|
811
|
+
);
|
|
812
|
+
if (!selectedConfig) return null;
|
|
813
|
+
|
|
814
|
+
const targetName =
|
|
815
|
+
selectedConfig.productName &&
|
|
816
|
+
selectedConfig.productName !== '$(TARGET_NAME)'
|
|
817
|
+
? selectedConfig.productName
|
|
818
|
+
: 'Default';
|
|
819
|
+
|
|
820
|
+
return {
|
|
821
|
+
name: `${targetName}::${appId}`,
|
|
822
|
+
targetName,
|
|
823
|
+
label: buildIosEntryLabel({
|
|
824
|
+
targetName,
|
|
825
|
+
buildConfiguration: selectedConfig.name ?? null,
|
|
826
|
+
appId: selectedConfig.appId ?? null,
|
|
827
|
+
}),
|
|
828
|
+
appId: selectedConfig.appId ?? null,
|
|
829
|
+
version: selectedConfig.version ?? null,
|
|
830
|
+
productName: selectedConfig.productName ?? targetName,
|
|
831
|
+
buildConfiguration: selectedConfig.name ?? null,
|
|
832
|
+
};
|
|
833
|
+
})
|
|
834
|
+
.filter(Boolean);
|
|
835
|
+
|
|
836
|
+
const mergedTargets = [...uniqueTargets, ...fallbackEntries];
|
|
837
|
+
|
|
838
|
+
if (!mergedTargets.length) {
|
|
839
|
+
const fallbackVersionMatch = pbxprojContent.match(
|
|
840
|
+
/MARKETING_VERSION\s*=\s*([^;]+);/
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
return {
|
|
844
|
+
defaultConfig: {
|
|
845
|
+
name: 'default',
|
|
846
|
+
label: 'Default',
|
|
847
|
+
appId: null,
|
|
848
|
+
version: cleanPbxString(fallbackVersionMatch?.[1]) ?? null,
|
|
849
|
+
},
|
|
850
|
+
targets: [],
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
619
854
|
return {
|
|
620
|
-
defaultConfig:
|
|
621
|
-
targets:
|
|
855
|
+
defaultConfig: mergedTargets[0] ?? null,
|
|
856
|
+
targets: mergedTargets,
|
|
622
857
|
};
|
|
623
858
|
}
|
|
624
859
|
|