decap-cms-lib-util 3.8.1 → 3.8.3

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/dist/esm/index.js CHANGED
@@ -7,6 +7,8 @@ import { isAbsolutePath, basename, fileExtensionWithSeparator, fileExtension } f
7
7
  import { onlySuccessfulPromises, flowAsync, then } from './promise';
8
8
  import unsentRequest from './unsentRequest';
9
9
  import { filterByExtension, getAllResponses, parseLinkHeader, parseResponse, responseParser, getPathDepth } from './backendUtil';
10
+ import { NotesPollingManager } from './notesPolling';
11
+ import { formatNoteBody, parseNoteBody, commentToNote, commentsToNotes, markOwnNotes } from './notesFormat';
10
12
  import loadScript from './loadScript';
11
13
  import getBlobSHA from './getBlobSHA';
12
14
  import { asyncLock } from './asyncLock';
@@ -68,4 +70,4 @@ export const DecapCmsLibUtil = {
68
70
  AccessTokenError,
69
71
  throwOnConflictingBranches
70
72
  };
71
- export { APIError, Cursor, CURSOR_COMPATIBILITY_SYMBOL, EditorialWorkflowError, EDITORIAL_WORKFLOW_ERROR, localForage, basename, fileExtensionWithSeparator, fileExtension, onlySuccessfulPromises, flowAsync, then, unsentRequest, filterByExtension, parseLinkHeader, getAllResponses, parseResponse, responseParser, loadScript, getBlobSHA, asyncLock, isAbsolutePath, getPathDepth, entriesByFiles, entriesByFolder, unpublishedEntries, getMediaDisplayURL, getMediaAsBlob, readFile, readFileMetadata, CMS_BRANCH_PREFIX, generateContentKey, isCMSLabel, labelToStatus, statusToLabel, DEFAULT_PR_BODY, MERGE_COMMIT_MESSAGE, isPreviewContext, getPreviewStatus, runWithLock, PreviewState, parseContentKey, createPointerFile, getLargeMediaFilteredMediaFiles, getLargeMediaPatternsFromGitAttributesFile, parsePointerFile, getPointerFileForMediaFileObj, branchFromContentKey, contentKeyFromBranch, blobToFileObj, requestWithBackoff, getDefaultBranchName, allEntriesByFolder, AccessTokenError, throwOnConflictingBranches };
73
+ export { NotesPollingManager, formatNoteBody, parseNoteBody, commentToNote, commentsToNotes, markOwnNotes, APIError, Cursor, CURSOR_COMPATIBILITY_SYMBOL, EditorialWorkflowError, EDITORIAL_WORKFLOW_ERROR, localForage, basename, fileExtensionWithSeparator, fileExtension, onlySuccessfulPromises, flowAsync, then, unsentRequest, filterByExtension, parseLinkHeader, getAllResponses, parseResponse, responseParser, loadScript, getBlobSHA, asyncLock, isAbsolutePath, getPathDepth, entriesByFiles, entriesByFolder, unpublishedEntries, getMediaDisplayURL, getMediaAsBlob, readFile, readFileMetadata, CMS_BRANCH_PREFIX, generateContentKey, isCMSLabel, labelToStatus, statusToLabel, DEFAULT_PR_BODY, MERGE_COMMIT_MESSAGE, isPreviewContext, getPreviewStatus, runWithLock, PreviewState, parseContentKey, createPointerFile, getLargeMediaFilteredMediaFiles, getLargeMediaPatternsFromGitAttributesFile, parsePointerFile, getPointerFileForMediaFileObj, branchFromContentKey, contentKeyFromBranch, blobToFileObj, requestWithBackoff, getDefaultBranchName, allEntriesByFolder, AccessTokenError, throwOnConflictingBranches };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * How a note is encoded inside a comment on the host
3
+ *
4
+ * A note is an ordinary comment with a leading HTML comment carrying
5
+ * whether the note is resolved and who wrote it - which can differ from
6
+ * the account that posted it.
7
+ * Hosts render HTML comments invisibly, so the marker stays out of the way
8
+ * when the thread is read on the host's own site.
9
+ *
10
+ * Encoding is identical across backends
11
+ */
12
+
13
+ const MARKER_PREFIX = '<!-- DecapCMS Note ';
14
+ const MARKER_SUFFIX = ' -->';
15
+ const NOTE_PATTERN = /^<!-- DecapCMS Note (\{[\s\S]*?\}) -->\n?([\s\S]*)$/;
16
+ const LEGACY_NOTE_PATTERN = /^<!-- DecapCMS Note - Status: (RESOLVED|OPEN) -->\n?([\s\S]*)$/;
17
+
18
+ /**
19
+ * A JSON string may contain `-->`, which would close the HTML comment early.
20
+ * Escaping every hyphen that precedes another removes every `--`, so `-->`
21
+ * cannot survive; `JSON.parse` decodes `\u002d` back, so it stays lossless
22
+ * and a lone hyphen (`Jean-Luc`) stays readable.
23
+ *
24
+ * Replacing `--` instead is not enough - a run of five hyphens leaves one
25
+ * behind at the seam.
26
+ */
27
+ function encode(payload) {
28
+ return JSON.stringify(payload).replace(/-(?=-)/g, '\\u002d');
29
+ }
30
+ export function formatNoteBody(note) {
31
+ // Both or neither: a name with no id to compare against buys nothing, and a
32
+ // backend whose poster IS the editor records neither.
33
+ const identity = note.authorId && note.author ? {
34
+ author: note.author,
35
+ authorId: note.authorId
36
+ } : {};
37
+ const marker = encode({
38
+ resolved: note.resolved,
39
+ ...identity
40
+ });
41
+ return `${MARKER_PREFIX}${marker}${MARKER_SUFFIX}\n${note.content}`;
42
+ }
43
+
44
+ /**
45
+ * The marker is hand-editable on the host, so nothing in it is trusted to be
46
+ * the type it should be. A non-string author reaching `Note` crashes the pane
47
+ * outright - the avatar initials call `.split()` on it - so anything that is
48
+ * not a usable string is treated as no recorded author, which falls back to
49
+ * the account that posted the comment.
50
+ */
51
+ function asString(value) {
52
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
53
+ }
54
+ export function parseNoteBody(body) {
55
+ const match = body.match(NOTE_PATTERN);
56
+ if (!match) {
57
+ const legacy = body.match(LEGACY_NOTE_PATTERN);
58
+ if (legacy) {
59
+ return {
60
+ content: legacy[2].trim(),
61
+ resolved: legacy[1] === 'RESOLVED'
62
+ };
63
+ }
64
+
65
+ // A comment typed straight into the thread on the host. It is still a note,
66
+ // just an unresolved one with no recorded author.
67
+ return {
68
+ content: body.trim(),
69
+ resolved: false
70
+ };
71
+ }
72
+ let payload;
73
+ try {
74
+ payload = JSON.parse(match[1]);
75
+ } catch {
76
+ // Someone edited the marker by hand into something unparseable. Treat the
77
+ // whole comment as content rather than dropping the note.
78
+ return {
79
+ content: body.trim(),
80
+ resolved: false
81
+ };
82
+ }
83
+ const author = asString(payload.author);
84
+ const authorId = asString(payload.authorId);
85
+
86
+ // Both or neither, as formatNoteBody writes them.
87
+ const identity = author && authorId ? {
88
+ author,
89
+ authorId
90
+ } : {};
91
+ return {
92
+ content: match[2].trim(),
93
+ resolved: payload.resolved === true,
94
+ author: identity.author,
95
+ authorId: identity.authorId
96
+ };
97
+ }
98
+ export function commentToNote(comment) {
99
+ if (!comment || !comment.body) {
100
+ throw new Error('Invalid comment structure');
101
+ }
102
+ const {
103
+ content,
104
+ resolved,
105
+ author,
106
+ authorId
107
+ } = parseNoteBody(comment.body);
108
+ if (!content) {
109
+ throw new Error('Empty note content');
110
+ }
111
+ return {
112
+ id: comment.id.toString(),
113
+ // Falls back to the account that posted, for a comment typed on the host
114
+ // and for any note with no recorded author.
115
+ author: author || comment.user?.login || 'Unknown',
116
+ authorId,
117
+ // Only when the posting account IS the note's author — a recorded author
118
+ // means someone posted on their behalf, and its avatar would mislabel the
119
+ // note. The pane shows initials instead.
120
+ avatarUrl: authorId ? undefined : comment.user?.avatar_url || undefined,
121
+ timestamp: comment.created_at,
122
+ content,
123
+ resolved,
124
+ entrySlug: ''
125
+ };
126
+ }
127
+ export function commentsToNotes(comments, issueUrl, toNote = commentToNote) {
128
+ return comments.reduce((notes, comment) => {
129
+ try {
130
+ notes.push({
131
+ ...toNote(comment),
132
+ issueUrl
133
+ });
134
+ } catch (error) {
135
+ // Not a note; skip it
136
+ }
137
+ return notes;
138
+ }, []);
139
+ }
140
+ export function markOwnNotes(notes, identity) {
141
+ return notes.map(note => ({
142
+ ...note,
143
+ isOwn: note.authorId ? note.authorId === identity.authorId : note.author === identity.author
144
+ }));
145
+ }
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Notes polling
3
+ *
4
+ * Watches the thread an entry's notes live in and reports what changed, so
5
+ * notes another editor adds appear without a reload. Where the host supports
6
+ * conditional requests the poll is an ETag round trip that returns 304 and
7
+ * costs no rate limit; where it does not, the manager still only reports a
8
+ * change when the thread's contents actually differ, so a host that always
9
+ * answers 200 is slower, not wrong.
10
+ *
11
+ * Not specific to one host: it talks to `NotesPollingAPI` below,
12
+ * which any backend with a comment thread per entry can satisfy.
13
+ *
14
+ * @module notesPolling
15
+ */
16
+
17
+ import { commentsToNotes } from './notesFormat';
18
+ function noop() {
19
+ /* nothing to unwatch */
20
+ }
21
+
22
+ // Redux action types
23
+ export const NOTES_POLLING_START = 'NOTES_POLLING_START';
24
+ export const NOTES_POLLING_STOP = 'NOTES_POLLING_STOP';
25
+ export const NOTES_POLLING_UPDATE = 'NOTES_POLLING_UPDATE';
26
+ export const NOTES_CHANGE_DETECTED = 'NOTES_CHANGE_DETECTED';
27
+ export class NotesPollingManager {
28
+ currentWatch = null;
29
+ currentIssueKey = null;
30
+ pollingInterval = 15000;
31
+ intervalId = null;
32
+ isDocumentVisible = true;
33
+ isPolling = false;
34
+ pendingRetryTimeout = null;
35
+ cancelPendingRetry = null;
36
+ pendingIssueKey = null;
37
+ watchGeneration = 0;
38
+ constructor(api, pollingInterval = 15000) {
39
+ this.api = api;
40
+ this.pollingInterval = pollingInterval;
41
+ this.setupVisibilityListener();
42
+ }
43
+
44
+ /**
45
+ * Setup Page Visibility API listener
46
+ * Pauses polling when tab is hidden
47
+ */
48
+ setupVisibilityListener() {
49
+ if (typeof document !== 'undefined') {
50
+ document.addEventListener('visibilitychange', this.handleVisibilityChange);
51
+ }
52
+ }
53
+ handleVisibilityChange = () => {
54
+ this.isDocumentVisible = !document.hidden;
55
+ if (this.isDocumentVisible) {
56
+ this.startPolling();
57
+ this.checkAllIssuesNow();
58
+ } else {
59
+ this.stopPolling();
60
+ }
61
+ };
62
+
63
+ /**
64
+ * Start watching an issue for changes
65
+ * This will automatically stop watching any previously watched issue
66
+ *
67
+ * @param issueNumber - GitHub issue number
68
+ * @param collection - Collection name
69
+ * @param slug - Entry slug
70
+ * @returns Function to stop watching
71
+ */
72
+ async watchIssue(issueNumber, collection, slug, callbacks, initialState = null) {
73
+ const issueKey = this.getIssueKey(collection, slug);
74
+
75
+ // STOP ANY EXISTING WATCH FIRST
76
+ this.stopCurrentWatch();
77
+ const generation = this.watchGeneration;
78
+
79
+ // Get initial state if not provided
80
+ if (!initialState) {
81
+ try {
82
+ initialState = await this.api.getIssueState(issueNumber);
83
+ } catch (error) {
84
+ console.error('[DecapNotes Polling] Failed to get initial state:', error);
85
+ }
86
+ if (generation !== this.watchGeneration) {
87
+ return noop;
88
+ }
89
+ }
90
+ const watch = {
91
+ issueNumber,
92
+ collection,
93
+ slug,
94
+ etag: null,
95
+ lastState: initialState,
96
+ onUpdate: callbacks.onUpdate,
97
+ onChange: callbacks.onChange,
98
+ prepareNotes: callbacks.prepareNotes,
99
+ retryCount: 0,
100
+ maxRetries: 5
101
+ };
102
+ this.currentWatch = watch;
103
+ this.currentIssueKey = issueKey;
104
+
105
+ // Start polling if not already running
106
+ if (!this.intervalId && this.isDocumentVisible) {
107
+ this.startPolling();
108
+ }
109
+
110
+ // Do an immediate check
111
+ this.checkCurrentIssue();
112
+
113
+ // Return unwatch function
114
+ return () => {
115
+ if (this.currentWatch === watch) {
116
+ this.stopCurrentWatch();
117
+ }
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Watch issue with retry logic for newly created issues
123
+ */
124
+ async watchIssueWithRetry(collection, slug, callbacks, maxRetries = 5, retryDelay = 2000) {
125
+ const issueKey = this.getIssueKey(collection, slug);
126
+
127
+ // STOP ANY EXISTING WATCH FIRST
128
+ this.stopCurrentWatch();
129
+ const generation = this.watchGeneration;
130
+ this.pendingIssueKey = issueKey;
131
+ const isSuperseded = () => generation !== this.watchGeneration;
132
+ const attemptWatch = async attempt => {
133
+ let lookupError;
134
+ let issue = null;
135
+ try {
136
+ issue = await this.api.findEntryIssue(collection, slug);
137
+ } catch (error) {
138
+ console.error(`[DecapNotes Polling] Error finding issue for ${issueKey}:`, error);
139
+ lookupError = error;
140
+ }
141
+ if (isSuperseded()) {
142
+ return noop;
143
+ }
144
+ if (issue) {
145
+ this.pendingIssueKey = null;
146
+ return this.watchIssue(issue.number, collection, slug, callbacks);
147
+ }
148
+ if (attempt < maxRetries) {
149
+ const elapsed = await this.waitForRetry(retryDelay);
150
+ return elapsed && !isSuperseded() ? attemptWatch(attempt + 1) : noop;
151
+ }
152
+ this.pendingIssueKey = null;
153
+ if (lookupError) {
154
+ throw lookupError;
155
+ }
156
+ console.log(`[DecapNotes Polling] No issue found for ${issueKey} after ${maxRetries} attempts. This is expected if there are no notes for this entry yet.`);
157
+ return noop;
158
+ };
159
+ return attemptWatch(1);
160
+ }
161
+ waitForRetry(delay) {
162
+ return new Promise(resolve => {
163
+ this.cancelPendingRetry = () => resolve(false);
164
+ this.pendingRetryTimeout = setTimeout(() => {
165
+ this.pendingRetryTimeout = null;
166
+ this.cancelPendingRetry = null;
167
+ resolve(true);
168
+ }, delay);
169
+ });
170
+ }
171
+ clearPendingRetry() {
172
+ if (this.pendingRetryTimeout) {
173
+ clearTimeout(this.pendingRetryTimeout);
174
+ this.pendingRetryTimeout = null;
175
+ }
176
+ this.cancelPendingRetry?.();
177
+ this.cancelPendingRetry = null;
178
+ this.pendingIssueKey = null;
179
+ }
180
+
181
+ /**
182
+ * Stop watching an entry, including a pending retry
183
+ */
184
+ stopWatching(collection, slug) {
185
+ const issueKey = this.getIssueKey(collection, slug);
186
+ if (this.currentIssueKey === issueKey || this.pendingIssueKey === issueKey) {
187
+ this.stopCurrentWatch();
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Stop watching the current issue - complete cleanup
193
+ */
194
+ stopCurrentWatch() {
195
+ this.watchGeneration += 1;
196
+ this.clearPendingRetry();
197
+ if (!this.currentWatch) {
198
+ return;
199
+ }
200
+
201
+ // Clear current watch
202
+ this.currentWatch = null;
203
+ this.currentIssueKey = null;
204
+
205
+ // Stop polling since there's nothing to watch
206
+ this.stopPolling();
207
+ }
208
+
209
+ /**
210
+ * Start the polling loop
211
+ */
212
+ startPolling() {
213
+ if (this.intervalId || !this.isDocumentVisible || !this.currentWatch) return;
214
+ console.log(`[DecapNotes Polling] Starting polling loop (${this.pollingInterval}ms interval) for ${this.currentIssueKey}`);
215
+ this.intervalId = setInterval(() => {
216
+ this.pollAllIssues();
217
+ }, this.pollingInterval);
218
+ }
219
+
220
+ /**
221
+ * Stop the polling loop
222
+ */
223
+ stopPolling() {
224
+ if (this.intervalId) {
225
+ console.log('[DecapNotes Polling] Stopping polling loop');
226
+ clearInterval(this.intervalId);
227
+ this.intervalId = null;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Poll current watched issue
233
+ */
234
+ async pollAllIssues() {
235
+ await this.checkCurrentIssue();
236
+ }
237
+
238
+ /**
239
+ * Check current issue for changes using ETag
240
+ */
241
+ async checkCurrentIssue() {
242
+ if (!this.currentWatch) {
243
+ return;
244
+ }
245
+ if (this.isPolling) {
246
+ return;
247
+ }
248
+ this.isPolling = true;
249
+ try {
250
+ const watch = this.currentWatch;
251
+ const response = await this.api.getIssueWithETag(watch.issueNumber, watch.etag);
252
+ if (response.status === 304) {
253
+ return;
254
+ }
255
+ if (this.currentWatch !== watch) {
256
+ return;
257
+ }
258
+ if (response.status === 200) {
259
+ const newState = response.data;
260
+
261
+ // Detect specific changes
262
+ const changes = this.detectChanges(watch.lastState, newState);
263
+ if (changes.length > 0) {
264
+ // Convert comments to notes
265
+ let newNotes = commentsToNotes(newState.comments, newState.html_url, this.api.parseCommentToNote && (comment => this.api.parseCommentToNote(comment)));
266
+ if (watch.prepareNotes) {
267
+ newNotes = await watch.prepareNotes(newNotes);
268
+ if (this.currentWatch !== watch) {
269
+ return;
270
+ }
271
+ }
272
+ if (watch.onUpdate) {
273
+ watch.onUpdate(newNotes, changes);
274
+ }
275
+ if (watch.onChange) {
276
+ changes.forEach(change => {
277
+ watch.onChange(change);
278
+ });
279
+ }
280
+ }
281
+
282
+ // Update ETag and stored state
283
+ watch.etag = response.etag || null;
284
+ watch.lastState = newState;
285
+ }
286
+ } catch (error) {
287
+ console.error(`[DecapNotes Polling] Error checking ${this.currentIssueKey}:`, error);
288
+ } finally {
289
+ this.isPolling = false;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Immediately check current issue
295
+ */
296
+ async checkAllIssuesNow() {
297
+ await this.checkCurrentIssue();
298
+ }
299
+
300
+ /**
301
+ * Manually trigger a check - only works if this is the current entry
302
+ */
303
+ async checkIssueNow(collection, slug) {
304
+ const issueKey = this.getIssueKey(collection, slug);
305
+ if (this.currentIssueKey !== issueKey) {
306
+ console.warn(`[DecapNotes Polling] Cannot check ${issueKey} - currently watching ${this.currentIssueKey}`);
307
+ return;
308
+ }
309
+ await this.checkCurrentIssue();
310
+ }
311
+
312
+ /**
313
+ * Detect what changed between two states
314
+ */
315
+ detectChanges(previous, current) {
316
+ if (!previous) {
317
+ return [];
318
+ }
319
+ const changes = [];
320
+
321
+ // New comments
322
+ const newComments = current.comments.filter(comment => !previous.comments.some(prev => prev.id === comment.id));
323
+ newComments.forEach(comment => {
324
+ changes.push({
325
+ type: 'comment_added',
326
+ data: comment,
327
+ timestamp: comment.created_at
328
+ });
329
+ });
330
+
331
+ // Updated comments
332
+ current.comments.forEach(comment => {
333
+ const prevComment = previous.comments.find(prev => prev.id === comment.id);
334
+ if (prevComment && prevComment.updated_at !== comment.updated_at) {
335
+ changes.push({
336
+ type: 'comment_updated',
337
+ data: comment,
338
+ previousData: prevComment,
339
+ timestamp: comment.updated_at
340
+ });
341
+ }
342
+ });
343
+
344
+ // Deleted comments
345
+ const deletedComments = previous.comments.filter(prevComment => !current.comments.some(comment => comment.id === prevComment.id));
346
+ deletedComments.forEach(comment => {
347
+ changes.push({
348
+ type: 'comment_deleted',
349
+ data: comment,
350
+ timestamp: new Date().toISOString()
351
+ });
352
+ });
353
+
354
+ // Issue state changed
355
+ if (previous.state !== current.state) {
356
+ changes.push({
357
+ type: 'issue_state_changed',
358
+ data: {
359
+ from: previous.state,
360
+ to: current.state
361
+ },
362
+ timestamp: current.updated_at
363
+ });
364
+ }
365
+
366
+ // Labels changed
367
+ if (this.hasLabelsChanged(previous.labels, current.labels)) {
368
+ changes.push({
369
+ type: 'issue_labels_changed',
370
+ data: {
371
+ from: previous.labels,
372
+ to: current.labels
373
+ },
374
+ timestamp: current.updated_at
375
+ });
376
+ }
377
+ return changes;
378
+ }
379
+
380
+ /**
381
+ * Check if labels changed
382
+ */
383
+ hasLabelsChanged(previous, current) {
384
+ if (previous.length !== current.length) return true;
385
+ const prevNames = previous.map(l => l.name).sort();
386
+ const currNames = current.map(l => l.name).sort();
387
+ return prevNames.join(',') !== currNames.join(',');
388
+ }
389
+
390
+ /**
391
+ * Get issue key for storage
392
+ */
393
+ getIssueKey(collection, slug) {
394
+ return `${collection}/${slug}`;
395
+ }
396
+
397
+ /**
398
+ * Get polling status
399
+ */
400
+ getStatus() {
401
+ return {
402
+ isPolling: this.intervalId !== null,
403
+ currentWatch: this.currentIssueKey,
404
+ watchedCount: this.currentWatch ? 1 : 0,
405
+ pollingInterval: this.pollingInterval,
406
+ isDocumentVisible: this.isDocumentVisible,
407
+ hasPendingRetry: this.pendingRetryTimeout !== null
408
+ };
409
+ }
410
+
411
+ /**
412
+ * Clean up - stop all polling
413
+ */
414
+ destroy() {
415
+ console.log('[DecapNotes Polling] Destroying polling manager');
416
+
417
+ // Stop current watch
418
+ this.stopCurrentWatch();
419
+ if (typeof document !== 'undefined') {
420
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
421
+ }
422
+ }
423
+ }
424
+ export default NotesPollingManager;
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "decap-cms-lib-util",
3
3
  "description": "Shared utilities for Decap CMS.",
4
- "version": "3.8.1",
5
- "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-util",
4
+ "version": "3.8.3",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-util"
8
+ },
6
9
  "bugs": "https://github.com/decaporg/decap-cms/issues",
7
10
  "module": "dist/esm/index.js",
8
11
  "main": "dist/decap-cms-lib-util.js",
@@ -14,23 +17,22 @@
14
17
  "decap-cms"
15
18
  ],
16
19
  "sideEffects": false,
17
- "scripts": {
18
- "develop": "pnpm run build:esm --watch",
19
- "build": "cross-env NODE_ENV=production webpack",
20
- "build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
21
- },
22
20
  "dependencies": {
23
- "js-sha256": "catalog:",
24
- "localforage": "catalog:",
25
- "semaphore": "catalog:"
21
+ "js-sha256": "^0.9.0",
22
+ "localforage": "^1.7.3",
23
+ "semaphore": "^1.1.0"
26
24
  },
27
25
  "peerDependencies": {
28
- "common-tags": "catalog:",
29
- "immutable": "catalog:",
30
- "lodash": "catalog:"
26
+ "common-tags": "1.8.2",
27
+ "immutable": "^4.3.9",
28
+ "lodash": "^4.17.11"
31
29
  },
32
30
  "devDependencies": {
33
- "common-tags": "catalog:"
31
+ "common-tags": "1.8.2"
34
32
  },
35
- "gitHead": "570c3e7fbc399c455729964b9b5fdec079846393"
36
- }
33
+ "scripts": {
34
+ "develop": "pnpm run build:esm --watch",
35
+ "build": "cross-env NODE_ENV=production webpack",
36
+ "build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
37
+ }
38
+ }
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2016 Netlify <decap@p-m.si>
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.