@stacksjs/defaults 0.72.50 → 0.72.55
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.
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Commands:
|
|
10
10
|
* bun browse.ts navigate <url>
|
|
11
|
-
* bun browse.ts screenshot <url> [--viewport WxH] [--full
|
|
11
|
+
* bun browse.ts screenshot <url> [--viewport WxH] [--full | --element SEL | --scroll-y N] [--scale N] [--out PATH]
|
|
12
12
|
* bun browse.ts responsive <url> [--out-dir DIR]
|
|
13
13
|
* bun browse.ts monitor <url> [--ms 5000]
|
|
14
14
|
* bun browse.ts snapshot <url>
|
|
@@ -403,7 +403,39 @@ async function title(cdp: Cdp): Promise<string> {
|
|
|
403
403
|
return r.result?.value ?? ''
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Scroll the real page to `y` and let one frame settle before the caller
|
|
408
|
+
* screenshots it.
|
|
409
|
+
*
|
|
410
|
+
* `--full` captures the whole document through `captureBeyondViewport`,
|
|
411
|
+
* which paints content at its full-page layout position without ever
|
|
412
|
+
* moving `scrollY` - correct for a top-to-bottom review, but useless for
|
|
413
|
+
* checking anything that depends on an actual mid-scroll viewport: a
|
|
414
|
+
* `position: sticky` nav overlapping content beneath it, a CSS
|
|
415
|
+
* `animation-timeline: view()` reveal mid-transition, or simply "what does
|
|
416
|
+
* the fold look like 800px down". `window.scrollTo(..., { behavior:
|
|
417
|
+
* 'instant' })` forces the jump synchronously even when the page sets
|
|
418
|
+
* `scroll-behavior: smooth`, which would otherwise animate the scroll and
|
|
419
|
+
* leave a screenshot taken immediately after mid-flight. The two rAF
|
|
420
|
+
* round-trips that follow give the compositor one full frame to repaint
|
|
421
|
+
* sticky/fixed layers and any scroll-driven animation at the new offset
|
|
422
|
+
* before `Page.captureScreenshot` reads back pixels, which
|
|
423
|
+
* `Runtime.evaluate` returning does not by itself guarantee.
|
|
424
|
+
*/
|
|
425
|
+
async function scrollTo(cdp: Cdp, y: number): Promise<void> {
|
|
426
|
+
const r = await cdp.send('Runtime.evaluate', {
|
|
427
|
+
expression: `(() => {
|
|
428
|
+
window.scrollTo({ top: ${y}, left: 0, behavior: 'instant' })
|
|
429
|
+
return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(window.scrollY))))
|
|
430
|
+
})()`,
|
|
431
|
+
returnByValue: true,
|
|
432
|
+
awaitPromise: true,
|
|
433
|
+
})
|
|
434
|
+
if (r.exceptionDetails)
|
|
435
|
+
throw new Error(r.exceptionDetails.exception?.description || r.exceptionDetails.text || 'scrollTo failed')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function captureScreenshot(cdp: Cdp, opts: { full?: boolean, element?: string, scrollY?: number } = {}): Promise<Buffer> {
|
|
407
439
|
let clip: any
|
|
408
440
|
let captureBeyondViewport = false
|
|
409
441
|
|
|
@@ -423,6 +455,9 @@ async function captureScreenshot(cdp: Cdp, opts: { full?: boolean, element?: str
|
|
|
423
455
|
clip = { x: 0, y: 0, width: Math.ceil(size.width), height: Math.ceil(size.height), scale: 1 }
|
|
424
456
|
captureBeyondViewport = true
|
|
425
457
|
}
|
|
458
|
+
else if (opts.scrollY != null) {
|
|
459
|
+
await scrollTo(cdp, opts.scrollY)
|
|
460
|
+
}
|
|
426
461
|
|
|
427
462
|
const r = await cdp.send('Page.captureScreenshot', { format: 'png', ...(clip ? { clip, captureBeyondViewport } : {}) })
|
|
428
463
|
return Buffer.from(r.data, 'base64')
|
|
@@ -523,6 +558,22 @@ function parseViewport(value: string | undefined): { w: number, h: number } {
|
|
|
523
558
|
return { w, h }
|
|
524
559
|
}
|
|
525
560
|
|
|
561
|
+
/** `--scroll-y` on `screenshot`: an explicit document Y to jump to before
|
|
562
|
+
* capturing just the viewport, for content a full-page capture cannot show
|
|
563
|
+
* (sticky/fixed elements, scroll-linked animation) at a specific fold. */
|
|
564
|
+
function parseScrollY(value: string | boolean | Array<string | boolean> | undefined): number | null {
|
|
565
|
+
if (value === undefined)
|
|
566
|
+
return null
|
|
567
|
+
if (typeof value !== 'string')
|
|
568
|
+
throw new TypeError('--scroll-y requires a value, for example --scroll-y 800.')
|
|
569
|
+
|
|
570
|
+
const y = Number(value)
|
|
571
|
+
if (!Number.isFinite(y) || y < 0)
|
|
572
|
+
throw new TypeError(`Invalid --scroll-y "${value}". Expected a non-negative number of pixels.`)
|
|
573
|
+
|
|
574
|
+
return y
|
|
575
|
+
}
|
|
576
|
+
|
|
526
577
|
type ScenarioAction = 'assert' | 'click' | 'evaluate' | 'fill' | 'focus' | 'press' | 'wait'
|
|
527
578
|
|
|
528
579
|
interface ScenarioStep {
|
|
@@ -703,6 +754,10 @@ async function main() {
|
|
|
703
754
|
console.log(' --cookie "name=value" repeatable; pre-seeds cookies (e.g. coming-soon bypass)')
|
|
704
755
|
console.log(' --settle 1500 ms to wait after load before acting (default 700; stretch for entrance animations)')
|
|
705
756
|
console.log(' --scheme dark emulate prefers-color-scheme (light|dark) for QA of theme-aware pages')
|
|
757
|
+
console.log(' --scroll-y 800 screenshot: jump to this document Y first, then capture just the viewport')
|
|
758
|
+
console.log(' (--full renders beyond-viewport without ever scrolling, so it cannot show a')
|
|
759
|
+
console.log(' position:sticky/fixed element overlapping content, or a scroll-linked')
|
|
760
|
+
console.log(' animation mid-transition, at a specific fold - --scroll-y can)')
|
|
706
761
|
process.exit(url ? 0 : 1)
|
|
707
762
|
}
|
|
708
763
|
|
|
@@ -733,12 +788,16 @@ async function main() {
|
|
|
733
788
|
const cdp = await openPage(session.port)
|
|
734
789
|
const viewport = parseViewport(typeof flags.viewport === 'string' ? flags.viewport : undefined)
|
|
735
790
|
const scale = flags.scale ? Number(flags.scale) : 1
|
|
791
|
+
const scrollY = parseScrollY(flags['scroll-y'])
|
|
792
|
+
const modes = [flags.full && 'full', flags.element && 'element', scrollY != null && 'scroll-y'].filter(Boolean)
|
|
793
|
+
if (modes.length > 1)
|
|
794
|
+
throw new TypeError(`--full, --element, and --scroll-y are mutually exclusive; got ${modes.join(', ')}.`)
|
|
736
795
|
const out = (flags.out as string) || `storage/framework/runtime/shots/${new URL(url).pathname.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '') || 'home'}.png`
|
|
737
796
|
mkdirSync(out.split('/').slice(0, -1).join('/') || '.', { recursive: true })
|
|
738
797
|
const state = await gotoAndInstrument(cdp, url, { viewport, scale, cookies, settleMs, scheme })
|
|
739
|
-
const png = await captureScreenshot(cdp, { full: !!flags.full, element: flags.element as string | undefined })
|
|
798
|
+
const png = await captureScreenshot(cdp, { full: !!flags.full, element: flags.element as string | undefined, scrollY: scrollY ?? undefined })
|
|
740
799
|
await Bun.write(out, png)
|
|
741
|
-
console.log(JSON.stringify({ url, out, viewport: `${viewport.w}x${viewport.h}`, scale, full: !!flags.full, element: flags.element ?? null, bytes: png.length }, null, 2))
|
|
800
|
+
console.log(JSON.stringify({ url, out, viewport: `${viewport.w}x${viewport.h}`, scale, full: !!flags.full, element: flags.element ?? null, scrollY: scrollY ?? null, bytes: png.length }, null, 2))
|
|
742
801
|
state.dispose()
|
|
743
802
|
cdp.close()
|
|
744
803
|
}
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.55",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
9
|
+
"directory": "./storage/framework/core/defaults"
|
|
10
|
+
},
|
|
6
11
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
12
|
"author": "Chris Breuer",
|
|
8
13
|
"license": "MIT",
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
"dependencies": {
|
|
47
52
|
"@iconify-json/f7": "^1.2.2",
|
|
48
53
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
49
|
-
"@stacksjs/mobile": "^0.72.
|
|
54
|
+
"@stacksjs/mobile": "^0.72.55",
|
|
50
55
|
"@stacksjs/sanitizer": "^0.2.113"
|
|
51
56
|
},
|
|
52
57
|
"scripts": {
|
|
@@ -26,7 +26,9 @@ const backgroundImage = props.backgroundImage || '/svgs/404.svg'
|
|
|
26
26
|
Sorry, we couldn't find the page you're looking for.
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
<!-- Same ember as the illustration beside it. White on #c14a15 is
|
|
30
|
+
4.93:1 and on the #9c3510 hover 7.17:1, so both states clear AA. -->
|
|
31
|
+
<a href="/" class="inline-block mt-10 px-4 py-2 text-white bg-[#c14a15] hover:bg-[#9c3510] rounded">
|
|
30
32
|
Go back home
|
|
31
33
|
</a>
|
|
32
34
|
</main>
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// served from public/svgs.
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
+
@title('Page not found')
|
|
14
|
+
|
|
13
15
|
<div class="flex flex-col md:flex-row min-h-screen bg-neutral-50 dark:bg-neutral-900">
|
|
14
16
|
<div class="overflow-hidden md:order-2 h-56 sm:h-72 md:h-auto md:w-1/2 bg-neutral-200 dark:bg-neutral-800">
|
|
15
17
|
<img src="/svgs/404.svg" alt="" class="object-cover h-full w-full">
|
|
@@ -18,7 +20,11 @@
|
|
|
18
20
|
<section class="flex md:order-1 flex-1 flex-col items-start justify-center md:flex-none px-8 py-12 md:px-16 md:w-1/2">
|
|
19
21
|
<div class="font-extrabold text-6xl text-neutral-950 tracking-tight md:text-8xl dark:text-white">404</div>
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
<!-- The rule takes the illustration's own deep rose rather than a
|
|
24
|
+
framework accent: it sits inches from the artwork, and a blue mark
|
|
25
|
+
against a warm dusk scene reads as a mistake in any app's brand. The
|
|
26
|
+
dark value is the same ramp's coral, from the scene's night variant. -->
|
|
27
|
+
<div class="my-6 h-1 w-14 bg-[#c14a15] dark:bg-[#ff8a4c] rounded-full"></div>
|
|
22
28
|
|
|
23
29
|
<h1 class="mb-2 font-semibold text-2xl text-neutral-950 dark:text-white">Page not found</h1>
|
|
24
30
|
|
|
@@ -9,6 +9,9 @@ on:
|
|
|
9
9
|
branches:
|
|
10
10
|
- main
|
|
11
11
|
|
|
12
|
+
# So a maintainer can ship without pushing an empty commit.
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
12
15
|
concurrency:
|
|
13
16
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
14
17
|
cancel-in-progress: true
|
|
@@ -119,3 +122,87 @@ jobs:
|
|
|
119
122
|
|
|
120
123
|
- name: Test Suite
|
|
121
124
|
run: bun run test
|
|
125
|
+
|
|
126
|
+
# Push to deploy: `buddy deploy` on every green push to main.
|
|
127
|
+
#
|
|
128
|
+
# A fresh app has nothing to deploy to, and red CI on day one is worse than
|
|
129
|
+
# no deploy, so this checks for its secrets first and finishes green with a
|
|
130
|
+
# notice when they are missing. Add them under
|
|
131
|
+
# Settings > Secrets and variables > Actions to turn it on:
|
|
132
|
+
#
|
|
133
|
+
# DOTENV_PRIVATE_KEY_PRODUCTION decrypts .env.production for the deploy
|
|
134
|
+
# DEPLOY_SSH_KEY private key for the target server
|
|
135
|
+
#
|
|
136
|
+
# Cloud tokens are optional. Without them the release still ships and DNS is
|
|
137
|
+
# still published; only the provider-specific parts are skipped.
|
|
138
|
+
deploy:
|
|
139
|
+
if: >-
|
|
140
|
+
github.event_name == 'workflow_dispatch' ||
|
|
141
|
+
(github.event_name == 'push' && github.ref == 'refs/heads/main')
|
|
142
|
+
needs: [lint, typecheck, test]
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
timeout-minutes: 30
|
|
145
|
+
permissions:
|
|
146
|
+
contents: read
|
|
147
|
+
deployments: write
|
|
148
|
+
|
|
149
|
+
steps:
|
|
150
|
+
- name: Checkout Code
|
|
151
|
+
uses: actions/checkout@v4
|
|
152
|
+
|
|
153
|
+
- name: Is deployment configured?
|
|
154
|
+
id: check
|
|
155
|
+
env:
|
|
156
|
+
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
|
|
157
|
+
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
158
|
+
run: |
|
|
159
|
+
if [ -n "$DOTENV_PRIVATE_KEY_PRODUCTION" ] && [ -n "$DEPLOY_SSH_KEY" ]; then
|
|
160
|
+
echo 'configured=true' >> "$GITHUB_OUTPUT"
|
|
161
|
+
else
|
|
162
|
+
echo 'configured=false' >> "$GITHUB_OUTPUT"
|
|
163
|
+
echo '::notice::Deployment is not configured, so this step did nothing. Add DOTENV_PRIVATE_KEY_PRODUCTION and DEPLOY_SSH_KEY to deploy on every push to main.'
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
- name: Setup Pantry
|
|
167
|
+
if: steps.check.outputs.configured == 'true'
|
|
168
|
+
uses: pantry-pm/pantry/packages/action@c1417d98f5613290bc3b1f96d35086b834684659 # v0.10.47 + the socket-hang-up retry fix
|
|
169
|
+
with:
|
|
170
|
+
install: 'false'
|
|
171
|
+
|
|
172
|
+
- name: Use cached node_modules
|
|
173
|
+
if: steps.check.outputs.configured == 'true'
|
|
174
|
+
uses: actions/cache@v4
|
|
175
|
+
with:
|
|
176
|
+
path: node_modules
|
|
177
|
+
key: node-modules-${{ hashFiles('**/bun.lock') }}
|
|
178
|
+
restore-keys: |
|
|
179
|
+
node-modules-
|
|
180
|
+
|
|
181
|
+
- name: Install Dependencies
|
|
182
|
+
if: steps.check.outputs.configured == 'true'
|
|
183
|
+
run: bun install
|
|
184
|
+
|
|
185
|
+
- name: Prepare Deploy SSH Key
|
|
186
|
+
if: steps.check.outputs.configured == 'true'
|
|
187
|
+
env:
|
|
188
|
+
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
189
|
+
run: |
|
|
190
|
+
mkdir -p ~/.ssh
|
|
191
|
+
chmod 700 ~/.ssh
|
|
192
|
+
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
|
193
|
+
chmod 600 ~/.ssh/id_ed25519
|
|
194
|
+
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
|
|
195
|
+
chmod 644 ~/.ssh/id_ed25519.pub
|
|
196
|
+
|
|
197
|
+
- name: Deploy production
|
|
198
|
+
if: steps.check.outputs.configured == 'true'
|
|
199
|
+
run: bun buddy deploy --yes
|
|
200
|
+
env:
|
|
201
|
+
APP_ENV: production
|
|
202
|
+
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
|
|
203
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
204
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
205
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
206
|
+
# macOS resource forks in a tarball built on a developer machine make
|
|
207
|
+
# the extraction on the server noisy and occasionally fatal.
|
|
208
|
+
COPYFILE_DISABLE: '1'
|