dsh-plugin-upgrader 1.0.0 → 1.0.2

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.en.md +52 -21
  3. package/README.md +57 -20
  4. package/lib/client.js +959 -0
  5. package/lib/host/bundle.d.ts +52 -0
  6. package/lib/host/bundle.d.ts.map +1 -0
  7. package/lib/host/bundle.js +73 -0
  8. package/lib/host/bundle.js.map +1 -0
  9. package/lib/host/http.d.ts +46 -0
  10. package/lib/host/http.d.ts.map +1 -0
  11. package/lib/host/http.js +104 -0
  12. package/lib/host/http.js.map +1 -0
  13. package/lib/host/install.d.ts +71 -0
  14. package/lib/host/install.d.ts.map +1 -0
  15. package/lib/host/install.js +161 -0
  16. package/lib/host/install.js.map +1 -0
  17. package/lib/host/manager.d.ts +151 -0
  18. package/lib/host/manager.d.ts.map +1 -0
  19. package/lib/host/manager.js +28 -0
  20. package/lib/host/manager.js.map +1 -0
  21. package/lib/host/overview.d.ts +52 -0
  22. package/lib/host/overview.d.ts.map +1 -0
  23. package/lib/host/overview.js +109 -0
  24. package/lib/host/overview.js.map +1 -0
  25. package/lib/host/registry.d.ts +116 -0
  26. package/lib/host/registry.d.ts.map +1 -0
  27. package/lib/host/registry.js +177 -0
  28. package/lib/host/registry.js.map +1 -0
  29. package/lib/host/repo.d.ts +62 -0
  30. package/lib/host/repo.d.ts.map +1 -0
  31. package/lib/host/repo.js +102 -0
  32. package/lib/host/repo.js.map +1 -0
  33. package/lib/host/version.d.ts +67 -0
  34. package/lib/host/version.d.ts.map +1 -0
  35. package/lib/host/version.js +114 -0
  36. package/lib/host/version.js.map +1 -0
  37. package/lib/index.d.ts +12 -0
  38. package/lib/index.d.ts.map +1 -0
  39. package/lib/index.js +254 -0
  40. package/lib/index.js.map +1 -0
  41. package/package.json +32 -14
  42. package/screenshots/history.png +0 -0
  43. package/screenshots/selection.png +0 -0
  44. package/screenshots.json +0 -4
  45. package/src/client.js +0 -1136
  46. package/src/index.js +0 -275
  47. package/src/lib/bundle.js +0 -64
  48. package/src/lib/http.js +0 -93
  49. package/src/lib/install.js +0 -163
  50. package/src/lib/overview.js +0 -108
  51. package/src/lib/registry.js +0 -188
  52. package/src/lib/repo.js +0 -91
  53. package/src/lib/version.js +0 -107
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Repository-URL normalisation.
3
+ *
4
+ * A registry packument can declare its `repository` in half a dozen shapes, and
5
+ * the tab only ever wants one thing out of it: the https URL of the project
6
+ * page. Turning that into data is pure work, so it lives here rather than in
7
+ * the request handler.
8
+ *
9
+ * @module dsh-plugin-upgrader/lib/repo
10
+ */
11
+ /** The manifest fields this module reads. */
12
+ export interface RepoManifest {
13
+ repository?: unknown;
14
+ homepage?: unknown;
15
+ bugs?: unknown;
16
+ }
17
+ /** The shape of the registry document this module reads. */
18
+ export interface PackumentLike {
19
+ 'dist-tags'?: {
20
+ latest?: unknown;
21
+ } | undefined;
22
+ versions?: Record<string, RepoManifest | undefined> | undefined;
23
+ repository?: unknown;
24
+ homepage?: unknown;
25
+ bugs?: unknown;
26
+ }
27
+ /**
28
+ * Turn any of the shapes npm allows for `repository` into an https URL.
29
+ *
30
+ * Handles `owner/repo`, `github:owner/repo`, `git@host:owner/repo.git`,
31
+ * `git+https://…`, and plain URLs; returns `undefined` for anything else.
32
+ *
33
+ * @param raw - the manifest field.
34
+ * @returns an https URL without a trailing slash.
35
+ */
36
+ export declare function normalizeRepoUrl(raw: unknown): string | undefined;
37
+ /**
38
+ * Is this a github.com URL?
39
+ *
40
+ * @param url - an absolute URL.
41
+ * @returns whether the host is GitHub.
42
+ */
43
+ export declare function isGithubUrl(url: string): boolean;
44
+ /**
45
+ * The project page a user would recognise as "the plugin's GitHub".
46
+ *
47
+ * `repository` wins; `homepage`/`bugs` are only used when they point at GitHub,
48
+ * so a marketing site never stands in for the source link.
49
+ *
50
+ * @param packument - the registry document.
51
+ * @param manifest - one version manifest.
52
+ * @returns an https GitHub URL.
53
+ */
54
+ export declare function githubUrlOf(packument: PackumentLike | undefined, manifest: RepoManifest | undefined): string | undefined;
55
+ /**
56
+ * The repository a packument points at, independent of any version row.
57
+ *
58
+ * @param packument - the registry document.
59
+ * @returns an https GitHub URL.
60
+ */
61
+ export declare function repoUrlOf(packument: PackumentLike | undefined): string | undefined;
62
+ //# sourceMappingURL=repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/host/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,GAAG,SAAS,CAAA;IAC/D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA0BjE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMhD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,aAAa,GAAG,SAAS,EACpC,QAAQ,EAAE,YAAY,GAAG,SAAS,GACjC,MAAM,GAAG,SAAS,CAYpB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIlF"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Repository-URL normalisation.
3
+ *
4
+ * A registry packument can declare its `repository` in half a dozen shapes, and
5
+ * the tab only ever wants one thing out of it: the https URL of the project
6
+ * page. Turning that into data is pure work, so it lives here rather than in
7
+ * the request handler.
8
+ *
9
+ * @module dsh-plugin-upgrader/lib/repo
10
+ */
11
+ /**
12
+ * Turn any of the shapes npm allows for `repository` into an https URL.
13
+ *
14
+ * Handles `owner/repo`, `github:owner/repo`, `git@host:owner/repo.git`,
15
+ * `git+https://…`, and plain URLs; returns `undefined` for anything else.
16
+ *
17
+ * @param raw - the manifest field.
18
+ * @returns an https URL without a trailing slash.
19
+ */
20
+ export function normalizeRepoUrl(raw) {
21
+ const text = typeof raw === 'string'
22
+ ? raw
23
+ : raw !== null && typeof raw === 'object' && typeof raw.url === 'string'
24
+ ? raw.url
25
+ : undefined;
26
+ if (text === undefined)
27
+ return undefined;
28
+ let value = text.trim();
29
+ if (value === '')
30
+ return undefined;
31
+ const shorthand = /^(?:github:)?([\w.-]+\/[\w.-]+)$/.exec(value);
32
+ if (shorthand !== null)
33
+ value = `https://github.com/${shorthand[1]}`;
34
+ else if (value.startsWith('git@'))
35
+ value = `https://${value.slice(4).replace(':', '/')}`;
36
+ else if (value.startsWith('ssh://git@'))
37
+ value = `https://${value.slice('ssh://git@'.length)}`;
38
+ else
39
+ value = value.replace(/^git\+/, '');
40
+ value = value.replace(/\.git$/, '').replace(/\/+$/, '');
41
+ try {
42
+ const parsed = new URL(value);
43
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
44
+ return undefined;
45
+ parsed.hash = '';
46
+ parsed.search = '';
47
+ return parsed.toString().replace(/\/+$/, '');
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ /**
54
+ * Is this a github.com URL?
55
+ *
56
+ * @param url - an absolute URL.
57
+ * @returns whether the host is GitHub.
58
+ */
59
+ export function isGithubUrl(url) {
60
+ try {
61
+ return /^(www\.)?github\.com$/.test(new URL(url).hostname);
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * The project page a user would recognise as "the plugin's GitHub".
69
+ *
70
+ * `repository` wins; `homepage`/`bugs` are only used when they point at GitHub,
71
+ * so a marketing site never stands in for the source link.
72
+ *
73
+ * @param packument - the registry document.
74
+ * @param manifest - one version manifest.
75
+ * @returns an https GitHub URL.
76
+ */
77
+ export function githubUrlOf(packument, manifest) {
78
+ const direct = [
79
+ normalizeRepoUrl(manifest?.repository),
80
+ normalizeRepoUrl(packument?.repository),
81
+ ].find((url) => url !== undefined && isGithubUrl(url));
82
+ if (direct !== undefined)
83
+ return direct;
84
+ const fallbacks = [
85
+ normalizeRepoUrl(packument?.homepage),
86
+ normalizeRepoUrl(manifest?.homepage),
87
+ normalizeRepoUrl(packument?.bugs),
88
+ ];
89
+ return fallbacks.find((url) => url !== undefined && isGithubUrl(url));
90
+ }
91
+ /**
92
+ * The repository a packument points at, independent of any version row.
93
+ *
94
+ * @param packument - the registry document.
95
+ * @returns an https GitHub URL.
96
+ */
97
+ export function repoUrlOf(packument) {
98
+ const latest = packument?.['dist-tags']?.latest;
99
+ const manifest = typeof latest === 'string' ? packument?.versions?.[latest] : undefined;
100
+ return githubUrlOf(packument, manifest);
101
+ }
102
+ //# sourceMappingURL=repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/host/repo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Version ordering and release-link helpers.
3
+ *
4
+ * Everything here is pure: no I/O, no clock, no ambient state. The host half
5
+ * uses it to sort published versions and to decide which GitHub page a version
6
+ * line opens, and the test suite exercises it directly.
7
+ *
8
+ * @module dsh-plugin-upgrader/lib/version
9
+ */
10
+ /** A semver split into comparable parts. */
11
+ export interface VersionParts {
12
+ major: number;
13
+ minor: number;
14
+ patch: number;
15
+ /** Prerelease identifiers, empty for a stable release. */
16
+ pre: string[];
17
+ }
18
+ /**
19
+ * Parse a semver string into comparable parts.
20
+ *
21
+ * @param value - a version like `1.2.3-rc.1`.
22
+ * @returns parts, or `null` when the string is not semver.
23
+ */
24
+ export declare function parseVersion(value: string | undefined | null): VersionParts | null;
25
+ /**
26
+ * Compare two prerelease identifier lists, semver order.
27
+ *
28
+ * @param left - identifiers of the left version.
29
+ * @param right - identifiers of the right version.
30
+ * @returns negative, zero, or positive.
31
+ */
32
+ export declare function comparePre(left: readonly string[], right: readonly string[]): number;
33
+ /**
34
+ * Compare two versions, semver order; unparsable pairs compare equal.
35
+ *
36
+ * @param left - left version.
37
+ * @param right - right version; an absent value compares equal to anything.
38
+ * @returns negative when left is older.
39
+ */
40
+ export declare function compareVersions(left: string | undefined | null, right: string | undefined | null): number;
41
+ /**
42
+ * The tag a published version was released under.
43
+ *
44
+ * @param version - the published version.
45
+ * @returns `v<version>` unless the version already carries the `v`.
46
+ */
47
+ export declare function tagOf(version: string): string;
48
+ /**
49
+ * The GitHub path a version line opens.
50
+ *
51
+ * Users read a version line as "what did this release change", so the target is
52
+ * the release page. That only resolves when the repository actually tagged the
53
+ * version — `dsh-pet` publishes 59 versions with 8 tags — so the tag list
54
+ * decides, and a version without a tag of its own falls back to the repository's
55
+ * release feed rather than to a page GitHub would answer with 404. When the tag
56
+ * list cannot be read at all (rate limit, offline) the same feed is the only
57
+ * honest answer: guessing a tag would make one link in six a dead end.
58
+ *
59
+ * The result is the `releases` tail only: the row already carries the project
60
+ * URL, and repeating it on all ~80 rows costs tens of kilobytes.
61
+ *
62
+ * @param tags - the repository's tag names, when known.
63
+ * @param version - the published version.
64
+ * @returns a path such as `/releases/tag/v1.2.3`.
65
+ */
66
+ export declare function releasePathOf(tags: ReadonlySet<string> | undefined, version: string): string;
67
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/host/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,GAAG,EAAE,MAAM,EAAE,CAAA;CACd;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,CAWlF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsBpF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAUzG;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG5F"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Version ordering and release-link helpers.
3
+ *
4
+ * Everything here is pure: no I/O, no clock, no ambient state. The host half
5
+ * uses it to sort published versions and to decide which GitHub page a version
6
+ * line opens, and the test suite exercises it directly.
7
+ *
8
+ * @module dsh-plugin-upgrader/lib/version
9
+ */
10
+ /**
11
+ * Parse a semver string into comparable parts.
12
+ *
13
+ * @param value - a version like `1.2.3-rc.1`.
14
+ * @returns parts, or `null` when the string is not semver.
15
+ */
16
+ export function parseVersion(value) {
17
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(String(value ?? '').trim());
18
+ if (match === null)
19
+ return null;
20
+ return {
21
+ major: Number(match[1]),
22
+ minor: Number(match[2]),
23
+ patch: Number(match[3]),
24
+ pre: match[4] === undefined ? [] : match[4].split('.'),
25
+ };
26
+ }
27
+ /**
28
+ * Compare two prerelease identifier lists, semver order.
29
+ *
30
+ * @param left - identifiers of the left version.
31
+ * @param right - identifiers of the right version.
32
+ * @returns negative, zero, or positive.
33
+ */
34
+ export function comparePre(left, right) {
35
+ if (left.length === 0 && right.length === 0)
36
+ return 0;
37
+ if (left.length === 0)
38
+ return 1;
39
+ if (right.length === 0)
40
+ return -1;
41
+ const length = Math.max(left.length, right.length);
42
+ for (let index = 0; index < length; index += 1) {
43
+ const a = left[index];
44
+ const b = right[index];
45
+ if (a === undefined)
46
+ return -1;
47
+ if (b === undefined)
48
+ return 1;
49
+ const aNumeric = /^\d+$/.test(a);
50
+ const bNumeric = /^\d+$/.test(b);
51
+ if (aNumeric && bNumeric) {
52
+ const delta = Number(a) - Number(b);
53
+ if (delta !== 0)
54
+ return delta;
55
+ continue;
56
+ }
57
+ if (aNumeric)
58
+ return -1;
59
+ if (bNumeric)
60
+ return 1;
61
+ if (a !== b)
62
+ return a < b ? -1 : 1;
63
+ }
64
+ return 0;
65
+ }
66
+ /**
67
+ * Compare two versions, semver order; unparsable pairs compare equal.
68
+ *
69
+ * @param left - left version.
70
+ * @param right - right version; an absent value compares equal to anything.
71
+ * @returns negative when left is older.
72
+ */
73
+ export function compareVersions(left, right) {
74
+ const a = parseVersion(left);
75
+ const b = parseVersion(right);
76
+ if (a === null || b === null)
77
+ return 0;
78
+ return (a.major - b.major ||
79
+ a.minor - b.minor ||
80
+ a.patch - b.patch ||
81
+ comparePre(a.pre, b.pre));
82
+ }
83
+ /**
84
+ * The tag a published version was released under.
85
+ *
86
+ * @param version - the published version.
87
+ * @returns `v<version>` unless the version already carries the `v`.
88
+ */
89
+ export function tagOf(version) {
90
+ return version.startsWith('v') ? version : `v${version}`;
91
+ }
92
+ /**
93
+ * The GitHub path a version line opens.
94
+ *
95
+ * Users read a version line as "what did this release change", so the target is
96
+ * the release page. That only resolves when the repository actually tagged the
97
+ * version — `dsh-pet` publishes 59 versions with 8 tags — so the tag list
98
+ * decides, and a version without a tag of its own falls back to the repository's
99
+ * release feed rather than to a page GitHub would answer with 404. When the tag
100
+ * list cannot be read at all (rate limit, offline) the same feed is the only
101
+ * honest answer: guessing a tag would make one link in six a dead end.
102
+ *
103
+ * The result is the `releases` tail only: the row already carries the project
104
+ * URL, and repeating it on all ~80 rows costs tens of kilobytes.
105
+ *
106
+ * @param tags - the repository's tag names, when known.
107
+ * @param version - the published version.
108
+ * @returns a path such as `/releases/tag/v1.2.3`.
109
+ */
110
+ export function releasePathOf(tags, version) {
111
+ const tag = tagOf(version);
112
+ return tags?.has(tag) === true ? `/releases/tag/${tag}` : '/releases';
113
+ }
114
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/host/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgC;IAC3D,MAAM,KAAK,GAAG,oEAAoE,CAAC,IAAI,CACrF,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAC3B,CAAA;IACD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC/B,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;KACvD,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAuB,EAAE,KAAwB;IAC1E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAA;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAClD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;QACtB,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,CAAA;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChC,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;YACnC,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YAC7B,SAAQ;QACV,CAAC;QACD,IAAI,QAAQ;YAAE,OAAO,CAAC,CAAC,CAAA;QACvB,IAAI,QAAQ;YAAE,OAAO,CAAC,CAAA;QACtB,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAA+B,EAAE,KAAgC;IAC/F,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;IAC7B,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,CAAA;IACtC,OAAO,CACL,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;QACjB,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;QACjB,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;QACjB,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CACzB,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,OAAe;IACnC,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAA;AAC1D,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAqC,EAAE,OAAe;IAClF,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,OAAO,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;AACvE,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { type HostContext } from './host/manager.ts';
2
+ /** Stable plugin name; matches the cordis.patch.yml insert id. */
3
+ export declare const name = "plugin-upgrader";
4
+ /** The web server is the only hard dependency; pluginManager is read lazily. */
5
+ export declare const inject: string[];
6
+ /**
7
+ * Mount the host half.
8
+ *
9
+ * @param ctx - the plugin context.
10
+ */
11
+ export declare function apply(ctx: HostContext): void;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,mBAAmB,CAAA;AAE1B,kEAAkE;AAClE,eAAO,MAAM,IAAI,oBAAoB,CAAA;AAErC,gFAAgF;AAChF,eAAO,MAAM,MAAM,UAAgB,CAAA;AA2DnC;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAkO5C"}
package/lib/index.js ADDED
@@ -0,0 +1,254 @@
1
+ import { writeJson, isLoopbackRequest, readJsonBody } from './host/http.js';
2
+ import { buildOverview } from './host/overview.js';
3
+ import { runStep, validateStep } from './host/install.js';
4
+ import { asPluginManager, } from './host/manager.js';
5
+ /** Stable plugin name; matches the cordis.patch.yml insert id. */
6
+ export const name = 'plugin-upgrader';
7
+ /** The web server is the only hard dependency; pluginManager is read lazily. */
8
+ export const inject = ['webServer'];
9
+ /**
10
+ * Prefix every route this bundle owns. The prefix is registered without a
11
+ * trailing slash because the carrier matches `path` exactly or `path + '/'`;
12
+ * a trailing slash here would only ever match the bare prefix itself.
13
+ */
14
+ const API_PREFIX = '/api/plugin-upgrader';
15
+ /** Path segments are sliced off after the registered prefix. */
16
+ const API_BASE = `${API_PREFIX}/`;
17
+ /** Finished jobs are kept only long enough for the last poll to read them. */
18
+ const JOB_RETENTION_MS = 10 * 60 * 1000;
19
+ /**
20
+ * A job that stops reporting progress for this long is treated as abandoned
21
+ * and no longer blocks new work. Installs report progress per step, so a step
22
+ * that legitimately runs longer than this is a hung child process, not work.
23
+ */
24
+ const STALE_JOB_MS = 15 * 60 * 1000;
25
+ /** How many log lines one job keeps for the panel. */
26
+ const JOB_LOG_LIMIT = 200;
27
+ /** How many log lines the overview ships for a job the tab adopts. */
28
+ const ADOPTED_LOG_LIMIT = 40;
29
+ /**
30
+ * Mount the host half.
31
+ *
32
+ * @param ctx - the plugin context.
33
+ */
34
+ export function apply(ctx) {
35
+ const packumentCache = new Map();
36
+ const tagCache = new Map();
37
+ const jobs = new Map();
38
+ let inFlight = null;
39
+ const pruneJobs = () => {
40
+ const cutoff = Date.now() - JOB_RETENTION_MS;
41
+ for (const [id, job] of jobs) {
42
+ if (job.finishedAt !== undefined && job.finishedAt < cutoff)
43
+ jobs.delete(id);
44
+ }
45
+ };
46
+ /** The manager, or a reason the request cannot be served. */
47
+ const requireManager = () => asPluginManager(ctx.get('pluginManager', false));
48
+ /**
49
+ * Run a batch job to completion in the background.
50
+ *
51
+ * @param job - the mutable job record.
52
+ * @param steps - the requested steps.
53
+ */
54
+ const runJob = async (job, steps) => {
55
+ const manager = requireManager();
56
+ if (manager === undefined) {
57
+ job.status = 'finished';
58
+ job.finishedAt = Date.now();
59
+ job.log.push('job failed: plugin manager unavailable');
60
+ return;
61
+ }
62
+ job.status = 'running';
63
+ job.heartbeatAt = Date.now();
64
+ for (const step of steps) {
65
+ if (job.cancelled)
66
+ break;
67
+ inFlight = { requestId: step.requestId ?? '' };
68
+ job.current = `${step.name}@${step.version}`;
69
+ job.log.push(`→ ${job.current}`);
70
+ const outcome = await runStep(manager, step, (message) => {
71
+ job.log.push(message);
72
+ if (job.log.length > JOB_LOG_LIMIT)
73
+ job.log.splice(0, job.log.length - JOB_LOG_LIMIT);
74
+ });
75
+ inFlight = null;
76
+ job.heartbeatAt = Date.now();
77
+ job.results.push(outcome);
78
+ job.done = job.results.length;
79
+ if (!outcome.ok)
80
+ job.failed += 1;
81
+ job.log.push(outcome.ok
82
+ ? `✓ ${job.current} ${outcome.application ?? 'applied'}`
83
+ : `✗ ${job.current} ${outcome.message ?? 'failed'}`);
84
+ }
85
+ job.status = job.cancelled ? 'cancelled' : 'finished';
86
+ job.current = undefined;
87
+ job.finishedAt = Date.now();
88
+ job.cacheDirty = true;
89
+ };
90
+ /**
91
+ * A running job is only blocking while it is actually making progress.
92
+ *
93
+ * @returns the live job, if one is genuinely running.
94
+ */
95
+ const activeJob = () => {
96
+ for (const job of jobs.values()) {
97
+ if (job.status !== 'running' && job.status !== 'queued')
98
+ continue;
99
+ const seen = job.heartbeatAt ?? job.startedAt ?? 0;
100
+ if (Date.now() - seen < STALE_JOB_MS)
101
+ return job;
102
+ job.status = 'finished';
103
+ job.abandoned = true;
104
+ job.current = undefined;
105
+ job.finishedAt = Date.now();
106
+ job.log.push('! job abandoned: no progress for 15 minutes');
107
+ }
108
+ return undefined;
109
+ };
110
+ /**
111
+ * One dispatcher for every route under the prefix.
112
+ *
113
+ * @param req - the request.
114
+ * @param res - the response.
115
+ */
116
+ const handler = async (req, res) => {
117
+ if (!isLoopbackRequest(req)) {
118
+ writeJson(res, 403, { error: 'loopback-only' });
119
+ return;
120
+ }
121
+ const url = new URL(req.url ?? '/', 'http://localhost');
122
+ const pathname = url.pathname.endsWith('/') && url.pathname.length > API_BASE.length
123
+ ? url.pathname.slice(0, -1)
124
+ : url.pathname;
125
+ const route = pathname.startsWith(API_BASE) ? pathname.slice(API_BASE.length) : '';
126
+ try {
127
+ if (req.method === 'GET' && route === 'overview') {
128
+ const force = url.searchParams.get('refresh') === '1';
129
+ const locale = url.searchParams.get('locale') === 'zh' ? 'zh' : 'en';
130
+ pruneJobs();
131
+ const live = activeJob();
132
+ writeJson(res, 200, {
133
+ ...(await buildOverview(ctx, packumentCache, tagCache, { force, locale })),
134
+ /**
135
+ * The tab is remounted whenever the Plugins page is reopened, and the
136
+ * job map only lives in this process. Reporting the live job lets a
137
+ * fresh tab adopt work that is still running instead of firing a
138
+ * second batch into a 409.
139
+ */
140
+ active: live === undefined
141
+ ? null
142
+ : {
143
+ id: live.id,
144
+ status: live.status,
145
+ total: live.total,
146
+ done: live.done,
147
+ failed: live.failed,
148
+ current: live.current,
149
+ startedAt: live.startedAt,
150
+ log: live.log.slice(-ADOPTED_LOG_LIMIT),
151
+ },
152
+ jobs: [...jobs.entries()]
153
+ .filter(([, job]) => job.finishedAt !== undefined)
154
+ .map(([id, job]) => ({
155
+ id,
156
+ status: job.status,
157
+ failed: job.failed,
158
+ done: job.done,
159
+ total: job.total,
160
+ finishedAt: job.finishedAt,
161
+ })),
162
+ });
163
+ return;
164
+ }
165
+ if (req.method === 'POST' && route === 'job') {
166
+ const body = await readJsonBody(req);
167
+ const rawSteps = body?.steps;
168
+ const steps = Array.isArray(rawSteps) ? rawSteps : [];
169
+ if (steps.length === 0) {
170
+ writeJson(res, 400, { error: 'no-steps' });
171
+ return;
172
+ }
173
+ for (const step of steps) {
174
+ const problem = validateStep(step);
175
+ if (problem !== undefined) {
176
+ writeJson(res, 400, { error: problem });
177
+ return;
178
+ }
179
+ }
180
+ const running = activeJob();
181
+ if (running !== undefined) {
182
+ writeJson(res, 409, {
183
+ error: 'busy',
184
+ id: running.id,
185
+ current: running.current,
186
+ done: running.done,
187
+ total: running.total,
188
+ });
189
+ return;
190
+ }
191
+ const id = globalThis.crypto?.randomUUID?.() ??
192
+ `job-${Date.now()}-${Math.random().toString(36).slice(2)}`;
193
+ const job = {
194
+ id,
195
+ status: 'queued',
196
+ total: steps.length,
197
+ done: 0,
198
+ failed: 0,
199
+ results: [],
200
+ log: [],
201
+ cancelled: false,
202
+ startedAt: Date.now(),
203
+ };
204
+ jobs.set(id, job);
205
+ void runJob(job, steps).catch((error) => {
206
+ job.status = 'finished';
207
+ job.finishedAt = Date.now();
208
+ job.log.push(`job failed: ${error instanceof Error ? error.message : String(error)}`);
209
+ });
210
+ writeJson(res, 202, { id });
211
+ return;
212
+ }
213
+ if (req.method === 'GET' && route === 'job') {
214
+ const id = url.searchParams.get('id');
215
+ const job = id === null ? undefined : jobs.get(id);
216
+ if (job === undefined) {
217
+ writeJson(res, 404, { error: 'unknown-job' });
218
+ return;
219
+ }
220
+ writeJson(res, 200, job);
221
+ return;
222
+ }
223
+ if (req.method === 'POST' && route === 'cancel') {
224
+ const running = [...jobs.values()].find((job) => job.status === 'running');
225
+ if (running === undefined) {
226
+ writeJson(res, 200, { status: 'not-running' });
227
+ return;
228
+ }
229
+ running.cancelled = true;
230
+ const manager = requireManager();
231
+ const target = inFlight;
232
+ if (manager !== undefined && target !== null && target.requestId !== '') {
233
+ try {
234
+ await manager.cancelInstall(target.requestId);
235
+ }
236
+ catch {
237
+ /* the step settles on its own; the flag above already stops the batch */
238
+ }
239
+ }
240
+ writeJson(res, 200, { status: 'cancelling' });
241
+ return;
242
+ }
243
+ writeJson(res, 404, { error: 'not-found' });
244
+ }
245
+ catch (error) {
246
+ const status = typeof error?.status === 'number'
247
+ ? error.status
248
+ : 500;
249
+ writeJson(res, status, { error: error instanceof Error ? error.message : String(error) });
250
+ }
251
+ };
252
+ ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: API_PREFIX, handler }), 'plugin-upgrader: routes');
253
+ }
254
+ //# sourceMappingURL=index.js.map