@solana-mobile/dapp-store-cli 0.15.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.
@@ -0,0 +1,437 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _define_property(obj, key, value) {
31
+ if (key in obj) {
32
+ Object.defineProperty(obj, key, {
33
+ value: value,
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true
37
+ });
38
+ } else {
39
+ obj[key] = value;
40
+ }
41
+ return obj;
42
+ }
43
+ function _ts_generator(thisArg, body) {
44
+ var f, y, t, _ = {
45
+ label: 0,
46
+ sent: function() {
47
+ if (t[0] & 1) throw t[1];
48
+ return t[1];
49
+ },
50
+ trys: [],
51
+ ops: []
52
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
53
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
54
+ return this;
55
+ }), g;
56
+ function verb(n) {
57
+ return function(v) {
58
+ return step([
59
+ n,
60
+ v
61
+ ]);
62
+ };
63
+ }
64
+ function step(op) {
65
+ if (f) throw new TypeError("Generator is already executing.");
66
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
67
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
68
+ if (y = 0, t) op = [
69
+ op[0] & 2,
70
+ t.value
71
+ ];
72
+ switch(op[0]){
73
+ case 0:
74
+ case 1:
75
+ t = op;
76
+ break;
77
+ case 4:
78
+ _.label++;
79
+ return {
80
+ value: op[1],
81
+ done: false
82
+ };
83
+ case 5:
84
+ _.label++;
85
+ y = op[1];
86
+ op = [
87
+ 0
88
+ ];
89
+ continue;
90
+ case 7:
91
+ op = _.ops.pop();
92
+ _.trys.pop();
93
+ continue;
94
+ default:
95
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
96
+ _ = 0;
97
+ continue;
98
+ }
99
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
100
+ _.label = op[1];
101
+ break;
102
+ }
103
+ if (op[0] === 6 && _.label < t[1]) {
104
+ _.label = t[1];
105
+ t = op;
106
+ break;
107
+ }
108
+ if (t && _.label < t[2]) {
109
+ _.label = t[2];
110
+ _.ops.push(op);
111
+ break;
112
+ }
113
+ if (t[2]) _.ops.pop();
114
+ _.trys.pop();
115
+ continue;
116
+ }
117
+ op = body.call(thisArg, _);
118
+ } catch (e) {
119
+ op = [
120
+ 6,
121
+ e
122
+ ];
123
+ y = 0;
124
+ } finally{
125
+ f = t = 0;
126
+ }
127
+ if (op[0] & 5) throw op[1];
128
+ return {
129
+ value: op[0] ? op[1] : void 0,
130
+ done: true
131
+ };
132
+ }
133
+ }
134
+ import fs from "fs";
135
+ import os from "os";
136
+ import path from "path";
137
+ import { createHash } from "crypto";
138
+ import { jest } from "@jest/globals";
139
+ import { CachedStorageDriver } from "../CachedStorageDriver";
140
+ describe("CachedStorageDriver", function() {
141
+ var originalCwd = process.cwd();
142
+ var tempDir;
143
+ beforeEach(function() {
144
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "cached-storage-driver-"));
145
+ process.chdir(tempDir);
146
+ });
147
+ afterEach(function() {
148
+ process.chdir(originalCwd);
149
+ fs.rmSync(tempDir, {
150
+ recursive: true,
151
+ force: true
152
+ });
153
+ });
154
+ test("normalizes a cached legacy arweave URL and persists the rewritten manifest", function() {
155
+ return _async_to_generator(function() {
156
+ var file, hash, manifestPath, uploadCalls, storageDriver, driver, rewrittenManifest;
157
+ return _ts_generator(this, function(_state) {
158
+ switch(_state.label){
159
+ case 0:
160
+ file = makeMetaplexFile("icon.png", Buffer.from("cached-asset"));
161
+ hash = hashBuffer(file.buffer);
162
+ manifestPath = ".asset-manifest.json";
163
+ fs.writeFileSync(path.join(tempDir, manifestPath), JSON.stringify({
164
+ assets: _define_property({}, file.fileName, {
165
+ path: file.fileName,
166
+ sha256: hash,
167
+ uri: "https://arweave.net/legacy-id"
168
+ })
169
+ }, null, 2));
170
+ uploadCalls = 0;
171
+ storageDriver = {
172
+ getUploadPrice: function(_bytes) {
173
+ return _async_to_generator(function() {
174
+ return _ts_generator(this, function(_state) {
175
+ throw new Error("getUploadPrice should not be called in this test");
176
+ });
177
+ })();
178
+ },
179
+ upload: function(_file) {
180
+ return _async_to_generator(function() {
181
+ return _ts_generator(this, function(_state) {
182
+ uploadCalls += 1;
183
+ throw new Error("upload should not be called in this test");
184
+ });
185
+ })();
186
+ }
187
+ };
188
+ driver = new CachedStorageDriver(storageDriver, {
189
+ assetManifestPath: manifestPath
190
+ });
191
+ return [
192
+ 4,
193
+ expect(driver.upload(file)).resolves.toBe("https://dappstorecontent.com/legacy-id")
194
+ ];
195
+ case 1:
196
+ _state.sent();
197
+ expect(uploadCalls).toBe(0);
198
+ rewrittenManifest = JSON.parse(fs.readFileSync(path.join(tempDir, manifestPath), "utf-8"));
199
+ expect(rewrittenManifest.schema_version).toBe("0.1");
200
+ expect(rewrittenManifest.assets[file.fileName].uri).toBe("https://dappstorecontent.com/legacy-id");
201
+ return [
202
+ 2
203
+ ];
204
+ }
205
+ });
206
+ })();
207
+ });
208
+ test("supports absolute asset manifest paths", function() {
209
+ return _async_to_generator(function() {
210
+ var file, hash, manifestPath, uploadCalls, storageDriver, driver, rewrittenManifest;
211
+ return _ts_generator(this, function(_state) {
212
+ switch(_state.label){
213
+ case 0:
214
+ file = makeMetaplexFile("absolute-icon.png", Buffer.from("cached-asset"));
215
+ hash = hashBuffer(file.buffer);
216
+ manifestPath = path.join(tempDir, ".asset-manifest.json");
217
+ fs.writeFileSync(manifestPath, JSON.stringify({
218
+ assets: _define_property({}, file.fileName, {
219
+ path: file.fileName,
220
+ sha256: hash,
221
+ uri: "https://arweave.net/absolute-id"
222
+ })
223
+ }, null, 2));
224
+ uploadCalls = 0;
225
+ storageDriver = {
226
+ getUploadPrice: function(_bytes) {
227
+ return _async_to_generator(function() {
228
+ return _ts_generator(this, function(_state) {
229
+ throw new Error("getUploadPrice should not be called in this test");
230
+ });
231
+ })();
232
+ },
233
+ upload: function(_file) {
234
+ return _async_to_generator(function() {
235
+ return _ts_generator(this, function(_state) {
236
+ uploadCalls += 1;
237
+ throw new Error("upload should not be called in this test");
238
+ });
239
+ })();
240
+ }
241
+ };
242
+ driver = new CachedStorageDriver(storageDriver, {
243
+ assetManifestPath: manifestPath
244
+ });
245
+ return [
246
+ 4,
247
+ expect(driver.upload(file)).resolves.toBe("https://dappstorecontent.com/absolute-id")
248
+ ];
249
+ case 1:
250
+ _state.sent();
251
+ expect(uploadCalls).toBe(0);
252
+ rewrittenManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
253
+ expect(rewrittenManifest.assets[file.fileName].uri).toBe("https://dappstorecontent.com/absolute-id");
254
+ return [
255
+ 2
256
+ ];
257
+ }
258
+ });
259
+ })();
260
+ });
261
+ test("normalizes fresh uploads before persisting them in the manifest", function() {
262
+ return _async_to_generator(function() {
263
+ var file, manifestPath, uploadCalls, storageDriver, driver, persistedManifest;
264
+ return _ts_generator(this, function(_state) {
265
+ switch(_state.label){
266
+ case 0:
267
+ file = makeMetaplexFile("banner.png", Buffer.from("fresh-asset"));
268
+ manifestPath = ".asset-manifest.json";
269
+ uploadCalls = 0;
270
+ storageDriver = {
271
+ getUploadPrice: function(_bytes) {
272
+ return _async_to_generator(function() {
273
+ return _ts_generator(this, function(_state) {
274
+ throw new Error("getUploadPrice should not be called in this test");
275
+ });
276
+ })();
277
+ },
278
+ upload: function(_file) {
279
+ return _async_to_generator(function() {
280
+ return _ts_generator(this, function(_state) {
281
+ uploadCalls += 1;
282
+ return [
283
+ 2,
284
+ "https://arweave.com/fresh-id"
285
+ ];
286
+ });
287
+ })();
288
+ }
289
+ };
290
+ driver = new CachedStorageDriver(storageDriver, {
291
+ assetManifestPath: manifestPath
292
+ });
293
+ return [
294
+ 4,
295
+ expect(driver.upload(file)).resolves.toBe("https://dappstorecontent.com/fresh-id")
296
+ ];
297
+ case 1:
298
+ _state.sent();
299
+ expect(uploadCalls).toBe(1);
300
+ persistedManifest = JSON.parse(fs.readFileSync(path.join(tempDir, manifestPath), "utf-8"));
301
+ expect(persistedManifest.schema_version).toBe("0.1");
302
+ expect(persistedManifest.assets[file.fileName].uri).toBe("https://dappstorecontent.com/fresh-id");
303
+ return [
304
+ 2
305
+ ];
306
+ }
307
+ });
308
+ })();
309
+ });
310
+ test("normalizes inline metadata uploads without caching them", function() {
311
+ return _async_to_generator(function() {
312
+ var file, uploadCalls, storageDriver, driver;
313
+ return _ts_generator(this, function(_state) {
314
+ switch(_state.label){
315
+ case 0:
316
+ file = makeMetaplexFile("inline.json", Buffer.from("{}"));
317
+ uploadCalls = 0;
318
+ storageDriver = {
319
+ getUploadPrice: function(_bytes) {
320
+ return _async_to_generator(function() {
321
+ return _ts_generator(this, function(_state) {
322
+ throw new Error("getUploadPrice should not be called in this test");
323
+ });
324
+ })();
325
+ },
326
+ upload: function(_file) {
327
+ return _async_to_generator(function() {
328
+ return _ts_generator(this, function(_state) {
329
+ uploadCalls += 1;
330
+ return [
331
+ 2,
332
+ "https://arweave.net/metadata-id"
333
+ ];
334
+ });
335
+ })();
336
+ }
337
+ };
338
+ driver = new CachedStorageDriver(storageDriver, {
339
+ assetManifestPath: ".asset-manifest.json"
340
+ });
341
+ return [
342
+ 4,
343
+ expect(driver.upload(file)).resolves.toBe("https://dappstorecontent.com/metadata-id")
344
+ ];
345
+ case 1:
346
+ _state.sent();
347
+ expect(uploadCalls).toBe(1);
348
+ expect(fs.existsSync(path.join(tempDir, ".asset-manifest.json"))).toBe(false);
349
+ return [
350
+ 2
351
+ ];
352
+ }
353
+ });
354
+ })();
355
+ });
356
+ test("returns a normalized cached URL even when manifest rewrite persistence fails", function() {
357
+ return _async_to_generator(function() {
358
+ var file, hash, manifestPath, writeFileSpy, warnSpy, storageDriver, driver;
359
+ return _ts_generator(this, function(_state) {
360
+ switch(_state.label){
361
+ case 0:
362
+ file = makeMetaplexFile("icon.png", Buffer.from("cached-asset"));
363
+ hash = hashBuffer(file.buffer);
364
+ manifestPath = ".asset-manifest.json";
365
+ fs.writeFileSync(path.join(tempDir, manifestPath), JSON.stringify({
366
+ assets: _define_property({}, file.fileName, {
367
+ path: file.fileName,
368
+ sha256: hash,
369
+ uri: "https://arweave.net/rewrite-failure-id"
370
+ })
371
+ }, null, 2));
372
+ writeFileSpy = jest.spyOn(fs.promises, "writeFile").mockRejectedValueOnce(new Error("disk full"));
373
+ warnSpy = jest.spyOn(console, "warn").mockImplementation(function() {
374
+ return undefined;
375
+ });
376
+ storageDriver = {
377
+ getUploadPrice: function(_bytes) {
378
+ return _async_to_generator(function() {
379
+ return _ts_generator(this, function(_state) {
380
+ throw new Error("getUploadPrice should not be called in this test");
381
+ });
382
+ })();
383
+ },
384
+ upload: function(_file) {
385
+ return _async_to_generator(function() {
386
+ return _ts_generator(this, function(_state) {
387
+ throw new Error("upload should not be called in this test");
388
+ });
389
+ })();
390
+ }
391
+ };
392
+ driver = new CachedStorageDriver(storageDriver, {
393
+ assetManifestPath: manifestPath
394
+ });
395
+ _state.label = 1;
396
+ case 1:
397
+ _state.trys.push([
398
+ 1,
399
+ ,
400
+ 3,
401
+ 4
402
+ ]);
403
+ return [
404
+ 4,
405
+ expect(driver.upload(file)).resolves.toBe("https://dappstorecontent.com/rewrite-failure-id")
406
+ ];
407
+ case 2:
408
+ _state.sent();
409
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to rewrite .asset-manifest.json; continuing with normalized URL"));
410
+ return [
411
+ 3,
412
+ 4
413
+ ];
414
+ case 3:
415
+ writeFileSpy.mockRestore();
416
+ warnSpy.mockRestore();
417
+ return [
418
+ 7
419
+ ];
420
+ case 4:
421
+ return [
422
+ 2
423
+ ];
424
+ }
425
+ });
426
+ })();
427
+ });
428
+ });
429
+ var makeMetaplexFile = function(fileName, buffer) {
430
+ return {
431
+ fileName: fileName,
432
+ buffer: buffer
433
+ };
434
+ };
435
+ var hashBuffer = function(buffer) {
436
+ return createHash("sha256").update(buffer).digest("base64");
437
+ };
@@ -0,0 +1,17 @@
1
+ import { getTurboServiceConfig } from "../TurboStorageDriver";
2
+ describe("TurboStorageDriver", function() {
3
+ test("uses the latest Turbo SDK service config shape for devnet", function() {
4
+ expect(getTurboServiceConfig("devnet")).toEqual({
5
+ gatewayUrl: "https://api.devnet.solana.com",
6
+ uploadServiceConfig: {
7
+ url: "https://upload.ardrive.dev"
8
+ },
9
+ paymentServiceConfig: {
10
+ url: "https://payment.ardrive.dev"
11
+ }
12
+ });
13
+ });
14
+ test("relies on the SDK defaults for mainnet service URLs", function() {
15
+ expect(getTurboServiceConfig("mainnet")).toEqual({});
16
+ });
17
+ });
@@ -0,0 +1,17 @@
1
+ import { buildPublicContentUrl, normalizePublicContentUrl } from "../contentGateway";
2
+ describe("contentGateway", function() {
3
+ test("builds a mainnet dappstorecontent URL from an upload id", function() {
4
+ expect(buildPublicContentUrl("abc123", "mainnet")).toBe("https://dappstorecontent.com/abc123");
5
+ });
6
+ test("builds a devnet Turbo raw URL from an upload id", function() {
7
+ expect(buildPublicContentUrl("abc123", "devnet")).toBe("https://turbo.ardrive.dev/raw/abc123");
8
+ });
9
+ test("normalizes arweave.net and arweave.com URLs to dappstorecontent", function() {
10
+ expect(normalizePublicContentUrl("https://arweave.net/abc123")).toBe("https://dappstorecontent.com/abc123");
11
+ expect(normalizePublicContentUrl("https://www.arweave.com/abc123?ext=png")).toBe("https://dappstorecontent.com/abc123?ext=png");
12
+ });
13
+ test("leaves non-arweave URLs unchanged", function() {
14
+ expect(normalizePublicContentUrl("https://turbo.ardrive.dev/raw/abc123")).toBe("https://turbo.ardrive.dev/raw/abc123");
15
+ expect(normalizePublicContentUrl("not-a-url")).toBe("not-a-url");
16
+ });
17
+ });
@@ -0,0 +1,23 @@
1
+ var MAINNET_PUBLIC_CONTENT_GATEWAY = "https://dappstorecontent.com";
2
+ var DEVNET_PUBLIC_CONTENT_GATEWAY = "https://turbo.ardrive.dev/raw";
3
+ var LEGACY_MAINNET_CONTENT_HOSTS = new Set([
4
+ "arweave.net",
5
+ "www.arweave.net",
6
+ "arweave.com",
7
+ "www.arweave.com"
8
+ ]);
9
+ export var buildPublicContentUrl = function(id, network) {
10
+ var gateway = network === "devnet" ? DEVNET_PUBLIC_CONTENT_GATEWAY : MAINNET_PUBLIC_CONTENT_GATEWAY;
11
+ return "".concat(gateway, "/").concat(id);
12
+ };
13
+ export var normalizePublicContentUrl = function(url) {
14
+ try {
15
+ var parsed = new URL(url);
16
+ if (!LEGACY_MAINNET_CONTENT_HOSTS.has(parsed.host.toLowerCase())) {
17
+ return url;
18
+ }
19
+ return "".concat(MAINNET_PUBLIC_CONTENT_GATEWAY).concat(parsed.pathname).concat(parsed.search).concat(parsed.hash);
20
+ } catch (e) {
21
+ return url;
22
+ }
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-mobile/dapp-store-cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -44,10 +44,10 @@
44
44
  "ts-node": "^10.9.1"
45
45
  },
46
46
  "dependencies": {
47
- "@ardrive/turbo-sdk": "^1.31.1",
47
+ "@ardrive/turbo-sdk": "^1.41.0",
48
48
  "@aws-sdk/client-s3": "^3.321.1",
49
49
  "@metaplex-foundation/js-plugin-aws": "^0.20.0",
50
- "@solana-mobile/dapp-store-publishing-tools": "0.15.0",
50
+ "@solana-mobile/dapp-store-publishing-tools": "0.16.0",
51
51
  "@solana/web3.js": "1.92.1",
52
52
  "@types/semver": "^7.3.13",
53
53
  "ajv": "^8.11.0",
package/src/CliUtils.ts CHANGED
@@ -17,7 +17,7 @@ import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
17
17
  import { S3StorageManager } from "./config/index.js";
18
18
 
19
19
  export class Constants {
20
- static CLI_VERSION = "0.15.0";
20
+ static CLI_VERSION = "0.16.0";
21
21
  static CONFIG_FILE_NAME = "config.yaml";
22
22
  static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
23
23
  static DEFAULT_PRIORITY_FEE = 500000;
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import type { MetaplexFile, StorageDriver } from "@metaplex-foundation/js";
4
4
  import { createHash } from "crypto";
5
+ import { normalizePublicContentUrl } from "./contentGateway.js";
5
6
 
6
7
  type URI = string;
7
8
 
@@ -44,11 +45,83 @@ export class CachedStorageDriver implements StorageDriver {
44
45
  return this.storageDriver.getUploadPrice(bytes);
45
46
  }
46
47
 
48
+ private resolveAssetManifestPath(filename = this.assetManifestPath): string {
49
+ return path.resolve(process.cwd(), filename);
50
+ }
51
+
52
+ private normalizeAsset(
53
+ filename: string,
54
+ asset: unknown
55
+ ): Asset | undefined {
56
+ if (!asset || typeof asset !== "object") return;
57
+
58
+ const candidate = asset as Partial<Asset>;
59
+ const pathValue =
60
+ typeof candidate.path === "string" ? candidate.path : filename;
61
+
62
+ if (
63
+ typeof candidate.sha256 !== "string" ||
64
+ typeof candidate.uri !== "string"
65
+ ) {
66
+ return;
67
+ }
68
+
69
+ return {
70
+ path: pathValue,
71
+ sha256: candidate.sha256,
72
+ uri: candidate.uri,
73
+ };
74
+ }
75
+
76
+ private normalizeAssetManifest(
77
+ assetManifest: Partial<AssetManifestSchema> | undefined
78
+ ): AssetManifestSchema | undefined {
79
+ if (!assetManifest || typeof assetManifest !== "object") return;
80
+
81
+ const assets: Record<string, Asset> = {};
82
+ const assetEntries =
83
+ assetManifest.assets && typeof assetManifest.assets === "object"
84
+ ? Object.entries(assetManifest.assets)
85
+ : [];
86
+
87
+ for (const [filename, asset] of assetEntries) {
88
+ const normalizedAsset = this.normalizeAsset(filename, asset);
89
+ if (normalizedAsset) {
90
+ assets[filename] = normalizedAsset;
91
+ }
92
+ }
93
+
94
+ return {
95
+ schema_version:
96
+ typeof assetManifest.schema_version === "string"
97
+ ? assetManifest.schema_version
98
+ : CachedStorageDriver.SCHEMA_VERSION,
99
+ assets,
100
+ };
101
+ }
102
+
103
+ private async writeAssetManifest(): Promise<void> {
104
+ const normalizedAssetManifest = this.normalizeAssetManifest(
105
+ this.assetManifest
106
+ );
107
+
108
+ if (!normalizedAssetManifest) {
109
+ throw new Error("Asset manifest is not serializable");
110
+ }
111
+
112
+ this.assetManifest = normalizedAssetManifest;
113
+ await fs.promises.writeFile(
114
+ this.resolveAssetManifestPath(),
115
+ JSON.stringify(this.assetManifest, null, 2),
116
+ "utf-8"
117
+ );
118
+ }
119
+
47
120
  loadAssetManifest(filename: string): AssetManifestSchema | undefined {
48
121
  try {
49
- return JSON.parse(
50
- fs.readFileSync(filename, "utf-8")
51
- ) as AssetManifestSchema;
122
+ return this.normalizeAssetManifest(
123
+ JSON.parse(fs.readFileSync(this.resolveAssetManifestPath(filename), "utf-8"))
124
+ );
52
125
  } catch (error) {
53
126
  console.warn(`Failed opening ${filename}; initializing with a blank asset manifest`);
54
127
  return;
@@ -65,20 +138,32 @@ export class CachedStorageDriver implements StorageDriver {
65
138
  async upload(file: MetaplexFile): Promise<string> {
66
139
  // `inline.json` is the NFT-related metadata. This data is not stable so we'll skip the caching step
67
140
  if (file.fileName === "inline.json") {
68
- return await this.storageDriver.upload(file);
141
+ return normalizePublicContentUrl(await this.storageDriver.upload(file));
69
142
  }
70
143
  const hash = createHash("sha256").update(file.buffer).digest("base64");
71
144
 
72
145
  const uploadedAsset = this.uploadedAsset(file.fileName, { sha256: hash });
73
146
  if (uploadedAsset) {
147
+ const normalizedUri = normalizePublicContentUrl(uploadedAsset.uri);
148
+ if (normalizedUri !== uploadedAsset.uri) {
149
+ uploadedAsset.uri = normalizedUri;
150
+ try {
151
+ await this.writeAssetManifest();
152
+ } catch (error) {
153
+ const message = error instanceof Error ? error.message : String(error);
154
+ console.warn(
155
+ `Failed to rewrite ${this.assetManifestPath}; continuing with normalized URL: ${message}`
156
+ );
157
+ }
158
+ }
74
159
  console.log(
75
- `Asset ${file.fileName} already uploaded at ${uploadedAsset.uri}`
160
+ `Asset ${file.fileName} already uploaded at ${normalizedUri}`
76
161
  );
77
- return uploadedAsset.uri;
162
+ return normalizedUri;
78
163
  }
79
164
 
80
165
  console.log(`Uploading ${file.fileName}`);
81
- const uri = await this.storageDriver.upload(file);
166
+ const uri = normalizePublicContentUrl(await this.storageDriver.upload(file));
82
167
 
83
168
  this.assetManifest.assets[file.fileName] = {
84
169
  path: file.fileName,
@@ -86,12 +171,7 @@ export class CachedStorageDriver implements StorageDriver {
86
171
  uri,
87
172
  };
88
173
 
89
- await fs.promises.writeFile(
90
- path.join(process.cwd(), this.assetManifestPath),
91
- // Something is really weird, I can't seem to stringify `this.assetManifest` straight-up. Here be dragons
92
- JSON.stringify({ assets: { ...this.assetManifest.assets } }, null, 2),
93
- "utf-8"
94
- );
174
+ await this.writeAssetManifest();
95
175
  console.log(`${file.fileName} uploaded at ${uri}`)
96
176
 
97
177
  return uri;