@smoothbricks/cli 0.4.2 → 0.5.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -0
- package/dist/lib/conflict-markers.d.ts +54 -0
- package/dist/lib/conflict-markers.d.ts.map +1 -0
- package/dist/lib/conflict-markers.js +118 -0
- package/dist/lib/run.d.ts.map +1 -1
- package/dist/lib/run.js +1 -5
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +132 -24
- package/dist/pr/index.d.ts +27 -0
- package/dist/pr/index.d.ts.map +1 -0
- package/dist/pr/index.js +256 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +5 -0
- package/package.json +2 -2
- package/src/cli.ts +13 -0
- package/src/lib/conflict-markers.test.ts +165 -0
- package/src/lib/conflict-markers.ts +150 -0
- package/src/lib/run.ts +1 -5
- package/src/monorepo/packed-package.ts +145 -24
- package/src/pr/index.test.ts +187 -0
- package/src/pr/index.ts +361 -0
- package/src/release/index.ts +5 -0
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,wBAAsB,MAAM,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAexE"}
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command, CommanderError } from 'commander';
|
|
|
2
2
|
import { variants } from './generate/index.js';
|
|
3
3
|
import { cliPackageVersion } from './lib/cli-package.js';
|
|
4
4
|
import { findRepoRoot } from './lib/run.js';
|
|
5
|
+
import { resolvePrConflicts } from './pr/index.js';
|
|
5
6
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
6
7
|
const program = buildProgram();
|
|
7
8
|
try {
|
|
@@ -278,6 +279,17 @@ function buildProgram() {
|
|
|
278
279
|
const { githubCiNxDeploy } = await import('./github-ci/index.js');
|
|
279
280
|
await githubCiNxDeploy(await findRepoRoot(), options);
|
|
280
281
|
});
|
|
282
|
+
const pr = program.command('pr').description('Work with GitHub pull requests');
|
|
283
|
+
pr.command('resolve [pr]')
|
|
284
|
+
.description('Resolve conflict markers in a PR (agent-first, two-phase)')
|
|
285
|
+
.option('--remote <name>', 'git remote hosting the PR branch (auto-inferred when omitted)')
|
|
286
|
+
.option('--abort', 'discard an in-progress resolution and return to the original branch')
|
|
287
|
+
.action(async (prArg, options) => {
|
|
288
|
+
const exitCode = await resolvePrConflicts(await findRepoRoot(), prArg, options);
|
|
289
|
+
if (exitCode !== 0) {
|
|
290
|
+
process.exitCode = exitCode;
|
|
291
|
+
}
|
|
292
|
+
});
|
|
281
293
|
return program;
|
|
282
294
|
}
|
|
283
295
|
function booleanOption(value) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared detection of Git merge-conflict markers.
|
|
3
|
+
*
|
|
4
|
+
* Real conflict markers always sit at column 0 and are a 7-character run of
|
|
5
|
+
* `<`, `|`, or `>` followed by a space (the diff3 base marker `|||||||` and the
|
|
6
|
+
* ours/theirs markers `<<<<<<< ` / `>>>>>>> `). We deliberately do NOT match a
|
|
7
|
+
* bare `=======` separator: it is ambiguous with Markdown h1 underlines and adds
|
|
8
|
+
* no signal (a real conflict always carries the angle/pipe markers too). The
|
|
9
|
+
* column-0 anchor also means quoted marker strings inside source (which are
|
|
10
|
+
* indented) never false-positive.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CONFLICT_MARKER_PATTERN = "^(<<<<<<< |\\|\\|\\|\\|\\|\\|\\| |>>>>>>> )";
|
|
13
|
+
export interface ConflictMarkerHit {
|
|
14
|
+
/** Repo-relative path of the offending file. */
|
|
15
|
+
readonly file: string;
|
|
16
|
+
/** 1-based line numbers carrying a conflict marker. */
|
|
17
|
+
readonly lines: number[];
|
|
18
|
+
}
|
|
19
|
+
/** 1-based line numbers of every conflict-marker line in `text`. */
|
|
20
|
+
export declare function findMarkerLines(text: string): number[];
|
|
21
|
+
export interface MarkerScanShell {
|
|
22
|
+
runResult(command: string, args: string[], cwd: string): Promise<{
|
|
23
|
+
exitCode: number;
|
|
24
|
+
stdout: string;
|
|
25
|
+
stderr: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Scan tracked files in the working tree for conflict markers via `git grep`.
|
|
30
|
+
* `pathspecs` optionally restricts the scan (e.g. release package roots).
|
|
31
|
+
*/
|
|
32
|
+
export declare function scanTrackedForMarkers(shell: MarkerScanShell, root: string, pathspecs?: string[]): Promise<ConflictMarkerHit[]>;
|
|
33
|
+
/** Thrown by {@link assertNoConflictMarkers} so callers/tests can inspect the hits. */
|
|
34
|
+
export declare class ConflictMarkersError extends Error {
|
|
35
|
+
readonly hits: ConflictMarkerHit[];
|
|
36
|
+
constructor(hits: ConflictMarkerHit[], context: string);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reusable publish/merge guard: throw {@link ConflictMarkersError} when any
|
|
40
|
+
* tracked file carries conflict markers. `context` names the blocked operation
|
|
41
|
+
* (e.g. `'publish'`) for the message.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertNoConflictMarkers(shell: MarkerScanShell, root: string, context: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Scan the files changed between `baseRef` and `headRef` (three-dot, i.e. since
|
|
46
|
+
* their merge base) for conflict markers, reading blobs at `headRef` — no
|
|
47
|
+
* checkout or working-tree mutation required.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scanRefChangedForMarkers(shell: MarkerScanShell, root: string, baseRef: string, headRef: string): Promise<ConflictMarkerHit[]>;
|
|
50
|
+
/** Parse `path:line:content` rows from `git grep -n` into per-file hits. */
|
|
51
|
+
export declare function parseGitGrep(stdout: string): ConflictMarkerHit[];
|
|
52
|
+
/** Human-readable one-liner summary of marker hits, e.g. for CLI output. */
|
|
53
|
+
export declare function formatMarkerHits(hits: ConflictMarkerHit[]): string;
|
|
54
|
+
//# sourceMappingURL=conflict-markers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-markers.d.ts","sourceRoot":"","sources":["../../src/lib/conflict-markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,gDAAgD,CAAC;AAIrF,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAStD;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAW9B;AAED,uFAAuF;AACvF,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE;gBAAzB,IAAI,EAAE,iBAAiB,EAAE,EAClC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlH;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAkB9B;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA2BhE;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAElE"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared detection of Git merge-conflict markers.
|
|
3
|
+
*
|
|
4
|
+
* Real conflict markers always sit at column 0 and are a 7-character run of
|
|
5
|
+
* `<`, `|`, or `>` followed by a space (the diff3 base marker `|||||||` and the
|
|
6
|
+
* ours/theirs markers `<<<<<<< ` / `>>>>>>> `). We deliberately do NOT match a
|
|
7
|
+
* bare `=======` separator: it is ambiguous with Markdown h1 underlines and adds
|
|
8
|
+
* no signal (a real conflict always carries the angle/pipe markers too). The
|
|
9
|
+
* column-0 anchor also means quoted marker strings inside source (which are
|
|
10
|
+
* indented) never false-positive.
|
|
11
|
+
*/
|
|
12
|
+
export const CONFLICT_MARKER_PATTERN = '^(<<<<<<< |\\|\\|\\|\\|\\|\\|\\| |>>>>>>> )';
|
|
13
|
+
const conflictMarkerRe = new RegExp(CONFLICT_MARKER_PATTERN);
|
|
14
|
+
/** 1-based line numbers of every conflict-marker line in `text`. */
|
|
15
|
+
export function findMarkerLines(text) {
|
|
16
|
+
const hits = [];
|
|
17
|
+
const lines = text.split('\n');
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
if (conflictMarkerRe.test(lines[i])) {
|
|
20
|
+
hits.push(i + 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return hits;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Scan tracked files in the working tree for conflict markers via `git grep`.
|
|
27
|
+
* `pathspecs` optionally restricts the scan (e.g. release package roots).
|
|
28
|
+
*/
|
|
29
|
+
export async function scanTrackedForMarkers(shell, root, pathspecs = []) {
|
|
30
|
+
const args = ['grep', '-nI', '-E', CONFLICT_MARKER_PATTERN];
|
|
31
|
+
if (pathspecs.length > 0) {
|
|
32
|
+
args.push('--', ...pathspecs);
|
|
33
|
+
}
|
|
34
|
+
const { exitCode, stdout, stderr } = await shell.runResult('git', args, root);
|
|
35
|
+
// git grep: 0 = matches found, 1 = no matches, >1 = error.
|
|
36
|
+
if (exitCode > 1) {
|
|
37
|
+
throw new Error(`git grep for conflict markers failed: ${stderr.trim() || `exit ${exitCode}`}`);
|
|
38
|
+
}
|
|
39
|
+
return parseGitGrep(stdout);
|
|
40
|
+
}
|
|
41
|
+
/** Thrown by {@link assertNoConflictMarkers} so callers/tests can inspect the hits. */
|
|
42
|
+
export class ConflictMarkersError extends Error {
|
|
43
|
+
hits;
|
|
44
|
+
constructor(hits, context) {
|
|
45
|
+
super(`Refusing to ${context}: conflict markers found in ${hits.length} file(s):\n${formatMarkerHits(hits)}`);
|
|
46
|
+
this.hits = hits;
|
|
47
|
+
this.name = 'ConflictMarkersError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reusable publish/merge guard: throw {@link ConflictMarkersError} when any
|
|
52
|
+
* tracked file carries conflict markers. `context` names the blocked operation
|
|
53
|
+
* (e.g. `'publish'`) for the message.
|
|
54
|
+
*/
|
|
55
|
+
export async function assertNoConflictMarkers(shell, root, context) {
|
|
56
|
+
const hits = await scanTrackedForMarkers(shell, root);
|
|
57
|
+
if (hits.length > 0) {
|
|
58
|
+
throw new ConflictMarkersError(hits, context);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Scan the files changed between `baseRef` and `headRef` (three-dot, i.e. since
|
|
63
|
+
* their merge base) for conflict markers, reading blobs at `headRef` — no
|
|
64
|
+
* checkout or working-tree mutation required.
|
|
65
|
+
*/
|
|
66
|
+
export async function scanRefChangedForMarkers(shell, root, baseRef, headRef) {
|
|
67
|
+
const diff = await shell.runResult('git', ['diff', '--name-only', `${baseRef}...${headRef}`], root);
|
|
68
|
+
if (diff.exitCode !== 0) {
|
|
69
|
+
throw new Error(`git diff ${baseRef}...${headRef} failed: ${diff.stderr.trim() || `exit ${diff.exitCode}`}`);
|
|
70
|
+
}
|
|
71
|
+
const files = diff.stdout.split('\n').filter((line) => line.length > 0);
|
|
72
|
+
const hits = [];
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
const show = await shell.runResult('git', ['show', `${headRef}:${file}`, '--textconv'], root);
|
|
75
|
+
if (show.exitCode !== 0) {
|
|
76
|
+
continue; // deleted at head / binary / unreadable — not a marker source
|
|
77
|
+
}
|
|
78
|
+
const lines = findMarkerLines(show.stdout);
|
|
79
|
+
if (lines.length > 0) {
|
|
80
|
+
hits.push({ file, lines });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return hits;
|
|
84
|
+
}
|
|
85
|
+
/** Parse `path:line:content` rows from `git grep -n` into per-file hits. */
|
|
86
|
+
export function parseGitGrep(stdout) {
|
|
87
|
+
const byFile = new Map();
|
|
88
|
+
for (const row of stdout.split('\n')) {
|
|
89
|
+
if (row.length === 0) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const firstColon = row.indexOf(':');
|
|
93
|
+
if (firstColon < 0) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const secondColon = row.indexOf(':', firstColon + 1);
|
|
97
|
+
if (secondColon < 0) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const file = row.slice(0, firstColon);
|
|
101
|
+
const line = Number.parseInt(row.slice(firstColon + 1, secondColon), 10);
|
|
102
|
+
if (!Number.isFinite(line)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const existing = byFile.get(file);
|
|
106
|
+
if (existing) {
|
|
107
|
+
existing.push(line);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
byFile.set(file, [line]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...byFile.entries()].map(([file, lines]) => ({ file, lines }));
|
|
114
|
+
}
|
|
115
|
+
/** Human-readable one-liner summary of marker hits, e.g. for CLI output. */
|
|
116
|
+
export function formatMarkerHits(hits) {
|
|
117
|
+
return hits.map((hit) => ` ${hit.file} (lines ${hit.lines.join(', ')})`).join('\n');
|
|
118
|
+
}
|
package/dist/lib/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAMA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAMA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE;AAmCD,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAMpD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
|
package/dist/lib/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { $ } from 'bun';
|
|
6
6
|
export async function run(command, args, cwd, env) {
|
|
@@ -82,10 +82,6 @@ function resolveBundledCommand(command) {
|
|
|
82
82
|
if (command === 'sherif') {
|
|
83
83
|
return fileURLToPath(import.meta.resolve('sherif'));
|
|
84
84
|
}
|
|
85
|
-
if (command === 'attw') {
|
|
86
|
-
const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/cli/package.json'));
|
|
87
|
-
return join(dirname(packageJson), 'dist', 'index.js');
|
|
88
|
-
}
|
|
89
85
|
}
|
|
90
86
|
catch {
|
|
91
87
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAYA,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhF;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtF;AAED,wBAAsB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;AAED,wBAAsB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpF"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { readFileSync, rmSync, unlinkSync } from 'node:fs';
|
|
2
|
-
import {
|
|
1
|
+
import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join, relative } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
5
|
import { publint } from 'publint';
|
|
4
6
|
import { formatMessage } from 'publint/utils';
|
|
5
7
|
import { isRecord } from '../lib/json.js';
|
|
@@ -65,37 +67,143 @@ async function validatePackedManifest(root, pkg, packed) {
|
|
|
65
67
|
return failures;
|
|
66
68
|
}
|
|
67
69
|
async function validateAttw(root, pkg, packed) {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'--profile',
|
|
74
|
-
'node16',
|
|
75
|
-
'--ignore-rules',
|
|
76
|
-
'cjs-resolves-to-esm',
|
|
77
|
-
...attwExcludedEntrypointArgs(pkg),
|
|
78
|
-
];
|
|
79
|
-
const attw = await runResult('attw', attwArgs, root);
|
|
80
|
-
if (attw.exitCode === 0) {
|
|
70
|
+
const attw = await loadAttwCore();
|
|
71
|
+
const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
|
|
72
|
+
excludeEntrypoints: wasmExportEntrypoints(pkg.json.exports),
|
|
73
|
+
});
|
|
74
|
+
if (!isRecord(analysis) || analysis.types === false) {
|
|
81
75
|
return 0;
|
|
82
76
|
}
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
const rawProblems = Array.isArray(analysis.problems) ? analysis.problems : [];
|
|
78
|
+
const problems = rawProblems.filter(isReportedAttwProblem);
|
|
79
|
+
if (problems.length === 0) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
for (const problem of problems) {
|
|
83
|
+
console.error(`${pkg.path}: are-the-types-wrong ${problem.kind}${formatProblemLocation(problem)}`);
|
|
84
|
+
}
|
|
85
85
|
console.error(`${pkg.path}: are-the-types-wrong validation failed`);
|
|
86
86
|
return 1;
|
|
87
87
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
async function loadAttwCore() {
|
|
89
|
+
const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/core/package.json'));
|
|
90
|
+
const core = await import(pathToFileURL(join(dirname(packageJson), 'dist', 'index.js')).href);
|
|
91
|
+
if (!isRecord(core)) {
|
|
92
|
+
throw new Error('@arethetypeswrong/core does not expose the expected API');
|
|
93
|
+
}
|
|
94
|
+
const PackageConstructor = core.Package;
|
|
95
|
+
const checkPackage = core.checkPackage;
|
|
96
|
+
if (typeof PackageConstructor !== 'function' || typeof checkPackage !== 'function') {
|
|
97
|
+
throw new Error('@arethetypeswrong/core does not expose the expected API');
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
createPackage: (files, packageName, packageVersion) => Reflect.construct(PackageConstructor, [files, packageName, packageVersion]),
|
|
101
|
+
checkPackage: (pkg, options) => Promise.resolve(checkPackage(pkg, options)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function createAttwPackageFromTarball(attw, root, tarballPath) {
|
|
105
|
+
const temp = mkdtempSync(join(tmpdir(), 'smoo-attw-'));
|
|
106
|
+
try {
|
|
107
|
+
const extract = await runResult('tar', ['-xzf', tarballPath, '-C', temp], root);
|
|
108
|
+
if (extract.exitCode !== 0) {
|
|
109
|
+
printCommandOutput(extract.stdout, extract.stderr);
|
|
110
|
+
throw new Error('unable to extract packed package for are-the-types-wrong');
|
|
111
|
+
}
|
|
112
|
+
return createAttwPackageFromDirectory(attw, join(temp, 'package'));
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
rmSync(temp, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function createAttwPackageFromDirectory(attw, packageDir) {
|
|
119
|
+
const packageJson = readJsonFile(join(packageDir, 'package.json'));
|
|
120
|
+
const packageName = typeof packageJson.name === 'string' ? packageJson.name : null;
|
|
121
|
+
const packageVersion = typeof packageJson.version === 'string' ? packageJson.version : null;
|
|
122
|
+
if (!packageName || !packageVersion) {
|
|
123
|
+
throw new Error('packed package.json must contain name and version');
|
|
124
|
+
}
|
|
125
|
+
const files = {};
|
|
126
|
+
collectAttwFiles(packageDir, packageDir, packageName, files);
|
|
127
|
+
return attw.createPackage(files, packageName, packageVersion);
|
|
128
|
+
}
|
|
129
|
+
function collectAttwFiles(root, current, packageName, files) {
|
|
130
|
+
for (const entry of readdirSync(current)) {
|
|
131
|
+
const path = join(current, entry);
|
|
132
|
+
const stat = statSync(path);
|
|
133
|
+
if (stat.isDirectory()) {
|
|
134
|
+
collectAttwFiles(root, path, packageName, files);
|
|
91
135
|
continue;
|
|
92
136
|
}
|
|
93
|
-
|
|
137
|
+
if (!stat.isFile()) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const packageRelativePath = relative(root, path).split('\\').join('/');
|
|
141
|
+
files[`/node_modules/${packageName}/${packageRelativePath}`] = new Uint8Array(readFileSync(path));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function readJsonFile(path) {
|
|
145
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
146
|
+
if (!isRecord(parsed)) {
|
|
147
|
+
throw new Error(`${path} is not a JSON object`);
|
|
148
|
+
}
|
|
149
|
+
return parsed;
|
|
150
|
+
}
|
|
151
|
+
function isReportedAttwProblem(problem) {
|
|
152
|
+
if (!isRecord(problem) || typeof problem.kind !== 'string') {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const flag = attwProblemFlag(problem.kind);
|
|
156
|
+
if (flag === 'cjs-resolves-to-esm') {
|
|
157
|
+
return false;
|
|
94
158
|
}
|
|
159
|
+
return problem.resolutionKind !== 'node10';
|
|
95
160
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
161
|
+
function attwProblemFlag(kind) {
|
|
162
|
+
switch (kind) {
|
|
163
|
+
case 'NoResolution':
|
|
164
|
+
return 'no-resolution';
|
|
165
|
+
case 'UntypedResolution':
|
|
166
|
+
return 'untyped-resolution';
|
|
167
|
+
case 'FalseCJS':
|
|
168
|
+
return 'false-cjs';
|
|
169
|
+
case 'FalseESM':
|
|
170
|
+
return 'false-esm';
|
|
171
|
+
case 'CJSResolvesToESM':
|
|
172
|
+
return 'cjs-resolves-to-esm';
|
|
173
|
+
case 'FallbackCondition':
|
|
174
|
+
return 'fallback-condition';
|
|
175
|
+
case 'CJSOnlyExportsDefault':
|
|
176
|
+
return 'cjs-only-exports-default';
|
|
177
|
+
case 'NamedExports':
|
|
178
|
+
return 'named-exports';
|
|
179
|
+
case 'FalseExportDefault':
|
|
180
|
+
return 'false-export-default';
|
|
181
|
+
case 'MissingExportEquals':
|
|
182
|
+
return 'missing-export-equals';
|
|
183
|
+
case 'UnexpectedModuleSyntax':
|
|
184
|
+
return 'unexpected-module-syntax';
|
|
185
|
+
case 'InternalResolutionError':
|
|
186
|
+
return 'internal-resolution-error';
|
|
187
|
+
default:
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function formatProblemLocation(problem) {
|
|
192
|
+
if (!isRecord(problem)) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
const entrypoint = typeof problem.entrypoint === 'string' ? ` ${problem.entrypoint}` : '';
|
|
196
|
+
const resolution = typeof problem.resolutionKind === 'string' ? ` ${formatResolutionKind(problem.resolutionKind)}` : '';
|
|
197
|
+
return `${entrypoint}${resolution}`;
|
|
198
|
+
}
|
|
199
|
+
function formatResolutionKind(kind) {
|
|
200
|
+
if (kind === 'node16-cjs') {
|
|
201
|
+
return 'node16 (from CJS)';
|
|
202
|
+
}
|
|
203
|
+
if (kind === 'node16-esm') {
|
|
204
|
+
return 'node16 (from ESM)';
|
|
205
|
+
}
|
|
206
|
+
return kind;
|
|
99
207
|
}
|
|
100
208
|
function wasmExportEntrypoints(exports) {
|
|
101
209
|
if (!isRecord(exports)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** 0 = clean/done, 2 = action required (conflicts to resolve), 1 = usage/error. */
|
|
2
|
+
export type PrResolveExit = 0 | 1 | 2;
|
|
3
|
+
export interface PrResolveOptions {
|
|
4
|
+
/** Git remote hosting the PR branch. Auto-inferred from the PR repo when omitted. */
|
|
5
|
+
remote?: string;
|
|
6
|
+
/** Abort an in-progress resolution: restore the original branch, drop state. */
|
|
7
|
+
abort?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PrResolveShell {
|
|
10
|
+
runResult(command: string, args: string[], cwd: string): Promise<{
|
|
11
|
+
exitCode: number;
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
}>;
|
|
15
|
+
run(command: string, args: string[], cwd: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Agent-first conflict resolution for a GitHub PR.
|
|
19
|
+
*
|
|
20
|
+
* First run (pointed at a PR): reports the conflict markers and, if any, checks
|
|
21
|
+
* out the PR branch and instructs the next step. Second run (on that branch,
|
|
22
|
+
* after the human/agent has resolved + committed): verifies no markers remain,
|
|
23
|
+
* pushes, and returns to the original branch. Idempotent via a state file under
|
|
24
|
+
* the git dir.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolvePrConflicts(root: string, prArg: string | undefined, options: PrResolveOptions, shell?: PrResolveShell): Promise<PrResolveExit>;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pr/index.ts"],"names":[],"mappings":"AAMA,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,gBAAgB,EACzB,KAAK,GAAE,cAA6B,GACnC,OAAO,CAAC,aAAa,CAAC,CAWxB"}
|