pakstr 0.13.2 → 0.13.3

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.
@@ -11,9 +11,10 @@ const zapStore_1 = require("../core/zapStore");
11
11
  const blossom_1 = require("../core/blossom");
12
12
  const nostr_1 = require("../core/nostr");
13
13
  const identityProof_1 = require("../core/identityProof");
14
+ const zapstoreConfig_1 = require("../core/zapstoreConfig");
14
15
  /**
15
- * `pakstr publish` — upload the signed APK to Blossom and publish NIP-82
16
- * events (32267/30063/3063) to the configured relay. Spec §5.4 / §8.
16
+ * `pakstr publish` — publish the NIP-82 application event, upload the signed APK
17
+ * to Blossom, then publish the APK-dependent events. Spec §5.4 / §8.
17
18
  *
18
19
  * Requires that `pakstr build` + `pakstr sign` have already run (the signed
19
20
  * APK at build.out, plus its `.signer-sha256` sidecar).
@@ -34,28 +35,21 @@ async function publishCommand(configPath, options = {}) {
34
35
  let blossomUrl;
35
36
  let apkSha256;
36
37
  let apkSize;
37
- let signerCertificateSha256;
38
- let filename;
38
+ let signerCertificateSha256 = "";
39
+ let filename = "";
39
40
  let identityProof;
40
- if (options.dryRun) {
41
- // No network, no APK required. Synthesize a stub descriptor so the events
42
- // can still be built and printed for preview.
43
- apkSha256 = "0".repeat(64);
44
- apkSize = 0;
45
- blossomUrl = `${config.publish.blossom.replace(/\/$/, "")}/${apkSha256}`;
46
- signerCertificateSha256 = "0".repeat(64);
47
- filename = path_1.default.basename(apkPath);
48
- // Stub identity proof so the 30509 event path is exercised end-to-end.
49
- identityProof = {
50
- certHash: signerCertificateSha256,
51
- signature: "dry-run-stub==",
52
- createdAt: Math.floor(Date.now() / 1000),
53
- expiry: Math.floor(Date.now() / 1000) + 365 * 24 * 3600,
54
- pubkeyHex: await (0, nostr_1.getNpubHex)(publishNsec.bytes),
55
- };
56
- console.log("📦 (dry-run) Blossom URL:", blossomUrl);
57
- }
58
- else {
41
+ const transport = options.transport ?? (options.dryRun ? new zapStore_1.MockRelayTransport() : new zapStore_1.WebsocketRelayTransport());
42
+ const repository = (0, zapstoreConfig_1.loadZapstoreRepository)(config.configDir);
43
+ const app = {
44
+ appId: config.app.appId,
45
+ appName: config.app.appName,
46
+ versionName: config.app.versionName,
47
+ versionCode: config.app.versionCode,
48
+ description: config.app.description,
49
+ repository,
50
+ };
51
+ const target = { relayUrl: config.publish.relay };
52
+ if (!options.dryRun) {
59
53
  if (!fs_1.default.existsSync(apkPath) || !fs_1.default.lstatSync(apkPath).isFile()) {
60
54
  throw new Error(`Signed APK not found at ${apkPath}. Run \`pakstr build\` and \`pakstr sign\` first.`);
61
55
  }
@@ -64,6 +58,7 @@ async function publishCommand(configPath, options = {}) {
64
58
  throw new Error(`Signer certificate sidecar not found: ${certShaPath}. Run \`pakstr sign\` first.`);
65
59
  }
66
60
  signerCertificateSha256 = fs_1.default.readFileSync(certShaPath, "utf8").trim();
61
+ filename = path_1.default.basename(apkPath);
67
62
  // Read the NIP-C1 identity proof sidecar (optional; generated by
68
63
  // `pakstr sign` when a publish nsec is available).
69
64
  const proofPath = `${apkPath}.identity-proof`;
@@ -81,8 +76,32 @@ async function publishCommand(configPath, options = {}) {
81
76
  // No sidecar found — warn so users can spot a missing 30509 proof
82
77
  // in CI output (the sign step may have run without a publish nsec).
83
78
  console.log("⚠️ No .identity-proof sidecar found — the kind 30509 identity proof will not be published.");
84
- console.log(" Re-run \`pakstr sign\` with a publish nsec available to generate it.");
79
+ console.log(" Re-run `pakstr sign` with a publish nsec available to generate it.");
85
80
  }
81
+ }
82
+ // Publish the application event first so Zapstore can verify the repository
83
+ // and authorize a first-time publisher before the Blossom upload.
84
+ const appPublish = await (0, zapStore_1.buildAppMetadataEvent)({ app, publishNsec, target });
85
+ await transport.publish(appPublish.appMetadata, config.publish.relay);
86
+ if (options.dryRun) {
87
+ // No network, no APK required. Synthesize a stub descriptor so the events
88
+ // can still be built and printed for preview.
89
+ apkSha256 = "0".repeat(64);
90
+ apkSize = 0;
91
+ blossomUrl = `${config.publish.blossom.replace(/\/$/, "")}/${apkSha256}`;
92
+ signerCertificateSha256 = "0".repeat(64);
93
+ filename = path_1.default.basename(apkPath);
94
+ // Stub identity proof so the 30509 event path is exercised end-to-end.
95
+ identityProof = {
96
+ certHash: signerCertificateSha256,
97
+ signature: "dry-run-stub==",
98
+ createdAt: Math.floor(Date.now() / 1000),
99
+ expiry: Math.floor(Date.now() / 1000) + 365 * 24 * 3600,
100
+ pubkeyHex: await (0, nostr_1.getNpubHex)(publishNsec.bytes),
101
+ };
102
+ console.log("📦 (dry-run) Blossom URL:", blossomUrl);
103
+ }
104
+ else {
86
105
  const blossom = await (0, blossom_1.uploadToBlossom)({
87
106
  serverUrl: config.publish.blossom,
88
107
  filePath: apkPath,
@@ -92,20 +111,11 @@ async function publishCommand(configPath, options = {}) {
92
111
  blossomUrl = blossom.url;
93
112
  apkSha256 = blossom.sha256;
94
113
  apkSize = blossom.size;
95
- filename = path_1.default.basename(apkPath);
96
114
  console.log("📦 Uploaded to Blossom:", blossom.url);
97
115
  console.log(" APK SHA-256:", blossom.sha256, "| size:", blossom.size);
98
116
  }
99
- // Build + publish the NIP-82 events.
100
- const transport = options.transport ?? (options.dryRun ? new zapStore_1.MockRelayTransport() : new zapStore_1.WebsocketRelayTransport());
101
- const result = await (0, zapStore_1.publishToZapStore)({
102
- app: {
103
- appId: config.app.appId,
104
- appName: config.app.appName,
105
- versionName: config.app.versionName,
106
- versionCode: config.app.versionCode,
107
- description: config.app.description,
108
- },
117
+ const releasePublish = await (0, zapStore_1.buildReleaseEvents)({
118
+ app,
109
119
  apk: {
110
120
  apkSha256,
111
121
  apkSize,
@@ -114,9 +124,29 @@ async function publishCommand(configPath, options = {}) {
114
124
  filename,
115
125
  },
116
126
  publishNsec,
117
- target: { relayUrl: config.publish.relay },
127
+ target,
118
128
  identityProof,
119
- }, transport);
129
+ });
130
+ await transport.publish(releasePublish.asset, config.publish.relay);
131
+ await transport.publish(releasePublish.release, config.publish.relay);
132
+ if (releasePublish.identityProof) {
133
+ await transport.publish(releasePublish.identityProof, config.publish.relay);
134
+ }
135
+ const result = {
136
+ npub: appPublish.npub,
137
+ pubkeyHex: appPublish.pubkeyHex,
138
+ appMetadataEventId: appPublish.appMetadata.id,
139
+ releaseEventId: releasePublish.release.id,
140
+ assetEventId: releasePublish.asset.id,
141
+ identityProofEventId: releasePublish.identityProof?.id,
142
+ events: {
143
+ appMetadata: appPublish.appMetadata,
144
+ release: releasePublish.release,
145
+ asset: releasePublish.asset,
146
+ identityProof: releasePublish.identityProof,
147
+ },
148
+ relayUrl: config.publish.relay,
149
+ };
120
150
  console.log(options.dryRun ? "✅ PUBLISHED (dry-run — no network)" : "✅ PUBLISHED");
121
151
  console.log("🪪 Publisher npub:", result.npub);
122
152
  console.log("🧾 Asset event:", result.assetEventId);
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MockRelayTransport = exports.WebsocketRelayTransport = exports.IDENTITY_PROOF_MESSAGE = exports.KIND_IDENTITY_PROOF = exports.KIND_ASSET = exports.KIND_RELEASE = exports.KIND_APP_METADATA = exports.DEFAULT_COMMUNITY = void 0;
4
4
  exports.formatIdentityProofMessage = formatIdentityProofMessage;
5
+ exports.buildAppMetadataEvent = buildAppMetadataEvent;
6
+ exports.buildReleaseEvents = buildReleaseEvents;
5
7
  exports.buildSignedPublishEvents = buildSignedPublishEvents;
6
8
  exports.publishToZapStore = publishToZapStore;
7
9
  exports.resolvePublishSecret = resolvePublishSecret;
@@ -36,25 +38,31 @@ function formatIdentityProofMessage(createdAt, expiry, pubkeyHex) {
36
38
  const PLATFORM = "android-arm64-v8a";
37
39
  const MIN_SDK = 28;
38
40
  const TARGET_SDK = 36;
39
- /** Build the NIP-82 events (unsigned signed) for an app release.
40
- * Includes a NIP-C1 identity proof (kind 30509) if `identityProof` is set. */
41
- async function buildSignedPublishEvents(input) {
41
+ /** Build the kind-32267 application event independently of the APK upload. */
42
+ async function buildAppMetadataEvent(input) {
42
43
  const pubkeyHex = await (0, nostr_1.getNpubHex)(input.publishNsec.bytes);
43
44
  const npub = (0, nostr_1.pubkeyHexToNpub)(pubkeyHex);
44
- const now = Math.floor(Date.now() / 1000);
45
45
  const communities = input.communities?.length ? input.communities : [exports.DEFAULT_COMMUNITY];
46
- // kind 32267 Software Application (app metadata)
46
+ const repositoryTags = input.app.repository ? [["repository", input.app.repository]] : [];
47
47
  const appMetadata = await (0, nostr_1.signNostrEvent)({
48
48
  kind: exports.KIND_APP_METADATA,
49
- created_at: now,
49
+ created_at: Math.floor(Date.now() / 1000),
50
50
  tags: [
51
51
  ["d", input.app.appId],
52
52
  ["name", input.app.appName],
53
53
  ["f", PLATFORM],
54
54
  ...communities.map(c => ["h", c]),
55
+ ...repositoryTags,
55
56
  ],
56
57
  content: input.app.description ?? "",
57
58
  }, input.publishNsec.bytes);
59
+ return { npub, pubkeyHex, appMetadata };
60
+ }
61
+ /** Build the APK-dependent NIP-82 events after the Blossom upload. */
62
+ async function buildReleaseEvents(input) {
63
+ const pubkeyHex = await (0, nostr_1.getNpubHex)(input.publishNsec.bytes);
64
+ const npub = (0, nostr_1.pubkeyHexToNpub)(pubkeyHex);
65
+ const now = Math.floor(Date.now() / 1000);
58
66
  // kind 3063 — Software Asset (the APK)
59
67
  const asset = await (0, nostr_1.signNostrEvent)({
60
68
  kind: exports.KIND_ASSET,
@@ -112,7 +120,21 @@ async function buildSignedPublishEvents(input) {
112
120
  content: formatIdentityProofMessage(input.identityProof.createdAt, input.identityProof.expiry, input.identityProof.pubkeyHex),
113
121
  }, input.publishNsec.bytes);
114
122
  }
115
- return { npub, pubkeyHex, appMetadata, release, asset, identityProof: identityProofEvent ?? undefined };
123
+ return { npub, pubkeyHex, release, asset, identityProof: identityProofEvent ?? undefined };
124
+ }
125
+ /** Build the NIP-82 events (unsigned → signed) for an app release.
126
+ * Includes a NIP-C1 identity proof (kind 30509) if `identityProof` is set. */
127
+ async function buildSignedPublishEvents(input) {
128
+ const app = await buildAppMetadataEvent(input);
129
+ const release = await buildReleaseEvents(input);
130
+ return {
131
+ npub: app.npub,
132
+ pubkeyHex: app.pubkeyHex,
133
+ appMetadata: app.appMetadata,
134
+ release: release.release,
135
+ asset: release.asset,
136
+ identityProof: release.identityProof,
137
+ };
116
138
  }
117
139
  /** Publish all three events to the relay via the given transport. */
118
140
  async function publishToZapStore(input, transport) {
@@ -0,0 +1,22 @@
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.ZAPSTORE_CONFIG_FILENAME = void 0;
7
+ exports.loadZapstoreRepository = loadZapstoreRepository;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const js_yaml_1 = __importDefault(require("js-yaml"));
11
+ exports.ZAPSTORE_CONFIG_FILENAME = "zapstore.yaml";
12
+ /** Read the repository URL from the zapstore.yaml beside pakstr.yaml. */
13
+ function loadZapstoreRepository(configDir) {
14
+ const configPath = path_1.default.join(configDir, exports.ZAPSTORE_CONFIG_FILENAME);
15
+ if (!fs_1.default.existsSync(configPath))
16
+ return undefined;
17
+ const parsed = js_yaml_1.default.load(fs_1.default.readFileSync(configPath, "utf8"));
18
+ const repository = parsed?.repository;
19
+ if (typeof repository !== "string" || repository.trim().length === 0)
20
+ return undefined;
21
+ return repository.trim();
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "description": "CLI for packaging Nostr web apps into Android APKs",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",