@craft-native/ios 0.0.153 → 0.0.155
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 +5 -1
- package/dist/cli.js +95 -75
- package/dist/index.d.ts +4 -0
- package/dist/index.js +249 -90
- package/dist/testing.d.ts +71 -0
- package/package.json +1 -1
- package/templates/CraftNativeActions.swift +10 -0
- package/templates/CraftNativeFiles.swift +34 -0
- package/templates/CraftNativeServices.swift +5 -1
- package/templates/testing/DriverTests.swift.template +305 -0
- package/templates/testing/HostApp.swift +6 -0
- package/templates/testing/MutationValidator.swift +15 -0
- package/templates/testing/project.yml.template +36 -0
package/README.md
CHANGED
|
@@ -602,8 +602,12 @@ await fetch('https://example.com/upload', { method: 'POST', body: form });
|
|
|
602
602
|
// A file for the share sheet (Save to Files, AirDrop, another app), fetched
|
|
603
603
|
// straight to disk with your headers. Not 2xx: { ok: false, status, body }.
|
|
604
604
|
const gpx = await craft.files.download('https://example.com/route.gpx', { headers: { Authorization: 'Bearer …' } });
|
|
605
|
-
if (gpx.ok) await craft.share({ files: [gpx
|
|
605
|
+
if (gpx.ok) await craft.share({ files: [gpx] }); // a file, or its path
|
|
606
606
|
await craft.files.remove(gpx.path); // or leave it: tmp, swept after a day
|
|
607
|
+
// Something the screen made itself (a CSV export): text crosses the bridge
|
|
608
|
+
// once, as it is; bytes go as base64 ({ encoding: 'base64' }).
|
|
609
|
+
const csv = await craft.files.write('health-data.csv', 'date,steps\n…', { type: 'text/csv' });
|
|
610
|
+
await craft.share({ files: [csv] });
|
|
607
611
|
|
|
608
612
|
// Speech: short spoken cues that duck the person's music instead of stopping
|
|
609
613
|
// it, and play with the silent switch on. Always available (no config flag).
|
package/dist/cli.js
CHANGED
|
@@ -6,12 +6,18 @@ import { parseArgs } from "util";
|
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
9
|
-
import { dirname, join, resolve } from "path";
|
|
9
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
10
10
|
var {$ } = globalThis.Bun;
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
// src/testing.ts
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
var templates = join(dirname(import.meta.dir), "templates/testing");
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
var TEMPLATES_DIR = join2(dirname2(import.meta.dir), "templates");
|
|
12
18
|
var CRAFT_VERSION = (() => {
|
|
13
19
|
try {
|
|
14
|
-
return JSON.parse(readFileSync(
|
|
20
|
+
return JSON.parse(readFileSync(join2(dirname2(import.meta.dir), "package.json"), "utf8")).version ?? "0.0.0";
|
|
15
21
|
} catch {
|
|
16
22
|
return "0.0.0";
|
|
17
23
|
}
|
|
@@ -67,8 +73,8 @@ function hybridConfigProblems(config) {
|
|
|
67
73
|
function installNativeBundle(source, output) {
|
|
68
74
|
if (!existsSync(source))
|
|
69
75
|
throw new Error(`Native bundle not found: ${source}`);
|
|
70
|
-
const destination =
|
|
71
|
-
mkdirSync(
|
|
76
|
+
const destination = join2(output, "dist", "native-screen.js");
|
|
77
|
+
mkdirSync(dirname2(destination), { recursive: true });
|
|
72
78
|
if (resolve(source) !== resolve(destination))
|
|
73
79
|
cpSync(source, destination);
|
|
74
80
|
}
|
|
@@ -432,20 +438,20 @@ function writeOpaqueIcon(source, target) {
|
|
|
432
438
|
}
|
|
433
439
|
}
|
|
434
440
|
function renderAssetCatalog(output, config) {
|
|
435
|
-
const catalog =
|
|
436
|
-
const appIcon =
|
|
437
|
-
const launchBackground =
|
|
441
|
+
const catalog = join2(output, "Assets.xcassets");
|
|
442
|
+
const appIcon = join2(catalog, "AppIcon.appiconset");
|
|
443
|
+
const launchBackground = join2(catalog, "LaunchBackground.colorset");
|
|
438
444
|
mkdirSync(appIcon, { recursive: true });
|
|
439
445
|
mkdirSync(launchBackground, { recursive: true });
|
|
440
|
-
writeFileSync(
|
|
446
|
+
writeFileSync(join2(catalog, "Contents.json"), `${JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)}
|
|
441
447
|
`);
|
|
442
448
|
const iconFilename = config.appIconPath ? "AppIcon-1024.png" : undefined;
|
|
443
449
|
if (config.appIconPath) {
|
|
444
450
|
if (!existsSync(config.appIconPath))
|
|
445
451
|
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
446
|
-
writeOpaqueIcon(config.appIconPath,
|
|
452
|
+
writeOpaqueIcon(config.appIconPath, join2(appIcon, iconFilename));
|
|
447
453
|
}
|
|
448
|
-
writeFileSync(
|
|
454
|
+
writeFileSync(join2(appIcon, "Contents.json"), `${JSON.stringify({
|
|
449
455
|
images: iconFilename ? [{
|
|
450
456
|
filename: iconFilename,
|
|
451
457
|
idiom: "universal",
|
|
@@ -472,7 +478,7 @@ function renderAssetCatalog(output, config) {
|
|
|
472
478
|
idiom: "universal"
|
|
473
479
|
});
|
|
474
480
|
}
|
|
475
|
-
writeFileSync(
|
|
481
|
+
writeFileSync(join2(launchBackground, "Contents.json"), `${JSON.stringify({
|
|
476
482
|
colors,
|
|
477
483
|
info: { author: "xcode", version: 1 }
|
|
478
484
|
}, null, 2)}
|
|
@@ -481,7 +487,7 @@ function renderAssetCatalog(output, config) {
|
|
|
481
487
|
}
|
|
482
488
|
var LAUNCH_LOGO = "LaunchLogo";
|
|
483
489
|
function renderLaunchLogo(catalog, config) {
|
|
484
|
-
const imageset =
|
|
490
|
+
const imageset = join2(catalog, `${LAUNCH_LOGO}.imageset`);
|
|
485
491
|
rmSync(imageset, { recursive: true, force: true });
|
|
486
492
|
if (!config.splashImagePath)
|
|
487
493
|
return;
|
|
@@ -497,14 +503,14 @@ function renderLaunchLogo(catalog, config) {
|
|
|
497
503
|
if (extension === "svg" || extension === "pdf")
|
|
498
504
|
vector = true;
|
|
499
505
|
const filename = `${LAUNCH_LOGO}${dark ? "-dark" : ""}.${extension}`;
|
|
500
|
-
cpSync(source,
|
|
506
|
+
cpSync(source, join2(imageset, filename));
|
|
501
507
|
images.push({
|
|
502
508
|
...dark ? { appearances: [{ appearance: "luminosity", value: "dark" }] } : {},
|
|
503
509
|
filename,
|
|
504
510
|
idiom: "universal"
|
|
505
511
|
});
|
|
506
512
|
}
|
|
507
|
-
writeFileSync(
|
|
513
|
+
writeFileSync(join2(imageset, "Contents.json"), `${JSON.stringify({
|
|
508
514
|
images,
|
|
509
515
|
info: { author: "xcode", version: 1 },
|
|
510
516
|
...vector ? { properties: { "preserves-vector-representation": true } } : {}
|
|
@@ -618,15 +624,15 @@ function syncWebAssets(source, output) {
|
|
|
618
624
|
const sourcePath = resolve(source);
|
|
619
625
|
if (!existsSync(sourcePath))
|
|
620
626
|
throw new Error(`Web asset path not found: ${source}`);
|
|
621
|
-
const distDir =
|
|
627
|
+
const distDir = join2(output, "dist");
|
|
622
628
|
rmSync(distDir, { recursive: true, force: true });
|
|
623
629
|
mkdirSync(distDir, { recursive: true });
|
|
624
630
|
if (statSync(sourcePath).isDirectory()) {
|
|
625
631
|
cpSync(sourcePath, distDir, { recursive: true });
|
|
626
632
|
} else {
|
|
627
|
-
cpSync(sourcePath,
|
|
633
|
+
cpSync(sourcePath, join2(distDir, "index.html"));
|
|
628
634
|
}
|
|
629
|
-
if (!existsSync(
|
|
635
|
+
if (!existsSync(join2(distDir, "index.html"))) {
|
|
630
636
|
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
631
637
|
}
|
|
632
638
|
}
|
|
@@ -648,32 +654,32 @@ var RUNTIME_ARCHIVES = {
|
|
|
648
654
|
};
|
|
649
655
|
async function installRuntime(output, runtimeDir) {
|
|
650
656
|
const resolved = Object.entries(RUNTIME_ARCHIVES).map(([sdk, archives]) => {
|
|
651
|
-
const present = archives.filter((a) => existsSync(
|
|
657
|
+
const present = archives.filter((a) => existsSync(join2(runtimeDir, a)));
|
|
652
658
|
if (present.length === 0) {
|
|
653
659
|
throw new Error(`${runtimeDir} has none of ${archives.join(", ")}. ` + `Run \`zig build build-ios-all\` in packages/zig and point at its zig-out/lib.`);
|
|
654
660
|
}
|
|
655
661
|
return { sdk, archives, present };
|
|
656
662
|
});
|
|
657
|
-
const dest =
|
|
663
|
+
const dest = join2(output, "Runtime");
|
|
658
664
|
rmSync(dest, { recursive: true, force: true });
|
|
659
665
|
for (const { sdk, archives, present } of resolved) {
|
|
660
|
-
const sdkDir =
|
|
666
|
+
const sdkDir = join2(dest, sdk);
|
|
661
667
|
mkdirSync(sdkDir, { recursive: true });
|
|
662
|
-
const target =
|
|
668
|
+
const target = join2(sdkDir, "libcraft-ios.a");
|
|
663
669
|
if (present.length === 1) {
|
|
664
670
|
const missing = archives.filter((a) => !present.includes(a));
|
|
665
671
|
if (missing.length > 0) {
|
|
666
672
|
console.warn(` \u26A0 ${sdk}: only ${present[0]} was found; ${missing.join(", ")} is missing. ` + `The generated project will not link on the other architecture.`);
|
|
667
673
|
}
|
|
668
|
-
cpSync(
|
|
674
|
+
cpSync(join2(runtimeDir, present[0]), target);
|
|
669
675
|
} else {
|
|
670
|
-
await $`lipo -create ${present.map((a) =>
|
|
676
|
+
await $`lipo -create ${present.map((a) => join2(runtimeDir, a))} -output ${target}`.quiet();
|
|
671
677
|
}
|
|
672
678
|
}
|
|
673
679
|
return true;
|
|
674
680
|
}
|
|
675
681
|
async function refreshRuntime(output, override) {
|
|
676
|
-
if (!existsSync(
|
|
682
|
+
if (!existsSync(join2(output, "Runtime")))
|
|
677
683
|
return;
|
|
678
684
|
const dir = resolveRuntimeDir(override);
|
|
679
685
|
if (!dir) {
|
|
@@ -690,6 +696,39 @@ function productName(name) {
|
|
|
690
696
|
return "Craft";
|
|
691
697
|
return /^\d/.test(joined) ? `App${joined}` : joined;
|
|
692
698
|
}
|
|
699
|
+
function writeShell(output, config) {
|
|
700
|
+
if (!config.appName || !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.bundleId))
|
|
701
|
+
throw new Error("The iOS shell needs an appName and a valid bundleId in craft.config.json.");
|
|
702
|
+
const name = productName(config.appName);
|
|
703
|
+
mkdirSync(join2(output, "Sources"), { recursive: true });
|
|
704
|
+
mkdirSync(join2(output, "Shared"), { recursive: true });
|
|
705
|
+
const swiftTemplate = readFileSync(join2(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
706
|
+
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, config.bundleId).replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_VERSION);
|
|
707
|
+
writeFileSync(join2(output, "Sources", `${name}App.swift`), swiftSource);
|
|
708
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeScreen.swift"), join2(output, "Sources", "CraftNativeScreen.swift"));
|
|
709
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeFlatList.swift"), join2(output, "Sources", "CraftNativeFlatList.swift"));
|
|
710
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeActions.swift"), join2(output, "Sources", "CraftNativeActions.swift"));
|
|
711
|
+
cpSync(join2(TEMPLATES_DIR, "CraftHealth.swift"), join2(output, "Sources", "CraftHealth.swift"));
|
|
712
|
+
cpSync(join2(TEMPLATES_DIR, "CraftMusic.swift"), join2(output, "Sources", "CraftMusic.swift"));
|
|
713
|
+
cpSync(join2(TEMPLATES_DIR, "CraftHeartRate.swift"), join2(output, "Sources", "CraftHeartRate.swift"));
|
|
714
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeMutation.swift"), join2(output, "Sources", "CraftNativeMutation.swift"));
|
|
715
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeUI.swift"), join2(output, "Sources", "CraftNativeUI.swift"));
|
|
716
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeServices.swift"), join2(output, "Sources", "CraftNativeServices.swift"));
|
|
717
|
+
cpSync(join2(TEMPLATES_DIR, "CraftNativeFiles.swift"), join2(output, "Sources", "CraftNativeFiles.swift"));
|
|
718
|
+
cpSync(join2(TEMPLATES_DIR, "CraftWebAudio.swift"), join2(output, "Sources", "CraftWebAudio.swift"));
|
|
719
|
+
cpSync(join2(TEMPLATES_DIR, "CraftHybrid.swift"), join2(output, "Sources", "CraftHybrid.swift"));
|
|
720
|
+
cpSync(join2(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join2(output, "Shared", "CraftActivityAttributes.swift"));
|
|
721
|
+
for (const [enabled, folder, template, filename] of [
|
|
722
|
+
[config.enableLiveActivities, "WidgetExtension", "CraftLiveActivityWidget.swift.template", `${name}LiveActivity.swift`],
|
|
723
|
+
[config.enableWatchApp, "WatchExtension", "CraftWatchApp.swift.template", `${name}WatchApp.swift`]
|
|
724
|
+
]) {
|
|
725
|
+
if (!enabled)
|
|
726
|
+
continue;
|
|
727
|
+
mkdirSync(join2(output, folder), { recursive: true });
|
|
728
|
+
const source = readFileSync(join2(TEMPLATES_DIR, template), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
729
|
+
writeFileSync(join2(output, folder, filename), source);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
693
732
|
async function init(options) {
|
|
694
733
|
const { name: displayName, bundleId, teamId, output } = options;
|
|
695
734
|
const name = productName(displayName);
|
|
@@ -697,9 +736,9 @@ async function init(options) {
|
|
|
697
736
|
\u26A1 Initializing Craft iOS project: ${displayName}`);
|
|
698
737
|
console.log(` Output: ${output}
|
|
699
738
|
`);
|
|
700
|
-
const dirs = [output,
|
|
739
|
+
const dirs = [output, join2(output, "Sources"), join2(output, "Shared"), join2(output, "dist")];
|
|
701
740
|
if (options.config?.enableWatchApp)
|
|
702
|
-
dirs.push(
|
|
741
|
+
dirs.push(join2(output, "WatchApp"), join2(output, "WatchExtension"));
|
|
703
742
|
for (const dir of dirs) {
|
|
704
743
|
if (!existsSync(dir)) {
|
|
705
744
|
mkdirSync(dir, { recursive: true });
|
|
@@ -726,29 +765,15 @@ async function init(options) {
|
|
|
726
765
|
`));
|
|
727
766
|
if (Object.keys(config.shareStorage ?? {}).length)
|
|
728
767
|
config.enableSecureStorage = true;
|
|
729
|
-
writeFileSync(
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
733
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeScreen.swift"), join(output, "Sources", "CraftNativeScreen.swift"));
|
|
734
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeFlatList.swift"), join(output, "Sources", "CraftNativeFlatList.swift"));
|
|
735
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
736
|
-
cpSync(join(TEMPLATES_DIR, "CraftHealth.swift"), join(output, "Sources", "CraftHealth.swift"));
|
|
737
|
-
cpSync(join(TEMPLATES_DIR, "CraftMusic.swift"), join(output, "Sources", "CraftMusic.swift"));
|
|
738
|
-
cpSync(join(TEMPLATES_DIR, "CraftHeartRate.swift"), join(output, "Sources", "CraftHeartRate.swift"));
|
|
739
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeMutation.swift"), join(output, "Sources", "CraftNativeMutation.swift"));
|
|
740
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeUI.swift"), join(output, "Sources", "CraftNativeUI.swift"));
|
|
741
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeServices.swift"), join(output, "Sources", "CraftNativeServices.swift"));
|
|
742
|
-
cpSync(join(TEMPLATES_DIR, "CraftNativeFiles.swift"), join(output, "Sources", "CraftNativeFiles.swift"));
|
|
743
|
-
cpSync(join(TEMPLATES_DIR, "CraftWebAudio.swift"), join(output, "Sources", "CraftWebAudio.swift"));
|
|
744
|
-
cpSync(join(TEMPLATES_DIR, "CraftHybrid.swift"), join(output, "Sources", "CraftHybrid.swift"));
|
|
745
|
-
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
768
|
+
writeFileSync(join2(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
769
|
+
writeShell(output, config);
|
|
770
|
+
const infoPlistTemplate = readFileSync(join2(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
746
771
|
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, renderAppearance(config).interfaceStyle).replace(/\{\{STATUS_BAR_STYLE\}\}/g, renderAppearance(config).statusBarStyle).replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{APP_BOUND_DOMAINS\}\}/g, renderAppBoundDomains(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{BACKGROUND_TASK_IDENTIFIERS\}\}/g, renderBackgroundTaskIdentifiers(config)).replace(/\{\{LAUNCH_SCREEN\}\}/g, renderLaunchScreen(config)).replace(/\{\{NON_EXEMPT_ENCRYPTION\}\}/g, config.usesNonExemptEncryption ? "true" : "false").replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
747
772
|
<true/>
|
|
748
773
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
749
774
|
<true/>` : "");
|
|
750
|
-
writeFileSync(
|
|
751
|
-
const projectYmlTemplate = readFileSync(
|
|
775
|
+
writeFileSync(join2(output, "Info.plist"), infoPlist);
|
|
776
|
+
const projectYmlTemplate = readFileSync(join2(TEMPLATES_DIR, "project.yml.template"), "utf-8");
|
|
752
777
|
const nativeDependencies = [];
|
|
753
778
|
if (config.enableLiveActivities)
|
|
754
779
|
nativeDependencies.push(` - target: ${name}LiveActivity`);
|
|
@@ -805,35 +830,30 @@ async function init(options) {
|
|
|
805
830
|
if (hasRuntime) {
|
|
806
831
|
console.log(" Linked the Zig runtime from", runtimeDir);
|
|
807
832
|
} else {
|
|
808
|
-
rmSync(
|
|
833
|
+
rmSync(join2(output, "Runtime"), { recursive: true, force: true });
|
|
809
834
|
}
|
|
810
835
|
const projectYml = projectYmlTemplate.replace(/\{\{CRAFT_RUNTIME_SETTINGS\}\}/g, hasRuntime ? renderRuntimeSettings() : "").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{BUNDLE_ID_PREFIX\}\}/g, bundleIdPrefix).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{IOS_VERSION\}\}/g, config.iosVersion || "15.0").replace(/\{\{DEVICE_FAMILIES\}\}/g, renderDeviceFamilies(config)).replace(/\{\{TEAM_ID\}\}/g, teamId || "").replace(/\{\{NATIVE_DEPENDENCIES\}\}/g, nativeDependencies.length ? ` dependencies:
|
|
811
836
|
${nativeDependencies.join(`
|
|
812
837
|
`)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
|
|
813
838
|
`));
|
|
814
|
-
writeFileSync(
|
|
815
|
-
writeFileSync(
|
|
816
|
-
writeFileSync(
|
|
839
|
+
writeFileSync(join2(output, "project.yml"), projectYml);
|
|
840
|
+
writeFileSync(join2(output, "Craft.entitlements"), renderEntitlements(config));
|
|
841
|
+
writeFileSync(join2(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
|
|
817
842
|
renderAssetCatalog(output, config);
|
|
818
|
-
cpSync(join(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join(output, "Shared", "CraftActivityAttributes.swift"));
|
|
819
843
|
if (config.enableLiveActivities) {
|
|
820
|
-
mkdirSync(
|
|
821
|
-
const
|
|
822
|
-
writeFileSync(
|
|
823
|
-
const widgetInfo = readFileSync(join(TEMPLATES_DIR, "WidgetExtension.Info.plist"), "utf8");
|
|
824
|
-
writeFileSync(join(output, "WidgetExtension", "Info.plist"), widgetInfo);
|
|
844
|
+
mkdirSync(join2(output, "WidgetExtension"), { recursive: true });
|
|
845
|
+
const widgetInfo = readFileSync(join2(TEMPLATES_DIR, "WidgetExtension.Info.plist"), "utf8");
|
|
846
|
+
writeFileSync(join2(output, "WidgetExtension", "Info.plist"), widgetInfo);
|
|
825
847
|
}
|
|
826
848
|
if (config.enableWatchApp) {
|
|
827
|
-
const
|
|
828
|
-
writeFileSync(
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
writeFileSync(
|
|
832
|
-
|
|
833
|
-
writeFileSync(join(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
834
|
-
writeFileSync(join(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
849
|
+
const watchInfo = readFileSync(join2(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
850
|
+
writeFileSync(join2(output, "WatchApp", "Info.plist"), watchInfo);
|
|
851
|
+
writeFileSync(join2(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
852
|
+
const watchExtensionInfo = readFileSync(join2(TEMPLATES_DIR, "WatchExtension.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
853
|
+
writeFileSync(join2(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
854
|
+
writeFileSync(join2(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
835
855
|
}
|
|
836
|
-
writeFileSync(
|
|
856
|
+
writeFileSync(join2(output, "dist", "index.html"), renderOfflinePage(config));
|
|
837
857
|
if (isHybrid(config)) {
|
|
838
858
|
if (config.nativeBundle)
|
|
839
859
|
installNativeBundle(config.nativeBundle, output);
|
|
@@ -858,7 +878,7 @@ async function build(options) {
|
|
|
858
878
|
const { htmlPath, nativeBundlePath, devServer, output, generateProject = true } = options;
|
|
859
879
|
console.log(`
|
|
860
880
|
\uD83D\uDCE6 Building Craft iOS project...`);
|
|
861
|
-
const configPath =
|
|
881
|
+
const configPath = join2(output, "craft.config.json");
|
|
862
882
|
if (!existsSync(configPath)) {
|
|
863
883
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
|
|
864
884
|
}
|
|
@@ -871,11 +891,11 @@ async function build(options) {
|
|
|
871
891
|
throw new Error("A native iOS screen cannot use --html-path or --dev-server.");
|
|
872
892
|
}
|
|
873
893
|
if (nativeBundlePath) {
|
|
874
|
-
const destination =
|
|
894
|
+
const destination = join2(output, "dist", "native-screen.js");
|
|
875
895
|
if (resolve(nativeBundlePath) !== resolve(destination))
|
|
876
896
|
cpSync(nativeBundlePath, destination);
|
|
877
897
|
}
|
|
878
|
-
if (!existsSync(
|
|
898
|
+
if (!existsSync(join2(output, "dist", "native-screen.js"))) {
|
|
879
899
|
throw new Error("Native iOS mode needs dist/native-screen.js. Compile a .stx screen with `stx native compile` first.");
|
|
880
900
|
}
|
|
881
901
|
} else if (nativeBundlePath && !isHybrid(config)) {
|
|
@@ -901,7 +921,7 @@ async function build(options) {
|
|
|
901
921
|
if (bundle) {
|
|
902
922
|
installNativeBundle(bundle, output);
|
|
903
923
|
console.log(` Native screens: ${bundle} \u2192 dist/native-screen.js`);
|
|
904
|
-
} else if (!existsSync(
|
|
924
|
+
} else if (!existsSync(join2(output, "dist", "native-screen.js"))) {
|
|
905
925
|
console.warn(" \u26A0 nativeScreens is set but there is no native bundle: every path opens in the web view.");
|
|
906
926
|
}
|
|
907
927
|
}
|
|
@@ -931,7 +951,7 @@ async function open(options) {
|
|
|
931
951
|
if (!xcodeproj) {
|
|
932
952
|
throw new Error(`No Xcode project found in ${output}. Run 'craft ios build' first.`);
|
|
933
953
|
}
|
|
934
|
-
const projectPath =
|
|
954
|
+
const projectPath = join2(output, xcodeproj);
|
|
935
955
|
console.log(`\uD83D\uDE80 Opening ${xcodeproj}...`);
|
|
936
956
|
await $`open ${projectPath}`;
|
|
937
957
|
}
|
|
@@ -972,7 +992,7 @@ async function showSimulator() {
|
|
|
972
992
|
const selected = await $`xcode-select -p`.quiet().nothrow();
|
|
973
993
|
const developer = selected.exitCode === 0 ? selected.stdout.toString().trim() : "";
|
|
974
994
|
if (developer) {
|
|
975
|
-
const app =
|
|
995
|
+
const app = join2(developer, "Applications", "Simulator.app");
|
|
976
996
|
if (existsSync(app)) {
|
|
977
997
|
await $`open ${app}`.quiet().nothrow();
|
|
978
998
|
return;
|
|
@@ -988,9 +1008,9 @@ async function run(options) {
|
|
|
988
1008
|
if (!xcodeproj) {
|
|
989
1009
|
throw new Error(`No Xcode project found in ${output}`);
|
|
990
1010
|
}
|
|
991
|
-
const projectPath =
|
|
1011
|
+
const projectPath = join2(output, xcodeproj);
|
|
992
1012
|
const appName = xcodeproj.replace(".xcodeproj", "");
|
|
993
|
-
const configPath =
|
|
1013
|
+
const configPath = join2(output, "craft.config.json");
|
|
994
1014
|
if (!existsSync(configPath))
|
|
995
1015
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft-ios init' first.`);
|
|
996
1016
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
@@ -1003,8 +1023,8 @@ async function run(options) {
|
|
|
1003
1023
|
console.log(` Device: ${device.name} (${device.runtime})`);
|
|
1004
1024
|
try {
|
|
1005
1025
|
await bootSimulator(device);
|
|
1006
|
-
const derivedData =
|
|
1007
|
-
const product =
|
|
1026
|
+
const derivedData = join2(output, "build");
|
|
1027
|
+
const product = join2(derivedData, "Build", "Products", "Debug-iphonesimulator", `${appName}.app`);
|
|
1008
1028
|
await $`xcodebuild -project ${projectPath} -scheme ${appName} -configuration Debug -destination ${`id=${device.udid}`} -derivedDataPath ${derivedData} build`;
|
|
1009
1029
|
if (!existsSync(product)) {
|
|
1010
1030
|
throw new Error(`xcodebuild reported success but ${product} does not exist. ` + "The scheme may build a different product name than the project.");
|
package/dist/index.d.ts
CHANGED
|
@@ -412,6 +412,8 @@ export declare function installRuntime(output: string, runtimeDir: string): Prom
|
|
|
412
412
|
* valid Swift type.
|
|
413
413
|
*/
|
|
414
414
|
export declare function productName(name: string): string;
|
|
415
|
+
/** Restore package-owned Swift into an existing project, without changing its Xcode metadata or bundled screens. */
|
|
416
|
+
export declare function restoreShell(output: string): void;
|
|
415
417
|
export declare function init(options: InitOptions): Promise<void>;
|
|
416
418
|
/**
|
|
417
419
|
* Build web assets and generate Xcode project
|
|
@@ -475,3 +477,5 @@ export declare function bootSimulator(device: SimulatorDevice): Promise<void>;
|
|
|
475
477
|
*/
|
|
476
478
|
export declare function showSimulator(): Promise<void>;
|
|
477
479
|
export declare function run(options: RunOptions): Promise<void>;
|
|
480
|
+
export { createNativeMutationValidator, initSimulatorDriver, serializeSimulatorSteps } from './testing.js';
|
|
481
|
+
export type { NativeMutationValidator, NativeValidationResult, SimulatorDriverOptions, SimulatorStep } from './testing.js';
|