swipium 1.5.0 → 2.0.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/CHANGELOG.md +171 -0
- package/README.md +173 -280
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +30 -27
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +47 -21
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +2 -2
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +3 -3
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +2 -2
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +2 -2
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/issues.js +10 -10
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +5 -5
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +77 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +2 -2
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +3 -3
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +1 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +17 -17
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +55 -7
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +3 -3
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +5 -5
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +10 -10
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +3 -3
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +1 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +7 -7
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +3 -3
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +14 -25
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +5 -5
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +2 -2
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +13 -7
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +5 -5
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +265 -106
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +71 -26
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +276 -105
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +6 -4
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +20 -8
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +2 -2
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +7 -7
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +35 -14
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +6 -6
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +45 -14
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +6 -6
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +3 -3
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +65 -62
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +300 -36
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +120 -7
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +350 -50
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +2 -2
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +5 -5
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/policy.js +5 -5
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +301 -284
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +1 -1
- package/dist/featureTesting/executionBootstrap.js +29 -30
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +2 -2
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +12 -12
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +7 -7
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +10 -10
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +6 -6
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -4
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +1 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +1 -1
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +11 -11
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +3 -3
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +5 -5
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +11 -11
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +8 -8
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +9 -9
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +5 -5
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +33 -3
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +20 -10
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/pack.js +3 -3
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +111 -23
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +153 -57
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +50 -5
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +4 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/issues/classify.js +16 -16
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +24 -4
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +94 -31
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +4 -4
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +4 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +4 -4
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +75 -7
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +32 -7
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +1 -1
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +370 -31
- package/dist/lib/lockfile.js.map +1 -1
- package/dist/lib/needsInput.js +3 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -2
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +79 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +3 -3
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -2
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +52 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +294 -15
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/checks.js +12 -12
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +1 -1
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +3 -3
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +72 -18
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +219 -36
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +63 -17
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +13 -13
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +4 -4
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +62 -21
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +119 -30
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js +162 -73
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +34 -16
- package/dist/orchestration/testThis/terminal.js.map +1 -1
- package/dist/orchestration/testThis/types.js +1 -1
- package/dist/orchestration/testThis/types.js.map +1 -1
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +22 -22
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +392 -37
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/policy.js +7 -4
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +8 -8
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +472 -45
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +2 -2
- package/dist/services/build.js.map +1 -1
- package/dist/{tools → services}/flowGenerate.js +29 -13
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +4 -4
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -8
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +24 -9
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +100 -45
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +32 -16
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +28 -3
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +40 -27
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +270 -6
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +317 -48
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/session/progress.js +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +275 -48
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +53 -8
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +28 -4
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +11 -5
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +46 -2
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +9 -5
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +4 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +50 -41
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +9 -7
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +4 -4
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +13 -9
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +5 -5
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +5 -5
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +3 -3
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +794 -164
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +451 -128
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +69 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +188 -130
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +32 -42
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +21 -31
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +55 -12
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +76 -24
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +68 -28
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +41 -47
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +31 -48
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +34 -41
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +183 -103
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +25 -13
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +52 -89
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +16 -5
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +15 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +45 -223
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +251 -35
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +41 -16
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +36 -35
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +50 -45
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +31 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +26 -26
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +10 -16
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +117 -50
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +11 -22
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +11 -15
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +78 -24
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +23 -21
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -16
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +20 -16
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +155 -118
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +104 -116
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +72 -43
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +30 -20
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +24 -28
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +686 -210
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -38
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +187 -57
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +31 -22
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +53 -6
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +80 -12
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +194 -128
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +812 -152
- package/package.json +3 -2
- package/dist/tools/assertVisual.js +0 -84
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -179
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -86
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/locator.js +0 -85
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/permissions.js +0 -174
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -52
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -76
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -133
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -217
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -106
- package/dist/tools/visualText.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Shared screen-relative gesture geometry. qa_act and the flow runner derive
|
|
2
|
+
// swipe/scroll vectors the SAME way: anchored as fractions of the real screen size and
|
|
3
|
+
// clamped on-screen, with fixed legacy coordinates ONLY as the fallback when the driver
|
|
4
|
+
// cannot report a size. WDA swipes are in POINTS (a modern iPhone is ≤~440pt wide), so any
|
|
5
|
+
// fixed pixel constant is off-screen there.
|
|
6
|
+
/** ~8% edge inset used by qa_act gestures. Stays clear of the iOS system-gesture zones
|
|
7
|
+
* (home indicator, notification/control-center edges). Flow replay keeps inset 0 so
|
|
8
|
+
* existing recorded flows keep their historical [1, size-1] clamp. */
|
|
9
|
+
export const GESTURE_EDGE_INSET = 0.08;
|
|
10
|
+
/** Legacy fixed-coordinate vectors, used ONLY when screenSize() is unknown. */
|
|
11
|
+
export const FALLBACK_SWIPE = {
|
|
12
|
+
up: [540, 1500, 540, 600],
|
|
13
|
+
down: [540, 600, 540, 1500],
|
|
14
|
+
left: [800, 1100, 200, 1100],
|
|
15
|
+
right: [200, 1100, 800, 1100],
|
|
16
|
+
};
|
|
17
|
+
export const AREA_ANCHOR = {
|
|
18
|
+
center: [0.5, 0.5],
|
|
19
|
+
top: [0.5, 0.3],
|
|
20
|
+
bottom: [0.5, 0.7],
|
|
21
|
+
left: [0.3, 0.5],
|
|
22
|
+
right: [0.7, 0.5],
|
|
23
|
+
};
|
|
24
|
+
/** Legacy swipe travel in px when the screen size is unknown (the old qa_act constant). */
|
|
25
|
+
const FALLBACK_DISTANCE = 800;
|
|
26
|
+
function clamper(size, insetFrac) {
|
|
27
|
+
const insetX = Math.max(1, Math.round(size.width * insetFrac));
|
|
28
|
+
const insetY = Math.max(1, Math.round(size.height * insetFrac));
|
|
29
|
+
return {
|
|
30
|
+
x: (x) => Math.max(insetX, Math.min(size.width - insetX, Math.round(x))),
|
|
31
|
+
y: (y) => Math.max(insetY, Math.min(size.height - insetY, Math.round(y))),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Device-relative swipe vector (fractions of the screen), falling back to fixed coords if no size.
|
|
35
|
+
* insetFrac keeps every endpoint at least that fraction of the screen away from each edge;
|
|
36
|
+
* the default 0 preserves the flow runner's historical [1, size-1] clamp. */
|
|
37
|
+
export function swipeVector(size, dir, area = 'center', distance = 0.6, insetFrac = 0) {
|
|
38
|
+
if (!size)
|
|
39
|
+
return FALLBACK_SWIPE[dir];
|
|
40
|
+
const [ax, ay] = AREA_ANCHOR[area];
|
|
41
|
+
const cx = size.width * ax;
|
|
42
|
+
const cy = size.height * ay;
|
|
43
|
+
const vd = (size.height * distance) / 2;
|
|
44
|
+
const hd = (size.width * distance) / 2;
|
|
45
|
+
const clamp = clamper(size, insetFrac);
|
|
46
|
+
const v = {
|
|
47
|
+
up: [cx, cy + vd, cx, cy - vd],
|
|
48
|
+
down: [cx, cy - vd, cx, cy + vd],
|
|
49
|
+
left: [cx + hd, cy, cx - hd, cy],
|
|
50
|
+
right: [cx - hd, cy, cx + hd, cy],
|
|
51
|
+
}[dir];
|
|
52
|
+
return [clamp.x(v[0]), clamp.y(v[1]), clamp.x(v[2]), clamp.y(v[3])];
|
|
53
|
+
}
|
|
54
|
+
/** Swipe starting at an explicit point (a resolved qa_act target). The start is used
|
|
55
|
+
* VERBATIM (it is a real element the caller asked to swipe from, and 0 is a legitimate
|
|
56
|
+
* coordinate), while the derived end point is clamped on-screen. */
|
|
57
|
+
export function swipeFromPoint(size, from, dir, distance = 0.5, insetFrac = 0) {
|
|
58
|
+
const dist = size ? (dir === 'up' || dir === 'down' ? size.height : size.width) * distance : FALLBACK_DISTANCE;
|
|
59
|
+
const to = {
|
|
60
|
+
up: { x: from.x, y: from.y - dist },
|
|
61
|
+
down: { x: from.x, y: from.y + dist },
|
|
62
|
+
left: { x: from.x - dist, y: from.y },
|
|
63
|
+
right: { x: from.x + dist, y: from.y },
|
|
64
|
+
}[dir];
|
|
65
|
+
if (!size)
|
|
66
|
+
return [from.x, from.y, Math.round(to.x), Math.round(to.y)];
|
|
67
|
+
const clamp = clamper(size, insetFrac);
|
|
68
|
+
return [from.x, from.y, clamp.x(to.x), clamp.y(to.y)];
|
|
69
|
+
}
|
|
70
|
+
/** Fraction of a scrollable container kept clear on each side when a scroll is anchored in it.
|
|
71
|
+
* keeps the finger off sticky headers/footers that sit flush with the container's edges. */
|
|
72
|
+
export const SCROLL_CONTAINER_INSET = 0.1;
|
|
73
|
+
/** Minimum on-screen extent (px/pt) a scrollable container needs on each axis to anchor a swipe. */
|
|
74
|
+
const MIN_SCROLL_CONTAINER = 48;
|
|
75
|
+
/** PURE: the visible rect of the LARGEST scrollable node, or null when there is none worth using.
|
|
76
|
+
* Android marks containers `scrollable="true"`; the WDA source normalizer maps
|
|
77
|
+
* ScrollView/Table/CollectionView to the same attribute. A clipped Android node can report
|
|
78
|
+
* inverted bounds (e.g. `[210,315][750,124]`, its visible area is empty): such nodes are
|
|
79
|
+
* ignored, and every rect is clipped to the screen when the size is known. */
|
|
80
|
+
export function largestScrollableRect(nodes, size) {
|
|
81
|
+
let best = null;
|
|
82
|
+
let bestArea = 0;
|
|
83
|
+
for (const n of nodes ?? []) {
|
|
84
|
+
if (!n.scrollable)
|
|
85
|
+
continue;
|
|
86
|
+
let [x1, y1, x2, y2] = n.bounds;
|
|
87
|
+
if (size) {
|
|
88
|
+
x1 = Math.max(0, x1);
|
|
89
|
+
y1 = Math.max(0, y1);
|
|
90
|
+
x2 = Math.min(size.width, x2);
|
|
91
|
+
y2 = Math.min(size.height, y2);
|
|
92
|
+
}
|
|
93
|
+
// inverted / collapsed after clipping > not visible, can't be a gesture anchor
|
|
94
|
+
if (x2 - x1 < MIN_SCROLL_CONTAINER || y2 - y1 < MIN_SCROLL_CONTAINER)
|
|
95
|
+
continue;
|
|
96
|
+
const area = (x2 - x1) * (y2 - y1);
|
|
97
|
+
if (area > bestArea) {
|
|
98
|
+
bestArea = area;
|
|
99
|
+
best = [x1, y1, x2, y2];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return best;
|
|
103
|
+
}
|
|
104
|
+
/** Swipe vector anchored INSIDE a scrollable container: centered on the container, travel =
|
|
105
|
+
* `distance` × the container's extent on the swipe axis, both endpoints kept `containerInset`
|
|
106
|
+
* inside the container AND (when possible) `screenInset` inside the screen. A swipe that starts
|
|
107
|
+
* on a sticky app bar above the list moves nothing. That's why the container matters. */
|
|
108
|
+
export function swipeInRect(rect, dir, size, distance = 0.6, containerInset = SCROLL_CONTAINER_INSET, screenInset = 0) {
|
|
109
|
+
const [x1, y1, x2, y2] = rect;
|
|
110
|
+
const w = x2 - x1;
|
|
111
|
+
const h = y2 - y1;
|
|
112
|
+
const cx = x1 + w / 2;
|
|
113
|
+
const cy = y1 + h / 2;
|
|
114
|
+
const axisRange = (lo, hi, extent, screen) => {
|
|
115
|
+
const inset = Math.max(1, Math.round(extent * containerInset));
|
|
116
|
+
let a = lo + inset;
|
|
117
|
+
let b = hi - inset;
|
|
118
|
+
if (screen != null && screenInset > 0) {
|
|
119
|
+
const si = Math.max(1, Math.round(screen * screenInset));
|
|
120
|
+
const sa = Math.max(a, si);
|
|
121
|
+
const sb = Math.min(b, screen - si);
|
|
122
|
+
if (sb > sa)
|
|
123
|
+
[a, b] = [sa, sb]; // only when the two insets still leave room
|
|
124
|
+
}
|
|
125
|
+
return [a, b];
|
|
126
|
+
};
|
|
127
|
+
const [minX, maxX] = axisRange(x1, x2, w, size?.width);
|
|
128
|
+
const [minY, maxY] = axisRange(y1, y2, h, size?.height);
|
|
129
|
+
const cX = (x) => Math.round(Math.max(minX, Math.min(maxX, x)));
|
|
130
|
+
const cY = (y) => Math.round(Math.max(minY, Math.min(maxY, y)));
|
|
131
|
+
const vd = (h * distance) / 2;
|
|
132
|
+
const hd = (w * distance) / 2;
|
|
133
|
+
const v = {
|
|
134
|
+
up: [cx, cy + vd, cx, cy - vd],
|
|
135
|
+
down: [cx, cy - vd, cx, cy + vd],
|
|
136
|
+
left: [cx + hd, cy, cx - hd, cy],
|
|
137
|
+
right: [cx - hd, cy, cx + hd, cy],
|
|
138
|
+
}[dir];
|
|
139
|
+
return [cX(v[0]), cY(v[1]), cX(v[2]), cY(v[3])];
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=gestures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../src/lib/gestures.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/gitignore.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// Keep Swipium's generated artifacts out of the user's VCS.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Keep Swipium's generated artifacts out of the user's VCS. Called when Swipium writes the app
|
|
2
|
+
// map (src/appMap/store.ts) or the issue ledger (src/issues/store.ts), so the first such write
|
|
3
|
+
// appends `.swipium/` to the project's .gitignore. `swipium scan` itself does not call this.
|
|
4
|
+
// Idempotent; only touches a real git repo's .gitignore (it never runs Git).
|
|
4
5
|
import { existsSync, readFileSync, appendFileSync } from 'node:fs';
|
|
5
6
|
import { join } from 'node:path';
|
|
6
7
|
export function ensureGitignored(root) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,+FAA+F;AAC/F,6FAA6F;AAC7F,6EAA6E;AAE7E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,YAAY,CAAC;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACxD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,cAAc,CAAC,EAAE,EAAE,GAAG,MAAM,mDAAmD,CAAC,CAAC;QACjF,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC,CAAC,+DAA+D;IACtF,CAAC;AACH,CAAC"}
|
package/dist/lib/image.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Local, deterministic visual ops
|
|
1
|
+
// Local, deterministic visual ops: baseline diff + template matching on
|
|
2
2
|
// decoded pixels. No AI, no network, no third-party code: just arithmetic over PNG bytes.
|
|
3
3
|
import { decodePng, toGray } from './png.js';
|
|
4
4
|
/** Per-pixel diff of two same-size screenshots. A pixel "changed" if any channel differs by > tol. */
|
package/dist/lib/image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/lockfile.js
CHANGED
|
@@ -1,60 +1,399 @@
|
|
|
1
1
|
// Minimal advisory file lock for cross-process coordination on the small shared JSON files
|
|
2
|
-
// under ~/.swipium (registry.json, processes.json). Two concurrent
|
|
3
|
-
// common (multiple MCP clients), and an unlocked read-modify-write lets them
|
|
4
|
-
// other's entries. The lock is a DIRECTORY (mkdir is atomic on POSIX and Windows):
|
|
5
|
-
// creates it holds it.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
2
|
+
// under ~/.swipium (registry.json, processes.json) and .swipium/app-map.json. Two concurrent
|
|
3
|
+
// server instances are common (multiple MCP clients), and an unlocked read-modify-write lets them
|
|
4
|
+
// clobber each other's entries. The lock is a DIRECTORY (mkdir is atomic on POSIX and Windows):
|
|
5
|
+
// whoever creates it holds it. Each acquisition writes an OWNER file (`<lockPath>/owner` =
|
|
6
|
+
// `pid:token`, created exclusively) so release can verify the lock is still ours. A holder that
|
|
7
|
+
// stalled past the stale threshold and was taken over must NOT delete the new owner's lock.
|
|
8
|
+
//
|
|
9
|
+
// Stale takeover (holder crashed mid-write) is CLAIM-then-MOVE (H8, review round 2):
|
|
10
|
+
// 1. claim: the taker creates `<lockPath>/.takeover` EXCLUSIVELY ('wx', with its own token).
|
|
11
|
+
// Only one taker can claim a given lock-directory instance, and the lock path never
|
|
12
|
+
// disappears during a failed claim, so a newcomer can't slip in;
|
|
13
|
+
// 2. verify: the claimed directory must still be exactly the lock it judged stale (same owner,
|
|
14
|
+
// still old before the claim, same inode after it). If a racing waiter already replaced it with a FRESH lock, the claim is dropped
|
|
15
|
+
// and the fresh lock is left untouched (it never moved);
|
|
16
|
+
// 3. move: rename the lock to a unique tombstone (`<lockPath>.stale-<pid>-<uuid>`), confirm the
|
|
17
|
+
// tombstone carries OUR claim (i.e. we moved the instance we verified), delete it, retry mkdir.
|
|
18
|
+
// Residual race: between verify and move the path can only change if the presumed-dead holder
|
|
19
|
+
// wakes up and releases, and a new holder re-creates the lock, inside that window. The post-move
|
|
20
|
+
// claim check catches it: the moved lock is renamed back. If that restore fails because yet
|
|
21
|
+
// another contender already created the path, the taker does NOT proceed (takeover failed > it
|
|
22
|
+
// goes back to waiting) and does NOT delete the moved lock. It stays as a tombstone (never
|
|
23
|
+
// treated as safe to rm while it may belong to a live holder) and is swept once older than the
|
|
24
|
+
// stale threshold; the displaced holder is warned at release and removes its own tombstone.
|
|
25
|
+
// Orphaned tombstones / claims (a taker crashed mid-takeover) are swept / expired as stale.
|
|
26
|
+
//
|
|
27
|
+
// Two variants:
|
|
28
|
+
// - withFileLock (sync): for the tiny synchronous JSON rewrites (registry/processes/app-map
|
|
29
|
+
// save). Waits are short synchronous sleeps (Atomics.wait, 25 ms slices, ≤ MAX_WAIT_MS total),
|
|
30
|
+
// so the critical section MUST be short: a sync fn cannot be heartbeated (no timer can fire
|
|
31
|
+
// while it runs). Slow work (e.g. the app-map static scan) belongs OUTSIDE the lock.
|
|
32
|
+
// - withFileLockAsync: for async critical sections. Waits yield to the event loop and a heartbeat
|
|
33
|
+
// refreshes the lock's mtime every staleMs/3 so a long holder is never judged stale.
|
|
34
|
+
// On timeout both variants fail rather than proceed unlocked (that would reintroduce clobbers).
|
|
35
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { randomUUID } from 'node:crypto';
|
|
37
|
+
import { basename, dirname, join } from 'node:path';
|
|
9
38
|
import { log } from './logger.js';
|
|
10
|
-
const STALE_LOCK_MS = 10_000;
|
|
39
|
+
export const STALE_LOCK_MS = 10_000;
|
|
11
40
|
const MAX_WAIT_MS = 2_000;
|
|
12
41
|
const RETRY_SLEEP_MS = 25;
|
|
13
|
-
/**
|
|
42
|
+
/** 'retry' (lock vanished / stale takeover attempted) loops without sleeping at most this many
|
|
43
|
+
* times in a row; after that it sleeps like 'busy'. A takeover that can never succeed (EACCES on
|
|
44
|
+
* the claim, rename failure, a fresh claim from a crashed taker) must not busy-spin, since it would
|
|
45
|
+
* block the event loop forever (the deadline is checked on EVERY iteration too). */
|
|
46
|
+
const MAX_IMMEDIATE_RETRIES = 3;
|
|
47
|
+
const OWNER_FILE = 'owner';
|
|
48
|
+
const TOMBSTONE_MARK = '.stale-';
|
|
49
|
+
const CLAIM_FILE = '.takeover';
|
|
50
|
+
/** Test seams for simulating contender interleavings inside a takeover. Never set in production. */
|
|
51
|
+
export const lockHooks = {};
|
|
52
|
+
/** Synchronous sleep without spinning the CPU (Node allows Atomics.wait on the main thread).
|
|
53
|
+
* Only ever called with short slices (RETRY_SLEEP_MS / rename backoff). */
|
|
14
54
|
function sleepSync(ms) {
|
|
15
55
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
16
56
|
}
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
57
|
+
/** The `pid:token` string of the lock's current owner, or null when absent/unreadable. */
|
|
58
|
+
function currentOwner(lockPath) {
|
|
59
|
+
try {
|
|
60
|
+
return readFileSync(join(lockPath, OWNER_FILE), 'utf8');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function readFileOrNull(p) {
|
|
67
|
+
try {
|
|
68
|
+
return readFileSync(p, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Remove our takeover claim from `dir` (only if it is still ours). */
|
|
75
|
+
function dropClaim(dir, claimToken) {
|
|
76
|
+
const claim = join(dir, CLAIM_FILE);
|
|
77
|
+
if (readFileOrNull(claim) === claimToken)
|
|
78
|
+
rmSync(claim, { force: true });
|
|
79
|
+
}
|
|
80
|
+
/** A claim left by a taker that crashed mid-takeover would block every future takeover of that
|
|
81
|
+
* lock: expire it once older than the stale threshold (moved aside atomically, then removed). */
|
|
82
|
+
function expireStaleClaim(lockPath, staleMs) {
|
|
83
|
+
const claim = join(lockPath, CLAIM_FILE);
|
|
84
|
+
try {
|
|
85
|
+
if (Date.now() - statSync(claim).mtimeMs <= staleMs)
|
|
86
|
+
return;
|
|
87
|
+
const lockStat = statSync(lockPath);
|
|
88
|
+
const dead = `${claim}.dead-${randomUUID()}`;
|
|
89
|
+
renameSync(claim, dead);
|
|
90
|
+
rmSync(dead, { force: true });
|
|
91
|
+
// Removing the claim bumped the lock dir's mtime; keep it looking as stale as it is.
|
|
92
|
+
utimesSync(lockPath, lockStat.atime, lockStat.mtime);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* gone or raced another expirer */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Take over a lock judged stale (claim > verify > move; see the header). Returns true when the
|
|
99
|
+
* stale lock was removed (the caller should retry mkdir), false when the takeover failed and the
|
|
100
|
+
* caller must go back to waiting: the lock was already gone, another taker holds the claim, a
|
|
101
|
+
* racing waiter replaced it with a fresh lock (left untouched), or the moved lock turned out not
|
|
102
|
+
* to be the verified one (restored, or kept as a tombstone if the path was re-created meanwhile).
|
|
103
|
+
* Exported for tests. */
|
|
104
|
+
export function takeOverStaleLock(lockPath, observed, staleMs = STALE_LOCK_MS) {
|
|
105
|
+
const claimToken = `${process.pid}:${randomUUID()}`;
|
|
106
|
+
// Identify the instance BEFORE claiming (writing the claim bumps the directory's mtime).
|
|
107
|
+
let before;
|
|
108
|
+
try {
|
|
109
|
+
const st = statSync(lockPath);
|
|
110
|
+
if (currentOwner(lockPath) !== observed.owner || Date.now() - st.mtimeMs <= staleMs)
|
|
111
|
+
return false; // replaced/refreshed
|
|
112
|
+
before = { ino: st.ino, dev: st.dev };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false; // already gone
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
writeFileSync(join(lockPath, CLAIM_FILE), claimToken, { flag: 'wx' });
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
if (e.code === 'EEXIST')
|
|
122
|
+
expireStaleClaim(lockPath, staleMs);
|
|
123
|
+
return false; // lock gone (ENOENT) or another taker is mid-takeover
|
|
124
|
+
}
|
|
125
|
+
let isSame = false;
|
|
126
|
+
try {
|
|
127
|
+
const st = statSync(lockPath);
|
|
128
|
+
isSame = st.ino === before.ino && st.dev === before.dev && currentOwner(lockPath) === observed.owner;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
isSame = false;
|
|
132
|
+
}
|
|
133
|
+
if (!isSame) {
|
|
134
|
+
// We claimed a FRESH lock (a racing waiter won the takeover and re-acquired after our
|
|
135
|
+
// observation). It never moved: just withdraw the claim.
|
|
136
|
+
dropClaim(lockPath, claimToken);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
lockHooks.beforeTakeoverMove?.();
|
|
140
|
+
const tomb = `${lockPath}${TOMBSTONE_MARK}${process.pid}-${randomUUID()}`;
|
|
141
|
+
try {
|
|
142
|
+
renameSync(lockPath, tomb); // atomic
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return false; // released meanwhile
|
|
146
|
+
}
|
|
147
|
+
if (readFileOrNull(join(tomb, CLAIM_FILE)) !== claimToken) {
|
|
148
|
+
// The path was replaced between verify and move: we moved someone else's (possibly live) lock.
|
|
149
|
+
lockHooks.beforeTakeoverRestore?.();
|
|
23
150
|
try {
|
|
24
|
-
|
|
25
|
-
locked = true;
|
|
151
|
+
renameSync(tomb, lockPath); // put it back untouched
|
|
26
152
|
}
|
|
27
153
|
catch {
|
|
154
|
+
// Another contender already re-created the path. Do NOT delete the moved lock (it may belong
|
|
155
|
+
// to a live holder) and do NOT proceed as owner: the takeover failed. The tombstone is swept
|
|
156
|
+
// once stale; its holder is warned at release.
|
|
157
|
+
log('error', 'file lock takeover moved a replaced lock and could not restore it; left as tombstone, not acquiring', {
|
|
158
|
+
lockPath,
|
|
159
|
+
tomb,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
rmSync(tomb, { recursive: true, force: true });
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/** Remove tombstones left by a taker that crashed between rename and rm. Best-effort. */
|
|
168
|
+
export function sweepLockTombstones(lockPath, staleMs = STALE_LOCK_MS) {
|
|
169
|
+
let removed = 0;
|
|
170
|
+
try {
|
|
171
|
+
const dir = dirname(lockPath);
|
|
172
|
+
const prefix = `${basename(lockPath)}${TOMBSTONE_MARK}`;
|
|
173
|
+
for (const name of readdirSync(dir)) {
|
|
174
|
+
if (!name.startsWith(prefix))
|
|
175
|
+
continue;
|
|
176
|
+
const p = join(dir, name);
|
|
28
177
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
continue;
|
|
178
|
+
if (Date.now() - statSync(p).mtimeMs > staleMs) {
|
|
179
|
+
rmSync(p, { recursive: true, force: true });
|
|
180
|
+
removed++;
|
|
33
181
|
}
|
|
34
182
|
}
|
|
35
183
|
catch {
|
|
36
|
-
/*
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (Date.now() > deadline) {
|
|
40
|
-
log('error', 'file lock wait timed out — refusing unlocked registry mutation', { lockPath });
|
|
41
|
-
throw new Error(`Timed out waiting for file lock ${lockPath}`);
|
|
184
|
+
/* raced another sweeper */
|
|
42
185
|
}
|
|
43
|
-
sleepSync(RETRY_SLEEP_MS);
|
|
44
186
|
}
|
|
45
187
|
}
|
|
188
|
+
catch {
|
|
189
|
+
/* parent missing/unreadable, nothing to sweep */
|
|
190
|
+
}
|
|
191
|
+
return removed;
|
|
192
|
+
}
|
|
193
|
+
/** One acquisition attempt: mkdir, else stale-check/takeover. Never sleeps. */
|
|
194
|
+
function tryAcquire(lockPath, token, staleMs) {
|
|
195
|
+
try {
|
|
196
|
+
mkdirSync(lockPath); // atomic: throws EEXIST if another process holds the lock
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
if (e.code !== 'EEXIST')
|
|
200
|
+
throw e;
|
|
201
|
+
let observed;
|
|
202
|
+
try {
|
|
203
|
+
observed = { owner: currentOwner(lockPath), mtimeMs: statSync(lockPath).mtimeMs };
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return 'retry'; // lock released between attempts, retry immediately
|
|
207
|
+
}
|
|
208
|
+
if (Date.now() - observed.mtimeMs > staleMs) {
|
|
209
|
+
// stale: the holder crashed or stalled; take over atomically, then retry mkdir
|
|
210
|
+
takeOverStaleLock(lockPath, observed, staleMs);
|
|
211
|
+
return 'retry';
|
|
212
|
+
}
|
|
213
|
+
return 'busy';
|
|
214
|
+
}
|
|
215
|
+
// We created the dir. Record ownership EXCLUSIVELY ('wx'): if an owner file already exists the
|
|
216
|
+
// directory is not really ours (a takeover restore swapped a live lock into this path).
|
|
217
|
+
try {
|
|
218
|
+
writeFileSync(join(lockPath, OWNER_FILE), token, { flag: 'wx' });
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
if (e.code === 'EEXIST')
|
|
222
|
+
return 'busy';
|
|
223
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
224
|
+
throw e;
|
|
225
|
+
}
|
|
226
|
+
// Re-check ownership after acquiring: the lock we hold must carry OUR token.
|
|
227
|
+
if (currentOwner(lockPath) !== token)
|
|
228
|
+
return 'busy';
|
|
229
|
+
sweepLockTombstones(lockPath, staleMs);
|
|
230
|
+
return 'acquired';
|
|
231
|
+
}
|
|
232
|
+
function release(lockPath, token) {
|
|
233
|
+
// Verify-then-release: only remove the lock while the owner file still holds OUR token. If fn()
|
|
234
|
+
// stalled past the stale threshold another process legitimately took the lock over, and deleting it
|
|
235
|
+
// here would let a THIRD process acquire while the new owner still runs.
|
|
236
|
+
if (currentOwner(lockPath) === token) {
|
|
237
|
+
try {
|
|
238
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
/* already removed (stale takeover raced our release) */
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
log('warn', 'file lock was taken over while held: this holder stalled past the stale threshold (or was displaced by a takeover race) and its work may have raced the new holder', { lockPath });
|
|
246
|
+
removeOwnTombstones(lockPath, token);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** A displaced holder's lock may survive as a tombstone (see takeOverStaleLock): remove the ones
|
|
250
|
+
* carrying OUR token. They are ours, so this can never touch another holder's lock. */
|
|
251
|
+
function removeOwnTombstones(lockPath, token) {
|
|
252
|
+
try {
|
|
253
|
+
const dir = dirname(lockPath);
|
|
254
|
+
const prefix = `${basename(lockPath)}${TOMBSTONE_MARK}`;
|
|
255
|
+
for (const name of readdirSync(dir)) {
|
|
256
|
+
if (!name.startsWith(prefix))
|
|
257
|
+
continue;
|
|
258
|
+
const p = join(dir, name);
|
|
259
|
+
if (currentOwner(p) === token)
|
|
260
|
+
rmSync(p, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
/* best-effort */
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function newToken() {
|
|
268
|
+
// Ownership token: verify identity before acting (the same spirit as processRegistry's
|
|
269
|
+
// serverStillAlive pid+command check).
|
|
270
|
+
return `${process.pid}:${randomUUID()}`;
|
|
271
|
+
}
|
|
272
|
+
function timeoutError(lockPath) {
|
|
273
|
+
log('error', 'file lock wait timed out, refusing unlocked registry mutation', { lockPath });
|
|
274
|
+
return new Error(`Timed out waiting for file lock ${lockPath}`);
|
|
275
|
+
}
|
|
276
|
+
/** Run a SHORT synchronous `fn` while holding the advisory lock at `lockPath` (a directory that
|
|
277
|
+
* must not pre-exist). Retries for up to maxWaitMs (blocking in 25 ms slices), taking over locks
|
|
278
|
+
* older than staleMs. fn cannot be heartbeated, so keep it well under staleMs. */
|
|
279
|
+
export function withFileLock(lockPath, fn, opts = {}) {
|
|
280
|
+
const staleMs = opts.staleMs ?? STALE_LOCK_MS;
|
|
281
|
+
const deadline = Date.now() + (opts.maxWaitMs ?? MAX_WAIT_MS);
|
|
282
|
+
const token = newToken();
|
|
283
|
+
let immediate = 0;
|
|
284
|
+
for (;;) {
|
|
285
|
+
const r = tryAcquire(lockPath, token, staleMs);
|
|
286
|
+
if (r === 'acquired')
|
|
287
|
+
break;
|
|
288
|
+
if (Date.now() > deadline)
|
|
289
|
+
throw timeoutError(lockPath);
|
|
290
|
+
if (r === 'retry' && immediate++ < MAX_IMMEDIATE_RETRIES)
|
|
291
|
+
continue;
|
|
292
|
+
immediate = 0;
|
|
293
|
+
sleepSync(RETRY_SLEEP_MS);
|
|
294
|
+
}
|
|
46
295
|
try {
|
|
47
296
|
return fn();
|
|
48
297
|
}
|
|
49
298
|
finally {
|
|
50
|
-
|
|
299
|
+
release(lockPath, token);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** Async variant: waits yield to the event loop, and a heartbeat refreshes the lock's mtime every
|
|
303
|
+
* staleMs/3 while `fn` runs so a long (async) critical section is never judged stale. */
|
|
304
|
+
export async function withFileLockAsync(lockPath, fn, opts = {}) {
|
|
305
|
+
const staleMs = opts.staleMs ?? STALE_LOCK_MS;
|
|
306
|
+
const deadline = Date.now() + (opts.maxWaitMs ?? MAX_WAIT_MS);
|
|
307
|
+
const token = newToken();
|
|
308
|
+
let immediate = 0;
|
|
309
|
+
for (;;) {
|
|
310
|
+
const r = tryAcquire(lockPath, token, staleMs);
|
|
311
|
+
if (r === 'acquired')
|
|
312
|
+
break;
|
|
313
|
+
if (Date.now() > deadline)
|
|
314
|
+
throw timeoutError(lockPath);
|
|
315
|
+
if (r === 'retry' && immediate++ < MAX_IMMEDIATE_RETRIES)
|
|
316
|
+
continue;
|
|
317
|
+
immediate = 0;
|
|
318
|
+
await new Promise((res) => setTimeout(res, RETRY_SLEEP_MS));
|
|
319
|
+
}
|
|
320
|
+
const heartbeat = setInterval(() => {
|
|
321
|
+
// Only refresh a lock that is still ours. Never keep a thief's lock alive on its behalf.
|
|
322
|
+
if (currentOwner(lockPath) !== token)
|
|
323
|
+
return;
|
|
324
|
+
try {
|
|
325
|
+
const now = new Date();
|
|
326
|
+
utimesSync(lockPath, now, now);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
/* lock vanished; release will log */
|
|
330
|
+
}
|
|
331
|
+
}, Math.max(10, Math.floor(staleMs / 3)));
|
|
332
|
+
heartbeat.unref?.();
|
|
333
|
+
try {
|
|
334
|
+
return await fn();
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
clearInterval(heartbeat);
|
|
338
|
+
release(lockPath, token);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const RENAME_RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
342
|
+
/** renameSync that tolerates transient Windows sharing violations (antivirus/indexer briefly
|
|
343
|
+
* holding the target): retries EPERM/EBUSY/EACCES with short backoff (≤ ~310 ms total). On
|
|
344
|
+
* POSIX those errors are permanent, so it throws immediately. */
|
|
345
|
+
export function renameWithRetry(from, to, opts = {}) {
|
|
346
|
+
const rename = opts.renameFn ?? renameSync;
|
|
347
|
+
const isWindows = opts.isWindows ?? process.platform === 'win32';
|
|
348
|
+
const delays = opts.delaysMs ?? [10, 20, 40, 80, 160];
|
|
349
|
+
for (let attempt = 0;; attempt++) {
|
|
350
|
+
try {
|
|
351
|
+
rename(from, to);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
const code = e.code ?? '';
|
|
356
|
+
if (!isWindows || !RENAME_RETRY_CODES.has(code) || attempt >= delays.length)
|
|
357
|
+
throw e;
|
|
358
|
+
sleepSync(delays[attempt]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Atomic file write: unique tmp sibling + rename (with Windows retry). Removes the tmp on failure. */
|
|
363
|
+
export function writeFileAtomicSync(path, data) {
|
|
364
|
+
const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
365
|
+
writeFileSync(tmp, data);
|
|
366
|
+
try {
|
|
367
|
+
renameWithRetry(tmp, path);
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
rmSync(tmp, { force: true });
|
|
371
|
+
throw e;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** Delete `<baseName>.*.tmp` siblings older than maxAgeMs (residue of a writer that crashed
|
|
375
|
+
* between write and rename). Young tmp files may belong to a live writer and are kept. */
|
|
376
|
+
export function cleanupOrphanTmpFiles(dir, baseName, maxAgeMs = 10 * 60_000) {
|
|
377
|
+
let removed = 0;
|
|
378
|
+
try {
|
|
379
|
+
for (const name of readdirSync(dir)) {
|
|
380
|
+
if (!name.startsWith(`${baseName}.`) || !name.endsWith('.tmp'))
|
|
381
|
+
continue;
|
|
382
|
+
const p = join(dir, name);
|
|
51
383
|
try {
|
|
52
|
-
|
|
384
|
+
if (Date.now() - statSync(p).mtimeMs > maxAgeMs) {
|
|
385
|
+
rmSync(p, { force: true });
|
|
386
|
+
removed++;
|
|
387
|
+
}
|
|
53
388
|
}
|
|
54
389
|
catch {
|
|
55
|
-
/*
|
|
390
|
+
/* raced another cleaner */
|
|
56
391
|
}
|
|
57
392
|
}
|
|
58
393
|
}
|
|
394
|
+
catch {
|
|
395
|
+
/* dir missing, nothing to clean */
|
|
396
|
+
}
|
|
397
|
+
return removed;
|
|
59
398
|
}
|
|
60
399
|
//# sourceMappingURL=lockfile.js.map
|