@golar-rstack/rspack 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 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,61 @@
1
+ # @golar-rstack/rspack
2
+
3
+ Run [golar](https://golar.dev) type checking and type-aware linting in a
4
+ separate process as an Rspack plugin.
5
+
6
+ Because golar is built on `typescript-go` and understands embedded languages,
7
+ type errors inside `.vue`, `.svelte`, `.astro` and `.gts` files are reported at
8
+ their real position in the source file.
9
+
10
+ Using Rsbuild? Use [`@golar-rstack/rsbuild`](https://www.npmjs.com/package/@golar-rstack/rsbuild)
11
+ instead.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add -D @golar-rstack/rspack golar
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Create `golar.config.ts` in your project root. golar discovers it relative to
22
+ its working directory:
23
+
24
+ ```ts
25
+ import { defineConfig } from 'golar/unstable'
26
+ import '@golar/vue'
27
+
28
+ export default defineConfig({})
29
+ ```
30
+
31
+ ```ts
32
+ // rspack.config.ts
33
+ import { GolarRspackPlugin } from '@golar-rstack/rspack'
34
+
35
+ export default {
36
+ plugins: [new GolarRspackPlugin()],
37
+ }
38
+ ```
39
+
40
+ In watch mode the check runs asynchronously and is pushed to the dev server's
41
+ error overlay. In a one-off build the compilation waits for golar, so type
42
+ errors fail the build.
43
+
44
+ ## Options
45
+
46
+ See the [repository README](https://github.com/OskarLebuda/golar-rstack-plugin#options)
47
+ for the full list. Two worth knowing up front:
48
+
49
+ - `lintSeverity` defaults to `'warning'`, because golar exits 0 when only lint
50
+ rules fire.
51
+ - `cwd` defaults to the compiler `context` and is what selects the golar config,
52
+ since golar has no `--config` flag.
53
+
54
+ ## Requirements
55
+
56
+ - Node.js >= 22.12
57
+ - `golar` installed in the project
58
+
59
+ ## License
60
+
61
+ MIT
@@ -0,0 +1,122 @@
1
+ import type { Compiler } from '@rspack/core';
2
+ import { GolarMode } from '@golar-rstack/core';
3
+ import { GolarOptions } from '@golar-rstack/core';
4
+ import { Issue } from '@golar-rstack/core';
5
+ import { IssueFilter } from '@golar-rstack/core';
6
+ import { IssueOrigin } from '@golar-rstack/core';
7
+ import { IssueSeverity } from '@golar-rstack/core';
8
+
9
+ /**
10
+ * An error shaped the way Rspack expects.
11
+ *
12
+ * Rspack only renders `loc` for errors carrying a real `NormalModule`, which
13
+ * this plugin has no way to supply, so the position is folded into `file`
14
+ * instead. This is the same workaround ts-checker-rspack-plugin uses.
15
+ */
16
+ export declare class GolarIssueError extends Error {
17
+ readonly issue: Issue;
18
+ readonly hideStack = true;
19
+ file?: string;
20
+ /**
21
+ * @param message The formatted text Rspack will display.
22
+ * @param issue The issue this error was built from, kept so consumers can
23
+ * inspect the original diagnostic.
24
+ */
25
+ constructor(message: string, issue: Issue);
26
+ }
27
+
28
+ export { GolarMode }
29
+
30
+ export { GolarOptions }
31
+
32
+ /**
33
+ * Runs [golar](https://golar.dev), which type checks and lints TypeScript and
34
+ * embedded languages such as Vue, Svelte and Astro, in a separate process, and
35
+ * reports what it finds as Rspack errors and warnings.
36
+ */
37
+ export declare class GolarRspackPlugin {
38
+ private readonly options;
39
+ /**
40
+ * @param options Plugin options. See the package README for the defaults.
41
+ */
42
+ constructor(options?: GolarRspackPluginOptions);
43
+ /**
44
+ * Registers the plugin on a compiler. Called by Rspack.
45
+ *
46
+ * @param compiler The compiler to attach to.
47
+ */
48
+ apply(compiler: Compiler): void;
49
+ /**
50
+ * Records the dev server's `done` tap so async results can be replayed into
51
+ * the browser overlay.
52
+ *
53
+ * Installed from `apply` so the interceptor is in place before the dev server
54
+ * registers its own tap.
55
+ *
56
+ * @param compiler The compiler being set up.
57
+ * @param state State to store the captured tap on.
58
+ */
59
+ private interceptDevServerTap;
60
+ /**
61
+ * Resolves options and installs the reporting hooks on the first run.
62
+ *
63
+ * This is deferred because the default for `async` depends on whether this is
64
+ * a build or a watch, which is not known when `apply` runs.
65
+ *
66
+ * @param compiler The compiler being set up.
67
+ * @param state State to mark as initialized.
68
+ */
69
+ private tapInitialization;
70
+ /**
71
+ * Starts a golar run for each compilation, superseding any run still going.
72
+ *
73
+ * Runs are chained rather than overlapped, because golar is a whole project
74
+ * check backed by a native process and concurrency would only contend for
75
+ * CPU.
76
+ *
77
+ * @param compiler The compiler being set up.
78
+ * @param state State holding the current run and its abort controller.
79
+ * @param logger Infrastructure logger used for debug output.
80
+ */
81
+ private tapCompilationToRunGolar;
82
+ /**
83
+ * Installs blocking reporting, where the compilation waits for golar and owns
84
+ * the issues it found.
85
+ *
86
+ * @param compiler The compiler being set up.
87
+ * @param state State holding the current run.
88
+ */
89
+ private tapAfterCompileToReport;
90
+ /**
91
+ * Installs async reporting, where the build is never held up.
92
+ *
93
+ * Issues are logged when they arrive and, if a dev server is listening, its
94
+ * `done` tap is replayed with the issues attached. That re-sends the stats
95
+ * over the HMR socket, which is what makes the browser overlay show them.
96
+ *
97
+ * @param compiler The compiler being set up.
98
+ * @param state State holding the current run and the captured dev server tap.
99
+ */
100
+ private tapDoneToReportAsync;
101
+ /**
102
+ * Aborts any run still going when the compiler shuts down.
103
+ *
104
+ * @param compiler The compiler being set up.
105
+ * @param state State holding the abort controller.
106
+ */
107
+ private tapCloseToAbort;
108
+ }
109
+
110
+ export declare interface GolarRspackPluginOptions extends GolarOptions {
111
+ devServer?: boolean;
112
+ }
113
+
114
+ export { Issue }
115
+
116
+ export { IssueFilter }
117
+
118
+ export { IssueOrigin }
119
+
120
+ export { IssueSeverity }
121
+
122
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,159 @@
1
+ import node_path from "node:path";
2
+ import node_process from "node:process";
3
+ import { GolarAbortError, formatIssue, formatIssueSummary, resolveGolarOptions, runGolar } from "@golar-rstack/core";
4
+ class GolarIssueError extends Error {
5
+ issue;
6
+ hideStack = true;
7
+ file;
8
+ constructor(message, issue){
9
+ super(message), this.issue = issue;
10
+ this.name = 'GolarIssueError';
11
+ if (issue.file) {
12
+ const relative = node_path.relative(node_process.cwd(), issue.file);
13
+ this.file = relative.startsWith('..') ? issue.file : relative;
14
+ if (issue.location) this.file += `:${issue.location.line}:${issue.location.column}`;
15
+ }
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+ function prepareIssues(issues, options) {
20
+ const filtered = options.filter ? issues.filter((issue)=>options.filter(issue)) : issues;
21
+ if (options.failOnError) return filtered;
22
+ return filtered.map((issue)=>'error' === issue.severity ? {
23
+ ...issue,
24
+ severity: 'warning'
25
+ } : issue);
26
+ }
27
+ function pushIssues(compilation, issues, options) {
28
+ for (const issue of issues){
29
+ const error = new GolarIssueError(formatIssue(issue, {
30
+ cwd: options.cwd,
31
+ formatter: options.formatter
32
+ }), issue);
33
+ if ('warning' === issue.severity) compilation.warnings.push(error);
34
+ else compilation.errors.push(error);
35
+ }
36
+ }
37
+ function createPluginState() {
38
+ return {
39
+ initialized: false,
40
+ watching: false,
41
+ iteration: 0
42
+ };
43
+ }
44
+ const PLUGIN_NAME = 'GolarRspackPlugin';
45
+ const DEV_SERVER_TAPS = [
46
+ 'webpack-dev-server',
47
+ 'rspack-dev-server',
48
+ 'rsbuild-dev-server'
49
+ ];
50
+ class GolarRspackPlugin {
51
+ options;
52
+ constructor(options = {}){
53
+ this.options = options;
54
+ }
55
+ apply(compiler) {
56
+ const state = createPluginState();
57
+ const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
58
+ this.interceptDevServerTap(compiler, state);
59
+ this.tapInitialization(compiler, state);
60
+ this.tapCompilationToRunGolar(compiler, state, logger);
61
+ this.tapCloseToAbort(compiler, state);
62
+ }
63
+ interceptDevServerTap(compiler, state) {
64
+ if (false === this.options.devServer) return;
65
+ compiler.hooks.done.intercept({
66
+ register: (tap)=>{
67
+ const candidate = tap;
68
+ if (DEV_SERVER_TAPS.includes(candidate.name) && 'sync' === candidate.type) state.devServerDoneTap = candidate;
69
+ return tap;
70
+ }
71
+ });
72
+ }
73
+ tapInitialization(compiler, state) {
74
+ const initialize = (watching)=>{
75
+ if (state.initialized) return;
76
+ state.initialized = true;
77
+ state.watching = watching;
78
+ state.options = resolveGolarOptions(this.options, {
79
+ cwd: compiler.context ?? process.cwd(),
80
+ watch: watching
81
+ });
82
+ if (state.options.async) this.tapDoneToReportAsync(compiler, state);
83
+ else this.tapAfterCompileToReport(compiler, state);
84
+ };
85
+ compiler.hooks.run.tap(PLUGIN_NAME, ()=>initialize(false));
86
+ compiler.hooks.watchRun.tap(PLUGIN_NAME, ()=>initialize(true));
87
+ }
88
+ tapCompilationToRunGolar(compiler, state, logger) {
89
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation)=>{
90
+ if (compilation.compiler !== compiler) return;
91
+ const options = state.options;
92
+ if (!options) return;
93
+ const iteration = ++state.iteration;
94
+ state.abortController?.abort();
95
+ const abortController = new AbortController();
96
+ state.abortController = abortController;
97
+ state.issuesPromise = (state.issuesPromise ?? Promise.resolve(void 0)).catch(()=>void 0).then(async ()=>{
98
+ if (abortController.signal.aborted) return;
99
+ logger.debug(`Running golar, iteration ${iteration}.`);
100
+ try {
101
+ const result = await runGolar(options, abortController.signal);
102
+ logger.debug(`golar iteration ${iteration} found ${result.issues.length} issue(s).`);
103
+ return result.issues;
104
+ } catch (error) {
105
+ if (error instanceof GolarAbortError) return void logger.debug(`golar iteration ${iteration} aborted.`);
106
+ compilation.errors.push(error);
107
+ return;
108
+ } finally{
109
+ if (state.abortController === abortController) state.abortController = void 0;
110
+ }
111
+ });
112
+ });
113
+ }
114
+ tapAfterCompileToReport(compiler, state) {
115
+ compiler.hooks.afterCompile.tapPromise(PLUGIN_NAME, async (compilation)=>{
116
+ if (compilation.compiler !== compiler || !state.options) return;
117
+ const issues = await state.issuesPromise;
118
+ if (!issues) return;
119
+ pushIssues(compilation, prepareIssues(issues, state.options), state.options);
120
+ });
121
+ }
122
+ tapDoneToReportAsync(compiler, state) {
123
+ const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
124
+ compiler.hooks.done.tap(PLUGIN_NAME, (stats)=>{
125
+ if (stats.compilation.compiler !== compiler || !state.options) return;
126
+ const options = state.options;
127
+ const issuesPromise = state.issuesPromise;
128
+ (async ()=>{
129
+ let issues;
130
+ try {
131
+ issues = await issuesPromise;
132
+ } catch {
133
+ return;
134
+ }
135
+ if (!issues || state.issuesPromise !== issuesPromise) return;
136
+ const prepared = prepareIssues(issues, options);
137
+ if (0 === prepared.length) return void logger.info('No golar issues found.');
138
+ logger.error(formatIssueSummary(prepared));
139
+ for (const issue of prepared)logger.error(formatIssue(issue, {
140
+ cwd: options.cwd,
141
+ formatter: options.formatter
142
+ }));
143
+ if (state.devServerDoneTap) {
144
+ pushIssues(stats.compilation, prepared, options);
145
+ state.devServerDoneTap.fn(stats);
146
+ }
147
+ })();
148
+ });
149
+ }
150
+ tapCloseToAbort(compiler, state) {
151
+ const abort = ()=>{
152
+ state.abortController?.abort();
153
+ state.abortController = void 0;
154
+ };
155
+ compiler.hooks.watchClose.tap(PLUGIN_NAME, abort);
156
+ compiler.hooks.shutdown?.tap(PLUGIN_NAME, abort);
157
+ }
158
+ }
159
+ export { GolarIssueError, GolarRspackPlugin };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@golar-rstack/rspack",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Run golar type checking and linting in a separate process as an Rspack plugin",
6
+ "license": "MIT",
7
+ "author": "Oskar Lebuda",
8
+ "keywords": [
9
+ "golar",
10
+ "rspack",
11
+ "rspack-plugin",
12
+ "typecheck",
13
+ "typescript",
14
+ "vue",
15
+ "svelte",
16
+ "astro",
17
+ "rstack"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "sideEffects": false,
31
+ "dependencies": {
32
+ "@golar-rstack/core": "0.1.0"
33
+ },
34
+ "peerDependencies": {
35
+ "@rspack/core": "^1.0.0 || ^2.0.0",
36
+ "golar": ">=0.1.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@rspack/core": {
40
+ "optional": true
41
+ },
42
+ "golar": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@rslib/core": "^0.23.2",
48
+ "@rspack/core": "^2.1.5",
49
+ "@rstest/core": "^0.11.4",
50
+ "@types/node": "^24.13.3",
51
+ "typescript": "^6.0.3"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "engines": {
57
+ "node": ">=22.12"
58
+ },
59
+ "scripts": {
60
+ "build": "rslib build",
61
+ "dev": "rslib build --watch",
62
+ "test": "rstest run",
63
+ "check:publish": "publint && attw --pack ."
64
+ }
65
+ }