pakstr 0.12.0 → 0.13.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/src/androidTest/java/com/pakstr/app/debug/AppDebugLoggerTest.kt +28 -0
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +1 -0
- package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +9 -2
- package/android-template/app/src/test/java/com/pakstr/app/debug/AppDebugLoggerTest.kt +25 -0
- package/dist/commands/init.js +1 -0
- package/dist/core/pakstrConfig.js +24 -4
- package/dist/core/versioning.js +91 -0
- package/package.json +3 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.pakstr.app.debug
|
|
2
|
+
|
|
3
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
import org.junit.runner.RunWith
|
|
7
|
+
|
|
8
|
+
@RunWith(AndroidJUnit4::class)
|
|
9
|
+
class AppDebugLoggerTest {
|
|
10
|
+
|
|
11
|
+
@Test
|
|
12
|
+
fun release_sanitizer_redacts_object_and_array_payloads() {
|
|
13
|
+
assertEquals(
|
|
14
|
+
"\"event\":\"***\"",
|
|
15
|
+
AppDebugLogger.sanitize(
|
|
16
|
+
"\"event\":{\"kind\":1}",
|
|
17
|
+
isDebugBuild = false
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
assertEquals(
|
|
21
|
+
"\"signing_payload\":\"***\"",
|
|
22
|
+
AppDebugLogger.sanitize(
|
|
23
|
+
"\"signing_payload\":[1,2,3]",
|
|
24
|
+
isDebugBuild = false
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -209,6 +209,7 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
209
209
|
private fun setupSupportUnlockGesture() {
|
|
210
210
|
supportUnlockGestureLayout.onUnlock = {
|
|
211
211
|
SupportUnlock.unlock(this)
|
|
212
|
+
DeveloperToolsManager.enableDebug(this)
|
|
212
213
|
WebViewDebugManager.enable(this)
|
|
213
214
|
Toast.makeText(
|
|
214
215
|
this,
|
|
@@ -138,8 +138,15 @@ object AppDebugLogger {
|
|
|
138
138
|
internal fun sanitize(
|
|
139
139
|
message: String
|
|
140
140
|
): String {
|
|
141
|
+
return sanitize(message, BuildConfig.DEBUG)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
internal fun sanitize(
|
|
145
|
+
message: String,
|
|
146
|
+
isDebugBuild: Boolean
|
|
147
|
+
): String {
|
|
141
148
|
|
|
142
|
-
if (
|
|
149
|
+
if (isDebugBuild) {
|
|
143
150
|
return message
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -154,7 +161,7 @@ object AppDebugLogger {
|
|
|
154
161
|
}
|
|
155
162
|
.replace(
|
|
156
163
|
Regex(
|
|
157
|
-
"(?i)\"(event|signing_?payload)\"\\s*:\\s*(\\{[^\\r\\n]
|
|
164
|
+
"(?i)\"(event|signing_?payload)\"\\s*:\\s*(\\{[^\\r\\n]*\\}|\\[[^\\r\\n]*\\])"
|
|
158
165
|
)
|
|
159
166
|
) { match ->
|
|
160
167
|
val key = match.groupValues[1]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.pakstr.app.debug
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
|
|
6
|
+
class AppDebugLoggerTest {
|
|
7
|
+
|
|
8
|
+
@Test
|
|
9
|
+
fun release_sanitizer_redacts_object_and_array_payloads() {
|
|
10
|
+
assertEquals(
|
|
11
|
+
"\"event\":\"***\"",
|
|
12
|
+
AppDebugLogger.sanitize(
|
|
13
|
+
"\"event\":{\"kind\":1}",
|
|
14
|
+
isDebugBuild = false
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
assertEquals(
|
|
18
|
+
"\"signing_payload\":\"***\"",
|
|
19
|
+
AppDebugLogger.sanitize(
|
|
20
|
+
"\"signing_payload\":[1,2,3]",
|
|
21
|
+
isDebugBuild = false
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -167,6 +167,7 @@ function renderConfig(c) {
|
|
|
167
167
|
app:
|
|
168
168
|
appId: ${c.appId} # REQUIRED. Java package id; also feeds key derivation.
|
|
169
169
|
appName: ${escapeYamlScalar(c.appName)} # REQUIRED. Launcher label.
|
|
170
|
+
# Using auto requires a stable SemVer Git tag pointing exactly at HEAD.
|
|
170
171
|
versionName: "${c.versionName}" # REQUIRED. Human-readable version.
|
|
171
172
|
versionCode: ${c.versionCode} # REQUIRED. Monotonic integer.
|
|
172
173
|
description: ${c.description === "" ? '""' : escapeYamlScalar(c.description)} # OPTIONAL.
|
|
@@ -10,6 +10,7 @@ exports.validatePakstrConfig = validatePakstrConfig;
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
13
|
+
const versioning_1 = require("./versioning");
|
|
13
14
|
/**
|
|
14
15
|
* Pakstr configuration model — the single `pakstr.yaml` file.
|
|
15
16
|
* Spec: docs/SPEC.md §3. Secrets are NEVER placed in this file.
|
|
@@ -77,11 +78,16 @@ function loadPakstrConfig(configPath) {
|
|
|
77
78
|
}
|
|
78
79
|
return resolveAndValidate(raw, resolvedPath);
|
|
79
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Validate an in-memory Pakstr configuration through `resolveAndValidate`.
|
|
83
|
+
* Automatic version fields may invoke Git tag resolution and fail with Git or
|
|
84
|
+
* version-resolution errors.
|
|
85
|
+
*/
|
|
80
86
|
function validatePakstrConfig(config, configPath) {
|
|
81
87
|
resolveAndValidate(config, configPath ?? "");
|
|
82
88
|
}
|
|
83
89
|
function resolveAndValidate(config, configPath) {
|
|
84
|
-
const configDir = path_1.default.dirname(configPath);
|
|
90
|
+
const configDir = configPath ? path_1.default.dirname(configPath) : process.cwd();
|
|
85
91
|
const where = (field) => configPath ? `${field} (in ${configPath})` : field;
|
|
86
92
|
const app = config.app;
|
|
87
93
|
if (!app || typeof app !== "object")
|
|
@@ -90,7 +96,16 @@ function resolveAndValidate(config, configPath) {
|
|
|
90
96
|
requireString(app.appId, "app.appId", configPath);
|
|
91
97
|
requireString(app.appName, "app.appName", configPath);
|
|
92
98
|
requireString(app.versionName, "app.versionName", configPath);
|
|
93
|
-
|
|
99
|
+
if (app.versionCode !== versioning_1.AUTO_VERSION) {
|
|
100
|
+
requirePositiveInt(app.versionCode, "app.versionCode", configPath);
|
|
101
|
+
}
|
|
102
|
+
let resolvedVersion;
|
|
103
|
+
try {
|
|
104
|
+
resolvedVersion = (0, versioning_1.resolveReleaseVersion)(app.versionName, app.versionCode, configDir);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
fail(`Unable to resolve app version: ${error instanceof Error ? error.message : String(error)}`, configPath);
|
|
108
|
+
}
|
|
94
109
|
// appId rules (§3.3): non-empty, ≤255 UTF-8 bytes, equals trimmed form,
|
|
95
110
|
// and must be a valid Android/Java package name (segments of lower-case
|
|
96
111
|
// alphanumerics + underscores separated by dots; no hyphens). AAPT rejects
|
|
@@ -109,7 +124,7 @@ function resolveAndValidate(config, configPath) {
|
|
|
109
124
|
if (/[\r\n]/.test(app.appName)) {
|
|
110
125
|
fail(`${where("app.appName")} must not contain line breaks`, configPath);
|
|
111
126
|
}
|
|
112
|
-
if (/[\r\n"\\]/.test(
|
|
127
|
+
if (/[\r\n"\\]/.test(resolvedVersion.versionName)) {
|
|
113
128
|
fail(`${where("app.versionName")} must not contain line breaks, quotes, or backslashes`, configPath);
|
|
114
129
|
}
|
|
115
130
|
if (app.description !== undefined && typeof app.description !== "string") {
|
|
@@ -155,6 +170,11 @@ function resolveAndValidate(config, configPath) {
|
|
|
155
170
|
const builder = build.builder ?? "docker";
|
|
156
171
|
const outDefault = path_1.default.join(configDir, "build", `${app.appId}.apk`);
|
|
157
172
|
const out = build.out ? path_1.default.resolve(configDir, build.out) : outDefault;
|
|
173
|
+
const resolvedApp = {
|
|
174
|
+
...app,
|
|
175
|
+
versionName: resolvedVersion.versionName,
|
|
176
|
+
versionCode: resolvedVersion.versionCode,
|
|
177
|
+
};
|
|
158
178
|
// publish section
|
|
159
179
|
const publish = config.publish;
|
|
160
180
|
let zapstoreEnabled = true;
|
|
@@ -184,7 +204,7 @@ function resolveAndValidate(config, configPath) {
|
|
|
184
204
|
return {
|
|
185
205
|
configDir,
|
|
186
206
|
configPath,
|
|
187
|
-
app,
|
|
207
|
+
app: resolvedApp,
|
|
188
208
|
build: { web: webAbs, out, builder },
|
|
189
209
|
publish: { zapstoreEnabled, publishKey, relay, blossom },
|
|
190
210
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_MAJOR = exports.MAX_MINOR_OR_PATCH = exports.MAX_ANDROID_VERSION_CODE = exports.AUTO_VERSION = void 0;
|
|
4
|
+
exports.parseStableSemVer = parseStableSemVer;
|
|
5
|
+
exports.encodeAndroidVersionCode = encodeAndroidVersionCode;
|
|
6
|
+
exports.resolveExactGitReleaseVersion = resolveExactGitReleaseVersion;
|
|
7
|
+
exports.resolveReleaseVersion = resolveReleaseVersion;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
exports.AUTO_VERSION = "auto";
|
|
10
|
+
exports.MAX_ANDROID_VERSION_CODE = 2_100_000_000;
|
|
11
|
+
exports.MAX_MINOR_OR_PATCH = 99;
|
|
12
|
+
exports.MAX_MAJOR = 209_999;
|
|
13
|
+
const STABLE_SEMVER_RE = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
14
|
+
function parseStableSemVer(value) {
|
|
15
|
+
const match = STABLE_SEMVER_RE.exec(value);
|
|
16
|
+
if (!match) {
|
|
17
|
+
throw new Error(`Unsupported release version "${value}"; expected stable SemVer such as v1.2.3 or 1.2.3`);
|
|
18
|
+
}
|
|
19
|
+
const major = Number(match[1]);
|
|
20
|
+
const minor = Number(match[2]);
|
|
21
|
+
const patch = Number(match[3]);
|
|
22
|
+
if (![major, minor, patch].every(Number.isSafeInteger)) {
|
|
23
|
+
throw new Error(`Release version "${value}" contains an unsafe integer`);
|
|
24
|
+
}
|
|
25
|
+
return { major, minor, patch, versionName: `${major}.${minor}.${patch}` };
|
|
26
|
+
}
|
|
27
|
+
function encodeAndroidVersionCode(version) {
|
|
28
|
+
if (version.minor > exports.MAX_MINOR_OR_PATCH || version.patch > exports.MAX_MINOR_OR_PATCH) {
|
|
29
|
+
throw new Error(`Release version "${version.versionName}" cannot be encoded automatically: minor and patch must each be between 0 and ${exports.MAX_MINOR_OR_PATCH}`);
|
|
30
|
+
}
|
|
31
|
+
if (version.major > exports.MAX_MAJOR) {
|
|
32
|
+
throw new Error(`Release version "${version.versionName}" cannot be encoded automatically: major must be between 0 and ${exports.MAX_MAJOR}`);
|
|
33
|
+
}
|
|
34
|
+
const versionCode = ((version.major * 100) + version.minor) * 100 + version.patch + 1;
|
|
35
|
+
if (!Number.isSafeInteger(versionCode) || versionCode <= 0 || versionCode > exports.MAX_ANDROID_VERSION_CODE) {
|
|
36
|
+
throw new Error(`Release version "${version.versionName}" produces Android versionCode ${versionCode}; expected 1..${exports.MAX_ANDROID_VERSION_CODE}`);
|
|
37
|
+
}
|
|
38
|
+
return versionCode;
|
|
39
|
+
}
|
|
40
|
+
function resolveExactGitReleaseVersion(cwd) {
|
|
41
|
+
let output;
|
|
42
|
+
try {
|
|
43
|
+
output = (0, child_process_1.execFileSync)("git", ["tag", "--points-at", "HEAD"], {
|
|
44
|
+
cwd,
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
timeout: 5_000,
|
|
48
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
49
|
+
killSignal: "SIGKILL",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
54
|
+
throw new Error(`Unable to resolve an automatic release version from Git in ${cwd}: ${detail}`);
|
|
55
|
+
}
|
|
56
|
+
const tags = output.split(/\r?\n/).filter(Boolean);
|
|
57
|
+
const releases = new Map();
|
|
58
|
+
for (const tag of tags) {
|
|
59
|
+
try {
|
|
60
|
+
const release = parseStableSemVer(tag);
|
|
61
|
+
releases.set(release.versionName, release);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Non-release tags on HEAD do not become release versions.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (releases.size === 0) {
|
|
68
|
+
const observed = tags.length ? ` Tags on HEAD: ${tags.join(", ")}.` : "";
|
|
69
|
+
throw new Error(`HEAD has no exact stable SemVer release tag. Create a tag such as v1.2.3 or set app.versionName explicitly.${observed}`);
|
|
70
|
+
}
|
|
71
|
+
if (releases.size > 1) {
|
|
72
|
+
throw new Error(`HEAD has conflicting stable SemVer release tags: ${[...releases.keys()].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
return [...releases.values()][0];
|
|
75
|
+
}
|
|
76
|
+
function resolveReleaseVersion(versionName, versionCode, cwd) {
|
|
77
|
+
let release;
|
|
78
|
+
let resolvedName = versionName;
|
|
79
|
+
if (versionName === exports.AUTO_VERSION) {
|
|
80
|
+
release = resolveExactGitReleaseVersion(cwd);
|
|
81
|
+
resolvedName = release.versionName;
|
|
82
|
+
}
|
|
83
|
+
if (versionCode === exports.AUTO_VERSION) {
|
|
84
|
+
release ??= parseStableSemVer(resolvedName);
|
|
85
|
+
return {
|
|
86
|
+
versionName: resolvedName,
|
|
87
|
+
versionCode: encodeAndroidVersionCode(release),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { versionName: resolvedName, versionCode };
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pakstr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "CLI for packaging Nostr web apps into Android APKs",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc",
|
|
30
|
-
"test": "
|
|
30
|
+
"type-test": "tsc -p tsconfig.type-tests.json",
|
|
31
|
+
"test": "npm run build && npm run type-test && node --test test/*.test.js",
|
|
31
32
|
"start": "node dist/index.js",
|
|
32
33
|
"dev": "ts-node src/index.ts",
|
|
33
34
|
"pack:test": "npm pack"
|