turtb 0.5.3 → 0.5.6
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/bin/turtb.js +164 -47
- package/package.json +1 -1
package/bin/turtb.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { readFileSync } from 'fs'
|
|
4
4
|
import { start } from 'repl'
|
|
5
5
|
import { Option, program } from 'commander'
|
|
6
|
-
import { question, questionNewPassword } from 'readline-sync'
|
|
7
|
-
import { LOG_LEVELS, logError, logInfo,
|
|
6
|
+
import { keyInSelect, keyInYN, question, questionNewPassword } from 'readline-sync'
|
|
7
|
+
import { LOG_LEVELS, logDebug, logError, logInfo, setLogLevel } from '../lib/utils/logger.js'
|
|
8
8
|
import { Signer } from '../lib/turtle/Signer.js'
|
|
9
9
|
import { TurtleDB } from '../lib/turtle/connections/TurtleDB.js'
|
|
10
10
|
import { Recaller } from '../lib/utils/Recaller.js'
|
|
@@ -18,8 +18,11 @@ import { originSync } from '../src/originSync.js'
|
|
|
18
18
|
import { outletSync } from '../src/outletSync.js'
|
|
19
19
|
import { webSync } from '../src/webSync.js'
|
|
20
20
|
import { config } from 'dotenv'
|
|
21
|
+
import { proxyFolder } from '../src/proxyFolder.js'
|
|
22
|
+
import { fileURLToPath } from 'url'
|
|
23
|
+
import { dirname, join } from 'path'
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
const startTime = new Date()
|
|
23
26
|
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)))
|
|
24
27
|
|
|
25
28
|
const defaultWebPort = 8080
|
|
@@ -39,6 +42,28 @@ const makeParserWithOptions = (...options) => value => {
|
|
|
39
42
|
throw new Error(`value must be one of: ${options.join(', ')}`)
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
const optionNameToEnvName = {
|
|
46
|
+
turtlename: 'TURTLEDB_TURTLENAME',
|
|
47
|
+
username: 'TURTLEDB_USERNAME',
|
|
48
|
+
password: 'TURTLEDB_PASSWORD',
|
|
49
|
+
fsMirror: 'TURTLEDB_FS_MIRROR',
|
|
50
|
+
interactive: 'TURTLEDB_INTERACTIVE',
|
|
51
|
+
archive: 'TURTLEDB_ARCHIVE',
|
|
52
|
+
verbose: 'TURTLEDB_VERBOSE',
|
|
53
|
+
turtleDBFolder: 'TURTLEDB_FOLDER',
|
|
54
|
+
webPort: 'TURTLEDB_WEB_PORT',
|
|
55
|
+
webCertpath: 'TURTLEDB_WEB_CERTPATH',
|
|
56
|
+
webInsecure: 'TURTLEDB_WEB_INSECURE',
|
|
57
|
+
remoteHost: 'TURTLEDB_REMOTE_HOST',
|
|
58
|
+
remotePort: 'TURTLEDB_REMOTE_PORT',
|
|
59
|
+
syncPort: 'TURTLEDB_PORT',
|
|
60
|
+
s3EndPoint: 'TURTLEDB_S3_END_POINT',
|
|
61
|
+
s3Region: 'TURTLEDB_S3_REGION',
|
|
62
|
+
s3AccessKeyId: 'TURTLEDB_S3_ACCESS_KEY_ID',
|
|
63
|
+
s3SecretAccessKey: 'TURTLEDB_S3_SECRET_ACCESS_KEY',
|
|
64
|
+
s3Bucket: 'TURTLEDB_S3_BUCKET'
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
program
|
|
43
68
|
.name('turtledb-com')
|
|
44
69
|
.version(version)
|
|
@@ -46,16 +71,16 @@ program
|
|
|
46
71
|
.option('--env-file <path>', 'path to .env file')
|
|
47
72
|
|
|
48
73
|
.addOption(
|
|
49
|
-
new Option('--
|
|
50
|
-
.env(
|
|
74
|
+
new Option('--turtlename <string>', 'name for dataset')
|
|
75
|
+
.env(optionNameToEnvName.turtlename)
|
|
51
76
|
)
|
|
52
77
|
.addOption(
|
|
53
|
-
new Option('--
|
|
54
|
-
.env(
|
|
78
|
+
new Option('--username <string>', 'username to use for Signer')
|
|
79
|
+
.env(optionNameToEnvName.username)
|
|
55
80
|
)
|
|
56
81
|
.addOption(
|
|
57
|
-
new Option('--
|
|
58
|
-
.env(
|
|
82
|
+
new Option('--password <string>', 'password to use for Signer')
|
|
83
|
+
.env(optionNameToEnvName.password)
|
|
59
84
|
)
|
|
60
85
|
|
|
61
86
|
.addOption(
|
|
@@ -64,17 +89,17 @@ program
|
|
|
64
89
|
.preset(THROW)
|
|
65
90
|
.choices([OURS, THEIRS, THROW, ''])
|
|
66
91
|
.argParser(makeParserWithOptions(THROW, OURS, THEIRS))
|
|
67
|
-
.env(
|
|
92
|
+
.env(optionNameToEnvName.fsMirror)
|
|
68
93
|
)
|
|
69
94
|
.addOption(
|
|
70
95
|
new Option('-i, --interactive', 'flag to start repl')
|
|
71
96
|
.default(false)
|
|
72
|
-
.env(
|
|
97
|
+
.env(optionNameToEnvName.interactive)
|
|
73
98
|
)
|
|
74
99
|
.addOption(
|
|
75
100
|
new Option('-a, --archive', 'save all turtles to files by public key')
|
|
76
101
|
.default(false)
|
|
77
|
-
.env(
|
|
102
|
+
.env(optionNameToEnvName.archive)
|
|
78
103
|
)
|
|
79
104
|
.addOption(
|
|
80
105
|
new Option('-v, --verbose [level]', 'log data flows')
|
|
@@ -82,12 +107,12 @@ program
|
|
|
82
107
|
.preset(1)
|
|
83
108
|
.choices(Object.values(LOG_LEVELS).map(v => v.toString()))
|
|
84
109
|
.argParser(makeParserWithOptions(1, ...Object.values(LOG_LEVELS)))
|
|
85
|
-
.env(
|
|
110
|
+
.env(optionNameToEnvName.verbose)
|
|
86
111
|
)
|
|
87
112
|
.addOption(
|
|
88
113
|
new Option('--turtleDB-folder <path>', 'path to folder for TurtleDB files')
|
|
89
114
|
.default('.turtleDB')
|
|
90
|
-
.env(
|
|
115
|
+
.env(optionNameToEnvName.turtleDBFolder)
|
|
91
116
|
)
|
|
92
117
|
|
|
93
118
|
.addOption(
|
|
@@ -95,22 +120,19 @@ program
|
|
|
95
120
|
.default(false)
|
|
96
121
|
.preset(defaultWebPort)
|
|
97
122
|
.argParser(makeParserWithOptions(defaultWebPort))
|
|
98
|
-
.env(
|
|
99
|
-
.helpGroup('Web Server:')
|
|
100
|
-
)
|
|
101
|
-
.addOption(
|
|
102
|
-
new Option('--web-fallback <string>', 'project public key to use as fallback for web')
|
|
103
|
-
.env('TURTLEDB_WEB_FALLBACK')
|
|
123
|
+
.env(optionNameToEnvName.webPort)
|
|
104
124
|
.helpGroup('Web Server:')
|
|
105
125
|
)
|
|
106
126
|
.addOption(
|
|
107
127
|
new Option('--web-certpath <string>', 'path to self-cert for web')
|
|
108
|
-
.
|
|
128
|
+
.default(false)
|
|
129
|
+
.env(optionNameToEnvName.webCertpath)
|
|
109
130
|
.helpGroup('Web Server:')
|
|
110
131
|
)
|
|
111
132
|
.addOption(
|
|
112
133
|
new Option('--web-insecure', '(local dev) allow unauthorized for web')
|
|
113
|
-
.
|
|
134
|
+
.default(false)
|
|
135
|
+
.env(optionNameToEnvName.webInsecure)
|
|
114
136
|
.helpGroup('Web Server:')
|
|
115
137
|
)
|
|
116
138
|
|
|
@@ -119,7 +141,7 @@ program
|
|
|
119
141
|
.default(false)
|
|
120
142
|
.preset(defaultRemoteHost)
|
|
121
143
|
.argParser(makeParserWithOptions(defaultRemoteHost))
|
|
122
|
-
.env(
|
|
144
|
+
.env(optionNameToEnvName.remoteHost)
|
|
123
145
|
.helpGroup('TurtleDB Syncing:')
|
|
124
146
|
)
|
|
125
147
|
.addOption(
|
|
@@ -127,7 +149,7 @@ program
|
|
|
127
149
|
.default(false)
|
|
128
150
|
.preset(defaultRemotePort)
|
|
129
151
|
.argParser(makeParserWithOptions(defaultRemotePort))
|
|
130
|
-
.env(
|
|
152
|
+
.env(optionNameToEnvName.remotePort)
|
|
131
153
|
.helpGroup('TurtleDB Syncing:')
|
|
132
154
|
)
|
|
133
155
|
|
|
@@ -136,7 +158,7 @@ program
|
|
|
136
158
|
.default(false)
|
|
137
159
|
.preset(defaultSyncPort)
|
|
138
160
|
.argParser(makeParserWithOptions(defaultSyncPort))
|
|
139
|
-
.env(
|
|
161
|
+
.env(optionNameToEnvName.syncPort)
|
|
140
162
|
.helpGroup('TurtleDB Syncing:')
|
|
141
163
|
)
|
|
142
164
|
|
|
@@ -144,32 +166,43 @@ program
|
|
|
144
166
|
new Option('--s3-end-point <string>', 'endpoint for s3 (like "https://sfo3.digitaloceanspaces.com")')
|
|
145
167
|
.default(false)
|
|
146
168
|
.argParser(makeParserWithOptions())
|
|
147
|
-
.env(
|
|
169
|
+
.env(optionNameToEnvName.s3EndPoint)
|
|
148
170
|
.helpGroup('S3-like Service Syncing:')
|
|
149
171
|
)
|
|
150
172
|
.addOption(
|
|
151
173
|
new Option('--s3-region <string>', 'region for s3 (like "sfo3")')
|
|
152
|
-
.
|
|
174
|
+
.default(false)
|
|
175
|
+
.env(optionNameToEnvName.s3Region)
|
|
153
176
|
.helpGroup('S3-like Service Syncing:')
|
|
154
177
|
)
|
|
155
178
|
.addOption(
|
|
156
179
|
new Option('--s3-access-key-id <string>', 'accessKeyId for s3')
|
|
157
|
-
.
|
|
180
|
+
.default(false)
|
|
181
|
+
.env(optionNameToEnvName.s3AccessKeyId)
|
|
158
182
|
.helpGroup('S3-like Service Syncing:')
|
|
159
183
|
)
|
|
160
184
|
.addOption(
|
|
161
185
|
new Option('--s3-secret-access-key <string>', 'secretAccessKey for s3')
|
|
162
|
-
.
|
|
186
|
+
.default(false)
|
|
187
|
+
.env(optionNameToEnvName.s3SecretAccessKey)
|
|
163
188
|
.helpGroup('S3-like Service Syncing:')
|
|
164
189
|
)
|
|
165
190
|
.addOption(
|
|
166
191
|
new Option('--s3-bucket <string>', 'bucket for s3')
|
|
167
|
-
.
|
|
192
|
+
.default(false)
|
|
193
|
+
.env(optionNameToEnvName.s3Bucket)
|
|
168
194
|
.helpGroup('S3-like Service Syncing:')
|
|
169
195
|
)
|
|
170
196
|
|
|
171
197
|
.parse()
|
|
172
198
|
|
|
199
|
+
const optionsByName = Object.fromEntries(
|
|
200
|
+
Object.entries(optionNameToEnvName).map(([optionName, envName]) => [
|
|
201
|
+
optionName,
|
|
202
|
+
program.options.find(option => option.envVar === envName)
|
|
203
|
+
])
|
|
204
|
+
)
|
|
205
|
+
|
|
173
206
|
const options = program.opts()
|
|
174
207
|
if (options.envFile) {
|
|
175
208
|
config({ path: options.envFile })
|
|
@@ -177,40 +210,124 @@ if (options.envFile) {
|
|
|
177
210
|
Object.assign(options, program.opts()) // update options with new env vars
|
|
178
211
|
}
|
|
179
212
|
setLogLevel(options.verbose)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
213
|
+
logInfo(() => console.log())
|
|
214
|
+
logInfo(() => console.log(`\x1b[32mturtb v${version}\x1b[0m`))
|
|
215
|
+
logInfo(() => console.log(`\x1b[34m${startTime.toLocaleString()} \x1b[2m(${startTime.toISOString()})\x1b[0m`))
|
|
216
|
+
logInfo(() => console.log())
|
|
217
|
+
options.turtlename ||= question('Turtlename: ')
|
|
218
|
+
options.username ||= question('Username: ')
|
|
219
|
+
const turtlename = options.turtlename
|
|
220
|
+
const username = options.username
|
|
221
|
+
const signer = new Signer(username, options.password || questionNewPassword('Password [ATTENTION!: Backspace won\'t work here]: ', { min: 4, max: 999 }))
|
|
190
222
|
const publicKey = (await signer.makeKeysFor(turtlename)).publicKey
|
|
191
|
-
logInfo(() =>
|
|
223
|
+
logInfo(() => {
|
|
224
|
+
const maxLength = Math.max(turtlename.length, username.length, publicKey.length)
|
|
225
|
+
console.log(`\x1b[35m
|
|
226
|
+
╭─────────────────────────${'─'.repeat(maxLength)}──╮
|
|
227
|
+
╞══════════════════════╤══${'═'.repeat(maxLength)}══╡
|
|
228
|
+
│ TURTLENAME: │ \x1b[0m${turtlename}${' '.repeat(maxLength - turtlename.length)}\x1b[35m │
|
|
229
|
+
├──────────────────────┼──${'─'.repeat(maxLength)}──┤
|
|
230
|
+
│ USERNAME: │ \x1b[0m${username}${' '.repeat(maxLength - username.length)}\x1b[35m │
|
|
231
|
+
├──────────────────────┼──${'─'.repeat(maxLength)}──┤
|
|
232
|
+
│ COMPACT PUBLIC KEY: │ \x1b[0m${publicKey}${' '.repeat(maxLength - publicKey.length)}\x1b[35m │
|
|
233
|
+
╰━━━━━━━━━━━━━━━━━━━━━━┷━━${'━'.repeat(maxLength)}━━╯\x1b[0m`)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
let initOption = -1
|
|
237
|
+
if (!options.envFile) {
|
|
238
|
+
initOption = keyInSelect([
|
|
239
|
+
'.env file only',
|
|
240
|
+
'.env, package.json, LICENSE, and .gitignore'
|
|
241
|
+
], 'Create (replace existing) files for easier startup?', { cancel: 'CANCEL: Don\'t create any files' })
|
|
242
|
+
}
|
|
243
|
+
const willSync = options.syncPort || options.remoteHost || options.remotePort || options.s3EndPoint || options.archive || options.fsMirror || options.webPort || options.interactive
|
|
244
|
+
if (!willSync) {
|
|
245
|
+
if (keyInYN(`Mirror local files as state (${optionsByName.fsMirror.envVar})?`)) {
|
|
246
|
+
options.fsMirror = optionsByName.fsMirror.presetArg
|
|
247
|
+
}
|
|
248
|
+
if (keyInYN(`Start localhost web server(${optionsByName.webPort.envVar})?`)) {
|
|
249
|
+
options.webPort = optionsByName.webPort.presetArg
|
|
250
|
+
}
|
|
251
|
+
if (keyInYN(`Mirror local files as state (${optionsByName.fsMirror.envVar})?`)) {
|
|
252
|
+
options.fsMirror = optionsByName.fsMirror.presetArg
|
|
253
|
+
}
|
|
254
|
+
if (keyInYN(`Sync with remote turtle (${optionsByName.remoteHost.envVar} and ${optionsByName.remotePort.envVar})?`)) {
|
|
255
|
+
options.remoteHost = optionsByName.remoteHost.presetArg
|
|
256
|
+
options.remotePort = optionsByName.remotePort.presetArg
|
|
257
|
+
}
|
|
258
|
+
if (keyInYN(`Archive changes (${optionsByName.archive.envVar})?`)) {
|
|
259
|
+
options.archive = optionsByName.archive.presetArg
|
|
260
|
+
}
|
|
261
|
+
if (keyInYN(`Interactive mode (${optionsByName.interactive.envVar})?`)) {
|
|
262
|
+
options.interactive = optionsByName.interactive.presetArg
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (initOption !== -1) {
|
|
266
|
+
const folder = proxyFolder('.')
|
|
267
|
+
const optionsAsEnv = Object.entries(optionsByName).map(([name, option]) => {
|
|
268
|
+
const value = options[name]
|
|
269
|
+
if (!option.envVar) return null
|
|
270
|
+
if (option.defaultValue === value) return `# ${option.envVar}=`
|
|
271
|
+
if (option.presetArg === value) return `${option.envVar}=`
|
|
272
|
+
return `${option.envVar}=${value}`
|
|
273
|
+
})
|
|
274
|
+
console.log(optionsAsEnv.join('\n'))
|
|
275
|
+
folder['.env'] = optionsAsEnv
|
|
276
|
+
if (initOption > 0) {
|
|
277
|
+
folder['package.json'] = {
|
|
278
|
+
name: options.turtlename,
|
|
279
|
+
author: options.username,
|
|
280
|
+
license: 'AGPL-3.0-only',
|
|
281
|
+
scripts: {
|
|
282
|
+
start: 'npx turtb --env-file .env'
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
286
|
+
const __dirname = dirname(__filename)
|
|
287
|
+
folder.LICENSE = readFileSync(join(__dirname, '../LICENSE'), { encoding: 'utf8' }).split(/\n/)
|
|
288
|
+
folder['.gitignore'] = [
|
|
289
|
+
'# don\'t commit passwords',
|
|
290
|
+
'.env*',
|
|
291
|
+
'',
|
|
292
|
+
'# devDependencies... DEV ONLY! if your app needs it don\t .gitignore it, maybe copy it out of node_modules post-install?',
|
|
293
|
+
'node_modules/',
|
|
294
|
+
'',
|
|
295
|
+
'# turtleDB uses this file and should ignore git stuff',
|
|
296
|
+
'.git/',
|
|
297
|
+
'',
|
|
298
|
+
'# git uses this file and should ignore turtleDB stuff',
|
|
299
|
+
'.turtleDB/',
|
|
300
|
+
'',
|
|
301
|
+
'# Optional REPL history',
|
|
302
|
+
'.node_repl_history',
|
|
303
|
+
'',
|
|
304
|
+
'# the true price of a pretty laptop',
|
|
305
|
+
'**/.DS_Store'
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
}
|
|
192
309
|
|
|
193
|
-
logSilly(() => console.log({ options }))
|
|
194
310
|
// console.log({ options })
|
|
311
|
+
logDebug(() => console.log({ options }))
|
|
195
312
|
// process.exit(0)
|
|
196
313
|
|
|
197
314
|
const recaller = new Recaller('turtledb-com')
|
|
198
315
|
const turtleDB = new TurtleDB('turtledb-com', recaller)
|
|
199
316
|
|
|
200
|
-
if (options.syncPort
|
|
317
|
+
if (options.syncPort) {
|
|
201
318
|
const syncPort = +options.syncPort || defaultSyncPort
|
|
202
319
|
logInfo(() => console.log(`listening for local connections on port ${syncPort}`))
|
|
203
320
|
outletSync(turtleDB, syncPort)
|
|
204
321
|
}
|
|
205
322
|
|
|
206
|
-
if (options.remoteHost
|
|
323
|
+
if (options.remoteHost || options.remotePort) {
|
|
207
324
|
const remoteHost = options.remoteHost || defaultRemoteHost
|
|
208
325
|
const remotePort = +options.remotePort || defaultRemotePort
|
|
209
326
|
logInfo(() => console.log(`connecting to remote at ${remoteHost}:${remotePort}`))
|
|
210
327
|
originSync(turtleDB, remoteHost, remotePort)
|
|
211
328
|
}
|
|
212
329
|
|
|
213
|
-
if (options.s3EndPoint
|
|
330
|
+
if (options.s3EndPoint) {
|
|
214
331
|
s3Sync(turtleDB, recaller, options.s3EndPoint, options.s3Region, options.s3AccessKeyId, options.s3SecretAccessKey, options.s3Bucket)
|
|
215
332
|
}
|
|
216
333
|
|
|
@@ -220,7 +337,7 @@ if (options.archive) {
|
|
|
220
337
|
archiveSync(turtleDB, recaller, archivePath)
|
|
221
338
|
}
|
|
222
339
|
|
|
223
|
-
if (options.fsMirror
|
|
340
|
+
if (options.fsMirror) {
|
|
224
341
|
if (![OURS, THEIRS, THROW].includes(options.fsMirror)) {
|
|
225
342
|
logError(() => console.error(`fs-mirror resolve option must be "${OURS}", "${THEIRS}" or "${THROW}" (you provided: "${options.fsMirror}")`))
|
|
226
343
|
process.exit(1)
|
|
@@ -229,7 +346,7 @@ if (options.fsMirror !== false) {
|
|
|
229
346
|
fileSync(turtlename, turtleDB, signer, undefined, options.fsMirror, options.turtleDBFolder)
|
|
230
347
|
}
|
|
231
348
|
|
|
232
|
-
if (options.webPort
|
|
349
|
+
if (options.webPort) {
|
|
233
350
|
const webPort = +options.webPort
|
|
234
351
|
const insecure = !!options.webInsecure
|
|
235
352
|
const https = insecure || !!options.webCertpath
|