pakstr 0.12.1 → 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.
@@ -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
- requirePositiveInt(app.versionCode, "app.versionCode", configPath);
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(app.versionName)) {
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.12.1",
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": "npm run build && node --test test/*.test.js",
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"