carveout 0.2.0 → 0.3.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/NOTICE.md +44 -0
- package/dist/src/api.d.ts +135 -3
- package/dist/src/api.js +144 -2
- package/dist/src/carve.d.ts +55 -2
- package/dist/src/carve.js +31 -19
- package/dist/src/cut.d.ts +17 -0
- package/dist/src/cut.js +13 -7
- package/dist/src/extract.d.ts +31 -0
- package/dist/src/extract.js +31 -0
- package/dist/src/graph.d.ts +7 -2
- package/dist/src/graph.js +7 -4
- package/dist/src/project/mock-runtime.d.ts +9 -0
- package/dist/src/project/mock-runtime.js +81 -0
- package/dist/src/project/paths.d.ts +10 -0
- package/dist/src/project/paths.js +35 -0
- package/dist/src/project/project.d.ts +3 -6
- package/dist/src/project/project.js +3 -104
- package/dist/src/pseudonyms/carry.d.ts +161 -0
- package/dist/src/pseudonyms/carry.js +301 -0
- package/dist/src/pseudonyms/draft.js +1 -1
- package/dist/src/pseudonyms/folder.js +2 -40
- package/dist/src/pseudonyms/legacy/aliases-v1.d.ts +107 -0
- package/dist/src/pseudonyms/legacy/aliases-v1.js +391 -0
- package/dist/src/pseudonyms/legacy/vocabulary-v1.d.ts +107 -0
- package/dist/src/pseudonyms/legacy/vocabulary-v1.js +460 -0
- package/dist/src/pseudonyms/legacy.d.ts +34 -0
- package/dist/src/pseudonyms/legacy.js +46 -0
- package/dist/src/pseudonyms/vocabulary.d.ts +20 -0
- package/dist/src/pseudonyms/vocabulary.js +57 -0
- package/dist/src/pseudonyms.d.ts +20 -0
- package/dist/src/pseudonyms.js +20 -0
- package/package.json +9 -1
- package/skills/carveout/SKILL.md +15 -13
- package/skills/carveout-gate/SKILL.md +3 -3
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// Frozen: sandbox-factory's packages/core/src/sandbox/vocabulary.ts as of its
|
|
2
|
+
// commit f31d780, the renaming every platform version built before carveout
|
|
3
|
+
// 0.3.0 was made with. Never edited: a version carries back only under the
|
|
4
|
+
// renaming it was built with. carveout's own is ../vocabulary.ts. Exported, with a
|
|
5
|
+
// V1 suffix, from ../legacy.ts.
|
|
6
|
+
/** Shorter names, separators aside, match too much to block on. */
|
|
7
|
+
export const VOCABULARY_TERM_MIN = 4;
|
|
8
|
+
/** Files a leak's detail names before it counts the rest. */
|
|
9
|
+
const LEAK_FILES_SHOWN = 5;
|
|
10
|
+
const LETTER = /\p{L}/u;
|
|
11
|
+
const UPPER = /\p{Lu}/u;
|
|
12
|
+
const LOWER = /\p{Ll}/u;
|
|
13
|
+
const DIGIT = /\p{N}/u;
|
|
14
|
+
const is = (pattern, char) => char !== undefined && pattern.test(char);
|
|
15
|
+
/** Words any workspace might name itself with, which say nothing about whose. */
|
|
16
|
+
const GENERIC_WORDS = new Set([
|
|
17
|
+
"admin",
|
|
18
|
+
"android",
|
|
19
|
+
"api",
|
|
20
|
+
"app",
|
|
21
|
+
"apps",
|
|
22
|
+
"backend",
|
|
23
|
+
"client",
|
|
24
|
+
"code",
|
|
25
|
+
"common",
|
|
26
|
+
"company",
|
|
27
|
+
"core",
|
|
28
|
+
"corp",
|
|
29
|
+
"dashboard",
|
|
30
|
+
"demo",
|
|
31
|
+
"docs",
|
|
32
|
+
"example",
|
|
33
|
+
"frontend",
|
|
34
|
+
"internal",
|
|
35
|
+
"ios",
|
|
36
|
+
"lib",
|
|
37
|
+
"libs",
|
|
38
|
+
"local",
|
|
39
|
+
"main",
|
|
40
|
+
"mobile",
|
|
41
|
+
"mono",
|
|
42
|
+
"monorepo",
|
|
43
|
+
"packages",
|
|
44
|
+
"personal",
|
|
45
|
+
"platform",
|
|
46
|
+
"private",
|
|
47
|
+
"project",
|
|
48
|
+
"repo",
|
|
49
|
+
"root",
|
|
50
|
+
"server",
|
|
51
|
+
"service",
|
|
52
|
+
"services",
|
|
53
|
+
"shared",
|
|
54
|
+
"site",
|
|
55
|
+
"src",
|
|
56
|
+
"team",
|
|
57
|
+
"test",
|
|
58
|
+
"tests",
|
|
59
|
+
"utils",
|
|
60
|
+
"web",
|
|
61
|
+
"website",
|
|
62
|
+
"workspace",
|
|
63
|
+
"www",
|
|
64
|
+
]);
|
|
65
|
+
/**
|
|
66
|
+
* Words an organization's name is made of that say nothing about which
|
|
67
|
+
* organization: its legal form, its trade and the words code uses anyway.
|
|
68
|
+
* Matched whole, they are part of its name; alone, they are not scanned for.
|
|
69
|
+
*/
|
|
70
|
+
const ORGANIZATION_WORDS = new Set([
|
|
71
|
+
"academy",
|
|
72
|
+
"agency",
|
|
73
|
+
"airlines",
|
|
74
|
+
"analytics",
|
|
75
|
+
"bank",
|
|
76
|
+
"bhd",
|
|
77
|
+
"capital",
|
|
78
|
+
"cloud",
|
|
79
|
+
"co",
|
|
80
|
+
"communications",
|
|
81
|
+
"construction",
|
|
82
|
+
"consulting",
|
|
83
|
+
"corporation",
|
|
84
|
+
"data",
|
|
85
|
+
"design",
|
|
86
|
+
"digital",
|
|
87
|
+
"education",
|
|
88
|
+
"email",
|
|
89
|
+
"energy",
|
|
90
|
+
"engineering",
|
|
91
|
+
"enterprise",
|
|
92
|
+
"enterprises",
|
|
93
|
+
"finance",
|
|
94
|
+
"financial",
|
|
95
|
+
"foods",
|
|
96
|
+
"foundation",
|
|
97
|
+
"games",
|
|
98
|
+
"global",
|
|
99
|
+
"gmbh",
|
|
100
|
+
"group",
|
|
101
|
+
"health",
|
|
102
|
+
"holding",
|
|
103
|
+
"holdings",
|
|
104
|
+
"inc",
|
|
105
|
+
"incorporated",
|
|
106
|
+
"industries",
|
|
107
|
+
"info",
|
|
108
|
+
"insurance",
|
|
109
|
+
"interactive",
|
|
110
|
+
"international",
|
|
111
|
+
"labs",
|
|
112
|
+
"limited",
|
|
113
|
+
"llc",
|
|
114
|
+
"llp",
|
|
115
|
+
"logistics",
|
|
116
|
+
"ltd",
|
|
117
|
+
"mail",
|
|
118
|
+
"manufacturing",
|
|
119
|
+
"marketing",
|
|
120
|
+
"media",
|
|
121
|
+
"medical",
|
|
122
|
+
"motors",
|
|
123
|
+
"network",
|
|
124
|
+
"networks",
|
|
125
|
+
"official",
|
|
126
|
+
"online",
|
|
127
|
+
"open",
|
|
128
|
+
"partners",
|
|
129
|
+
"payments",
|
|
130
|
+
"pharma",
|
|
131
|
+
"plc",
|
|
132
|
+
"properties",
|
|
133
|
+
"pt",
|
|
134
|
+
"pte",
|
|
135
|
+
"pty",
|
|
136
|
+
"realty",
|
|
137
|
+
"retail",
|
|
138
|
+
"sdn",
|
|
139
|
+
"shop",
|
|
140
|
+
"software",
|
|
141
|
+
"solutions",
|
|
142
|
+
"source",
|
|
143
|
+
"store",
|
|
144
|
+
"studio",
|
|
145
|
+
"studios",
|
|
146
|
+
"supply",
|
|
147
|
+
"systems",
|
|
148
|
+
"tbk",
|
|
149
|
+
"tech",
|
|
150
|
+
"technologies",
|
|
151
|
+
"technology",
|
|
152
|
+
"telecom",
|
|
153
|
+
"trading",
|
|
154
|
+
"transport",
|
|
155
|
+
"ventures",
|
|
156
|
+
"work",
|
|
157
|
+
"works",
|
|
158
|
+
]);
|
|
159
|
+
/**
|
|
160
|
+
* Everyday English words an organization's name is often made of (`First
|
|
161
|
+
* Choice`, `Blue Harbor`, `Bright Path`) that code also uses for itself
|
|
162
|
+
* (`firstName`, `blueButton`, `pathname`). Matched whole, they are part of
|
|
163
|
+
* its name; alone, they are not scanned for. Brand-like words (`acme`,
|
|
164
|
+
* `payflow`) are deliberately absent.
|
|
165
|
+
*/
|
|
166
|
+
const COMMON_WORDS = new Set(("able about above access account action active actual advance after " +
|
|
167
|
+
"again agent alert align alpha amber anchor angle apex apple area " +
|
|
168
|
+
"army arrow asset atlas atom audio auto avenue away back badge " +
|
|
169
|
+
"balance band base basic beach beacon bear beat bell best beta better " +
|
|
170
|
+
"beyond bird black blank blaze block blue board body bold bolt bond " +
|
|
171
|
+
"book boost border boss bound brain branch brand brave bread break " +
|
|
172
|
+
"bridge bright broad brown budget build bull button cache call camp " +
|
|
173
|
+
"canvas card care cargo carbon case cash castle catch cedar cell " +
|
|
174
|
+
"center central chain change channel charge chart chase check chef " +
|
|
175
|
+
"cherry chief choice circle city civic class clean clear click climb " +
|
|
176
|
+
"clock close cloud coast coin color comet command compass connect " +
|
|
177
|
+
"contact content control coral count country county cove craft crane " +
|
|
178
|
+
"create credit crest cross crown crystal cube current custom cycle " +
|
|
179
|
+
"daily dash date dawn deal deep delta desk detail direct domain door " +
|
|
180
|
+
"double dove draft dragon dream drive drop eagle early earth east " +
|
|
181
|
+
"easy echo eden edge elite ember empire entry equal ever event every " +
|
|
182
|
+
"exact expert express fair faith falcon family farm fast field fire " +
|
|
183
|
+
"first fish flag flash fleet flex flight float flow focus force " +
|
|
184
|
+
"forest form forward fountain free fresh front frost fuel full fund " +
|
|
185
|
+
"fusion future galaxy game garden gate gear giant gold golden good " +
|
|
186
|
+
"grand graph great green grid ground grove guard guide happy harbor " +
|
|
187
|
+
"harbour harmony hawk head heart helix help hero high hill home honey " +
|
|
188
|
+
"hope horizon host house human idea image impact index inner input " +
|
|
189
|
+
"insight iron island item jade join journey jump just keep kind king " +
|
|
190
|
+
"kite label land large laser last launch layer leader leaf legacy " +
|
|
191
|
+
"level liberty life light lime line link lion list little live local " +
|
|
192
|
+
"lock logic long loop lotus love lucky lunar magic main major maple " +
|
|
193
|
+
"mark market master match matrix meadow mega member mercury merit " +
|
|
194
|
+
"meta metro middle mind mint mission mode model modern moment monster " +
|
|
195
|
+
"moon motion mountain move name nation national native nature navy " +
|
|
196
|
+
"near nest next noble node north nova number oasis ocean office omega " +
|
|
197
|
+
"orange orbit order origin output pacific page palm panda panel paper " +
|
|
198
|
+
"park path peak pearl people phoenix photo pilot pine pink pixel " +
|
|
199
|
+
"place plan planet plus pocket point polar portal post power premier " +
|
|
200
|
+
"press prime profile progress pulse pure purple quality quantum quest " +
|
|
201
|
+
"quick radar rain rainbow range rapid raven ready real record region " +
|
|
202
|
+
"reliable result ridge right ring rise river road rock rocket rose " +
|
|
203
|
+
"round royal ruby safe sage sail sales salt scale scope search secure " +
|
|
204
|
+
"select sense series shape share shield shift signal silver simple " +
|
|
205
|
+
"single smart snow social solar solid sound south space spark speed " +
|
|
206
|
+
"sphere spirit spring square stack standard star start state status " +
|
|
207
|
+
"steel step stone storm stream street strong summit sunrise super " +
|
|
208
|
+
"swift table talent target task team terra thunder tide tiger time " +
|
|
209
|
+
"title today token total touch tower track trade trail tree trend " +
|
|
210
|
+
"tribe trinity true trust truth turbo twin union unique united unity " +
|
|
211
|
+
"urban user valley value vector venture vertex view village vision " +
|
|
212
|
+
"vista vital voice wave west white wild wind wing winter wise wolf " +
|
|
213
|
+
"wood world year yellow young zero zone").split(" "));
|
|
214
|
+
/**
|
|
215
|
+
* Mail providers anyone may have an address at: an author's address there
|
|
216
|
+
* says who they are, not whom they work for.
|
|
217
|
+
*/
|
|
218
|
+
const PUBLIC_MAIL_DOMAINS = new Set([
|
|
219
|
+
"163.com",
|
|
220
|
+
"aol.com",
|
|
221
|
+
"fastmail.com",
|
|
222
|
+
"gmail.com",
|
|
223
|
+
"gmx.com",
|
|
224
|
+
"gmx.de",
|
|
225
|
+
"gmx.net",
|
|
226
|
+
"googlemail.com",
|
|
227
|
+
"hey.com",
|
|
228
|
+
"hotmail.com",
|
|
229
|
+
"icloud.com",
|
|
230
|
+
"live.com",
|
|
231
|
+
"mac.com",
|
|
232
|
+
"mail.com",
|
|
233
|
+
"me.com",
|
|
234
|
+
"msn.com",
|
|
235
|
+
"outlook.com",
|
|
236
|
+
"pm.me",
|
|
237
|
+
"proton.me",
|
|
238
|
+
"protonmail.com",
|
|
239
|
+
"qq.com",
|
|
240
|
+
"tutanota.com",
|
|
241
|
+
"users.noreply.github.com",
|
|
242
|
+
"yahoo.com",
|
|
243
|
+
"yandex.com",
|
|
244
|
+
"yandex.ru",
|
|
245
|
+
"ymail.com",
|
|
246
|
+
"zoho.com",
|
|
247
|
+
]);
|
|
248
|
+
/** Names that say who the organization is, to be scanned word by word too. */
|
|
249
|
+
export function organizationNames(names) {
|
|
250
|
+
return names.map((name) => ({ name, kind: "organization" }));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The names the slice's privately hosted packages give (see the module
|
|
254
|
+
* comment): one of the workspace's own, or one a lockfile or `.npmrc` says
|
|
255
|
+
* comes from another registry. A package nothing says is public is mocked
|
|
256
|
+
* too, but by default, so it is not one.
|
|
257
|
+
*/
|
|
258
|
+
export function privatePackageNames(packages) {
|
|
259
|
+
const names = [];
|
|
260
|
+
for (const item of packages) {
|
|
261
|
+
if (item.workspace !== true && item.registry?.source !== "private")
|
|
262
|
+
continue;
|
|
263
|
+
const slash = item.name.indexOf("/");
|
|
264
|
+
if (item.name.startsWith("@") && slash > 1)
|
|
265
|
+
names.push({ name: item.name.slice(1, slash), kind: "organization" });
|
|
266
|
+
else if (termWords(item.name).length > 1)
|
|
267
|
+
names.push({ name: item.name, kind: "name" });
|
|
268
|
+
}
|
|
269
|
+
return names;
|
|
270
|
+
}
|
|
271
|
+
/** Shared and automated mailboxes: they name a function, not a person. */
|
|
272
|
+
const ROLE_MAILBOXES = new Set([
|
|
273
|
+
"abuse",
|
|
274
|
+
"accounts",
|
|
275
|
+
"admin",
|
|
276
|
+
"billing",
|
|
277
|
+
"bot",
|
|
278
|
+
"careers",
|
|
279
|
+
"ci",
|
|
280
|
+
"contact",
|
|
281
|
+
"dev",
|
|
282
|
+
"developer",
|
|
283
|
+
"developers",
|
|
284
|
+
"devops",
|
|
285
|
+
"engineering",
|
|
286
|
+
"github",
|
|
287
|
+
"hello",
|
|
288
|
+
"help",
|
|
289
|
+
"hi",
|
|
290
|
+
"hostmaster",
|
|
291
|
+
"hr",
|
|
292
|
+
"info",
|
|
293
|
+
"it",
|
|
294
|
+
"jobs",
|
|
295
|
+
"legal",
|
|
296
|
+
"mail",
|
|
297
|
+
"maintainers",
|
|
298
|
+
"marketing",
|
|
299
|
+
"noreply",
|
|
300
|
+
"npm",
|
|
301
|
+
"office",
|
|
302
|
+
"ops",
|
|
303
|
+
"opensource",
|
|
304
|
+
"oss",
|
|
305
|
+
"postmaster",
|
|
306
|
+
"press",
|
|
307
|
+
"privacy",
|
|
308
|
+
"release",
|
|
309
|
+
"releases",
|
|
310
|
+
"root",
|
|
311
|
+
"sales",
|
|
312
|
+
"security",
|
|
313
|
+
"support",
|
|
314
|
+
"team",
|
|
315
|
+
"tech",
|
|
316
|
+
"webmaster",
|
|
317
|
+
]);
|
|
318
|
+
/**
|
|
319
|
+
* The names a package author's address gives: its local part unless a role
|
|
320
|
+
* mailbox's (`support@`) or without a letter, and its domain as the
|
|
321
|
+
* organization's unless it is a public mail provider's. A GitHub noreply
|
|
322
|
+
* address (`12345+octocat@users.noreply.github.com`) gives the login after
|
|
323
|
+
* the `+`; any other drops its `+tag`.
|
|
324
|
+
*/
|
|
325
|
+
export function emailNames(email) {
|
|
326
|
+
const at = email.lastIndexOf("@");
|
|
327
|
+
if (at <= 0)
|
|
328
|
+
return [];
|
|
329
|
+
const domain = email
|
|
330
|
+
.slice(at + 1)
|
|
331
|
+
.trim()
|
|
332
|
+
.toLowerCase();
|
|
333
|
+
const address = email.slice(0, at).trim();
|
|
334
|
+
const plus = address.indexOf("+");
|
|
335
|
+
const local = plus === -1
|
|
336
|
+
? address
|
|
337
|
+
: domain === "users.noreply.github.com"
|
|
338
|
+
? address.slice(plus + 1)
|
|
339
|
+
: address.slice(0, plus);
|
|
340
|
+
const names = [];
|
|
341
|
+
if (/\p{L}/u.test(local) && !ROLE_MAILBOXES.has(termWords(local).join("")))
|
|
342
|
+
names.push({ name: local, kind: "name" });
|
|
343
|
+
if (domain !== "" && !PUBLIC_MAIL_DOMAINS.has(domain))
|
|
344
|
+
names.push({ name: domain, kind: "organization" });
|
|
345
|
+
return names;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A name's words, lowercased: split at anything not a letter or digit, and
|
|
349
|
+
* at each case change an identifier marks (`acmeOrders`, `XMLParser`).
|
|
350
|
+
*/
|
|
351
|
+
export function termWords(name) {
|
|
352
|
+
return name
|
|
353
|
+
.replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, "$1 $2")
|
|
354
|
+
.replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1 $2")
|
|
355
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
356
|
+
.filter((word) => word.length > 0)
|
|
357
|
+
.map((word) => word.toLowerCase());
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The names worth scanning for, each as its words joined by `-`, in the
|
|
361
|
+
* order first given; an organization's name is followed by each of its
|
|
362
|
+
* distinctive words (see the module comment). Repeats, names under
|
|
363
|
+
* `VOCABULARY_TERM_MIN`, names without a letter and names made only of
|
|
364
|
+
* generic words are dropped.
|
|
365
|
+
*/
|
|
366
|
+
export function workspaceVocabulary(names) {
|
|
367
|
+
const terms = [];
|
|
368
|
+
const add = (term) => {
|
|
369
|
+
if (!terms.includes(term))
|
|
370
|
+
terms.push(term);
|
|
371
|
+
};
|
|
372
|
+
for (const entry of names) {
|
|
373
|
+
const { name, kind } = typeof entry === "string" ? { name: entry, kind: "name" } : entry;
|
|
374
|
+
const words = termWords(name);
|
|
375
|
+
if (words.join("").length < VOCABULARY_TERM_MIN)
|
|
376
|
+
continue;
|
|
377
|
+
if (!LETTER.test(name))
|
|
378
|
+
continue;
|
|
379
|
+
if (words.every((word) => GENERIC_WORDS.has(word)))
|
|
380
|
+
continue;
|
|
381
|
+
add(words.join("-"));
|
|
382
|
+
if (kind !== "organization")
|
|
383
|
+
continue;
|
|
384
|
+
for (const word of words)
|
|
385
|
+
if (word.length >= VOCABULARY_TERM_MIN &&
|
|
386
|
+
LETTER.test(word) &&
|
|
387
|
+
!GENERIC_WORDS.has(word) &&
|
|
388
|
+
!ORGANIZATION_WORDS.has(word) &&
|
|
389
|
+
!COMMON_WORDS.has(word))
|
|
390
|
+
add(word);
|
|
391
|
+
}
|
|
392
|
+
return terms;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Every match of `term` in `text` that starts and ends on a word boundary:
|
|
396
|
+
* not a letter, or a case change an identifier marks (`myAcme`, `acmeOrders`,
|
|
397
|
+
* and in a run of capitals the last one before a lowercase, `XMLAcme`,
|
|
398
|
+
* `ACMEOrders`), or a capital after a digit (`web3Labs`). A term that starts
|
|
399
|
+
* or ends on a digit (`web3`) needs a non-digit there too, so `web3` is not
|
|
400
|
+
* found in `web30`.
|
|
401
|
+
*/
|
|
402
|
+
export function termMatches(text, term) {
|
|
403
|
+
const pattern = new RegExp(term.split("-").join("[\\s_\\-./]*"), "giu");
|
|
404
|
+
const matches = [];
|
|
405
|
+
for (const match of text.matchAll(pattern)) {
|
|
406
|
+
const start = match.index;
|
|
407
|
+
const end = start + match[0].length;
|
|
408
|
+
const before = text[start - 1];
|
|
409
|
+
const after = text[end];
|
|
410
|
+
if (is(DIGIT, text[start]) && is(DIGIT, before))
|
|
411
|
+
continue;
|
|
412
|
+
if (is(DIGIT, text[end - 1]) && is(DIGIT, after))
|
|
413
|
+
continue;
|
|
414
|
+
const opens = !is(LETTER, before) ||
|
|
415
|
+
(is(LOWER, before) && is(UPPER, text[start])) ||
|
|
416
|
+
(is(UPPER, before) &&
|
|
417
|
+
is(UPPER, text[start]) &&
|
|
418
|
+
is(LOWER, text[start + 1]));
|
|
419
|
+
const closes = !is(LETTER, after) ||
|
|
420
|
+
(is(UPPER, after) &&
|
|
421
|
+
(is(LOWER, text[end - 1]) || is(DIGIT, text[end - 1]))) ||
|
|
422
|
+
(is(UPPER, after) &&
|
|
423
|
+
is(UPPER, text[end - 1]) &&
|
|
424
|
+
is(LOWER, text[end + 1]));
|
|
425
|
+
if (opens && closes)
|
|
426
|
+
matches.push({ start, end });
|
|
427
|
+
}
|
|
428
|
+
return matches;
|
|
429
|
+
}
|
|
430
|
+
const countTerm = (text, term) => termMatches(text, term).length;
|
|
431
|
+
/** Each term found in the files' paths or text, in the vocabulary's order. */
|
|
432
|
+
export function vocabularyLeaks(files, vocabulary) {
|
|
433
|
+
const leaks = [];
|
|
434
|
+
for (const term of vocabulary) {
|
|
435
|
+
let occurrences = 0;
|
|
436
|
+
const found = [];
|
|
437
|
+
for (const file of files) {
|
|
438
|
+
const count = countTerm(file.path, term) + countTerm(file.text, term);
|
|
439
|
+
if (count === 0)
|
|
440
|
+
continue;
|
|
441
|
+
occurrences += count;
|
|
442
|
+
found.push(file.path);
|
|
443
|
+
}
|
|
444
|
+
if (occurrences > 0)
|
|
445
|
+
leaks.push({ term, occurrences, files: found });
|
|
446
|
+
}
|
|
447
|
+
return leaks;
|
|
448
|
+
}
|
|
449
|
+
/** A blocker for each leak, naming where it was found and what clears it. */
|
|
450
|
+
export function vocabularyBlockers(leaks) {
|
|
451
|
+
return leaks.map((leak) => {
|
|
452
|
+
const shown = leak.files.slice(0, LEAK_FILES_SHOWN).join(", ");
|
|
453
|
+
const rest = leak.files.length - LEAK_FILES_SHOWN;
|
|
454
|
+
return {
|
|
455
|
+
code: "vocabulary_leaked",
|
|
456
|
+
file: leak.files[0] ?? null,
|
|
457
|
+
detail: `"${leak.term}" names the workspace and appears ${leak.occurrences} time${leak.occurrences === 1 ? "" : "s"} in ${leak.files.length} public file${leak.files.length === 1 ? "" : "s"}: ${shown}${rest > 0 ? `, and ${rest} more` : ""}. Give it a pseudonym.`,
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform's renaming and vocabulary scan as they were before carveout
|
|
3
|
+
* 0.3.0 (sandbox-factory's packages/core/src/sandbox `aliases.ts` and
|
|
4
|
+
* `vocabulary.ts`, commit f31d780), frozen under a V1 suffix, for one reason
|
|
5
|
+
* each:
|
|
6
|
+
*
|
|
7
|
+
* - A version the platform already built was renamed by `applyAliasesV1`,
|
|
8
|
+
* and carries back only under the same renaming: `ALIASES_V1` to
|
|
9
|
+
* `reverseSubmission`, `forwardChange` or `projectPathOf` (`carry.ts`).
|
|
10
|
+
* carveout's renaming matches a path rule after a `/` too, and lets a
|
|
11
|
+
* path hold any character but a NUL, a backslash, a quote or a newline.
|
|
12
|
+
* - carveout's scan (`scanVocabulary`) leaves out a found name of one word
|
|
13
|
+
* too common to scan for alone (`tooCommonAlone`), and joins a term's
|
|
14
|
+
* words across a space, `_`, `-` or `.` but never a `/`; the old one
|
|
15
|
+
* scanned for every name and joined across `/`. For one release a
|
|
16
|
+
* platform can block on both: `unionVocabularyLeaks` of `vocabularyLeaks`
|
|
17
|
+
* and `vocabularyLeaksV1`.
|
|
18
|
+
*
|
|
19
|
+
* `organizationNames`, `privatePackageNames`, `emailNames`, `termWords`
|
|
20
|
+
* and `workspaceVocabulary` are the same in both; the V1 workspace
|
|
21
|
+
* vocabulary is exported for a caller that keeps the old scan whole.
|
|
22
|
+
*/
|
|
23
|
+
import type { AliasImplementation } from "./carry.js";
|
|
24
|
+
import type { VocabularyLeak } from "./vocabulary.js";
|
|
25
|
+
export { applyAliases as applyAliasesV1, invertAliasRules as invertAliasRulesV1, renameFile as renameFileV1, validateAliasRules as validateAliasRulesV1, } from "./legacy/aliases-v1.js";
|
|
26
|
+
export { termMatches as termMatchesV1, vocabularyBlockers as vocabularyBlockersV1, vocabularyLeaks as vocabularyLeaksV1, workspaceVocabulary as workspaceVocabularyV1, } from "./legacy/vocabulary-v1.js";
|
|
27
|
+
/** The renaming of versions built before carveout 0.3.0, for `carry.ts`. */
|
|
28
|
+
export declare const ALIASES_V1: AliasImplementation;
|
|
29
|
+
/**
|
|
30
|
+
* Leaks under either scan, by term: carveout's first in its order, then
|
|
31
|
+
* the terms only the other found. A term both found counts the larger
|
|
32
|
+
* number of occurrences, in the files either named.
|
|
33
|
+
*/
|
|
34
|
+
export declare function unionVocabularyLeaks(current: readonly VocabularyLeak[], legacy: readonly VocabularyLeak[]): VocabularyLeak[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform's renaming and vocabulary scan as they were before carveout
|
|
3
|
+
* 0.3.0 (sandbox-factory's packages/core/src/sandbox `aliases.ts` and
|
|
4
|
+
* `vocabulary.ts`, commit f31d780), frozen under a V1 suffix, for one reason
|
|
5
|
+
* each:
|
|
6
|
+
*
|
|
7
|
+
* - A version the platform already built was renamed by `applyAliasesV1`,
|
|
8
|
+
* and carries back only under the same renaming: `ALIASES_V1` to
|
|
9
|
+
* `reverseSubmission`, `forwardChange` or `projectPathOf` (`carry.ts`).
|
|
10
|
+
* carveout's renaming matches a path rule after a `/` too, and lets a
|
|
11
|
+
* path hold any character but a NUL, a backslash, a quote or a newline.
|
|
12
|
+
* - carveout's scan (`scanVocabulary`) leaves out a found name of one word
|
|
13
|
+
* too common to scan for alone (`tooCommonAlone`), and joins a term's
|
|
14
|
+
* words across a space, `_`, `-` or `.` but never a `/`; the old one
|
|
15
|
+
* scanned for every name and joined across `/`. For one release a
|
|
16
|
+
* platform can block on both: `unionVocabularyLeaks` of `vocabularyLeaks`
|
|
17
|
+
* and `vocabularyLeaksV1`.
|
|
18
|
+
*
|
|
19
|
+
* `organizationNames`, `privatePackageNames`, `emailNames`, `termWords`
|
|
20
|
+
* and `workspaceVocabulary` are the same in both; the V1 workspace
|
|
21
|
+
* vocabulary is exported for a caller that keeps the old scan whole.
|
|
22
|
+
*/
|
|
23
|
+
import { invertAliasRules, renameFile } from "./legacy/aliases-v1.js";
|
|
24
|
+
export { applyAliases as applyAliasesV1, invertAliasRules as invertAliasRulesV1, renameFile as renameFileV1, validateAliasRules as validateAliasRulesV1, } from "./legacy/aliases-v1.js";
|
|
25
|
+
export { termMatches as termMatchesV1, vocabularyBlockers as vocabularyBlockersV1, vocabularyLeaks as vocabularyLeaksV1, workspaceVocabulary as workspaceVocabularyV1, } from "./legacy/vocabulary-v1.js";
|
|
26
|
+
/** The renaming of versions built before carveout 0.3.0, for `carry.ts`. */
|
|
27
|
+
export const ALIASES_V1 = { renameFile, invertAliasRules };
|
|
28
|
+
/**
|
|
29
|
+
* Leaks under either scan, by term: carveout's first in its order, then
|
|
30
|
+
* the terms only the other found. A term both found counts the larger
|
|
31
|
+
* number of occurrences, in the files either named.
|
|
32
|
+
*/
|
|
33
|
+
export function unionVocabularyLeaks(current, legacy) {
|
|
34
|
+
const merged = new Map();
|
|
35
|
+
for (const leak of [...current, ...legacy]) {
|
|
36
|
+
const known = merged.get(leak.term);
|
|
37
|
+
merged.set(leak.term, known === undefined
|
|
38
|
+
? { term: leak.term, occurrences: leak.occurrences, files: [...leak.files] }
|
|
39
|
+
: {
|
|
40
|
+
term: leak.term,
|
|
41
|
+
occurrences: Math.max(known.occurrences, leak.occurrences),
|
|
42
|
+
files: [...new Set([...known.files, ...leak.files])],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return [...merged.values()];
|
|
46
|
+
}
|
|
@@ -84,6 +84,26 @@ export declare function workspaceVocabulary(names: readonly (string | Vocabulary
|
|
|
84
84
|
* of. A name of several words is scanned whole whatever its words.
|
|
85
85
|
*/
|
|
86
86
|
export declare function tooCommonAlone(name: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* More spellings an organization's or a person's name goes by: the stem of
|
|
89
|
+
* a handle (`lunox` of `lunoxhq`), its words split at a digit (`web3 labs`
|
|
90
|
+
* of `web3labs`), and its letters without accents (`lunox` of `Lunöx`).
|
|
91
|
+
*/
|
|
92
|
+
export declare function nameSpellings(entry: VocabularyName): VocabularyName[];
|
|
93
|
+
/**
|
|
94
|
+
* The names a public folder is scanned for, from the names a repository
|
|
95
|
+
* goes by (`found`) and the owner's own (`owner`, always scanned, as an
|
|
96
|
+
* organization's): a found name of one word too common to scan for alone
|
|
97
|
+
* (`tooCommonAlone`) is dropped, and each name is joined by its other
|
|
98
|
+
* spellings (`nameSpellings`). `workspaceVocabulary` turns them into terms.
|
|
99
|
+
*/
|
|
100
|
+
export declare function scannedNames(found: readonly (string | VocabularyName)[], owner?: readonly string[]): VocabularyName[];
|
|
101
|
+
/**
|
|
102
|
+
* carveout's vocabulary: the terms a public folder must not hold, as
|
|
103
|
+
* `carveout cut --public` scans for them (`scannedNames`, then
|
|
104
|
+
* `workspaceVocabulary`). `vocabularyLeaks` finds them in files.
|
|
105
|
+
*/
|
|
106
|
+
export declare function scanVocabulary(found: readonly (string | VocabularyName)[], owner?: readonly string[]): string[];
|
|
87
107
|
export interface VocabularyLeak {
|
|
88
108
|
readonly term: string;
|
|
89
109
|
readonly occurrences: number;
|
|
@@ -451,6 +451,63 @@ export function tooCommonAlone(name) {
|
|
|
451
451
|
const word = words[0] ?? "";
|
|
452
452
|
return GENERIC_WORDS.has(word) || ORGANIZATION_WORDS.has(word) || COMMON_WORDS.has(word) || CODE_WORDS.has(word);
|
|
453
453
|
}
|
|
454
|
+
/** Affixes a handle wraps a name in: `lunoxhq`, `getlunox`, `lunoxapp`. */
|
|
455
|
+
const HANDLE_PREFIXES = ["get", "try", "use", "join", "go", "hello", "meet"];
|
|
456
|
+
const HANDLE_SUFFIXES = ["hq", "app", "apps", "inc", "io", "dev", "devs", "labs", "lab", "team", "co", "ai", "hub", "official", "corp", "tech", "studio", "group"];
|
|
457
|
+
/**
|
|
458
|
+
* More spellings an organization's or a person's name goes by: the stem of
|
|
459
|
+
* a handle (`lunox` of `lunoxhq`), its words split at a digit (`web3 labs`
|
|
460
|
+
* of `web3labs`), and its letters without accents (`lunox` of `Lunöx`).
|
|
461
|
+
*/
|
|
462
|
+
export function nameSpellings(entry) {
|
|
463
|
+
const out = [entry];
|
|
464
|
+
const plain = entry.name.normalize("NFD").replace(/\p{M}/gu, "");
|
|
465
|
+
if (plain !== entry.name)
|
|
466
|
+
out.push({ ...entry, name: plain });
|
|
467
|
+
// Matched whole: its words alone (`web3`) are everyone's.
|
|
468
|
+
const split = plain.replace(/(\d)(\p{L})/gu, "$1 $2");
|
|
469
|
+
if (split !== plain)
|
|
470
|
+
out.push({ name: split, kind: "name" });
|
|
471
|
+
if (entry.kind === "organization") {
|
|
472
|
+
const word = plain.toLowerCase();
|
|
473
|
+
// A stem of letters alone, and not one everyone's code uses (`web3`).
|
|
474
|
+
const stem = (candidate) => {
|
|
475
|
+
if (candidate.length >= 4 && /^\p{L}+$/u.test(candidate) && !tooCommonAlone(candidate))
|
|
476
|
+
out.push({ name: candidate, kind: "organization" });
|
|
477
|
+
};
|
|
478
|
+
for (const prefix of HANDLE_PREFIXES)
|
|
479
|
+
if (word.startsWith(prefix))
|
|
480
|
+
stem(word.slice(prefix.length));
|
|
481
|
+
for (const suffix of HANDLE_SUFFIXES)
|
|
482
|
+
if (word.endsWith(suffix))
|
|
483
|
+
stem(word.slice(0, -suffix.length));
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* The names a public folder is scanned for, from the names a repository
|
|
489
|
+
* goes by (`found`) and the owner's own (`owner`, always scanned, as an
|
|
490
|
+
* organization's): a found name of one word too common to scan for alone
|
|
491
|
+
* (`tooCommonAlone`) is dropped, and each name is joined by its other
|
|
492
|
+
* spellings (`nameSpellings`). `workspaceVocabulary` turns them into terms.
|
|
493
|
+
*/
|
|
494
|
+
export function scannedNames(found, owner = []) {
|
|
495
|
+
const named = found.map((entry) => (typeof entry === "string" ? { name: entry, kind: "name" } : entry));
|
|
496
|
+
// A name found, of one word the code calls its own things by, is not
|
|
497
|
+
// scanned for alone; the owner's own names always are.
|
|
498
|
+
return [
|
|
499
|
+
...owner.map((name) => ({ name, kind: "organization" })),
|
|
500
|
+
...named.filter((entry) => !tooCommonAlone(entry.name)),
|
|
501
|
+
].flatMap(nameSpellings);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* carveout's vocabulary: the terms a public folder must not hold, as
|
|
505
|
+
* `carveout cut --public` scans for them (`scannedNames`, then
|
|
506
|
+
* `workspaceVocabulary`). `vocabularyLeaks` finds them in files.
|
|
507
|
+
*/
|
|
508
|
+
export function scanVocabulary(found, owner = []) {
|
|
509
|
+
return workspaceVocabulary(scannedNames(found, owner));
|
|
510
|
+
}
|
|
454
511
|
/**
|
|
455
512
|
* Every match of `term` in `text` that starts and ends on a word boundary:
|
|
456
513
|
* not a letter, or a case change an identifier marks (`myAcme`, `acmeOrders`,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `carveout/pseudonyms`: the pure half of the pseudonyms, for a caller that
|
|
3
|
+
* cannot load the compiler or Node's modules (a web app): the alias table
|
|
4
|
+
* and its application (`aliases.ts`), the names a workspace goes by and the
|
|
5
|
+
* scan for them (`vocabulary.ts`), the rule-based draft of a table
|
|
6
|
+
* (`draft.ts`), carrying a change between a version's public names and the
|
|
7
|
+
* repository's (`carry.ts`), the platform's renaming and scan before 0.3.0
|
|
8
|
+
* (`legacy.ts`), and the recording mock runtime's text.
|
|
9
|
+
*
|
|
10
|
+
* Nothing this entry loads imports `typescript` or a `node:` module;
|
|
11
|
+
* `test/pseudonyms-entry.test.ts` holds it to that. How a cut and a patch
|
|
12
|
+
* use the table (`pseudonyms/folder.ts`, which reads Git) is not here.
|
|
13
|
+
*/
|
|
14
|
+
export * from "./pseudonyms/aliases.js";
|
|
15
|
+
export * from "./pseudonyms/vocabulary.js";
|
|
16
|
+
export * from "./pseudonyms/draft.js";
|
|
17
|
+
export * from "./pseudonyms/carry.js";
|
|
18
|
+
export * from "./pseudonyms/legacy.js";
|
|
19
|
+
export { MOCK_RUNTIME, MOCK_RUNTIME_TYPES } from "./project/mock-runtime.js";
|
|
20
|
+
export { dirnameOf, relativeSpecifier, runtimePath } from "./project/paths.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `carveout/pseudonyms`: the pure half of the pseudonyms, for a caller that
|
|
3
|
+
* cannot load the compiler or Node's modules (a web app): the alias table
|
|
4
|
+
* and its application (`aliases.ts`), the names a workspace goes by and the
|
|
5
|
+
* scan for them (`vocabulary.ts`), the rule-based draft of a table
|
|
6
|
+
* (`draft.ts`), carrying a change between a version's public names and the
|
|
7
|
+
* repository's (`carry.ts`), the platform's renaming and scan before 0.3.0
|
|
8
|
+
* (`legacy.ts`), and the recording mock runtime's text.
|
|
9
|
+
*
|
|
10
|
+
* Nothing this entry loads imports `typescript` or a `node:` module;
|
|
11
|
+
* `test/pseudonyms-entry.test.ts` holds it to that. How a cut and a patch
|
|
12
|
+
* use the table (`pseudonyms/folder.ts`, which reads Git) is not here.
|
|
13
|
+
*/
|
|
14
|
+
export * from "./pseudonyms/aliases.js";
|
|
15
|
+
export * from "./pseudonyms/vocabulary.js";
|
|
16
|
+
export * from "./pseudonyms/draft.js";
|
|
17
|
+
export * from "./pseudonyms/carry.js";
|
|
18
|
+
export * from "./pseudonyms/legacy.js";
|
|
19
|
+
export { MOCK_RUNTIME, MOCK_RUNTIME_TYPES } from "./project/mock-runtime.js";
|
|
20
|
+
export { dirnameOf, relativeSpecifier, runtimePath } from "./project/paths.js";
|