pakstr 0.8.6 → 0.9.0
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/android-template/app/build.gradle.kts +11 -3
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +2 -0
- package/android-template/app/src/main/java/com/pakstr/app/debug/DebugReportExporter.kt +1 -1
- package/android-template/app/src/main/java/com/pakstr/app/debug/DeveloperToolsActivity.kt +1 -1
- package/android-template/app/src/main/java/com/pakstr/app/debug/DeviceDeveloperState.kt +23 -0
- package/android-template/app/src/main/java/com/pakstr/app/debug/WebViewDebugManager.kt +31 -4
- package/android-template/app/src/test/java/com/pakstr/app/debug/DeviceDeveloperStateTest.kt +21 -0
- package/android-template/app/src/test/java/com/pakstr/app/debug/WebViewDebugManagerTest.kt +84 -0
- package/bin/cli.js +11 -6
- package/dist/cli.js +53 -16
- package/dist/commands/build.js +42 -114
- package/dist/commands/init.js +191 -0
- package/dist/commands/publish.js +137 -0
- package/dist/commands/run.js +95 -0
- package/dist/commands/sign.js +41 -0
- package/dist/core/androidProject.js +70 -0
- package/dist/core/appIdentity.js +7 -0
- package/dist/core/blossom.js +122 -0
- package/dist/core/dotenv.js +51 -0
- package/dist/core/nostr.js +153 -0
- package/dist/core/pakstrConfig.js +206 -0
- package/dist/core/zapStore.js +221 -0
- package/dist/runners/DockerGradleRunner.js +38 -128
- package/dist/runners/DockerSignRunner.js +182 -0
- package/dist/runners/LocalGradleRunner.js +26 -129
- package/dist/runners/createRunner.js +4 -4
- package/package.json +7 -1
- package/dist/android/releaseVerification.js +0 -164
- package/dist/config.js +0 -4
- package/dist/core/buildContext.js +0 -102
- package/dist/core/config.js +0 -24
- package/dist/core/copyFile.js +0 -17
- package/dist/core/manifest.js +0 -47
- package/dist/core/releaseSigning.js +0 -134
- package/dist/core/resolveProjectInputs.js +0 -20
- package/dist/core/validateProject.js +0 -21
- package/dist/core/zeroConfig.js +0 -97
- package/dist/runtime/runtimeConfig.js +0 -22
|
@@ -21,8 +21,11 @@ android {
|
|
|
21
21
|
|
|
22
22
|
signingConfigs {
|
|
23
23
|
create("release") {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Signing is optional: pakstr build produces an UNSIGNED release APK and
|
|
25
|
+
// pakstr sign signs it with apksigner afterwards. Only wire up the
|
|
26
|
+
// Gradle signingConfig when PAKSTR_ANDROID_KEYSTORE_PATH is present.
|
|
27
|
+
val keystorePath = System.getenv("PAKSTR_ANDROID_KEYSTORE_PATH")
|
|
28
|
+
if (releaseRequested && keystorePath != null && keystorePath.isNotEmpty()) {
|
|
26
29
|
val keystoreFile = File(keystorePath)
|
|
27
30
|
if (!keystoreFile.isFile) {
|
|
28
31
|
throw GradleException("Release signing keystore path is not a file")
|
|
@@ -53,7 +56,12 @@ android {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
release {
|
|
56
|
-
|
|
59
|
+
// Only attach the signing config when a keystore was provided;
|
|
60
|
+
// otherwise emit an unsigned release APK for pakstr sign to sign.
|
|
61
|
+
val keystorePath = System.getenv("PAKSTR_ANDROID_KEYSTORE_PATH")
|
|
62
|
+
if (keystorePath != null && keystorePath.isNotEmpty()) {
|
|
63
|
+
signingConfig = signingConfigs.getByName("release")
|
|
64
|
+
}
|
|
57
65
|
isMinifyEnabled = true
|
|
58
66
|
isShrinkResources = true
|
|
59
67
|
proguardFiles(
|
|
@@ -209,6 +209,7 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
209
209
|
private fun setupSupportUnlockGesture() {
|
|
210
210
|
supportUnlockGestureLayout.onUnlock = {
|
|
211
211
|
SupportUnlock.unlock(this)
|
|
212
|
+
WebViewDebugManager.enable(this)
|
|
212
213
|
Toast.makeText(
|
|
213
214
|
this,
|
|
214
215
|
R.string.diagnostics_mode_enabled,
|
|
@@ -289,6 +290,7 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
289
290
|
override fun onResume() {
|
|
290
291
|
super.onResume()
|
|
291
292
|
|
|
293
|
+
WebViewDebugManager.enable(this)
|
|
292
294
|
if (::fab.isInitialized) {
|
|
293
295
|
showDebugFab()
|
|
294
296
|
}
|
|
@@ -129,7 +129,7 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
private fun isWebViewDebugEnabled(): Boolean {
|
|
132
|
-
return
|
|
132
|
+
return WebViewDebugManager.isInspectionAllowed(this)
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
private fun setupLogActions() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.pakstr.app.debug
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.provider.Settings
|
|
5
|
+
|
|
6
|
+
object DeviceDeveloperState {
|
|
7
|
+
|
|
8
|
+
fun isAdbEnabled(context: Context): Boolean {
|
|
9
|
+
return isAdbEnabled {
|
|
10
|
+
Settings.Global.getInt(
|
|
11
|
+
context.contentResolver,
|
|
12
|
+
Settings.Global.ADB_ENABLED,
|
|
13
|
+
0
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
internal fun isAdbEnabled(
|
|
19
|
+
readSetting: () -> Int
|
|
20
|
+
): Boolean {
|
|
21
|
+
return readSetting() == 1
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -6,15 +6,36 @@ import com.pakstr.app.BuildConfig
|
|
|
6
6
|
|
|
7
7
|
object WebViewDebugManager {
|
|
8
8
|
|
|
9
|
+
internal var debuggingStateApplier: (Boolean) -> Unit =
|
|
10
|
+
WebView::setWebContentsDebuggingEnabled
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
fun isInspectionAllowed(
|
|
14
|
+
context: Context
|
|
15
|
+
): Boolean {
|
|
16
|
+
return isInspectionAllowed(
|
|
17
|
+
isDebugBuild = BuildConfig.DEBUG,
|
|
18
|
+
isSupportUnlocked = SupportUnlock.isActive(context),
|
|
19
|
+
isAdbEnabled = DeviceDeveloperState.isAdbEnabled(context)
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
internal fun isInspectionAllowed(
|
|
24
|
+
isDebugBuild: Boolean,
|
|
25
|
+
isSupportUnlocked: Boolean,
|
|
26
|
+
isAdbEnabled: Boolean
|
|
27
|
+
): Boolean {
|
|
28
|
+
return isDebugBuild ||
|
|
29
|
+
(isSupportUnlocked && isAdbEnabled)
|
|
30
|
+
}
|
|
9
31
|
|
|
10
32
|
fun enable(
|
|
11
33
|
context: Context
|
|
12
34
|
) {
|
|
13
35
|
|
|
14
|
-
val enabled =
|
|
15
|
-
SupportUnlock.isActive(context)
|
|
36
|
+
val enabled = isInspectionAllowed(context)
|
|
16
37
|
|
|
17
|
-
|
|
38
|
+
setWebContentsDebuggingEnabled(enabled)
|
|
18
39
|
|
|
19
40
|
AppDebugLogger.log(
|
|
20
41
|
context,
|
|
@@ -22,4 +43,10 @@ object WebViewDebugManager {
|
|
|
22
43
|
"WebView debugging enabled: $enabled"
|
|
23
44
|
)
|
|
24
45
|
}
|
|
25
|
-
|
|
46
|
+
|
|
47
|
+
internal fun setWebContentsDebuggingEnabled(
|
|
48
|
+
enabled: Boolean
|
|
49
|
+
) {
|
|
50
|
+
debuggingStateApplier(enabled)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package com.pakstr.app.debug
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class DeviceDeveloperStateTest {
|
|
8
|
+
|
|
9
|
+
@Test
|
|
10
|
+
fun adb_is_enabled_only_for_setting_value_one() {
|
|
11
|
+
assertTrue(
|
|
12
|
+
DeviceDeveloperState.isAdbEnabled { 1 }
|
|
13
|
+
)
|
|
14
|
+
assertFalse(
|
|
15
|
+
DeviceDeveloperState.isAdbEnabled { 0 }
|
|
16
|
+
)
|
|
17
|
+
assertFalse(
|
|
18
|
+
DeviceDeveloperState.isAdbEnabled { 2 }
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.pakstr.app.debug
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class WebViewDebugManagerTest {
|
|
8
|
+
|
|
9
|
+
@Test
|
|
10
|
+
fun debug_build_allows_inspection_without_release_prerequisites() {
|
|
11
|
+
assertTrue(
|
|
12
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
13
|
+
isDebugBuild = true,
|
|
14
|
+
isSupportUnlocked = false,
|
|
15
|
+
isAdbEnabled = false
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
fun release_allows_inspection_with_support_unlock_and_adb() {
|
|
22
|
+
assertTrue(
|
|
23
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
24
|
+
isDebugBuild = false,
|
|
25
|
+
isSupportUnlocked = true,
|
|
26
|
+
isAdbEnabled = true
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun release_rejects_support_unlock_without_adb() {
|
|
33
|
+
assertFalse(
|
|
34
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
35
|
+
isDebugBuild = false,
|
|
36
|
+
isSupportUnlocked = true,
|
|
37
|
+
isAdbEnabled = false
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
fun release_rejects_adb_without_support_unlock() {
|
|
44
|
+
assertFalse(
|
|
45
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
46
|
+
isDebugBuild = false,
|
|
47
|
+
isSupportUnlocked = false,
|
|
48
|
+
isAdbEnabled = true
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
fun release_rejects_inspection_without_either_prerequisite() {
|
|
55
|
+
assertFalse(
|
|
56
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
57
|
+
isDebugBuild = false,
|
|
58
|
+
isSupportUnlocked = false,
|
|
59
|
+
isAdbEnabled = false
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test
|
|
65
|
+
fun allowed_release_state_is_applied_to_webview_debugging() {
|
|
66
|
+
val appliedStates = mutableListOf<Boolean>()
|
|
67
|
+
val previous = WebViewDebugManager.debuggingStateApplier
|
|
68
|
+
WebViewDebugManager.debuggingStateApplier = appliedStates::add
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
val allowed =
|
|
72
|
+
WebViewDebugManager.isInspectionAllowed(
|
|
73
|
+
isDebugBuild = false,
|
|
74
|
+
isSupportUnlocked = true,
|
|
75
|
+
isAdbEnabled = true
|
|
76
|
+
)
|
|
77
|
+
WebViewDebugManager.setWebContentsDebuggingEnabled(allowed)
|
|
78
|
+
} finally {
|
|
79
|
+
WebViewDebugManager.debuggingStateApplier = previous
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
assertTrue(appliedStates.single())
|
|
83
|
+
}
|
|
84
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
const { runCLI } = require("../dist/cli.js");
|
|
4
4
|
|
|
5
5
|
runCLI(process.argv.slice(2))
|
|
6
|
-
|
|
6
|
+
.then(() => {
|
|
7
|
+
// runCLI sets process.exitCode on user-facing errors; nothing to do here.
|
|
8
|
+
})
|
|
7
9
|
.catch((err) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
// Known, human-readable errors: print just the message.
|
|
11
|
+
const name = err && err.name;
|
|
12
|
+
if (name === "ConfigError" || name === "NsecError" || err && err.envVar !== undefined) {
|
|
13
|
+
console.error("Error:", err.message);
|
|
14
|
+
} else {
|
|
15
|
+
console.error("Error:", err && err.message ? err.message : err);
|
|
16
|
+
}
|
|
11
17
|
process.exit(1);
|
|
12
|
-
|
|
13
|
-
});
|
|
18
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runCLI = runCLI;
|
|
7
7
|
const build_1 = require("./commands/build");
|
|
8
|
+
const sign_1 = require("./commands/sign");
|
|
9
|
+
const publish_1 = require("./commands/publish");
|
|
10
|
+
const run_1 = require("./commands/run");
|
|
11
|
+
const init_1 = require("./commands/init");
|
|
12
|
+
const dotenv_1 = require("./core/dotenv");
|
|
8
13
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
9
14
|
async function runCLI(argv) {
|
|
15
|
+
// Auto-load a local `.env` (gitignored) so `pakstr run` works out of the box
|
|
16
|
+
// after `pakstr init`. Existing env vars (e.g. CI secrets) always win.
|
|
17
|
+
(0, dotenv_1.loadDotEnv)();
|
|
10
18
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
11
19
|
printHelp();
|
|
12
20
|
return;
|
|
@@ -16,41 +24,70 @@ async function runCLI(argv) {
|
|
|
16
24
|
return;
|
|
17
25
|
}
|
|
18
26
|
const command = argv[0];
|
|
19
|
-
const
|
|
20
|
-
const args = argv.slice(2);
|
|
27
|
+
const args = argv.slice(1);
|
|
21
28
|
if (!command) {
|
|
22
29
|
printHelp();
|
|
23
30
|
return;
|
|
24
31
|
}
|
|
32
|
+
const configFlag = extractFlag(args, "--config");
|
|
25
33
|
switch (command) {
|
|
34
|
+
case "init":
|
|
35
|
+
(0, init_1.initCommand)({ force: args.includes("--force"), out: configFlag });
|
|
36
|
+
return;
|
|
26
37
|
case "build":
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
await (0, build_1.buildCommand)(configFlag ?? undefined);
|
|
39
|
+
return;
|
|
40
|
+
case "sign":
|
|
41
|
+
await (0, sign_1.signCommand)(configFlag ?? undefined);
|
|
42
|
+
return;
|
|
43
|
+
case "publish":
|
|
44
|
+
await (0, publish_1.publishCommand)(configFlag ?? undefined, {
|
|
45
|
+
dryRun: args.includes("--dry-run"),
|
|
46
|
+
verify: args.includes("--verify"),
|
|
47
|
+
outJson: extractFlag(args, "--out-json"),
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
case "run":
|
|
51
|
+
await (0, run_1.runCommand)(configFlag ?? undefined, {
|
|
52
|
+
verifyPublish: args.includes("--verify-publish"),
|
|
53
|
+
publishOutJson: extractFlag(args, "--publish-out-json"),
|
|
54
|
+
});
|
|
33
55
|
return;
|
|
34
56
|
default:
|
|
35
|
-
console.error(
|
|
57
|
+
console.error(`Unknown command: ${command}`);
|
|
36
58
|
printHelp();
|
|
37
59
|
process.exitCode = 1;
|
|
38
60
|
return;
|
|
39
61
|
}
|
|
40
62
|
}
|
|
63
|
+
function extractFlag(args, flag) {
|
|
64
|
+
const i = args.indexOf(flag);
|
|
65
|
+
if (i === -1)
|
|
66
|
+
return undefined;
|
|
67
|
+
return args[i + 1];
|
|
68
|
+
}
|
|
41
69
|
function printHelp() {
|
|
42
70
|
console.log(`
|
|
43
|
-
|
|
71
|
+
Pakstr — one-step nsec -> build -> sign -> publish.
|
|
44
72
|
|
|
45
73
|
Usage:
|
|
74
|
+
pakstr init [--force] [--config <path>]
|
|
75
|
+
pakstr build [--config <path>]
|
|
76
|
+
pakstr sign [--config <path>]
|
|
77
|
+
pakstr publish [--config <path>] [--dry-run] [--verify] [--out-json <path>]
|
|
78
|
+
pakstr run [--config <path>] [--verify-publish] [--publish-out-json <path>]
|
|
46
79
|
|
|
47
|
-
pakstr
|
|
80
|
+
pakstr --version
|
|
81
|
+
pakstr --help
|
|
48
82
|
|
|
49
|
-
|
|
83
|
+
Config:
|
|
84
|
+
pakstr.yaml is the single source of truth (see docs/SPEC.md §3).
|
|
85
|
+
Secrets live in env vars, never in the config file:
|
|
86
|
+
PAKSTR_NSEC nsec that derives the APK signing key (and publish auth by default)
|
|
87
|
+
PAKSTR_PUBLISH_NSEC optional separate publish nsec (see publish.publishKey)
|
|
50
88
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
--out ./build/app.apk
|
|
89
|
+
Example:
|
|
90
|
+
pakstr init
|
|
91
|
+
PAKSTR_NSEC=nsec1... pakstr run
|
|
55
92
|
`);
|
|
56
93
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -6,95 +6,70 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.buildCommand = buildCommand;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const child_process_1 = require("child_process");
|
|
10
|
-
const generatedProjectVerification_1 = require("../android/generatedProjectVerification");
|
|
11
|
-
const gradle_1 = require("../android/gradle");
|
|
12
|
-
const branding_1 = require("../android/branding");
|
|
13
|
-
const icon_1 = require("../android/icon");
|
|
14
|
-
const permissions_1 = require("../android/permissions");
|
|
15
|
-
const releaseWorkspace_1 = require("../android/releaseWorkspace");
|
|
16
|
-
const splash_1 = require("../android/splash");
|
|
17
9
|
const template_1 = require("../android/template");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const validateProject_1 = require("../core/validateProject");
|
|
10
|
+
const releaseWorkspace_1 = require("../android/releaseWorkspace");
|
|
11
|
+
const pakstrConfig_1 = require("../core/pakstrConfig");
|
|
12
|
+
const androidProject_1 = require("../core/androidProject");
|
|
22
13
|
const createRunner_1 = require("../runners/createRunner");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
console.log("
|
|
32
|
-
console.log("
|
|
33
|
-
console.log("🌐 Web:", ctx.dist);
|
|
14
|
+
/**
|
|
15
|
+
* `pakstr build` — produce an UNSIGNED release APK at `build.out`.
|
|
16
|
+
* Spec §5.2. No nsec required.
|
|
17
|
+
*/
|
|
18
|
+
async function buildCommand(configPath) {
|
|
19
|
+
const config = (0, pakstrConfig_1.loadPakstrConfig)(configPath);
|
|
20
|
+
const finalPath = path_1.default.resolve(config.build.out);
|
|
21
|
+
console.log("\n🚀 pakstr build");
|
|
22
|
+
console.log("📦 App:", config.app.appName);
|
|
23
|
+
console.log("🌐 Web:", config.build.web);
|
|
34
24
|
console.log("📱 Out:", finalPath);
|
|
35
|
-
console.log("🏗️
|
|
36
|
-
console.log("🚀 BUILD MODE:", mode.toUpperCase());
|
|
37
|
-
(0, validateProject_1.validateProject)(ctx.dist, manifest);
|
|
25
|
+
console.log("🏗️ Builder:", config.build.builder);
|
|
38
26
|
validateOutputParent(finalPath);
|
|
39
|
-
|
|
40
|
-
removeExistingOutput(finalPath);
|
|
41
|
-
}
|
|
42
|
-
const signingInput = mode === "release" ? (0, releaseSigning_1.readReleaseSigningInput)() : undefined;
|
|
27
|
+
removeExistingOutput(finalPath);
|
|
43
28
|
const templateRoot = await (0, template_1.ensureTemplate)();
|
|
44
|
-
|
|
45
|
-
let signing;
|
|
29
|
+
const workspace = (0, releaseWorkspace_1.createReleaseWorkspace)(templateRoot);
|
|
46
30
|
let stagedPath;
|
|
47
31
|
let primaryError;
|
|
48
|
-
let cleanupError;
|
|
49
32
|
try {
|
|
50
|
-
const androidRoot =
|
|
51
|
-
? (workspace = (0, releaseWorkspace_1.createReleaseWorkspace)(templateRoot)).androidRoot
|
|
52
|
-
: templateRoot;
|
|
33
|
+
const androidRoot = workspace.androidRoot;
|
|
53
34
|
console.log("📱 Android:", androidRoot);
|
|
54
|
-
await
|
|
55
|
-
if (signingInput) {
|
|
56
|
-
signing = (0, releaseSigning_1.createReleaseSigningContext)(signingInput);
|
|
57
|
-
signingInput.keystoreBase64 = "";
|
|
58
|
-
}
|
|
59
|
-
console.log("\n⚙️ Running build runner...");
|
|
60
|
-
const result = await (0, createRunner_1.createRunner)(config).build({
|
|
35
|
+
await (0, androidProject_1.prepareAndroidProject)({
|
|
61
36
|
androidRoot,
|
|
62
|
-
|
|
37
|
+
webDir: config.build.web,
|
|
38
|
+
app: config.app,
|
|
39
|
+
configDir: config.configDir,
|
|
40
|
+
});
|
|
41
|
+
console.log("\n⚙️ Running build runner...");
|
|
42
|
+
const result = await (0, createRunner_1.createRunner)(config.build.builder).build({
|
|
43
|
+
androidRoot,
|
|
44
|
+
mode: "release",
|
|
63
45
|
expectedMetadata: {
|
|
64
|
-
applicationId:
|
|
65
|
-
versionCode:
|
|
66
|
-
versionName:
|
|
67
|
-
appLabel: appName,
|
|
46
|
+
applicationId: config.app.appId,
|
|
47
|
+
versionCode: config.app.versionCode,
|
|
48
|
+
versionName: config.app.versionName,
|
|
49
|
+
appLabel: config.app.appName,
|
|
68
50
|
},
|
|
69
|
-
signing,
|
|
70
51
|
});
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
console.warn("ℹ️ CI/Docker release verification will be used.");
|
|
52
|
+
if (!result.unsigned) {
|
|
53
|
+
throw new Error("Build runner did not produce an unsigned release APK");
|
|
74
54
|
}
|
|
75
55
|
stagedPath = stageArtifact(result.artifactPath, finalPath);
|
|
76
56
|
}
|
|
77
57
|
catch (error) {
|
|
78
58
|
primaryError = error;
|
|
79
59
|
}
|
|
60
|
+
let cleanupError;
|
|
80
61
|
try {
|
|
81
|
-
|
|
62
|
+
workspace.cleanup();
|
|
82
63
|
}
|
|
83
64
|
catch (error) {
|
|
84
65
|
cleanupError = error;
|
|
85
66
|
}
|
|
86
|
-
try {
|
|
87
|
-
workspace?.cleanup();
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
cleanupError ??= error;
|
|
91
|
-
}
|
|
92
67
|
if (primaryError || cleanupError) {
|
|
93
68
|
removeStagedArtifact(stagedPath);
|
|
94
69
|
removeExistingOutput(finalPath);
|
|
95
70
|
if (primaryError) {
|
|
96
71
|
if (cleanupError) {
|
|
97
|
-
console.error("
|
|
72
|
+
console.error("Workspace cleanup also failed:", safeErrorMessage(cleanupError));
|
|
98
73
|
}
|
|
99
74
|
throw primaryError;
|
|
100
75
|
}
|
|
@@ -104,41 +79,17 @@ async function buildCommand(args) {
|
|
|
104
79
|
throw new Error("Build completed without a staged APK");
|
|
105
80
|
}
|
|
106
81
|
publishArtifact(stagedPath, finalPath);
|
|
107
|
-
console.log("\n
|
|
108
|
-
console.log("App:", appName);
|
|
109
|
-
console.log("Mode:", mode.toUpperCase());
|
|
82
|
+
console.log("\n✅ BUILD COMPLETE (unsigned)");
|
|
110
83
|
console.log("📦 Output:", finalPath);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async function prepareAndroidProject(androidRoot, distPath, manifest, appName, manifestDir) {
|
|
114
|
-
const assetsTarget = path_1.default.join(androidRoot, "app/src/main/assets/www");
|
|
115
|
-
if (!fs_1.default.existsSync(distPath)) {
|
|
116
|
-
throw new Error("Web assets not found");
|
|
117
|
-
}
|
|
118
|
-
fs_1.default.rmSync(assetsTarget, { recursive: true, force: true });
|
|
119
|
-
fs_1.default.mkdirSync(assetsTarget, { recursive: true });
|
|
120
|
-
copyFolder(distPath, assetsTarget);
|
|
121
|
-
(0, runtimeConfig_1.generateRuntimeConfig)(assetsTarget, manifest);
|
|
122
|
-
(0, gradle_1.patchGradle)(androidRoot, manifest);
|
|
123
|
-
(0, branding_1.patchAppName)(androidRoot, appName);
|
|
124
|
-
if (manifest.ui?.icon) {
|
|
125
|
-
await (0, icon_1.patchIcon)(androidRoot, manifest.ui.icon, manifest.backgroundColor ?? "#FFFFFF", manifestDir);
|
|
126
|
-
}
|
|
127
|
-
if (manifest.ui?.splash) {
|
|
128
|
-
await (0, splash_1.patchSplash)(androidRoot, manifest.ui.splash.image, manifest.ui.splash.background ?? "#FFFFFF", manifestDir);
|
|
129
|
-
}
|
|
130
|
-
(0, permissions_1.patchPermissions)(androidRoot, manifest);
|
|
131
|
-
(0, generatedProjectVerification_1.verifyGeneratedAndroidProject)(androidRoot, {
|
|
132
|
-
applicationId: manifest.appId,
|
|
133
|
-
versionCode: manifest.versionCode,
|
|
134
|
-
versionName: manifest.versionName,
|
|
135
|
-
appLabel: appName,
|
|
136
|
-
});
|
|
84
|
+
console.log("➡️ Next: pakstr sign");
|
|
85
|
+
return finalPath;
|
|
137
86
|
}
|
|
138
87
|
function validateOutputParent(finalPath) {
|
|
139
88
|
const parent = path_1.default.dirname(finalPath);
|
|
140
89
|
if (!fs_1.default.existsSync(parent) || !fs_1.default.lstatSync(parent).isDirectory()) {
|
|
141
|
-
|
|
90
|
+
// Create it per spec §5.2 ("Create the parent directory of build.out if missing").
|
|
91
|
+
fs_1.default.mkdirSync(parent, { recursive: true });
|
|
92
|
+
return;
|
|
142
93
|
}
|
|
143
94
|
if (fs_1.default.lstatSync(parent).isSymbolicLink()) {
|
|
144
95
|
throw new Error(`Output directory must not be a symbolic link: ${parent}`);
|
|
@@ -157,7 +108,7 @@ function stageArtifact(artifactPath, finalPath) {
|
|
|
157
108
|
fs_1.default.copyFileSync(artifactPath, stagedPath, fs_1.default.constants.COPYFILE_EXCL);
|
|
158
109
|
if (fs_1.default.statSync(stagedPath).size !== fs_1.default.statSync(artifactPath).size) {
|
|
159
110
|
fs_1.default.rmSync(stageDir, { recursive: true, force: true });
|
|
160
|
-
throw new Error("Staged APK size does not match
|
|
111
|
+
throw new Error("Staged APK size does not match built artifact");
|
|
161
112
|
}
|
|
162
113
|
return stagedPath;
|
|
163
114
|
}
|
|
@@ -183,29 +134,6 @@ function removeStagedArtifact(stagedPath) {
|
|
|
183
134
|
return;
|
|
184
135
|
fs_1.default.rmSync(path_1.default.dirname(stagedPath), { recursive: true, force: true });
|
|
185
136
|
}
|
|
186
|
-
function copyFolder(src, dest) {
|
|
187
|
-
for (const entry of fs_1.default.readdirSync(src, { withFileTypes: true })) {
|
|
188
|
-
const srcPath = path_1.default.join(src, entry.name);
|
|
189
|
-
const destPath = path_1.default.join(dest, entry.name);
|
|
190
|
-
if (entry.isDirectory()) {
|
|
191
|
-
fs_1.default.mkdirSync(destPath, { recursive: true });
|
|
192
|
-
copyFolder(srcPath, destPath);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
fs_1.default.copyFileSync(srcPath, destPath);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function openFolder(filePath) {
|
|
200
|
-
if (process.platform !== "darwin" || process.env.CI)
|
|
201
|
-
return;
|
|
202
|
-
try {
|
|
203
|
-
(0, child_process_1.execFileSync)("open", [path_1.default.dirname(filePath)], { stdio: "ignore" });
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
// Opening the output folder is optional.
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
137
|
function safeErrorMessage(error) {
|
|
210
138
|
return error instanceof Error ? error.message : "unknown cleanup error";
|
|
211
139
|
}
|