@wix/agent-skills 1.33.0 → 1.34.0
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/README.md +23 -0
- package/package.json +2 -2
- package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
- package/skills/wix-export/SKILL.md +83 -0
- package/skills/wix-harmony-editor/.publish-manifest.json +8 -0
- package/skills/wix-harmony-editor/COPY-SITE.md +494 -0
- package/skills/wix-harmony-editor/SKILL.md +317 -0
- package/skills/wix-harmony-editor/scripts/copy/build-description.mjs +249 -0
- package/skills/wix-harmony-editor/scripts/copy/check-description.mjs +225 -0
- package/skills/wix-harmony-editor/scripts/copy/check-mirror.mjs +89 -0
- package/skills/wix-harmony-editor/scripts/copy/compare-preview.mjs +76 -0
- package/skills/wix-harmony-editor/scripts/copy/deploy-description.mjs +212 -0
- package/skills/wix-harmony-editor/scripts/copy/desc-hash.mjs +44 -0
- package/skills/wix-harmony-editor/scripts/copy/editor-run.mjs +45 -0
- package/skills/wix-harmony-editor/scripts/copy/export-page.mjs +53 -0
- package/skills/wix-harmony-editor/scripts/copy/find-interactions.mjs +76 -0
- package/skills/wix-harmony-editor/scripts/copy/hide-scrollbars.mjs +16 -0
- package/skills/wix-harmony-editor/scripts/copy/import-media.mjs +150 -0
- package/skills/wix-harmony-editor/scripts/copy/link-url.mjs +27 -0
- package/skills/wix-harmony-editor/scripts/copy/lint-api.mjs +56 -0
- package/skills/wix-harmony-editor/scripts/copy/measure-source.mjs +211 -0
- package/skills/wix-harmony-editor/scripts/copy/merge-mirrors.mjs +29 -0
- package/skills/wix-harmony-editor/scripts/copy/mirror-site.mjs +92 -0
- package/skills/wix-harmony-editor/scripts/copy/mobile-stage.mjs +56 -0
- package/skills/wix-harmony-editor/scripts/copy/preview-widths.mjs +73 -0
- package/skills/wix-harmony-editor/scripts/copy/render-plate.mjs +67 -0
- package/skills/wix-harmony-editor/scripts/copy/serve-mirror.mjs +102 -0
- package/skills/wix-harmony-editor/scripts/copy/snapshot-capabilities.mjs +40 -0
- package/skills/wix-harmony-editor/scripts/copy/split-sections.mjs +191 -0
- package/skills/wix-harmony-editor/scripts/copy/upload-media-manager.mjs +61 -0
- package/skills/wix-harmony-editor/scripts/launch.sh +359 -0
- package/skills/wix-headless/SKILL.md +7 -2
- package/skills/wix-headless/references/CAPABILITIES.md +6 -1
- package/skills/wix-headless/references/DISCOVERY.md +2 -1
- package/skills/wix-headless/references/SDK_HANDOFF.md +6 -1
- package/skills/wix-headless/references/SEED.md +3 -2
- package/skills/wix-headless/references/SETUP.md +1 -0
- package/skills/wix-headless/references/inline-recipes/experience-donations.md +102 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-donations.md +146 -0
- package/skills/wix-headless/references/inline-recipes/setup-donations.md +15 -0
- package/skills/wix-headless/references/managed/CREATE.md +1 -1
- package/skills/wix-headless-kit/SKILL.md +110 -21
- package/skills/wix-headless-kit/guides/api-run.md +15 -13
- package/skills/wix-headless-kit/guides/existing-site.md +31 -0
- package/skills/wix-headless-kit/guides/migration.md +49 -17
- package/skills/wix-headless-kit/install/agents-md.mjs +1 -1
- package/skills/wix-headless-kit/install/context.mjs +61 -6
- package/skills/wix-headless-kit/install/pins.json +1 -1
- package/skills/wix-headless-kit/install/setup.mjs +107 -12
- package/skills/wix-headless-replatform/.publish-manifest.json +8 -0
- package/skills/wix-headless-replatform/SKILL.md +116 -0
- package/skills/wix-headless-replatform/agents/openai.yaml +6 -0
- package/skills/wix-headless-replatform/references/control-and-visual-fidelity-contract.md +43 -0
- package/skills/wix-headless-replatform/references/extraction-schema.md +187 -0
- package/skills/wix-headless-replatform/references/interaction-runtime-contract.md +137 -0
- package/skills/wix-headless-replatform/references/layout-blueprint-contract.md +41 -0
- package/skills/wix-headless-replatform/references/output-contract.md +93 -0
- package/skills/wix-headless-replatform/references/post-build-gap-contract.md +125 -0
- package/skills/wix-headless-replatform/references/qa-checklist.md +83 -0
- package/skills/wix-headless-replatform/references/safety.md +19 -0
- package/skills/wix-headless-replatform/references/ui-normalization-contract.md +70 -0
- package/skills/wix-headless-replatform/references/workflow.md +157 -0
- package/skills/wix-headless-replatform/registry/README.md +20 -0
- package/skills/wix-headless-replatform/registry/approvals/button/shadcn-default-ac60ef5.json +28 -0
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/button.tsx +54 -0
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/capabilities.json +60 -0
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/contract.md +48 -0
- package/skills/wix-headless-replatform/registry/licenses/shadcn-ui-ac60ef5-MIT.txt +21 -0
- package/skills/wix-headless-replatform/registry/registry.json +36 -0
- package/skills/wix-headless-replatform/resources/rp-facelift/SKILL.md +32 -0
- package/skills/wix-headless-replatform/resources/rp-project-context/SKILL.md +62 -0
- package/skills/wix-headless-replatform/resources/rp-qa-gap-loop/SKILL.md +64 -0
- package/skills/wix-headless-replatform/resources/rp-source-evidence/SKILL.md +42 -0
- package/skills/wix-headless-replatform/resources/rp-ui-implementation/SKILL.md +73 -0
- package/skills/wix-headless-replatform/schemas/build-plan.schema.json +10 -0
- package/skills/wix-headless-replatform/schemas/candidate.schema.json +19 -0
- package/skills/wix-headless-replatform/schemas/common.schema.json +33 -0
- package/skills/wix-headless-replatform/schemas/decision-patch.schema.json +10 -0
- package/skills/wix-headless-replatform/schemas/decision-request.schema.json +10 -0
- package/skills/wix-headless-replatform/schemas/extraction-gap.schema.json +28 -0
- package/skills/wix-headless-replatform/schemas/extraction-manifest.schema.json +34 -0
- package/skills/wix-headless-replatform/schemas/final-report.schema.json +10 -0
- package/skills/wix-headless-replatform/schemas/foundation.schema.json +11 -0
- package/skills/wix-headless-replatform/schemas/metadata.schema.json +11 -0
- package/skills/wix-headless-replatform/schemas/observation-packet.schema.json +17 -0
- package/skills/wix-headless-replatform/schemas/page-capture.schema.json +11 -0
- package/skills/wix-headless-replatform/schemas/page-resolution.schema.json +33 -0
- package/skills/wix-headless-replatform/schemas/shared-chrome.schema.json +11 -0
- package/skills/wix-headless-replatform/schemas/unit.schema.json +40 -0
- package/skills/wix-headless-replatform/scripts/assemble-extraction.mjs +844 -0
- package/skills/wix-headless-replatform/scripts/benchmark-extraction.mjs +163 -0
- package/skills/wix-headless-replatform/scripts/browser-extraction-preflight.mjs +42 -0
- package/skills/wix-headless-replatform/scripts/build-extracted-homepage.mjs +228 -0
- package/skills/wix-headless-replatform/scripts/clear-wix-starter.mjs +87 -0
- package/skills/wix-headless-replatform/scripts/create-wix-project.mjs +505 -0
- package/skills/wix-headless-replatform/scripts/discover-urls.mjs +142 -0
- package/skills/wix-headless-replatform/scripts/extract-assets.mjs +140 -0
- package/skills/wix-headless-replatform/scripts/extract-design-system.mjs +637 -0
- package/skills/wix-headless-replatform/scripts/extract-interactions.mjs +1903 -0
- package/skills/wix-headless-replatform/scripts/extract-page.mjs +2461 -0
- package/skills/wix-headless-replatform/scripts/extract-seo.mjs +25 -0
- package/skills/wix-headless-replatform/scripts/extract-tokens.mjs +63 -0
- package/skills/wix-headless-replatform/scripts/finalize-extraction-report.mjs +126 -0
- package/skills/wix-headless-replatform/scripts/finalize-gap-review.mjs +180 -0
- package/skills/wix-headless-replatform/scripts/finalize-gap-review.test.mjs +98 -0
- package/skills/wix-headless-replatform/scripts/finalize-migration-website.mjs +103 -0
- package/skills/wix-headless-replatform/scripts/generate-control-state-contract.mjs +176 -0
- package/skills/wix-headless-replatform/scripts/generate-design-md.mjs +261 -0
- package/skills/wix-headless-replatform/scripts/generate-extraction-validators.mjs +25 -0
- package/skills/wix-headless-replatform/scripts/generate-invoice-download-backend.mjs +342 -0
- package/skills/wix-headless-replatform/scripts/generate-layout-blueprint.mjs +140 -0
- package/skills/wix-headless-replatform/scripts/generate-repeater-cms.mjs +22 -0
- package/skills/wix-headless-replatform/scripts/generate-route-plan.mjs +143 -0
- package/skills/wix-headless-replatform/scripts/generate-scene-contract.mjs +288 -0
- package/skills/wix-headless-replatform/scripts/generate-ui-normalization.mjs +266 -0
- package/skills/wix-headless-replatform/scripts/generate-visual-assets.mjs +111 -0
- package/skills/wix-headless-replatform/scripts/lib/browser-tooling.mjs +570 -0
- package/skills/wix-headless-replatform/scripts/lib/common.mjs +329 -0
- package/skills/wix-headless-replatform/scripts/lib/component-capabilities.mjs +121 -0
- package/skills/wix-headless-replatform/scripts/lib/component-registry.mjs +282 -0
- package/skills/wix-headless-replatform/scripts/lib/content-boundary.mjs +61 -0
- package/skills/wix-headless-replatform/scripts/lib/extraction-contract.mjs +374 -0
- package/skills/wix-headless-replatform/scripts/lib/extraction-detectors.mjs +243 -0
- package/skills/wix-headless-replatform/scripts/lib/extraction-validators.generated.mjs +1139 -0
- package/skills/wix-headless-replatform/scripts/lib/font-contract.mjs +326 -0
- package/skills/wix-headless-replatform/scripts/lib/frontend-automation-state.mjs +184 -0
- package/skills/wix-headless-replatform/scripts/lib/gap-analysis.mjs +396 -0
- package/skills/wix-headless-replatform/scripts/lib/html-extract.mjs +168 -0
- package/skills/wix-headless-replatform/scripts/lib/interaction-bootstrap.mjs +10 -0
- package/skills/wix-headless-replatform/scripts/lib/interaction-runtime.mjs +194 -0
- package/skills/wix-headless-replatform/scripts/lib/interaction-timeline.mjs +348 -0
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/classifiers.mjs +141 -0
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/geometry.mjs +43 -0
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/vocabulary.mjs +18 -0
- package/skills/wix-headless-replatform/scripts/lib/ui-normalization.css +64 -0
- package/skills/wix-headless-replatform/scripts/optimize-svg.mjs +31 -0
- package/skills/wix-headless-replatform/scripts/post-build-gap.mjs +358 -0
- package/skills/wix-headless-replatform/scripts/provision-repeater-cms.mjs +25 -0
- package/skills/wix-headless-replatform/scripts/record-gap-decision.mjs +39 -0
- package/skills/wix-headless-replatform/scripts/replay-extraction.mjs +107 -0
- package/skills/wix-headless-replatform/scripts/resolve-home-page.mjs +96 -0
- package/skills/wix-headless-replatform/scripts/site-clone.mjs +658 -0
- package/skills/wix-headless-replatform/scripts/validate-font-contract.mjs +29 -0
- package/skills/wix-headless-replatform/scripts/verify-interactions.mjs +669 -0
- package/skills/wix-headless-replatform/scripts/visual-qa.mjs +370 -0
- package/skills/wix-headless-templates/donations/INSTRUCTIONS.md +37 -24
- package/skills/wix-headless-templates/donations/app/components/donations/DonateForm.tsx +49 -23
- package/skills/wix-headless-templates/donations/app/components/donations/DonationReceipt.tsx +6 -5
- package/skills/wix-headless-templates/donations/app/components/donations/GoalProgress.tsx +5 -5
- package/skills/wix-headless-templates/donations/app/wix/donations/campaigns.ts +4 -4
- package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +7 -3
- package/skills/wix-headless-templates/donations/app/wix/donations/donation-store.ts +2 -2
- package/skills/wix-headless-templates/donations/app/wix/donations/donations-core.ts +12 -9
- package/skills/wix-headless-templates/donations/project/src/components/donations/DonateForm.tsx +49 -23
- package/skills/wix-headless-templates/donations/project/src/components/donations/DonationReceipt.tsx +6 -5
- package/skills/wix-headless-templates/donations/project/src/components/donations/GoalProgress.tsx +5 -5
- package/skills/wix-headless-templates/donations/project/src/wix/donations/campaigns.ts +4 -4
- package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +7 -3
- package/skills/wix-headless-templates/donations/project/src/wix/donations/donation-store.ts +2 -2
- package/skills/wix-headless-templates/donations/project/src/wix/donations/donations-core.ts +12 -9
- package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
- package/skills/wix-headless-templates/donations/rest/donate.ts +7 -3
- package/skills/wix-headless-templates/donations/seed/SEED.md +12 -7
- package/skills/wix-headless-templates/donations/tests/donation-contract.test.mjs +71 -0
- package/skills/wix-headless-templates/donations/tests/donation-form.test.cjs +137 -0
- package/skills/wix-headless-templates/restaurants/INSTRUCTIONS.md +28 -14
- package/skills/wix-headless-templates/restaurants/app/components/restaurants/OrderCartDrawer.tsx +87 -5
- package/skills/wix-headless-templates/restaurants/app/hooks/restaurants/useOrderCart.ts +9 -1
- package/skills/wix-headless-templates/restaurants/app/wix/restaurants/order-store.ts +33 -4
- package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering-core.ts +100 -5
- package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering.ts +101 -15
- package/skills/wix-headless-templates/restaurants/app/wix/restaurants/types.ts +35 -1
- package/skills/wix-headless-templates/restaurants/project/src/components/restaurants/OrderCartDrawer.tsx +87 -5
- package/skills/wix-headless-templates/restaurants/project/src/hooks/restaurants/useOrderCart.ts +9 -1
- package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/order-store.ts +33 -4
- package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering-core.ts +100 -5
- package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering.ts +101 -15
- package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/types.ts +35 -1
- package/skills/wix-headless-templates/restaurants/rest/ordering.ts +91 -14
- package/skills/wix-import/SKILL.md +90 -0
- package/skills/wix-manage/SKILL.md +38 -10
- package/skills/wix-manage/references/automations/actions.md +11 -11
- package/skills/wix-manage/references/automations/activation-status.md +6 -9
- package/skills/wix-manage/references/automations/api-catalog.md +48 -32
- package/skills/wix-manage/references/automations/automation-model.md +97 -22
- package/skills/wix-manage/references/automations/bracket-expressions.md +7 -7
- package/skills/wix-manage/references/automations/build-and-manage-automations.md +140 -309
- package/skills/wix-manage/references/automations/conditions.md +187 -0
- package/skills/wix-manage/references/automations/schemas-and-scheduling.md +191 -0
- package/skills/wix-manage/references/automations/special-actions.md +210 -0
- package/skills/wix-manage/references/automations/triggers.md +47 -10
- package/skills/wix-manage/references/automations/validation-and-verification.md +320 -0
- package/skills/wix-manage/references/custom-apps/use-app-tools.md +124 -0
- package/skills/wix-manage/references/dashboard-navigation/dashboard-navigation.md +2 -1
- package/skills/wix-manage/references/dashboard-navigation/routes.json +249 -0
- package/skills/wix-manage/references/donations/create-and-update-donation-campaigns.md +139 -0
- package/skills/wix-manage/references/events/create-wix-event.md +136 -73
- package/skills/wix-manage/references/events/events-dashboard-navigation.md +81 -0
- package/skills/wix-manage/references/events/manage-event-guests.md +366 -0
- package/skills/wix-manage/references/events/manage-event-tickets.md +293 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +313 -55
- package/skills/wix-manage/references/sites/manage-oauth-apps.md +1 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +127 -8
- package/skills/wix-manage/references/stores/update-product-with-options.md +58 -2
- package/skills/wix-replatform/.publish-manifest.json +5 -5
- package/skills/wix-replatform/CLI.md +162 -0
- package/skills/wix-replatform/README.md +52 -0
- package/skills/wix-replatform/SKILL.md +229 -684
- package/skills/wix-replatform/console/account.css +1 -0
- package/skills/wix-replatform/console/app.css +1 -0
- package/skills/wix-replatform/console/app.js +117 -0
- package/skills/wix-replatform/console/index.html +4 -0
- package/skills/wix-replatform/console/json.css +11 -0
- package/skills/wix-replatform/lib/agent-adapters/index.js +71 -0
- package/skills/wix-replatform/lib/app-page-routing.js +75 -0
- package/skills/wix-replatform/lib/artifact-freshness.js +11 -1
- package/skills/wix-replatform/lib/blocked-data-requests.js +427 -0
- package/skills/wix-replatform/lib/census-reconciliation.js +426 -0
- package/skills/wix-replatform/lib/completion-report.js +1393 -13
- package/skills/wix-replatform/lib/config-env.js +85 -11
- package/skills/wix-replatform/lib/confirmed-pictures.js +212 -0
- package/skills/wix-replatform/lib/dependency-readiness.js +423 -0
- package/skills/wix-replatform/lib/dependency-recovery.js +21 -0
- package/skills/wix-replatform/lib/destination-choice.js +163 -0
- package/skills/wix-replatform/lib/destination-handoff.js +62 -0
- package/skills/wix-replatform/lib/destination-http.js +81 -0
- package/skills/wix-replatform/lib/destination-route.js +98 -0
- package/skills/wix-replatform/lib/destination.js +919 -0
- package/skills/wix-replatform/lib/discovered-census.js +231 -0
- package/skills/wix-replatform/lib/discovered-set-status.js +255 -0
- package/skills/wix-replatform/lib/document-url-guard.js +203 -0
- package/skills/wix-replatform/lib/entity-qa-attestation.js +339 -0
- package/skills/wix-replatform/lib/entity-verification.js +18 -0
- package/skills/wix-replatform/lib/execution-capability.js +120 -0
- package/skills/wix-replatform/lib/execution-log.js +170 -0
- package/skills/wix-replatform/lib/execution-manifest.js +20 -2
- package/skills/wix-replatform/lib/execution-state.js +22 -0
- package/skills/wix-replatform/lib/expectations-ledger.js +96 -0
- package/skills/wix-replatform/lib/generated-plugin-handoff.js +140 -0
- package/skills/wix-replatform/lib/import-readiness-contract.js +23 -0
- package/skills/wix-replatform/lib/import-recovery.js +800 -6
- package/skills/wix-replatform/lib/imported-redirect-targets.js +608 -0
- package/skills/wix-replatform/lib/internal-link-reconciliation.js +166 -0
- package/skills/wix-replatform/lib/link-audit.js +173 -0
- package/skills/wix-replatform/lib/link-census.js +194 -0
- package/skills/wix-replatform/lib/link-plan.js +148 -0
- package/skills/wix-replatform/lib/link-verification.js +274 -0
- package/skills/wix-replatform/lib/link-write-journal.js +42 -0
- package/skills/wix-replatform/lib/local-state.js +869 -30
- package/skills/wix-replatform/lib/member-completion.js +658 -0
- package/skills/wix-replatform/lib/member-crosswalk.js +69 -0
- package/skills/wix-replatform/lib/member-outcome-registry.js +265 -0
- package/skills/wix-replatform/lib/migration-completion.js +311 -0
- package/skills/wix-replatform/lib/operator-identity.js +47 -0
- package/skills/wix-replatform/lib/orchestration-decisions.js +248 -0
- package/skills/wix-replatform/lib/orchestration-preflight.js +401 -19
- package/skills/wix-replatform/lib/orchestration-router.js +608 -32
- package/skills/wix-replatform/lib/orchestration-state.js +188 -12
- package/skills/wix-replatform/lib/order-reference-readiness.js +30 -0
- package/skills/wix-replatform/lib/payments-outcome-registry.js +200 -0
- package/skills/wix-replatform/lib/plugin-cms-fallback.js +618 -0
- package/skills/wix-replatform/lib/preservation-approval-stamp.js +54 -0
- package/skills/wix-replatform/lib/product-media-deferral.js +96 -0
- package/skills/wix-replatform/lib/progress-log.js +19 -3
- package/skills/wix-replatform/lib/read-back-approval.js +184 -0
- package/skills/wix-replatform/lib/read-back-comparison.js +136 -0
- package/skills/wix-replatform/lib/read-back-contract.js +243 -0
- package/skills/wix-replatform/lib/read-back-extract.js +118 -0
- package/skills/wix-replatform/lib/read-back-gate.js +734 -0
- package/skills/wix-replatform/lib/read-back-reads.js +94 -0
- package/skills/wix-replatform/lib/read-back-state.js +107 -0
- package/skills/wix-replatform/lib/read-back-summary.js +81 -0
- package/skills/wix-replatform/lib/rerun-conflicts.js +334 -0
- package/skills/wix-replatform/lib/run-controller.js +401 -0
- package/skills/wix-replatform/lib/seo-outcome-registry.js +45 -0
- package/skills/wix-replatform/lib/setup-verification.js +148 -0
- package/skills/wix-replatform/lib/source-access.js +231 -0
- package/skills/wix-replatform/lib/source-coverage.js +1130 -0
- package/skills/wix-replatform/lib/source-secrets.js +277 -0
- package/skills/wix-replatform/lib/structured-link-patch.js +59 -0
- package/skills/wix-replatform/lib/task-state.js +110 -0
- package/skills/wix-replatform/lib/url-preservation-report.js +319 -0
- package/skills/wix-replatform/lib/url-preservation-state.js +349 -1
- package/skills/wix-replatform/lib/url-redirect-plan.js +728 -0
- package/skills/wix-replatform/lib/url-verification.js +315 -0
- package/skills/wix-replatform/lib/user-action-effects.js +42 -0
- package/skills/wix-replatform/lib/user-actions.js +264 -0
- package/skills/wix-replatform/lib/website-handoff.js +1220 -0
- package/skills/wix-replatform/lib/wix-client-callers.js +54 -0
- package/skills/wix-replatform/lib/wix-dashboard-url.js +35 -0
- package/skills/wix-replatform/lib/write-verification-state.js +92 -0
- package/skills/wix-replatform/lib/write-verification.js +274 -0
- package/skills/wix-replatform/resources/rp-destination/SKILL.md +137 -0
- package/skills/wix-replatform/resources/rp-discovery/SKILL.md +189 -16
- package/skills/wix-replatform/resources/rp-discovery/scripts/promote-source-coverage.js +47 -0
- package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +869 -29
- package/skills/wix-replatform/resources/rp-execute-import/scripts/shipping-estimate-audit.js +70 -0
- package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +195 -29
- package/skills/wix-replatform/resources/rp-execute-setup/scripts/execute-setup.js +442 -0
- package/skills/wix-replatform/resources/rp-execution-policy/SKILL.md +112 -0
- package/skills/wix-replatform/resources/rp-execution-policy/lib/coupon-currency-plan-report.js +143 -0
- package/skills/wix-replatform/resources/rp-execution-policy/lib/payments-report.js +244 -0
- package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +2082 -38
- package/skills/wix-replatform/resources/rp-mapper/SKILL.md +528 -11
- package/skills/wix-replatform/resources/rp-mapper/lib/structure-bridge-planning.js +303 -0
- package/skills/wix-replatform/resources/rp-mapper/scripts/coverage-reconcile.js +88 -0
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +6 -5
- package/skills/wix-replatform/resources/rp-preflight/SKILL.md +40 -0
- package/skills/wix-replatform/resources/rp-project-intake/SKILL.md +55 -0
- package/skills/wix-replatform/resources/rp-quick-runtime/SKILL.md +60 -0
- package/skills/wix-replatform/resources/rp-quick-runtime/lib/import-progress.js +89 -0
- package/skills/wix-replatform/resources/rp-quick-runtime/lib/limit-recovery.js +36 -0
- package/skills/wix-replatform/resources/rp-quick-shopify/SKILL.md +123 -0
- package/skills/wix-replatform/resources/rp-quick-shopify/quick-mode.json +124 -0
- package/skills/wix-replatform/resources/rp-quick-shopify/scripts/quick-mode.js +522 -0
- package/skills/wix-replatform/resources/rp-quick-woocommerce/SKILL.md +56 -0
- package/skills/wix-replatform/resources/rp-quick-woocommerce/quick-mode.json +91 -0
- package/skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js +255 -0
- package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +80 -10
- package/skills/wix-replatform/resources/rp-setup-discovery/scripts/quick-setup-plan.js +44 -0
- package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +3 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
- package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +21 -1
- package/skills/wix-replatform/resources/rp-source-inputs/SKILL.md +66 -0
- package/skills/wix-replatform/resources/rp-source-sfcc/SKILL.md +153 -0
- package/skills/wix-replatform/resources/rp-source-shopify/SKILL.md +394 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/discovery-reads.js +200 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/import-findings.js +34 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk-sqlite-worker.js +44 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk.js +1470 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-cli.js +446 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-gift-cards.js +474 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-protected-fields.js +879 -0
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-session.js +426 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/articles.graphql +24 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/blogs.graphql +18 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/collections.graphql +40 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/customers.graphql +76 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/delivery-profiles.graphql +51 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/discounts.graphql +35 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/files.graphql +20 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/gift-cards.graphql +18 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/inventory.graphql +31 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/locations.graphql +21 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/menus.graphql +21 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/metaobjects.graphql +30 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-core.graphql +150 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-fulfillments.graphql +36 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-refunds.graphql +48 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/pages.graphql +20 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-address.graphql +8 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-email.graphql +6 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-name.graphql +8 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-phone.graphql +8 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-address.graphql +12 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-email.graphql +6 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-name.graphql +13 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-phone.graphql +13 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/products.graphql +73 -0
- package/skills/wix-replatform/resources/rp-source-shopify/queries/url-redirects.graphql +13 -0
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-discovery.js +693 -0
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-extract.js +283 -0
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-gift-card-remedy.js +191 -0
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-session-check.js +115 -0
- package/skills/wix-replatform/resources/rp-source-shopify/source-schema.example.json +201 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/PLUGIN-READ-PLANS.md +146 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +736 -22
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/blocked-data-handlers.js +73 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.js +115 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.test-fixture.js +91 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/coupon-discovery.js +440 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/currency-gate.js +125 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.js +252 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.json +183 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-summary.mjs +333 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/invoice-discovery.js +426 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-disposition.js +400 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-entity-read-plan.js +633 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-knowledge.js +1127 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.js +194 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.json +100 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/sampled-ids-batch.js +157 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/secret-value-guard.js +234 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/store-currency.js +101 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/structure-bridge-reader.js +755 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.js +1074 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.test-fixture.js +149 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/woo-inventory.js +25 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +204 -9
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-image-url.js +186 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-plugin-detect.js +1422 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-redirect-sources.js +343 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +262 -26
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/yoast-product-seo-evidence.js +272 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/README.md +373 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/advanced-custom-fields.json +101 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/back-in-stock-notifier-for-woocommerce.json +81 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/capabilities-pending-decision.json +128 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/cms-fallback-eligible.json +18 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/contact-form-7.json +104 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/facebook-for-woocommerce.json +44 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/fingerprint-aliases.json +49 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/free-gifts-for-woocommerce.json +94 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/index.json +863 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mailpoet.json +52 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/membership-for-woocommerce.json +161 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json +180 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/no-migration-needed.json +286 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pricing-deals-for-woocommerce.json +79 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/product-extras-for-woocommerce.json +120 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pw-woocommerce-gift-cards.json +128 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/requires-development.json +88 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seo-by-rank-math.json +80 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seriously-simple-podcasting.json +46 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/strong-testimonials.json +55 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/subscriptions-for-woocommerce.json +99 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/tablepress.json +55 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/the-events-calendar.json +160 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/ti-woocommerce-wishlist.json +81 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-checkout-field-editor-pro.json +60 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-discount-rules.json +74 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-product-bundle.json +75 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-gift-cards.json +60 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-other-payment-gateway.json +44 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-pdf-invoices-packing-slips.json +57 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-product-addon.json +141 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-progressive-discounts.json +54 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wordpress-seo.json +158 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpc-composite-products.json +97 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpforms-lite.json +86 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/capture-store-settings.js +90 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge-validate.js +51 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge.js +103 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +1668 -33
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-plugin-inventory.js +273 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/secret-value-patterns.v1.json +11 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/sub-parts.json +13 -0
- package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +446 -32
- package/skills/wix-replatform/resources/rp-target-wix/app-page-routing-capabilities.json +36 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +38 -2
- package/skills/wix-replatform/resources/rp-target-wix/domains/SERVICE-PLUGINS.md +322 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/docs-survey.json +70 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +19 -9
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +42 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +54 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +66 -10
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +38 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/docs-survey.json +332 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/domain.json +75 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/booking.json +181 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service-add-on.json +130 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service.json +180 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/docs-survey.json +161 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +15 -4
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +39 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +47 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +7 -1
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/docs-survey.json +204 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +17 -4
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +159 -15
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +80 -12
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +48 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/docs-survey.json +306 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +30 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/delivery-profile.json +171 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/discount-rule.json +182 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-applied-discount.json +129 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-invoice-document.json +192 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item-options.json +123 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +60 -12
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-merchant-note.json +112 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-payment.json +191 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +140 -13
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +383 -18
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/pickup-location.json +122 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/shipping-option.json +98 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/wishlist-item.json +86 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/docs-survey.json +128 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/domain.json +38 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/category.json +73 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/event.json +107 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/registration-form.json +89 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json +118 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/ticket-definition.json +93 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/docs-survey.json +86 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/domain.json +56 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form-submission.json +121 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form.json +134 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/docs-survey.json +41 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/domain.json +50 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card-product.json +87 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card.json +130 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +914 -24
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/docs-survey.json +41 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +14 -4
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +45 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +41 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/docs-survey.json +119 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +13 -3
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +41 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/custom-field.json +82 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member-about.json +77 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +165 -13
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/docs-survey.json +41 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/domain.json +46 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan-order.json +137 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan.json +133 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/docs-survey.json +7 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/domain.json +36 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/amazon-shop-connect.json +71 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/ebay-shop-connect.json +67 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/google-youtube-connect.json +72 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/meta-catalog-connect.json +74 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/pinterest-catalog-connect.json +69 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/tiktok-catalog-connect.json +61 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +28 -1
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/docs-survey.json +109 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/domain.json +73 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/item-seo-tags.json +148 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/seo-pattern.json +91 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/site-seo-tags.json +79 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/docs-survey.json +32 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/domain.json +49 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/entities/business-contact.json +103 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/docs-survey.json +203 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +27 -10
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/back-in-stock-request.json +119 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/brand.json +111 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +52 -8
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +214 -10
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +93 -13
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-customization.json +111 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +52 -10
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +145 -14
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/promotion.json +84 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/review.json +178 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/subscription-product.json +87 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/docs-survey.json +106 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/domain.json +64 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/manual-tax-mapping.json +119 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-group.json +94 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-region.json +113 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-settings.json +65 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-author-attribution.js +165 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-client.js +60 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-duplicates.js +201 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-ledger.js +306 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-report.js +229 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-stage.js +55 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build.js +715 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-media.js +567 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/bulk-results.js +73 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/contact-extended-fields.js +194 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/contact-import.js +133 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +13 -4
- package/skills/wix-replatform/resources/rp-target-wix/lib/coupon-redemption-drift.js +491 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/customer-member.js +104 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/data-extension-schema.js +673 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +453 -2
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-balance-reconciliation.js +119 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-build.js +566 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-redemption-build.js +249 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-applied-discount-build.js +63 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-buyer.js +187 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-channel-build.js +64 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-history.js +1027 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-invoice-contract.js +90 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-payment-mapping.js +420 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/pricing-plan-definition.js +177 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/product-import.js +151 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/product-seo.js +723 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/seo-redirects.js +353 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-build.js +1437 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-option-stage.js +354 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/store-config-build.js +134 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-category-attach.js +221 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-inventory.js +248 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +86 -3
- package/skills/wix-replatform/resources/rp-target-wix/lib/tax-build.js +231 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/url-mapper-transport.js +28 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/value-utils.js +14 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/variant-correlation.js +34 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +166 -15
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +19 -2
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +4079 -257
- package/skills/wix-replatform/resources/rp-target-wix/lib/write-verification.js +3 -0
- package/skills/wix-replatform/resources/rp-target-wix/scripts/docs-survey-sync.js +150 -0
- package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +40 -2
- package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +6 -1
- package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +148 -8
- package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +83 -15
- package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +1119 -215
- package/skills/wix-replatform/resources/rp-telemetry/lib/transcript-digest.js +318 -0
- package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +2 -2
- package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +106 -4
- package/skills/wix-replatform/resources/rp-website-continuation/SKILL.md +94 -0
- package/skills/wix-replatform/scripts/census-reconciliation.js +25 -0
- package/skills/wix-replatform/scripts/cms-fallback.js +89 -0
- package/skills/wix-replatform/scripts/discovered-census.js +26 -0
- package/skills/wix-replatform/scripts/discovered-set-status.js +33 -0
- package/skills/wix-replatform/scripts/entity-qa-attestation.js +130 -0
- package/skills/wix-replatform/scripts/execution-health-check.js +415 -0
- package/skills/wix-replatform/scripts/execution-manifest-validate.js +1 -1
- package/skills/wix-replatform/scripts/execution-state-prepare.js +1 -1
- package/skills/wix-replatform/scripts/expectations-ledger.js +44 -0
- package/skills/wix-replatform/scripts/import-contract-verify.js +183 -0
- package/skills/wix-replatform/scripts/internal-link-audit.js +90 -0
- package/skills/wix-replatform/scripts/item-seo-tags-verify.js +205 -0
- package/skills/wix-replatform/scripts/migration-completion.js +76 -0
- package/skills/wix-replatform/scripts/orchestration-preflight.js +21 -2
- package/skills/wix-replatform/scripts/orchestration-route.js +32 -1
- package/skills/wix-replatform/scripts/orchestration-state.js +146 -10
- package/skills/wix-replatform/scripts/read-back-gate.js +175 -0
- package/skills/wix-replatform/scripts/replatform-console.js +146 -0
- package/skills/wix-replatform/scripts/replatform.js +436 -0
- package/skills/wix-replatform/scripts/source-secrets.js +77 -0
- package/skills/wix-replatform/scripts/url-redirects.js +286 -0
- package/skills/wix-replatform/scripts/user-action.js +58 -0
- package/skills/wix-replatform/scripts/website-handoff-generate.js +64 -0
- package/skills/wix-replatform/scripts/wix-dashboard-url.js +54 -0
- package/skills/wix-replatform/scripts/wix-destination.js +143 -0
- package/skills/wix-replatform/scripts/wix-headless-scaffold.js +762 -0
- package/skills/wix-vibe-headless/SKILL.md +7 -3
- package/skills/wix-vibe-headless/install/deploy.cjs +61 -34
- package/skills/wix-vibe-headless/platforms/base44.md +1 -0
- package/skills/wix-vibe-headless/references/donations/INSTRUCTIONS.md +88 -0
- package/skills/wix-vibe-headless/references/donations/app/rest/wix-donations.js +112 -0
- package/skills/wix-vibe-headless/references/donations/seed/SEED.md +11 -0
- package/skills/wix-vibe-headless/references/donations/tests/donations.test.mjs +258 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -2
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +67 -3
- package/skills/wix-vibe-headless/references/shared/app/rest/wix-client.js +4 -3
- package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +0 -208
- package/skills/wix-replatform/resources/rp-target-wix/SPEC-product-reviews-and-blog-authorship.md +0 -330
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// An imported order's history as ONE composite write.
|
|
4
|
+
//
|
|
5
|
+
// Four writes with an ordering constraint, a shared identity, two inputs that come from neither
|
|
6
|
+
// the source nor the mapping plan, and one piece of state that must outlive a crash. The first
|
|
7
|
+
// caller to assemble that by hand -- with the whole spec in context -- made four mistakes in an
|
|
8
|
+
// afternoon, and two of them were silent: a mis-named gate parameter that reported a plausible
|
|
9
|
+
// zero, and a missing order crosswalk that would have duplicated the store. The writers were
|
|
10
|
+
// right every time. So the SEQUENCE lives here, and a caller supplies facts: a mapped order body
|
|
11
|
+
// and the source's evidence. No ordering, no reconciliation, no gate invocation, no state.
|
|
12
|
+
//
|
|
13
|
+
// THE ORDER WRITE'S IDENTITY. LIVE-VERIFIED 2026-09-05 against a disposable site:
|
|
14
|
+
// Import Order ACCEPTS a
|
|
15
|
+
// caller-supplied `id` it has never seen and keeps it; a re-send with the same id fully replaces
|
|
16
|
+
// the order body; payments and extendedFields attached to the order SURVIVE the replace; a
|
|
17
|
+
// non-GUID id is refused with a 400. So the order id is DERIVED here from the source site and
|
|
18
|
+
// the source order id, which makes the order write idempotent on the target's own terms: a run
|
|
19
|
+
// that lost its crosswalk asks the target whether the derived id exists, and finds it. The
|
|
20
|
+
// crosswalk is still written -- before anything else -- but it is an accelerator and a report
|
|
21
|
+
// input, not the only thing standing between a crash and a duplicated store.
|
|
22
|
+
//
|
|
23
|
+
// What the probe ALSO showed, and why an existing order is never re-sent: a different order body
|
|
24
|
+
// with an existing id replaces it silently, and nothing distinguishes "replace mine" from
|
|
25
|
+
// "clobber a merchant's edits". So an order that already exists is RECONCILED (left alone) and
|
|
26
|
+
// its history continued, never re-imported. Search Orders is not used as an existence check: it
|
|
27
|
+
// read a stale number within a second of a change, and an id read is exact.
|
|
28
|
+
|
|
29
|
+
const crypto = require('node:crypto');
|
|
30
|
+
const fs = require('node:fs/promises');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const w = require('./wix-writers.js');
|
|
33
|
+
const paymentMapping = require('./order-payment-mapping.js');
|
|
34
|
+
const localState = require('../../../lib/local-state.js');
|
|
35
|
+
const approvalStamp = require('../../../lib/preservation-approval-stamp.js');
|
|
36
|
+
const orderBuyer = require('./order-buyer.js');
|
|
37
|
+
const customerMember = require('./customer-member.js');
|
|
38
|
+
const fsSync = require('node:fs');
|
|
39
|
+
|
|
40
|
+
const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41
|
+
|
|
42
|
+
// A bounded retry for the TWO writes in this file that have earned one, and nowhere else. It is
|
|
43
|
+
// deliberately not on the shared client: bulk coupon, contact, member, form-submission, product
|
|
44
|
+
// and media creation are not reconciled against the target before writing, so a failure arriving
|
|
45
|
+
// AFTER the target committed would be retried into a second copy -- worse than the problem.
|
|
46
|
+
//
|
|
47
|
+
// Both sites below are reconciled first, so the second attempt is demonstrably the SAME attempt:
|
|
48
|
+
// the order id is derived from the source and the writer refuses a caller-chosen one, and the
|
|
49
|
+
// payment write is preceded by a read that decides create-or-adopt.
|
|
50
|
+
//
|
|
51
|
+
// `isTransientWixError` says the condition is likely to clear; whether a retry can duplicate
|
|
52
|
+
// anything is this call site's question, and only these two can answer it yes.
|
|
53
|
+
const WRITE_RETRY_ATTEMPTS = 3;
|
|
54
|
+
const WRITE_RETRY_DELAYS_MS = [500, 1500];
|
|
55
|
+
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
|
|
57
|
+
const ORDER_OUTCOMES = Object.freeze({
|
|
58
|
+
IMPORTED: 'order-imported',
|
|
59
|
+
ALREADY_IMPORTED: 'order-already-imported',
|
|
60
|
+
IMPORT_FAILED: 'order-import-failed',
|
|
61
|
+
TARGET_READ_FAILED: 'order-target-read-failed',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// The evidence contract. A key outside this list is REFUSED, not ignored: an ignored key is how
|
|
65
|
+
// `sourceAmount` for `amount` reported every order in a live store as never paid, with no reason.
|
|
66
|
+
const EVIDENCE_KEYS = Object.freeze([
|
|
67
|
+
'amount', 'paidDate', 'altPaidDate', 'currency', 'status', 'distinctAmountCount', 'utcOffsetMinutes',
|
|
68
|
+
'paidDateZone', 'altPaidDateZone',
|
|
69
|
+
'methodName', 'providerTransactionId', 'offlinePayment', 'cardDisplay',
|
|
70
|
+
'refunds', 'invoice',
|
|
71
|
+
]);
|
|
72
|
+
const REFUND_KEYS = Object.freeze(['sourceId', 'amount', 'reason']);
|
|
73
|
+
const INVOICE_KEYS = Object.freeze(['approval', 'sourceUrl', 'invoiceNumber', 'pendingMediaFileId']);
|
|
74
|
+
// Names a caller is likely to reach for, and where each actually goes.
|
|
75
|
+
const MISNAMED = Object.freeze({
|
|
76
|
+
sourceAmount: 'amount', total: 'amount', orderTotal: 'amount',
|
|
77
|
+
sourcePaidDate: 'paidDate', datePaid: 'paidDate', paid: 'paidDate',
|
|
78
|
+
// The two source stamps are NOT interchangeable and must not both land on `paidDate`: the
|
|
79
|
+
// `_gmt` sibling is universal by the source schema's definition of that key, the plain one is
|
|
80
|
+
// the shop's wall clock. Routing both here is how one shop offset came to be applied to a
|
|
81
|
+
// value that was already universal, moving the payment by the whole offset.
|
|
82
|
+
date_paid_gmt: "paidDate, declared paidDateZone: 'utc'",
|
|
83
|
+
date_paid: "altPaidDate, declared altPaidDateZone: 'site-local'",
|
|
84
|
+
sourceCurrency: 'currency',
|
|
85
|
+
targetCurrency: 'nothing here -- the target currency is read from order.currency',
|
|
86
|
+
transactionId: 'providerTransactionId', transaction_id: 'providerTransactionId', reference: 'providerTransactionId',
|
|
87
|
+
method: 'methodName', paymentMethod: 'methodName', payment_method: 'methodName', payment_method_title: 'methodName',
|
|
88
|
+
sourceStatus: 'status',
|
|
89
|
+
refund: 'refunds (an array)', invoices: 'invoice (one document per order)',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function isPlainObject(value) {
|
|
93
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function assertKeys(object, allowed, label) {
|
|
97
|
+
if (!isPlainObject(object)) throw new Error(`${label} must be an object, got ${JSON.stringify(object)}`);
|
|
98
|
+
const unknown = Object.keys(object).filter((key) => !allowed.includes(key));
|
|
99
|
+
if (unknown.length === 0) return;
|
|
100
|
+
const hints = unknown.map((key) => (MISNAMED[key] ? `${key} (this contract calls it ${MISNAMED[key]})` : key));
|
|
101
|
+
throw new Error(
|
|
102
|
+
`${label} carries keys this contract does not read: ${hints.join(', ')}. Accepted: ${allowed.join(', ')}. `
|
|
103
|
+
+ 'Unknown keys are refused rather than ignored, because an ignored key reads exactly like a source that evidences nothing.',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Known keys with malformed VALUES are as silent as unknown keys: `offlinePayment: "false"` was
|
|
108
|
+
// dropped rather than read, a scalar `cardDisplay` lost the card evidence, and
|
|
109
|
+
// `distinctAmountCount: "unknown"` became NaN, which is "not more than one", which let a
|
|
110
|
+
// multi-payment order through the gate. Each is refused with the key and the value it got.
|
|
111
|
+
const EVIDENCE_TYPES = Object.freeze({
|
|
112
|
+
amount: (v) => typeof v === 'string' || (typeof v === 'number' && Number.isFinite(v)),
|
|
113
|
+
paidDate: (v) => typeof v === 'string',
|
|
114
|
+
altPaidDate: (v) => typeof v === 'string',
|
|
115
|
+
currency: (v) => typeof v === 'string',
|
|
116
|
+
status: (v) => typeof v === 'string',
|
|
117
|
+
distinctAmountCount: (v) => Number.isInteger(v) && v >= 1,
|
|
118
|
+
// UTC-14 to UTC+14 is every offset that exists; 999999 accepted here moved a payment two years.
|
|
119
|
+
utcOffsetMinutes: (v) => Number.isInteger(v) && v >= -840 && v <= 840,
|
|
120
|
+
// A clock is a two-member enumeration, refused rather than coerced: 'UTC', true and 0 each
|
|
121
|
+
// read exactly like a correctly-declared clock once coerced, and would restore the defect
|
|
122
|
+
// these keys exist to remove -- silently, on a financial value.
|
|
123
|
+
paidDateZone: (v) => v === 'utc' || v === 'site-local',
|
|
124
|
+
altPaidDateZone: (v) => v === 'utc' || v === 'site-local',
|
|
125
|
+
methodName: (v) => typeof v === 'string',
|
|
126
|
+
providerTransactionId: (v) => typeof v === 'string',
|
|
127
|
+
offlinePayment: (v) => typeof v === 'boolean',
|
|
128
|
+
cardDisplay: (v) => isPlainObject(v)
|
|
129
|
+
&& Object.keys(v).every((k) => k === 'lastFourDigits' || k === 'brand')
|
|
130
|
+
&& Object.values(v).every((x) => x === null || x === undefined || typeof x === 'string'),
|
|
131
|
+
});
|
|
132
|
+
const EVIDENCE_TYPE_HINTS = Object.freeze({
|
|
133
|
+
amount: 'a decimal string or finite number',
|
|
134
|
+
paidDate: 'an ISO date-time string', altPaidDate: 'an ISO date-time string',
|
|
135
|
+
currency: 'an ISO 4217 code string', status: 'a string',
|
|
136
|
+
distinctAmountCount: 'a positive integer', utcOffsetMinutes: 'an integer between -840 and 840',
|
|
137
|
+
paidDateZone: "'utc' or 'site-local'", altPaidDateZone: "'utc' or 'site-local'",
|
|
138
|
+
methodName: 'a string', providerTransactionId: 'a string',
|
|
139
|
+
offlinePayment: 'a real boolean (not the string "false")',
|
|
140
|
+
cardDisplay: 'an object { lastFourDigits?, brand? } of strings',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function assertEvidence(evidence, { sourceId = null } = {}) {
|
|
144
|
+
assertKeys(evidence, EVIDENCE_KEYS, 'evidence');
|
|
145
|
+
// Internal: the source id the approval must name. Not a contract key -- set on a shallow copy.
|
|
146
|
+
evidence = { ...evidence, __sourceId: sourceId };
|
|
147
|
+
for (const [key, accepts] of Object.entries(EVIDENCE_TYPES)) {
|
|
148
|
+
const value = evidence[key];
|
|
149
|
+
if (value === undefined || value === null) continue;
|
|
150
|
+
if (!accepts(value)) {
|
|
151
|
+
throw new Error(`evidence.${key} must be ${EVIDENCE_TYPE_HINTS[key]}, got ${JSON.stringify(value)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Cross-key: a shop offset supplied beside a stamp whose clock nobody stated. Knowing the shop's
|
|
155
|
+
// offset is exactly the case where getting the clock wrong moves the money -- the offset is
|
|
156
|
+
// applied, so a stamp that was already universal lands hours out. The 'site-local' default exists
|
|
157
|
+
// to leave callers that never knew the offset untouched; it is not allowed to cover this shape.
|
|
158
|
+
// Per PRESENT stamp, not once per record: declaring one clock and leaving the other on the
|
|
159
|
+
// default reads as though the caller had been updated while still exposing a stamp the offset can
|
|
160
|
+
// move. Deliberately NOT widened to "offset present => declaration required": an unpaid order in
|
|
161
|
+
// a shop whose offset the run happens to know has no stamp to misapply it to.
|
|
162
|
+
if (evidence.utcOffsetMinutes !== undefined && evidence.utcOffsetMinutes !== null) {
|
|
163
|
+
for (const [stamp, zoneKey] of [['paidDate', 'paidDateZone'], ['altPaidDate', 'altPaidDateZone']]) {
|
|
164
|
+
if (evidence[stamp] === undefined || evidence[stamp] === null) continue;
|
|
165
|
+
if (evidence[zoneKey] !== undefined && evidence[zoneKey] !== null) continue;
|
|
166
|
+
throw new Error(
|
|
167
|
+
`evidence.${stamp} needs ${zoneKey} ('utc' or 'site-local') when utcOffsetMinutes is supplied; `
|
|
168
|
+
+ 'without it the offset may be applied to a stamp that is already universal',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (evidence.refunds !== undefined) {
|
|
173
|
+
if (!Array.isArray(evidence.refunds)) throw new Error('evidence.refunds must be an array (one entry per source refund)');
|
|
174
|
+
evidence.refunds.forEach((refund, index) => {
|
|
175
|
+
assertKeys(refund, REFUND_KEYS, `evidence.refunds[${index}]`);
|
|
176
|
+
if (refund.sourceId === undefined || refund.sourceId === null || String(refund.sourceId).trim() === '') {
|
|
177
|
+
throw new Error(`evidence.refunds[${index}] needs a sourceId; occurrence numbers are assigned by it and a re-run recognizes its own refunds through it`);
|
|
178
|
+
}
|
|
179
|
+
// A refund with no positive amount would reach Refund Payments as `amount: "undefined"` --
|
|
180
|
+
// after the payment create it hangs off. Refused here, before any target call.
|
|
181
|
+
const amount = paymentMapping.canonicalizeDecimal(refund.amount);
|
|
182
|
+
if (amount === null || !(Number(amount) > 0)) {
|
|
183
|
+
throw new Error(`evidence.refunds[${index}] needs a positive amount, got ${JSON.stringify(refund.amount)}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (evidence.invoice !== undefined && evidence.invoice !== null) {
|
|
188
|
+
const invoice = evidence.invoice;
|
|
189
|
+
assertKeys(invoice, INVOICE_KEYS, 'evidence.invoice');
|
|
190
|
+
// The approval must be the PLANNER's. The writer only insists on the stamp where an opt-in is
|
|
191
|
+
// relied on; this composite insists everywhere, because a hand-built reference-only approval
|
|
192
|
+
// with an object for a number wrote "[object Object]" onto an order in review. The plan is the
|
|
193
|
+
// one producer, and it stamps everything it issues.
|
|
194
|
+
if (!isPlainObject(invoice.approval) || !approvalStamp.verifyApprovalStamp(invoice.approval)) {
|
|
195
|
+
throw new Error('evidence.invoice.approval must be an approval issued by resolvePreservationPlan() (it carries the plan\'s stamp); pass the plan\'s object, do not rebuild it');
|
|
196
|
+
}
|
|
197
|
+
// The planner's approvals name their order, and the stamp covers that name. One handed over
|
|
198
|
+
// for a different source order would preserve somebody else's document onto this one (review
|
|
199
|
+
// demonstrated it), and one with the order DELETED after stamping would fit any order (review
|
|
200
|
+
// demonstrated that too) -- so the order is required, not merely compared when present.
|
|
201
|
+
if (invoice.approval.orderId === undefined || invoice.approval.orderId === null || String(invoice.approval.orderId).trim() === '') {
|
|
202
|
+
throw new Error('evidence.invoice.approval names no source order (orderId); resolvePreservationPlan() issues approvals per order, and one without an order could be handed to any');
|
|
203
|
+
}
|
|
204
|
+
if (String(invoice.approval.orderId) !== String(evidence.__sourceId)) {
|
|
205
|
+
throw new Error(`evidence.invoice.approval is for source order ${invoice.approval.orderId}, not ${evidence.__sourceId}`);
|
|
206
|
+
}
|
|
207
|
+
const optional = (key, accepts, hint) => {
|
|
208
|
+
const v = invoice[key];
|
|
209
|
+
if (v === undefined || v === null) return;
|
|
210
|
+
if (!accepts(v)) throw new Error(`evidence.invoice.${key} must be ${hint}, got ${JSON.stringify(v)}`);
|
|
211
|
+
};
|
|
212
|
+
optional('sourceUrl', (v) => typeof v === 'string', 'a URL string');
|
|
213
|
+
optional('invoiceNumber', (v) => typeof v === 'string' || typeof v === 'number', 'a string or number');
|
|
214
|
+
optional('pendingMediaFileId', (v) => typeof v === 'string' && v.trim() !== '', 'a Media file id string');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A stable GUID for a source order on this target. Version-5 style: a SHA-1 over a namespaced
|
|
219
|
+
// name with the version and variant bits set, which is what Import Order accepted live. The
|
|
220
|
+
// source SITE is part of the name so two stores importing "order 1042" into one Wix site cannot
|
|
221
|
+
// collide -- and so a migration re-run from the same store converges on the same order.
|
|
222
|
+
// `shop.example`, `https://shop.example/`, `SHOP.EXAMPLE:443` and `shop.example/wp` are one site.
|
|
223
|
+
// Four spellings deriving four ids would re-import a store the day a config string gained a
|
|
224
|
+
// scheme, so the host is normalized to its bare lowercase hostname (a non-default port is kept).
|
|
225
|
+
function normalizeSourceSiteHost(sourceSiteHost) {
|
|
226
|
+
let text = String(sourceSiteHost === undefined || sourceSiteHost === null ? '' : sourceSiteHost).trim().toLowerCase();
|
|
227
|
+
if (!text) return '';
|
|
228
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//.test(text)) text = `https://${text}`;
|
|
229
|
+
let url;
|
|
230
|
+
try { url = new URL(text); } catch (error) { return ''; }
|
|
231
|
+
return url.port && url.port !== '443' ? `${url.hostname}:${url.port}` : url.hostname;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function deriveImportedOrderId({ sourceSiteHost, sourceId } = {}) {
|
|
235
|
+
const host = normalizeSourceSiteHost(sourceSiteHost);
|
|
236
|
+
const id = sourceId === undefined || sourceId === null ? '' : String(sourceId).trim();
|
|
237
|
+
if (!host) throw new Error('deriveImportedOrderId needs sourceSiteHost (a hostname, or a URL it can be read from): the id is namespaced by the source site');
|
|
238
|
+
if (!id) throw new Error('deriveImportedOrderId needs the source order id');
|
|
239
|
+
const hash = crypto.createHash('sha1').update(`replatform:ecom:order:${host}:${id}`).digest();
|
|
240
|
+
hash[6] = (hash[6] & 0x0f) | 0x50;
|
|
241
|
+
hash[8] = (hash[8] & 0x3f) | 0x80;
|
|
242
|
+
const hex = hash.toString('hex');
|
|
243
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Same rule as rp-source-wordpress's assignRefundOccurrences, kept target-side so this module does
|
|
247
|
+
// not depend on a source adapter: two source refunds of equal amount on one order are the first
|
|
248
|
+
// and second OCCURRENCE of that amount, ordered by source id, and reconcile as such. Pass ALL of an
|
|
249
|
+
// order's refunds in one call -- occurrences are relative to the list.
|
|
250
|
+
function withOccurrences(refunds) {
|
|
251
|
+
const seen = new Map();
|
|
252
|
+
return [...refunds]
|
|
253
|
+
.sort((a, b) => String(a.sourceId).localeCompare(String(b.sourceId)))
|
|
254
|
+
.map((refund) => {
|
|
255
|
+
const key = paymentMapping.canonicalizeDecimal(refund.amount) || String(refund.amount);
|
|
256
|
+
const occurrence = (seen.get(key) || 0) + 1;
|
|
257
|
+
seen.set(key, occurrence);
|
|
258
|
+
return { ...refund, sourceOccurrence: occurrence };
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function describeError(error) {
|
|
263
|
+
return {
|
|
264
|
+
message: String((error && error.message) || error).slice(0, 300),
|
|
265
|
+
...(error && error.status ? { status: error.status } : {}),
|
|
266
|
+
// Conditional, like the rest: an unknown outcome is the exceptional case, and making this
|
|
267
|
+
// unconditional would change the shape of every row already written to a results ledger.
|
|
268
|
+
// Without it, the rejected-vs-unknown distinction is lost the moment it is recorded.
|
|
269
|
+
...(error && error.outcomeKnown === false ? { outcomeKnown: false } : {}),
|
|
270
|
+
...(error && error.applicationErrorCode ? { applicationErrorCode: error.applicationErrorCode } : {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function assertCrosswalk(crosswalk) {
|
|
275
|
+
const has = (name) => crosswalk && typeof crosswalk[name] === 'function';
|
|
276
|
+
if (!has('get') || !has('set') || !has('getInvoiceFile') || !has('setInvoiceFile')) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
'importOrderHistory needs a persisted crosswalk with get(sourceId), set(sourceId, orderId), '
|
|
279
|
+
+ 'getInvoiceFile(sourceId) and setInvoiceFile(sourceId, fileId). It is not optional: a run that '
|
|
280
|
+
+ 'forgets it re-imports the store, and one that forgets the invoice file imports a second copy of a '
|
|
281
|
+
+ 'tax document. createProjectOrderCrosswalk({ projectDir }) provides all four over state/crosswalk/.',
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Fill every remaining stage with a named outcome once the order stage has failed. Nothing else
|
|
287
|
+
// is attempted: with no order there is nothing to attach a payment or a document to, and a payment
|
|
288
|
+
// that was never attempted because of an import failure is a FAILURE, not an ineligibility.
|
|
289
|
+
function failRemaining(outcomes, errors, { gate, refunds, evidence, cause }) {
|
|
290
|
+
outcomes.payment = gate.eligible ? 'payment-failed' : gate.outcome;
|
|
291
|
+
if (gate.eligible) errors.payment = { cause };
|
|
292
|
+
outcomes.refunds = refunds.map((refund) => ({
|
|
293
|
+
sourceId: refund.sourceId,
|
|
294
|
+
sourceOccurrence: refund.sourceOccurrence,
|
|
295
|
+
outcome: gate.eligible ? 'refund-failed' : 'refund-without-payment',
|
|
296
|
+
...(gate.eligible ? { error: { cause } } : {}),
|
|
297
|
+
}));
|
|
298
|
+
outcomes.invoice = evidence.invoice ? 'invoice-failed' : 'invoice-none-found';
|
|
299
|
+
if (evidence.invoice) errors.invoice = { cause };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---- contact fields --------------------------
|
|
303
|
+
// A source email or phone Import Order refuses fails the WHOLE order. The policy: a
|
|
304
|
+
// contact detail is removed only when Import Order's own rejection NAMES that field, and the
|
|
305
|
+
// order is then sent once more without it. Never removed on a local or customer-path check
|
|
306
|
+
// alone, never rewritten, never replaced with an invented value. Every removal is a named
|
|
307
|
+
// finding carrying the field and a reason, never the value.
|
|
308
|
+
//
|
|
309
|
+
// The field list is READ from `domains/ecom/entities/order.json` -> `safeModeContactFields[]`,
|
|
310
|
+
// the same list safe mode replaces, so the two cannot drift apart.
|
|
311
|
+
const ORDER_ENTITY_JSON = path.join(__dirname, '..', 'domains', 'ecom', 'entities', 'order.json');
|
|
312
|
+
let CONTACT_FIELDS = null;
|
|
313
|
+
function orderContactFields() {
|
|
314
|
+
if (CONTACT_FIELDS) return CONTACT_FIELDS;
|
|
315
|
+
const entity = JSON.parse(fsSync.readFileSync(ORDER_ENTITY_JSON, 'utf8'));
|
|
316
|
+
const fields = Array.isArray(entity.safeModeContactFields) ? entity.safeModeContactFields : [];
|
|
317
|
+
if (fields.length === 0) throw new Error(`${ORDER_ENTITY_JSON} carries no safeModeContactFields[]; the contact-field rules read their list from there and will not guess one`);
|
|
318
|
+
for (const field of fields) {
|
|
319
|
+
if (field.kind !== 'email' && field.kind !== 'phone') {
|
|
320
|
+
throw new Error(`safeModeContactFields[] entry ${field.targetPath} has kind ${JSON.stringify(field.kind)}; only "email" and "phone" are known`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
CONTACT_FIELDS = Object.freeze(fields.map((field) => Object.freeze({ kind: String(field.kind), targetPath: String(field.targetPath) })));
|
|
324
|
+
return CONTACT_FIELDS;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function readAtPath(object, dotted) {
|
|
328
|
+
let node = object;
|
|
329
|
+
for (const key of dotted.split('.')) {
|
|
330
|
+
if (!isPlainObject(node)) return undefined;
|
|
331
|
+
node = node[key];
|
|
332
|
+
}
|
|
333
|
+
return node;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Copy-on-write down the path only; the caller's object is never mutated.
|
|
337
|
+
function withoutPath(object, dotted) {
|
|
338
|
+
const [head, ...rest] = dotted.split('.');
|
|
339
|
+
if (!isPlainObject(object) || !(head in object)) return object;
|
|
340
|
+
if (rest.length === 0) {
|
|
341
|
+
const next = { ...object };
|
|
342
|
+
delete next[head];
|
|
343
|
+
return next;
|
|
344
|
+
}
|
|
345
|
+
const child = withoutPath(object[head], rest.join('.'));
|
|
346
|
+
return child === object[head] ? object : { ...object, [head]: child };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function presentValue(value) {
|
|
350
|
+
if (value === undefined || value === null) return null;
|
|
351
|
+
const text = String(value).trim();
|
|
352
|
+
return text === '' ? null : text;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Advisory only. Run on the SOURCE-DERIVED body, before `buildImportOrderRequest`
|
|
356
|
+
// applies safe mode: safe mode's mocks always pass, so a check placed after it would report zero
|
|
357
|
+
// suspects on every rehearsal. The body is never changed; a suspect is a forecast, not a loss.
|
|
358
|
+
function checkOrderContactFields(order, { sourceId = null } = {}) {
|
|
359
|
+
if (!isPlainObject(order)) throw new Error('checkOrderContactFields needs the mapped target order body as `order`');
|
|
360
|
+
const findings = [];
|
|
361
|
+
const id = sourceId === undefined || sourceId === null ? null : String(sourceId);
|
|
362
|
+
const countryCode = presentValue(readAtPath(order, 'billingInfo.address.country'));
|
|
363
|
+
for (const field of orderContactFields()) {
|
|
364
|
+
const value = presentValue(readAtPath(order, field.targetPath));
|
|
365
|
+
if (value === null) continue; // an absent detail is not a defect
|
|
366
|
+
if (field.kind === 'email') {
|
|
367
|
+
if (orderBuyer.normalizeEmail(value) === null) {
|
|
368
|
+
findings.push({ code: 'order-contact-email-suspect', ...(id ? { sourceId: id } : {}), field: field.targetPath, reason: 'not-a-sendable-address' });
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
const normalized = customerMember.normalizePhoneE164(value, countryCode);
|
|
372
|
+
if (!normalized.ok) {
|
|
373
|
+
findings.push({ code: 'order-contact-phone-suspect', ...(id ? { sourceId: id } : {}), field: field.targetPath, reason: String(normalized.reason) });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return { findings };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Which contact fields did Import Order's rejection NAME? Only a 400 with a structured
|
|
381
|
+
// `fieldViolations[]` counts, and only an EXACT path match (after an optional leading `order.`
|
|
382
|
+
// request wrapper) against `safeModeContactFields[]`. Message text is never parsed. All or
|
|
383
|
+
// nothing: one violation that is not an identified contact field means no removal (null).
|
|
384
|
+
function identifyRejectedContactFields(error) {
|
|
385
|
+
if (!error || error.status !== 400) return null;
|
|
386
|
+
const violations = error.body && error.body.details && error.body.details.validationError
|
|
387
|
+
&& error.body.details.validationError.fieldViolations;
|
|
388
|
+
if (!Array.isArray(violations) || violations.length === 0) return null;
|
|
389
|
+
const known = new Set(orderContactFields().map((field) => field.targetPath));
|
|
390
|
+
const paths = [];
|
|
391
|
+
for (const violation of violations) {
|
|
392
|
+
const raw = violation && typeof violation.field === 'string' ? violation.field : null;
|
|
393
|
+
if (raw === null) return null;
|
|
394
|
+
const field = raw.startsWith('order.') ? raw.slice('order.'.length) : raw;
|
|
395
|
+
if (!known.has(field)) return null;
|
|
396
|
+
if (!paths.includes(field)) paths.push(field);
|
|
397
|
+
}
|
|
398
|
+
return paths;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// The composite. Returns outcomes; throws only for CALLER errors (a malformed contract), never for
|
|
402
|
+
// a target failure -- each stage's failure is a named outcome so a resumed run reconciles what
|
|
403
|
+
// landed and retries only what did not.
|
|
404
|
+
async function importOrderHistory(wix, {
|
|
405
|
+
order,
|
|
406
|
+
sourceId,
|
|
407
|
+
sourceSiteHost,
|
|
408
|
+
evidence,
|
|
409
|
+
crosswalk,
|
|
410
|
+
setupVerification = null,
|
|
411
|
+
pacer = null,
|
|
412
|
+
safeModeOptions = undefined,
|
|
413
|
+
// Injectable only so the retry tests do not spend the delay ladder in real time.
|
|
414
|
+
sleep = null,
|
|
415
|
+
// The buyer, from resolveOrderBuyer -- stamped, so a hand-built memberId cannot reach
|
|
416
|
+
// the order. Optional: an order with no buyer resolution imports with whatever contactId the
|
|
417
|
+
// mapped body already carries, and never a memberId.
|
|
418
|
+
buyer = null,
|
|
419
|
+
} = {}) {
|
|
420
|
+
if (!wix || typeof wix.send !== 'function') throw new Error('importOrderHistory needs a Wix client');
|
|
421
|
+
const sleepFor = sleep || realSleep;
|
|
422
|
+
const findings_pre = [];
|
|
423
|
+
if (!isPlainObject(order)) throw new Error('importOrderHistory needs the mapped target order body as `order`');
|
|
424
|
+
assertCrosswalk(crosswalk);
|
|
425
|
+
assertEvidence(evidence, { sourceId });
|
|
426
|
+
const derivedId = deriveImportedOrderId({ sourceSiteHost, sourceId });
|
|
427
|
+
if (order.id !== undefined && order.id !== derivedId) {
|
|
428
|
+
throw new Error(`order.id is not the caller's to choose: got ${order.id}, this order derives to ${derivedId}. Omit it.`);
|
|
429
|
+
}
|
|
430
|
+
// buyerInfo.memberId is what member-facing reads authorize on, and Import Order stores any GUID
|
|
431
|
+
// unvalidated (verified live 2026-09-05). So it is never the caller's to set on the body: it comes
|
|
432
|
+
// through `buyer`, corroborated and stamped by resolveOrderBuyer, or not at all.
|
|
433
|
+
orderBuyer.assertBodyBuyerInfo(order);
|
|
434
|
+
// No buyer resolution is itself a counted outcome, so the buyer stage partitions the order
|
|
435
|
+
// population like the payment stage does.
|
|
436
|
+
let buyerOutcome = 'buyer-not-resolved';
|
|
437
|
+
if (buyer !== null && buyer !== undefined) {
|
|
438
|
+
if (!orderBuyer.verifyBuyerStamp(buyer)) throw new Error('`buyer` must be the unchanged result of resolveOrderBuyer (its stamp did not verify)');
|
|
439
|
+
// The resolver corroborated the member against ONE order's billing email, and the stamp covers
|
|
440
|
+
// that email. The BODY's email must be the same one: a result resolved for jane@ handed over
|
|
441
|
+
// beside a body carrying victim@ would attribute the victim's order and invoice to Jane, with
|
|
442
|
+
// the stamp verifying (review demonstrated it). Bound for every outcome -- a resolution made for
|
|
443
|
+
// a different order is wrong whatever it decided -- and a linked result requires the body to
|
|
444
|
+
// carry the email at all, so the two are never merely "both absent".
|
|
445
|
+
const rawBodyEmail = order.buyerInfo && order.buyerInfo.email;
|
|
446
|
+
const bodyEmail = orderBuyer.normalizeEmail(rawBodyEmail);
|
|
447
|
+
const bodyHasEmail = rawBodyEmail !== undefined && rawBodyEmail !== null && String(rawBodyEmail).trim() !== '';
|
|
448
|
+
if (buyer.email && bodyHasEmail && bodyEmail !== buyer.email) {
|
|
449
|
+
throw new Error('`buyer` was resolved for a different billing email than order.buyerInfo.email carries; resolve the buyer from THIS order\'s billing email');
|
|
450
|
+
}
|
|
451
|
+
if (buyer.memberId && (!bodyHasEmail || bodyEmail !== buyer.email)) {
|
|
452
|
+
throw new Error('a linked `buyer` requires order.buyerInfo.email to be the billing email the member was corroborated against; the body carries none or another');
|
|
453
|
+
}
|
|
454
|
+
const info = orderBuyer.buyerInfoFrom(buyer);
|
|
455
|
+
// Replace, not merge: only the body's email survives beside what the resolver decided.
|
|
456
|
+
const kept = order.buyerInfo && order.buyerInfo.email ? { email: order.buyerInfo.email } : {};
|
|
457
|
+
order = { ...order, buyerInfo: { ...kept, ...(order.buyerInfo && order.buyerInfo.contactId && !info.contactId ? { contactId: order.buyerInfo.contactId } : {}), ...info } };
|
|
458
|
+
buyerOutcome = buyer.outcome;
|
|
459
|
+
for (const finding of buyer.findings || []) findings_pre.push(finding);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const findings = findings_pre;
|
|
463
|
+
const errors = {};
|
|
464
|
+
const outcomes = { order: null, payment: null, refunds: [], invoice: null, buyer: buyerOutcome };
|
|
465
|
+
|
|
466
|
+
// The gate runs FIRST and from the evidence structure, so a caller cannot mis-name a parameter
|
|
467
|
+
// into a silent zero. It is pure: no target read is spent on an order the source never paid.
|
|
468
|
+
const gate = paymentMapping.evaluatePaymentEligibility({
|
|
469
|
+
sourceAmount: evidence.amount === undefined ? null : evidence.amount,
|
|
470
|
+
paidDate: evidence.paidDate === undefined ? null : evidence.paidDate,
|
|
471
|
+
altPaidDate: evidence.altPaidDate === undefined ? null : evidence.altPaidDate,
|
|
472
|
+
distinctAmountCount: evidence.distinctAmountCount === undefined ? 1 : evidence.distinctAmountCount,
|
|
473
|
+
sourceCurrency: evidence.currency === undefined ? null : evidence.currency,
|
|
474
|
+
targetCurrency: order.currency === undefined ? null : order.currency,
|
|
475
|
+
utcOffsetMinutes: evidence.utcOffsetMinutes === undefined ? null : evidence.utcOffsetMinutes,
|
|
476
|
+
paidDateZone: evidence.paidDateZone === undefined || evidence.paidDateZone === null ? 'site-local' : evidence.paidDateZone,
|
|
477
|
+
altPaidDateZone: evidence.altPaidDateZone === undefined || evidence.altPaidDateZone === null ? 'site-local' : evidence.altPaidDateZone,
|
|
478
|
+
sourceStatus: evidence.status === undefined ? null : evidence.status,
|
|
479
|
+
});
|
|
480
|
+
findings.push(...(gate.findings || []));
|
|
481
|
+
|
|
482
|
+
let payment = null;
|
|
483
|
+
if (gate.eligible) {
|
|
484
|
+
const built = paymentMapping.buildPaymentRecord({
|
|
485
|
+
amount: gate.amount,
|
|
486
|
+
createdDate: gate.createdDate,
|
|
487
|
+
methodName: evidence.methodName === undefined ? null : evidence.methodName,
|
|
488
|
+
providerTransactionId: evidence.providerTransactionId === undefined ? null : evidence.providerTransactionId,
|
|
489
|
+
offlinePayment: evidence.offlinePayment,
|
|
490
|
+
cardDisplay: evidence.cardDisplay === undefined ? null : evidence.cardDisplay,
|
|
491
|
+
});
|
|
492
|
+
payment = built.payment;
|
|
493
|
+
findings.push(...(built.findings || []));
|
|
494
|
+
}
|
|
495
|
+
const refunds = withOccurrences(evidence.refunds || []);
|
|
496
|
+
|
|
497
|
+
// ---- 1. The order. Crosswalk hit or miss, the TARGET is asked once by id. -------------------
|
|
498
|
+
// A hit is not proof: the docs' own use of Bulk Delete Imported Orders is "clear a faulty
|
|
499
|
+
// migration before re-importing", and a crosswalk that survived that must not report an order
|
|
500
|
+
// the target no longer has. Only a readable 404 proves absence; an unreadable gateway
|
|
501
|
+
// response must not authorize Import Order, which can replace an existing order.
|
|
502
|
+
let orderId = derivedId;
|
|
503
|
+
const readPacer = pacer || w.sharedTargetReadPacer();
|
|
504
|
+
const existsOnTarget = async (id) => {
|
|
505
|
+
const read = await readPacer.read(() => w.getOrder(wix, id));
|
|
506
|
+
if (read.ok) return { exists: true };
|
|
507
|
+
if (read.error && read.error.status === 404 && read.error.outcomeKnown !== false) return { exists: false };
|
|
508
|
+
return { exists: null, error: read.error };
|
|
509
|
+
};
|
|
510
|
+
const known = await crosswalk.get(sourceId);
|
|
511
|
+
// The crosswalk's answer is trusted as the order's identity, so its SHAPE is not: a row object
|
|
512
|
+
// where a string was expected became `orders/[object Object]/add-payment` in review. Only a
|
|
513
|
+
// GUID-shaped string may stand for an order; anything else is a broken crosswalk, which is a
|
|
514
|
+
// caller problem and stops the run rather than continuing history onto a nonsense id.
|
|
515
|
+
if (known !== null && known !== undefined && known !== '' && !GUID.test(String(known))) {
|
|
516
|
+
throw new Error(`the crosswalk returned ${JSON.stringify(known)} for source order ${sourceId}; a crosswalk value must be the Wix order id (a GUID string) or null`);
|
|
517
|
+
}
|
|
518
|
+
// A row from before deterministic ids (a Wix-minted id) is still THE order: honouring it is what
|
|
519
|
+
// keeps an earlier migration from being re-imported under a derived id.
|
|
520
|
+
const candidateId = known ? String(known) : derivedId;
|
|
521
|
+
const probe = await existsOnTarget(candidateId);
|
|
522
|
+
if (probe.exists === null) {
|
|
523
|
+
// Cannot tell whether the order is there. Importing now could replace a merchant-edited order;
|
|
524
|
+
// not importing loses nothing a retry cannot recover. So: a named outcome, no write.
|
|
525
|
+
outcomes.order = ORDER_OUTCOMES.TARGET_READ_FAILED;
|
|
526
|
+
errors.order = describeError(probe.error);
|
|
527
|
+
failRemaining(outcomes, errors, { gate, refunds, evidence, cause: 'order-not-imported' });
|
|
528
|
+
return { orderId: null, derivedId, outcomes, findings, errors, paymentId: null, invoice: null };
|
|
529
|
+
}
|
|
530
|
+
if (probe.exists) {
|
|
531
|
+
orderId = candidateId;
|
|
532
|
+
if (known && orderId !== derivedId) findings.push({ code: 'order-id-from-crosswalk', sourceId: String(sourceId), orderId });
|
|
533
|
+
outcomes.order = ORDER_OUTCOMES.ALREADY_IMPORTED;
|
|
534
|
+
if (!known) {
|
|
535
|
+
// The crosswalk had lost it; adopt it.
|
|
536
|
+
try { await crosswalk.set(sourceId, orderId); } catch (error) { errors.crosswalk = describeError(error); }
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
let derivedExists = false;
|
|
540
|
+
if (known) {
|
|
541
|
+
// The crosswalk points at an order the target no longer has -- a cleared migration, or a
|
|
542
|
+
// stale row. Before importing under the derived id, ask whether THAT order already exists:
|
|
543
|
+
// Import Order with an existing id silently replaces it, and a merchant's edits with it
|
|
544
|
+
// (review demonstrated exactly that). If it exists, adopt it and replace the row.
|
|
545
|
+
const derivedProbe = await existsOnTarget(derivedId);
|
|
546
|
+
if (derivedProbe.exists === null) {
|
|
547
|
+
outcomes.order = ORDER_OUTCOMES.TARGET_READ_FAILED;
|
|
548
|
+
errors.order = describeError(derivedProbe.error);
|
|
549
|
+
failRemaining(outcomes, errors, { gate, refunds, evidence, cause: 'order-not-imported' });
|
|
550
|
+
return { orderId: null, derivedId, outcomes, findings, errors, paymentId: null, invoice: null };
|
|
551
|
+
}
|
|
552
|
+
derivedExists = derivedProbe.exists === true;
|
|
553
|
+
findings.push(derivedExists
|
|
554
|
+
? { code: 'order-crosswalk-row-stale', sourceId: String(sourceId), previousOrderId: String(known), orderId: derivedId }
|
|
555
|
+
: { code: 'order-reimported-after-deletion', sourceId: String(sourceId), previousOrderId: String(known) });
|
|
556
|
+
}
|
|
557
|
+
if (derivedExists) {
|
|
558
|
+
orderId = derivedId;
|
|
559
|
+
outcomes.order = ORDER_OUTCOMES.ALREADY_IMPORTED;
|
|
560
|
+
} else {
|
|
561
|
+
try {
|
|
562
|
+
// Retried on a transient failure, and safe to: the id is derived from the source and
|
|
563
|
+
// `:296` refuses a caller-chosen one, so a second attempt re-sends the same identity. A
|
|
564
|
+
// retry that comes back already-imported is a SUCCESS -- the existing ALREADY_IMPORTED
|
|
565
|
+
// outcome covers it. Only the send is wrapped: the checks below it are permanent
|
|
566
|
+
// refusals, not conditions that clear.
|
|
567
|
+
const send = async (body) => {
|
|
568
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
569
|
+
try {
|
|
570
|
+
return await w.importOrder(wix, { ...body, id: derivedId }, safeModeOptions);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
if (attempt >= WRITE_RETRY_ATTEMPTS || !w.isTransientWixError(error)) throw error;
|
|
573
|
+
await sleepFor(WRITE_RETRY_DELAYS_MS[attempt - 1]);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
// Advisory, on the source-derived body, once, before the first send
|
|
578
|
+
// (where safe mode is applied). Not repeated for the retry below.
|
|
579
|
+
findings.push(...checkOrderContactFields(order, { sourceId }).findings);
|
|
580
|
+
let imported = null;
|
|
581
|
+
let removed = [];
|
|
582
|
+
try {
|
|
583
|
+
imported = await send(order);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
// One retry, only when the rejection names contact fields the body
|
|
586
|
+
// actually carries. The first call was refused and created nothing, so the retry under
|
|
587
|
+
// the same derived id is not a duplicate. The retry body is the SOURCE-DERIVED order
|
|
588
|
+
// minus exactly those paths -- safe mode still applies to what remains -- and on a
|
|
589
|
+
// member-linked order it keeps `buyerInfo.memberId`/`contactId` untouched.
|
|
590
|
+
const paths = identifyRejectedContactFields(error);
|
|
591
|
+
if (!paths || !paths.every((p) => readAtPath(order, p) !== undefined)) throw error;
|
|
592
|
+
const retryBody = paths.reduce((body, p) => withoutPath(body, p), order);
|
|
593
|
+
imported = await send(retryBody);
|
|
594
|
+
removed = paths;
|
|
595
|
+
}
|
|
596
|
+
if (!imported || !imported.id) {
|
|
597
|
+
throw new Error('Import Order returned no order id');
|
|
598
|
+
}
|
|
599
|
+
if (imported.id !== derivedId && !GUID.test(String(imported.id))) {
|
|
600
|
+
// Cannot be recorded (the crosswalk accepts only GUIDs) and cannot be trusted. Reported
|
|
601
|
+
// with the id so a human can find the order by number; the run does not continue history
|
|
602
|
+
// onto it.
|
|
603
|
+
throw new Error(`Import Order returned a non-GUID id ${JSON.stringify(imported.id)} for source order ${sourceId} (number ${order.number}); the order may exist on the target under it`);
|
|
604
|
+
}
|
|
605
|
+
if (imported.id !== derivedId) {
|
|
606
|
+
// The order EXISTS, under an id we did not choose. Reporting a failure here would leave
|
|
607
|
+
// no crosswalk row, and every re-run would import it again -- the one outcome this
|
|
608
|
+
// module exists to prevent. So the target's id is adopted and recorded, the order counts
|
|
609
|
+
// as imported, and the finding says the identity model did not hold on this target.
|
|
610
|
+
orderId = String(imported.id);
|
|
611
|
+
findings.push({ code: 'order-id-not-derived', sourceId: String(sourceId), orderId, derivedId });
|
|
612
|
+
}
|
|
613
|
+
outcomes.order = ORDER_OUTCOMES.IMPORTED;
|
|
614
|
+
// One drop finding per removed path, only once the order landed without it. Field and
|
|
615
|
+
// reason, never the value, the member id or the rejection's wording.
|
|
616
|
+
const kinds = new Map(orderContactFields().map((field) => [field.targetPath, field.kind]));
|
|
617
|
+
for (const field of removed) {
|
|
618
|
+
findings.push({
|
|
619
|
+
code: kinds.get(field) === 'email' ? 'order-contact-email-dropped' : 'order-contact-phone-dropped',
|
|
620
|
+
sourceId: String(sourceId),
|
|
621
|
+
field,
|
|
622
|
+
reason: 'import-order-rejected-field',
|
|
623
|
+
...(field === 'buyerInfo.email' && buyerOutcome === orderBuyer.OUTCOMES.LINKED ? { memberLinkKept: true } : {}),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
outcomes.order = ORDER_OUTCOMES.IMPORT_FAILED;
|
|
628
|
+
errors.order = describeError(error);
|
|
629
|
+
failRemaining(outcomes, errors, { gate, refunds, evidence, cause: 'order-not-imported' });
|
|
630
|
+
return { orderId: null, derivedId, outcomes, findings, errors, paymentId: null, invoice: null };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Persisted BEFORE any further write. The one thing nothing else catches. If the write
|
|
634
|
+
// itself fails the order is still on the target and the next run finds it by derived id, so
|
|
635
|
+
// the run continues -- but the caller is told, because a crosswalk that silently stopped
|
|
636
|
+
// persisting is a report that undercounts what landed.
|
|
637
|
+
try {
|
|
638
|
+
await crosswalk.set(sourceId, orderId);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
errors.crosswalk = describeError(error);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ---- 2. Payment and refunds, from ONE reconciliation read. ---------------------------------
|
|
645
|
+
let paymentId = null;
|
|
646
|
+
if (!gate.eligible) {
|
|
647
|
+
outcomes.payment = gate.outcome;
|
|
648
|
+
outcomes.refunds = refunds.map((refund) => ({ sourceId: refund.sourceId, sourceOccurrence: refund.sourceOccurrence, outcome: 'refund-without-payment' }));
|
|
649
|
+
} else {
|
|
650
|
+
// The read and the write are retried as ONE pair or not at all. The read decides create-or-
|
|
651
|
+
// adopt, and a failed write attempt is exactly what can make that decision stale -- the
|
|
652
|
+
// attempt that failed may be what created the payment. Retrying the write alone would re-use
|
|
653
|
+
// the pre-failure decision and add a second payment. `assertReconciledBinding`
|
|
654
|
+
// (`wix-writers.js:3729`) refuses a read taken for a different order or a different payment
|
|
655
|
+
// fingerprint, but it cannot detect a stale read for the right one.
|
|
656
|
+
//
|
|
657
|
+
// A `payment-target-read-failed` reconcile does NOT enter this loop: the pacer already
|
|
658
|
+
// retried it internally and `writeOrderPayment` returns that outcome rather than throwing.
|
|
659
|
+
let reconciled = await w.reconcileOrderPaymentTarget(wix, { orderId, payment, pacer });
|
|
660
|
+
let written = null;
|
|
661
|
+
try {
|
|
662
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
663
|
+
try {
|
|
664
|
+
written = await w.writeOrderPayment(wix, { orderId, payment, pacer, reconciled });
|
|
665
|
+
break;
|
|
666
|
+
} catch (error) {
|
|
667
|
+
if (attempt >= WRITE_RETRY_ATTEMPTS || !w.isTransientWixError(error)) throw error;
|
|
668
|
+
await sleepFor(WRITE_RETRY_DELAYS_MS[attempt - 1]);
|
|
669
|
+
// Re-read before re-writing. Coming back RECONCILED means the failed attempt committed
|
|
670
|
+
// after all, and the writer turns that into `payment-reconciled-existing` with no
|
|
671
|
+
// second write -- which is the outcome, not a failure.
|
|
672
|
+
reconciled = await w.reconcileOrderPaymentTarget(wix, { orderId, payment, pacer });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
outcomes.payment = written.outcome;
|
|
676
|
+
paymentId = written.paymentId || null;
|
|
677
|
+
if (written.error) errors.payment = describeError(written.error);
|
|
678
|
+
// "Written" with no id is not written: the refund path would see CREATE still standing and
|
|
679
|
+
// add the payment a second time. Demonstrated in review with an empty `paymentsIds`.
|
|
680
|
+
if (outcomes.payment === 'payment-written' && !paymentId) {
|
|
681
|
+
outcomes.payment = 'payment-failed';
|
|
682
|
+
errors.payment = { cause: 'add-payment-returned-no-id' };
|
|
683
|
+
}
|
|
684
|
+
} catch (error) {
|
|
685
|
+
outcomes.payment = 'payment-failed';
|
|
686
|
+
errors.payment = describeError(error);
|
|
687
|
+
}
|
|
688
|
+
// No payment id means no payment to hang anything off, whatever the outcome's name: ambiguous
|
|
689
|
+
// reconciliation carries no error object from the writer, so the cause is recorded here.
|
|
690
|
+
if (!paymentId && !errors.payment) errors.payment = { cause: outcomes.payment };
|
|
691
|
+
// The refund path sees the same read, updated with the payment this run just created so it
|
|
692
|
+
// does not create a second one. A payment stage that failed leaves nothing to refund against.
|
|
693
|
+
const forRefunds = paymentId && reconciled.outcome === paymentMapping.RECONCILE.CREATE
|
|
694
|
+
? { ...reconciled, outcome: paymentMapping.RECONCILE.RECONCILED, paymentId, matchedBy: 'written-this-run' }
|
|
695
|
+
: reconciled;
|
|
696
|
+
for (const refund of refunds) {
|
|
697
|
+
const line = { sourceId: refund.sourceId, sourceOccurrence: refund.sourceOccurrence, outcome: null, refundId: null, paymentId: null };
|
|
698
|
+
if (!paymentId) {
|
|
699
|
+
// Refunds are REFUND outcomes. Letting the writer answer here returned the payment stage's
|
|
700
|
+
// own code as the refund's, which the report counts as a payment bucket and never as a
|
|
701
|
+
// refund that did not land.
|
|
702
|
+
line.outcome = 'refund-failed';
|
|
703
|
+
line.error = { cause: outcomes.payment };
|
|
704
|
+
} else {
|
|
705
|
+
try {
|
|
706
|
+
const result = await w.ensureOrderPaymentAndRefund(wix, {
|
|
707
|
+
orderId,
|
|
708
|
+
payment,
|
|
709
|
+
refundAmount: refund.amount,
|
|
710
|
+
reason: refund.reason,
|
|
711
|
+
sourceRefundId: refund.sourceId,
|
|
712
|
+
sourceOccurrence: refund.sourceOccurrence,
|
|
713
|
+
pacer,
|
|
714
|
+
reconciled: forRefunds,
|
|
715
|
+
});
|
|
716
|
+
line.outcome = result.outcome;
|
|
717
|
+
line.refundId = result.refundId || (result.refund && result.refund.id) || null;
|
|
718
|
+
line.paymentId = result.paymentId || null;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
line.outcome = 'refund-failed';
|
|
721
|
+
line.error = describeError(error);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
outcomes.refunds.push(line);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ---- 3. The invoice document. ---------------------------------------------------------------
|
|
729
|
+
let invoice = null;
|
|
730
|
+
if (!evidence.invoice) {
|
|
731
|
+
outcomes.invoice = 'invoice-none-found';
|
|
732
|
+
} else {
|
|
733
|
+
// A document that reached Media but whose order patch failed is remembered HERE, in the same
|
|
734
|
+
// persisted state as the order id, not by the caller: a generated importer following the
|
|
735
|
+
// documented loop without persisting it imported a second copy in review. The caller may still
|
|
736
|
+
// pass one explicitly (an older project's state); the crosswalk's answer is the fallback.
|
|
737
|
+
let pendingMediaFileId = evidence.invoice.pendingMediaFileId || null;
|
|
738
|
+
let rememberedPending = null;
|
|
739
|
+
if (!pendingMediaFileId) {
|
|
740
|
+
try {
|
|
741
|
+
const remembered = await crosswalk.getInvoiceFile(sourceId);
|
|
742
|
+
// Either a bare id (older rows) or { fileId, sourceUrl }. A remembered file is only THIS
|
|
743
|
+
// document's if it was imported from the same URL: a re-issued invoice under a new link
|
|
744
|
+
// must not be "resumed" onto the old bytes (review demonstrated it).
|
|
745
|
+
const rememberedId = typeof remembered === 'string' ? remembered : remembered && remembered.fileId;
|
|
746
|
+
const rememberedUrl = remembered && typeof remembered === 'object' ? remembered.sourceUrl || null : null;
|
|
747
|
+
if (rememberedId && String(rememberedId).trim() !== '') {
|
|
748
|
+
if (!rememberedUrl || rememberedUrl === (evidence.invoice.sourceUrl || null)) { pendingMediaFileId = String(rememberedId); rememberedPending = pendingMediaFileId; }
|
|
749
|
+
else findings.push({ code: 'invoice-pending-file-superseded', sourceId: String(sourceId), note: 'a remembered Media file was imported from a different URL and was not resumed' });
|
|
750
|
+
}
|
|
751
|
+
} catch (error) {
|
|
752
|
+
errors.crosswalk = errors.crosswalk || describeError(error);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
invoice = await w.preserveOrderInvoiceDocument(wix, {
|
|
757
|
+
orderId,
|
|
758
|
+
sourceUrl: evidence.invoice.sourceUrl,
|
|
759
|
+
invoiceNumber: evidence.invoice.invoiceNumber,
|
|
760
|
+
approval: evidence.invoice.approval,
|
|
761
|
+
setupVerification,
|
|
762
|
+
sourceSiteHost,
|
|
763
|
+
pendingMediaFileId,
|
|
764
|
+
});
|
|
765
|
+
outcomes.invoice = invoice.outcome;
|
|
766
|
+
// Remember a READY-but-unrecorded file (bound to its URL); CLEAR the memory once the document
|
|
767
|
+
// is settled or the remembered import is dead. Left in place, a FAILED file id was resumed
|
|
768
|
+
// forever and the document never imported (review finding).
|
|
769
|
+
const settled = ['invoice-preserved', 'invoice-already-preserved', 'invoice-number-only', 'invoice-import-failed'].includes(invoice.outcome);
|
|
770
|
+
try {
|
|
771
|
+
if (invoice.pendingMediaFileId && invoice.pendingMediaFileId !== pendingMediaFileId) {
|
|
772
|
+
await crosswalk.setInvoiceFile(sourceId, invoice.pendingMediaFileId, { sourceUrl: evidence.invoice.sourceUrl || null });
|
|
773
|
+
} else if (settled && (rememberedPending || (invoice.outcome === 'invoice-import-failed' && pendingMediaFileId))) {
|
|
774
|
+
await crosswalk.setInvoiceFile(sourceId, null, { sourceUrl: null });
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
errors.crosswalk = errors.crosswalk || describeError(error);
|
|
778
|
+
}
|
|
779
|
+
} catch (error) {
|
|
780
|
+
outcomes.invoice = 'invoice-failed';
|
|
781
|
+
errors.invoice = describeError(error);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return { orderId, derivedId, outcomes, findings, errors, paymentId, invoice };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// A crosswalk over the project's own state/crosswalk/crosswalk.ndjson, in the row shape and key
|
|
789
|
+
// convention the rest of the runtime uses (`<sourceSystem>:<sourceEntityType>:<sourceId>`, as in
|
|
790
|
+
// lib/import-recovery.js), so recovery tooling sees these rows and these rows see its. Loaded
|
|
791
|
+
// once, appended per order (O(1) -- the last row per key wins), so a large store does not rewrite
|
|
792
|
+
// its whole crosswalk seven thousand times.
|
|
793
|
+
//
|
|
794
|
+
// The loader is deliberately NOT lib/local-state's: that one throws on any unparsable line and on
|
|
795
|
+
// any invalid row of any entity type, which turns a process killed mid-append -- the crash this
|
|
796
|
+
// module exists to survive -- into a run that fails every order after it. Here a TRAILING partial
|
|
797
|
+
// line (no newline terminator, not parseable) is dropped and reported; any other bad line still
|
|
798
|
+
// throws, because that is corruption, not a crash.
|
|
799
|
+
function createProjectOrderCrosswalk({ projectDir, sourceSystem = 'wordpress', sourceEntityType = 'order' } = {}) {
|
|
800
|
+
if (!projectDir) throw new Error('createProjectOrderCrosswalk needs the migration projectDir');
|
|
801
|
+
const filePath = localState.crosswalkPath(projectDir);
|
|
802
|
+
const pendingPath = path.join(path.dirname(filePath), 'pending-invoice-files.ndjson');
|
|
803
|
+
const key = (sourceId) => `${sourceSystem}:${sourceEntityType}:${String(sourceId)}`;
|
|
804
|
+
const index = new Map();
|
|
805
|
+
// Bytes of COMPLETE lines already indexed. The file is append-only, so a refresh reads from here
|
|
806
|
+
// rather than re-parsing seven thousand rows -- and a MISS always refreshes first, because
|
|
807
|
+
// another process (or lib/local-state's upsert) may have appended a row since we last looked.
|
|
808
|
+
// Review demonstrated the alternative: a cached index returning null for a legacy row written
|
|
809
|
+
// moments earlier, and a second order imported.
|
|
810
|
+
let offset = 0;
|
|
811
|
+
let lineCount = 0;
|
|
812
|
+
let loaded = false;
|
|
813
|
+
let inode = null;
|
|
814
|
+
const state = { droppedPartialLine: null, droppedPendingPartialLine: null };
|
|
815
|
+
|
|
816
|
+
// The file is append-only for THIS adapter, but lib/local-state's upsertCrosswalkRow rewrites
|
|
817
|
+
// and compacts it (temp file + rename). A rewrite that shrank the file below our offset made the
|
|
818
|
+
// incremental read return nothing and a legacy row stay invisible -- review demonstrated a
|
|
819
|
+
// second order imported that way. So every refresh stats first: a new inode or a smaller file
|
|
820
|
+
// means "start over", not "nothing new".
|
|
821
|
+
async function statFile() {
|
|
822
|
+
try {
|
|
823
|
+
const st = await fs.stat(filePath);
|
|
824
|
+
return { size: st.size, ino: st.ino };
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (error && error.code === 'ENOENT') return { size: 0, ino: null };
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function readFrom(position) {
|
|
832
|
+
let handle;
|
|
833
|
+
try {
|
|
834
|
+
handle = await fs.open(filePath, 'r');
|
|
835
|
+
} catch (error) {
|
|
836
|
+
if (error && error.code === 'ENOENT') return '';
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
const { size } = await handle.stat();
|
|
841
|
+
if (size <= position) return '';
|
|
842
|
+
const buffer = Buffer.alloc(size - position);
|
|
843
|
+
await handle.read(buffer, 0, buffer.length, position);
|
|
844
|
+
return buffer;
|
|
845
|
+
} finally {
|
|
846
|
+
await handle.close();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function refresh() {
|
|
851
|
+
const st = await statFile();
|
|
852
|
+
if ((inode !== null && st.ino !== inode) || st.size < offset) {
|
|
853
|
+
index.clear();
|
|
854
|
+
offset = 0;
|
|
855
|
+
lineCount = 0;
|
|
856
|
+
}
|
|
857
|
+
inode = st.ino;
|
|
858
|
+
const chunkBuffer = await readFrom(offset);
|
|
859
|
+
if (!chunkBuffer || chunkBuffer.length === 0) return;
|
|
860
|
+
// Split on the BYTE, not the decoded string: a tail torn inside a multibyte character decodes
|
|
861
|
+
// to U+FFFD and mis-measures the bytes to truncate (review demonstrated a glued, fatal file).
|
|
862
|
+
const lastNewline = chunkBuffer.lastIndexOf(0x0a);
|
|
863
|
+
const completeBytes = lastNewline === -1 ? 0 : lastNewline + 1;
|
|
864
|
+
const tailBytes = chunkBuffer.length - completeBytes;
|
|
865
|
+
const parts = completeBytes > 0 ? chunkBuffer.subarray(0, completeBytes).toString('utf8').split('\n') : [''];
|
|
866
|
+
if (completeBytes > 0) parts.pop(); // the trailing '' after the final newline
|
|
867
|
+
const tail = tailBytes > 0 ? chunkBuffer.subarray(completeBytes).toString('utf8') : '';
|
|
868
|
+
for (const raw of parts) {
|
|
869
|
+
lineCount += 1;
|
|
870
|
+
const line = raw.replace(/\r$/, '').trim();
|
|
871
|
+
if (!line) continue;
|
|
872
|
+
let row;
|
|
873
|
+
try {
|
|
874
|
+
row = JSON.parse(line);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
throw new Error(`${filePath}:${lineCount} invalid NDJSON: ${error.message}`);
|
|
877
|
+
}
|
|
878
|
+
if (row && row.targetEntityType === 'ecom/order' && typeof row.sourceStableKey === 'string' && typeof row.targetId === 'string') {
|
|
879
|
+
index.set(row.sourceStableKey, row.targetId);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
offset += completeBytes;
|
|
883
|
+
if (tailBytes > 0 && tail.trim()) {
|
|
884
|
+
// A last line with no newline: either a complete row from a writer that omits the
|
|
885
|
+
// terminator, or a line torn by a kill mid-append. A torn line is TRUNCATED on disk, not just
|
|
886
|
+
// skipped -- skipped, the next append would glue a row onto it and corrupt the file for every
|
|
887
|
+
// later reader (review demonstrated this too).
|
|
888
|
+
let row = null;
|
|
889
|
+
try { row = JSON.parse(tail.trim()); } catch (error) { row = null; }
|
|
890
|
+
if (row) {
|
|
891
|
+
// A complete row another writer left unterminated. Indexed; the terminator is added by
|
|
892
|
+
// set() just before our next append, so the file is never touched only to tidy it.
|
|
893
|
+
lineCount += 1;
|
|
894
|
+
if (row.targetEntityType === 'ecom/order' && typeof row.sourceStableKey === 'string' && typeof row.targetId === 'string') {
|
|
895
|
+
index.set(row.sourceStableKey, row.targetId);
|
|
896
|
+
}
|
|
897
|
+
offset += tailBytes;
|
|
898
|
+
needsTerminator = true;
|
|
899
|
+
} else {
|
|
900
|
+
// Truncate only if nothing has been appended since we read: a neighbour's row landing
|
|
901
|
+
// between the read and the truncate would otherwise be deleted. One process per project
|
|
902
|
+
// crosswalk is the supported model (lib/local-state has a state lock for the runtime); this
|
|
903
|
+
// check narrows the window, it does not close it.
|
|
904
|
+
const now = await statFile();
|
|
905
|
+
if (now.ino === inode && now.size === offset + tailBytes) {
|
|
906
|
+
await fs.truncate(filePath, offset);
|
|
907
|
+
state.droppedPartialLine = { line: lineCount + 1, length: tail.length };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
let needsTerminator = false;
|
|
913
|
+
|
|
914
|
+
// The pending-invoice sibling file gets the SAME crash discipline as the main crosswalk. Review
|
|
915
|
+
// demonstrated the alternative: a row torn by a kill, the next append glued onto it, one
|
|
916
|
+
// unparsable line holding two pending file ids, both silently ignored -- and a tax document
|
|
917
|
+
// imported twice. A torn TAIL is truncated on disk; a bad line anywhere else is corruption and
|
|
918
|
+
// throws; a complete row left unterminated is read and terminated before the next append.
|
|
919
|
+
async function readPendingRows() {
|
|
920
|
+
let buffer;
|
|
921
|
+
try {
|
|
922
|
+
buffer = await fs.readFile(pendingPath);
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (!error || error.code !== 'ENOENT') throw error;
|
|
925
|
+
return { rows: [], needsTerminator: false };
|
|
926
|
+
}
|
|
927
|
+
const lastNewline = buffer.lastIndexOf(0x0a);
|
|
928
|
+
const completeBytes = lastNewline === -1 ? 0 : lastNewline + 1;
|
|
929
|
+
const complete = completeBytes > 0 ? buffer.subarray(0, completeBytes).toString('utf8').split('\n') : [];
|
|
930
|
+
if (complete.length > 0) complete.pop(); // the '' after the final newline
|
|
931
|
+
const rows = [];
|
|
932
|
+
complete.forEach((raw, i) => {
|
|
933
|
+
const line = raw.replace(/\r$/, '').trim();
|
|
934
|
+
if (!line) return;
|
|
935
|
+
try { rows.push(JSON.parse(line)); } catch (error) { throw new Error(`${pendingPath}:${i + 1} invalid NDJSON: ${error.message}`); }
|
|
936
|
+
});
|
|
937
|
+
const tail = buffer.subarray(completeBytes).toString('utf8');
|
|
938
|
+
let pendingNeedsTerminator = false;
|
|
939
|
+
if (tail.trim()) {
|
|
940
|
+
let row = null;
|
|
941
|
+
try { row = JSON.parse(tail.trim()); } catch (error) { row = null; }
|
|
942
|
+
if (row) {
|
|
943
|
+
rows.push(row);
|
|
944
|
+
pendingNeedsTerminator = true;
|
|
945
|
+
} else {
|
|
946
|
+
await fs.truncate(pendingPath, completeBytes);
|
|
947
|
+
state.droppedPendingPartialLine = { line: complete.length + 1, length: tail.length };
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return { rows, needsTerminator: pendingNeedsTerminator };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function ensureLoaded() {
|
|
954
|
+
if (!loaded) {
|
|
955
|
+
await refresh();
|
|
956
|
+
loaded = true;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
async get(sourceId) {
|
|
962
|
+
await ensureLoaded();
|
|
963
|
+
const hit = index.get(key(sourceId));
|
|
964
|
+
if (hit) return hit;
|
|
965
|
+
await refresh();
|
|
966
|
+
return index.get(key(sourceId)) || null;
|
|
967
|
+
},
|
|
968
|
+
async set(sourceId, targetId) {
|
|
969
|
+
await ensureLoaded();
|
|
970
|
+
if (needsTerminator) {
|
|
971
|
+
await fs.appendFile(filePath, '\n', 'utf8');
|
|
972
|
+
needsTerminator = false;
|
|
973
|
+
}
|
|
974
|
+
await localState.appendCrosswalkRow(projectDir, {
|
|
975
|
+
schemaVersion: localState.SCHEMA_VERSION,
|
|
976
|
+
sourceSystem,
|
|
977
|
+
sourceEntityType,
|
|
978
|
+
sourceId: String(sourceId),
|
|
979
|
+
sourceStableKey: key(sourceId),
|
|
980
|
+
targetSystem: 'wix',
|
|
981
|
+
targetEntityType: 'ecom/order',
|
|
982
|
+
targetId: String(targetId),
|
|
983
|
+
status: 'imported',
|
|
984
|
+
updatedAt: new Date().toISOString(),
|
|
985
|
+
});
|
|
986
|
+
index.set(key(sourceId), String(targetId));
|
|
987
|
+
await refresh(); // pick up our own row's bytes (and anything a neighbour appended)
|
|
988
|
+
},
|
|
989
|
+
// A Media file that reached READY but whose order patch failed, remembered per source order so
|
|
990
|
+
// the resume records THAT file rather than importing another. Kept in a sibling file, read in
|
|
991
|
+
// full on every ask: it only ever holds the failures, so it stays small, and a stale in-memory
|
|
992
|
+
// copy here would cost a duplicated tax document.
|
|
993
|
+
async getInvoiceFile(sourceId) {
|
|
994
|
+
const { rows } = await readPendingRows();
|
|
995
|
+
let found = null;
|
|
996
|
+
for (const row of rows) {
|
|
997
|
+
if (row && row.sourceStableKey === key(sourceId)) found = row.fileId ? { fileId: row.fileId, sourceUrl: row.sourceUrl || null } : null; // last row wins; a null row clears
|
|
998
|
+
}
|
|
999
|
+
return found;
|
|
1000
|
+
},
|
|
1001
|
+
async setInvoiceFile(sourceId, fileId, { sourceUrl = null } = {}) {
|
|
1002
|
+
// Repair first: a torn tail is truncated and an unterminated complete row gets its newline,
|
|
1003
|
+
// so this append always starts a clean line.
|
|
1004
|
+
const { needsTerminator } = await readPendingRows();
|
|
1005
|
+
await fs.mkdir(path.dirname(pendingPath), { recursive: true });
|
|
1006
|
+
if (needsTerminator) await fs.appendFile(pendingPath, '\n', 'utf8');
|
|
1007
|
+
await fs.appendFile(pendingPath, `${JSON.stringify({ sourceStableKey: key(sourceId), fileId: fileId === null ? null : String(fileId), sourceUrl: sourceUrl === null ? null : String(sourceUrl), updatedAt: new Date().toISOString() })}\n`, 'utf8');
|
|
1008
|
+
},
|
|
1009
|
+
// What the loaders had to drop, if anything -- a caller may want to report it.
|
|
1010
|
+
droppedPartialLine() { return state.droppedPartialLine; },
|
|
1011
|
+
droppedPendingPartialLine() { return state.droppedPendingPartialLine; },
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
module.exports = {
|
|
1016
|
+
ORDER_OUTCOMES,
|
|
1017
|
+
EVIDENCE_KEYS,
|
|
1018
|
+
REFUND_KEYS,
|
|
1019
|
+
INVOICE_KEYS,
|
|
1020
|
+
deriveImportedOrderId,
|
|
1021
|
+
normalizeSourceSiteHost,
|
|
1022
|
+
assertEvidence,
|
|
1023
|
+
checkOrderContactFields,
|
|
1024
|
+
identifyRejectedContactFields,
|
|
1025
|
+
importOrderHistory,
|
|
1026
|
+
createProjectOrderCrosswalk,
|
|
1027
|
+
};
|