pakstr 0.14.0 → 0.16.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.
@@ -38,6 +38,7 @@ async function publishCommand(configPath, options = {}) {
38
38
  console.log("\nšŸ“¤ pakstr publish" + (options.dryRun ? " (dry-run)" : ""));
39
39
  console.log("App:", config.app.appName, `(${config.app.appId})`);
40
40
  console.log("Publishing as:", publishNsec.envVar);
41
+ console.log("Publisher npub:", npub);
41
42
  console.log("Relay:", config.publish.relay);
42
43
  console.log("Blossom:", config.publish.blossom);
43
44
  let blossomUrl;
@@ -50,10 +51,15 @@ async function publishCommand(configPath, options = {}) {
50
51
  const repository = zapstoreConfig?.repository ?? (0, zapstoreConfig_1.loadZapstoreRepository)(config.configDir);
51
52
  const app = {
52
53
  appId: config.app.appId,
53
- appName: config.app.appName,
54
+ appName: zapstoreConfig?.name ?? config.app.appName,
54
55
  versionName: config.app.versionName,
55
56
  versionCode: config.app.versionCode,
56
- description: config.app.description,
57
+ description: zapstoreConfig?.description ?? config.app.description,
58
+ summary: zapstoreConfig?.summary,
59
+ tags: zapstoreConfig?.tags,
60
+ license: zapstoreConfig?.license,
61
+ website: zapstoreConfig?.website,
62
+ releaseNotes: zapstoreConfig?.releaseNotes,
57
63
  repository,
58
64
  };
59
65
  const target = { relayUrl: config.publish.relay };
@@ -97,14 +103,17 @@ async function publishCommand(configPath, options = {}) {
97
103
  pubkeyHex,
98
104
  relayUrl: config.publish.relay,
99
105
  });
106
+ console.log(`šŸ“‹ Existing kind-32267: ${existingAppMetadata?.id ?? "none"}`);
100
107
  const appMetadata = existingAppMetadata && (0, zapStore_1.isCurrentAppMetadataEvent)(existingAppMetadata, appPublish.appMetadata)
101
108
  ? existingAppMetadata
102
109
  : appPublish.appMetadata;
103
110
  if (appMetadata === appPublish.appMetadata) {
111
+ console.log("šŸ“¤ Publishing kind-32267...");
104
112
  await transport.publish(appMetadata, config.publish.relay);
113
+ console.log(`āœ… Published kind-32267: ${appMetadata.id}`);
105
114
  }
106
115
  else {
107
- console.log("šŸ“± Existing Zapstore application metadata is current; skipping kind 32267 publish.");
116
+ console.log("āœ… kind-32267 already up to date, skipping publish");
108
117
  }
109
118
  if (options.dryRun) {
110
119
  // No network, no APK required. Synthesize a stub descriptor so the events
@@ -76,6 +76,10 @@ async function buildAppMetadataEvent(input) {
76
76
  const pubkeyHex = await (0, nostr_1.getNpubHex)(input.publishNsec.bytes);
77
77
  const npub = (0, nostr_1.pubkeyHexToNpub)(pubkeyHex);
78
78
  const communities = input.communities?.length ? input.communities : [exports.DEFAULT_COMMUNITY];
79
+ const summaryTags = input.app.summary !== undefined ? [["summary", input.app.summary]] : [];
80
+ const topicTags = (input.app.tags ?? []).map(tag => ["t", tag]);
81
+ const licenseTags = input.app.license !== undefined ? [["license", input.app.license]] : [];
82
+ const websiteTags = input.app.website !== undefined ? [["url", input.app.website]] : [];
79
83
  const repositoryTags = input.app.repository ? [["repository", input.app.repository]] : [];
80
84
  const appMetadata = await (0, nostr_1.signNostrEvent)({
81
85
  kind: exports.KIND_APP_METADATA,
@@ -83,8 +87,12 @@ async function buildAppMetadataEvent(input) {
83
87
  tags: [
84
88
  ["d", input.app.appId],
85
89
  ["name", input.app.appName],
90
+ ...summaryTags,
86
91
  ["f", PLATFORM],
87
92
  ...communities.map(c => ["h", c]),
93
+ ...topicTags,
94
+ ...licenseTags,
95
+ ...websiteTags,
88
96
  ...repositoryTags,
89
97
  ],
90
98
  content: input.app.description ?? "",
@@ -128,7 +136,7 @@ async function buildReleaseEvents(input) {
128
136
  ["f", PLATFORM],
129
137
  ["e", asset.id, input.target.relayUrl],
130
138
  ],
131
- content: "",
139
+ content: input.app.releaseNotes ?? "",
132
140
  }, input.publishNsec.bytes);
133
141
  // kind 30509 — NIP-C1 Identity Proof (optional, links the APK signing
134
142
  // certificate to the publisher Nostr identity). The signature is produced
@@ -29,7 +29,7 @@ function loadZapstoreRepository(configDir) {
29
29
  const repository = parsed?.repository;
30
30
  if (typeof repository !== "string" || repository.trim().length === 0)
31
31
  return undefined;
32
- return repository.trim();
32
+ return normalizeRepositoryUrl(repository.trim());
33
33
  }
34
34
  /** Load and validate the Zapstore publisher configuration used for publication. */
35
35
  function loadZapstorePublisherConfig(configDir) {
@@ -48,9 +48,20 @@ function loadZapstorePublisherConfig(configDir) {
48
48
  throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} must contain a YAML mapping`, configPath);
49
49
  }
50
50
  const config = parsed;
51
- const repository = requireString(config.repository, "repository", configPath);
52
- validateRepositoryUrl(repository, configPath);
51
+ const rawRepository = requireString(config.repository, "repository", configPath);
52
+ validateRepositoryUrl(rawRepository, configPath);
53
+ const repository = normalizeRepositoryUrl(rawRepository);
53
54
  const pubkey = requireString(config.pubkey, "pubkey", configPath);
55
+ const name = optionalString(config.name, "name", configPath);
56
+ const summary = optionalString(config.summary, "summary", configPath);
57
+ const description = optionalString(config.description, "description", configPath, false);
58
+ const tags = optionalTags(config.tags, configPath);
59
+ const license = optionalString(config.license, "license", configPath);
60
+ const website = optionalString(config.website, "website", configPath);
61
+ const releaseNotesPath = optionalString(config.release_notes, "release_notes", configPath);
62
+ const releaseNotes = releaseNotesPath === undefined
63
+ ? undefined
64
+ : readReleaseNotes(releaseNotesPath, configDir, configPath);
54
65
  let pubkeyHex;
55
66
  try {
56
67
  pubkeyHex = (0, nostr_1.npubToPubkeyHex)(pubkey);
@@ -58,7 +69,18 @@ function loadZapstorePublisherConfig(configDir) {
58
69
  catch (error) {
59
70
  throw new ZapstoreConfigError(`Invalid publisher npub in ${exports.ZAPSTORE_CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`, configPath);
60
71
  }
61
- return { repository, pubkey, pubkeyHex };
72
+ return {
73
+ repository,
74
+ pubkey,
75
+ pubkeyHex,
76
+ name,
77
+ summary,
78
+ description,
79
+ tags,
80
+ license,
81
+ website,
82
+ releaseNotes,
83
+ };
62
84
  }
63
85
  function requireString(value, field, configPath) {
64
86
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -66,6 +88,49 @@ function requireString(value, field, configPath) {
66
88
  }
67
89
  return value.trim();
68
90
  }
91
+ function optionalString(value, field, configPath, trim = true) {
92
+ if (value === undefined)
93
+ return undefined;
94
+ if (typeof value !== "string") {
95
+ throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} field ${field} must be a string`, configPath);
96
+ }
97
+ if (value.trim().length === 0 && field === "release_notes") {
98
+ throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} field ${field} must not be empty`, configPath);
99
+ }
100
+ return trim ? value.trim() : value;
101
+ }
102
+ function optionalTags(value, configPath) {
103
+ if (value === undefined)
104
+ return undefined;
105
+ if (!Array.isArray(value)) {
106
+ throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} field tags must be an array of non-empty strings`, configPath);
107
+ }
108
+ return value.map(tag => {
109
+ if (typeof tag !== "string" || tag.trim().length === 0) {
110
+ throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} field tags must be an array of non-empty strings`, configPath);
111
+ }
112
+ return tag.trim();
113
+ });
114
+ }
115
+ function readReleaseNotes(releaseNotesPath, configDir, configPath) {
116
+ const resolved = path_1.default.resolve(configDir, releaseNotesPath);
117
+ if (!fs_1.default.existsSync(resolved) || !fs_1.default.lstatSync(resolved).isFile()) {
118
+ throw new ZapstoreConfigError(`${exports.ZAPSTORE_CONFIG_FILENAME} release_notes file not found: ${resolved}`, configPath);
119
+ }
120
+ try {
121
+ return fs_1.default.readFileSync(resolved, "utf8");
122
+ }
123
+ catch (error) {
124
+ throw new ZapstoreConfigError(`Could not read ${exports.ZAPSTORE_CONFIG_FILENAME} release_notes file ${resolved}: ${error instanceof Error ? error.message : String(error)}`, configPath);
125
+ }
126
+ }
127
+ function normalizeRepositoryUrl(repository) {
128
+ const queryIndex = repository.indexOf("?");
129
+ const fragmentIndex = repository.indexOf("#");
130
+ if (queryIndex !== -1 || fragmentIndex !== -1)
131
+ return repository;
132
+ return repository.endsWith(".git") ? repository.slice(0, -4) : repository;
133
+ }
69
134
  function validateRepositoryUrl(repository, configPath) {
70
135
  let parsed;
71
136
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "CLI for packaging Nostr web apps into Android APKs",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",