dsh-desktop-kit 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +87 -2
- package/client.js +3349 -0
- package/cordis.patch.yml +5 -0
- package/index.js +80 -0
- package/package.json +60 -4
- package/plugins/dsh-gkb-ru-ui/cordis.patch.yml +6 -0
- package/plugins/dsh-gkb-ru-ui/lib/client.js +1083 -0
- package/plugins/dsh-gkb-ru-ui/lib/index.js +142 -0
- package/plugins/dsh-gkb-ru-ui/lib/skills.js +376 -0
- package/plugins/dsh-gkb-ru-ui/package.json +28 -0
- package/plugins/dsh-peak-guard/README.md +70 -0
- package/plugins/dsh-peak-guard/client.js +741 -0
- package/plugins/dsh-peak-guard/cordis.patch.yml +5 -0
- package/plugins/dsh-peak-guard/icon.svg +12 -0
- package/plugins/dsh-peak-guard/index.js +2 -0
- package/plugins/dsh-peak-guard/locale/en.json +6 -0
- package/plugins/dsh-peak-guard/locale/ru.json +6 -0
- package/plugins/dsh-peak-guard/locale/zh.json +6 -0
- package/plugins/dsh-peak-guard/package.json +33 -0
- package/plugins/dsh-profile-transfer/README.md +62 -0
- package/plugins/dsh-profile-transfer/bootstrap/01-INSTALL.cmd +7 -0
- package/plugins/dsh-profile-transfer/bootstrap/README-ARCHIVE.md +54 -0
- package/plugins/dsh-profile-transfer/bootstrap/README-/320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/232/320/220.md +42 -0
- package/plugins/dsh-profile-transfer/bootstrap/install-on-clean-machine.ps1 +119 -0
- package/plugins/dsh-profile-transfer/bootstrap//320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/230/320/242/320/254.cmd +5 -0
- package/plugins/dsh-profile-transfer/client.js +806 -0
- package/plugins/dsh-profile-transfer/cordis.patch.yml +3 -0
- package/plugins/dsh-profile-transfer/icon.svg +13 -0
- package/plugins/dsh-profile-transfer/index.js +375 -0
- package/plugins/dsh-profile-transfer/locale/en.json +6 -0
- package/plugins/dsh-profile-transfer/locale/ru.json +6 -0
- package/plugins/dsh-profile-transfer/locale/zh.json +6 -0
- package/plugins/dsh-profile-transfer/package.json +36 -0
- package/plugins/dsh-profile-transfer/transfer.js +1004 -0
- package/plugins/dsh-project-memory/README.md +71 -0
- package/plugins/dsh-project-memory/cordis.patch.yml +4 -0
- package/plugins/dsh-project-memory/icon.svg +12 -0
- package/plugins/dsh-project-memory/index.js +1240 -0
- package/plugins/dsh-project-memory/locale/en.json +6 -0
- package/plugins/dsh-project-memory/locale/ru.json +6 -0
- package/plugins/dsh-project-memory/locale/zh.json +6 -0
- package/plugins/dsh-project-memory/package.json +26 -0
- package/plugins/dsh-session-move/README.md +67 -0
- package/plugins/dsh-session-move/client.js +719 -0
- package/plugins/dsh-session-move/cordis.patch.yml +5 -0
- package/plugins/dsh-session-move/icon.svg +14 -0
- package/plugins/dsh-session-move/index.js +497 -0
- package/plugins/dsh-session-move/locale/en.json +6 -0
- package/plugins/dsh-session-move/locale/ru.json +6 -0
- package/plugins/dsh-session-move/locale/zh.json +6 -0
- package/plugins/dsh-session-move/package.json +31 -0
- package/skills/code-review-checklist/SKILL.md +37 -0
- package/skills/content-writing/README.md +87 -0
- package/skills/content-writing/SKILL.md +348 -0
- package/skills/content-writing/assets/checklists/fact-check.md +53 -0
- package/skills/content-writing/assets/checklists/publish.md +65 -0
- package/skills/content-writing/assets/checklists/social.md +40 -0
- package/skills/content-writing/assets/templates/article.md +47 -0
- package/skills/content-writing/assets/templates/brief.md +44 -0
- package/skills/content-writing/assets/templates/doctor-card.md +41 -0
- package/skills/content-writing/assets/templates/image-prompts.md +128 -0
- package/skills/content-writing/assets/templates/post-telegram.md +36 -0
- package/skills/content-writing/assets/templates/post-vk.md +42 -0
- package/skills/content-writing/assets/templates/press-release.md +56 -0
- package/skills/content-writing/assets/templates/science-pop-note.md +55 -0
- package/skills/content-writing/assets/templates/service-card.md +46 -0
- package/skills/content-writing/references/anti-ai-style.md +248 -0
- package/skills/content-writing/references/editing.md +185 -0
- package/skills/content-writing/references/fact-checking.md +205 -0
- package/skills/content-writing/references/government-medical.md +246 -0
- package/skills/content-writing/references/images.md +212 -0
- package/skills/content-writing/references/journalism.md +252 -0
- package/skills/content-writing/references/science-popular.md +218 -0
- package/skills/content-writing/references/seo-content.md +172 -0
- package/skills/content-writing/references/social-media.md +253 -0
- package/skills/content-writing/references/templates.md +291 -0
- package/skills/database-safety/SKILL.md +31 -0
- package/skills/debugging-playbook/SKILL.md +39 -0
- package/skills/design-critique/SKILL.md +118 -0
- package/skills/dev-workflow/SKILL.md +41 -0
- package/skills/git-and-ci/SKILL.md +40 -0
- package/skills/pg-upgrade-verify/SKILL.md +48 -0
- package/skills/project-deep-dive/SKILL.md +170 -0
- package/skills/project-deep-dive/references/recon-commands.md +78 -0
- package/skills/project-deep-dive/references/section-templates.md +102 -0
- package/skills/project-memory-maintenance/SKILL.md +170 -0
- package/skills/project-memory-maintenance/references/journal-template.md +48 -0
- package/skills/project-memory-maintenance/references/section-rules.md +57 -0
- package/skills/release-and-deploy/SKILL.md +39 -0
- package/skills/requirements-and-planning/SKILL.md +115 -0
- package/skills/security-review/SKILL.md +36 -0
- package/skills/seo/README.md +60 -0
- package/skills/seo/SKILL.md +329 -0
- package/skills/seo/assets/README.md +66 -0
- package/skills/seo/assets/scripts/LICENSE-playwright-core.txt +202 -0
- package/skills/seo/assets/scripts/README.md +179 -0
- package/skills/seo/assets/scripts/lib/args.mjs +105 -0
- package/skills/seo/assets/scripts/lib/browser.mjs +620 -0
- package/skills/seo/assets/scripts/lib/zip.mjs +191 -0
- package/skills/seo/assets/scripts/seo-audit.mjs +1675 -0
- package/skills/seo/assets/snippets/jsonld-breadcrumb.json +30 -0
- package/skills/seo/assets/snippets/jsonld-faq.json +39 -0
- package/skills/seo/assets/snippets/jsonld-medical-clinic.json +49 -0
- package/skills/seo/assets/snippets/jsonld-medical-webpage.json +33 -0
- package/skills/seo/assets/snippets/jsonld-organization.json +74 -0
- package/skills/seo/assets/snippets/jsonld-physician.json +46 -0
- package/skills/seo/assets/snippets/meta.blade.php +61 -0
- package/skills/seo/assets/snippets/robots.txt +61 -0
- package/skills/seo/assets/snippets/sitemap-urlset-example.xml +72 -0
- package/skills/seo/assets/snippets/sitemap.xml +36 -0
- package/skills/seo/references/audit-checklist.md +305 -0
- package/skills/seo/references/content-seo.md +198 -0
- package/skills/seo/references/eeat-medical.md +217 -0
- package/skills/seo/references/measurement.md +223 -0
- package/skills/seo/references/on-page.md +209 -0
- package/skills/seo/references/stack-recipes.md +693 -0
- package/skills/seo/references/structured-data.md +485 -0
- package/skills/seo/references/technical-seo.md +294 -0
- package/skills/service-desk-conventions/SKILL.md +40 -0
- package/skills/service-desk-db-safety/SKILL.md +52 -0
- package/skills/service-desk-release/SKILL.md +43 -0
- package/skills/service-desk-testing/SKILL.md +47 -0
- package/skills/service-desk-ui/SKILL.md +33 -0
- package/skills/skills-router/SKILL.md +91 -0
- package/skills/testing-strategy/SKILL.md +37 -0
- package/skills/ui-design-system/SKILL.md +45 -0
- package/skills/web-research/SKILL.md +40 -0
- package/skills/wow-design/SKILL.md +168 -0
- package/skills/wow-design/assets/kit/README.md +195 -0
- package/skills/wow-design/assets/kit/assets/css/base.css +158 -0
- package/skills/wow-design/assets/kit/assets/css/components.css +1001 -0
- package/skills/wow-design/assets/kit/assets/css/fonts.css +73 -0
- package/skills/wow-design/assets/kit/assets/css/pages.css +737 -0
- package/skills/wow-design/assets/kit/assets/css/tokens.css +218 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-golos-text.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-lora.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/LICENSE-unbounded.txt +93 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-700-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-700-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-italic.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/lora-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-400-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-500-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-600-normal.woff2 +0 -0
- package/skills/wow-design/assets/kit/assets/js/kit.js +550 -0
- package/skills/wow-design/assets/kit/pages/dashboard.html +539 -0
- package/skills/wow-design/assets/kit/pages/hospital.html +430 -0
- package/skills/wow-design/assets/kit/showcase.html +1164 -0
- package/skills/wow-design/assets/scripts/LICENSE-axe-core.txt +362 -0
- package/skills/wow-design/assets/scripts/LICENSE-playwright-core.txt +202 -0
- package/skills/wow-design/assets/scripts/README.md +312 -0
- package/skills/wow-design/assets/scripts/audit.mjs +1200 -0
- package/skills/wow-design/assets/scripts/capture.mjs +337 -0
- package/skills/wow-design/assets/scripts/package.json +30 -0
- package/skills/wow-design/references/anti-patterns.md +249 -0
- package/skills/wow-design/references/critique-rubric.md +259 -0
- package/skills/wow-design/references/design-brief-template.md +46 -0
- package/skills/wow-design/references/inspiration.md +200 -0
- package/skills/wow-design/references/presets.md +96 -0
- package/skills/wow-design/references/principles.md +368 -0
- package/skills/wow-design/references/stack-recipes.md +93 -0
- package/skills/wow-design/references/visual-qa.md +79 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Transfer and backups">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="tr-a" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#2f7df6"/>
|
|
5
|
+
<stop offset="1" stop-color="#173a8f"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="14" fill="url(#tr-a)"/>
|
|
9
|
+
<path d="M15 24h27l-6-6" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" opacity=".95"/>
|
|
10
|
+
<path d="M49 40H22l6 6" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" opacity=".95"/>
|
|
11
|
+
<rect x="13" y="30" width="13" height="4" rx="2" fill="#ffffff" opacity=".5"/>
|
|
12
|
+
<rect x="38" y="30" width="13" height="4" rx="2" fill="#ffffff" opacity=".5"/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-profile-transfer — Host half.
|
|
3
|
+
*
|
|
4
|
+
* Authenticated Connection RPC routes for the Settings page:
|
|
5
|
+
* options — what can be exported and how big it is
|
|
6
|
+
* export — build a .tar.gz transfer archive
|
|
7
|
+
* inspect — read an archive manifest without applying it
|
|
8
|
+
* import — restore an archive into <DSH_HOME> with a backup
|
|
9
|
+
* install — run `pnpm install` in the restored profile
|
|
10
|
+
* reveal — open the archive folder in the OS file manager
|
|
11
|
+
*
|
|
12
|
+
* All file work lives in transfer.js (pure Node, testable on temporary homes).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { spawn } from "node:child_process";
|
|
19
|
+
import {
|
|
20
|
+
applyBundle,
|
|
21
|
+
defaultHome,
|
|
22
|
+
describeSources,
|
|
23
|
+
exportBundle,
|
|
24
|
+
humanSize,
|
|
25
|
+
inspectBundle,
|
|
26
|
+
normalizeSelection,
|
|
27
|
+
} from "./transfer.js";
|
|
28
|
+
|
|
29
|
+
const ROUTE_BASE = "/api/dsh-profile-transfer";
|
|
30
|
+
const RPC_NAMESPACE = "dsh-profile-transfer";
|
|
31
|
+
const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
export const inject = ["connection"];
|
|
34
|
+
|
|
35
|
+
class TransferError extends Error {
|
|
36
|
+
constructor(code, message, details = {}) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.details = details;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function environment(ctx) {
|
|
44
|
+
const home = defaultHome();
|
|
45
|
+
let profileName = process.env.DSH_PROFILE ?? "desktop";
|
|
46
|
+
if (typeof profileName !== "string" || profileName.length === 0) profileName = "desktop";
|
|
47
|
+
const profileDir = path.join(home, "profiles", profileName);
|
|
48
|
+
return { home, profileName, profileDir };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function suggestedDestination() {
|
|
52
|
+
return path.join(os.homedir(), "Documents", "DSH-обмены");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function lastSummary(home) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(path.join(home, "profile-transfer-last.json"), "utf8"));
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Candidate pnpm invocations, most portable first: bundled runtime of the Desktop app, then PATH. */
|
|
64
|
+
function pnpmCandidates() {
|
|
65
|
+
const candidates = [];
|
|
66
|
+
const configured = process.env.PNPM_EXECUTABLE ?? process.env.DSH_PNPM;
|
|
67
|
+
if (typeof configured === "string" && configured.trim().length > 0) {
|
|
68
|
+
candidates.push({ command: configured.trim(), prefix: [], shell: false, label: "env" });
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
// The Desktop app bundles pnpm and runs it through its own Electron binary
|
|
72
|
+
// with ELECTRON_RUN_AS_NODE, exactly like resources/runtime/bin/node.cmd does.
|
|
73
|
+
const resources = path.join(path.dirname(process.execPath), "resources");
|
|
74
|
+
const pnpmCjs = path.join(resources, "runtime", "primary-runtime", "dependencies", "pnpm", "bin", "pnpm.cjs");
|
|
75
|
+
if (fs.existsSync(pnpmCjs)) {
|
|
76
|
+
candidates.push({ command: process.execPath, prefix: [pnpmCjs], shell: false, label: "bundled", env: { ELECTRON_RUN_AS_NODE: "1" } });
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
/* non-Desktop installs have no bundled runtime */
|
|
80
|
+
}
|
|
81
|
+
candidates.push({ command: "pnpm", prefix: [], shell: process.platform === "win32", label: "path" });
|
|
82
|
+
return candidates;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function runPnpmInstall(profileDir) {
|
|
86
|
+
const candidates = pnpmCandidates();
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
const started = Date.now();
|
|
89
|
+
let output = "";
|
|
90
|
+
let index = 0;
|
|
91
|
+
const attempt = () => {
|
|
92
|
+
const candidate = candidates[index];
|
|
93
|
+
if (candidate === undefined) {
|
|
94
|
+
resolve({ ok: false, code: "spawn-failed", output: `${output}\nни один из pnpm-кандидатов не запустился`, ms: Date.now() - started });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let child;
|
|
98
|
+
try {
|
|
99
|
+
child = spawn(candidate.command, [...candidate.prefix, "install"], {
|
|
100
|
+
cwd: profileDir,
|
|
101
|
+
shell: candidate.shell,
|
|
102
|
+
windowsHide: true,
|
|
103
|
+
env: { ...process.env, ...(candidate.env ?? {}) },
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
output += `\n[${candidate.label}] ${String(error)}`;
|
|
107
|
+
index += 1;
|
|
108
|
+
attempt();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let settled = false;
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
try {
|
|
114
|
+
child.kill();
|
|
115
|
+
} catch {
|
|
116
|
+
/* ignore */
|
|
117
|
+
}
|
|
118
|
+
}, INSTALL_TIMEOUT_MS);
|
|
119
|
+
const finish = (result) => {
|
|
120
|
+
if (settled) return;
|
|
121
|
+
settled = true;
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
resolve({ ...result, executable: candidate.command, via: candidate.label });
|
|
124
|
+
};
|
|
125
|
+
const push = (chunk) => {
|
|
126
|
+
output += chunk.toString();
|
|
127
|
+
if (output.length > 20000) output = output.slice(-20000);
|
|
128
|
+
};
|
|
129
|
+
child.stdout?.on("data", push);
|
|
130
|
+
child.stderr?.on("data", push);
|
|
131
|
+
child.on("error", (error) => {
|
|
132
|
+
output += `\n[${candidate.label}] ${String(error)}`;
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
if (settled) return;
|
|
135
|
+
settled = true;
|
|
136
|
+
index += 1;
|
|
137
|
+
attempt();
|
|
138
|
+
});
|
|
139
|
+
child.on("close", (code) => {
|
|
140
|
+
finish({ ok: code === 0, code: String(code), output: output.slice(-6000), ms: Date.now() - started });
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
attempt();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function runReveal(target) {
|
|
148
|
+
try {
|
|
149
|
+
const platform = process.platform;
|
|
150
|
+
let child;
|
|
151
|
+
if (platform === "win32") {
|
|
152
|
+
child = spawn("explorer.exe", [fs.existsSync(target) && fs.statSync(target).isFile() ? `/select,${target}` : target], {
|
|
153
|
+
detached: true,
|
|
154
|
+
stdio: "ignore",
|
|
155
|
+
});
|
|
156
|
+
} else if (platform === "darwin") {
|
|
157
|
+
child = spawn("open", [fs.existsSync(target) && fs.statSync(target).isFile() ? path.dirname(target) : target], { detached: true, stdio: "ignore" });
|
|
158
|
+
} else {
|
|
159
|
+
child = spawn("xdg-open", [fs.existsSync(target) && fs.statSync(target).isFile() ? path.dirname(target) : target], { detached: true, stdio: "ignore" });
|
|
160
|
+
}
|
|
161
|
+
child.unref?.();
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ------------------------------------------------------------------ */
|
|
169
|
+
/* handlers */
|
|
170
|
+
/* ------------------------------------------------------------------ */
|
|
171
|
+
|
|
172
|
+
async function handleOptions(ctx) {
|
|
173
|
+
const { home, profileName, profileDir } = environment(ctx);
|
|
174
|
+
const description = describeSources({ home, profile: profileName });
|
|
175
|
+
return {
|
|
176
|
+
home,
|
|
177
|
+
profile: profileName,
|
|
178
|
+
profileExists: fs.existsSync(profileDir),
|
|
179
|
+
destination: suggestedDestination(),
|
|
180
|
+
selection: description.selection,
|
|
181
|
+
groups: description.groups,
|
|
182
|
+
last: lastSummary(home),
|
|
183
|
+
platform: process.platform,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function handleExport(ctx, payload) {
|
|
188
|
+
const { home, profileName } = environment(ctx);
|
|
189
|
+
const selection = normalizeSelection(payload?.selection);
|
|
190
|
+
const destinationDirectory = typeof payload?.destinationDirectory === "string" && payload.destinationDirectory.trim().length > 0
|
|
191
|
+
? path.resolve(payload.destinationDirectory.trim())
|
|
192
|
+
: suggestedDestination();
|
|
193
|
+
const result = exportBundle({ home, profile: profileName, selection, destinationDirectory });
|
|
194
|
+
return {
|
|
195
|
+
ok: true,
|
|
196
|
+
archivePath: result.archivePath,
|
|
197
|
+
human: result.human,
|
|
198
|
+
bytes: result.bytes,
|
|
199
|
+
manifest: {
|
|
200
|
+
createdAt: result.manifest.createdAt,
|
|
201
|
+
totals: result.manifest.totals,
|
|
202
|
+
selection: result.manifest.selection,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function handleInspect(ctx, payload) {
|
|
208
|
+
const archivePath = typeof payload?.archivePath === "string" ? payload.archivePath.trim() : "";
|
|
209
|
+
if (archivePath.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к архиву");
|
|
210
|
+
if (!fs.existsSync(archivePath)) throw new TransferError("transfer/not-found", `архив не найден: ${archivePath}`);
|
|
211
|
+
const inspected = inspectBundle(archivePath);
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
archivePath,
|
|
215
|
+
human: inspected.human,
|
|
216
|
+
manifest: {
|
|
217
|
+
createdAt: inspected.manifest.createdAt,
|
|
218
|
+
host: inspected.manifest.host,
|
|
219
|
+
platform: inspected.manifest.platform,
|
|
220
|
+
dshVersion: inspected.manifest.dshVersion,
|
|
221
|
+
profile: inspected.manifest.profile,
|
|
222
|
+
selection: inspected.manifest.selection,
|
|
223
|
+
totals: inspected.manifest.totals,
|
|
224
|
+
localPlugins: inspected.manifest.localPlugins,
|
|
225
|
+
},
|
|
226
|
+
files: inspected.files.length,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function handleImport(ctx, payload) {
|
|
231
|
+
const { home, profileName } = environment(ctx);
|
|
232
|
+
const archivePath = typeof payload?.archivePath === "string" ? payload.archivePath.trim() : "";
|
|
233
|
+
if (archivePath.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к архиву");
|
|
234
|
+
if (!fs.existsSync(archivePath)) throw new TransferError("transfer/not-found", `архив не найден: ${archivePath}`);
|
|
235
|
+
const apply = applyBundle({
|
|
236
|
+
home,
|
|
237
|
+
profile: profileName,
|
|
238
|
+
archivePath,
|
|
239
|
+
selection: payload?.selection,
|
|
240
|
+
backup: payload?.backup !== false,
|
|
241
|
+
});
|
|
242
|
+
let install;
|
|
243
|
+
if (payload?.installDependencies === true) {
|
|
244
|
+
install = await runPnpmInstall(path.join(home, "profiles", profileName));
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
archivePath,
|
|
249
|
+
restored: apply.restored,
|
|
250
|
+
backupDir: apply.backupDir,
|
|
251
|
+
needsPnpm: apply.needsPnpm,
|
|
252
|
+
warnings: apply.warnings,
|
|
253
|
+
manifest: { createdAt: apply.manifest.createdAt, totals: apply.manifest.totals, host: apply.manifest.host },
|
|
254
|
+
install,
|
|
255
|
+
summaryPath: apply.summaryPath,
|
|
256
|
+
restartRequired: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function handleInstall(ctx) {
|
|
261
|
+
const { home, profileName, profileDir } = environment(ctx);
|
|
262
|
+
if (!fs.existsSync(profileDir)) throw new TransferError("transfer/not-found", `профиль не найден: ${profileDir}`);
|
|
263
|
+
const result = await runPnpmInstall(profileDir);
|
|
264
|
+
return { ok: result.ok, code: result.code, ms: result.ms, output: result.output, profileDir, home, profile: profileName };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function handleReveal(ctx, payload) {
|
|
268
|
+
const target = typeof payload?.path === "string" ? payload.path.trim() : "";
|
|
269
|
+
if (target.length === 0) throw new TransferError("transfer/bad-request", "укажите путь");
|
|
270
|
+
if (!fs.existsSync(target)) throw new TransferError("transfer/not-found", `путь не найден: ${target}`);
|
|
271
|
+
const opened = runReveal(target);
|
|
272
|
+
return { ok: opened, path: target };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function handleDiagnose(ctx) {
|
|
276
|
+
const { home, profileName, profileDir } = environment(ctx);
|
|
277
|
+
const manifest = (() => {
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(fs.readFileSync(path.join(profileDir, "package.json"), "utf8"));
|
|
280
|
+
} catch {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
})();
|
|
284
|
+
return {
|
|
285
|
+
home,
|
|
286
|
+
profile: profileName,
|
|
287
|
+
profileExists: fs.existsSync(profileDir),
|
|
288
|
+
profileDependencies: manifest?.dependencies === null || typeof manifest?.dependencies !== "object" ? {} : manifest.dependencies,
|
|
289
|
+
nodeModules: fs.existsSync(path.join(profileDir, "node_modules")),
|
|
290
|
+
destination: suggestedDestination(),
|
|
291
|
+
last: lastSummary(home),
|
|
292
|
+
platform: process.platform,
|
|
293
|
+
node: process.version,
|
|
294
|
+
pnpmCandidates: pnpmCandidates().map((candidate) => candidate.label + ":" + candidate.command),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ------------------------------------------------------------------ */
|
|
299
|
+
/* wire */
|
|
300
|
+
/* ------------------------------------------------------------------ */
|
|
301
|
+
|
|
302
|
+
function okEnvelope(rpcId, value) {
|
|
303
|
+
return Response.json({ type: "server-response", rpcId, result: { ok: true, value } });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function errorEnvelope(rpcId, code, message, details = {}) {
|
|
307
|
+
return Response.json({ type: "server-response", rpcId, result: { ok: false, error: { code, message, details } } });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function readEnvelope(request) {
|
|
311
|
+
let body;
|
|
312
|
+
try {
|
|
313
|
+
body = await request.json();
|
|
314
|
+
} catch {
|
|
315
|
+
throw new TransferError("transfer/bad-request", "тело запроса не JSON");
|
|
316
|
+
}
|
|
317
|
+
if (body === null || typeof body !== "object" || body.type !== "client-request" || typeof body.rpcId !== "string" || typeof body.method !== "string") {
|
|
318
|
+
throw new TransferError("transfer/bad-request", "некорректный конверт Connection");
|
|
319
|
+
}
|
|
320
|
+
return { rpcId: body.rpcId, method: body.method, payload: body.payload ?? {} };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function registerOperation(ctx, operation, handler) {
|
|
324
|
+
const route = {
|
|
325
|
+
path: `${ROUTE_BASE}/${operation}`,
|
|
326
|
+
methods: ["POST"],
|
|
327
|
+
requestBody: "buffered",
|
|
328
|
+
fetch: async (request) => {
|
|
329
|
+
let rpcId = "invalid-request";
|
|
330
|
+
try {
|
|
331
|
+
const envelope = await readEnvelope(request);
|
|
332
|
+
rpcId = envelope.rpcId;
|
|
333
|
+
if (envelope.method !== `${RPC_NAMESPACE}/${operation}`) {
|
|
334
|
+
return errorEnvelope(rpcId, "transfer/bad-request", "метод конверта не совпадает с endpoint", {});
|
|
335
|
+
}
|
|
336
|
+
const value = await handler(ctx, envelope.payload);
|
|
337
|
+
return okEnvelope(rpcId, value);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const code = typeof error?.code === "string" ? error.code : "transfer/internal";
|
|
340
|
+
if (code === "transfer/internal") ctx.logger?.warn?.(`[profile-transfer] ${operation} failed: ${String(error)}`);
|
|
341
|
+
return errorEnvelope(rpcId, code, error instanceof Error ? error.message : String(error));
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
ctx.effect(() => {
|
|
346
|
+
const dispose = ctx.connection.fetch.register(route);
|
|
347
|
+
return () => {
|
|
348
|
+
try {
|
|
349
|
+
void Promise.resolve(dispose()).catch(() => {});
|
|
350
|
+
} catch {
|
|
351
|
+
/* carrier already unloading */
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}, `profile-transfer: ${operation} route`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function apply(ctx) {
|
|
358
|
+
const logger = ctx.logger;
|
|
359
|
+
const { home, profileName } = environment(ctx);
|
|
360
|
+
try {
|
|
361
|
+
fs.mkdirSync(home, { recursive: true });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
logger?.warn?.(`[profile-transfer] cannot prepare ${home}: ${String(error)}`);
|
|
364
|
+
}
|
|
365
|
+
registerOperation(ctx, "options", (context) => handleOptions(context));
|
|
366
|
+
registerOperation(ctx, "export", (context, payload) => handleExport(context, payload));
|
|
367
|
+
registerOperation(ctx, "inspect", (context, payload) => handleInspect(context, payload));
|
|
368
|
+
registerOperation(ctx, "import", (context, payload) => handleImport(context, payload));
|
|
369
|
+
registerOperation(ctx, "install", (context) => handleInstall(context));
|
|
370
|
+
registerOperation(ctx, "reveal", (context, payload) => handleReveal(context, payload));
|
|
371
|
+
registerOperation(ctx, "diagnose", (context) => handleDiagnose(context));
|
|
372
|
+
logger?.info?.(`[profile-transfer] ready: home ${home}, profile ${profileName}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export { humanSize, normalizeSelection };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-profile-transfer",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Move a complete DSH setup between machines: pick what to export (profile, skills, memory, local plugins, sessions, secrets), pack one archive, restore it on a fresh install.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./client": "./client.js",
|
|
11
|
+
"./transfer": "./transfer.js",
|
|
12
|
+
"./locale/*.json": "./locale/*.json",
|
|
13
|
+
"./icon": "./icon.svg"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"client.js",
|
|
18
|
+
"transfer.js",
|
|
19
|
+
"cordis.patch.yml",
|
|
20
|
+
"locale/*.json",
|
|
21
|
+
"icon.svg",
|
|
22
|
+
"README.md",
|
|
23
|
+
"bootstrap/*"
|
|
24
|
+
],
|
|
25
|
+
"dsh": {
|
|
26
|
+
"manifestVersion": 1,
|
|
27
|
+
"bundle": {
|
|
28
|
+
"patch": "./cordis.patch.yml"
|
|
29
|
+
},
|
|
30
|
+
"client": {
|
|
31
|
+
"platform": "web",
|
|
32
|
+
"immediately": true,
|
|
33
|
+
"inject": []
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|