@openneuro/server 5.0.0 → 5.1.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/package.json +8 -5
- package/src/app.ts +2 -4
- package/src/cache/__tests__/tree.spec.ts +2 -0
- package/src/cache/tree.ts +4 -0
- package/src/cache/types.ts +1 -0
- package/src/datalad/__tests__/contributors.spec.ts +1 -1
- package/src/datalad/__tests__/dataRetentionNotifications.spec.ts +11 -11
- package/src/datalad/__tests__/files.spec.ts +31 -1
- package/src/datalad/__tests__/snapshots.spec.ts +4 -4
- package/src/datalad/contributors.ts +2 -2
- package/src/datalad/dataRetentionNotifications.ts +5 -5
- package/src/datalad/dataset.ts +9 -5
- package/src/datalad/description.ts +2 -2
- package/src/datalad/draft.ts +8 -3
- package/src/datalad/files.ts +71 -12
- package/src/datalad/readme.ts +2 -2
- package/src/datalad/snapshots.ts +19 -14
- package/src/elasticsearch/elastic-client.ts +11 -6
- package/src/elasticsearch/reindex-dataset.ts +8 -4
- package/src/graphql/__tests__/comment.spec.ts +3 -2
- package/src/graphql/__tests__/schema.spec.ts +28 -0
- package/src/graphql/builder.ts +42 -0
- package/src/graphql/resolvers/__tests__/dataset.spec.ts +6 -119
- package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +2 -1
- package/src/graphql/resolvers/__tests__/permssions.spec.ts +3 -2
- package/src/graphql/resolvers/__tests__/user.spec.ts +39 -11
- package/src/graphql/resolvers/brainInitiative.ts +4 -3
- package/src/graphql/resolvers/cache.ts +7 -6
- package/src/graphql/resolvers/comment.ts +35 -19
- package/src/graphql/resolvers/dataset-search.ts +7 -6
- package/src/graphql/resolvers/dataset.ts +77 -45
- package/src/graphql/resolvers/datasetEvents.ts +18 -16
- package/src/graphql/resolvers/description.ts +2 -1
- package/src/graphql/resolvers/draft.ts +14 -3
- package/src/graphql/resolvers/fileCheck.ts +5 -4
- package/src/graphql/resolvers/flaggedFiles.ts +2 -1
- package/src/graphql/resolvers/follow.ts +2 -1
- package/src/graphql/resolvers/git.ts +2 -1
- package/src/graphql/resolvers/gitEvents.ts +2 -1
- package/src/graphql/resolvers/history.ts +3 -1
- package/src/graphql/resolvers/holdDeletion.ts +1 -1
- package/src/graphql/resolvers/importRemoteDataset.ts +3 -2
- package/src/graphql/resolvers/issues.ts +2 -1
- package/src/graphql/resolvers/metadata.ts +3 -2
- package/src/graphql/resolvers/permissions.ts +26 -6
- package/src/graphql/resolvers/publish.ts +2 -1
- package/src/graphql/resolvers/readme.ts +2 -1
- package/src/graphql/resolvers/reexporter.ts +2 -1
- package/src/graphql/resolvers/relation.ts +3 -2
- package/src/graphql/resolvers/reset.ts +2 -1
- package/src/graphql/resolvers/reviewer.ts +14 -6
- package/src/graphql/resolvers/snapshots.ts +42 -10
- package/src/graphql/resolvers/stars.ts +2 -1
- package/src/graphql/resolvers/upload.ts +3 -2
- package/src/graphql/resolvers/user.ts +44 -32
- package/src/graphql/resolvers/validation.ts +6 -5
- package/src/graphql/resolvers/worker.ts +2 -1
- package/src/graphql/schema/analytics.ts +12 -0
- package/src/graphql/schema/comment.ts +30 -0
- package/src/graphql/schema/dataset-events.ts +91 -0
- package/src/graphql/schema/dataset-search.ts +32 -0
- package/src/graphql/schema/dataset.ts +167 -0
- package/src/graphql/schema/description.ts +44 -0
- package/src/graphql/schema/draft.ts +80 -0
- package/src/graphql/schema/enums.ts +55 -0
- package/src/graphql/schema/files.ts +44 -0
- package/src/graphql/schema/inputs.ts +231 -0
- package/src/graphql/schema/metadata.ts +86 -0
- package/src/graphql/schema/misc.ts +154 -0
- package/src/graphql/schema/mutation.ts +549 -0
- package/src/graphql/schema/pagination.ts +12 -0
- package/src/graphql/schema/permissions.ts +21 -0
- package/src/graphql/schema/query.ts +119 -0
- package/src/graphql/schema/refs.ts +23 -0
- package/src/graphql/schema/reviewer.ts +11 -0
- package/src/graphql/schema/scalars.ts +9 -0
- package/src/graphql/schema/snapshot.ts +111 -0
- package/src/graphql/schema/upload.ts +13 -0
- package/src/graphql/schema/user.ts +61 -0
- package/src/graphql/schema/validation.ts +70 -0
- package/src/graphql/schema/worker.ts +16 -0
- package/src/graphql/schema.ts +29 -1114
- package/src/handlers/subscriptions.ts +1 -1
- package/src/libs/apikey.ts +1 -1
- package/src/libs/authentication/crypto.ts +14 -9
- package/src/libs/notifications.ts +1 -1
- package/src/libs/presign.ts +32 -20
- package/src/libs/redis.ts +12 -2
- package/src/models/comment.ts +2 -4
- package/src/models/counter.ts +2 -2
- package/src/models/ingestDataset.ts +1 -2
- package/src/models/notification.ts +0 -2
- package/src/models/subscription.ts +0 -1
- package/src/models/user.ts +1 -2
- package/src/models/userMigration.ts +1 -1
- package/src/models/userNotificationStatus.ts +0 -1
- package/src/utils/__tests__/snapshots.spec.ts +120 -0
- package/src/utils/snapshots.ts +12 -0
|
@@ -64,7 +64,7 @@ export const deleteAll = (req, res, next) => {
|
|
|
64
64
|
.then((subscriptions) => {
|
|
65
65
|
subscriptions.forEach((subscription) => {
|
|
66
66
|
Subscription.deleteOne({
|
|
67
|
-
_id: new ObjectID(subscription._id),
|
|
67
|
+
_id: new ObjectID(subscription._id.toString()),
|
|
68
68
|
})
|
|
69
69
|
})
|
|
70
70
|
return res.send()
|
package/src/libs/apikey.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import crypto from "crypto"
|
|
2
2
|
import config from "../../config"
|
|
3
3
|
|
|
4
|
-
const secret = config.auth.jwt.secret
|
|
5
4
|
const algorithm = "aes256"
|
|
6
|
-
const key = crypto
|
|
7
|
-
.createHash("sha256")
|
|
8
|
-
.update(secret)
|
|
9
|
-
.digest("base64")
|
|
10
|
-
.substr(0, 32)
|
|
11
|
-
|
|
12
5
|
const delimiter = "."
|
|
13
6
|
const encoding = "base64"
|
|
14
7
|
|
|
8
|
+
let _key: string | null = null
|
|
9
|
+
function getKey(): string {
|
|
10
|
+
if (!_key) {
|
|
11
|
+
_key = crypto
|
|
12
|
+
.createHash("sha256")
|
|
13
|
+
.update(config.auth.jwt.secret)
|
|
14
|
+
.digest("base64")
|
|
15
|
+
.substr(0, 32)
|
|
16
|
+
}
|
|
17
|
+
return _key
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
const pack = (iv, encrypted) => iv.toString(encoding) + delimiter + encrypted
|
|
16
21
|
|
|
17
22
|
const unpack = (encryptedPackage) => {
|
|
@@ -22,7 +27,7 @@ const unpack = (encryptedPackage) => {
|
|
|
22
27
|
|
|
23
28
|
export const encrypt = (plainText) => {
|
|
24
29
|
const iv = crypto.randomBytes(16)
|
|
25
|
-
const cipher = crypto.createCipheriv(algorithm,
|
|
30
|
+
const cipher = crypto.createCipheriv(algorithm, getKey(), iv)
|
|
26
31
|
const encryptedText = cipher.update(plainText, "utf8", "hex") +
|
|
27
32
|
cipher.final("hex")
|
|
28
33
|
const encryptedPackage = pack(iv, encryptedText)
|
|
@@ -31,7 +36,7 @@ export const encrypt = (plainText) => {
|
|
|
31
36
|
|
|
32
37
|
export const decrypt = (encryptedPackage) => {
|
|
33
38
|
const [iv, encryptedText] = unpack(encryptedPackage)
|
|
34
|
-
const decipher = crypto.createDecipheriv(algorithm,
|
|
39
|
+
const decipher = crypto.createDecipheriv(algorithm, getKey(), iv)
|
|
35
40
|
const decryptedText = decipher.update(encryptedText, "hex", "utf8") +
|
|
36
41
|
decipher.final("utf8")
|
|
37
42
|
return decryptedText
|
package/src/libs/presign.ts
CHANGED
|
@@ -82,7 +82,7 @@ export async function getPresignedUrl(
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Bulk-resolve presigned URLs for many files in
|
|
85
|
+
* Bulk-resolve presigned URLs for many files in pipelined Redis calls.
|
|
86
86
|
* Returns an array of resolved URLs matching the input order.
|
|
87
87
|
*/
|
|
88
88
|
export async function getPresignedUrlsBulk(
|
|
@@ -96,29 +96,41 @@ export async function getPresignedUrlsBulk(
|
|
|
96
96
|
bucket: resolveBucket(item.bucket),
|
|
97
97
|
}))
|
|
98
98
|
const keys = resolved.map((r) => presignKey(r.bucket, r.s3Key, r.versionId))
|
|
99
|
-
const cached = await redis.mget(...keys)
|
|
100
99
|
|
|
101
|
-
// Fill hits from cache, sign misses and queue them for write-back
|
|
102
100
|
const hmac = await getHMAC()
|
|
103
101
|
const expires = Math.floor(Date.now() / 1000) + PRESIGN_EXPIRATION
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
const results: string[] = []
|
|
103
|
+
const CHUNK_SIZE = 1000
|
|
104
|
+
|
|
105
|
+
// Process in chunks to avoid blocking Redis
|
|
106
|
+
for (let i = 0; i < keys.length; i += CHUNK_SIZE) {
|
|
107
|
+
const chunkKeys = keys.slice(i, i + CHUNK_SIZE)
|
|
108
|
+
const chunkResolved = resolved.slice(i, i + CHUNK_SIZE)
|
|
109
|
+
const cached = await redis.mget(chunkKeys)
|
|
110
|
+
const writePipeline = redis.pipeline()
|
|
111
|
+
let misses = 0
|
|
112
|
+
|
|
113
|
+
for (let j = 0; j < chunkKeys.length; j++) {
|
|
114
|
+
let val = cached[j]
|
|
115
|
+
if (!val) {
|
|
116
|
+
misses++
|
|
117
|
+
val = presignV2(
|
|
118
|
+
hmac,
|
|
119
|
+
chunkResolved[j].bucket,
|
|
120
|
+
chunkResolved[j].s3Key,
|
|
121
|
+
chunkResolved[j].versionId,
|
|
122
|
+
expires,
|
|
123
|
+
)
|
|
124
|
+
writePipeline.setex(chunkKeys[j], PRESIGN_TTL, val)
|
|
125
|
+
}
|
|
126
|
+
results.push(val)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (misses > 0) {
|
|
130
|
+
await writePipeline.exec()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
120
133
|
|
|
121
|
-
if (misses > 0) await writePipeline.exec()
|
|
122
134
|
return results
|
|
123
135
|
}
|
|
124
136
|
|
package/src/libs/redis.ts
CHANGED
|
@@ -5,5 +5,15 @@ import Redis from "ioredis"
|
|
|
5
5
|
import Redlock from "redlock"
|
|
6
6
|
import config from "../config"
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let _redis: Redis | null = null
|
|
9
|
+
let _redlock: Redlock | null = null
|
|
10
|
+
|
|
11
|
+
export function getRedis(): Redis {
|
|
12
|
+
if (!_redis) _redis = new Redis(config.redis)
|
|
13
|
+
return _redis
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getRedlock(): Redlock {
|
|
17
|
+
if (!_redlock) _redlock = new Redlock([getRedis()])
|
|
18
|
+
return _redlock
|
|
19
|
+
}
|
package/src/models/comment.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import mongoose from "mongoose"
|
|
2
2
|
import type { Document } from "mongoose"
|
|
3
|
+
import type { UserDocument } from "./user"
|
|
3
4
|
const { Schema, model } = mongoose
|
|
4
5
|
|
|
5
6
|
export interface CommentDocument extends Document {
|
|
6
|
-
_id: string
|
|
7
7
|
createDate: Date
|
|
8
8
|
datasetId: string
|
|
9
9
|
datasetLabel: string
|
|
10
|
-
user:
|
|
11
|
-
_id: string
|
|
12
|
-
}
|
|
10
|
+
user: UserDocument
|
|
13
11
|
parentId: string
|
|
14
12
|
text: string
|
|
15
13
|
}
|
package/src/models/counter.ts
CHANGED
|
@@ -2,12 +2,12 @@ import mongoose from "mongoose"
|
|
|
2
2
|
import type { Document } from "mongoose"
|
|
3
3
|
const { Schema, model } = mongoose
|
|
4
4
|
|
|
5
|
-
export interface CounterDocument extends Document {
|
|
5
|
+
export interface CounterDocument extends Omit<Document, "_id"> {
|
|
6
6
|
_id: string
|
|
7
7
|
sequence_value: number
|
|
8
8
|
}
|
|
9
9
|
const countersSchema = new Schema({
|
|
10
|
-
_id: { type: String },
|
|
10
|
+
_id: { type: String, required: true },
|
|
11
11
|
sequence_value: { type: Number, default: 0 },
|
|
12
12
|
})
|
|
13
13
|
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Model for ingest of new datasets from a remote URL (zip/tarball)
|
|
3
3
|
*/
|
|
4
4
|
import mongoose from "mongoose"
|
|
5
|
-
import type { Document
|
|
5
|
+
import type { Document } from "mongoose"
|
|
6
6
|
const { Schema, model } = mongoose
|
|
7
7
|
import { validateUrl } from "../utils/validateUrl"
|
|
8
8
|
|
|
9
9
|
export interface IngestDatasetDocument extends Document {
|
|
10
|
-
_id: ObjectId
|
|
11
10
|
datasetId: string
|
|
12
11
|
userId: string
|
|
13
12
|
url: string
|
|
@@ -3,7 +3,6 @@ import type { Document } from "mongoose"
|
|
|
3
3
|
const { Schema, model } = mongoose
|
|
4
4
|
|
|
5
5
|
export interface NotificationDocument extends Document {
|
|
6
|
-
_id: string
|
|
7
6
|
type: string
|
|
8
7
|
email: {
|
|
9
8
|
to: string
|
|
@@ -38,7 +37,6 @@ export interface NotificationDocument extends Document {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
const notificationSchema = new Schema({
|
|
41
|
-
_id: String,
|
|
42
40
|
type: String,
|
|
43
41
|
email: {
|
|
44
42
|
to: String,
|
package/src/models/user.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { Document } from "mongoose"
|
|
|
4
4
|
const { Schema, model } = mongoose
|
|
5
5
|
|
|
6
6
|
export interface UserDocument extends Document {
|
|
7
|
-
_id: string
|
|
8
7
|
// OpenNeuro specific user uuid
|
|
9
8
|
id: string
|
|
10
9
|
// Best contact email for the user (notifications)
|
|
@@ -12,7 +11,7 @@ export interface UserDocument extends Document {
|
|
|
12
11
|
// User's preferred name (visible)
|
|
13
12
|
name: string
|
|
14
13
|
// Login provider
|
|
15
|
-
provider:
|
|
14
|
+
provider: string
|
|
16
15
|
// The id from the login provider
|
|
17
16
|
providerId: string
|
|
18
17
|
// ORCID iD associated with this OpenNeuro user
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { snapshotCreationComparison } from "../snapshots"
|
|
2
|
+
|
|
3
|
+
describe("snapshotCreationComparison()", () => {
|
|
4
|
+
it('sorts array of objects by the "created" and "tag" properties', () => {
|
|
5
|
+
const testArray = [
|
|
6
|
+
{ id: 2, created: new Date("2018-11-20T00:05:43.473Z"), tag: "1.0.0" },
|
|
7
|
+
{ id: 1, created: new Date("2018-11-19T00:05:43.473Z"), tag: "1.0.1" },
|
|
8
|
+
{ id: 3, created: new Date("2018-11-23T00:05:43.473Z"), tag: "1.0.2" },
|
|
9
|
+
{ id: 5, created: new Date("2018-11-23T00:05:43.473Z"), tag: "1.0.10" },
|
|
10
|
+
{ id: 4, created: new Date("2018-11-23T00:05:43.473Z"), tag: "1.0.3" },
|
|
11
|
+
]
|
|
12
|
+
const sorted = testArray.sort(snapshotCreationComparison)
|
|
13
|
+
expect(sorted[0].id).toBe(2)
|
|
14
|
+
expect(sorted[1].id).toBe(1)
|
|
15
|
+
expect(sorted[2].id).toBe(3)
|
|
16
|
+
expect(sorted[3].id).toBe(4)
|
|
17
|
+
expect(sorted[4].id).toBe(5)
|
|
18
|
+
})
|
|
19
|
+
it('sorts array of objects by the "created" property as strings', () => {
|
|
20
|
+
const testArray = [
|
|
21
|
+
{ id: 2, created: "2018-11-20T00:05:43.473Z", tag: "2.0.0" },
|
|
22
|
+
{ id: 1, created: "2018-11-19T00:05:43.473Z", tag: "1.0.0" },
|
|
23
|
+
{ id: 3, created: "2018-11-23T00:05:43.473Z", tag: "3.0.0" },
|
|
24
|
+
]
|
|
25
|
+
const sorted = testArray.sort(snapshotCreationComparison)
|
|
26
|
+
expect(sorted[0].id).toBe(1)
|
|
27
|
+
expect(sorted[1].id).toBe(2)
|
|
28
|
+
expect(sorted[2].id).toBe(3)
|
|
29
|
+
})
|
|
30
|
+
it("sorts non-semver tags mixed with semver tags", () => {
|
|
31
|
+
const testArray = [
|
|
32
|
+
{ id: 2, created: new Date("2018-11-19T00:05:43.473Z"), tag: "1.0.2" },
|
|
33
|
+
{
|
|
34
|
+
id: 1,
|
|
35
|
+
created: new Date("2018-11-19T00:05:43.473Z"),
|
|
36
|
+
tag: "57fed018cce88d000ac1757f",
|
|
37
|
+
},
|
|
38
|
+
{ id: 3, created: new Date("2018-11-19T00:05:43.473Z"), tag: "1.0.1" },
|
|
39
|
+
]
|
|
40
|
+
const sorted = testArray.sort(snapshotCreationComparison)
|
|
41
|
+
expect(sorted[0].id).toBe(2)
|
|
42
|
+
expect(sorted[1].id).toBe(1)
|
|
43
|
+
expect(sorted[2].id).toBe(3)
|
|
44
|
+
})
|
|
45
|
+
it("sorts snapshots with only non-semver tags", () => {
|
|
46
|
+
const testArray = [
|
|
47
|
+
{
|
|
48
|
+
id: 2,
|
|
49
|
+
created: new Date("2018-11-19T00:05:43.473Z"),
|
|
50
|
+
tag: "00001",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 1,
|
|
54
|
+
created: new Date("2018-11-19T00:05:43.473Z"),
|
|
55
|
+
tag: "57fed018cce88d000ac1757f",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 3,
|
|
59
|
+
created: new Date("2018-11-19T00:05:43.473Z"),
|
|
60
|
+
tag: "57fed018cce88d000ac1757f",
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
const sorted = testArray.sort(snapshotCreationComparison)
|
|
64
|
+
expect(sorted[0].id).toBe(2)
|
|
65
|
+
expect(sorted[1].id).toBe(1)
|
|
66
|
+
expect(sorted[2].id).toBe(3)
|
|
67
|
+
})
|
|
68
|
+
it("sorts very similar creation times by semver order", () => {
|
|
69
|
+
const testSnapshots = [
|
|
70
|
+
{
|
|
71
|
+
id: "ds002680:1.0.0",
|
|
72
|
+
created: "2020-04-03T23:19:56.000Z",
|
|
73
|
+
tag: "1.0.0",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "ds002680:1.2.0",
|
|
77
|
+
created: "2021-10-19T16:26:43.000Z",
|
|
78
|
+
tag: "1.2.0",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "ds002680:1.1.0",
|
|
82
|
+
created: "2021-10-19T16:26:44.000Z",
|
|
83
|
+
tag: "1.1.0",
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
const sorted = testSnapshots.sort(snapshotCreationComparison)
|
|
87
|
+
expect(sorted[0].id).toBe("ds002680:1.0.0")
|
|
88
|
+
expect(sorted[1].id).toBe("ds002680:1.1.0")
|
|
89
|
+
expect(sorted[2].id).toBe("ds002680:1.2.0")
|
|
90
|
+
})
|
|
91
|
+
it("sorts 000002 (legacy snapshots) before 1.0.1 (current format)", () => {
|
|
92
|
+
const testSnapshots = [
|
|
93
|
+
{
|
|
94
|
+
id: "ds000247:00002",
|
|
95
|
+
created: "2018-07-18T02:27:39.000Z",
|
|
96
|
+
tag: "00002",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "ds000247:00001",
|
|
100
|
+
created: "2018-07-18T02:35:37.000Z",
|
|
101
|
+
tag: "00001",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "ds000247:1.0.0",
|
|
105
|
+
created: "2021-07-05T15:58:18.000Z",
|
|
106
|
+
tag: "1.0.0",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "ds000247:1.0.1",
|
|
110
|
+
created: "2021-08-25T23:37:53.000Z",
|
|
111
|
+
tag: "1.0.1",
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
const sorted = testSnapshots.sort(snapshotCreationComparison)
|
|
115
|
+
expect(sorted[0].id).toBe("ds000247:00002")
|
|
116
|
+
expect(sorted[1].id).toBe("ds000247:00001")
|
|
117
|
+
expect(sorted[2].id).toBe("ds000247:1.0.0")
|
|
118
|
+
expect(sorted[3].id).toBe("ds000247:1.0.1")
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import semver from "semver"
|
|
2
|
+
|
|
3
|
+
export const snapshotCreationComparison = (
|
|
4
|
+
{ created: a, tag: a_tag }: { created: Date | string; tag: string },
|
|
5
|
+
{ created: b, tag: b_tag }: { created: Date | string; tag: string },
|
|
6
|
+
) => {
|
|
7
|
+
if (semver.valid(a_tag) && semver.valid(b_tag)) {
|
|
8
|
+
return semver.compare(a_tag, b_tag)
|
|
9
|
+
} else {
|
|
10
|
+
return new Date(a).getTime() - new Date(b).getTime()
|
|
11
|
+
}
|
|
12
|
+
}
|