@ocfl/ocfl 0.2.2 → 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/index.js +72 -97
- package/index.mjs +3 -3
- package/lib/digest.js +245 -77
- package/lib/enum.js +73 -75
- package/lib/extension.js +4 -16
- package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +5 -3
- package/lib/extensions/0004-hashed-n-tuple-storage-layout.js +6 -4
- package/lib/extensions/0009-digest-algorithms.js +18 -0
- package/lib/extensions/index.js +1 -1
- package/lib/file.js +129 -0
- package/lib/index.js +0 -12
- package/lib/inventory.js +294 -101
- package/lib/object.js +287 -492
- package/lib/storage.js +23 -14
- package/lib/store.js +26 -18
- package/lib/transaction.js +177 -142
- package/lib/types.d.ts +75 -16
- package/lib/utils.js +52 -21
- package/lib/validation.js +1 -1
- package/package.json +11 -8
- package/lib/extensions/0001-digest-algorithms.js +0 -46
- package/lib/temp.js +0 -264
package/lib/utils.js
CHANGED
|
@@ -6,24 +6,10 @@ const validation = require('./validation.js');
|
|
|
6
6
|
const { OCFL_VERSIONS, NAMASTE_T } = require('./constants').OcflConstants;
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/** @typedef {import('./store').OcflStore} OcflStore */
|
|
10
|
+
/** @typedef {import('./types.js').OcflVersion} OcflVersion */
|
|
11
|
+
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {*} data
|
|
16
|
-
*/
|
|
17
|
-
function dataSourceAsIterable(data) {
|
|
18
|
-
let d = data;
|
|
19
|
-
if (ArrayBuffer.isView(data) && data.buffer) {
|
|
20
|
-
d = Buffer.from(data.buffer);
|
|
21
|
-
}
|
|
22
|
-
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
23
|
-
d = [data];
|
|
24
|
-
}
|
|
25
|
-
return d;
|
|
26
|
-
}
|
|
27
13
|
//
|
|
28
14
|
/**
|
|
29
15
|
* @template T
|
|
@@ -69,7 +55,7 @@ async function isDirEmpty(store, dirPath) {
|
|
|
69
55
|
* Check if there is any valid namaste in the rootPath and return the version number
|
|
70
56
|
* @param {OcflStore} store
|
|
71
57
|
* @param {string} prefix
|
|
72
|
-
* @param {string} rootPath
|
|
58
|
+
* @param {string} rootPath
|
|
73
59
|
*/
|
|
74
60
|
async function findNamasteVersion(store, prefix, rootPath) {
|
|
75
61
|
let namastePath = path.join(rootPath, NAMASTE_T + prefix);
|
|
@@ -81,12 +67,57 @@ async function findNamasteVersion(store, prefix, rootPath) {
|
|
|
81
67
|
} catch (error) {
|
|
82
68
|
if (!error.errors || error.errors.some(e => e.code !== 'ENOENT')) throw error;
|
|
83
69
|
}
|
|
84
|
-
return version;
|
|
70
|
+
return /** @type {OcflVersion} */(version);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @typedef {ArrayLike<number> & { buffer: ArrayBuffer, byteLength: number, set(array: ArrayLike<number>, offset?: number): void }} TypedArray */
|
|
74
|
+
/**
|
|
75
|
+
* @param {any} obj
|
|
76
|
+
* @returns {obj is TypedArray}
|
|
77
|
+
*/
|
|
78
|
+
function isTypedArray(obj) {
|
|
79
|
+
return ArrayBuffer.isView(obj) && !(obj instanceof DataView);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param {ArrayBuffer|TypedArray} target
|
|
85
|
+
* @param {ArrayBuffer|TypedArray} source
|
|
86
|
+
* @returns {TypedArray}
|
|
87
|
+
*/
|
|
88
|
+
function joinTypedArray(target, source) {
|
|
89
|
+
const sourceArray = isTypedArray(source) ? source : new Uint8Array(source);
|
|
90
|
+
const [targetBuffer, targetArray] = isTypedArray(target) ? [/** @type {ArrayBuffer}*/(target.buffer), target] : [target, new Uint8Array(target)];
|
|
91
|
+
const targetLength = targetArray.byteLength;
|
|
92
|
+
const totalSize = targetLength + sourceArray.byteLength;
|
|
93
|
+
let mergedArray = targetArray;
|
|
94
|
+
if (targetBuffer.resizable && targetBuffer.maxByteLength >= totalSize) {
|
|
95
|
+
targetBuffer.resize(totalSize);
|
|
96
|
+
} else {
|
|
97
|
+
mergedArray = new Uint8Array(totalSize);
|
|
98
|
+
mergedArray.set(targetArray, 0);
|
|
99
|
+
}
|
|
100
|
+
mergedArray.set(sourceArray, targetLength);
|
|
101
|
+
return mergedArray;
|
|
85
102
|
}
|
|
86
103
|
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @param {string} absOrRelPath
|
|
107
|
+
* @return {string}
|
|
108
|
+
*/
|
|
109
|
+
// toAbsPath(absOrRelPath = '') {
|
|
110
|
+
// if (!path.isAbsolute(absOrRelPath)) return path.join(this.root, absOrRelPath);
|
|
111
|
+
// return absOrRelPath;
|
|
112
|
+
// }
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
const testSymbol = Symbol('testSymbol');
|
|
116
|
+
|
|
87
117
|
module.exports = {
|
|
88
118
|
parallelize,
|
|
89
|
-
dataSourceAsIterable,
|
|
90
119
|
isDirEmpty,
|
|
91
|
-
findNamasteVersion
|
|
120
|
+
findNamasteVersion,
|
|
121
|
+
joinTypedArray,
|
|
122
|
+
testSymbol
|
|
92
123
|
};
|
package/lib/validation.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ocfl/ocfl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Oxford Common File Layout (OCFL) JS library",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "mocha",
|
|
18
|
-
"build-docs": "
|
|
18
|
+
"build-docs": "typedoc --plugin typedoc-plugin-missing-exports"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"ocfl",
|
|
@@ -34,13 +34,16 @@
|
|
|
34
34
|
],
|
|
35
35
|
"license": "GPL-3.0-or-later",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"hash-wasm": "^4.
|
|
37
|
+
"hash-wasm": "^4.12.0",
|
|
38
|
+
"js-sha512": "^0.9.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@types/mocha": "^
|
|
41
|
-
"@types/node": "^
|
|
42
|
-
"
|
|
43
|
-
"
|
|
41
|
+
"@types/mocha": "^10.0.10",
|
|
42
|
+
"@types/node": "^24",
|
|
43
|
+
"memfs": "^4.50.0",
|
|
44
|
+
"mocha": "^11.7.5",
|
|
45
|
+
"typedoc": "^0.28.15",
|
|
46
|
+
"typedoc-plugin-missing-exports": "^4.1.2"
|
|
44
47
|
},
|
|
45
48
|
"mocha": {
|
|
46
49
|
"extension": [
|
|
@@ -55,7 +58,7 @@
|
|
|
55
58
|
},
|
|
56
59
|
"homepage": "https://github.com/Language-Research-Technology/ocfl-js#readme",
|
|
57
60
|
"engines": {
|
|
58
|
-
"node": ">=
|
|
61
|
+
"node": ">=18.0.0"
|
|
59
62
|
},
|
|
60
63
|
"directories": {
|
|
61
64
|
"lib": "lib",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
|
|
3
|
-
const {OcflExtension} = require("../extension");
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
//const blake2 = require('blake2');
|
|
6
|
-
const { createBLAKE2b } = require('hash-wasm');
|
|
7
|
-
|
|
8
|
-
class DigestAlgorithm extends OcflExtension {
|
|
9
|
-
static get NAME() { return '0001-digest-algorithms' }
|
|
10
|
-
|
|
11
|
-
// static create() {
|
|
12
|
-
// if (!instance) instance = new DigestAlgorithm();
|
|
13
|
-
// return instance;
|
|
14
|
-
// }
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
* @param {import('ocfl')} ocfl
|
|
18
|
-
*/
|
|
19
|
-
static async setup(ocfl) {
|
|
20
|
-
let {OcflDigest} = ocfl;
|
|
21
|
-
let { FIXITY } = OcflDigest;
|
|
22
|
-
class FIXITY_EX extends FIXITY {
|
|
23
|
-
static blake2b160 = new this('blake2b-160');
|
|
24
|
-
static blake2b256 = new this('blake2b-256');
|
|
25
|
-
static blake2b384 = new this('blake2b-384');
|
|
26
|
-
static ['sha512-256'] = new this('sha512/256');
|
|
27
|
-
};
|
|
28
|
-
OcflDigest.FIXITY = FIXITY_EX;
|
|
29
|
-
//console.log(OcflDigest.FIXITY);
|
|
30
|
-
const blake2b = Object.fromEntries(await Promise.all([160, 256, 384].map(bits => [bits, createBLAKE2b(bits)])));
|
|
31
|
-
|
|
32
|
-
for (let bits of [160, 256, 384]) {
|
|
33
|
-
OcflDigest.algorithms['blake2b-' + bits] = function() {
|
|
34
|
-
//return blake2.createHash('blake2b', {digestLength: len/8});
|
|
35
|
-
blake2b[bits].init();
|
|
36
|
-
return blake2b[bits];
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
OcflDigest.algorithms['sha512/256'] = function() {
|
|
40
|
-
return crypto.createHash('sha512-256');
|
|
41
|
-
}
|
|
42
|
-
//console.log(OcflDigest.algorithms);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
module.exports = { DigestAlgorithm };
|
package/lib/temp.js
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const hasha = require('hasha');
|
|
4
|
-
const uuidv5 = require('uuid/v5');
|
|
5
|
-
const _ = require('lodash');
|
|
6
|
-
const DIGEST_ALGORITHM = 'sha512';
|
|
7
|
-
|
|
8
|
-
class OcflObject {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
constructor(path) {
|
|
12
|
-
this.path = path;
|
|
13
|
-
this.ocflVersion = '1.0';
|
|
14
|
-
this.contentVersion = null; // No content yet
|
|
15
|
-
this.id = null; // Not set yet
|
|
16
|
-
this.DIGEST_ALGORITHM = DIGEST_ALGORITHM;
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async writeInventories(inv, version) {
|
|
21
|
-
const main_inv = await fs.writeJson(path.join(this.path, 'inventory.json'), inv, { spaces: 2 });
|
|
22
|
-
const version_inv = await fs.writeJson(path.join(this.path, version, 'inventory.json'), inv, { spaces: 2 });
|
|
23
|
-
const inv_hash = await this.hash_file(path.join(this.path, 'inventory.json'))
|
|
24
|
-
const digest_file_v1 = await fs.writeFile(path.join(this.path, version, 'inventory.json.' + DIGEST_ALGORITHM), inv_hash + " inventory.json");
|
|
25
|
-
const digest_file = await fs.writeFile(path.join(this.path, 'inventory.json.' + DIGEST_ALGORITHM), inv_hash + " inventory.json");
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// addContent is passed an id and a callback - this takes one argument, the
|
|
30
|
-
// directory into which content is to be written.
|
|
31
|
-
// This is called for both new and merged versions: the incoming version is
|
|
32
|
-
// written as v1 of a new object in the deposit directory, and if it's not the
|
|
33
|
-
// first version it's then merged with the existing most recent version
|
|
34
|
-
|
|
35
|
-
async addContent(id, writeContent) {
|
|
36
|
-
// Copy files into v1
|
|
37
|
-
|
|
38
|
-
const version = "v1" // Always a fresh start as we're not touching an existing repo object
|
|
39
|
-
const versionPath = path.join(this.path, version, "content");
|
|
40
|
-
await fs.ensureDir(versionPath);
|
|
41
|
-
await writeContent(versionPath);
|
|
42
|
-
|
|
43
|
-
// Make an inventory
|
|
44
|
-
const inv = await this.inventory(id, versionPath);
|
|
45
|
-
|
|
46
|
-
// Put the inventory in the root AND version dir
|
|
47
|
-
await this.writeInventories(inv, version)
|
|
48
|
-
this.contentVersion = await this.determineVersion();
|
|
49
|
-
this.id = id;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// preserving the old interface here
|
|
53
|
-
|
|
54
|
-
async importDir(id, sourceDir) {
|
|
55
|
-
await this.addContent(id, async (targetDir) => {
|
|
56
|
-
await fs.copy(sourceDir, targetDir);
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async create(path) {
|
|
62
|
-
// Creates a blank object with a content dir but no content at <path>
|
|
63
|
-
if (this.path) {
|
|
64
|
-
throw new Error("This object has already been initialized at: " + this.path)
|
|
65
|
-
}
|
|
66
|
-
this.path = path;
|
|
67
|
-
const stats = await fs.stat(this.path);
|
|
68
|
-
if (await fs.pathExists(this.path) && stats.isDirectory()) {
|
|
69
|
-
const readDir = await fs.readdir(this.path);
|
|
70
|
-
if (readDir.length <= 0) { // empty so initialise an object here
|
|
71
|
-
const generateNamaste = await this.generateNamaste(this.path, this.ocflVersion);
|
|
72
|
-
} else {
|
|
73
|
-
throw new Error('can\'t initialise an object here as there are already files')
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
//else if it doesnt it dies
|
|
77
|
-
throw new Error('directory does not exist');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async load(path) {
|
|
82
|
-
// Tries to load an existing object residing at <path>
|
|
83
|
-
if (this.path) {
|
|
84
|
-
throw new Error("This object has already been initialized at: " + this.path)
|
|
85
|
-
}
|
|
86
|
-
this.path = path;
|
|
87
|
-
const stats = await fs.stat(this.path);
|
|
88
|
-
if (await fs.pathExists(this.path) && stats.isDirectory()) {
|
|
89
|
-
|
|
90
|
-
const ocflVersion = await this.isObject(this.path);
|
|
91
|
-
if (!ocflVersion) {
|
|
92
|
-
throw new Error('can\'t initialise an object here as there are already files')
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
throw new Error(path + ' does not exist or is not a directory');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
getVersionString(i) {
|
|
100
|
-
// Make a version name as per the SHOULD in the spec v1..vn
|
|
101
|
-
// TODO have an option for zero padding
|
|
102
|
-
return "v" + i
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async getInventory() {
|
|
106
|
-
const inventoryPath = path.join(this.path, "inventory.json");
|
|
107
|
-
if (await fs.exists(inventoryPath)) {
|
|
108
|
-
return await JSON.parse(fs.readFileSync(inventoryPath));
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async determineVersion() {
|
|
116
|
-
const inv = await this.getInventory();
|
|
117
|
-
if (inv) {
|
|
118
|
-
return inv.head;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
// Here's not how to do it:
|
|
124
|
-
/* var version = 0;
|
|
125
|
-
const dirContents = await fs.readdir(this.path);
|
|
126
|
-
for (let f of dirContents.filter(function(d){return d.match(/^v\d+$/)})){
|
|
127
|
-
const v = parseInt(f.replace("v",""));
|
|
128
|
-
if (v > version) {
|
|
129
|
-
version = v;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return version;10. */
|
|
133
|
-
// Look at each dir that matches v\d+
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async isObject(aPath) {
|
|
138
|
-
// TODO: Check if this content root with NAMASTE and returns ocfl version
|
|
139
|
-
// 0=ocfl_object_1.0
|
|
140
|
-
// looks at path and see if the content of the file is
|
|
141
|
-
// TODO: Make this look for a namaste file beginning with 0=ocfl_object_ and extract the version
|
|
142
|
-
const theFile = path.join(aPath, "0=" + this.nameVersion(this.ocflVersion));
|
|
143
|
-
return await fs.pathExists(theFile);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
nameVersion(version) {
|
|
147
|
-
return 'ocfl_object_' + version;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async generateNamaste(aPath, version) {
|
|
151
|
-
const fileName = '0=' + this.nameVersion(version);
|
|
152
|
-
const thePath = path.join(aPath, fileName);
|
|
153
|
-
const writeFile = await fs.writeFile(thePath, this.nameVersion(version));
|
|
154
|
-
const contentDir = await fs.mkdir(path.join(aPath, "v1"));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
async digest_dir(dir) {
|
|
159
|
-
var items = {};
|
|
160
|
-
const contents = await fs.readdir(dir);
|
|
161
|
-
items = _.flatten(await Promise.all(contents.map(async (p1) => {
|
|
162
|
-
const p = path.join(dir, p1);
|
|
163
|
-
const stats = await fs.stat(p);
|
|
164
|
-
if (stats.isDirectory()) {
|
|
165
|
-
return await this.digest_dir(p);
|
|
166
|
-
} else {
|
|
167
|
-
const h = await this.hash_file(p);
|
|
168
|
-
return [p, h];
|
|
169
|
-
}
|
|
170
|
-
})));
|
|
171
|
-
return items;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async hash_file(p) {
|
|
175
|
-
const hash = await hasha.fromFile(p, { algorithm: DIGEST_ALGORITHM })
|
|
176
|
-
return hash;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
async removeEmptyDirectories(folder) {
|
|
181
|
-
// Remove empty directories
|
|
182
|
-
// Adapted (nade async) from: https://gist.github.com/jakub-g/5903dc7e4028133704a4
|
|
183
|
-
if (!folder) {
|
|
184
|
-
folder = this.path;
|
|
185
|
-
}
|
|
186
|
-
const stats = await fs.stat(folder);
|
|
187
|
-
var isDir = await stats.isDirectory();
|
|
188
|
-
if (isDir) {
|
|
189
|
-
var files = await fs.readdir(folder);
|
|
190
|
-
if (files.length > 0) {
|
|
191
|
-
for (let f of files) {
|
|
192
|
-
var fullPath = path.join(folder, f);
|
|
193
|
-
await this.removeEmptyDirectories(fullPath);
|
|
194
|
-
}
|
|
195
|
-
files = await fs.readdir(folder);
|
|
196
|
-
}
|
|
197
|
-
if (files.length == 0) {
|
|
198
|
-
const rm = await fs.rmdir(folder);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async inventory(id, dir) {
|
|
205
|
-
const versionId = "v1";
|
|
206
|
-
const inv = {
|
|
207
|
-
'id': id,
|
|
208
|
-
'type': 'https://ocfl.io/1.0/spec/#inventory',
|
|
209
|
-
'digestAlgorithm': DIGEST_ALGORITHM,
|
|
210
|
-
'head': versionId,
|
|
211
|
-
'versions': {
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
inv.versions[versionId] = {
|
|
216
|
-
"created": new Date().toISOString(),
|
|
217
|
-
"state": {}
|
|
218
|
-
}
|
|
219
|
-
// TODO Message and state keys in version
|
|
220
|
-
var hashpairs = await this.digest_dir(dir);
|
|
221
|
-
var versionState = inv.versions[versionId].state;
|
|
222
|
-
inv['manifest'] = {};
|
|
223
|
-
for (let i = 0; i < hashpairs.length; i += 2) {
|
|
224
|
-
const thisHash = hashpairs[i + 1]
|
|
225
|
-
const thisPath = path.relative(this.path, hashpairs[i])
|
|
226
|
-
const versionPath = path.relative(path.join("v1", "content"), thisPath)
|
|
227
|
-
if (!inv['manifest'][thisHash]) {
|
|
228
|
-
//Store only ONE path
|
|
229
|
-
inv['manifest'][thisHash] = [thisPath]
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
// TODO DELETE THIS FILE FROM THE OBJECT BEFORE WE STORE IT
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (versionState[thisHash]) {
|
|
236
|
-
// Store all
|
|
237
|
-
versionState[thisHash].push(versionPath)
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
versionState[thisHash] = [versionPath]
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
return inv
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async getFilePath(filePath, version) {
|
|
249
|
-
const inv = await this.getInventory();
|
|
250
|
-
if (!version) {
|
|
251
|
-
version = inv.head;
|
|
252
|
-
}
|
|
253
|
-
const state = inv.versions[version].state;
|
|
254
|
-
for (let hash of Object.keys(state)) {
|
|
255
|
-
const aPath = state[hash];
|
|
256
|
-
const findPath = _.find(aPath, (p) => p === filePath);
|
|
257
|
-
if (findPath) {
|
|
258
|
-
return inv.manifest[hash][0];
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
throw new Error('cannot find file');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
}
|