@plinth-music/cli 0.2.0 → 0.3.0
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/dist/cli.js +1214 -336
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4102,11 +4102,11 @@ var require_excerpt = __commonJS((exports, module) => {
|
|
|
4102
4102
|
if (typeof opts.excerpt === "function") {
|
|
4103
4103
|
return opts.excerpt(file2, opts);
|
|
4104
4104
|
}
|
|
4105
|
-
const
|
|
4106
|
-
if (
|
|
4105
|
+
const sep3 = file2.data.excerpt_separator || opts.excerpt_separator;
|
|
4106
|
+
if (sep3 == null && (opts.excerpt === false || opts.excerpt == null)) {
|
|
4107
4107
|
return file2;
|
|
4108
4108
|
}
|
|
4109
|
-
const delimiter = typeof opts.excerpt === "string" ? opts.excerpt :
|
|
4109
|
+
const delimiter = typeof opts.excerpt === "string" ? opts.excerpt : sep3 || opts.delimiters[0];
|
|
4110
4110
|
const idx = file2.content.indexOf(delimiter);
|
|
4111
4111
|
if (idx !== -1) {
|
|
4112
4112
|
file2.excerpt = file2.content.slice(0, idx);
|
|
@@ -5570,15 +5570,15 @@ class AsyncTaskManager {
|
|
|
5570
5570
|
this.#browserFrame = browserFrame;
|
|
5571
5571
|
}
|
|
5572
5572
|
waitUntilComplete() {
|
|
5573
|
-
return new Promise((
|
|
5574
|
-
this.waitUntilCompleteResolvers.push({ resolve, reject });
|
|
5573
|
+
return new Promise((resolve2, reject) => {
|
|
5574
|
+
this.waitUntilCompleteResolvers.push({ resolve: resolve2, reject });
|
|
5575
5575
|
this.resolveWhenComplete();
|
|
5576
5576
|
});
|
|
5577
5577
|
}
|
|
5578
5578
|
abort() {
|
|
5579
5579
|
if (this.destroyed) {
|
|
5580
|
-
return new Promise((
|
|
5581
|
-
this.waitUntilCompleteResolvers.push({ resolve, reject });
|
|
5580
|
+
return new Promise((resolve2, reject) => {
|
|
5581
|
+
this.waitUntilCompleteResolvers.push({ resolve: resolve2, reject });
|
|
5582
5582
|
this.resolveWhenComplete();
|
|
5583
5583
|
});
|
|
5584
5584
|
}
|
|
@@ -5586,8 +5586,8 @@ class AsyncTaskManager {
|
|
|
5586
5586
|
}
|
|
5587
5587
|
destroy() {
|
|
5588
5588
|
if (this.destroyed) {
|
|
5589
|
-
return new Promise((
|
|
5590
|
-
this.waitUntilCompleteResolvers.push({ resolve, reject });
|
|
5589
|
+
return new Promise((resolve2, reject) => {
|
|
5590
|
+
this.waitUntilCompleteResolvers.push({ resolve: resolve2, reject });
|
|
5591
5591
|
this.resolveWhenComplete();
|
|
5592
5592
|
});
|
|
5593
5593
|
}
|
|
@@ -5773,8 +5773,8 @@ The following traces were recorded:
|
|
|
5773
5773
|
for (const key of Object.keys(runningTasks)) {
|
|
5774
5774
|
runningTasks[key](destroy2);
|
|
5775
5775
|
}
|
|
5776
|
-
return new Promise((
|
|
5777
|
-
this.waitUntilCompleteResolvers.push({ resolve, reject });
|
|
5776
|
+
return new Promise((resolve2, reject) => {
|
|
5777
|
+
this.waitUntilCompleteResolvers.push({ resolve: resolve2, reject });
|
|
5778
5778
|
this.resolveWhenComplete();
|
|
5779
5779
|
});
|
|
5780
5780
|
}
|
|
@@ -16194,12 +16194,12 @@ var init_CustomElementRegistry = __esm(() => {
|
|
|
16194
16194
|
if (this.get(name2)) {
|
|
16195
16195
|
return Promise.resolve();
|
|
16196
16196
|
}
|
|
16197
|
-
return new Promise((
|
|
16197
|
+
return new Promise((resolve2) => {
|
|
16198
16198
|
const callbacks2 = this[callbacks].get(name2);
|
|
16199
16199
|
if (callbacks2) {
|
|
16200
|
-
callbacks2.push(
|
|
16200
|
+
callbacks2.push(resolve2);
|
|
16201
16201
|
} else {
|
|
16202
|
-
this[callbacks].set(name2, [
|
|
16202
|
+
this[callbacks].set(name2, [resolve2]);
|
|
16203
16203
|
}
|
|
16204
16204
|
});
|
|
16205
16205
|
}
|
|
@@ -18788,7 +18788,7 @@ class Fetch {
|
|
|
18788
18788
|
return true;
|
|
18789
18789
|
}
|
|
18790
18790
|
sendRequest() {
|
|
18791
|
-
return new Promise((
|
|
18791
|
+
return new Promise((resolve2, reject) => {
|
|
18792
18792
|
const taskID = this.#browserFrame[asyncTaskManager].startTask(() => this.onAsyncTaskManagerAbort());
|
|
18793
18793
|
if (this.resolve) {
|
|
18794
18794
|
throw new this.#window.Error("Fetch already sent.");
|
|
@@ -18812,7 +18812,7 @@ class Fetch {
|
|
|
18812
18812
|
if (returnResponse instanceof Response && !returnResponse.ok) {
|
|
18813
18813
|
this.#browserFrame.page.console.error(`${this.request.method} ${this.request.url} ${returnResponse.status} (${returnResponse.statusText})`);
|
|
18814
18814
|
}
|
|
18815
|
-
|
|
18815
|
+
resolve2(returnResponse);
|
|
18816
18816
|
};
|
|
18817
18817
|
this.reject = (error53) => {
|
|
18818
18818
|
this.#browserFrame[asyncTaskManager].endTask(taskID);
|
|
@@ -27205,9 +27205,9 @@ class BrowserFrameFactory {
|
|
|
27205
27205
|
return Promise.resolve();
|
|
27206
27206
|
}
|
|
27207
27207
|
frame.closed = true;
|
|
27208
|
-
return new Promise((
|
|
27208
|
+
return new Promise((resolve2, reject) => {
|
|
27209
27209
|
if (!frame.window) {
|
|
27210
|
-
|
|
27210
|
+
resolve2();
|
|
27211
27211
|
return;
|
|
27212
27212
|
}
|
|
27213
27213
|
if (frame.parentFrame) {
|
|
@@ -27227,7 +27227,7 @@ class BrowserFrameFactory {
|
|
|
27227
27227
|
if (frame[listeners]) {
|
|
27228
27228
|
frame[listeners].navigation = [];
|
|
27229
27229
|
}
|
|
27230
|
-
|
|
27230
|
+
resolve2();
|
|
27231
27231
|
}).catch((error53) => reject(error53));
|
|
27232
27232
|
if (frame.window) {
|
|
27233
27233
|
frame.window[destroy]();
|
|
@@ -27245,7 +27245,7 @@ class BrowserFrameFactory {
|
|
|
27245
27245
|
if (frame[listeners]) {
|
|
27246
27246
|
frame[listeners].navigation = [];
|
|
27247
27247
|
}
|
|
27248
|
-
|
|
27248
|
+
resolve2();
|
|
27249
27249
|
}).catch((error53) => reject(error53));
|
|
27250
27250
|
if (frame.window) {
|
|
27251
27251
|
frame.window[destroy]();
|
|
@@ -27351,15 +27351,15 @@ class BrowserFrameNavigator {
|
|
|
27351
27351
|
const asyncTaskManager3 = frame[asyncTaskManager];
|
|
27352
27352
|
const taskID2 = readyStateManager3.startTask();
|
|
27353
27353
|
const code = targetURL.href.replace("javascript:", "");
|
|
27354
|
-
await new Promise((
|
|
27354
|
+
await new Promise((resolve2) => {
|
|
27355
27355
|
frame.window.requestAnimationFrame(() => {
|
|
27356
27356
|
const immediate = setImmediate(() => {
|
|
27357
27357
|
asyncTaskManager3.endTask(taskID3);
|
|
27358
|
-
|
|
27358
|
+
resolve2(null);
|
|
27359
27359
|
});
|
|
27360
27360
|
const taskID3 = asyncTaskManager3.startTask(() => () => {
|
|
27361
27361
|
clearImmediate(immediate);
|
|
27362
|
-
|
|
27362
|
+
resolve2(null);
|
|
27363
27363
|
});
|
|
27364
27364
|
frame.window[evaluateScript](code, { filename: frame.url });
|
|
27365
27365
|
});
|
|
@@ -27430,7 +27430,7 @@ class BrowserFrameNavigator {
|
|
|
27430
27430
|
if (frame.page.context.browser.settings.navigation.beforeContentCallback) {
|
|
27431
27431
|
frame.page.context.browser.settings.navigation.beforeContentCallback(frame.window);
|
|
27432
27432
|
}
|
|
27433
|
-
await new Promise((
|
|
27433
|
+
await new Promise((resolve2) => frame.page.mainFrame.window.requestAnimationFrame(resolve2));
|
|
27434
27434
|
resolveNavigationListeners();
|
|
27435
27435
|
return null;
|
|
27436
27436
|
}
|
|
@@ -27499,16 +27499,16 @@ class BrowserFrameNavigator {
|
|
|
27499
27499
|
if (frame.page.context.browser.settings.navigation.beforeContentCallback) {
|
|
27500
27500
|
frame.page.context.browser.settings.navigation.beforeContentCallback(frame.window);
|
|
27501
27501
|
}
|
|
27502
|
-
await new Promise((
|
|
27502
|
+
await new Promise((resolve2) => {
|
|
27503
27503
|
frame.window.requestAnimationFrame(() => {
|
|
27504
27504
|
let immediate;
|
|
27505
27505
|
const taskID2 = asyncTaskManager2.startTask(() => () => {
|
|
27506
27506
|
clearImmediate(immediate);
|
|
27507
|
-
|
|
27507
|
+
resolve2(null);
|
|
27508
27508
|
});
|
|
27509
27509
|
immediate = setImmediate(() => {
|
|
27510
27510
|
asyncTaskManager2.endTask(taskID2);
|
|
27511
|
-
|
|
27511
|
+
resolve2(null);
|
|
27512
27512
|
});
|
|
27513
27513
|
frame.content = responseText;
|
|
27514
27514
|
});
|
|
@@ -27521,14 +27521,14 @@ class BrowserFrameNavigator {
|
|
|
27521
27521
|
const history2 = frame[history];
|
|
27522
27522
|
const historyItem = history2.items[history2.items.indexOf(history2.currentItem) - 1];
|
|
27523
27523
|
if (!historyItem) {
|
|
27524
|
-
return new Promise((
|
|
27524
|
+
return new Promise((resolve2) => {
|
|
27525
27525
|
frame.window.requestAnimationFrame(() => {
|
|
27526
27526
|
const listeners2 = frame[listeners].navigation;
|
|
27527
27527
|
frame[listeners].navigation = [];
|
|
27528
27528
|
for (const listener of listeners2) {
|
|
27529
27529
|
listener();
|
|
27530
27530
|
}
|
|
27531
|
-
|
|
27531
|
+
resolve2(null);
|
|
27532
27532
|
});
|
|
27533
27533
|
});
|
|
27534
27534
|
}
|
|
@@ -27561,14 +27561,14 @@ class BrowserFrameNavigator {
|
|
|
27561
27561
|
const history2 = frame[history];
|
|
27562
27562
|
const historyItem = history2.items[history2.items.indexOf(history2.currentItem) + 1];
|
|
27563
27563
|
if (!historyItem) {
|
|
27564
|
-
return new Promise((
|
|
27564
|
+
return new Promise((resolve2) => {
|
|
27565
27565
|
frame.window.requestAnimationFrame(() => {
|
|
27566
27566
|
const listeners2 = frame[listeners].navigation;
|
|
27567
27567
|
frame[listeners].navigation = [];
|
|
27568
27568
|
for (const listener of listeners2) {
|
|
27569
27569
|
listener();
|
|
27570
27570
|
}
|
|
27571
|
-
|
|
27571
|
+
resolve2(null);
|
|
27572
27572
|
});
|
|
27573
27573
|
});
|
|
27574
27574
|
}
|
|
@@ -27606,14 +27606,14 @@ class BrowserFrameNavigator {
|
|
|
27606
27606
|
const toIndex = fromIndex + steps;
|
|
27607
27607
|
const historyItem = history2.items[toIndex];
|
|
27608
27608
|
if (!historyItem) {
|
|
27609
|
-
return new Promise((
|
|
27609
|
+
return new Promise((resolve2) => {
|
|
27610
27610
|
frame.window.requestAnimationFrame(() => {
|
|
27611
27611
|
const listeners2 = frame[listeners].navigation;
|
|
27612
27612
|
frame[listeners].navigation = [];
|
|
27613
27613
|
for (const listener of listeners2) {
|
|
27614
27614
|
listener();
|
|
27615
27615
|
}
|
|
27616
|
-
|
|
27616
|
+
resolve2(null);
|
|
27617
27617
|
});
|
|
27618
27618
|
});
|
|
27619
27619
|
}
|
|
@@ -30379,12 +30379,12 @@ class UnresolvedModule {
|
|
|
30379
30379
|
async preload() {
|
|
30380
30380
|
throw new this.#window.TypeError("Unresolved module. We should never end up here.");
|
|
30381
30381
|
}
|
|
30382
|
-
addResolveListener(
|
|
30382
|
+
addResolveListener(resolve2, reject) {
|
|
30383
30383
|
if (this.#error) {
|
|
30384
30384
|
reject(this.#error);
|
|
30385
30385
|
return;
|
|
30386
30386
|
}
|
|
30387
|
-
this.#hooks.push({ resolve, reject });
|
|
30387
|
+
this.#hooks.push({ resolve: resolve2, reject });
|
|
30388
30388
|
}
|
|
30389
30389
|
resolve(error53) {
|
|
30390
30390
|
if (error53) {
|
|
@@ -31150,8 +31150,8 @@ var init_ECMAScriptModule = __esm(() => {
|
|
|
31150
31150
|
}
|
|
31151
31151
|
return this.#exports;
|
|
31152
31152
|
}
|
|
31153
|
-
return new Promise((
|
|
31154
|
-
this.#evaluateQueue.push(
|
|
31153
|
+
return new Promise((resolve2) => {
|
|
31154
|
+
this.#evaluateQueue.push(resolve2);
|
|
31155
31155
|
});
|
|
31156
31156
|
}
|
|
31157
31157
|
if (this.#exports) {
|
|
@@ -31206,8 +31206,8 @@ var init_ECMAScriptModule = __esm(() => {
|
|
|
31206
31206
|
});
|
|
31207
31207
|
const evaluateQueue = this.#evaluateQueue;
|
|
31208
31208
|
this.#evaluateQueue = null;
|
|
31209
|
-
for (const
|
|
31210
|
-
|
|
31209
|
+
for (const resolve2 of evaluateQueue) {
|
|
31210
|
+
resolve2(exports);
|
|
31211
31211
|
}
|
|
31212
31212
|
return exports;
|
|
31213
31213
|
}
|
|
@@ -31267,8 +31267,8 @@ class ModuleFactory {
|
|
|
31267
31267
|
const cached2 = window3[modules][type2].get(absoluteURLString);
|
|
31268
31268
|
if (cached2) {
|
|
31269
31269
|
if (cached2 instanceof UnresolvedModule) {
|
|
31270
|
-
await new Promise((
|
|
31271
|
-
cached2.addResolveListener(
|
|
31270
|
+
await new Promise((resolve2, reject) => {
|
|
31271
|
+
cached2.addResolveListener(resolve2, reject);
|
|
31272
31272
|
});
|
|
31273
31273
|
return window3[modules][type2].get(absoluteURLString);
|
|
31274
31274
|
}
|
|
@@ -31342,8 +31342,8 @@ class PreloadEntry {
|
|
|
31342
31342
|
error = null;
|
|
31343
31343
|
#callback = null;
|
|
31344
31344
|
onResponseAvailable() {
|
|
31345
|
-
return new Promise((
|
|
31346
|
-
this.#callback = { resolve, reject };
|
|
31345
|
+
return new Promise((resolve2, reject) => {
|
|
31346
|
+
this.#callback = { resolve: resolve2, reject };
|
|
31347
31347
|
});
|
|
31348
31348
|
}
|
|
31349
31349
|
responseAvailable(error53, response) {
|
|
@@ -54743,14 +54743,14 @@ var init_BrowserFrame = __esm(() => {
|
|
|
54743
54743
|
]);
|
|
54744
54744
|
}
|
|
54745
54745
|
waitForNavigation() {
|
|
54746
|
-
return new Promise((
|
|
54746
|
+
return new Promise((resolve2) => this[listeners].navigation.push(resolve2));
|
|
54747
54747
|
}
|
|
54748
54748
|
abort() {
|
|
54749
54749
|
if (!this.childFrames.length) {
|
|
54750
54750
|
return this[asyncTaskManager].abort();
|
|
54751
54751
|
}
|
|
54752
|
-
return new Promise((
|
|
54753
|
-
Promise.all(this.childFrames.map((frame) => frame.abort()).concat([this[asyncTaskManager].abort()])).then(() =>
|
|
54752
|
+
return new Promise((resolve2, reject) => {
|
|
54753
|
+
Promise.all(this.childFrames.map((frame) => frame.abort()).concat([this[asyncTaskManager].abort()])).then(() => resolve2()).catch(reject);
|
|
54754
54754
|
});
|
|
54755
54755
|
}
|
|
54756
54756
|
evaluate(script) {
|
|
@@ -55205,12 +55205,12 @@ var init_BrowserContext = __esm(() => {
|
|
|
55205
55205
|
await Promise.all(this.pages.map((page) => page.waitUntilComplete()));
|
|
55206
55206
|
}
|
|
55207
55207
|
abort() {
|
|
55208
|
-
return new Promise((
|
|
55208
|
+
return new Promise((resolve2, reject) => {
|
|
55209
55209
|
if (!this.pages.length) {
|
|
55210
|
-
|
|
55210
|
+
resolve2();
|
|
55211
55211
|
return;
|
|
55212
55212
|
}
|
|
55213
|
-
Promise.all(this.pages.slice().map((page) => page.abort())).then(() =>
|
|
55213
|
+
Promise.all(this.pages.slice().map((page) => page.abort())).then(() => resolve2()).catch((error53) => reject(error53));
|
|
55214
55214
|
});
|
|
55215
55215
|
}
|
|
55216
55216
|
newPage() {
|
|
@@ -55515,14 +55515,14 @@ var init_DetachedBrowserFrame = __esm(() => {
|
|
|
55515
55515
|
]);
|
|
55516
55516
|
}
|
|
55517
55517
|
waitForNavigation() {
|
|
55518
|
-
return new Promise((
|
|
55518
|
+
return new Promise((resolve2) => this[listeners].navigation.push(resolve2));
|
|
55519
55519
|
}
|
|
55520
55520
|
abort() {
|
|
55521
55521
|
if (!this.childFrames.length) {
|
|
55522
55522
|
return this[asyncTaskManager].abort();
|
|
55523
55523
|
}
|
|
55524
|
-
return new Promise((
|
|
55525
|
-
Promise.all(this.childFrames.map((frame) => frame.abort()).concat([this[asyncTaskManager].abort()])).then(() =>
|
|
55524
|
+
return new Promise((resolve2, reject) => {
|
|
55525
|
+
Promise.all(this.childFrames.map((frame) => frame.abort()).concat([this[asyncTaskManager].abort()])).then(() => resolve2()).catch(reject);
|
|
55526
55526
|
});
|
|
55527
55527
|
}
|
|
55528
55528
|
evaluate(script) {
|
|
@@ -55605,13 +55605,13 @@ class DetachedBrowserPage {
|
|
|
55605
55605
|
this.mainFrame.url = url3;
|
|
55606
55606
|
}
|
|
55607
55607
|
close() {
|
|
55608
|
-
return new Promise((
|
|
55608
|
+
return new Promise((resolve2, reject) => {
|
|
55609
55609
|
const context = this.context;
|
|
55610
55610
|
BrowserPageUtility.closePage(this).then(() => {
|
|
55611
55611
|
if (context.pages[0] === this) {
|
|
55612
|
-
context.browser.close().then(
|
|
55612
|
+
context.browser.close().then(resolve2).catch(reject);
|
|
55613
55613
|
} else {
|
|
55614
|
-
|
|
55614
|
+
resolve2();
|
|
55615
55615
|
}
|
|
55616
55616
|
}).catch(reject);
|
|
55617
55617
|
});
|
|
@@ -55705,12 +55705,12 @@ var init_DetachedBrowserContext = __esm(() => {
|
|
|
55705
55705
|
await Promise.all(this.pages.map((page) => page.waitUntilComplete()));
|
|
55706
55706
|
}
|
|
55707
55707
|
abort() {
|
|
55708
|
-
return new Promise((
|
|
55708
|
+
return new Promise((resolve2, reject) => {
|
|
55709
55709
|
if (!this.pages.length) {
|
|
55710
|
-
|
|
55710
|
+
resolve2();
|
|
55711
55711
|
return;
|
|
55712
55712
|
}
|
|
55713
|
-
Promise.all(this.pages.slice().map((page) => page.abort())).then(() =>
|
|
55713
|
+
Promise.all(this.pages.slice().map((page) => page.abort())).then(() => resolve2()).catch((error53) => reject(error53));
|
|
55714
55714
|
});
|
|
55715
55715
|
}
|
|
55716
55716
|
newPage() {
|
|
@@ -55765,12 +55765,12 @@ var init_DetachedBrowser = __esm(() => {
|
|
|
55765
55765
|
await Promise.all(this.contexts.map((page) => page.waitUntilComplete()));
|
|
55766
55766
|
}
|
|
55767
55767
|
abort() {
|
|
55768
|
-
return new Promise((
|
|
55768
|
+
return new Promise((resolve2, reject) => {
|
|
55769
55769
|
if (!this.contexts.length) {
|
|
55770
|
-
|
|
55770
|
+
resolve2();
|
|
55771
55771
|
return;
|
|
55772
55772
|
}
|
|
55773
|
-
Promise.all(this.contexts.slice().map((context) => context.abort())).then(() =>
|
|
55773
|
+
Promise.all(this.contexts.slice().map((context) => context.abort())).then(() => resolve2()).catch((error53) => reject(error53));
|
|
55774
55774
|
});
|
|
55775
55775
|
}
|
|
55776
55776
|
newIncognitoContext() {
|
|
@@ -73049,7 +73049,7 @@ var require_DOMImplementation = __commonJS((exports, module) => {
|
|
|
73049
73049
|
utils.InvalidCharacterError();
|
|
73050
73050
|
return new DocumentType2(this.contextObject, qualifiedName, publicId2, systemId2);
|
|
73051
73051
|
},
|
|
73052
|
-
createDocument: function
|
|
73052
|
+
createDocument: function createDocument2(namespace, qualifiedName, doctype) {
|
|
73053
73053
|
var d3 = new Document2(false, null);
|
|
73054
73054
|
var e2;
|
|
73055
73055
|
if (qualifiedName)
|
|
@@ -73931,14 +73931,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
73931
73931
|
} else if (node.nodeType === 1) {
|
|
73932
73932
|
replacement = replacementForNode.call(self2, node);
|
|
73933
73933
|
}
|
|
73934
|
-
return
|
|
73934
|
+
return join4(output, replacement);
|
|
73935
73935
|
}, "");
|
|
73936
73936
|
}
|
|
73937
73937
|
function postProcess(output) {
|
|
73938
73938
|
var self2 = this;
|
|
73939
73939
|
this.rules.forEach(function(rule) {
|
|
73940
73940
|
if (typeof rule.append === "function") {
|
|
73941
|
-
output =
|
|
73941
|
+
output = join4(output, rule.append(self2.options));
|
|
73942
73942
|
}
|
|
73943
73943
|
});
|
|
73944
73944
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -73951,7 +73951,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
73951
73951
|
content2 = content2.trim();
|
|
73952
73952
|
return whitespace.leading + rule.replacement(content2, node, this.options) + whitespace.trailing;
|
|
73953
73953
|
}
|
|
73954
|
-
function
|
|
73954
|
+
function join4(output, replacement) {
|
|
73955
73955
|
var s1 = trimTrailingNewlines(output);
|
|
73956
73956
|
var s2 = trimLeadingNewlines(replacement);
|
|
73957
73957
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -75578,7 +75578,7 @@ async function runMain(cmd, opts = {}) {
|
|
|
75578
75578
|
// package.json
|
|
75579
75579
|
var package_default = {
|
|
75580
75580
|
name: "@plinth-music/cli",
|
|
75581
|
-
version: "0.
|
|
75581
|
+
version: "0.3.0",
|
|
75582
75582
|
description: "Plinth capstone sync daemon and CLI",
|
|
75583
75583
|
type: "module",
|
|
75584
75584
|
private: false,
|
|
@@ -89969,6 +89969,12 @@ var PatchConflictResponseSchema = exports_external.object({
|
|
|
89969
89969
|
code: exports_external.literal("precondition_failed"),
|
|
89970
89970
|
current_updated_at: exports_external.string().datetime({ offset: true })
|
|
89971
89971
|
});
|
|
89972
|
+
var SlugConflictResponseSchema = exports_external.object({
|
|
89973
|
+
error: exports_external.string(),
|
|
89974
|
+
code: exports_external.literal("slug_conflict"),
|
|
89975
|
+
existing: SyncDocumentLiveSchema
|
|
89976
|
+
});
|
|
89977
|
+
var DeleteDocumentResponseSchema = SyncDocumentTombstoneSchema;
|
|
89972
89978
|
|
|
89973
89979
|
// src/lib/api.ts
|
|
89974
89980
|
class ApiError extends Error {
|
|
@@ -90043,9 +90049,18 @@ async function getDocument(args) {
|
|
|
90043
90049
|
return parsed.data;
|
|
90044
90050
|
}
|
|
90045
90051
|
async function patchDocument(args) {
|
|
90046
|
-
const {
|
|
90047
|
-
|
|
90048
|
-
|
|
90052
|
+
const {
|
|
90053
|
+
baseUrl,
|
|
90054
|
+
pat,
|
|
90055
|
+
id,
|
|
90056
|
+
expectedUpdatedAt,
|
|
90057
|
+
body,
|
|
90058
|
+
frontmatter,
|
|
90059
|
+
slug,
|
|
90060
|
+
fetchImpl = fetch
|
|
90061
|
+
} = args;
|
|
90062
|
+
if (body === undefined && frontmatter === undefined && slug === undefined) {
|
|
90063
|
+
throw new Error("patchDocument requires at least one of body, frontmatter, or slug");
|
|
90049
90064
|
}
|
|
90050
90065
|
const url2 = `${baseUrl.replace(/\/$/, "")}/api/sync/documents/${id}`;
|
|
90051
90066
|
const payload = {};
|
|
@@ -90053,6 +90068,8 @@ async function patchDocument(args) {
|
|
|
90053
90068
|
payload.body = body;
|
|
90054
90069
|
if (frontmatter !== undefined)
|
|
90055
90070
|
payload.frontmatter = frontmatter;
|
|
90071
|
+
if (slug !== undefined)
|
|
90072
|
+
payload.slug = slug;
|
|
90056
90073
|
let res;
|
|
90057
90074
|
try {
|
|
90058
90075
|
res = await fetchImpl(url2, {
|
|
@@ -90082,11 +90099,25 @@ async function patchDocument(args) {
|
|
|
90082
90099
|
}
|
|
90083
90100
|
if (res.status === 409) {
|
|
90084
90101
|
const json2 = await res.json();
|
|
90085
|
-
const
|
|
90086
|
-
if (
|
|
90087
|
-
|
|
90102
|
+
const slugParse = SlugConflictResponseSchema.safeParse(json2);
|
|
90103
|
+
if (slugParse.success) {
|
|
90104
|
+
return {
|
|
90105
|
+
ok: false,
|
|
90106
|
+
code: 409,
|
|
90107
|
+
reason: "slug_conflict",
|
|
90108
|
+
existing: slugParse.data.existing
|
|
90109
|
+
};
|
|
90088
90110
|
}
|
|
90089
|
-
|
|
90111
|
+
const preconditionParse = PatchConflictResponseSchema.safeParse(json2);
|
|
90112
|
+
if (preconditionParse.success) {
|
|
90113
|
+
return {
|
|
90114
|
+
ok: false,
|
|
90115
|
+
code: 409,
|
|
90116
|
+
reason: "precondition_failed",
|
|
90117
|
+
currentUpdatedAt: preconditionParse.data.current_updated_at
|
|
90118
|
+
};
|
|
90119
|
+
}
|
|
90120
|
+
throw new ApiError(409, "patch document 409 body did not match either precondition_failed or slug_conflict shape");
|
|
90090
90121
|
}
|
|
90091
90122
|
if (res.status === 401) {
|
|
90092
90123
|
throw new ApiError(401, "Token was rejected by the server. Run `plinth login --workspace <slug>` to refresh.");
|
|
@@ -90143,11 +90174,135 @@ async function syncDocuments(args) {
|
|
|
90143
90174
|
}
|
|
90144
90175
|
return parsed.data;
|
|
90145
90176
|
}
|
|
90177
|
+
async function createDocument(args) {
|
|
90178
|
+
const {
|
|
90179
|
+
baseUrl,
|
|
90180
|
+
pat,
|
|
90181
|
+
slug,
|
|
90182
|
+
title,
|
|
90183
|
+
body,
|
|
90184
|
+
frontmatter,
|
|
90185
|
+
artist_id,
|
|
90186
|
+
project_id,
|
|
90187
|
+
fetchImpl = fetch
|
|
90188
|
+
} = args;
|
|
90189
|
+
const url2 = `${baseUrl.replace(/\/$/, "")}/api/sync/documents`;
|
|
90190
|
+
const payload = { slug, title, body, frontmatter };
|
|
90191
|
+
if (artist_id !== undefined)
|
|
90192
|
+
payload.artist_id = artist_id;
|
|
90193
|
+
if (project_id !== undefined)
|
|
90194
|
+
payload.project_id = project_id;
|
|
90195
|
+
let res;
|
|
90196
|
+
try {
|
|
90197
|
+
res = await fetchImpl(url2, {
|
|
90198
|
+
method: "POST",
|
|
90199
|
+
headers: {
|
|
90200
|
+
Authorization: `Bearer ${pat}`,
|
|
90201
|
+
Accept: "application/json",
|
|
90202
|
+
"Content-Type": "application/json"
|
|
90203
|
+
},
|
|
90204
|
+
body: JSON.stringify(payload)
|
|
90205
|
+
});
|
|
90206
|
+
} catch (err) {
|
|
90207
|
+
const msg = err instanceof Error ? err.message : "network error";
|
|
90208
|
+
throw new ApiError(0, `Could not reach ${url2}: ${msg}`);
|
|
90209
|
+
}
|
|
90210
|
+
if (res.status === 200 || res.status === 201) {
|
|
90211
|
+
const json2 = await res.json();
|
|
90212
|
+
const parsed = SyncDocumentResponseSchema.safeParse(json2);
|
|
90213
|
+
if (!parsed.success) {
|
|
90214
|
+
throw new ApiError(res.status, "create document response did not match the expected shape");
|
|
90215
|
+
}
|
|
90216
|
+
return { ok: true, doc: parsed.data };
|
|
90217
|
+
}
|
|
90218
|
+
if (res.status === 409) {
|
|
90219
|
+
const json2 = await res.json();
|
|
90220
|
+
const parsed = SlugConflictResponseSchema.safeParse(json2);
|
|
90221
|
+
if (!parsed.success) {
|
|
90222
|
+
throw new ApiError(409, "create document 409 body did not match slug_conflict shape");
|
|
90223
|
+
}
|
|
90224
|
+
return { ok: false, code: 409, existing: parsed.data.existing };
|
|
90225
|
+
}
|
|
90226
|
+
if (res.status === 401) {
|
|
90227
|
+
throw new ApiError(401, "Token was rejected by the server. Run `plinth login --workspace <slug>` to refresh.");
|
|
90228
|
+
}
|
|
90229
|
+
if (res.status === 429) {
|
|
90230
|
+
throw new ApiError(429, "Rate limited by the server. Wait a few seconds and retry.");
|
|
90231
|
+
}
|
|
90232
|
+
if (res.status === 400) {
|
|
90233
|
+
let serverMsg = "bad request";
|
|
90234
|
+
try {
|
|
90235
|
+
const json2 = await res.json();
|
|
90236
|
+
if (typeof json2.error === "string")
|
|
90237
|
+
serverMsg = json2.error;
|
|
90238
|
+
} catch {}
|
|
90239
|
+
throw new ApiError(400, serverMsg);
|
|
90240
|
+
}
|
|
90241
|
+
throw new ApiError(res.status, `create document failed with HTTP ${res.status}`);
|
|
90242
|
+
}
|
|
90243
|
+
async function deleteDocument(args) {
|
|
90244
|
+
const { baseUrl, pat, id, expectedUpdatedAt, fetchImpl = fetch } = args;
|
|
90245
|
+
const url2 = `${baseUrl.replace(/\/$/, "")}/api/sync/documents/${id}`;
|
|
90246
|
+
let res;
|
|
90247
|
+
try {
|
|
90248
|
+
res = await fetchImpl(url2, {
|
|
90249
|
+
method: "DELETE",
|
|
90250
|
+
headers: {
|
|
90251
|
+
Authorization: `Bearer ${pat}`,
|
|
90252
|
+
Accept: "application/json",
|
|
90253
|
+
"X-Plinth-Expected-Updated-At": expectedUpdatedAt
|
|
90254
|
+
}
|
|
90255
|
+
});
|
|
90256
|
+
} catch (err) {
|
|
90257
|
+
const msg = err instanceof Error ? err.message : "network error";
|
|
90258
|
+
throw new ApiError(0, `Could not reach ${url2}: ${msg}`);
|
|
90259
|
+
}
|
|
90260
|
+
if (res.status === 200) {
|
|
90261
|
+
const json2 = await res.json();
|
|
90262
|
+
const parsed = DeleteDocumentResponseSchema.safeParse(json2);
|
|
90263
|
+
if (!parsed.success) {
|
|
90264
|
+
throw new ApiError(200, "delete document response did not match the expected tombstone shape");
|
|
90265
|
+
}
|
|
90266
|
+
return { ok: true, tombstone: parsed.data };
|
|
90267
|
+
}
|
|
90268
|
+
if (res.status === 404) {
|
|
90269
|
+
return { ok: false, code: 404 };
|
|
90270
|
+
}
|
|
90271
|
+
if (res.status === 409) {
|
|
90272
|
+
const json2 = await res.json();
|
|
90273
|
+
const parsed = PatchConflictResponseSchema.safeParse(json2);
|
|
90274
|
+
if (!parsed.success) {
|
|
90275
|
+
throw new ApiError(409, "delete document 409 body did not match precondition_failed shape");
|
|
90276
|
+
}
|
|
90277
|
+
return {
|
|
90278
|
+
ok: false,
|
|
90279
|
+
code: 409,
|
|
90280
|
+
currentUpdatedAt: parsed.data.current_updated_at
|
|
90281
|
+
};
|
|
90282
|
+
}
|
|
90283
|
+
if (res.status === 401) {
|
|
90284
|
+
throw new ApiError(401, "Token was rejected by the server. Run `plinth login --workspace <slug>` to refresh.");
|
|
90285
|
+
}
|
|
90286
|
+
if (res.status === 429) {
|
|
90287
|
+
throw new ApiError(429, "Rate limited by the server. Wait a few seconds and retry.");
|
|
90288
|
+
}
|
|
90289
|
+
if (res.status === 400) {
|
|
90290
|
+
let serverMsg = "bad request";
|
|
90291
|
+
try {
|
|
90292
|
+
const json2 = await res.json();
|
|
90293
|
+
if (typeof json2.error === "string")
|
|
90294
|
+
serverMsg = json2.error;
|
|
90295
|
+
} catch {}
|
|
90296
|
+
throw new ApiError(400, serverMsg);
|
|
90297
|
+
}
|
|
90298
|
+
throw new ApiError(res.status, `delete document failed with HTTP ${res.status}`);
|
|
90299
|
+
}
|
|
90146
90300
|
|
|
90147
90301
|
// src/lib/pat.ts
|
|
90148
|
-
var
|
|
90149
|
-
function
|
|
90150
|
-
|
|
90302
|
+
var PAT_SEARCH_REGEX = /plnth_pat_[a-z2-7]{52}/;
|
|
90303
|
+
function extractPat(raw) {
|
|
90304
|
+
const match = raw.match(PAT_SEARCH_REGEX);
|
|
90305
|
+
return match ? match[0] : null;
|
|
90151
90306
|
}
|
|
90152
90307
|
function patLastFour(raw) {
|
|
90153
90308
|
return raw.slice(-4);
|
|
@@ -90158,9 +90313,89 @@ var ETX = "\x03";
|
|
|
90158
90313
|
var BACKSPACE = "\b";
|
|
90159
90314
|
var DEL = "";
|
|
90160
90315
|
var CSI_RE = /\x1b\[[\d;]*[A-Za-z~]/g;
|
|
90316
|
+
var PASTE_START = "\x1B[200~";
|
|
90317
|
+
var PASTE_END = "\x1B[201~";
|
|
90318
|
+
var ENTER_CSI = /^\x1b\[13(?:;\d+)?u$/;
|
|
90161
90319
|
function sanitizeInput(input) {
|
|
90162
90320
|
return input.replace(CSI_RE, "");
|
|
90163
90321
|
}
|
|
90322
|
+
var INITIAL_READER_STATE = {
|
|
90323
|
+
buf: "",
|
|
90324
|
+
pending: "",
|
|
90325
|
+
inPaste: false
|
|
90326
|
+
};
|
|
90327
|
+
function matchEscape(s2) {
|
|
90328
|
+
if (s2[0] !== "\x1B")
|
|
90329
|
+
return { len: 0 };
|
|
90330
|
+
if (s2.length < 2)
|
|
90331
|
+
return null;
|
|
90332
|
+
const c1 = s2[1];
|
|
90333
|
+
if (c1 === "[") {
|
|
90334
|
+
let i2 = 2;
|
|
90335
|
+
while (i2 < s2.length) {
|
|
90336
|
+
const code = s2.charCodeAt(i2);
|
|
90337
|
+
const isDigit = code >= 48 && code <= 57;
|
|
90338
|
+
const isSemicolon = s2[i2] === ";";
|
|
90339
|
+
if (!isDigit && !isSemicolon)
|
|
90340
|
+
break;
|
|
90341
|
+
i2++;
|
|
90342
|
+
}
|
|
90343
|
+
if (i2 >= s2.length)
|
|
90344
|
+
return null;
|
|
90345
|
+
return { len: i2 + 1 };
|
|
90346
|
+
}
|
|
90347
|
+
if (c1 === "O") {
|
|
90348
|
+
if (s2.length < 3)
|
|
90349
|
+
return null;
|
|
90350
|
+
return { len: 3 };
|
|
90351
|
+
}
|
|
90352
|
+
return { len: 2 };
|
|
90353
|
+
}
|
|
90354
|
+
function feedChunk(state, chunk) {
|
|
90355
|
+
let { buf, pending, inPaste } = state;
|
|
90356
|
+
pending = pending + chunk;
|
|
90357
|
+
while (pending.length > 0) {
|
|
90358
|
+
const ch = pending[0];
|
|
90359
|
+
if (ch === ETX) {
|
|
90360
|
+
return { kind: "abort" };
|
|
90361
|
+
}
|
|
90362
|
+
if (ch === "\x1B") {
|
|
90363
|
+
const m2 = matchEscape(pending);
|
|
90364
|
+
if (m2 === null) {
|
|
90365
|
+
return { kind: "continue", state: { buf, pending, inPaste } };
|
|
90366
|
+
}
|
|
90367
|
+
const seq = pending.slice(0, m2.len);
|
|
90368
|
+
pending = pending.slice(m2.len);
|
|
90369
|
+
if (seq === PASTE_START) {
|
|
90370
|
+
inPaste = true;
|
|
90371
|
+
continue;
|
|
90372
|
+
}
|
|
90373
|
+
if (seq === PASTE_END) {
|
|
90374
|
+
inPaste = false;
|
|
90375
|
+
continue;
|
|
90376
|
+
}
|
|
90377
|
+
if (!inPaste && ENTER_CSI.test(seq)) {
|
|
90378
|
+
return { kind: "submit", value: buf };
|
|
90379
|
+
}
|
|
90380
|
+
continue;
|
|
90381
|
+
}
|
|
90382
|
+
pending = pending.slice(1);
|
|
90383
|
+
if (ch === "\r" || ch === `
|
|
90384
|
+
`) {
|
|
90385
|
+
if (!inPaste)
|
|
90386
|
+
return { kind: "submit", value: buf };
|
|
90387
|
+
buf += ch;
|
|
90388
|
+
continue;
|
|
90389
|
+
}
|
|
90390
|
+
if (ch === BACKSPACE || ch === DEL) {
|
|
90391
|
+
if (!inPaste)
|
|
90392
|
+
buf = buf.slice(0, -1);
|
|
90393
|
+
continue;
|
|
90394
|
+
}
|
|
90395
|
+
buf += ch;
|
|
90396
|
+
}
|
|
90397
|
+
return { kind: "continue", state: { buf, pending, inPaste } };
|
|
90398
|
+
}
|
|
90164
90399
|
function readSecret(promptText) {
|
|
90165
90400
|
const stdin2 = process.stdin;
|
|
90166
90401
|
if (stdin2.isTTY === true) {
|
|
@@ -90175,7 +90410,7 @@ function readTtySecret(stdin2, promptText) {
|
|
|
90175
90410
|
stdin2.setRawMode(true);
|
|
90176
90411
|
stdin2.resume();
|
|
90177
90412
|
stdin2.setEncoding("utf8");
|
|
90178
|
-
let
|
|
90413
|
+
let state = INITIAL_READER_STATE;
|
|
90179
90414
|
const cleanup = () => {
|
|
90180
90415
|
stdin2.removeListener("data", onData);
|
|
90181
90416
|
stdin2.removeListener("error", onError);
|
|
@@ -90183,37 +90418,27 @@ function readTtySecret(stdin2, promptText) {
|
|
|
90183
90418
|
stdin2.setRawMode(false);
|
|
90184
90419
|
stdin2.pause();
|
|
90185
90420
|
};
|
|
90186
|
-
const
|
|
90187
|
-
|
|
90188
|
-
|
|
90421
|
+
const onData = (chunk) => {
|
|
90422
|
+
const result = feedChunk(state, chunk);
|
|
90423
|
+
if (result.kind === "submit") {
|
|
90424
|
+
cleanup();
|
|
90425
|
+
process.stdout.write(`
|
|
90189
90426
|
`);
|
|
90190
|
-
|
|
90427
|
+
resolve(result.value);
|
|
90428
|
+
return;
|
|
90429
|
+
}
|
|
90430
|
+
if (result.kind === "abort") {
|
|
90431
|
+
cleanup();
|
|
90432
|
+
process.stdout.write(`
|
|
90433
|
+
`);
|
|
90434
|
+
process.exit(130);
|
|
90435
|
+
}
|
|
90436
|
+
state = result.state;
|
|
90191
90437
|
};
|
|
90192
90438
|
const onError = (err) => {
|
|
90193
90439
|
cleanup();
|
|
90194
90440
|
reject(err);
|
|
90195
90441
|
};
|
|
90196
|
-
const onData = (chunk) => {
|
|
90197
|
-
for (const ch of chunk) {
|
|
90198
|
-
if (ch === ETX) {
|
|
90199
|
-
if (typeof stdin2.setRawMode === "function")
|
|
90200
|
-
stdin2.setRawMode(false);
|
|
90201
|
-
process.stdout.write(`
|
|
90202
|
-
`);
|
|
90203
|
-
process.exit(130);
|
|
90204
|
-
}
|
|
90205
|
-
if (ch === "\r" || ch === `
|
|
90206
|
-
`) {
|
|
90207
|
-
finish(buf);
|
|
90208
|
-
return;
|
|
90209
|
-
}
|
|
90210
|
-
if (ch === BACKSPACE || ch === DEL) {
|
|
90211
|
-
buf = buf.slice(0, -1);
|
|
90212
|
-
continue;
|
|
90213
|
-
}
|
|
90214
|
-
buf += ch;
|
|
90215
|
-
}
|
|
90216
|
-
};
|
|
90217
90442
|
stdin2.on("data", onData);
|
|
90218
90443
|
stdin2.on("error", onError);
|
|
90219
90444
|
});
|
|
@@ -90393,8 +90618,9 @@ async function runLogin(workspaceSlug, apiBaseUrl, deps = {}) {
|
|
|
90393
90618
|
|
|
90394
90619
|
`);
|
|
90395
90620
|
openFn(settingsUrl);
|
|
90396
|
-
const
|
|
90397
|
-
|
|
90621
|
+
const raw = await readFn("Paste your token here (nothing will appear — paste, then press Enter): ");
|
|
90622
|
+
const pat = extractPat(raw);
|
|
90623
|
+
if (pat === null) {
|
|
90398
90624
|
err.write(`Error: that does not look like a Plinth personal access token.
|
|
90399
90625
|
`);
|
|
90400
90626
|
return 1;
|
|
@@ -90463,6 +90689,94 @@ var loginCommand = defineCommand({
|
|
|
90463
90689
|
}
|
|
90464
90690
|
});
|
|
90465
90691
|
|
|
90692
|
+
// src/commands/start.ts
|
|
90693
|
+
import { statSync as statSync4 } from "node:fs";
|
|
90694
|
+
|
|
90695
|
+
// src/lib/mirror.ts
|
|
90696
|
+
import {
|
|
90697
|
+
lstatSync,
|
|
90698
|
+
mkdirSync as mkdirSync2,
|
|
90699
|
+
renameSync as renameSync2,
|
|
90700
|
+
unlinkSync as unlinkSync2,
|
|
90701
|
+
writeFileSync as writeFileSync2
|
|
90702
|
+
} from "node:fs";
|
|
90703
|
+
import { dirname as dirname2, join as join2, resolve, sep as sep2 } from "node:path";
|
|
90704
|
+
var SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
90705
|
+
|
|
90706
|
+
class MirrorPathError extends Error {
|
|
90707
|
+
constructor(message) {
|
|
90708
|
+
super(message);
|
|
90709
|
+
this.name = "MirrorPathError";
|
|
90710
|
+
}
|
|
90711
|
+
}
|
|
90712
|
+
function assertSlug(label, slug) {
|
|
90713
|
+
if (!SLUG_RE.test(slug) || slug.length > 200) {
|
|
90714
|
+
throw new MirrorPathError(`Invalid ${label}: ${JSON.stringify(slug)}`);
|
|
90715
|
+
}
|
|
90716
|
+
}
|
|
90717
|
+
function assertNotSymlink(path) {
|
|
90718
|
+
try {
|
|
90719
|
+
const stat = lstatSync(path);
|
|
90720
|
+
if (stat.isSymbolicLink()) {
|
|
90721
|
+
throw new MirrorPathError(`Refusing to follow symlink at ${path}. ` + `Remove it and re-run plinth sync.`);
|
|
90722
|
+
}
|
|
90723
|
+
} catch (err) {
|
|
90724
|
+
if (err?.code === "ENOENT")
|
|
90725
|
+
return;
|
|
90726
|
+
if (err instanceof MirrorPathError)
|
|
90727
|
+
throw err;
|
|
90728
|
+
throw err;
|
|
90729
|
+
}
|
|
90730
|
+
}
|
|
90731
|
+
function workspaceMirrorPath(workspaceSlug) {
|
|
90732
|
+
assertSlug("workspace slug", workspaceSlug);
|
|
90733
|
+
const root = mirrorRoot();
|
|
90734
|
+
const ws = join2(root, workspaceSlug);
|
|
90735
|
+
const resolved = resolve(ws);
|
|
90736
|
+
if (resolved !== resolve(root, workspaceSlug)) {
|
|
90737
|
+
throw new MirrorPathError(`Resolved workspace path escapes mirror root`);
|
|
90738
|
+
}
|
|
90739
|
+
assertNotSymlink(resolved);
|
|
90740
|
+
return resolved;
|
|
90741
|
+
}
|
|
90742
|
+
function entityFilePath(workspaceSlug, entityType, entitySlug, ext = "md") {
|
|
90743
|
+
assertSlug("entity type", entityType);
|
|
90744
|
+
assertSlug("entity slug", entitySlug);
|
|
90745
|
+
const ws = workspaceMirrorPath(workspaceSlug);
|
|
90746
|
+
const target = join2(ws, entityType, `${entitySlug}.${ext}`);
|
|
90747
|
+
const resolved = resolve(target);
|
|
90748
|
+
const prefix = ws.endsWith(sep2) ? ws : ws + sep2;
|
|
90749
|
+
if (!resolved.startsWith(prefix)) {
|
|
90750
|
+
throw new MirrorPathError(`Resolved entity path escapes workspace folder`);
|
|
90751
|
+
}
|
|
90752
|
+
return resolved;
|
|
90753
|
+
}
|
|
90754
|
+
function writeMirrorFile(workspaceSlug, entityType, entitySlug, content, ext = "md") {
|
|
90755
|
+
const target = entityFilePath(workspaceSlug, entityType, entitySlug, ext);
|
|
90756
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
90757
|
+
mkdirSync2(dirname2(target), { recursive: true, mode: 448 });
|
|
90758
|
+
writeFileSync2(tmp, content, { mode: 384 });
|
|
90759
|
+
try {
|
|
90760
|
+
renameSync2(tmp, target);
|
|
90761
|
+
} catch (err) {
|
|
90762
|
+
try {
|
|
90763
|
+
unlinkSync2(tmp);
|
|
90764
|
+
} catch {}
|
|
90765
|
+
throw err;
|
|
90766
|
+
}
|
|
90767
|
+
}
|
|
90768
|
+
function deleteMirrorFile(workspaceSlug, entityType, entitySlug, ext = "md") {
|
|
90769
|
+
const target = entityFilePath(workspaceSlug, entityType, entitySlug, ext);
|
|
90770
|
+
try {
|
|
90771
|
+
unlinkSync2(target);
|
|
90772
|
+
return true;
|
|
90773
|
+
} catch (err) {
|
|
90774
|
+
if (err?.code === "ENOENT")
|
|
90775
|
+
return false;
|
|
90776
|
+
throw err;
|
|
90777
|
+
}
|
|
90778
|
+
}
|
|
90779
|
+
|
|
90466
90780
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
90467
90781
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
90468
90782
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
@@ -93906,118 +94220,53 @@ class Output {
|
|
|
93906
94220
|
}
|
|
93907
94221
|
}
|
|
93908
94222
|
|
|
94223
|
+
// src/lib/per-id-mutex.ts
|
|
94224
|
+
class PerIdMutex {
|
|
94225
|
+
chains = new Map;
|
|
94226
|
+
async run(key, fn) {
|
|
94227
|
+
const prior = this.chains.get(key) ?? Promise.resolve();
|
|
94228
|
+
const next = prior.then(fn, fn);
|
|
94229
|
+
this.chains.set(key, next);
|
|
94230
|
+
const cleanup = () => {
|
|
94231
|
+
if (this.chains.get(key) === next) {
|
|
94232
|
+
this.chains.delete(key);
|
|
94233
|
+
}
|
|
94234
|
+
};
|
|
94235
|
+
next.then(cleanup, cleanup);
|
|
94236
|
+
return next;
|
|
94237
|
+
}
|
|
94238
|
+
size() {
|
|
94239
|
+
return this.chains.size;
|
|
94240
|
+
}
|
|
94241
|
+
}
|
|
94242
|
+
|
|
93909
94243
|
// src/lib/push.ts
|
|
93910
94244
|
var import_gray_matter2 = __toESM(require_gray_matter(), 1);
|
|
93911
94245
|
import { createHash } from "node:crypto";
|
|
93912
|
-
import {
|
|
93913
|
-
import { relative } from "node:path";
|
|
94246
|
+
import { readFileSync as readFileSync3, statSync } from "node:fs";
|
|
94247
|
+
import { basename, relative } from "node:path";
|
|
93914
94248
|
|
|
93915
94249
|
// src/lib/conflicts.ts
|
|
93916
|
-
import { mkdirSync as
|
|
93917
|
-
import { dirname as
|
|
94250
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
94251
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
93918
94252
|
function conflictsRoot(workspaceSlug) {
|
|
93919
|
-
return
|
|
94253
|
+
return join3(plinthHome(), "_conflicts", workspaceSlug);
|
|
93920
94254
|
}
|
|
93921
94255
|
function conflictFilePath(args) {
|
|
93922
94256
|
const { workspaceSlug, entityType, entitySlug, timestamp } = args;
|
|
93923
94257
|
const iso = timestamp.toISOString().replace(/[:.]/g, "-");
|
|
93924
|
-
return
|
|
94258
|
+
return join3(conflictsRoot(workspaceSlug), `${entityType}.${entitySlug}.${iso}.md`);
|
|
93925
94259
|
}
|
|
93926
94260
|
function stashConflict(args) {
|
|
93927
94261
|
const target = conflictFilePath(args);
|
|
93928
|
-
|
|
93929
|
-
|
|
94262
|
+
mkdirSync3(dirname3(target), { recursive: true, mode: 448 });
|
|
94263
|
+
writeFileSync3(target, args.contents, { mode: 384 });
|
|
93930
94264
|
return target;
|
|
93931
94265
|
}
|
|
93932
94266
|
|
|
93933
94267
|
// src/lib/push.ts
|
|
93934
94268
|
init_markdown();
|
|
93935
94269
|
|
|
93936
|
-
// src/lib/mirror.ts
|
|
93937
|
-
import {
|
|
93938
|
-
lstatSync,
|
|
93939
|
-
mkdirSync as mkdirSync3,
|
|
93940
|
-
renameSync as renameSync2,
|
|
93941
|
-
unlinkSync as unlinkSync2,
|
|
93942
|
-
writeFileSync as writeFileSync3
|
|
93943
|
-
} from "node:fs";
|
|
93944
|
-
import { dirname as dirname3, join as join3, resolve, sep as sep2 } from "node:path";
|
|
93945
|
-
var SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
93946
|
-
|
|
93947
|
-
class MirrorPathError extends Error {
|
|
93948
|
-
constructor(message) {
|
|
93949
|
-
super(message);
|
|
93950
|
-
this.name = "MirrorPathError";
|
|
93951
|
-
}
|
|
93952
|
-
}
|
|
93953
|
-
function assertSlug(label2, slug) {
|
|
93954
|
-
if (!SLUG_RE.test(slug) || slug.length > 200) {
|
|
93955
|
-
throw new MirrorPathError(`Invalid ${label2}: ${JSON.stringify(slug)}`);
|
|
93956
|
-
}
|
|
93957
|
-
}
|
|
93958
|
-
function assertNotSymlink(path) {
|
|
93959
|
-
try {
|
|
93960
|
-
const stat = lstatSync(path);
|
|
93961
|
-
if (stat.isSymbolicLink()) {
|
|
93962
|
-
throw new MirrorPathError(`Refusing to follow symlink at ${path}. ` + `Remove it and re-run plinth sync.`);
|
|
93963
|
-
}
|
|
93964
|
-
} catch (err) {
|
|
93965
|
-
if (err?.code === "ENOENT")
|
|
93966
|
-
return;
|
|
93967
|
-
if (err instanceof MirrorPathError)
|
|
93968
|
-
throw err;
|
|
93969
|
-
throw err;
|
|
93970
|
-
}
|
|
93971
|
-
}
|
|
93972
|
-
function workspaceMirrorPath(workspaceSlug) {
|
|
93973
|
-
assertSlug("workspace slug", workspaceSlug);
|
|
93974
|
-
const root2 = mirrorRoot();
|
|
93975
|
-
const ws = join3(root2, workspaceSlug);
|
|
93976
|
-
const resolved = resolve(ws);
|
|
93977
|
-
if (resolved !== resolve(root2, workspaceSlug)) {
|
|
93978
|
-
throw new MirrorPathError(`Resolved workspace path escapes mirror root`);
|
|
93979
|
-
}
|
|
93980
|
-
assertNotSymlink(resolved);
|
|
93981
|
-
return resolved;
|
|
93982
|
-
}
|
|
93983
|
-
function entityFilePath(workspaceSlug, entityType, entitySlug, ext = "md") {
|
|
93984
|
-
assertSlug("entity type", entityType);
|
|
93985
|
-
assertSlug("entity slug", entitySlug);
|
|
93986
|
-
const ws = workspaceMirrorPath(workspaceSlug);
|
|
93987
|
-
const target2 = join3(ws, entityType, `${entitySlug}.${ext}`);
|
|
93988
|
-
const resolved = resolve(target2);
|
|
93989
|
-
const prefix2 = ws.endsWith(sep2) ? ws : ws + sep2;
|
|
93990
|
-
if (!resolved.startsWith(prefix2)) {
|
|
93991
|
-
throw new MirrorPathError(`Resolved entity path escapes workspace folder`);
|
|
93992
|
-
}
|
|
93993
|
-
return resolved;
|
|
93994
|
-
}
|
|
93995
|
-
function writeMirrorFile(workspaceSlug, entityType, entitySlug, content2, ext = "md") {
|
|
93996
|
-
const target2 = entityFilePath(workspaceSlug, entityType, entitySlug, ext);
|
|
93997
|
-
const tmp = `${target2}.${process.pid}.${Date.now()}.tmp`;
|
|
93998
|
-
mkdirSync3(dirname3(target2), { recursive: true, mode: 448 });
|
|
93999
|
-
writeFileSync3(tmp, content2, { mode: 384 });
|
|
94000
|
-
try {
|
|
94001
|
-
renameSync2(tmp, target2);
|
|
94002
|
-
} catch (err) {
|
|
94003
|
-
try {
|
|
94004
|
-
unlinkSync2(tmp);
|
|
94005
|
-
} catch {}
|
|
94006
|
-
throw err;
|
|
94007
|
-
}
|
|
94008
|
-
}
|
|
94009
|
-
function deleteMirrorFile(workspaceSlug, entityType, entitySlug, ext = "md") {
|
|
94010
|
-
const target2 = entityFilePath(workspaceSlug, entityType, entitySlug, ext);
|
|
94011
|
-
try {
|
|
94012
|
-
unlinkSync2(target2);
|
|
94013
|
-
return true;
|
|
94014
|
-
} catch (err) {
|
|
94015
|
-
if (err?.code === "ENOENT")
|
|
94016
|
-
return false;
|
|
94017
|
-
throw err;
|
|
94018
|
-
}
|
|
94019
|
-
}
|
|
94020
|
-
|
|
94021
94270
|
// src/lib/state.ts
|
|
94022
94271
|
import {
|
|
94023
94272
|
mkdirSync as mkdirSync4,
|
|
@@ -94033,7 +94282,8 @@ var StateEntrySchema = exports_external.object({
|
|
|
94033
94282
|
updated_at: exports_external.string().datetime({ offset: true }),
|
|
94034
94283
|
revision: exports_external.number().int().nonnegative(),
|
|
94035
94284
|
body_hash: exports_external.string().min(1),
|
|
94036
|
-
frontmatter_hash: exports_external.string().min(1)
|
|
94285
|
+
frontmatter_hash: exports_external.string().min(1),
|
|
94286
|
+
inode: exports_external.number().int().nonnegative().optional()
|
|
94037
94287
|
});
|
|
94038
94288
|
var WorkspaceStateSchema = exports_external.object({
|
|
94039
94289
|
version: exports_external.literal(1),
|
|
@@ -94086,6 +94336,9 @@ function upsertEntry(state2, entry2) {
|
|
|
94086
94336
|
};
|
|
94087
94337
|
return { state: next, changed: true };
|
|
94088
94338
|
}
|
|
94339
|
+
function getEntry(state2, id2) {
|
|
94340
|
+
return state2.documents[id2];
|
|
94341
|
+
}
|
|
94089
94342
|
function getEntryBySlug(state2, slug) {
|
|
94090
94343
|
for (const entry2 of Object.values(state2.documents)) {
|
|
94091
94344
|
if (entry2.slug === slug)
|
|
@@ -94094,7 +94347,7 @@ function getEntryBySlug(state2, slug) {
|
|
|
94094
94347
|
return;
|
|
94095
94348
|
}
|
|
94096
94349
|
function entriesEqual(a2, b3) {
|
|
94097
|
-
return a2.id === b3.id && a2.slug === b3.slug && a2.updated_at === b3.updated_at && a2.revision === b3.revision && a2.body_hash === b3.body_hash && a2.frontmatter_hash === b3.frontmatter_hash;
|
|
94350
|
+
return a2.id === b3.id && a2.slug === b3.slug && a2.updated_at === b3.updated_at && a2.revision === b3.revision && a2.body_hash === b3.body_hash && a2.frontmatter_hash === b3.frontmatter_hash && a2.inode === b3.inode;
|
|
94098
94351
|
}
|
|
94099
94352
|
|
|
94100
94353
|
// src/lib/push.ts
|
|
@@ -94151,30 +94404,120 @@ function classifyMirrorPath(args) {
|
|
|
94151
94404
|
}
|
|
94152
94405
|
return { entityType, entitySlug };
|
|
94153
94406
|
}
|
|
94407
|
+
function resolveTitle(parsedBody, fm, slug) {
|
|
94408
|
+
const lines = parsedBody.split(`
|
|
94409
|
+
`);
|
|
94410
|
+
for (const line of lines) {
|
|
94411
|
+
const m3 = line.match(/^#\s+(.+?)\s*$/);
|
|
94412
|
+
if (m3 && m3[1]?.length)
|
|
94413
|
+
return m3[1];
|
|
94414
|
+
}
|
|
94415
|
+
if (typeof fm.title === "string" && fm.title.length > 0)
|
|
94416
|
+
return fm.title;
|
|
94417
|
+
return humaniseSlug(slug);
|
|
94418
|
+
}
|
|
94419
|
+
function humaniseSlug(slug) {
|
|
94420
|
+
return slug.split("-").filter((part) => part.length > 0).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
94421
|
+
}
|
|
94422
|
+
function safeInode(absolutePath) {
|
|
94423
|
+
try {
|
|
94424
|
+
return statSync(absolutePath).ino;
|
|
94425
|
+
} catch {
|
|
94426
|
+
return;
|
|
94427
|
+
}
|
|
94428
|
+
}
|
|
94429
|
+
function persistEntry(state2, entry2) {
|
|
94430
|
+
const { state: nextState, changed } = upsertEntry(state2, entry2);
|
|
94431
|
+
if (changed)
|
|
94432
|
+
writeState(nextState);
|
|
94433
|
+
return nextState;
|
|
94434
|
+
}
|
|
94435
|
+
function dropEntry(state2, id2) {
|
|
94436
|
+
if (!(id2 in state2.documents))
|
|
94437
|
+
return state2;
|
|
94438
|
+
const nextDocs = { ...state2.documents };
|
|
94439
|
+
delete nextDocs[id2];
|
|
94440
|
+
const next = { ...state2, documents: nextDocs };
|
|
94441
|
+
writeState(next);
|
|
94442
|
+
return next;
|
|
94443
|
+
}
|
|
94444
|
+
async function pullAndWriteMirror(args) {
|
|
94445
|
+
const cloud = await getDocument({
|
|
94446
|
+
baseUrl: args.baseUrl,
|
|
94447
|
+
pat: args.pat,
|
|
94448
|
+
id: args.id,
|
|
94449
|
+
fetchImpl: args.fetchImpl
|
|
94450
|
+
});
|
|
94451
|
+
const { serialiseDocument: serialiseDocument2 } = await Promise.resolve().then(() => (init_markdown(), exports_markdown));
|
|
94452
|
+
writeMirrorFile(args.workspaceSlug, args.entityType, cloud.slug, serialiseDocument2(cloud));
|
|
94453
|
+
const mirrorPath = entityFilePath(args.workspaceSlug, args.entityType, cloud.slug);
|
|
94454
|
+
const mirrorRaw = readFileSync3(mirrorPath, "utf8");
|
|
94455
|
+
const mirrorParsed = import_gray_matter2.default(mirrorRaw);
|
|
94456
|
+
const refreshed = {
|
|
94457
|
+
id: cloud.id,
|
|
94458
|
+
slug: cloud.slug,
|
|
94459
|
+
updated_at: cloud.updated_at,
|
|
94460
|
+
revision: cloud.revision,
|
|
94461
|
+
body_hash: stableHash(mirrorParsed.content),
|
|
94462
|
+
frontmatter_hash: stableHash(userFrontmatter(mirrorParsed.data ?? {})),
|
|
94463
|
+
inode: safeInode(mirrorPath)
|
|
94464
|
+
};
|
|
94465
|
+
return persistEntry(args.state, refreshed);
|
|
94466
|
+
}
|
|
94154
94467
|
async function pushFile(args) {
|
|
94155
|
-
const {
|
|
94468
|
+
const {
|
|
94469
|
+
workspaceSlug,
|
|
94470
|
+
baseUrl,
|
|
94471
|
+
pat,
|
|
94472
|
+
absolutePath,
|
|
94473
|
+
entityType,
|
|
94474
|
+
state: state2,
|
|
94475
|
+
exists,
|
|
94476
|
+
unlinkContext,
|
|
94477
|
+
deps
|
|
94478
|
+
} = args;
|
|
94156
94479
|
const fetchImpl = deps?.fetchImpl;
|
|
94157
94480
|
const now = deps?.now ?? (() => new Date);
|
|
94481
|
+
const mutex = deps?.mutex ?? new PerIdMutex;
|
|
94158
94482
|
if (entityType !== "documents") {
|
|
94159
94483
|
return {
|
|
94160
|
-
outcome: {
|
|
94484
|
+
outcome: {
|
|
94485
|
+
kind: "ignored",
|
|
94486
|
+
absolutePath,
|
|
94487
|
+
reason: `entity type ${entityType} not yet pushed`
|
|
94488
|
+
},
|
|
94161
94489
|
state: state2
|
|
94162
94490
|
};
|
|
94163
94491
|
}
|
|
94164
94492
|
if (!exists) {
|
|
94165
|
-
|
|
94166
|
-
|
|
94167
|
-
|
|
94168
|
-
|
|
94169
|
-
|
|
94170
|
-
|
|
94493
|
+
return runPushDelete({
|
|
94494
|
+
workspaceSlug,
|
|
94495
|
+
entityType,
|
|
94496
|
+
baseUrl,
|
|
94497
|
+
pat,
|
|
94498
|
+
absolutePath,
|
|
94499
|
+
state: state2,
|
|
94500
|
+
unlinkContext,
|
|
94501
|
+
mutex,
|
|
94502
|
+
fetchImpl
|
|
94503
|
+
});
|
|
94171
94504
|
}
|
|
94172
94505
|
let raw;
|
|
94173
94506
|
try {
|
|
94174
94507
|
raw = readFileSync3(absolutePath, "utf8");
|
|
94175
94508
|
} catch (err) {
|
|
94176
94509
|
if (err?.code === "ENOENT") {
|
|
94177
|
-
return {
|
|
94510
|
+
return runPushDelete({
|
|
94511
|
+
workspaceSlug,
|
|
94512
|
+
entityType,
|
|
94513
|
+
baseUrl,
|
|
94514
|
+
pat,
|
|
94515
|
+
absolutePath,
|
|
94516
|
+
state: state2,
|
|
94517
|
+
unlinkContext: null,
|
|
94518
|
+
mutex,
|
|
94519
|
+
fetchImpl
|
|
94520
|
+
});
|
|
94178
94521
|
}
|
|
94179
94522
|
throw err;
|
|
94180
94523
|
}
|
|
@@ -94189,100 +94532,404 @@ async function pushFile(args) {
|
|
|
94189
94532
|
}
|
|
94190
94533
|
const fm = parsed.data ?? {};
|
|
94191
94534
|
const slugFromFm = typeof fm.slug === "string" && fm.slug.length > 0 ? fm.slug : null;
|
|
94192
|
-
|
|
94535
|
+
const slugFromPath = basename(absolutePath, ".md");
|
|
94536
|
+
const slug = slugFromFm ?? slugFromPath;
|
|
94537
|
+
if (slug.length === 0) {
|
|
94193
94538
|
return {
|
|
94194
|
-
outcome: { kind: "
|
|
94539
|
+
outcome: { kind: "ignored", absolutePath, reason: "empty slug" },
|
|
94195
94540
|
state: state2
|
|
94196
94541
|
};
|
|
94197
94542
|
}
|
|
94198
|
-
const stateEntry = getEntryBySlug(state2,
|
|
94543
|
+
const stateEntry = getEntryBySlug(state2, slug);
|
|
94199
94544
|
if (!stateEntry) {
|
|
94200
|
-
return {
|
|
94201
|
-
|
|
94202
|
-
|
|
94203
|
-
|
|
94545
|
+
return runPushCreate({
|
|
94546
|
+
workspaceSlug,
|
|
94547
|
+
entityType,
|
|
94548
|
+
baseUrl,
|
|
94549
|
+
pat,
|
|
94550
|
+
absolutePath,
|
|
94551
|
+
slug,
|
|
94552
|
+
raw,
|
|
94553
|
+
parsed,
|
|
94554
|
+
state: state2,
|
|
94555
|
+
mutex,
|
|
94556
|
+
fetchImpl,
|
|
94557
|
+
now
|
|
94558
|
+
});
|
|
94204
94559
|
}
|
|
94205
|
-
|
|
94206
|
-
|
|
94207
|
-
|
|
94208
|
-
const newFrontmatterHash = stableHash(userFm);
|
|
94209
|
-
const bodyChanged = newBodyHash !== stateEntry.body_hash;
|
|
94210
|
-
const frontmatterChanged = newFrontmatterHash !== stateEntry.frontmatter_hash;
|
|
94211
|
-
if (!bodyChanged && !frontmatterChanged) {
|
|
94212
|
-
return { outcome: { kind: "noop", entitySlug: slugFromFm }, state: state2 };
|
|
94213
|
-
}
|
|
94214
|
-
const html = bodyChanged ? markdownToHtml(bodyMarkdown) : undefined;
|
|
94215
|
-
const patchResult = await patchDocument({
|
|
94560
|
+
return runPushUpsert({
|
|
94561
|
+
workspaceSlug,
|
|
94562
|
+
entityType,
|
|
94216
94563
|
baseUrl,
|
|
94217
94564
|
pat,
|
|
94218
|
-
|
|
94219
|
-
|
|
94220
|
-
|
|
94221
|
-
|
|
94222
|
-
|
|
94565
|
+
absolutePath,
|
|
94566
|
+
raw,
|
|
94567
|
+
parsed,
|
|
94568
|
+
stateEntry,
|
|
94569
|
+
state: state2,
|
|
94570
|
+
mutex,
|
|
94571
|
+
fetchImpl,
|
|
94572
|
+
now
|
|
94223
94573
|
});
|
|
94224
|
-
|
|
94225
|
-
|
|
94226
|
-
|
|
94227
|
-
|
|
94228
|
-
|
|
94229
|
-
|
|
94230
|
-
|
|
94231
|
-
|
|
94574
|
+
}
|
|
94575
|
+
async function runPushUpsert(args) {
|
|
94576
|
+
return args.mutex.run(args.stateEntry.id, async () => {
|
|
94577
|
+
const fm = args.parsed.data ?? {};
|
|
94578
|
+
const userFm = userFrontmatter(fm);
|
|
94579
|
+
const bodyMarkdown = args.parsed.content;
|
|
94580
|
+
const newBodyHash = stableHash(bodyMarkdown);
|
|
94581
|
+
const newFrontmatterHash = stableHash(userFm);
|
|
94582
|
+
const liveEntry = getEntry(args.state, args.stateEntry.id) ?? args.stateEntry;
|
|
94583
|
+
const bodyChanged = newBodyHash !== liveEntry.body_hash;
|
|
94584
|
+
const frontmatterChanged = newFrontmatterHash !== liveEntry.frontmatter_hash;
|
|
94585
|
+
if (!bodyChanged && !frontmatterChanged) {
|
|
94586
|
+
return { outcome: { kind: "noop", entitySlug: liveEntry.slug }, state: args.state };
|
|
94587
|
+
}
|
|
94588
|
+
const html = bodyChanged ? markdownToHtml(bodyMarkdown) : undefined;
|
|
94589
|
+
const patchResult = await patchDocument({
|
|
94590
|
+
baseUrl: args.baseUrl,
|
|
94591
|
+
pat: args.pat,
|
|
94592
|
+
id: liveEntry.id,
|
|
94593
|
+
expectedUpdatedAt: liveEntry.updated_at,
|
|
94594
|
+
body: html,
|
|
94595
|
+
frontmatter: frontmatterChanged ? userFm : undefined,
|
|
94596
|
+
fetchImpl: args.fetchImpl
|
|
94597
|
+
});
|
|
94598
|
+
if (patchResult.ok) {
|
|
94599
|
+
const updated = {
|
|
94600
|
+
id: patchResult.doc.id,
|
|
94601
|
+
slug: patchResult.doc.slug,
|
|
94602
|
+
updated_at: patchResult.doc.updated_at,
|
|
94603
|
+
revision: patchResult.doc.revision,
|
|
94604
|
+
body_hash: newBodyHash,
|
|
94605
|
+
frontmatter_hash: newFrontmatterHash,
|
|
94606
|
+
inode: safeInode(args.absolutePath)
|
|
94607
|
+
};
|
|
94608
|
+
const nextState2 = persistEntry(args.state, updated);
|
|
94609
|
+
return {
|
|
94610
|
+
outcome: { kind: "pushed", entitySlug: updated.slug },
|
|
94611
|
+
state: nextState2
|
|
94612
|
+
};
|
|
94613
|
+
}
|
|
94614
|
+
if (patchResult.code === 404) {
|
|
94615
|
+
return {
|
|
94616
|
+
outcome: { kind: "not_found", entitySlug: liveEntry.slug },
|
|
94617
|
+
state: args.state
|
|
94618
|
+
};
|
|
94619
|
+
}
|
|
94620
|
+
if (patchResult.reason === "slug_conflict") {
|
|
94621
|
+
throw new Error("patchDocument returned slug_conflict for a body+frontmatter PATCH (no slug sent)");
|
|
94622
|
+
}
|
|
94623
|
+
const stashPath = stashConflict({
|
|
94624
|
+
workspaceSlug: args.workspaceSlug,
|
|
94625
|
+
entityType: args.entityType,
|
|
94626
|
+
entitySlug: liveEntry.slug,
|
|
94627
|
+
timestamp: args.now(),
|
|
94628
|
+
contents: args.raw
|
|
94629
|
+
});
|
|
94630
|
+
const nextState = await pullAndWriteMirror({
|
|
94631
|
+
workspaceSlug: args.workspaceSlug,
|
|
94632
|
+
entityType: args.entityType,
|
|
94633
|
+
baseUrl: args.baseUrl,
|
|
94634
|
+
pat: args.pat,
|
|
94635
|
+
id: liveEntry.id,
|
|
94636
|
+
state: args.state,
|
|
94637
|
+
fetchImpl: args.fetchImpl
|
|
94638
|
+
});
|
|
94639
|
+
return {
|
|
94640
|
+
outcome: { kind: "conflict", entitySlug: liveEntry.slug, stashPath },
|
|
94641
|
+
state: nextState
|
|
94232
94642
|
};
|
|
94233
|
-
|
|
94234
|
-
|
|
94235
|
-
|
|
94643
|
+
});
|
|
94644
|
+
}
|
|
94645
|
+
async function runPushCreate(args) {
|
|
94646
|
+
const key = `create:${args.entityType}:${args.slug}`;
|
|
94647
|
+
return args.mutex.run(key, async () => {
|
|
94648
|
+
const inLock = getEntryBySlug(args.state, args.slug);
|
|
94649
|
+
if (inLock) {
|
|
94650
|
+
return runPushUpsert({
|
|
94651
|
+
workspaceSlug: args.workspaceSlug,
|
|
94652
|
+
entityType: args.entityType,
|
|
94653
|
+
baseUrl: args.baseUrl,
|
|
94654
|
+
pat: args.pat,
|
|
94655
|
+
absolutePath: args.absolutePath,
|
|
94656
|
+
raw: args.raw,
|
|
94657
|
+
parsed: args.parsed,
|
|
94658
|
+
stateEntry: inLock,
|
|
94659
|
+
state: args.state,
|
|
94660
|
+
mutex: args.mutex,
|
|
94661
|
+
fetchImpl: args.fetchImpl,
|
|
94662
|
+
now: args.now
|
|
94663
|
+
});
|
|
94664
|
+
}
|
|
94665
|
+
const fm = args.parsed.data ?? {};
|
|
94666
|
+
const userFm = userFrontmatter(fm);
|
|
94667
|
+
if ("title" in userFm)
|
|
94668
|
+
delete userFm.title;
|
|
94669
|
+
const title = resolveTitle(args.parsed.content, fm, args.slug);
|
|
94670
|
+
const htmlBody = markdownToHtml(args.parsed.content);
|
|
94671
|
+
const artistId = isOptionalUuid(fm.artist_id);
|
|
94672
|
+
const projectId = isOptionalUuid(fm.project_id);
|
|
94673
|
+
const createResult = await createDocument({
|
|
94674
|
+
baseUrl: args.baseUrl,
|
|
94675
|
+
pat: args.pat,
|
|
94676
|
+
slug: args.slug,
|
|
94677
|
+
title,
|
|
94678
|
+
body: htmlBody,
|
|
94679
|
+
frontmatter: userFm,
|
|
94680
|
+
artist_id: artistId,
|
|
94681
|
+
project_id: projectId,
|
|
94682
|
+
fetchImpl: args.fetchImpl
|
|
94683
|
+
});
|
|
94684
|
+
if (createResult.ok) {
|
|
94685
|
+
const doc2 = createResult.doc;
|
|
94686
|
+
const nextState2 = persistEntry(args.state, {
|
|
94687
|
+
id: doc2.id,
|
|
94688
|
+
slug: doc2.slug,
|
|
94689
|
+
updated_at: doc2.updated_at,
|
|
94690
|
+
revision: doc2.revision,
|
|
94691
|
+
body_hash: stableHash(args.parsed.content),
|
|
94692
|
+
frontmatter_hash: stableHash(userFm),
|
|
94693
|
+
inode: safeInode(args.absolutePath)
|
|
94694
|
+
});
|
|
94695
|
+
return {
|
|
94696
|
+
outcome: { kind: "created", entitySlug: doc2.slug },
|
|
94697
|
+
state: nextState2
|
|
94698
|
+
};
|
|
94699
|
+
}
|
|
94700
|
+
const stashPath = stashConflict({
|
|
94701
|
+
workspaceSlug: args.workspaceSlug,
|
|
94702
|
+
entityType: args.entityType,
|
|
94703
|
+
entitySlug: args.slug,
|
|
94704
|
+
timestamp: args.now(),
|
|
94705
|
+
contents: args.raw
|
|
94706
|
+
});
|
|
94707
|
+
const nextState = await pullAndWriteMirror({
|
|
94708
|
+
workspaceSlug: args.workspaceSlug,
|
|
94709
|
+
entityType: args.entityType,
|
|
94710
|
+
baseUrl: args.baseUrl,
|
|
94711
|
+
pat: args.pat,
|
|
94712
|
+
id: createResult.existing.id,
|
|
94713
|
+
state: args.state,
|
|
94714
|
+
fetchImpl: args.fetchImpl
|
|
94715
|
+
});
|
|
94236
94716
|
return {
|
|
94237
|
-
outcome: {
|
|
94238
|
-
|
|
94717
|
+
outcome: {
|
|
94718
|
+
kind: "create_slug_conflict",
|
|
94719
|
+
entitySlug: args.slug,
|
|
94720
|
+
stashPath
|
|
94721
|
+
},
|
|
94722
|
+
state: nextState
|
|
94239
94723
|
};
|
|
94724
|
+
});
|
|
94725
|
+
}
|
|
94726
|
+
async function runPushDelete(args) {
|
|
94727
|
+
let id2;
|
|
94728
|
+
let slug;
|
|
94729
|
+
let expectedUpdatedAt;
|
|
94730
|
+
if (args.unlinkContext) {
|
|
94731
|
+
id2 = args.unlinkContext.id;
|
|
94732
|
+
slug = args.unlinkContext.slug;
|
|
94733
|
+
expectedUpdatedAt = args.unlinkContext.updated_at;
|
|
94734
|
+
} else {
|
|
94735
|
+
const slugFromPath = basename(args.absolutePath, ".md");
|
|
94736
|
+
const entry2 = getEntryBySlug(args.state, slugFromPath);
|
|
94737
|
+
if (entry2) {
|
|
94738
|
+
id2 = entry2.id;
|
|
94739
|
+
slug = entry2.slug;
|
|
94740
|
+
expectedUpdatedAt = entry2.updated_at;
|
|
94741
|
+
}
|
|
94240
94742
|
}
|
|
94241
|
-
if (
|
|
94743
|
+
if (!id2 || !slug || !expectedUpdatedAt) {
|
|
94242
94744
|
return {
|
|
94243
|
-
outcome: {
|
|
94244
|
-
|
|
94745
|
+
outcome: {
|
|
94746
|
+
kind: "ignored",
|
|
94747
|
+
absolutePath: args.absolutePath,
|
|
94748
|
+
reason: "untracked file — no cloud row to delete"
|
|
94749
|
+
},
|
|
94750
|
+
state: args.state
|
|
94245
94751
|
};
|
|
94246
94752
|
}
|
|
94247
|
-
|
|
94248
|
-
|
|
94249
|
-
|
|
94250
|
-
|
|
94251
|
-
|
|
94252
|
-
|
|
94753
|
+
return args.mutex.run(id2, async () => {
|
|
94754
|
+
const live = getEntry(args.state, id2) ?? null;
|
|
94755
|
+
const effectiveExpected = live?.updated_at ?? expectedUpdatedAt;
|
|
94756
|
+
const result2 = await deleteDocument({
|
|
94757
|
+
baseUrl: args.baseUrl,
|
|
94758
|
+
pat: args.pat,
|
|
94759
|
+
id: id2,
|
|
94760
|
+
expectedUpdatedAt: effectiveExpected,
|
|
94761
|
+
fetchImpl: args.fetchImpl
|
|
94762
|
+
});
|
|
94763
|
+
if (result2.ok) {
|
|
94764
|
+
const nextState2 = dropEntry(args.state, id2);
|
|
94765
|
+
return {
|
|
94766
|
+
outcome: { kind: "deleted", entitySlug: slug },
|
|
94767
|
+
state: nextState2
|
|
94768
|
+
};
|
|
94769
|
+
}
|
|
94770
|
+
if (result2.code === 404) {
|
|
94771
|
+
const nextState2 = dropEntry(args.state, id2);
|
|
94772
|
+
return {
|
|
94773
|
+
outcome: { kind: "deleted", entitySlug: slug },
|
|
94774
|
+
state: nextState2
|
|
94775
|
+
};
|
|
94776
|
+
}
|
|
94777
|
+
const nextState = await pullAndWriteMirror({
|
|
94778
|
+
workspaceSlug: args.workspaceSlug,
|
|
94779
|
+
entityType: args.entityType,
|
|
94780
|
+
baseUrl: args.baseUrl,
|
|
94781
|
+
pat: args.pat,
|
|
94782
|
+
id: id2,
|
|
94783
|
+
state: args.state,
|
|
94784
|
+
fetchImpl: args.fetchImpl
|
|
94785
|
+
});
|
|
94786
|
+
return {
|
|
94787
|
+
outcome: { kind: "conflict", entitySlug: slug, stashPath: "" },
|
|
94788
|
+
state: nextState
|
|
94789
|
+
};
|
|
94253
94790
|
});
|
|
94254
|
-
|
|
94791
|
+
}
|
|
94792
|
+
async function pushRename(args) {
|
|
94793
|
+
const {
|
|
94794
|
+
workspaceSlug,
|
|
94255
94795
|
baseUrl,
|
|
94256
94796
|
pat,
|
|
94257
|
-
|
|
94258
|
-
|
|
94797
|
+
fromPath,
|
|
94798
|
+
toPath,
|
|
94799
|
+
id: id2,
|
|
94800
|
+
expectedUpdatedAt,
|
|
94801
|
+
fromSlug,
|
|
94802
|
+
state: state2,
|
|
94803
|
+
deps
|
|
94804
|
+
} = args;
|
|
94805
|
+
const fetchImpl = deps?.fetchImpl;
|
|
94806
|
+
const now = deps?.now ?? (() => new Date);
|
|
94807
|
+
const mutex = deps?.mutex ?? new PerIdMutex;
|
|
94808
|
+
const entityType = "documents";
|
|
94809
|
+
const toSlug = basename(toPath, ".md");
|
|
94810
|
+
return mutex.run(id2, async () => {
|
|
94811
|
+
const live = getEntry(state2, id2);
|
|
94812
|
+
const effectiveExpected = live?.updated_at ?? expectedUpdatedAt;
|
|
94813
|
+
const patchResult = await patchDocument({
|
|
94814
|
+
baseUrl,
|
|
94815
|
+
pat,
|
|
94816
|
+
id: id2,
|
|
94817
|
+
expectedUpdatedAt: effectiveExpected,
|
|
94818
|
+
slug: toSlug,
|
|
94819
|
+
fetchImpl
|
|
94820
|
+
});
|
|
94821
|
+
if (patchResult.ok) {
|
|
94822
|
+
const doc2 = patchResult.doc;
|
|
94823
|
+
const prior = live ?? null;
|
|
94824
|
+
const updated = {
|
|
94825
|
+
id: doc2.id,
|
|
94826
|
+
slug: doc2.slug,
|
|
94827
|
+
updated_at: doc2.updated_at,
|
|
94828
|
+
revision: doc2.revision,
|
|
94829
|
+
body_hash: prior?.body_hash ?? "",
|
|
94830
|
+
frontmatter_hash: prior?.frontmatter_hash ?? "",
|
|
94831
|
+
inode: safeInode(toPath)
|
|
94832
|
+
};
|
|
94833
|
+
if (!updated.body_hash || !updated.frontmatter_hash) {
|
|
94834
|
+
try {
|
|
94835
|
+
const mirrorRaw = readFileSync3(toPath, "utf8");
|
|
94836
|
+
const mirrorParsed = import_gray_matter2.default(mirrorRaw);
|
|
94837
|
+
updated.body_hash = stableHash(mirrorParsed.content);
|
|
94838
|
+
updated.frontmatter_hash = stableHash(userFrontmatter(mirrorParsed.data ?? {}));
|
|
94839
|
+
} catch {}
|
|
94840
|
+
}
|
|
94841
|
+
const nextState2 = persistEntry(state2, updated);
|
|
94842
|
+
return {
|
|
94843
|
+
outcome: { kind: "renamed", fromSlug, toSlug: doc2.slug },
|
|
94844
|
+
state: nextState2
|
|
94845
|
+
};
|
|
94846
|
+
}
|
|
94847
|
+
if (patchResult.code === 404) {
|
|
94848
|
+
return {
|
|
94849
|
+
outcome: { kind: "not_found", entitySlug: fromSlug },
|
|
94850
|
+
state: state2
|
|
94851
|
+
};
|
|
94852
|
+
}
|
|
94853
|
+
if (patchResult.reason === "slug_conflict") {
|
|
94854
|
+
let contents2 = "";
|
|
94855
|
+
try {
|
|
94856
|
+
contents2 = readFileSync3(toPath, "utf8");
|
|
94857
|
+
} catch {}
|
|
94858
|
+
const stashPath2 = stashConflict({
|
|
94859
|
+
workspaceSlug,
|
|
94860
|
+
entityType,
|
|
94861
|
+
entitySlug: fromSlug,
|
|
94862
|
+
timestamp: now(),
|
|
94863
|
+
contents: contents2
|
|
94864
|
+
});
|
|
94865
|
+
const afterColliding = await pullAndWriteMirror({
|
|
94866
|
+
workspaceSlug,
|
|
94867
|
+
entityType,
|
|
94868
|
+
baseUrl,
|
|
94869
|
+
pat,
|
|
94870
|
+
id: patchResult.existing.id,
|
|
94871
|
+
state: state2,
|
|
94872
|
+
fetchImpl
|
|
94873
|
+
});
|
|
94874
|
+
const finalState = await pullAndWriteMirror({
|
|
94875
|
+
workspaceSlug,
|
|
94876
|
+
entityType,
|
|
94877
|
+
baseUrl,
|
|
94878
|
+
pat,
|
|
94879
|
+
id: id2,
|
|
94880
|
+
state: afterColliding,
|
|
94881
|
+
fetchImpl
|
|
94882
|
+
});
|
|
94883
|
+
return {
|
|
94884
|
+
outcome: {
|
|
94885
|
+
kind: "rename_slug_conflict",
|
|
94886
|
+
fromSlug,
|
|
94887
|
+
toSlug,
|
|
94888
|
+
stashPath: stashPath2
|
|
94889
|
+
},
|
|
94890
|
+
state: finalState
|
|
94891
|
+
};
|
|
94892
|
+
}
|
|
94893
|
+
let contents = "";
|
|
94894
|
+
try {
|
|
94895
|
+
contents = readFileSync3(toPath, "utf8");
|
|
94896
|
+
} catch {}
|
|
94897
|
+
const stashPath = stashConflict({
|
|
94898
|
+
workspaceSlug,
|
|
94899
|
+
entityType,
|
|
94900
|
+
entitySlug: fromSlug,
|
|
94901
|
+
timestamp: now(),
|
|
94902
|
+
contents
|
|
94903
|
+
});
|
|
94904
|
+
const nextState = await pullAndWriteMirror({
|
|
94905
|
+
workspaceSlug,
|
|
94906
|
+
entityType,
|
|
94907
|
+
baseUrl,
|
|
94908
|
+
pat,
|
|
94909
|
+
id: id2,
|
|
94910
|
+
state: state2,
|
|
94911
|
+
fetchImpl
|
|
94912
|
+
});
|
|
94913
|
+
return {
|
|
94914
|
+
outcome: { kind: "conflict", entitySlug: fromSlug, stashPath },
|
|
94915
|
+
state: nextState
|
|
94916
|
+
};
|
|
94259
94917
|
});
|
|
94260
|
-
|
|
94261
|
-
|
|
94262
|
-
|
|
94263
|
-
|
|
94264
|
-
|
|
94265
|
-
|
|
94266
|
-
|
|
94267
|
-
|
|
94268
|
-
|
|
94269
|
-
|
|
94270
|
-
const mirrorPath = entityFilePath(workspaceSlug, entityType, cloud.slug);
|
|
94271
|
-
const mirrorRaw = readFileSync3(mirrorPath, "utf8");
|
|
94272
|
-
const mirrorParsed = import_gray_matter2.default(mirrorRaw);
|
|
94273
|
-
refreshed.body_hash = stableHash(mirrorParsed.content);
|
|
94274
|
-
refreshed.frontmatter_hash = stableHash(userFrontmatter(mirrorParsed.data ?? {}));
|
|
94275
|
-
const { state: nextState } = upsertEntry(state2, refreshed);
|
|
94276
|
-
writeState(nextState);
|
|
94277
|
-
return {
|
|
94278
|
-
outcome: { kind: "conflict", entitySlug: slugFromFm, stashPath },
|
|
94279
|
-
state: nextState
|
|
94280
|
-
};
|
|
94918
|
+
}
|
|
94919
|
+
function isOptionalUuid(value2) {
|
|
94920
|
+
if (value2 === null)
|
|
94921
|
+
return null;
|
|
94922
|
+
if (typeof value2 !== "string")
|
|
94923
|
+
return;
|
|
94924
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value2)) {
|
|
94925
|
+
return value2;
|
|
94926
|
+
}
|
|
94927
|
+
return;
|
|
94281
94928
|
}
|
|
94282
94929
|
|
|
94283
94930
|
// src/commands/sync.ts
|
|
94284
94931
|
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
94285
|
-
import { existsSync as existsSync2, readdirSync } from "node:fs";
|
|
94932
|
+
import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "node:fs";
|
|
94286
94933
|
init_markdown();
|
|
94287
94934
|
var ENTITY_TYPE = "documents";
|
|
94288
94935
|
function hasExistingContent(workspacePath) {
|
|
@@ -94404,13 +95051,20 @@ function applyDocument(workspaceSlug, doc2, state2) {
|
|
|
94404
95051
|
writeMirrorFile(workspaceSlug, ENTITY_TYPE, doc2.slug, content2);
|
|
94405
95052
|
const parsed = import_gray_matter3.default(content2);
|
|
94406
95053
|
const userFm = userFrontmatter(parsed.data ?? {});
|
|
95054
|
+
let inode;
|
|
95055
|
+
try {
|
|
95056
|
+
inode = statSync2(entityFilePath(workspaceSlug, ENTITY_TYPE, doc2.slug)).ino;
|
|
95057
|
+
} catch {
|
|
95058
|
+
inode = undefined;
|
|
95059
|
+
}
|
|
94407
95060
|
const { state: nextState } = upsertEntry(state2, {
|
|
94408
95061
|
id: doc2.id,
|
|
94409
95062
|
slug: doc2.slug,
|
|
94410
95063
|
updated_at: doc2.updated_at,
|
|
94411
95064
|
revision: doc2.revision,
|
|
94412
95065
|
body_hash: stableHash(parsed.content),
|
|
94413
|
-
frontmatter_hash: stableHash(userFm)
|
|
95066
|
+
frontmatter_hash: stableHash(userFm),
|
|
95067
|
+
inode
|
|
94414
95068
|
});
|
|
94415
95069
|
return nextState;
|
|
94416
95070
|
}
|
|
@@ -94608,10 +95262,10 @@ class ReaddirpStream extends Readable {
|
|
|
94608
95262
|
}
|
|
94609
95263
|
async _formatEntry(dirent, path) {
|
|
94610
95264
|
let entry2;
|
|
94611
|
-
const
|
|
95265
|
+
const basename2 = this._isDirent ? dirent.name : dirent;
|
|
94612
95266
|
try {
|
|
94613
|
-
const fullPath = presolve(pjoin(path,
|
|
94614
|
-
entry2 = { path: prelative(this._root, fullPath), fullPath, basename };
|
|
95267
|
+
const fullPath = presolve(pjoin(path, basename2));
|
|
95268
|
+
entry2 = { path: prelative(this._root, fullPath), fullPath, basename: basename2 };
|
|
94615
95269
|
entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
94616
95270
|
} catch (err) {
|
|
94617
95271
|
this._onError(err);
|
|
@@ -95138,9 +95792,9 @@ class NodeFsHandler {
|
|
|
95138
95792
|
_watchWithNodeFs(path, listener) {
|
|
95139
95793
|
const opts = this.fsw.options;
|
|
95140
95794
|
const directory = sp.dirname(path);
|
|
95141
|
-
const
|
|
95795
|
+
const basename3 = sp.basename(path);
|
|
95142
95796
|
const parent2 = this.fsw._getWatchedDir(directory);
|
|
95143
|
-
parent2.add(
|
|
95797
|
+
parent2.add(basename3);
|
|
95144
95798
|
const absolutePath = sp.resolve(path);
|
|
95145
95799
|
const options3 = {
|
|
95146
95800
|
persistent: opts.persistent
|
|
@@ -95150,7 +95804,7 @@ class NodeFsHandler {
|
|
|
95150
95804
|
let closer;
|
|
95151
95805
|
if (opts.usePolling) {
|
|
95152
95806
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
95153
|
-
options3.interval = enableBin && isBinaryPath(
|
|
95807
|
+
options3.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
95154
95808
|
closer = setFsWatchFileListener(path, absolutePath, options3, {
|
|
95155
95809
|
listener,
|
|
95156
95810
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -95169,10 +95823,10 @@ class NodeFsHandler {
|
|
|
95169
95823
|
return;
|
|
95170
95824
|
}
|
|
95171
95825
|
const dirname6 = sp.dirname(file2);
|
|
95172
|
-
const
|
|
95826
|
+
const basename3 = sp.basename(file2);
|
|
95173
95827
|
const parent2 = this.fsw._getWatchedDir(dirname6);
|
|
95174
95828
|
let prevStats = stats;
|
|
95175
|
-
if (parent2.has(
|
|
95829
|
+
if (parent2.has(basename3))
|
|
95176
95830
|
return;
|
|
95177
95831
|
const listener = async (path, newStats) => {
|
|
95178
95832
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
|
|
@@ -95197,9 +95851,9 @@ class NodeFsHandler {
|
|
|
95197
95851
|
prevStats = newStats2;
|
|
95198
95852
|
}
|
|
95199
95853
|
} catch (error53) {
|
|
95200
|
-
this.fsw._remove(dirname6,
|
|
95854
|
+
this.fsw._remove(dirname6, basename3);
|
|
95201
95855
|
}
|
|
95202
|
-
} else if (parent2.has(
|
|
95856
|
+
} else if (parent2.has(basename3)) {
|
|
95203
95857
|
const at = newStats.atimeMs;
|
|
95204
95858
|
const mt = newStats.mtimeMs;
|
|
95205
95859
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -96053,7 +96707,121 @@ function watch(paths, options3 = {}) {
|
|
|
96053
96707
|
var chokidar_default = { watch, FSWatcher };
|
|
96054
96708
|
|
|
96055
96709
|
// src/lib/watcher.ts
|
|
96710
|
+
import { statSync as statSync3 } from "node:fs";
|
|
96056
96711
|
import { sep as sep3 } from "node:path";
|
|
96712
|
+
|
|
96713
|
+
// src/lib/rename-classifier.ts
|
|
96714
|
+
import { basename as basename4, dirname as dirname7 } from "node:path";
|
|
96715
|
+
|
|
96716
|
+
class RenameClassifier {
|
|
96717
|
+
buffer = new Map;
|
|
96718
|
+
emit;
|
|
96719
|
+
opts;
|
|
96720
|
+
constructor(emit, opts) {
|
|
96721
|
+
this.emit = emit;
|
|
96722
|
+
this.opts = opts;
|
|
96723
|
+
}
|
|
96724
|
+
onAdd(absolutePath) {
|
|
96725
|
+
this.handleArrival(absolutePath);
|
|
96726
|
+
}
|
|
96727
|
+
onChange(absolutePath) {
|
|
96728
|
+
this.handleArrival(absolutePath);
|
|
96729
|
+
}
|
|
96730
|
+
handleArrival(absolutePath) {
|
|
96731
|
+
let inode;
|
|
96732
|
+
try {
|
|
96733
|
+
inode = this.opts.statSync(absolutePath).ino;
|
|
96734
|
+
} catch {
|
|
96735
|
+
this.emit({ kind: "upsert", absolutePath });
|
|
96736
|
+
return;
|
|
96737
|
+
}
|
|
96738
|
+
const buffered2 = this.buffer.get(inode);
|
|
96739
|
+
if (buffered2) {
|
|
96740
|
+
clearTimeout(buffered2.timer);
|
|
96741
|
+
this.buffer.delete(inode);
|
|
96742
|
+
this.emit({
|
|
96743
|
+
kind: "renamed",
|
|
96744
|
+
fromPath: buffered2.absolutePath,
|
|
96745
|
+
toPath: absolutePath,
|
|
96746
|
+
id: buffered2.stateEntry.id,
|
|
96747
|
+
expectedUpdatedAt: buffered2.stateEntry.updated_at,
|
|
96748
|
+
fromSlug: buffered2.stateEntry.slug
|
|
96749
|
+
});
|
|
96750
|
+
return;
|
|
96751
|
+
}
|
|
96752
|
+
this.emit({ kind: "upsert", absolutePath });
|
|
96753
|
+
}
|
|
96754
|
+
onUnlink(absolutePath) {
|
|
96755
|
+
const slug = slugFromPath(absolutePath);
|
|
96756
|
+
if (slug === null) {
|
|
96757
|
+
this.emit({
|
|
96758
|
+
kind: "unlink",
|
|
96759
|
+
absolutePath,
|
|
96760
|
+
stateEntry: null,
|
|
96761
|
+
renameFallback: false
|
|
96762
|
+
});
|
|
96763
|
+
return;
|
|
96764
|
+
}
|
|
96765
|
+
const entry2 = this.opts.getStateEntry(slug);
|
|
96766
|
+
if (!entry2 || entry2.inode === undefined) {
|
|
96767
|
+
this.emit({
|
|
96768
|
+
kind: "unlink",
|
|
96769
|
+
absolutePath,
|
|
96770
|
+
stateEntry: entry2,
|
|
96771
|
+
renameFallback: true
|
|
96772
|
+
});
|
|
96773
|
+
return;
|
|
96774
|
+
}
|
|
96775
|
+
const inode = entry2.inode;
|
|
96776
|
+
const existing = this.buffer.get(inode);
|
|
96777
|
+
if (existing)
|
|
96778
|
+
clearTimeout(existing.timer);
|
|
96779
|
+
const timer = setTimeout(() => {
|
|
96780
|
+
this.buffer.delete(inode);
|
|
96781
|
+
this.opts.onWindowExpired?.(inode);
|
|
96782
|
+
this.emit({
|
|
96783
|
+
kind: "unlink",
|
|
96784
|
+
absolutePath,
|
|
96785
|
+
stateEntry: entry2,
|
|
96786
|
+
renameFallback: false
|
|
96787
|
+
});
|
|
96788
|
+
}, this.opts.pairingWindowMs);
|
|
96789
|
+
this.buffer.set(inode, {
|
|
96790
|
+
absolutePath,
|
|
96791
|
+
inode,
|
|
96792
|
+
stateEntry: entry2,
|
|
96793
|
+
timer
|
|
96794
|
+
});
|
|
96795
|
+
}
|
|
96796
|
+
shutdown() {
|
|
96797
|
+
const entries2 = Array.from(this.buffer.entries());
|
|
96798
|
+
this.buffer.clear();
|
|
96799
|
+
for (const [, buffered2] of entries2) {
|
|
96800
|
+
clearTimeout(buffered2.timer);
|
|
96801
|
+
this.emit({
|
|
96802
|
+
kind: "unlink",
|
|
96803
|
+
absolutePath: buffered2.absolutePath,
|
|
96804
|
+
stateEntry: buffered2.stateEntry,
|
|
96805
|
+
renameFallback: false
|
|
96806
|
+
});
|
|
96807
|
+
}
|
|
96808
|
+
}
|
|
96809
|
+
size() {
|
|
96810
|
+
return this.buffer.size;
|
|
96811
|
+
}
|
|
96812
|
+
}
|
|
96813
|
+
function slugFromPath(absolutePath) {
|
|
96814
|
+
const base = basename4(absolutePath);
|
|
96815
|
+
if (!base.endsWith(".md"))
|
|
96816
|
+
return null;
|
|
96817
|
+
const slug = base.slice(0, -3);
|
|
96818
|
+
if (slug.length === 0)
|
|
96819
|
+
return null;
|
|
96820
|
+
dirname7(absolutePath);
|
|
96821
|
+
return slug;
|
|
96822
|
+
}
|
|
96823
|
+
|
|
96824
|
+
// src/lib/watcher.ts
|
|
96057
96825
|
var DEFAULT_DEBOUNCE_MS = 500;
|
|
96058
96826
|
var DEFAULT_MAX_PENDING_PATHS = 5000;
|
|
96059
96827
|
function defaultIgnores(workspaceRoot) {
|
|
@@ -96082,15 +96850,16 @@ class DebounceMap {
|
|
|
96082
96850
|
this.maxPending = opts.maxPending;
|
|
96083
96851
|
}
|
|
96084
96852
|
schedule(event) {
|
|
96085
|
-
const
|
|
96853
|
+
const key = debounceKey(event);
|
|
96854
|
+
const existing = this.pending.get(key);
|
|
96086
96855
|
if (existing) {
|
|
96087
96856
|
clearTimeout(existing.timer);
|
|
96088
96857
|
}
|
|
96089
96858
|
const timer = setTimeout(() => {
|
|
96090
|
-
this.pending.delete(
|
|
96859
|
+
this.pending.delete(key);
|
|
96091
96860
|
this.handler(event);
|
|
96092
96861
|
}, this.debounceMs);
|
|
96093
|
-
this.pending.set(
|
|
96862
|
+
this.pending.set(key, {
|
|
96094
96863
|
timer,
|
|
96095
96864
|
event,
|
|
96096
96865
|
insertedAt: Date.now()
|
|
@@ -96132,12 +96901,26 @@ class DebounceMap {
|
|
|
96132
96901
|
return this.pending.size;
|
|
96133
96902
|
}
|
|
96134
96903
|
}
|
|
96904
|
+
function debounceKey(event) {
|
|
96905
|
+
switch (event.kind) {
|
|
96906
|
+
case "upsert":
|
|
96907
|
+
case "unlink":
|
|
96908
|
+
return event.absolutePath;
|
|
96909
|
+
case "renamed":
|
|
96910
|
+
return event.toPath;
|
|
96911
|
+
}
|
|
96912
|
+
}
|
|
96135
96913
|
function startWatcher(opts, handler) {
|
|
96136
96914
|
const workspaceRoot = workspaceMirrorPath(opts.workspaceSlug);
|
|
96137
96915
|
const debounce = new DebounceMap(handler, {
|
|
96138
96916
|
debounceMs: opts.debounceMs ?? DEFAULT_DEBOUNCE_MS,
|
|
96139
96917
|
maxPending: opts.maxPendingPaths ?? DEFAULT_MAX_PENDING_PATHS
|
|
96140
96918
|
});
|
|
96919
|
+
const classifier = new RenameClassifier((event) => debounce.schedule(event), {
|
|
96920
|
+
pairingWindowMs: opts.renamePairingWindowMs ?? DEFAULT_DEBOUNCE_MS,
|
|
96921
|
+
getStateEntry: opts.getStateEntry,
|
|
96922
|
+
statSync: opts.statSync ?? ((p) => ({ ino: statSync3(p).ino }))
|
|
96923
|
+
});
|
|
96141
96924
|
const factory = opts.chokidarFactory ?? ((paths, watcherOpts) => chokidar_default.watch(paths, watcherOpts));
|
|
96142
96925
|
const watcher = factory(workspaceRoot, {
|
|
96143
96926
|
ignored: defaultIgnores(workspaceRoot),
|
|
@@ -96149,11 +96932,12 @@ function startWatcher(opts, handler) {
|
|
|
96149
96932
|
pollInterval: 50
|
|
96150
96933
|
}
|
|
96151
96934
|
});
|
|
96152
|
-
watcher.on("add", (p) =>
|
|
96153
|
-
watcher.on("change", (p) =>
|
|
96154
|
-
watcher.on("unlink", (p) =>
|
|
96935
|
+
watcher.on("add", (p) => classifier.onAdd(p));
|
|
96936
|
+
watcher.on("change", (p) => classifier.onChange(p));
|
|
96937
|
+
watcher.on("unlink", (p) => classifier.onUnlink(p));
|
|
96155
96938
|
return {
|
|
96156
96939
|
async stop() {
|
|
96940
|
+
classifier.shutdown();
|
|
96157
96941
|
await watcher.close();
|
|
96158
96942
|
},
|
|
96159
96943
|
drainPending() {
|
|
@@ -96167,6 +96951,90 @@ function startWatcher(opts, handler) {
|
|
|
96167
96951
|
|
|
96168
96952
|
// src/commands/start.ts
|
|
96169
96953
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
96954
|
+
function backfillStateInodes(workspaceSlug, state2, statFn = (p) => statSync4(p)) {
|
|
96955
|
+
let changed = false;
|
|
96956
|
+
const nextDocs = {};
|
|
96957
|
+
for (const [id2, entry2] of Object.entries(state2.documents)) {
|
|
96958
|
+
if (entry2.inode !== undefined) {
|
|
96959
|
+
nextDocs[id2] = entry2;
|
|
96960
|
+
continue;
|
|
96961
|
+
}
|
|
96962
|
+
try {
|
|
96963
|
+
const path = entityFilePath(workspaceSlug, "documents", entry2.slug);
|
|
96964
|
+
const inode = statFn(path).ino;
|
|
96965
|
+
nextDocs[id2] = { ...entry2, inode };
|
|
96966
|
+
changed = true;
|
|
96967
|
+
} catch {
|
|
96968
|
+
nextDocs[id2] = entry2;
|
|
96969
|
+
}
|
|
96970
|
+
}
|
|
96971
|
+
if (!changed)
|
|
96972
|
+
return { state: state2, changed: false };
|
|
96973
|
+
return { state: { ...state2, documents: nextDocs }, changed: true };
|
|
96974
|
+
}
|
|
96975
|
+
function makeWatchHandler(deps) {
|
|
96976
|
+
const {
|
|
96977
|
+
workspaceSlug,
|
|
96978
|
+
baseUrl,
|
|
96979
|
+
pat,
|
|
96980
|
+
spinner,
|
|
96981
|
+
mutex,
|
|
96982
|
+
getState,
|
|
96983
|
+
setState,
|
|
96984
|
+
fetchImpl,
|
|
96985
|
+
now
|
|
96986
|
+
} = deps;
|
|
96987
|
+
return async (event) => {
|
|
96988
|
+
try {
|
|
96989
|
+
if (event.kind === "renamed") {
|
|
96990
|
+
const result3 = await pushRename({
|
|
96991
|
+
workspaceSlug,
|
|
96992
|
+
baseUrl,
|
|
96993
|
+
pat,
|
|
96994
|
+
fromPath: event.fromPath,
|
|
96995
|
+
toPath: event.toPath,
|
|
96996
|
+
id: event.id,
|
|
96997
|
+
expectedUpdatedAt: event.expectedUpdatedAt,
|
|
96998
|
+
fromSlug: event.fromSlug,
|
|
96999
|
+
state: getState(),
|
|
97000
|
+
deps: { fetchImpl, now, mutex }
|
|
97001
|
+
});
|
|
97002
|
+
setState(result3.state);
|
|
97003
|
+
reportOutcome(spinner, workspaceSlug, result3.state, result3.outcome);
|
|
97004
|
+
return;
|
|
97005
|
+
}
|
|
97006
|
+
const absolutePath = event.kind === "upsert" ? event.absolutePath : event.absolutePath;
|
|
97007
|
+
const classified = classifyMirrorPath({ workspaceSlug, absolutePath });
|
|
97008
|
+
if (!classified)
|
|
97009
|
+
return;
|
|
97010
|
+
if (event.kind === "unlink" && event.renameFallback) {
|
|
97011
|
+
const slugHint = event.stateEntry?.slug ? `documents/${event.stateEntry.slug}` : absolutePath;
|
|
97012
|
+
spinner.warn(`Rename pair miss for ${slugHint} — no inode in state, treated as delete (classifier degraded to create+delete). ` + `If you just ran \`mv\`, the cloud row was likely tombstoned; check ~/.plinth/_conflicts/ or restore from cloud.`);
|
|
97013
|
+
}
|
|
97014
|
+
const result2 = await pushFile({
|
|
97015
|
+
workspaceSlug,
|
|
97016
|
+
baseUrl,
|
|
97017
|
+
pat,
|
|
97018
|
+
absolutePath,
|
|
97019
|
+
entityType: classified.entityType,
|
|
97020
|
+
state: getState(),
|
|
97021
|
+
exists: event.kind === "upsert",
|
|
97022
|
+
unlinkContext: event.kind === "unlink" && event.stateEntry ? {
|
|
97023
|
+
id: event.stateEntry.id,
|
|
97024
|
+
slug: event.stateEntry.slug,
|
|
97025
|
+
updated_at: event.stateEntry.updated_at
|
|
97026
|
+
} : null,
|
|
97027
|
+
deps: { fetchImpl, now, mutex }
|
|
97028
|
+
});
|
|
97029
|
+
setState(result2.state);
|
|
97030
|
+
reportOutcome(spinner, workspaceSlug, result2.state, result2.outcome);
|
|
97031
|
+
} catch (err) {
|
|
97032
|
+
const msg = err instanceof Error ? err.message : "unknown push error";
|
|
97033
|
+
const path = event.kind === "renamed" ? event.toPath : event.absolutePath;
|
|
97034
|
+
spinner.warn(`Push failed for ${path}: ${msg}`);
|
|
97035
|
+
}
|
|
97036
|
+
};
|
|
97037
|
+
}
|
|
96170
97038
|
async function runStart(workspaceSlug, deps = {}) {
|
|
96171
97039
|
const out = new Output({ sink: deps.sink, noColor: deps.noColor });
|
|
96172
97040
|
const keychain = deps.keychain ?? macKeychain;
|
|
@@ -96196,40 +97064,44 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
96196
97064
|
out.error(`No personal access token in Keychain for "${slug}".`);
|
|
96197
97065
|
return { exitCode: 1 };
|
|
96198
97066
|
}
|
|
96199
|
-
const warnedPaths = new Set;
|
|
96200
97067
|
let state2 = loadState(slug);
|
|
97068
|
+
const backfill = backfillStateInodes(slug, state2);
|
|
97069
|
+
if (backfill.changed) {
|
|
97070
|
+
state2 = backfill.state;
|
|
97071
|
+
writeState(state2);
|
|
97072
|
+
}
|
|
96201
97073
|
const spinner = (deps.spinnerFactory ?? (() => createSpinner({ noColor: deps.noColor })))();
|
|
96202
97074
|
spinner.start(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
96203
|
-
const
|
|
96204
|
-
|
|
96205
|
-
|
|
96206
|
-
|
|
96207
|
-
|
|
96208
|
-
|
|
96209
|
-
|
|
96210
|
-
|
|
96211
|
-
|
|
96212
|
-
|
|
96213
|
-
|
|
96214
|
-
pat,
|
|
96215
|
-
absolutePath: event.absolutePath,
|
|
96216
|
-
entityType: classified.entityType,
|
|
96217
|
-
state: state2,
|
|
96218
|
-
exists: event.kind === "upsert",
|
|
96219
|
-
deps: { fetchImpl, now: deps.now }
|
|
96220
|
-
});
|
|
96221
|
-
state2 = result2.state;
|
|
96222
|
-
reportOutcome(spinner, slug, state2, result2.outcome, warnedPaths);
|
|
96223
|
-
} catch (err) {
|
|
96224
|
-
const msg = err instanceof Error ? err.message : "unknown push error";
|
|
96225
|
-
spinner.warn(`Push failed for ${event.absolutePath}: ${msg}`);
|
|
96226
|
-
}
|
|
97075
|
+
const mutex = new PerIdMutex;
|
|
97076
|
+
const getStateEntry = (entitySlug) => {
|
|
97077
|
+
const entry3 = getEntryBySlug(state2, entitySlug);
|
|
97078
|
+
if (!entry3)
|
|
97079
|
+
return null;
|
|
97080
|
+
return {
|
|
97081
|
+
id: entry3.id,
|
|
97082
|
+
slug: entry3.slug,
|
|
97083
|
+
updated_at: entry3.updated_at,
|
|
97084
|
+
inode: entry3.inode
|
|
97085
|
+
};
|
|
96227
97086
|
};
|
|
97087
|
+
const handler = makeWatchHandler({
|
|
97088
|
+
workspaceSlug: slug,
|
|
97089
|
+
baseUrl: entry2.api_base_url,
|
|
97090
|
+
pat,
|
|
97091
|
+
spinner,
|
|
97092
|
+
mutex,
|
|
97093
|
+
getState: () => state2,
|
|
97094
|
+
setState: (s2) => {
|
|
97095
|
+
state2 = s2;
|
|
97096
|
+
},
|
|
97097
|
+
fetchImpl,
|
|
97098
|
+
now: deps.now
|
|
97099
|
+
});
|
|
96228
97100
|
if (deps.skipWatch) {
|
|
96229
97101
|
spinner.stop();
|
|
96230
97102
|
return { exitCode: 0 };
|
|
96231
97103
|
}
|
|
96232
|
-
const watcher = startWatcher({ workspaceSlug: slug }, handler);
|
|
97104
|
+
const watcher = startWatcher({ workspaceSlug: slug, getStateEntry }, handler);
|
|
96233
97105
|
const shutdown = (signal2) => {
|
|
96234
97106
|
(async () => {
|
|
96235
97107
|
spinner.warn(`Received ${signal2}, draining pending pushes…`);
|
|
@@ -96252,35 +97124,41 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
96252
97124
|
});
|
|
96253
97125
|
return { exitCode: 0 };
|
|
96254
97126
|
}
|
|
96255
|
-
function reportOutcome(spinner, slug, state2, outcome
|
|
97127
|
+
function reportOutcome(spinner, slug, state2, outcome) {
|
|
96256
97128
|
switch (outcome.kind) {
|
|
96257
97129
|
case "pushed":
|
|
96258
97130
|
spinner.success(`Pushed documents/${outcome.entitySlug}`);
|
|
96259
97131
|
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
96260
97132
|
return;
|
|
97133
|
+
case "created":
|
|
97134
|
+
spinner.success(`Created documents/${outcome.entitySlug}`);
|
|
97135
|
+
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
97136
|
+
return;
|
|
97137
|
+
case "deleted":
|
|
97138
|
+
spinner.success(`Deleted documents/${outcome.entitySlug}`);
|
|
97139
|
+
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
97140
|
+
return;
|
|
97141
|
+
case "renamed":
|
|
97142
|
+
spinner.success(`Renamed documents/${outcome.fromSlug} → documents/${outcome.toSlug}`);
|
|
97143
|
+
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
97144
|
+
return;
|
|
96261
97145
|
case "noop":
|
|
96262
97146
|
return;
|
|
96263
97147
|
case "conflict":
|
|
96264
97148
|
spinner.warn(`Conflict on documents/${outcome.entitySlug} → ${outcome.stashPath} (cloud-wins; local copy stashed)`);
|
|
96265
97149
|
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
96266
97150
|
return;
|
|
96267
|
-
case "
|
|
96268
|
-
spinner.warn(`documents/${outcome.entitySlug}
|
|
97151
|
+
case "create_slug_conflict":
|
|
97152
|
+
spinner.warn(`Slug conflict on documents/${outcome.entitySlug} → ${outcome.stashPath} (cloud copy adopted; local stashed)`);
|
|
97153
|
+
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
96269
97154
|
return;
|
|
96270
|
-
case "
|
|
96271
|
-
|
|
96272
|
-
|
|
96273
|
-
warnedPaths.add(outcome.absolutePath);
|
|
96274
|
-
spinner.warn(`${outcome.absolutePath} - new local file not pushed (rename + create handling lands in PR #8)`);
|
|
97155
|
+
case "rename_slug_conflict":
|
|
97156
|
+
spinner.warn(`Rename slug conflict documents/${outcome.fromSlug} → documents/${outcome.toSlug}: target slug already exists on cloud. Local stashed at ${outcome.stashPath}`);
|
|
97157
|
+
spinner.update(`Watching ${slug} · ${Object.keys(state2.documents).length} docs tracked`);
|
|
96275
97158
|
return;
|
|
96276
|
-
|
|
96277
|
-
|
|
96278
|
-
if (warnedPaths.has(outcome.absolutePath))
|
|
96279
|
-
return;
|
|
96280
|
-
warnedPaths.add(outcome.absolutePath);
|
|
96281
|
-
spinner.warn(`${outcome.absolutePath} - local delete not pushed (delete handling lands in PR #8)`);
|
|
97159
|
+
case "not_found":
|
|
97160
|
+
spinner.warn(`documents/${outcome.entitySlug} not on cloud (deleted or never existed)`);
|
|
96282
97161
|
return;
|
|
96283
|
-
}
|
|
96284
97162
|
case "ignored":
|
|
96285
97163
|
return;
|
|
96286
97164
|
}
|