@casomoltd/tooling 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/check-personal-data.mjs +37 -27
- package/identity/index.d.mts +13 -0
- package/identity/index.mjs +6 -1
- package/identity/organization.mjs +34 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Shared linting, formatting, commit config, and CLI tools for Casomo Ltd's repos.
|
|
|
15
15
|
| `@casomoltd/tooling/knip` | Knip config for unused exports/dependencies |
|
|
16
16
|
| `@casomoltd/tooling/jscpd` | Copy-paste detection config |
|
|
17
17
|
| `@casomoltd/tooling/readability` | `scoreText()` — readability scoring for page content |
|
|
18
|
-
| `@casomoltd/tooling/identity` | `CASOMO_ORGANIZATION`, the schema.org `Organization`, logo included, that every Casomo site names as publisher in its JSON-LD. App code imports it at build time, so a site that installs only production dependencies at runtime lists the package under `dependencies` |
|
|
18
|
+
| `@casomoltd/tooling/identity` | `CASOMO_ORGANIZATION`, the schema.org `Organization`, logo included, that every Casomo site names as publisher in its JSON-LD; and the company's public registrations, `COMPANY_REGISTER`, `ICO_REGISTER` and `REGISTERED_OFFICE` ([types](identity/index.d.mts)). App code imports it at build time, so a site that installs only production dependencies at runtime lists the package under `dependencies` |
|
|
19
19
|
| `@casomoltd/tooling/buttondown` | For a repo using the `buttondown` command: the shared email blocks and skins (`createEmailBlocks`, `emailBaseCss`, `composeWebSkin`), `tokenReader()`, `loadSettings()` and `buildPayload()`, and `EMAIL_CHROME_HIDES` and `WEB_CHROME_HIDES` ([docs/buttondown.md](docs/buttondown.md)) |
|
|
20
20
|
|
|
21
21
|
### Bin commands
|
|
@@ -66,6 +66,9 @@ const FIELD_SUFFIX = /(?:value|val|text|node|display|content)$/;
|
|
|
66
66
|
/** A key that names a birth. */
|
|
67
67
|
const BIRTH_KEY = /(?:dob|dateofbirth|birthdate|born)$/;
|
|
68
68
|
|
|
69
|
+
/** How many nested field-name layers a lead-in may carry before the key. */
|
|
70
|
+
const MAX_FIELD_SUFFIXES = 4;
|
|
71
|
+
|
|
69
72
|
/** The constructor a source file writes a date with. `new Date(1983, 6, 14)`
|
|
70
73
|
* matches neither the printed nor the ISO rule, and the words before it end
|
|
71
74
|
* `newdate` rather than a field name, so both halves would be blind at once
|
|
@@ -83,7 +86,7 @@ const DATE_CALL = /(?:new\s*)?date\($/;
|
|
|
83
86
|
*/
|
|
84
87
|
function namesABirth(lead) {
|
|
85
88
|
let rest = lead;
|
|
86
|
-
for (let depth = 0; depth <
|
|
89
|
+
for (let depth = 0; depth < MAX_FIELD_SUFFIXES; depth += 1) {
|
|
87
90
|
if (BIRTH_KEY.test(rest)) return true;
|
|
88
91
|
const trimmed = rest.replace(FIELD_SUFFIX, "");
|
|
89
92
|
if (trimmed === rest) return false;
|
|
@@ -94,11 +97,14 @@ function namesABirth(lead) {
|
|
|
94
97
|
|
|
95
98
|
const NON_WORD = /[^a-z0-9]/gi;
|
|
96
99
|
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
+
/** How far back a label may sit: generous, because a label and its value
|
|
101
|
+
* are separated by a cell boundary and whatever styling that cell carries. */
|
|
102
|
+
const LEAD_IN_CHARS = 120;
|
|
103
|
+
|
|
104
|
+
/** What precedes a date, as a reader sees it: tags dropped, within
|
|
105
|
+
* LEAD_IN_CHARS. */
|
|
100
106
|
const leadIn = (before) =>
|
|
101
|
-
before.replace(/<[^<>]*>/g, " ").slice(-
|
|
107
|
+
before.replace(/<[^<>]*>/g, " ").slice(-LEAD_IN_CHARS).replace(NON_WORD, "")
|
|
102
108
|
.toLowerCase();
|
|
103
109
|
|
|
104
110
|
/** A real address ends in a letters-only top-level domain; without this a
|
|
@@ -155,31 +161,34 @@ export function identityRules(allowEmail = []) {
|
|
|
155
161
|
|
|
156
162
|
/** Every finding in one file's text, against the rules and the names. */
|
|
157
163
|
export function findPersonalData(text, rules, names = []) {
|
|
158
|
-
const hits = [];
|
|
159
164
|
const lines = text.split("\n");
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
hits.push({line: i + 1, rule: rule.name, text: m[0]});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
for (const name of names) {
|
|
170
|
-
// No `g`: `.test()` on a global regex advances `lastIndex`, and a name
|
|
171
|
-
// on consecutive lines would be reported on every other one.
|
|
172
|
-
const re = new RegExp(
|
|
173
|
-
`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
174
|
-
for (const [i, line] of lines.entries()) {
|
|
175
|
-
if (re.test(line)) {
|
|
176
|
-
hits.push({line: i + 1, rule: "name on the local denylist", text: name});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return hits;
|
|
165
|
+
return [
|
|
166
|
+
...rules.flatMap((rule) =>
|
|
167
|
+
lines.flatMap((line, i) => ruleHitsOnLine(rule, line, i + 1))),
|
|
168
|
+
...names.flatMap((name) => nameHits(lines, name)),
|
|
169
|
+
];
|
|
181
170
|
}
|
|
182
171
|
|
|
172
|
+
/** One rule's findings on one line: each match its `allow` does not
|
|
173
|
+
* excuse and its `context`, if it has one, confirms. */
|
|
174
|
+
const ruleHitsOnLine = (rule, line, lineNumber) =>
|
|
175
|
+
[...line.matchAll(rule.re)]
|
|
176
|
+
.filter((m) => !rule.allow?.(m[0]))
|
|
177
|
+
.filter((m) => !rule.context || rule.context(line.slice(0, m.index)))
|
|
178
|
+
.map((m) => ({line: lineNumber, rule: rule.name, text: m[0]}));
|
|
179
|
+
|
|
180
|
+
/** Each line naming someone on the local denylist. */
|
|
181
|
+
const nameHits = (lines, name) => {
|
|
182
|
+
// No `g`: `.test()` on a global regex advances `lastIndex`, and a name on
|
|
183
|
+
// consecutive lines would be reported on every other one.
|
|
184
|
+
const re = new RegExp(
|
|
185
|
+
`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
186
|
+
return lines.flatMap((line, i) =>
|
|
187
|
+
re.test(line)
|
|
188
|
+
? [{line: i + 1, rule: "name on the local denylist", text: name}]
|
|
189
|
+
: []);
|
|
190
|
+
};
|
|
191
|
+
|
|
183
192
|
/* Tracked AND untracked-but-not-ignored: the moment this exists for is the
|
|
184
193
|
* first commit of a new directory, and a tracked-only listing is blind to
|
|
185
194
|
* exactly that. Ignored files are left out on purpose; they are the working
|
|
@@ -263,6 +272,7 @@ const isEntryPoint = () => {
|
|
|
263
272
|
try {
|
|
264
273
|
return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
265
274
|
} catch {
|
|
275
|
+
// No resolvable argv[1] (imported by a test, or a bare REPL): not the entry.
|
|
266
276
|
return false;
|
|
267
277
|
}
|
|
268
278
|
};
|
package/identity/index.d.mts
CHANGED
|
@@ -13,3 +13,16 @@ export interface CasomoOrganization {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export declare const CASOMO_ORGANIZATION: CasomoOrganization;
|
|
16
|
+
|
|
17
|
+
/** An entry on a public register: its number, and the page that shows it. */
|
|
18
|
+
export interface Registration {
|
|
19
|
+
readonly number: string;
|
|
20
|
+
readonly url: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The company's Companies House record. */
|
|
24
|
+
export declare const COMPANY_REGISTER: Registration;
|
|
25
|
+
/** The company's registration with the ICO, as a data controller. */
|
|
26
|
+
export declare const ICO_REGISTER: Registration;
|
|
27
|
+
/** The company's registered office, as Companies House holds it. */
|
|
28
|
+
export declare const REGISTERED_OFFICE: string;
|
package/identity/index.mjs
CHANGED
|
@@ -3,4 +3,9 @@
|
|
|
3
3
|
* lives in organization.mjs, where the typecheck can read it against the
|
|
4
4
|
* declaration here; beside a .d.mts, TypeScript would skip it.
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
CASOMO_ORGANIZATION,
|
|
8
|
+
COMPANY_REGISTER,
|
|
9
|
+
ICO_REGISTER,
|
|
10
|
+
REGISTERED_OFFICE,
|
|
11
|
+
} from "./organization.mjs";
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Casomo Ltd
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Casomo Ltd's public identity, which every Casomo site imports rather than
|
|
3
|
+
* typing its own, so no two sites can disagree about the company. Here are the
|
|
4
|
+
* schema.org `Organization` every site names in its JSON-LD, whose `sameAs`
|
|
5
|
+
* ties them all to the same profiles, and the company's entries on the public
|
|
6
|
+
* registers, each by its number and the page that shows it.
|
|
6
7
|
*
|
|
7
|
-
* Public facts only: the company's name, its mark
|
|
8
|
-
* is known by. A person, such as an article's
|
|
8
|
+
* Public facts only: the company's name, its mark, the public addresses it
|
|
9
|
+
* is known by, and its public registrations. A person, such as an article's
|
|
10
|
+
* author, stays with the site.
|
|
9
11
|
*/
|
|
10
12
|
const SITE = "https://casomoltd.com";
|
|
11
13
|
|
|
@@ -25,3 +27,29 @@ export const CASOMO_ORGANIZATION = Object.freeze({
|
|
|
25
27
|
"https://www.linkedin.com/company/111967567/",
|
|
26
28
|
]),
|
|
27
29
|
});
|
|
30
|
+
|
|
31
|
+
const COMPANY_NUMBER = "15030496";
|
|
32
|
+
/**
|
|
33
|
+
* The company's Companies House record.
|
|
34
|
+
* @type {import("./index.d.mts").Registration}
|
|
35
|
+
*/
|
|
36
|
+
export const COMPANY_REGISTER = Object.freeze({
|
|
37
|
+
number: COMPANY_NUMBER,
|
|
38
|
+
url:
|
|
39
|
+
"https://find-and-update.company-information.service.gov.uk"
|
|
40
|
+
+ `/company/${COMPANY_NUMBER}`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ICO_NUMBER = "ZC219565";
|
|
44
|
+
/**
|
|
45
|
+
* The company's registration with the ICO, as a data controller.
|
|
46
|
+
* @type {import("./index.d.mts").Registration}
|
|
47
|
+
*/
|
|
48
|
+
export const ICO_REGISTER = Object.freeze({
|
|
49
|
+
number: ICO_NUMBER,
|
|
50
|
+
url: `https://ico.org.uk/ESDWebPages/Entry/${ICO_NUMBER}`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** The company's registered office, as Companies House holds it. */
|
|
54
|
+
export const REGISTERED_OFFICE =
|
|
55
|
+
"71-75 Shelton Street, Covent Garden, London, WC2H 9JQ";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@casomoltd/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"description": "Casomo's shared dev config: ESLint, Prettier, commitlint, knip & jscpd presets, dev CLIs (git hooks, readability, screenshots, design specs), and the build-time data and helpers sites import (the Buttondown engine, the shared Organization)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|