braidfs 0.0.13 → 0.0.15
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/index.js +186 -201
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
|
|
2
2
|
let http = require('http');
|
|
3
|
-
|
|
4
3
|
let { diff_main } = require('./diff.js')
|
|
5
4
|
let braid_text = require("braid-text");
|
|
6
5
|
let braid_fetch = require('braid-http').fetch
|
|
7
6
|
|
|
7
|
+
process.on("unhandledRejection", (x) => console.log(`unhandledRejection: ${x.stack}`))
|
|
8
|
+
process.on("uncaughtException", (x) => console.log(`uncaughtException: ${x.stack}`))
|
|
9
|
+
|
|
8
10
|
let port = 10000
|
|
9
11
|
let cookie = null
|
|
10
12
|
let pin_urls = []
|
|
11
13
|
let pindex_urls = []
|
|
12
14
|
let proxy_base = `./proxy_base`
|
|
15
|
+
let proxy_base_support = `./proxy_base_support`
|
|
13
16
|
|
|
14
17
|
let argv = process.argv.slice(2)
|
|
15
18
|
while (argv.length) {
|
|
@@ -30,8 +33,19 @@ while (argv.length) {
|
|
|
30
33
|
}
|
|
31
34
|
console.log({ pin_urls, pindex_urls })
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
for (let url of pin_urls) proxy_url(url)
|
|
37
|
+
pindex_urls.forEach(async url => {
|
|
38
|
+
let prefix = new URL(url).origin
|
|
39
|
+
while (true) {
|
|
40
|
+
let urls = await (await fetch(url)).json()
|
|
41
|
+
for (let url of urls) proxy_url(prefix + url)
|
|
42
|
+
await new Promise(done => setTimeout(done, 1000 * 60 * 60))
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
braid_text.list().then(x => {
|
|
47
|
+
for (let xx of x) proxy_url(xx)
|
|
48
|
+
})
|
|
35
49
|
|
|
36
50
|
const server = http.createServer(async (req, res) => {
|
|
37
51
|
console.log(`${req.method} ${req.url}`);
|
|
@@ -71,7 +85,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
71
85
|
proxy_url(url)
|
|
72
86
|
|
|
73
87
|
// Now serve the collaborative text!
|
|
74
|
-
braid_text.serve(req, res, { key: url })
|
|
88
|
+
braid_text.serve(req, res, { key: normalize_url(url) })
|
|
75
89
|
});
|
|
76
90
|
|
|
77
91
|
server.listen(port, () => {
|
|
@@ -79,22 +93,12 @@ server.listen(port, () => {
|
|
|
79
93
|
console.log('This proxy is only accessible from localhost');
|
|
80
94
|
});
|
|
81
95
|
|
|
82
|
-
for (let url of pin_urls) proxy_url(url)
|
|
83
|
-
pindex_urls.forEach(async url => {
|
|
84
|
-
let prefix = new URL(url).origin
|
|
85
|
-
while (true) {
|
|
86
|
-
let urls = await (await fetch(url)).json()
|
|
87
|
-
for (let url of urls) proxy_url(prefix + url)
|
|
88
|
-
await new Promise(done => setTimeout(done, 1000 * 60 * 60))
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
braid_text.list().then(x => {
|
|
93
|
-
for (let xx of x) proxy_url(xx)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
96
|
////////////////////////////////
|
|
97
97
|
|
|
98
|
+
function normalize_url(url) {
|
|
99
|
+
return url.replace(/(\/index|\/)+$/, '')
|
|
100
|
+
}
|
|
101
|
+
|
|
98
102
|
async function proxy_url(url) {
|
|
99
103
|
let chain = proxy_url.chain || (proxy_url.chain = Promise.resolve())
|
|
100
104
|
|
|
@@ -115,7 +119,7 @@ async function proxy_url(url) {
|
|
|
115
119
|
await require("fs").promises.mkdir(path, { recursive: true })
|
|
116
120
|
|
|
117
121
|
while (await is_dir(partial))
|
|
118
|
-
partial = require("path").join(partial, 'index
|
|
122
|
+
partial = require("path").join(partial, 'index')
|
|
119
123
|
|
|
120
124
|
await require("fs").promises.writeFile(partial, save)
|
|
121
125
|
break
|
|
@@ -125,8 +129,8 @@ async function proxy_url(url) {
|
|
|
125
129
|
}))
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
// normalize url by removing any trailing /index
|
|
129
|
-
let normalized_url = url
|
|
132
|
+
// normalize url by removing any trailing /index/index/
|
|
133
|
+
let normalized_url = normalize_url(url)
|
|
130
134
|
let wasnt_normal = normalized_url != url
|
|
131
135
|
url = normalized_url
|
|
132
136
|
|
|
@@ -134,18 +138,114 @@ async function proxy_url(url) {
|
|
|
134
138
|
if (proxy_url.cache[url]) return
|
|
135
139
|
proxy_url.cache[url] = true
|
|
136
140
|
|
|
141
|
+
console.log(`proxy_url: ${url}`)
|
|
142
|
+
|
|
137
143
|
let path = url.replace(/^https?:\/\//, '')
|
|
138
144
|
let fullpath = require("path").join(proxy_base, path)
|
|
139
145
|
|
|
140
|
-
// if we're accessing /blah/index
|
|
146
|
+
// if we're accessing /blah/index, it will be normalized to /blah,
|
|
141
147
|
// but we still want to create a directory out of blah in this case
|
|
142
|
-
if (wasnt_normal && !(await is_dir(fullpath))) ensure_path(fullpath)
|
|
148
|
+
if (wasnt_normal && !(await is_dir(fullpath))) await ensure_path(fullpath)
|
|
143
149
|
|
|
144
|
-
|
|
150
|
+
await ensure_path(require("path").dirname(fullpath))
|
|
145
151
|
|
|
146
|
-
|
|
152
|
+
await require("fs").promises.mkdir(proxy_base_support, { recursive: true })
|
|
153
|
+
|
|
154
|
+
async function get_fullpath() {
|
|
155
|
+
let p = fullpath
|
|
156
|
+
while (await is_dir(p)) p = require("path").join(p, 'index')
|
|
157
|
+
return p
|
|
158
|
+
}
|
|
147
159
|
|
|
148
160
|
let peer = Math.random().toString(36).slice(2)
|
|
161
|
+
var char_counter = -1
|
|
162
|
+
let file_last_version = null
|
|
163
|
+
let file_last_text = null
|
|
164
|
+
let file_needs_reading = true
|
|
165
|
+
let file_needs_writing = null
|
|
166
|
+
let file_loop_pump_lock = 0
|
|
167
|
+
|
|
168
|
+
function signal_file_needs_reading() {
|
|
169
|
+
file_needs_reading = true
|
|
170
|
+
file_loop_pump()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function signal_file_needs_writing() {
|
|
174
|
+
file_needs_writing = true
|
|
175
|
+
file_loop_pump()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function send_out(stuff) {
|
|
179
|
+
await braid_fetch_wrapper(url, {
|
|
180
|
+
headers: {
|
|
181
|
+
"Merge-Type": "dt",
|
|
182
|
+
"Content-Type": 'text/plain',
|
|
183
|
+
...(cookie ? { "Cookie": cookie } : {}),
|
|
184
|
+
},
|
|
185
|
+
method: "PUT",
|
|
186
|
+
retry: true,
|
|
187
|
+
...stuff
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
file_loop_pump()
|
|
192
|
+
async function file_loop_pump() {
|
|
193
|
+
if (file_loop_pump_lock) return
|
|
194
|
+
file_loop_pump_lock++
|
|
195
|
+
|
|
196
|
+
if (file_last_version === null) {
|
|
197
|
+
try {
|
|
198
|
+
file_last_version = JSON.parse(await require('fs').promises.readFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), { encoding: 'utf8' }))
|
|
199
|
+
file_last_text = (await braid_text.get(url, { version: file_last_version })).body
|
|
200
|
+
file_needs_writing = !v_eq(file_last_version, (await braid_text.get(url, {})).version)
|
|
201
|
+
} catch (e) {
|
|
202
|
+
file_last_version = []
|
|
203
|
+
file_last_text = ''
|
|
204
|
+
file_needs_writing = true
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
while (file_needs_reading || file_needs_writing) {
|
|
209
|
+
if (file_needs_reading) {
|
|
210
|
+
file_needs_reading = false
|
|
211
|
+
|
|
212
|
+
let text = ''
|
|
213
|
+
try { text = await require('fs').promises.readFile(await get_fullpath(), { encoding: 'utf8' }) } catch (e) { }
|
|
214
|
+
|
|
215
|
+
var patches = diff(file_last_text, text)
|
|
216
|
+
if (patches.length) {
|
|
217
|
+
// convert from js-indicies to code-points
|
|
218
|
+
char_counter += patches_to_code_points(patches, file_last_text)
|
|
219
|
+
|
|
220
|
+
file_last_text = text
|
|
221
|
+
|
|
222
|
+
var version = [peer + "-" + char_counter]
|
|
223
|
+
var parents = file_last_version
|
|
224
|
+
file_last_version = version
|
|
225
|
+
|
|
226
|
+
send_out({ version, parents, patches, peer })
|
|
227
|
+
|
|
228
|
+
await braid_text.put(url, { version, parents, patches, peer })
|
|
229
|
+
|
|
230
|
+
await require('fs').promises.writeFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), JSON.stringify(file_last_version))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (file_needs_writing) {
|
|
234
|
+
file_needs_writing = false
|
|
235
|
+
|
|
236
|
+
console.log(`writing file ${await get_fullpath()}`)
|
|
237
|
+
|
|
238
|
+
let { version, body } = await braid_text.get(url, {})
|
|
239
|
+
if (!v_eq(version, file_last_version)) {
|
|
240
|
+
file_last_version = version
|
|
241
|
+
file_last_text = body
|
|
242
|
+
await require('fs').promises.writeFile(await get_fullpath(), file_last_text)
|
|
243
|
+
await require('fs').promises.writeFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), JSON.stringify(file_last_version))
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
file_loop_pump_lock--
|
|
248
|
+
}
|
|
149
249
|
|
|
150
250
|
braid_fetch_wrapper(url, {
|
|
151
251
|
headers: {
|
|
@@ -160,81 +260,18 @@ async function proxy_url(url) {
|
|
|
160
260
|
},
|
|
161
261
|
peer
|
|
162
262
|
}).then(x => {
|
|
163
|
-
x.subscribe(update => {
|
|
263
|
+
x.subscribe(async update => {
|
|
164
264
|
// console.log(`update: ${JSON.stringify(update, null, 4)}`)
|
|
165
265
|
if (update.version.length == 0) return;
|
|
166
266
|
|
|
167
|
-
braid_text.put(url, { ...update, peer })
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// try a HEAD without subscribe to get the version
|
|
172
|
-
braid_fetch_wrapper(url, {
|
|
173
|
-
method: 'HEAD',
|
|
174
|
-
headers: { Accept: 'text/plain' },
|
|
175
|
-
retry: true,
|
|
176
|
-
}).then(async head_res => {
|
|
177
|
-
let parents = head_res.headers.get('version') ?
|
|
178
|
-
JSON.parse(`[${head_res.headers.get('version')}]`) :
|
|
179
|
-
null
|
|
267
|
+
await braid_text.put(url, { ...update, peer })
|
|
180
268
|
|
|
181
|
-
|
|
182
|
-
braid_text.get(url, {
|
|
183
|
-
parents,
|
|
184
|
-
merge_type: 'dt',
|
|
185
|
-
peer,
|
|
186
|
-
subscribe: async ({ version, parents, body, patches }) => {
|
|
187
|
-
if (version.length == 0) return;
|
|
188
|
-
|
|
189
|
-
// console.log(`local got: ${JSON.stringify({ version, parents, body, patches }, null, 4)}`)
|
|
190
|
-
// console.log(`cookie = ${cookie}`)
|
|
191
|
-
|
|
192
|
-
await braid_fetch_wrapper(url, {
|
|
193
|
-
headers: {
|
|
194
|
-
"Merge-Type": "dt",
|
|
195
|
-
"Content-Type": 'text/plain',
|
|
196
|
-
...(cookie ? { "Cookie": cookie } : {}),
|
|
197
|
-
},
|
|
198
|
-
method: "PUT",
|
|
199
|
-
retry: true,
|
|
200
|
-
version, parents, body, patches,
|
|
201
|
-
peer
|
|
202
|
-
})
|
|
203
|
-
},
|
|
269
|
+
signal_file_needs_writing()
|
|
204
270
|
})
|
|
205
271
|
})
|
|
206
272
|
|
|
207
|
-
await ensure_path(require("path").dirname(fullpath))
|
|
208
|
-
|
|
209
|
-
async function get_fullpath() {
|
|
210
|
-
let p = fullpath
|
|
211
|
-
while (await is_dir(p)) p = require("path").join(p, 'index.html')
|
|
212
|
-
return p
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
let simpleton = simpleton_client(url, {
|
|
216
|
-
apply_remote_update: async ({ state, patches }) => {
|
|
217
|
-
return await (chain = chain.then(async () => {
|
|
218
|
-
console.log(`writing file ${await get_fullpath()}`)
|
|
219
|
-
|
|
220
|
-
if (state !== undefined) last_text = state
|
|
221
|
-
else last_text = apply_patches(last_text, patches)
|
|
222
|
-
await require('fs').promises.writeFile(await get_fullpath(), last_text)
|
|
223
|
-
return last_text
|
|
224
|
-
}))
|
|
225
|
-
},
|
|
226
|
-
generate_local_diff_update: async (_) => {
|
|
227
|
-
return await (chain = chain.then(async () => {
|
|
228
|
-
let text = await require('fs').promises.readFile(await get_fullpath(), { encoding: 'utf8' })
|
|
229
|
-
var patches = diff(last_text, text)
|
|
230
|
-
last_text = text
|
|
231
|
-
return patches.length ? { patches, new_state: last_text } : null
|
|
232
|
-
}))
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
|
|
236
273
|
if (!proxy_url.path_to_func) proxy_url.path_to_func = {}
|
|
237
|
-
proxy_url.path_to_func[path] =
|
|
274
|
+
proxy_url.path_to_func[path] = signal_file_needs_reading
|
|
238
275
|
|
|
239
276
|
if (!proxy_url.chokidar) {
|
|
240
277
|
proxy_url.chokidar = true
|
|
@@ -242,12 +279,44 @@ async function proxy_url(url) {
|
|
|
242
279
|
path = require('path').relative(proxy_base, path)
|
|
243
280
|
console.log(`path changed: ${path}`)
|
|
244
281
|
|
|
245
|
-
path = path
|
|
246
|
-
console.log(`normalized path: ${path}`)
|
|
282
|
+
path = normalize_url(path)
|
|
283
|
+
// console.log(`normalized path: ${path}`)
|
|
247
284
|
|
|
248
285
|
proxy_url.path_to_func[path]()
|
|
249
286
|
});
|
|
250
287
|
}
|
|
288
|
+
|
|
289
|
+
// try a HEAD without subscribe to get the version
|
|
290
|
+
let parents = null
|
|
291
|
+
try {
|
|
292
|
+
let head_res = await braid_fetch_wrapper(url, {
|
|
293
|
+
method: 'HEAD',
|
|
294
|
+
headers: { Accept: 'text/plain' },
|
|
295
|
+
retry: true,
|
|
296
|
+
})
|
|
297
|
+
parents = head_res.headers.get('version') ?
|
|
298
|
+
JSON.parse(`[${head_res.headers.get('version')}]`) :
|
|
299
|
+
null
|
|
300
|
+
} catch (e) {
|
|
301
|
+
console.log('HEAD failed: ', e)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// now get everything since then, and send it back..
|
|
305
|
+
braid_text.get(url, {
|
|
306
|
+
parents,
|
|
307
|
+
merge_type: 'dt',
|
|
308
|
+
peer,
|
|
309
|
+
subscribe: async ({ version, parents, body, patches }) => {
|
|
310
|
+
if (version.length == 0) return;
|
|
311
|
+
|
|
312
|
+
// console.log(`local got: ${JSON.stringify({ version, parents, body, patches }, null, 4)}`)
|
|
313
|
+
// console.log(`cookie = ${cookie}`)
|
|
314
|
+
|
|
315
|
+
signal_file_needs_writing()
|
|
316
|
+
|
|
317
|
+
send_out({ version, parents, body, patches, peer })
|
|
318
|
+
},
|
|
319
|
+
})
|
|
251
320
|
}
|
|
252
321
|
|
|
253
322
|
async function is_dir(p) {
|
|
@@ -291,118 +360,30 @@ function free_the_cors(req, res) {
|
|
|
291
360
|
}
|
|
292
361
|
}
|
|
293
362
|
|
|
294
|
-
function
|
|
295
|
-
let
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
p.range[1] += offset;
|
|
299
|
-
offset -= p.range[1] - p.range[0];
|
|
300
|
-
offset += p.content.length;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
let result = originalString;
|
|
304
|
-
|
|
363
|
+
function patches_to_code_points(patches, prev_state) {
|
|
364
|
+
let char_counter = 0
|
|
365
|
+
let c = 0
|
|
366
|
+
let i = 0
|
|
305
367
|
for (let p of patches) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
p.content +
|
|
310
|
-
result.substring(range[1]);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return result;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function simpleton_client(url, { apply_remote_update, generate_local_diff_update, content_type }) {
|
|
317
|
-
var peer = Math.random().toString(36).slice(2)
|
|
318
|
-
var current_version = []
|
|
319
|
-
var prev_state = ""
|
|
320
|
-
var char_counter = -1
|
|
321
|
-
var chain = Promise.resolve()
|
|
322
|
-
var queued_changes = 0
|
|
323
|
-
|
|
324
|
-
braid_text.get(url, {
|
|
325
|
-
peer,
|
|
326
|
-
subscribe: (update) => {
|
|
327
|
-
chain = chain.then(async () => {
|
|
328
|
-
// Only accept the update if its parents == our current version
|
|
329
|
-
update.parents.sort()
|
|
330
|
-
if (current_version.length === update.parents.length
|
|
331
|
-
&& current_version.every((v, i) => v === update.parents[i])) {
|
|
332
|
-
current_version = update.version.sort()
|
|
333
|
-
update.state = update.body
|
|
334
|
-
|
|
335
|
-
if (update.patches) {
|
|
336
|
-
for (let p of update.patches) p.range = p.range.match(/\d+/g).map((x) => 1 * x)
|
|
337
|
-
update.patches.sort((a, b) => a.range[0] - b.range[0])
|
|
338
|
-
|
|
339
|
-
// convert from code-points to js-indicies
|
|
340
|
-
let c = 0
|
|
341
|
-
let i = 0
|
|
342
|
-
for (let p of update.patches) {
|
|
343
|
-
while (c < p.range[0]) {
|
|
344
|
-
i += get_char_size(prev_state, i)
|
|
345
|
-
c++
|
|
346
|
-
}
|
|
347
|
-
p.range[0] = i
|
|
348
|
-
|
|
349
|
-
while (c < p.range[1]) {
|
|
350
|
-
i += get_char_size(prev_state, i)
|
|
351
|
-
c++
|
|
352
|
-
}
|
|
353
|
-
p.range[1] = i
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
prev_state = await apply_remote_update(update)
|
|
358
|
-
}
|
|
359
|
-
})
|
|
368
|
+
while (i < p.range[0]) {
|
|
369
|
+
i += get_char_size(prev_state, i)
|
|
370
|
+
c++
|
|
360
371
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
changed: () => {
|
|
365
|
-
if (queued_changes) return
|
|
366
|
-
queued_changes++
|
|
367
|
-
chain = chain.then(async () => {
|
|
368
|
-
queued_changes--
|
|
369
|
-
var update = await generate_local_diff_update(prev_state)
|
|
370
|
-
if (!update) return // Stop if there wasn't a change!
|
|
371
|
-
var { patches, new_state } = update
|
|
372
|
-
|
|
373
|
-
// convert from js-indicies to code-points
|
|
374
|
-
let c = 0
|
|
375
|
-
let i = 0
|
|
376
|
-
for (let p of patches) {
|
|
377
|
-
while (i < p.range[0]) {
|
|
378
|
-
i += get_char_size(prev_state, i)
|
|
379
|
-
c++
|
|
380
|
-
}
|
|
381
|
-
p.range[0] = c
|
|
382
|
-
|
|
383
|
-
while (i < p.range[1]) {
|
|
384
|
-
i += get_char_size(prev_state, i)
|
|
385
|
-
c++
|
|
386
|
-
}
|
|
387
|
-
p.range[1] = c
|
|
372
|
+
p.range[0] = c
|
|
388
373
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
var version = [peer + "-" + char_counter]
|
|
374
|
+
while (i < p.range[1]) {
|
|
375
|
+
i += get_char_size(prev_state, i)
|
|
376
|
+
c++
|
|
377
|
+
}
|
|
378
|
+
p.range[1] = c
|
|
397
379
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
prev_state = new_state
|
|
380
|
+
char_counter += p.range[1] - p.range[0]
|
|
381
|
+
char_counter += count_code_points(p.content)
|
|
401
382
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
383
|
+
p.unit = "text"
|
|
384
|
+
p.range = `[${p.range[0]}:${p.range[1]}]`
|
|
405
385
|
}
|
|
386
|
+
return char_counter
|
|
406
387
|
}
|
|
407
388
|
|
|
408
389
|
function get_char_size(s, i) {
|
|
@@ -456,4 +437,8 @@ async function braid_fetch_wrapper(url, params) {
|
|
|
456
437
|
}
|
|
457
438
|
})
|
|
458
439
|
}
|
|
459
|
-
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function v_eq(v1, v2) {
|
|
443
|
+
return v1.length == v2.length && v1.every((x, i) => x == v2[i])
|
|
444
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braidfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "braid technology synchronizing files and webpages",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braidfs",
|
|
7
7
|
"homepage": "https://braid.org",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"braid-http": "^0.3.20",
|
|
10
|
-
"braid-text": "^0.0.
|
|
10
|
+
"braid-text": "^0.0.25",
|
|
11
11
|
"chokidar": "^3.6.0"
|
|
12
12
|
}
|
|
13
13
|
}
|