pakstr 0.13.1 → 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.
- package/dist/commands/publish.js +67 -37
- package/dist/core/blossom.js +1 -0
- package/dist/core/zapStore.js +29 -7
- package/dist/core/zapstoreConfig.js +22 -0
- package/package.json +1 -1
package/dist/commands/publish.js
CHANGED
|
@@ -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` —
|
|
16
|
-
*
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
127
|
+
target,
|
|
118
128
|
identityProof,
|
|
119
|
-
}
|
|
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);
|
package/dist/core/blossom.js
CHANGED
package/dist/core/zapStore.js
CHANGED
|
@@ -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
|
|
40
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
+
}
|