@macrulez/devtoolz 0.4.1 → 0.4.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 (85) hide show
  1. package/README.md +13 -0
  2. package/dist/cli.js +2 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/image-batch/run.d.ts +4 -1
  5. package/dist/commands/image-batch/run.js +5 -0
  6. package/dist/commands/image-batch/run.js.map +1 -1
  7. package/dist/commands/image-hash/sharp-loader.d.ts +3 -0
  8. package/dist/commands/image-hash/sharp-loader.js.map +1 -1
  9. package/dist/commands/ui/cli.d.ts +2 -0
  10. package/dist/commands/ui/cli.js +78 -0
  11. package/dist/commands/ui/cli.js.map +1 -0
  12. package/dist/commands/ui/fs-routes.d.ts +7 -0
  13. package/dist/commands/ui/fs-routes.js +135 -0
  14. package/dist/commands/ui/fs-routes.js.map +1 -0
  15. package/dist/commands/ui/http.d.ts +41 -0
  16. package/dist/commands/ui/http.js +81 -0
  17. package/dist/commands/ui/http.js.map +1 -0
  18. package/dist/commands/ui/jobs.d.ts +15 -0
  19. package/dist/commands/ui/jobs.js +73 -0
  20. package/dist/commands/ui/jobs.js.map +1 -0
  21. package/dist/commands/ui/modules/checks.d.ts +28 -0
  22. package/dist/commands/ui/modules/checks.js +556 -0
  23. package/dist/commands/ui/modules/checks.js.map +1 -0
  24. package/dist/commands/ui/modules/cleanup.d.ts +2 -0
  25. package/dist/commands/ui/modules/cleanup.js +305 -0
  26. package/dist/commands/ui/modules/cleanup.js.map +1 -0
  27. package/dist/commands/ui/modules/image-batch-backups.d.ts +2 -0
  28. package/dist/commands/ui/modules/image-batch-backups.js +116 -0
  29. package/dist/commands/ui/modules/image-batch-backups.js.map +1 -0
  30. package/dist/commands/ui/modules/image-batch-manage.d.ts +2 -0
  31. package/dist/commands/ui/modules/image-batch-manage.js +245 -0
  32. package/dist/commands/ui/modules/image-batch-manage.js.map +1 -0
  33. package/dist/commands/ui/modules/image-batch.d.ts +2 -0
  34. package/dist/commands/ui/modules/image-batch.js +225 -0
  35. package/dist/commands/ui/modules/image-batch.js.map +1 -0
  36. package/dist/commands/ui/modules/image-hash.d.ts +2 -0
  37. package/dist/commands/ui/modules/image-hash.js +158 -0
  38. package/dist/commands/ui/modules/image-hash.js.map +1 -0
  39. package/dist/commands/ui/modules/index.d.ts +2 -0
  40. package/dist/commands/ui/modules/index.js +11 -0
  41. package/dist/commands/ui/modules/index.js.map +1 -0
  42. package/dist/commands/ui/open-browser.d.ts +1 -0
  43. package/dist/commands/ui/open-browser.js +18 -0
  44. package/dist/commands/ui/open-browser.js.map +1 -0
  45. package/dist/commands/ui/scan.d.ts +20 -0
  46. package/dist/commands/ui/scan.js +57 -0
  47. package/dist/commands/ui/scan.js.map +1 -0
  48. package/dist/commands/ui/server.d.ts +18 -0
  49. package/dist/commands/ui/server.js +196 -0
  50. package/dist/commands/ui/server.js.map +1 -0
  51. package/dist/ui/assets/ChecksPage-MLWSelKP.js +1 -0
  52. package/dist/ui/assets/ChecksPage-pbC0Txsh.css +1 -0
  53. package/dist/ui/assets/CleanupPage-B2ZTFOpP.js +4 -0
  54. package/dist/ui/assets/CleanupPage-k7sTI4iz.css +1 -0
  55. package/dist/ui/assets/FolderBrowser-BX3-y8sM.js +1 -0
  56. package/dist/ui/assets/FolderBrowser-DRh-WZPz.css +1 -0
  57. package/dist/ui/assets/ImageBatchPage-CvsCvu1q.js +4 -0
  58. package/dist/ui/assets/ImageBatchPage-DiK8ODUc.css +1 -0
  59. package/dist/ui/assets/ImageHashPage-Bbhxvjj0.js +1 -0
  60. package/dist/ui/assets/ImageHashPage-CUXnoJEA.css +1 -0
  61. package/dist/ui/assets/SourcePicker-C3qFDUoL.css +1 -0
  62. package/dist/ui/assets/SourcePicker-CVgtCk78.js +1 -0
  63. package/dist/ui/assets/UiSegmented-Q_ter8VK.css +1 -0
  64. package/dist/ui/assets/UiSegmented-YV1YATxM.js +1 -0
  65. package/dist/ui/assets/core-DVezgdzc.js +12 -0
  66. package/dist/ui/assets/css-Z72CF80r.js +1 -0
  67. package/dist/ui/assets/engine-javascript-CNmxVhbB.js +141 -0
  68. package/dist/ui/assets/github-dark-DHJKELXO.js +1 -0
  69. package/dist/ui/assets/github-light-DAi9KRSo.js +1 -0
  70. package/dist/ui/assets/highlight-CsAszz7F.js +2 -0
  71. package/dist/ui/assets/html-BlS82ilI.js +1 -0
  72. package/dist/ui/assets/index-BzfkjuG8.css +1 -0
  73. package/dist/ui/assets/index-pDKL0Y31.js +3 -0
  74. package/dist/ui/assets/javascript-wDzz0qaB.js +1 -0
  75. package/dist/ui/assets/json-Cp-IABpG.js +1 -0
  76. package/dist/ui/assets/jsx-g9-lgVsj.js +1 -0
  77. package/dist/ui/assets/markdown-Cvjx9yec.js +1 -0
  78. package/dist/ui/assets/scss-Cw6XMfVs.js +1 -0
  79. package/dist/ui/assets/tsx-COt5Ahok.js +1 -0
  80. package/dist/ui/assets/typescript-BPQ3VLAy.js +1 -0
  81. package/dist/ui/assets/useCopy-vCfkPGWW.js +1 -0
  82. package/dist/ui/assets/vue-CwSeJimo.js +1 -0
  83. package/dist/ui/assets/yaml-Buea-lGh.js +1 -0
  84. package/dist/ui/index.html +17 -0
  85. package/package.json +17 -4
@@ -0,0 +1,135 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, extname, isAbsolute, join, parse, resolve } from 'node:path';
4
+ import { DEFAULT_IMAGE_EXTENSIONS } from '../image-hash/core.js';
5
+ import { defaultSharpLoaderDeps } from '../image-hash/sharp-loader.js';
6
+ import { HttpError, sendBytes, sendJson } from './http.js';
7
+ const IMAGE_EXTENSIONS = new Set([...DEFAULT_IMAGE_EXTENSIONS, '.svg']);
8
+ const BROWSER_TYPES = {
9
+ '.jpg': 'image/jpeg',
10
+ '.jpeg': 'image/jpeg',
11
+ '.png': 'image/png',
12
+ '.webp': 'image/webp',
13
+ '.gif': 'image/gif',
14
+ '.avif': 'image/avif',
15
+ '.svg': 'image/svg+xml',
16
+ };
17
+ const MAX_ENTRIES = 5000;
18
+ const MAX_RAW_BYTES = 20 * 1024 * 1024;
19
+ const SHARP_RETRY_MS = 5000;
20
+ let sharpCache;
21
+ export function findSharp(cwd) {
22
+ if (sharpCache?.factory)
23
+ return sharpCache.factory;
24
+ if (sharpCache && Date.now() - sharpCache.checked < SHARP_RETRY_MS)
25
+ return null;
26
+ const deps = defaultSharpLoaderDeps();
27
+ const factory = deps.resolveBundled() ?? deps.resolveFromCwd(cwd) ?? deps.resolveManaged();
28
+ sharpCache = { factory, checked: Date.now() };
29
+ return factory;
30
+ }
31
+ export function forgetSharp() {
32
+ sharpCache = undefined;
33
+ }
34
+ export function isImagePath(path) {
35
+ return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
36
+ }
37
+ function roots() {
38
+ if (process.platform !== 'win32')
39
+ return ['/'];
40
+ const found = [];
41
+ for (let code = 65; code <= 90; code++) {
42
+ const drive = `${String.fromCharCode(code)}:\\`;
43
+ if (existsSync(drive))
44
+ found.push(drive);
45
+ }
46
+ return found;
47
+ }
48
+ export function resolveUserPath(raw, fallback) {
49
+ if (raw === null || raw.trim() === '')
50
+ return fallback;
51
+ const path = raw.trim();
52
+ return isAbsolute(path) ? resolve(path) : resolve(fallback, path);
53
+ }
54
+ export const fsRoutes = [
55
+ {
56
+ method: 'GET',
57
+ path: '/api/fs/list',
58
+ handler: ({ res, url, server }) => {
59
+ const path = resolveUserPath(url.searchParams.get('path'), server.cwd);
60
+ const showHidden = url.searchParams.get('hidden') === '1';
61
+ const stat = statSync(path, { throwIfNoEntry: false });
62
+ if (!stat?.isDirectory())
63
+ throw new HttpError(404, `${path} is not a folder`);
64
+ let names;
65
+ try {
66
+ names = readdirSync(path, { withFileTypes: true });
67
+ }
68
+ catch (error) {
69
+ throw new HttpError(403, error instanceof Error ? error.message : String(error));
70
+ }
71
+ const folders = [];
72
+ const images = [];
73
+ for (const entry of names) {
74
+ if (!showHidden && entry.name.startsWith('.'))
75
+ continue;
76
+ if (entry.isDirectory())
77
+ folders.push({ name: entry.name, kind: 'dir' });
78
+ else if (entry.isFile() && isImagePath(entry.name)) {
79
+ const size = statSync(join(path, entry.name), { throwIfNoEntry: false })?.size ?? 0;
80
+ images.push({ name: entry.name, kind: 'image', size });
81
+ }
82
+ }
83
+ folders.sort((a, b) => a.name.localeCompare(b.name));
84
+ images.sort((a, b) => a.name.localeCompare(b.name));
85
+ const entries = [...folders, ...images];
86
+ const parent = dirname(path);
87
+ sendJson(res, 200, {
88
+ path,
89
+ parent: parent === path ? null : parent,
90
+ home: homedir(),
91
+ cwd: server.cwd,
92
+ root: parse(path).root,
93
+ roots: roots(),
94
+ entries: entries.slice(0, MAX_ENTRIES),
95
+ truncated: entries.length > MAX_ENTRIES,
96
+ });
97
+ },
98
+ },
99
+ {
100
+ method: 'GET',
101
+ path: '/api/fs/image',
102
+ handler: async ({ res, url, server }) => {
103
+ const path = resolveUserPath(url.searchParams.get('path'), server.cwd);
104
+ if (!isImagePath(path))
105
+ throw new HttpError(400, 'not an image file');
106
+ const stat = statSync(path, { throwIfNoEntry: false });
107
+ if (!stat?.isFile())
108
+ throw new HttpError(404, 'no such file');
109
+ const requested = Number(url.searchParams.get('size') ?? 0);
110
+ const size = Number.isFinite(requested) ? Math.min(Math.max(requested, 0), 2000) : 0;
111
+ const sharp = findSharp(server.cwd);
112
+ if (sharp && size > 0) {
113
+ try {
114
+ const bytes = await sharp(path, { failOn: 'none' })
115
+ .rotate()
116
+ .resize({ width: size, height: size, fit: 'inside', withoutEnlargement: true })
117
+ .webp({ quality: 78 })
118
+ .toBuffer();
119
+ sendBytes(res, 'image/webp', bytes, 'private, max-age=60');
120
+ return;
121
+ }
122
+ catch {
123
+ throw new HttpError(422, 'this image could not be read');
124
+ }
125
+ }
126
+ const type = BROWSER_TYPES[extname(path).toLowerCase()];
127
+ if (!type)
128
+ throw new HttpError(415, 'this format needs sharp for a preview');
129
+ if (stat.size > MAX_RAW_BYTES)
130
+ throw new HttpError(413, 'too large to preview without sharp');
131
+ sendBytes(res, type, readFileSync(path), 'private, max-age=60');
132
+ },
133
+ },
134
+ ];
135
+ //# sourceMappingURL=fs-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-routes.js","sourceRoot":"","sources":["../../../src/commands/ui/fs-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export declare class HttpError extends Error {
3
+ readonly status: number;
4
+ constructor(status: number, message: string);
5
+ }
6
+ export interface UiServerOptions {
7
+ cwd: string;
8
+ host: string;
9
+ port: number;
10
+ token: string;
11
+ staticDir: string | undefined;
12
+ version: string;
13
+ }
14
+ export interface RouteContext {
15
+ req: IncomingMessage;
16
+ res: ServerResponse;
17
+ url: URL;
18
+ params: Record<string, string>;
19
+ server: UiServerOptions;
20
+ readBody: () => Promise<unknown>;
21
+ }
22
+ export interface Route {
23
+ method: 'GET' | 'POST';
24
+ path: string;
25
+ handler: (ctx: RouteContext) => Promise<void> | void;
26
+ }
27
+ export interface UiModule {
28
+ id: string;
29
+ title: string;
30
+ description: string;
31
+ status: 'available' | 'planned';
32
+ routes: Route[];
33
+ }
34
+ export declare function sendJson(res: ServerResponse, status: number, data: unknown): void;
35
+ export declare function sendBytes(res: ServerResponse, contentType: string, bytes: Uint8Array, cache?: string): void;
36
+ export declare function readJsonBody(req: IncomingMessage): Promise<unknown>;
37
+ export declare function asObject(value: unknown, what?: string): Record<string, unknown>;
38
+ export declare function matchRoute(routes: Route[], method: string, pathname: string): {
39
+ route: Route;
40
+ params: Record<string, string>;
41
+ } | undefined;
@@ -0,0 +1,81 @@
1
+ export class HttpError extends Error {
2
+ status;
3
+ constructor(status, message) {
4
+ super(message);
5
+ this.status = status;
6
+ }
7
+ }
8
+ const MAX_BODY_BYTES = 4 * 1024 * 1024;
9
+ export function sendJson(res, status, data) {
10
+ const body = JSON.stringify(data);
11
+ res.writeHead(status, {
12
+ 'Content-Type': 'application/json; charset=utf-8',
13
+ 'Content-Length': Buffer.byteLength(body),
14
+ 'Cache-Control': 'no-store',
15
+ });
16
+ res.end(body);
17
+ }
18
+ export function sendBytes(res, contentType, bytes, cache = 'no-store') {
19
+ res.writeHead(200, {
20
+ 'Content-Type': contentType,
21
+ 'Content-Length': bytes.length,
22
+ 'Cache-Control': cache,
23
+ });
24
+ res.end(bytes);
25
+ }
26
+ export async function readJsonBody(req) {
27
+ const type = req.headers['content-type'] ?? '';
28
+ if (!type.toLowerCase().startsWith('application/json')) {
29
+ throw new HttpError(415, 'expected Content-Type: application/json');
30
+ }
31
+ const chunks = [];
32
+ let size = 0;
33
+ for await (const chunk of req) {
34
+ const buffer = chunk;
35
+ size += buffer.length;
36
+ if (size > MAX_BODY_BYTES)
37
+ throw new HttpError(413, 'the request body is too large');
38
+ chunks.push(buffer);
39
+ }
40
+ const text = Buffer.concat(chunks).toString('utf8');
41
+ if (text.trim() === '')
42
+ return {};
43
+ try {
44
+ return JSON.parse(text);
45
+ }
46
+ catch {
47
+ throw new HttpError(400, 'the request body is not valid JSON');
48
+ }
49
+ }
50
+ export function asObject(value, what = 'the request body') {
51
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
52
+ throw new HttpError(400, `${what} must be a JSON object`);
53
+ }
54
+ return value;
55
+ }
56
+ export function matchRoute(routes, method, pathname) {
57
+ for (const route of routes) {
58
+ if (route.method !== method)
59
+ continue;
60
+ const want = route.path.split('/');
61
+ const have = pathname.split('/');
62
+ if (want.length !== have.length)
63
+ continue;
64
+ const params = {};
65
+ let ok = true;
66
+ for (let i = 0; i < want.length; i++) {
67
+ const part = want[i];
68
+ const value = have[i];
69
+ if (part.startsWith(':'))
70
+ params[part.slice(1)] = decodeURIComponent(value);
71
+ else if (part !== value) {
72
+ ok = false;
73
+ break;
74
+ }
75
+ }
76
+ if (ok)
77
+ return { route, params };
78
+ }
79
+ return undefined;
80
+ }
81
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/commands/ui/http.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,CAAQ;IAEvB,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF;AAkCD,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;AAEtC,MAAM,UAAU,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IACzE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACjC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,iCAAiC;QACjD,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;QACzC,eAAe,EAAE,UAAU;KAC5B,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,GAAmB,EACnB,WAAmB,EACnB,KAAiB,EACjB,KAAK,GAAG,UAAU;IAElB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,WAAW;QAC3B,gBAAgB,EAAE,KAAK,CAAC,MAAM;QAC9B,eAAe,EAAE,KAAK;KACvB,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACrD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;IAC9C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,yCAAyC,CAAC,CAAA;IACrE,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAe,CAAA;QAC9B,IAAI,IAAI,MAAM,CAAC,MAAM,CAAA;QACrB,IAAI,IAAI,GAAG,cAAc;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAA;QACpF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACnD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAA;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,oCAAoC,CAAC,CAAA;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,IAAI,GAAG,kBAAkB;IAChE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,wBAAwB,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,KAAgC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,MAAe,EACf,MAAc,EACd,QAAgB;IAEhB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,SAAQ;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,SAAQ;QACzC,MAAM,MAAM,GAA2B,EAAE,CAAA;QACzC,IAAI,EAAE,GAAG,IAAI,CAAA;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAW,CAAA;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAW,CAAA;YAC/B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;iBACtE,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;gBACxB,EAAE,GAAG,KAAK,CAAA;gBACV,MAAK;YACP,CAAC;QACH,CAAC;QACD,IAAI,EAAE;YAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IAClC,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Route } from './http.js';
2
+ export type JobEvent = {
3
+ type: 'progress';
4
+ data: unknown;
5
+ } | {
6
+ type: 'done';
7
+ data: unknown;
8
+ } | {
9
+ type: 'failed';
10
+ data: {
11
+ message: string;
12
+ };
13
+ };
14
+ export declare function startJob(task: (progress: (data: unknown) => void) => Promise<unknown>): string;
15
+ export declare const jobRoutes: Route[];
@@ -0,0 +1,73 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { HttpError } from './http.js';
3
+ const MAX_JOBS = 50;
4
+ const jobs = new Map();
5
+ function write(res, event) {
6
+ res.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`);
7
+ }
8
+ function prune() {
9
+ if (jobs.size <= MAX_JOBS)
10
+ return;
11
+ const finished = [...jobs.values()]
12
+ .filter((job) => job.final !== undefined)
13
+ .sort((a, b) => a.created - b.created);
14
+ for (const job of finished.slice(0, jobs.size - MAX_JOBS))
15
+ jobs.delete(job.id);
16
+ }
17
+ function emit(job, event) {
18
+ if (event.type === 'progress')
19
+ job.progress = event.data;
20
+ else
21
+ job.final = event;
22
+ for (const res of job.listeners) {
23
+ write(res, event);
24
+ if (event.type !== 'progress')
25
+ res.end();
26
+ }
27
+ if (event.type !== 'progress')
28
+ job.listeners.clear();
29
+ }
30
+ export function startJob(task) {
31
+ const job = {
32
+ id: randomUUID(),
33
+ progress: undefined,
34
+ final: undefined,
35
+ listeners: new Set(),
36
+ created: Date.now(),
37
+ };
38
+ jobs.set(job.id, job);
39
+ prune();
40
+ task((data) => emit(job, { type: 'progress', data }))
41
+ .then((data) => emit(job, { type: 'done', data }))
42
+ .catch((error) => emit(job, {
43
+ type: 'failed',
44
+ data: { message: error instanceof Error ? error.message : String(error) },
45
+ }));
46
+ return job.id;
47
+ }
48
+ export const jobRoutes = [
49
+ {
50
+ method: 'GET',
51
+ path: '/api/jobs/:id/events',
52
+ handler: ({ req, res, params }) => {
53
+ const job = jobs.get(params.id ?? '');
54
+ if (!job)
55
+ throw new HttpError(404, 'no such job');
56
+ res.writeHead(200, {
57
+ 'Content-Type': 'text/event-stream; charset=utf-8',
58
+ 'Cache-Control': 'no-store',
59
+ Connection: 'keep-alive',
60
+ });
61
+ if (job.progress !== undefined)
62
+ write(res, { type: 'progress', data: job.progress });
63
+ if (job.final) {
64
+ write(res, job.final);
65
+ res.end();
66
+ return;
67
+ }
68
+ job.listeners.add(res);
69
+ req.on('close', () => job.listeners.delete(res));
70
+ },
71
+ },
72
+ ];
73
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../../src/commands/ui/jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type UiModule } from '../http.js';
2
+ export type Severity = 'error' | 'warning' | 'info';
3
+ export interface CheckFinding {
4
+ file?: string;
5
+ line?: number;
6
+ column?: number;
7
+ severity: Severity;
8
+ code: string;
9
+ message: string;
10
+ hint?: string;
11
+ files?: string[];
12
+ }
13
+ export interface CheckOutcome {
14
+ summary: string;
15
+ filesScanned?: number;
16
+ findings: CheckFinding[];
17
+ error?: string;
18
+ exitCode: number;
19
+ notes?: string[];
20
+ }
21
+ export interface OptionDef {
22
+ key: string;
23
+ label: string;
24
+ type: 'boolean' | 'list';
25
+ default: boolean | string;
26
+ help: string;
27
+ }
28
+ export declare const checksModule: UiModule;