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.
Files changed (2) hide show
  1. package/bin/turtb.js +164 -47
  2. 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, logSilly, setLogLevel } from '../lib/utils/logger.js'
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
- console.log('\n\n\n!!!!!')
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('--username <string>', 'username to use for Signer')
50
- .env('TURTLEDB_USERNAME')
74
+ new Option('--turtlename <string>', 'name for dataset')
75
+ .env(optionNameToEnvName.turtlename)
51
76
  )
52
77
  .addOption(
53
- new Option('--password <string>', 'password to use for Signer')
54
- .env('TURTLEDB_PASSWORD')
78
+ new Option('--username <string>', 'username to use for Signer')
79
+ .env(optionNameToEnvName.username)
55
80
  )
56
81
  .addOption(
57
- new Option('--turtlename <string>', 'name for dataset')
58
- .env('TURTLEDB_TURTLENAME')
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('TURTLEDB_FS_MIRROR')
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('TURTLEDB_INTERACTIVE')
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('TURTLEDB_ARCHIVE')
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('TURTLEDB_VERBOSE')
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('TURTLEDB_FOLDER')
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('TURTLEDB_WEB_PORT')
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
- .env('TURTLEDB_WEB_CERTPATH')
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
- .env('TURTLEDB_WEB_INSECURE')
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('TURTLEDB_REMOTE_HOST')
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('TURTLEDB_REMOTE_PORT')
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('TURTLEDB_PORT')
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('TURTLEDB_S3_END_POINT')
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
- .env('TURTLEDB_S3_REGION')
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
- .env('TURTLEDB_S3_ACCESS_KEY_ID')
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
- .env('TURTLEDB_S3_SECRET_ACCESS_KEY')
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
- .env('TURTLEDB_S3_BUCKET')
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
- let username = options.username
181
- let turtlename = options.turtlename
182
- let signer
183
- if (options.fsMirror !== false) {
184
- username ||= question('Username: ')
185
- turtlename ||= question('Turtlename: ')
186
- signer = new Signer(username, options.password || questionNewPassword('Password [ATTENTION!: Backspace won\'t work here]: ', { min: 4, max: 999 }))
187
- } else if (username && turtlename && options.password) {
188
- signer = new Signer(username, options.password)
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(() => console.log({ username, turtlename, publicKey }))
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 !== false) {
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 !== false || options.remotePort !== false) {
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 !== false) {
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 !== false) {
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 !== false) {
349
+ if (options.webPort) {
233
350
  const webPort = +options.webPort
234
351
  const insecure = !!options.webInsecure
235
352
  const https = insecure || !!options.webCertpath
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turtb",
3
- "version": "0.5.3",
3
+ "version": "0.5.6",
4
4
  "description": "binary for turtledb projects",
5
5
  "repository": "git@github.com:dtudury/turtb.git",
6
6
  "author": "David Tudury <david.tudury@gmail.com>",