dsh-workspace-mover 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/client/client.js +1918 -0
- package/cordis.patch.yml +14 -0
- package/docs/ARCHITECTURE.md +141 -0
- package/docs/media/batch_move_selection.png +0 -0
- package/docs/media/confirm_popup.png +0 -0
- package/docs/media/drag_session_to_another_workspace.png +0 -0
- package/docs/media/original_workspace.png +0 -0
- package/docs/media/remove_popup.png +0 -0
- package/docs/media/setting_dialogue_repair.png +0 -0
- package/docs/media/workspace_after_move.png +0 -0
- package/docs/media/workspace_after_rename.png +0 -0
- package/docs/media/workspace_examination.png +0 -0
- package/docs/media/workspace_move.png +0 -0
- package/lib/index.js +2446 -0
- package/package.json +62 -0
- package/test/core.test.mjs +124 -0
- package/test/e2e-sandbox.test.mjs +1804 -0
- package/test/release-downloads.test.mjs +102 -0
- package/test/traffic-badge.test.mjs +66 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { applyReleaseBadgeToMarkdown, buildReleaseBadgeFiles, RELEASE_BADGE_END, RELEASE_BADGE_START, sanitizeGistPatchFiles, summarizeReleaseAssets } from '../.github/scripts/release-downloads-core.mjs';
|
|
6
|
+
|
|
7
|
+
const gistId = 'c14345658550a4a308570acfbaf9d170';
|
|
8
|
+
|
|
9
|
+
test('counts prefixed tgz/zip assets and hides the badge at 10 or fewer downloads', () => {
|
|
10
|
+
const summary = summarizeReleaseAssets([
|
|
11
|
+
{
|
|
12
|
+
tag_name: 'v1.4.2',
|
|
13
|
+
assets: [
|
|
14
|
+
{ name: 'dsh-workspace-mover-1.4.2.tgz', download_count: 6 },
|
|
15
|
+
{ name: 'dsh-workspace-mover-1.4.2.zip', download_count: 4 },
|
|
16
|
+
{ name: 'source.tar.gz', download_count: 99 }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]);
|
|
20
|
+
assert.equal(summary.total, 10);
|
|
21
|
+
assert.equal(summary.visible, false);
|
|
22
|
+
assert.equal(summary.assets.length, 2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('shows a shields.io endpoint badge only after the total exceeds 10', () => {
|
|
26
|
+
const summary = summarizeReleaseAssets([
|
|
27
|
+
{ tag_name: 'v2.0.0', assets: [{ name: 'dsh-workspace-mover-2.0.0.tgz', download_count: 11 }] }
|
|
28
|
+
]);
|
|
29
|
+
assert.equal(summary.visible, true);
|
|
30
|
+
const files = buildReleaseBadgeFiles({ total: summary.total, assets: summary.assets, updatedAt: '2026-09-10T00:00:00Z', visible: summary.visible });
|
|
31
|
+
const badge = JSON.parse(files['wsm-release-downloads.json'].content);
|
|
32
|
+
assert.equal(badge.label, 'Release downloads');
|
|
33
|
+
assert.equal(badge.message, '11');
|
|
34
|
+
assert.equal(badge.color, 'blue');
|
|
35
|
+
assert.equal(files['wsm-release-downloads.svg'], null);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('hidden state deletes gist SVG and shields files so GitHub cannot render a broken alt-text link', () => {
|
|
39
|
+
const files = buildReleaseBadgeFiles({ total: 3, assets: [], updatedAt: '2026-09-10T00:00:00Z', visible: false });
|
|
40
|
+
assert.equal(files['wsm-release-downloads.json'], null);
|
|
41
|
+
assert.equal(files['wsm-release-downloads.svg'], null);
|
|
42
|
+
const data = JSON.parse(files['wsm-release-downloads-data.json'].content);
|
|
43
|
+
assert.equal(data.total, 3);
|
|
44
|
+
assert.equal(data.visible, false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('sanitizeGistPatchFiles skips deletes for files that are not on the gist', () => {
|
|
48
|
+
const patch = buildReleaseBadgeFiles({ total: 3, assets: [], updatedAt: '2026-09-10T00:00:00Z', visible: false });
|
|
49
|
+
const sanitized = sanitizeGistPatchFiles({ 'wsm-release-downloads-data.json': { content: '{}' } }, patch);
|
|
50
|
+
assert.deepEqual(Object.keys(sanitized).sort(), ['wsm-release-downloads-data.json']);
|
|
51
|
+
assert.equal(sanitized['wsm-release-downloads.json'], undefined);
|
|
52
|
+
assert.equal(sanitized['wsm-release-downloads.svg'], undefined);
|
|
53
|
+
|
|
54
|
+
const withExisting = sanitizeGistPatchFiles(
|
|
55
|
+
{
|
|
56
|
+
'wsm-release-downloads-data.json': { content: '{}' },
|
|
57
|
+
'wsm-release-downloads.json': { content: '{}' },
|
|
58
|
+
'wsm-clones-14d.json': { content: 'x' }
|
|
59
|
+
},
|
|
60
|
+
{ ...patch, 'wsm-clones-14d.json': null }
|
|
61
|
+
);
|
|
62
|
+
assert.equal(withExisting['wsm-release-downloads.json'], null);
|
|
63
|
+
assert.equal(withExisting['wsm-release-downloads.svg'], undefined);
|
|
64
|
+
assert.equal(withExisting['wsm-clones-14d.json'], null);
|
|
65
|
+
assert.equal(typeof withExisting['wsm-release-downloads-data.json'].content, 'string');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('sanitizeGistPatchFiles turns empty content into a delete only when the file exists', () => {
|
|
69
|
+
const empty = sanitizeGistPatchFiles({}, { 'a.json': { content: '' } });
|
|
70
|
+
assert.deepEqual(empty, {});
|
|
71
|
+
const present = sanitizeGistPatchFiles({ 'a.json': { content: 'x' } }, { 'a.json': { content: '' } });
|
|
72
|
+
assert.deepEqual(present, { 'a.json': null });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('README keeps an invisible marker below 10 and a shields graphic above 10', () => {
|
|
76
|
+
const source = [
|
|
77
|
+
'<p>',
|
|
78
|
+
' <img alt="GitHub clones observed" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FPianoPrince%2Fc14345658550a4a308570acfbaf9d170%2Fraw%2Fwsm-clones-total.json" style="height:20px; margin:0 2px;" />',
|
|
79
|
+
' <img alt="Release downloads (shown after 10 downloads)" src="https://gist.githubusercontent.com/PianoPrince/c14345658550a4a308570acfbaf9d170/raw/wsm-release-downloads.svg" style="height:20px; margin:0 2px;" />',
|
|
80
|
+
'</p>'
|
|
81
|
+
].join('\n');
|
|
82
|
+
const hidden = applyReleaseBadgeToMarkdown(source, { visible: false, gistId });
|
|
83
|
+
assert.equal(hidden.includes('wsm-release-downloads.svg'), false);
|
|
84
|
+
assert.equal(hidden.includes('Release downloads (shown after 10 downloads)'), false);
|
|
85
|
+
assert.equal(hidden.includes('<img alt="Release downloads"'), false);
|
|
86
|
+
assert.equal(hidden.includes(RELEASE_BADGE_START), true);
|
|
87
|
+
assert.equal(hidden.includes(RELEASE_BADGE_END), true);
|
|
88
|
+
const shown = applyReleaseBadgeToMarkdown(hidden, { visible: true, gistId });
|
|
89
|
+
assert.equal(shown.includes('img.shields.io/endpoint?url='), true);
|
|
90
|
+
assert.equal(shown.includes('wsm-release-downloads.json'), true);
|
|
91
|
+
assert.equal(shown.includes('wsm-release-downloads.svg'), false);
|
|
92
|
+
assert.equal(shown.includes('<img alt="Release downloads"'), true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('extract-changelog accepts both current and historical heading styles', () => {
|
|
96
|
+
const script = fileURLToPath(new URL('../.github/scripts/extract-changelog.mjs', import.meta.url));
|
|
97
|
+
const current = execFileSync(process.execPath, [script, 'v2.0.1'], { encoding: 'utf8' });
|
|
98
|
+
const historical = execFileSync(process.execPath, [script, 'v1.4.2'], { encoding: 'utf8' });
|
|
99
|
+
assert.match(current, /Compatible with DeepSeek Harness/);
|
|
100
|
+
assert.match(historical, /GitHub Release tags now publish/);
|
|
101
|
+
assert.doesNotMatch(historical, /Compatible with DeepSeek Harness/);
|
|
102
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildBadgeFiles, buildState, mergeHistory, normalizeClonesResponse, normalizePathsResponse, normalizeReferrersResponse, normalizeViewsResponse, parseHistory } from '../.github/scripts/traffic-badge-core.mjs';
|
|
4
|
+
|
|
5
|
+
const snapshot = {
|
|
6
|
+
count: 12,
|
|
7
|
+
uniques: 7,
|
|
8
|
+
clones: [
|
|
9
|
+
{ timestamp: '2026-09-08T00:00:00Z', count: 5, uniques: 3 },
|
|
10
|
+
{ timestamp: '2026-09-09T00:00:00Z', count: 7, uniques: 4 }
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
test('traffic response uses GitHub clones[] rows and rejects the old days[] shape', () => {
|
|
15
|
+
const normalized = normalizeClonesResponse(snapshot);
|
|
16
|
+
assert.deepEqual(normalized.days, [
|
|
17
|
+
{ date: '2026-09-08', count: 5, uniques: 3 },
|
|
18
|
+
{ date: '2026-09-09', count: 7, uniques: 4 }
|
|
19
|
+
]);
|
|
20
|
+
assert.throws(() => normalizeClonesResponse({ count: 12, uniques: 7, days: snapshot.clones }), /no clones array/);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('traffic normalizes views, referrers, and popular paths', () => {
|
|
24
|
+
assert.deepEqual(normalizeViewsResponse({ count: 9, uniques: 4, views: [{ timestamp: '2026-09-09T00:00:00Z', count: 9, uniques: 4 }] }).days, [
|
|
25
|
+
{ date: '2026-09-09', count: 9, uniques: 4 }
|
|
26
|
+
]);
|
|
27
|
+
assert.deepEqual(normalizeReferrersResponse([{ referrer: 'github.com', count: 3, uniques: 2 }]), [
|
|
28
|
+
{ referrer: 'github.com', count: 3, uniques: 2 }
|
|
29
|
+
]);
|
|
30
|
+
assert.deepEqual(normalizePathsResponse([{ path: '/README.md', title: 'README', count: 5, uniques: 3 }]), [
|
|
31
|
+
{ path: '/README.md', title: 'README', count: 5, uniques: 3 }
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('history upsert replaces corrected dates without double counting', () => {
|
|
36
|
+
const old = parseHistory('{"date":"2026-09-08","count":4,"uniques":2}\n');
|
|
37
|
+
const merged = mergeHistory(old, [
|
|
38
|
+
{ date: '2026-09-08', count: 5, uniques: 3 },
|
|
39
|
+
{ date: '2026-09-09', count: 7, uniques: 4 }
|
|
40
|
+
]);
|
|
41
|
+
assert.deepEqual(merged, [
|
|
42
|
+
{ date: '2026-09-08', count: 5, uniques: 3 },
|
|
43
|
+
{ date: '2026-09-09', count: 7, uniques: 4 }
|
|
44
|
+
]);
|
|
45
|
+
const state = buildState({}, merged, normalizeClonesResponse(snapshot), '2026-09-10T00:00:00Z');
|
|
46
|
+
assert.equal(state.cumulativeClones, 12);
|
|
47
|
+
assert.equal(state.observedSince, '2026-09-08');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('schema migration preserves the old value as a legacy estimate only', () => {
|
|
51
|
+
const state = buildState({ cumulative: 193 }, [], { ...normalizeClonesResponse(snapshot), days: [] }, '2026-09-10T00:00:00Z');
|
|
52
|
+
assert.equal(state.schemaVersion, 2);
|
|
53
|
+
assert.equal(state.cumulativeClones, 0);
|
|
54
|
+
assert.equal(state.legacyEstimate, 193);
|
|
55
|
+
assert.equal(state.lastWindow.count, 12);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('badge exposes only observed GitHub clones and 14d uniques', () => {
|
|
59
|
+
const state = buildState({}, parseHistory('{"date":"2026-09-08","count":5,"uniques":3}\n{"date":"2026-09-09","count":7,"uniques":4}\n'), normalizeClonesResponse(snapshot), '2026-09-10T00:00:00Z');
|
|
60
|
+
const files = buildBadgeFiles(state);
|
|
61
|
+
const total = JSON.parse(files['wsm-clones-total.json'].content);
|
|
62
|
+
assert.equal(total.label, 'GitHub clones observed');
|
|
63
|
+
assert.match(total.message, /12 since 2026-09-08/);
|
|
64
|
+
assert.match(total.message, /7 unique/);
|
|
65
|
+
assert.equal(files['wsm-clones-14d.json'], undefined);
|
|
66
|
+
});
|