pm-ops 2026.9.13 → 2026.9.18

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-driver.d.ts","sourceRoot":"","sources":["../merge-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,CAC3B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAAE,EACpB,OAAO,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,KAClE,OAAO,CAAC;AAEb;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CASjF;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAuBjG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,GAAE,MAAM,CAAC,UAAwB,EAC5C,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,OAAO,GAAE,cAA6B,GACrC,MAAM,CAgDR"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Canonical installer for pm's field-aware Git merge drivers.
3
+ *
4
+ * Git never clones `.git/config`, so every fleet package has to run
5
+ * `pm merge install` from its npm `prepare` script or concurrent agents
6
+ * conflict on tracker files. Twelve repositories vendored a byte-identical
7
+ * untyped JavaScript hook for that step. This module is the typed, tested
8
+ * replacement those packages import as `pm-ops/merge-driver`.
9
+ *
10
+ * Absence of `pm` is a supported production-install state and skips with a
11
+ * one-line notice. A present but broken CLI fails loudly so an install cannot
12
+ * pretend it configured merge safety when it did not. Windows PATH quoting,
13
+ * PATHEXT shims, and the `execFile` `.cmd` limitation are honoured through an
14
+ * injectable process boundary so the POSIX path is never faked.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ import { execFileSync } from "node:child_process";
19
+ import { accessSync, constants, statSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ /**
22
+ * Return whether `path` is a regular file the current platform would execute.
23
+ *
24
+ * POSIX requires the executable bit. Windows keys executability off PATHEXT,
25
+ * so a regular file is enough; directories, missing paths, and (on POSIX)
26
+ * mode-000 files are rejected without turning an ordinary absent-CLI install
27
+ * into a failure.
28
+ */
29
+ export function isExecutableFile(path, platform) {
30
+ try {
31
+ if (!statSync(path).isFile())
32
+ return false;
33
+ if (platform === "win32")
34
+ return true;
35
+ accessSync(path, constants.X_OK);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * Resolve the exact `pm` launcher on the supplied PATH, or `null` if none.
44
+ *
45
+ * Inspection never executes a candidate, so a present-but-broken CLI is not
46
+ * mistaken for absence. Empty POSIX components mean the current directory;
47
+ * Windows ignores them, may quote directories that contain spaces, and appends
48
+ * each PATHEXT suffix (defaulting to `.COM;.EXE;.BAT;.CMD` when unset).
49
+ */
50
+ export function pmOnPath(environment, platform) {
51
+ const directories = (environment.PATH ?? "")
52
+ .split(platform === "win32" ? ";" : ":")
53
+ .map((entry) => platform === "win32" && entry.startsWith('"') && entry.endsWith('"')
54
+ ? entry.slice(1, -1)
55
+ : entry === "" && platform !== "win32"
56
+ ? "."
57
+ : entry)
58
+ .filter((entry) => entry !== "");
59
+ const extensions = platform === "win32"
60
+ ? (environment.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((entry) => entry.trim()).filter((entry) => entry.length > 0)
61
+ : [""];
62
+ for (const directory of directories) {
63
+ for (const extension of extensions) {
64
+ const candidate = join(directory, `pm${extension}`);
65
+ if (isExecutableFile(candidate, platform))
66
+ return candidate;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Install clone-local merge drivers when `pm` is available.
73
+ *
74
+ * @returns `0` when installation ran or `pm` was absent (a one-line notice is
75
+ * printed on skip). A present CLI whose `pm merge install` fails returns that
76
+ * command's numeric status, or `1` when the failure has no status.
77
+ */
78
+ export function runPrepareMergeDriver(environment = process.env, platform = process.platform, install = execFileSync) {
79
+ const executable = pmOnPath(environment, platform);
80
+ if (executable === null) {
81
+ console.error("pm is not on PATH; skipping merge-driver install");
82
+ return 0;
83
+ }
84
+ try {
85
+ // Node cannot execFile a Windows .cmd shim, so Windows has to go through
86
+ // cmd.exe. Only the constant command `pm merge install` may cross that
87
+ // boundary: interpolating the resolved path would let a PATH directory
88
+ // containing spaces split the command line, or one containing cmd
89
+ // metacharacters (`&`, `|`, `^`, `%`) inject into it. cmd resolves `pm`
90
+ // from the same PATH and PATHEXT that discovery just validated. POSIX
91
+ // executes the validated absolute path directly, with no shell at all.
92
+ //
93
+ // cmd.exe also searches the CURRENT directory before PATH, so a `pm.cmd`
94
+ // committed at the repository root (the prepare hook's cwd) would replace
95
+ // the validated launcher (CWE-426). NoDefaultCurrentDirectoryInExePath
96
+ // turns that search off for the child; any case variant already in the
97
+ // environment is dropped first, because Windows treats names
98
+ // case-insensitively and a stale `nodefaultcurrentdirectoryinexepath=`
99
+ // could otherwise shadow the one set here.
100
+ const windows = platform === "win32";
101
+ install(windows ? "pm" : executable, ["merge", "install"], {
102
+ stdio: "inherit",
103
+ env: windows
104
+ ? {
105
+ ...Object.fromEntries(Object.entries(environment).filter(([name]) => name.toLowerCase() !== "nodefaultcurrentdirectoryinexepath")),
106
+ NoDefaultCurrentDirectoryInExePath: "1",
107
+ }
108
+ : environment,
109
+ shell: windows,
110
+ });
111
+ return 0;
112
+ }
113
+ catch (error) {
114
+ console.error(error instanceof Error ? error.message : error);
115
+ if (typeof error === "object" && error !== null && "status" in error &&
116
+ typeof error.status === "number") {
117
+ return error.status;
118
+ }
119
+ return 1;
120
+ }
121
+ }
122
+ //# sourceMappingURL=merge-driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-driver.js","sourceRoot":"","sources":["../merge-driver.ts"],"names":[],"mappings":"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"}
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-ops",
3
- "version": "2026.9.13",
3
+ "version": "2026.9.18",
4
4
  "description": "Multi-repo fleet operations for pm-cli",
5
5
  "author": "@unbraind",
6
6
  "entry": "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-ops",
3
- "version": "2026.9.13",
3
+ "version": "2026.9.18",
4
4
  "description": "Multi-repo fleet operations for pm-cli",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,9 +34,21 @@
34
34
  "types": "./dist/attestation.d.ts",
35
35
  "default": "./dist/attestation.js"
36
36
  },
37
+ "./merge-driver": {
38
+ "types": "./dist/merge-driver.d.ts",
39
+ "default": "./dist/merge-driver.js"
40
+ },
37
41
  "./lifecycle-policy": {
38
42
  "types": "./dist/lifecycle-policy.d.ts",
39
43
  "default": "./dist/lifecycle-policy.js"
44
+ },
45
+ "./eslint": {
46
+ "types": "./dist/eslint.d.ts",
47
+ "default": "./dist/eslint.js"
48
+ },
49
+ "./duplication": {
50
+ "types": "./dist/duplication.d.ts",
51
+ "default": "./dist/duplication.js"
40
52
  }
41
53
  },
42
54
  "files": [
@@ -58,7 +70,9 @@
58
70
  "changelog:full": "pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-ops/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --pm-arg=--output-budget --pm-arg=unbounded --pm-arg=--output-limit --pm-arg=unbounded",
59
71
  "changelog:check": "pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-ops/blob/main/.agents/pm --respect-item-release --check --pm-bin ./node_modules/.bin/pm --pm-arg=--output-budget --pm-arg=unbounded --pm-arg=--output-limit --pm-arg=unbounded",
60
72
  "docstring": "node scripts/docstring-gate.ts",
61
- "release:check": "npm run check && npm run docstring && npm run coverage && npm run audit:prod && npm run pack:dry-run && npm run changelog:check && npm run verify:release-changelog-date && npm run verify:release-publish-attestation && npm run verify:lifecycle-policy",
73
+ "lint": "node scripts/lint.ts",
74
+ "duplication": "node scripts/duplication-gate.ts",
75
+ "release:check": "npm run check && npm run lint && npm run duplication && npm run docstring && npm run coverage && npm run audit:prod && npm run pack:dry-run && npm run changelog:check && npm run verify:release-changelog-date && npm run verify:release-publish-attestation && npm run verify:lifecycle-policy",
62
76
  "prepublishOnly": "npm run release:check",
63
77
  "release:notes": "pm-changelog --pm-root .agents/pm --stdout --since-previous-tag --until-release-tag --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-ops/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --pm-arg=--output-budget --pm-arg=unbounded --pm-arg=--output-limit --pm-arg=unbounded --github-step-summary",
64
78
  "prepare": "node scripts/prepare-merge-driver.ts",
@@ -74,13 +88,40 @@
74
88
  "typescript": "7.0.2"
75
89
  },
76
90
  "peerDependencies": {
77
- "@unbrained/pm-cli": ">=2026.8.20"
91
+ "@babel/eslint-parser": "^8.0.5",
92
+ "@babel/plugin-syntax-typescript": "^8.0.3",
93
+ "@unbrained/pm-cli": ">=2026.8.20",
94
+ "eslint": "^10.10.0",
95
+ "fast-glob": "^3.3.3",
96
+ "jscpd": "^4.3.0"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "@babel/eslint-parser": {
100
+ "optional": true
101
+ },
102
+ "@babel/plugin-syntax-typescript": {
103
+ "optional": true
104
+ },
105
+ "eslint": {
106
+ "optional": true
107
+ },
108
+ "fast-glob": {
109
+ "optional": true
110
+ },
111
+ "jscpd": {
112
+ "optional": true
113
+ }
78
114
  },
79
115
  "devDependencies": {
116
+ "@babel/eslint-parser": "^8.0.5",
117
+ "@babel/plugin-syntax-typescript": "^8.0.3",
80
118
  "@types/node": "^26.2.0",
81
- "@unbrained/pm-cli": "2026.9.10",
119
+ "@unbrained/pm-cli": "2026.9.17",
82
120
  "c8": "^12.0.0",
83
- "pm-changelog": "2026.9.6"
121
+ "eslint": "^10.10.0",
122
+ "fast-glob": "^3.3.3",
123
+ "jscpd": "^4.3.0",
124
+ "pm-changelog": "^2026.9.16"
84
125
  },
85
126
  "keywords": [
86
127
  "pm-cli",
@@ -145,5 +186,9 @@
145
186
  "functions": 100
146
187
  },
147
188
  "ignore": []
189
+ },
190
+ "duplicationGate": {
191
+ "threshold": 0,
192
+ "minTokens": 50
148
193
  }
149
194
  }