gitsheets 0.22.4 → 1.0.3

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 (72) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +21 -0
  3. package/bin/gitsheets +5 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +256 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/errors.d.ts +72 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +74 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.d.ts +17 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +12 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/patch.d.ts +2 -0
  17. package/dist/patch.d.ts.map +1 -0
  18. package/dist/patch.js +39 -0
  19. package/dist/patch.js.map +1 -0
  20. package/dist/path-template/index.d.ts +42 -0
  21. package/dist/path-template/index.d.ts.map +1 -0
  22. package/dist/path-template/index.js +288 -0
  23. package/dist/path-template/index.js.map +1 -0
  24. package/dist/push-daemon.d.ts +53 -0
  25. package/dist/push-daemon.d.ts.map +1 -0
  26. package/dist/push-daemon.js +148 -0
  27. package/dist/push-daemon.js.map +1 -0
  28. package/dist/repository.d.ts +67 -0
  29. package/dist/repository.d.ts.map +1 -0
  30. package/dist/repository.js +322 -0
  31. package/dist/repository.js.map +1 -0
  32. package/dist/sheet.d.ts +107 -0
  33. package/dist/sheet.d.ts.map +1 -0
  34. package/dist/sheet.js +605 -0
  35. package/dist/sheet.js.map +1 -0
  36. package/dist/store.d.ts +41 -0
  37. package/dist/store.d.ts.map +1 -0
  38. package/dist/store.js +49 -0
  39. package/dist/store.js.map +1 -0
  40. package/dist/toml.d.ts +11 -0
  41. package/dist/toml.d.ts.map +1 -0
  42. package/dist/toml.js +28 -0
  43. package/dist/toml.js.map +1 -0
  44. package/dist/transaction.d.ts +96 -0
  45. package/dist/transaction.d.ts.map +1 -0
  46. package/dist/transaction.js +227 -0
  47. package/dist/transaction.js.map +1 -0
  48. package/dist/validation.d.ts +37 -0
  49. package/dist/validation.d.ts.map +1 -0
  50. package/dist/validation.js +105 -0
  51. package/dist/validation.js.map +1 -0
  52. package/package.json +41 -35
  53. package/bin/cli.js +0 -61
  54. package/commands/edit.js +0 -90
  55. package/commands/normalize.js +0 -81
  56. package/commands/query.js +0 -206
  57. package/commands/read.js +0 -64
  58. package/commands/singer-target.js +0 -214
  59. package/commands/upsert.js +0 -260
  60. package/lib/GitSheets.js +0 -464
  61. package/lib/Repository.js +0 -88
  62. package/lib/Sheet.js +0 -625
  63. package/lib/errors.js +0 -21
  64. package/lib/hologit.js +0 -1
  65. package/lib/logger.js +0 -18
  66. package/lib/path/BaseComponent.js +0 -24
  67. package/lib/path/ExpressionComponent.js +0 -26
  68. package/lib/path/FieldComponent.js +0 -13
  69. package/lib/path/LiteralComponent.js +0 -12
  70. package/lib/path/Query.js +0 -18
  71. package/lib/path/Template.js +0 -214
  72. package/server.js +0 -120
package/lib/GitSheets.js DELETED
@@ -1,464 +0,0 @@
1
- const { Repo, BlobObject } = require('hologit/lib');
2
- const { Readable } = require('stream');
3
- const TOML = require('@iarna/toml');
4
- const maxstache = require('maxstache');
5
- const jsonpatch = require('fast-json-patch');
6
- const csvParser = require('csv-parser');
7
-
8
- const {
9
- SerializationError,
10
- ConfigError,
11
- InvalidRefError,
12
- MergeError,
13
- } = require('./errors')
14
-
15
- const DIFF_PATTERN = /([A-Z])\d*\t(\S+)\t?(\S*)/;
16
- const diffStatusMap = {
17
- A: 'added',
18
- D: 'deleted',
19
- M: 'modified',
20
- R: 'renamed',
21
- };
22
-
23
- /**
24
- * @class
25
- */
26
- module.exports = class GitSheets {
27
- static async create(gitDir = null) {
28
- const repo = (gitDir)
29
- ? new Repo({ gitDir })
30
- : await Repo.getFromEnvironment();
31
- const git = await repo.getGit();
32
-
33
- return new GitSheets(repo, git);
34
- }
35
-
36
- constructor (repo, git) {
37
- this.repo = repo;
38
- this.git = git;
39
- this.version = 2;
40
- }
41
-
42
- /**
43
- * Get config object from .gitsheets/config on a particular ref
44
- * @public
45
- * @param {string} ref - Ref of tree (e.g. master)
46
- * @return {Promise<Object>}
47
- * @throws {ConfigError}
48
- */
49
- async getConfig (ref) {
50
- const tree = await this._createTreeFromRef(ref);
51
- const child = await tree.getChild('.gitsheets/config'); // TODO: Wrap errors
52
- if (!child) return {};
53
-
54
- const contents = await child.read(); // TODO: Wrap errors
55
- return this._deserialize(contents);
56
- }
57
-
58
- /**
59
- * Get a specific property from the config object
60
- * @public
61
- * @param {string} ref - Ref of tree (e.g. master)
62
- * @param {string} key - Top-level property name (e.g. path)
63
- * @return {Promise<*>}
64
- * @throws {ConfigError}
65
- */
66
- async getConfigItem (ref, key) {
67
- const config = await this.getConfig(ref);
68
- if (!config.hasOwnProperty(key)) {
69
- throw new ConfigError(`config is missing property ${key}`)
70
- } else {
71
- return config[key];
72
- }
73
- }
74
-
75
- /**
76
- * Save a config object to .gitsheets/config on an existing branch
77
- * @public
78
- * @param {string} ref - Ref of parent tree (e.g. master)
79
- * @param {Object} config - Config object to save
80
- * @return {Promise<void>}
81
- * @throws {ConfigError}
82
- */
83
- async setConfig (ref, config) {
84
- const path = '.gitsheets/config';
85
- const contents = this._serialize(config);
86
- const tree = await this._createTreeFromRef(ref);
87
- await tree.writeChild(path, contents); // TODO: Wrap errors
88
- const treeHash = await tree.write();
89
- await this._saveTreeToExistingBranch({
90
- treeHash,
91
- branch: ref,
92
- msg: 'set config',
93
- });
94
- }
95
-
96
- /**
97
- * Save a specific config property. Merges with existing config, if any.
98
- * @public
99
- * @param {string} ref - Ref of parent tree (e.g. master)
100
- * @param {string} key - Top-level property name (e.g. path)
101
- * @param {*} value - Value to set
102
- * @return {Promise<void>}
103
- * @throws {ConfigError}
104
- */
105
- async setConfigItem (ref, key, value) {
106
- const config = await this.getConfig(ref);
107
- const newConfig = { ...config, [key]: value };
108
- await this.setConfig(ref, newConfig);
109
- }
110
-
111
- /**
112
- * Serialise a dataset and commit it onto current or new branch
113
- * @public
114
- * @param {Object} opts
115
- * @param {Readable} opts.data - Stream of data to be imported
116
- * @param {string} [opts.dataType] - Type of input data (valid: csv)
117
- * @param {string} opts.parentRef - Ref of parent tree
118
- * @param {boolean} opts.merge - Whether to merge/upsert data, as opposed to replace
119
- * @param {string} [opts.saveToBranch] - Branch name to create or update. Omit to not save.
120
- * @return {Promise<string>} - Tree hash
121
- */
122
- async import ({
123
- data,
124
- dataType = null,
125
- parentRef,
126
- merge = false,
127
- saveToBranch = null,
128
- }) {
129
- const pathTemplate = await this.getConfigItem(parentRef, 'path');
130
-
131
- const treeObject = (merge)
132
- ? await this._createTreeFromRef(parentRef)
133
- : await this._createTruncatedTree(parentRef);
134
-
135
- const treeHash = await this._writeDataToTree({
136
- data: this._attachDataParser(data, dataType),
137
- treeObject,
138
- pathTemplate,
139
- });
140
-
141
- if (saveToBranch && saveToBranch === parentRef) { // TODO: check if branch exists instead
142
- await this._saveTreeToExistingBranch({
143
- treeHash,
144
- branch: saveToBranch,
145
- msg: 'import to existing branch',
146
- });
147
- } else if (saveToBranch) {
148
- await this._saveTreeToNewBranch({
149
- treeHash,
150
- parentRef,
151
- branch: saveToBranch,
152
- msg: 'import to new branch',
153
- });
154
- }
155
-
156
- return treeHash;
157
- }
158
-
159
- /**
160
- * Deserialize a dataset from a particular ref
161
- * @public
162
- * @param {string} ref - Ref of tree (e.g. master)
163
- * @returns {Promise<Readable>}
164
- */
165
- async export (ref) {
166
- const treeObject = await this._createTreeFromRef(ref);
167
-
168
- const blobMap = await treeObject.getBlobMap();
169
- let blobsRemaining = Object.entries(blobMap)
170
- .filter(this._isDataBlob)
171
-
172
- const deserialize = this._deserialize.bind(this);
173
- return new Readable({
174
- objectMode: true,
175
- async read () {
176
- if (blobsRemaining.length > 0) {
177
- const [key, blob] = blobsRemaining.shift() // mutates array
178
- this.push(
179
- await blob.read()
180
- .then(deserialize)
181
- .then((data) => ({ ...data, _path: key.substr(0, key.length-5) }))
182
- );
183
- } else {
184
- this.push(null);
185
- }
186
- },
187
- })
188
- }
189
-
190
- /**
191
- * Compare a dataset between two refs and return the diffs
192
- * @public
193
- * @param {string} srcRef - Ref of original state (e.g. master)
194
- * @param {string} dstRef - Ref of new state (e.g. proposal)
195
- * @returns {Promise<Array>}
196
- */
197
- async compare (srcRef, dstRef) {
198
- const [srcBlobMap, dstBlobMap] = await Promise.all([
199
- this._getBlobMapFromRef(srcRef),
200
- this._getBlobMapFromRef(dstRef),
201
- ]);
202
-
203
- const diffs = await this._getDiffs(srcRef, dstRef);
204
-
205
- const fullDiffs = diffs.map(({ status, path, newPath }) => ({
206
- added: async () => ({
207
- _path: path,
208
- status,
209
- value: await this._parseBlob(dstBlobMap[path]),
210
- }),
211
- deleted: async () => ({
212
- _path: path,
213
- status,
214
- value: await this._parseBlob(srcBlobMap[path]),
215
- }),
216
- modified: async () => ({
217
- _path: path,
218
- status,
219
- patch: await this._generatePatch(srcBlobMap[path], dstBlobMap[path]),
220
- }),
221
- renamed: async () => ({
222
- _path: path,
223
- status: 'modified',
224
- patch: await this._generatePatch(srcBlobMap[path], dstBlobMap[newPath]),
225
- }),
226
- }[status]()));
227
-
228
- return Promise.all(fullDiffs);
229
- }
230
-
231
- /**
232
- * Create a merge commit between two refs and update srcRef to point to it
233
- * Note: Only works if srcRef is ancestor of dstRef
234
- * Note: Force deletes dstRef
235
- * @public
236
- * @param {string} srcRef - Ref of original state (e.g. master)
237
- * @param {string} dstRef - Ref of new state (e.g. proposal)
238
- * @param {string} [msg=Merge <dstRef>] - Message of merge commit
239
- * @returns {Promise<void>}
240
- * @throws {MergeError}
241
- */
242
- async merge (srcRef, dstRef, msg = null) {
243
- await this._verifyIsAncestor(srcRef, dstRef);
244
- const commitMsg = msg || `Merge ${dstRef}`;
245
-
246
- const [
247
- qualifiedSrcRef,
248
- srcCommitHash,
249
- dstCommitHash,
250
- dstTreeHash,
251
- ] = await Promise.all([
252
- this._getQualifiedRef(srcRef),
253
- this._getCommitHash(srcRef),
254
- this._getCommitHash(dstRef),
255
- this._getTreeHash(dstRef),
256
- ]);
257
-
258
- const mergeCommitHash = await this.git.commitTree(dstTreeHash, {
259
- p: [ srcCommitHash, dstCommitHash ],
260
- m: commitMsg,
261
- });
262
-
263
- // TODO: Wrap errors
264
- await this.git.updateRef(qualifiedSrcRef, mergeCommitHash, srcCommitHash);
265
- await this.git.branch({D: true}, dstRef); // force delete in case srcRef is not checked out
266
- }
267
-
268
- _isDataBlob ([key, blob]) {
269
- return !key.startsWith('.gitsheets/') && key.endsWith('.toml') && blob instanceof BlobObject;
270
- }
271
-
272
- async _verifyIsAncestor (srcRef, dstRef) {
273
- try {
274
- await this.git.mergeBase({'is-ancestor': true}, srcRef, dstRef);
275
- } catch (err) {
276
- throw new MergeError(`${srcRef} is not an ancestor of ${dstRef}`);
277
- }
278
- }
279
-
280
- async _getBlobMapFromRef (ref) {
281
- const treeObject = await this._createTreeFromRef(ref);
282
- return treeObject.getBlobMap();
283
- }
284
-
285
- async _generatePatch (srcBlob, dstBlob) {
286
- const [srcData, dstData] = await Promise.all([
287
- this._parseBlob(srcBlob),
288
- this._parseBlob(dstBlob),
289
- ]);
290
- return this._compareObjects(srcData, dstData);
291
- }
292
-
293
- async _getDiffs (srcRef, dstRef) {
294
- const output = await this.git.diff({'name-status': true}, srcRef, dstRef);
295
-
296
- return output
297
- .trim()
298
- .split('\n')
299
- .filter((line) => line.length > 0)
300
- .map(this._parseDiffLine)
301
- .filter((diff) => diff.status !== null)
302
- }
303
-
304
- _parseDiffLine (line) {
305
- const [ , statusCode, path, newPath ] = line.match(DIFF_PATTERN);
306
- const status = diffStatusMap[statusCode] || null;
307
- return { status, path, newPath };
308
- }
309
-
310
- _parseBlob (blob) {
311
- return blob.read()
312
- .then(this._deserialize);
313
- }
314
-
315
- _compareObjects (src, dst) {
316
- const includeTestOps = true;
317
- const ops = jsonpatch.compare(src, dst, includeTestOps);
318
- return this._mergeTestAndReplaceOps(ops);
319
- }
320
-
321
- _mergeTestAndReplaceOps (items) {
322
- const mergeableItems = items.map((item) => {
323
- if (item.op === 'test') return { path: item.path, from: item.value };
324
- else return item;
325
- })
326
- const keyedItems = mergeableItems.reduce((accum, item) => {
327
- if (accum.has(item.path)) {
328
- const currentItem = accum.get(item.path);
329
- accum.set(item.path, { ...currentItem, ...item });
330
- } else {
331
- accum.set(item.path, item);
332
- }
333
- return accum;
334
- }, new Map());
335
-
336
- return Array.from(keyedItems.values());
337
- }
338
-
339
- /**
340
- * WARNING: mutates tree
341
- */
342
- _writeDataToTree ({ data, treeObject, pathTemplate }) {
343
- return new Promise((resolve, reject) => {
344
- const pendingWrites = [];
345
-
346
- data
347
- .on('data', (row) => {
348
- const path = this._renderTemplate(pathTemplate, row);
349
- const contents = this._serialize(row);
350
-
351
- pendingWrites.push(treeObject.writeChild(`${path}.toml`, contents));
352
- })
353
- .on('end', () => {
354
- Promise.all(pendingWrites)
355
- .then(() => treeObject.write())
356
- .then(resolve)
357
- .catch(reject);
358
- })
359
- .on('error', (err) => {
360
- reject(new SerializationError(err.message))
361
- })
362
- })
363
- }
364
-
365
- _attachDataParser (data, dataType) {
366
- if (dataType === 'csv') {
367
- return data.pipe(csvParser({ strict: true }));
368
- } else {
369
- return data;
370
- }
371
- }
372
-
373
- /**
374
- * Wraps Hologit.Repo.createTreeFromRef with error matching
375
- * @private
376
- */
377
- async _createTreeFromRef (parent) {
378
- try {
379
- return await this.repo.createTreeFromRef(parent);
380
- } catch (err) {
381
- if (err.message.startsWith('invalid tree ref')) {
382
- throw new InvalidRefError('unknown ref');
383
- } else {
384
- throw err;
385
- }
386
- }
387
- }
388
-
389
- /**
390
- * Creates an empty tree and merges GitSheets config
391
- * @private
392
- */
393
- async _createTruncatedTree (parent) {
394
- const parentTree = await this._createTreeFromRef(parent);
395
- const tree = this.repo.createTree();
396
- await tree.merge(parentTree, { files: ['.gitsheets/*'] });
397
- return tree;
398
- }
399
-
400
- _serialize (row) {
401
- try {
402
- return TOML.stringify(this._sortObjectKeys(row));
403
- } catch (err) {
404
- throw new SerializationError(err.message);
405
- }
406
- }
407
-
408
- _deserialize (contents) {
409
- try {
410
- return TOML.parse(contents);
411
- } catch (err) {
412
- throw new SerializationError(err.message);
413
- }
414
- }
415
-
416
- _renderTemplate (template, data) {
417
- try {
418
- return maxstache(template, data);
419
- } catch (err) {
420
- throw new SerializationError(err.message);
421
- }
422
- }
423
-
424
- _sortObjectKeys (unsorted) {
425
- const sortedKeys = Object.keys(unsorted).sort()
426
-
427
- return sortedKeys.reduce((accum, key) => {
428
- accum[key] = unsorted[key]
429
- return accum
430
- }, {})
431
- }
432
-
433
- async _saveTreeToNewBranch({ treeHash, parentRef, branch, msg }) {
434
- const commitHash = await this.git.commitTree(treeHash, { // TODO: Wrap errors
435
- p: parentRef,
436
- m: msg,
437
- });
438
- await this.git.branch(branch, commitHash); // TODO: Wrap errors
439
- }
440
-
441
- async _saveTreeToExistingBranch ({ treeHash, branch, msg }) {
442
- const commitHash = await this.git.commitTree(treeHash, { // TODO: Wrap errors
443
- p: branch,
444
- m: msg,
445
- });
446
- const qualifiedBranch = await this._getQualifiedRef(branch);
447
- await this.git.updateRef(qualifiedBranch, commitHash); // TODO: Wrap errors
448
- }
449
-
450
- _getQualifiedRef (ref) {
451
- // TODO: Wrap errors
452
- return this.git.revParse({'symbolic-full-name': true}, ref);
453
- }
454
-
455
- _getCommitHash (ref) {
456
- return this.git.revParse({verify: true}, ref);
457
- }
458
-
459
- async _getTreeHash (ref) {
460
- const tree = await this._createTreeFromRef(ref);
461
- const hash = await tree.getHash();
462
- return hash
463
- }
464
- }
package/lib/Repository.js DELETED
@@ -1,88 +0,0 @@
1
- const path = require('path');
2
- const Sheet = require('./Sheet.js');
3
- const { Repo: HoloRepo, BlobObject } = require('hologit/lib');
4
-
5
- class Repository extends HoloRepo
6
- {
7
- constructor(options = {}) {
8
- super(options);
9
- }
10
-
11
- async resolveDataTree (root, dataTree) {
12
- const workspace = await this.getWorkspace();
13
-
14
- root = path.join('.', root);
15
-
16
- if (typeof dataTree == 'string') {
17
- dataTree = await workspace.root.getSubtree(path.join(root, dataTree), true);
18
- } else if (!dataTree) {
19
- dataTree = await workspace.root.getSubtree(root);
20
- }
21
-
22
- const sheetsPath = path.join(root, '.gitsheets');
23
- const sheetsTree = await workspace.root.getSubtree(sheetsPath, true);
24
-
25
- return {
26
- workspace,
27
- root,
28
- sheetsPath,
29
- sheetsTree,
30
- dataTree,
31
- };
32
- }
33
-
34
- async openSheet (name, { root = '/', dataTree: dataTreeInput = null, config = null } = {}) {
35
- const { workspace, sheetsPath, dataTree } = await this.resolveDataTree(root, dataTreeInput);
36
-
37
- return new Sheet({
38
- workspace,
39
- dataTree,
40
- name,
41
- configPath: path.join(sheetsPath, `${name}.toml`),
42
- phantom: config,
43
- });
44
- }
45
-
46
- async openSheets ({ root = '/', dataTree: dataTreeInput = null } = {}) {
47
- const { workspace, sheetsPath, sheetsTree, dataTree } = await this.resolveDataTree(root, dataTreeInput);
48
-
49
- const children = await sheetsTree.getChildren();
50
- const childNameRe = /^([^\/]+)\.toml$/;
51
-
52
- const sheets = {};
53
-
54
- for (const childName in children) {
55
-
56
- // skip any child not ending in .toml
57
- const filenameMatches = childName.match(childNameRe);
58
- if (!filenameMatches) {
59
- continue;
60
- }
61
-
62
- // skip any child that is deleted or isn't a blob
63
- const treeChild = children[childName];
64
- if (!treeChild || !treeChild.isBlob) {
65
- continue;
66
- }
67
-
68
- // read sheet
69
- const [, name] = filenameMatches;
70
-
71
- sheets[name] = new Sheet({
72
- workspace,
73
- dataTree,
74
- name,
75
- configPath: path.join(sheetsPath, childName),
76
- });
77
- }
78
-
79
- return sheets;
80
- }
81
-
82
- async finishWriting () {
83
- // TODO: using this instead of awaiting each upsert isn't safe currently as TreeObject.writeChild isn't safe to parallelize due to async subtree building
84
- return Sheet.finishWriting(this);
85
- }
86
- }
87
-
88
- module.exports = Repository;