amalgm 0.1.221 → 0.1.234
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/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/state/attachments.js +7 -1
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +39 -2
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +180 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +47 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +28 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +152 -12
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +6 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +105 -67
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +183 -23
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +99 -206
- package/runtime/scripts/amalgm-mcp/workspace/card.js +30 -9
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +3 -2
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +194 -20
- package/runtime/scripts/amalgm-mcp/workspace/git.js +23 -5
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +24 -186
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +13 -1
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +323 -0
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +115 -101
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +7 -7
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +106 -137
|
@@ -24,13 +24,14 @@ const {
|
|
|
24
24
|
readCheckpoint,
|
|
25
25
|
repoKeyOfCard,
|
|
26
26
|
sameTransition,
|
|
27
|
+
stampCheckpointMutation,
|
|
27
28
|
} = require('./checkpoint');
|
|
29
|
+
const projectStream = require('./project-stream');
|
|
28
30
|
|
|
29
|
-
const WALL_CONTRACT =
|
|
31
|
+
const { WALL_CONTRACT } = projectStream;
|
|
30
32
|
const PROJECT_DIR = path.join(AMALGM_DIR, 'state', 'code-projects');
|
|
31
33
|
const REGISTRY_FILE = path.join(PROJECT_DIR, 'projects.json');
|
|
32
34
|
const PUBLISH_QUIET_MS = 25;
|
|
33
|
-
const COMPACT_AT_BYTES = 1024 * 1024;
|
|
34
35
|
const ENSURE_STEP_TIMEOUT_MS = 20_000;
|
|
35
36
|
const ENSURE_INTERVAL_MS = 15_000;
|
|
36
37
|
const ATTACH_REJECTED = /shared_resource_read_forbidden/;
|
|
@@ -46,10 +47,6 @@ const service = {
|
|
|
46
47
|
lastPublishedAt: null,
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
function sha256(text) {
|
|
50
|
-
return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
function readRegistry() {
|
|
54
51
|
try {
|
|
55
52
|
const parsed = JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf8'));
|
|
@@ -67,14 +64,14 @@ function writeRegistry(registry) {
|
|
|
67
64
|
function projectResourceId(repoKey, ownerUserId = userId()) {
|
|
68
65
|
if (!repoKey || !ownerUserId) return null;
|
|
69
66
|
const digest = crypto.createHash('sha256')
|
|
70
|
-
.update(`amalgm-code-project@
|
|
67
|
+
.update(`amalgm-code-project@2|${ownerUserId}|${repoKey}`)
|
|
71
68
|
.digest('base64url');
|
|
72
69
|
return `shr_cp1${digest.slice(0, 40)}`;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
function projectFile(resourceId) {
|
|
76
73
|
const digest = crypto.createHash('sha256').update(resourceId).digest('hex').slice(0, 24);
|
|
77
|
-
return path.join(PROJECT_DIR, `${digest}.
|
|
74
|
+
return path.join(PROJECT_DIR, `${digest}.json`);
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
function projectForRepo(repoKey) {
|
|
@@ -132,7 +129,8 @@ function projects() {
|
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
function wallReplica(project) {
|
|
135
|
-
|
|
132
|
+
const replica = require('../state/replicas').getReplicaByResourceId(project.resourceId);
|
|
133
|
+
return replica?.contract === projectStream.STREAM_CONTRACT ? replica : null;
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
function publicationStatus(repoKey, reduced = undefined) {
|
|
@@ -158,57 +156,38 @@ function publicationStatus(repoKey, reduced = undefined) {
|
|
|
158
156
|
};
|
|
159
157
|
}
|
|
160
158
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|| !entry.machine
|
|
168
|
-
|| !entry.mutationId
|
|
169
|
-
|| !Array.isArray(entry.cards)
|
|
170
|
-
) return null;
|
|
171
|
-
return entry;
|
|
172
|
-
} catch {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function sealCardVersions(project, card) {
|
|
159
|
+
function sealCardVersions(project, card, record = null) {
|
|
160
|
+
const versionFor = (mutationId) => (
|
|
161
|
+
mutationId === record?.mutationId && validVersion(record?.version)
|
|
162
|
+
? record.version
|
|
163
|
+
: officialVersion(project.resourceId, mutationId)
|
|
164
|
+
);
|
|
178
165
|
return {
|
|
179
166
|
...card,
|
|
180
167
|
positionVersion: validVersion(card.positionVersion)
|
|
181
168
|
? card.positionVersion
|
|
182
|
-
:
|
|
169
|
+
: versionFor(card.positionMutationId),
|
|
183
170
|
branches: (card.branches || []).map((ref) => ({
|
|
184
171
|
...ref,
|
|
185
172
|
version: validVersion(ref.version)
|
|
186
173
|
? ref.version
|
|
187
|
-
:
|
|
174
|
+
: versionFor(ref.mutationId),
|
|
188
175
|
})),
|
|
189
176
|
checkpoints: (card.checkpoints || []).map((checkpoint) => ({
|
|
190
177
|
...checkpoint,
|
|
191
178
|
version: validVersion(checkpoint.version)
|
|
192
179
|
? checkpoint.version
|
|
193
|
-
:
|
|
180
|
+
: versionFor(checkpoint.mutationId),
|
|
194
181
|
})),
|
|
195
182
|
};
|
|
196
183
|
}
|
|
197
184
|
|
|
198
185
|
function readProjectWall(project) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (entry?.project !== project.repoKey) continue;
|
|
205
|
-
entries.set(entry.machine, {
|
|
206
|
-
...entry,
|
|
207
|
-
resourceId: project.resourceId,
|
|
208
|
-
cards: entry.cards.map((card) => sealCardVersions(project, card)),
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return [...entries.values()];
|
|
186
|
+
return projectStream.records(project).map((entry) => ({
|
|
187
|
+
...entry,
|
|
188
|
+
resourceId: project.resourceId,
|
|
189
|
+
cards: entry.cards.map((card) => sealCardVersions(project, card, entry)),
|
|
190
|
+
}));
|
|
212
191
|
}
|
|
213
192
|
|
|
214
193
|
function readWall(repoKey = null) {
|
|
@@ -228,7 +207,6 @@ function publishedCardFor(card) {
|
|
|
228
207
|
|| published.head?.branch !== card.head?.branch
|
|
229
208
|
|| published.head?.oid !== card.head?.oid
|
|
230
209
|
|| Boolean(published.head?.detached) !== Boolean(card.head?.detached)
|
|
231
|
-
|| hasObjectUpgrade(card, { card: published, branches: published.branches })
|
|
232
210
|
) return null;
|
|
233
211
|
return published;
|
|
234
212
|
}
|
|
@@ -304,16 +282,17 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
|
|
|
304
282
|
return false;
|
|
305
283
|
}
|
|
306
284
|
|
|
307
|
-
function
|
|
308
|
-
if (!plan?.card || !samePosition(card, plan.card))
|
|
309
|
-
|
|
285
|
+
function hasPendingContributionObjects(card, plan) {
|
|
286
|
+
if (!plan?.card || !samePosition(card, plan.card)) {
|
|
287
|
+
return card.objects?.kind === 'pending';
|
|
288
|
+
}
|
|
310
289
|
const shared = new Map((plan.branches || []).map((ref) => [ref.name, ref]));
|
|
311
290
|
return (card.refs || []).some((ref) => {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
&&
|
|
315
|
-
&&
|
|
316
|
-
|
|
291
|
+
const accepted = shared.get(ref.name);
|
|
292
|
+
const alreadyAvailable = accepted
|
|
293
|
+
&& validVersion(accepted.version)
|
|
294
|
+
&& (accepted.oid ?? null) === (ref.oid ?? null);
|
|
295
|
+
return !alreadyAvailable && ref.objects?.kind === 'pending';
|
|
317
296
|
});
|
|
318
297
|
}
|
|
319
298
|
|
|
@@ -348,16 +327,6 @@ function checkpointBaseFor(card, baseline, project, shared = null) {
|
|
|
348
327
|
};
|
|
349
328
|
}
|
|
350
329
|
|
|
351
|
-
function hasCheckpointBaseUpgrade(card, plan, baseline, project) {
|
|
352
|
-
const shared = plan?.card?.checkpointBase;
|
|
353
|
-
const candidate = checkpointBaseFor(card, baseline, project, shared);
|
|
354
|
-
return Boolean(shared && candidate) && (
|
|
355
|
-
candidate.through === shared.through
|
|
356
|
-
&& candidate.value?.revision === shared.value?.revision
|
|
357
|
-
&& JSON.stringify(candidate.value) !== JSON.stringify(shared.value)
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
330
|
function validVersion(value) {
|
|
362
331
|
return Number.isInteger(value) && value > 0;
|
|
363
332
|
}
|
|
@@ -373,6 +342,30 @@ function officialVersion(resourceId, mutationId) {
|
|
|
373
342
|
return Number.isInteger(mutation?.officialCloudVersion) ? mutation.officialCloudVersion : null;
|
|
374
343
|
}
|
|
375
344
|
|
|
345
|
+
function durableMutationVersion(project, mutationId) {
|
|
346
|
+
if (!project || !mutationId) return null;
|
|
347
|
+
for (const record of projectStream.records(project)) {
|
|
348
|
+
if (record.mutationId === mutationId && validVersion(record.version)) return record.version;
|
|
349
|
+
for (const card of record.cards) {
|
|
350
|
+
if (card.positionMutationId === mutationId) {
|
|
351
|
+
return validVersion(card.positionVersion) ? card.positionVersion : null;
|
|
352
|
+
}
|
|
353
|
+
const nested = [
|
|
354
|
+
...(card.branches || []),
|
|
355
|
+
...(card.checkpoints || []),
|
|
356
|
+
].find((entry) => entry.mutationId === mutationId);
|
|
357
|
+
if (nested) return validVersion(nested.version) ? nested.version : null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function mutationVersion(resourceId, mutationId, repoKey = null) {
|
|
364
|
+
if (!resourceId) return null;
|
|
365
|
+
return officialVersion(resourceId, mutationId)
|
|
366
|
+
|| durableMutationVersion(repoKey ? projectForRepo(repoKey) : null, mutationId);
|
|
367
|
+
}
|
|
368
|
+
|
|
376
369
|
function sealedVersion(resourceId, mutationId, ...versions) {
|
|
377
370
|
return versions.find(validVersion)
|
|
378
371
|
|| officialVersion(resourceId, mutationId)
|
|
@@ -537,41 +530,18 @@ function stampCards(cards, previousCards, sharedPlan, baseline, mutationId, proj
|
|
|
537
530
|
});
|
|
538
531
|
}
|
|
539
532
|
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
533
|
+
function cardsUseMutation(cards, mutationId) {
|
|
534
|
+
return cards.some((card) => (
|
|
535
|
+
card.positionMutationId === mutationId
|
|
536
|
+
|| (card.branches || []).some((ref) => ref.mutationId === mutationId)
|
|
537
|
+
|| (card.checkpoints || []).some((checkpoint) => checkpoint.mutationId === mutationId)
|
|
538
|
+
));
|
|
545
539
|
}
|
|
546
540
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
* beginning. Each pass is one deletion, so concurrent appends commute in Yjs.
|
|
552
|
-
*/
|
|
553
|
-
function compactWallText(current, repoKey) {
|
|
554
|
-
const latest = new Map();
|
|
555
|
-
const lines = current.split('\n');
|
|
556
|
-
let offset = 0;
|
|
557
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
558
|
-
const line = lines[index];
|
|
559
|
-
const entry = line ? parseWallLine(line) : null;
|
|
560
|
-
const end = offset + line.length + (index < lines.length - 1 ? 1 : 0);
|
|
561
|
-
if (entry?.project === repoKey) latest.set(entry.machine, { start: offset, end });
|
|
562
|
-
offset = end;
|
|
563
|
-
}
|
|
564
|
-
if (latest.size === 0) return current;
|
|
565
|
-
const records = [...latest.values()].sort((left, right) => left.start - right.start);
|
|
566
|
-
const gaps = [{ start: 0, end: records[0].start }];
|
|
567
|
-
for (let index = 0; index < records.length - 1; index += 1) {
|
|
568
|
-
gaps.push({ start: records[index].end, end: records[index + 1].start });
|
|
569
|
-
}
|
|
570
|
-
const gap = gaps.reduce((largest, candidate) => (
|
|
571
|
-
candidate.end - candidate.start > largest.end - largest.start ? candidate : largest
|
|
572
|
-
));
|
|
573
|
-
if (gap.end <= gap.start) return current;
|
|
574
|
-
return `${current.slice(0, gap.start)}${current.slice(gap.end)}`;
|
|
541
|
+
function samePublishedRecord(left, right) {
|
|
542
|
+
if (!left || !right) return false;
|
|
543
|
+
const stripTransport = ({ publishedAt, mutationId, version, ...record }) => record;
|
|
544
|
+
return JSON.stringify(stripTransport(left)) === JSON.stringify(stripTransport(right));
|
|
575
545
|
}
|
|
576
546
|
|
|
577
547
|
function publishProject(project, rawCards) {
|
|
@@ -584,33 +554,26 @@ function publishProject(project, rawCards) {
|
|
|
584
554
|
service.counters.publishSkips += 1;
|
|
585
555
|
return { published: false, reason: 'repo-materializing', repoKey: project.repoKey };
|
|
586
556
|
}
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
let previous = null;
|
|
590
|
-
let previousEntry = null;
|
|
591
|
-
for (const line of current.split('\n')) {
|
|
592
|
-
if (!line) continue;
|
|
593
|
-
const entry = parseWallLine(line);
|
|
594
|
-
if (entry?.project === project.repoKey && entry.machine === machineId()) {
|
|
595
|
-
previous = line;
|
|
596
|
-
previousEntry = entry;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
557
|
+
const previousEntry = projectStream.records(project)
|
|
558
|
+
.find((entry) => entry.machine === machineId()) || null;
|
|
599
559
|
|
|
600
560
|
const reducer = require('./reducer');
|
|
601
561
|
const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
|
|
602
562
|
const baseline = reducer.readRepoState(project.repoKey);
|
|
603
|
-
const previousCards = (previousEntry?.cards || [])
|
|
563
|
+
const previousCards = (previousEntry?.cards || [])
|
|
564
|
+
.map((card) => sealCardVersions(project, card, previousEntry));
|
|
604
565
|
const previousReplicas = new Set(previousCards.map((card) => card.replica));
|
|
605
566
|
const currentReplicas = new Set(rawCards.map((card) => card.replica));
|
|
606
567
|
const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
|
|
607
568
|
const contributes = rawCards.some((card) => {
|
|
608
569
|
const checkpoint = readCheckpoint(machineId(), project.repoKey);
|
|
609
570
|
const previousCard = previousCards.find((entry) => entry.replica === card.replica);
|
|
610
|
-
return hasLocalContribution(card, sharedPlan, baseline, checkpoint, previousCard)
|
|
611
|
-
|| hasObjectUpgrade(card, sharedPlan)
|
|
612
|
-
|| hasCheckpointBaseUpgrade(card, sharedPlan, baseline, project);
|
|
571
|
+
return hasLocalContribution(card, sharedPlan, baseline, checkpoint, previousCard);
|
|
613
572
|
});
|
|
573
|
+
if (contributes && rawCards.some((card) => hasPendingContributionObjects(card, sharedPlan))) {
|
|
574
|
+
service.counters.publishSkips += 1;
|
|
575
|
+
return { published: false, reason: 'objects-preparing', repoKey: project.repoKey };
|
|
576
|
+
}
|
|
614
577
|
if (!removed && !contributes) {
|
|
615
578
|
service.counters.publishSkips += 1;
|
|
616
579
|
return { published: false, reason: 'shared-state-covered', repoKey: project.repoKey };
|
|
@@ -624,20 +587,22 @@ function publishProject(project, rawCards) {
|
|
|
624
587
|
mutationId,
|
|
625
588
|
project,
|
|
626
589
|
);
|
|
627
|
-
const mine =
|
|
590
|
+
const mine = {
|
|
628
591
|
wall: WALL_CONTRACT,
|
|
629
592
|
project: project.repoKey,
|
|
630
593
|
machine: machineId(),
|
|
631
594
|
mutationId,
|
|
632
595
|
publishedAt: new Date().toISOString(),
|
|
633
596
|
cards,
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
&& withoutPublishedAt(previous) === withoutPublishedAt(mine);
|
|
637
|
-
if (unchanged) {
|
|
597
|
+
};
|
|
598
|
+
if (samePublishedRecord(previousEntry, mine)) {
|
|
638
599
|
service.counters.publishSkips += 1;
|
|
639
600
|
return { published: false, reason: 'unchanged', repoKey: project.repoKey };
|
|
640
601
|
}
|
|
602
|
+
if (!cardsUseMutation(cards, mutationId)) {
|
|
603
|
+
service.counters.publishSkips += 1;
|
|
604
|
+
return { published: false, reason: 'no-content-change', repoKey: project.repoKey };
|
|
605
|
+
}
|
|
641
606
|
const status = publicationStatus(project.repoKey, baseline);
|
|
642
607
|
if (!status.current) {
|
|
643
608
|
service.counters.publishSkips += 1;
|
|
@@ -649,31 +614,22 @@ function publishProject(project, rawCards) {
|
|
|
649
614
|
};
|
|
650
615
|
}
|
|
651
616
|
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
mutationId: `mut_project_compact_${crypto.randomBytes(16).toString('base64url')}`,
|
|
659
|
-
});
|
|
660
|
-
current = docs.getDocText(project.file);
|
|
617
|
+
for (const card of cards) {
|
|
618
|
+
const checkpoint = (card.checkpoints || []).find((entry) => (
|
|
619
|
+
entry.machine === machineId() && entry.mutationId === mutationId
|
|
620
|
+
));
|
|
621
|
+
if (checkpoint) {
|
|
622
|
+
stampCheckpointMutation(machineId(), project.repoKey, checkpoint, mutationId);
|
|
661
623
|
}
|
|
662
624
|
}
|
|
663
|
-
const
|
|
664
|
-
const result = docs.writeDocText(
|
|
665
|
-
project.file,
|
|
666
|
-
`${current}${nextSeparator}${mine}\n`,
|
|
667
|
-
{ baseHash: sha256(current), mutationId },
|
|
668
|
-
);
|
|
625
|
+
const result = projectStream.publish(project, mine);
|
|
669
626
|
service.counters.publishes += 1;
|
|
670
627
|
service.lastPublishedAt = new Date().toISOString();
|
|
671
628
|
return {
|
|
672
629
|
published: true,
|
|
673
630
|
repoKey: project.repoKey,
|
|
674
631
|
resourceId: project.resourceId,
|
|
675
|
-
mutationId: result.mutationId,
|
|
676
|
-
merge: result.merge,
|
|
632
|
+
mutationId: result.mutation.mutationId,
|
|
677
633
|
cards: cards.length,
|
|
678
634
|
};
|
|
679
635
|
}
|
|
@@ -748,6 +704,7 @@ async function ensureProject(project) {
|
|
|
748
704
|
await callLocal(shared.handleAttachmentStart, {
|
|
749
705
|
resourceId: project.resourceId,
|
|
750
706
|
path: project.file,
|
|
707
|
+
contract: projectStream.STREAM_CONTRACT,
|
|
751
708
|
});
|
|
752
709
|
} else if (request.state === 'rejected') {
|
|
753
710
|
if (
|
|
@@ -758,6 +715,9 @@ async function ensureProject(project) {
|
|
|
758
715
|
await callLocal(shared.handlePromotionStart, {
|
|
759
716
|
resourceId: project.resourceId,
|
|
760
717
|
path: project.file,
|
|
718
|
+
project: project.repoKey,
|
|
719
|
+
contract: projectStream.STREAM_CONTRACT,
|
|
720
|
+
schemaVersion: 1,
|
|
761
721
|
});
|
|
762
722
|
await waitForSettled(project, () => promotions.getPromotionByResource(project.resourceId));
|
|
763
723
|
}
|
|
@@ -802,13 +762,17 @@ function startWallService() {
|
|
|
802
762
|
if (service.started) return;
|
|
803
763
|
service.started = true;
|
|
804
764
|
service.unsubscribe = subscribeStateEvents((event) => {
|
|
805
|
-
const
|
|
806
|
-
|
|
765
|
+
const acknowledgedProject = event.resource === 'shared-mutations'
|
|
766
|
+
? projects().find((project) => project.resourceId === event.sharedResourceId)
|
|
767
|
+
: null;
|
|
768
|
+
if (acknowledgedProject) {
|
|
769
|
+
projectStream.seal(acknowledgedProject, event.mutationId || event.id, event.resourceVersion);
|
|
770
|
+
}
|
|
807
771
|
if (
|
|
808
772
|
event.resource === 'repo-cards'
|
|
809
773
|
|| event.resource === 'repo-checkpoints'
|
|
810
774
|
|| event.resource === 'repo-objects'
|
|
811
|
-
||
|
|
775
|
+
|| acknowledgedProject
|
|
812
776
|
) {
|
|
813
777
|
ensureProjects().catch(() => {});
|
|
814
778
|
schedulePublish();
|
|
@@ -865,12 +829,17 @@ function wallStats() {
|
|
|
865
829
|
}
|
|
866
830
|
|
|
867
831
|
module.exports = {
|
|
868
|
-
_test: {
|
|
832
|
+
_test: {
|
|
833
|
+
cardsUseMutation,
|
|
834
|
+
hasPendingContributionObjects,
|
|
835
|
+
publishSelected,
|
|
836
|
+
samePublishedRecord,
|
|
837
|
+
},
|
|
869
838
|
WALL_CONTRACT,
|
|
870
839
|
bindProject,
|
|
871
|
-
compactWallText,
|
|
872
840
|
hasLocalContribution,
|
|
873
841
|
isCommittedCard,
|
|
842
|
+
mutationVersion,
|
|
874
843
|
projectFile,
|
|
875
844
|
projectForRepo,
|
|
876
845
|
projectResourceId,
|