create-dig-app 0.2.3 → 0.3.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/lib/nft-cli.js CHANGED
@@ -89,10 +89,18 @@ function resolveItems(root, imageFiles) {
89
89
  * metadata_hash from the canonical metadata JSON; if a license has been generated, wires its URI +
90
90
  * hash in too.
91
91
  *
92
+ * The store id defaults to {@link PLACEHOLDER_STORE_ID} for the initial content-prep pass (before
93
+ * the capsule is published). After `digstore deploy` returns the real store id, re-run with
94
+ * `{ storeId }` (CLI `--store-id <id>`) to bake the REAL id into every generated URI so the minted
95
+ * NFT points at the published capsule — otherwise the on-chain `data_uris`/`metadata_uris`/
96
+ * `license_uris` keep the literal placeholder permanently (there is no separate substitution step).
97
+ *
92
98
  * @param {string} root Project directory.
99
+ * @param {{storeId?:string}} [opts] `storeId` — the real published store id; omit for the placeholder.
93
100
  * @returns {{count:number, manifestPath:string, metadataDir:string}}
94
101
  */
95
- export function generateMetadata(root) {
102
+ export function generateMetadata(root, opts = {}) {
103
+ const storeId = opts.storeId ?? PLACEHOLDER_STORE_ID;
96
104
  const collection = loadCollection(root);
97
105
  const imageFiles = listImages(root);
98
106
  const items = resolveItems(root, imageFiles);
@@ -101,7 +109,7 @@ export function generateMetadata(root) {
101
109
  mkdirSync(metadataDir, { recursive: true });
102
110
 
103
111
  // If a license file already exists in licenses/, fold its URI + hash into each item's media.
104
- const license = readGeneratedLicense(root, collection);
112
+ const license = readGeneratedLicense(root, collection, storeId);
105
113
 
106
114
  const mds = generateItemMetadata(collection, items);
107
115
  const manifest = [];
@@ -122,8 +130,8 @@ export function generateMetadata(root) {
122
130
  writeFileSync(join(metadataDir, metaName), metaJson + "\n");
123
131
  const metadataHash = metadataHashHex(md);
124
132
 
125
- const data = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `images/${file}` });
126
- const meta = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `metadata/${metaName}` });
133
+ const data = capsuleResourceUris({ storeId, resource: `images/${file}` });
134
+ const meta = capsuleResourceUris({ storeId, resource: `metadata/${metaName}` });
127
135
 
128
136
  const media = {
129
137
  data_uris: data.uris,
@@ -148,14 +156,14 @@ export function generateMetadata(root) {
148
156
  }
149
157
 
150
158
  /** Read an already-generated license file (if any) and return its uris + hash, else null. */
151
- function readGeneratedLicense(root, collection) {
159
+ function readGeneratedLicense(root, collection, storeId = PLACEHOLDER_STORE_ID) {
152
160
  const id = collection?.license;
153
161
  if (!id || !LICENSES[id]) return null;
154
162
  const file = licenseFileName(id);
155
163
  const path = join(root, "licenses", file);
156
164
  if (!existsSync(path)) return null;
157
165
  const bytes = readFileSync(path);
158
- const { uris } = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `licenses/${file}` });
166
+ const { uris } = capsuleResourceUris({ storeId, resource: `licenses/${file}` });
159
167
  return { id, file, hash: sha256Hex(bytes), uris };
160
168
  }
161
169
 
@@ -237,7 +245,7 @@ export function validateProject(root) {
237
245
  throw new Error(`item "${item.name}": metadata_hash does not match any metadata/*.json`);
238
246
  }
239
247
  }
240
- // data_hash agrees with the real image bytes (resolve the image path from the dig:// URN tail).
248
+ // data_hash agrees with the real image bytes (resolve the image path from the URN's resource tail).
241
249
  const imgFile = resourceTail(media.data_uris?.[0]);
242
250
  if (imgFile && media.data_hash != null) {
243
251
  const imgPath = join(root, imgFile);
@@ -257,9 +265,14 @@ export function validateProject(root) {
257
265
  return { ok: true, checked };
258
266
  }
259
267
 
260
- /** Extract the resource path (everything after the first `/`) from a `dig://urn:…/<resource>` URI. */
268
+ /**
269
+ * Extract the resource path from a capsule URI — the canonical bare URN
270
+ * `urn:dig:chia:<storeId>:<root>/<resource>` or the `https://<host>/<resource>` gateway fallback.
271
+ * Strip an optional `scheme://authority` prefix, then the resource is everything after the first `/`.
272
+ */
261
273
  function resourceTail(uri) {
262
274
  if (!uri || typeof uri !== "string") return null;
263
- const i = uri.indexOf("/", uri.indexOf("://") + 3);
264
- return i >= 0 ? uri.slice(i + 1) : null;
275
+ const afterAuthority = uri.includes("://") ? uri.slice(uri.indexOf("://") + 3) : uri;
276
+ const i = afterAuthority.indexOf("/");
277
+ return i >= 0 ? afterAuthority.slice(i + 1) : null;
265
278
  }
@@ -364,14 +364,18 @@ export function itemsFromImages(fileNames) {
364
364
  });
365
365
  }
366
366
 
367
- // ── Capsule resource URIs (dig:// URN first, https gateway fallback) ───────────────────────────────
367
+ // ── Capsule resource URIs (canonical URN first, https gateway fallback) ─────────────────────────────
368
368
 
369
369
  /** The HTTPS gateway host for decrypted capsule resources (mirrors the hub's CAPSULE_HTTPS_GATEWAY). */
370
370
  export const CAPSULE_HTTPS_GATEWAY = "usercontent.dig.net";
371
371
 
372
372
  /**
373
- * Build the CHIP-0007 media URIs for a resource inside a capsule: the canonical `dig://` URN first,
373
+ * Build the CHIP-0007 media URIs for a resource inside a capsule: the canonical bare URN first,
374
374
  * then the https gateway fallback (the order the hub + digstore use).
375
+ *
376
+ * The URN is the bare, root-pinned `urn:dig:chia:<storeId>:<root>/<resource>` form — NEVER a
377
+ * `dig://`-prefixed URN (the `dig://urn:dig:chia:…` double-scheme is the #686 bug). `dig://` is not
378
+ * a content/resource scheme; the dig-urn-resolver consumes the bare URN and decides where to fetch.
375
379
  * @param {{storeId:string,root?:string,resource:string}} args
376
380
  * @returns {{urn:string,https:string,uris:string[]}}
377
381
  */
@@ -379,7 +383,7 @@ export function capsuleResourceUris({ storeId, root, resource }) {
379
383
  const sid = String(storeId ?? "").trim();
380
384
  const r = String(root ?? "").trim();
381
385
  const res = String(resource ?? "").trim().replace(/^\/+/, "");
382
- const urn = `dig://urn:dig:chia:${sid}${r ? `:${r}` : ""}/${res}`;
386
+ const urn = `urn:dig:chia:${sid}${r ? `:${r}` : ""}/${res}`;
383
387
  const https = `https://${sid}.${CAPSULE_HTTPS_GATEWAY}/${res}`;
384
388
  return { urn, https, uris: [urn, https] };
385
389
  }
package/lib/templates.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // `__PLACEHOLDER__` token substitution (see substitute.js). The metadata here drives:
5
5
  // - the interactive picker + `--template` validation (templateNames / resolveTemplate),
6
6
  // - the dig.toml the scaffolder writes (outputDir + buildCommand, which the SDK adapter and
7
- // `digstore deploy` both read — see modules/dig-sdk/src/adapters/dig-toml.ts),
7
+ // `digstore deploy` both read — see modules/dx/dig-sdk/src/adapters/dig-toml.ts),
8
8
  // - which templates wire @dignetwork/dig-sdk (wallet) vs stay dependency-light static sites.
9
9
  //
10
10
  // Templates are kept MINIMAL but real: each one `npm install`s and builds, and none bundles a heavy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dig-app",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold a wallet-wired, deployable DIG Network app — free, no mint. The JS front door for building dapps, frontends, and NFT collections on Chia.",
5
5
  "license": "MIT",
6
6
  "author": "DIG Network",
@@ -86,10 +86,17 @@ Everything shared by all items lives here:
86
86
  ## Mint it
87
87
 
88
88
  ```sh
89
- # Publish the art + metadata + licenses as ONE capsule (this is the only deploy that spends $DIG):
89
+ # Publish the art + metadata + licenses as ONE capsule (this is the only deploy that spends $DIG).
90
+ # The deploy prints your capsule's real store id — copy it for the next step:
90
91
  digstore deploy
91
92
 
92
- # Mint the NFTs on-chain from collection.json + the generated items.json (wallet-signed):
93
+ # Bake the REAL store id into every metadata/item URI. The first `npm run generate` used a
94
+ # placeholder (STORE_ID_AFTER_PUBLISH) because the store id isn't known until you deploy. Skip this
95
+ # and the minted NFT points at the placeholder FOREVER — there is no later substitution step:
96
+ npm run generate:metadata -- --store-id <store-id-from-deploy>
97
+ npm run validate
98
+
99
+ # Mint the NFTs on-chain from collection.json + the finalized items.json (wallet-signed):
93
100
  digstore collection create --name "__DISPLAY_NAME__" # one-time: register the collection on-chain
94
101
  digstore collection mint --collection collection.json --manifest items.json --did <your-did>
95
102
  ```
@@ -215,7 +215,7 @@ function capsuleResourceUris({ storeId, root, resource }) {
215
215
  const sid = String(storeId ?? "").trim();
216
216
  const r = String(root ?? "").trim();
217
217
  const res = String(resource ?? "").trim().replace(/^\/+/, "");
218
- const urn = `dig://urn:dig:chia:${sid}${r ? `:${r}` : ""}/${res}`;
218
+ const urn = `urn:dig:chia:${sid}${r ? `:${r}` : ""}/${res}`;
219
219
  const https = `https://${sid}.${CAPSULE_HTTPS_GATEWAY}/${res}`;
220
220
  return { urn, https, uris: [urn, https] };
221
221
  }
@@ -346,24 +346,25 @@ function resolveItems(root, imageFiles) {
346
346
  return items.map((item, i) => ({ ...item, file: item.file || imageFiles[i] }));
347
347
  }
348
348
 
349
- function readGeneratedLicense(root, collection) {
349
+ function readGeneratedLicense(root, collection, storeId = PLACEHOLDER_STORE_ID) {
350
350
  const id = collection?.license;
351
351
  if (!id || !LICENSES[id]) return null;
352
352
  const file = licenseFileName(id);
353
353
  const path = join(root, "licenses", file);
354
354
  if (!existsSync(path)) return null;
355
355
  const bytes = readFileSync(path);
356
- const { uris } = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `licenses/${file}` });
356
+ const { uris } = capsuleResourceUris({ storeId, resource: `licenses/${file}` });
357
357
  return { id, file, hash: sha256Hex(bytes), uris };
358
358
  }
359
359
 
360
- function generateMetadata(root) {
360
+ function generateMetadata(root, opts = {}) {
361
+ const storeId = opts.storeId ?? PLACEHOLDER_STORE_ID;
361
362
  const collection = loadCollection(root);
362
363
  const imageFiles = listImages(root);
363
364
  const items = resolveItems(root, imageFiles);
364
365
  const metadataDir = join(root, "metadata");
365
366
  mkdirSync(metadataDir, { recursive: true });
366
- const license = readGeneratedLicense(root, collection);
367
+ const license = readGeneratedLicense(root, collection, storeId);
367
368
  const mds = generateItemMetadata(collection, items);
368
369
  const manifest = [];
369
370
  for (let i = 0; i < items.length; i++) {
@@ -378,8 +379,8 @@ function generateMetadata(root) {
378
379
  const metaName = `${stem}.json`;
379
380
  writeFileSync(join(metadataDir, metaName), canonicalJson(md) + "\n");
380
381
  const metadataHash = metadataHashHex(md);
381
- const data = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `images/${file}` });
382
- const meta = capsuleResourceUris({ storeId: PLACEHOLDER_STORE_ID, resource: `metadata/${metaName}` });
382
+ const data = capsuleResourceUris({ storeId, resource: `images/${file}` });
383
+ const meta = capsuleResourceUris({ storeId, resource: `metadata/${metaName}` });
383
384
  manifest.push({
384
385
  name: md.name,
385
386
  description: item.description || undefined,
@@ -461,13 +462,28 @@ function validateProject(root) {
461
462
 
462
463
  // ── CLI dispatch ──────────────────────────────────────────────────────────────────────────────────
463
464
 
465
+ /** Read a `--flag value` / `--flag=value` option from an argv slice; undefined if absent. */
466
+ function flagValue(args, name) {
467
+ for (let i = 0; i < args.length; i++) {
468
+ if (args[i] === name) return args[i + 1];
469
+ if (args[i].startsWith(`${name}=`)) return args[i].slice(name.length + 1);
470
+ }
471
+ return undefined;
472
+ }
473
+
464
474
  function main() {
465
475
  const cmd = process.argv[2];
476
+ const args = process.argv.slice(3);
466
477
  try {
467
478
  if (cmd === "metadata") {
468
- const r = generateMetadata(ROOT);
479
+ const storeId = flagValue(args, "--store-id");
480
+ const r = generateMetadata(ROOT, storeId ? { storeId } : {});
469
481
  console.log(`Generated ${r.count} CHIP-0007 metadata file(s) → metadata/ and the items.json manifest.`);
470
- console.log("Next: `npm run validate`, then mint with `digstore collection mint --collection collection.json --manifest items.json`.");
482
+ if (storeId) {
483
+ console.log(`Baked store id ${storeId} into every URI. Next: \`npm run validate\`, then \`digstore collection mint --collection collection.json --manifest items.json\`.`);
484
+ } else {
485
+ console.log("Used the placeholder store id. After `digstore deploy` gives you the real store id, re-run `npm run generate:metadata -- --store-id <id>` to bake it in — otherwise the minted NFT keeps the placeholder. Then `npm run validate` and `digstore collection mint`.");
486
+ }
471
487
  } else if (cmd === "license") {
472
488
  const r = generateLicense(ROOT);
473
489
  console.log(`Wrote licenses/${r.file} (sha256 ${r.hash}).`);
@@ -476,8 +492,9 @@ function main() {
476
492
  const r = validateProject(ROOT);
477
493
  console.log(`OK — ${r.checked} item(s) valid: CHIP-0007 schema + data/metadata/license hashes agree with the real bytes.`);
478
494
  } else {
479
- console.error("Usage: node scripts/dig-nft.mjs <metadata|license|validate>");
495
+ console.error("Usage: node scripts/dig-nft.mjs <metadata|license|validate> [--store-id <id>]");
480
496
  console.error(" metadata generate CHIP-0007 metadata/*.json + items.json from images/ + collection.json");
497
+ console.error(" --store-id <id> bake the REAL published store id into URIs (after `digstore deploy`)");
481
498
  console.error(" license write the license chosen in collection.json into licenses/");
482
499
  console.error(" validate re-verify schema + URI/hash agreement before minting");
483
500
  process.exit(2);