@deckio/deck-engine 1.7.5 → 1.7.7

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.
@@ -1,287 +1,287 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Export slides to PDF using Puppeteer + embedded Vite dev server.
4
- *
5
- * For project exports (--project), the script spins up its own Vite dev
6
- * server so no separate `npm run dev` is needed.
7
- *
8
- * Usage:
9
- * node scripts/export-pdf.mjs --project dev-plan
10
- * node scripts/export-pdf.mjs # ghcp slides (needs running dev server)
11
- * node scripts/export-pdf.mjs --customer --customer-name Rabobank --from 12 --to 19
12
- * node scripts/export-pdf.mjs --internal --from 2 --to 10
13
- */
14
- import puppeteer from 'puppeteer'
15
- import { existsSync, mkdirSync } from 'fs'
16
- import path from 'path'
17
- import { createServer } from 'vite'
18
-
19
- const args = process.argv.slice(2)
20
- const flag = (name) => args.includes(`--${name}`)
21
- const getArg = (name, fallback) => {
22
- const idx = args.indexOf(`--${name}`)
23
- return idx !== -1 && args[idx + 1] ? Number(args[idx + 1]) : fallback
24
- }
25
- const getStringArg = (name, fallback) => {
26
- const idx = args.indexOf(`--${name}`)
27
- return idx !== -1 && args[idx + 1] ? args[idx + 1] : fallback
28
- }
29
-
30
- const ROOT = path.resolve(getStringArg('root', process.cwd()))
31
- const outDir = path.resolve(ROOT, getStringArg('out-dir', 'exports'))
32
- const viteConfigPath = path.resolve(ROOT, getStringArg('config', 'vite.config.js'))
33
-
34
- const PORT = getArg('port', 5173)
35
- const FROM = getArg('from', null)
36
- const TO = getArg('to', null)
37
- const CUSTOMER_NAME = getStringArg('customer-name', null)
38
- const PROJECT = getStringArg('project', null)
39
- const isCustomer = flag('customer')
40
- const slug = (s) => s.toLowerCase().replace(/\s+/g, '-')
41
- const outFile = PROJECT
42
- ? (CUSTOMER_NAME
43
- ? `${slug(CUSTOMER_NAME)}-slides.pdf`
44
- : `${PROJECT}-slides.pdf`)
45
- : isCustomer
46
- ? `${slug(CUSTOMER_NAME || 'customer')}-slides.pdf`
47
- : flag('internal') ? 'internal-slides.pdf' : 'slides.pdf'
48
-
49
- if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true })
50
-
51
- async function hideNav(page) {
52
- await page.evaluate(() => {
53
- document.querySelectorAll(
54
- '[class*="nav"], [class*="Nav"], [class*="progress"], ' +
55
- '[class*="Progress"], [class*="hint"], [class*="Hint"]'
56
- ).forEach(el => {
57
- if (el.tagName !== 'BODY' && el.tagName !== 'HTML') {
58
- el.style.display = 'none'
59
- }
60
- })
61
- })
62
- }
63
-
64
- const sleep = ms => new Promise(r => setTimeout(r, ms))
65
-
66
- async function goToSlide(page, targetIdx, totalSlides) {
67
- for (let i = 0; i < totalSlides; i++) {
68
- await page.keyboard.press('ArrowLeft')
69
- await sleep(50)
70
- }
71
- for (let i = 0; i < targetIdx; i++) {
72
- await page.keyboard.press('ArrowRight')
73
- await sleep(100)
74
- }
75
- await sleep(800)
76
- }
77
-
78
- async function captureSlides(page, fromIdx, toIdx, totalSlides) {
79
- await goToSlide(page, fromIdx, totalSlides)
80
- const shots = []
81
- for (let i = fromIdx; i <= toIdx; i++) {
82
- console.log(` šŸ“ø Slide ${i + 1}/${totalSlides}`)
83
- await sleep(400)
84
- shots.push(await page.screenshot({ type: 'png', encoding: 'binary' }))
85
- if (i < toIdx) {
86
- await page.keyboard.press('ArrowRight')
87
- await sleep(700)
88
- }
89
- }
90
- return shots
91
- }
92
-
93
- async function buildPDF(browser, screenshots, pdfPath) {
94
- const pdfPage = await browser.newPage()
95
- await pdfPage.setViewport({ width: 1920, height: 1080 })
96
-
97
- const imgTags = screenshots.map(buf => {
98
- const b64 = Buffer.from(buf).toString('base64')
99
- return `<div class="page"><img src="data:image/png;base64,${b64}" /></div>`
100
- }).join('\n')
101
-
102
- await pdfPage.setContent(`<!DOCTYPE html>
103
- <html><head><style>
104
- *{margin:0;padding:0;box-sizing:border-box}
105
- html,body{width:1920px;overflow:hidden}
106
- @page{size:1920px 1080px;margin:0}
107
- .page{width:1920px;height:1080px;page-break-after:always;page-break-inside:avoid;overflow:hidden}
108
- .page:last-child{page-break-after:avoid}
109
- .page img{display:block;width:1920px;height:1080px;object-fit:fill}
110
- </style></head><body>${imgTags}</body></html>`, { waitUntil: 'load' })
111
-
112
- await pdfPage.pdf({
113
- path: pdfPath,
114
- width: '1920px',
115
- height: '1080px',
116
- printBackground: true,
117
- preferCSSPageSize: true,
118
- margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' },
119
- })
120
- await pdfPage.close()
121
- }
122
-
123
- async function exportProjectPDF() {
124
- console.log(`\nšŸ“‚ Exporting project: ${PROJECT}`)
125
-
126
- const server = await createServer({
127
- root: ROOT,
128
- configFile: viteConfigPath,
129
- server: { port: 0, strictPort: false, host: '127.0.0.1' },
130
- logLevel: 'silent',
131
- })
132
- await server.listen()
133
- const addr = server.httpServer.address()
134
- const base = `http://127.0.0.1:${addr.port}`
135
- console.log(` Vite server listening on ${base}`)
136
-
137
- await sleep(2000)
138
-
139
- const browser = await puppeteer.launch({
140
- headless: true,
141
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
142
- })
143
- const page = await browser.newPage()
144
- await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 })
145
-
146
- const url = `${base}/#/${PROJECT}`
147
- console.log(`ā³ Loading ${url}`)
148
- let loaded = false
149
- for (let attempt = 1; attempt <= 3; attempt++) {
150
- try {
151
- await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 })
152
- loaded = true
153
- break
154
- } catch (err) {
155
- console.log(` Attempt ${attempt}/3 failed: ${err.message}`)
156
- await sleep(2000)
157
- }
158
- }
159
- if (!loaded) throw new Error('Could not load the Vite dev server after 3 attempts')
160
- await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 })
161
-
162
- await page.waitForFunction(
163
- () => document.querySelectorAll('.slide').length > 0,
164
- { timeout: 15000 }
165
- )
166
- await sleep(1500)
167
-
168
- await hideNav(page)
169
-
170
- if (CUSTOMER_NAME) {
171
- console.log(`šŸ‘¤ Selecting customer: ${CUSTOMER_NAME}`)
172
- const clicked = await page.evaluate(() => {
173
- const btn = [...document.querySelectorAll('button')].find(b => b.textContent.includes('Customer Facing'))
174
- if (btn) {
175
- btn.click()
176
- return true
177
- }
178
- return false
179
- })
180
- if (!clicked) {
181
- console.error('āŒ "Customer Facing" button not found')
182
- await browser.close()
183
- await server.close()
184
- process.exit(1)
185
- }
186
- await sleep(800)
187
-
188
- const picked = await page.evaluate(name => {
189
- const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim().includes(name))
190
- if (btn) {
191
- btn.click()
192
- return true
193
- }
194
- return false
195
- }, CUSTOMER_NAME)
196
- if (!picked) {
197
- console.error(`āŒ Customer "${CUSTOMER_NAME}" not found`)
198
- await browser.close()
199
- await server.close()
200
- process.exit(1)
201
- }
202
- await sleep(1000)
203
- console.log('āœ… Customer selected')
204
- await hideNav(page)
205
- }
206
-
207
- const totalSlides = await page.evaluate(() => document.querySelectorAll('.slide').length)
208
- const fromIdx = FROM ? FROM - 1 : 0
209
- const toIdx = TO ? TO - 1 : totalSlides - 1
210
- console.log(`šŸ“Š ${totalSlides} slides — exporting ${fromIdx + 1}–${toIdx + 1}`)
211
-
212
- const screenshots = await captureSlides(page, fromIdx, toIdx, totalSlides)
213
-
214
- const pdfPath = path.join(outDir, outFile)
215
- await buildPDF(browser, screenshots, pdfPath)
216
-
217
- await browser.close()
218
- await server.close()
219
- console.log(`\nāœ… PDF saved to ${pdfPath}`)
220
- }
221
-
222
- async function exportDevServerPDF() {
223
- const BASE = `http://localhost:${PORT}`
224
- console.log(`\nā³ Connecting to dev server at ${BASE}`)
225
-
226
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] })
227
- const page = await browser.newPage()
228
- await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 })
229
-
230
- await page.goto(`${BASE}/#/ghcp`, { waitUntil: 'networkidle0', timeout: 30000 })
231
- await page.waitForSelector('.slide.active', { timeout: 10000 })
232
-
233
- if (isCustomer && CUSTOMER_NAME) {
234
- console.log(`šŸ‘¤ Selecting customer: ${CUSTOMER_NAME}`)
235
- const clicked = await page.evaluate(() => {
236
- const btn = [...document.querySelectorAll('button')].find(b => b.textContent.includes('Customer Facing'))
237
- if (btn) {
238
- btn.click()
239
- return true
240
- }
241
- return false
242
- })
243
- if (!clicked) {
244
- console.error('āŒ "Customer Facing" button not found')
245
- await browser.close()
246
- process.exit(1)
247
- }
248
- await sleep(800)
249
-
250
- const picked = await page.evaluate(name => {
251
- const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim().includes(name))
252
- if (btn) {
253
- btn.click()
254
- return true
255
- }
256
- return false
257
- }, CUSTOMER_NAME)
258
- if (!picked) {
259
- console.error(`āŒ Customer "${CUSTOMER_NAME}" not found`)
260
- await browser.close()
261
- process.exit(1)
262
- }
263
- await sleep(1000)
264
- console.log('āœ… Customer selected')
265
- }
266
-
267
- await hideNav(page)
268
-
269
- const totalSlides = await page.evaluate(() => document.querySelectorAll('.slide').length)
270
- const fromIdx = FROM ? FROM - 1 : 0
271
- const toIdx = TO ? TO - 1 : totalSlides - 1
272
- console.log(`šŸ“Š ${totalSlides} slides — exporting ${fromIdx + 1}–${toIdx + 1}`)
273
-
274
- const screenshots = await captureSlides(page, fromIdx, toIdx, totalSlides)
275
-
276
- const pdfPath = path.join(outDir, outFile)
277
- await buildPDF(browser, screenshots, pdfPath)
278
-
279
- await browser.close()
280
- console.log(`\nāœ… PDF saved to ${pdfPath}`)
281
- }
282
-
283
- const run = PROJECT ? exportProjectPDF : exportDevServerPDF
284
- run().catch(err => {
285
- console.error('āŒ Export failed:', err.message)
286
- process.exit(1)
287
- })
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Export slides to PDF using Puppeteer + embedded Vite dev server.
4
+ *
5
+ * For project exports (--project), the script spins up its own Vite dev
6
+ * server so no separate `npm run dev` is needed.
7
+ *
8
+ * Usage:
9
+ * node scripts/export-pdf.mjs --project dev-plan
10
+ * node scripts/export-pdf.mjs # ghcp slides (needs running dev server)
11
+ * node scripts/export-pdf.mjs --customer --customer-name Rabobank --from 12 --to 19
12
+ * node scripts/export-pdf.mjs --internal --from 2 --to 10
13
+ */
14
+ import puppeteer from 'puppeteer'
15
+ import { existsSync, mkdirSync } from 'fs'
16
+ import path from 'path'
17
+ import { createServer } from 'vite'
18
+
19
+ const args = process.argv.slice(2)
20
+ const flag = (name) => args.includes(`--${name}`)
21
+ const getArg = (name, fallback) => {
22
+ const idx = args.indexOf(`--${name}`)
23
+ return idx !== -1 && args[idx + 1] ? Number(args[idx + 1]) : fallback
24
+ }
25
+ const getStringArg = (name, fallback) => {
26
+ const idx = args.indexOf(`--${name}`)
27
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : fallback
28
+ }
29
+
30
+ const ROOT = path.resolve(getStringArg('root', process.cwd()))
31
+ const outDir = path.resolve(ROOT, getStringArg('out-dir', 'exports'))
32
+ const viteConfigPath = path.resolve(ROOT, getStringArg('config', 'vite.config.js'))
33
+
34
+ const PORT = getArg('port', 5173)
35
+ const FROM = getArg('from', null)
36
+ const TO = getArg('to', null)
37
+ const CUSTOMER_NAME = getStringArg('customer-name', null)
38
+ const PROJECT = getStringArg('project', null)
39
+ const isCustomer = flag('customer')
40
+ const slug = (s) => s.toLowerCase().replace(/\s+/g, '-')
41
+ const outFile = PROJECT
42
+ ? (CUSTOMER_NAME
43
+ ? `${slug(CUSTOMER_NAME)}-slides.pdf`
44
+ : `${PROJECT}-slides.pdf`)
45
+ : isCustomer
46
+ ? `${slug(CUSTOMER_NAME || 'customer')}-slides.pdf`
47
+ : flag('internal') ? 'internal-slides.pdf' : 'slides.pdf'
48
+
49
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true })
50
+
51
+ async function hideNav(page) {
52
+ await page.evaluate(() => {
53
+ document.querySelectorAll(
54
+ '[class*="nav"], [class*="Nav"], [class*="progress"], ' +
55
+ '[class*="Progress"], [class*="hint"], [class*="Hint"]'
56
+ ).forEach(el => {
57
+ if (el.tagName !== 'BODY' && el.tagName !== 'HTML') {
58
+ el.style.display = 'none'
59
+ }
60
+ })
61
+ })
62
+ }
63
+
64
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
65
+
66
+ async function goToSlide(page, targetIdx, totalSlides) {
67
+ for (let i = 0; i < totalSlides; i++) {
68
+ await page.keyboard.press('ArrowLeft')
69
+ await sleep(50)
70
+ }
71
+ for (let i = 0; i < targetIdx; i++) {
72
+ await page.keyboard.press('ArrowRight')
73
+ await sleep(100)
74
+ }
75
+ await sleep(800)
76
+ }
77
+
78
+ async function captureSlides(page, fromIdx, toIdx, totalSlides) {
79
+ await goToSlide(page, fromIdx, totalSlides)
80
+ const shots = []
81
+ for (let i = fromIdx; i <= toIdx; i++) {
82
+ console.log(` šŸ“ø Slide ${i + 1}/${totalSlides}`)
83
+ await sleep(400)
84
+ shots.push(await page.screenshot({ type: 'png', encoding: 'binary' }))
85
+ if (i < toIdx) {
86
+ await page.keyboard.press('ArrowRight')
87
+ await sleep(700)
88
+ }
89
+ }
90
+ return shots
91
+ }
92
+
93
+ async function buildPDF(browser, screenshots, pdfPath) {
94
+ const pdfPage = await browser.newPage()
95
+ await pdfPage.setViewport({ width: 1920, height: 1080 })
96
+
97
+ const imgTags = screenshots.map(buf => {
98
+ const b64 = Buffer.from(buf).toString('base64')
99
+ return `<div class="page"><img src="data:image/png;base64,${b64}" /></div>`
100
+ }).join('\n')
101
+
102
+ await pdfPage.setContent(`<!DOCTYPE html>
103
+ <html><head><style>
104
+ *{margin:0;padding:0;box-sizing:border-box}
105
+ html,body{width:1920px;overflow:hidden}
106
+ @page{size:1920px 1080px;margin:0}
107
+ .page{width:1920px;height:1080px;page-break-after:always;page-break-inside:avoid;overflow:hidden}
108
+ .page:last-child{page-break-after:avoid}
109
+ .page img{display:block;width:1920px;height:1080px;object-fit:fill}
110
+ </style></head><body>${imgTags}</body></html>`, { waitUntil: 'load' })
111
+
112
+ await pdfPage.pdf({
113
+ path: pdfPath,
114
+ width: '1920px',
115
+ height: '1080px',
116
+ printBackground: true,
117
+ preferCSSPageSize: true,
118
+ margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' },
119
+ })
120
+ await pdfPage.close()
121
+ }
122
+
123
+ async function exportProjectPDF() {
124
+ console.log(`\nšŸ“‚ Exporting project: ${PROJECT}`)
125
+
126
+ const server = await createServer({
127
+ root: ROOT,
128
+ configFile: viteConfigPath,
129
+ server: { port: 0, strictPort: false, host: '127.0.0.1' },
130
+ logLevel: 'silent',
131
+ })
132
+ await server.listen()
133
+ const addr = server.httpServer.address()
134
+ const base = `http://127.0.0.1:${addr.port}`
135
+ console.log(` Vite server listening on ${base}`)
136
+
137
+ await sleep(2000)
138
+
139
+ const browser = await puppeteer.launch({
140
+ headless: true,
141
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
142
+ })
143
+ const page = await browser.newPage()
144
+ await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 })
145
+
146
+ const url = `${base}/#/${PROJECT}`
147
+ console.log(`ā³ Loading ${url}`)
148
+ let loaded = false
149
+ for (let attempt = 1; attempt <= 3; attempt++) {
150
+ try {
151
+ await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 })
152
+ loaded = true
153
+ break
154
+ } catch (err) {
155
+ console.log(` Attempt ${attempt}/3 failed: ${err.message}`)
156
+ await sleep(2000)
157
+ }
158
+ }
159
+ if (!loaded) throw new Error('Could not load the Vite dev server after 3 attempts')
160
+ await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 })
161
+
162
+ await page.waitForFunction(
163
+ () => document.querySelectorAll('.slide').length > 0,
164
+ { timeout: 15000 }
165
+ )
166
+ await sleep(1500)
167
+
168
+ await hideNav(page)
169
+
170
+ if (CUSTOMER_NAME) {
171
+ console.log(`šŸ‘¤ Selecting customer: ${CUSTOMER_NAME}`)
172
+ const clicked = await page.evaluate(() => {
173
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.includes('Customer Facing'))
174
+ if (btn) {
175
+ btn.click()
176
+ return true
177
+ }
178
+ return false
179
+ })
180
+ if (!clicked) {
181
+ console.error('āŒ "Customer Facing" button not found')
182
+ await browser.close()
183
+ await server.close()
184
+ process.exit(1)
185
+ }
186
+ await sleep(800)
187
+
188
+ const picked = await page.evaluate(name => {
189
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim().includes(name))
190
+ if (btn) {
191
+ btn.click()
192
+ return true
193
+ }
194
+ return false
195
+ }, CUSTOMER_NAME)
196
+ if (!picked) {
197
+ console.error(`āŒ Customer "${CUSTOMER_NAME}" not found`)
198
+ await browser.close()
199
+ await server.close()
200
+ process.exit(1)
201
+ }
202
+ await sleep(1000)
203
+ console.log('āœ… Customer selected')
204
+ await hideNav(page)
205
+ }
206
+
207
+ const totalSlides = await page.evaluate(() => document.querySelectorAll('.slide').length)
208
+ const fromIdx = FROM ? FROM - 1 : 0
209
+ const toIdx = TO ? TO - 1 : totalSlides - 1
210
+ console.log(`šŸ“Š ${totalSlides} slides — exporting ${fromIdx + 1}–${toIdx + 1}`)
211
+
212
+ const screenshots = await captureSlides(page, fromIdx, toIdx, totalSlides)
213
+
214
+ const pdfPath = path.join(outDir, outFile)
215
+ await buildPDF(browser, screenshots, pdfPath)
216
+
217
+ await browser.close()
218
+ await server.close()
219
+ console.log(`\nāœ… PDF saved to ${pdfPath}`)
220
+ }
221
+
222
+ async function exportDevServerPDF() {
223
+ const BASE = `http://localhost:${PORT}`
224
+ console.log(`\nā³ Connecting to dev server at ${BASE}`)
225
+
226
+ const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] })
227
+ const page = await browser.newPage()
228
+ await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 })
229
+
230
+ await page.goto(`${BASE}/#/ghcp`, { waitUntil: 'networkidle0', timeout: 30000 })
231
+ await page.waitForSelector('.slide.active', { timeout: 10000 })
232
+
233
+ if (isCustomer && CUSTOMER_NAME) {
234
+ console.log(`šŸ‘¤ Selecting customer: ${CUSTOMER_NAME}`)
235
+ const clicked = await page.evaluate(() => {
236
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.includes('Customer Facing'))
237
+ if (btn) {
238
+ btn.click()
239
+ return true
240
+ }
241
+ return false
242
+ })
243
+ if (!clicked) {
244
+ console.error('āŒ "Customer Facing" button not found')
245
+ await browser.close()
246
+ process.exit(1)
247
+ }
248
+ await sleep(800)
249
+
250
+ const picked = await page.evaluate(name => {
251
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim().includes(name))
252
+ if (btn) {
253
+ btn.click()
254
+ return true
255
+ }
256
+ return false
257
+ }, CUSTOMER_NAME)
258
+ if (!picked) {
259
+ console.error(`āŒ Customer "${CUSTOMER_NAME}" not found`)
260
+ await browser.close()
261
+ process.exit(1)
262
+ }
263
+ await sleep(1000)
264
+ console.log('āœ… Customer selected')
265
+ }
266
+
267
+ await hideNav(page)
268
+
269
+ const totalSlides = await page.evaluate(() => document.querySelectorAll('.slide').length)
270
+ const fromIdx = FROM ? FROM - 1 : 0
271
+ const toIdx = TO ? TO - 1 : totalSlides - 1
272
+ console.log(`šŸ“Š ${totalSlides} slides — exporting ${fromIdx + 1}–${toIdx + 1}`)
273
+
274
+ const screenshots = await captureSlides(page, fromIdx, toIdx, totalSlides)
275
+
276
+ const pdfPath = path.join(outDir, outFile)
277
+ await buildPDF(browser, screenshots, pdfPath)
278
+
279
+ await browser.close()
280
+ console.log(`\nāœ… PDF saved to ${pdfPath}`)
281
+ }
282
+
283
+ const run = PROJECT ? exportProjectPDF : exportDevServerPDF
284
+ run().catch(err => {
285
+ console.error('āŒ Export failed:', err.message)
286
+ process.exit(1)
287
+ })