craft-native 0.0.92 → 0.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/android/src/index.d.ts +3 -0
- package/dist/android/src/index.js +8 -4
- package/dist/android/templates/CraftBridge.kt.template +271 -72
- package/dist/android/templates/CraftNative.kt.template +12 -6
- package/dist/android/templates/MainActivity.kt.template +86 -9
- package/dist/api/mobile.d.ts +32 -0
- package/dist/binary-resolver.d.ts +4 -0
- package/dist/cli.js +17 -8
- package/dist/index.cjs +77 -49
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +78 -50
- package/dist/ios/templates/CraftApp.swift +450 -27
- package/dist/mobile.js +24 -1
- package/dist/sdk-version.d.ts +1 -0
- package/package.json +2 -2
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
package {{PACKAGE_NAME}}
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
4
5
|
import android.os.Bundle
|
|
5
6
|
import android.content.Intent
|
|
6
7
|
import android.graphics.Bitmap
|
|
8
|
+
import android.net.ConnectivityManager
|
|
9
|
+
import android.net.Network
|
|
7
10
|
import android.net.Uri
|
|
11
|
+
import android.util.Log
|
|
8
12
|
import android.webkit.WebResourceRequest
|
|
9
13
|
import android.webkit.WebResourceError
|
|
10
14
|
import android.webkit.WebResourceResponse
|
|
@@ -19,6 +23,20 @@ import org.json.JSONObject
|
|
|
19
23
|
import java.io.FileNotFoundException
|
|
20
24
|
import java.net.URLConnection
|
|
21
25
|
|
|
26
|
+
/** Main-frame failures for which the bundled app is an honest fallback (#254). */
|
|
27
|
+
object CraftLoadFailure {
|
|
28
|
+
fun isUnreachable(errorCode: Int): Boolean = when (errorCode) {
|
|
29
|
+
WebViewClient.ERROR_HOST_LOOKUP,
|
|
30
|
+
WebViewClient.ERROR_CONNECT,
|
|
31
|
+
WebViewClient.ERROR_TIMEOUT,
|
|
32
|
+
// Android uses ERROR_IO for connection interruptions as well as local
|
|
33
|
+
// I/O failures. Keep it here deliberately: it is the closest Android
|
|
34
|
+
// equivalent to iOS's NSURLErrorNetworkConnectionLost.
|
|
35
|
+
WebViewClient.ERROR_IO -> true
|
|
36
|
+
else -> false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
class MainActivity : AppCompatActivity() {
|
|
23
41
|
private lateinit var webView: WebView
|
|
24
42
|
private lateinit var craftBridge: CraftBridge
|
|
@@ -32,6 +50,28 @@ class MainActivity : AppCompatActivity() {
|
|
|
32
50
|
.build()
|
|
33
51
|
}
|
|
34
52
|
private var loadedBundledFallback = false
|
|
53
|
+
private lateinit var connectivityManager: ConnectivityManager
|
|
54
|
+
@Volatile
|
|
55
|
+
private var isConnected = false
|
|
56
|
+
private var networkMonitoringRegistered = false
|
|
57
|
+
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
58
|
+
override fun onAvailable(network: Network) {
|
|
59
|
+
val wasConnected = isConnected
|
|
60
|
+
isConnected = true
|
|
61
|
+
if (!wasConnected && isConnected) {
|
|
62
|
+
runOnUiThread {
|
|
63
|
+
returnFromBundledFallback("the network came back")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun onLost(network: Network) {
|
|
69
|
+
// A second transport can already be the default by the time the
|
|
70
|
+
// old one reports its loss, so read current state instead of
|
|
71
|
+
// blindly turning the latch off.
|
|
72
|
+
isConnected = connectivityManager.activeNetwork != null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
35
75
|
|
|
36
76
|
@SuppressLint("SetJavaScriptEnabled")
|
|
37
77
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
@@ -46,21 +86,29 @@ class MainActivity : AppCompatActivity() {
|
|
|
46
86
|
craftBridge = CraftBridge(this, webView, CraftHealthConnect(this, webView))
|
|
47
87
|
// The link that launched the app (#215). Queued until the page's bridge
|
|
48
88
|
// is injected, then dispatched once with `initial: true`, so a page that
|
|
49
|
-
// subscribes rather than calling getInitialURL still hears it.
|
|
50
|
-
//
|
|
51
|
-
// A
|
|
52
|
-
//
|
|
89
|
+
// subscribes rather than calling getInitialURL still hears it.
|
|
90
|
+
//
|
|
91
|
+
// A relaunch from Recents carries the intent the app was first opened
|
|
92
|
+
// with. The person picked the task out of a list, not the link or the
|
|
93
|
+
// shortcut inside it, and neither is news the second time — so both
|
|
94
|
+
// are skipped, not just the link (#229).
|
|
95
|
+
//
|
|
96
|
+
// A recreated activity restores what the bridge saved rather than
|
|
97
|
+
// reading its intent, which onNewIntent may have replaced with a
|
|
98
|
+
// later warm link.
|
|
99
|
+
val launchedFromHistory = intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0
|
|
53
100
|
if (savedInstanceState == null) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
101
|
+
if (!launchedFromHistory) {
|
|
102
|
+
intent.data?.toString()?.let(craftBridge::receiveDeepLink)
|
|
103
|
+
handleIncomingShortcut(intent)
|
|
104
|
+
}
|
|
57
105
|
} else {
|
|
58
106
|
craftBridge.restoreDeepLinks(savedInstanceState)
|
|
59
107
|
}
|
|
60
|
-
if (savedInstanceState == null) handleIncomingShortcut(intent)
|
|
61
108
|
|
|
62
109
|
setupWebView()
|
|
63
110
|
loadContent()
|
|
111
|
+
setupNetworkMonitoring()
|
|
64
112
|
}
|
|
65
113
|
|
|
66
114
|
@SuppressLint("SetJavaScriptEnabled")
|
|
@@ -106,7 +154,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
106
154
|
|
|
107
155
|
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
|
|
108
156
|
super.onReceivedError(view, request, error)
|
|
109
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
request?.isForMainFrame == true &&
|
|
159
|
+
error != null &&
|
|
160
|
+
CraftLoadFailure.isUnreachable(error.errorCode) &&
|
|
161
|
+
hasBundledFallback &&
|
|
162
|
+
!loadedBundledFallback
|
|
163
|
+
) {
|
|
110
164
|
loadedBundledFallback = true
|
|
111
165
|
webView.loadUrl(BUNDLED_APP_URL)
|
|
112
166
|
}
|
|
@@ -119,6 +173,23 @@ class MainActivity : AppCompatActivity() {
|
|
|
119
173
|
webView.addJavascriptInterface(craftBridge, "CraftAndroid")
|
|
120
174
|
}
|
|
121
175
|
|
|
176
|
+
private fun setupNetworkMonitoring() {
|
|
177
|
+
if (!hasBundledFallback || devServerUrl == null) return
|
|
178
|
+
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
179
|
+
isConnected = connectivityManager.activeNetwork != null
|
|
180
|
+
connectivityManager.registerDefaultNetworkCallback(networkCallback)
|
|
181
|
+
networkMonitoringRegistered = true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Leave the bundled copy for the configured remote origin again. */
|
|
185
|
+
private fun returnFromBundledFallback(reason: String) {
|
|
186
|
+
val remoteUrl = devServerUrl ?: return
|
|
187
|
+
if (!loadedBundledFallback) return
|
|
188
|
+
loadedBundledFallback = false
|
|
189
|
+
Log.i(TAG, "$reason; loading $remoteUrl again instead of the bundled copy")
|
|
190
|
+
webView.loadUrl(remoteUrl)
|
|
191
|
+
}
|
|
192
|
+
|
|
122
193
|
private fun loadContent() {
|
|
123
194
|
// Check for dev server URL in config
|
|
124
195
|
val remoteUrl = devServerUrl
|
|
@@ -227,6 +298,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
227
298
|
override fun onResume() {
|
|
228
299
|
super.onResume()
|
|
229
300
|
webView.onResume()
|
|
301
|
+
returnFromBundledFallback("the app returned to the foreground")
|
|
230
302
|
}
|
|
231
303
|
|
|
232
304
|
override fun onPause() {
|
|
@@ -235,6 +307,10 @@ class MainActivity : AppCompatActivity() {
|
|
|
235
307
|
}
|
|
236
308
|
|
|
237
309
|
override fun onDestroy() {
|
|
310
|
+
if (networkMonitoringRegistered) {
|
|
311
|
+
connectivityManager.unregisterNetworkCallback(networkCallback)
|
|
312
|
+
networkMonitoringRegistered = false
|
|
313
|
+
}
|
|
238
314
|
craftBridge.close()
|
|
239
315
|
webView.destroy()
|
|
240
316
|
super.onDestroy()
|
|
@@ -256,6 +332,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
256
332
|
}
|
|
257
333
|
|
|
258
334
|
companion object {
|
|
335
|
+
private const val TAG = "CraftMainActivity"
|
|
259
336
|
private const val BUNDLED_APP_HOST = "appassets.androidplatform.net"
|
|
260
337
|
private const val BUNDLED_APP_ORIGIN = "https://appassets.androidplatform.net"
|
|
261
338
|
private const val BUNDLED_APP_URL = "$BUNDLED_APP_ORIGIN/"
|
package/dist/api/mobile.d.ts
CHANGED
|
@@ -617,6 +617,22 @@ export interface NotificationOptions {
|
|
|
617
617
|
/** Schedule for future (timestamp) */
|
|
618
618
|
scheduleAt?: number;
|
|
619
619
|
}
|
|
620
|
+
/** A notification as the bridges read it: `delay` in place of `scheduleAt`. */
|
|
621
|
+
export type BridgeNotification = Omit<NotificationOptions, 'scheduleAt'> & {
|
|
622
|
+
delay?: number;
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* The bridges' spelling of a notification: `scheduleAt` becomes `delay`.
|
|
626
|
+
*
|
|
627
|
+
* Both bridges schedule from `delay`, milliseconds from now, and iOS also from
|
|
628
|
+
* `timestamp`; neither reads `scheduleAt`, so a reminder meant for an hour
|
|
629
|
+
* from now fired at once (#261). `delay` rather than `timestamp` because
|
|
630
|
+
* Android reads only `delay`, and because an interval is what an instant
|
|
631
|
+
* means, where iOS's calendar trigger would move with the device's time zone.
|
|
632
|
+
* A moment already past sends no delay at all, which both bridges deliver
|
|
633
|
+
* now: a zero delay raises inside UserNotifications on the Swift side.
|
|
634
|
+
*/
|
|
635
|
+
export declare function bridgeNotification(options: NotificationOptions, now?: number): BridgeNotification;
|
|
620
636
|
/**
|
|
621
637
|
* Local notifications API.
|
|
622
638
|
*
|
|
@@ -693,7 +709,23 @@ export declare const appReview: {
|
|
|
693
709
|
export declare const pushNotifications: {
|
|
694
710
|
register(): Promise<string>;
|
|
695
711
|
onToken(callback: (token: string) => void): () => void;
|
|
712
|
+
/**
|
|
713
|
+
* A tap on a notification, with the payload it was sent with.
|
|
714
|
+
*
|
|
715
|
+
* Goes through the bridge's replay where there is one, so a tap that
|
|
716
|
+
* launched the app still reaches a page that subscribes once it has
|
|
717
|
+
* hydrated — rather than only one listening before the bridge was ready.
|
|
718
|
+
*/
|
|
696
719
|
onNotification(callback: (data: Record<string, unknown>) => void): () => void;
|
|
720
|
+
/**
|
|
721
|
+
* A notification that arrived while the app was open, with the payload it
|
|
722
|
+
* was sent with — the same shape `onNotification` hands a tap.
|
|
723
|
+
*
|
|
724
|
+
* The banner still shows. This is for a page that wants to react while it
|
|
725
|
+
* is on screen, such as refreshing what the push was about. Live only: an
|
|
726
|
+
* arrival before the page subscribed is not held for it. iOS only for now.
|
|
727
|
+
*/
|
|
728
|
+
onReceive(callback: (data: Record<string, unknown>) => void): () => void;
|
|
697
729
|
};
|
|
698
730
|
export type HealthDataType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts';
|
|
699
731
|
export interface HealthDataOptions {
|
|
@@ -46,6 +46,10 @@ export declare function resolveCraftBinary(explicit?: string): string;
|
|
|
46
46
|
* path-matrix behaviour the pantry contract exists to remove. So instead the
|
|
47
47
|
* spawn is marked, and a CLI that starts up already marked knows it *is* the
|
|
48
48
|
* thing that was spawned, and says so.
|
|
49
|
+
*
|
|
50
|
+
* Both spawners set it: `runCraftBinary()` in the CLI, and `CraftApp.show()`
|
|
51
|
+
* in the SDK. The SDK one was missing until #236, so every SDK caller on such
|
|
52
|
+
* a machine still met the raw parser error this marker exists to replace.
|
|
49
53
|
*/
|
|
50
54
|
export declare const CRAFT_CLI_SPAWN_MARKER = "CRAFT_CLI_SPAWNED_FROM";
|
|
51
55
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -1736,7 +1736,8 @@ __export(exports_src2, {
|
|
|
1736
1736
|
open: () => open2,
|
|
1737
1737
|
installRuntime: () => installRuntime2,
|
|
1738
1738
|
init: () => init2,
|
|
1739
|
-
build: () => build2
|
|
1739
|
+
build: () => build2,
|
|
1740
|
+
ANDROID_MIN_SDK: () => ANDROID_MIN_SDK
|
|
1740
1741
|
});
|
|
1741
1742
|
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
1743
|
import { dirname as dirname2, extname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
@@ -1840,6 +1841,9 @@ function validateAndroidConfig(config) {
|
|
|
1840
1841
|
throw new Error(`Android ${name} must be a positive integer`);
|
|
1841
1842
|
}
|
|
1842
1843
|
}
|
|
1844
|
+
if (Number(config.minSdk) < ANDROID_MIN_SDK) {
|
|
1845
|
+
throw new Error(`Android minSdk must be at least ${ANDROID_MIN_SDK} (Android 8.0) for the Craft runtime`);
|
|
1846
|
+
}
|
|
1843
1847
|
if (Number(config.minSdk) > Number(config.targetSdk)) {
|
|
1844
1848
|
throw new Error("Android minSdk must not exceed targetSdk");
|
|
1845
1849
|
}
|
|
@@ -1977,7 +1981,6 @@ async function init2(options) {
|
|
|
1977
1981
|
if (config.enableBackgroundLocation)
|
|
1978
1982
|
config.enableGeolocation = true;
|
|
1979
1983
|
if (config.enableHealthConnect) {
|
|
1980
|
-
config.minSdk = Math.max(config.minSdk ?? 26, 26);
|
|
1981
1984
|
config.compileSdk = Math.max(config.compileSdk ?? 36, 36);
|
|
1982
1985
|
}
|
|
1983
1986
|
normalizeAndroidNetworkConfig(config);
|
|
@@ -2061,7 +2064,7 @@ async function init2(options) {
|
|
|
2061
2064
|
const projectGradleTemplate = readFileSync4(join3(TEMPLATES_DIR2, "build.gradle.kts.project.template"), "utf-8");
|
|
2062
2065
|
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
2066
|
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
|
|
2067
|
+
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 ?? ANDROID_MIN_SDK)).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")
|
|
2065
2068
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "");
|
|
2066
2069
|
writeFileSync4(join3(output, "app/build.gradle.kts"), appGradle);
|
|
2067
2070
|
const proguardTemplate = readFileSync4(join3(TEMPLATES_DIR2, "proguard-rules.pro.template"), "utf-8");
|
|
@@ -2359,7 +2362,7 @@ window.__craftRejectPendingPromises = function(message) {
|
|
|
2359
2362
|
if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));
|
|
2360
2363
|
});
|
|
2361
2364
|
};
|
|
2362
|
-
`, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, DEFAULT_CONFIG2, RUNTIME_ABIS;
|
|
2365
|
+
`, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, ANDROID_MIN_SDK = 26, DEFAULT_CONFIG2, RUNTIME_ABIS;
|
|
2363
2366
|
var init_src2 = __esm(() => {
|
|
2364
2367
|
({ $: $2 } = globalThis.Bun);
|
|
2365
2368
|
TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
|
|
@@ -2413,7 +2416,7 @@ var init_src2 = __esm(() => {
|
|
|
2413
2416
|
enableDeepLinks: false,
|
|
2414
2417
|
urlSchemes: [],
|
|
2415
2418
|
trustedOrigins: [],
|
|
2416
|
-
minSdk:
|
|
2419
|
+
minSdk: ANDROID_MIN_SDK,
|
|
2417
2420
|
compileSdk: 36,
|
|
2418
2421
|
targetSdk: 35
|
|
2419
2422
|
};
|
|
@@ -4530,7 +4533,7 @@ function craftBinaryIsCliShimMessage(spawnedFrom) {
|
|
|
4530
4533
|
return [
|
|
4531
4534
|
`"${spawnedFrom}" on PATH is the Craft CLI, not the native binary.`,
|
|
4532
4535
|
"",
|
|
4533
|
-
"
|
|
4536
|
+
"Craft spawned it expecting the native build and reached this CLI instead \u2014",
|
|
4534
4537
|
"which is why an option you never typed (--url) came back as unknown.",
|
|
4535
4538
|
"",
|
|
4536
4539
|
"Fix it in one of two ways:",
|
|
@@ -4584,7 +4587,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
4584
4587
|
`);
|
|
4585
4588
|
}
|
|
4586
4589
|
// package.json
|
|
4587
|
-
var version = "0.0.
|
|
4590
|
+
var version = "0.0.95";
|
|
4588
4591
|
|
|
4589
4592
|
// bin/cli.ts
|
|
4590
4593
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
|
@@ -5072,10 +5075,16 @@ cli.command("publish", "Publish app to App Store or Play Store").option("--ios",
|
|
|
5072
5075
|
}
|
|
5073
5076
|
});
|
|
5074
5077
|
cli.command("init <name>", "Initialize a new Craft project").option("--template <type>", "Project template (blank, tabs, drawer, dashboard, desktop, ios, android, all)", { default: "blank" }).option("--bundle-id <id>", "Bundle identifier for mobile").example("craft init MyApp").example("craft init MyApp --template tabs").example("craft init MyApp --template dashboard").example("craft init MyApp --template all").action(async (name, options) => {
|
|
5078
|
+
const template = options?.template || "blank";
|
|
5079
|
+
const templates = ["blank", "tabs", "drawer", "dashboard", "desktop", "ios", "android", "all"];
|
|
5080
|
+
if (!templates.includes(template)) {
|
|
5081
|
+
console.error(`Error: Unknown template "${template}"`);
|
|
5082
|
+
console.error(`Available templates: ${templates.join(", ")}`);
|
|
5083
|
+
process6.exit(1);
|
|
5084
|
+
}
|
|
5075
5085
|
console.log(`
|
|
5076
5086
|
\u26A1 Creating new Craft project: ${name}
|
|
5077
5087
|
`);
|
|
5078
|
-
const template = options?.template || "blank";
|
|
5079
5088
|
const bundleId = options?.bundleId || `com.example.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
5080
5089
|
const appNameSlug = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
5081
5090
|
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync5, cpSync: cpSync4 } = await import("fs");
|
package/dist/index.cjs
CHANGED
|
@@ -218,6 +218,7 @@ __export(exports_src, {
|
|
|
218
218
|
createArcSidebar: () => createArcSidebar,
|
|
219
219
|
createApp: () => createApp,
|
|
220
220
|
craftUnavailableMessage: () => craftUnavailableMessage,
|
|
221
|
+
craftProcessFailedMessage: () => craftProcessFailedMessage,
|
|
221
222
|
craftBinaryNotFoundMessage: () => craftBinaryNotFoundMessage,
|
|
222
223
|
copy: () => copy,
|
|
223
224
|
components: () => exports_components,
|
|
@@ -291,6 +292,7 @@ function resolveCraftBinary(explicit) {
|
|
|
291
292
|
}
|
|
292
293
|
return "craft";
|
|
293
294
|
}
|
|
295
|
+
var CRAFT_CLI_SPAWN_MARKER = "CRAFT_CLI_SPAWNED_FROM";
|
|
294
296
|
function craftBinaryNotFoundMessage(triedPath) {
|
|
295
297
|
return [
|
|
296
298
|
`Craft native binary not found (tried "${triedPath}").`,
|
|
@@ -307,6 +309,11 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
307
309
|
].join(`
|
|
308
310
|
`);
|
|
309
311
|
}
|
|
312
|
+
// package.json
|
|
313
|
+
var version = "0.0.95";
|
|
314
|
+
|
|
315
|
+
// src/sdk-version.ts
|
|
316
|
+
var SDK_VERSION = version;
|
|
310
317
|
|
|
311
318
|
// src/package.ts
|
|
312
319
|
var import_child_process = require("child_process");
|
|
@@ -422,13 +429,13 @@ async function packageApp(config) {
|
|
|
422
429
|
}
|
|
423
430
|
async function packageMacOS(config, outDir) {
|
|
424
431
|
const results = [];
|
|
425
|
-
const { name, version, bundleId = `com.myapp.${name.toLowerCase()}` } = config;
|
|
432
|
+
const { name, version: version2, bundleId = `com.myapp.${name.toLowerCase()}` } = config;
|
|
426
433
|
const opts = config.macos || {};
|
|
427
434
|
const hardenedRuntime = !opts.appStore;
|
|
428
435
|
const appBundlePath = import_path.join(outDir, `${name}.app`);
|
|
429
436
|
const appBundle = createMacOSAppBundle({
|
|
430
437
|
name,
|
|
431
|
-
version,
|
|
438
|
+
version: version2,
|
|
432
439
|
bundleId,
|
|
433
440
|
buildNumber: opts.buildNumber,
|
|
434
441
|
category: opts.category,
|
|
@@ -472,7 +479,7 @@ async function packageMacOS(config, outDir) {
|
|
|
472
479
|
}
|
|
473
480
|
results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
|
|
474
481
|
if (opts.dmg !== false) {
|
|
475
|
-
const outputPath = import_path.join(outDir, `${name}-${
|
|
482
|
+
const outputPath = import_path.join(outDir, `${name}-${version2}.dmg`);
|
|
476
483
|
const dmgResult = await createDMG({ appBundlePath, outputPath, volumeName: name });
|
|
477
484
|
results.push({
|
|
478
485
|
success: dmgResult.success,
|
|
@@ -485,12 +492,12 @@ async function packageMacOS(config, outDir) {
|
|
|
485
492
|
results.push(...await notarizeIfRequested(opts, outputPath, "dmg"));
|
|
486
493
|
}
|
|
487
494
|
if (opts.pkg || opts.appStore) {
|
|
488
|
-
const outputPath = import_path.join(outDir, `${name}-${
|
|
495
|
+
const outputPath = import_path.join(outDir, `${name}-${version2}.pkg`);
|
|
489
496
|
const pkgResult = await createPKG({
|
|
490
497
|
appBundlePath,
|
|
491
498
|
outputPath,
|
|
492
499
|
identifier: bundleId,
|
|
493
|
-
version,
|
|
500
|
+
version: version2,
|
|
494
501
|
appStore: opts.appStore === true,
|
|
495
502
|
installerIdentity: opts.installerIdentity
|
|
496
503
|
});
|
|
@@ -536,14 +543,14 @@ async function notarizeIfRequested(opts, artifactPath, format) {
|
|
|
536
543
|
}
|
|
537
544
|
async function packageWindows(config, outDir) {
|
|
538
545
|
const results = [];
|
|
539
|
-
const { name, version } = config;
|
|
546
|
+
const { name, version: version2 } = config;
|
|
540
547
|
const opts = config.windows || {};
|
|
541
548
|
if (opts.msi !== false) {
|
|
542
549
|
const msiResult = await createMSI({
|
|
543
550
|
name,
|
|
544
|
-
version,
|
|
551
|
+
version: version2,
|
|
545
552
|
binaryPath: config.binaryPath,
|
|
546
|
-
outputPath: import_path.join(outDir, `${name}-${
|
|
553
|
+
outputPath: import_path.join(outDir, `${name}-${version2}.msi`),
|
|
547
554
|
manufacturer: config.author || "Unknown",
|
|
548
555
|
architecture: opts.architecture || windowsArchitecture(process.arch),
|
|
549
556
|
certificatePath: opts.certificatePath,
|
|
@@ -560,9 +567,9 @@ async function packageWindows(config, outDir) {
|
|
|
560
567
|
if (opts.zip || opts.msi === false) {
|
|
561
568
|
const zipResult = await createZIP({
|
|
562
569
|
name,
|
|
563
|
-
version,
|
|
570
|
+
version: version2,
|
|
564
571
|
binaryPath: config.binaryPath,
|
|
565
|
-
outputPath: import_path.join(outDir, `${name}-${
|
|
572
|
+
outputPath: import_path.join(outDir, `${name}-${version2}-windows.zip`)
|
|
566
573
|
});
|
|
567
574
|
results.push({
|
|
568
575
|
success: zipResult.success,
|
|
@@ -576,14 +583,14 @@ async function packageWindows(config, outDir) {
|
|
|
576
583
|
}
|
|
577
584
|
async function packageLinux(config, outDir) {
|
|
578
585
|
const results = [];
|
|
579
|
-
const { name, version } = config;
|
|
586
|
+
const { name, version: version2 } = config;
|
|
580
587
|
const opts = config.linux || {};
|
|
581
588
|
if (opts.deb !== false) {
|
|
582
589
|
const debResult = await createDEB({
|
|
583
590
|
name,
|
|
584
|
-
version,
|
|
591
|
+
version: version2,
|
|
585
592
|
binaryPath: config.binaryPath,
|
|
586
|
-
outputPath: import_path.join(outDir, `${name}_${
|
|
593
|
+
outputPath: import_path.join(outDir, `${name}_${version2}_amd64.deb`),
|
|
587
594
|
description: config.description || "",
|
|
588
595
|
maintainer: config.author || "Unknown",
|
|
589
596
|
dependencies: opts.debDependencies || ["libgtk-3-0", "libwebkit2gtk-4.1-37"]
|
|
@@ -599,9 +606,9 @@ async function packageLinux(config, outDir) {
|
|
|
599
606
|
if (opts.rpm) {
|
|
600
607
|
const rpmResult = await createRPM({
|
|
601
608
|
name,
|
|
602
|
-
version,
|
|
609
|
+
version: version2,
|
|
603
610
|
binaryPath: config.binaryPath,
|
|
604
|
-
outputPath: import_path.join(outDir, `${name}-${
|
|
611
|
+
outputPath: import_path.join(outDir, `${name}-${version2}-1.x86_64.rpm`),
|
|
605
612
|
description: config.description || "",
|
|
606
613
|
requires: opts.rpmDependencies || ["gtk3", "webkit2gtk3"]
|
|
607
614
|
});
|
|
@@ -616,9 +623,9 @@ async function packageLinux(config, outDir) {
|
|
|
616
623
|
if (opts.appImage) {
|
|
617
624
|
const appImageResult = await createAppImage({
|
|
618
625
|
name,
|
|
619
|
-
version,
|
|
626
|
+
version: version2,
|
|
620
627
|
binaryPath: config.binaryPath,
|
|
621
|
-
outputPath: import_path.join(outDir, `${name}-${
|
|
628
|
+
outputPath: import_path.join(outDir, `${name}-${version2}-x86_64.AppImage`),
|
|
622
629
|
iconPath: config.iconPath
|
|
623
630
|
});
|
|
624
631
|
results.push({
|
|
@@ -2809,7 +2816,7 @@ class SystemTray {
|
|
|
2809
2816
|
}
|
|
2810
2817
|
async setIcon(icon) {
|
|
2811
2818
|
this._options.icon = icon;
|
|
2812
|
-
await this._call("setIcon", icon);
|
|
2819
|
+
await this._call("setIcon", JSON.stringify({ icon }));
|
|
2813
2820
|
}
|
|
2814
2821
|
async setTooltip(tooltip) {
|
|
2815
2822
|
this._options.tooltip = tooltip;
|
|
@@ -2823,6 +2830,9 @@ class SystemTray {
|
|
|
2823
2830
|
if (!processedItem.id && processedItem.label) {
|
|
2824
2831
|
processedItem.id = `menu_${index}_${Date.now()}`;
|
|
2825
2832
|
}
|
|
2833
|
+
if (!processedItem.action && processedItem.id && processedItem.type !== "separator") {
|
|
2834
|
+
processedItem.action = processedItem.id;
|
|
2835
|
+
}
|
|
2826
2836
|
if (processedItem.submenu) {
|
|
2827
2837
|
processedItem.submenu = processItems(processedItem.submenu);
|
|
2828
2838
|
}
|
|
@@ -2885,9 +2895,9 @@ class SystemTray {
|
|
|
2885
2895
|
return new Promise((resolve, reject) => {
|
|
2886
2896
|
try {
|
|
2887
2897
|
window.webkit.messageHandlers.craft.postMessage({
|
|
2888
|
-
|
|
2889
|
-
action,
|
|
2890
|
-
|
|
2898
|
+
t: "tray",
|
|
2899
|
+
a: action,
|
|
2900
|
+
d: typeof data === "string" ? data : JSON.stringify(data)
|
|
2891
2901
|
});
|
|
2892
2902
|
resolve(undefined);
|
|
2893
2903
|
} catch (error) {
|
|
@@ -4167,8 +4177,8 @@ var crypto = {
|
|
|
4167
4177
|
if (combined.length < 1 + SALT_LEN + IV_LEN + TAG_LEN) {
|
|
4168
4178
|
throw new CraftCryptoError("Ciphertext too short to be valid");
|
|
4169
4179
|
}
|
|
4170
|
-
const
|
|
4171
|
-
if (
|
|
4180
|
+
const version2 = combined[0];
|
|
4181
|
+
if (version2 !== FORMAT_VERSION) {
|
|
4172
4182
|
throw new LegacyCiphertextError;
|
|
4173
4183
|
}
|
|
4174
4184
|
const salt = combined.slice(1, 1 + SALT_LEN);
|
|
@@ -5133,6 +5143,13 @@ var lifecycle = {
|
|
|
5133
5143
|
return () => document.removeEventListener("visibilitychange", handler);
|
|
5134
5144
|
}
|
|
5135
5145
|
};
|
|
5146
|
+
function bridgeNotification(options, now = Date.now()) {
|
|
5147
|
+
const { scheduleAt, ...rest } = options;
|
|
5148
|
+
if (scheduleAt === undefined)
|
|
5149
|
+
return rest;
|
|
5150
|
+
const wait = Math.ceil(scheduleAt - now);
|
|
5151
|
+
return wait > 0 ? { ...rest, delay: wait } : rest;
|
|
5152
|
+
}
|
|
5136
5153
|
var notifications = {
|
|
5137
5154
|
async show(options) {
|
|
5138
5155
|
const craft = getCraftMobile();
|
|
@@ -5146,7 +5163,7 @@ var notifications = {
|
|
|
5146
5163
|
async schedule(options) {
|
|
5147
5164
|
const craft = getCraftMobile();
|
|
5148
5165
|
if (typeof window !== "undefined" && craft?.notifications) {
|
|
5149
|
-
return craft.notifications.schedule(options);
|
|
5166
|
+
return craft.notifications.schedule(bridgeNotification(options));
|
|
5150
5167
|
}
|
|
5151
5168
|
throw new Error("Scheduled notifications not available in web");
|
|
5152
5169
|
},
|
|
@@ -10822,14 +10839,14 @@ async function updaterCommand(args) {
|
|
|
10822
10839
|
switch (subcommand) {
|
|
10823
10840
|
case "check": {
|
|
10824
10841
|
const url = rest[0];
|
|
10825
|
-
const
|
|
10842
|
+
const version2 = rest[1] || "0.0.0";
|
|
10826
10843
|
if (!url) {
|
|
10827
10844
|
console.error("Usage: craft updater check <update-url> [current-version]");
|
|
10828
10845
|
process.exit(1);
|
|
10829
10846
|
}
|
|
10830
10847
|
const updater = new AutoUpdater({
|
|
10831
10848
|
updateUrl: url,
|
|
10832
|
-
currentVersion:
|
|
10849
|
+
currentVersion: version2,
|
|
10833
10850
|
appPath: process.cwd(),
|
|
10834
10851
|
autoDownload: false
|
|
10835
10852
|
});
|
|
@@ -10863,14 +10880,14 @@ ${update.releaseNotes}`);
|
|
|
10863
10880
|
break;
|
|
10864
10881
|
}
|
|
10865
10882
|
case "generate-manifest": {
|
|
10866
|
-
const
|
|
10883
|
+
const version2 = rest[0];
|
|
10867
10884
|
const outputPath = rest[1] || "update.json";
|
|
10868
|
-
if (!
|
|
10885
|
+
if (!version2) {
|
|
10869
10886
|
console.error("Usage: craft updater generate-manifest <version> [output]");
|
|
10870
10887
|
process.exit(1);
|
|
10871
10888
|
}
|
|
10872
10889
|
const manifest = {
|
|
10873
|
-
version,
|
|
10890
|
+
version: version2,
|
|
10874
10891
|
releaseDate: new Date().toISOString(),
|
|
10875
10892
|
platforms: {}
|
|
10876
10893
|
};
|
|
@@ -10902,21 +10919,13 @@ var updater_default = AutoUpdater;
|
|
|
10902
10919
|
// src/index.ts
|
|
10903
10920
|
var execFileAsync2 = import_node_util.promisify(import_node_child_process.execFile);
|
|
10904
10921
|
var versionMismatchWarned = false;
|
|
10905
|
-
function getSdkVersion() {
|
|
10906
|
-
try {
|
|
10907
|
-
const pkgPath = import_node_path.join("/home/runner/work/craft/craft/packages/typescript/src", "..", "package.json");
|
|
10908
|
-
return JSON.parse(import_node_fs2.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
10909
|
-
} catch {
|
|
10910
|
-
return "unknown";
|
|
10911
|
-
}
|
|
10912
|
-
}
|
|
10913
10922
|
async function probeBinaryVersion(craftPath) {
|
|
10914
10923
|
if (versionMismatchWarned)
|
|
10915
10924
|
return;
|
|
10916
10925
|
try {
|
|
10917
10926
|
const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
|
|
10918
10927
|
const nativeVersion = parseCraftVersionOutput(stdout);
|
|
10919
|
-
const sdkVersion =
|
|
10928
|
+
const sdkVersion = SDK_VERSION;
|
|
10920
10929
|
if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
|
|
10921
10930
|
versionMismatchWarned = true;
|
|
10922
10931
|
console.warn(`⚠️ Craft binary/SDK version mismatch: SDK=${sdkVersion}, native=${nativeVersion}.
|
|
@@ -10926,7 +10935,27 @@ async function probeBinaryVersion(craftPath) {
|
|
|
10926
10935
|
}
|
|
10927
10936
|
function parseCraftVersionOutput(output) {
|
|
10928
10937
|
const firstLine = output.trim().split(/\r?\n/, 1)[0] ?? "";
|
|
10929
|
-
|
|
10938
|
+
const withoutName = firstLine.replace(/^craft(?:\s+version)?[\s/]+/i, "").trim();
|
|
10939
|
+
const firstToken = withoutName.split(/\s+/, 1)[0] ?? "";
|
|
10940
|
+
return firstToken.replace(/^v/, "").trim();
|
|
10941
|
+
}
|
|
10942
|
+
var STDERR_TAIL_LIMIT = 4000;
|
|
10943
|
+
function craftProcessFailedMessage(code, stderr, quiet) {
|
|
10944
|
+
const said = stderr.trim();
|
|
10945
|
+
const header = `❌ Craft process exited with code ${code}`;
|
|
10946
|
+
if (said)
|
|
10947
|
+
return `${header}
|
|
10948
|
+
|
|
10949
|
+
${said}`;
|
|
10950
|
+
const where = quiet ? "It wrote nothing to stderr. Re-run without `quiet` to see its full output." : "Check the console output above for more details.";
|
|
10951
|
+
return `${header}
|
|
10952
|
+
|
|
10953
|
+
` + `This may indicate:
|
|
10954
|
+
` + ` • Invalid window configuration
|
|
10955
|
+
` + ` • Malformed HTML content
|
|
10956
|
+
` + ` • System resource constraints
|
|
10957
|
+
|
|
10958
|
+
` + where;
|
|
10930
10959
|
}
|
|
10931
10960
|
function detectDevMode() {
|
|
10932
10961
|
if (typeof process === "undefined")
|
|
@@ -10989,8 +11018,14 @@ class CraftApp {
|
|
|
10989
11018
|
const args = this.buildArgs();
|
|
10990
11019
|
const craftPath = await this.findCraftBinary();
|
|
10991
11020
|
probeBinaryVersion(craftPath);
|
|
11021
|
+
const quiet = this.config.quiet === true;
|
|
10992
11022
|
this.process = import_node_child_process.spawn(craftPath, args, {
|
|
10993
|
-
stdio:
|
|
11023
|
+
stdio: quiet ? ["ignore", "ignore", "pipe"] : "inherit",
|
|
11024
|
+
env: { ...process.env, [CRAFT_CLI_SPAWN_MARKER]: craftPath }
|
|
11025
|
+
});
|
|
11026
|
+
let stderrTail = "";
|
|
11027
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
11028
|
+
stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_LIMIT);
|
|
10994
11029
|
});
|
|
10995
11030
|
return new Promise((resolve, reject) => {
|
|
10996
11031
|
this.process?.on("error", (error) => {
|
|
@@ -11010,18 +11045,11 @@ class CraftApp {
|
|
|
11010
11045
|
` + `For troubleshooting, visit:
|
|
11011
11046
|
` + " https://github.com/home-lang/craft/issues"));
|
|
11012
11047
|
});
|
|
11013
|
-
this.process?.on("
|
|
11048
|
+
this.process?.on("close", (code) => {
|
|
11014
11049
|
if (code === 0 || code === null) {
|
|
11015
11050
|
resolve();
|
|
11016
11051
|
} else {
|
|
11017
|
-
reject(new Error(
|
|
11018
|
-
|
|
11019
|
-
` + `This may indicate:
|
|
11020
|
-
` + ` • Invalid window configuration
|
|
11021
|
-
` + ` • Malformed HTML content
|
|
11022
|
-
` + ` • System resource constraints
|
|
11023
|
-
|
|
11024
|
-
` + "Check the console output above for more details."));
|
|
11052
|
+
reject(new Error(craftProcessFailedMessage(code, stderrTail, quiet)));
|
|
11025
11053
|
}
|
|
11026
11054
|
});
|
|
11027
11055
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,27 @@
|
|
|
3
3
|
* TypeScript SDK powered by Bun
|
|
4
4
|
*/
|
|
5
5
|
import type { AppConfig, WindowOptions } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* The version a `craft --version` first line reports, whichever form it takes:
|
|
8
|
+
* `craft version 0.0.92`, `craft/0.0.92 darwin-arm64 bun-v1.4.1`,
|
|
9
|
+
* `craft 0.0.92`, `v0.0.92`, or a bare `0.0.92`.
|
|
10
|
+
*
|
|
11
|
+
* The slash form is the CLI's own, and used to survive the strip intact, so
|
|
12
|
+
* the whole line was compared against a semver and never matched — two
|
|
13
|
+
* identical versions reported as drift, on the one path where the real fault
|
|
14
|
+
* was that PATH had resolved to the CLI at all (#236).
|
|
15
|
+
*/
|
|
6
16
|
export declare function parseCraftVersionOutput(output: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The rejection for a child that exited non-zero.
|
|
19
|
+
*
|
|
20
|
+
* Whatever the child managed to say comes first, because it is the only part
|
|
21
|
+
* that can name the actual fault. `quiet` used to map to `stdio: 'ignore'`,
|
|
22
|
+
* which threw that away and then told the caller to read output that no
|
|
23
|
+
* longer existed — leaving `exited with code 1` as the whole of the error
|
|
24
|
+
* (#236).
|
|
25
|
+
*/
|
|
26
|
+
export declare function craftProcessFailedMessage(code: number | null, stderr: string, quiet: boolean): string;
|
|
7
27
|
export { packageApp, pack, type PackageConfig, type PackageResult } from './package.js';
|
|
8
28
|
export { craftBinaryNotFoundMessage, resolveCraftBinary } from './binary-resolver.js';
|
|
9
29
|
export * from './utils/index.js';
|