@backblaze-labs/b2-sdk 0.3.0 → 0.4.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 +45 -1
- package/README.md +18 -5
- package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
- package/dist/_virtual/_b2-sdk-version-json.js +1 -1
- package/dist/auth/reauth-coalescer.cjs +65 -0
- package/dist/auth/reauth-coalescer.cjs.map +1 -0
- package/dist/auth/reauth-coalescer.d.cts +34 -0
- package/dist/auth/reauth-coalescer.d.cts.map +1 -0
- package/dist/auth/reauth-coalescer.d.ts +34 -0
- package/dist/auth/reauth-coalescer.d.ts.map +1 -0
- package/dist/auth/reauth-coalescer.js +65 -0
- package/dist/auth/reauth-coalescer.js.map +1 -0
- package/dist/backup/raw.cjs +37 -15
- package/dist/backup/raw.cjs.map +1 -1
- package/dist/backup/raw.d.cts +2 -2
- package/dist/backup/raw.d.cts.map +1 -1
- package/dist/backup/raw.d.ts +2 -2
- package/dist/backup/raw.d.ts.map +1 -1
- package/dist/backup/raw.js +37 -15
- package/dist/backup/raw.js.map +1 -1
- package/dist/bucket-validation.cjs +111 -0
- package/dist/bucket-validation.cjs.map +1 -0
- package/dist/bucket-validation.d.cts +27 -0
- package/dist/bucket-validation.d.cts.map +1 -0
- package/dist/bucket-validation.d.ts +27 -0
- package/dist/bucket-validation.d.ts.map +1 -0
- package/dist/bucket-validation.js +109 -0
- package/dist/bucket-validation.js.map +1 -0
- package/dist/bucket.cjs +123 -73
- package/dist/bucket.cjs.map +1 -1
- package/dist/bucket.d.cts +156 -61
- package/dist/bucket.d.cts.map +1 -1
- package/dist/bucket.d.ts +156 -61
- package/dist/bucket.d.ts.map +1 -1
- package/dist/bucket.js +123 -73
- package/dist/bucket.js.map +1 -1
- package/dist/client.cjs +29 -8
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +30 -9
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.ts +30 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +29 -8
- package/dist/client.js.map +1 -1
- package/dist/download/single.cjs +38 -13
- package/dist/download/single.cjs.map +1 -1
- package/dist/download/single.d.cts +10 -4
- package/dist/download/single.d.cts.map +1 -1
- package/dist/download/single.d.ts +10 -4
- package/dist/download/single.d.ts.map +1 -1
- package/dist/download/single.js +38 -13
- package/dist/download/single.js.map +1 -1
- package/dist/errors/index.cjs +27 -4
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +15 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +15 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +27 -5
- package/dist/errors/index.js.map +1 -1
- package/dist/http/non-idempotent-mutations.cjs +38 -0
- package/dist/http/non-idempotent-mutations.cjs.map +1 -0
- package/dist/http/non-idempotent-mutations.d.cts +25 -0
- package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
- package/dist/http/non-idempotent-mutations.d.ts +25 -0
- package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
- package/dist/http/non-idempotent-mutations.js +35 -0
- package/dist/http/non-idempotent-mutations.js.map +1 -0
- package/dist/http/transport.cjs +9 -8
- package/dist/http/transport.cjs.map +1 -1
- package/dist/http/transport.d.cts +5 -0
- package/dist/http/transport.d.cts.map +1 -1
- package/dist/http/transport.d.ts +5 -0
- package/dist/http/transport.d.ts.map +1 -1
- package/dist/http/transport.js +9 -8
- package/dist/http/transport.js.map +1 -1
- package/dist/http/user-agent.cjs +12 -1
- package/dist/http/user-agent.cjs.map +1 -1
- package/dist/http/user-agent.d.cts +8 -0
- package/dist/http/user-agent.d.cts.map +1 -1
- package/dist/http/user-agent.d.ts +8 -0
- package/dist/http/user-agent.d.ts.map +1 -1
- package/dist/http/user-agent.js +13 -3
- package/dist/http/user-agent.js.map +1 -1
- package/dist/index.cjs +25 -0
- package/dist/index.d.cts +10 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/notifications/index.cjs +3 -0
- package/dist/notifications/index.d.cts +2 -0
- package/dist/notifications/index.d.cts.map +1 -1
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +2 -1
- package/dist/object.cjs +12 -1
- package/dist/object.cjs.map +1 -1
- package/dist/object.d.cts +7 -1
- package/dist/object.d.cts.map +1 -1
- package/dist/object.d.ts +7 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +12 -1
- package/dist/object.js.map +1 -1
- package/dist/partner/auth-core.cjs +4 -23
- package/dist/partner/auth-core.cjs.map +1 -1
- package/dist/partner/auth-core.d.cts +1 -1
- package/dist/partner/auth-core.d.cts.map +1 -1
- package/dist/partner/auth-core.d.ts +1 -1
- package/dist/partner/auth-core.d.ts.map +1 -1
- package/dist/partner/auth-core.js +5 -24
- package/dist/partner/auth-core.js.map +1 -1
- package/dist/partner/client.cjs +25 -10
- package/dist/partner/client.cjs.map +1 -1
- package/dist/partner/client.d.cts +32 -10
- package/dist/partner/client.d.cts.map +1 -1
- package/dist/partner/client.d.ts +32 -10
- package/dist/partner/client.d.ts.map +1 -1
- package/dist/partner/client.js +25 -10
- package/dist/partner/client.js.map +1 -1
- package/dist/partner/endpoint-url.cjs +1 -0
- package/dist/partner/endpoint-url.cjs.map +1 -1
- package/dist/partner/endpoint-url.d.cts +2 -0
- package/dist/partner/endpoint-url.d.cts.map +1 -1
- package/dist/partner/endpoint-url.d.ts +2 -0
- package/dist/partner/endpoint-url.d.ts.map +1 -1
- package/dist/partner/endpoint-url.js +1 -0
- package/dist/partner/endpoint-url.js.map +1 -1
- package/dist/partner/in-memory.cjs.map +1 -1
- package/dist/partner/in-memory.js.map +1 -1
- package/dist/partner/index.cjs +9 -0
- package/dist/partner/index.d.cts +11 -2
- package/dist/partner/index.d.cts.map +1 -1
- package/dist/partner/index.d.ts +11 -2
- package/dist/partner/index.d.ts.map +1 -1
- package/dist/partner/index.js +3 -1
- package/dist/partner/raw.cjs +52 -44
- package/dist/partner/raw.cjs.map +1 -1
- package/dist/partner/raw.d.cts +16 -19
- package/dist/partner/raw.d.cts.map +1 -1
- package/dist/partner/raw.d.ts +16 -19
- package/dist/partner/raw.d.ts.map +1 -1
- package/dist/partner/raw.js +52 -44
- package/dist/partner/raw.js.map +1 -1
- package/dist/partner/redaction.cjs +133 -108
- package/dist/partner/redaction.cjs.map +1 -1
- package/dist/partner/redaction.d.cts +47 -13
- package/dist/partner/redaction.d.cts.map +1 -1
- package/dist/partner/redaction.d.ts +47 -13
- package/dist/partner/redaction.d.ts.map +1 -1
- package/dist/partner/redaction.js +133 -109
- package/dist/partner/redaction.js.map +1 -1
- package/dist/raw/encoding.cjs +29 -10
- package/dist/raw/encoding.cjs.map +1 -1
- package/dist/raw/encoding.d.cts +3 -2
- package/dist/raw/encoding.d.cts.map +1 -1
- package/dist/raw/encoding.d.ts +3 -2
- package/dist/raw/encoding.d.ts.map +1 -1
- package/dist/raw/encoding.js +29 -10
- package/dist/raw/encoding.js.map +1 -1
- package/dist/raw/index.cjs +64 -39
- package/dist/raw/index.cjs.map +1 -1
- package/dist/raw/index.d.cts +21 -19
- package/dist/raw/index.d.cts.map +1 -1
- package/dist/raw/index.d.ts +21 -19
- package/dist/raw/index.d.ts.map +1 -1
- package/dist/raw/index.js +47 -40
- package/dist/raw/index.js.map +1 -1
- package/dist/raw/url.cjs.map +1 -1
- package/dist/raw/url.d.cts +3 -3
- package/dist/raw/url.d.ts +3 -3
- package/dist/raw/url.js.map +1 -1
- package/dist/simulator/index.cjs +456 -125
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.cts +16 -1
- package/dist/simulator/index.d.cts.map +1 -1
- package/dist/simulator/index.d.ts +16 -1
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +449 -130
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/partner-capabilities.cjs +77 -0
- package/dist/simulator/partner-capabilities.cjs.map +1 -0
- package/dist/simulator/partner-capabilities.d.cts +53 -0
- package/dist/simulator/partner-capabilities.d.cts.map +1 -0
- package/dist/simulator/partner-capabilities.d.ts +53 -0
- package/dist/simulator/partner-capabilities.d.ts.map +1 -0
- package/dist/simulator/partner-capabilities.js +73 -0
- package/dist/simulator/partner-capabilities.js.map +1 -0
- package/dist/simulator/partner.cjs +55 -61
- package/dist/simulator/partner.cjs.map +1 -1
- package/dist/simulator/partner.d.cts +5 -2
- package/dist/simulator/partner.d.cts.map +1 -1
- package/dist/simulator/partner.d.ts +5 -2
- package/dist/simulator/partner.d.ts.map +1 -1
- package/dist/simulator/partner.js +56 -61
- package/dist/simulator/partner.js.map +1 -1
- package/dist/simulator/validation.cjs +160 -30
- package/dist/simulator/validation.cjs.map +1 -1
- package/dist/simulator/validation.d.cts +28 -6
- package/dist/simulator/validation.d.cts.map +1 -1
- package/dist/simulator/validation.d.ts +28 -6
- package/dist/simulator/validation.d.ts.map +1 -1
- package/dist/simulator/validation.js +148 -31
- package/dist/simulator/validation.js.map +1 -1
- package/dist/streams/file-source.cjs +1 -1
- package/dist/streams/file-source.cjs.map +1 -1
- package/dist/streams/file-source.js +1 -1
- package/dist/streams/file-source.js.map +1 -1
- package/dist/streams/hash.cjs +246 -13
- package/dist/streams/hash.cjs.map +1 -1
- package/dist/streams/hash.d.cts +37 -12
- package/dist/streams/hash.d.cts.map +1 -1
- package/dist/streams/hash.d.ts +37 -12
- package/dist/streams/hash.d.ts.map +1 -1
- package/dist/streams/hash.js +246 -15
- package/dist/streams/hash.js.map +1 -1
- package/dist/streams/index.cjs +2 -0
- package/dist/streams/index.d.cts +1 -1
- package/dist/streams/index.d.cts.map +1 -1
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.d.ts.map +1 -1
- package/dist/streams/index.js +2 -2
- package/dist/streams/progress.cjs +63 -4
- package/dist/streams/progress.cjs.map +1 -1
- package/dist/streams/progress.d.cts +28 -2
- package/dist/streams/progress.d.cts.map +1 -1
- package/dist/streams/progress.d.ts +28 -2
- package/dist/streams/progress.d.ts.map +1 -1
- package/dist/streams/progress.js +63 -4
- package/dist/streams/progress.js.map +1 -1
- package/dist/sync/download-staging.cjs +147 -35
- package/dist/sync/download-staging.cjs.map +1 -1
- package/dist/sync/download-staging.d.cts +25 -2
- package/dist/sync/download-staging.d.cts.map +1 -1
- package/dist/sync/download-staging.d.ts +25 -2
- package/dist/sync/download-staging.d.ts.map +1 -1
- package/dist/sync/download-staging.js +146 -36
- package/dist/sync/download-staging.js.map +1 -1
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.cts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/local-file-io.cjs +5 -1
- package/dist/sync/local-file-io.cjs.map +1 -1
- package/dist/sync/local-file-io.d.cts +2 -0
- package/dist/sync/local-file-io.d.cts.map +1 -1
- package/dist/sync/local-file-io.d.ts +2 -0
- package/dist/sync/local-file-io.d.ts.map +1 -1
- package/dist/sync/local-file-io.js +5 -1
- package/dist/sync/local-file-io.js.map +1 -1
- package/dist/sync/pairing.cjs +1 -0
- package/dist/sync/pairing.cjs.map +1 -1
- package/dist/sync/pairing.js +1 -0
- package/dist/sync/pairing.js.map +1 -1
- package/dist/sync/scanners/b2.cjs +2 -0
- package/dist/sync/scanners/b2.cjs.map +1 -1
- package/dist/sync/scanners/b2.d.cts.map +1 -1
- package/dist/sync/scanners/b2.d.ts.map +1 -1
- package/dist/sync/scanners/b2.js +2 -0
- package/dist/sync/scanners/b2.js.map +1 -1
- package/dist/sync/scanners/local.cjs +150 -33
- package/dist/sync/scanners/local.cjs.map +1 -1
- package/dist/sync/scanners/local.d.cts +6 -0
- package/dist/sync/scanners/local.d.cts.map +1 -1
- package/dist/sync/scanners/local.d.ts +6 -0
- package/dist/sync/scanners/local.d.ts.map +1 -1
- package/dist/sync/scanners/local.js +150 -33
- package/dist/sync/scanners/local.js.map +1 -1
- package/dist/sync/synchronizer.cjs +76 -29
- package/dist/sync/synchronizer.cjs.map +1 -1
- package/dist/sync/synchronizer.d.cts +1 -0
- package/dist/sync/synchronizer.d.cts.map +1 -1
- package/dist/sync/synchronizer.d.ts +1 -0
- package/dist/sync/synchronizer.d.ts.map +1 -1
- package/dist/sync/synchronizer.js +76 -29
- package/dist/sync/synchronizer.js.map +1 -1
- package/dist/sync/types.d.cts +19 -3
- package/dist/sync/types.d.cts.map +1 -1
- package/dist/sync/types.d.ts +19 -3
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/types/backup.d.cts +6 -1
- package/dist/types/backup.d.cts.map +1 -1
- package/dist/types/backup.d.ts +6 -1
- package/dist/types/backup.d.ts.map +1 -1
- package/dist/types/bucket.cjs +81 -11
- package/dist/types/bucket.cjs.map +1 -1
- package/dist/types/bucket.d.cts +190 -39
- package/dist/types/bucket.d.cts.map +1 -1
- package/dist/types/bucket.d.ts +190 -39
- package/dist/types/bucket.d.ts.map +1 -1
- package/dist/types/bucket.js +67 -12
- package/dist/types/bucket.js.map +1 -1
- package/dist/types/download.cjs.map +1 -1
- package/dist/types/download.d.cts +3 -17
- package/dist/types/download.d.cts.map +1 -1
- package/dist/types/download.d.ts +3 -17
- package/dist/types/download.d.ts.map +1 -1
- package/dist/types/download.js.map +1 -1
- package/dist/types/encryption.cjs +18 -0
- package/dist/types/encryption.cjs.map +1 -1
- package/dist/types/encryption.d.cts +21 -11
- package/dist/types/encryption.d.cts.map +1 -1
- package/dist/types/encryption.d.ts +21 -11
- package/dist/types/encryption.d.ts.map +1 -1
- package/dist/types/encryption.js +18 -0
- package/dist/types/encryption.js.map +1 -1
- package/dist/types/file.cjs +3 -0
- package/dist/types/file.cjs.map +1 -1
- package/dist/types/file.d.cts +125 -16
- package/dist/types/file.d.cts.map +1 -1
- package/dist/types/file.d.ts +125 -16
- package/dist/types/file.d.ts.map +1 -1
- package/dist/types/file.js +3 -1
- package/dist/types/file.js.map +1 -1
- package/dist/types/index.d.cts +7 -7
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/key.d.cts +4 -3
- package/dist/types/key.d.cts.map +1 -1
- package/dist/types/key.d.ts +4 -3
- package/dist/types/key.d.ts.map +1 -1
- package/dist/types/notifications.cjs +30 -0
- package/dist/types/notifications.cjs.map +1 -1
- package/dist/types/notifications.d.cts +26 -1
- package/dist/types/notifications.d.cts.map +1 -1
- package/dist/types/notifications.d.ts +26 -1
- package/dist/types/notifications.d.ts.map +1 -1
- package/dist/types/notifications.js +29 -1
- package/dist/types/notifications.js.map +1 -1
- package/dist/types/partner.cjs.map +1 -1
- package/dist/types/partner.d.cts +28 -36
- package/dist/types/partner.d.cts.map +1 -1
- package/dist/types/partner.d.ts +28 -36
- package/dist/types/partner.d.ts.map +1 -1
- package/dist/types/partner.js.map +1 -1
- package/dist/types/upload.d.cts +4 -0
- package/dist/types/upload.d.cts.map +1 -1
- package/dist/types/upload.d.ts +4 -0
- package/dist/types/upload.d.ts.map +1 -1
- package/dist/upload/large.cjs +10 -7
- package/dist/upload/large.cjs.map +1 -1
- package/dist/upload/large.d.cts +15 -6
- package/dist/upload/large.d.cts.map +1 -1
- package/dist/upload/large.d.ts +15 -6
- package/dist/upload/large.d.ts.map +1 -1
- package/dist/upload/large.js +10 -7
- package/dist/upload/large.js.map +1 -1
- package/dist/upload/options.cjs.map +1 -1
- package/dist/upload/options.d.cts +12 -0
- package/dist/upload/options.d.cts.map +1 -1
- package/dist/upload/options.d.ts +12 -0
- package/dist/upload/options.d.ts.map +1 -1
- package/dist/upload/options.js.map +1 -1
- package/dist/upload/resume.cjs +4 -3
- package/dist/upload/resume.cjs.map +1 -1
- package/dist/upload/resume.d.cts +6 -4
- package/dist/upload/resume.d.cts.map +1 -1
- package/dist/upload/resume.d.ts +6 -4
- package/dist/upload/resume.d.ts.map +1 -1
- package/dist/upload/resume.js +4 -3
- package/dist/upload/resume.js.map +1 -1
- package/dist/upload/retry.cjs +17 -10
- package/dist/upload/retry.cjs.map +1 -1
- package/dist/upload/retry.d.cts.map +1 -1
- package/dist/upload/retry.d.ts.map +1 -1
- package/dist/upload/retry.js +17 -10
- package/dist/upload/retry.js.map +1 -1
- package/dist/upload/single.cjs +35 -31
- package/dist/upload/single.cjs.map +1 -1
- package/dist/upload/single.d.cts +6 -0
- package/dist/upload/single.d.cts.map +1 -1
- package/dist/upload/single.d.ts +6 -0
- package/dist/upload/single.d.ts.map +1 -1
- package/dist/upload/single.js +35 -31
- package/dist/upload/single.js.map +1 -1
- package/dist/upload/stream.cjs +12 -1
- package/dist/upload/stream.cjs.map +1 -1
- package/dist/upload/stream.d.cts +6 -0
- package/dist/upload/stream.d.cts.map +1 -1
- package/dist/upload/stream.d.ts +6 -0
- package/dist/upload/stream.d.ts.map +1 -1
- package/dist/upload/stream.js +12 -1
- package/dist/upload/stream.js.map +1 -1
- package/dist/version.cjs +56 -4
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +53 -4
- package/dist/version.d.cts.map +1 -1
- package/dist/version.d.ts +53 -4
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +52 -5
- package/dist/version.js.map +1 -1
- package/package.json +11 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/sync/scanners/local.js"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAkB,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/sync/scanners/local.js"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAkB,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA+B7F;;;;;;;;GAQG;AACH,qBAAa,WAAY,YAAW,UAAU;IAC5C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;IAChC,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;gBACS,IAAI,EAAE,MAAM;IAKxB;;;OAGG;IACI,IAAI,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAAC,aAAa,CAAC;IAqCzE;;;;;;;;;;;OAWG;YACW,IAAI;YAuFJ,aAAa;YA4Db,WAAW;YA4CX,aAAa;IAuF3B,OAAO,CAAC,aAAa;CAetB"}
|
|
@@ -4,9 +4,9 @@ import { directoryMayContainSyncPaths, pathPassesSyncFilters, pathSkippedByRegEx
|
|
|
4
4
|
import { compareSyncRelativePaths } from "../path-order.js";
|
|
5
5
|
import { assertScanEntryLimit, scanEntryLimit } from "../scan-limit.js";
|
|
6
6
|
import { localFileIdentityFromStats } from "../local-file-identity.js";
|
|
7
|
+
import { localFilesystemErrorReason } from "../filesystem-errors.js";
|
|
7
8
|
import { isReservedSyncTempFileName } from "../path-safety.js";
|
|
8
9
|
import { isDownloadStagingDirectorySegment, isManagedDownloadStagingRoot } from "../download-staging.js";
|
|
9
|
-
import { localFilesystemErrorReason } from "../filesystem-errors.js";
|
|
10
10
|
import { registerLocalFilesystemRoot } from "../local-filesystem-root.js";
|
|
11
11
|
//#region src/sync/scanners/local.ts
|
|
12
12
|
/**
|
|
@@ -40,8 +40,17 @@ var LocalFolder = class {
|
|
|
40
40
|
validateSyncFilters(options);
|
|
41
41
|
const nodeDeps = await loadLocalNodeDeps();
|
|
42
42
|
const root = nodeDeps.resolve(this.root);
|
|
43
|
+
const followSymlinks = options.localSymlinks === "follow" && options.requireLocalSafePaths !== true;
|
|
44
|
+
let scanRoot = root;
|
|
43
45
|
const collected = [];
|
|
44
|
-
|
|
46
|
+
const activeDirectoryIds = /* @__PURE__ */ new Set();
|
|
47
|
+
const followedDirectoryIds = /* @__PURE__ */ new Set();
|
|
48
|
+
if (followSymlinks) try {
|
|
49
|
+
scanRoot = await nodeDeps.realpath(root);
|
|
50
|
+
const rootStats = await nodeDeps.stat(scanRoot);
|
|
51
|
+
if (rootStats.isDirectory()) activeDirectoryIds.add(localNodeIdentity(rootStats));
|
|
52
|
+
} catch {}
|
|
53
|
+
await this.walk(scanRoot, scanRoot, "", collected, options, scanEntryLimit(options), nodeDeps, activeDirectoryIds, followedDirectoryIds);
|
|
45
54
|
collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath));
|
|
46
55
|
for (const entry of collected) {
|
|
47
56
|
throwIfScanAborted(options);
|
|
@@ -52,25 +61,28 @@ var LocalFolder = class {
|
|
|
52
61
|
* Recursively collects files from {@link dir} into {@link out}.
|
|
53
62
|
* @param root - Resolved scan root used for relative path calculation.
|
|
54
63
|
* @param dir - Absolute path of the directory to scan.
|
|
64
|
+
* @param dirRel - Sync-relative path represented by {@link dir}.
|
|
55
65
|
* @param out - Accumulator array that receives discovered file entries.
|
|
56
66
|
* @param options - Optional scan controls.
|
|
57
67
|
* @param maxScanEntries - Maximum number of entries to retain before failing.
|
|
58
68
|
* @param nodeDeps - Lazy-loaded Node filesystem and path helpers.
|
|
69
|
+
* @param activeDirectoryIds - Directory identities in the current recursion stack.
|
|
70
|
+
* @param followedDirectoryIds - Followed symlink directory identities already traversed.
|
|
59
71
|
*/
|
|
60
|
-
async walk(root, dir, out, options, maxScanEntries, nodeDeps) {
|
|
72
|
+
async walk(root, dir, dirRel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds) {
|
|
61
73
|
throwIfScanAborted(options);
|
|
62
74
|
let entries;
|
|
63
75
|
try {
|
|
64
76
|
entries = await nodeDeps.readdir(dir, { withFileTypes: true });
|
|
65
77
|
} catch (err) {
|
|
66
|
-
const error = this.emitScanError(options,
|
|
78
|
+
const error = this.emitScanError(options, dirRel, "directory", err);
|
|
67
79
|
if (dir === root) throw error;
|
|
68
80
|
return;
|
|
69
81
|
}
|
|
70
82
|
for (const entry of entries) {
|
|
71
83
|
throwIfScanAborted(options);
|
|
72
84
|
const fullPath = nodeDeps.join(dir, entry.name);
|
|
73
|
-
const rel =
|
|
85
|
+
const rel = joinSyncRelativePath(dirRel, entry.name);
|
|
74
86
|
if (isDownloadStagingDirectorySegment(rel) && entry.isDirectory() && isDownloadStagingDirectorySegment(entry.name) && await isManagedDownloadStagingRoot(fullPath)) continue;
|
|
75
87
|
if (rel.includes("\\")) {
|
|
76
88
|
emitScannerSkip(options, {
|
|
@@ -93,35 +105,130 @@ var LocalFolder = class {
|
|
|
93
105
|
continue;
|
|
94
106
|
}
|
|
95
107
|
if (entry.isDirectory()) {
|
|
96
|
-
if (directoryMayContainSyncPaths(rel, options)) await this.
|
|
97
|
-
} else if (entry.isFile())
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
if (directoryMayContainSyncPaths(rel, options)) await this.walkDirectory(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
|
|
109
|
+
} else if (entry.isFile()) await this.collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps);
|
|
110
|
+
else if (entry.isSymbolicLink()) await this.handleSymlink(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async walkDirectory(root, dir, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds, knownStats) {
|
|
114
|
+
let stats = knownStats;
|
|
115
|
+
if (stats === void 0) try {
|
|
116
|
+
stats = await nodeDeps.stat(dir);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
/* v8 ignore start -- stat TOCTOU failures after a Dirent directory result are not deterministic */
|
|
119
|
+
this.emitScanError(options, rel, "directory", err);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
/* v8 ignore start -- stat TOCTOU races after a Dirent directory result are not deterministic */
|
|
123
|
+
if (!stats.isDirectory()) {
|
|
124
|
+
this.emitScanError(options, rel, "directory", /* @__PURE__ */ new Error("not a directory"));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
/* v8 ignore stop */
|
|
128
|
+
const identity = localNodeIdentity(stats);
|
|
129
|
+
if (activeDirectoryIds.has(identity)) {
|
|
130
|
+
emitScannerSkip(options, {
|
|
131
|
+
type: "skip",
|
|
132
|
+
path: rel,
|
|
133
|
+
size: 0,
|
|
134
|
+
reason: "local-symlink-loop",
|
|
135
|
+
message: `Skipped local path ${JSON.stringify(rel)}: symlink would revisit an ancestor directory`
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
activeDirectoryIds.add(identity);
|
|
140
|
+
try {
|
|
141
|
+
await this.walk(root, dir, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
|
|
142
|
+
} finally {
|
|
143
|
+
activeDirectoryIds.delete(identity);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps, knownStats) {
|
|
147
|
+
if (!pathPassesSyncFilters(rel, options)) {
|
|
148
|
+
if (pathSkippedByRegExpInputLimit(rel, options)) emitScannerSkip(options, regexpInputTooLongSkip(rel));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
let s = knownStats;
|
|
152
|
+
if (s === void 0) try {
|
|
153
|
+
s = await nodeDeps.lstat(fullPath);
|
|
154
|
+
/* v8 ignore start -- lstat race after a Dirent file result is not deterministic */
|
|
155
|
+
if (!s.isFile()) {
|
|
156
|
+
this.emitScanError(options, rel, "file", /* @__PURE__ */ new Error("not a regular file"));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
/* v8 ignore start -- stat TOCTOU failures are not deterministic to trigger */
|
|
161
|
+
this.emitScanError(options, rel, "file", err);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
assertScanEntryLimit(out.length + 1, maxScanEntries);
|
|
165
|
+
out.push({
|
|
166
|
+
relativePath: rel,
|
|
167
|
+
absolutePath: fullPath,
|
|
168
|
+
modTimeMillis: Math.floor(s.mtimeMs),
|
|
169
|
+
size: s.size,
|
|
170
|
+
fileIdentity: localFileIdentityFromStats(s)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async handleSymlink(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds) {
|
|
174
|
+
if ((options.localSymlinks ?? "skip") === "skip" || options.requireLocalSafePaths === true) {
|
|
175
|
+
emitScannerSkip(options, {
|
|
176
|
+
type: "skip",
|
|
177
|
+
path: rel,
|
|
178
|
+
size: 0,
|
|
179
|
+
reason: "local-symlink",
|
|
180
|
+
message: `Skipped local path ${JSON.stringify(rel)}: local symlinks are disabled`
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let targetPath;
|
|
185
|
+
let targetStats;
|
|
186
|
+
try {
|
|
187
|
+
targetPath = await nodeDeps.realpath(fullPath);
|
|
188
|
+
targetStats = await nodeDeps.stat(targetPath);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
this.emitScanError(options, rel, "symlink", err);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!isPathInsideRoot(root, targetPath, nodeDeps)) {
|
|
194
|
+
emitScannerSkip(options, {
|
|
195
|
+
type: "skip",
|
|
196
|
+
path: rel,
|
|
197
|
+
size: 0,
|
|
198
|
+
reason: "local-symlink",
|
|
199
|
+
message: `Skipped local path ${JSON.stringify(rel)}: symlink target is outside the sync root`
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (targetStats.isDirectory()) {
|
|
204
|
+
if (directoryMayContainSyncPaths(rel, options)) {
|
|
205
|
+
const identity = localNodeIdentity(targetStats);
|
|
206
|
+
if (followedDirectoryIds.has(identity)) {
|
|
207
|
+
emitScannerSkip(options, {
|
|
208
|
+
type: "skip",
|
|
209
|
+
path: rel,
|
|
210
|
+
size: 0,
|
|
211
|
+
reason: "local-symlink-loop",
|
|
212
|
+
message: `Skipped local path ${JSON.stringify(rel)}: symlink target was already visited`
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
114
215
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
relativePath: rel,
|
|
118
|
-
absolutePath: fullPath,
|
|
119
|
-
modTimeMillis: Math.floor(s.mtimeMs),
|
|
120
|
-
size: s.size,
|
|
121
|
-
fileIdentity: localFileIdentityFromStats(s)
|
|
122
|
-
});
|
|
216
|
+
followedDirectoryIds.add(identity);
|
|
217
|
+
await this.walkDirectory(root, targetPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds, targetStats);
|
|
123
218
|
}
|
|
219
|
+
return;
|
|
124
220
|
}
|
|
221
|
+
if (targetStats.isFile()) {
|
|
222
|
+
await this.collectFile(targetPath, rel, out, options, maxScanEntries, nodeDeps, targetStats);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
emitScannerSkip(options, {
|
|
226
|
+
type: "skip",
|
|
227
|
+
path: rel,
|
|
228
|
+
size: 0,
|
|
229
|
+
reason: "local-symlink",
|
|
230
|
+
message: `Skipped local path ${JSON.stringify(rel)}: symlink target is not a regular file or directory`
|
|
231
|
+
});
|
|
125
232
|
}
|
|
126
233
|
emitScanError(options, path, kind, err) {
|
|
127
234
|
const event = {
|
|
@@ -139,9 +246,12 @@ async function loadLocalNodeDeps() {
|
|
|
139
246
|
return {
|
|
140
247
|
readdir: fsPromises.readdir,
|
|
141
248
|
lstat: fsPromises.lstat,
|
|
249
|
+
stat: fsPromises.stat,
|
|
142
250
|
join: path.join,
|
|
143
251
|
relative: path.relative,
|
|
252
|
+
realpath: fsPromises.realpath,
|
|
144
253
|
resolve: path.resolve,
|
|
254
|
+
isAbsolute: path.isAbsolute,
|
|
145
255
|
sep: path.sep
|
|
146
256
|
};
|
|
147
257
|
}
|
|
@@ -205,8 +315,15 @@ function normalizePathSegments(segments, separator) {
|
|
|
205
315
|
}
|
|
206
316
|
return out.join(separator);
|
|
207
317
|
}
|
|
208
|
-
function
|
|
209
|
-
return
|
|
318
|
+
function joinSyncRelativePath(parent, child) {
|
|
319
|
+
return parent === "" ? child : `${parent}/${child}`;
|
|
320
|
+
}
|
|
321
|
+
function isPathInsideRoot(root, path, nodeDeps) {
|
|
322
|
+
const rel = nodeDeps.relative(root, path);
|
|
323
|
+
return rel === "" || !rel.startsWith(`..${nodeDeps.sep}`) && rel !== ".." && !nodeDeps.isAbsolute(rel);
|
|
324
|
+
}
|
|
325
|
+
function localNodeIdentity(stats) {
|
|
326
|
+
return `${stats.dev}:${stats.ino}`;
|
|
210
327
|
}
|
|
211
328
|
function throwIfScanAborted(options) {
|
|
212
329
|
if (options.signal?.aborted === true) throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.js","names":[],"sources":["../../../src/sync/scanners/local.ts"],"sourcesContent":["import {\n isDownloadStagingDirectorySegment,\n isManagedDownloadStagingRoot,\n} from '../download-staging.ts'\nimport { localFilesystemErrorReason } from '../filesystem-errors.ts'\nimport {\n directoryMayContainSyncPaths,\n pathPassesSyncFilters,\n pathSkippedByRegExpInputLimit,\n} from '../filters.ts'\nimport { localFileIdentityFromStats } from '../local-file-identity.ts'\nimport { registerLocalFilesystemRoot } from '../local-filesystem-root.ts'\nimport { compareSyncRelativePaths } from '../path-order.ts'\nimport { isReservedSyncTempFileName } from '../path-safety.ts'\nimport { validateSyncFilters } from '../regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from '../scan-events.ts'\nimport { assertScanEntryLimit, scanEntryLimit } from '../scan-limit.ts'\nimport type { LocalSyncPath, SyncErrorEvent, SyncFolder, SyncScanOptions } from '../types.ts'\n\ntype LocalDirent = {\n readonly name: string\n isDirectory(): boolean\n isFile(): boolean\n}\n\ntype LocalStats = {\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n isFile(): boolean\n}\n\ntype LocalNodeDeps = {\n readdir(path: string, options: { readonly withFileTypes: true }): Promise<LocalDirent[]>\n lstat(path: string): Promise<LocalStats>\n join(...paths: string[]): string\n relative(from: string, to: string): string\n resolve(...paths: string[]): string\n sep: string\n}\n\n/**\n * Scans a local directory tree and yields {@link LocalSyncPath} entries sorted by relative path.\n * A root directory read failure aborts the scan with an error diagnostic. Per-entry file or\n * directory failures are reported through `onError` and the scan continues over readable siblings.\n * SDK-managed partial download file names are skipped so unfinished internal\n * temp files are not synchronized.\n * The current implementation collects matching entries before sorting, so memory usage is\n * proportional to the number of matched files.\n */\nexport class LocalFolder implements SyncFolder {\n readonly type = 'local' as const\n readonly appliesScanFilters = true as const\n readonly appliesScanSorting = true as const\n /** Resolved absolute path to the local root directory. */\n readonly root: string\n\n /**\n * Creates a new LocalFolder for the given root directory.\n * @param root - Absolute or relative path to the local directory to scan.\n */\n constructor(root: string) {\n this.root = resolvePathAtConstruction(root)\n registerLocalFilesystemRoot(this)\n }\n\n /**\n * Recursively walks the directory and yields files in sync path order.\n * @param options - Optional scan controls.\n */\n async *scan(options: SyncScanOptions = {}): AsyncGenerator<LocalSyncPath> {\n validateSyncFilters(options)\n const nodeDeps = await loadLocalNodeDeps()\n const root = nodeDeps.resolve(this.root)\n const collected: LocalSyncPath[] = []\n await this.walk(root, root, collected, options, scanEntryLimit(options), nodeDeps)\n collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath))\n for (const entry of collected) {\n throwIfScanAborted(options)\n yield entry\n }\n }\n\n /**\n * Recursively collects files from {@link dir} into {@link out}.\n * @param root - Resolved scan root used for relative path calculation.\n * @param dir - Absolute path of the directory to scan.\n * @param out - Accumulator array that receives discovered file entries.\n * @param options - Optional scan controls.\n * @param maxScanEntries - Maximum number of entries to retain before failing.\n * @param nodeDeps - Lazy-loaded Node filesystem and path helpers.\n */\n private async walk(\n root: string,\n dir: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n ): Promise<void> {\n throwIfScanAborted(options)\n let entries: LocalDirent[]\n try {\n entries = await nodeDeps.readdir(dir, { withFileTypes: true })\n } catch (err) {\n const error = this.emitScanError(\n options,\n relativePathFromRoot(root, dir, nodeDeps),\n 'directory',\n err,\n )\n if (dir === root) throw error\n return\n }\n\n for (const entry of entries) {\n throwIfScanAborted(options)\n const fullPath = nodeDeps.join(dir, entry.name)\n const rel = relativePathFromRoot(root, fullPath, nodeDeps)\n if (\n isDownloadStagingDirectorySegment(rel) &&\n entry.isDirectory() &&\n isDownloadStagingDirectorySegment(entry.name) &&\n (await isManagedDownloadStagingRoot(fullPath))\n ) {\n continue\n }\n if (rel.includes('\\\\')) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'unsafe-name',\n message: `Skipped local path ${JSON.stringify(rel)}: backslashes are not safe sync path characters`,\n })\n continue\n }\n if (isReservedSyncTempFileName(entry.name)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'stale-download-partial',\n message: `Skipped local path ${JSON.stringify(rel)}: reserved SDK partial download file`,\n })\n continue\n }\n // Symlinks, FIFOs, sockets, and device nodes are not syncable files.\n // Ignore them without poisoning delete-mode orphan handling for unrelated paths.\n if (entry.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n await this.walk(root, fullPath, out, options, maxScanEntries, nodeDeps)\n }\n } else if (entry.isFile()) {\n if (!pathPassesSyncFilters(rel, options)) {\n if (pathSkippedByRegExpInputLimit(rel, options)) {\n emitScannerSkip(options, regexpInputTooLongSkip(rel))\n }\n continue\n }\n let s: LocalStats\n try {\n s = await nodeDeps.lstat(fullPath)\n /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */\n if (!s.isFile()) {\n this.emitScanError(options, rel, 'file', new Error('not a regular file'))\n continue\n }\n /* v8 ignore stop */\n } catch (err) {\n /* v8 ignore next -- stat TOCTOU failures are not deterministic to trigger */\n this.emitScanError(options, relativePathFromRoot(root, fullPath, nodeDeps), 'file', err)\n continue\n }\n assertScanEntryLimit(out.length + 1, maxScanEntries)\n out.push({\n relativePath: rel,\n absolutePath: fullPath,\n modTimeMillis: Math.floor(s.mtimeMs),\n size: s.size,\n fileIdentity: localFileIdentityFromStats(s),\n })\n }\n }\n }\n\n private emitScanError(\n options: SyncScanOptions,\n path: string,\n kind: 'directory' | 'file',\n err: unknown,\n ): Error {\n const event: SyncErrorEvent = {\n type: 'error',\n path,\n size: 0,\n message: `failed to scan local ${kind}: ${localFilesystemErrorReason(err)}`,\n }\n options.onError?.(event)\n return new Error(event.message)\n }\n}\n\nasync function loadLocalNodeDeps(): Promise<LocalNodeDeps> {\n const [fsPromises, path] = await Promise.all([import('node:fs/promises'), import('node:path')])\n return {\n readdir: fsPromises.readdir as LocalNodeDeps['readdir'],\n lstat: fsPromises.lstat as LocalNodeDeps['lstat'],\n join: path.join,\n relative: path.relative,\n resolve: path.resolve,\n sep: path.sep,\n }\n}\n\nfunction resolvePathAtConstruction(root: string): string {\n const processLike = (\n globalThis as {\n process?: { cwd?: () => string; platform?: string }\n }\n ).process\n if (typeof processLike?.cwd !== 'function') return root\n\n const cwd = processLike.cwd()\n if (processLike.platform === 'win32') return resolveWindowsPath(cwd, root)\n return resolvePosixPath(cwd, root)\n}\n\nfunction resolvePosixPath(cwd: string, root: string): string {\n const joined = root.startsWith('/') ? root : `${cwd}/${root}`\n const resolved = normalizePathSegments(joined.split('/'), '/')\n return resolved === '' ? '/' : `/${resolved}`\n}\n\nfunction resolveWindowsPath(cwd: string, root: string): string {\n const normalizedRoot = root.replaceAll('/', '\\\\')\n const normalizedCwd = cwd.replaceAll('/', '\\\\')\n const drive = /^[A-Za-z]:/.exec(normalizedCwd)?.[0] ?? ''\n const cwdUnc = splitUncPath(normalizedCwd)\n if (/^\\\\\\\\/.test(normalizedRoot)) return normalizeUncPath(normalizedRoot)\n if (/^[A-Za-z]:\\\\/.test(normalizedRoot)) {\n const prefix = normalizedRoot.slice(0, 2)\n const rest = normalizedRoot.slice(3).split('\\\\')\n return joinWindowsRoot(prefix, normalizePathSegments(rest, '\\\\'))\n }\n if (/^[A-Za-z]:/.test(normalizedRoot)) {\n throw new Error('LocalFolder root must not be a drive-relative Windows path')\n }\n if (normalizedRoot.startsWith('\\\\')) {\n const rest = normalizePathSegments(normalizedRoot.slice(1).split('\\\\'), '\\\\')\n return joinWindowsRoot(cwdUnc?.prefix ?? drive, rest)\n }\n if (cwdUnc !== undefined) {\n const resolved = normalizePathSegments([...cwdUnc.rest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(cwdUnc.prefix, resolved)\n }\n const base = /^[A-Za-z]:\\\\/.test(normalizedCwd) ? normalizedCwd : `${drive}\\\\`\n const prefix = /^[A-Za-z]:/.exec(base)?.[0] ?? drive\n const baseRest = base.slice(prefix.length).replace(/^\\\\/, '').split('\\\\')\n const resolved = normalizePathSegments([...baseRest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(prefix, resolved)\n}\n\nfunction normalizeUncPath(path: string): string {\n const unc = splitUncPath(path)\n if (unc === undefined) return path\n return joinWindowsRoot(unc.prefix, normalizePathSegments(unc.rest, '\\\\'))\n}\n\nfunction splitUncPath(\n path: string,\n): { readonly prefix: string; readonly rest: string[] } | undefined {\n if (!path.startsWith('\\\\\\\\')) return undefined\n const parts = path.split('\\\\').filter((part) => part !== '')\n const [server, share, ...rest] = parts\n if (server === undefined || share === undefined) return undefined\n return { prefix: `\\\\\\\\${server}\\\\${share}`, rest }\n}\n\nfunction joinWindowsRoot(prefix: string, rest: string): string {\n return rest === '' ? `${prefix}\\\\` : `${prefix}\\\\${rest}`\n}\n\nfunction normalizePathSegments(segments: readonly string[], separator: '/' | '\\\\'): string {\n const out: string[] = []\n for (const segment of segments) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n out.pop()\n continue\n }\n out.push(segment)\n }\n return out.join(separator)\n}\n\nfunction relativePathFromRoot(root: string, path: string, nodeDeps: LocalNodeDeps): string {\n return nodeDeps.relative(root, path).split(nodeDeps.sep).join('/')\n}\n\nfunction throwIfScanAborted(options: SyncScanOptions): void {\n if (options.signal?.aborted === true) {\n throw options.signal.reason ?? new DOMException('Aborted', 'AbortError')\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local.js","names":[],"sources":["../../../src/sync/scanners/local.ts"],"sourcesContent":["import {\n isDownloadStagingDirectorySegment,\n isManagedDownloadStagingRoot,\n} from '../download-staging.ts'\nimport { localFilesystemErrorReason } from '../filesystem-errors.ts'\nimport {\n directoryMayContainSyncPaths,\n pathPassesSyncFilters,\n pathSkippedByRegExpInputLimit,\n} from '../filters.ts'\nimport { localFileIdentityFromStats } from '../local-file-identity.ts'\nimport { registerLocalFilesystemRoot } from '../local-filesystem-root.ts'\nimport { compareSyncRelativePaths } from '../path-order.ts'\nimport { isReservedSyncTempFileName } from '../path-safety.ts'\nimport { validateSyncFilters } from '../regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from '../scan-events.ts'\nimport { assertScanEntryLimit, scanEntryLimit } from '../scan-limit.ts'\nimport type { LocalSyncPath, SyncErrorEvent, SyncFolder, SyncScanOptions } from '../types.ts'\n\ntype LocalDirent = {\n readonly name: string\n isDirectory(): boolean\n isFile(): boolean\n isSymbolicLink(): boolean\n}\n\ntype LocalStats = {\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n isDirectory(): boolean\n isFile(): boolean\n}\n\ntype LocalNodeDeps = {\n readdir(path: string, options: { readonly withFileTypes: true }): Promise<LocalDirent[]>\n lstat(path: string): Promise<LocalStats>\n stat(path: string): Promise<LocalStats>\n join(...paths: string[]): string\n relative(from: string, to: string): string\n realpath(path: string): Promise<string>\n resolve(...paths: string[]): string\n isAbsolute(path: string): boolean\n sep: string\n}\n\n/**\n * Scans a local directory tree and yields {@link LocalSyncPath} entries sorted by relative path.\n * A root directory read failure aborts the scan with an error diagnostic. Per-entry file or\n * directory failures are reported through `onError` and the scan continues over readable siblings.\n * SDK-managed partial download file names are skipped so unfinished internal\n * temp files are not synchronized.\n * The current implementation collects matching entries before sorting, so memory usage is\n * proportional to the number of matched files.\n */\nexport class LocalFolder implements SyncFolder {\n readonly type = 'local' as const\n readonly appliesScanFilters = true as const\n readonly appliesScanSorting = true as const\n /** Resolved absolute path to the local root directory. */\n readonly root: string\n\n /**\n * Creates a new LocalFolder for the given root directory.\n * @param root - Absolute or relative path to the local directory to scan.\n */\n constructor(root: string) {\n this.root = resolvePathAtConstruction(root)\n registerLocalFilesystemRoot(this)\n }\n\n /**\n * Recursively walks the directory and yields files in sync path order.\n * @param options - Optional scan controls.\n */\n async *scan(options: SyncScanOptions = {}): AsyncGenerator<LocalSyncPath> {\n validateSyncFilters(options)\n const nodeDeps = await loadLocalNodeDeps()\n const root = nodeDeps.resolve(this.root)\n const followSymlinks =\n options.localSymlinks === 'follow' && options.requireLocalSafePaths !== true\n let scanRoot = root\n const collected: LocalSyncPath[] = []\n const activeDirectoryIds = new Set<string>()\n const followedDirectoryIds = new Set<string>()\n if (followSymlinks) {\n try {\n scanRoot = await nodeDeps.realpath(root)\n const rootStats = await nodeDeps.stat(scanRoot)\n if (rootStats.isDirectory()) activeDirectoryIds.add(localNodeIdentity(rootStats))\n } catch {\n // Root read errors are reported by walk(), preserving the existing diagnostic shape.\n }\n }\n await this.walk(\n scanRoot,\n scanRoot,\n '',\n collected,\n options,\n scanEntryLimit(options),\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath))\n for (const entry of collected) {\n throwIfScanAborted(options)\n yield entry\n }\n }\n\n /**\n * Recursively collects files from {@link dir} into {@link out}.\n * @param root - Resolved scan root used for relative path calculation.\n * @param dir - Absolute path of the directory to scan.\n * @param dirRel - Sync-relative path represented by {@link dir}.\n * @param out - Accumulator array that receives discovered file entries.\n * @param options - Optional scan controls.\n * @param maxScanEntries - Maximum number of entries to retain before failing.\n * @param nodeDeps - Lazy-loaded Node filesystem and path helpers.\n * @param activeDirectoryIds - Directory identities in the current recursion stack.\n * @param followedDirectoryIds - Followed symlink directory identities already traversed.\n */\n private async walk(\n root: string,\n dir: string,\n dirRel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n ): Promise<void> {\n throwIfScanAborted(options)\n let entries: LocalDirent[]\n try {\n entries = await nodeDeps.readdir(dir, { withFileTypes: true })\n } catch (err) {\n const error = this.emitScanError(options, dirRel, 'directory', err)\n if (dir === root) throw error\n return\n }\n\n for (const entry of entries) {\n throwIfScanAborted(options)\n const fullPath = nodeDeps.join(dir, entry.name)\n const rel = joinSyncRelativePath(dirRel, entry.name)\n if (\n isDownloadStagingDirectorySegment(rel) &&\n entry.isDirectory() &&\n isDownloadStagingDirectorySegment(entry.name) &&\n (await isManagedDownloadStagingRoot(fullPath))\n ) {\n continue\n }\n if (rel.includes('\\\\')) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'unsafe-name',\n message: `Skipped local path ${JSON.stringify(rel)}: backslashes are not safe sync path characters`,\n })\n continue\n }\n if (isReservedSyncTempFileName(entry.name)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'stale-download-partial',\n message: `Skipped local path ${JSON.stringify(rel)}: reserved SDK partial download file`,\n })\n continue\n }\n // Symlinks, FIFOs, sockets, and device nodes are not syncable files.\n // Ignore them without poisoning delete-mode orphan handling for unrelated paths.\n if (entry.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n await this.walkDirectory(\n root,\n fullPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n }\n } else if (entry.isFile()) {\n await this.collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps)\n } else if (entry.isSymbolicLink()) {\n await this.handleSymlink(\n root,\n fullPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n }\n }\n }\n\n private async walkDirectory(\n root: string,\n dir: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n knownStats?: LocalStats,\n ): Promise<void> {\n let stats = knownStats\n if (stats === undefined) {\n try {\n stats = await nodeDeps.stat(dir)\n } catch (err) {\n /* v8 ignore start -- stat TOCTOU failures after a Dirent directory result are not deterministic */\n this.emitScanError(options, rel, 'directory', err)\n return\n /* v8 ignore stop */\n }\n }\n /* v8 ignore start -- stat TOCTOU races after a Dirent directory result are not deterministic */\n if (!stats.isDirectory()) {\n this.emitScanError(options, rel, 'directory', new Error('not a directory'))\n return\n }\n /* v8 ignore stop */\n\n const identity = localNodeIdentity(stats)\n if (activeDirectoryIds.has(identity)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink-loop',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink would revisit an ancestor directory`,\n })\n return\n }\n\n activeDirectoryIds.add(identity)\n try {\n await this.walk(\n root,\n dir,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n } finally {\n activeDirectoryIds.delete(identity)\n }\n }\n\n private async collectFile(\n fullPath: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n knownStats?: LocalStats,\n ): Promise<void> {\n if (!pathPassesSyncFilters(rel, options)) {\n if (pathSkippedByRegExpInputLimit(rel, options)) {\n emitScannerSkip(options, regexpInputTooLongSkip(rel))\n }\n return\n }\n\n let s = knownStats\n if (s === undefined) {\n try {\n s = await nodeDeps.lstat(fullPath)\n /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */\n if (!s.isFile()) {\n this.emitScanError(options, rel, 'file', new Error('not a regular file'))\n return\n }\n /* v8 ignore stop */\n } catch (err) {\n /* v8 ignore start -- stat TOCTOU failures are not deterministic to trigger */\n this.emitScanError(options, rel, 'file', err)\n return\n /* v8 ignore stop */\n }\n }\n\n assertScanEntryLimit(out.length + 1, maxScanEntries)\n out.push({\n relativePath: rel,\n absolutePath: fullPath,\n modTimeMillis: Math.floor(s.mtimeMs),\n size: s.size,\n fileIdentity: localFileIdentityFromStats(s),\n })\n }\n\n private async handleSymlink(\n root: string,\n fullPath: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n ): Promise<void> {\n if ((options.localSymlinks ?? 'skip') === 'skip' || options.requireLocalSafePaths === true) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: local symlinks are disabled`,\n })\n return\n }\n\n let targetPath: string\n let targetStats: LocalStats\n try {\n targetPath = await nodeDeps.realpath(fullPath)\n targetStats = await nodeDeps.stat(targetPath)\n } catch (err) {\n this.emitScanError(options, rel, 'symlink', err)\n return\n }\n\n if (!isPathInsideRoot(root, targetPath, nodeDeps)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target is outside the sync root`,\n })\n return\n }\n\n if (targetStats.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n const identity = localNodeIdentity(targetStats)\n if (followedDirectoryIds.has(identity)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink-loop',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target was already visited`,\n })\n return\n }\n followedDirectoryIds.add(identity)\n await this.walkDirectory(\n root,\n targetPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n targetStats,\n )\n }\n return\n }\n\n if (targetStats.isFile()) {\n await this.collectFile(targetPath, rel, out, options, maxScanEntries, nodeDeps, targetStats)\n return\n }\n\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target is not a regular file or directory`,\n })\n }\n\n private emitScanError(\n options: SyncScanOptions,\n path: string,\n kind: 'directory' | 'file' | 'symlink',\n err: unknown,\n ): Error {\n const event: SyncErrorEvent = {\n type: 'error',\n path,\n size: 0,\n message: `failed to scan local ${kind}: ${localFilesystemErrorReason(err)}`,\n }\n options.onError?.(event)\n return new Error(event.message)\n }\n}\n\nasync function loadLocalNodeDeps(): Promise<LocalNodeDeps> {\n const [fsPromises, path] = await Promise.all([import('node:fs/promises'), import('node:path')])\n return {\n readdir: fsPromises.readdir as LocalNodeDeps['readdir'],\n lstat: fsPromises.lstat as LocalNodeDeps['lstat'],\n stat: fsPromises.stat as LocalNodeDeps['stat'],\n join: path.join,\n relative: path.relative,\n realpath: fsPromises.realpath,\n resolve: path.resolve,\n isAbsolute: path.isAbsolute,\n sep: path.sep,\n }\n}\n\nfunction resolvePathAtConstruction(root: string): string {\n const processLike = (\n globalThis as {\n process?: { cwd?: () => string; platform?: string }\n }\n ).process\n if (typeof processLike?.cwd !== 'function') return root\n\n const cwd = processLike.cwd()\n if (processLike.platform === 'win32') return resolveWindowsPath(cwd, root)\n return resolvePosixPath(cwd, root)\n}\n\nfunction resolvePosixPath(cwd: string, root: string): string {\n const joined = root.startsWith('/') ? root : `${cwd}/${root}`\n const resolved = normalizePathSegments(joined.split('/'), '/')\n return resolved === '' ? '/' : `/${resolved}`\n}\n\nfunction resolveWindowsPath(cwd: string, root: string): string {\n const normalizedRoot = root.replaceAll('/', '\\\\')\n const normalizedCwd = cwd.replaceAll('/', '\\\\')\n const drive = /^[A-Za-z]:/.exec(normalizedCwd)?.[0] ?? ''\n const cwdUnc = splitUncPath(normalizedCwd)\n if (/^\\\\\\\\/.test(normalizedRoot)) return normalizeUncPath(normalizedRoot)\n if (/^[A-Za-z]:\\\\/.test(normalizedRoot)) {\n const prefix = normalizedRoot.slice(0, 2)\n const rest = normalizedRoot.slice(3).split('\\\\')\n return joinWindowsRoot(prefix, normalizePathSegments(rest, '\\\\'))\n }\n if (/^[A-Za-z]:/.test(normalizedRoot)) {\n throw new Error('LocalFolder root must not be a drive-relative Windows path')\n }\n if (normalizedRoot.startsWith('\\\\')) {\n const rest = normalizePathSegments(normalizedRoot.slice(1).split('\\\\'), '\\\\')\n return joinWindowsRoot(cwdUnc?.prefix ?? drive, rest)\n }\n if (cwdUnc !== undefined) {\n const resolved = normalizePathSegments([...cwdUnc.rest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(cwdUnc.prefix, resolved)\n }\n const base = /^[A-Za-z]:\\\\/.test(normalizedCwd) ? normalizedCwd : `${drive}\\\\`\n const prefix = /^[A-Za-z]:/.exec(base)?.[0] ?? drive\n const baseRest = base.slice(prefix.length).replace(/^\\\\/, '').split('\\\\')\n const resolved = normalizePathSegments([...baseRest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(prefix, resolved)\n}\n\nfunction normalizeUncPath(path: string): string {\n const unc = splitUncPath(path)\n if (unc === undefined) return path\n return joinWindowsRoot(unc.prefix, normalizePathSegments(unc.rest, '\\\\'))\n}\n\nfunction splitUncPath(\n path: string,\n): { readonly prefix: string; readonly rest: string[] } | undefined {\n if (!path.startsWith('\\\\\\\\')) return undefined\n const parts = path.split('\\\\').filter((part) => part !== '')\n const [server, share, ...rest] = parts\n if (server === undefined || share === undefined) return undefined\n return { prefix: `\\\\\\\\${server}\\\\${share}`, rest }\n}\n\nfunction joinWindowsRoot(prefix: string, rest: string): string {\n return rest === '' ? `${prefix}\\\\` : `${prefix}\\\\${rest}`\n}\n\nfunction normalizePathSegments(segments: readonly string[], separator: '/' | '\\\\'): string {\n const out: string[] = []\n for (const segment of segments) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n out.pop()\n continue\n }\n out.push(segment)\n }\n return out.join(separator)\n}\n\nfunction joinSyncRelativePath(parent: string, child: string): string {\n return parent === '' ? child : `${parent}/${child}`\n}\n\nfunction isPathInsideRoot(root: string, path: string, nodeDeps: LocalNodeDeps): boolean {\n const rel = nodeDeps.relative(root, path)\n return (\n rel === '' ||\n (!rel.startsWith(`..${nodeDeps.sep}`) && rel !== '..' && !nodeDeps.isAbsolute(rel))\n )\n}\n\nfunction localNodeIdentity(stats: LocalStats): string {\n return `${stats.dev}:${stats.ino}`\n}\n\nfunction throwIfScanAborted(options: SyncScanOptions): void {\n if (options.signal?.aborted === true) {\n throw options.signal.reason ?? new DOMException('Aborted', 'AbortError')\n }\n}\n"],"mappings":"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"}
|
|
@@ -7,9 +7,10 @@ const require_sha1_options = require("./sha1-options.cjs");
|
|
|
7
7
|
const require_local_sha1 = require("./local-sha1.cjs");
|
|
8
8
|
const require_compare = require("./policies/compare.cjs");
|
|
9
9
|
const require_index$1 = require("./policies/index.cjs");
|
|
10
|
+
const require_filesystem_errors = require("./filesystem-errors.cjs");
|
|
10
11
|
const require_path_safety = require("./path-safety.cjs");
|
|
12
|
+
const require_download_staging = require("./download-staging.cjs");
|
|
11
13
|
const require_prefix = require("./prefix.cjs");
|
|
12
|
-
const require_filesystem_errors = require("./filesystem-errors.cjs");
|
|
13
14
|
const require_local_filesystem_root = require("./local-filesystem-root.cjs");
|
|
14
15
|
const require_b2_sha1_reader = require("./b2-sha1-reader.cjs");
|
|
15
16
|
const require_local_file_io = require("./local-file-io.cjs");
|
|
@@ -64,6 +65,7 @@ async function* synchronize(config) {
|
|
|
64
65
|
...options.exclude !== void 0 ? { exclude: options.exclude } : {},
|
|
65
66
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
66
67
|
...options.maxScanEntries !== void 0 ? { maxScanEntries: options.maxScanEntries } : {},
|
|
68
|
+
...options.localSymlinks !== void 0 ? { localSymlinks: options.localSymlinks } : {},
|
|
67
69
|
...direction === "b2-to-local" ? { requireLocalSafePaths: true } : {},
|
|
68
70
|
onError: (event) => {
|
|
69
71
|
scanHadError = true;
|
|
@@ -71,7 +73,10 @@ async function* synchronize(config) {
|
|
|
71
73
|
queueEvent(event);
|
|
72
74
|
}
|
|
73
75
|
};
|
|
74
|
-
const
|
|
76
|
+
const downloadStagingRoots = await downloadStagingRootCleanupTargets(direction, localRootContexts);
|
|
77
|
+
const factory = createActionFactory(config, localRootContexts, { onDownloadStagingRoot(target) {
|
|
78
|
+
downloadStagingRoots.set(target.managedDirectory, target);
|
|
79
|
+
} });
|
|
75
80
|
const readB2Sha1 = dryRun ? void 0 : createB2Sha1Reader(config);
|
|
76
81
|
const actionAbortController = new AbortController();
|
|
77
82
|
const removeAbortForwarder = forwardAbortSignal(options.signal, actionAbortController);
|
|
@@ -127,6 +132,7 @@ async function* synchronize(config) {
|
|
|
127
132
|
if (!completed) abortActionController(actionAbortController, new DOMException("Sync iterator closed", "AbortError"));
|
|
128
133
|
removeAbortForwarder();
|
|
129
134
|
await drainActions();
|
|
135
|
+
await removeDownloadStagingRootMarkersAndDirectoriesAfterSync(downloadStagingRoots);
|
|
130
136
|
}
|
|
131
137
|
async function collectPairs() {
|
|
132
138
|
const pairs = [];
|
|
@@ -145,6 +151,7 @@ async function* synchronize(config) {
|
|
|
145
151
|
return pairs;
|
|
146
152
|
}
|
|
147
153
|
async function* emitSha1Batch(batch) {
|
|
154
|
+
/* v8 ignore next -- SHA-1 batches are sliced only while index is within pairs.length */
|
|
148
155
|
if (batch.length === 0) return false;
|
|
149
156
|
await drainActions();
|
|
150
157
|
yield* emitQueuedEvents();
|
|
@@ -174,6 +181,7 @@ async function* synchronize(config) {
|
|
|
174
181
|
return false;
|
|
175
182
|
}
|
|
176
183
|
async function processPreparedBatch(batch) {
|
|
184
|
+
/* v8 ignore next -- callers guard empty SHA-1 batches before preparing pairs */
|
|
177
185
|
if (batch.length === 0) return {
|
|
178
186
|
items: [],
|
|
179
187
|
aborted: false
|
|
@@ -201,6 +209,7 @@ async function* synchronize(config) {
|
|
|
201
209
|
function planPreparedPair(pair, prepared) {
|
|
202
210
|
const event = {
|
|
203
211
|
type: "compare",
|
|
212
|
+
/* v8 ignore next -- zipFolders never emits a pair with both sides absent */
|
|
204
213
|
path: (pair[0] ?? pair[1])?.relativePath ?? "",
|
|
205
214
|
size: 0,
|
|
206
215
|
bytesHashed: prepared.bytesHashed,
|
|
@@ -224,9 +233,9 @@ async function* synchronize(config) {
|
|
|
224
233
|
event,
|
|
225
234
|
actions: [new require_index.SkipAction(prepared.pair[1].relativePath, "not removed because scan errors occurred")]
|
|
226
235
|
};
|
|
227
|
-
if (
|
|
236
|
+
if (scanEvents.sourceInventoryIncompleteMessage !== void 0 && prepared.pair[0] === null && prepared.pair[1] !== null) return {
|
|
228
237
|
event,
|
|
229
|
-
actions: [new require_index.SkipAction(prepared.pair[1].relativePath, scanEvents.sourceInventoryIncompleteMessage
|
|
238
|
+
actions: [new require_index.SkipAction(prepared.pair[1].relativePath, scanEvents.sourceInventoryIncompleteMessage)]
|
|
230
239
|
};
|
|
231
240
|
return {
|
|
232
241
|
event,
|
|
@@ -288,6 +297,24 @@ async function* synchronize(config) {
|
|
|
288
297
|
while (runningActions.size > 0) await Promise.race(runningActions);
|
|
289
298
|
}
|
|
290
299
|
}
|
|
300
|
+
async function downloadStagingRootCleanupTargets(direction, localRootContexts) {
|
|
301
|
+
const targets = /* @__PURE__ */ new Map();
|
|
302
|
+
if (direction !== "b2-to-local" || localRootContexts.dest === void 0) return targets;
|
|
303
|
+
const target = {
|
|
304
|
+
managedDirectory: (await import("node:path")).join(localRootContexts.dest.realPath, require_download_staging.DOWNLOAD_STAGING_DIRECTORY_NAME),
|
|
305
|
+
rootRealPath: localRootContexts.dest.realPath
|
|
306
|
+
};
|
|
307
|
+
targets.set(target.managedDirectory, target);
|
|
308
|
+
return targets;
|
|
309
|
+
}
|
|
310
|
+
async function removeDownloadStagingRootMarkersAndDirectoriesAfterSync(targets) {
|
|
311
|
+
if (targets.size === 0) return;
|
|
312
|
+
const path = await import("node:path");
|
|
313
|
+
for (const target of targets.values()) {
|
|
314
|
+
await synchronizerTestHooks.beforeDownloadStagingRootCleanup?.(target.managedDirectory);
|
|
315
|
+
await require_download_staging.removeDownloadStagingRootMarkerAndDirectory(target.rootRealPath, target.managedDirectory, path);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
291
318
|
/**
|
|
292
319
|
* Normalizes user-provided sync concurrency before it controls compare batches and transfers.
|
|
293
320
|
*
|
|
@@ -463,10 +490,11 @@ function toSseCDownloadKey(setting) {
|
|
|
463
490
|
*
|
|
464
491
|
* @param config - Synchronizer configuration containing source, destination, and options.
|
|
465
492
|
* @param localRootContexts - Resolved filesystem roots captured before action creation.
|
|
493
|
+
* @param lifecycleHooks - Hooks used to report action-scoped cleanup state.
|
|
466
494
|
*
|
|
467
495
|
* @returns An action factory bound to the provided configuration.
|
|
468
496
|
*/
|
|
469
|
-
function createActionFactory(config, localRootContexts) {
|
|
497
|
+
function createActionFactory(config, localRootContexts, lifecycleHooks = {}) {
|
|
470
498
|
const upConfig = config;
|
|
471
499
|
const downConfig = config;
|
|
472
500
|
const uploadPrefix = require_prefix.asRawB2KeyPrefix(upConfig.prefix ?? b2FolderRawPrefix(config.dest) ?? "");
|
|
@@ -481,16 +509,16 @@ function createActionFactory(config, localRootContexts) {
|
|
|
481
509
|
const root = rootContext?.root ?? upConfig.source.root ?? "";
|
|
482
510
|
const fileName = dest !== void 0 ? validateB2SyncPathInPrefix(uploadPrefix, dest) : `${uploadPrefix}${relPath}`;
|
|
483
511
|
if (rootContext !== void 0) await assertLocalRootContextCurrent(rootContext, relPath, { allowSymlinkRoot: true });
|
|
484
|
-
const targetPath = rootContext === void 0 ? await resolveContainedLocalPath(root, source.relativePath, absPath) : await resolveContainedLocalPath(rootContext.realPath, source.relativePath);
|
|
485
|
-
throwIfAborted(signal);
|
|
512
|
+
const targetPath = rootContext === void 0 ? await resolveContainedLocalPath(root, source.relativePath, absPath) : config.options.localSymlinks === "follow" ? await resolveContainedLocalPath(rootContext.realPath, source.relativePath, absPath) : await resolveContainedLocalPath(rootContext.realPath, source.relativePath);
|
|
513
|
+
throwIfAborted(actionSignal(signal));
|
|
486
514
|
const fileSource = await createValidatedUploadFileSource(source, targetPath);
|
|
487
|
-
throwIfAborted(signal);
|
|
515
|
+
throwIfAborted(actionSignal(signal));
|
|
488
516
|
const serverSideEncryption = config.options.encryptionProvider?.getSettingForUpload(fileName, fileSource.size);
|
|
489
517
|
await bucket.upload({
|
|
490
518
|
fileName,
|
|
491
519
|
source: fileSource,
|
|
492
520
|
...serverSideEncryption !== void 0 ? { serverSideEncryption } : {},
|
|
493
|
-
|
|
521
|
+
signal: actionSignal(signal)
|
|
494
522
|
});
|
|
495
523
|
});
|
|
496
524
|
},
|
|
@@ -509,14 +537,22 @@ function createActionFactory(config, localRootContexts) {
|
|
|
509
537
|
const serverSideEncryption = toSseCDownloadKey(config.options.encryptionProvider?.getSettingForDownload(source.selectedVersion));
|
|
510
538
|
const result = await bucket.file(b2FileName).downloadById(source.selectedVersion.fileId, {
|
|
511
539
|
...serverSideEncryption !== void 0 ? { serverSideEncryption } : {},
|
|
512
|
-
|
|
540
|
+
signal: actionSignal(signal)
|
|
513
541
|
});
|
|
514
542
|
try {
|
|
515
543
|
await require_local_file_io.writeLocalStreamInsideRoot(root, relPath, result.body, {
|
|
516
544
|
expectedBytes,
|
|
517
545
|
...scannedDest !== void 0 ? { expectedDestination: scannedDest } : {},
|
|
518
546
|
idleTimeoutMillis,
|
|
519
|
-
...
|
|
547
|
+
...lifecycleHooks.onDownloadStagingRoot !== void 0 ? { onDownloadStagingRoot: (managedDirectory) => {
|
|
548
|
+
const rootRealPath = localRootContexts.dest?.realPath;
|
|
549
|
+
if (rootRealPath === void 0) return;
|
|
550
|
+
lifecycleHooks.onDownloadStagingRoot?.({
|
|
551
|
+
managedDirectory,
|
|
552
|
+
rootRealPath
|
|
553
|
+
});
|
|
554
|
+
} } : {},
|
|
555
|
+
signal: actionSignal(signal)
|
|
520
556
|
});
|
|
521
557
|
} catch (err) {
|
|
522
558
|
await cancelReadableStreamBody(result.body, err);
|
|
@@ -534,7 +570,7 @@ function createActionFactory(config, localRootContexts) {
|
|
|
534
570
|
const bucket = upConfig.bucket ?? downConfig.bucket;
|
|
535
571
|
assertBucket(bucket, "hide");
|
|
536
572
|
return new require_index.HideAction(path, async (_relPath, signal) => {
|
|
537
|
-
await bucket.hideFile(`${uploadPrefix}${path}`, signal
|
|
573
|
+
await bucket.hideFile(`${uploadPrefix}${path}`, { signal: actionSignal(signal) });
|
|
538
574
|
});
|
|
539
575
|
},
|
|
540
576
|
hideB2Path(path) {
|
|
@@ -542,7 +578,7 @@ function createActionFactory(config, localRootContexts) {
|
|
|
542
578
|
assertBucket(bucket, "hide");
|
|
543
579
|
const b2FileName = validateB2SyncPathInPrefix(uploadPrefix, path);
|
|
544
580
|
return new require_index.HideAction(path.relativePath, async (_relPath, signal) => {
|
|
545
|
-
await bucket.hideFile(b2FileName, signal
|
|
581
|
+
await bucket.hideFile(b2FileName, { signal: actionSignal(signal) });
|
|
546
582
|
});
|
|
547
583
|
},
|
|
548
584
|
deleteRemote(path) {
|
|
@@ -550,16 +586,16 @@ function createActionFactory(config, localRootContexts) {
|
|
|
550
586
|
assertBucket(bucket, "delete");
|
|
551
587
|
const b2FileName = validateB2SyncPathInPrefix(uploadPrefix, path);
|
|
552
588
|
return new require_index.DeleteRemoteAction(path.relativePath, path.selectedVersion.fileId, async (fileId$1, _fileName, signal) => {
|
|
553
|
-
await bucket.deleteFileVersion(b2FileName, require_ids.fileId(fileId$1), signal
|
|
589
|
+
await bucket.deleteFileVersion(b2FileName, require_ids.fileId(fileId$1), { signal: actionSignal(signal) });
|
|
554
590
|
});
|
|
555
591
|
},
|
|
556
592
|
deleteLocal(path) {
|
|
557
593
|
const rootContext = localRootContexts.dest;
|
|
558
594
|
const root = rootContext?.root ?? localSyncRoot(downConfig.dest);
|
|
559
595
|
return new require_index.DeleteLocalAction(path.relativePath, path.absolutePath, async (absPath, signal) => {
|
|
560
|
-
signal
|
|
596
|
+
actionSignal(signal).throwIfAborted();
|
|
561
597
|
if (absPath !== path.absolutePath) throw new Error(`Refusing to delete outside sync root: ${path.relativePath}`);
|
|
562
|
-
signal
|
|
598
|
+
actionSignal(signal).throwIfAborted();
|
|
563
599
|
try {
|
|
564
600
|
if (rootContext !== void 0) await assertLocalRootContextCurrent(rootContext, path.relativePath, { allowSymlinkRoot: false });
|
|
565
601
|
await require_local_file_io.deleteLocalFileInsideRoot(root, path);
|
|
@@ -585,7 +621,7 @@ function createActionFactory(config, localRootContexts) {
|
|
|
585
621
|
fileName: targetPath,
|
|
586
622
|
...destinationServerSideEncryption !== void 0 ? { destinationServerSideEncryption } : {},
|
|
587
623
|
...sourceServerSideEncryption !== void 0 ? { sourceServerSideEncryption } : {},
|
|
588
|
-
|
|
624
|
+
signal: actionSignal(signal)
|
|
589
625
|
});
|
|
590
626
|
});
|
|
591
627
|
}
|
|
@@ -651,10 +687,15 @@ function validateB2SyncPathInPrefix(prefix, path, operation = "mutate") {
|
|
|
651
687
|
function isB2SyncPath(path) {
|
|
652
688
|
return "selectedVersion" in path;
|
|
653
689
|
}
|
|
690
|
+
var defaultActionSignal = new AbortController().signal;
|
|
691
|
+
function actionSignal(signal) {
|
|
692
|
+
return signal ?? defaultActionSignal;
|
|
693
|
+
}
|
|
654
694
|
function throwIfAborted(signal) {
|
|
655
|
-
if (signal
|
|
695
|
+
if (signal.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
656
696
|
}
|
|
657
697
|
async function cancelReadableStreamBody(body, reason) {
|
|
698
|
+
/* v8 ignore next -- writeLocalStreamInsideRoot releases the body reader before throwing */
|
|
658
699
|
if (body.locked) return;
|
|
659
700
|
try {
|
|
660
701
|
await body.cancel(reason);
|
|
@@ -704,10 +745,7 @@ function bufferScanEvent(buffer, event, direction, scanSide) {
|
|
|
704
745
|
else buffer.dropped++;
|
|
705
746
|
}
|
|
706
747
|
function* drainScanEvents(buffer) {
|
|
707
|
-
while (buffer.events.length > 0)
|
|
708
|
-
const event = buffer.events.shift();
|
|
709
|
-
if (event) yield event;
|
|
710
|
-
}
|
|
748
|
+
while (buffer.events.length > 0) yield buffer.events.shift();
|
|
711
749
|
if (buffer.dropped > 0) {
|
|
712
750
|
yield {
|
|
713
751
|
type: "skip",
|
|
@@ -722,9 +760,6 @@ function* drainScanEvents(buffer) {
|
|
|
722
760
|
function scanHadFilesystemError(scanEvents) {
|
|
723
761
|
return scanEvents.fatalFilesystemErrorMessage !== void 0;
|
|
724
762
|
}
|
|
725
|
-
function sourceInventoryIncomplete(scanEvents) {
|
|
726
|
-
return scanEvents.sourceInventoryIncompleteMessage !== void 0;
|
|
727
|
-
}
|
|
728
763
|
function scanFilesystemError(scanEvents) {
|
|
729
764
|
return scanEvents.fatalFilesystemErrorMessage === void 0 ? void 0 : new Error(scanEvents.fatalFilesystemErrorMessage);
|
|
730
765
|
}
|
|
@@ -743,14 +778,26 @@ function isLocalDeleteSafetyError(err) {
|
|
|
743
778
|
}
|
|
744
779
|
async function resolveContainedLocalPath(root, relativePath, absolutePath) {
|
|
745
780
|
if (root === "") throw new Error("Local sync root required for filesystem mutation");
|
|
781
|
+
const { realpath } = await import("node:fs/promises");
|
|
746
782
|
const { isAbsolute, relative, resolve, sep } = await import("node:path");
|
|
747
783
|
const safeRoot = resolve(root);
|
|
748
784
|
const target = absolutePath === void 0 ? resolve(safeRoot, relativePath) : resolve(absolutePath);
|
|
749
|
-
|
|
785
|
+
let containmentRoot = safeRoot;
|
|
786
|
+
let pathFromRoot = relative(containmentRoot, target);
|
|
787
|
+
let escapesRoot = pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot);
|
|
788
|
+
if (escapesRoot && absolutePath !== void 0) {
|
|
789
|
+
const realRoot = await realpath(safeRoot).catch(() => safeRoot);
|
|
790
|
+
const realPathFromRoot = relative(realRoot, target);
|
|
791
|
+
if (!(realPathFromRoot === ".." || realPathFromRoot.startsWith(`..${sep}`) || isAbsolute(realPathFromRoot))) {
|
|
792
|
+
containmentRoot = realRoot;
|
|
793
|
+
pathFromRoot = realPathFromRoot;
|
|
794
|
+
escapesRoot = false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
750
797
|
/* v8 ignore next -- defense-in-depth after prior no-follow and symlink checks. */
|
|
751
|
-
if (
|
|
752
|
-
await assertLocalRootHasNoSymlink(
|
|
753
|
-
await assertPathHasNoSymlinkComponents(
|
|
798
|
+
if (escapesRoot) throw new Error(`Refusing to access path outside sync root: ${relativePath}`);
|
|
799
|
+
await assertLocalRootHasNoSymlink(containmentRoot, relativePath);
|
|
800
|
+
await assertPathHasNoSymlinkComponents(containmentRoot, pathFromRoot, relativePath);
|
|
754
801
|
return target;
|
|
755
802
|
}
|
|
756
803
|
async function assertLocalRootHasNoSymlink(safeRoot, relativePath) {
|