@dxtmisha/scripts 0.7.9 → 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 +13 -9
- package/src/classes/BrowserItem.ts +375 -0
- package/src/classes/Design/DesignFigma.ts +34 -0
- package/src/classes/Design/DesignReplace.ts +1 -1
- package/src/classes/Design/DesignScreenshot.ts +123 -0
- package/src/classes/Design/DesignTypes.ts +81 -30
- 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
|
+
}
|
|
@@ -215,7 +215,7 @@ export class DesignReplace {
|
|
|
215
215
|
&& !this.isNoMark(mark, name)
|
|
216
216
|
) {
|
|
217
217
|
const typesString = !constructor && !types.match(/string|boolean/) && types.match(/'/)
|
|
218
|
-
?
|
|
218
|
+
? `string | ${types}`
|
|
219
219
|
: types
|
|
220
220
|
|
|
221
221
|
templates.push(`${name}?: ${typesString}`)
|
|
@@ -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
|
+
}
|