gitsheets 0.21.1 → 0.21.5

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/cli.js CHANGED
@@ -2,18 +2,22 @@
2
2
 
3
3
 
4
4
  // setup logger
5
- const logger = require('winston')
6
- module.exports = { logger }
5
+ const logger = require('winston');
6
+ const loggerConsole = new logger.transports.Console({
7
+ level: process.env.DEBUG ? 'debug' : 'info',
8
+ format: logger.format.combine(
9
+ logger.format.colorize(),
10
+ logger.format.prettyPrint(),
11
+ logger.format.splat(),
12
+ logger.format.simple(),
13
+ ),
7
14
 
8
- if (process.env.DEBUG) {
9
- logger.level = 'debug'
10
- }
15
+ // all logger output to STDERR
16
+ stderrLevels: Object.keys(require('winston/lib/winston/config').cli.levels),
17
+ });
18
+ logger.add(loggerConsole);
11
19
 
12
-
13
- // all logger output to STDERR
14
- for (const level in logger.levels) {
15
- logger.default.transports.console.stderrLevels[level] = true;
16
- }
20
+ module.exports = { logger };
17
21
 
18
22
 
19
23
  // route command line
@@ -25,15 +29,33 @@ require('yargs')
25
29
  default: false,
26
30
  global: true,
27
31
  })
32
+ .option('q', {
33
+ alias: 'quiet',
34
+ type: 'boolean',
35
+ default: false,
36
+ global: true,
37
+ })
28
38
  .check(function (argv) {
29
39
  if (argv.debug) {
30
- logger.level = 'debug'
40
+ loggerConsole.level = 'debug';
31
41
  } else if (argv.quiet) {
32
- logger.level = 'error'
42
+ loggerConsole.level = 'error';
33
43
  }
34
44
 
35
45
  return true;
36
46
  })
37
- .commandDir('../commands')
47
+ .commandDir('../commands', { exclude: /\.test\.js$/ })
38
48
  .demandCommand()
39
- .argv
49
+ .showHelpOnFail(false, 'Specify --help for available options')
50
+ .fail((msg, err) => {
51
+ logger.error(msg || err.message);
52
+
53
+ if (err) {
54
+ logger.debug(err.stack);
55
+ }
56
+
57
+ process.exit(1);
58
+ })
59
+ .strict()
60
+ .help()
61
+ .argv;
package/commands/edit.js CHANGED
@@ -19,6 +19,8 @@ exports.builder = {
19
19
  exports.handler = async function edit({ recordPath, resumePath, encoding }) {
20
20
  const fs = require('fs');
21
21
  const { spawn } = require('child_process');
22
+ const path = require('path');
23
+ const tmp = require('tmp');
22
24
  const TOML = require('@iarna/toml');
23
25
  const Repository = require('../lib/Repository.js');
24
26
  const Sheet = require('../lib/Sheet.js')
@@ -26,17 +28,21 @@ exports.handler = async function edit({ recordPath, resumePath, encoding }) {
26
28
  const git = await repo.getGit();
27
29
 
28
30
  // open record
29
- const recordToml = fs.readFileSync(resumePath || recordPath, encoding);
31
+ let recordToml = fs.readFileSync(resumePath || recordPath, encoding);
30
32
 
31
- // get temp path
32
- const tempFilePath = await new Promise((resolve, reject) => {
33
- const mktemp = spawn('mktemp', ['-t', 'gitsheet.XXXXXX.toml']);
34
-
35
- let stdout = '', stderr = '';
36
- mktemp.stdout.on('data', chunk => stdout += chunk);
37
- mktemp.stderr.on('data', chunk => stderr += chunk);
33
+ // try to parse and format
34
+ try {
35
+ const record = TOML.parse(recordToml);
36
+ recordToml = Sheet.stringifyRecord(record);
37
+ } catch (err) {
38
+ console.warn(`Failed to parse opened record:\n${err}`);
39
+ }
38
40
 
39
- mktemp.on('close', code => code === 0 ? resolve(stdout.trim()) : reject(stderr.trim()));
41
+ // get temp path
42
+ const { name: tempFilePath } = tmp.fileSync({
43
+ prefix: path.basename(recordPath, '.toml'),
44
+ postfix: '.toml',
45
+ discardDescriptor: true,
40
46
  });
41
47
 
42
48
  // populate temp path
@@ -67,7 +73,7 @@ exports.handler = async function edit({ recordPath, resumePath, encoding }) {
67
73
  try {
68
74
  editedRecord = TOML.parse(editedToml);
69
75
  } catch (err) {
70
- console.error(`Failed to parse record:\n${err}`);
76
+ console.error(`Failed to parse edited record:\n${err}`);
71
77
  console.error(`To resume editing, run: git sheet edit ${recordPath} ${tempFilePath}`);
72
78
  process.exit(1);
73
79
  }
@@ -54,9 +54,23 @@ exports.handler = async function query({
54
54
  }
55
55
 
56
56
  // loop through all records and re-upsert
57
- for await (const record of sheet.query()) {
58
- logger.info(`rewriting ${sheetName}/${record[Symbol.for('gitsheets-path')]}`);
59
- await sheet.upsert(record);
57
+ try {
58
+ for await (const record of sheet.query()) {
59
+ const originalPath = record[Symbol.for('gitsheets-path')];
60
+ logger.info(`rewriting ${path.join(root, prefix, sheetName, originalPath)}.toml`);
61
+ const { path: normalizedPath } = await sheet.upsert(record);
62
+
63
+ if (normalizedPath !== originalPath) {
64
+ logger.warn(`^- moved to ${path.join(root, prefix, sheetName, normalizedPath)}.toml`);
65
+ }
66
+ }
67
+ } catch (err) {
68
+ if (err.constructor.name == 'TomlError') {
69
+ logger.error(`failed to parse ${path.join(root, prefix, err.file)}\n${err.message}`);
70
+ process.exit(1);
71
+ }
72
+
73
+ throw err;
60
74
  }
61
75
  }
62
76
 
package/commands/read.js CHANGED
@@ -3,7 +3,7 @@ exports.desc = 'Read a record, converting to desired format';
3
3
  exports.builder = {
4
4
  'record-path': {
5
5
  type: 'string',
6
- describe: 'The path to a record file to edit',
6
+ describe: 'The path to a record file to read',
7
7
  demandOption: true,
8
8
  },
9
9
  encoding: {
@@ -24,12 +24,7 @@ exports.builder = {
24
24
 
25
25
  exports.handler = async function edit({ recordPath, encoding, format, headers }) {
26
26
  const fs = require('fs');
27
- const { spawn } = require('child_process');
28
27
  const TOML = require('@iarna/toml');
29
- const Repository = require('../lib/Repository.js');
30
- const Sheet = require('../lib/Sheet.js')
31
- const repo = await Repository.getFromEnvironment({ working: true });
32
- const git = await repo.getGit();
33
28
 
34
29
  // open record
35
30
  const recordToml = fs.readFileSync(recordPath, encoding);
@@ -64,6 +59,6 @@ async function outputCsv(record, { headers = true, delimiter = ',' } = {}) {
64
59
  }
65
60
 
66
61
  async function outputToml(record) {
67
- const TOML = require('@iarna/toml');
68
- console.log(`${TOML.stringify(record)}`);
62
+ const Sheet = require('../lib/Sheet.js')
63
+ console.log(`${Sheet.stringifyRecord(record)}`);
69
64
  }
@@ -101,11 +101,20 @@ exports.handler = async function singerTarget({
101
101
  const clearedSheets = new Set();
102
102
  const writtenStreams = new Set();
103
103
  for await (const { type, stream, ...message} of readMessages({ jsonlFile })) {
104
- const sheet = sheets[stream];
105
-
106
104
  console.log(`${type}\t${stream}`, message);
107
105
 
108
106
 
107
+ // ignore unhandled message types for now
108
+ if (type == 'STATE' || type == 'ACTIVATE_VERSION') {
109
+ console.warn(`ignoring ${type} message`);
110
+ continue;
111
+ }
112
+
113
+
114
+ // get sheet
115
+ const sheet = sheets[stream];
116
+
117
+
109
118
  // create schema if needed
110
119
  if (!sheet) {
111
120
  if (type == 'SCHEMA') {
@@ -129,13 +138,6 @@ exports.handler = async function singerTarget({
129
138
  }
130
139
 
131
140
 
132
- // ignore state for now
133
- if (type == 'STATE') {
134
- console.warn('ignoring STATE message');
135
- continue;
136
- }
137
-
138
-
139
141
  // handle record message
140
142
  if (type == 'RECORD') {
141
143
  if (deleteMissing && !clearedSheets.has(sheet)) {
package/lib/Sheet.js CHANGED
@@ -118,9 +118,20 @@ class Sheet extends Configurable
118
118
  async readRecord (blob, path = null) {
119
119
  const cache = this.#recordCache.get(blob.hash);
120
120
 
121
- const record = cache
122
- ? v8.deserialize(cache)
123
- : await blob.read().then(TOML.parse);
121
+ let record;
122
+
123
+ if (cache) {
124
+ record = v8.deserialize(cache);
125
+ } else {
126
+ const toml = await blob.read();
127
+
128
+ try {
129
+ record = TOML.parse(toml);
130
+ } catch (err) {
131
+ err.file = path;
132
+ throw err;
133
+ }
134
+ }
124
135
 
125
136
  // annotate with gitsheets keys
126
137
  record[RECORD_SHEET_KEY] = this.name;
@@ -153,14 +164,14 @@ class Sheet extends Configurable
153
164
  const pathTemplate = PathTemplate.fromString(pathTemplateString);
154
165
  const sheetDataTree = await this.dataTree.getSubtree(sheetRoot);
155
166
 
156
- BLOBS: for await (const blob of pathTemplate.queryTree(sheetDataTree, query)) {
157
- const record = await this.readRecord(blob);
167
+ BLOBS: for await (const { blob, path: blobPath } of pathTemplate.queryTree(sheetDataTree, query)) {
168
+ const record = await this.readRecord(blob, blobPath);
158
169
 
159
170
  if (!queryMatches(query, record)) {
160
171
  continue BLOBS;
161
172
  }
162
173
 
163
- record[RECORD_PATH_KEY] = pathTemplate.render(record);
174
+ record[RECORD_PATH_KEY] = blobPath || pathTemplate.render(record);
164
175
 
165
176
  yield record;
166
177
  }
@@ -50,7 +50,7 @@ class Template
50
50
  return recordPath.join('/');
51
51
  }
52
52
 
53
- async* queryTree (tree, query, depth = 0) {
53
+ async* queryTree (tree, query, pathPrefix = '', depth = 0) {
54
54
  const numComponents = this.#components.length;
55
55
 
56
56
  if (!tree) {
@@ -68,7 +68,7 @@ class Template
68
68
  const child = await currentTree.getChild(`${nextName}.toml`);
69
69
 
70
70
  if (child) {
71
- yield child;
71
+ yield { path: path.join(pathPrefix, nextName), blob: child };
72
72
  }
73
73
 
74
74
  // absolute match on a leaf, we're done with this query
@@ -97,8 +97,9 @@ class Template
97
97
  continue;
98
98
  }
99
99
 
100
- attachmentsPrefix = `${childPath.substr(0, childPath.length - 5)}/`;
101
- yield child;
100
+ const childName = childPath.substr(0, childPath.length - 5);
101
+ attachmentsPrefix = `${childName}/`;
102
+ yield { path: path.join(pathPrefix, childName), blob: child };
102
103
  }
103
104
 
104
105
  return;
@@ -117,14 +118,14 @@ class Template
117
118
  } else {
118
119
  // each tree in current tree could contain matching records
119
120
  const children = await currentTree.getChildren();
120
- for (const childName in children) {
121
- const child = children[childName];
121
+ for (const childPath in children) {
122
+ const child = children[childPath];
122
123
 
123
124
  if (!child.isTree) {
124
125
  continue;
125
126
  }
126
127
 
127
- yield* this.queryTree(child, query, i+1);
128
+ yield* this.queryTree(child, query, path.join(pathPrefix, childPath), i+1);
128
129
  }
129
130
 
130
131
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitsheets",
3
- "version": "0.21.1",
3
+ "version": "0.21.5",
4
4
  "description": "A toolkit for using a git repository to store low-volume, high-touch, human-scale data",
5
5
  "main": "lib/GitSheets.js",
6
6
  "scripts": {
@@ -26,7 +26,7 @@
26
26
  "fast-csv": "^4.3.6",
27
27
  "fast-json-patch": "^2.2.1",
28
28
  "get-stream": "^5.2.0",
29
- "hologit": "^0.40.1",
29
+ "hologit": "^0.40.5",
30
30
  "http-assert": "^1.4.1",
31
31
  "koa": "^2.13.1",
32
32
  "koa-bodyparser": "^4.3.0",
@@ -36,8 +36,9 @@
36
36
  "rfc6902": "^4.0.1",
37
37
  "sort-keys": "^4.2.0",
38
38
  "streaming-json-stringify": "^3.1.0",
39
+ "tmp": "^0.2.1",
39
40
  "to-readable-stream": "^2.1.0",
40
- "winston": "^2.4.5",
41
+ "winston": "^3.3.3",
41
42
  "yargs": "^13.3.2"
42
43
  },
43
44
  "devDependencies": {