feat: unify smartui-storybook into smartui-cli (built-in storybook command + TUI) - #1
feat: unify smartui-storybook into smartui-cli (built-in storybook command + TUI)#1chaitanyas-maker wants to merge 59 commits into
storybook command + TUI)#1Conversation
Replace long-lived NPM_TOKEN with GitHub Actions OIDC. Adds id-token: write permission and --provenance flag on publish; removes NODE_AUTH_TOKEN env. Trusted publisher must be configured on npmjs.com (LambdaTest org > smartui-cli > Publishing access) before this workflow can succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: switch npm publish to OIDC trusted publishing
sushobhit-lt
left a comment
There was a problem hiding this comment.
Automated review of the storybook-unification PR. Scope: the hand-written TS/.cjs glue (vendored engine reviewed for defects but treated as a faithful relocation). Lint/syntax: the repo has no ESLint/Prettier config and no lint script; tsconfig.json is strict with noUncheckedIndexedAccess. No parse-level syntax errors found in the changed source. Main findings are in the URL-mode capture path (sendDoM in src/storybookVendor/commands/utils/dom.cjs and the URL branch of storybook.cjs), which appears untested — the described live test looks like it exercised DIR/static mode only. Details inline.
| await page.goto(storyInfo.url, { waitUntil: 'networkidle0' }); | ||
| const html = await page.content(); | ||
|
|
||
| dom = new JSDOM(html, { |
There was a problem hiding this comment.
[bug] dom is assigned with no declaration keyword (implicit global) — and so are clone (line 34) and element (line 37, the for…of binding). This is the exact class of defect the PR description says was fixed for res/filename/githubURL ("only crash once bundled into strict mode"), but these three were missed. esbuild bundles the ESM entry (src/index.ts), so this code runs strict → an assignment to an undeclared identifier throws ReferenceError: dom is not defined, breaking the whole URL-mode capture path. Even in sloppy mode it leaks JSDOM instances onto the global object on every story iteration. Declare it:
| dom = new JSDOM(html, { | |
| const dom = new JSDOM(html, { |
| url: storybookUrl, | ||
| resources: 'usable' | ||
| }); | ||
| clone = new JSDOM(html); |
There was a problem hiding this comment.
[bug] Same implicit-global issue — declare clone:
| clone = new JSDOM(html); | |
| const clone = new JSDOM(html); |
| clone = new JSDOM(html); | ||
|
|
||
| // Serialize DOM | ||
| for(element of clone.window.document.querySelectorAll('img')) { |
There was a problem hiding this comment.
[bug] Same implicit-global issue — the loop variable element is undeclared:
| for(element of clone.window.document.querySelectorAll('img')) { | |
| for (const element of clone.window.document.querySelectorAll('img')) { |
|
|
||
| // Convert browsers and resolutions arrays to string | ||
| let resolutions = []; | ||
| storybookConfig.resolutions.forEach(element => { |
There was a problem hiding this comment.
[bug] In URL mode with no -c flag, storybookConfig falls back to defaultSmartUIConfig.storybook, which defines viewports but no resolutions key (see utils/config.cjs). So smartui storybook <url> without a config file throws TypeError: Cannot read properties of undefined (reading 'forEach') right here. (With a config file, validateConfig normalizes viewports→resolutions, which is why DIR-mode testing wouldn't surface it.) Guard with the viewports fallback:
| storybookConfig.resolutions.forEach(element => { | |
| (storybookConfig.resolutions || storybookConfig.viewports || []).forEach(element => { |
| @@ -0,0 +1,105 @@ | |||
| // @ts-nocheck | |||
There was a problem hiding this comment.
[nit] // @ts-nocheck disables all type checking for this new file in an otherwise strict project, so future regressions here (e.g. the options/target handling, the regex grab helper) won't be caught by tsup/tsc. Prefer typing the command callback params (commander infers them) and dropping the blanket suppression, or scope suppressions to the few lines that actually need them.
Two bugs that broke the URL-mode capture path (surfaced by live URL testing, confirmed by @sushobhit-lt review on PR #1): - storybook.ts: merge root global flags via optsWithGlobals() so `--config` actually reaches the engine (local -c/--config collided with the global one), letting validateConfig normalize viewports->resolutions. - dom.cjs: declare `dom`/`clone`/`element` with const — they were implicit globals that throw ReferenceError once bundled into strict mode (same class as the earlier res/filename/githubURL fixes; these three were missed). - storybook.cjs: guard the URL-mode resolutions loop with a viewports fallback so `smartui storybook <url>` with no -c flag doesn't crash on the default config (per review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
|
Thanks @sushobhit-lt — sharp review; all three
You were exactly right that the live test only exercised DIR/static mode — I've since run URL mode end-to-end. After these fixes it completes the full pipeline (discover → puppeteer render → serialize →
|
|
Moved upstream: this is now proposed as a draft PR against |
…, fullPage box synthesis Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… no longer suppresses selectors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elector-group flattening Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…enshot Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(TE-20613): ignoreColors snapshot option — selectors, coordinates, fullPage
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: bump version to 4.1.72
Stage->Prod
launchBrowsers() launched the capture browser with { headless } only, never
applying constants.LAUNCH_ARGS (used by the discovery path at
processSnapshot.ts). navigator.webdriver stayed true, so WAF-protected sites
(e.g. michaelkors.co.uk via Akamai) served an Access Denied page that was
screenshotted and uploaded as a successful build.
- utils.ts: add args: constants.LAUNCH_ARGS to shared launchOptions
- utils.ts: Edge launches inline with [...LAUNCH_ARGS, '--headless=new'];
stop mutating shared launchOptions (leaked --headless=new onto the later
mobile-Android Chromium launch)
- constants.ts: fix truncated CHROME_USER_AGENT (Safari/537.3 -> 537.36)
Firefox/WebKit ignore the Chromium-only flags (verified on Playwright 1.59.1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tag nesting rewrite + expression-safe IIFE wrap Bundle built from smartui-cli-dom-serializer TE-21960 (LambdatestIncPrivate/smartui-cli-dom-serializer#13): - rewrites button-in-button / a-in-a (invalid HTML from JS-built DOMs) to <div data-smartui-original-tag=...> so the serialized string survives re-parsing at render without flattening/reparenting (phantom clicked/open UI) - bundle now deterministically starts with '(' via post-build IIFE wrap (page.evaluate-safe; replaces the rebuild-until-good-roll ritual) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hat blocks OIDC trusted publishing
setup-node with registry-url writes an .npmrc with _authToken=${NODE_AUTH_TOKEN}
and exports the literal placeholder XXXXX-XXXXX-XXXXX-XXXXX when no token env is
set (visible in run 29919621698's step env). PR LambdaTest#525 removed the NPM_TOKEN env
from publish steps, so publishes now authenticate with the placeholder — npm
masks the auth failure as '404 @lambdatest/smartui-cli@4.1.72 is not in this
registry', and the configured token prevents the OIDC exchange from running at
all. Dropping registry-url removes the token config so pnpm publish uses
trusted publishing; pnpm pinned to v10 (OIDC support).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(release): remove placeholder NODE_AUTH_TOKEN blocking OIDC publish (4.1.72 unpublished)
Stage->Prod
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: bump version to 4.1.73
…C publish (4.1.72 unpublished)"
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ase-oidc-auth Revert "fix(release): remove placeholder NODE_AUTH_TOKEN blocking OIDC publish (4.1.72 unpublished)"
…m-oidc-stage Revert "chore: switch npm publish to OIDC trusted publishing"
…essChrome WAF block Headless Chromium leaks 'HeadlessChrome' into the Sec-CH-UA client hint. WAFs like Akamai 403 on it (michaelkors.co.uk served Access Denied only on Chrome; Firefox/WebKit send no client hints and were never blocked). Seed the clean constants.REQUEST_HEADERS Sec-CH-UA on the capture page for Chromium engines only (chrome/edge); user-supplied requestHeaders still override. Verified end-to-end: michaelkors 403 -> 200, real homepage rendered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s them Playwright's Linux WebKit build can't decode AVIF, so sites serving AVIF (e.g. michaelkors.co.uk, 44 avif images) render blank with alt-text on the capture VM; macOS WebKit and Chromium decode AVIF and were unaffected. For WebKit captures only, reject avif/webp on image requests (Accept: image/avif;q=0,image/webp;q=0, image/*,*/*;q=0.8) so the origin content-negotiates down to JPEG/PNG. Uses route.fallback so it composes with the CAPTURE_RENDERING_ERRORS / basicAuth handlers. Verified against the live site: images served as JPEG/PNG, no avif. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…WebKit route /simplify cleanups on the capture-hardening diff: - add isChromiumEngine/isWebkitEngine predicates in utils, replacing inline browserName string checks at the Sec-CH-UA seed and WebKit AVIF route - Edge launch reuses chromiumLaunchOptions base instead of re-listing LAUNCH_ARGS - collapse the WebKit route's duplicated route.fallback into one call No behavior change (allHeaders() kept — request.headers() would drop cookies). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n, scope client hints, harden WebKit Accept - utils.ts: revert launchBrowsers to stage — the Sec-CH-UA override (not launch args) fixes Access Denied; applying LAUNCH_ARGS to chrome/android capture (which never had args) would diff every existing baseline and also hit the double --disable-features bug. Keep only the engine predicates. - screenshot.ts: gate the CAPTURE_RENDERING_ERRORS route's REQUEST_HEADERS on isChromiumEngine so Chromium client hints no longer leak onto WebKit/Firefox. - screenshot.ts: WebKit image route now only rewrites Accept when it actually requests avif/webp (preserves a user's custom Accept) and early-returns non-image requests. - constants.ts: fix malformed sec-ch-ua-mobile '"?0"' -> '?0'. - trim long comments per repo convention. Verified: Sec-CH-UA override with plain headless (navigator.webdriver=true) -> MK 200 real page; WebKit route still forces JPEG/PNG (no avif). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(TE-21958): webscanner capture — fix Chrome Access-Denied (Sec-CH-UA) and WebKit AVIF blank images
Stage->Prod
degrade PW version
… hang the build On rate-limited/WAF-protected sites the target can crash mid-capture (page.goto: Page crashed / setViewportSize: Target crashed). page.close()/context.close() then never resolve on the dead target, so the capture promise never settles, Promise.allSettled never returns and finalizeBuild is unreachable — the build stays 'running' until the job times out (seen as ~30 minute scans). Evidence from an instrumented run: 12 'closing page/context' vs 11 'page/context closed'. Race the teardown against BROWSER_CLOSE_TIMEOUT (15s) and continue if it does not return; closeBrowsers() force-kills the browser process afterwards. Scope: this fixes only the hang, so the build completes quickly. Screenshots may still show Access Denied after the first few captures — that is the customer's WAF rate limiting and is resolved only once the HYE VMs are whitelisted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(TE-23055): bound page/context teardown so a crashed target cannot hang the web scanner build
Stage->Prod
Added moved status to pass build result
…command Vendor the proven @lambdatest/smartui-storybook v1.1.32 engine into the single smartui binary (faithful relocation hitting the same /storybook/* backend), add config:create-storybook, and resolve the colliding-bin problem. One install, one binary. Version 5.0.0-unified.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
…ent, log-symbols) Gradient figlet banner + launch config box + end-of-run summary box (dashboard link, screenshot/approval/change counts) around the storybook command. Summary deferred to process 'beforeExit' so it lands after the engine's polling. Verified with a live baseline build (11 stories -> 33 screenshots) on SmartUI cloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
Two bugs that broke the URL-mode capture path (surfaced by live URL testing, confirmed by @sushobhit-lt review on PR #1): - storybook.ts: merge root global flags via optsWithGlobals() so `--config` actually reaches the engine (local -c/--config collided with the global one), letting validateConfig normalize viewports->resolutions. - dom.cjs: declare `dom`/`clone`/`element` with const — they were implicit globals that throw ReferenceError once bundled into strict mode (same class as the earlier res/filename/githubURL fixes; these three were missed). - storybook.cjs: guard the URL-mode resolutions loop with a viewports fallback so `smartui storybook <url>` with no -c flag doesn't crash on the default config (per review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
…TE-24909) URL mode fetched stories.json only. Storybook 8 dropped that file and serves index.json instead, so the request 404s, discovery reports "Cannot fetch stories" and the run ends with 0 screenshots. DIR mode already read either file. Try index.json first and fall back to stories.json, accepting both payload shapes (entries for v8, stories for v7) and both title fields (title for v8, kind for v7). Verified against a real Storybook 8.6 index (17 stories) served with index.json only and with stories.json only: before the change the first case failed with 404, after it resolves 17 stories from either file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
…po (TE-24912, TE-24910) Two auth-adjacent problems in the vendored engine: PROJECT_NAME (TE-24912). The engine only accepted a pre-issued PROJECT_TOKEN, while the rest of the CLI accepts PROJECT_NAME and auto-creates the project. Resolve PROJECT_NAME through the same /visualui/1.0/token/verify call the core client uses, then continue on the resulting token, so downstream code is unchanged. No git repo (TE-24910). The duplicate-build check keys off branch and commit. Outside a git repo both are empty, the API rejects the call, and the CLI prints "Cannot fetch latest build of the project. Error: Request failed with status code 401", which reads like an auth failure. Skip the check with a message naming the real cause, matching how the core CLI skips git details when it is not a git repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
…nch failures (TE-24911) This branch switched the lockfile to pnpm, and pnpm 10 blocks dependency install scripts by default. puppeteer's postinstall never runs, so its Chromium is never downloaded and URL mode dies on a WS endpoint timeout that says nothing about the missing browser. Declare the packages whose install scripts are required in pnpm.onlyBuiltDependencies, and wrap puppeteer.launch so a failure names the likely cause and the command that fixes it. The lockfile change is the playwright bump that came in from stage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
… refusing (TE-23033) config:create and config:create-storybook both default to .smartui.json, so running the second after the first only produced "SmartUI Storybook config already exists". The schema already allows one file to carry both a web and a storybook block, so write the block into the existing file instead. An existing storybook block is still left alone and reported. Also give the storybook browsers uniqueItems rule its own message, which the enum catch-all was swallowing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
850ee15 to
6f67b26
Compare
|
Superseded by LambdaTest#527, which now carries the same branch rebased onto current |
Unify
@lambdatest/smartui-storybookinto@lambdatest/smartui-cliWhy
The two packages both declare
bin.smartui, so they collide onPATH— installing@lambdatest/smartui-storybooksilently clobbers@lambdatest/smartui-cli(and vice-versa). Users need two installs for one tool. This PR makes one binary own the name and ships Storybook as a first-class command.Approach — faithful relocation (not a rewrite)
Rather than re-plumb Storybook onto the CLI's
createBuild/finalizeBuildlifecycle (which depends on an unverified backend question — see Open Questions), this vendors the proven, shipping@lambdatest/smartui-storybookv1.1.32 engine verbatim into the CLI undersrc/storybookVendor/, hitting the same/storybook/*endpoints it already uses in production. Behaviour is byte-identical to the standalone tool; only the collision and the double-install are removed.What changed
smartui storybook <url-or-dir>— URL mode (live server) and DIR mode (static build), identical grammar to the old package.smartui config:create-storybook+ astorybookblock added to the ajvConfigSchema(andanyOf, since top-level isadditionalProperties:false).src/storybookVendor/**(12.cjsmodules) — the unique Storybook logic (story discovery, DOM/CSSOM serialize, filtering, zip+render) relocated and bundled by tsup.src/lib/tui.cjs— gradient figlet banner, boxed launch summary, and an end-of-run results box (dashboard link + screenshot/approval/change counts). Degrades gracefully with no TTY (CI-safe).puppeteer,jsdom,archiver,form-data,cli-table3, proxy agents (engine);figlet,gradient-string,boxen,log-symbols,ora(TUI).pnpm-lock.yamlupdated.Notable engineering notes
.js→.cjsso esbuild bundles them as CommonJS under the CLI's"type":"module"(otherwisemodule.exportsis silently dropped).res,filename,githubURL) that only crash once bundled into strict mode.process.beforeExitbecause the engine doesn't fully await its polling — this guarantees it prints last.Testing (live)
Built a 5-component / 17-story demo Storybook and ran it against SmartUI:
smartui --help,storybook --help,config:create-storybook, and a cleannpm i -g <tarball>global install all verified.Open questions for eng
createBuild/finalizeBuild) needs confirmation that the CLI'svisualui/1.0client can reach the/storybook/*routes. This PR sidesteps it by vendoring; that refactor is the documented follow-up.5.0.0(MAJOR) + a deprecation shim on@lambdatest/smartui-storybook. Not done here.Not included (deliberately)
Live publish, official version bump, deprecation shim, docs updates.
🤖 Generated with Claude Code