datool 0.0.1
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/README.md +218 -0
- package/client-dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/client-dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/client-dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/client-dist/assets/index-BeRNeRUq.css +1 -0
- package/client-dist/assets/index-uoZ4c_I8.js +164 -0
- package/client-dist/index.html +13 -0
- package/index.html +12 -0
- package/package.json +55 -0
- package/src/client/App.tsx +885 -0
- package/src/client/components/connection-status.tsx +43 -0
- package/src/client/components/data-table-cell.tsx +235 -0
- package/src/client/components/data-table-col-icon.tsx +73 -0
- package/src/client/components/data-table-header-col.tsx +225 -0
- package/src/client/components/data-table-search-input.tsx +729 -0
- package/src/client/components/data-table.tsx +2014 -0
- package/src/client/components/stream-controls.tsx +157 -0
- package/src/client/components/theme-provider.tsx +230 -0
- package/src/client/components/ui/button.tsx +68 -0
- package/src/client/components/ui/combobox.tsx +308 -0
- package/src/client/components/ui/context-menu.tsx +261 -0
- package/src/client/components/ui/dropdown-menu.tsx +267 -0
- package/src/client/components/ui/input-group.tsx +153 -0
- package/src/client/components/ui/input.tsx +19 -0
- package/src/client/components/ui/textarea.tsx +18 -0
- package/src/client/components/viewer-settings.tsx +185 -0
- package/src/client/index.css +192 -0
- package/src/client/lib/data-table-search.ts +750 -0
- package/src/client/lib/datool-icons.ts +37 -0
- package/src/client/lib/datool-url-state.ts +159 -0
- package/src/client/lib/filterable-table.ts +146 -0
- package/src/client/lib/table-search-persistence.ts +94 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.tsx +14 -0
- package/src/index.ts +19 -0
- package/src/node/cli.ts +54 -0
- package/src/node/config.ts +231 -0
- package/src/node/lines.ts +82 -0
- package/src/node/runtime.ts +102 -0
- package/src/node/server.ts +403 -0
- package/src/node/sources/command.ts +82 -0
- package/src/node/sources/file.ts +116 -0
- package/src/node/sources/ssh.ts +59 -0
- package/src/shared/columns.ts +41 -0
- package/src/shared/types.ts +188 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Ban,
|
|
3
|
+
Check,
|
|
4
|
+
CircleAlert,
|
|
5
|
+
Copy,
|
|
6
|
+
Download,
|
|
7
|
+
ExternalLink,
|
|
8
|
+
Filter,
|
|
9
|
+
Info,
|
|
10
|
+
Play,
|
|
11
|
+
RefreshCcw,
|
|
12
|
+
Search,
|
|
13
|
+
Trash2,
|
|
14
|
+
X,
|
|
15
|
+
} from "lucide-react"
|
|
16
|
+
import type { ComponentType } from "react"
|
|
17
|
+
|
|
18
|
+
import type { DatoolIconName } from "../../shared/types"
|
|
19
|
+
|
|
20
|
+
export const LOG_VIEWER_ICONS: Record<
|
|
21
|
+
DatoolIconName,
|
|
22
|
+
ComponentType<{ className?: string }>
|
|
23
|
+
> = {
|
|
24
|
+
Ban,
|
|
25
|
+
Check,
|
|
26
|
+
CircleAlert,
|
|
27
|
+
Copy,
|
|
28
|
+
Download,
|
|
29
|
+
ExternalLink,
|
|
30
|
+
Filter,
|
|
31
|
+
Info,
|
|
32
|
+
Play,
|
|
33
|
+
RefreshCcw,
|
|
34
|
+
Search,
|
|
35
|
+
Trash: Trash2,
|
|
36
|
+
X,
|
|
37
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { VisibilityState } from "@tanstack/react-table"
|
|
2
|
+
|
|
3
|
+
type PersistedTableState = {
|
|
4
|
+
columnFilters?: unknown[]
|
|
5
|
+
columnSizing?: Record<string, number>
|
|
6
|
+
columnVisibility?: VisibilityState
|
|
7
|
+
globalFilter?: string
|
|
8
|
+
highlightedColumns?: Record<string, boolean>
|
|
9
|
+
sorting?: unknown[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STREAM_PARAM = "stream"
|
|
13
|
+
const DATA_TABLE_URL_PARAM_PREFIX = "datatable-"
|
|
14
|
+
const LOG_VIEWER_TABLE_ID_PREFIX = "datool"
|
|
15
|
+
|
|
16
|
+
function getSearchUrlParam(tableId: string) {
|
|
17
|
+
return `${tableId}-search`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getTableUrlParam(tableId: string) {
|
|
21
|
+
return `${DATA_TABLE_URL_PARAM_PREFIX}${tableId}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isDatoolUrlParam(key: string) {
|
|
25
|
+
return (
|
|
26
|
+
key.startsWith(`${DATA_TABLE_URL_PARAM_PREFIX}${LOG_VIEWER_TABLE_ID_PREFIX}`) ||
|
|
27
|
+
(key.startsWith(`${LOG_VIEWER_TABLE_ID_PREFIX}-`) && key.endsWith("-search"))
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readPersistedTableState(tableId: string) {
|
|
32
|
+
if (typeof window === "undefined") {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const rawValue = new URL(window.location.href).searchParams.get(
|
|
38
|
+
getTableUrlParam(tableId)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return rawValue ? (JSON.parse(rawValue) as PersistedTableState) : null
|
|
42
|
+
} catch {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sanitizeColumnVisibility(
|
|
48
|
+
columnVisibility: VisibilityState | undefined,
|
|
49
|
+
columnIds: string[]
|
|
50
|
+
) {
|
|
51
|
+
const validIds = new Set(columnIds)
|
|
52
|
+
|
|
53
|
+
return Object.fromEntries(
|
|
54
|
+
Object.entries(columnVisibility ?? {}).filter(([columnId]) =>
|
|
55
|
+
validIds.has(columnId)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cleanUpDatoolParams(url: URL, tableId: string) {
|
|
61
|
+
const activeSearchParam = getSearchUrlParam(tableId)
|
|
62
|
+
const activeTableParam = getTableUrlParam(tableId)
|
|
63
|
+
|
|
64
|
+
for (const key of Array.from(url.searchParams.keys())) {
|
|
65
|
+
if (
|
|
66
|
+
key === STREAM_PARAM ||
|
|
67
|
+
key === activeSearchParam ||
|
|
68
|
+
key === activeTableParam
|
|
69
|
+
) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isDatoolUrlParam(key)) {
|
|
74
|
+
url.searchParams.delete(key)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function readSelectedStreamId() {
|
|
80
|
+
if (typeof window === "undefined") {
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return new URL(window.location.href).searchParams.get(STREAM_PARAM)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function readDatoolSearch(tableId: string) {
|
|
88
|
+
if (typeof window === "undefined") {
|
|
89
|
+
return ""
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new URL(window.location.href).searchParams.get(getSearchUrlParam(tableId)) ?? ""
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function readDatoolColumnVisibility(
|
|
96
|
+
tableId: string,
|
|
97
|
+
columnIds: string[]
|
|
98
|
+
) {
|
|
99
|
+
return sanitizeColumnVisibility(
|
|
100
|
+
readPersistedTableState(tableId)?.columnVisibility,
|
|
101
|
+
columnIds
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function writeDatoolUrlState({
|
|
106
|
+
columnIds,
|
|
107
|
+
columnVisibility,
|
|
108
|
+
search,
|
|
109
|
+
selectedStreamId,
|
|
110
|
+
tableId,
|
|
111
|
+
}: {
|
|
112
|
+
columnIds: string[]
|
|
113
|
+
columnVisibility: VisibilityState
|
|
114
|
+
search: string
|
|
115
|
+
selectedStreamId: string | null
|
|
116
|
+
tableId: string
|
|
117
|
+
}) {
|
|
118
|
+
if (typeof window === "undefined") {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const url = new URL(window.location.href)
|
|
123
|
+
const searchValue = search.trim()
|
|
124
|
+
const nextColumnVisibility = sanitizeColumnVisibility(
|
|
125
|
+
columnVisibility,
|
|
126
|
+
columnIds
|
|
127
|
+
)
|
|
128
|
+
const nextTableState = {
|
|
129
|
+
...readPersistedTableState(tableId),
|
|
130
|
+
} satisfies PersistedTableState
|
|
131
|
+
|
|
132
|
+
cleanUpDatoolParams(url, tableId)
|
|
133
|
+
|
|
134
|
+
if (selectedStreamId) {
|
|
135
|
+
url.searchParams.set(STREAM_PARAM, selectedStreamId)
|
|
136
|
+
} else {
|
|
137
|
+
url.searchParams.delete(STREAM_PARAM)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (searchValue) {
|
|
141
|
+
url.searchParams.set(getSearchUrlParam(tableId), search)
|
|
142
|
+
} else {
|
|
143
|
+
url.searchParams.delete(getSearchUrlParam(tableId))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (Object.keys(nextColumnVisibility).length > 0) {
|
|
147
|
+
nextTableState.columnVisibility = nextColumnVisibility
|
|
148
|
+
} else {
|
|
149
|
+
delete nextTableState.columnVisibility
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (Object.keys(nextTableState).length > 0) {
|
|
153
|
+
url.searchParams.set(getTableUrlParam(tableId), JSON.stringify(nextTableState))
|
|
154
|
+
} else {
|
|
155
|
+
url.searchParams.delete(getTableUrlParam(tableId))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
window.history.replaceState(window.history.state, "", url)
|
|
159
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { FilterFn } from "@tanstack/react-table"
|
|
2
|
+
|
|
3
|
+
import { type DataTableColumnConfig } from "@/components/data-table"
|
|
4
|
+
import {
|
|
5
|
+
buildEnumOptions,
|
|
6
|
+
matchesFieldClauses,
|
|
7
|
+
type DataTableSearchField,
|
|
8
|
+
type DataTableSearchFieldKind,
|
|
9
|
+
type DataTableSearchFilterClause,
|
|
10
|
+
} from "@/lib/data-table-search"
|
|
11
|
+
|
|
12
|
+
type TableRow = Record<string, unknown>
|
|
13
|
+
|
|
14
|
+
export type BuildTableSearchFieldsOptions = {
|
|
15
|
+
fieldOptions?: Partial<Record<string, string[]>>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stringifySampleValue(value: unknown) {
|
|
19
|
+
if (value === null || value === undefined) {
|
|
20
|
+
return new Date().toISOString()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (value instanceof Date) {
|
|
24
|
+
return value.toISOString()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof value === "object") {
|
|
28
|
+
return JSON.stringify(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return String(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getColumnValueGetter<TData extends TableRow>(
|
|
35
|
+
column: DataTableColumnConfig<TData>
|
|
36
|
+
) {
|
|
37
|
+
return (row: TData) => {
|
|
38
|
+
if (column.accessorFn) {
|
|
39
|
+
return column.accessorFn(row)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (column.accessorKey) {
|
|
43
|
+
return row[column.accessorKey]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDefaultFieldKind<TData extends TableRow>(
|
|
51
|
+
column: DataTableColumnConfig<TData>
|
|
52
|
+
): DataTableSearchFieldKind {
|
|
53
|
+
switch (column.kind) {
|
|
54
|
+
case "date":
|
|
55
|
+
return "date"
|
|
56
|
+
case "enum":
|
|
57
|
+
return "enum"
|
|
58
|
+
case "json":
|
|
59
|
+
return "json"
|
|
60
|
+
case "number":
|
|
61
|
+
return "number"
|
|
62
|
+
default:
|
|
63
|
+
return "text"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveTableColumnId<TData extends TableRow>(
|
|
68
|
+
column: DataTableColumnConfig<TData>,
|
|
69
|
+
index: number
|
|
70
|
+
) {
|
|
71
|
+
return (
|
|
72
|
+
column.id ??
|
|
73
|
+
column.accessorKey ??
|
|
74
|
+
(column.header
|
|
75
|
+
? column.header.toLowerCase().replace(/\s+/g, "-")
|
|
76
|
+
: `column-${index}`)
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildTableSearchFields<TData extends TableRow>(
|
|
81
|
+
columns: DataTableColumnConfig<TData>[],
|
|
82
|
+
rows: TData[],
|
|
83
|
+
options: BuildTableSearchFieldsOptions = {}
|
|
84
|
+
) {
|
|
85
|
+
const latestRow = rows.at(-1)
|
|
86
|
+
|
|
87
|
+
return columns.map<DataTableSearchField<TData>>((column, index) => {
|
|
88
|
+
const columnId = resolveTableColumnId(column, index)
|
|
89
|
+
const getValue = getColumnValueGetter(column)
|
|
90
|
+
const kind = getDefaultFieldKind(column)
|
|
91
|
+
|
|
92
|
+
if (kind === "enum") {
|
|
93
|
+
return {
|
|
94
|
+
getValue,
|
|
95
|
+
id: columnId,
|
|
96
|
+
kind,
|
|
97
|
+
options:
|
|
98
|
+
options.fieldOptions?.[columnId] ?? buildEnumOptions(rows, getValue),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (kind === "date") {
|
|
103
|
+
return {
|
|
104
|
+
getValue,
|
|
105
|
+
id: columnId,
|
|
106
|
+
kind,
|
|
107
|
+
sample: stringifySampleValue(
|
|
108
|
+
latestRow ? getValue(latestRow) : new Date().toISOString()
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
getValue,
|
|
115
|
+
id: columnId,
|
|
116
|
+
kind,
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function withColumnSearchFilters<TData extends TableRow>(
|
|
122
|
+
columns: DataTableColumnConfig<TData>[],
|
|
123
|
+
fields: DataTableSearchField<TData>[]
|
|
124
|
+
) {
|
|
125
|
+
const fieldMap = new Map(fields.map((field) => [field.id, field]))
|
|
126
|
+
|
|
127
|
+
return columns.map((column, index) => {
|
|
128
|
+
const columnId = resolveTableColumnId(column, index)
|
|
129
|
+
const field = fieldMap.get(columnId)
|
|
130
|
+
|
|
131
|
+
if (!field || column.filterFn) {
|
|
132
|
+
return column
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...column,
|
|
137
|
+
filterFn: ((row, _columnId, filterValue) => {
|
|
138
|
+
const clauses = Array.isArray(filterValue)
|
|
139
|
+
? (filterValue as DataTableSearchFilterClause[])
|
|
140
|
+
: []
|
|
141
|
+
|
|
142
|
+
return matchesFieldClauses(row.original, field, clauses)
|
|
143
|
+
}) as FilterFn<TData>,
|
|
144
|
+
} satisfies DataTableColumnConfig<TData>
|
|
145
|
+
})
|
|
146
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type SearchStatePersistence = "localStorage" | "url"
|
|
2
|
+
|
|
3
|
+
function getSearchLocalStorageKey(tableId: string) {
|
|
4
|
+
return `${tableId}:search`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function getSearchUrlParam(tableId: string) {
|
|
8
|
+
return `${tableId}-search`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getTableUrlParam(tableId: string) {
|
|
12
|
+
return `datatable-${tableId}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function hasSearchUrlPersistence(tableId: string) {
|
|
16
|
+
if (typeof window === "undefined") {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const params = new URL(window.location.href).searchParams
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
params.has(getSearchUrlParam(tableId)) ||
|
|
24
|
+
params.has(getTableUrlParam(tableId))
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getInitialSearchPersistence(
|
|
29
|
+
tableId: string
|
|
30
|
+
): SearchStatePersistence {
|
|
31
|
+
return hasSearchUrlPersistence(tableId) ? "url" : "localStorage"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readPersistedSearch(
|
|
35
|
+
tableId: string,
|
|
36
|
+
statePersistence: SearchStatePersistence
|
|
37
|
+
) {
|
|
38
|
+
if (typeof window === "undefined") {
|
|
39
|
+
return ""
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (statePersistence === "url") {
|
|
43
|
+
return (
|
|
44
|
+
new URL(window.location.href).searchParams.get(
|
|
45
|
+
getSearchUrlParam(tableId)
|
|
46
|
+
) ?? ""
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return window.localStorage.getItem(getSearchLocalStorageKey(tableId)) ?? ""
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function writePersistedSearch(
|
|
54
|
+
tableId: string,
|
|
55
|
+
statePersistence: SearchStatePersistence,
|
|
56
|
+
value: string
|
|
57
|
+
) {
|
|
58
|
+
if (typeof window === "undefined") {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (statePersistence === "url") {
|
|
63
|
+
const url = new URL(window.location.href)
|
|
64
|
+
|
|
65
|
+
if (value.trim()) {
|
|
66
|
+
url.searchParams.set(getSearchUrlParam(tableId), value)
|
|
67
|
+
} else {
|
|
68
|
+
url.searchParams.delete(getSearchUrlParam(tableId))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
window.history.replaceState(window.history.state, "", url)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (value) {
|
|
76
|
+
window.localStorage.setItem(getSearchLocalStorageKey(tableId), value)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
window.localStorage.removeItem(getSearchLocalStorageKey(tableId))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function clearUrlSearchPersistence(tableId: string) {
|
|
84
|
+
if (typeof window === "undefined") {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const url = new URL(window.location.href)
|
|
89
|
+
|
|
90
|
+
url.searchParams.delete(getSearchUrlParam(tableId))
|
|
91
|
+
url.searchParams.delete(getTableUrlParam(tableId))
|
|
92
|
+
|
|
93
|
+
window.history.replaceState(window.history.state, "", url)
|
|
94
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StrictMode } from "react"
|
|
2
|
+
import { createRoot } from "react-dom/client"
|
|
3
|
+
|
|
4
|
+
import "./index.css"
|
|
5
|
+
import App from "./App.tsx"
|
|
6
|
+
import { ThemeProvider } from "./components/theme-provider.tsx"
|
|
7
|
+
|
|
8
|
+
createRoot(document.getElementById("root")!).render(
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<ThemeProvider storageKey="datool-theme">
|
|
11
|
+
<App />
|
|
12
|
+
</ThemeProvider>
|
|
13
|
+
</StrictMode>
|
|
14
|
+
)
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { commandSource } from "./node/sources/command"
|
|
2
|
+
import { fileSource } from "./node/sources/file"
|
|
3
|
+
import { sshSource } from "./node/sources/ssh"
|
|
4
|
+
import type { DatoolConfig } from "./shared/types"
|
|
5
|
+
|
|
6
|
+
export * from "./shared/types"
|
|
7
|
+
export { startDatoolServer } from "./node/server"
|
|
8
|
+
|
|
9
|
+
export function defineDatoolConfig<TConfig extends DatoolConfig>(
|
|
10
|
+
config: TConfig
|
|
11
|
+
) {
|
|
12
|
+
return config
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const sources = {
|
|
16
|
+
command: commandSource,
|
|
17
|
+
file: fileSource,
|
|
18
|
+
ssh: sshSource,
|
|
19
|
+
}
|
package/src/node/cli.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { startDatoolServer } from "./server"
|
|
3
|
+
|
|
4
|
+
function parseCliArgs(argv: string[]) {
|
|
5
|
+
const parsedArgs: {
|
|
6
|
+
configPath?: string
|
|
7
|
+
host?: string
|
|
8
|
+
port?: number
|
|
9
|
+
} = {}
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
12
|
+
const currentArg = argv[index]
|
|
13
|
+
const nextArg = argv[index + 1]
|
|
14
|
+
|
|
15
|
+
if (currentArg === "--host" && nextArg) {
|
|
16
|
+
parsedArgs.host = nextArg
|
|
17
|
+
index += 1
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (currentArg === "--port" && nextArg) {
|
|
22
|
+
parsedArgs.port = Number.parseInt(nextArg, 10)
|
|
23
|
+
index += 1
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (currentArg === "--config" && nextArg) {
|
|
28
|
+
parsedArgs.configPath = nextArg
|
|
29
|
+
index += 1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return parsedArgs
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const args = parseCliArgs(process.argv.slice(2))
|
|
38
|
+
const server = await startDatoolServer({
|
|
39
|
+
configPath: args.configPath,
|
|
40
|
+
host: args.host,
|
|
41
|
+
port: args.port,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
console.log(`datool ready`)
|
|
45
|
+
console.log(`config: ${server.configPath}`)
|
|
46
|
+
console.log(`url: ${server.url}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch((error) => {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
51
|
+
|
|
52
|
+
console.error(`Failed to start datool: ${message}`)
|
|
53
|
+
process.exit(1)
|
|
54
|
+
})
|