@blamejs/blamejs-shop 0.4.33 → 0.4.37
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 +8 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +54 -38
- package/lib/vendor/blamejs/.github/workflows/ci.yml +12 -12
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +37 -5
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +4 -0
- package/lib/vendor/blamejs/README.md +5 -2
- package/lib/vendor/blamejs/SECURITY.md +3 -1
- package/lib/vendor/blamejs/api-snapshot.json +137 -2
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +1 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/archive-read.js +2 -1
- package/lib/vendor/blamejs/lib/archive-tar-read.js +2 -1
- package/lib/vendor/blamejs/lib/atomic-file.js +5 -0
- package/lib/vendor/blamejs/lib/audit.js +2 -0
- package/lib/vendor/blamejs/lib/cli.js +8 -1
- package/lib/vendor/blamejs/lib/config-drift.js +2 -1
- package/lib/vendor/blamejs/lib/db.js +15 -2
- package/lib/vendor/blamejs/lib/dsa.js +482 -0
- package/lib/vendor/blamejs/lib/framework-error.js +14 -0
- package/lib/vendor/blamejs/lib/http-client.js +5 -2
- package/lib/vendor/blamejs/lib/local-db-thin.js +3 -2
- package/lib/vendor/blamejs/lib/log-stream-local.js +1 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +9 -2
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +16 -7
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +36 -11
- package/lib/vendor/blamejs/lib/mtls-ca.js +2 -2
- package/lib/vendor/blamejs/lib/observability.js +3 -2
- package/lib/vendor/blamejs/lib/pipl-cn.js +377 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +5 -5
- package/lib/vendor/blamejs/lib/self-update.js +1 -1
- package/lib/vendor/blamejs/lib/session.js +64 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -3
- package/lib/vendor/blamejs/lib/watcher.js +8 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.15.8.json +48 -0
- package/lib/vendor/blamejs/release-notes/v0.15.9.json +58 -0
- package/lib/vendor/blamejs/scripts/generate-ssdf-attestation.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-rename-retry.test.js +70 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +174 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsa.test.js +169 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +40 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/pipl-cn.test.js +172 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +57 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +7 -3
- package/package.json +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// b.pipl.sccFilingAssessment + b.pipl.securityAssessmentCertificate —
|
|
3
|
+
// China PIPL Art. 38/40/55 cross-border transfer record-builders (pure;
|
|
4
|
+
// no DB). Drives the real b.pipl.* consumer path, asserts frozen records +
|
|
5
|
+
// audit emission via a captured injected sink, and the config-time throws.
|
|
6
|
+
|
|
7
|
+
var helpers = require("../helpers");
|
|
8
|
+
var b = helpers.b;
|
|
9
|
+
var check = helpers.check;
|
|
10
|
+
|
|
11
|
+
// A b.audit-shaped capture sink — the builder prefers an injected
|
|
12
|
+
// opts.audit object over the global b.audit (drop-silent without a DB
|
|
13
|
+
// handler), so this is how the test asserts emission.
|
|
14
|
+
function _captureAudit() {
|
|
15
|
+
var events = [];
|
|
16
|
+
return { events: events, safeEmit: function (ev) { events.push(ev); } };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function expectCode(label, fn, code) {
|
|
20
|
+
var threw = null;
|
|
21
|
+
try { fn(); } catch (e) { threw = e; }
|
|
22
|
+
check(label, threw && (threw.code || "") === code);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expectThrows(label, fn) {
|
|
26
|
+
var threw = null;
|
|
27
|
+
try { fn(); } catch (e) { threw = e; }
|
|
28
|
+
check(label, threw instanceof Error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function run() {
|
|
32
|
+
check("sccFilingAssessment is a function", typeof b.pipl.sccFilingAssessment === "function");
|
|
33
|
+
check("securityAssessmentCertificate is a function", typeof b.pipl.securityAssessmentCertificate === "function");
|
|
34
|
+
check("PiplError exposed on b.frameworkError", typeof b.frameworkError.PiplError === "function");
|
|
35
|
+
check("b.pipl.PiplError is the same constructor", b.pipl.PiplError === b.frameworkError.PiplError);
|
|
36
|
+
check("LEGAL_BASES is the Art. 38(1) triad",
|
|
37
|
+
b.pipl.LEGAL_BASES.length === 3 &&
|
|
38
|
+
b.pipl.LEGAL_BASES.indexOf("standard-contract") !== -1 &&
|
|
39
|
+
b.pipl.LEGAL_BASES.indexOf("security-assessment") !== -1 &&
|
|
40
|
+
b.pipl.LEGAL_BASES.indexOf("certification") !== -1);
|
|
41
|
+
check("pipl-cn is a cross-border-regulated posture",
|
|
42
|
+
b.compliance.isCrossBorderRegulated("pipl-cn") === true);
|
|
43
|
+
|
|
44
|
+
var recordedAt = 1700000000000;
|
|
45
|
+
|
|
46
|
+
// ---- sccFilingAssessment: below-threshold standard contract ----
|
|
47
|
+
var sink1 = _captureAudit();
|
|
48
|
+
var scc = b.pipl.sccFilingAssessment({
|
|
49
|
+
assessmentId: "xfer-1", transferType: "processor", recipientJurisdiction: "US",
|
|
50
|
+
dataCategories: ["contact", "billing"], legalBasis: "standard-contract",
|
|
51
|
+
volume: 5000, sensitivePI: false, recordedAt: recordedAt, audit: sink1,
|
|
52
|
+
});
|
|
53
|
+
check("scc: honors standard-contract below thresholds", scc.mechanismRequired === "standard-contract");
|
|
54
|
+
check("scc: securityAssessmentRequired false", scc.securityAssessmentRequired === false);
|
|
55
|
+
check("scc: record frozen", Object.isFrozen(scc));
|
|
56
|
+
check("scc: dataCategories frozen", Object.isFrozen(scc.dataCategories));
|
|
57
|
+
check("scc: nextReviewDueBy = recordedAt + 1 year",
|
|
58
|
+
scc.nextReviewDueBy === recordedAt + b.constants.TIME.days(365));
|
|
59
|
+
check("scc: emitted pipl.transfer.assessed",
|
|
60
|
+
sink1.events.length === 1 && sink1.events[0].action === "pipl.transfer.assessed");
|
|
61
|
+
check("scc: audit carries mechanismRequired",
|
|
62
|
+
sink1.events[0].metadata.mechanismRequired === "standard-contract");
|
|
63
|
+
|
|
64
|
+
// ---- CIIO forces security assessment over declared basis ----
|
|
65
|
+
var ciio = b.pipl.sccFilingAssessment({
|
|
66
|
+
assessmentId: "xfer-2", transferType: "controller-to-controller", recipientJurisdiction: "EU",
|
|
67
|
+
dataCategories: ["health"], legalBasis: "standard-contract",
|
|
68
|
+
volume: 100, sensitivePI: true, ciio: true, recordedAt: recordedAt,
|
|
69
|
+
});
|
|
70
|
+
check("scc: CIIO forces security-assessment", ciio.mechanismRequired === "security-assessment");
|
|
71
|
+
check("scc: CIIO sets securityAssessmentRequired", ciio.securityAssessmentRequired === true);
|
|
72
|
+
check("scc: CIIO trigger named", ciio.securityAssessmentTriggers.indexOf("ciio") !== -1);
|
|
73
|
+
check("scc: mandated assessment carries 3-year clock",
|
|
74
|
+
ciio.nextReviewDueBy === recordedAt + b.constants.TIME.days(365 * 3));
|
|
75
|
+
|
|
76
|
+
// ---- >1M non-sensitive PI forces it ----
|
|
77
|
+
var bigVol = b.pipl.sccFilingAssessment({
|
|
78
|
+
assessmentId: "xfer-3", transferType: "processor", recipientJurisdiction: "SG",
|
|
79
|
+
dataCategories: ["contact"], legalBasis: "certification",
|
|
80
|
+
volume: 1000001, sensitivePI: false, recordedAt: recordedAt,
|
|
81
|
+
});
|
|
82
|
+
check("scc: >1M non-sensitive volume forces security-assessment",
|
|
83
|
+
bigVol.mechanismRequired === "security-assessment" &&
|
|
84
|
+
bigVol.securityAssessmentTriggers.indexOf("non-sensitive-pi-volume") !== -1);
|
|
85
|
+
|
|
86
|
+
// ---- 100k-1M non-sensitive band is SCC, NOT security-assessment (the
|
|
87
|
+
// 100k cumulative threshold is the standard-contract tier per the CAC
|
|
88
|
+
// 2024 Provisions; a 200k non-sensitive transfer must not over-classify) ----
|
|
89
|
+
var midBand = b.pipl.sccFilingAssessment({
|
|
90
|
+
assessmentId: "xfer-3b", transferType: "processor", recipientJurisdiction: "US",
|
|
91
|
+
dataCategories: ["contact"], legalBasis: "standard-contract",
|
|
92
|
+
volume: 200000, sensitivePI: false, recordedAt: recordedAt,
|
|
93
|
+
});
|
|
94
|
+
check("scc: 200k non-sensitive stays standard-contract (no over-classify)",
|
|
95
|
+
midBand.mechanismRequired === "standard-contract" &&
|
|
96
|
+
midBand.securityAssessmentRequired === false);
|
|
97
|
+
|
|
98
|
+
// ---- THIS transfer's volume counts toward the cumulative sensitive
|
|
99
|
+
// threshold: a first transfer of 10,001 sensitive subjects forces it
|
|
100
|
+
// even with cumulativeSensitivePI omitted (defaults 0) ----
|
|
101
|
+
var firstSens = b.pipl.sccFilingAssessment({
|
|
102
|
+
assessmentId: "xfer-3c", transferType: "processor", recipientJurisdiction: "US",
|
|
103
|
+
dataCategories: ["biometric"], legalBasis: "standard-contract",
|
|
104
|
+
volume: 10001, sensitivePI: true, recordedAt: recordedAt,
|
|
105
|
+
});
|
|
106
|
+
check("scc: first 10,001 sensitive-PI transfer forces it (own volume counts)",
|
|
107
|
+
firstSens.securityAssessmentRequired === true &&
|
|
108
|
+
firstSens.securityAssessmentTriggers.indexOf("sensitive-pi-volume") !== -1);
|
|
109
|
+
|
|
110
|
+
// ---- cumulative sensitive-PI threshold (this transfer + prior cumulative) ----
|
|
111
|
+
var cumSens = b.pipl.sccFilingAssessment({
|
|
112
|
+
assessmentId: "xfer-4", transferType: "processor", recipientJurisdiction: "US",
|
|
113
|
+
dataCategories: ["biometric"], legalBasis: "standard-contract",
|
|
114
|
+
volume: 200, sensitivePI: true, cumulativeSensitivePI: 10001, recordedAt: recordedAt,
|
|
115
|
+
});
|
|
116
|
+
check("scc: >10k cumulative sensitive-PI forces it",
|
|
117
|
+
cumSens.securityAssessmentRequired === true &&
|
|
118
|
+
cumSens.securityAssessmentTriggers.indexOf("sensitive-pi-volume") !== -1);
|
|
119
|
+
|
|
120
|
+
// ---- securityAssessmentCertificate: happy path ----
|
|
121
|
+
var sink3 = _captureAudit();
|
|
122
|
+
var cert = b.pipl.securityAssessmentCertificate({
|
|
123
|
+
certId: "sa-1", assessmentScope: "CRM outbound replication",
|
|
124
|
+
dataExporter: "Acme (Shanghai) Co., Ltd.", overseasRecipient: "Acme Inc.",
|
|
125
|
+
riskRating: "medium", safeguards: ["XChaCha20 at rest", "standard contractual clauses"],
|
|
126
|
+
filingRef: "CAC-2026-0042", recordedAt: recordedAt, audit: sink3,
|
|
127
|
+
});
|
|
128
|
+
check("cert: record frozen", Object.isFrozen(cert));
|
|
129
|
+
check("cert: safeguards frozen", Object.isFrozen(cert.safeguards));
|
|
130
|
+
check("cert: validUntil = recordedAt + 3 years",
|
|
131
|
+
cert.validUntil === recordedAt + b.constants.TIME.days(365 * 3));
|
|
132
|
+
check("cert: filingRef carried", cert.filingRef === "CAC-2026-0042");
|
|
133
|
+
check("cert: emitted pipl.security_assessment.recorded",
|
|
134
|
+
sink3.events.length === 1 && sink3.events[0].action === "pipl.security_assessment.recorded");
|
|
135
|
+
check("cert: filingRef omitted defaults null",
|
|
136
|
+
b.pipl.securityAssessmentCertificate({
|
|
137
|
+
certId: "sa-2", assessmentScope: "s", dataExporter: "e", overseasRecipient: "r",
|
|
138
|
+
riskRating: "low", safeguards: ["x"], recordedAt: recordedAt,
|
|
139
|
+
}).filingRef === null);
|
|
140
|
+
|
|
141
|
+
// ---- Config-time throws ----
|
|
142
|
+
expectCode("scc: non-object opts throws",
|
|
143
|
+
function () { b.pipl.sccFilingAssessment("nope"); }, "pipl/bad-opts");
|
|
144
|
+
expectThrows("scc: unknown opt key throws",
|
|
145
|
+
function () { b.pipl.sccFilingAssessment({ assessmentId: "x", bogusKey: 1 }); });
|
|
146
|
+
expectCode("scc: missing assessmentId throws",
|
|
147
|
+
function () { b.pipl.sccFilingAssessment({ transferType: "p", recipientJurisdiction: "US", dataCategories: ["c"], legalBasis: "standard-contract", volume: 1, sensitivePI: false, recordedAt: recordedAt }); }, "pipl/bad-assessment-id");
|
|
148
|
+
expectCode("scc: empty dataCategories throws",
|
|
149
|
+
function () { b.pipl.sccFilingAssessment({ assessmentId: "x", transferType: "p", recipientJurisdiction: "US", dataCategories: [], legalBasis: "standard-contract", volume: 1, sensitivePI: false, recordedAt: recordedAt }); }, "pipl/bad-data-categories");
|
|
150
|
+
expectCode("scc: bad legalBasis throws",
|
|
151
|
+
function () { b.pipl.sccFilingAssessment({ assessmentId: "x", transferType: "p", recipientJurisdiction: "US", dataCategories: ["c"], legalBasis: "bogus", volume: 1, sensitivePI: false, recordedAt: recordedAt }); }, "pipl/bad-legal-basis");
|
|
152
|
+
expectCode("scc: non-boolean sensitivePI throws",
|
|
153
|
+
function () { b.pipl.sccFilingAssessment({ assessmentId: "x", transferType: "p", recipientJurisdiction: "US", dataCategories: ["c"], legalBasis: "standard-contract", volume: 1, sensitivePI: "yes", recordedAt: recordedAt }); }, "pipl/bad-sensitive-pi");
|
|
154
|
+
expectCode("scc: missing recordedAt throws",
|
|
155
|
+
function () { b.pipl.sccFilingAssessment({ assessmentId: "x", transferType: "p", recipientJurisdiction: "US", dataCategories: ["c"], legalBasis: "standard-contract", volume: 1, sensitivePI: false }); }, "pipl/bad-recorded-at");
|
|
156
|
+
|
|
157
|
+
expectCode("cert: missing certId throws",
|
|
158
|
+
function () { b.pipl.securityAssessmentCertificate({ assessmentScope: "s", dataExporter: "e", overseasRecipient: "r", riskRating: "low", safeguards: ["x"], recordedAt: recordedAt }); }, "pipl/bad-cert-id");
|
|
159
|
+
expectCode("cert: bad riskRating throws",
|
|
160
|
+
function () { b.pipl.securityAssessmentCertificate({ certId: "c", assessmentScope: "s", dataExporter: "e", overseasRecipient: "r", riskRating: "critical", safeguards: ["x"], recordedAt: recordedAt }); }, "pipl/bad-risk-rating");
|
|
161
|
+
expectCode("cert: empty safeguards throws",
|
|
162
|
+
function () { b.pipl.securityAssessmentCertificate({ certId: "c", assessmentScope: "s", dataExporter: "e", overseasRecipient: "r", riskRating: "low", safeguards: [], recordedAt: recordedAt }); }, "pipl/bad-safeguards");
|
|
163
|
+
expectCode("cert: bad audit sink shape throws",
|
|
164
|
+
function () { b.pipl.securityAssessmentCertificate({ certId: "c", assessmentScope: "s", dataExporter: "e", overseasRecipient: "r", riskRating: "low", safeguards: ["x"], recordedAt: recordedAt, audit: { nope: 1 } }); }, "pipl/bad-audit");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { run: run };
|
|
168
|
+
|
|
169
|
+
if (require.main === module) {
|
|
170
|
+
try { run(); console.log("[pipl-cn] OK"); }
|
|
171
|
+
catch (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
172
|
+
}
|
|
@@ -317,7 +317,64 @@ async function testRotateRekeysFingerprint() {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
async function testLogoutEmitsClearSiteData() {
|
|
321
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ses-logout-"));
|
|
322
|
+
try {
|
|
323
|
+
await setupTestDb(tmpDir);
|
|
324
|
+
var s = await b.session.create({ userId: "u-logout" });
|
|
325
|
+
check("session created", typeof s.token === "string");
|
|
326
|
+
|
|
327
|
+
var headers = {};
|
|
328
|
+
var res = {
|
|
329
|
+
setHeader: function (k, v) { headers[k] = v; },
|
|
330
|
+
};
|
|
331
|
+
var destroyed = await b.session.logout(res, s.token);
|
|
332
|
+
|
|
333
|
+
check("logout returns true (session destroyed)", destroyed === true);
|
|
334
|
+
check("logout emits Clear-Site-Data header",
|
|
335
|
+
typeof headers["Clear-Site-Data"] === "string" &&
|
|
336
|
+
headers["Clear-Site-Data"].indexOf('"cookies"') !== -1 &&
|
|
337
|
+
headers["Clear-Site-Data"].indexOf('"storage"') !== -1);
|
|
338
|
+
check("logout expires the session cookie",
|
|
339
|
+
typeof headers["Set-Cookie"] === "string" &&
|
|
340
|
+
/(^|;)\s*Max-Age=0/.test(headers["Set-Cookie"]) &&
|
|
341
|
+
headers["Set-Cookie"].indexOf("sid=;") === 0);
|
|
342
|
+
check("logout cookie is Secure + HttpOnly",
|
|
343
|
+
/HttpOnly/.test(headers["Set-Cookie"]) && /Secure/.test(headers["Set-Cookie"]));
|
|
344
|
+
|
|
345
|
+
// The session is gone cluster-wide.
|
|
346
|
+
var after = await b.session.verify(s.token);
|
|
347
|
+
check("logout destroyed the session (verify returns null)", after === null);
|
|
348
|
+
|
|
349
|
+
// Custom cookie name + an unknown Clear-Site-Data directive throws.
|
|
350
|
+
var s2 = await b.session.create({ userId: "u-logout-2" });
|
|
351
|
+
var h2 = {}; var res2 = { setHeader: function (k, v) { h2[k] = v; } };
|
|
352
|
+
await b.session.logout(res2, s2.token, { cookieName: "__Host-sid" });
|
|
353
|
+
check("logout honors custom cookieName", h2["Set-Cookie"].indexOf("__Host-sid=;") === 0);
|
|
354
|
+
|
|
355
|
+
// An unknown directive throws BEFORE any side effect — the session is NOT
|
|
356
|
+
// destroyed and no client-wipe headers are queued (validate-before-revoke).
|
|
357
|
+
var s3 = await b.session.create({ userId: "u-logout-3" });
|
|
358
|
+
var h3 = {}; var res3 = { setHeader: function (k, v) { h3[k] = v; } };
|
|
359
|
+
var threw = null;
|
|
360
|
+
try { await b.session.logout(res3, s3.token, { types: ["bogus"] }); }
|
|
361
|
+
catch (e) { threw = e; }
|
|
362
|
+
check("logout rejects an unknown Clear-Site-Data directive", threw !== null);
|
|
363
|
+
check("logout did NOT queue headers on the bad-directive throw",
|
|
364
|
+
h3["Clear-Site-Data"] === undefined && h3["Set-Cookie"] === undefined);
|
|
365
|
+
check("logout did NOT destroy the session on the bad-directive throw",
|
|
366
|
+
(await b.session.verify(s3.token)) !== null);
|
|
367
|
+
|
|
368
|
+
var badRes = null;
|
|
369
|
+
try { await b.session.logout({}, "x"); } catch (e) { badRes = e; }
|
|
370
|
+
check("logout rejects a res without setHeader", badRes && badRes.code === "session/bad-res");
|
|
371
|
+
} finally {
|
|
372
|
+
await teardownTestDb(tmpDir);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
320
376
|
async function run() {
|
|
377
|
+
await testLogoutEmitsClearSiteData();
|
|
321
378
|
await testSealedCookieDefault();
|
|
322
379
|
await testSealedCookieRotateAndDestroy();
|
|
323
380
|
await testClientIpPrefixV4();
|
|
@@ -81,8 +81,11 @@ async function run() {
|
|
|
81
81
|
audit: false,
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// .root is canonicalized (realpathSync.native) so a Windows 8.3 short-name
|
|
85
|
+
// or a macOS /var -> /private/var symlink resolves — fs.watch event paths
|
|
86
|
+
// then prefix-match the watched root.
|
|
87
|
+
check("watcher.create: returns stop + canonical root",
|
|
88
|
+
typeof w.stop === "function" && w.root === fs.realpathSync.native(path.resolve(tmpDir)));
|
|
86
89
|
|
|
87
90
|
// Drop the legacy "prime the watcher with a 200ms sleep" step —
|
|
88
91
|
// helpers.waitForWatcher (15s default budget) absorbs fs.watch's
|
|
@@ -151,7 +154,8 @@ async function run() {
|
|
|
151
154
|
var shape = changes.find(function (e) { return e.relativePath === "shape.txt"; });
|
|
152
155
|
check("watcher.create: onChange has type/relativePath/fullPath/size/mtime",
|
|
153
156
|
shape && shape.type === "file" && shape.size === 4 &&
|
|
154
|
-
|
|
157
|
+
// fullPath is rooted at the canonical (realpath'd) watcher root.
|
|
158
|
+
shape.fullPath === path.join(w.root, "shape.txt") &&
|
|
155
159
|
shape.mtime instanceof Date);
|
|
156
160
|
|
|
157
161
|
// Symlinks must be skipped on the post-event lstat path. Skip on
|
package/package.json
CHANGED