@golar-rstack/core 0.1.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/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/index.d.ts +224 -0
- package/dist/index.js +312 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oskar Lebuda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @golar-rstack/core
|
|
2
|
+
|
|
3
|
+
Shared internals for the [golar](https://golar.dev) Rstack plugins: spawning the
|
|
4
|
+
golar CLI, parsing its diagnostics, and formatting issues.
|
|
5
|
+
|
|
6
|
+
You usually want [`@golar-rstack/rsbuild`](https://www.npmjs.com/package/@golar-rstack/rsbuild)
|
|
7
|
+
or [`@golar-rstack/rspack`](https://www.npmjs.com/package/@golar-rstack/rspack)
|
|
8
|
+
instead. This package is published for reuse by other bundler integrations.
|
|
9
|
+
|
|
10
|
+
## Why a parser
|
|
11
|
+
|
|
12
|
+
golar ships no machine-readable reporter, so diagnostics are recovered from
|
|
13
|
+
stdout. It emits two unrelated layouts:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
typecheck src/App.vue(2,7): error TS2322: Type 'string' is not assignable to type 'number'.
|
|
17
|
+
lint src/math.ts:6:33: explicit-anys: Unexpected any. Specify a different type.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Indented lines elaborate on the diagnostic above them and are folded into its
|
|
21
|
+
message. This is not a stable API. The parser is covered by tests pinned to the
|
|
22
|
+
formats emitted by golar 0.1.10.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { formatIssue, resolveGolarOptions, runGolar } from '@golar-rstack/core'
|
|
28
|
+
|
|
29
|
+
const options = resolveGolarOptions({ mode: 'all' }, { cwd: process.cwd(), watch: false })
|
|
30
|
+
const { issues } = await runGolar(options)
|
|
31
|
+
|
|
32
|
+
for (const issue of issues)
|
|
33
|
+
console.log(formatIssue(issue, { cwd: options.cwd, formatter: 'codeframe' }))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`runGolar` accepts an `AbortSignal`; aborting kills the child process, which is
|
|
37
|
+
how a new build cancels an in-flight check.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an issue describing a failure of the plugin itself rather than a
|
|
3
|
+
* problem in the checked project, such as a crash of the golar process.
|
|
4
|
+
*
|
|
5
|
+
* @param message The text to report.
|
|
6
|
+
* @param severity How the issue should be reported. Defaults to `error`.
|
|
7
|
+
* @returns An issue with the `internal` origin.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createInternalIssue(message: string, severity?: IssueSeverity): Issue;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Removes duplicate issues, keeping the first occurrence of each.
|
|
13
|
+
*
|
|
14
|
+
* @param issues The issues to deduplicate.
|
|
15
|
+
* @returns A new array containing only distinct issues, in their original order.
|
|
16
|
+
*/
|
|
17
|
+
export declare function dedupeIssues(issues: Issue[]): Issue[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Looks for a golar config file, mirroring the CLI's own discovery order.
|
|
21
|
+
*
|
|
22
|
+
* @param cwd The directory golar will run in.
|
|
23
|
+
* @returns The path of the first config found, or `undefined` if there is none.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findGolarConfig(cwd: string): string | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Renders a single issue for display.
|
|
29
|
+
*
|
|
30
|
+
* @param issue The issue to render.
|
|
31
|
+
* @param options Directory to make paths relative to, and which formatter to
|
|
32
|
+
* use. The `codeframe` formatter appends a source excerpt when the file can be
|
|
33
|
+
* read.
|
|
34
|
+
* @returns The formatted text, without a trailing newline.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatIssue(issue: Issue, options: FormatOptions): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds a one line summary of a set of issues.
|
|
40
|
+
*
|
|
41
|
+
* @param issues The issues to count.
|
|
42
|
+
* @returns Text such as `Found 2 errors and 1 warning.`, or a note that nothing
|
|
43
|
+
* was found.
|
|
44
|
+
*/
|
|
45
|
+
export declare function formatIssueSummary(issues: Issue[]): string;
|
|
46
|
+
|
|
47
|
+
export declare interface FormatOptions {
|
|
48
|
+
cwd: string;
|
|
49
|
+
formatter: 'basic' | 'codeframe';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Builds the argument list for a golar invocation.
|
|
54
|
+
*
|
|
55
|
+
* The `all` mode is the bare command, which is what golar recommends, so it
|
|
56
|
+
* contributes no subcommand.
|
|
57
|
+
*
|
|
58
|
+
* @param options Resolved options carrying the mode and any extra arguments.
|
|
59
|
+
* @returns The arguments to pass to the golar executable.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getGolarArgs(options: ResolvedGolarOptions): string[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Builds a stable identity for an issue.
|
|
65
|
+
*
|
|
66
|
+
* Overlapping runs can report the same diagnostic twice, so issues need a key
|
|
67
|
+
* that does not depend on object identity.
|
|
68
|
+
*
|
|
69
|
+
* @param issue The issue to identify.
|
|
70
|
+
* @returns A string that is equal for two issues describing the same problem.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getIssueKey(issue: Issue): string;
|
|
73
|
+
|
|
74
|
+
export declare class GolarAbortError extends Error {
|
|
75
|
+
constructor();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export declare type GolarMode = 'all' | 'typecheck' | 'lint';
|
|
79
|
+
|
|
80
|
+
export declare interface GolarOptions {
|
|
81
|
+
cwd?: string;
|
|
82
|
+
mode?: GolarMode;
|
|
83
|
+
async?: boolean;
|
|
84
|
+
typecheckSeverity?: IssueSeverity;
|
|
85
|
+
lintSeverity?: IssueSeverity;
|
|
86
|
+
filter?: IssueFilter;
|
|
87
|
+
formatter?: 'basic' | 'codeframe';
|
|
88
|
+
bin?: string;
|
|
89
|
+
args?: string[];
|
|
90
|
+
env?: Record<string, string | undefined>;
|
|
91
|
+
failOnError?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export declare interface GolarRunResult {
|
|
95
|
+
issues: Issue[];
|
|
96
|
+
output: string;
|
|
97
|
+
exitCode: number | null;
|
|
98
|
+
aborted: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export declare interface Issue {
|
|
102
|
+
severity: IssueSeverity;
|
|
103
|
+
code: string;
|
|
104
|
+
message: string;
|
|
105
|
+
origin: IssueOrigin;
|
|
106
|
+
file?: string;
|
|
107
|
+
location?: IssuePosition;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export declare type IssueFilter = (issue: {
|
|
111
|
+
file?: string;
|
|
112
|
+
code: string;
|
|
113
|
+
severity: IssueSeverity;
|
|
114
|
+
origin: string;
|
|
115
|
+
}) => boolean;
|
|
116
|
+
|
|
117
|
+
export declare type IssueOrigin = 'typecheck' | 'lint' | 'internal';
|
|
118
|
+
|
|
119
|
+
export declare interface IssuePosition {
|
|
120
|
+
line: number;
|
|
121
|
+
column: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare type IssueSeverity = 'error' | 'warning';
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parses golar's output into structured issues.
|
|
128
|
+
*
|
|
129
|
+
* golar has no machine readable reporter, so this consumes the human output.
|
|
130
|
+
* Indented continuation lines are TypeScript's elaborations and get folded into
|
|
131
|
+
* the message of the diagnostic that opened them. Lines matching nothing are
|
|
132
|
+
* CLI chatter, such as Node warnings or a stack trace from a crash, and are
|
|
133
|
+
* skipped. The runner surfaces those separately when the process fails.
|
|
134
|
+
*
|
|
135
|
+
* @param output The combined stdout and stderr of a golar run.
|
|
136
|
+
* @param options Working directory and the severity to apply to each phase.
|
|
137
|
+
* @returns The issues found, in the order golar reported them.
|
|
138
|
+
*/
|
|
139
|
+
export declare function parseGolarOutput(output: string, options: ParseOptions): Issue[];
|
|
140
|
+
|
|
141
|
+
export declare interface ParseOptions {
|
|
142
|
+
cwd: string;
|
|
143
|
+
typecheckSeverity: IssueSeverity;
|
|
144
|
+
lintSeverity: IssueSeverity;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare interface ResolvedGolarOptions {
|
|
148
|
+
cwd: string;
|
|
149
|
+
mode: GolarMode;
|
|
150
|
+
async: boolean;
|
|
151
|
+
typecheckSeverity: IssueSeverity;
|
|
152
|
+
lintSeverity: IssueSeverity;
|
|
153
|
+
filter?: IssueFilter;
|
|
154
|
+
formatter: 'basic' | 'codeframe';
|
|
155
|
+
bin?: string;
|
|
156
|
+
args: string[];
|
|
157
|
+
env?: Record<string, string | undefined>;
|
|
158
|
+
failOnError: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Locates golar's entrypoint by walking up the directory tree.
|
|
163
|
+
*
|
|
164
|
+
* `require.resolve('golar/package.json')` is not an option, because golar's
|
|
165
|
+
* exports map only exposes `./unstable` and `./unstable-tsgo`.
|
|
166
|
+
*
|
|
167
|
+
* @param cwd The directory to start searching from.
|
|
168
|
+
* @returns The path of `golar/dist/bin.js`, or `undefined` if golar is not
|
|
169
|
+
* installed anywhere above `cwd`.
|
|
170
|
+
*/
|
|
171
|
+
export declare function resolveGolarBin(cwd: string): string | undefined;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Fills in the defaults for a set of user supplied options.
|
|
175
|
+
*
|
|
176
|
+
* Checking asynchronously is the default while watching, so a rebuild is never
|
|
177
|
+
* held up, and off otherwise, so a one-off build fails on type errors. Lint
|
|
178
|
+
* findings default to `warning` because golar exits 0 when only lint rules
|
|
179
|
+
* fire, and reporting them as errors would fail builds golar considers clean.
|
|
180
|
+
*
|
|
181
|
+
* @param options The options given by the user.
|
|
182
|
+
* @param context The working directory to fall back to, and whether this run is
|
|
183
|
+
* a watch rather than a one-off build.
|
|
184
|
+
* @returns Options with every field resolved.
|
|
185
|
+
*/
|
|
186
|
+
export declare function resolveGolarOptions(options: GolarOptions, context: {
|
|
187
|
+
cwd: string;
|
|
188
|
+
watch: boolean;
|
|
189
|
+
}): ResolvedGolarOptions;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Runs golar once and collects the issues it reports.
|
|
193
|
+
*
|
|
194
|
+
* golar has no watch mode and no structured reporter, so every check is a fresh
|
|
195
|
+
* process whose output gets parsed. A non-zero exit that yields no issues means
|
|
196
|
+
* golar itself failed, and the raw output is reported instead of a clean check.
|
|
197
|
+
*
|
|
198
|
+
* @param options Resolved options describing where and how to run golar.
|
|
199
|
+
* @param signal Aborting this kills the child process, which is how a new build
|
|
200
|
+
* cancels a check that is still running.
|
|
201
|
+
* @returns The issues found, along with the raw output and exit code.
|
|
202
|
+
* @throws {GolarAbortError} If `signal` is aborted before or during the run.
|
|
203
|
+
* @throws If no golar config exists in the working directory, or golar cannot
|
|
204
|
+
* be located or spawned.
|
|
205
|
+
*/
|
|
206
|
+
export declare function runGolar(options: ResolvedGolarOptions, signal?: AbortSignal): Promise<GolarRunResult>;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Orders issues by file and then by position, so output is stable across runs.
|
|
210
|
+
*
|
|
211
|
+
* @param issues The issues to sort.
|
|
212
|
+
* @returns A new sorted array. The input is left untouched.
|
|
213
|
+
*/
|
|
214
|
+
export declare function sortIssues(issues: Issue[]): Issue[];
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Removes ANSI colour escape sequences from a string.
|
|
218
|
+
*
|
|
219
|
+
* @param value The text to clean.
|
|
220
|
+
* @returns The text without escape sequences.
|
|
221
|
+
*/
|
|
222
|
+
export declare function stripAnsi(value: string): string;
|
|
223
|
+
|
|
224
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import node_path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import node_process from "node:process";
|
|
5
|
+
const CONTEXT_LINES = 2;
|
|
6
|
+
function renderCodeFrame(file, line, column) {
|
|
7
|
+
let source;
|
|
8
|
+
try {
|
|
9
|
+
source = node_fs.readFileSync(file, 'utf8');
|
|
10
|
+
} catch {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const lines = source.split(/\r?\n/);
|
|
14
|
+
if (line < 1 || line > lines.length) return;
|
|
15
|
+
const start = Math.max(1, line - CONTEXT_LINES);
|
|
16
|
+
const end = Math.min(lines.length, line + CONTEXT_LINES);
|
|
17
|
+
const gutterWidth = String(end).length;
|
|
18
|
+
const frame = [];
|
|
19
|
+
for(let n = start; n <= end; n++){
|
|
20
|
+
const isTarget = n === line;
|
|
21
|
+
const gutter = String(n).padStart(gutterWidth, ' ');
|
|
22
|
+
frame.push(`${isTarget ? '>' : ' '} ${gutter} | ${lines[n - 1] ?? ''}`);
|
|
23
|
+
if (isTarget) {
|
|
24
|
+
const padding = ' '.repeat(Math.max(0, column - 1));
|
|
25
|
+
frame.push(` ${' '.repeat(gutterWidth)} | ${padding}^`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return frame.join('\n');
|
|
29
|
+
}
|
|
30
|
+
function formatIssue(issue, options) {
|
|
31
|
+
const parts = [];
|
|
32
|
+
const origin = 'lint' === issue.origin ? 'lint' : 'type';
|
|
33
|
+
if (issue.file) {
|
|
34
|
+
const relative = node_path.relative(options.cwd, issue.file) || issue.file;
|
|
35
|
+
const position = issue.location ? `:${issue.location.line}:${issue.location.column}` : '';
|
|
36
|
+
parts.push(`${relative}${position}`);
|
|
37
|
+
}
|
|
38
|
+
parts.push(`${origin} ${issue.code}: ${issue.message}`);
|
|
39
|
+
let result = parts.join('\n');
|
|
40
|
+
if ('codeframe' === options.formatter && issue.file && issue.location) {
|
|
41
|
+
const frame = renderCodeFrame(issue.file, issue.location.line, issue.location.column);
|
|
42
|
+
if (frame) result += `\n${frame}`;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
function formatIssueSummary(issues) {
|
|
47
|
+
const errors = issues.filter((issue)=>'error' === issue.severity).length;
|
|
48
|
+
const warnings = issues.length - errors;
|
|
49
|
+
if (0 === errors && 0 === warnings) return 'No issues found.';
|
|
50
|
+
const parts = [];
|
|
51
|
+
if (errors > 0) parts.push(`${errors} ${1 === errors ? 'error' : 'errors'}`);
|
|
52
|
+
if (warnings > 0) parts.push(`${warnings} ${1 === warnings ? 'warning' : 'warnings'}`);
|
|
53
|
+
return `Found ${parts.join(' and ')}.`;
|
|
54
|
+
}
|
|
55
|
+
function getIssueKey(issue) {
|
|
56
|
+
const { file = '', location, code, message, severity } = issue;
|
|
57
|
+
return [
|
|
58
|
+
file,
|
|
59
|
+
location?.line ?? 0,
|
|
60
|
+
location?.column ?? 0,
|
|
61
|
+
severity,
|
|
62
|
+
code,
|
|
63
|
+
message
|
|
64
|
+
].join(' ');
|
|
65
|
+
}
|
|
66
|
+
function dedupeIssues(issues) {
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
const result = [];
|
|
69
|
+
for (const issue of issues){
|
|
70
|
+
const key = getIssueKey(issue);
|
|
71
|
+
if (!seen.has(key)) {
|
|
72
|
+
seen.add(key);
|
|
73
|
+
result.push(issue);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function sortIssues(issues) {
|
|
79
|
+
return [
|
|
80
|
+
...issues
|
|
81
|
+
].sort((a, b)=>{
|
|
82
|
+
if (a.file !== b.file) return (a.file ?? '').localeCompare(b.file ?? '');
|
|
83
|
+
const lineDiff = (a.location?.line ?? 0) - (b.location?.line ?? 0);
|
|
84
|
+
if (0 !== lineDiff) return lineDiff;
|
|
85
|
+
return (a.location?.column ?? 0) - (b.location?.column ?? 0);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function createInternalIssue(message, severity = 'error') {
|
|
89
|
+
return {
|
|
90
|
+
severity,
|
|
91
|
+
code: 'GOLAR',
|
|
92
|
+
message,
|
|
93
|
+
origin: 'internal'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const CONFIG_FILENAMES = [
|
|
97
|
+
'golar.config.ts',
|
|
98
|
+
'golar.config.mts',
|
|
99
|
+
'golar.config.mjs',
|
|
100
|
+
'golar.config.cts',
|
|
101
|
+
'golar.config.cjs',
|
|
102
|
+
'golar.config.js'
|
|
103
|
+
];
|
|
104
|
+
function findGolarConfig(cwd) {
|
|
105
|
+
for (const name of CONFIG_FILENAMES){
|
|
106
|
+
const candidate = node_path.join(cwd, name);
|
|
107
|
+
if (node_fs.existsSync(candidate)) return candidate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function resolveGolarOptions(options, context) {
|
|
111
|
+
return {
|
|
112
|
+
cwd: options.cwd ? node_path.resolve(options.cwd) : context.cwd,
|
|
113
|
+
mode: options.mode ?? 'all',
|
|
114
|
+
async: options.async ?? context.watch,
|
|
115
|
+
typecheckSeverity: options.typecheckSeverity ?? 'error',
|
|
116
|
+
lintSeverity: options.lintSeverity ?? 'warning',
|
|
117
|
+
filter: options.filter,
|
|
118
|
+
formatter: options.formatter ?? 'codeframe',
|
|
119
|
+
bin: options.bin,
|
|
120
|
+
args: options.args ?? [],
|
|
121
|
+
env: options.env,
|
|
122
|
+
failOnError: options.failOnError ?? true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function getGolarArgs(options) {
|
|
126
|
+
const subcommand = 'all' === options.mode ? [] : [
|
|
127
|
+
options.mode
|
|
128
|
+
];
|
|
129
|
+
return [
|
|
130
|
+
...subcommand,
|
|
131
|
+
...options.args
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
const ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
|
135
|
+
const TYPECHECK_PATTERN = /^(.+?)\((\d+),(\d+)\): (error|warning|message) (TS\d+): (.*)$/;
|
|
136
|
+
const LINT_PATTERN = /^(.+?):(\d+):(\d+): ([\w@/-]+): (.*)$/;
|
|
137
|
+
const GLOBAL_PATTERN = /^(error|warning) (TS\d+): (.*)$/;
|
|
138
|
+
const HEADER_PATTERN = /^Using config from /;
|
|
139
|
+
function stripAnsi(value) {
|
|
140
|
+
return value.replace(ANSI_PATTERN, '');
|
|
141
|
+
}
|
|
142
|
+
function resolveFile(cwd, file) {
|
|
143
|
+
return node_path.isAbsolute(file) ? file : node_path.resolve(cwd, file);
|
|
144
|
+
}
|
|
145
|
+
function parseGolarOutput(output, options) {
|
|
146
|
+
const { cwd, typecheckSeverity, lintSeverity } = options;
|
|
147
|
+
const issues = [];
|
|
148
|
+
let current;
|
|
149
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)){
|
|
150
|
+
const line = rawLine.replace(/\s+$/, '');
|
|
151
|
+
if (0 === line.length) continue;
|
|
152
|
+
if (/^\s/.test(line)) {
|
|
153
|
+
if (current) current.message += `\n${line}`;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (HEADER_PATTERN.test(line)) {
|
|
157
|
+
current = void 0;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const typecheck = TYPECHECK_PATTERN.exec(line);
|
|
161
|
+
if (typecheck) {
|
|
162
|
+
const [, file, lineNo, column, severity, code, message] = typecheck;
|
|
163
|
+
current = {
|
|
164
|
+
severity: 'message' === severity ? 'warning' : typecheckSeverity,
|
|
165
|
+
code: code,
|
|
166
|
+
message: message,
|
|
167
|
+
origin: 'typecheck',
|
|
168
|
+
file: resolveFile(cwd, file),
|
|
169
|
+
location: {
|
|
170
|
+
line: Number(lineNo),
|
|
171
|
+
column: Number(column)
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
issues.push(current);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const lint = LINT_PATTERN.exec(line);
|
|
178
|
+
if (lint) {
|
|
179
|
+
const [, file, lineNo, column, rule, message] = lint;
|
|
180
|
+
current = {
|
|
181
|
+
severity: lintSeverity,
|
|
182
|
+
code: rule,
|
|
183
|
+
message: message,
|
|
184
|
+
origin: 'lint',
|
|
185
|
+
file: resolveFile(cwd, file),
|
|
186
|
+
location: {
|
|
187
|
+
line: Number(lineNo),
|
|
188
|
+
column: Number(column)
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
issues.push(current);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const global = GLOBAL_PATTERN.exec(line);
|
|
195
|
+
if (global) {
|
|
196
|
+
const [, severity, code, message] = global;
|
|
197
|
+
current = {
|
|
198
|
+
severity: 'warning' === severity ? 'warning' : typecheckSeverity,
|
|
199
|
+
code: code,
|
|
200
|
+
message: message,
|
|
201
|
+
origin: 'typecheck'
|
|
202
|
+
};
|
|
203
|
+
issues.push(current);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
current = void 0;
|
|
207
|
+
}
|
|
208
|
+
return issues;
|
|
209
|
+
}
|
|
210
|
+
class GolarAbortError extends Error {
|
|
211
|
+
constructor(){
|
|
212
|
+
super('golar run aborted');
|
|
213
|
+
this.name = 'GolarAbortError';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function resolveGolarBin(cwd) {
|
|
217
|
+
let dir = node_path.resolve(cwd);
|
|
218
|
+
while(true){
|
|
219
|
+
const entry = node_path.join(dir, 'node_modules', 'golar', 'dist', 'bin.js');
|
|
220
|
+
if (node_fs.existsSync(entry)) return entry;
|
|
221
|
+
const parent = node_path.dirname(dir);
|
|
222
|
+
if (parent === dir) return;
|
|
223
|
+
dir = parent;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function createCommand(options) {
|
|
227
|
+
if (options.bin) return options.bin.endsWith('.js') ? {
|
|
228
|
+
command: node_process.execPath,
|
|
229
|
+
args: [
|
|
230
|
+
options.bin
|
|
231
|
+
]
|
|
232
|
+
} : {
|
|
233
|
+
command: options.bin,
|
|
234
|
+
args: []
|
|
235
|
+
};
|
|
236
|
+
const resolved = resolveGolarBin(options.cwd);
|
|
237
|
+
if (!resolved) throw new Error(`Could not find the "golar" package from "${options.cwd}". Install it with \`pnpm add -D golar\`, or set the \`bin\` option.`);
|
|
238
|
+
return {
|
|
239
|
+
command: node_process.execPath,
|
|
240
|
+
args: [
|
|
241
|
+
resolved
|
|
242
|
+
]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function runGolar(options, signal) {
|
|
246
|
+
if (signal?.aborted) throw new GolarAbortError();
|
|
247
|
+
if (!findGolarConfig(options.cwd)) throw new Error(`No golar config found in "${options.cwd}". Create a \`golar.config.ts\` there, or point the \`cwd\` option at the directory that has one.`);
|
|
248
|
+
const { command, args: commandArgs } = createCommand(options);
|
|
249
|
+
const args = [
|
|
250
|
+
...commandArgs,
|
|
251
|
+
...getGolarArgs(options)
|
|
252
|
+
];
|
|
253
|
+
return await new Promise((resolve, reject)=>{
|
|
254
|
+
let settled = false;
|
|
255
|
+
let output = '';
|
|
256
|
+
const child = spawn(command, args, {
|
|
257
|
+
cwd: options.cwd,
|
|
258
|
+
stdio: [
|
|
259
|
+
'ignore',
|
|
260
|
+
'pipe',
|
|
261
|
+
'pipe'
|
|
262
|
+
],
|
|
263
|
+
env: options.env ? {
|
|
264
|
+
...node_process.env,
|
|
265
|
+
...options.env
|
|
266
|
+
} : node_process.env
|
|
267
|
+
});
|
|
268
|
+
const cleanup = ()=>{
|
|
269
|
+
signal?.removeEventListener('abort', onAbort);
|
|
270
|
+
child.stdout?.removeAllListeners();
|
|
271
|
+
child.stderr?.removeAllListeners();
|
|
272
|
+
child.removeAllListeners();
|
|
273
|
+
};
|
|
274
|
+
const settle = (fn)=>{
|
|
275
|
+
if (settled) return;
|
|
276
|
+
settled = true;
|
|
277
|
+
cleanup();
|
|
278
|
+
fn();
|
|
279
|
+
};
|
|
280
|
+
function onAbort() {
|
|
281
|
+
child.kill('SIGKILL');
|
|
282
|
+
settle(()=>reject(new GolarAbortError()));
|
|
283
|
+
}
|
|
284
|
+
signal?.addEventListener('abort', onAbort, {
|
|
285
|
+
once: true
|
|
286
|
+
});
|
|
287
|
+
child.stdout?.on('data', (chunk)=>{
|
|
288
|
+
output += chunk.toString();
|
|
289
|
+
});
|
|
290
|
+
child.stderr?.on('data', (chunk)=>{
|
|
291
|
+
output += chunk.toString();
|
|
292
|
+
});
|
|
293
|
+
child.on('error', (error)=>settle(()=>reject(error)));
|
|
294
|
+
child.on('close', (exitCode)=>{
|
|
295
|
+
settle(()=>{
|
|
296
|
+
const issues = sortIssues(dedupeIssues(parseGolarOutput(output, {
|
|
297
|
+
cwd: options.cwd,
|
|
298
|
+
typecheckSeverity: options.typecheckSeverity,
|
|
299
|
+
lintSeverity: options.lintSeverity
|
|
300
|
+
})));
|
|
301
|
+
if (0 !== exitCode && 0 === issues.length) issues.push(createInternalIssue(`golar exited with code ${exitCode}.\n${stripAnsi(output).trim()}`));
|
|
302
|
+
resolve({
|
|
303
|
+
issues,
|
|
304
|
+
output,
|
|
305
|
+
exitCode,
|
|
306
|
+
aborted: false
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
export { GolarAbortError, createInternalIssue, dedupeIssues, findGolarConfig, formatIssue, formatIssueSummary, getGolarArgs, getIssueKey, parseGolarOutput, resolveGolarBin, resolveGolarOptions, runGolar, sortIssues, stripAnsi };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@golar-rstack/core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Shared golar runner and diagnostic parsing for the Rstack golar plugins",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Oskar Lebuda",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"golar",
|
|
10
|
+
"typecheck",
|
|
11
|
+
"typescript",
|
|
12
|
+
"rstack"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"golar": ">=0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"golar": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@rslib/core": "^0.23.2",
|
|
36
|
+
"@rstest/core": "^0.11.2",
|
|
37
|
+
"@types/node": "^24.13.3",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.12"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "rslib build",
|
|
48
|
+
"dev": "rslib build --watch",
|
|
49
|
+
"test": "rstest run",
|
|
50
|
+
"check:publish": "publint && attw --pack ."
|
|
51
|
+
}
|
|
52
|
+
}
|