craft-native 0.0.89 → 0.0.91
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.js +285 -44
- package/dist/android/src/promise-runtime.d.ts +3 -0
- package/dist/android/templates/CraftBridge.kt.template +1951 -1193
- 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 +2231 -0
- package/dist/android/templates/LocationRecordingService.kt.template +34 -9
- package/dist/android/templates/MainActivity.kt.template +25 -2
- 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 +13 -5
- package/dist/api/window.d.ts +2 -0
- package/dist/cli.js +404 -128
- package/dist/index.cjs +65 -17
- package/dist/index.js +65 -17
- package/dist/ios/src/index.js +22 -4
- package/dist/ios/templates/CraftApp.swift +473 -60
- 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 +36 -13
- 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) {
|
|
@@ -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();
|
|
@@ -1718,25 +1736,149 @@ __export(exports_src2, {
|
|
|
1718
1736
|
init: () => init2,
|
|
1719
1737
|
build: () => build2
|
|
1720
1738
|
});
|
|
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";
|
|
1739
|
+
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1740
|
+
import { dirname as dirname2, extname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
1741
|
+
function renderAndroidPromiseRuntime(indent = "") {
|
|
1742
|
+
return ANDROID_PROMISE_RUNTIME.trim().split(`
|
|
1743
|
+
`).map((line) => `${indent}${line}`).join(`
|
|
1744
|
+
`);
|
|
1745
|
+
}
|
|
1746
|
+
function escapeXml(value) {
|
|
1747
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1748
|
+
}
|
|
1749
|
+
function escapeKotlinString(value) {
|
|
1750
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("\r", "\\r").replaceAll(`
|
|
1751
|
+
`, "\\n");
|
|
1752
|
+
}
|
|
1753
|
+
function generatedPackageSegment(name) {
|
|
1754
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
1755
|
+
if (!normalized)
|
|
1756
|
+
return "app";
|
|
1757
|
+
return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`;
|
|
1758
|
+
}
|
|
1759
|
+
function generatedGradleProjectName(name) {
|
|
1760
|
+
const invalidCharacters = ["/", "\\", ":", "<", ">", '"', "?", "*", "|"];
|
|
1761
|
+
const normalized = invalidCharacters.reduce((value, character) => value.replaceAll(character, "-"), name).trim();
|
|
1762
|
+
return normalized || "craft-app";
|
|
1763
|
+
}
|
|
1764
|
+
function requireRegularFile(path2, label) {
|
|
1765
|
+
if (!existsSync5(path2))
|
|
1766
|
+
throw new Error(`${label} not found: ${path2}`);
|
|
1767
|
+
if (!statSync2(path2).isFile())
|
|
1768
|
+
throw new Error(`${label} must be a file: ${path2}`);
|
|
1769
|
+
}
|
|
1770
|
+
function containsPath(parent, candidate) {
|
|
1771
|
+
const relativePath = relative(parent, candidate);
|
|
1772
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
1773
|
+
}
|
|
1774
|
+
function validateGoogleServicesFile(path2, packageName) {
|
|
1775
|
+
requireRegularFile(path2, "Google services file");
|
|
1776
|
+
let document;
|
|
1777
|
+
try {
|
|
1778
|
+
document = JSON.parse(readFileSync4(path2, "utf8"));
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
throw new Error(`Google services file must contain valid JSON: ${path2}`, { cause: error });
|
|
1781
|
+
}
|
|
1782
|
+
const matchingClient = document.client?.some((client) => {
|
|
1783
|
+
return client.client_info?.android_client_info?.package_name === packageName;
|
|
1784
|
+
});
|
|
1785
|
+
if (!matchingClient) {
|
|
1786
|
+
throw new Error(`Google services file has no client for Android package ${packageName}: ${path2}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
function androidWebUrl(value, field) {
|
|
1790
|
+
let url;
|
|
1791
|
+
try {
|
|
1792
|
+
url = new URL(value);
|
|
1793
|
+
} catch {
|
|
1794
|
+
throw new Error(`Invalid ${field}: ${value}`);
|
|
1795
|
+
}
|
|
1796
|
+
const localDevelopment = url.protocol === "http:" && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname);
|
|
1797
|
+
if (url.protocol !== "https:" && !localDevelopment || url.username || url.password) {
|
|
1798
|
+
throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`);
|
|
1799
|
+
}
|
|
1800
|
+
return url;
|
|
1801
|
+
}
|
|
1802
|
+
function normalizeAndroidNetworkConfig(config) {
|
|
1803
|
+
const schemes = config.urlSchemes?.map((value) => value.trim().toLowerCase()) ?? [];
|
|
1804
|
+
if (schemes.some((value) => !/^[a-z][a-z0-9+.-]*$/.test(value))) {
|
|
1805
|
+
throw new Error("Android deep-link schemes must be valid URI schemes");
|
|
1806
|
+
}
|
|
1807
|
+
config.urlSchemes = [...new Set(schemes)];
|
|
1808
|
+
if (config.enableDeepLinks && config.urlSchemes.length === 0) {
|
|
1809
|
+
throw new Error("Android deep links require at least one URL scheme");
|
|
1810
|
+
}
|
|
1811
|
+
const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {
|
|
1812
|
+
return androidWebUrl(value, "Android trusted origin").origin;
|
|
1813
|
+
});
|
|
1814
|
+
if (config.devServerURL) {
|
|
1815
|
+
const devServer = androidWebUrl(config.devServerURL, "Android dev server URL");
|
|
1816
|
+
config.devServerURL = devServer.toString();
|
|
1817
|
+
trustedOrigins.push(devServer.origin);
|
|
1818
|
+
}
|
|
1819
|
+
config.trustedOrigins = [...new Set(trustedOrigins)];
|
|
1820
|
+
}
|
|
1821
|
+
function validateAndroidConfig(config) {
|
|
1822
|
+
if (!config.appName.trim())
|
|
1823
|
+
throw new Error("Android app name must not be empty");
|
|
1824
|
+
const packageSegments = config.packageName.split(".");
|
|
1825
|
+
if (packageSegments.length < 2 || packageSegments.some((segment) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {
|
|
1826
|
+
throw new Error(`Invalid Android package name: ${config.packageName}`);
|
|
1827
|
+
}
|
|
1828
|
+
if (!/^#(?:[\dA-F]{3,4}|[\dA-F]{6}|[\dA-F]{8})$/i.test(config.backgroundColor ?? "")) {
|
|
1829
|
+
throw new Error(`Invalid Android background color: ${config.backgroundColor}`);
|
|
1830
|
+
}
|
|
1831
|
+
for (const [name, value] of [
|
|
1832
|
+
["versionCode", config.versionCode],
|
|
1833
|
+
["minSdk", config.minSdk],
|
|
1834
|
+
["compileSdk", config.compileSdk],
|
|
1835
|
+
["targetSdk", config.targetSdk]
|
|
1836
|
+
]) {
|
|
1837
|
+
if (!Number.isInteger(value) || Number(value) < 1) {
|
|
1838
|
+
throw new Error(`Android ${name} must be a positive integer`);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (Number(config.minSdk) > Number(config.targetSdk)) {
|
|
1842
|
+
throw new Error("Android minSdk must not exceed targetSdk");
|
|
1843
|
+
}
|
|
1844
|
+
if (Number(config.targetSdk) > Number(config.compileSdk)) {
|
|
1845
|
+
throw new Error("Android targetSdk must not exceed compileSdk");
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
function writeAndroidConfig(output, config) {
|
|
1849
|
+
writeFileSync4(join3(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1850
|
+
const runtimeConfig = { ...config };
|
|
1851
|
+
delete runtimeConfig.appIconPath;
|
|
1852
|
+
delete runtimeConfig.googleServicesFile;
|
|
1853
|
+
writeFileSync4(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(runtimeConfig, null, 2));
|
|
1854
|
+
}
|
|
1723
1855
|
function syncAndroidWebAssets(source, output) {
|
|
1724
1856
|
const sourcePath = resolve2(source);
|
|
1725
1857
|
if (!existsSync5(sourcePath))
|
|
1726
1858
|
throw new Error(`Web asset path not found: ${source}`);
|
|
1727
|
-
const
|
|
1859
|
+
const sourceStat = statSync2(sourcePath);
|
|
1860
|
+
if (!sourceStat.isDirectory() && !sourceStat.isFile()) {
|
|
1861
|
+
throw new Error(`Web asset path must be a file or directory: ${source}`);
|
|
1862
|
+
}
|
|
1863
|
+
const assetsDir = resolve2(output, "app/src/main/assets");
|
|
1864
|
+
if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {
|
|
1865
|
+
throw new Error(`Web asset source must not overlap generated asset directory: ${source}`);
|
|
1866
|
+
}
|
|
1867
|
+
if (sourceStat.isDirectory()) {
|
|
1868
|
+
requireRegularFile(join3(sourcePath, "index.html"), "Web asset directory entry point");
|
|
1869
|
+
}
|
|
1728
1870
|
const configPath = join3(assetsDir, "craft.config.json");
|
|
1729
|
-
const config = existsSync5(configPath) ?
|
|
1871
|
+
const config = existsSync5(configPath) ? readFileSync4(configPath) : undefined;
|
|
1730
1872
|
rmSync3(assetsDir, { recursive: true, force: true });
|
|
1731
1873
|
mkdirSync3(assetsDir, { recursive: true });
|
|
1732
|
-
if (
|
|
1874
|
+
if (sourceStat.isDirectory())
|
|
1733
1875
|
cpSync3(sourcePath, assetsDir, { recursive: true });
|
|
1734
1876
|
else
|
|
1735
1877
|
cpSync3(sourcePath, join3(assetsDir, "index.html"));
|
|
1736
1878
|
if (!existsSync5(join3(assetsDir, "index.html")))
|
|
1737
1879
|
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
1738
1880
|
if (config)
|
|
1739
|
-
|
|
1881
|
+
writeFileSync4(configPath, config);
|
|
1740
1882
|
}
|
|
1741
1883
|
function renderAndroidPermissions(config) {
|
|
1742
1884
|
const permissions = new Set(["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]);
|
|
@@ -1792,8 +1934,33 @@ async function init2(options) {
|
|
|
1792
1934
|
\u26A1 Initializing Craft Android project: ${name}`);
|
|
1793
1935
|
console.log(` Output: ${output}
|
|
1794
1936
|
`);
|
|
1795
|
-
const finalPackageName = packageName || `com.craft.${name
|
|
1937
|
+
const finalPackageName = packageName || `com.craft.${generatedPackageSegment(name)}`;
|
|
1796
1938
|
const packagePath = finalPackageName.replace(/\./g, "/");
|
|
1939
|
+
const config = {
|
|
1940
|
+
...DEFAULT_CONFIG2,
|
|
1941
|
+
...options.config,
|
|
1942
|
+
appName: name,
|
|
1943
|
+
packageName: finalPackageName
|
|
1944
|
+
};
|
|
1945
|
+
if (config.enableBackgroundLocation)
|
|
1946
|
+
config.enableGeolocation = true;
|
|
1947
|
+
if (config.enableHealthConnect) {
|
|
1948
|
+
config.minSdk = Math.max(config.minSdk ?? 26, 26);
|
|
1949
|
+
config.compileSdk = Math.max(config.compileSdk ?? 36, 36);
|
|
1950
|
+
}
|
|
1951
|
+
normalizeAndroidNetworkConfig(config);
|
|
1952
|
+
validateAndroidConfig(config);
|
|
1953
|
+
if (config.enablePushNotifications && !config.googleServicesFile) {
|
|
1954
|
+
throw new Error("Android push notifications require a googleServicesFile");
|
|
1955
|
+
}
|
|
1956
|
+
if (config.googleServicesFile)
|
|
1957
|
+
validateGoogleServicesFile(config.googleServicesFile, finalPackageName);
|
|
1958
|
+
if (config.appIconPath)
|
|
1959
|
+
requireRegularFile(config.appIconPath, "App icon");
|
|
1960
|
+
const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined;
|
|
1961
|
+
if (config.appIconPath && ![".gif", ".jpg", ".png", ".webp"].includes(appIconExtension ?? "")) {
|
|
1962
|
+
throw new Error(`Unsupported Android app icon format: ${appIconExtension || "(none)"}`);
|
|
1963
|
+
}
|
|
1797
1964
|
const dirs = [
|
|
1798
1965
|
output,
|
|
1799
1966
|
join3(output, "app/src/main/java", packagePath),
|
|
@@ -1803,32 +1970,24 @@ async function init2(options) {
|
|
|
1803
1970
|
join3(output, "app/src/main/assets"),
|
|
1804
1971
|
join3(output, "gradle/wrapper")
|
|
1805
1972
|
];
|
|
1973
|
+
if (existsSync5(output) && !statSync2(output).isDirectory()) {
|
|
1974
|
+
throw new Error(`Android project output must be a directory: ${output}`);
|
|
1975
|
+
}
|
|
1806
1976
|
for (const dir of dirs) {
|
|
1807
1977
|
if (!existsSync5(dir)) {
|
|
1808
1978
|
mkdirSync3(dir, { recursive: true });
|
|
1809
1979
|
}
|
|
1810
1980
|
}
|
|
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));
|
|
1981
|
+
writeAndroidConfig(output, config);
|
|
1821
1982
|
const hasGoogleServices = Boolean(config.googleServicesFile);
|
|
1822
1983
|
if (config.googleServicesFile) {
|
|
1823
|
-
if (!existsSync5(config.googleServicesFile))
|
|
1824
|
-
throw new Error(`Google services file not found: ${config.googleServicesFile}`);
|
|
1825
1984
|
cpSync3(config.googleServicesFile, join3(output, "app/google-services.json"));
|
|
1826
1985
|
}
|
|
1827
|
-
const mainActivityTemplate =
|
|
1986
|
+
const mainActivityTemplate = readFileSync4(join3(TEMPLATES_DIR2, "MainActivity.kt.template"), "utf-8");
|
|
1828
1987
|
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 {
|
|
1988
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
1989
|
+
const craftBridgeTemplate = readFileSync4(join3(TEMPLATES_DIR2, "CraftBridge.kt.template"), "utf-8");
|
|
1990
|
+
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
1991
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
1833
1992
|
&& ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
|
1834
1993
|
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)
|
|
@@ -1838,27 +1997,26 @@ async function init2(options) {
|
|
|
1838
1997
|
val token = if (task.isSuccessful) task.result else null
|
|
1839
1998
|
val callback = if (token.isNullOrBlank()) "window._craftPushReject" else "window._craftPushResolve"
|
|
1840
1999
|
val payload = JSONObject.quote(token ?: task.exception?.message ?: "Firebase Cloud Messaging is not configured")
|
|
1841
|
-
|
|
2000
|
+
evaluatePromiseJavascript("$callback && $callback($payload)")
|
|
1842
2001
|
}
|
|
1843
2002
|
} catch (error: Exception) {
|
|
1844
2003
|
val payload = JSONObject.quote(error.message ?: "Firebase Cloud Messaging is not configured")
|
|
1845
|
-
|
|
2004
|
+
evaluatePromiseJavascript("window._craftPushReject && window._craftPushReject($payload)")
|
|
1846
2005
|
}
|
|
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>
|
|
2006
|
+
}` : `evaluatePromiseJavascript(
|
|
2007
|
+
"window._craftPushReject && window._craftPushReject('Push notifications are disabled')"
|
|
2008
|
+
)`);
|
|
2009
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
|
|
2010
|
+
const craftNative = readFileSync4(join3(TEMPLATES_DIR2, "CraftNative.kt.template"), "utf-8");
|
|
2011
|
+
const nativeDir = join3(output, "app/src/main/java/com/craft/runtime");
|
|
2012
|
+
mkdirSync3(nativeDir, { recursive: true });
|
|
2013
|
+
writeFileSync4(join3(nativeDir, "CraftNative.kt"), craftNative);
|
|
2014
|
+
const serviceTemplate = readFileSync4(join3(TEMPLATES_DIR2, "LocationRecordingService.kt.template"), "utf-8");
|
|
2015
|
+
writeFileSync4(join3(nativeDir, "LocationRecordingService.kt"), serviceTemplate);
|
|
2016
|
+
const healthTemplate = readFileSync4(join3(TEMPLATES_DIR2, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
|
|
2017
|
+
writeFileSync4(join3(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
2018
|
+
const manifestTemplate = readFileSync4(join3(TEMPLATES_DIR2, "AndroidManifest.xml.template"), "utf-8");
|
|
2019
|
+
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
2020
|
<package android:name="com.google.android.apps.healthdata" />
|
|
1863
2021
|
</queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
|
|
1864
2022
|
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
@@ -1867,25 +2025,25 @@ async function init2(options) {
|
|
|
1867
2025
|
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
1868
2026
|
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
1869
2027
|
</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(
|
|
2028
|
+
writeFileSync4(join3(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
2029
|
+
const projectGradleTemplate = readFileSync4(join3(TEMPLATES_DIR2, "build.gradle.kts.project.template"), "utf-8");
|
|
2030
|
+
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' : ""));
|
|
2031
|
+
const appGradleTemplate = readFileSync4(join3(TEMPLATES_DIR2, "build.gradle.kts.app.template"), "utf-8");
|
|
2032
|
+
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
2033
|
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
|
-
|
|
2034
|
+
writeFileSync4(join3(output, "app/build.gradle.kts"), appGradle);
|
|
2035
|
+
const proguardTemplate = readFileSync4(join3(TEMPLATES_DIR2, "proguard-rules.pro.template"), "utf-8");
|
|
2036
|
+
writeFileSync4(join3(output, "app/proguard-rules.pro"), proguardTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
2037
|
+
const settingsTemplate = readFileSync4(join3(TEMPLATES_DIR2, "settings.gradle.kts.template"), "utf-8");
|
|
2038
|
+
const settings2 = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, escapeKotlinString(generatedGradleProjectName(name)));
|
|
2039
|
+
writeFileSync4(join3(output, "settings.gradle.kts"), settings2);
|
|
1882
2040
|
const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
1883
2041
|
android.useAndroidX=true
|
|
1884
2042
|
kotlin.code.style=official
|
|
1885
2043
|
android.nonTransitiveRClass=true
|
|
1886
2044
|
`;
|
|
1887
|
-
|
|
1888
|
-
|
|
2045
|
+
writeFileSync4(join3(output, "gradle.properties"), gradleProperties);
|
|
2046
|
+
writeFileSync4(join3(output, "local.properties"), `# SDK location will be set by Android Studio
|
|
1889
2047
|
`);
|
|
1890
2048
|
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
1891
2049
|
distributionPath=wrapper/dists
|
|
@@ -1895,13 +2053,13 @@ validateDistributionUrl=true
|
|
|
1895
2053
|
zipStoreBase=GRADLE_USER_HOME
|
|
1896
2054
|
zipStorePath=wrapper/dists
|
|
1897
2055
|
`;
|
|
1898
|
-
|
|
2056
|
+
writeFileSync4(join3(output, "gradle/wrapper/gradle-wrapper.properties"), gradleWrapperProps);
|
|
1899
2057
|
const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1900
2058
|
<resources>
|
|
1901
|
-
<string name="app_name">${name}</string>
|
|
2059
|
+
<string name="app_name">${escapeXml(name)}</string>
|
|
1902
2060
|
</resources>
|
|
1903
2061
|
`;
|
|
1904
|
-
|
|
2062
|
+
writeFileSync4(join3(output, "app/src/main/res/values/strings.xml"), stringsXml);
|
|
1905
2063
|
const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1906
2064
|
<resources>
|
|
1907
2065
|
<color name="primary">#1a1a2e</color>
|
|
@@ -1910,7 +2068,7 @@ zipStorePath=wrapper/dists
|
|
|
1910
2068
|
<color name="background">${config.backgroundColor}</color>
|
|
1911
2069
|
</resources>
|
|
1912
2070
|
`;
|
|
1913
|
-
|
|
2071
|
+
writeFileSync4(join3(output, "app/src/main/res/values/colors.xml"), colorsXml);
|
|
1914
2072
|
const appIconXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1915
2073
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1916
2074
|
android:width="108dp"
|
|
@@ -1922,11 +2080,9 @@ zipStorePath=wrapper/dists
|
|
|
1922
2080
|
</vector>
|
|
1923
2081
|
`;
|
|
1924
2082
|
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"));
|
|
2083
|
+
cpSync3(config.appIconPath, join3(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`));
|
|
1928
2084
|
} else {
|
|
1929
|
-
|
|
2085
|
+
writeFileSync4(join3(output, "app/src/main/res/drawable/craft_app_icon.xml"), appIconXml);
|
|
1930
2086
|
}
|
|
1931
2087
|
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1932
2088
|
<resources>
|
|
@@ -1937,7 +2093,7 @@ zipStorePath=wrapper/dists
|
|
|
1937
2093
|
</style>
|
|
1938
2094
|
</resources>
|
|
1939
2095
|
`;
|
|
1940
|
-
|
|
2096
|
+
writeFileSync4(join3(output, "app/src/main/res/values/themes.xml"), themesXml);
|
|
1941
2097
|
const activityMainXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1942
2098
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
1943
2099
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
@@ -1952,13 +2108,13 @@ zipStorePath=wrapper/dists
|
|
|
1952
2108
|
|
|
1953
2109
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
1954
2110
|
`;
|
|
1955
|
-
|
|
2111
|
+
writeFileSync4(join3(output, "app/src/main/res/layout/activity_main.xml"), activityMainXml);
|
|
1956
2112
|
const placeholderHtml = `<!DOCTYPE html>
|
|
1957
2113
|
<html>
|
|
1958
2114
|
<head>
|
|
1959
2115
|
<meta charset="UTF-8">
|
|
1960
2116
|
<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>
|
|
2117
|
+
<title>${escapeXml(name)}</title>
|
|
1962
2118
|
<style>
|
|
1963
2119
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1964
2120
|
body {
|
|
@@ -1978,7 +2134,7 @@ zipStorePath=wrapper/dists
|
|
|
1978
2134
|
</head>
|
|
1979
2135
|
<body>
|
|
1980
2136
|
<div class="container">
|
|
1981
|
-
<h1>\u26A1 ${name}</h1>
|
|
2137
|
+
<h1>\u26A1 ${escapeXml(name)}</h1>
|
|
1982
2138
|
<p>Built with Craft Android</p>
|
|
1983
2139
|
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
1984
2140
|
</div>
|
|
@@ -1990,7 +2146,7 @@ zipStorePath=wrapper/dists
|
|
|
1990
2146
|
</script>
|
|
1991
2147
|
</body>
|
|
1992
2148
|
</html>`;
|
|
1993
|
-
|
|
2149
|
+
writeFileSync4(join3(output, "app/src/main/assets/index.html"), placeholderHtml);
|
|
1994
2150
|
console.log("\u2705 Project initialized");
|
|
1995
2151
|
console.log("");
|
|
1996
2152
|
console.log("Next steps:");
|
|
@@ -2008,19 +2164,21 @@ async function build2(options) {
|
|
|
2008
2164
|
if (!existsSync5(configPath)) {
|
|
2009
2165
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`);
|
|
2010
2166
|
}
|
|
2011
|
-
const config = JSON.parse(
|
|
2167
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
2012
2168
|
if (devServer) {
|
|
2013
|
-
|
|
2014
|
-
config.
|
|
2015
|
-
|
|
2016
|
-
|
|
2169
|
+
const url = androidWebUrl(devServer, "Android dev server URL");
|
|
2170
|
+
config.devServerURL = url.toString();
|
|
2171
|
+
config.trustedOrigins = [...new Set([
|
|
2172
|
+
...(config.trustedOrigins ?? []).map((value) => androidWebUrl(value, "Android trusted origin").origin),
|
|
2173
|
+
url.origin
|
|
2174
|
+
])];
|
|
2175
|
+
writeAndroidConfig(output, config);
|
|
2017
2176
|
console.log(` Dev server: ${devServer}`);
|
|
2018
2177
|
}
|
|
2019
2178
|
if (htmlPath) {
|
|
2020
2179
|
syncAndroidWebAssets(htmlPath, output);
|
|
2021
2180
|
config.hasBundledFallback = Boolean(devServer);
|
|
2022
|
-
|
|
2023
|
-
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
2181
|
+
writeAndroidConfig(output, config);
|
|
2024
2182
|
console.log(` Synced: ${htmlPath} \u2192 assets/`);
|
|
2025
2183
|
}
|
|
2026
2184
|
if (!compile)
|
|
@@ -2075,7 +2233,7 @@ async function run2(options) {
|
|
|
2075
2233
|
} else {
|
|
2076
2234
|
await $2`adb install -r ${apkPath}`;
|
|
2077
2235
|
}
|
|
2078
|
-
const config = JSON.parse(
|
|
2236
|
+
const config = JSON.parse(readFileSync4(join3(output, "craft.config.json"), "utf-8"));
|
|
2079
2237
|
const launchCmd = `${config.packageName}/.MainActivity`;
|
|
2080
2238
|
if (device) {
|
|
2081
2239
|
await $2`adb -s ${device} shell am start -n ${launchCmd}`;
|
|
@@ -2090,10 +2248,106 @@ async function run2(options) {
|
|
|
2090
2248
|
throw error;
|
|
2091
2249
|
}
|
|
2092
2250
|
}
|
|
2093
|
-
var $2,
|
|
2251
|
+
var $2, ANDROID_PROMISE_RUNTIME = `
|
|
2252
|
+
if (window.__craftRejectPendingPromises) {
|
|
2253
|
+
window.__craftRejectPendingPromises('Android bridge reinitialized');
|
|
2254
|
+
}
|
|
2255
|
+
if (window.__craftRejectPermissionRequests) {
|
|
2256
|
+
window.__craftRejectPermissionRequests('Android bridge reinitialized');
|
|
2257
|
+
}
|
|
2258
|
+
window.__craftPromiseRuntimeClosed = false;
|
|
2259
|
+
window.__craftPendingPromises = Object.create(null);
|
|
2260
|
+
window.__craftPromise = function(channel, resolveName, rejectName, invoke, timeoutMs, timeoutError) {
|
|
2261
|
+
if (window.__craftPromiseRuntimeClosed) {
|
|
2262
|
+
return Promise.reject(new Error('Android bridge is closed'));
|
|
2263
|
+
}
|
|
2264
|
+
if (window.__craftPendingPromises[channel]) {
|
|
2265
|
+
return Promise.reject(new Error('A '.concat(channel, ' request is already in progress')));
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
return new Promise(function(resolve, reject) {
|
|
2269
|
+
var entry = {settled: false, timer: null, settle: null};
|
|
2270
|
+
var resolveCallback;
|
|
2271
|
+
var rejectCallback;
|
|
2272
|
+
|
|
2273
|
+
entry.settle = function(succeeded, value) {
|
|
2274
|
+
if (entry.settled || window.__craftPendingPromises[channel] !== entry) return;
|
|
2275
|
+
entry.settled = true;
|
|
2276
|
+
if (entry.timer !== null) clearTimeout(entry.timer);
|
|
2277
|
+
if (window[resolveName] === resolveCallback) window[resolveName] = null;
|
|
2278
|
+
if (window[rejectName] === rejectCallback) window[rejectName] = null;
|
|
2279
|
+
delete window.__craftPendingPromises[channel];
|
|
2280
|
+
if (succeeded) resolve(value);
|
|
2281
|
+
else reject(value);
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2284
|
+
resolveCallback = function(value) {
|
|
2285
|
+
entry.settle(true, value);
|
|
2286
|
+
};
|
|
2287
|
+
rejectCallback = function(error) {
|
|
2288
|
+
entry.settle(false, error);
|
|
2289
|
+
};
|
|
2290
|
+
window[resolveName] = resolveCallback;
|
|
2291
|
+
window[rejectName] = rejectCallback;
|
|
2292
|
+
window.__craftPendingPromises[channel] = entry;
|
|
2293
|
+
|
|
2294
|
+
if (timeoutMs > 0) {
|
|
2295
|
+
entry.timer = setTimeout(function() {
|
|
2296
|
+
entry.settle(false, timeoutError || new Error(channel.concat(' request timed out')));
|
|
2297
|
+
}, timeoutMs);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
try {
|
|
2301
|
+
invoke();
|
|
2302
|
+
}
|
|
2303
|
+
catch (error) {
|
|
2304
|
+
entry.settle(false, error);
|
|
2305
|
+
}
|
|
2306
|
+
});
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
window.__craftRejectPendingPromises = function(message) {
|
|
2310
|
+
window.__craftPromiseRuntimeClosed = true;
|
|
2311
|
+
Object.keys(window.__craftPendingPromises).forEach(function(channel) {
|
|
2312
|
+
var entry = window.__craftPendingPromises[channel];
|
|
2313
|
+
if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));
|
|
2314
|
+
});
|
|
2315
|
+
};
|
|
2316
|
+
`, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, DEFAULT_CONFIG2;
|
|
2094
2317
|
var init_src2 = __esm(() => {
|
|
2095
2318
|
({ $: $2 } = globalThis.Bun);
|
|
2096
2319
|
TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
|
|
2320
|
+
LOCAL_DEVELOPMENT_HOSTS = new Set(["localhost", "127.0.0.1", "10.0.2.2"]);
|
|
2321
|
+
KOTLIN_KEYWORDS = new Set([
|
|
2322
|
+
"as",
|
|
2323
|
+
"break",
|
|
2324
|
+
"class",
|
|
2325
|
+
"continue",
|
|
2326
|
+
"do",
|
|
2327
|
+
"else",
|
|
2328
|
+
"false",
|
|
2329
|
+
"for",
|
|
2330
|
+
"fun",
|
|
2331
|
+
"if",
|
|
2332
|
+
"in",
|
|
2333
|
+
"interface",
|
|
2334
|
+
"is",
|
|
2335
|
+
"null",
|
|
2336
|
+
"object",
|
|
2337
|
+
"package",
|
|
2338
|
+
"return",
|
|
2339
|
+
"super",
|
|
2340
|
+
"this",
|
|
2341
|
+
"throw",
|
|
2342
|
+
"true",
|
|
2343
|
+
"try",
|
|
2344
|
+
"typealias",
|
|
2345
|
+
"typeof",
|
|
2346
|
+
"val",
|
|
2347
|
+
"var",
|
|
2348
|
+
"when",
|
|
2349
|
+
"while"
|
|
2350
|
+
]);
|
|
2097
2351
|
DEFAULT_CONFIG2 = {
|
|
2098
2352
|
version: "1.0.0",
|
|
2099
2353
|
versionCode: 1,
|
|
@@ -4262,8 +4516,28 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
4262
4516
|
].join(`
|
|
4263
4517
|
`);
|
|
4264
4518
|
}
|
|
4519
|
+
|
|
4520
|
+
// src/scaffold-version.ts
|
|
4521
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
4522
|
+
function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
4523
|
+
const manifest = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
4524
|
+
const sections = [manifest.dependencies, manifest.devDependencies];
|
|
4525
|
+
let found = false;
|
|
4526
|
+
for (const dependencies of sections) {
|
|
4527
|
+
if (dependencies && Object.hasOwn(dependencies, "craft-native")) {
|
|
4528
|
+
dependencies["craft-native"] = `^${craftVersion}`;
|
|
4529
|
+
found = true;
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
if (!found) {
|
|
4533
|
+
manifest.dependencies ??= {};
|
|
4534
|
+
manifest.dependencies["craft-native"] = `^${craftVersion}`;
|
|
4535
|
+
}
|
|
4536
|
+
writeFileSync(packagePath, `${JSON.stringify(manifest, null, 2)}
|
|
4537
|
+
`);
|
|
4538
|
+
}
|
|
4265
4539
|
// package.json
|
|
4266
|
-
var version = "0.0.
|
|
4540
|
+
var version = "0.0.91";
|
|
4267
4541
|
|
|
4268
4542
|
// bin/cli.ts
|
|
4269
4543
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
|
@@ -4757,7 +5031,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4757
5031
|
const template = options?.template || "blank";
|
|
4758
5032
|
const bundleId = options?.bundleId || `com.example.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
4759
5033
|
const appNameSlug = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
4760
|
-
const { mkdirSync: mkdirSync4, writeFileSync:
|
|
5034
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync5, cpSync: cpSync4 } = await import("fs");
|
|
4761
5035
|
const { join: join4, dirname: dirname3 } = await import("path");
|
|
4762
5036
|
const replaceVars = (content) => {
|
|
4763
5037
|
return content.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{APP_NAME_SLUG\}\}/g, appNameSlug).replace(/\{\{BUNDLE_ID\}\}/g, bundleId).replace(/\{\{AUTHOR\}\}/g, "Developer");
|
|
@@ -4779,9 +5053,9 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4779
5053
|
if (entry.isDirectory()) {
|
|
4780
5054
|
copyRecursive(srcPath, destPath);
|
|
4781
5055
|
} else {
|
|
4782
|
-
const content =
|
|
5056
|
+
const content = readFileSync5(srcPath, "utf-8");
|
|
4783
5057
|
const processedContent = replaceVars(content);
|
|
4784
|
-
|
|
5058
|
+
writeFileSync5(destPath, processedContent);
|
|
4785
5059
|
}
|
|
4786
5060
|
}
|
|
4787
5061
|
};
|
|
@@ -4796,7 +5070,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4796
5070
|
const copied = await copyTemplate(template, name);
|
|
4797
5071
|
if (!copied) {
|
|
4798
5072
|
mkdirSync4(join4(name, "src"), { recursive: true });
|
|
4799
|
-
|
|
5073
|
+
writeFileSync5(join4(name, "index.html"), replaceVars(`<!DOCTYPE html>
|
|
4800
5074
|
<html lang="en">
|
|
4801
5075
|
<head>
|
|
4802
5076
|
<meta charset="UTF-8">
|
|
@@ -4810,7 +5084,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4810
5084
|
</div>
|
|
4811
5085
|
</body>
|
|
4812
5086
|
</html>`));
|
|
4813
|
-
|
|
5087
|
+
writeFileSync5(join4(name, "package.json"), replaceVars(JSON.stringify({
|
|
4814
5088
|
name: "{{APP_NAME_SLUG}}",
|
|
4815
5089
|
version: "1.0.0",
|
|
4816
5090
|
private: true,
|
|
@@ -4820,10 +5094,11 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
4820
5094
|
build: "craft build"
|
|
4821
5095
|
},
|
|
4822
5096
|
dependencies: {
|
|
4823
|
-
"craft-native":
|
|
5097
|
+
"craft-native": `^${version}`
|
|
4824
5098
|
}
|
|
4825
5099
|
}, null, 2)));
|
|
4826
5100
|
}
|
|
5101
|
+
pinCraftNativeDependency(join4(name, "package.json"), version);
|
|
4827
5102
|
console.log(`\u2705 ${template} project created`);
|
|
4828
5103
|
}
|
|
4829
5104
|
if (template === "desktop" || template === "all") {
|
|
@@ -4843,7 +5118,7 @@ export default {
|
|
|
4843
5118
|
},
|
|
4844
5119
|
} satisfies CraftConfig
|
|
4845
5120
|
`;
|
|
4846
|
-
|
|
5121
|
+
writeFileSync5(`${name}/craft.config.ts`, configContent);
|
|
4847
5122
|
const htmlContent = `<!DOCTYPE html>
|
|
4848
5123
|
<html lang="en">
|
|
4849
5124
|
<head>
|
|
@@ -4874,7 +5149,7 @@ export default {
|
|
|
4874
5149
|
</body>
|
|
4875
5150
|
</html>
|
|
4876
5151
|
`;
|
|
4877
|
-
|
|
5152
|
+
writeFileSync5(`${name}/index.html`, htmlContent);
|
|
4878
5153
|
const packageJson = {
|
|
4879
5154
|
name: appNameSlug,
|
|
4880
5155
|
version: "0.1.0",
|
|
@@ -4887,10 +5162,11 @@ export default {
|
|
|
4887
5162
|
"ios:open": "craft ios open"
|
|
4888
5163
|
},
|
|
4889
5164
|
devDependencies: {
|
|
4890
|
-
"craft-native":
|
|
5165
|
+
"craft-native": `^${version}`
|
|
4891
5166
|
}
|
|
4892
5167
|
};
|
|
4893
|
-
|
|
5168
|
+
writeFileSync5(`${name}/package.json`, JSON.stringify(packageJson, null, 2));
|
|
5169
|
+
pinCraftNativeDependency(`${name}/package.json`, version);
|
|
4894
5170
|
console.log("\u2705 Desktop project created");
|
|
4895
5171
|
}
|
|
4896
5172
|
if (template === "ios" || template === "all") {
|