@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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/cli.ts +49 -4
  3. package/src/sync.ts +28 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heliosgraphics/epoque",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.4",
4
4
  "author": "Chris Puska <chris@puska.org>",
5
5
  "description": "sync a local folder",
6
6
  "type": "module",
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 hasFlag = (args: Array<string>, name: string): boolean => args.includes(name)
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 = process.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: hasFlag(args, "--dry-run"),
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
- if (!left || !right) return false
54
+ const leftTime = parseRemoteTime(left)
55
+ const rightTime = parseRemoteTime(right)
40
56
 
41
- return new Date(left).getTime() > new Date(right).getTime()
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 collectionId = await resolveCollectionId({ api, prompt })
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