@salesforce-ux/slds-linter 0.2.0 → 0.2.2-alpha-2-alpha.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.
Files changed (47) hide show
  1. package/build/commands/emit.d.ts +2 -0
  2. package/build/commands/emit.js +48 -0
  3. package/build/commands/lint.d.ts +2 -0
  4. package/build/commands/lint.js +55 -0
  5. package/build/commands/report.d.ts +2 -0
  6. package/build/commands/report.js +66 -0
  7. package/build/executor/__tests__/executor.test.js +216 -0
  8. package/build/executor/index.d.ts +20 -0
  9. package/build/executor/index.js +105 -0
  10. package/build/index.d.ts +2 -0
  11. package/build/index.js +33 -0
  12. package/build/services/__tests__/file-scanner.test.js +47 -0
  13. package/build/services/artifact-processor.d.ts +6 -0
  14. package/build/services/artifact-processor.js +37 -0
  15. package/build/services/batch-processor.d.ts +29 -0
  16. package/build/services/batch-processor.js +84 -0
  17. package/build/services/config.resolver.d.ts +6 -0
  18. package/build/services/config.resolver.js +20 -0
  19. package/build/services/file-patterns.d.ts +3 -0
  20. package/build/services/file-patterns.js +21 -0
  21. package/build/services/file-scanner.d.ts +26 -0
  22. package/build/services/file-scanner.js +71 -0
  23. package/build/services/lint-runner.d.ts +17 -0
  24. package/build/services/lint-runner.js +69 -0
  25. package/build/services/report-generator.d.ts +43 -0
  26. package/build/services/report-generator.js +186 -0
  27. package/build/types/index.d.ts +75 -0
  28. package/build/types/index.js +0 -0
  29. package/build/utils/config-utils.d.ts +33 -0
  30. package/build/utils/config-utils.js +68 -0
  31. package/build/utils/editorLinkUtil.d.ts +21 -0
  32. package/build/utils/editorLinkUtil.js +21 -0
  33. package/build/utils/index.d.ts +2 -0
  34. package/build/utils/index.js +7 -0
  35. package/build/utils/lintResultsUtil.d.ts +21 -0
  36. package/build/utils/lintResultsUtil.js +70 -0
  37. package/build/utils/logger.d.ts +8 -0
  38. package/build/utils/logger.js +28 -0
  39. package/build/utils/nodeVersionUtil.d.ts +19 -0
  40. package/build/utils/nodeVersionUtil.js +42 -0
  41. package/build/workers/base.worker.d.ts +15 -0
  42. package/build/workers/base.worker.js +44 -0
  43. package/build/workers/eslint.worker.d.ts +1 -0
  44. package/build/workers/eslint.worker.js +49 -0
  45. package/build/workers/stylelint.worker.d.ts +1 -0
  46. package/build/workers/stylelint.worker.js +40 -0
  47. package/package.json +4 -4
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerEmitCommand(program: Command): void;
@@ -0,0 +1,48 @@
1
+ // src/commands/emit.ts
2
+ import chalk from "chalk";
3
+ import { Logger } from "../utils/logger.js";
4
+ import { normalizeCliOptions } from "../utils/config-utils.js";
5
+ import {
6
+ DEFAULT_ESLINT_CONFIG_PATH,
7
+ DEFAULT_STYLELINT_CONFIG_PATH
8
+ } from "../services/config.resolver.js";
9
+ import path from "path";
10
+ import { copyFile } from "fs/promises";
11
+ function registerEmitCommand(program) {
12
+ program.command("emit").description("Emits the configuration files used by slds-linter cli").option(
13
+ "-d, --directory <path>",
14
+ "Target directory to emit (defaults to current directory). Support glob patterns"
15
+ ).action(async (options) => {
16
+ try {
17
+ Logger.info(chalk.blue("Emitting configuration files..."));
18
+ const normalizedOptions = normalizeCliOptions(options, {
19
+ configStylelint: DEFAULT_STYLELINT_CONFIG_PATH,
20
+ configEslint: DEFAULT_ESLINT_CONFIG_PATH
21
+ });
22
+ const destStyleConfigPath = path.join(
23
+ normalizedOptions.directory,
24
+ path.basename(normalizedOptions.configStylelint)
25
+ );
26
+ await copyFile(normalizedOptions.configStylelint, destStyleConfigPath);
27
+ Logger.success(chalk.green(`Stylelint configuration created at:
28
+ ${destStyleConfigPath}
29
+ `));
30
+ const destESLintConfigPath = path.join(
31
+ normalizedOptions.directory,
32
+ path.basename(normalizedOptions.configEslint)
33
+ );
34
+ await copyFile(normalizedOptions.configEslint, destESLintConfigPath);
35
+ Logger.success(chalk.green(`ESLint configuration created at:
36
+ ${destESLintConfigPath}
37
+ `));
38
+ } catch (error) {
39
+ Logger.error(
40
+ chalk.red(`Failed to emit configuration: ${error.message}`)
41
+ );
42
+ process.exit(1);
43
+ }
44
+ });
45
+ }
46
+ export {
47
+ registerEmitCommand
48
+ };
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerLintCommand(program: Command): void;
@@ -0,0 +1,55 @@
1
+ // src/commands/lint.ts
2
+ import { Option } from "commander";
3
+ import chalk from "chalk";
4
+ import { printLintResults } from "../utils/lintResultsUtil.js";
5
+ import { normalizeCliOptions, normalizeDirectoryPath } from "../utils/config-utils.js";
6
+ import { Logger } from "../utils/logger.js";
7
+ import { DEFAULT_ESLINT_CONFIG_PATH, DEFAULT_STYLELINT_CONFIG_PATH } from "../services/config.resolver.js";
8
+ import { lint } from "../executor/index.js";
9
+ function registerLintCommand(program) {
10
+ program.command("lint").aliases(["lint:styles", "lint:components"]).configureHelp({
11
+ commandUsage: () => {
12
+ return `${program.name()} lint [directory] [options]`;
13
+ }
14
+ }).description("Run both style and component linting").argument("[directory]", "Target directory to scan (defaults to current directory). Support glob patterns").addOption(new Option("-d, --directory <path>", "Target directory to scan (defaults to current directory). Support glob patterns").hideHelp()).option("--fix", "Automatically fix problems").option("--config-stylelint <path>", "Path to stylelint config file").option("--config-eslint <path>", "Path to eslint config file").option("--editor <editor>", "Editor to open files with (e.g., vscode, atom, sublime). Defaults to vscode", "vscode").action(async (directory, options) => {
15
+ const startTime = Date.now();
16
+ try {
17
+ Logger.info(chalk.blue("Starting lint process..."));
18
+ const normalizedOptions = normalizeCliOptions(options, {
19
+ configStylelint: DEFAULT_STYLELINT_CONFIG_PATH,
20
+ configEslint: DEFAULT_ESLINT_CONFIG_PATH
21
+ });
22
+ if (directory) {
23
+ normalizedOptions.directory = normalizeDirectoryPath(directory);
24
+ } else if (options.directory) {
25
+ Logger.newLine().warning(chalk.yellow(
26
+ `WARNING: --directory, -d option is deprecated. Supply as argument instead.
27
+ Example: npx @salesforce-ux/slds-linter lint ${options.directory}`
28
+ ));
29
+ }
30
+ const lintResults = await lint({
31
+ directory: normalizedOptions.directory,
32
+ fix: normalizedOptions.fix,
33
+ configStylelint: normalizedOptions.configStylelint,
34
+ configEslint: normalizedOptions.configEslint
35
+ });
36
+ printLintResults(lintResults, normalizedOptions.editor);
37
+ const errorCount = lintResults.reduce((sum, r) => sum + r.errors.length, 0);
38
+ const warningCount = lintResults.reduce((sum, r) => sum + r.warnings.length, 0);
39
+ Logger.info(
40
+ `
41
+ ${chalk.red(`${errorCount} error${errorCount !== 1 ? "s" : ""}`)} ${chalk.yellow(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`)}`
42
+ );
43
+ const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
44
+ Logger.success(chalk.green(`
45
+ Linting completed in ${elapsedTime} seconds.`));
46
+ process.exit(errorCount > 0 ? 1 : 0);
47
+ } catch (error) {
48
+ Logger.error(chalk.red(`Failed to complete linting: ${error.message}`));
49
+ process.exit(1);
50
+ }
51
+ });
52
+ }
53
+ export {
54
+ registerLintCommand
55
+ };
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerReportCommand(program: Command): void;
@@ -0,0 +1,66 @@
1
+ // src/commands/report.ts
2
+ import { Option } from "commander";
3
+ import path from "path";
4
+ import ora from "ora";
5
+ import chalk from "chalk";
6
+ import fs from "fs";
7
+ import { normalizeCliOptions, normalizeDirectoryPath } from "../utils/config-utils.js";
8
+ import { Logger } from "../utils/logger.js";
9
+ import { DEFAULT_ESLINT_CONFIG_PATH, DEFAULT_STYLELINT_CONFIG_PATH } from "../services/config.resolver.js";
10
+ import { report, lint } from "../executor/index.js";
11
+ function registerReportCommand(program) {
12
+ program.command("report").description("Generate report from linting results").argument("[directory]", "Target directory to scan (defaults to current directory). Support glob patterns").addOption(new Option("-d, --directory <path>", "Target directory to scan (defaults to current directory). Support glob patterns").hideHelp()).option("-o, --output <path>", "Output directory for reports (defaults to current directory)").option("--config-stylelint <path>", "Path to stylelint config file").option("--config-eslint <path>", "Path to eslint config file").addOption(new Option("--format <type>", "Output format").choices(["sarif", "csv"]).default("sarif")).action(async (directory, options) => {
13
+ const spinner = ora("Starting report generation...");
14
+ try {
15
+ const normalizedOptions = normalizeCliOptions(options, {
16
+ configStylelint: DEFAULT_STYLELINT_CONFIG_PATH,
17
+ configEslint: DEFAULT_ESLINT_CONFIG_PATH
18
+ });
19
+ if (directory) {
20
+ normalizedOptions.directory = normalizeDirectoryPath(directory);
21
+ } else if (options.directory) {
22
+ Logger.newLine().warning(chalk.yellow(
23
+ `WARNING: --directory, -d option is deprecated. Supply as argument instead.
24
+ Example: npx @salesforce-ux/slds-linter report ${options.directory}`
25
+ ));
26
+ }
27
+ spinner.start();
28
+ const reportFormat = normalizedOptions.format?.toLowerCase() || "sarif";
29
+ const lintResults = await lint({
30
+ directory: normalizedOptions.directory,
31
+ configStylelint: normalizedOptions.configStylelint,
32
+ configEslint: normalizedOptions.configEslint
33
+ });
34
+ const reportStream = await report({
35
+ format: reportFormat
36
+ }, lintResults);
37
+ let outputFilePath;
38
+ if (reportFormat === "sarif") {
39
+ spinner.text = "Saving SARIF report...";
40
+ outputFilePath = path.join(normalizedOptions.output, "slds-linter-report.sarif");
41
+ } else if (reportFormat === "csv") {
42
+ spinner.text = "Saving CSV report...";
43
+ outputFilePath = path.join(normalizedOptions.output, "slds-linter-report.csv");
44
+ } else {
45
+ throw new Error(`Invalid format: ${reportFormat}. Supported formats: sarif, csv`);
46
+ }
47
+ const writeStream = fs.createWriteStream(outputFilePath);
48
+ reportStream.pipe(writeStream);
49
+ await new Promise((resolve, reject) => {
50
+ writeStream.on("finish", resolve);
51
+ writeStream.on("error", reject);
52
+ });
53
+ Logger.success(`${reportFormat.toUpperCase()} report generated: ${outputFilePath}
54
+ `);
55
+ spinner.succeed("Report generation completed");
56
+ process.exit(0);
57
+ } catch (error) {
58
+ spinner?.fail("Report generation failed");
59
+ Logger.error(`Failed to generate report: ${error.message}`);
60
+ process.exit(1);
61
+ }
62
+ });
63
+ }
64
+ export {
65
+ registerReportCommand
66
+ };
@@ -0,0 +1,216 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/executor/index.ts
23
+ var executor_exports = {};
24
+ __export(executor_exports, {
25
+ lint: () => lint,
26
+ report: () => report
27
+ });
28
+ import { Readable } from "stream";
29
+ import { FileScanner } from "../services/file-scanner.js";
30
+ import { LintRunner } from "../services/lint-runner.js";
31
+ import { StyleFilePatterns, ComponentFilePatterns } from "../services/file-patterns.js";
32
+ import { ReportGenerator, CsvReportGenerator } from "../services/report-generator.js";
33
+ import { LINTER_CLI_VERSION } from "../services/config.resolver.js";
34
+ import { normalizeCliOptions } from "../utils/config-utils.js";
35
+ import { Logger } from "../utils/logger.js";
36
+ async function lint(config) {
37
+ try {
38
+ Logger.debug("Starting linting with Node API");
39
+ const normalizedConfig = normalizeCliOptions(config, {}, true);
40
+ const styleFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
41
+ patterns: StyleFilePatterns,
42
+ batchSize: 100
43
+ });
44
+ const componentFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
45
+ patterns: ComponentFilePatterns,
46
+ batchSize: 100
47
+ });
48
+ const lintOptions = {
49
+ fix: normalizedConfig.fix,
50
+ configPath: normalizedConfig.configStylelint
51
+ };
52
+ const styleResults = await LintRunner.runLinting(styleFiles, "style", {
53
+ ...lintOptions,
54
+ configPath: normalizedConfig.configStylelint
55
+ });
56
+ const componentResults = await LintRunner.runLinting(componentFiles, "component", {
57
+ ...lintOptions,
58
+ configPath: normalizedConfig.configEslint
59
+ });
60
+ const combinedResults = [...styleResults, ...componentResults];
61
+ return standardizeLintMessages(combinedResults);
62
+ } catch (error) {
63
+ const errorMessage = `Linting failed: ${error.message}`;
64
+ Logger.error(errorMessage);
65
+ throw new Error(errorMessage);
66
+ }
67
+ }
68
+ async function report(config, results) {
69
+ try {
70
+ Logger.debug("Starting report generation with Node API");
71
+ const normalizedConfig = normalizeCliOptions(config, {}, true);
72
+ const format = normalizedConfig.format || "sarif";
73
+ const lintResults = results || await lint({
74
+ directory: normalizedConfig.directory,
75
+ configStylelint: normalizedConfig.configStylelint,
76
+ configEslint: normalizedConfig.configEslint
77
+ });
78
+ switch (format) {
79
+ case "sarif":
80
+ return ReportGenerator.generateSarifReportStream(lintResults, {
81
+ toolName: "slds-linter",
82
+ toolVersion: LINTER_CLI_VERSION
83
+ });
84
+ case "csv":
85
+ const csvString = CsvReportGenerator.generateCsvString(lintResults);
86
+ const csvStream = new Readable();
87
+ csvStream.push(csvString);
88
+ csvStream.push(null);
89
+ return csvStream;
90
+ default:
91
+ const errorMessage = `Unsupported format: ${format}`;
92
+ Logger.error(errorMessage);
93
+ throw new Error(errorMessage);
94
+ }
95
+ } catch (error) {
96
+ const errorMessage = `Report generation failed: ${error.message}`;
97
+ Logger.error(errorMessage);
98
+ throw new Error(errorMessage);
99
+ }
100
+ }
101
+ function standardizeLintMessages(results) {
102
+ return results.map((result) => ({
103
+ ...result,
104
+ errors: result.errors.map((entry) => {
105
+ let msgObj;
106
+ try {
107
+ msgObj = JSON.parse(entry.message);
108
+ if (typeof msgObj === "object" && "message" in msgObj) {
109
+ return { ...entry, ...msgObj };
110
+ }
111
+ } catch {
112
+ }
113
+ return { ...entry, message: entry.message };
114
+ }),
115
+ warnings: result.warnings.map((entry) => {
116
+ let msgObj;
117
+ try {
118
+ msgObj = JSON.parse(entry.message);
119
+ if (typeof msgObj === "object" && "message" in msgObj) {
120
+ return { ...entry, ...msgObj };
121
+ }
122
+ } catch {
123
+ }
124
+ return { ...entry, message: entry.message };
125
+ })
126
+ }));
127
+ }
128
+ var init_executor = __esm({
129
+ "src/executor/index.ts"() {
130
+ }
131
+ });
132
+
133
+ // src/executor/__tests__/executor.test.ts
134
+ import { lint as lint2, report as report2 } from "../index.js";
135
+ import { LintRunner as LintRunner2 } from "../../services/lint-runner.js";
136
+ import { FileScanner as FileScanner2 } from "../../services/file-scanner.js";
137
+ import { Readable as Readable2 } from "stream";
138
+ import { jest } from "@jest/globals.js";
139
+ var mockLintResult = {
140
+ filePath: "file1.css",
141
+ errors: [{ line: 1, column: 1, endColumn: 10, message: "Test error", ruleId: "test-rule", severity: 2 }],
142
+ warnings: []
143
+ };
144
+ jest.mock("../../services/lint-runner", () => {
145
+ return {
146
+ LintRunner: {
147
+ runLinting: jest.fn().mockImplementation(() => {
148
+ return Promise.resolve([mockLintResult]);
149
+ })
150
+ }
151
+ };
152
+ });
153
+ jest.mock("../../services/file-scanner", () => {
154
+ return {
155
+ FileScanner: {
156
+ scanFiles: jest.fn().mockImplementation(() => {
157
+ return Promise.resolve([["file1.css"]]);
158
+ })
159
+ }
160
+ };
161
+ });
162
+ jest.mock("fs/promises");
163
+ xdescribe("Executor functions", () => {
164
+ beforeEach(() => {
165
+ jest.clearAllMocks();
166
+ });
167
+ describe("lint", () => {
168
+ it("should scan directory and run linting when no files are provided", async () => {
169
+ const config = {
170
+ directory: "./src",
171
+ fix: true
172
+ };
173
+ const results = await lint2(config);
174
+ expect(FileScanner2.scanFiles).toHaveBeenCalledTimes(2);
175
+ expect(LintRunner2.runLinting).toHaveBeenCalledTimes(2);
176
+ expect(results).toHaveLength(2);
177
+ });
178
+ it("should use provided files and skip scanning when files are provided", async () => {
179
+ const config = {
180
+ files: ["file1.css", "component1.html"]
181
+ };
182
+ await lint2(config);
183
+ expect(FileScanner2.scanFiles).not.toHaveBeenCalled();
184
+ expect(LintRunner2.runLinting).toHaveBeenCalledTimes(2);
185
+ });
186
+ });
187
+ describe("report", () => {
188
+ it("should return a readable stream", async () => {
189
+ const config = {
190
+ directory: "./src",
191
+ format: "sarif"
192
+ };
193
+ const stream = await report2(config);
194
+ expect(stream).toBeInstanceOf(Readable2);
195
+ });
196
+ it("should use lint results to generate a report", async () => {
197
+ const lintMock = jest.spyOn((init_executor(), __toCommonJS(executor_exports)), "lint").mockResolvedValue([mockLintResult]);
198
+ const config = {
199
+ directory: "./src",
200
+ format: "sarif"
201
+ };
202
+ await report2(config);
203
+ expect(lintMock).toHaveBeenCalledWith({
204
+ directory: "./src",
205
+ configStylelint: expect.any(String),
206
+ configEslint: expect.any(String)
207
+ });
208
+ lintMock.mockRestore();
209
+ });
210
+ });
211
+ });
212
+ describe("Executor placeholder tests", () => {
213
+ it("should be implemented in the future", () => {
214
+ expect(true).toBe(true);
215
+ });
216
+ });
@@ -0,0 +1,20 @@
1
+ import { Readable } from 'stream';
2
+ import { LintResult, LintConfig, ReportConfig } from '../types';
3
+ /**
4
+ * Run linting on specified files or directory
5
+ *
6
+ * @param config Linting configuration options
7
+ * @returns Promise resolving to an array of lint results
8
+ * @throws Error if linting fails
9
+ */
10
+ export declare function lint(config: LintConfig): Promise<LintResult[]>;
11
+ /**
12
+ * Generate a report from linting results
13
+ *
14
+ * @param config Report configuration options
15
+ * @param results Optional lint results (if not provided, will run lint)
16
+ * @returns A readable stream containing the report data
17
+ * @throws Error if report generation fails
18
+ */
19
+ export declare function report(config: ReportConfig, results?: LintResult[]): Promise<Readable>;
20
+ export type { LintResult, LintResultEntry, LintConfig, ReportConfig, ExitCode, WorkerResult, SarifResultEntry } from '../types';
@@ -0,0 +1,105 @@
1
+ // src/executor/index.ts
2
+ import { Readable } from "stream";
3
+ import { FileScanner } from "../services/file-scanner.js";
4
+ import { LintRunner } from "../services/lint-runner.js";
5
+ import { StyleFilePatterns, ComponentFilePatterns } from "../services/file-patterns.js";
6
+ import { ReportGenerator, CsvReportGenerator } from "../services/report-generator.js";
7
+ import { LINTER_CLI_VERSION } from "../services/config.resolver.js";
8
+ import { normalizeCliOptions } from "../utils/config-utils.js";
9
+ import { Logger } from "../utils/logger.js";
10
+ async function lint(config) {
11
+ try {
12
+ Logger.debug("Starting linting with Node API");
13
+ const normalizedConfig = normalizeCliOptions(config, {}, true);
14
+ const styleFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
15
+ patterns: StyleFilePatterns,
16
+ batchSize: 100
17
+ });
18
+ const componentFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
19
+ patterns: ComponentFilePatterns,
20
+ batchSize: 100
21
+ });
22
+ const lintOptions = {
23
+ fix: normalizedConfig.fix,
24
+ configPath: normalizedConfig.configStylelint
25
+ };
26
+ const styleResults = await LintRunner.runLinting(styleFiles, "style", {
27
+ ...lintOptions,
28
+ configPath: normalizedConfig.configStylelint
29
+ });
30
+ const componentResults = await LintRunner.runLinting(componentFiles, "component", {
31
+ ...lintOptions,
32
+ configPath: normalizedConfig.configEslint
33
+ });
34
+ const combinedResults = [...styleResults, ...componentResults];
35
+ return standardizeLintMessages(combinedResults);
36
+ } catch (error) {
37
+ const errorMessage = `Linting failed: ${error.message}`;
38
+ Logger.error(errorMessage);
39
+ throw new Error(errorMessage);
40
+ }
41
+ }
42
+ async function report(config, results) {
43
+ try {
44
+ Logger.debug("Starting report generation with Node API");
45
+ const normalizedConfig = normalizeCliOptions(config, {}, true);
46
+ const format = normalizedConfig.format || "sarif";
47
+ const lintResults = results || await lint({
48
+ directory: normalizedConfig.directory,
49
+ configStylelint: normalizedConfig.configStylelint,
50
+ configEslint: normalizedConfig.configEslint
51
+ });
52
+ switch (format) {
53
+ case "sarif":
54
+ return ReportGenerator.generateSarifReportStream(lintResults, {
55
+ toolName: "slds-linter",
56
+ toolVersion: LINTER_CLI_VERSION
57
+ });
58
+ case "csv":
59
+ const csvString = CsvReportGenerator.generateCsvString(lintResults);
60
+ const csvStream = new Readable();
61
+ csvStream.push(csvString);
62
+ csvStream.push(null);
63
+ return csvStream;
64
+ default:
65
+ const errorMessage = `Unsupported format: ${format}`;
66
+ Logger.error(errorMessage);
67
+ throw new Error(errorMessage);
68
+ }
69
+ } catch (error) {
70
+ const errorMessage = `Report generation failed: ${error.message}`;
71
+ Logger.error(errorMessage);
72
+ throw new Error(errorMessage);
73
+ }
74
+ }
75
+ function standardizeLintMessages(results) {
76
+ return results.map((result) => ({
77
+ ...result,
78
+ errors: result.errors.map((entry) => {
79
+ let msgObj;
80
+ try {
81
+ msgObj = JSON.parse(entry.message);
82
+ if (typeof msgObj === "object" && "message" in msgObj) {
83
+ return { ...entry, ...msgObj };
84
+ }
85
+ } catch {
86
+ }
87
+ return { ...entry, message: entry.message };
88
+ }),
89
+ warnings: result.warnings.map((entry) => {
90
+ let msgObj;
91
+ try {
92
+ msgObj = JSON.parse(entry.message);
93
+ if (typeof msgObj === "object" && "message" in msgObj) {
94
+ return { ...entry, ...msgObj };
95
+ }
96
+ } catch {
97
+ }
98
+ return { ...entry, message: entry.message };
99
+ })
100
+ }));
101
+ }
102
+ export {
103
+ lint,
104
+ report
105
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { registerLintCommand } from "./commands/lint.js";
6
+ import { registerReportCommand } from "./commands/report.js";
7
+ import { registerEmitCommand } from "./commands/emit.js";
8
+ import { Logger } from "./utils/logger.js";
9
+ import { validateNodeVersion } from "./utils/nodeVersionUtil.js";
10
+ validateNodeVersion();
11
+ process.on("unhandledRejection", (error) => {
12
+ Logger.error(`Unhandled rejection: ${error}`);
13
+ process.exit(1);
14
+ });
15
+ process.on("uncaughtException", (error) => {
16
+ Logger.error(`Uncaught exception: ${error}`);
17
+ process.exit(1);
18
+ });
19
+ var program = new Command();
20
+ program.name("npx @salesforce-ux/slds-linter@latest").showHelpAfterError();
21
+ function registerVersion() {
22
+ program.description("SLDS Linter CLI tool for linting styles and components").version("0.2.2-alpha-2-alpha.0");
23
+ }
24
+ registerLintCommand(program);
25
+ registerReportCommand(program);
26
+ registerEmitCommand(program);
27
+ registerVersion();
28
+ program.configureHelp({
29
+ subcommandTerm: (cmd) => {
30
+ return cmd.name();
31
+ }
32
+ });
33
+ program.parse(process.argv);
@@ -0,0 +1,47 @@
1
+ // src/services/__tests__/file-scanner.test.ts
2
+ import path from "path";
3
+ import { FileScanner } from "../file-scanner.js";
4
+ import { StyleFilePatterns } from "../file-patterns.js";
5
+ import { mkdir, writeFile, rm } from "fs/promises";
6
+ import { fileURLToPath } from "url";
7
+ var __filename = fileURLToPath(import.meta.url);
8
+ var __dirname = path.dirname(__filename);
9
+ describe("FileScanner", () => {
10
+ const testDir = path.join(__dirname, "fixtures");
11
+ beforeAll(async () => {
12
+ await mkdir(testDir, { recursive: true });
13
+ await writeFile(
14
+ path.join(testDir, "test.css"),
15
+ "body { color: red; }"
16
+ );
17
+ await writeFile(
18
+ path.join(testDir, "test.scss"),
19
+ "$color: red;"
20
+ );
21
+ });
22
+ afterAll(async () => {
23
+ await rm(testDir, { recursive: true });
24
+ });
25
+ it("should scan and batch files correctly", async () => {
26
+ const options = {
27
+ patterns: StyleFilePatterns,
28
+ batchSize: 1
29
+ };
30
+ const batches = await FileScanner.scanFiles(testDir, options);
31
+ expect(batches).toHaveLength(2);
32
+ expect(batches[0]).toHaveLength(1);
33
+ expect(batches[1]).toHaveLength(1);
34
+ expect(batches[0][0]).toMatch(/test\.(css|scss)$/);
35
+ expect(batches[1][0]).toMatch(/test\.(css|scss)$/);
36
+ });
37
+ it("should handle invalid files gracefully", async () => {
38
+ const options = {
39
+ patterns: {
40
+ extensions: ["nonexistent"],
41
+ exclude: []
42
+ }
43
+ };
44
+ const batches = await FileScanner.scanFiles(testDir, options);
45
+ expect(batches).toHaveLength(0);
46
+ });
47
+ });
@@ -0,0 +1,6 @@
1
+ import { Artifact as SarifArtifact } from 'sarif';
2
+ /**
3
+ * Process artifacts and add file content properties
4
+ * @param artifacts Array of artifacts to process
5
+ */
6
+ export declare function processArtifacts(artifacts: SarifArtifact[]): Promise<void>;
@@ -0,0 +1,37 @@
1
+ // src/services/artifact-processor.ts
2
+ import path from "path";
3
+ import fs from "fs/promises";
4
+ import crypto from "crypto";
5
+ var BATCH_SIZE = 10;
6
+ async function processArtifacts(artifacts) {
7
+ const batches = Array.from(
8
+ { length: Math.ceil(artifacts.length / BATCH_SIZE) },
9
+ (_, i) => artifacts.slice(i * BATCH_SIZE, (i + 1) * BATCH_SIZE)
10
+ );
11
+ for (const batch of batches) {
12
+ await Promise.all(
13
+ batch.map(async (artifact) => {
14
+ try {
15
+ artifact.sourceLanguage = "html";
16
+ if (!artifact.location?.uri) {
17
+ console.warn("Warning: Artifact missing location URI");
18
+ return;
19
+ }
20
+ const filePath = path.join(process.cwd(), artifact.location.uri);
21
+ const content = await fs.readFile(filePath, "utf-8");
22
+ artifact.length = content.length;
23
+ const hash = crypto.createHash("sha256");
24
+ hash.update(content);
25
+ artifact.hashes = {
26
+ "sha-256": hash.digest("hex")
27
+ };
28
+ } catch (error) {
29
+ console.warn(`Warning: Could not process artifact ${artifact.location?.uri}: ${error.message}`);
30
+ }
31
+ })
32
+ );
33
+ }
34
+ }
35
+ export {
36
+ processArtifacts
37
+ };