@heliosgraphics/epoque 0.0.1-alpha.2 → 0.0.1-alpha.4
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 +1 -1
- package/src/cli.ts +49 -4
- package/src/sync.ts +28 -6
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
import { createInterface } from "node:readline/promises"
|
|
4
4
|
import { stdin as input, stdout as output } from "node:process"
|
|
5
|
+
import { resolve } from "node:path"
|
|
5
6
|
import { EpoqueApi } from "./api"
|
|
6
7
|
import { getApiKey, normalizeOrigin, readGlobalConfig, writeGlobalConfig } from "./config"
|
|
7
8
|
import { syncFolder } from "./sync"
|
|
8
9
|
|
|
9
10
|
const HELP = `Usage:
|
|
10
11
|
epoque login
|
|
11
|
-
epoque sync [--dry-run]
|
|
12
|
+
epoque sync [folder] [--dry-run] [--collection id]
|
|
12
13
|
`
|
|
13
14
|
|
|
15
|
+
interface SyncArgs {
|
|
16
|
+
cwd: string
|
|
17
|
+
dryRun: boolean
|
|
18
|
+
collectionId?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
const promptLine = async (question: string): Promise<string> => {
|
|
15
22
|
const readline = createInterface({ input, output })
|
|
16
23
|
|
|
@@ -68,7 +75,44 @@ const promptSecret = async (question: string): Promise<string> => {
|
|
|
68
75
|
})
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
const
|
|
78
|
+
const parseSyncArgs = (args: Array<string>): SyncArgs => {
|
|
79
|
+
let target: string | undefined
|
|
80
|
+
let collectionId: string | undefined
|
|
81
|
+
let dryRun: boolean = false
|
|
82
|
+
|
|
83
|
+
for (let index = 0; index < args.length; index++) {
|
|
84
|
+
const arg = args[index]
|
|
85
|
+
|
|
86
|
+
if (arg === "--dry-run") {
|
|
87
|
+
dryRun = true
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (arg === "--collection") {
|
|
92
|
+
collectionId = args[index + 1]?.trim()
|
|
93
|
+
if (!collectionId) throw new Error("--collection requires a collection id")
|
|
94
|
+
index += 1
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (arg.startsWith("--collection=")) {
|
|
99
|
+
collectionId = arg.slice("--collection=".length).trim()
|
|
100
|
+
if (!collectionId) throw new Error("--collection requires a collection id")
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (arg.startsWith("--")) throw new Error(`unknown option: ${arg}`)
|
|
105
|
+
|
|
106
|
+
if (target) throw new Error(`unexpected argument: ${arg}`)
|
|
107
|
+
target = arg
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
collectionId,
|
|
112
|
+
cwd: target ? resolve(process.cwd(), target) : process.cwd(),
|
|
113
|
+
dryRun,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
72
116
|
|
|
73
117
|
const login = async (): Promise<void> => {
|
|
74
118
|
const origin = normalizeOrigin(undefined)
|
|
@@ -85,7 +129,7 @@ const login = async (): Promise<void> => {
|
|
|
85
129
|
}
|
|
86
130
|
|
|
87
131
|
const sync = async (args: Array<string>): Promise<void> => {
|
|
88
|
-
const cwd =
|
|
132
|
+
const { collectionId, cwd, dryRun } = parseSyncArgs(args)
|
|
89
133
|
const origin = normalizeOrigin(undefined)
|
|
90
134
|
const apiKey = await getApiKey()
|
|
91
135
|
|
|
@@ -93,8 +137,9 @@ const sync = async (args: Array<string>): Promise<void> => {
|
|
|
93
137
|
|
|
94
138
|
const summary = await syncFolder({
|
|
95
139
|
api: new EpoqueApi({ apiKey, origin }),
|
|
140
|
+
collectionId,
|
|
96
141
|
cwd,
|
|
97
|
-
dryRun
|
|
142
|
+
dryRun,
|
|
98
143
|
origin,
|
|
99
144
|
prompt: promptLine,
|
|
100
145
|
})
|
package/src/sync.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface SyncOptions {
|
|
|
10
10
|
cwd: string
|
|
11
11
|
origin: string
|
|
12
12
|
api: EpoqueApi
|
|
13
|
+
collectionId?: string
|
|
13
14
|
dryRun: boolean
|
|
14
15
|
prompt: Prompt
|
|
15
16
|
}
|
|
@@ -35,10 +36,25 @@ const EMPTY_SUMMARY: SyncSummary = {
|
|
|
35
36
|
const isUuid = (value: string): boolean =>
|
|
36
37
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
|
|
37
38
|
|
|
39
|
+
const TIMEZONE_PATTERN: RegExp = /[zZ]|[+-]\d{2}:?\d{2}$/
|
|
40
|
+
const TIME_PATTERN: RegExp = /T|\d{2}:\d{2}/
|
|
41
|
+
|
|
42
|
+
const parseRemoteTime = (value: string | null | undefined): number | null => {
|
|
43
|
+
const trimmed = value?.trim()
|
|
44
|
+
if (!trimmed) return null
|
|
45
|
+
|
|
46
|
+
const normalized = TIME_PATTERN.test(trimmed) ? trimmed.replace(" ", "T") : `${trimmed}T00:00:00`
|
|
47
|
+
const withTimezone = TIMEZONE_PATTERN.test(normalized) ? normalized : `${normalized}Z`
|
|
48
|
+
const parsed = Date.parse(withTimezone)
|
|
49
|
+
|
|
50
|
+
return Number.isNaN(parsed) ? null : parsed
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
const isAfter = (left: string | null | undefined, right: string | null | undefined): boolean => {
|
|
39
|
-
|
|
54
|
+
const leftTime = parseRemoteTime(left)
|
|
55
|
+
const rightTime = parseRemoteTime(right)
|
|
40
56
|
|
|
41
|
-
return
|
|
57
|
+
return leftTime !== null && rightTime !== null && leftTime > rightTime
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
const getCollectionSearchTerms = (input: string): Array<string> => {
|
|
@@ -82,12 +98,12 @@ const resolveCollectionId = async ({ api, prompt }: { api: EpoqueApi; prompt: Pr
|
|
|
82
98
|
return collection.id
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
const ensureProjectConfig = async ({ api, cwd, dryRun, origin, prompt }: SyncOptions): Promise<ProjectConfig> => {
|
|
101
|
+
const ensureProjectConfig = async ({ api, collectionId, cwd, dryRun, origin, prompt }: SyncOptions): Promise<ProjectConfig> => {
|
|
86
102
|
const existing = await readProjectConfig(cwd)
|
|
87
|
-
if (existing) return { ...existing, origin }
|
|
103
|
+
if (existing && (!collectionId || collectionId === existing.collectionId)) return { ...existing, origin }
|
|
88
104
|
|
|
89
|
-
const
|
|
90
|
-
const config = createProjectConfig({ collectionId, origin })
|
|
105
|
+
const resolvedCollectionId = collectionId ?? (await resolveCollectionId({ api, prompt }))
|
|
106
|
+
const config = createProjectConfig({ collectionId: resolvedCollectionId, origin })
|
|
91
107
|
|
|
92
108
|
if (!dryRun) await writeProjectConfig(cwd, config)
|
|
93
109
|
|
|
@@ -286,6 +302,12 @@ export const syncFolder = async (options: SyncOptions): Promise<SyncSummary> =>
|
|
|
286
302
|
let articleId = localArticle.id
|
|
287
303
|
let remoteArticle = articleId ? remoteById.get(articleId) : undefined
|
|
288
304
|
|
|
305
|
+
if (articleId && !remoteArticle) {
|
|
306
|
+
delete config.articles[articleId]
|
|
307
|
+
articleId = null
|
|
308
|
+
if (!options.dryRun) localArticle.id = null
|
|
309
|
+
}
|
|
310
|
+
|
|
289
311
|
if (!articleId) {
|
|
290
312
|
summary.createdArticles += 1
|
|
291
313
|
|