pakstr 0.8.7 → 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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pakstr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CLI for packaging Nostr web apps into Android APKs",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,12 +41,18 @@
|
|
|
41
41
|
],
|
|
42
42
|
"license": "ISC",
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@types/debug": "^4.1.13",
|
|
45
|
+
"@types/js-yaml": "^4.0.9",
|
|
44
46
|
"@types/node": "^26.1.0",
|
|
45
47
|
"@types/unzipper": "^0.10.11",
|
|
46
48
|
"typescript": "^6.0.3"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
51
|
+
"@scure/base": "^2.3.0",
|
|
52
|
+
"applesauce-core": "^6.2.0",
|
|
53
|
+
"applesauce-signers": "^6.2.2",
|
|
49
54
|
"fs-extra": "^11.3.6",
|
|
55
|
+
"js-yaml": "^5.3.0",
|
|
50
56
|
"node-fetch": "^3.3.2",
|
|
51
57
|
"sharp": "^0.35.3",
|
|
52
58
|
"unzipper": "^0.12.5"
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.verifyReleaseApk = verifyReleaseApk;
|
|
4
|
-
exports.parseApkSignerFingerprints = parseApkSignerFingerprints;
|
|
5
|
-
exports.parseKeystoreFingerprints = parseKeystoreFingerprints;
|
|
6
|
-
exports.parseAaptBadging = parseAaptBadging;
|
|
7
|
-
const crypto_1 = require("crypto");
|
|
8
|
-
const releaseSigning_1 = require("../core/releaseSigning");
|
|
9
|
-
function verifyReleaseApk(input) {
|
|
10
|
-
const signerResult = input.executor.run("apksigner", [
|
|
11
|
-
"verify",
|
|
12
|
-
"--print-certs",
|
|
13
|
-
input.artifactPath,
|
|
14
|
-
]);
|
|
15
|
-
const signerFingerprints = parseApkSignerFingerprints(signerResult.stdout);
|
|
16
|
-
if (signerFingerprints.length !== 1) {
|
|
17
|
-
throw new Error(`Release APK must have exactly one current signer; found ${signerFingerprints.length}`);
|
|
18
|
-
}
|
|
19
|
-
const keytoolEnv = {
|
|
20
|
-
[releaseSigning_1.PUBLIC_SIGNING_ENV.keystorePassword]: input.signing.storePassword,
|
|
21
|
-
};
|
|
22
|
-
const keytoolResult = input.executor.run("keytool", [
|
|
23
|
-
"-list",
|
|
24
|
-
"-rfc",
|
|
25
|
-
"-keystore",
|
|
26
|
-
input.toolKeystorePath ?? input.signing.keystorePath,
|
|
27
|
-
"-storepass:env",
|
|
28
|
-
releaseSigning_1.PUBLIC_SIGNING_ENV.keystorePassword,
|
|
29
|
-
], keytoolEnv);
|
|
30
|
-
const keystoreFingerprints = parseKeystoreFingerprints(keytoolResult.stdout);
|
|
31
|
-
const matchingEntries = keystoreFingerprints.filter(fingerprint => fingerprint === signerFingerprints[0]);
|
|
32
|
-
if (matchingEntries.length !== 1) {
|
|
33
|
-
throw new Error(`Release APK signer must match exactly one certificate in the supplied keystore; found ${matchingEntries.length}`);
|
|
34
|
-
}
|
|
35
|
-
const analyzerMetadata = {
|
|
36
|
-
applicationId: readAnalyzerValue(input, "application-id"),
|
|
37
|
-
versionCode: parseVersionCode(readAnalyzerValue(input, "version-code")),
|
|
38
|
-
versionName: readAnalyzerValue(input, "version-name"),
|
|
39
|
-
};
|
|
40
|
-
const badging = parseAaptBadging(input.executor.run("aapt", ["dump", "badging", input.artifactPath]).stdout);
|
|
41
|
-
assertMetadataField("Application ID", analyzerMetadata.applicationId, input.expectedMetadata.applicationId);
|
|
42
|
-
assertMetadataField("versionCode", analyzerMetadata.versionCode, input.expectedMetadata.versionCode);
|
|
43
|
-
assertMetadataField("versionName", analyzerMetadata.versionName, input.expectedMetadata.versionName);
|
|
44
|
-
assertMetadataField("aapt Application ID", badging.applicationId, analyzerMetadata.applicationId);
|
|
45
|
-
assertMetadataField("aapt versionCode", badging.versionCode, analyzerMetadata.versionCode);
|
|
46
|
-
assertMetadataField("aapt versionName", badging.versionName, analyzerMetadata.versionName);
|
|
47
|
-
assertMetadataField("app label", badging.appLabel, input.expectedMetadata.appLabel);
|
|
48
|
-
return {
|
|
49
|
-
signatureVerified: true,
|
|
50
|
-
signerMatched: true,
|
|
51
|
-
expectedMetadata: input.expectedMetadata,
|
|
52
|
-
actualMetadata: {
|
|
53
|
-
applicationId: analyzerMetadata.applicationId,
|
|
54
|
-
versionCode: analyzerMetadata.versionCode,
|
|
55
|
-
versionName: analyzerMetadata.versionName,
|
|
56
|
-
appLabel: badging.appLabel,
|
|
57
|
-
},
|
|
58
|
-
tools: {
|
|
59
|
-
signature: "apksigner",
|
|
60
|
-
signer: "keytool",
|
|
61
|
-
manifest: "apkanalyzer",
|
|
62
|
-
badging: "aapt",
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function parseApkSignerFingerprints(output) {
|
|
67
|
-
const fingerprints = output
|
|
68
|
-
.split(/\r?\n/)
|
|
69
|
-
.map(line => line.match(/Signer #\d+ certificate SHA-256 digest:\s*([0-9a-fA-F]+)/)?.[1])
|
|
70
|
-
.filter((value) => value !== undefined)
|
|
71
|
-
.map(normalizeFingerprint);
|
|
72
|
-
return [...new Set(fingerprints)];
|
|
73
|
-
}
|
|
74
|
-
function parseKeystoreFingerprints(output) {
|
|
75
|
-
const certificates = output.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g);
|
|
76
|
-
if (!certificates?.length) {
|
|
77
|
-
throw new Error("No certificate-bearing entries found in supplied keystore");
|
|
78
|
-
}
|
|
79
|
-
return certificates.map(pem => {
|
|
80
|
-
const certificate = new crypto_1.X509Certificate(pem);
|
|
81
|
-
return (0, crypto_1.createHash)("sha256").update(certificate.raw).digest("hex");
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
function parseAaptBadging(output) {
|
|
85
|
-
const lines = output.split(/\r?\n/);
|
|
86
|
-
const packageLines = lines.filter(line => line.startsWith("package: "));
|
|
87
|
-
const labelLines = lines.filter(line => line.startsWith("application-label:"));
|
|
88
|
-
if (packageLines.length !== 1) {
|
|
89
|
-
throw new Error(`Expected exactly one aapt package line, found ${packageLines.length}`);
|
|
90
|
-
}
|
|
91
|
-
if (labelLines.length !== 1) {
|
|
92
|
-
throw new Error(`Expected exactly one default aapt application label, found ${labelLines.length}`);
|
|
93
|
-
}
|
|
94
|
-
const packageLine = packageLines[0];
|
|
95
|
-
const applicationId = readAaptAttribute(packageLine, "name");
|
|
96
|
-
const versionCode = parseVersionCode(readAaptAttribute(packageLine, "versionCode"));
|
|
97
|
-
const versionName = readAaptAttribute(packageLine, "versionName");
|
|
98
|
-
const appLabel = parseQuotedAaptValue(labelLines[0], "application-label");
|
|
99
|
-
return {
|
|
100
|
-
applicationId,
|
|
101
|
-
versionCode,
|
|
102
|
-
versionName,
|
|
103
|
-
appLabel,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function readAnalyzerValue(input, verb) {
|
|
107
|
-
const result = input.executor.run("apkanalyzer", [
|
|
108
|
-
"manifest",
|
|
109
|
-
verb,
|
|
110
|
-
input.artifactPath,
|
|
111
|
-
]);
|
|
112
|
-
const values = result.stdout
|
|
113
|
-
.split(/\r?\n/)
|
|
114
|
-
.map(value => value.trim())
|
|
115
|
-
.filter(Boolean);
|
|
116
|
-
if (values.length !== 1) {
|
|
117
|
-
throw new Error(`Unable to derive one unambiguous ${verb} from release APK`);
|
|
118
|
-
}
|
|
119
|
-
return values[0];
|
|
120
|
-
}
|
|
121
|
-
function parseQuotedAaptValue(line, field) {
|
|
122
|
-
const pattern = new RegExp(`^${field}:'((?:\\\\.|[^'])*)'$`);
|
|
123
|
-
const match = pattern.exec(line);
|
|
124
|
-
if (!match || match[1] === undefined) {
|
|
125
|
-
throw new Error(`Unable to derive one unambiguous aapt ${field}`);
|
|
126
|
-
}
|
|
127
|
-
return decodeAaptEscapes(match[1]);
|
|
128
|
-
}
|
|
129
|
-
function readAaptAttribute(line, name) {
|
|
130
|
-
const pattern = new RegExp(`${name}='((?:\\\\.|[^'])*)'`);
|
|
131
|
-
const match = pattern.exec(line);
|
|
132
|
-
if (!match || match[1] === undefined) {
|
|
133
|
-
throw new Error(`Unable to derive one unambiguous aapt ${name}`);
|
|
134
|
-
}
|
|
135
|
-
return decodeAaptEscapes(match[1]);
|
|
136
|
-
}
|
|
137
|
-
function decodeAaptEscapes(value) {
|
|
138
|
-
return value.replace(/\\(.)/gs, (_match, escaped) => {
|
|
139
|
-
switch (escaped) {
|
|
140
|
-
case "n":
|
|
141
|
-
return "\n";
|
|
142
|
-
case "r":
|
|
143
|
-
return "\r";
|
|
144
|
-
case "t":
|
|
145
|
-
return "\t";
|
|
146
|
-
default:
|
|
147
|
-
return escaped;
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
function parseVersionCode(value) {
|
|
152
|
-
if (!/^\d+$/.test(value)) {
|
|
153
|
-
throw new Error(`APK versionCode is not an integer: ${value}`);
|
|
154
|
-
}
|
|
155
|
-
return Number(value);
|
|
156
|
-
}
|
|
157
|
-
function assertMetadataField(field, actual, expected) {
|
|
158
|
-
if (actual !== expected) {
|
|
159
|
-
throw new Error(`Release APK ${field} mismatch: expected ${expected}, received ${actual}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function normalizeFingerprint(value) {
|
|
163
|
-
return value.replace(/[^0-9a-fA-F]/g, "").toLowerCase();
|
|
164
|
-
}
|
package/dist/config.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createBuildContext = createBuildContext;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const manifest_1 = require("./manifest");
|
|
10
|
-
// -----------------------------
|
|
11
|
-
// CONFIG LOADER
|
|
12
|
-
// -----------------------------
|
|
13
|
-
function loadConfig() {
|
|
14
|
-
const configPath = path_1.default.resolve(process.cwd(), "nostr.config.json");
|
|
15
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(fs_1.default.readFileSync(configPath, "utf-8"));
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
throw new Error("❌ Invalid nostr.config.json");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function createBuildContext(args) {
|
|
26
|
-
const getArg = (key) => {
|
|
27
|
-
const i = args.indexOf(key);
|
|
28
|
-
return i !== -1 ? args[i + 1] : null;
|
|
29
|
-
};
|
|
30
|
-
function resolvePath(p) {
|
|
31
|
-
if (!p)
|
|
32
|
-
return null;
|
|
33
|
-
// absolute path
|
|
34
|
-
if (path_1.default.isAbsolute(p)) {
|
|
35
|
-
return p;
|
|
36
|
-
}
|
|
37
|
-
// try cwd
|
|
38
|
-
const cwdPath = path_1.default.resolve(process.cwd(), p);
|
|
39
|
-
if (fs_1.default.existsSync(cwdPath)) {
|
|
40
|
-
return cwdPath;
|
|
41
|
-
}
|
|
42
|
-
// try one level up (VERY IMPORTANT for your case)
|
|
43
|
-
const parentPath = path_1.default.resolve(process.cwd(), "..", p);
|
|
44
|
-
if (fs_1.default.existsSync(parentPath)) {
|
|
45
|
-
return parentPath;
|
|
46
|
-
}
|
|
47
|
-
return cwdPath;
|
|
48
|
-
}
|
|
49
|
-
const mode = args.includes("--release") ? "release" : "debug";
|
|
50
|
-
// -----------------------------
|
|
51
|
-
// 1. LOAD CONFIG (OPTIONAL)
|
|
52
|
-
// -----------------------------
|
|
53
|
-
const config = loadConfig();
|
|
54
|
-
// -----------------------------
|
|
55
|
-
// 2. RESOLVE INPUTS (PRIORITY ORDER)
|
|
56
|
-
// args > config > error
|
|
57
|
-
// -----------------------------
|
|
58
|
-
const webRaw = getArg("--web") ?? config?.web;
|
|
59
|
-
const manifestPathRaw = getArg("--manifest") ?? config?.manifest;
|
|
60
|
-
const outPathRaw = getArg("--out") ?? config?.out;
|
|
61
|
-
const web = resolvePath(webRaw);
|
|
62
|
-
const manifestPath = resolvePath(manifestPathRaw);
|
|
63
|
-
const outPath = resolvePath(outPathRaw);
|
|
64
|
-
// -----------------------------
|
|
65
|
-
// 3. VALIDATION (STRICT)
|
|
66
|
-
// -----------------------------
|
|
67
|
-
if (!web) {
|
|
68
|
-
throw new Error("❌ Missing web path (--web or nostr.config.json)");
|
|
69
|
-
}
|
|
70
|
-
if (!manifestPath) {
|
|
71
|
-
throw new Error("❌ Missing manifest path (--manifest or nostr.config.json)");
|
|
72
|
-
}
|
|
73
|
-
if (!fs_1.default.existsSync(web)) {
|
|
74
|
-
throw new Error(`❌ Web folder not found: ${web}`);
|
|
75
|
-
}
|
|
76
|
-
if (!fs_1.default.existsSync(manifestPath)) {
|
|
77
|
-
throw new Error(`❌ Manifest not found: ${manifestPath}`);
|
|
78
|
-
}
|
|
79
|
-
// -----------------------------
|
|
80
|
-
// 4. LOAD MANIFEST
|
|
81
|
-
// -----------------------------
|
|
82
|
-
const manifest = (0, manifest_1.loadManifest)(manifestPath);
|
|
83
|
-
// -----------------------------
|
|
84
|
-
// 5. OUTPUT PATH
|
|
85
|
-
// -----------------------------
|
|
86
|
-
const out = outPath
|
|
87
|
-
? path_1.default.resolve(outPath)
|
|
88
|
-
: path_1.default.join(process.cwd(), "build/app.apk");
|
|
89
|
-
// -----------------------------
|
|
90
|
-
// 6. ANDROID ROOT
|
|
91
|
-
// -----------------------------
|
|
92
|
-
const androidRoot = path_1.default.resolve(process.cwd(), "android-template");
|
|
93
|
-
return {
|
|
94
|
-
manifest,
|
|
95
|
-
manifestPath,
|
|
96
|
-
manifestDir: path_1.default.dirname(manifestPath),
|
|
97
|
-
dist: path_1.default.resolve(web),
|
|
98
|
-
out,
|
|
99
|
-
androidRoot,
|
|
100
|
-
mode,
|
|
101
|
-
};
|
|
102
|
-
}
|
package/dist/core/config.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadConfig = loadConfig;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function loadConfig(cwd) {
|
|
10
|
-
const configPath = path_1.default.join(cwd, "nostr.config.json");
|
|
11
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
12
|
-
return {
|
|
13
|
-
builder: "local"
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
const json = JSON.parse(fs_1.default.readFileSync(configPath, "utf-8"));
|
|
17
|
-
const builder = json.builder === "docker"
|
|
18
|
-
? "docker"
|
|
19
|
-
: "local";
|
|
20
|
-
return {
|
|
21
|
-
builder,
|
|
22
|
-
dockerImage: json.dockerImage
|
|
23
|
-
};
|
|
24
|
-
}
|
package/dist/core/copyFile.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.copyFile = copyFile;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function copyFile(source, destination) {
|
|
10
|
-
const dir = path_1.default.dirname(destination);
|
|
11
|
-
if (!fs_1.default.existsSync(dir)) {
|
|
12
|
-
fs_1.default.mkdirSync(dir, {
|
|
13
|
-
recursive: true
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
fs_1.default.copyFileSync(source, destination);
|
|
17
|
-
}
|
package/dist/core/manifest.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadManifest = loadManifest;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
function loadManifest(path) {
|
|
9
|
-
if (!fs_1.default.existsSync(path)) {
|
|
10
|
-
throw new Error(`❌ Manifest not found: ${path}`);
|
|
11
|
-
}
|
|
12
|
-
const manifest = JSON.parse(fs_1.default.readFileSync(path, "utf-8"));
|
|
13
|
-
validateManifest(manifest);
|
|
14
|
-
return manifest;
|
|
15
|
-
}
|
|
16
|
-
function validateManifest(m) {
|
|
17
|
-
if (!m.appId)
|
|
18
|
-
throw new Error("❌ appId is required");
|
|
19
|
-
const appIdPattern = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
20
|
-
if (m.appId.length < 3 ||
|
|
21
|
-
m.appId.length > 255 ||
|
|
22
|
-
!appIdPattern.test(m.appId)) {
|
|
23
|
-
throw new Error("❌ appId must be a lowercase reverse-domain Android Application ID");
|
|
24
|
-
}
|
|
25
|
-
const debugAppId = `${m.appId}.debug`;
|
|
26
|
-
if (debugAppId.length > 255 || !appIdPattern.test(debugAppId)) {
|
|
27
|
-
throw new Error("❌ appId is too long to derive the debug Application ID");
|
|
28
|
-
}
|
|
29
|
-
if (!m.appName)
|
|
30
|
-
throw new Error("❌ appName is required");
|
|
31
|
-
if (/[\r\n]/.test(m.appName)) {
|
|
32
|
-
throw new Error("❌ appName must not contain line breaks");
|
|
33
|
-
}
|
|
34
|
-
if (!m.versionName)
|
|
35
|
-
throw new Error("❌ versionName is required");
|
|
36
|
-
if (/[\r\n"\\]/.test(m.versionName)) {
|
|
37
|
-
throw new Error("❌ versionName must not contain line breaks, quotes, or backslashes");
|
|
38
|
-
}
|
|
39
|
-
if (m.versionCode === undefined)
|
|
40
|
-
throw new Error("❌ versionCode is required");
|
|
41
|
-
if (!Number.isInteger(m.versionCode) || m.versionCode <= 0) {
|
|
42
|
-
throw new Error("❌ versionCode must be a positive integer");
|
|
43
|
-
}
|
|
44
|
-
if (m.ui?.splash && !m.ui.splash.image) {
|
|
45
|
-
throw new Error("❌ splash.image is required");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.INTERNAL_KEYSTORE_PATH_ENV = exports.PUBLIC_SIGNING_ENV = void 0;
|
|
7
|
-
exports.readReleaseSigningInput = readReleaseSigningInput;
|
|
8
|
-
exports.createReleaseSigningContext = createReleaseSigningContext;
|
|
9
|
-
exports.removeSigningVariables = removeSigningVariables;
|
|
10
|
-
exports.createGradleSigningEnvironment = createGradleSigningEnvironment;
|
|
11
|
-
const fs_1 = __importDefault(require("fs"));
|
|
12
|
-
const os_1 = __importDefault(require("os"));
|
|
13
|
-
const path_1 = __importDefault(require("path"));
|
|
14
|
-
exports.PUBLIC_SIGNING_ENV = {
|
|
15
|
-
keystoreBase64: "PAKSTR_ANDROID_KEYSTORE_BASE64",
|
|
16
|
-
keystorePassword: "PAKSTR_ANDROID_KEYSTORE_PASSWORD",
|
|
17
|
-
keyAlias: "PAKSTR_ANDROID_KEY_ALIAS",
|
|
18
|
-
keyPassword: "PAKSTR_ANDROID_KEY_PASSWORD",
|
|
19
|
-
};
|
|
20
|
-
exports.INTERNAL_KEYSTORE_PATH_ENV = "PAKSTR_ANDROID_KEYSTORE_PATH";
|
|
21
|
-
const MAX_KEYSTORE_BYTES = 16 * 1024 * 1024;
|
|
22
|
-
const CANONICAL_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
23
|
-
function readReleaseSigningInput(env = process.env) {
|
|
24
|
-
const keystoreBase64 = requireValue(env, exports.PUBLIC_SIGNING_ENV.keystoreBase64);
|
|
25
|
-
const storePassword = requireValue(env, exports.PUBLIC_SIGNING_ENV.keystorePassword);
|
|
26
|
-
const keyAlias = requireValue(env, exports.PUBLIC_SIGNING_ENV.keyAlias);
|
|
27
|
-
const keyPassword = requireValue(env, exports.PUBLIC_SIGNING_ENV.keyPassword);
|
|
28
|
-
validateCanonicalBase64(keystoreBase64);
|
|
29
|
-
return {
|
|
30
|
-
keystoreBase64,
|
|
31
|
-
storePassword,
|
|
32
|
-
keyAlias,
|
|
33
|
-
keyPassword,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function createReleaseSigningContext(input, temporaryRoot = os_1.default.tmpdir()) {
|
|
37
|
-
let decoded = decodeKeystore(input.keystoreBase64);
|
|
38
|
-
fs_1.default.mkdirSync(temporaryRoot, { recursive: true, mode: 0o700 });
|
|
39
|
-
const signingDir = fs_1.default.mkdtempSync(path_1.default.join(temporaryRoot, "pakstr-signing-"));
|
|
40
|
-
const keystorePath = path_1.default.join(signingDir, "release.keystore");
|
|
41
|
-
let cleaned = false;
|
|
42
|
-
try {
|
|
43
|
-
fs_1.default.chmodSync(signingDir, 0o700);
|
|
44
|
-
fs_1.default.writeFileSync(keystorePath, decoded, {
|
|
45
|
-
flag: "wx",
|
|
46
|
-
mode: 0o600,
|
|
47
|
-
});
|
|
48
|
-
verifyProtectedKeystore(signingDir, keystorePath, decoded.length);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
fs_1.default.rmSync(signingDir, { recursive: true, force: true });
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
finally {
|
|
55
|
-
decoded.fill(0);
|
|
56
|
-
decoded = Buffer.alloc(0);
|
|
57
|
-
}
|
|
58
|
-
return Object.freeze({
|
|
59
|
-
keystorePath,
|
|
60
|
-
storePassword: input.storePassword,
|
|
61
|
-
keyAlias: input.keyAlias,
|
|
62
|
-
keyPassword: input.keyPassword,
|
|
63
|
-
cleanup() {
|
|
64
|
-
if (cleaned)
|
|
65
|
-
return;
|
|
66
|
-
fs_1.default.rmSync(signingDir, { recursive: true, force: true, maxRetries: 2 });
|
|
67
|
-
if (fs_1.default.existsSync(signingDir)) {
|
|
68
|
-
throw new Error(`Failed to remove temporary signing directory: ${signingDir}`);
|
|
69
|
-
}
|
|
70
|
-
cleaned = true;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
function removeSigningVariables(env) {
|
|
75
|
-
const sanitized = { ...env };
|
|
76
|
-
delete sanitized[exports.PUBLIC_SIGNING_ENV.keystoreBase64];
|
|
77
|
-
delete sanitized[exports.PUBLIC_SIGNING_ENV.keystorePassword];
|
|
78
|
-
delete sanitized[exports.PUBLIC_SIGNING_ENV.keyAlias];
|
|
79
|
-
delete sanitized[exports.PUBLIC_SIGNING_ENV.keyPassword];
|
|
80
|
-
delete sanitized[exports.INTERNAL_KEYSTORE_PATH_ENV];
|
|
81
|
-
return sanitized;
|
|
82
|
-
}
|
|
83
|
-
function createGradleSigningEnvironment(context, env = process.env, keystorePath = context.keystorePath) {
|
|
84
|
-
return {
|
|
85
|
-
...removeSigningVariables(env),
|
|
86
|
-
[exports.INTERNAL_KEYSTORE_PATH_ENV]: keystorePath,
|
|
87
|
-
[exports.PUBLIC_SIGNING_ENV.keystorePassword]: context.storePassword,
|
|
88
|
-
[exports.PUBLIC_SIGNING_ENV.keyAlias]: context.keyAlias,
|
|
89
|
-
[exports.PUBLIC_SIGNING_ENV.keyPassword]: context.keyPassword,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function requireValue(env, name) {
|
|
93
|
-
const value = env[name];
|
|
94
|
-
if (value === undefined || value.length === 0) {
|
|
95
|
-
throw new Error(`${name} is required and must not be empty for release builds`);
|
|
96
|
-
}
|
|
97
|
-
return value;
|
|
98
|
-
}
|
|
99
|
-
function validateCanonicalBase64(value) {
|
|
100
|
-
if (!CANONICAL_BASE64.test(value)) {
|
|
101
|
-
throw new Error(`${exports.PUBLIC_SIGNING_ENV.keystoreBase64} must be canonical single-line RFC 4648 base64`);
|
|
102
|
-
}
|
|
103
|
-
const decodedLength = Buffer.byteLength(value, "base64");
|
|
104
|
-
if (decodedLength === 0) {
|
|
105
|
-
throw new Error(`${exports.PUBLIC_SIGNING_ENV.keystoreBase64} decodes to an empty keystore`);
|
|
106
|
-
}
|
|
107
|
-
if (decodedLength > MAX_KEYSTORE_BYTES) {
|
|
108
|
-
throw new Error(`${exports.PUBLIC_SIGNING_ENV.keystoreBase64} exceeds the 16 MiB decoded size limit`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function decodeKeystore(value) {
|
|
112
|
-
const decoded = Buffer.from(value, "base64");
|
|
113
|
-
if (decoded.toString("base64") !== value) {
|
|
114
|
-
decoded.fill(0);
|
|
115
|
-
throw new Error(`${exports.PUBLIC_SIGNING_ENV.keystoreBase64} is not canonical base64`);
|
|
116
|
-
}
|
|
117
|
-
return decoded;
|
|
118
|
-
}
|
|
119
|
-
function verifyProtectedKeystore(signingDir, keystorePath, expectedSize) {
|
|
120
|
-
const dirStat = fs_1.default.lstatSync(signingDir);
|
|
121
|
-
const fileStat = fs_1.default.lstatSync(keystorePath);
|
|
122
|
-
if (!dirStat.isDirectory() || dirStat.isSymbolicLink()) {
|
|
123
|
-
throw new Error("Temporary signing directory is not a real directory");
|
|
124
|
-
}
|
|
125
|
-
if (!fileStat.isFile() || fileStat.isSymbolicLink()) {
|
|
126
|
-
throw new Error("Temporary keystore is not a regular file");
|
|
127
|
-
}
|
|
128
|
-
if ((dirStat.mode & 0o777) !== 0o700 || (fileStat.mode & 0o777) !== 0o600) {
|
|
129
|
-
throw new Error("Temporary signing storage does not have owner-only permissions");
|
|
130
|
-
}
|
|
131
|
-
if (fileStat.size !== expectedSize) {
|
|
132
|
-
throw new Error("Temporary keystore size does not match decoded input");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.resolveProjectInputs = resolveProjectInputs;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function resolveProjectInputs() {
|
|
10
|
-
const cwd = process.cwd();
|
|
11
|
-
const dist = path_1.default.join(cwd, "dist");
|
|
12
|
-
const manifest = path_1.default.join(cwd, "app.manifest.json");
|
|
13
|
-
if (!fs_1.default.existsSync(dist)) {
|
|
14
|
-
throw new Error("❌ Missing dist folder. Expected: ./dist");
|
|
15
|
-
}
|
|
16
|
-
if (!fs_1.default.existsSync(manifest)) {
|
|
17
|
-
throw new Error("❌ Missing app.manifest.json");
|
|
18
|
-
}
|
|
19
|
-
return { dist, manifest };
|
|
20
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.validateProject = validateProject;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function validateProject(dist, manifest) {
|
|
10
|
-
const index = path_1.default.join(dist, "index.html");
|
|
11
|
-
if (!fs_1.default.existsSync(dist)) {
|
|
12
|
-
throw new Error("❌ dist folder not found");
|
|
13
|
-
}
|
|
14
|
-
if (!fs_1.default.existsSync(index)) {
|
|
15
|
-
throw new Error("❌ dist/index.html missing (not a web build)");
|
|
16
|
-
}
|
|
17
|
-
if (!manifest.appId || !manifest.appName) {
|
|
18
|
-
throw new Error("❌ Invalid manifest (missing appId/appName)");
|
|
19
|
-
}
|
|
20
|
-
console.log("✔ Project validation OK");
|
|
21
|
-
}
|
package/dist/core/zeroConfig.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.resolveZeroConfig = resolveZeroConfig;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
/**
|
|
10
|
-
* MAIN ENTRY
|
|
11
|
-
* Tries to auto-detect web build + manifest
|
|
12
|
-
*/
|
|
13
|
-
function resolveZeroConfig() {
|
|
14
|
-
const cwd = process.cwd();
|
|
15
|
-
const dist = findWebDist(cwd);
|
|
16
|
-
const manifest = findManifest(cwd);
|
|
17
|
-
if (!dist) {
|
|
18
|
-
throw new Error("❌ No valid web dist found (index.html missing)");
|
|
19
|
-
}
|
|
20
|
-
if (!manifest) {
|
|
21
|
-
throw new Error("❌ No manifest found (app.manifest.json or manifest.json)");
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
dist,
|
|
25
|
-
manifest,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* -----------------------------
|
|
30
|
-
* WEB DIST DETECTION
|
|
31
|
-
* -----------------------------
|
|
32
|
-
*/
|
|
33
|
-
function findWebDist(cwd) {
|
|
34
|
-
const candidates = ["dist", "build", "www"];
|
|
35
|
-
// 1. local folder scan
|
|
36
|
-
for (const c of candidates) {
|
|
37
|
-
const p = path_1.default.join(cwd, c);
|
|
38
|
-
if (isValidWebDist(p)) {
|
|
39
|
-
return p;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// 2. parent folder scan (IMPORTANT for monorepos)
|
|
43
|
-
const parent = findInParentDirs(cwd);
|
|
44
|
-
if (parent)
|
|
45
|
-
return parent;
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Valid web app check
|
|
50
|
-
*/
|
|
51
|
-
function isValidWebDist(dir) {
|
|
52
|
-
return fs_1.default.existsSync(path_1.default.join(dir, "index.html"));
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Scan upward in directory tree
|
|
56
|
-
*/
|
|
57
|
-
function findInParentDirs(startDir) {
|
|
58
|
-
let dir = startDir;
|
|
59
|
-
while (dir !== path_1.default.parse(dir).root) {
|
|
60
|
-
const candidates = ["dist", "build", "www"];
|
|
61
|
-
for (const c of candidates) {
|
|
62
|
-
const p = path_1.default.join(dir, c);
|
|
63
|
-
if (isValidWebDist(p)) {
|
|
64
|
-
return p;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
dir = path_1.default.dirname(dir);
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* -----------------------------
|
|
73
|
-
* MANIFEST DETECTION
|
|
74
|
-
* -----------------------------
|
|
75
|
-
*/
|
|
76
|
-
function findManifest(cwd) {
|
|
77
|
-
const candidates = ["app.manifest.json", "manifest.json"];
|
|
78
|
-
// local scan
|
|
79
|
-
for (const c of candidates) {
|
|
80
|
-
const p = path_1.default.join(cwd, c);
|
|
81
|
-
if (fs_1.default.existsSync(p)) {
|
|
82
|
-
return p;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// parent scan (monorepo support)
|
|
86
|
-
let dir = cwd;
|
|
87
|
-
while (dir !== path_1.default.parse(dir).root) {
|
|
88
|
-
for (const c of candidates) {
|
|
89
|
-
const p = path_1.default.join(dir, c);
|
|
90
|
-
if (fs_1.default.existsSync(p)) {
|
|
91
|
-
return p;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
dir = path_1.default.dirname(dir);
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|