craft-native 0.0.90 → 0.0.92
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/dist/android/src/index.d.ts +36 -1
- package/dist/android/src/index.js +332 -44
- package/dist/android/src/promise-runtime.d.ts +3 -0
- package/dist/android/templates/CraftBridge.kt.template +2167 -1177
- package/dist/android/templates/CraftHealthConnect.kt.template +45 -6
- package/dist/android/templates/CraftHealthConnectStub.kt.template +7 -1
- package/dist/android/templates/CraftNative.kt.template +2250 -0
- package/dist/android/templates/LocationRecordingService.kt.template +34 -9
- package/dist/android/templates/MainActivity.kt.template +42 -8
- package/dist/android/templates/proguard-rules.pro.template +4 -1
- package/dist/android/templates/test-bridges.html +10 -33
- package/dist/api/index.d.ts +1 -1
- package/dist/api/ios-advanced.d.ts +8 -5
- package/dist/api/live-activity-handle.d.ts +6 -0
- package/dist/api/mobile.d.ts +25 -7
- package/dist/api/window.d.ts +2 -0
- package/dist/cli.js +452 -129
- package/dist/index.cjs +77 -22
- package/dist/index.js +77 -22
- package/dist/ios/src/index.d.ts +1 -1
- package/dist/ios/src/index.js +23 -5
- package/dist/ios/templates/CraftApp.swift +706 -92
- package/dist/ios/templates/CraftWatchApp.swift.template +8 -0
- package/dist/ios/templates/WatchApp.Info.plist.template +2 -0
- package/dist/ios/templates/WatchExtension.Info.plist.template +34 -0
- package/dist/ios/templates/project.yml.template +10 -4
- package/dist/mobile.js +52 -21
- package/dist/scaffold-version.d.ts +5 -0
- package/package.json +1 -1
- package/dist/android/templates/CraftBridgeExtensions.kt.template +0 -383
- package/dist/android/templates/CraftWidgetProvider.kt.template +0 -246
package/dist/cli.js
CHANGED
|
@@ -48,10 +48,10 @@ import {
|
|
|
48
48
|
lstatSync,
|
|
49
49
|
mkdirSync,
|
|
50
50
|
mkdtempSync,
|
|
51
|
-
readFileSync,
|
|
51
|
+
readFileSync as readFileSync2,
|
|
52
52
|
readdirSync,
|
|
53
53
|
rmSync,
|
|
54
|
-
writeFileSync
|
|
54
|
+
writeFileSync as writeFileSync2
|
|
55
55
|
} from "fs";
|
|
56
56
|
import { homedir, tmpdir } from "os";
|
|
57
57
|
import { basename, join } from "path";
|
|
@@ -488,7 +488,7 @@ function createMacOSAppBundle(opts) {
|
|
|
488
488
|
return { success: false, error: `Provisioning profile not found: ${provisioningProfile}` };
|
|
489
489
|
copyFileSync(provisioningProfile, join(contents, "embedded.provisionprofile"));
|
|
490
490
|
}
|
|
491
|
-
|
|
491
|
+
writeFileSync2(join(contents, "Info.plist"), macOSInfoPlist({ ...opts, iconName }));
|
|
492
492
|
return { success: true };
|
|
493
493
|
} catch (error) {
|
|
494
494
|
return { success: false, error: error.message };
|
|
@@ -626,7 +626,7 @@ async function createPKG(opts) {
|
|
|
626
626
|
const bundleName = basename(opts.appBundlePath);
|
|
627
627
|
cpSync(opts.appBundlePath, join(appsDir, bundleName), { recursive: true });
|
|
628
628
|
const componentPlistPath = join(tempDir, "component.plist");
|
|
629
|
-
|
|
629
|
+
writeFileSync2(componentPlistPath, pkgbuildComponentPlist(`Applications/${bundleName}`));
|
|
630
630
|
const built = await runTool("pkgbuild", pkgbuildArguments({
|
|
631
631
|
root,
|
|
632
632
|
componentPlistPath,
|
|
@@ -716,7 +716,7 @@ async function createMSI(opts) {
|
|
|
716
716
|
const wxsPath = join(tempDir, "installer.wxs");
|
|
717
717
|
const wixobjPath = join(tempDir, "installer.wixobj");
|
|
718
718
|
copyFileSync(opts.binaryPath, sourcePath);
|
|
719
|
-
|
|
719
|
+
writeFileSync2(wxsPath, renderWixSource(opts, binaryName));
|
|
720
720
|
await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
|
|
721
721
|
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
722
722
|
if (opts.certificatePath) {
|
|
@@ -738,9 +738,9 @@ async function createMSI(opts) {
|
|
|
738
738
|
}
|
|
739
739
|
async function createZIP(opts) {
|
|
740
740
|
try {
|
|
741
|
-
const data = new Uint8Array(
|
|
741
|
+
const data = new Uint8Array(readFileSync2(opts.binaryPath));
|
|
742
742
|
const zip = buildZip([{ name: `${opts.name}.exe`, data }]);
|
|
743
|
-
|
|
743
|
+
writeFileSync2(opts.outputPath, zip);
|
|
744
744
|
return { success: true, outputPath: opts.outputPath };
|
|
745
745
|
} catch (err) {
|
|
746
746
|
return { success: false, error: `Failed to write ZIP: ${err.message}` };
|
|
@@ -788,7 +788,7 @@ Depends: ${opts.dependencies.join(", ")}
|
|
|
788
788
|
Maintainer: ${maintainer}
|
|
789
789
|
Description: ${description || opts.name}
|
|
790
790
|
`;
|
|
791
|
-
|
|
791
|
+
writeFileSync2(join(debianDir, "control"), controlContent);
|
|
792
792
|
const desktopContent = `[Desktop Entry]
|
|
793
793
|
Type=Application
|
|
794
794
|
Name=${opts.name}
|
|
@@ -796,7 +796,7 @@ Exec=/usr/bin/${binaryName}
|
|
|
796
796
|
Terminal=false
|
|
797
797
|
Categories=Utility;
|
|
798
798
|
`;
|
|
799
|
-
|
|
799
|
+
writeFileSync2(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
|
|
800
800
|
const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
|
|
801
801
|
proc.on("close", (code) => {
|
|
802
802
|
rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -865,7 +865,7 @@ install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
|
865
865
|
/usr/bin/${sanitizedName}
|
|
866
866
|
`;
|
|
867
867
|
const specPath = join(specDir, `${opts.name.toLowerCase()}.spec`);
|
|
868
|
-
|
|
868
|
+
writeFileSync2(specPath, specContent);
|
|
869
869
|
const proc = spawn("rpmbuild", ["-bb", specPath]);
|
|
870
870
|
proc.on("close", (code) => {
|
|
871
871
|
if (code === 0) {
|
|
@@ -907,7 +907,7 @@ HERE=\${SELF%/*}
|
|
|
907
907
|
export PATH="\${HERE}/usr/bin/:\${PATH}"
|
|
908
908
|
exec "\${HERE}/usr/bin/${binaryName}" "$@"
|
|
909
909
|
`;
|
|
910
|
-
|
|
910
|
+
writeFileSync2(join(appDirPath, "AppRun"), appRunContent);
|
|
911
911
|
chmodSync(join(appDirPath, "AppRun"), 493);
|
|
912
912
|
const desktopContent = `[Desktop Entry]
|
|
913
913
|
Type=Application
|
|
@@ -917,7 +917,7 @@ Terminal=false
|
|
|
917
917
|
Categories=Utility;
|
|
918
918
|
Icon=${binaryName}
|
|
919
919
|
`;
|
|
920
|
-
|
|
920
|
+
writeFileSync2(join(appDirPath, `${binaryName}.desktop`), desktopContent);
|
|
921
921
|
if (opts.iconPath && existsSync3(opts.iconPath)) {
|
|
922
922
|
copyFileSync(opts.iconPath, join(appDirPath, `${binaryName}.png`));
|
|
923
923
|
} else {
|
|
@@ -992,7 +992,7 @@ Icon=${binaryName}
|
|
|
992
992
|
96,
|
|
993
993
|
130
|
|
994
994
|
]);
|
|
995
|
-
|
|
995
|
+
writeFileSync2(join(appDirPath, `${binaryName}.png`), placeholderPng);
|
|
996
996
|
}
|
|
997
997
|
const proc = spawn("appimagetool", [appDirPath, opts.outputPath], {
|
|
998
998
|
env: { ...process.env, ARCH: "x86_64" }
|
|
@@ -1071,7 +1071,7 @@ __export(exports_src, {
|
|
|
1071
1071
|
build: () => build,
|
|
1072
1072
|
bootSimulator: () => bootSimulator
|
|
1073
1073
|
});
|
|
1074
|
-
import { cpSync as cpSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
1074
|
+
import { cpSync as cpSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1075
1075
|
import { dirname, join as join2, resolve } from "path";
|
|
1076
1076
|
function renderDeviceFamilies(config) {
|
|
1077
1077
|
const values = new Set(((config.deviceFamilies?.length) ? config.deviceFamilies : ["iphone", "ipad"]).map((family) => family === "ipad" ? "2" : "1"));
|
|
@@ -1170,7 +1170,7 @@ ${plistArray(config.appGroups)}
|
|
|
1170
1170
|
}
|
|
1171
1171
|
if (config.enablePushNotifications) {
|
|
1172
1172
|
entries.push(` <key>aps-environment</key>
|
|
1173
|
-
<string
|
|
1173
|
+
<string>$(CRAFT_APNS_ENVIRONMENT)</string>`);
|
|
1174
1174
|
}
|
|
1175
1175
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1176
1176
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -1258,7 +1258,7 @@ function renderAssetCatalog(output, config) {
|
|
|
1258
1258
|
const launchBackground = join2(catalog, "LaunchBackground.colorset");
|
|
1259
1259
|
mkdirSync2(appIcon, { recursive: true });
|
|
1260
1260
|
mkdirSync2(launchBackground, { recursive: true });
|
|
1261
|
-
|
|
1261
|
+
writeFileSync3(join2(catalog, "Contents.json"), `${JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)}
|
|
1262
1262
|
`);
|
|
1263
1263
|
const iconFilename = config.appIconPath ? "AppIcon-1024.png" : undefined;
|
|
1264
1264
|
if (config.appIconPath) {
|
|
@@ -1266,7 +1266,7 @@ function renderAssetCatalog(output, config) {
|
|
|
1266
1266
|
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
1267
1267
|
cpSync2(config.appIconPath, join2(appIcon, iconFilename));
|
|
1268
1268
|
}
|
|
1269
|
-
|
|
1269
|
+
writeFileSync3(join2(appIcon, "Contents.json"), `${JSON.stringify({
|
|
1270
1270
|
images: iconFilename ? [{
|
|
1271
1271
|
filename: iconFilename,
|
|
1272
1272
|
idiom: "universal",
|
|
@@ -1279,7 +1279,7 @@ function renderAssetCatalog(output, config) {
|
|
|
1279
1279
|
const color = config.backgroundColor?.replace(/^#/, "") || "000000";
|
|
1280
1280
|
const normalized = color.length === 3 ? [...color].map((value) => `${value}${value}`).join("") : color.padEnd(6, "0").slice(0, 6);
|
|
1281
1281
|
const components = [0, 2, 4].map((index) => (Number.parseInt(normalized.slice(index, index + 2), 16) / 255).toFixed(3));
|
|
1282
|
-
|
|
1282
|
+
writeFileSync3(join2(launchBackground, "Contents.json"), `${JSON.stringify({
|
|
1283
1283
|
colors: [{
|
|
1284
1284
|
color: {
|
|
1285
1285
|
"color-space": "srgb",
|
|
@@ -1364,7 +1364,7 @@ async function init(options) {
|
|
|
1364
1364
|
`);
|
|
1365
1365
|
const dirs = [output, join2(output, "Sources"), join2(output, "Shared"), join2(output, "dist")];
|
|
1366
1366
|
if (options.config?.enableWatchApp)
|
|
1367
|
-
dirs.push(join2(output, "WatchApp"));
|
|
1367
|
+
dirs.push(join2(output, "WatchApp"), join2(output, "WatchExtension"));
|
|
1368
1368
|
for (const dir of dirs) {
|
|
1369
1369
|
if (!existsSync4(dir)) {
|
|
1370
1370
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -1381,17 +1381,17 @@ async function init(options) {
|
|
|
1381
1381
|
};
|
|
1382
1382
|
if (config.enableBackgroundLocation)
|
|
1383
1383
|
config.enableGeolocation = true;
|
|
1384
|
-
|
|
1385
|
-
const swiftTemplate =
|
|
1384
|
+
writeFileSync3(join2(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1385
|
+
const swiftTemplate = readFileSync3(join2(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
1386
1386
|
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1387
|
-
|
|
1388
|
-
const infoPlistTemplate =
|
|
1387
|
+
writeFileSync3(join2(output, "Sources", `${name}App.swift`), swiftSource);
|
|
1388
|
+
const infoPlistTemplate = readFileSync3(join2(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
1389
1389
|
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, config.darkMode ? "Dark" : "Light").replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
1390
1390
|
<true/>
|
|
1391
1391
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
1392
1392
|
<true/>` : "");
|
|
1393
|
-
|
|
1394
|
-
const projectYmlTemplate =
|
|
1393
|
+
writeFileSync3(join2(output, "Info.plist"), infoPlist);
|
|
1394
|
+
const projectYmlTemplate = readFileSync3(join2(TEMPLATES_DIR, "project.yml.template"), "utf-8");
|
|
1395
1395
|
const nativeDependencies = [];
|
|
1396
1396
|
if (config.enableLiveActivities)
|
|
1397
1397
|
nativeDependencies.push(` - target: ${name}LiveActivity`);
|
|
@@ -1416,7 +1416,7 @@ async function init(options) {
|
|
|
1416
1416
|
}
|
|
1417
1417
|
if (config.enableWatchApp) {
|
|
1418
1418
|
nativeTargets.push(` ${name}Watch:
|
|
1419
|
-
type: application
|
|
1419
|
+
type: application.watchapp2
|
|
1420
1420
|
platform: watchOS
|
|
1421
1421
|
deploymentTarget: "${config.watchosVersion || "9.0"}"
|
|
1422
1422
|
sources:
|
|
@@ -1426,6 +1426,21 @@ async function init(options) {
|
|
|
1426
1426
|
CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
|
|
1427
1427
|
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
|
|
1428
1428
|
SWIFT_VERSION: "5.0"
|
|
1429
|
+
SKIP_INSTALL: YES
|
|
1430
|
+
dependencies:
|
|
1431
|
+
- target: ${name}WatchExtension
|
|
1432
|
+
${name}WatchExtension:
|
|
1433
|
+
type: watchkit2-extension
|
|
1434
|
+
platform: watchOS
|
|
1435
|
+
deploymentTarget: "${config.watchosVersion || "9.0"}"
|
|
1436
|
+
sources:
|
|
1437
|
+
- WatchExtension
|
|
1438
|
+
settings:
|
|
1439
|
+
INFOPLIST_FILE: WatchExtension/Info.plist
|
|
1440
|
+
CODE_SIGN_ENTITLEMENTS: WatchExtension/Watch.entitlements
|
|
1441
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp.watchkitextension
|
|
1442
|
+
SWIFT_VERSION: "5.0"
|
|
1443
|
+
APPLICATION_EXTENSION_API_ONLY: YES
|
|
1429
1444
|
SKIP_INSTALL: YES`);
|
|
1430
1445
|
}
|
|
1431
1446
|
const runtimeDir = resolveRuntimeDir(options.runtimeDir);
|
|
@@ -1439,24 +1454,27 @@ async function init(options) {
|
|
|
1439
1454
|
${nativeDependencies.join(`
|
|
1440
1455
|
`)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
|
|
1441
1456
|
`));
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1457
|
+
writeFileSync3(join2(output, "project.yml"), projectYml);
|
|
1458
|
+
writeFileSync3(join2(output, "Craft.entitlements"), renderEntitlements(config));
|
|
1459
|
+
writeFileSync3(join2(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
|
|
1445
1460
|
renderAssetCatalog(output, config);
|
|
1446
1461
|
cpSync2(join2(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join2(output, "Shared", "CraftActivityAttributes.swift"));
|
|
1447
1462
|
if (config.enableLiveActivities) {
|
|
1448
1463
|
mkdirSync2(join2(output, "WidgetExtension"), { recursive: true });
|
|
1449
|
-
const widgetSource =
|
|
1450
|
-
|
|
1451
|
-
const widgetInfo =
|
|
1452
|
-
|
|
1464
|
+
const widgetSource = readFileSync3(join2(TEMPLATES_DIR, "CraftLiveActivityWidget.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
1465
|
+
writeFileSync3(join2(output, "WidgetExtension", `${name}LiveActivity.swift`), widgetSource);
|
|
1466
|
+
const widgetInfo = readFileSync3(join2(TEMPLATES_DIR, "WidgetExtension.Info.plist"), "utf8");
|
|
1467
|
+
writeFileSync3(join2(output, "WidgetExtension", "Info.plist"), widgetInfo);
|
|
1453
1468
|
}
|
|
1454
1469
|
if (config.enableWatchApp) {
|
|
1455
|
-
const watchSource =
|
|
1456
|
-
|
|
1457
|
-
const watchInfo =
|
|
1458
|
-
|
|
1459
|
-
|
|
1470
|
+
const watchSource = readFileSync3(join2(TEMPLATES_DIR, "CraftWatchApp.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
1471
|
+
writeFileSync3(join2(output, "WatchExtension", `${name}WatchApp.swift`), watchSource);
|
|
1472
|
+
const watchInfo = readFileSync3(join2(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1473
|
+
writeFileSync3(join2(output, "WatchApp", "Info.plist"), watchInfo);
|
|
1474
|
+
writeFileSync3(join2(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
1475
|
+
const watchExtensionInfo = readFileSync3(join2(TEMPLATES_DIR, "WatchExtension.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1476
|
+
writeFileSync3(join2(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
1477
|
+
writeFileSync3(join2(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
1460
1478
|
}
|
|
1461
1479
|
const placeholderHtml = `<!DOCTYPE html>
|
|
1462
1480
|
<html>
|
|
@@ -1496,7 +1514,7 @@ ${nativeDependencies.join(`
|
|
|
1496
1514
|
</script>
|
|
1497
1515
|
</body>
|
|
1498
1516
|
</html>`;
|
|
1499
|
-
|
|
1517
|
+
writeFileSync3(join2(output, "dist", "index.html"), placeholderHtml);
|
|
1500
1518
|
console.log("\u2705 Project initialized");
|
|
1501
1519
|
console.log("");
|
|
1502
1520
|
console.log("Next steps:");
|
|
@@ -1514,12 +1532,12 @@ async function build(options) {
|
|
|
1514
1532
|
if (!existsSync4(configPath)) {
|
|
1515
1533
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
|
|
1516
1534
|
}
|
|
1517
|
-
const config = JSON.parse(
|
|
1535
|
+
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1518
1536
|
if (devServer) {
|
|
1519
1537
|
config.devServerURL = devServer;
|
|
1520
1538
|
const origin = new URL(devServer).origin;
|
|
1521
1539
|
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], origin])];
|
|
1522
|
-
|
|
1540
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2));
|
|
1523
1541
|
console.log(` Dev server: ${devServer}`);
|
|
1524
1542
|
}
|
|
1525
1543
|
if (htmlPath) {
|
|
@@ -1557,7 +1575,7 @@ async function open(options) {
|
|
|
1557
1575
|
await $`open ${projectPath}`;
|
|
1558
1576
|
}
|
|
1559
1577
|
function orderSimulators(devices) {
|
|
1560
|
-
return
|
|
1578
|
+
return devices.filter((device) => device.runtime.startsWith("iOS")).sort((left, right) => {
|
|
1561
1579
|
const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
|
|
1562
1580
|
if (booted !== 0)
|
|
1563
1581
|
return booted;
|
|
@@ -1614,7 +1632,7 @@ async function run(options) {
|
|
|
1614
1632
|
const configPath = join2(output, "craft.config.json");
|
|
1615
1633
|
if (!existsSync4(configPath))
|
|
1616
1634
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft-ios init' first.`);
|
|
1617
|
-
const config = JSON.parse(
|
|
1635
|
+
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1618
1636
|
if (simulator) {
|
|
1619
1637
|
console.log("\uD83D\uDCF1 Building and running on simulator...");
|
|
1620
1638
|
const device = await pickSimulator();
|
|
@@ -1712,31 +1730,157 @@ var exports_src2 = {};
|
|
|
1712
1730
|
__export(exports_src2, {
|
|
1713
1731
|
syncAndroidWebAssets: () => syncAndroidWebAssets,
|
|
1714
1732
|
run: () => run2,
|
|
1733
|
+
resolveRuntimeDir: () => resolveRuntimeDir2,
|
|
1715
1734
|
renderAndroidPermissions: () => renderAndroidPermissions,
|
|
1716
1735
|
renderAndroidDeepLinks: () => renderAndroidDeepLinks,
|
|
1717
1736
|
open: () => open2,
|
|
1737
|
+
installRuntime: () => installRuntime2,
|
|
1718
1738
|
init: () => init2,
|
|
1719
1739
|
build: () => build2
|
|
1720
1740
|
});
|
|
1721
|
-
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as
|
|
1722
|
-
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
1741
|
+
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1742
|
+
import { dirname as dirname2, extname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
1743
|
+
function renderAndroidPromiseRuntime(indent = "") {
|
|
1744
|
+
return ANDROID_PROMISE_RUNTIME.trim().split(`
|
|
1745
|
+
`).map((line) => `${indent}${line}`).join(`
|
|
1746
|
+
`);
|
|
1747
|
+
}
|
|
1748
|
+
function escapeXml(value) {
|
|
1749
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1750
|
+
}
|
|
1751
|
+
function escapeKotlinString(value) {
|
|
1752
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("\r", "\\r").replaceAll(`
|
|
1753
|
+
`, "\\n");
|
|
1754
|
+
}
|
|
1755
|
+
function generatedPackageSegment(name) {
|
|
1756
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
1757
|
+
if (!normalized)
|
|
1758
|
+
return "app";
|
|
1759
|
+
return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`;
|
|
1760
|
+
}
|
|
1761
|
+
function generatedGradleProjectName(name) {
|
|
1762
|
+
const invalidCharacters = ["/", "\\", ":", "<", ">", '"', "?", "*", "|"];
|
|
1763
|
+
const normalized = invalidCharacters.reduce((value, character) => value.replaceAll(character, "-"), name).trim();
|
|
1764
|
+
return normalized || "craft-app";
|
|
1765
|
+
}
|
|
1766
|
+
function requireRegularFile(path2, label) {
|
|
1767
|
+
if (!existsSync5(path2))
|
|
1768
|
+
throw new Error(`${label} not found: ${path2}`);
|
|
1769
|
+
if (!statSync2(path2).isFile())
|
|
1770
|
+
throw new Error(`${label} must be a file: ${path2}`);
|
|
1771
|
+
}
|
|
1772
|
+
function containsPath(parent, candidate) {
|
|
1773
|
+
const relativePath = relative(parent, candidate);
|
|
1774
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
1775
|
+
}
|
|
1776
|
+
function validateGoogleServicesFile(path2, packageName) {
|
|
1777
|
+
requireRegularFile(path2, "Google services file");
|
|
1778
|
+
let document;
|
|
1779
|
+
try {
|
|
1780
|
+
document = JSON.parse(readFileSync4(path2, "utf8"));
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
throw new Error(`Google services file must contain valid JSON: ${path2}`, { cause: error });
|
|
1783
|
+
}
|
|
1784
|
+
const matchingClient = document.client?.some((client) => {
|
|
1785
|
+
return client.client_info?.android_client_info?.package_name === packageName;
|
|
1786
|
+
});
|
|
1787
|
+
if (!matchingClient) {
|
|
1788
|
+
throw new Error(`Google services file has no client for Android package ${packageName}: ${path2}`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
function androidWebUrl(value, field) {
|
|
1792
|
+
let url;
|
|
1793
|
+
try {
|
|
1794
|
+
url = new URL(value);
|
|
1795
|
+
} catch {
|
|
1796
|
+
throw new Error(`Invalid ${field}: ${value}`);
|
|
1797
|
+
}
|
|
1798
|
+
const localDevelopment = url.protocol === "http:" && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname);
|
|
1799
|
+
if (url.protocol !== "https:" && !localDevelopment || url.username || url.password) {
|
|
1800
|
+
throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`);
|
|
1801
|
+
}
|
|
1802
|
+
return url;
|
|
1803
|
+
}
|
|
1804
|
+
function normalizeAndroidNetworkConfig(config) {
|
|
1805
|
+
const schemes = config.urlSchemes?.map((value) => value.trim().toLowerCase()) ?? [];
|
|
1806
|
+
if (schemes.some((value) => !/^[a-z][a-z0-9+.-]*$/.test(value))) {
|
|
1807
|
+
throw new Error("Android deep-link schemes must be valid URI schemes");
|
|
1808
|
+
}
|
|
1809
|
+
config.urlSchemes = [...new Set(schemes)];
|
|
1810
|
+
if (config.enableDeepLinks && config.urlSchemes.length === 0) {
|
|
1811
|
+
throw new Error("Android deep links require at least one URL scheme");
|
|
1812
|
+
}
|
|
1813
|
+
const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {
|
|
1814
|
+
return androidWebUrl(value, "Android trusted origin").origin;
|
|
1815
|
+
});
|
|
1816
|
+
if (config.devServerURL) {
|
|
1817
|
+
const devServer = androidWebUrl(config.devServerURL, "Android dev server URL");
|
|
1818
|
+
config.devServerURL = devServer.toString();
|
|
1819
|
+
trustedOrigins.push(devServer.origin);
|
|
1820
|
+
}
|
|
1821
|
+
config.trustedOrigins = [...new Set(trustedOrigins)];
|
|
1822
|
+
}
|
|
1823
|
+
function validateAndroidConfig(config) {
|
|
1824
|
+
if (!config.appName.trim())
|
|
1825
|
+
throw new Error("Android app name must not be empty");
|
|
1826
|
+
const packageSegments = config.packageName.split(".");
|
|
1827
|
+
if (packageSegments.length < 2 || packageSegments.some((segment) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {
|
|
1828
|
+
throw new Error(`Invalid Android package name: ${config.packageName}`);
|
|
1829
|
+
}
|
|
1830
|
+
if (!/^#(?:[\dA-F]{3,4}|[\dA-F]{6}|[\dA-F]{8})$/i.test(config.backgroundColor ?? "")) {
|
|
1831
|
+
throw new Error(`Invalid Android background color: ${config.backgroundColor}`);
|
|
1832
|
+
}
|
|
1833
|
+
for (const [name, value] of [
|
|
1834
|
+
["versionCode", config.versionCode],
|
|
1835
|
+
["minSdk", config.minSdk],
|
|
1836
|
+
["compileSdk", config.compileSdk],
|
|
1837
|
+
["targetSdk", config.targetSdk]
|
|
1838
|
+
]) {
|
|
1839
|
+
if (!Number.isInteger(value) || Number(value) < 1) {
|
|
1840
|
+
throw new Error(`Android ${name} must be a positive integer`);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (Number(config.minSdk) > Number(config.targetSdk)) {
|
|
1844
|
+
throw new Error("Android minSdk must not exceed targetSdk");
|
|
1845
|
+
}
|
|
1846
|
+
if (Number(config.targetSdk) > Number(config.compileSdk)) {
|
|
1847
|
+
throw new Error("Android targetSdk must not exceed compileSdk");
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
function writeAndroidConfig(output, config) {
|
|
1851
|
+
writeFileSync4(join3(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1852
|
+
const runtimeConfig = { ...config };
|
|
1853
|
+
delete runtimeConfig.appIconPath;
|
|
1854
|
+
delete runtimeConfig.googleServicesFile;
|
|
1855
|
+
writeFileSync4(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(runtimeConfig, null, 2));
|
|
1856
|
+
}
|
|
1723
1857
|
function syncAndroidWebAssets(source, output) {
|
|
1724
1858
|
const sourcePath = resolve2(source);
|
|
1725
1859
|
if (!existsSync5(sourcePath))
|
|
1726
1860
|
throw new Error(`Web asset path not found: ${source}`);
|
|
1727
|
-
const
|
|
1861
|
+
const sourceStat = statSync2(sourcePath);
|
|
1862
|
+
if (!sourceStat.isDirectory() && !sourceStat.isFile()) {
|
|
1863
|
+
throw new Error(`Web asset path must be a file or directory: ${source}`);
|
|
1864
|
+
}
|
|
1865
|
+
const assetsDir = resolve2(output, "app/src/main/assets");
|
|
1866
|
+
if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {
|
|
1867
|
+
throw new Error(`Web asset source must not overlap generated asset directory: ${source}`);
|
|
1868
|
+
}
|
|
1869
|
+
if (sourceStat.isDirectory()) {
|
|
1870
|
+
requireRegularFile(join3(sourcePath, "index.html"), "Web asset directory entry point");
|
|
1871
|
+
}
|
|
1728
1872
|
const configPath = join3(assetsDir, "craft.config.json");
|
|
1729
|
-
const config = existsSync5(configPath) ?
|
|
1873
|
+
const config = existsSync5(configPath) ? readFileSync4(configPath) : undefined;
|
|
1730
1874
|
rmSync3(assetsDir, { recursive: true, force: true });
|
|
1731
1875
|
mkdirSync3(assetsDir, { recursive: true });
|
|
1732
|
-
if (
|
|
1876
|
+
if (sourceStat.isDirectory())
|
|
1733
1877
|
cpSync3(sourcePath, assetsDir, { recursive: true });
|
|
1734
1878
|
else
|
|
1735
1879
|
cpSync3(sourcePath, join3(assetsDir, "index.html"));
|
|
1736
1880
|
if (!existsSync5(join3(assetsDir, "index.html")))
|
|
1737
1881
|
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
1738
1882
|
if (config)
|
|
1739
|
-
|
|
1883
|
+
writeFileSync4(configPath, config);
|
|
1740
1884
|
}
|
|
1741
1885
|
function renderAndroidPermissions(config) {
|
|
1742
1886
|
const permissions = new Set(["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]);
|
|
@@ -1786,14 +1930,69 @@ function renderAndroidDeepLinks(config) {
|
|
|
1786
1930
|
</intent-filter>`).join(`
|
|
1787
1931
|
`);
|
|
1788
1932
|
}
|
|
1933
|
+
function resolveRuntimeDir2(override) {
|
|
1934
|
+
if (override === null)
|
|
1935
|
+
return null;
|
|
1936
|
+
const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME;
|
|
1937
|
+
if (!dir)
|
|
1938
|
+
return null;
|
|
1939
|
+
if (!existsSync5(dir)) {
|
|
1940
|
+
const source = override === undefined ? "CRAFT_ANDROID_RUNTIME points at" : "runtimeDir is";
|
|
1941
|
+
throw new Error(`${source} ${dir}, which does not exist.`);
|
|
1942
|
+
}
|
|
1943
|
+
return dir;
|
|
1944
|
+
}
|
|
1945
|
+
function installRuntime2(output, runtimeDir) {
|
|
1946
|
+
const found = RUNTIME_ABIS.map((abi) => ({ abi, source: join3(runtimeDir, abi, "libcraft.so") })).filter((entry) => existsSync5(entry.source));
|
|
1947
|
+
if (found.length === 0) {
|
|
1948
|
+
throw new Error(`${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(", ")}. ` + "Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.");
|
|
1949
|
+
}
|
|
1950
|
+
if (found.length < RUNTIME_ABIS.length) {
|
|
1951
|
+
const missing = RUNTIME_ABIS.filter((abi) => !found.some((entry) => entry.abi === abi));
|
|
1952
|
+
console.warn(` \u26A0 only ${found.map((entry) => entry.abi).join(", ")} was found; ${missing.join(", ")} is missing. ` + "The app will fall back to the Kotlin shim on those devices.");
|
|
1953
|
+
}
|
|
1954
|
+
const dest = join3(output, "app/src/main/jniLibs");
|
|
1955
|
+
rmSync3(dest, { force: true, recursive: true });
|
|
1956
|
+
for (const { abi, source } of found) {
|
|
1957
|
+
const abiDir = join3(dest, abi);
|
|
1958
|
+
mkdirSync3(abiDir, { recursive: true });
|
|
1959
|
+
cpSync3(source, join3(abiDir, "libcraft.so"));
|
|
1960
|
+
}
|
|
1961
|
+
return true;
|
|
1962
|
+
}
|
|
1789
1963
|
async function init2(options) {
|
|
1790
1964
|
const { name, packageName, output } = options;
|
|
1791
1965
|
console.log(`
|
|
1792
1966
|
\u26A1 Initializing Craft Android project: ${name}`);
|
|
1793
1967
|
console.log(` Output: ${output}
|
|
1794
1968
|
`);
|
|
1795
|
-
const finalPackageName = packageName || `com.craft.${name
|
|
1969
|
+
const finalPackageName = packageName || `com.craft.${generatedPackageSegment(name)}`;
|
|
1796
1970
|
const packagePath = finalPackageName.replace(/\./g, "/");
|
|
1971
|
+
const config = {
|
|
1972
|
+
...DEFAULT_CONFIG2,
|
|
1973
|
+
...options.config,
|
|
1974
|
+
appName: name,
|
|
1975
|
+
packageName: finalPackageName
|
|
1976
|
+
};
|
|
1977
|
+
if (config.enableBackgroundLocation)
|
|
1978
|
+
config.enableGeolocation = true;
|
|
1979
|
+
if (config.enableHealthConnect) {
|
|
1980
|
+
config.minSdk = Math.max(config.minSdk ?? 26, 26);
|
|
1981
|
+
config.compileSdk = Math.max(config.compileSdk ?? 36, 36);
|
|
1982
|
+
}
|
|
1983
|
+
normalizeAndroidNetworkConfig(config);
|
|
1984
|
+
validateAndroidConfig(config);
|
|
1985
|
+
if (config.enablePushNotifications && !config.googleServicesFile) {
|
|
1986
|
+
throw new Error("Android push notifications require a googleServicesFile");
|
|
1987
|
+
}
|
|
1988
|
+
if (config.googleServicesFile)
|
|
1989
|
+
validateGoogleServicesFile(config.googleServicesFile, finalPackageName);
|
|
1990
|
+
if (config.appIconPath)
|
|
1991
|
+
requireRegularFile(config.appIconPath, "App icon");
|
|
1992
|
+
const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined;
|
|
1993
|
+
if (config.appIconPath && ![".gif", ".jpg", ".png", ".webp"].includes(appIconExtension ?? "")) {
|
|
1994
|
+
throw new Error(`Unsupported Android app icon format: ${appIconExtension || "(none)"}`);
|
|
1995
|
+
}
|
|
1797
1996
|
const dirs = [
|
|
1798
1997
|
output,
|
|
1799
1998
|
join3(output, "app/src/main/java", packagePath),
|
|
@@ -1803,32 +2002,24 @@ async function init2(options) {
|
|
|
1803
2002
|
join3(output, "app/src/main/assets"),
|
|
1804
2003
|
join3(output, "gradle/wrapper")
|
|
1805
2004
|
];
|
|
2005
|
+
if (existsSync5(output) && !statSync2(output).isDirectory()) {
|
|
2006
|
+
throw new Error(`Android project output must be a directory: ${output}`);
|
|
2007
|
+
}
|
|
1806
2008
|
for (const dir of dirs) {
|
|
1807
2009
|
if (!existsSync5(dir)) {
|
|
1808
2010
|
mkdirSync3(dir, { recursive: true });
|
|
1809
2011
|
}
|
|
1810
2012
|
}
|
|
1811
|
-
|
|
1812
|
-
...DEFAULT_CONFIG2,
|
|
1813
|
-
appName: name,
|
|
1814
|
-
packageName: finalPackageName,
|
|
1815
|
-
...options.config
|
|
1816
|
-
};
|
|
1817
|
-
if (config.enableBackgroundLocation)
|
|
1818
|
-
config.enableGeolocation = true;
|
|
1819
|
-
writeFileSync3(join3(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1820
|
-
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
2013
|
+
writeAndroidConfig(output, config);
|
|
1821
2014
|
const hasGoogleServices = Boolean(config.googleServicesFile);
|
|
1822
2015
|
if (config.googleServicesFile) {
|
|
1823
|
-
if (!existsSync5(config.googleServicesFile))
|
|
1824
|
-
throw new Error(`Google services file not found: ${config.googleServicesFile}`);
|
|
1825
2016
|
cpSync3(config.googleServicesFile, join3(output, "app/google-services.json"));
|
|
1826
2017
|
}
|
|
1827
|
-
const mainActivityTemplate =
|
|
2018
|
+
const mainActivityTemplate = readFileSync4(join3(TEMPLATES_DIR2, "MainActivity.kt.template"), "utf-8");
|
|
1828
2019
|
const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
1829
|
-
|
|
1830
|
-
const craftBridgeTemplate =
|
|
1831
|
-
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{ENABLE_SPEECH\}\}/g, String(Boolean(config.enableSpeechRecognition))).replace(/\{\{ENABLE_HAPTICS\}\}/g, String(Boolean(config.enableHaptics))).replace(/\{\{ENABLE_SHARE\}\}/g, String(Boolean(config.enableShare))).replace(/\{\{ENABLE_CAMERA\}\}/g, String(Boolean(config.enableCamera))).replace(/\{\{ENABLE_BIOMETRIC\}\}/g, String(Boolean(config.enableBiometric))).replace(/\{\{ENABLE_PUSH\}\}/g, String(Boolean(config.enablePushNotifications))).replace(/\{\{ENABLE_SECURE_STORAGE\}\}/g, String(Boolean(config.enableSecureStorage))).replace(/\{\{ENABLE_GEOLOCATION\}\}/g, String(Boolean(config.enableGeolocation))).replace(/\{\{ENABLE_BACKGROUND_LOCATION\}\}/g, String(Boolean(config.enableBackgroundLocation))).replace(/\{\{ENABLE_KEEP_AWAKE\}\}/g, String(Boolean(config.enableKeepAwake))).replace(/\{\{ENABLE_DEEP_LINKS\}\}/g, String(Boolean(config.enableDeepLinks))).replace(/\{\{ENABLE_HEALTH_CONNECT\}\}/g, String(Boolean(config.enableHealthConnect))).replace(/\{\{FIREBASE_IMPORT\}\}/g, config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace(/\{\{REGISTER_PUSH_IMPLEMENTATION\}\}/g, config.enablePushNotifications ? `activity.runOnUiThread {
|
|
2020
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
2021
|
+
const craftBridgeTemplate = readFileSync4(join3(TEMPLATES_DIR2, "CraftBridge.kt.template"), "utf-8");
|
|
2022
|
+
const craftBridge = craftBridgeTemplate.replace(/\{\{PROMISE_RUNTIME\}\}/g, () => renderAndroidPromiseRuntime(" ")).replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{ENABLE_SPEECH\}\}/g, String(Boolean(config.enableSpeechRecognition))).replace(/\{\{ENABLE_HAPTICS\}\}/g, String(Boolean(config.enableHaptics))).replace(/\{\{ENABLE_SHARE\}\}/g, String(Boolean(config.enableShare))).replace(/\{\{ENABLE_CAMERA\}\}/g, String(Boolean(config.enableCamera))).replace(/\{\{ENABLE_BIOMETRIC\}\}/g, String(Boolean(config.enableBiometric))).replace(/\{\{ENABLE_PUSH\}\}/g, String(Boolean(config.enablePushNotifications))).replace(/\{\{ENABLE_SECURE_STORAGE\}\}/g, String(Boolean(config.enableSecureStorage))).replace(/\{\{ENABLE_GEOLOCATION\}\}/g, String(Boolean(config.enableGeolocation))).replace(/\{\{ENABLE_BACKGROUND_LOCATION\}\}/g, String(Boolean(config.enableBackgroundLocation))).replace(/\{\{ENABLE_KEEP_AWAKE\}\}/g, String(Boolean(config.enableKeepAwake))).replace(/\{\{ENABLE_DEEP_LINKS\}\}/g, String(Boolean(config.enableDeepLinks))).replace(/\{\{ENABLE_HEALTH_CONNECT\}\}/g, String(Boolean(config.enableHealthConnect))).replace(/\{\{FIREBASE_IMPORT\}\}/g, config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace(/\{\{REGISTER_PUSH_IMPLEMENTATION\}\}/g, config.enablePushNotifications ? `activity.runOnUiThread {
|
|
1832
2023
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
1833
2024
|
&& ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
|
1834
2025
|
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)
|
|
@@ -1838,27 +2029,26 @@ async function init2(options) {
|
|
|
1838
2029
|
val token = if (task.isSuccessful) task.result else null
|
|
1839
2030
|
val callback = if (token.isNullOrBlank()) "window._craftPushReject" else "window._craftPushResolve"
|
|
1840
2031
|
val payload = JSONObject.quote(token ?: task.exception?.message ?: "Firebase Cloud Messaging is not configured")
|
|
1841
|
-
|
|
2032
|
+
evaluatePromiseJavascript("$callback && $callback($payload)")
|
|
1842
2033
|
}
|
|
1843
2034
|
} catch (error: Exception) {
|
|
1844
2035
|
val payload = JSONObject.quote(error.message ?: "Firebase Cloud Messaging is not configured")
|
|
1845
|
-
|
|
2036
|
+
evaluatePromiseJavascript("window._craftPushReject && window._craftPushReject($payload)")
|
|
1846
2037
|
}
|
|
1847
|
-
}` : `
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
const
|
|
1861
|
-
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name=".LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
|
|
2038
|
+
}` : `evaluatePromiseJavascript(
|
|
2039
|
+
"window._craftPushReject && window._craftPushReject('Push notifications are disabled')"
|
|
2040
|
+
)`);
|
|
2041
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
|
|
2042
|
+
const craftNative = readFileSync4(join3(TEMPLATES_DIR2, "CraftNative.kt.template"), "utf-8");
|
|
2043
|
+
const nativeDir = join3(output, "app/src/main/java/com/craft/runtime");
|
|
2044
|
+
mkdirSync3(nativeDir, { recursive: true });
|
|
2045
|
+
writeFileSync4(join3(nativeDir, "CraftNative.kt"), craftNative);
|
|
2046
|
+
const serviceTemplate = readFileSync4(join3(TEMPLATES_DIR2, "LocationRecordingService.kt.template"), "utf-8");
|
|
2047
|
+
writeFileSync4(join3(nativeDir, "LocationRecordingService.kt"), serviceTemplate);
|
|
2048
|
+
const healthTemplate = readFileSync4(join3(TEMPLATES_DIR2, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
|
|
2049
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
2050
|
+
const manifestTemplate = readFileSync4(join3(TEMPLATES_DIR2, "AndroidManifest.xml.template"), "utf-8");
|
|
2051
|
+
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name="com.craft.runtime.LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
|
|
1862
2052
|
<package android:name="com.google.android.apps.healthdata" />
|
|
1863
2053
|
</queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
|
|
1864
2054
|
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
@@ -1867,25 +2057,25 @@ async function init2(options) {
|
|
|
1867
2057
|
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
1868
2058
|
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
1869
2059
|
</intent-filter>` : "");
|
|
1870
|
-
|
|
1871
|
-
const projectGradleTemplate =
|
|
1872
|
-
|
|
1873
|
-
const appGradleTemplate =
|
|
1874
|
-
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{COMPILE_SDK\}\}/g, String(
|
|
2060
|
+
writeFileSync4(join3(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
2061
|
+
const projectGradleTemplate = readFileSync4(join3(TEMPLATES_DIR2, "build.gradle.kts.project.template"), "utf-8");
|
|
2062
|
+
writeFileSync4(join3(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
|
|
2063
|
+
const appGradleTemplate = readFileSync4(join3(TEMPLATES_DIR2, "build.gradle.kts.app.template"), "utf-8");
|
|
2064
|
+
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, escapeKotlinString(config.version || "1.0.0")).replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{COMPILE_SDK\}\}/g, String(config.compileSdk || 36)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
|
|
1875
2065
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "");
|
|
1876
|
-
|
|
1877
|
-
const proguardTemplate =
|
|
1878
|
-
|
|
1879
|
-
const settingsTemplate =
|
|
1880
|
-
const settings2 = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
|
|
1881
|
-
|
|
2066
|
+
writeFileSync4(join3(output, "app/build.gradle.kts"), appGradle);
|
|
2067
|
+
const proguardTemplate = readFileSync4(join3(TEMPLATES_DIR2, "proguard-rules.pro.template"), "utf-8");
|
|
2068
|
+
writeFileSync4(join3(output, "app/proguard-rules.pro"), proguardTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
2069
|
+
const settingsTemplate = readFileSync4(join3(TEMPLATES_DIR2, "settings.gradle.kts.template"), "utf-8");
|
|
2070
|
+
const settings2 = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, escapeKotlinString(generatedGradleProjectName(name)));
|
|
2071
|
+
writeFileSync4(join3(output, "settings.gradle.kts"), settings2);
|
|
1882
2072
|
const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
1883
2073
|
android.useAndroidX=true
|
|
1884
2074
|
kotlin.code.style=official
|
|
1885
2075
|
android.nonTransitiveRClass=true
|
|
1886
2076
|
`;
|
|
1887
|
-
|
|
1888
|
-
|
|
2077
|
+
writeFileSync4(join3(output, "gradle.properties"), gradleProperties);
|
|
2078
|
+
writeFileSync4(join3(output, "local.properties"), `# SDK location will be set by Android Studio
|
|
1889
2079
|
`);
|
|
1890
2080
|
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
1891
2081
|
distributionPath=wrapper/dists
|
|
@@ -1895,13 +2085,13 @@ validateDistributionUrl=true
|
|
|
1895
2085
|
zipStoreBase=GRADLE_USER_HOME
|
|
1896
2086
|
zipStorePath=wrapper/dists
|
|
1897
2087
|
`;
|
|
1898
|
-
|
|
2088
|
+
writeFileSync4(join3(output, "gradle/wrapper/gradle-wrapper.properties"), gradleWrapperProps);
|
|
1899
2089
|
const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1900
2090
|
<resources>
|
|
1901
|
-
<string name="app_name">${name}</string>
|
|
2091
|
+
<string name="app_name">${escapeXml(name)}</string>
|
|
1902
2092
|
</resources>
|
|
1903
2093
|
`;
|
|
1904
|
-
|
|
2094
|
+
writeFileSync4(join3(output, "app/src/main/res/values/strings.xml"), stringsXml);
|
|
1905
2095
|
const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1906
2096
|
<resources>
|
|
1907
2097
|
<color name="primary">#1a1a2e</color>
|
|
@@ -1910,7 +2100,7 @@ zipStorePath=wrapper/dists
|
|
|
1910
2100
|
<color name="background">${config.backgroundColor}</color>
|
|
1911
2101
|
</resources>
|
|
1912
2102
|
`;
|
|
1913
|
-
|
|
2103
|
+
writeFileSync4(join3(output, "app/src/main/res/values/colors.xml"), colorsXml);
|
|
1914
2104
|
const appIconXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1915
2105
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1916
2106
|
android:width="108dp"
|
|
@@ -1922,11 +2112,9 @@ zipStorePath=wrapper/dists
|
|
|
1922
2112
|
</vector>
|
|
1923
2113
|
`;
|
|
1924
2114
|
if (config.appIconPath) {
|
|
1925
|
-
|
|
1926
|
-
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
1927
|
-
cpSync3(config.appIconPath, join3(output, "app/src/main/res/drawable/craft_app_icon.png"));
|
|
2115
|
+
cpSync3(config.appIconPath, join3(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`));
|
|
1928
2116
|
} else {
|
|
1929
|
-
|
|
2117
|
+
writeFileSync4(join3(output, "app/src/main/res/drawable/craft_app_icon.xml"), appIconXml);
|
|
1930
2118
|
}
|
|
1931
2119
|
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1932
2120
|
<resources>
|
|
@@ -1937,7 +2125,7 @@ zipStorePath=wrapper/dists
|
|
|
1937
2125
|
</style>
|
|
1938
2126
|
</resources>
|
|
1939
2127
|
`;
|
|
1940
|
-
|
|
2128
|
+
writeFileSync4(join3(output, "app/src/main/res/values/themes.xml"), themesXml);
|
|
1941
2129
|
const activityMainXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1942
2130
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
1943
2131
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
@@ -1952,13 +2140,13 @@ zipStorePath=wrapper/dists
|
|
|
1952
2140
|
|
|
1953
2141
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
1954
2142
|
`;
|
|
1955
|
-
|
|
2143
|
+
writeFileSync4(join3(output, "app/src/main/res/layout/activity_main.xml"), activityMainXml);
|
|
1956
2144
|
const placeholderHtml = `<!DOCTYPE html>
|
|
1957
2145
|
<html>
|
|
1958
2146
|
<head>
|
|
1959
2147
|
<meta charset="UTF-8">
|
|
1960
2148
|
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
1961
|
-
<title>${name}</title>
|
|
2149
|
+
<title>${escapeXml(name)}</title>
|
|
1962
2150
|
<style>
|
|
1963
2151
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1964
2152
|
body {
|
|
@@ -1978,7 +2166,7 @@ zipStorePath=wrapper/dists
|
|
|
1978
2166
|
</head>
|
|
1979
2167
|
<body>
|
|
1980
2168
|
<div class="container">
|
|
1981
|
-
<h1>\u26A1 ${name}</h1>
|
|
2169
|
+
<h1>\u26A1 ${escapeXml(name)}</h1>
|
|
1982
2170
|
<p>Built with Craft Android</p>
|
|
1983
2171
|
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
1984
2172
|
</div>
|
|
@@ -1990,7 +2178,12 @@ zipStorePath=wrapper/dists
|
|
|
1990
2178
|
</script>
|
|
1991
2179
|
</body>
|
|
1992
2180
|
</html>`;
|
|
1993
|
-
|
|
2181
|
+
writeFileSync4(join3(output, "app/src/main/assets/index.html"), placeholderHtml);
|
|
2182
|
+
const runtimeDir = resolveRuntimeDir2(options.runtimeDir);
|
|
2183
|
+
if (runtimeDir) {
|
|
2184
|
+
installRuntime2(output, runtimeDir);
|
|
2185
|
+
console.log(" Installed the Zig runtime from", runtimeDir);
|
|
2186
|
+
}
|
|
1994
2187
|
console.log("\u2705 Project initialized");
|
|
1995
2188
|
console.log("");
|
|
1996
2189
|
console.log("Next steps:");
|
|
@@ -2008,19 +2201,30 @@ async function build2(options) {
|
|
|
2008
2201
|
if (!existsSync5(configPath)) {
|
|
2009
2202
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`);
|
|
2010
2203
|
}
|
|
2011
|
-
const config = JSON.parse(
|
|
2204
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
2205
|
+
if (existsSync5(join3(output, "app/src/main/jniLibs"))) {
|
|
2206
|
+
const runtimeDir = resolveRuntimeDir2(options.runtimeDir);
|
|
2207
|
+
if (runtimeDir) {
|
|
2208
|
+
installRuntime2(output, runtimeDir);
|
|
2209
|
+
console.log(" Refreshed the Zig runtime from", runtimeDir);
|
|
2210
|
+
} else {
|
|
2211
|
+
console.log(" Keeping the Zig runtime installed at init (no runtime directory configured)");
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2012
2214
|
if (devServer) {
|
|
2013
|
-
|
|
2014
|
-
config.
|
|
2015
|
-
|
|
2016
|
-
|
|
2215
|
+
const url = androidWebUrl(devServer, "Android dev server URL");
|
|
2216
|
+
config.devServerURL = url.toString();
|
|
2217
|
+
config.trustedOrigins = [...new Set([
|
|
2218
|
+
...(config.trustedOrigins ?? []).map((value) => androidWebUrl(value, "Android trusted origin").origin),
|
|
2219
|
+
url.origin
|
|
2220
|
+
])];
|
|
2221
|
+
writeAndroidConfig(output, config);
|
|
2017
2222
|
console.log(` Dev server: ${devServer}`);
|
|
2018
2223
|
}
|
|
2019
2224
|
if (htmlPath) {
|
|
2020
2225
|
syncAndroidWebAssets(htmlPath, output);
|
|
2021
2226
|
config.hasBundledFallback = Boolean(devServer);
|
|
2022
|
-
|
|
2023
|
-
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
2227
|
+
writeAndroidConfig(output, config);
|
|
2024
2228
|
console.log(` Synced: ${htmlPath} \u2192 assets/`);
|
|
2025
2229
|
}
|
|
2026
2230
|
if (!compile)
|
|
@@ -2075,7 +2279,7 @@ async function run2(options) {
|
|
|
2075
2279
|
} else {
|
|
2076
2280
|
await $2`adb install -r ${apkPath}`;
|
|
2077
2281
|
}
|
|
2078
|
-
const config = JSON.parse(
|
|
2282
|
+
const config = JSON.parse(readFileSync4(join3(output, "craft.config.json"), "utf-8"));
|
|
2079
2283
|
const launchCmd = `${config.packageName}/.MainActivity`;
|
|
2080
2284
|
if (device) {
|
|
2081
2285
|
await $2`adb -s ${device} shell am start -n ${launchCmd}`;
|
|
@@ -2090,10 +2294,106 @@ async function run2(options) {
|
|
|
2090
2294
|
throw error;
|
|
2091
2295
|
}
|
|
2092
2296
|
}
|
|
2093
|
-
var $2,
|
|
2297
|
+
var $2, ANDROID_PROMISE_RUNTIME = `
|
|
2298
|
+
if (window.__craftRejectPendingPromises) {
|
|
2299
|
+
window.__craftRejectPendingPromises('Android bridge reinitialized');
|
|
2300
|
+
}
|
|
2301
|
+
if (window.__craftRejectPermissionRequests) {
|
|
2302
|
+
window.__craftRejectPermissionRequests('Android bridge reinitialized');
|
|
2303
|
+
}
|
|
2304
|
+
window.__craftPromiseRuntimeClosed = false;
|
|
2305
|
+
window.__craftPendingPromises = Object.create(null);
|
|
2306
|
+
window.__craftPromise = function(channel, resolveName, rejectName, invoke, timeoutMs, timeoutError) {
|
|
2307
|
+
if (window.__craftPromiseRuntimeClosed) {
|
|
2308
|
+
return Promise.reject(new Error('Android bridge is closed'));
|
|
2309
|
+
}
|
|
2310
|
+
if (window.__craftPendingPromises[channel]) {
|
|
2311
|
+
return Promise.reject(new Error('A '.concat(channel, ' request is already in progress')));
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
return new Promise(function(resolve, reject) {
|
|
2315
|
+
var entry = {settled: false, timer: null, settle: null};
|
|
2316
|
+
var resolveCallback;
|
|
2317
|
+
var rejectCallback;
|
|
2318
|
+
|
|
2319
|
+
entry.settle = function(succeeded, value) {
|
|
2320
|
+
if (entry.settled || window.__craftPendingPromises[channel] !== entry) return;
|
|
2321
|
+
entry.settled = true;
|
|
2322
|
+
if (entry.timer !== null) clearTimeout(entry.timer);
|
|
2323
|
+
if (window[resolveName] === resolveCallback) window[resolveName] = null;
|
|
2324
|
+
if (window[rejectName] === rejectCallback) window[rejectName] = null;
|
|
2325
|
+
delete window.__craftPendingPromises[channel];
|
|
2326
|
+
if (succeeded) resolve(value);
|
|
2327
|
+
else reject(value);
|
|
2328
|
+
};
|
|
2329
|
+
|
|
2330
|
+
resolveCallback = function(value) {
|
|
2331
|
+
entry.settle(true, value);
|
|
2332
|
+
};
|
|
2333
|
+
rejectCallback = function(error) {
|
|
2334
|
+
entry.settle(false, error);
|
|
2335
|
+
};
|
|
2336
|
+
window[resolveName] = resolveCallback;
|
|
2337
|
+
window[rejectName] = rejectCallback;
|
|
2338
|
+
window.__craftPendingPromises[channel] = entry;
|
|
2339
|
+
|
|
2340
|
+
if (timeoutMs > 0) {
|
|
2341
|
+
entry.timer = setTimeout(function() {
|
|
2342
|
+
entry.settle(false, timeoutError || new Error(channel.concat(' request timed out')));
|
|
2343
|
+
}, timeoutMs);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
try {
|
|
2347
|
+
invoke();
|
|
2348
|
+
}
|
|
2349
|
+
catch (error) {
|
|
2350
|
+
entry.settle(false, error);
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
window.__craftRejectPendingPromises = function(message) {
|
|
2356
|
+
window.__craftPromiseRuntimeClosed = true;
|
|
2357
|
+
Object.keys(window.__craftPendingPromises).forEach(function(channel) {
|
|
2358
|
+
var entry = window.__craftPendingPromises[channel];
|
|
2359
|
+
if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));
|
|
2360
|
+
});
|
|
2361
|
+
};
|
|
2362
|
+
`, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, DEFAULT_CONFIG2, RUNTIME_ABIS;
|
|
2094
2363
|
var init_src2 = __esm(() => {
|
|
2095
2364
|
({ $: $2 } = globalThis.Bun);
|
|
2096
2365
|
TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
|
|
2366
|
+
LOCAL_DEVELOPMENT_HOSTS = new Set(["localhost", "127.0.0.1", "10.0.2.2"]);
|
|
2367
|
+
KOTLIN_KEYWORDS = new Set([
|
|
2368
|
+
"as",
|
|
2369
|
+
"break",
|
|
2370
|
+
"class",
|
|
2371
|
+
"continue",
|
|
2372
|
+
"do",
|
|
2373
|
+
"else",
|
|
2374
|
+
"false",
|
|
2375
|
+
"for",
|
|
2376
|
+
"fun",
|
|
2377
|
+
"if",
|
|
2378
|
+
"in",
|
|
2379
|
+
"interface",
|
|
2380
|
+
"is",
|
|
2381
|
+
"null",
|
|
2382
|
+
"object",
|
|
2383
|
+
"package",
|
|
2384
|
+
"return",
|
|
2385
|
+
"super",
|
|
2386
|
+
"this",
|
|
2387
|
+
"throw",
|
|
2388
|
+
"true",
|
|
2389
|
+
"try",
|
|
2390
|
+
"typealias",
|
|
2391
|
+
"typeof",
|
|
2392
|
+
"val",
|
|
2393
|
+
"var",
|
|
2394
|
+
"when",
|
|
2395
|
+
"while"
|
|
2396
|
+
]);
|
|
2097
2397
|
DEFAULT_CONFIG2 = {
|
|
2098
2398
|
version: "1.0.0",
|
|
2099
2399
|
versionCode: 1,
|
|
@@ -2117,6 +2417,7 @@ var init_src2 = __esm(() => {
|
|
|
2117
2417
|
compileSdk: 36,
|
|
2118
2418
|
targetSdk: 35
|
|
2119
2419
|
};
|
|
2420
|
+
RUNTIME_ABIS = ["arm64-v8a", "x86_64"];
|
|
2120
2421
|
});
|
|
2121
2422
|
|
|
2122
2423
|
// ../../node_modules/@stacksjs/clapp/dist/index.js
|
|
@@ -4262,8 +4563,28 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
4262
4563
|
].join(`
|
|
4263
4564
|
`);
|
|
4264
4565
|
}
|
|
4566
|
+
|
|
4567
|
+
// src/scaffold-version.ts
|
|
4568
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
4569
|
+
function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
4570
|
+
const manifest = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
4571
|
+
const sections = [manifest.dependencies, manifest.devDependencies];
|
|
4572
|
+
let found = false;
|
|
4573
|
+
for (const dependencies of sections) {
|
|
4574
|
+
if (dependencies && Object.hasOwn(dependencies, "craft-native")) {
|
|
4575
|
+
dependencies["craft-native"] = `^${craftVersion}`;
|
|
4576
|
+
found = true;
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
if (!found) {
|
|
4580
|
+
manifest.dependencies ??= {};
|
|
4581
|
+
manifest.dependencies["craft-native"] = `^${craftVersion}`;
|
|
4582
|
+
}
|
|
4583
|
+
writeFileSync(packagePath, `${JSON.stringify(manifest, null, 2)}
|
|
4584
|
+
`);
|
|
4585
|
+
}
|
|
4265
4586
|
// package.json
|
|
4266
|
-
var version = "0.0.
|
|
4587
|
+
var version = "0.0.92";
|
|
4267
4588
|
|
|
4268
4589
|
// bin/cli.ts
|
|
4269
4590
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
|
@@ -4757,7 +5078,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4757
5078
|
const template = options?.template || "blank";
|
|
4758
5079
|
const bundleId = options?.bundleId || `com.example.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
4759
5080
|
const appNameSlug = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
4760
|
-
const { mkdirSync: mkdirSync4, writeFileSync:
|
|
5081
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync5, cpSync: cpSync4 } = await import("fs");
|
|
4761
5082
|
const { join: join4, dirname: dirname3 } = await import("path");
|
|
4762
5083
|
const replaceVars = (content) => {
|
|
4763
5084
|
return content.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{APP_NAME_SLUG\}\}/g, appNameSlug).replace(/\{\{BUNDLE_ID\}\}/g, bundleId).replace(/\{\{AUTHOR\}\}/g, "Developer");
|
|
@@ -4779,9 +5100,9 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4779
5100
|
if (entry.isDirectory()) {
|
|
4780
5101
|
copyRecursive(srcPath, destPath);
|
|
4781
5102
|
} else {
|
|
4782
|
-
const content =
|
|
5103
|
+
const content = readFileSync5(srcPath, "utf-8");
|
|
4783
5104
|
const processedContent = replaceVars(content);
|
|
4784
|
-
|
|
5105
|
+
writeFileSync5(destPath, processedContent);
|
|
4785
5106
|
}
|
|
4786
5107
|
}
|
|
4787
5108
|
};
|
|
@@ -4796,7 +5117,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4796
5117
|
const copied = await copyTemplate(template, name);
|
|
4797
5118
|
if (!copied) {
|
|
4798
5119
|
mkdirSync4(join4(name, "src"), { recursive: true });
|
|
4799
|
-
|
|
5120
|
+
writeFileSync5(join4(name, "index.html"), replaceVars(`<!DOCTYPE html>
|
|
4800
5121
|
<html lang="en">
|
|
4801
5122
|
<head>
|
|
4802
5123
|
<meta charset="UTF-8">
|
|
@@ -4810,7 +5131,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4810
5131
|
</div>
|
|
4811
5132
|
</body>
|
|
4812
5133
|
</html>`));
|
|
4813
|
-
|
|
5134
|
+
writeFileSync5(join4(name, "package.json"), replaceVars(JSON.stringify({
|
|
4814
5135
|
name: "{{APP_NAME_SLUG}}",
|
|
4815
5136
|
version: "1.0.0",
|
|
4816
5137
|
private: true,
|
|
@@ -4820,10 +5141,11 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4820
5141
|
build: "craft build"
|
|
4821
5142
|
},
|
|
4822
5143
|
dependencies: {
|
|
4823
|
-
"craft-native":
|
|
5144
|
+
"craft-native": `^${version}`
|
|
4824
5145
|
}
|
|
4825
5146
|
}, null, 2)));
|
|
4826
5147
|
}
|
|
5148
|
+
pinCraftNativeDependency(join4(name, "package.json"), version);
|
|
4827
5149
|
console.log(`\u2705 ${template} project created`);
|
|
4828
5150
|
}
|
|
4829
5151
|
if (template === "desktop" || template === "all") {
|
|
@@ -4843,7 +5165,7 @@ export default {
|
|
|
4843
5165
|
},
|
|
4844
5166
|
} satisfies CraftConfig
|
|
4845
5167
|
`;
|
|
4846
|
-
|
|
5168
|
+
writeFileSync5(`${name}/craft.config.ts`, configContent);
|
|
4847
5169
|
const htmlContent = `<!DOCTYPE html>
|
|
4848
5170
|
<html lang="en">
|
|
4849
5171
|
<head>
|
|
@@ -4874,7 +5196,7 @@ export default {
|
|
|
4874
5196
|
</body>
|
|
4875
5197
|
</html>
|
|
4876
5198
|
`;
|
|
4877
|
-
|
|
5199
|
+
writeFileSync5(`${name}/index.html`, htmlContent);
|
|
4878
5200
|
const packageJson = {
|
|
4879
5201
|
name: appNameSlug,
|
|
4880
5202
|
version: "0.1.0",
|
|
@@ -4887,10 +5209,11 @@ export default {
|
|
|
4887
5209
|
"ios:open": "craft ios open"
|
|
4888
5210
|
},
|
|
4889
5211
|
devDependencies: {
|
|
4890
|
-
"craft-native":
|
|
5212
|
+
"craft-native": `^${version}`
|
|
4891
5213
|
}
|
|
4892
5214
|
};
|
|
4893
|
-
|
|
5215
|
+
writeFileSync5(`${name}/package.json`, JSON.stringify(packageJson, null, 2));
|
|
5216
|
+
pinCraftNativeDependency(`${name}/package.json`, version);
|
|
4894
5217
|
console.log("\u2705 Desktop project created");
|
|
4895
5218
|
}
|
|
4896
5219
|
if (template === "ios" || template === "all") {
|