refactor(snapshot-admin): migrate to shared helix-admin client#363
refactor(snapshot-admin): migrate to shared helix-admin client#363shsteimer wants to merge 6 commits into
Conversation
- Adds admin.snapshot({org,site}) [GET/update/remove] and admin.sidekick({org,site}) [GET] namespaces to scripts/helix-admin.js
- Rewrites tools/snapshot-admin/utils.js: removes module-level org/site state and setOrgSite(), adds org/site as explicit params, replaces all direct fetch calls with admin.snapshot() + executeAdminRequest (PREFLIGHT_AND_RETRY on reads, RETRY_ON_401 on writes)
- Updates snapshot-admin.js and snapshot-details.js: removes setOrgSite() calls, passes org/site to each utils function, guards against null return (login cancelled)
- Migrates the sidekick config fetch in snapshot-details.js to admin.sidekick().get()
- Rewrites snapshot-utils.js: replaces all admin.hlx.page fetch calls with admin.snapshot()/admin.status() calls; keeps updateScheduledPublish + isRegisteredForSnapshotScheduler as direct fetch (external scheduler service, not admin.hlx.page)
- Rewrites popover.js: removes inline admin.hlx.page functions, imports shared helpers from snapshot-utils.js, uses admin.snapshot().get() directly for listing (sidekick iframe context, no AEM pipeline)
- palette.js unchanged — snapshot-utils.js exports same function signatures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
|
|
- Wrap getCustomReviewHost in try-catch so network errors fall back to default review host instead of aborting the details load - Sequence deleteSnapshotUrls via reduce to prevent concurrent auth modals when multiple paths are deleted in one batch - Add formatError helper to restore friendly 401/403 messages - Document that saveManifest does not return the saved manifest - Comment reviewSnapshot to explain why review state is sent in both query param and body Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the fetch() call in request() so that network-level failures (DNS, timeout, CORS block) are caught and returned as a normalized AdminResponse with ok=false, status=0, and error set to the thrown message. json()/text() reject with the original error. Previously these errors escaped the library as thrown exceptions, requiring every caller to add its own try-catch. Now the uniform ok/status/error contract holds for all failure modes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…og path in handleReviewAction
`null ?? { success: true }` was coercing login-cancelled returns to success,
bypassing callers' null guards and allowing deleteSnapshot/updatePaths to
proceed with partial server state. Fixes catch block log path for review
actions (was always logging /manifest, should be /review for
request/approve/reject).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| export async function deleteSnapshotUrls(org, site, name, paths = ['/*']) { | ||
| const earlyExit = await paths.reduce(async (chain, path) => { | ||
| const prev = await chain; | ||
| if (prev !== undefined) return prev; | ||
| const result = await executeAdminRequest( | ||
| () => admin.snapshot({ org, site }).remove(`${name}${path}`), | ||
| { org, site }, | ||
| ); | ||
| if (!result) return null; | ||
| if (!result.ok) return { error: formatError(result), status: result.status }; | ||
| return undefined; | ||
| }, Promise.resolve(undefined)); | ||
| return earlyExit !== undefined ? earlyExit : { success: true }; | ||
| } |
There was a problem hiding this comment.
SHOULD FIX: The async reduce-with-sentinel pattern is correct but hard to reason about — undefined means "keep going", null means "login cancelled", anything else means "early error return". A for...of loop expresses the same logic without the mental overhead.
| export async function deleteSnapshotUrls(org, site, name, paths = ['/*']) { | |
| const earlyExit = await paths.reduce(async (chain, path) => { | |
| const prev = await chain; | |
| if (prev !== undefined) return prev; | |
| const result = await executeAdminRequest( | |
| () => admin.snapshot({ org, site }).remove(`${name}${path}`), | |
| { org, site }, | |
| ); | |
| if (!result) return null; | |
| if (!result.ok) return { error: formatError(result), status: result.status }; | |
| return undefined; | |
| }, Promise.resolve(undefined)); | |
| return earlyExit !== undefined ? earlyExit : { success: true }; | |
| } | |
| export async function deleteSnapshotUrls(org, site, name, paths = ['/*']) { | |
| for (const path of paths) { | |
| const result = await executeAdminRequest( | |
| () => admin.snapshot({ org, site }).remove(`${name}${path}`), | |
| { org, site }, | |
| ); | |
| if (!result) return null; | |
| if (!result.ok) return { error: formatError(result), status: result.status }; | |
| } | |
| return { success: true }; | |
| } |
| } | ||
|
|
||
| function formatError(result) { | ||
| if (result.error) return result.error; |
There was a problem hiding this comment.
CONSIDER: The check order means x-error header value now wins over the friendly 401/403 fallbacks. In the old code, 401/403 always showed the actionable human copy regardless of x-error. In practice admin.hlx.page does set x-error on auth failures (e.g. [admin] not authenticated), so users who exhaust their login retries will see the technical header value instead of "Please make sure you are logged in."
If the intent is to surface admin's own message when available, that's reasonable — but it's worth confirming this is intentional rather than an accidental inversion.
Code ReviewClean refactor overall — the shift from module-level Issues FoundSHOULD FIX
CONSIDER
VerdictREQUEST CHANGES — one SHOULD FIX (clarity) and two CONSIDERs. No security or correctness issues found. |
…eedback - Merge branch 'main': add psi() namespace to helix-admin alongside snapshot() - Restore 401/403 friendly copy priority in formatError over x-error header - Replace async-reduce-with-sentinel in deleteSnapshotUrls with sequential for loop - Add admin.snapshot(coords) tests covering get/update/remove/url Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
admin.snapshot({org,site})(GET/update/remove) andadmin.sidekick({org,site})(GET-only) namespaces toscripts/helix-admin.jstools/snapshot-admin/utils.js: removes module-level state andsetOrgSite(), addsorg/siteas explicit params to all functions, replaces directfetchcalls withadmin.snapshot()+executeAdminRequest(PREFLIGHT_AND_RETRY on reads, RETRY_ON_401 on writes per auth policy)snapshot-utils.js: replaces alladmin.hlx.pagefetch functions withadmin.snapshot()/admin.status()calls; keepsupdateScheduledPublish+isRegisteredForSnapshotScheduleras direct fetch (external scheduler service, not admin.hlx.page)snapshot-admin.jsandsnapshot-details.js: removessetOrgSite()calls, passesorg/siteto each utils function, adds null guards for login-cancelled returns, migrates the sidekick config fetch toadmin.sidekick().get()popover.js: removes inline duplicated admin.hlx.page functions, imports shared helpers fromsnapshot-utils.js, usesadmin.snapshot().get()directly for listing (sidekick iframe — no AEM pipeline, noexecuteAdminRequest)palette.jsunchanged —snapshot-utils.jspreserves existing function signaturesPreview
https://refactor-snapshot-admin-helix-admin--helix-tools-website--adobe.aem.live/tools/snapshot-admin/index.html
Test plan
popover.html?owner=...&repo=...&referrer=...— snapshot list loads without login modal, add/remove/review actions workpalette.html?owner=...&repo=...&referrer=...— same as popover🤖 Generated with Claude Code