@cosyte/script-utils 0.1.2 → 0.1.4
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 +30 -12
- package/LICENSE +1 -1
- package/README.md +224 -15
- package/agent-notes.d.ts +92 -0
- package/agent-notes.js +512 -0
- package/attw.d.ts +18 -0
- package/attw.js +188 -8
- package/bin/cosyte-agent-notes.js +23 -0
- package/bin/cosyte-attw.js +24 -0
- package/bin/cosyte-catalog.js +23 -0
- package/bin/cosyte-claude-card.js +23 -0
- package/bin/cosyte-internal-refs.js +23 -0
- package/bin/cosyte-no-emdash.js +18 -0
- package/bin/cosyte-phi-scan.js +26 -0
- package/bin/cosyte-readme.js +22 -0
- package/bin/script-utils.js +44 -0
- package/catalog.d.ts +110 -0
- package/catalog.js +251 -0
- package/catalog.json +210 -0
- package/check-no-emdash.sh +69 -22
- package/claude-card.d.ts +64 -0
- package/claude-card.js +296 -0
- package/internal-refs.d.ts +12 -2
- package/internal-refs.js +131 -6
- package/package.json +40 -1
- package/phi-scan.d.ts +22 -0
- package/phi-scan.js +152 -4
- package/readme.d.ts +81 -0
- package/readme.js +489 -0
package/attw.js
CHANGED
|
@@ -160,9 +160,16 @@
|
|
|
160
160
|
* one, and `prepare` and `postpack` double the same way. `prepublishOnly` does
|
|
161
161
|
* not run under `npm pack` AT ALL (measured: 0 through either gate), which is
|
|
162
162
|
* why this cannot recurse into the gate that runs it. Of the three that do
|
|
163
|
-
* double,
|
|
164
|
-
*
|
|
165
|
-
*
|
|
163
|
+
* double, the repos here define only `prepare`. AN EARLIER VERSION OF THIS
|
|
164
|
+
* PARAGRAPH SAID EVERY ONE DEFINES IT AS THE SAME ONE-LINER, SO NOTHING REDS,
|
|
165
|
+
* AND THAT WAS FALSE. The `prepare` lines differ, and the one the parser
|
|
166
|
+
* template shipped printed simple-git-hooks' `[INFO]` lines on STDOUT, inside
|
|
167
|
+
* the JSON this net parses: every repo scaffolded from it was born with a gate
|
|
168
|
+
* that refused ("did not print JSON"). The template now sends that output to
|
|
169
|
+
* stderr (`simple-git-hooks 1>&2`, cosyte/config#131), and twelve sibling
|
|
170
|
+
* repos still carry the older line, so a `prepare` that writes to stdout is
|
|
171
|
+
* a refusal here rather than a pass, by design: what it printed is in the
|
|
172
|
+
* message. IT IS NOT FIXED WITH `--ignore-scripts`, AND THAT IS
|
|
166
173
|
* DELIBERATE: a `prepack` may GENERATE files that belong in the tarball, so
|
|
167
174
|
* suppressing it would make this net read a listing the real publish would not
|
|
168
175
|
* produce, which is a correctness bug wearing a performance fix.
|
|
@@ -734,15 +741,17 @@ import { spawnSync } from "node:child_process";
|
|
|
734
741
|
import {
|
|
735
742
|
accessSync,
|
|
736
743
|
constants,
|
|
744
|
+
existsSync,
|
|
737
745
|
mkdtempSync,
|
|
738
746
|
readdirSync,
|
|
739
747
|
readFileSync,
|
|
748
|
+
realpathSync,
|
|
740
749
|
rmSync,
|
|
741
750
|
statSync,
|
|
742
751
|
} from "node:fs";
|
|
743
752
|
import { tmpdir } from "node:os";
|
|
744
|
-
import { join } from "node:path";
|
|
745
|
-
import { fileURLToPath } from "node:url";
|
|
753
|
+
import { basename, join } from "node:path";
|
|
754
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
746
755
|
import { gunzipSync } from "node:zlib";
|
|
747
756
|
|
|
748
757
|
/**
|
|
@@ -990,6 +999,27 @@ function summarizeProblems(out) {
|
|
|
990
999
|
return [`attw reported ${lines.length} problem kind(s):`, ...lines].join("\n");
|
|
991
1000
|
}
|
|
992
1001
|
|
|
1002
|
+
/**
|
|
1003
|
+
* The entries of an `npm pack --json` report that describe the package `name`.
|
|
1004
|
+
*
|
|
1005
|
+
* npm 10 and 11 print an ARRAY of entries; npm 12 prints an OBJECT keyed by
|
|
1006
|
+
* package name. An entry counts only when its own `name` is `name`, and in the
|
|
1007
|
+
* object shape only under the key `name` as well, so a key and an entry that
|
|
1008
|
+
* disagree count for nothing. Any other document (a string, a number, `null`)
|
|
1009
|
+
* yields no entries, which the caller refuses rather than grades.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {unknown} doc The parsed report.
|
|
1012
|
+
* @param {string | undefined} name The package name from package.json.
|
|
1013
|
+
* @returns {Array<{ name?: unknown, files?: unknown }>}
|
|
1014
|
+
*/
|
|
1015
|
+
function packReportEntries(doc, name) {
|
|
1016
|
+
if (Array.isArray(doc)) return doc.filter((entry) => entry?.name === name);
|
|
1017
|
+
if (doc === null || typeof doc !== "object") return [];
|
|
1018
|
+
return Object.entries(doc)
|
|
1019
|
+
.filter(([key, entry]) => key === name && entry?.name === name)
|
|
1020
|
+
.map(([, entry]) => entry);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
993
1023
|
/**
|
|
994
1024
|
* The paths a published tarball of this directory would carry, read from npm
|
|
995
1025
|
* rather than recomputed from `files`/`.npmignore` here.
|
|
@@ -1006,6 +1036,15 @@ function summarizeProblems(out) {
|
|
|
1006
1036
|
* file list, because a partial answer graded as a whole one is the false green
|
|
1007
1037
|
* this whole file exists to refuse.
|
|
1008
1038
|
*
|
|
1039
|
+
* TWO SHAPES OF REPORT ARE READ, AND ONLY TWO. npm 10 and 11 print an ARRAY of
|
|
1040
|
+
* tarball entries; npm 12 prints an OBJECT keyed by package name whose values
|
|
1041
|
+
* are the same entries. Measured on npm 12.2.0: `npm pack --dry-run --json` on a
|
|
1042
|
+
* package named `@scope/probe` printed `{"@scope/probe": {"name": "@scope/probe",
|
|
1043
|
+
* "files": [...], ...}}`. Before this read both, the object fell through to an
|
|
1044
|
+
* empty list and the net refused every package ("npm described 0 tarball(s)"),
|
|
1045
|
+
* which is a red for the right reason (it read nothing) and the wrong outcome
|
|
1046
|
+
* (CI moving to npm 12 would have refused every publish). See `packReportEntries`.
|
|
1047
|
+
*
|
|
1009
1048
|
* @param {{ name?: string }} pkg The parsed manifest.
|
|
1010
1049
|
* @param {NodeJS.ProcessEnv} childEnv The environment to hand npm.
|
|
1011
1050
|
* @returns {{ files: Set<string>, error?: undefined } | { error: string, files?: undefined }}
|
|
@@ -1032,11 +1071,12 @@ function packedFiles(pkg, childEnv) {
|
|
|
1032
1071
|
error: `\`npm pack --dry-run --json\` did not print JSON. What it printed:\n\n${res.stdout ?? ""}`,
|
|
1033
1072
|
};
|
|
1034
1073
|
}
|
|
1035
|
-
// npm
|
|
1036
|
-
// root describes several
|
|
1074
|
+
// npm describes one entry per tarball, because the same command run at a
|
|
1075
|
+
// workspace root describes several, in one of the two shapes read by
|
|
1076
|
+
// `packReportEntries`. Only this package's own entry is gradeable here, and
|
|
1037
1077
|
// anything other than exactly one of them means npm was asked a different
|
|
1038
1078
|
// question than this net thinks it asked.
|
|
1039
|
-
const mine = (
|
|
1079
|
+
const mine = packReportEntries(doc, pkg.name);
|
|
1040
1080
|
if (mine.length !== 1) {
|
|
1041
1081
|
return {
|
|
1042
1082
|
error:
|
|
@@ -1677,3 +1717,143 @@ function gate({ callerUrl, argv = process.argv.slice(2) }) {
|
|
|
1677
1717
|
);
|
|
1678
1718
|
return 0;
|
|
1679
1719
|
}
|
|
1720
|
+
|
|
1721
|
+
// ---- The command-line form (ST34) --------------------------------------------
|
|
1722
|
+
//
|
|
1723
|
+
// `node node_modules/@cosyte/script-utils/attw.js` runs this gate for the
|
|
1724
|
+
// package in the working directory with no caller file of its own: it is what
|
|
1725
|
+
// the one-line `scripts/attw.mjs` caller does, resolving the package's own
|
|
1726
|
+
// `node_modules/.bin/attw`. What the package leaves out of attw's analysis is
|
|
1727
|
+
// declared in `scripts/attw.exclude`, one resolution per line, `#` for comments:
|
|
1728
|
+
//
|
|
1729
|
+
// node10 left out by attw's `node16` profile
|
|
1730
|
+
// node16-cjs left out, with node10, by its `esm-only` profile
|
|
1731
|
+
//
|
|
1732
|
+
// The file SELECTS A PROFILE and nothing else, so it reaches only what the
|
|
1733
|
+
// argument allow-list already forwards. A name it does not know, a repeat,
|
|
1734
|
+
// node16-cjs without node10 (no profile leaves out that one alone) and a
|
|
1735
|
+
// `--profile` on the command line beside the file are each refused: one source
|
|
1736
|
+
// for the profile, and nothing an exclusion could use to blind net 2.
|
|
1737
|
+
|
|
1738
|
+
/** The resolutions an exclusions file may name. */
|
|
1739
|
+
const EXCLUDABLE_RESOLUTIONS = new Set(["node10", "node16-cjs"]);
|
|
1740
|
+
|
|
1741
|
+
/** Where the command-line form reads what a package leaves out. */
|
|
1742
|
+
export const ATTW_EXCLUDE_PATH = "scripts/attw.exclude";
|
|
1743
|
+
|
|
1744
|
+
/**
|
|
1745
|
+
* Read an attw exclusions file into the one profile it selects.
|
|
1746
|
+
*
|
|
1747
|
+
* @param {string} text The file's contents.
|
|
1748
|
+
* @returns {{ profile: "node16" | "esm-only" | null, problems: string[] }}
|
|
1749
|
+
*/
|
|
1750
|
+
export function readAttwExclusions(text) {
|
|
1751
|
+
const problems = [];
|
|
1752
|
+
const seen = new Set();
|
|
1753
|
+
text.split("\n").forEach((raw, index) => {
|
|
1754
|
+
const hash = raw.indexOf("#");
|
|
1755
|
+
const entry = (hash < 0 ? raw : raw.slice(0, hash)).trim();
|
|
1756
|
+
if (entry === "") return;
|
|
1757
|
+
if (!EXCLUDABLE_RESOLUTIONS.has(entry)) {
|
|
1758
|
+
problems.push(
|
|
1759
|
+
`line ${index + 1}: ${JSON.stringify(entry)} is not a resolution this gate can ` +
|
|
1760
|
+
`leave out (node10, node16-cjs).`,
|
|
1761
|
+
);
|
|
1762
|
+
} else if (seen.has(entry)) {
|
|
1763
|
+
problems.push(`line ${index + 1}: ${entry} is listed twice.`);
|
|
1764
|
+
} else {
|
|
1765
|
+
seen.add(entry);
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
let profile = null;
|
|
1769
|
+
if (seen.has("node16-cjs") && !seen.has("node10")) {
|
|
1770
|
+
problems.push(
|
|
1771
|
+
"node16-cjs can be left out only with node10: attw's esm-only profile leaves out both " +
|
|
1772
|
+
"CommonJS resolutions, and no profile leaves out node16-cjs alone.",
|
|
1773
|
+
);
|
|
1774
|
+
} else if (seen.has("node16-cjs")) {
|
|
1775
|
+
profile = "esm-only";
|
|
1776
|
+
} else if (seen.has("node10")) {
|
|
1777
|
+
profile = "node16";
|
|
1778
|
+
}
|
|
1779
|
+
return { profile, problems };
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
/**
|
|
1783
|
+
* Run the gate from the command line for the package in the working directory.
|
|
1784
|
+
*
|
|
1785
|
+
* @param {string[]} argv The command-line arguments, graded by the same allow-list.
|
|
1786
|
+
* @returns {number} The gate's exit code: 0 pass, attw's own status, or 1 for a refusal.
|
|
1787
|
+
*/
|
|
1788
|
+
export function runAttwCli(argv) {
|
|
1789
|
+
const args = argv.filter((arg) => arg !== "--");
|
|
1790
|
+
const file = join(process.cwd(), ...ATTW_EXCLUDE_PATH.split("/"));
|
|
1791
|
+
const profile = [];
|
|
1792
|
+
if (existsSync(file)) {
|
|
1793
|
+
let text;
|
|
1794
|
+
try {
|
|
1795
|
+
text = readFileSync(file, "utf8");
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
process.stderr.write(`\n✗ attw gate: could not read ${ATTW_EXCLUDE_PATH}: ${err.message}\n`);
|
|
1798
|
+
return 1;
|
|
1799
|
+
}
|
|
1800
|
+
const read = readAttwExclusions(text);
|
|
1801
|
+
if (read.problems.length > 0) {
|
|
1802
|
+
process.stderr.write(
|
|
1803
|
+
`\n✗ attw gate: ${ATTW_EXCLUDE_PATH} is not usable, so nothing was checked:\n` +
|
|
1804
|
+
read.problems.map((problem) => ` ${problem}\n`).join(""),
|
|
1805
|
+
);
|
|
1806
|
+
return 1;
|
|
1807
|
+
}
|
|
1808
|
+
if (read.profile !== null) {
|
|
1809
|
+
if (args.some((arg) => arg.split("=")[0] === "--profile")) {
|
|
1810
|
+
process.stderr.write(
|
|
1811
|
+
`\n✗ attw gate: --profile was given and ${ATTW_EXCLUDE_PATH} selects a profile ` +
|
|
1812
|
+
`too. Declare it in the file alone.\n`,
|
|
1813
|
+
);
|
|
1814
|
+
return 1;
|
|
1815
|
+
}
|
|
1816
|
+
profile.push("--profile", read.profile);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
// The caller this form stands in for lives at `scripts/attw.mjs`, so the gate
|
|
1820
|
+
// resolves `../node_modules/.bin/attw` from there: this package's own binary.
|
|
1821
|
+
return runAttwGate({
|
|
1822
|
+
callerUrl: pathToFileURL(join(process.cwd(), "scripts", "attw.mjs")).href,
|
|
1823
|
+
argv: [...args, ...profile],
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* Whether node was pointed at this file, which is the command-line form.
|
|
1829
|
+
*
|
|
1830
|
+
* SELF-CONTAINED ON PURPOSE: this module is also vendored and copied as a single file, so it may not
|
|
1831
|
+
* import a sibling at load. A file whose name differs from the one node was pointed at cannot be the
|
|
1832
|
+
* entry point, and that is settled without loading anything; only a matching name asks `index.js`'s
|
|
1833
|
+
* `isCliEntrypoint`, and a copy with no `index.js` beside it compares real paths instead. Unsure is
|
|
1834
|
+
* yes, so a gate that cannot tell runs loudly rather than skipping.
|
|
1835
|
+
*
|
|
1836
|
+
* @param {string} moduleUrl This module's `import.meta.url`.
|
|
1837
|
+
* @returns {Promise<boolean>}
|
|
1838
|
+
*/
|
|
1839
|
+
async function invokedAsCommand(moduleUrl) {
|
|
1840
|
+
const entry = process.argv[1];
|
|
1841
|
+
if (typeof entry !== "string" || entry === "") return false;
|
|
1842
|
+
const own = fileURLToPath(moduleUrl);
|
|
1843
|
+
const stem = (path) => basename(path).replace(/\.[cm]?[jt]s$/, "");
|
|
1844
|
+
if (stem(entry) !== stem(own)) return false;
|
|
1845
|
+
try {
|
|
1846
|
+
const { isCliEntrypoint } = await import("./index.js");
|
|
1847
|
+
return isCliEntrypoint(moduleUrl);
|
|
1848
|
+
} catch {
|
|
1849
|
+
try {
|
|
1850
|
+
return realpathSync(entry) === realpathSync(own);
|
|
1851
|
+
} catch {
|
|
1852
|
+
return true;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
void invokedAsCommand(import.meta.url).then((command) => {
|
|
1858
|
+
if (command) process.exitCode = runAttwCli(process.argv.slice(2));
|
|
1859
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-agent-notes: the agent-notes checker for the repository in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-agent-notes`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runAgentNotesCheck } from "../agent-notes.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-agent-notes: check a repository's agent notes and every pointer into them.
|
|
10
|
+
|
|
11
|
+
cosyte-agent-notes [--dir <repository root>]
|
|
12
|
+
|
|
13
|
+
The notes are one file, documentation/agent-notes.md, and CLAUDE.md points into it as
|
|
14
|
+
documentation/agent-notes.md#<anchor>. Every pointer in a tracked file must name a heading or an
|
|
15
|
+
explicit anchor. Exit 0 clean, 1 a finding, 2 the check could not run.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
process.stdout.write(HELP);
|
|
21
|
+
} else {
|
|
22
|
+
process.exitCode = runAgentNotesCheck({ argv: args });
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-attw: the attw publish gate for the package in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-attw`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runAttwCli } from "../attw.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-attw: run the attw publish gate for the package in the working directory.
|
|
10
|
+
|
|
11
|
+
cosyte-attw [--no-definitely-typed]
|
|
12
|
+
|
|
13
|
+
It runs this package's own node_modules/.bin/attw over the tarball npm would publish, with four
|
|
14
|
+
nets attw lacks. scripts/attw.exclude names the resolutions the package leaves out, one per line:
|
|
15
|
+
node10 selects attw's node16 profile, node10 with node16-cjs selects esm-only. Exit 0 every net
|
|
16
|
+
passed; attw's own status when attw judged the package; 1 a refusal.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
21
|
+
process.stdout.write(HELP);
|
|
22
|
+
} else {
|
|
23
|
+
process.exitCode = runAttwCli(args);
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-catalog: the catalog check for the library in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-catalog`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runCatalogCheck } from "../catalog.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-catalog: check that a library's package.json description is the catalog's.
|
|
10
|
+
|
|
11
|
+
cosyte-catalog [--dir <package directory>]
|
|
12
|
+
|
|
13
|
+
The catalog is the one list of our libraries, generated in cosyte/config from each package.json.
|
|
14
|
+
Exit 0 the description is the catalog's, 1 it is not (both values printed), 2 the package is not
|
|
15
|
+
listed or package.json cannot be read.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
process.stdout.write(HELP);
|
|
21
|
+
} else {
|
|
22
|
+
process.exitCode = runCatalogCheck({ argv: args });
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-claude-card: the CLAUDE.md card checker for the repository in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-claude-card`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runClaudeCardGate } from "../claude-card.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-claude-card: check a repository's CLAUDE.md against the card spec.
|
|
10
|
+
|
|
11
|
+
cosyte-claude-card [--dir <repository root>]
|
|
12
|
+
|
|
13
|
+
Gate, Hazard and Rules in order, at most 60 lines of at most 100 characters and 5,120 bytes, and an
|
|
14
|
+
@AGENTS.md import when an AGENTS.md sits beside it. Exit 0 the card follows the spec, 1 a finding,
|
|
15
|
+
2 CLAUDE.md could not be read.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
process.stdout.write(HELP);
|
|
21
|
+
} else {
|
|
22
|
+
process.exitCode = runClaudeCardGate({ argv: args });
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-internal-refs: the internal-reference gate over the repository in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-internal-refs`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runInternalRefsCli } from "../internal-refs.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-internal-refs: run the internal-reference gate over the repository in the working directory.
|
|
10
|
+
|
|
11
|
+
cosyte-internal-refs [--config <path>]
|
|
12
|
+
|
|
13
|
+
It refuses internal bookkeeping on a public surface: item identifiers, phase and wave language, ADR
|
|
14
|
+
numbers, internal repo paths. The axes come from scripts/internal-refs.json (or --config). Exit 0
|
|
15
|
+
clean, 1 hits, 2 the gate could not run.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
process.stdout.write(HELP);
|
|
21
|
+
} else {
|
|
22
|
+
process.exitCode = runInternalRefsCli(args);
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-no-emdash: the em and en dash gate over the repository in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-no-emdash`. The gate is the shell script beside this
|
|
5
|
+
// package's entry points; this runs it with bash, forwards every argument (`--help` included, which
|
|
6
|
+
// the gate answers itself) and exits with its code.
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const gate = fileURLToPath(new URL("../check-no-emdash.sh", import.meta.url));
|
|
12
|
+
const run = spawnSync("bash", [gate, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
13
|
+
if (run.error) {
|
|
14
|
+
process.stderr.write(`cosyte-no-emdash: could not run bash ${gate}: ${run.error.message}\n`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
} else {
|
|
17
|
+
process.exitCode = run.status ?? 1;
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-phi-scan: the PHI commit-gate over the repository in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-phi-scan`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runPhiScanCli } from "../phi-scan.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-phi-scan: run the PHI commit-gate over the repository in the working directory.
|
|
10
|
+
|
|
11
|
+
cosyte-phi-scan scan the whole repository
|
|
12
|
+
cosyte-phi-scan --staged scan what is staged, Markdown excepted (the pre-commit route)
|
|
13
|
+
cosyte-phi-scan <path>... scan these paths
|
|
14
|
+
|
|
15
|
+
It carries the cross-cutting floor (a dashed SSN shape, an email at an undeclared domain) and
|
|
16
|
+
reports a place and a rule, never the matched value. scripts/phi-allow-list.txt declares synthetic
|
|
17
|
+
values, and scripts/phi-scan.exclude names literal tracked paths to leave out. Exit 0 clean, 1 hits,
|
|
18
|
+
2 the scan could not account for something.
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
23
|
+
process.stdout.write(HELP);
|
|
24
|
+
} else {
|
|
25
|
+
process.exitCode = runPhiScanCli(args);
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cosyte-readme: the README gate for the library in the working directory.
|
|
3
|
+
// A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
|
|
4
|
+
// `npx -y -p @cosyte/script-utils cosyte-readme`. It forwards every argument to the gate and exits with
|
|
5
|
+
// the gate's own code; `--help` prints what it does and exits 0.
|
|
6
|
+
|
|
7
|
+
import { runReadmeGate } from "../readme.js";
|
|
8
|
+
|
|
9
|
+
const HELP = `cosyte-readme: check a library README against the house skeleton and npm's size limit.
|
|
10
|
+
|
|
11
|
+
cosyte-readme [--dir <package directory>]
|
|
12
|
+
|
|
13
|
+
It reads README.md and package.json. Exit 0 the README follows the skeleton, 1 a finding (each
|
|
14
|
+
names its line and rule), 2 the package could not be read.
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
19
|
+
process.stdout.write(HELP);
|
|
20
|
+
} else {
|
|
21
|
+
process.exitCode = runReadmeGate({ argv: args });
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// script-utils: one entry for every gate in @cosyte/script-utils (D56). npx runs the bin named
|
|
3
|
+
// after the package, so `npx -y @cosyte/script-utils@<version> <command> [args]` reaches each gate
|
|
4
|
+
// from a repository with no package.json. `<command> --help` is the gate's own help.
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const COMMANDS = {
|
|
10
|
+
"no-emdash": ["cosyte-no-emdash", "the em and en dash gate, over tracked files and their names"],
|
|
11
|
+
attw: ["cosyte-attw", "the attw publish gate, for the package here"],
|
|
12
|
+
"phi-scan": ["cosyte-phi-scan", "the PHI commit-gate, over the repository here"],
|
|
13
|
+
"internal-refs": [
|
|
14
|
+
"cosyte-internal-refs",
|
|
15
|
+
"the internal-reference gate, over the repository here",
|
|
16
|
+
],
|
|
17
|
+
readme: ["cosyte-readme", "the README gate, for the library here"],
|
|
18
|
+
"claude-card": ["cosyte-claude-card", "the CLAUDE.md card checker"],
|
|
19
|
+
"agent-notes": ["cosyte-agent-notes", "the agent-notes checker"],
|
|
20
|
+
catalog: ["cosyte-catalog", "the catalog check, for the library here"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const HELP = `script-utils: run one of the @cosyte/script-utils gates over the working directory.
|
|
24
|
+
|
|
25
|
+
script-utils <command> [arguments]
|
|
26
|
+
script-utils <command> --help
|
|
27
|
+
|
|
28
|
+
${Object.entries(COMMANDS)
|
|
29
|
+
.map(([command, [bin, what]]) => ` ${command.padEnd(14)} ${what} (also the bin ${bin})`)
|
|
30
|
+
.join("\n")}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
34
|
+
if (command === undefined || command === "--help" || command === "-h") {
|
|
35
|
+
process.stdout.write(HELP);
|
|
36
|
+
process.exitCode = command === undefined ? 2 : 0;
|
|
37
|
+
} else if (!(command in COMMANDS)) {
|
|
38
|
+
process.stderr.write(`script-utils: ${JSON.stringify(command)} is not a command.\n\n${HELP}`);
|
|
39
|
+
process.exitCode = 2;
|
|
40
|
+
} else {
|
|
41
|
+
const bin = fileURLToPath(new URL(`./${COMMANDS[command][0]}.js`, import.meta.url));
|
|
42
|
+
const run = spawnSync(process.execPath, [bin, ...rest], { stdio: "inherit" });
|
|
43
|
+
process.exitCode = run.error ? 2 : (run.status ?? 2);
|
|
44
|
+
}
|
package/catalog.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for `@cosyte/script-utils/catalog`: the catalog of Cosyte's libraries and Pathways
|
|
3
|
+
* (ST13, ST14), generated in cosyte/config from each package's package.json, and the check every
|
|
4
|
+
* library runs that its description is the catalog's.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The three tiers, in the order ST13 names them. */
|
|
8
|
+
export declare const TIERS: readonly ["standard", "built-on", "engine"];
|
|
9
|
+
|
|
10
|
+
/** What every record carries, generated from the package's package.json. */
|
|
11
|
+
export interface CatalogRecord {
|
|
12
|
+
/** The npm package name, for example `@cosyte/hl7`. */
|
|
13
|
+
readonly name: string;
|
|
14
|
+
/** The name without its scope, for example `hl7`. */
|
|
15
|
+
readonly slug: string;
|
|
16
|
+
/** The package.json description: the package's one canonical line. */
|
|
17
|
+
readonly description: string;
|
|
18
|
+
/** The release line, for example `0.1`. */
|
|
19
|
+
readonly line: string;
|
|
20
|
+
readonly engines: { readonly node: string };
|
|
21
|
+
/** `https://github.com/cosyte/<repo>`. */
|
|
22
|
+
readonly repository: string;
|
|
23
|
+
/** `https://docs.cosyte.com/<slug>`. */
|
|
24
|
+
readonly docs: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One of the 14 libraries. */
|
|
28
|
+
export interface CatalogLibrary extends CatalogRecord {
|
|
29
|
+
/** `https://www.npmjs.com/package/<name>`. */
|
|
30
|
+
readonly npm: string;
|
|
31
|
+
readonly tier: "standard" | "built-on";
|
|
32
|
+
/** The standard, as readers see it (`HL7 v2`, `C-CDA`), for a library of the standard tier. */
|
|
33
|
+
readonly standard?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Pathways, our integration engine. */
|
|
37
|
+
export interface CatalogEngine extends CatalogRecord {
|
|
38
|
+
readonly tier: "engine";
|
|
39
|
+
/** The product name, `Pathways`. */
|
|
40
|
+
readonly displayName: string;
|
|
41
|
+
/** For example `in development`. */
|
|
42
|
+
readonly status: string;
|
|
43
|
+
/** Whether it is published to npm. */
|
|
44
|
+
readonly onNpm: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Catalog {
|
|
48
|
+
/** The 8 standards' libraries, then the 6 built on them, in the order ST13 names them. */
|
|
49
|
+
readonly libraries: readonly CatalogLibrary[];
|
|
50
|
+
readonly engine: CatalogEngine;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The hand-kept tiers file, `catalog/tiers.json` in cosyte/config. */
|
|
54
|
+
export interface CatalogTiers {
|
|
55
|
+
standards: { package: string; standard: string }[];
|
|
56
|
+
builtOn: string[];
|
|
57
|
+
engine: { package: string; name: string; status: string; onNpm: boolean };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The catalog, as the `catalog.json` beside this module carries it. Deeply frozen. */
|
|
61
|
+
export declare const CATALOG: Catalog;
|
|
62
|
+
|
|
63
|
+
/** The release line a version is on: `0.<minor>` below 1.0.0, the major from 1.0.0 on. */
|
|
64
|
+
export declare function releaseLine(version: string): string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build the catalog from the tiers file and each package's parsed package.json, keyed by name.
|
|
68
|
+
*
|
|
69
|
+
* @throws {TypeError} When a tier names a package with no usable manifest, or names one twice.
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildCatalog(
|
|
72
|
+
tiers: CatalogTiers,
|
|
73
|
+
manifests: Map<string, Record<string, unknown>> | Record<string, Record<string, unknown>>,
|
|
74
|
+
): Catalog;
|
|
75
|
+
|
|
76
|
+
/** The record for a package name: a library, the engine, or `undefined`. */
|
|
77
|
+
export declare function catalogEntry(
|
|
78
|
+
name: string,
|
|
79
|
+
catalog?: Catalog,
|
|
80
|
+
): CatalogLibrary | CatalogEngine | undefined;
|
|
81
|
+
|
|
82
|
+
/** A field where a package.json and its catalog record disagree. */
|
|
83
|
+
export interface CatalogDifference {
|
|
84
|
+
field: "description";
|
|
85
|
+
catalog: string;
|
|
86
|
+
packageJson: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Compare a package.json with its catalog record. Only the description is compared.
|
|
91
|
+
*
|
|
92
|
+
* @throws {TypeError} When the catalog does not list the package.
|
|
93
|
+
*/
|
|
94
|
+
export declare function compareWithCatalog(
|
|
95
|
+
manifest: Record<string, unknown>,
|
|
96
|
+
catalog?: Catalog,
|
|
97
|
+
): CatalogDifference[];
|
|
98
|
+
|
|
99
|
+
export interface CatalogCheckOptions {
|
|
100
|
+
/** The package directory holding package.json. Default: the working directory. */
|
|
101
|
+
dir?: string;
|
|
102
|
+
/** CLI arguments; only `--dir <path>` is accepted. */
|
|
103
|
+
argv?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Run the check over one package directory and return the exit code: 0 the description is the
|
|
108
|
+
* catalog's, 1 it is not, 2 the check could not run. It never exits the process.
|
|
109
|
+
*/
|
|
110
|
+
export declare function runCatalogCheck(options?: CatalogCheckOptions): 0 | 1 | 2;
|