scratch-storage 2.2.0 → 2.2.1

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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "scratch-storage",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Load and store project and asset files for Scratch 3.0",
5
5
  "license": "BSD-3-Clause",
6
6
  "homepage": "https://github.com/LLK/scratch-storage#readme",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/LLK/scratch-storage.git",
10
- "sha": "5c6588073bb3c9ba4500403054d6409db2379286"
10
+ "sha": "4294057468737f2e179afe2cc963326d20942aed"
11
11
  },
12
12
  "main": "./dist/node/scratch-storage.js",
13
13
  "browser": "./src/index.js",
@@ -1,4 +1,4 @@
1
- const {applyMetadata} = require('./scratchFetch');
1
+ const {Headers, applyMetadata} = require('./scratchFetch');
2
2
 
3
3
  /**
4
4
  * Get and send assets with a worker that uses fetch.
@@ -92,6 +92,13 @@ class PrivateFetchWorkerTool {
92
92
  const augmentedOptions = applyMetadata(
93
93
  Object.assign({method: 'GET'}, options)
94
94
  );
95
+ // the Fetch spec says options.headers could be:
96
+ // "A Headers object, an object literal, or an array of two-item arrays to set request's headers."
97
+ // structured clone (postMessage) doesn't support Headers objects
98
+ // so turn it into an array of two-item arrays to make it to the worker intact
99
+ if (augmentedOptions && augmentedOptions.headers instanceof Headers) {
100
+ augmentedOptions.headers = Array.from(augmentedOptions.headers.entries());
101
+ }
95
102
  this.worker.postMessage({
96
103
  id,
97
104
  url,
@@ -6,6 +6,7 @@ const WebHelper = require('./WebHelper');
6
6
  const _Asset = require('./Asset');
7
7
  const _AssetType = require('./AssetType');
8
8
  const _DataFormat = require('./DataFormat');
9
+ const _scratchFetch = require('./scratchFetch');
9
10
 
10
11
  class ScratchStorage {
11
12
  constructor () {
@@ -51,6 +52,14 @@ class ScratchStorage {
51
52
  return _DataFormat;
52
53
  }
53
54
 
55
+ /**
56
+ * Access the `scratchFetch` module within this library.
57
+ * @return {module} the scratchFetch module, with properties for `scratchFetch`, `setMetadata`, etc.
58
+ */
59
+ get scratchFetch () {
60
+ return _scratchFetch;
61
+ }
62
+
54
63
  /**
55
64
  * @deprecated Please use the `Asset` member of a storage instance instead.
56
65
  * @return {Asset} - the `Asset` class constructor.
@@ -1,4 +1,4 @@
1
- const {fetch, Headers} = require('cross-fetch');
1
+ const crossFetch = require('cross-fetch');
2
2
 
3
3
  /**
4
4
  * Metadata header names
@@ -7,16 +7,27 @@ const {fetch, Headers} = require('cross-fetch');
7
7
  */
8
8
  const RequestMetadata = {
9
9
  /** The ID of the project associated with this request */
10
- ProjectId: 'X-ProjectId',
10
+ ProjectId: 'X-Project-ID',
11
11
  /** The ID of the project run associated with this request */
12
- RunId: 'X-RunId'
12
+ RunId: 'X-Run-ID'
13
13
  };
14
14
 
15
15
  /**
16
- * Metadata for requests
17
- * @type {Map<string, string>}
16
+ * Metadata headers for requests
17
+ * @type {Headers}
18
18
  */
19
- const metadata = new Map();
19
+ const metadata = new crossFetch.Headers();
20
+
21
+ /**
22
+ * Check if there is any metadata to apply.
23
+ * @returns {boolean} true if `metadata` has contents, or false if it is empty.
24
+ */
25
+ const hasMetadata = () => {
26
+ for (const _ of metadata) {
27
+ return true;
28
+ }
29
+ return false;
30
+ };
20
31
 
21
32
  /**
22
33
  * Non-destructively merge any metadata state (if any) with the provided options object (if any).
@@ -28,12 +39,15 @@ const metadata = new Map();
28
39
  * @returns {RequestInit|undefined} the provided options parameter without modification, or a new options object.
29
40
  */
30
41
  const applyMetadata = options => {
31
- if (metadata.size > 0) {
42
+ if (hasMetadata()) {
32
43
  const augmentedOptions = Object.assign({}, options);
33
- augmentedOptions.headers = new Headers(Array.from(metadata));
44
+ augmentedOptions.headers = new crossFetch.Headers(metadata);
34
45
  if (options && options.headers) {
35
- const overrideHeaders =
36
- options.headers instanceof Headers ? options.headers : new Headers(options.headers);
46
+ // the Fetch spec says options.headers could be:
47
+ // "A Headers object, an object literal, or an array of two-item arrays to set request's headers."
48
+ // turn it into a Headers object to be sure of how to interact with it
49
+ const overrideHeaders = options.headers instanceof crossFetch.Headers ?
50
+ options.headers : new crossFetch.Headers(options.headers);
37
51
  for (const [name, value] of overrideHeaders.entries()) {
38
52
  augmentedOptions.headers.set(name, value);
39
53
  }
@@ -53,7 +67,7 @@ const applyMetadata = options => {
53
67
  */
54
68
  const scratchFetch = (resource, options) => {
55
69
  const augmentedOptions = applyMetadata(options);
56
- return fetch(resource, augmentedOptions);
70
+ return crossFetch.fetch(resource, augmentedOptions);
57
71
  };
58
72
 
59
73
  /**
@@ -78,9 +92,22 @@ const unsetMetadata = name => {
78
92
  module.exports = {
79
93
  default: scratchFetch,
80
94
 
95
+ Headers: crossFetch.Headers,
81
96
  RequestMetadata,
82
97
  applyMetadata,
83
98
  scratchFetch,
84
99
  setMetadata,
85
100
  unsetMetadata
86
101
  };
102
+
103
+ if (process.env.NODE_ENV === 'development') {
104
+ /**
105
+ * Retrieve a named request metadata item.
106
+ * Only for use in tests.
107
+ * @param {RequestMetadata} name The name of the metadata item to retrieve.
108
+ * @returns {any} value The value of the metadata item, or `undefined` if it was not found.
109
+ */
110
+ const getMetadata = name => metadata.get(name);
111
+
112
+ module.exports.getMetadata = getMetadata;
113
+ }
@@ -1,6 +1,7 @@
1
1
  const TextEncoder = require('util').TextEncoder;
2
- const {Headers} = require('cross-fetch');
2
+ const crossFetch = require('cross-fetch');
3
3
 
4
+ const Headers = crossFetch.Headers;
4
5
  const successText = 'successful response';
5
6
 
6
7
  /**
@@ -58,7 +59,11 @@ const mockFetch = (resource, options) => {
58
59
  return Promise.resolve(results);
59
60
  };
60
61
 
62
+ // Mimic the cross-fetch module, but replace its `fetch` with `mockFetch` and add a few extras
61
63
  module.exports = {
64
+ ...crossFetch, // Headers, Request, Response, etc.
65
+ default: mockFetch,
66
+ fetch: mockFetch,
62
67
  mockFetch,
63
68
  successText
64
69
  };
@@ -1,17 +1,14 @@
1
1
  const tap = require('tap');
2
2
  const TextDecoder = require('util').TextDecoder;
3
3
 
4
- const {mockFetch, successText} = require('../mocks/mockFetch.js');
4
+ const mockFetch = require('../mocks/mock-fetch.js');
5
5
 
6
6
  /**
7
7
  * This is the real FetchTool, but the 'cross-fetch' module has been replaced with the mockFetch function.
8
8
  * @type {typeof import('../../src/FetchTool')}
9
9
  */
10
10
  const FetchTool = tap.mock('../../src/FetchTool', {
11
- 'cross-fetch': {
12
- default: mockFetch,
13
- fetch: mockFetch
14
- }
11
+ 'cross-fetch': mockFetch
15
12
  });
16
13
 
17
14
  tap.test('send success returns response.text()', t => {
@@ -19,7 +16,7 @@ tap.test('send success returns response.text()', t => {
19
16
 
20
17
  return t.resolves(
21
18
  tool.send({url: '200'}).then(result => {
22
- t.equal(result, successText);
19
+ t.equal(result, mockFetch.successText);
23
20
  })
24
21
  );
25
22
  });
@@ -38,7 +35,7 @@ tap.test('get success returns Uint8Array.body(response.arrayBuffer())', t => {
38
35
 
39
36
  return t.resolves(
40
37
  tool.get({url: '200'}).then(result => {
41
- t.equal(decoder.decode(result), successText);
38
+ t.equal(decoder.decode(result), mockFetch.successText);
42
39
  })
43
40
  );
44
41
  });
@@ -1,14 +1,7 @@
1
1
  const tap = require('tap');
2
2
 
3
- const crossFetch = require('cross-fetch');
3
+ const mockFetchModule = require('../mocks/mock-fetch.js');
4
4
 
5
- const {mockFetch} = require('../mocks/mockFetch.js');
6
-
7
- const mockFetchModule = {
8
- ...crossFetch, // Headers, Request, Response, etc.
9
- default: mockFetch,
10
- fetch: mockFetch
11
- };
12
5
 
13
6
  // Call this separately from each test to ensure that metadata gets reset.
14
7
  // This is especially important when parallelizing tests!
@@ -39,7 +32,7 @@ tap.test('get without metadata', async t => {
39
32
 
40
33
  const tool = new FetchTool();
41
34
 
42
- /** @type import('../mocks/mockFetch.js').MockFetchTestData */
35
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
43
36
  const mockFetchTestData = {};
44
37
  const result = await tool.get({url: '200', mockFetchTestData});
45
38
 
@@ -57,7 +50,7 @@ tap.test('get with metadata', async t => {
57
50
  setMetadata(RequestMetadata.ProjectId, 1234);
58
51
  setMetadata(RequestMetadata.RunId, 5678);
59
52
 
60
- /** @type import('../mocks/mockFetch.js').MockFetchTestData */
53
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
61
54
  const mockFetchTestData = {};
62
55
  const result = await tool.get({url: '200', mockFetchTestData});
63
56
 
@@ -73,7 +66,7 @@ tap.test('send without metadata', async t => {
73
66
 
74
67
  const tool = new FetchTool();
75
68
 
76
- /** @type import('../mocks/mockFetch.js').MockFetchTestData */
69
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
77
70
  const mockFetchTestData = {};
78
71
  const result = await tool.send({url: '200', mockFetchTestData});
79
72
 
@@ -91,7 +84,7 @@ tap.test('send with metadata', async t => {
91
84
  setMetadata(RequestMetadata.ProjectId, 4321);
92
85
  setMetadata(RequestMetadata.RunId, 8765);
93
86
 
94
- /** @type import('../mocks/mockFetch.js').MockFetchTestData */
87
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
95
88
  const mockFetchTestData = {};
96
89
  const result = await tool.send({url: '200', mockFetchTestData});
97
90
 
@@ -112,7 +105,7 @@ tap.test('selectively delete metadata', async t => {
112
105
 
113
106
  const tool = new FetchTool();
114
107
 
115
- /** @type import('../mocks/mockFetch.js').MockFetchTestData */
108
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
116
109
  const mockFetchTestData = {};
117
110
 
118
111
  const result1 = await tool.send({url: '200', mockFetchTestData});
@@ -134,3 +127,21 @@ tap.test('selectively delete metadata', async t => {
134
127
  t.equal(mockFetchTestData.headers?.get(RequestMetadata.ProjectId), null); // value `null` means it's missing
135
128
  t.equal(mockFetchTestData.headers?.get(RequestMetadata.RunId), 'undefined');
136
129
  });
130
+
131
+ tap.test('metadata has case-insensitive keys', async t => {
132
+ const {scratchFetchModule, FetchTool} = setupModules();
133
+ const {setMetadata} = scratchFetchModule;
134
+
135
+ setMetadata('foo', 1);
136
+ setMetadata('FOO', 2);
137
+
138
+ const tool = new FetchTool();
139
+
140
+ /** @type import('../mocks/mock-fetch.js').MockFetchTestData */
141
+ const mockFetchTestData = {};
142
+ await tool.get({url: '200', mockFetchTestData});
143
+
144
+ t.ok(mockFetchTestData.headers, 'mockFetch did not report headers');
145
+ t.equal(mockFetchTestData.headersCount, 1);
146
+ t.equal(mockFetchTestData.headers?.get('foo'), '2');
147
+ });
@@ -1 +0,0 @@
1
- {"parent":null,"pid":263,"argv":["/usr/local/bin/node","/home/circleci/project/node_modules/.bin/tap","./test/integration/download-known-assets.js"],"execArgv":[],"cwd":"/home/circleci/project","time":1679325567414,"ppid":252,"coverageFilename":"/home/circleci/project/.nyc_output/c826ae6b-4ae2-4d8e-bdb2-162df5761fa7.json","externalId":"","uuid":"c826ae6b-4ae2-4d8e-bdb2-162df5761fa7","files":[]}
@@ -1 +0,0 @@
1
- {"parent":"c826ae6b-4ae2-4d8e-bdb2-162df5761fa7","pid":274,"argv":["/usr/local/bin/node","/home/circleci/project/test/integration/download-known-assets.js"],"execArgv":[],"cwd":"/home/circleci/project","time":1679325568227,"ppid":263,"coverageFilename":"/home/circleci/project/.nyc_output/ec2d52a1-5131-4dd0-9c23-59106f297082.json","externalId":"./test/integration/download-known-assets.js","uuid":"ec2d52a1-5131-4dd0-9c23-59106f297082","files":[]}