dsh-offwork 0.0.0-stage → 1.0.1

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.
@@ -0,0 +1,96 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { readFile, readdir } from 'node:fs/promises';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const root = new URL('../', import.meta.url);
7
+ const cwd = fileURLToPath(root);
8
+ const read = (path) => readFile(new URL(path, root), 'utf8');
9
+ const pkg = JSON.parse(await read('package.json'));
10
+
11
+ assert.equal(pkg.name, 'dsh-offwork');
12
+ assert.equal(pkg.type, 'module');
13
+ assert.equal(pkg.license, 'MIT');
14
+ assert.equal(pkg.repository.url, 'git+https://github.com/SnowBeatRain/dsh-offwork.git');
15
+ assert.ok(pkg.engines?.dsh, 'Declare the supported Harness versions');
16
+ assert.equal(pkg.dsh?.bundle?.patch, './cordis.patch.yml');
17
+ assert.equal(pkg.dsh?.client?.platform, 'web');
18
+ assert.match(await read('LICENSE'), /^MIT License/);
19
+
20
+ // This check covers this project's patch; Harness composition remains the
21
+ // authoritative check that the bundle can be installed in a real profile.
22
+ const patch = await read('cordis.patch.yml');
23
+ assert.match(patch, /^- insert:/m);
24
+ assert.match(patch, /^\s+- id: offwork-clock\s*$/m);
25
+ assert.match(patch, /^\s+name: ['"]?dsh-offwork['"]?\s*$/m);
26
+
27
+ for (const lifecycle of ['preinstall', 'install', 'postinstall', 'prepare']) {
28
+ assert.equal(pkg.scripts?.[lifecycle], undefined, `Unexpected install script: ${lifecycle}`);
29
+ }
30
+ for (const name of Object.keys(pkg.dependencies ?? {})) {
31
+ assert.ok(!name.startsWith('@deepseek-ai/'), `${name} belongs in peerDependencies`);
32
+ }
33
+
34
+ const sourceFiles = [];
35
+ for (const directory of ['', 'tests/', 'tools/']) {
36
+ for (const entry of await readdir(new URL(directory || '.', root), { withFileTypes: true })) {
37
+ if (entry.isFile() && /\.(?:js|mjs)$/.test(entry.name)) {
38
+ sourceFiles.push(directory + entry.name);
39
+ }
40
+ }
41
+ }
42
+ for (const path of sourceFiles) {
43
+ execFileSync(process.execPath, ['--check', path], { cwd, stdio: 'pipe' });
44
+ }
45
+
46
+ for (const language of ['zh', 'en']) {
47
+ const locale = JSON.parse(await read(`locale/${language}.json`));
48
+ assert.equal(typeof locale.meta?.title, 'string');
49
+ assert.ok(locale.meta.title.length > 0);
50
+ assert.equal(typeof locale.meta?.description, 'string');
51
+ assert.ok(locale.meta.description.length > 0);
52
+ }
53
+
54
+ // Ignore lifecycle scripts here: this checker also runs inside prepublishOnly.
55
+ // Inspect npm's actual package file list, rather than relying on .gitignore.
56
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
57
+ const [packed] = JSON.parse(
58
+ execFileSync(npm, ['pack', '--dry-run', '--json', '--ignore-scripts'], {
59
+ cwd,
60
+ encoding: 'utf8',
61
+ stdio: ['ignore', 'pipe', 'pipe'],
62
+ }),
63
+ );
64
+ const files = new Set(packed.files.map((file) => file.path));
65
+ assert.equal(packed.name, pkg.name);
66
+ assert.equal(packed.version, pkg.version);
67
+ for (const path of [
68
+ 'package.json',
69
+ 'index.js',
70
+ 'client.js',
71
+ 'cordis.patch.yml',
72
+ 'icon.svg',
73
+ 'locale/zh.json',
74
+ 'locale/en.json',
75
+ 'README.md',
76
+ 'LICENSE',
77
+ ]) {
78
+ assert.ok(files.has(path), `Missing package file: ${path}`);
79
+ }
80
+ for (const target of Object.values(pkg.exports)) {
81
+ if (typeof target === 'string' && !target.includes('*')) {
82
+ assert.ok(files.has(target.replace(/^\.\//, '')), `Missing export: ${target}`);
83
+ }
84
+ }
85
+ for (const path of files) {
86
+ assert.ok(
87
+ !/(^|\/)(?:data\.json|\.data-[^/]*|\.env(?:\.[^/]*)?|\.npmrc|\.git(?:\/|$)|node_modules(?:\/|$))/.test(
88
+ path,
89
+ ),
90
+ `Local settings or credentials included in package: ${path}`,
91
+ );
92
+ assert.ok(!path.endsWith('.tgz'), `Nested package archive: ${path}`);
93
+ }
94
+ console.log(
95
+ `Package checks passed: ${sourceFiles.length} JavaScript files, ${files.size} packed files.`,
96
+ );
package/ui-utils.js ADDED
@@ -0,0 +1,62 @@
1
+ // Small pure helpers shared by the browser artifact and its tests.
2
+ export function formatDuration(seconds) {
3
+ if (!Number.isFinite(seconds) || seconds < 0) return '--:--:--';
4
+ const n = Math.ceil(seconds);
5
+ return [Math.floor(n / 3600), Math.floor((n % 3600) / 60), n % 60]
6
+ .map((part) => String(part).padStart(2, '0'))
7
+ .join(':');
8
+ }
9
+
10
+ export function clampPosition(position, viewport, size) {
11
+ const width = Math.max(0, viewport.width),
12
+ height = Math.max(0, viewport.height);
13
+ const edge = Math.min(12, width / 2, height / 2);
14
+ const maxX = Math.max(edge, width - size.width - edge);
15
+ const maxY = Math.max(edge, height - size.height - edge);
16
+ return {
17
+ x: Math.max(edge, Math.min(Number.isFinite(position?.x) ? position.x : maxX, maxX)),
18
+ y: Math.max(edge, Math.min(Number.isFinite(position?.y) ? position.y : 88, maxY)),
19
+ };
20
+ }
21
+
22
+ export function clampOpacity(value, fallback = 0.82) {
23
+ const number = Number(value);
24
+ if (!Number.isFinite(number)) return fallback;
25
+ return Math.min(1, Math.max(0.3, number));
26
+ }
27
+
28
+ export function normalizeView(value) {
29
+ return {
30
+ mini: value?.mini !== false,
31
+ masked: value?.masked !== false,
32
+ opacity: clampOpacity(value?.opacity),
33
+ position:
34
+ Number.isFinite(value?.position?.x) && Number.isFinite(value?.position?.y)
35
+ ? { x: value.position.x, y: value.position.y }
36
+ : null,
37
+ };
38
+ }
39
+
40
+ export function decodeCommand(rpc) {
41
+ if (!rpc || rpc.ok !== true) throw new Error(rpc?.error?.message || 'RPC unavailable');
42
+ const result = rpc.value?.result;
43
+ if (!result || typeof result.text !== 'string') throw new Error('Command unavailable');
44
+ let data;
45
+ try {
46
+ data = JSON.parse(result.text);
47
+ } catch {
48
+ throw new Error('Invalid command response');
49
+ }
50
+ if (result.kind !== 'success' || data?.ok !== true)
51
+ throw new Error(data?.error || 'Request failed');
52
+ if (!data.settings || typeof data.settings !== 'object') throw new Error('Missing settings');
53
+ return data;
54
+ }
55
+
56
+ export function changedSettings(original, draft) {
57
+ return Object.fromEntries(
58
+ Object.entries(draft).filter(
59
+ ([key, value]) => JSON.stringify(value) !== JSON.stringify(original[key]),
60
+ ),
61
+ );
62
+ }