@cosyte/script-utils 0.1.9 → 0.1.11
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 +27 -0
- package/README.md +12 -1
- package/attw.d.ts +28 -0
- package/attw.js +109 -4
- package/catalog.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c854d59: `@cosyte/script-utils`: the attw gate (`cosyte-attw`, `runAttwGate` and `runAttwCli`) now starts on
|
|
8
|
+
Windows. It used to check `node_modules/.bin/attw` and start it without a shell; on Windows that file
|
|
9
|
+
is a POSIX shell script, so the start failed with ENOENT, and npm and pnpm, which the gate also
|
|
10
|
+
starts, are `.cmd` files there that Node will not start without a shell. On Windows the gate now
|
|
11
|
+
checks and starts `node_modules/.bin/attw.cmd`, and starts all three tools through `cmd.exe` with
|
|
12
|
+
every argument and every path on the command line in double quotes, a trailing run of backslashes
|
|
13
|
+
doubled so each argument arrives whole. Elsewhere nothing changes. Two new exports,
|
|
14
|
+
`attwBinaryPath(callerUrl, platform?)` and `toolCommand(file, args, platform?)`, say which binary and
|
|
15
|
+
which command line. The gate now refuses an argument carrying a double quote, a percent sign or a
|
|
16
|
+
line break, on every system: no argument it accepts needs one. A repository that skipped the attw
|
|
17
|
+
step on Windows can drop the skip once it takes this version. Callers change nothing.
|
|
18
|
+
|
|
19
|
+
`@cosyte/bench`: the README now says what the small-input memory budget gives up: a cost in fewer
|
|
20
|
+
than half of a small case's 20 calls is not bounded, the rule switches at a hard 64 KiB edge, and
|
|
21
|
+
the empty-call floor's highest call is not a fixed figure (usually about 29 KiB, once 369,888
|
|
22
|
+
bytes) while its median is stable at about 3.4 KiB. No behavior changes.
|
|
23
|
+
|
|
24
|
+
## 0.1.10
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- a926e03: The catalog now records the 0.2 line for @cosyte/transform and @cosyte/deid, matching their latest npm releases, so the release preflight passes again.
|
|
29
|
+
|
|
3
30
|
## 0.1.9
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -807,6 +807,16 @@ Four nets, each catching something the others cannot:
|
|
|
807
807
|
What each net does and does not claim, and every measurement behind them, is in `attw.js`'s own
|
|
808
808
|
docblock. It is not restated here: a claim written down twice is a claim that drifts.
|
|
809
809
|
|
|
810
|
+
On Windows the gate checks and starts `node_modules/.bin/attw.cmd`, because the extensionless
|
|
811
|
+
`attw` there is a POSIX shell script, and it starts attw, npm and pnpm through `cmd.exe`, because
|
|
812
|
+
all three are `.cmd` files that Node will not start without a shell. Every argument and every path
|
|
813
|
+
on that command line is in double quotes (a bare name found on PATH, such as `npm`, is not: `cmd.exe`
|
|
814
|
+
gives a quoted one the wrong `%~dp0`), with a trailing run of backslashes doubled so node still
|
|
815
|
+
reads the closing quote as one, and an argument carrying a double quote, a percent sign or a line break is
|
|
816
|
+
refused on every system. Two exports say what the gate starts: `attwBinaryPath(callerUrl,
|
|
817
|
+
platform?)`, the binary's path, and `toolCommand(file, args, platform?)`, the command, its arguments
|
|
818
|
+
and whether a shell runs it.
|
|
819
|
+
|
|
810
820
|
## Compatibility
|
|
811
821
|
|
|
812
822
|
Node `>=22.14`, ESM only, published as source (`index.js`, `phi-scan.js`, `internal-refs.js` and
|
|
@@ -814,7 +824,8 @@ Node `>=22.14`, ESM only, published as source (`index.js`, `phi-scan.js`, `inter
|
|
|
814
824
|
relative path from a checkout as well as by specifier. `runPhiScan` shells out to `git` for the
|
|
815
825
|
index-backed half of its sweep, so `all` mode needs git on the path, `runInternalRefsScan` shells
|
|
816
826
|
out to `git` for its whole enumeration, and `runAttwGate` spawns the consuming package's own `attw`
|
|
817
|
-
binary, `npm pack` and, where a `publishConfig` object is present, `pnpm pack
|
|
827
|
+
binary, `npm pack` and, where a `publishConfig` object is present, `pnpm pack`, through `cmd.exe`
|
|
828
|
+
on Windows.
|
|
818
829
|
|
|
819
830
|
## Contributing
|
|
820
831
|
|
package/attw.d.ts
CHANGED
|
@@ -52,3 +52,31 @@ export declare function readAttwExclusions(text: string): {
|
|
|
52
52
|
* `scripts/attw.exclude`, with no caller file. `node node_modules/@cosyte/script-utils/attw.js`.
|
|
53
53
|
*/
|
|
54
54
|
export declare function runAttwCli(argv: string[]): number;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The consuming package's own attw binary, resolved as `../node_modules/.bin/attw` from
|
|
58
|
+
* `callerUrl`, or `../node_modules/.bin/attw.cmd` on Windows, where the extensionless file is a
|
|
59
|
+
* POSIX shell script that Windows cannot start.
|
|
60
|
+
*
|
|
61
|
+
* @param callerUrl The caller's `import.meta.url`.
|
|
62
|
+
* @param platform Defaults to `process.platform`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function attwBinaryPath(callerUrl: string, platform?: string): string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* How the gate starts attw, npm and pnpm. Off Windows: the file and its arguments as they are,
|
|
68
|
+
* with no shell. On Windows, where all three are `.cmd` files that Node will not start without
|
|
69
|
+
* one: a single command line, every part but a bare tool name in double quotes, run through `cmd.exe`. A part carrying
|
|
70
|
+
* a double quote, a percent sign or a line break throws there rather than being escaped.
|
|
71
|
+
* A trailing run of backslashes in an argument is doubled, so node, which halves backslashes
|
|
72
|
+
* before a quote, still reads the closing quote as one.
|
|
73
|
+
*
|
|
74
|
+
* @param file The tool: a path, or a name found on PATH.
|
|
75
|
+
* @param args Its arguments.
|
|
76
|
+
* @param platform Defaults to `process.platform`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function toolCommand(
|
|
79
|
+
file: string,
|
|
80
|
+
args: string[],
|
|
81
|
+
platform?: string,
|
|
82
|
+
): { file: string; args: string[]; shell: boolean };
|
package/attw.js
CHANGED
|
@@ -787,6 +787,18 @@ const ALLOWED = new Set(["--profile", "--no-definitely-typed"]);
|
|
|
787
787
|
*/
|
|
788
788
|
function forwardable(args) {
|
|
789
789
|
const forwarded = [];
|
|
790
|
+
// REFUSED ON EVERY PLATFORM, though only Windows needs it: there attw is started
|
|
791
|
+
// through cmd.exe, which cannot take these characters inside quotes (see
|
|
792
|
+
// `toolCommand`). No option or value this gate accepts carries one, and refusing
|
|
793
|
+
// them everywhere keeps one behaviour on every system.
|
|
794
|
+
const unsafe = args.find((arg) => /["%\r\n]/.test(arg));
|
|
795
|
+
if (unsafe !== undefined) {
|
|
796
|
+
die(
|
|
797
|
+
`${JSON.stringify(unsafe)} carries a double quote, a percent sign or a line break.\n` +
|
|
798
|
+
` No argument this gate accepts needs one, and on Windows, where attw is started\n` +
|
|
799
|
+
` through cmd.exe, none of them can be passed on safely.`,
|
|
800
|
+
);
|
|
801
|
+
}
|
|
790
802
|
for (let i = 0; i < args.length; i++) {
|
|
791
803
|
const arg = args[i];
|
|
792
804
|
const name = arg.split("=")[0];
|
|
@@ -832,6 +844,92 @@ const PACK_PLACEMENT_CONFIG = /^npm_config_(dry[_-]run|pack[_-]destination)$/i;
|
|
|
832
844
|
// rather than left as `spawnSync ... ENOBUFS`.
|
|
833
845
|
const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
834
846
|
|
|
847
|
+
/**
|
|
848
|
+
* Where the consuming package's own attw binary is, on the platform given.
|
|
849
|
+
*
|
|
850
|
+
* ON WINDOWS THE RUNNABLE ONE IS `attw.cmd`. A package manager writes three files
|
|
851
|
+
* into `node_modules/.bin` there: `attw` (a POSIX shell script), `attw.cmd` and
|
|
852
|
+
* `attw.ps1`. The extensionless one exists, so a check for it passes, and then
|
|
853
|
+
* starting it fails with ENOENT, because Windows cannot start a shell script. That
|
|
854
|
+
* was the whole failure on the Windows legs that first ran this gate.
|
|
855
|
+
*
|
|
856
|
+
* @param {string} callerUrl The caller's `import.meta.url`.
|
|
857
|
+
* @param {NodeJS.Platform} [platform] Defaults to this process's own.
|
|
858
|
+
* @returns {string} The absolute path of the binary to check and start.
|
|
859
|
+
*/
|
|
860
|
+
export function attwBinaryPath(callerUrl, platform = process.platform) {
|
|
861
|
+
const name = platform === "win32" ? "attw.cmd" : "attw";
|
|
862
|
+
return fileURLToPath(new URL(`../node_modules/.bin/${name}`, callerUrl));
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* How many backslashes end `text`. Counted by hand rather than by a regular
|
|
867
|
+
* expression anchored at the end, which backtracks over every run of backslashes
|
|
868
|
+
* that does not end the string and is slow on a long one.
|
|
869
|
+
*
|
|
870
|
+
* @param {string} text
|
|
871
|
+
* @returns {number}
|
|
872
|
+
*/
|
|
873
|
+
function trailingBackslashes(text) {
|
|
874
|
+
let end = text.length;
|
|
875
|
+
while (end > 0 && text[end - 1] === "\\") end--;
|
|
876
|
+
return text.length - end;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* How to start one of the three tools this gate runs (attw, npm and pnpm), on the
|
|
881
|
+
* platform given, with no shell wherever a shell is not needed.
|
|
882
|
+
*
|
|
883
|
+
* Off Windows it is the file and its arguments, as they are. ON WINDOWS a `.cmd`
|
|
884
|
+
* file cannot be started without `cmd.exe`: Node refuses it (EINVAL) since the
|
|
885
|
+
* April 2024 security releases, and npm and pnpm are `.cmd` files there too. So
|
|
886
|
+
* the command line is built here, every part in double quotes (but one, below),
|
|
887
|
+
* and handed to the
|
|
888
|
+
* shell whole, with no separate arguments for Node to join. Inside double quotes
|
|
889
|
+
* `cmd.exe` takes `&`, `|`, `<`, `>`, `^` and spaces literally. It does not take
|
|
890
|
+
* `"` (which ends the quoting) or `%` (which expands a variable) literally, and a
|
|
891
|
+
* line break ends the command, so a part carrying any of those THROWS rather than
|
|
892
|
+
* being escaped: an escape that is wrong runs a different command. Nothing this
|
|
893
|
+
* gate passes carries one, because `forwardable()` refuses them in a caller's
|
|
894
|
+
* arguments on every platform, so a throw here is a defect in this file and is
|
|
895
|
+
* left to propagate. A trailing run of backslashes in an argument is doubled, for
|
|
896
|
+
* node's own reading of the line after cmd.exe (see below).
|
|
897
|
+
*
|
|
898
|
+
* A BARE NAME FOUND ON PATH IS THE ONE PART LEFT UNQUOTED. `cmd.exe` starts a
|
|
899
|
+
* quoted batch file found through PATH with `%~dp0` pointing at the working
|
|
900
|
+
* directory instead of the file's own, and `npm.cmd` finds `npm-cli.js` through
|
|
901
|
+
* `%~dp0`. Measured on `windows-latest` with every part quoted: npm looked for
|
|
902
|
+
* `<package>\node_modules\npm\bin\npm-cli.js` and exited 1. A name made only of
|
|
903
|
+
* letters, digits, `.`, `_` and `-` needs no quotes; a path is quoted, and a
|
|
904
|
+
* batch file named by its full path gets the right `%~dp0` either way.
|
|
905
|
+
*
|
|
906
|
+
* @param {string} file The tool: a path, or a name found on PATH.
|
|
907
|
+
* @param {string[]} args Its arguments.
|
|
908
|
+
* @param {NodeJS.Platform} [platform] Defaults to this process's own.
|
|
909
|
+
* @returns {{ file: string, args: string[], shell: boolean }}
|
|
910
|
+
*/
|
|
911
|
+
export function toolCommand(file, args, platform = process.platform) {
|
|
912
|
+
if (platform !== "win32") return { file, args, shell: false };
|
|
913
|
+
const parts = [file, ...args];
|
|
914
|
+
const unsafe = parts.find((part) => /["%\r\n]/.test(part));
|
|
915
|
+
if (unsafe !== undefined) {
|
|
916
|
+
throw new Error(
|
|
917
|
+
`${JSON.stringify(unsafe)} cannot be passed through cmd.exe safely: it carries a ` +
|
|
918
|
+
`double quote, a percent sign or a line break.`,
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
const command = /^[\w.-]+$/.test(file) ? file : `"${file}"`;
|
|
922
|
+
// A TRAILING RUN OF BACKSLASHES IS DOUBLED. cmd.exe passes the quotes on, and node then splits
|
|
923
|
+
// its command line by the Microsoft C rules, where backslashes before a quote are halved and an
|
|
924
|
+
// odd one makes the quote literal. Unescaped, `node16\` would arrive as `node16" --format json`,
|
|
925
|
+
// one argument where three were sent. A backslash anywhere else is literal and left alone.
|
|
926
|
+
const line = [
|
|
927
|
+
command,
|
|
928
|
+
...args.map((arg) => `"${arg}${"\\".repeat(trailingBackslashes(arg))}"`),
|
|
929
|
+
].join(" ");
|
|
930
|
+
return { file: line, args: [], shell: true };
|
|
931
|
+
}
|
|
932
|
+
|
|
835
933
|
/**
|
|
836
934
|
* The fields that CAN name a file and that `declaredArtifacts()` deliberately does
|
|
837
935
|
* not read. THIS IS THE ONLY COPY OF THAT CLAIM: the pass line prints it, and
|
|
@@ -1050,7 +1148,9 @@ function packReportEntries(doc, name) {
|
|
|
1050
1148
|
* @returns {{ files: Set<string>, error?: undefined } | { error: string, files?: undefined }}
|
|
1051
1149
|
*/
|
|
1052
1150
|
function packedFiles(pkg, childEnv) {
|
|
1053
|
-
const
|
|
1151
|
+
const npm = toolCommand("npm", ["pack", "--dry-run", "--json"]);
|
|
1152
|
+
const res = spawnSync(npm.file, npm.args, {
|
|
1153
|
+
shell: npm.shell,
|
|
1054
1154
|
encoding: "utf8",
|
|
1055
1155
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1056
1156
|
env: childEnv,
|
|
@@ -1263,7 +1363,9 @@ function pnpmPublished(childEnv) {
|
|
|
1263
1363
|
return { error: `could not create a temporary directory for \`pnpm pack\`: ${err.message}` };
|
|
1264
1364
|
}
|
|
1265
1365
|
try {
|
|
1266
|
-
const
|
|
1366
|
+
const pnpm = toolCommand("pnpm", ["pack", "--pack-destination", dest]);
|
|
1367
|
+
const res = spawnSync(pnpm.file, pnpm.args, {
|
|
1368
|
+
shell: pnpm.shell,
|
|
1267
1369
|
encoding: "utf8",
|
|
1268
1370
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1269
1371
|
env: childEnv,
|
|
@@ -1361,7 +1463,8 @@ function gate({ callerUrl, argv = process.argv.slice(2) }) {
|
|
|
1361
1463
|
` node_modules, which is not the package being checked.`,
|
|
1362
1464
|
);
|
|
1363
1465
|
}
|
|
1364
|
-
|
|
1466
|
+
// `attw.cmd` on Windows, `attw` everywhere else: see `attwBinaryPath`.
|
|
1467
|
+
const ATTW_BIN = attwBinaryPath(callerUrl);
|
|
1365
1468
|
// THE BINARY IS CHECKED BEFORE IT IS SPAWNED, so the refusal NAMES THE PATH it
|
|
1366
1469
|
// looked for. spawnSync reports ENOENT and EACCES the same way, and a message
|
|
1367
1470
|
// built out of `res.error.message` is about a syscall rather than about which
|
|
@@ -1449,7 +1552,9 @@ function gate({ callerUrl, argv = process.argv.slice(2) }) {
|
|
|
1449
1552
|
// reads structure, so the structure has to be there. A `.attw.json` can still
|
|
1450
1553
|
// override it (config is applied after argv), and that is handled where it lands,
|
|
1451
1554
|
// as an unparseable transcript, rather than by refusing the key by name.
|
|
1452
|
-
const
|
|
1555
|
+
const attw = toolCommand(ATTW_BIN, ["--pack", ".", ...forwarded, "--format", "json"]);
|
|
1556
|
+
const res = spawnSync(attw.file, attw.args, {
|
|
1557
|
+
shell: attw.shell,
|
|
1453
1558
|
encoding: "utf8",
|
|
1454
1559
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1455
1560
|
env,
|
package/catalog.json
CHANGED
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"name": "@cosyte/transform",
|
|
131
131
|
"slug": "transform",
|
|
132
132
|
"description": "HL7 v2 → FHIR R4 transformation library for Node.js and TypeScript: IG-grounded, fail-safe, value-free diagnostics; never a confident wrong FHIR value.",
|
|
133
|
-
"line": "0.
|
|
133
|
+
"line": "0.2",
|
|
134
134
|
"engines": {
|
|
135
135
|
"node": ">=22.0.0"
|
|
136
136
|
},
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"name": "@cosyte/deid",
|
|
144
144
|
"slug": "deid",
|
|
145
145
|
"description": "Healthcare de-identification for Node.js and TypeScript: applies a HIPAA Safe Harbor policy to HL7 v2, C-CDA, FHIR R4, X12, NCPDP Telecom, and DICOM, fails closed, and emits a value-free manifest.",
|
|
146
|
-
"line": "0.
|
|
146
|
+
"line": "0.2",
|
|
147
147
|
"engines": {
|
|
148
148
|
"node": ">=22.0.0"
|
|
149
149
|
},
|