@aopslab/domain-cli-docman 0.1.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.
@@ -0,0 +1,84 @@
1
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import { createRequire } from 'node:module'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const SQLITE_TABLE_SENTINEL = 'docman_documents'
7
+ const cliRoot = fileURLToPath(new URL('../', import.meta.url))
8
+ const sqliteBootstrapSqlPath = resolve(cliRoot, 'resources', 'sqlite-bootstrap.sql')
9
+ const nodeRequire = createRequire(import.meta.url)
10
+
11
+ function requireNodeSqlite(): typeof import('node:sqlite') {
12
+ return nodeRequire('node:sqlite') as typeof import('node:sqlite')
13
+ }
14
+
15
+ function isSqliteRepoUrl(repoUrl: string): boolean {
16
+ const normalized = repoUrl.trim().toLowerCase()
17
+ if (!normalized) return false
18
+ if (normalized === ':memory:') return true
19
+ if (normalized.startsWith('sqlite:') || normalized.startsWith('file:')) return true
20
+ return normalized.endsWith('.db') || normalized.endsWith('.sqlite') || normalized.endsWith('.sqlite3')
21
+ }
22
+
23
+ function resolveSqliteFilename(repoUrl: string): string {
24
+ const trimmed = repoUrl.trim()
25
+ if (trimmed === ':memory:') return ':memory:'
26
+
27
+ const stripScheme = (value: string, scheme: string): string =>
28
+ value.startsWith(scheme) ? value.slice(scheme.length).replace(/^\/\//, '') : value
29
+
30
+ const noSqlite = stripScheme(trimmed, 'sqlite:')
31
+ const noFile = stripScheme(noSqlite, 'file:')
32
+ return noFile || trimmed
33
+ }
34
+
35
+ function hasDocmanSchema(filename: string): boolean {
36
+ const { DatabaseSync } = requireNodeSqlite()
37
+ const db = new DatabaseSync(filename)
38
+ try {
39
+ const found = db
40
+ .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='${SQLITE_TABLE_SENTINEL}'`)
41
+ .get() as { name?: string } | undefined
42
+ return found?.name === SQLITE_TABLE_SENTINEL
43
+ } finally {
44
+ db.close()
45
+ }
46
+ }
47
+
48
+ function applyBootstrapSql(filename: string): void {
49
+ if (!existsSync(sqliteBootstrapSqlPath)) {
50
+ throw new Error(`missing_sqlite_bootstrap_sql:${sqliteBootstrapSqlPath}`)
51
+ }
52
+
53
+ const { DatabaseSync } = requireNodeSqlite()
54
+ const sql = readFileSync(sqliteBootstrapSqlPath, 'utf8')
55
+ const db = new DatabaseSync(filename)
56
+ try {
57
+ db.exec(sql)
58
+ } finally {
59
+ db.close()
60
+ }
61
+ }
62
+
63
+ export function ensureDocmanSqliteSchemaReady(repoUrlRaw: string | undefined): void {
64
+ const repoUrl = String(repoUrlRaw ?? '').trim()
65
+ if (!repoUrl || !isSqliteRepoUrl(repoUrl)) return
66
+
67
+ const filename = resolveSqliteFilename(repoUrl)
68
+ if (filename === ':memory:') return
69
+
70
+ mkdirSync(dirname(filename), { recursive: true })
71
+ if (!existsSync(filename)) {
72
+ const fd = openSync(filename, 'a')
73
+ closeSync(fd)
74
+ }
75
+
76
+ if (hasDocmanSchema(filename)) return
77
+
78
+ try {
79
+ applyBootstrapSql(filename)
80
+ } catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error)
82
+ throw new Error(`sqlite_schema_bootstrap_failed:${message}`)
83
+ }
84
+ }