@dxtmisha/scripts 0.7.10 → 0.7.11
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/CHANGELOG.md +15 -0
- package/README.md +42 -173
- package/bin/design-figma.ts +10 -0
- package/bin/design-screenshot.ts +7 -0
- package/bin/design-types.ts +3 -1
- package/package.json +12 -8
- package/src/classes/BrowserItem.ts +375 -0
- package/src/classes/Design/DesignFigma.ts +34 -0
- package/src/classes/Design/DesignScreenshot.ts +123 -0
- package/src/classes/Design/DesignTypes.ts +79 -29
- package/src/classes/FigmaApi.ts +190 -0
- package/src/classes/Properties/PropertiesConfig.ts +9 -0
- package/src/config.ts +1 -0
- package/src/library.ts +51 -50
- package/src/media/templates/componentDoc/figma/run-figma.ts +26 -0
- package/src/types/configTypes.ts +3 -0
- package/src/types/figmaApiTypes.ts +337 -0
- package/src/types/screenshotTypes.ts +35 -5
- package/src/functions/takeScreenshot.ts +0 -49
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Browser,
|
|
3
|
+
type EvaluateFunc,
|
|
4
|
+
type HTTPResponse,
|
|
5
|
+
launch,
|
|
6
|
+
type Page,
|
|
7
|
+
type PuppeteerLifeCycleEvent
|
|
8
|
+
} from 'puppeteer'
|
|
9
|
+
import { sleep } from '@dxtmisha/functional-basic'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type ScreenshotMetrics,
|
|
13
|
+
type ScreenshotOptions,
|
|
14
|
+
SCREENSHOT_ARGS,
|
|
15
|
+
SCREENSHOT_FORMAT,
|
|
16
|
+
SCREENSHOT_HEIGHTS,
|
|
17
|
+
SCREENSHOT_TIMEOUT,
|
|
18
|
+
SCREENSHOT_QUALITY,
|
|
19
|
+
SCREENSHOT_WAIT_UNTIL,
|
|
20
|
+
SCREENSHOT_WIDTHS
|
|
21
|
+
} from '../types/screenshotTypes'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Class for managing browser instances and taking screenshots using Puppeteer.
|
|
25
|
+
*
|
|
26
|
+
* Класс для управления экземплярами браузера и создания скриншотов с помощью Puppeteer.
|
|
27
|
+
*/
|
|
28
|
+
export class BrowserItem {
|
|
29
|
+
/** Browser instance / Экземпляр браузера */
|
|
30
|
+
protected browser?: Browser
|
|
31
|
+
|
|
32
|
+
/** Page instance / Экземпляр страницы */
|
|
33
|
+
protected page?: Page
|
|
34
|
+
|
|
35
|
+
/** HTTP Response from page loading / HTTP-ответ при загрузке страницы */
|
|
36
|
+
protected response?: HTTPResponse
|
|
37
|
+
|
|
38
|
+
/** Total scrollable width of the loaded page / Полная прокручиваемая ширина загруженной страницы */
|
|
39
|
+
protected pageWidth: number = -1
|
|
40
|
+
|
|
41
|
+
/** Total scrollable height of the loaded page / Полная прокручиваемая высота загруженной страницы */
|
|
42
|
+
protected pageHeight: number = -1
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Constructor
|
|
46
|
+
* @param url component path / путь к компоненту
|
|
47
|
+
* @param options additional options for capture / дополнительные опции захвата
|
|
48
|
+
*/
|
|
49
|
+
constructor(
|
|
50
|
+
protected url: string,
|
|
51
|
+
protected readonly options: ScreenshotOptions = {}
|
|
52
|
+
) {
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Additional arguments for browser launch / Дополнительные аргументы для запуска браузера */
|
|
56
|
+
get args(): string[] {
|
|
57
|
+
return this.options.args ?? SCREENSHOT_ARGS
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Condition to wait for when navigating to the page / Условие, которое нужно дождаться при переходе на страницу */
|
|
61
|
+
get waitUntil(): PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] {
|
|
62
|
+
return this.options.waitUntil ?? SCREENSHOT_WAIT_UNTIL
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Timeout for navigating to the page / Таймаут для перехода на страницу */
|
|
66
|
+
get timeout(): number {
|
|
67
|
+
return this.options.timeout ?? SCREENSHOT_TIMEOUT
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Image format for screenshots / Формат изображения для скриншотов */
|
|
71
|
+
get format(): ScreenshotOptions['format'] {
|
|
72
|
+
return this.options.format ?? SCREENSHOT_FORMAT
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Viewport width for the initial capture / Ширина порта просмотра для начального захвата */
|
|
76
|
+
get width(): number {
|
|
77
|
+
return this.options.width ?? SCREENSHOT_WIDTHS
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Viewport height for the initial capture and page-by-page slicing / Высота порта просмотра для начального захвата и постраничной нарезки */
|
|
81
|
+
get height(): number {
|
|
82
|
+
return this.options.height ?? SCREENSHOT_HEIGHTS
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Quality for JPEG/WebP formats (0-100) / Качество для форматов JPEG/WebP (0-100) */
|
|
86
|
+
get quality(): number {
|
|
87
|
+
return this.options.quality ?? SCREENSHOT_QUALITY
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Whether to capture the whole scrollable page at once or in slices / Захватывать ли всю прокручиваемую страницу сразу или по частям */
|
|
91
|
+
get fullPage(): boolean {
|
|
92
|
+
return this.options.fullPage ?? false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** File extension based on the selected format / Расширение файла на основе выбранного формата */
|
|
96
|
+
get extension(): string {
|
|
97
|
+
const format = this.format
|
|
98
|
+
|
|
99
|
+
if (format === 'jpeg') {
|
|
100
|
+
return '.jpg'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return `.${format}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* launches or returns the browser instance.
|
|
108
|
+
*
|
|
109
|
+
* Запускает или возвращает экземпляр браузера.
|
|
110
|
+
* @returns browser instance / экземпляр браузера
|
|
111
|
+
*/
|
|
112
|
+
async getBrowser(): Promise<Browser> {
|
|
113
|
+
if (!this.browser) {
|
|
114
|
+
this.browser = await launch({
|
|
115
|
+
headless: true,
|
|
116
|
+
args: this.args
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return this.browser
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Creates or returns the page instance.
|
|
125
|
+
*
|
|
126
|
+
* Создает или возвращает экземпляр страницы.
|
|
127
|
+
* @returns page instance / экземпляр страницы
|
|
128
|
+
*/
|
|
129
|
+
async getPage(): Promise<Page> {
|
|
130
|
+
if (!this.page) {
|
|
131
|
+
this.page = await (await this.getBrowser()).newPage()
|
|
132
|
+
|
|
133
|
+
await this.page.setViewport({
|
|
134
|
+
width: this.width,
|
|
135
|
+
height: this.height
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
this.page.on(
|
|
139
|
+
'console',
|
|
140
|
+
msg => this.toConsole(`Log: ${msg.text()}`)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
this.page.on(
|
|
144
|
+
'pageerror',
|
|
145
|
+
err => this.toConsole(`Error: ${(err as any)?.message}`)
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this.page
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Navigates to the URL and returns the HTTP response.
|
|
154
|
+
*
|
|
155
|
+
* Переходит по URL и возвращает HTTP-ответ.
|
|
156
|
+
* @returns HTTP response or undefined / HTTP-ответ или undefined
|
|
157
|
+
*/
|
|
158
|
+
async getResponse(): Promise<HTTPResponse | undefined> {
|
|
159
|
+
if (!this.response) {
|
|
160
|
+
const response = await (await this.getPage()).goto(this.url, {
|
|
161
|
+
waitUntil: this.waitUntil,
|
|
162
|
+
timeout: this.timeout
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
response
|
|
167
|
+
&& response.ok()
|
|
168
|
+
) {
|
|
169
|
+
this.response = response
|
|
170
|
+
} else {
|
|
171
|
+
this.toConsole(
|
|
172
|
+
`Failed to load page: ${this.url}. Status: ${response?.status()}`
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this.response
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Retrieves the HTML content of the page.
|
|
182
|
+
*
|
|
183
|
+
* Извлекает HTML-содержимое страницы.
|
|
184
|
+
* @returns html content string / HTML-содержимое
|
|
185
|
+
*/
|
|
186
|
+
async getDom(): Promise<string> {
|
|
187
|
+
return await (await this.getPage()).content()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extracts scrollable dimensions of the page.
|
|
192
|
+
*
|
|
193
|
+
* Извлекает прокручиваемые размеры страницы.
|
|
194
|
+
* @returns object with width and height / объект с шириной и высотой
|
|
195
|
+
*/
|
|
196
|
+
async getMetrics(): Promise<ScreenshotMetrics> {
|
|
197
|
+
if (
|
|
198
|
+
await this.getResponse()
|
|
199
|
+
&& (
|
|
200
|
+
this.pageWidth === -1
|
|
201
|
+
|| this.pageHeight === -1
|
|
202
|
+
)
|
|
203
|
+
) {
|
|
204
|
+
const metrics = await this.evaluate(() => {
|
|
205
|
+
return {
|
|
206
|
+
height: document.documentElement.scrollHeight,
|
|
207
|
+
width: document.documentElement.scrollWidth
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
if (metrics) {
|
|
212
|
+
this.pageWidth = metrics.width
|
|
213
|
+
this.pageHeight = metrics.height
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
height: this.pageHeight,
|
|
219
|
+
width: this.pageWidth
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Evaluates a function in the browser context.
|
|
225
|
+
*
|
|
226
|
+
* Выполняет функцию в контексте браузера.
|
|
227
|
+
* @param pageFunction function to evaluate / функция для выполнения
|
|
228
|
+
* @param args arguments for the function / аргументы для функции
|
|
229
|
+
* @returns result of evaluation / результат выполнения
|
|
230
|
+
*/
|
|
231
|
+
async evaluate<
|
|
232
|
+
Params extends unknown[],
|
|
233
|
+
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
|
234
|
+
>(
|
|
235
|
+
pageFunction: Func | string,
|
|
236
|
+
...args: Params
|
|
237
|
+
): Promise<Awaited<ReturnType<Func>> | undefined> {
|
|
238
|
+
return await (await this.getPage()).evaluate<Params, Func>(
|
|
239
|
+
pageFunction,
|
|
240
|
+
...args
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Captures screenshots based on the fullPage option.
|
|
246
|
+
*
|
|
247
|
+
* Создает скриншоты в зависимости от опции fullPage.
|
|
248
|
+
* @param outputPath base path for saving / базовый путь для сохранения
|
|
249
|
+
* @returns this instance / этот экземпляр
|
|
250
|
+
*/
|
|
251
|
+
async screenshot(outputPath: string): Promise<this> {
|
|
252
|
+
if (this.fullPage) {
|
|
253
|
+
await this.screenshotFull(outputPath)
|
|
254
|
+
} else {
|
|
255
|
+
await this.screenshotPages(outputPath)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return this
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Resets internal page and response states.
|
|
263
|
+
*
|
|
264
|
+
* Сбрасывает состояние страницы и ответа.
|
|
265
|
+
* @returns this instance / этот экземпляр
|
|
266
|
+
*/
|
|
267
|
+
async reset(): Promise<this> {
|
|
268
|
+
this.page = undefined
|
|
269
|
+
this.pageWidth = -1
|
|
270
|
+
this.pageHeight = -1
|
|
271
|
+
|
|
272
|
+
this.response = undefined
|
|
273
|
+
|
|
274
|
+
return this
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Formats the final file path based on current state and index.
|
|
279
|
+
*
|
|
280
|
+
* Форматирует итоговый путь к файлу на основе состояния и индекса.
|
|
281
|
+
* @param outputPath original output path / исходный путь сохранения
|
|
282
|
+
* @param index current page index / текущий индекс страницы
|
|
283
|
+
* @param max total pages count / общее количество страниц
|
|
284
|
+
* @returns formatted path string / отформатированная строка пути
|
|
285
|
+
*/
|
|
286
|
+
protected getOutputPath(
|
|
287
|
+
outputPath: string,
|
|
288
|
+
index: number = 1,
|
|
289
|
+
max: number = 1
|
|
290
|
+
): string {
|
|
291
|
+
if (this.fullPage) {
|
|
292
|
+
return outputPath + this.extension
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return outputPath + `-${max}_${index}` + this.extension
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Internal wrapper for Puppeteer's screenshot method.
|
|
300
|
+
*
|
|
301
|
+
* Внутренняя обертка для метода скриншота Puppeteer.
|
|
302
|
+
* @param path specific file path / путь к конкретному файлу
|
|
303
|
+
* @returns this instance / этот экземпляр
|
|
304
|
+
*/
|
|
305
|
+
protected async saveScreenshot(path: string): Promise<this> {
|
|
306
|
+
const type = this.format
|
|
307
|
+
const quality = type === 'png' ? undefined : this.quality
|
|
308
|
+
|
|
309
|
+
await (await this.getPage()).screenshot({
|
|
310
|
+
path,
|
|
311
|
+
type,
|
|
312
|
+
quality,
|
|
313
|
+
fullPage: this.fullPage
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
return this
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Iterates through page slices and saves multiple screenshots.
|
|
321
|
+
*
|
|
322
|
+
* Проходит по частям страницы и сохраняет несколько скриншотов.
|
|
323
|
+
* @param outputPath base output path / базовый путь сохранения
|
|
324
|
+
* @returns this instance / этот экземпляр
|
|
325
|
+
*/
|
|
326
|
+
protected async screenshotPages(outputPath: string): Promise<this> {
|
|
327
|
+
const {
|
|
328
|
+
height: pageHeight
|
|
329
|
+
} = await this.getMetrics()
|
|
330
|
+
|
|
331
|
+
const viewportHeight = this.height
|
|
332
|
+
const pages = Math.ceil(pageHeight / viewportHeight)
|
|
333
|
+
|
|
334
|
+
this.toConsole(`Page height: ${pageHeight}px, viewport height: ${viewportHeight}px, total pages: ${pages}`)
|
|
335
|
+
|
|
336
|
+
for (let i = 0; i < pages; i++) {
|
|
337
|
+
const currentY = i * viewportHeight
|
|
338
|
+
const fileOutputPath = this.getOutputPath(outputPath, i + 1, pages)
|
|
339
|
+
|
|
340
|
+
await this.evaluate(y => window.scrollTo(0, y), currentY)
|
|
341
|
+
await sleep(320)
|
|
342
|
+
await this.saveScreenshot(fileOutputPath)
|
|
343
|
+
|
|
344
|
+
this.toConsole(`Saved screenshot page ${i + 1}/${pages} to ${fileOutputPath}`)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return this
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Captures a single full page screenshot.
|
|
352
|
+
*
|
|
353
|
+
* Создает один скриншот всей страницы.
|
|
354
|
+
* @param outputPath output path / путь сохранения
|
|
355
|
+
* @returns this instance / этот экземпляр
|
|
356
|
+
*/
|
|
357
|
+
protected async screenshotFull(outputPath: string): Promise<this> {
|
|
358
|
+
const outputPathFull = this.getOutputPath(outputPath)
|
|
359
|
+
await this.saveScreenshot(outputPathFull)
|
|
360
|
+
|
|
361
|
+
this.toConsole(`Saved full page screenshot to ${outputPathFull}`)
|
|
362
|
+
|
|
363
|
+
return this
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Logs text with a "Browser" prefix.
|
|
368
|
+
*
|
|
369
|
+
* Выводит текст в консоль с префиксом "Browser".
|
|
370
|
+
* @param text text to log / текст для вывода
|
|
371
|
+
*/
|
|
372
|
+
protected toConsole(text: string): void {
|
|
373
|
+
console.log('Browser', text)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FigmaApi } from '../FigmaApi'
|
|
2
|
+
import { PropertiesConfig } from '../Properties/PropertiesConfig.ts'
|
|
3
|
+
|
|
4
|
+
export class DesignFigma {
|
|
5
|
+
protected readonly api: FigmaApi
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
protected readonly fileKey: string,
|
|
9
|
+
protected readonly nodeId?: string,
|
|
10
|
+
protected readonly token: string = PropertiesConfig.getFigmaToken()
|
|
11
|
+
) {
|
|
12
|
+
this.api = new FigmaApi(token, fileKey, nodeId)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async make(): Promise<void> {
|
|
16
|
+
console.log('Design Figma', await this.initImage())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async initImage(): Promise<Record<string, string> | undefined> {
|
|
20
|
+
const image = await this.api.fileImages({
|
|
21
|
+
ids: this.nodeId as string,
|
|
22
|
+
format: 'svg',
|
|
23
|
+
svg_outline_text: false,
|
|
24
|
+
svg_include_id: true,
|
|
25
|
+
svg_include_node_id: true
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (image?.err) {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return image?.images
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { ServerStorage } from '@dxtmisha/functional-basic'
|
|
3
|
+
import { BrowserItem } from '../BrowserItem'
|
|
4
|
+
import { PropertiesFile } from '../Properties/PropertiesFile.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Class for automatic capturing of screenshots by running dev server.
|
|
8
|
+
*
|
|
9
|
+
* Класс для автоматического захвата скриншотов путем запуска сервера разработки.
|
|
10
|
+
*/
|
|
11
|
+
export class DesignScreenshot {
|
|
12
|
+
/** indicates if screenshot process is running / указывает, запущен ли процесс создания скриншота */
|
|
13
|
+
protected isReading: boolean = false
|
|
14
|
+
|
|
15
|
+
/** server url / URL сервера */
|
|
16
|
+
protected url?: string
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constructor
|
|
20
|
+
* @param file output path / путь к файлу
|
|
21
|
+
*/
|
|
22
|
+
constructor(
|
|
23
|
+
protected readonly file: string = './ai-screenshot/screenshot'
|
|
24
|
+
) {
|
|
25
|
+
ServerStorage.setErrorStatus(true)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* starts the screenshot process.
|
|
30
|
+
*
|
|
31
|
+
* Запускает процесс создания скриншота.
|
|
32
|
+
*/
|
|
33
|
+
async make() {
|
|
34
|
+
console.info('Screenshot')
|
|
35
|
+
|
|
36
|
+
PropertiesFile.createDir(PropertiesFile.getPathDir(this.file + '.file'))
|
|
37
|
+
this.makeServer()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* screenshot trigger listener.
|
|
42
|
+
*
|
|
43
|
+
* Слушатель триггера для создания скриншота.
|
|
44
|
+
* @returns capture success status / статус успешного захвата
|
|
45
|
+
*/
|
|
46
|
+
protected readonly listener = async (): Promise<boolean> => {
|
|
47
|
+
if (
|
|
48
|
+
this.url
|
|
49
|
+
&& !this.isReading
|
|
50
|
+
) {
|
|
51
|
+
this.isReading = true
|
|
52
|
+
|
|
53
|
+
console.info('URL', this.url)
|
|
54
|
+
|
|
55
|
+
const browser = new BrowserItem(this.url, { height: 1024 * 12 })
|
|
56
|
+
await browser.screenshot(this.file)
|
|
57
|
+
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* spawns development server.
|
|
66
|
+
*
|
|
67
|
+
* Запускает сервер разработки.
|
|
68
|
+
* @returns this instance / этот экземпляр
|
|
69
|
+
*/
|
|
70
|
+
protected makeServer(): this {
|
|
71
|
+
const server = spawn(
|
|
72
|
+
'npm',
|
|
73
|
+
['run', 'dev'],
|
|
74
|
+
{
|
|
75
|
+
shell: true,
|
|
76
|
+
stdio: 'pipe'
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
server.stdout?.on('data', (data) => {
|
|
81
|
+
console.log('Server', 'start')
|
|
82
|
+
|
|
83
|
+
const dataString = data.toString()
|
|
84
|
+
this.makeUrl(dataString)
|
|
85
|
+
|
|
86
|
+
if (this.url) {
|
|
87
|
+
console.info('Server', 'init')
|
|
88
|
+
|
|
89
|
+
this.listener()
|
|
90
|
+
.then((success: boolean) => {
|
|
91
|
+
if (success) {
|
|
92
|
+
server.kill()
|
|
93
|
+
|
|
94
|
+
console.info('Server', 'kill')
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
server.stderr?.on('data', (data) => {
|
|
101
|
+
console.error(`Server Error: ${data.toString()}`)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* extracts server url from console output.
|
|
109
|
+
*
|
|
110
|
+
* Извлекает URL сервера из консольного вывода.
|
|
111
|
+
* @param data command output / вывод команды
|
|
112
|
+
* @returns this instance / этот экземпляр
|
|
113
|
+
*/
|
|
114
|
+
makeUrl(data: string): this {
|
|
115
|
+
const match = data.match(/(https?:\/\/localhost\S+)/)
|
|
116
|
+
|
|
117
|
+
if (match) {
|
|
118
|
+
this.url = match[1]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -6,7 +6,7 @@ import { PropertiesFile } from '../Properties/PropertiesFile'
|
|
|
6
6
|
|
|
7
7
|
import type { DesignTypesList } from '../../types/designTypes'
|
|
8
8
|
|
|
9
|
-
import { UI_DIR_CONSTRUCTOR, UI_FILE_AI_TYPES } from '../../config'
|
|
9
|
+
import { UI_DIR_CONSTRUCTOR, UI_FILE_AI_DESCRIPTION, UI_FILE_AI_TYPES } from '../../config'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Engine for generating compressed and AI-optimized TypeScript type definitions.
|
|
@@ -41,19 +41,19 @@ export class DesignTypes {
|
|
|
41
41
|
*
|
|
42
42
|
* Основной метод для выполнения процесса генерации типов.
|
|
43
43
|
*/
|
|
44
|
-
make() {
|
|
44
|
+
async make() {
|
|
45
45
|
console.log('DesignTypes: making AI types...')
|
|
46
46
|
|
|
47
47
|
const files = this.getListByFilter()
|
|
48
48
|
const fullContent = this.toOneFile(files)
|
|
49
49
|
|
|
50
|
-
this.toAiEdit(fullContent)
|
|
51
|
-
|
|
52
|
-
this.save(aiContent)
|
|
50
|
+
const aiContent = await this.toAiEdit(fullContent)
|
|
51
|
+
this.save(aiContent)
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const aiDescription = await this.toAiDescription(fullContent)
|
|
54
|
+
this.saveDescription(aiDescription)
|
|
55
|
+
|
|
56
|
+
console.log('DesignTypes: AI types saved.')
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -168,6 +168,19 @@ export class DesignTypes {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Saves the AI-generated project description to a file.
|
|
173
|
+
*
|
|
174
|
+
* Сохраняет сгенерированное ИИ описание проекта в файл.
|
|
175
|
+
* @param content content to save / контент для сохранения
|
|
176
|
+
*/
|
|
177
|
+
protected saveDescription(content: string) {
|
|
178
|
+
PropertiesFile.writeByPath(
|
|
179
|
+
UI_FILE_AI_DESCRIPTION,
|
|
180
|
+
content
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
171
184
|
/**
|
|
172
185
|
* Combines a list of files into a single string.
|
|
173
186
|
*
|
|
@@ -183,31 +196,17 @@ export class DesignTypes {
|
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
/**
|
|
186
|
-
* Sends content to AI for
|
|
199
|
+
* Sends content and a prompt to the AI for processing.
|
|
187
200
|
*
|
|
188
|
-
* Отправляет контент ИИ для
|
|
189
|
-
* @param content content
|
|
201
|
+
* Отправляет контент и промпт ИИ для обработки.
|
|
202
|
+
* @param content content for processing / контент для обработки
|
|
203
|
+
* @param prompt instructions for the AI / инструкции для ИИ
|
|
190
204
|
*/
|
|
191
|
-
protected async
|
|
205
|
+
protected async toAi(content: string, prompt: string): Promise<string | undefined> {
|
|
192
206
|
const ai = useAi()
|
|
193
207
|
|
|
194
208
|
if (ai) {
|
|
195
|
-
ai.addPrompt(
|
|
196
|
-
'Remove all Russian comments from this code. '
|
|
197
|
-
+ 'Simplify and shorten all English comments for AI readability while maintaining a clear balance between brevity and context. Do not delete them even if the code seems obvious. '
|
|
198
|
-
+ 'Always keep All JSDoc "@example", "@remarks", "@note", and any other notes or warnings. '
|
|
199
|
-
+ 'Remove all imports. '
|
|
200
|
-
+ 'Remove all non-public content: delete all private and protected class methods and properties, and any non-exported elements. The final output must contain only the members and entities that are accessible from outside. '
|
|
201
|
-
+ 'Remove any code segments or data that do not provide useful information for an AI assistant. '
|
|
202
|
-
+ 'You may remove abstract classes or other structures that provide no practical value for code generation, but do so with extreme caution. Maintain a strict balance: if there is even a 5% chance the item might be relevant for understanding the API or generating code, keep it. Think carefully before every deletion. '
|
|
203
|
-
+ 'Remove any large Enums that add excessive length without providing critical context. '
|
|
204
|
-
+ 'Your goal is to create a compact, context-rich file that enables any AI coding assistant to generate high-quality code for a developer. '
|
|
205
|
-
+ 'Ensure that no public API surface, essential data types, or required logic is lost. '
|
|
206
|
-
+ 'Do not delete any "type" definitions; they are strictly required. '
|
|
207
|
-
+ 'Do not delete file paths (labels starting with "// File:"). '
|
|
208
|
-
+ 'All instructions are mandatory and must be executed perfectly. '
|
|
209
|
-
+ 'Return ONLY the resulting code. No markdown code blocks, no tags, no explanations, and no additional comments from the AI. NOTHING but the pure code.'
|
|
210
|
-
)
|
|
209
|
+
ai.addPrompt(prompt)
|
|
211
210
|
ai.addPrompt(`File Content: ${content}`)
|
|
212
211
|
|
|
213
212
|
const generate = await ai.generate('go!')
|
|
@@ -217,6 +216,57 @@ export class DesignTypes {
|
|
|
217
216
|
}
|
|
218
217
|
}
|
|
219
218
|
|
|
220
|
-
return
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Sends content to AI for optimization.
|
|
224
|
+
*
|
|
225
|
+
* Отправляет контент ИИ для оптимизации.
|
|
226
|
+
* @param content content to optimize / контент для оптимизации
|
|
227
|
+
*/
|
|
228
|
+
protected async toAiEdit(content: string): Promise<string> {
|
|
229
|
+
const generate = await this.toAi(
|
|
230
|
+
content,
|
|
231
|
+
'Remove all Russian comments from this code. '
|
|
232
|
+
+ 'Simplify and shorten all English comments for AI readability while maintaining a clear balance between brevity and context. Do not delete them even if the code seems obvious. '
|
|
233
|
+
+ 'Always keep All JSDoc "@example", "@remarks", "@note", and any other notes or warnings. '
|
|
234
|
+
+ 'Remove all imports. '
|
|
235
|
+
+ 'Remove all non-public content: delete all private and protected class methods and properties, and any non-exported elements. The final output must contain only the members and entities that are accessible from outside. '
|
|
236
|
+
+ 'Remove any code segments or data that do not provide useful information for an AI assistant. '
|
|
237
|
+
+ 'You may remove abstract classes or other structures that provide no practical value for code generation, but do so with extreme caution. Maintain a strict balance: if there is even a 5% chance the item might be relevant for understanding the API or generating code, keep it. Think carefully before every deletion. '
|
|
238
|
+
+ 'Remove any large Enums that add excessive length without providing critical context. '
|
|
239
|
+
+ 'Your goal is to create a compact, context-rich file that enables any AI coding assistant to generate high-quality code for a developer. '
|
|
240
|
+
+ 'Ensure that no public API surface, essential data types, or required logic is lost. '
|
|
241
|
+
+ 'Do not delete any "type" definitions; they are strictly required. '
|
|
242
|
+
+ 'Do not delete file paths (labels starting with "// File:"). '
|
|
243
|
+
+ 'All instructions are mandatory and must be executed perfectly. '
|
|
244
|
+
+ 'Return ONLY the resulting code. No markdown code blocks, no tags, no explanations, and no additional comments from the AI. NOTHING but the pure code.'
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return generate ?? content
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generates a project description and usage guidelines using AI.
|
|
252
|
+
*
|
|
253
|
+
* Генерирует описание проекта и рекомендации по использованию с помощью ИИ.
|
|
254
|
+
* @param content cleaned type definitions / очищенные определения типов
|
|
255
|
+
*/
|
|
256
|
+
protected async toAiDescription(content: string): Promise<string> {
|
|
257
|
+
const generate = await this.toAi(
|
|
258
|
+
content,
|
|
259
|
+
'Analyze the provided code and generate a highly technical, concise project overview for another AI coding assistant. '
|
|
260
|
+
+ 'Your goal is to help the AI decide whether it needs to study this library to fulfill a user request. '
|
|
261
|
+
+ 'The description must be objective, factual, and free of marketing fluff. '
|
|
262
|
+
+ 'Include: '
|
|
263
|
+
+ '1. Core Purpose: What is the primary function of this library? (e.g., UI component library, state management, utility for X). '
|
|
264
|
+
+ '2. Usage Scenarios: In what specific cases and scenarios is this library indispensable? When should an AI study its API? '
|
|
265
|
+
+ '3. Integration Context: How does it relate to other technologies in the stack (if evident)? '
|
|
266
|
+
+ 'Ensure the structure is clean and enables immediate context retrieval. '
|
|
267
|
+
+ 'Return ONLY the resulting description text. No markdown, no labels like "Description:", no explanations. NOTHING but the pure content.'
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return generate ?? ''
|
|
221
271
|
}
|
|
222
272
|
}
|