@macrulez/mediatoolz 0.0.0-stage → 0.1.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.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +243 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/image-batch/backup.d.ts +22 -0
  7. package/dist/commands/image-batch/backup.js +55 -0
  8. package/dist/commands/image-batch/backup.js.map +1 -0
  9. package/dist/commands/image-batch/cache.d.ts +15 -0
  10. package/dist/commands/image-batch/cache.js +38 -0
  11. package/dist/commands/image-batch/cache.js.map +1 -0
  12. package/dist/commands/image-batch/cli.d.ts +15 -0
  13. package/dist/commands/image-batch/cli.js +323 -0
  14. package/dist/commands/image-batch/cli.js.map +1 -0
  15. package/dist/commands/image-batch/codecs.d.ts +38 -0
  16. package/dist/commands/image-batch/codecs.js +366 -0
  17. package/dist/commands/image-batch/codecs.js.map +1 -0
  18. package/dist/commands/image-batch/config.d.ts +121 -0
  19. package/dist/commands/image-batch/config.js +690 -0
  20. package/dist/commands/image-batch/config.js.map +1 -0
  21. package/dist/commands/image-batch/errors.d.ts +5 -0
  22. package/dist/commands/image-batch/errors.js +8 -0
  23. package/dist/commands/image-batch/errors.js.map +1 -0
  24. package/dist/commands/image-batch/geometry.d.ts +26 -0
  25. package/dist/commands/image-batch/geometry.js +90 -0
  26. package/dist/commands/image-batch/geometry.js.map +1 -0
  27. package/dist/commands/image-batch/inputs.d.ts +22 -0
  28. package/dist/commands/image-batch/inputs.js +87 -0
  29. package/dist/commands/image-batch/inputs.js.map +1 -0
  30. package/dist/commands/image-batch/manage-cli.d.ts +2 -0
  31. package/dist/commands/image-batch/manage-cli.js +276 -0
  32. package/dist/commands/image-batch/manage-cli.js.map +1 -0
  33. package/dist/commands/image-batch/manage.d.ts +51 -0
  34. package/dist/commands/image-batch/manage.js +1041 -0
  35. package/dist/commands/image-batch/manage.js.map +1 -0
  36. package/dist/commands/image-batch/overwrite.d.ts +14 -0
  37. package/dist/commands/image-batch/overwrite.js +31 -0
  38. package/dist/commands/image-batch/overwrite.js.map +1 -0
  39. package/dist/commands/image-batch/plan.d.ts +86 -0
  40. package/dist/commands/image-batch/plan.js +396 -0
  41. package/dist/commands/image-batch/plan.js.map +1 -0
  42. package/dist/commands/image-batch/process.d.ts +8 -0
  43. package/dist/commands/image-batch/process.js +97 -0
  44. package/dist/commands/image-batch/process.js.map +1 -0
  45. package/dist/commands/image-batch/report.d.ts +6 -0
  46. package/dist/commands/image-batch/report.js +251 -0
  47. package/dist/commands/image-batch/report.js.map +1 -0
  48. package/dist/commands/image-batch/restore.d.ts +29 -0
  49. package/dist/commands/image-batch/restore.js +125 -0
  50. package/dist/commands/image-batch/restore.js.map +1 -0
  51. package/dist/commands/image-batch/run.d.ts +127 -0
  52. package/dist/commands/image-batch/run.js +656 -0
  53. package/dist/commands/image-batch/run.js.map +1 -0
  54. package/dist/commands/image-batch/sharpen-ask.d.ts +6 -0
  55. package/dist/commands/image-batch/sharpen-ask.js +53 -0
  56. package/dist/commands/image-batch/sharpen-ask.js.map +1 -0
  57. package/dist/commands/image-batch/sharpen-manage.d.ts +26 -0
  58. package/dist/commands/image-batch/sharpen-manage.js +238 -0
  59. package/dist/commands/image-batch/sharpen-manage.js.map +1 -0
  60. package/dist/commands/image-batch/sharpen-presets.d.ts +24 -0
  61. package/dist/commands/image-batch/sharpen-presets.js +145 -0
  62. package/dist/commands/image-batch/sharpen-presets.js.map +1 -0
  63. package/dist/commands/image-batch/sharpen.d.ts +46 -0
  64. package/dist/commands/image-batch/sharpen.js +159 -0
  65. package/dist/commands/image-batch/sharpen.js.map +1 -0
  66. package/dist/commands/image-batch/template.d.ts +45 -0
  67. package/dist/commands/image-batch/template.js +142 -0
  68. package/dist/commands/image-batch/template.js.map +1 -0
  69. package/dist/commands/image-hash/cache.d.ts +14 -0
  70. package/dist/commands/image-hash/cache.js +40 -0
  71. package/dist/commands/image-hash/cache.js.map +1 -0
  72. package/dist/commands/image-hash/core.d.ts +61 -0
  73. package/dist/commands/image-hash/core.js +302 -0
  74. package/dist/commands/image-hash/core.js.map +1 -0
  75. package/dist/commands/image-hash/hash.d.ts +21 -0
  76. package/dist/commands/image-hash/hash.js +104 -0
  77. package/dist/commands/image-hash/hash.js.map +1 -0
  78. package/dist/commands/image-hash/report.d.ts +3 -0
  79. package/dist/commands/image-hash/report.js +96 -0
  80. package/dist/commands/image-hash/report.js.map +1 -0
  81. package/dist/commands/image-hash/run.d.ts +61 -0
  82. package/dist/commands/image-hash/run.js +317 -0
  83. package/dist/commands/image-hash/run.js.map +1 -0
  84. package/dist/commands/image-hash/sharp-loader.d.ts +64 -0
  85. package/dist/commands/image-hash/sharp-loader.js +81 -0
  86. package/dist/commands/image-hash/sharp-loader.js.map +1 -0
  87. package/dist/commands/ui/cli.d.ts +2 -0
  88. package/dist/commands/ui/cli.js +78 -0
  89. package/dist/commands/ui/cli.js.map +1 -0
  90. package/dist/commands/ui/fs-routes.d.ts +7 -0
  91. package/dist/commands/ui/fs-routes.js +135 -0
  92. package/dist/commands/ui/fs-routes.js.map +1 -0
  93. package/dist/commands/ui/http.d.ts +41 -0
  94. package/dist/commands/ui/http.js +81 -0
  95. package/dist/commands/ui/http.js.map +1 -0
  96. package/dist/commands/ui/jobs.d.ts +15 -0
  97. package/dist/commands/ui/jobs.js +73 -0
  98. package/dist/commands/ui/jobs.js.map +1 -0
  99. package/dist/commands/ui/modules/image-batch-backups.d.ts +2 -0
  100. package/dist/commands/ui/modules/image-batch-backups.js +116 -0
  101. package/dist/commands/ui/modules/image-batch-backups.js.map +1 -0
  102. package/dist/commands/ui/modules/image-batch-manage.d.ts +2 -0
  103. package/dist/commands/ui/modules/image-batch-manage.js +245 -0
  104. package/dist/commands/ui/modules/image-batch-manage.js.map +1 -0
  105. package/dist/commands/ui/modules/image-batch.d.ts +2 -0
  106. package/dist/commands/ui/modules/image-batch.js +225 -0
  107. package/dist/commands/ui/modules/image-batch.js.map +1 -0
  108. package/dist/commands/ui/modules/image-hash.d.ts +2 -0
  109. package/dist/commands/ui/modules/image-hash.js +158 -0
  110. package/dist/commands/ui/modules/image-hash.js.map +1 -0
  111. package/dist/commands/ui/modules/index.d.ts +2 -0
  112. package/dist/commands/ui/modules/index.js +4 -0
  113. package/dist/commands/ui/modules/index.js.map +1 -0
  114. package/dist/commands/ui/open-browser.d.ts +1 -0
  115. package/dist/commands/ui/open-browser.js +18 -0
  116. package/dist/commands/ui/open-browser.js.map +1 -0
  117. package/dist/commands/ui/scan.d.ts +20 -0
  118. package/dist/commands/ui/scan.js +57 -0
  119. package/dist/commands/ui/scan.js.map +1 -0
  120. package/dist/commands/ui/server.d.ts +18 -0
  121. package/dist/commands/ui/server.js +196 -0
  122. package/dist/commands/ui/server.js.map +1 -0
  123. package/dist/format/bytes.d.ts +2 -0
  124. package/dist/format/bytes.js +17 -0
  125. package/dist/format/bytes.js.map +1 -0
  126. package/dist/format/colors.d.ts +12 -0
  127. package/dist/format/colors.js +15 -0
  128. package/dist/format/colors.js.map +1 -0
  129. package/dist/format/diff-render.d.ts +2 -0
  130. package/dist/format/diff-render.js +35 -0
  131. package/dist/format/diff-render.js.map +1 -0
  132. package/dist/format/file-list.d.ts +6 -0
  133. package/dist/format/file-list.js +15 -0
  134. package/dist/format/file-list.js.map +1 -0
  135. package/dist/format/findings-list.d.ts +7 -0
  136. package/dist/format/findings-list.js +14 -0
  137. package/dist/format/findings-list.js.map +1 -0
  138. package/dist/format/style.d.ts +20 -0
  139. package/dist/format/style.js +36 -0
  140. package/dist/format/style.js.map +1 -0
  141. package/dist/format/table.d.ts +8 -0
  142. package/dist/format/table.js +44 -0
  143. package/dist/format/table.js.map +1 -0
  144. package/dist/format/vibes.d.ts +13 -0
  145. package/dist/format/vibes.js +82 -0
  146. package/dist/format/vibes.js.map +1 -0
  147. package/dist/index.d.ts +9 -0
  148. package/dist/index.js +6 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/ui/assets/ImageBatchPage-D3f4z6YA.js +4 -0
  151. package/dist/ui/assets/ImageBatchPage-DwaunraE.css +1 -0
  152. package/dist/ui/assets/ImageHashPage-D0xZlLGw.css +1 -0
  153. package/dist/ui/assets/ImageHashPage-DjIlDPu-.js +1 -0
  154. package/dist/ui/assets/SourcePicker-D8aZjDwP.css +1 -0
  155. package/dist/ui/assets/SourcePicker-Db0dG6zc.js +1 -0
  156. package/dist/ui/assets/index-D1s-23_t.css +1 -0
  157. package/dist/ui/assets/index-yiehSw5D.js +3 -0
  158. package/dist/ui/index.html +17 -0
  159. package/dist/utils/closest.d.ts +2 -0
  160. package/dist/utils/closest.js +35 -0
  161. package/dist/utils/closest.js.map +1 -0
  162. package/dist/utils/glob.d.ts +4 -0
  163. package/dist/utils/glob.js +86 -0
  164. package/dist/utils/glob.js.map +1 -0
  165. package/dist/utils/split-list.d.ts +1 -0
  166. package/dist/utils/split-list.js +4 -0
  167. package/dist/utils/split-list.js.map +1 -0
  168. package/dist/utils/tty.d.ts +60 -0
  169. package/dist/utils/tty.js +390 -0
  170. package/dist/utils/tty.js.map +1 -0
  171. package/dist/utils/walk.d.ts +21 -0
  172. package/dist/utils/walk.js +116 -0
  173. package/dist/utils/walk.js.map +1 -0
  174. package/package.json +82 -4
@@ -0,0 +1,158 @@
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { ALL_TYPES, DEFAULT_BLURHASH_COMPONENTS, DEFAULT_EXPORT_NAME, DEFAULT_HAZEHASH_BUDGET, DEFAULT_IMAGE_EXTENSIONS, ImageHashUsageError, MAX_HAZEHASH_BUDGET, MIN_HAZEHASH_BUDGET, OUTPUT_FORMATS, parseBudget, parseComponents, parseFormat, parseTypes, validateExportName, withUpperCaseVariants, } from '../../image-hash/core.js';
4
+ import { runImageHash } from '../../image-hash/run.js';
5
+ import { forgetSharp, findSharp, resolveUserPath } from '../fs-routes.js';
6
+ import { HttpError, asObject, sendJson, } from '../http.js';
7
+ import { startJob } from '../jobs.js';
8
+ import { scanHandler, stringList } from '../scan.js';
9
+ const TYPE_INFO = {
10
+ hazehash: {
11
+ title: 'Hazehash',
12
+ description: 'A compact placeholder (7–48 bytes) that keeps the aspect ratio and, when needed, alpha.',
13
+ },
14
+ blurhash: {
15
+ title: 'Blurhash',
16
+ description: 'A short string that decodes to a blurred preview.',
17
+ },
18
+ thumbhash: {
19
+ title: 'Thumbhash',
20
+ description: 'A blurred preview that also keeps the aspect ratio and transparency.',
21
+ },
22
+ color: { title: 'Dominant color', description: 'The most common color as #rrggbb.' },
23
+ preview: { title: 'Tiny preview', description: 'A very small PNG as a data URI.' },
24
+ };
25
+ function usage(task) {
26
+ try {
27
+ return task();
28
+ }
29
+ catch (error) {
30
+ if (error instanceof ImageHashUsageError)
31
+ throw new HttpError(400, error.message);
32
+ throw error;
33
+ }
34
+ }
35
+ function baseOf(paths, cwd) {
36
+ const first = paths[0];
37
+ if (first === undefined)
38
+ return cwd;
39
+ const abs = resolveUserPath(first, cwd);
40
+ const stat = statSync(abs, { throwIfNoEntry: false });
41
+ return stat?.isDirectory() ? abs : dirname(abs);
42
+ }
43
+ async function startRun(ctx) {
44
+ const body = asObject(await ctx.readBody());
45
+ const cwd = ctx.server.cwd;
46
+ const paths = stringList(body.paths, 'paths');
47
+ if (paths.length === 0)
48
+ throw new HttpError(400, 'add at least one folder or image');
49
+ const types = usage(() => parseTypes(stringList(body.types ?? ['both'], 'types').join(',')));
50
+ const components = typeof body.components === 'string'
51
+ ? usage(() => parseComponents(body.components))
52
+ : undefined;
53
+ const budget = body.budget !== undefined ? usage(() => parseBudget(String(body.budget))) : undefined;
54
+ const format = usage(() => parseFormat(typeof body.format === 'string' ? body.format : 'json'));
55
+ const exportName = usage(() => validateExportName(typeof body.exportName === 'string' ? body.exportName : DEFAULT_EXPORT_NAME));
56
+ const ignoreGlobs = body.ignoreGlobs !== undefined ? stringList(body.ignoreGlobs, 'ignoreGlobs') : [];
57
+ const base = baseOf(paths, cwd);
58
+ const installSharp = body.installSharp === true;
59
+ const id = startJob(async (progress) => {
60
+ try {
61
+ const report = await runImageHash({
62
+ paths: paths.map((path) => resolveUserPath(path, cwd)),
63
+ cwd,
64
+ types,
65
+ recursive: body.recursive === true,
66
+ ...(components ? { components } : {}),
67
+ ...(budget !== undefined ? { budget } : {}),
68
+ format,
69
+ exportName,
70
+ keyBase: base,
71
+ ignoreGlobs,
72
+ assumeYes: installSharp,
73
+ concurrency: 4,
74
+ onProgress: progress,
75
+ });
76
+ forgetSharp();
77
+ const entries = report.entries.map((entry) => ({
78
+ ...entry,
79
+ path: resolve(base, entry.file),
80
+ }));
81
+ return {
82
+ base,
83
+ filesScanned: report.filesScanned,
84
+ cached: report.cached,
85
+ errors: report.errors,
86
+ entries,
87
+ output: report.stdout,
88
+ format,
89
+ exitCode: report.exitCode,
90
+ };
91
+ }
92
+ catch (error) {
93
+ if (error instanceof ImageHashUsageError) {
94
+ throw new Error(error.message, { cause: error });
95
+ }
96
+ throw error;
97
+ }
98
+ });
99
+ sendJson(ctx.res, 202, { jobId: id });
100
+ }
101
+ async function saveOutput(ctx) {
102
+ const body = asObject(await ctx.readBody());
103
+ if (typeof body.path !== 'string' || body.path.trim() === '') {
104
+ throw new HttpError(400, 'give a file path');
105
+ }
106
+ if (typeof body.content !== 'string')
107
+ throw new HttpError(400, 'content must be a string');
108
+ const path = resolveUserPath(body.path, ctx.server.cwd);
109
+ if (statSync(path, { throwIfNoEntry: false })?.isDirectory()) {
110
+ throw new HttpError(400, `${path} is a folder`);
111
+ }
112
+ if (existsSync(path) && body.overwrite !== true) {
113
+ sendJson(ctx.res, 409, { error: `${path} already exists`, exists: true, path });
114
+ return;
115
+ }
116
+ mkdirSync(dirname(path), { recursive: true });
117
+ writeFileSync(path, body.content);
118
+ sendJson(ctx.res, 200, { path });
119
+ }
120
+ const routes = [
121
+ {
122
+ method: 'GET',
123
+ path: '/api/image-hash/options',
124
+ handler: ({ res, server }) => {
125
+ sendJson(res, 200, {
126
+ types: ALL_TYPES.map((id) => ({ id, ...TYPE_INFO[id] })),
127
+ formats: OUTPUT_FORMATS,
128
+ budget: {
129
+ min: MIN_HAZEHASH_BUDGET,
130
+ max: MAX_HAZEHASH_BUDGET,
131
+ default: DEFAULT_HAZEHASH_BUDGET,
132
+ },
133
+ components: {
134
+ default: `${DEFAULT_BLURHASH_COMPONENTS.x}x${DEFAULT_BLURHASH_COMPONENTS.y}`,
135
+ auto: true,
136
+ },
137
+ exportName: DEFAULT_EXPORT_NAME,
138
+ extensions: DEFAULT_IMAGE_EXTENSIONS,
139
+ sharp: findSharp(server.cwd) !== null,
140
+ });
141
+ },
142
+ },
143
+ { method: 'POST', path: '/api/image-hash/run', handler: startRun },
144
+ {
145
+ method: 'POST',
146
+ path: '/api/image-hash/scan',
147
+ handler: scanHandler(withUpperCaseVariants(DEFAULT_IMAGE_EXTENSIONS)),
148
+ },
149
+ { method: 'POST', path: '/api/image-hash/save', handler: saveOutput },
150
+ ];
151
+ export const imageHashModule = {
152
+ id: 'image-hash',
153
+ title: 'Image Hash',
154
+ description: 'Blurhash, thumbhash and hazehash placeholders for your images.',
155
+ status: 'available',
156
+ routes,
157
+ };
158
+ //# sourceMappingURL=image-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-hash.js","sourceRoot":"","sources":["../../../../src/commands/ui/modules/image-hash.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { UiModule } from '../http.js';
2
+ export declare const UI_MODULES: UiModule[];
@@ -0,0 +1,4 @@
1
+ import { imageBatchModule } from './image-batch.js';
2
+ import { imageHashModule } from './image-hash.js';
3
+ export const UI_MODULES = [imageHashModule, imageBatchModule];
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/ui/modules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,CAAC,MAAM,UAAU,GAAe,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export declare function openBrowser(url: string): boolean;
@@ -0,0 +1,18 @@
1
+ import { spawn } from 'node:child_process';
2
+ export function openBrowser(url) {
3
+ const [command, args] = process.platform === 'win32'
4
+ ? ['cmd', ['/c', 'start', '', url]]
5
+ : process.platform === 'darwin'
6
+ ? ['open', [url]]
7
+ : ['xdg-open', [url]];
8
+ try {
9
+ const child = spawn(command, args, { detached: true, stdio: 'ignore', windowsHide: true });
10
+ child.on('error', () => undefined);
11
+ child.unref();
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../../../src/commands/ui/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GACnB,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC1B,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ;YAC7B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAClC,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type RouteContext } from './http.js';
2
+ export interface ScanSource {
3
+ path: string;
4
+ exists: boolean;
5
+ kind: 'folder' | 'file' | 'missing';
6
+ files: number;
7
+ folders: number;
8
+ bytes: number;
9
+ }
10
+ export interface ScanResult {
11
+ sources: ScanSource[];
12
+ total: {
13
+ files: number;
14
+ folders: number;
15
+ bytes: number;
16
+ };
17
+ }
18
+ export declare function stringList(value: unknown, name: string): string[];
19
+ export declare function scanPaths(paths: string[], cwd: string, extensions: string[], recursive: boolean): ScanResult;
20
+ export declare function scanHandler(extensions: string[]): (ctx: RouteContext) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ import { statSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { walk } from '../../utils/walk.js';
4
+ import { HttpError, asObject, sendJson } from './http.js';
5
+ import { resolveUserPath } from './fs-routes.js';
6
+ export function stringList(value, name) {
7
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
8
+ throw new HttpError(400, `${name} must be a list of strings`);
9
+ }
10
+ return value;
11
+ }
12
+ export function scanPaths(paths, cwd, extensions, recursive) {
13
+ const seen = new Map();
14
+ const sources = [];
15
+ for (const path of paths) {
16
+ const abs = resolveUserPath(path, cwd);
17
+ const stat = statSync(abs, { throwIfNoEntry: false });
18
+ if (!stat) {
19
+ sources.push({ path, exists: false, kind: 'missing', files: 0, folders: 0, bytes: 0 });
20
+ continue;
21
+ }
22
+ const files = walk([abs], { cwd, extensions, recursive });
23
+ const directories = new Set();
24
+ let bytes = 0;
25
+ for (const file of files) {
26
+ const size = statSync(file, { throwIfNoEntry: false })?.size ?? 0;
27
+ bytes += size;
28
+ directories.add(dirname(file));
29
+ seen.set(file, size);
30
+ }
31
+ sources.push({
32
+ path,
33
+ exists: true,
34
+ kind: stat.isDirectory() ? 'folder' : 'file',
35
+ files: files.length,
36
+ folders: directories.size,
37
+ bytes,
38
+ });
39
+ }
40
+ const allFolders = new Set([...seen.keys()].map((file) => dirname(file)));
41
+ return {
42
+ sources,
43
+ total: {
44
+ files: seen.size,
45
+ folders: allFolders.size,
46
+ bytes: [...seen.values()].reduce((sum, size) => sum + size, 0),
47
+ },
48
+ };
49
+ }
50
+ export function scanHandler(extensions) {
51
+ return async (ctx) => {
52
+ const body = asObject(await ctx.readBody());
53
+ const paths = stringList(body.paths, 'paths');
54
+ sendJson(ctx.res, 200, scanPaths(paths, ctx.server.cwd, extensions, body.recursive === true));
55
+ };
56
+ }
57
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/commands/ui/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface UiServerHandle {
2
+ url: string;
3
+ port: number;
4
+ host: string;
5
+ token: string;
6
+ close: () => Promise<void>;
7
+ }
8
+ export interface StartUiOptions {
9
+ cwd: string;
10
+ host?: string;
11
+ port?: number;
12
+ token?: string;
13
+ staticDir?: string;
14
+ version?: string;
15
+ }
16
+ export declare function defaultStaticDir(): string;
17
+ export declare function cookieName(port: number): string;
18
+ export declare function startUiServer(options: StartUiOptions): Promise<UiServerHandle>;
@@ -0,0 +1,196 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { homedir } from 'node:os';
5
+ import { extname, join, resolve, sep } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { fsRoutes } from './fs-routes.js';
8
+ import { HttpError, matchRoute, readJsonBody, sendBytes, sendJson, } from './http.js';
9
+ import { jobRoutes } from './jobs.js';
10
+ import { UI_MODULES } from './modules/index.js';
11
+ const CONTENT_TYPES = {
12
+ '.html': 'text/html; charset=utf-8',
13
+ '.js': 'text/javascript; charset=utf-8',
14
+ '.css': 'text/css; charset=utf-8',
15
+ '.json': 'application/json; charset=utf-8',
16
+ '.svg': 'image/svg+xml',
17
+ '.png': 'image/png',
18
+ '.ico': 'image/x-icon',
19
+ '.woff2': 'font/woff2',
20
+ '.map': 'application/json; charset=utf-8',
21
+ };
22
+ export function defaultStaticDir() {
23
+ const candidates = ['../../ui', '../../../dist/ui'].map((relative) => fileURLToPath(new URL(relative, import.meta.url)));
24
+ return (candidates.find((path) => existsSync(join(path, 'index.html'))) ?? candidates[0]);
25
+ }
26
+ export function cookieName(port) {
27
+ return `mediatoolz-ui-${port}`;
28
+ }
29
+ function readCookie(req, name) {
30
+ const header = req.headers.cookie;
31
+ if (!header)
32
+ return undefined;
33
+ for (const part of header.split(';')) {
34
+ const index = part.indexOf('=');
35
+ if (index > 0 && part.slice(0, index).trim() === name)
36
+ return part.slice(index + 1).trim();
37
+ }
38
+ return undefined;
39
+ }
40
+ function sameToken(given, expected) {
41
+ if (given === undefined)
42
+ return false;
43
+ const a = Buffer.from(given);
44
+ const b = Buffer.from(expected);
45
+ return a.length === b.length && timingSafeEqual(a, b);
46
+ }
47
+ function allRoutes() {
48
+ return [
49
+ ...fsRoutes,
50
+ ...jobRoutes,
51
+ ...UI_MODULES.flatMap((module) => module.routes),
52
+ {
53
+ method: 'GET',
54
+ path: '/api/status',
55
+ handler: ({ res, server }) => {
56
+ sendJson(res, 200, {
57
+ version: server.version,
58
+ cwd: server.cwd,
59
+ home: homedir(),
60
+ platform: process.platform,
61
+ modules: UI_MODULES.map(({ id, title, description, status }) => ({
62
+ id,
63
+ title,
64
+ description,
65
+ status,
66
+ })),
67
+ });
68
+ },
69
+ },
70
+ ];
71
+ }
72
+ function serveStatic(res, staticDir, pathname) {
73
+ if (!staticDir || !existsSync(join(staticDir, 'index.html'))) {
74
+ const message = 'The mediatoolz UI is not built in this copy of the package. Run "npm run build" in the mediatoolz repository.';
75
+ res.writeHead(503, { 'Content-Type': 'text/plain; charset=utf-8' });
76
+ res.end(message);
77
+ return;
78
+ }
79
+ const root = resolve(staticDir);
80
+ const wanted = resolve(root, `.${pathname === '/' ? '/index.html' : pathname}`);
81
+ let file = wanted;
82
+ const inside = wanted === root || wanted.startsWith(root + sep);
83
+ if (!inside || !statSync(wanted, { throwIfNoEntry: false })?.isFile()) {
84
+ if (extname(pathname) !== '') {
85
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
86
+ res.end('not found');
87
+ return;
88
+ }
89
+ file = join(root, 'index.html');
90
+ }
91
+ const type = CONTENT_TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream';
92
+ const cache = file.includes(`${sep}assets${sep}`)
93
+ ? 'public, max-age=31536000, immutable'
94
+ : 'no-cache';
95
+ sendBytes(res, type, readFileSync(file), cache);
96
+ }
97
+ export async function startUiServer(options) {
98
+ const host = options.host ?? '127.0.0.1';
99
+ const token = options.token ?? randomBytes(18).toString('hex');
100
+ const routes = allRoutes();
101
+ const state = {
102
+ cwd: resolve(options.cwd),
103
+ host,
104
+ port: options.port ?? 0,
105
+ token,
106
+ staticDir: options.staticDir ?? defaultStaticDir(),
107
+ version: options.version ?? '',
108
+ };
109
+ const server = createServer((req, res) => {
110
+ void handle(req, res).catch((error) => {
111
+ if (res.headersSent) {
112
+ res.end();
113
+ return;
114
+ }
115
+ if (error instanceof HttpError) {
116
+ sendJson(res, error.status, { error: error.message });
117
+ return;
118
+ }
119
+ sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
120
+ });
121
+ });
122
+ async function handle(req, res) {
123
+ const allowedHosts = new Set([
124
+ `${host}:${state.port}`,
125
+ `localhost:${state.port}`,
126
+ `127.0.0.1:${state.port}`,
127
+ `[::1]:${state.port}`,
128
+ ]);
129
+ if (!allowedHosts.has((req.headers.host ?? '').toLowerCase())) {
130
+ throw new HttpError(403, 'unexpected Host header');
131
+ }
132
+ const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
133
+ const cookie = cookieName(state.port);
134
+ if (req.method === 'GET' && url.pathname === '/' && url.searchParams.has('token')) {
135
+ if (!sameToken(url.searchParams.get('token') ?? undefined, token)) {
136
+ throw new HttpError(403, 'wrong token');
137
+ }
138
+ res.writeHead(302, {
139
+ Location: '/',
140
+ 'Set-Cookie': `${cookie}=${token}; Path=/; HttpOnly; SameSite=Strict`,
141
+ 'Cache-Control': 'no-store',
142
+ });
143
+ res.end();
144
+ return;
145
+ }
146
+ const authorized = sameToken(readCookie(req, cookie), token);
147
+ if (url.pathname.startsWith('/api/')) {
148
+ if (!authorized)
149
+ throw new HttpError(401, 'open the address that mediatoolz ui printed');
150
+ const origin = req.headers.origin;
151
+ if (origin !== undefined &&
152
+ !allowedHosts.has(origin.replace(/^https?:\/\//, '').toLowerCase())) {
153
+ throw new HttpError(403, 'unexpected Origin header');
154
+ }
155
+ const found = matchRoute(routes, req.method ?? 'GET', url.pathname);
156
+ if (!found)
157
+ throw new HttpError(404, 'no such endpoint');
158
+ await found.route.handler({
159
+ req,
160
+ res,
161
+ url,
162
+ params: found.params,
163
+ server: state,
164
+ readBody: () => readJsonBody(req),
165
+ });
166
+ return;
167
+ }
168
+ if (req.method !== 'GET')
169
+ throw new HttpError(405, 'method not allowed');
170
+ if (!authorized) {
171
+ res.writeHead(401, { 'Content-Type': 'text/plain; charset=utf-8' });
172
+ res.end('Open the address that "mediatoolz ui" printed in the terminal.');
173
+ return;
174
+ }
175
+ serveStatic(res, state.staticDir, url.pathname);
176
+ }
177
+ await new Promise((resolveListen, rejectListen) => {
178
+ server.once('error', rejectListen);
179
+ server.listen(state.port, host, () => {
180
+ server.off('error', rejectListen);
181
+ resolveListen();
182
+ });
183
+ });
184
+ state.port = server.address().port;
185
+ return {
186
+ url: `http://${host === '::1' ? '[::1]' : host}:${state.port}/?token=${token}`,
187
+ port: state.port,
188
+ host,
189
+ token,
190
+ close: () => new Promise((resolveClose) => {
191
+ server.closeAllConnections();
192
+ server.close(() => resolveClose());
193
+ }),
194
+ };
195
+ }
196
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/commands/ui/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function formatBytes(bytes: number): string;
2
+ export declare function formatPercent(part: number, whole: number): string;
@@ -0,0 +1,17 @@
1
+ const UNITS = ['B', 'KB', 'MB', 'GB'];
2
+ export function formatBytes(bytes) {
3
+ let value = bytes;
4
+ let unit = 0;
5
+ while (value >= 1024 && unit < UNITS.length - 1) {
6
+ value /= 1024;
7
+ unit += 1;
8
+ }
9
+ const digits = unit === 0 || value >= 100 ? 0 : value >= 10 ? 1 : 2;
10
+ return `${value.toFixed(digits)} ${UNITS[unit]}`;
11
+ }
12
+ export function formatPercent(part, whole) {
13
+ if (whole <= 0)
14
+ return '0%';
15
+ return `${Math.round((part / whole) * 100)}%`;
16
+ }
17
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../src/format/bytes.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAErC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,KAAK,CAAA;IACjB,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,OAAO,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,KAAK,IAAI,IAAI,CAAA;QACb,IAAI,IAAI,CAAC,CAAA;IACX,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnE,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAA;AAClD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,KAAa;IACvD,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3B,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,CAAA;AAC/C,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const RESET = "\u001B[0m";
2
+ export declare const BOLD = "\u001B[1m";
3
+ export declare const DIM = "\u001B[2m";
4
+ export declare const RED = "\u001B[31m";
5
+ export declare const GREEN = "\u001B[32m";
6
+ export declare const YELLOW = "\u001B[33m";
7
+ export declare const BLUE = "\u001B[34m";
8
+ export declare const MAGENTA = "\u001B[35m";
9
+ export declare const CYAN = "\u001B[36m";
10
+ export declare const WHITE = "\u001B[97m";
11
+ export declare const GRAY = "\u001B[90m";
12
+ export declare function colorize(color: string, text: string): string;
@@ -0,0 +1,15 @@
1
+ export const RESET = '\x1b[0m';
2
+ export const BOLD = '\x1b[1m';
3
+ export const DIM = '\x1b[2m';
4
+ export const RED = '\x1b[31m';
5
+ export const GREEN = '\x1b[32m';
6
+ export const YELLOW = '\x1b[33m';
7
+ export const BLUE = '\x1b[34m';
8
+ export const MAGENTA = '\x1b[35m';
9
+ export const CYAN = '\x1b[36m';
10
+ export const WHITE = '\x1b[97m';
11
+ export const GRAY = '\x1b[90m';
12
+ export function colorize(color, text) {
13
+ return `${color}${text}${RESET}`;
14
+ }
15
+ //# sourceMappingURL=colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/format/colors.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAA;AAC9B,MAAM,CAAC,MAAM,IAAI,GAAG,SAAS,CAAA;AAC7B,MAAM,CAAC,MAAM,GAAG,GAAG,SAAS,CAAA;AAC5B,MAAM,CAAC,MAAM,GAAG,GAAG,UAAU,CAAA;AAC7B,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAAA;AAC/B,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAA;AAChC,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAA;AAC9B,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAA;AACjC,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAA;AAC9B,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAAA;AAC/B,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAA;AAE9B,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,IAAY;IAClD,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,EAAE,CAAA;AAClC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type VibesOptions } from './vibes.js';
2
+ export declare function renderDiffForHumans(file: string, count: number, unit: string, patch: string, options?: VibesOptions): string;
@@ -0,0 +1,35 @@
1
+ import { isColorEnabled } from './vibes.js';
2
+ import { BOLD, CYAN, colorize, GREEN, RED } from './colors.js';
3
+ const RULE_CHAR = '─';
4
+ const DEFAULT_WIDTH = 70;
5
+ // `unifiedDiff()` (utils/diff.ts) produces a plain, colorless string with
6
+ // git's own "Index: x / ===.../ --- x / +++ x" preamble — fine as data
7
+ // (that's what --json returns verbatim), but with several files' diffs
8
+ // printed back to back it's genuinely hard to tell where one ends and the
9
+ // next begins, and +/- lines all look the same. This renders that same
10
+ // diff for a human terminal instead: the boilerplate preamble is replaced
11
+ // with one clear, colored rule bearing the file name (and its comment/
12
+ // statement count, so it doesn't need re-reading from the list above),
13
+ // and +/- lines get their usual red/green.
14
+ export function renderDiffForHumans(file, count, unit, patch, options = {}) {
15
+ const fancy = isColorEnabled(options);
16
+ const lines = patch.split('\n');
17
+ const firstHunkIndex = lines.findIndex((l) => l.startsWith('@@'));
18
+ const body = firstHunkIndex === -1 ? lines : lines.slice(firstHunkIndex);
19
+ const width = (fancy && process.stdout.isTTY && process.stdout.columns) || DEFAULT_WIDTH;
20
+ const label = ` ${file} (${count} ${unit}) `;
21
+ const ruleWidth = Math.max(0, width - label.length - 2);
22
+ const headerText = `${RULE_CHAR}${RULE_CHAR}${label}${RULE_CHAR.repeat(ruleWidth)}`;
23
+ const header = fancy ? colorize(BOLD + CYAN, headerText) : headerText;
24
+ const renderedBody = body.map((line) => {
25
+ if (line.startsWith('@@'))
26
+ return fancy ? colorize(CYAN, line) : line;
27
+ if (line.startsWith('+') && !line.startsWith('+++'))
28
+ return fancy ? colorize(GREEN, line) : line;
29
+ if (line.startsWith('-') && !line.startsWith('---'))
30
+ return fancy ? colorize(RED, line) : line;
31
+ return line;
32
+ });
33
+ return [header, ...renderedBody].join('\n');
34
+ }
35
+ //# sourceMappingURL=diff-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-render.js","sourceRoot":"","sources":["../../src/format/diff-render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAC9D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAE9D,MAAM,SAAS,GAAG,GAAG,CAAA;AACrB,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,0EAA0E;AAC1E,uEAAuE;AACvE,uEAAuE;AACvE,0EAA0E;AAC1E,uEAAuE;AACvE,0EAA0E;AAC1E,uEAAuE;AACvE,uEAAuE;AACvE,2CAA2C;AAC3C,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,KAAa,EACb,IAAY,EACZ,KAAa,EACb,UAAwB,EAAE;IAE1B,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IAErC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAExE,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,aAAa,CAAA;IAExF,MAAM,KAAK,GAAG,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAA;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACvD,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAA;IACnF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;IAErE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACrC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACrE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAChG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC9F,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,MAAM,EAAE,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { VibesOptions } from './vibes.js';
2
+ export interface FileCountEntry {
3
+ file: string;
4
+ count: number;
5
+ }
6
+ export declare function formatFileCountRows(entries: FileCountEntry[], unit: (count: number) => string, options?: VibesOptions): string[];