@chatcode/cco-market 1.45.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.
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/README.zh.md +143 -0
- package/UPDATE-API-V1.md +132 -0
- package/client/client.js +11534 -0
- package/cordis.patch.yml +5 -0
- package/lib/accelerate.js +184 -0
- package/lib/agents.js +36 -0
- package/lib/backup.js +572 -0
- package/lib/catalog-local-match.js +126 -0
- package/lib/catalog-npm.js +111 -0
- package/lib/changelog.js +242 -0
- package/lib/channels.js +64 -0
- package/lib/check.js +1067 -0
- package/lib/compatibility.js +198 -0
- package/lib/diagnostics.js +49 -0
- package/lib/discovery-compatibility.js +260 -0
- package/lib/dsh-cli.js +949 -0
- package/lib/dsh-install.js +108 -0
- package/lib/gist.js +352 -0
- package/lib/groups.js +97 -0
- package/lib/home-paths.js +42 -0
- package/lib/hot.js +519 -0
- package/lib/http.js +39 -0
- package/lib/index.js +97 -0
- package/lib/install.js +387 -0
- package/lib/log.js +197 -0
- package/lib/ndjson.js +154 -0
- package/lib/net.js +98 -0
- package/lib/order.js +284 -0
- package/lib/patch.js +521 -0
- package/lib/pnpm-compat.js +490 -0
- package/lib/presets.js +278 -0
- package/lib/profile.js +911 -0
- package/lib/region-probe.js +93 -0
- package/lib/regions.js +253 -0
- package/lib/registry.js +204 -0
- package/lib/restart.js +373 -0
- package/lib/routes.js +4372 -0
- package/lib/settings.js +109 -0
- package/lib/skill-market/api.js +119 -0
- package/lib/skill-market/config.js +29 -0
- package/lib/skill-market/installer.js +352 -0
- package/lib/skill-market/runtime.js +13 -0
- package/lib/skill-market/tui.js +119 -0
- package/lib/skill-market/types.js +1 -0
- package/lib/skill-market/web-routes.js +123 -0
- package/lib/snapshot.js +500 -0
- package/lib/source-migration.js +54 -0
- package/lib/sources.js +596 -0
- package/lib/store.js +91 -0
- package/lib/themes.js +102 -0
- package/lib/trial.js +116 -0
- package/lib/types/accelerate.d.ts +85 -0
- package/lib/types/agents.d.ts +22 -0
- package/lib/types/backup.d.ts +131 -0
- package/lib/types/catalog-local-match.d.ts +50 -0
- package/lib/types/catalog-npm.d.ts +47 -0
- package/lib/types/changelog.d.ts +102 -0
- package/lib/types/channels.d.ts +56 -0
- package/lib/types/check.d.ts +266 -0
- package/lib/types/compatibility.d.ts +64 -0
- package/lib/types/diagnostics.d.ts +29 -0
- package/lib/types/discovery-compatibility.d.ts +80 -0
- package/lib/types/dsh-cli.d.ts +324 -0
- package/lib/types/dsh-install.d.ts +46 -0
- package/lib/types/gist.d.ts +53 -0
- package/lib/types/groups.d.ts +34 -0
- package/lib/types/home-paths.d.ts +16 -0
- package/lib/types/hot.d.ts +221 -0
- package/lib/types/http.d.ts +12 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/install.d.ts +172 -0
- package/lib/types/log.d.ts +43 -0
- package/lib/types/ndjson.d.ts +52 -0
- package/lib/types/net.d.ts +58 -0
- package/lib/types/order.d.ts +100 -0
- package/lib/types/patch.d.ts +129 -0
- package/lib/types/pnpm-compat.d.ts +93 -0
- package/lib/types/presets.d.ts +90 -0
- package/lib/types/profile.d.ts +253 -0
- package/lib/types/region-probe.d.ts +50 -0
- package/lib/types/regions.d.ts +122 -0
- package/lib/types/registry.d.ts +86 -0
- package/lib/types/restart.d.ts +194 -0
- package/lib/types/routes.d.ts +67 -0
- package/lib/types/settings.d.ts +78 -0
- package/lib/types/skill-market/api.d.ts +10 -0
- package/lib/types/skill-market/config.d.ts +3 -0
- package/lib/types/skill-market/installer.d.ts +23 -0
- package/lib/types/skill-market/runtime.d.ts +10 -0
- package/lib/types/skill-market/tui.d.ts +4 -0
- package/lib/types/skill-market/types.d.ts +71 -0
- package/lib/types/skill-market/web-routes.d.ts +19 -0
- package/lib/types/snapshot.d.ts +93 -0
- package/lib/types/source-migration.d.ts +11 -0
- package/lib/types/sources.d.ts +216 -0
- package/lib/types/store.d.ts +30 -0
- package/lib/types/themes.d.ts +40 -0
- package/lib/types/trial.d.ts +61 -0
- package/lib/types/update-api-v1.d.ts +66 -0
- package/lib/types/updates.d.ts +123 -0
- package/lib/types/verify.d.ts +139 -0
- package/lib/update-api-v1.js +215 -0
- package/lib/updates.js +361 -0
- package/lib/verify.js +453 -0
- package/package.json +121 -0
- package/src/accelerate.ts +213 -0
- package/src/agents.ts +43 -0
- package/src/backup.ts +583 -0
- package/src/catalog-local-match.ts +144 -0
- package/src/catalog-npm.ts +120 -0
- package/src/changelog.ts +282 -0
- package/src/channels.ts +70 -0
- package/src/check.ts +1239 -0
- package/src/client/CommentsModal.tsx +119 -0
- package/src/client/Diagnostics.tsx +907 -0
- package/src/client/ErrorBoundary.tsx +111 -0
- package/src/client/InstallToast.tsx +31 -0
- package/src/client/Market.module.css +830 -0
- package/src/client/MarketSection.tsx +5301 -0
- package/src/client/OperationsPanel.tsx +365 -0
- package/src/client/SettingsCard.tsx +621 -0
- package/src/client/SkillMarket.module.css +21 -0
- package/src/client/SkillMarketSection.tsx +162 -0
- package/src/client/comments.ts +54 -0
- package/src/client/globals.d.ts +13 -0
- package/src/client/index.ts +180 -0
- package/src/client/locales.ts +1112 -0
- package/src/client/market-data.ts +1321 -0
- package/src/client/operations.ts +201 -0
- package/src/client/preset-panel.tsx +263 -0
- package/src/client/primitives.d.ts +140 -0
- package/src/client/self-check.ts +147 -0
- package/src/client/snapshot-panel.tsx +244 -0
- package/src/compatibility.ts +237 -0
- package/src/diagnostics.ts +84 -0
- package/src/discovery-compatibility.ts +315 -0
- package/src/dsh-cli.ts +1126 -0
- package/src/dsh-install.ts +118 -0
- package/src/gist.ts +362 -0
- package/src/groups.ts +111 -0
- package/src/home-paths.ts +53 -0
- package/src/hot.ts +628 -0
- package/src/http.ts +41 -0
- package/src/index.ts +128 -0
- package/src/install.ts +420 -0
- package/src/log.ts +206 -0
- package/src/ndjson.ts +185 -0
- package/src/net.ts +106 -0
- package/src/order.ts +303 -0
- package/src/patch.ts +522 -0
- package/src/pnpm-compat.ts +527 -0
- package/src/presets.ts +344 -0
- package/src/profile.ts +940 -0
- package/src/region-probe.ts +97 -0
- package/src/regions.ts +310 -0
- package/src/registry.ts +250 -0
- package/src/restart.ts +396 -0
- package/src/routes.ts +4506 -0
- package/src/settings.ts +141 -0
- package/src/skill-market/api.ts +130 -0
- package/src/skill-market/config.ts +32 -0
- package/src/skill-market/installer.ts +337 -0
- package/src/skill-market/runtime.ts +14 -0
- package/src/skill-market/tui.ts +157 -0
- package/src/skill-market/types.ts +80 -0
- package/src/skill-market/web-routes.ts +132 -0
- package/src/snapshot.ts +532 -0
- package/src/source-migration.ts +61 -0
- package/src/sources.ts +565 -0
- package/src/store.ts +89 -0
- package/src/themes.ts +125 -0
- package/src/trial.ts +156 -0
- package/src/update-api-v1.ts +277 -0
- package/src/updates.ts +400 -0
- package/src/verify.ts +492 -0
package/lib/verify.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-install activation verification (P0-2): what "installed" actually
|
|
3
|
+
* means for a package in a dsh profile.
|
|
4
|
+
*
|
|
5
|
+
* Two sources of truth, in strict order of authority:
|
|
6
|
+
*
|
|
7
|
+
* 1. The LOADER INVENTORY (observed): whatever the loader is running right
|
|
8
|
+
* now is live, full stop. A plain library with no `dsh` field can be
|
|
9
|
+
* loaded by name from someone else's bundle patch — the official
|
|
10
|
+
* dsh-base patch loads `@deepseek-ai/dsh-tools`, which has no `dsh`
|
|
11
|
+
* field at all — so no manifest check may overrule it (#135).
|
|
12
|
+
* 2. The profile manifest (inferred): `<profile>/package.json` →
|
|
13
|
+
* `dsh.profile.bundles`, what the dsh CLI reconciled. This predicts what
|
|
14
|
+
* the NEXT boot will load, and is the only evidence available for a
|
|
15
|
+
* package that is not currently running.
|
|
16
|
+
*
|
|
17
|
+
* State taxonomy (IMPROVEMENT-PLAN P0-2):
|
|
18
|
+
* live – running in the current composition (hot mount or loader entry)
|
|
19
|
+
* restart – installed and will activate on the next boot, but not live now
|
|
20
|
+
* inert – installed but not a profile-layer plugin (plain dependency, or
|
|
21
|
+
* client-only — the market shim-mounts those at boot)
|
|
22
|
+
* broken – would fail to load: listed as a bundle without a dsh surface,
|
|
23
|
+
* or a declared entry artifact that is missing
|
|
24
|
+
* missing – not present in node_modules
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
import { Script } from 'node:vm';
|
|
28
|
+
import { join } from 'node:path';
|
|
29
|
+
import { listHotMounts, parseSimplePatch } from './hot.js';
|
|
30
|
+
import { userPatchPackageReferences } from './patch.js';
|
|
31
|
+
import { bundlePatchInsertedIds, hasDshManifest, hasLoadableEntry, profileDir, readInstalled } from './profile.js';
|
|
32
|
+
/** The profile manifest's `dsh.profile.bundles` — what the CLI reconciled. */
|
|
33
|
+
function readBundles(profile, explicitDir) {
|
|
34
|
+
try {
|
|
35
|
+
const manifest = JSON.parse(readFileSync(join(profileDir(profile, explicitDir), 'package.json'), 'utf8'));
|
|
36
|
+
const bundles = manifest.dsh?.profile?.bundles;
|
|
37
|
+
return new Set(Array.isArray(bundles) ? bundles.filter((n) => typeof n === 'string') : []);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return new Set();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `live` contains the package itself or a subpath entry of it.
|
|
45
|
+
*
|
|
46
|
+
* The live set (see `liveNames` in routes.ts) holds loader entry names — the
|
|
47
|
+
* `name:` field of each bundle patch row. Bundles usually name the bare
|
|
48
|
+
* package (`dshmarket`, `@scope/pkg`), but may point at a subpath entry
|
|
49
|
+
* (`@vectorize-io/hindsight-coding-agents/dsh`, `aegis/extensions/dsh/index.js`).
|
|
50
|
+
* Either form means the package's fiber is up and it must read as live;
|
|
51
|
+
* a different package sharing a name prefix (`@scope/pkg2` vs `@scope/pkg`)
|
|
52
|
+
* must not — the `/` bound keeps the match a real subpath.
|
|
53
|
+
*/
|
|
54
|
+
function liveIncludes(live, packageName) {
|
|
55
|
+
if (live.has(packageName))
|
|
56
|
+
return true;
|
|
57
|
+
const prefix = `${packageName}/`;
|
|
58
|
+
for (const name of live)
|
|
59
|
+
if (name.startsWith(prefix))
|
|
60
|
+
return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* True when a loader entry this package's OWN bundle patch inserts is up.
|
|
65
|
+
*
|
|
66
|
+
* A carrier bundle (#103) ships no plugin of its own: its patch mounts
|
|
67
|
+
* ANOTHER package with configuration, so the live entry carries that other
|
|
68
|
+
* package's name and `liveIncludes` can never match. The entry ID is the
|
|
69
|
+
* part that belongs to this package — its patch created it — which is why
|
|
70
|
+
* matching on it is both sufficient and precise: a neighbour that happens
|
|
71
|
+
* to mount the same package does so under a different id.
|
|
72
|
+
*
|
|
73
|
+
* Without this the market kept telling users to restart for a plugin that
|
|
74
|
+
* had been running since the restart (#156).
|
|
75
|
+
*/
|
|
76
|
+
function carriedRowLive(live, profileDirectory, packageName) {
|
|
77
|
+
try {
|
|
78
|
+
return bundlePatchInsertedIds(join(profileDirectory, 'node_modules', packageName))
|
|
79
|
+
.some(id => live.has(`#${id}`));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function readPkgDsh(profile, name, explicitDir) {
|
|
86
|
+
try {
|
|
87
|
+
const manifest = JSON.parse(readFileSync(join(profileDir(profile, explicitDir), 'node_modules', name, 'package.json'), 'utf8'));
|
|
88
|
+
return manifest.dsh ?? {};
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function patchTextOf(profile, name, explicitDir) {
|
|
95
|
+
try {
|
|
96
|
+
return readFileSync(join(profileDir(profile, explicitDir), 'node_modules', name, 'cordis.patch.yml'), 'utf8');
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Verify the activation state of one installed package.
|
|
104
|
+
* @param live - names live in the current composition; defaults to the
|
|
105
|
+
* market's hot-mount table (injectable for tests).
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* Whether the profile's OWN `cordis.patch.yml` inserts this package by name.
|
|
109
|
+
*
|
|
110
|
+
* A third evidence source beside the loader inventory and the package's own
|
|
111
|
+
* manifest, and the one that was missing (#165): a plugin the user wired up
|
|
112
|
+
* themselves declares nothing, is not hot-mounted until the next boot, and so
|
|
113
|
+
* fell through to `broken` — the market told them the install had failed
|
|
114
|
+
* verification while the plugin was, in fact, working.
|
|
115
|
+
*
|
|
116
|
+
* Read with the same parser the uninstall guard uses. Unreadable returns
|
|
117
|
+
* null, which is treated here as NO evidence rather than as evidence: this
|
|
118
|
+
* only ever upgrades a verdict away from `broken`, so being unsure has to
|
|
119
|
+
* leave the stricter answer standing.
|
|
120
|
+
*/
|
|
121
|
+
function patchLoads(activeProfileDir, name) {
|
|
122
|
+
const references = userPatchPackageReferences(join(activeProfileDir, 'cordis.patch.yml'), name);
|
|
123
|
+
return references !== null && references.length > 0;
|
|
124
|
+
}
|
|
125
|
+
export function verifyActivation(profile, name, live = new Set(listHotMounts()), explicitDir, isDisabled = false) {
|
|
126
|
+
const activeProfileDir = profileDir(profile, explicitDir);
|
|
127
|
+
const bundles = readBundles(profile, activeProfileDir);
|
|
128
|
+
const inBundles = bundles.has(name);
|
|
129
|
+
const dsh = readPkgDsh(profile, name, activeProfileDir);
|
|
130
|
+
if (dsh === null) {
|
|
131
|
+
return { state: 'missing', reasons: ['未安装 / not installed'], bundle: inBundles, hot: false };
|
|
132
|
+
}
|
|
133
|
+
// A user-disabled plugin reads as disabled, never as "restart to apply":
|
|
134
|
+
// the switch state (market disable list or the user patch layer) is the
|
|
135
|
+
// dominant fact, and the loader keeps it off on every boot.
|
|
136
|
+
if (isDisabled) {
|
|
137
|
+
return {
|
|
138
|
+
state: 'disabled',
|
|
139
|
+
reasons: ['已停用(市场开关或补丁层),重启后保持关闭 / disabled (market toggle or the patch layer) — stays off across restarts'],
|
|
140
|
+
bundle: inBundles,
|
|
141
|
+
hot: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const dir = join(activeProfileDir, 'node_modules', name);
|
|
145
|
+
// OBSERVED beats INFERRED (#135): the loader inventory is ground truth, so
|
|
146
|
+
// a package the loader is running is live no matter what its manifest says.
|
|
147
|
+
// Plain library packages legitimately carry no `dsh` field and are still
|
|
148
|
+
// loaded by name from a bundle patch — @deepseek-ai/dsh-tools is loaded by
|
|
149
|
+
// the official dsh-base patch and has no `dsh` field at all — so this check
|
|
150
|
+
// has to come before any manifest-based verdict.
|
|
151
|
+
const loaderLive = liveIncludes(live, name) || carriedRowLive(live, activeProfileDir, name);
|
|
152
|
+
if (!hasDshManifest(dir)) {
|
|
153
|
+
if (loaderLive) {
|
|
154
|
+
return {
|
|
155
|
+
state: 'live',
|
|
156
|
+
reasons: ['已由 Loader 加载(该包未声明 dsh 元数据,由某个 bundle patch 按名加载)/ loaded by the loader (no dsh metadata of its own — a bundle patch loads it by name)'],
|
|
157
|
+
bundle: inBundles,
|
|
158
|
+
hot: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Not live and no dsh surface: for a package the profile lists as a
|
|
162
|
+
// BUNDLE this is a real defect; for a plain dependency it is normal —
|
|
163
|
+
// most dependencies are libraries, not plugins (#135).
|
|
164
|
+
if (inBundles && !patchLoads(activeProfileDir, name)) {
|
|
165
|
+
return {
|
|
166
|
+
state: 'broken',
|
|
167
|
+
reasons: ['已列入 profile bundle 层但未声明 dsh 元数据,加载会失败 / listed in the profile bundle layer but declares no dsh metadata — loading it fails'],
|
|
168
|
+
bundle: true,
|
|
169
|
+
hot: false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return inBundles
|
|
173
|
+
? {
|
|
174
|
+
state: 'restart',
|
|
175
|
+
reasons: ['由你自己的 cordis.patch.yml 按名加载,重启后生效 / loaded by name from your own cordis.patch.yml — live after a restart'],
|
|
176
|
+
bundle: true,
|
|
177
|
+
hot: false,
|
|
178
|
+
}
|
|
179
|
+
: {
|
|
180
|
+
state: 'inert',
|
|
181
|
+
reasons: ['普通依赖(未声明 dsh 元数据),不是 profile 层插件;若它由某个 bundle patch 按名加载,启动后会显示为已加载 / a plain dependency with no dsh metadata — not a profile-layer plugin; if some bundle patch loads it by name it will read as live once running'],
|
|
182
|
+
bundle: false,
|
|
183
|
+
hot: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Carrier bundles (#103) ship no entry of their own — what they mount is
|
|
187
|
+
// the point — so judge by "is anything loadable", not by this package's
|
|
188
|
+
// own artifact.
|
|
189
|
+
if (!loaderLive && !hasLoadableEntry(activeProfileDir, name) && !patchLoads(activeProfileDir, name)) {
|
|
190
|
+
return {
|
|
191
|
+
state: 'broken',
|
|
192
|
+
reasons: [
|
|
193
|
+
'声明的入口产物缺失(源码检出或构建被拦),下次启动会失败 / the declared entry artifact is missing (source-only checkout or blocked build) — the next boot would fail',
|
|
194
|
+
],
|
|
195
|
+
bundle: inBundles,
|
|
196
|
+
hot: false,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (loaderLive) {
|
|
200
|
+
const clientOnly = dsh.bundle === undefined && dsh.client !== undefined;
|
|
201
|
+
return {
|
|
202
|
+
state: 'live',
|
|
203
|
+
reasons: [
|
|
204
|
+
clientOnly
|
|
205
|
+
? '已热加载(纯客户端插件 shim)/ live via the client-only shim'
|
|
206
|
+
: '已热加载(bundle patch)/ live via its bundle patch',
|
|
207
|
+
],
|
|
208
|
+
bundle: inBundles,
|
|
209
|
+
hot: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (inBundles) {
|
|
213
|
+
const patch = patchTextOf(profile, name, activeProfileDir);
|
|
214
|
+
const complex = patch !== null && parseSimplePatch(patch) === null;
|
|
215
|
+
return {
|
|
216
|
+
state: 'restart',
|
|
217
|
+
reasons: [
|
|
218
|
+
complex
|
|
219
|
+
? 'bundle patch 含配置/表达式,热挂载仅支持纯 insert;重启后由 bundle 层生效 / the bundle patch contains config/expression rows; hot-mount only supports plain inserts — it activates on restart'
|
|
220
|
+
: '已进入 profile bundle 层但本次未能热挂载;重启后生效 / in the bundle layer but not hot-mounted this session — it activates on restart',
|
|
221
|
+
],
|
|
222
|
+
bundle: true,
|
|
223
|
+
hot: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Not a profile-layer plugin. Client-only packages never enter bundles
|
|
227
|
+
// (the dsh CLI skips them), so the market shim-mounts them at boot —
|
|
228
|
+
// they still work, but "installed" never means "bundle layer".
|
|
229
|
+
if (dsh.client !== undefined) {
|
|
230
|
+
return {
|
|
231
|
+
state: 'inert',
|
|
232
|
+
reasons: [
|
|
233
|
+
'未声明 dsh.bundle,不会进入 profile bundle 层(纯客户端插件);重启后由市场自动挂载生效 / no dsh.bundle — client-only plugins never enter the bundle layer; the market shim-mounts them at the next boot',
|
|
234
|
+
],
|
|
235
|
+
bundle: false,
|
|
236
|
+
hot: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
state: 'inert',
|
|
241
|
+
reasons: [
|
|
242
|
+
'未声明 dsh.bundle,已作为普通依赖安装,不会成为 profile 层 / no dsh.bundle — installed as a plain dependency, never a profile-layer plugin',
|
|
243
|
+
],
|
|
244
|
+
bundle: false,
|
|
245
|
+
hot: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Correct a post-UPDATE verdict for a plugin that was already running.
|
|
250
|
+
*
|
|
251
|
+
* `verifyActivation` answers "is this name in the live loader inventory".
|
|
252
|
+
* That is the right question after an install and the wrong one after an
|
|
253
|
+
* update: the plugin was already live, so the answer stays "live" while the
|
|
254
|
+
* process keeps serving the module it imported at boot. Replacing files under
|
|
255
|
+
* a running composition does not re-import anything.
|
|
256
|
+
*
|
|
257
|
+
* Measured on a real host rather than reasoned about — updating the market
|
|
258
|
+
* from 1.11.3 to 1.12.2 left `/dsh-market/status` reporting 1.11.3 with an
|
|
259
|
+
* unchanged boot id, while the update route called it hot-loaded in the same
|
|
260
|
+
* response. The browser half genuinely does refresh (the host re-serves the
|
|
261
|
+
* client bundle from disk), which is what makes the wrong verdict credible:
|
|
262
|
+
* the UI visibly becomes the new version while the server half does not.
|
|
263
|
+
*
|
|
264
|
+
* Only a plugin that was ALREADY live is affected. One that was missing,
|
|
265
|
+
* broken or disabled beforehand has nothing loaded to shadow the new build,
|
|
266
|
+
* so its fresh mount really does run the new code.
|
|
267
|
+
*
|
|
268
|
+
* Client-only packages are excluded for the same reason from the other end:
|
|
269
|
+
* they have no host half to go stale, and the browser fetches their bundle
|
|
270
|
+
* from disk on the next page load. Telling their users to restart would be
|
|
271
|
+
* #156 again, in a narrower place — see `hasHostHalf`.
|
|
272
|
+
* @param result the verdict computed from the loader inventory
|
|
273
|
+
* @param hostHalfWasLive whether a HOST half was live BEFORE the replacement
|
|
274
|
+
*/
|
|
275
|
+
export function activationAfterReplace(result, hostHalfWasLive) {
|
|
276
|
+
if (!hostHalfWasLive || result.state !== 'live')
|
|
277
|
+
return result;
|
|
278
|
+
return {
|
|
279
|
+
...result,
|
|
280
|
+
state: 'restart',
|
|
281
|
+
hot: false,
|
|
282
|
+
reasons: ['新版本已就位,但运行中的进程仍在使用启动时加载的旧模块——重启后生效(页面本身会立即变成新版,服务端不会) / the new build is in place, but the running process still serves the module it imported at boot — restart to apply (the page itself updates immediately; the server half does not)'],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Whether a package has a host (Node) half at all.
|
|
287
|
+
*
|
|
288
|
+
* A `dsh.client`-only package — themes, skins, most pure-UI plugins — runs
|
|
289
|
+
* no server code: the market shim-mounts it so the loader has a live row,
|
|
290
|
+
* and the browser re-fetches its bundle from disk on the next page load. An
|
|
291
|
+
* update to one takes effect on refresh, with no restart to ask for.
|
|
292
|
+
*/
|
|
293
|
+
export function hasHostHalf(profile, name, explicitDir) {
|
|
294
|
+
const dsh = readPkgDsh(profile, name, explicitDir);
|
|
295
|
+
if (dsh === null)
|
|
296
|
+
return false;
|
|
297
|
+
// Only a DEFINITE client-only package is excluded — the same test
|
|
298
|
+
// verifyActivation uses for its own verdict. Testing `dsh.bundle` on its
|
|
299
|
+
// own would read a package that declares neither key (`"dsh": {}`, which
|
|
300
|
+
// the bundle layer still loads) as client-only, and quietly disable the
|
|
301
|
+
// correction for it.
|
|
302
|
+
return !(dsh.bundle === undefined && dsh.client !== undefined);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The client bundle path a package's `exports["./client"]` names, relative
|
|
306
|
+
* to the package root — or `null` when it cannot be resolved CONFIDENTLY.
|
|
307
|
+
*
|
|
308
|
+
* Returning null is the important half. This feeds a post-install check
|
|
309
|
+
* whose only job is to catch a corrupt bundle, and a resolver that guessed
|
|
310
|
+
* wrong would report a healthy plugin as broken — worse than the silence it
|
|
311
|
+
* replaces. So every shape this does not fully understand resolves to null
|
|
312
|
+
* and the check simply does not run: unresolvable is not evidence of damage.
|
|
313
|
+
*
|
|
314
|
+
* Handles the two shapes real plugins ship: a plain string, and a
|
|
315
|
+
* conditional object. For the object, only `browser` and `default` are
|
|
316
|
+
* consulted — those are the conditions the host's client loader actually
|
|
317
|
+
* activates; `import`/`require` describe a Node resolution this file is not
|
|
318
|
+
* modelling, and picking one of those could name a different artifact.
|
|
319
|
+
* Nested conditions recurse; anything else (arrays, non-relative targets)
|
|
320
|
+
* gives up.
|
|
321
|
+
*/
|
|
322
|
+
export function clientBundlePath(exportsField, depth = 0) {
|
|
323
|
+
if (depth > 4)
|
|
324
|
+
return null;
|
|
325
|
+
if (typeof exportsField === 'string') {
|
|
326
|
+
// Only a relative in-package path. A bare specifier or URL is a shape
|
|
327
|
+
// this resolver does not model.
|
|
328
|
+
return exportsField.startsWith('./') ? exportsField : null;
|
|
329
|
+
}
|
|
330
|
+
if (exportsField === null || typeof exportsField !== 'object' || Array.isArray(exportsField))
|
|
331
|
+
return null;
|
|
332
|
+
const conditions = exportsField;
|
|
333
|
+
for (const key of ['browser', 'default']) {
|
|
334
|
+
if (conditions[key] === undefined)
|
|
335
|
+
continue;
|
|
336
|
+
const resolved = clientBundlePath(conditions[key], depth + 1);
|
|
337
|
+
if (resolved !== null)
|
|
338
|
+
return resolved;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Whether a package's client bundle still parses as JavaScript (#222).
|
|
344
|
+
*
|
|
345
|
+
* pnpm can leave a half-written or patch-mangled bundle behind — the report
|
|
346
|
+
* describes a profile whose client bundle was broken after an update. The
|
|
347
|
+
* browser is where that surfaces today, as a blank settings page long after
|
|
348
|
+
* the operation reported success, with nothing connecting the two.
|
|
349
|
+
*
|
|
350
|
+
* `vm.Script` COMPILES without executing: it catches the syntax damage this
|
|
351
|
+
* is looking for and never runs plugin code, so a hostile bundle gains
|
|
352
|
+
* nothing. A missing `dsh.client`, an unresolvable exports field, or a file
|
|
353
|
+
* that is simply absent all return ok — this check only ever fires on a file
|
|
354
|
+
* it actually read and actually failed to parse. Everything ambiguous stays
|
|
355
|
+
* silent, because a false "your plugin is corrupt" is the one outcome worse
|
|
356
|
+
* than not checking.
|
|
357
|
+
*/
|
|
358
|
+
/**
|
|
359
|
+
* V8's wording when a classic-script parse trips over module syntax.
|
|
360
|
+
*
|
|
361
|
+
* Matched on the message because there is no flag-free way to compile a
|
|
362
|
+
* module here (`SourceTextModule` needs --experimental-vm-modules, which the
|
|
363
|
+
* host process does not set). If V8 ever rewords these, the failure mode is
|
|
364
|
+
* the false positive we had before rather than a missed real break — the
|
|
365
|
+
* safer direction of the two.
|
|
366
|
+
*/
|
|
367
|
+
const MODULE_SYNTAX_ERROR = /Unexpected token 'export'|Cannot use import statement outside a module|await is only valid in async functions and the top level bodies of modules/;
|
|
368
|
+
/**
|
|
369
|
+
* Every installed plugin whose client bundle will not parse (#222 by
|
|
370
|
+
* @MicroMilo).
|
|
371
|
+
*
|
|
372
|
+
* The per-package check above only ever looked at what an operation added,
|
|
373
|
+
* which misses the failure that was actually reported: pnpm re-extracts the
|
|
374
|
+
* WHOLE tree on any install, so updating one plugin can restore another
|
|
375
|
+
* plugin's pristine — and broken — bundle, or fail to re-apply a patch that
|
|
376
|
+
* was holding it together. The damage then surfaces at the next boot as
|
|
377
|
+
* "failed to load plugins", with nothing connecting it to the install that
|
|
378
|
+
* caused it.
|
|
379
|
+
*
|
|
380
|
+
* Cheap enough to run on every operation: 0.40ms per plugin including the
|
|
381
|
+
* read (measured on a 385KB bundle), so a 30-plugin profile costs ~12ms
|
|
382
|
+
* against an install that takes seconds.
|
|
383
|
+
*
|
|
384
|
+
* Silent on ESM bundles, like the per-package check it calls — see there for
|
|
385
|
+
* why. Widening the sweep is exactly what would have turned that one false
|
|
386
|
+
* "corrupt" into one per ESM plugin in the profile.
|
|
387
|
+
*
|
|
388
|
+
* Callers compare a before-list with an after-list rather than reporting
|
|
389
|
+
* this one directly — a profile can carry a broken bundle indefinitely, and
|
|
390
|
+
* re-reporting a problem the user already had would put them in front of
|
|
391
|
+
* something this operation did not cause and cannot undo.
|
|
392
|
+
*/
|
|
393
|
+
export function brokenClientBundles(profile, explicitDir) {
|
|
394
|
+
const broken = [];
|
|
395
|
+
for (const name of Object.keys(readInstalled(profile, explicitDir))) {
|
|
396
|
+
const check = checkClientBundle(profile, name, explicitDir);
|
|
397
|
+
if (!check.ok)
|
|
398
|
+
broken.push({ name, reason: check.reason ?? 'parse failed' });
|
|
399
|
+
}
|
|
400
|
+
return broken;
|
|
401
|
+
}
|
|
402
|
+
/** Bundles broken after an operation that were intact before it. */
|
|
403
|
+
export function newlyBrokenBundles(before, after) {
|
|
404
|
+
const seen = new Set(before.map(entry => entry.name));
|
|
405
|
+
return after.filter(entry => !seen.has(entry.name));
|
|
406
|
+
}
|
|
407
|
+
export function checkClientBundle(profile, name, explicitDir) {
|
|
408
|
+
const root = join(profileDir(profile, explicitDir), 'node_modules', name);
|
|
409
|
+
let manifest;
|
|
410
|
+
try {
|
|
411
|
+
manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return { ok: true, reason: null };
|
|
415
|
+
}
|
|
416
|
+
if (manifest.dsh?.client === undefined)
|
|
417
|
+
return { ok: true, reason: null };
|
|
418
|
+
const exportsField = manifest.exports;
|
|
419
|
+
const relative = exportsField !== null && typeof exportsField === 'object' && !Array.isArray(exportsField)
|
|
420
|
+
? clientBundlePath(exportsField['./client'])
|
|
421
|
+
: null;
|
|
422
|
+
if (relative === null)
|
|
423
|
+
return { ok: true, reason: null };
|
|
424
|
+
let source;
|
|
425
|
+
try {
|
|
426
|
+
source = readFileSync(join(root, relative), 'utf8');
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
// Declared but absent is verifyActivation's territory (a missing entry
|
|
430
|
+
// artifact is already `broken` there); duplicating it here would report
|
|
431
|
+
// one problem twice in two different vocabularies.
|
|
432
|
+
return { ok: true, reason: null };
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
new Script(source, { filename: relative });
|
|
436
|
+
return { ok: true, reason: null };
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
440
|
+
// `new Script` compiles a CLASSIC script, so perfectly valid module
|
|
441
|
+
// syntax is a SyntaxError to it: `export`, `import`, and top-level
|
|
442
|
+
// `await` all throw. This package ships CJS, which is why nobody noticed
|
|
443
|
+
// — but a plugin whose client bundle is ESM was being told its file was
|
|
444
|
+
// corrupt, and offered a rollback for a file that is fine.
|
|
445
|
+
//
|
|
446
|
+
// That is the exact outcome this check's own contract forbids: a false
|
|
447
|
+
// "your plugin is corrupt" is worse than not checking. Module syntax
|
|
448
|
+
// means "cannot judge with this parser", not "broken".
|
|
449
|
+
if (MODULE_SYNTAX_ERROR.test(message))
|
|
450
|
+
return { ok: true, reason: null };
|
|
451
|
+
return { ok: false, reason: message };
|
|
452
|
+
}
|
|
453
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chatcode/cco-market",
|
|
3
|
+
"description": "ChatCode CLI Plugin Market — browse, search, and one-click install community plugins. · ChatCode CLI 可视化插件市场:逛一逛,点一下,装好。",
|
|
4
|
+
"version": "1.45.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/dsh-market/dsh-market.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://dshmarket.com",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"deepseek",
|
|
16
|
+
"harness",
|
|
17
|
+
"dsh",
|
|
18
|
+
"dsh-plugin",
|
|
19
|
+
"marketplace",
|
|
20
|
+
"market",
|
|
21
|
+
"plugin-manager"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
|
|
25
|
+
"build": "tsc -p tsconfig.json && npm run build:client",
|
|
26
|
+
"build:client": "tsdown && node scripts/normalize-client-banner.mjs",
|
|
27
|
+
"check": "npm run typecheck && npm run build && node scripts/restart-smoke.mjs",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:compat": "vitest run --config vitest.compat.config.ts",
|
|
30
|
+
"prepack": "npm run build && node scripts/preflight.mjs",
|
|
31
|
+
"prepare": "npm run build",
|
|
32
|
+
"validate:registry": "node scripts/validate-registry.mjs",
|
|
33
|
+
"snapshot": "node -e \"require('node:fs').mkdirSync('data',{recursive:true})\" && curl -sf https://awesome-dsh-plugin.com/plugins.json -o data/registry-snapshot.json && curl -sf https://awesome-dsh-plugin.com/readmes.json -o data/readmes-snapshot.json",
|
|
34
|
+
"check:web-auth-capture": "node scripts/check-web-auth-capture.mjs",
|
|
35
|
+
"test:web": "node scripts/check-web-auth-capture.mjs && vitest run --config vitest.web.config.ts",
|
|
36
|
+
"build:site": "node scripts/build-site.mjs",
|
|
37
|
+
"verify:brand": "node scripts/verify-brand.mjs"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"js-yaml": "^4.3.2",
|
|
41
|
+
"undici": "^7.29.0",
|
|
42
|
+
"yauzl": "^3.4.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
46
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
|
|
47
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
56
|
+
"@deepseek-ai/dsh-client-locale",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-theme"
|
|
59
|
+
],
|
|
60
|
+
"platform": "web"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"exports": {
|
|
64
|
+
".": {
|
|
65
|
+
"types": "./lib/types/index.d.ts",
|
|
66
|
+
"default": "./lib/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./update-api-v1": {
|
|
69
|
+
"types": "./lib/types/update-api-v1.d.ts",
|
|
70
|
+
"default": "./lib/update-api-v1.js"
|
|
71
|
+
},
|
|
72
|
+
"./client": "./client/client.js",
|
|
73
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
74
|
+
"./package.json": "./package.json"
|
|
75
|
+
},
|
|
76
|
+
"files": [
|
|
77
|
+
"lib",
|
|
78
|
+
"src",
|
|
79
|
+
"client",
|
|
80
|
+
"UPDATE-API-V1.md",
|
|
81
|
+
"cordis.patch.yml",
|
|
82
|
+
"LICENSE"
|
|
83
|
+
],
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
86
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.7",
|
|
89
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
90
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
91
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
92
|
+
"@testing-library/react": "^16.3.2",
|
|
93
|
+
"@types/js-yaml": "^4.0.9",
|
|
94
|
+
"@types/node": "^26.2.0",
|
|
95
|
+
"@types/react": "~18.3.1",
|
|
96
|
+
"@types/react-dom": "~18.3.1",
|
|
97
|
+
"@types/yauzl": "^2.10.3",
|
|
98
|
+
"@types/yazl": "^3.3.1",
|
|
99
|
+
"jsdom": "^29.1.1",
|
|
100
|
+
"lightningcss": "^1.33.0",
|
|
101
|
+
"marked": "^18.0.10",
|
|
102
|
+
"playwright": "^1.62.1",
|
|
103
|
+
"react": "^18.3.1",
|
|
104
|
+
"react-dom": "~18.3.1",
|
|
105
|
+
"tsdown": "^0.22.14",
|
|
106
|
+
"typescript": "^7.0.2",
|
|
107
|
+
"vitest": "^4.1.10",
|
|
108
|
+
"yazl": "^3.3.1"
|
|
109
|
+
},
|
|
110
|
+
"publishConfig": {
|
|
111
|
+
"registry": "https://registry.npmjs.org/"
|
|
112
|
+
},
|
|
113
|
+
"peerDependenciesMeta": {
|
|
114
|
+
"@deepseek-ai/dsh-settings": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@deepseek-ai/schemastery": {
|
|
118
|
+
"optional": true
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|