decap-cms-lib-util 3.2.0 → 3.3.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/README.md +1 -1
- package/dist/decap-cms-lib-util.js +1 -1
- package/dist/decap-cms-lib-util.js.LICENSE.txt +9 -0
- package/dist/decap-cms-lib-util.js.map +1 -1
- package/dist/esm/API.js +17 -25
- package/dist/esm/APIError.js +0 -7
- package/dist/esm/AccessTokenError.js +0 -4
- package/dist/esm/Cursor.js +4 -9
- package/dist/esm/EditorialWorkflowError.js +0 -5
- package/dist/esm/backendUtil.js +6 -7
- package/dist/esm/getBlobSHA.js +3 -3
- package/dist/esm/git-lfs.js +21 -29
- package/dist/esm/implementation.js +14 -17
- package/dist/esm/unsentRequest.js +9 -12
- package/package.json +5 -2
- package/CHANGELOG.md +0 -382
- package/dist/esm/stega.js +0 -129
- package/src/API.ts +0 -376
- package/src/APIError.ts +0 -17
- package/src/APIUtils.ts +0 -38
- package/src/AccessTokenError.ts +0 -11
- package/src/Cursor.ts +0 -178
- package/src/EditorialWorkflowError.ts +0 -12
- package/src/__tests__/api.spec.js +0 -13
- package/src/__tests__/apiUtils.spec.js +0 -74
- package/src/__tests__/asyncLock.spec.js +0 -85
- package/src/__tests__/backendUtil.spec.js +0 -97
- package/src/__tests__/implementation.spec.js +0 -58
- package/src/__tests__/path.spec.js +0 -53
- package/src/__tests__/unsentRequest.spec.js +0 -19
- package/src/asyncLock.ts +0 -43
- package/src/backendUtil.ts +0 -120
- package/src/getBlobSHA.ts +0 -12
- package/src/git-lfs.ts +0 -133
- package/src/implementation.ts +0 -575
- package/src/index.ts +0 -213
- package/src/loadScript.js +0 -24
- package/src/localForage.ts +0 -21
- package/src/path.ts +0 -86
- package/src/promise.ts +0 -21
- package/src/stega.ts +0 -134
- package/src/types/semaphore.d.ts +0 -5
- package/src/types.ts +0 -9
- package/src/unsentRequest.js +0 -133
- package/webpack.config.js +0 -3
package/src/git-lfs.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Pointer file parsing
|
|
3
|
-
|
|
4
|
-
import { filter, flow, fromPairs, map } from 'lodash/fp';
|
|
5
|
-
|
|
6
|
-
import getBlobSHA from './getBlobSHA';
|
|
7
|
-
|
|
8
|
-
import type { AssetProxy } from './implementation';
|
|
9
|
-
|
|
10
|
-
export interface PointerFile {
|
|
11
|
-
size: number;
|
|
12
|
-
sha: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function splitIntoLines(str: string) {
|
|
16
|
-
return str.split('\n');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function splitIntoWords(str: string) {
|
|
20
|
-
return str.split(/\s+/g);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isNonEmptyString(str: string) {
|
|
24
|
-
return str !== '';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const withoutEmptyLines = flow([map((str: string) => str.trim()), filter(isNonEmptyString)]);
|
|
28
|
-
export const parsePointerFile: (data: string) => PointerFile = flow([
|
|
29
|
-
splitIntoLines,
|
|
30
|
-
withoutEmptyLines,
|
|
31
|
-
map(splitIntoWords),
|
|
32
|
-
fromPairs,
|
|
33
|
-
({ size, oid, ...rest }) => ({
|
|
34
|
-
size: parseInt(size),
|
|
35
|
-
sha: oid?.split(':')[1],
|
|
36
|
-
...rest,
|
|
37
|
-
}),
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
// .gitattributes file parsing
|
|
42
|
-
|
|
43
|
-
function removeGitAttributesCommentsFromLine(line: string) {
|
|
44
|
-
return line.split('#')[0];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function parseGitPatternAttribute(attributeString: string) {
|
|
48
|
-
// There are three kinds of attribute settings:
|
|
49
|
-
// - a key=val pair sets an attribute to a specific value
|
|
50
|
-
// - a key without a value and a leading hyphen sets an attribute to false
|
|
51
|
-
// - a key without a value and no leading hyphen sets an attribute
|
|
52
|
-
// to true
|
|
53
|
-
if (attributeString.includes('=')) {
|
|
54
|
-
return attributeString.split('=');
|
|
55
|
-
}
|
|
56
|
-
if (attributeString.startsWith('-')) {
|
|
57
|
-
return [attributeString.slice(1), false];
|
|
58
|
-
}
|
|
59
|
-
return [attributeString, true];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const parseGitPatternAttributes = flow([map(parseGitPatternAttribute), fromPairs]);
|
|
63
|
-
|
|
64
|
-
const parseGitAttributesPatternLine = flow([
|
|
65
|
-
splitIntoWords,
|
|
66
|
-
([pattern, ...attributes]) => [pattern, parseGitPatternAttributes(attributes)],
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
const parseGitAttributesFileToPatternAttributePairs = flow([
|
|
70
|
-
splitIntoLines,
|
|
71
|
-
map(removeGitAttributesCommentsFromLine),
|
|
72
|
-
withoutEmptyLines,
|
|
73
|
-
map(parseGitAttributesPatternLine),
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
export const getLargeMediaPatternsFromGitAttributesFile = flow([
|
|
77
|
-
parseGitAttributesFileToPatternAttributePairs,
|
|
78
|
-
filter(
|
|
79
|
-
([, attributes]) =>
|
|
80
|
-
attributes.filter === 'lfs' && attributes.diff === 'lfs' && attributes.merge === 'lfs',
|
|
81
|
-
),
|
|
82
|
-
map(([pattern]) => pattern),
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
export function createPointerFile({ size, sha }: PointerFile) {
|
|
86
|
-
return `\
|
|
87
|
-
version https://git-lfs.github.com/spec/v1
|
|
88
|
-
oid sha256:${sha}
|
|
89
|
-
size ${size}
|
|
90
|
-
`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function getPointerFileForMediaFileObj(
|
|
94
|
-
client: { uploadResource: (pointer: PointerFile, resource: Blob) => Promise<string> },
|
|
95
|
-
fileObj: File,
|
|
96
|
-
path: string,
|
|
97
|
-
) {
|
|
98
|
-
const { name, size } = fileObj;
|
|
99
|
-
const sha = await getBlobSHA(fileObj);
|
|
100
|
-
await client.uploadResource({ sha, size }, fileObj);
|
|
101
|
-
const pointerFileString = createPointerFile({ sha, size });
|
|
102
|
-
const pointerFileBlob = new Blob([pointerFileString]);
|
|
103
|
-
const pointerFile = new File([pointerFileBlob], name, { type: 'text/plain' });
|
|
104
|
-
const pointerFileSHA = await getBlobSHA(pointerFile);
|
|
105
|
-
return {
|
|
106
|
-
fileObj: pointerFile,
|
|
107
|
-
size: pointerFileBlob.size,
|
|
108
|
-
sha: pointerFileSHA,
|
|
109
|
-
raw: pointerFileString,
|
|
110
|
-
path,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function getLargeMediaFilteredMediaFiles(
|
|
115
|
-
client: {
|
|
116
|
-
uploadResource: (pointer: PointerFile, resource: Blob) => Promise<string>;
|
|
117
|
-
matchPath: (path: string) => boolean;
|
|
118
|
-
},
|
|
119
|
-
mediaFiles: AssetProxy[],
|
|
120
|
-
) {
|
|
121
|
-
return await Promise.all(
|
|
122
|
-
mediaFiles.map(async mediaFile => {
|
|
123
|
-
const { fileObj, path } = mediaFile;
|
|
124
|
-
const fixedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
125
|
-
if (!client.matchPath(fixedPath)) {
|
|
126
|
-
return mediaFile;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const pointerFileDetails = await getPointerFileForMediaFileObj(client, fileObj as File, path);
|
|
130
|
-
return { ...mediaFile, ...pointerFileDetails };
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
133
|
-
}
|
package/src/implementation.ts
DELETED
|
@@ -1,575 +0,0 @@
|
|
|
1
|
-
import semaphore from 'semaphore';
|
|
2
|
-
import { unionBy, sortBy } from 'lodash';
|
|
3
|
-
|
|
4
|
-
import { basename } from './path';
|
|
5
|
-
|
|
6
|
-
import type { Semaphore } from 'semaphore';
|
|
7
|
-
import type Cursor from './Cursor';
|
|
8
|
-
import type { AsyncLock } from './asyncLock';
|
|
9
|
-
import type { FileMetadata } from './API';
|
|
10
|
-
|
|
11
|
-
export type DisplayURLObject = { id: string; path: string };
|
|
12
|
-
|
|
13
|
-
export type DisplayURL = DisplayURLObject | string;
|
|
14
|
-
|
|
15
|
-
export interface ImplementationMediaFile {
|
|
16
|
-
name: string;
|
|
17
|
-
id: string;
|
|
18
|
-
size?: number;
|
|
19
|
-
displayURL?: DisplayURL;
|
|
20
|
-
path: string;
|
|
21
|
-
draft?: boolean;
|
|
22
|
-
url?: string;
|
|
23
|
-
file?: File;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface UnpublishedEntryMediaFile {
|
|
27
|
-
id: string;
|
|
28
|
-
path: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ImplementationEntry {
|
|
32
|
-
data: string;
|
|
33
|
-
file: { path: string; label?: string; id?: string | null; author?: string; updatedOn?: string };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface UnpublishedEntryDiff {
|
|
37
|
-
id: string;
|
|
38
|
-
path: string;
|
|
39
|
-
newFile: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface UnpublishedEntry {
|
|
43
|
-
pullRequestAuthor?: string;
|
|
44
|
-
slug: string;
|
|
45
|
-
collection: string;
|
|
46
|
-
status: string;
|
|
47
|
-
diffs: UnpublishedEntryDiff[];
|
|
48
|
-
updatedAt: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface Map {
|
|
52
|
-
get: <T>(key: string, defaultValue?: T) => T;
|
|
53
|
-
getIn: <T>(key: string[], defaultValue?: T) => T;
|
|
54
|
-
setIn: <T>(key: string[], value: T) => Map;
|
|
55
|
-
set: <T>(key: string, value: T) => Map;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type DataFile = {
|
|
59
|
-
path: string;
|
|
60
|
-
slug: string;
|
|
61
|
-
raw: string;
|
|
62
|
-
newPath?: string;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export type AssetProxy = {
|
|
66
|
-
path: string;
|
|
67
|
-
fileObj?: File;
|
|
68
|
-
toBase64?: () => Promise<string>;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export type Entry = {
|
|
72
|
-
dataFiles: DataFile[];
|
|
73
|
-
assets: AssetProxy[];
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export type PersistOptions = {
|
|
77
|
-
newEntry?: boolean;
|
|
78
|
-
commitMessage: string;
|
|
79
|
-
collectionName?: string;
|
|
80
|
-
useWorkflow?: boolean;
|
|
81
|
-
unpublished?: boolean;
|
|
82
|
-
status?: string;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export type DeleteOptions = {};
|
|
86
|
-
|
|
87
|
-
export type Credentials = { token: string | {}; refresh_token?: string };
|
|
88
|
-
|
|
89
|
-
export type User = Credentials & {
|
|
90
|
-
backendName?: string;
|
|
91
|
-
login?: string;
|
|
92
|
-
name: string;
|
|
93
|
-
useOpenAuthoring?: boolean;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export type Config = {
|
|
97
|
-
backend: {
|
|
98
|
-
repo?: string | null;
|
|
99
|
-
open_authoring?: boolean;
|
|
100
|
-
always_fork?: boolean;
|
|
101
|
-
branch?: string;
|
|
102
|
-
api_root?: string;
|
|
103
|
-
squash_merges?: boolean;
|
|
104
|
-
use_graphql?: boolean;
|
|
105
|
-
graphql_api_root?: string;
|
|
106
|
-
preview_context?: string;
|
|
107
|
-
identity_url?: string;
|
|
108
|
-
gateway_url?: string;
|
|
109
|
-
large_media_url?: string;
|
|
110
|
-
use_large_media_transforms_in_media_library?: boolean;
|
|
111
|
-
proxy_url?: string;
|
|
112
|
-
auth_type?: string;
|
|
113
|
-
app_id?: string;
|
|
114
|
-
base_url?: string;
|
|
115
|
-
cms_label_prefix?: string;
|
|
116
|
-
api_version?: string;
|
|
117
|
-
};
|
|
118
|
-
media_folder: string;
|
|
119
|
-
base_url?: string;
|
|
120
|
-
site_id?: string;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export interface Implementation {
|
|
124
|
-
authComponent: () => void;
|
|
125
|
-
restoreUser: (user: User) => Promise<User>;
|
|
126
|
-
|
|
127
|
-
authenticate: (credentials: Credentials) => Promise<User>;
|
|
128
|
-
logout: () => Promise<void> | void | null;
|
|
129
|
-
getToken: () => Promise<string | null>;
|
|
130
|
-
|
|
131
|
-
getEntry: (path: string) => Promise<ImplementationEntry>;
|
|
132
|
-
entriesByFolder: (
|
|
133
|
-
folder: string,
|
|
134
|
-
extension: string,
|
|
135
|
-
depth: number,
|
|
136
|
-
) => Promise<ImplementationEntry[]>;
|
|
137
|
-
entriesByFiles: (files: ImplementationFile[]) => Promise<ImplementationEntry[]>;
|
|
138
|
-
|
|
139
|
-
getMediaDisplayURL?: (displayURL: DisplayURL) => Promise<string>;
|
|
140
|
-
getMedia: (folder?: string) => Promise<ImplementationMediaFile[]>;
|
|
141
|
-
getMediaFile: (path: string) => Promise<ImplementationMediaFile>;
|
|
142
|
-
|
|
143
|
-
persistEntry: (entry: Entry, opts: PersistOptions) => Promise<void>;
|
|
144
|
-
persistMedia: (file: AssetProxy, opts: PersistOptions) => Promise<ImplementationMediaFile>;
|
|
145
|
-
deleteFiles: (paths: string[], commitMessage: string) => Promise<void>;
|
|
146
|
-
|
|
147
|
-
unpublishedEntries: () => Promise<string[]>;
|
|
148
|
-
unpublishedEntry: (args: {
|
|
149
|
-
id?: string;
|
|
150
|
-
collection?: string;
|
|
151
|
-
slug?: string;
|
|
152
|
-
}) => Promise<UnpublishedEntry>;
|
|
153
|
-
unpublishedEntryDataFile: (
|
|
154
|
-
collection: string,
|
|
155
|
-
slug: string,
|
|
156
|
-
path: string,
|
|
157
|
-
id: string,
|
|
158
|
-
) => Promise<string>;
|
|
159
|
-
unpublishedEntryMediaFile: (
|
|
160
|
-
collection: string,
|
|
161
|
-
slug: string,
|
|
162
|
-
path: string,
|
|
163
|
-
id: string,
|
|
164
|
-
) => Promise<ImplementationMediaFile>;
|
|
165
|
-
updateUnpublishedEntryStatus: (
|
|
166
|
-
collection: string,
|
|
167
|
-
slug: string,
|
|
168
|
-
newStatus: string,
|
|
169
|
-
) => Promise<void>;
|
|
170
|
-
publishUnpublishedEntry: (collection: string, slug: string) => Promise<void>;
|
|
171
|
-
deleteUnpublishedEntry: (collection: string, slug: string) => Promise<void>;
|
|
172
|
-
getDeployPreview: (
|
|
173
|
-
collectionName: string,
|
|
174
|
-
slug: string,
|
|
175
|
-
) => Promise<{ url: string; status: string } | null>;
|
|
176
|
-
|
|
177
|
-
allEntriesByFolder?: (
|
|
178
|
-
folder: string,
|
|
179
|
-
extension: string,
|
|
180
|
-
depth: number,
|
|
181
|
-
pathRegex?: RegExp,
|
|
182
|
-
) => Promise<ImplementationEntry[]>;
|
|
183
|
-
traverseCursor?: (
|
|
184
|
-
cursor: Cursor,
|
|
185
|
-
action: string,
|
|
186
|
-
) => Promise<{ entries: ImplementationEntry[]; cursor: Cursor }>;
|
|
187
|
-
|
|
188
|
-
isGitBackend?: () => boolean;
|
|
189
|
-
status: () => Promise<{
|
|
190
|
-
auth: { status: boolean };
|
|
191
|
-
api: { status: boolean; statusPage: string };
|
|
192
|
-
}>;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const MAX_CONCURRENT_DOWNLOADS = 10;
|
|
196
|
-
|
|
197
|
-
export type ImplementationFile = {
|
|
198
|
-
id?: string | null | undefined;
|
|
199
|
-
label?: string;
|
|
200
|
-
path: string;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
type ReadFile = (
|
|
204
|
-
path: string,
|
|
205
|
-
id: string | null | undefined,
|
|
206
|
-
options: { parseText: boolean },
|
|
207
|
-
) => Promise<string | Blob>;
|
|
208
|
-
|
|
209
|
-
type ReadFileMetadata = (path: string, id: string | null | undefined) => Promise<FileMetadata>;
|
|
210
|
-
|
|
211
|
-
type CustomFetchFunc = (files: ImplementationFile[]) => Promise<ImplementationEntry[]>;
|
|
212
|
-
|
|
213
|
-
async function fetchFiles(
|
|
214
|
-
files: ImplementationFile[],
|
|
215
|
-
readFile: ReadFile,
|
|
216
|
-
readFileMetadata: ReadFileMetadata,
|
|
217
|
-
apiName: string,
|
|
218
|
-
) {
|
|
219
|
-
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
|
|
220
|
-
const promises = [] as Promise<ImplementationEntry | { error: boolean }>[];
|
|
221
|
-
files.forEach(file => {
|
|
222
|
-
promises.push(
|
|
223
|
-
new Promise(resolve =>
|
|
224
|
-
sem.take(async () => {
|
|
225
|
-
try {
|
|
226
|
-
const [data, fileMetadata] = await Promise.all([
|
|
227
|
-
readFile(file.path, file.id, { parseText: true }),
|
|
228
|
-
readFileMetadata(file.path, file.id),
|
|
229
|
-
]);
|
|
230
|
-
resolve({ file: { ...file, ...fileMetadata }, data: data as string });
|
|
231
|
-
sem.leave();
|
|
232
|
-
} catch (error) {
|
|
233
|
-
sem.leave();
|
|
234
|
-
console.error(`failed to load file from ${apiName}: ${file.path}`);
|
|
235
|
-
resolve({ error: true });
|
|
236
|
-
}
|
|
237
|
-
}),
|
|
238
|
-
),
|
|
239
|
-
);
|
|
240
|
-
});
|
|
241
|
-
return Promise.all(promises).then(loadedEntries =>
|
|
242
|
-
loadedEntries.filter(loadedEntry => !(loadedEntry as { error: boolean }).error),
|
|
243
|
-
) as Promise<ImplementationEntry[]>;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export async function entriesByFolder(
|
|
247
|
-
listFiles: () => Promise<ImplementationFile[]>,
|
|
248
|
-
readFile: ReadFile,
|
|
249
|
-
readFileMetadata: ReadFileMetadata,
|
|
250
|
-
apiName: string,
|
|
251
|
-
) {
|
|
252
|
-
const files = await listFiles();
|
|
253
|
-
return fetchFiles(files, readFile, readFileMetadata, apiName);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export async function entriesByFiles(
|
|
257
|
-
files: ImplementationFile[],
|
|
258
|
-
readFile: ReadFile,
|
|
259
|
-
readFileMetadata: ReadFileMetadata,
|
|
260
|
-
apiName: string,
|
|
261
|
-
) {
|
|
262
|
-
return fetchFiles(files, readFile, readFileMetadata, apiName);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function unpublishedEntries(listEntriesKeys: () => Promise<string[]>) {
|
|
266
|
-
try {
|
|
267
|
-
const keys = await listEntriesKeys();
|
|
268
|
-
return keys;
|
|
269
|
-
} catch (error) {
|
|
270
|
-
if (error.message === 'Not Found') {
|
|
271
|
-
return Promise.resolve([]);
|
|
272
|
-
}
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export function blobToFileObj(name: string, blob: Blob) {
|
|
278
|
-
const options = name.match(/.svg$/) ? { type: 'image/svg+xml' } : {};
|
|
279
|
-
return new File([blob], name, options);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export async function getMediaAsBlob(path: string, id: string | null, readFile: ReadFile) {
|
|
283
|
-
let blob: Blob;
|
|
284
|
-
if (path.match(/.svg$/)) {
|
|
285
|
-
const text = (await readFile(path, id, { parseText: true })) as string;
|
|
286
|
-
blob = new Blob([text], { type: 'image/svg+xml' });
|
|
287
|
-
} else {
|
|
288
|
-
blob = (await readFile(path, id, { parseText: false })) as Blob;
|
|
289
|
-
}
|
|
290
|
-
return blob;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export async function getMediaDisplayURL(
|
|
294
|
-
displayURL: DisplayURL,
|
|
295
|
-
readFile: ReadFile,
|
|
296
|
-
semaphore: Semaphore,
|
|
297
|
-
) {
|
|
298
|
-
const { path, id } = displayURL as DisplayURLObject;
|
|
299
|
-
return new Promise<string>((resolve, reject) =>
|
|
300
|
-
semaphore.take(() =>
|
|
301
|
-
getMediaAsBlob(path, id, readFile)
|
|
302
|
-
.then(blob => URL.createObjectURL(blob))
|
|
303
|
-
.then(resolve, reject)
|
|
304
|
-
.finally(() => semaphore.leave()),
|
|
305
|
-
),
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function runWithLock(lock: AsyncLock, func: Function, message: string) {
|
|
310
|
-
try {
|
|
311
|
-
const acquired = await lock.acquire();
|
|
312
|
-
if (!acquired) {
|
|
313
|
-
console.warn(message);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const result = await func();
|
|
317
|
-
return result;
|
|
318
|
-
} finally {
|
|
319
|
-
lock.release();
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const LOCAL_KEY = 'git.local';
|
|
324
|
-
|
|
325
|
-
type LocalTree = {
|
|
326
|
-
head: string;
|
|
327
|
-
files: { id: string; name: string; path: string }[];
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
type GetKeyArgs = {
|
|
331
|
-
branch: string;
|
|
332
|
-
folder: string;
|
|
333
|
-
extension: string;
|
|
334
|
-
depth: number;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
function getLocalKey({ branch, folder, extension, depth }: GetKeyArgs) {
|
|
338
|
-
return `${LOCAL_KEY}.${branch}.${folder}.${extension}.${depth}`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
type PersistLocalTreeArgs = GetKeyArgs & {
|
|
342
|
-
localForage: LocalForage;
|
|
343
|
-
localTree: LocalTree;
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
type GetLocalTreeArgs = GetKeyArgs & {
|
|
347
|
-
localForage: LocalForage;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export async function persistLocalTree({
|
|
351
|
-
localForage,
|
|
352
|
-
localTree,
|
|
353
|
-
branch,
|
|
354
|
-
folder,
|
|
355
|
-
extension,
|
|
356
|
-
depth,
|
|
357
|
-
}: PersistLocalTreeArgs) {
|
|
358
|
-
await localForage.setItem<LocalTree>(
|
|
359
|
-
getLocalKey({ branch, folder, extension, depth }),
|
|
360
|
-
localTree,
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function getLocalTree({
|
|
365
|
-
localForage,
|
|
366
|
-
branch,
|
|
367
|
-
folder,
|
|
368
|
-
extension,
|
|
369
|
-
depth,
|
|
370
|
-
}: GetLocalTreeArgs) {
|
|
371
|
-
const localTree = await localForage.getItem<LocalTree>(
|
|
372
|
-
getLocalKey({ branch, folder, extension, depth }),
|
|
373
|
-
);
|
|
374
|
-
return localTree;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
type GetDiffFromLocalTreeMethods = {
|
|
378
|
-
getDifferences: (
|
|
379
|
-
to: string,
|
|
380
|
-
from: string,
|
|
381
|
-
) => Promise<
|
|
382
|
-
{
|
|
383
|
-
oldPath: string;
|
|
384
|
-
newPath: string;
|
|
385
|
-
status: string;
|
|
386
|
-
}[]
|
|
387
|
-
>;
|
|
388
|
-
filterFile: (file: { path: string; name: string }) => boolean;
|
|
389
|
-
getFileId: (path: string) => Promise<string>;
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
type GetDiffFromLocalTreeArgs = GetDiffFromLocalTreeMethods & {
|
|
393
|
-
branch: { name: string; sha: string };
|
|
394
|
-
localTree: LocalTree;
|
|
395
|
-
folder: string;
|
|
396
|
-
extension: string;
|
|
397
|
-
depth: number;
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
async function getDiffFromLocalTree({
|
|
401
|
-
branch,
|
|
402
|
-
localTree,
|
|
403
|
-
folder,
|
|
404
|
-
getDifferences,
|
|
405
|
-
filterFile,
|
|
406
|
-
getFileId,
|
|
407
|
-
}: GetDiffFromLocalTreeArgs) {
|
|
408
|
-
const diff = await getDifferences(branch.sha, localTree.head);
|
|
409
|
-
const diffFiles = diff
|
|
410
|
-
.filter(d => d.oldPath?.startsWith(folder) || d.newPath?.startsWith(folder))
|
|
411
|
-
.reduce((acc, d) => {
|
|
412
|
-
if (d.status === 'renamed') {
|
|
413
|
-
acc.push({
|
|
414
|
-
path: d.oldPath,
|
|
415
|
-
name: basename(d.oldPath),
|
|
416
|
-
deleted: true,
|
|
417
|
-
});
|
|
418
|
-
acc.push({
|
|
419
|
-
path: d.newPath,
|
|
420
|
-
name: basename(d.newPath),
|
|
421
|
-
deleted: false,
|
|
422
|
-
});
|
|
423
|
-
} else if (d.status === 'deleted') {
|
|
424
|
-
acc.push({
|
|
425
|
-
path: d.oldPath,
|
|
426
|
-
name: basename(d.oldPath),
|
|
427
|
-
deleted: true,
|
|
428
|
-
});
|
|
429
|
-
} else {
|
|
430
|
-
acc.push({
|
|
431
|
-
path: d.newPath || d.oldPath,
|
|
432
|
-
name: basename(d.newPath || d.oldPath),
|
|
433
|
-
deleted: false,
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return acc;
|
|
438
|
-
}, [] as { path: string; name: string; deleted: boolean }[])
|
|
439
|
-
|
|
440
|
-
.filter(filterFile);
|
|
441
|
-
|
|
442
|
-
const diffFilesWithIds = await Promise.all(
|
|
443
|
-
diffFiles.map(async file => {
|
|
444
|
-
if (!file.deleted) {
|
|
445
|
-
const id = await getFileId(file.path);
|
|
446
|
-
return { ...file, id };
|
|
447
|
-
} else {
|
|
448
|
-
return { ...file, id: '' };
|
|
449
|
-
}
|
|
450
|
-
}),
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
return diffFilesWithIds;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
type AllEntriesByFolderArgs = GetKeyArgs &
|
|
457
|
-
GetDiffFromLocalTreeMethods & {
|
|
458
|
-
listAllFiles: (
|
|
459
|
-
folder: string,
|
|
460
|
-
extension: string,
|
|
461
|
-
depth: number,
|
|
462
|
-
) => Promise<ImplementationFile[]>;
|
|
463
|
-
readFile: ReadFile;
|
|
464
|
-
readFileMetadata: ReadFileMetadata;
|
|
465
|
-
getDefaultBranch: () => Promise<{ name: string; sha: string }>;
|
|
466
|
-
isShaExistsInBranch: (branch: string, sha: string) => Promise<boolean>;
|
|
467
|
-
apiName: string;
|
|
468
|
-
localForage: LocalForage;
|
|
469
|
-
customFetch?: CustomFetchFunc;
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
export async function allEntriesByFolder({
|
|
473
|
-
listAllFiles,
|
|
474
|
-
readFile,
|
|
475
|
-
readFileMetadata,
|
|
476
|
-
apiName,
|
|
477
|
-
branch,
|
|
478
|
-
localForage,
|
|
479
|
-
folder,
|
|
480
|
-
extension,
|
|
481
|
-
depth,
|
|
482
|
-
getDefaultBranch,
|
|
483
|
-
isShaExistsInBranch,
|
|
484
|
-
getDifferences,
|
|
485
|
-
getFileId,
|
|
486
|
-
filterFile,
|
|
487
|
-
customFetch,
|
|
488
|
-
}: AllEntriesByFolderArgs) {
|
|
489
|
-
async function listAllFilesAndPersist() {
|
|
490
|
-
const files = await listAllFiles(folder, extension, depth);
|
|
491
|
-
const branch = await getDefaultBranch();
|
|
492
|
-
await persistLocalTree({
|
|
493
|
-
localForage,
|
|
494
|
-
localTree: {
|
|
495
|
-
head: branch.sha,
|
|
496
|
-
files: files.map(f => ({ id: f.id!, path: f.path, name: basename(f.path) })),
|
|
497
|
-
},
|
|
498
|
-
branch: branch.name,
|
|
499
|
-
depth,
|
|
500
|
-
extension,
|
|
501
|
-
folder,
|
|
502
|
-
});
|
|
503
|
-
return files;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
async function listFiles() {
|
|
507
|
-
const localTree = await getLocalTree({ localForage, branch, folder, extension, depth });
|
|
508
|
-
if (localTree) {
|
|
509
|
-
const branch = await getDefaultBranch();
|
|
510
|
-
// if the branch was forced pushed the local tree sha can be removed from the remote tree
|
|
511
|
-
const localTreeInBranch = await isShaExistsInBranch(branch.name, localTree.head);
|
|
512
|
-
if (!localTreeInBranch) {
|
|
513
|
-
console.log(
|
|
514
|
-
`Can't find local tree head '${localTree.head}' in branch '${branch.name}', rebuilding local tree`,
|
|
515
|
-
);
|
|
516
|
-
return listAllFilesAndPersist();
|
|
517
|
-
}
|
|
518
|
-
const diff = await getDiffFromLocalTree({
|
|
519
|
-
branch,
|
|
520
|
-
localTree,
|
|
521
|
-
folder,
|
|
522
|
-
extension,
|
|
523
|
-
depth,
|
|
524
|
-
getDifferences,
|
|
525
|
-
getFileId,
|
|
526
|
-
filterFile,
|
|
527
|
-
}).catch(e => {
|
|
528
|
-
console.log('Failed getting diff from local tree:', e);
|
|
529
|
-
return null;
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
if (!diff) {
|
|
533
|
-
console.log(`Diff is null, rebuilding local tree`);
|
|
534
|
-
return listAllFilesAndPersist();
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (diff.length === 0) {
|
|
538
|
-
// return local copy
|
|
539
|
-
return localTree.files;
|
|
540
|
-
} else {
|
|
541
|
-
const deleted = diff.reduce((acc, d) => {
|
|
542
|
-
acc[d.path] = d.deleted;
|
|
543
|
-
return acc;
|
|
544
|
-
}, {} as Record<string, boolean>);
|
|
545
|
-
const newCopy = sortBy(
|
|
546
|
-
unionBy(
|
|
547
|
-
diff.filter(d => !deleted[d.path]),
|
|
548
|
-
localTree.files.filter(f => !deleted[f.path]),
|
|
549
|
-
file => file.path,
|
|
550
|
-
),
|
|
551
|
-
file => file.path,
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
await persistLocalTree({
|
|
555
|
-
localForage,
|
|
556
|
-
localTree: { head: branch.sha, files: newCopy },
|
|
557
|
-
branch: branch.name,
|
|
558
|
-
depth,
|
|
559
|
-
extension,
|
|
560
|
-
folder,
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
return newCopy;
|
|
564
|
-
}
|
|
565
|
-
} else {
|
|
566
|
-
return listAllFilesAndPersist();
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const files = await listFiles();
|
|
571
|
-
if (customFetch) {
|
|
572
|
-
return await customFetch(files);
|
|
573
|
-
}
|
|
574
|
-
return await fetchFiles(files, readFile, readFileMetadata, apiName);
|
|
575
|
-
}
|