Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/modern-tables-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@css-modules-kit/codegen': minor
---

feat: add `--watch` option
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
"group": "codegen"
}
},
{
"name": "codegen: watch mode (1-basic)",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/examples/1-basic",
"program": "${workspaceFolder}/packages/codegen/bin/cmk.mjs",
"args": ["--watch"],
"console": "integratedTerminal",
"preLaunchTask": "npm: build - packages/codegen",
"presentation": {
"group": "codegen"
}
},
{
"name": "codegen (2-named-exports)",
"type": "node",
Expand Down
31 changes: 30 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Options:
--project, -p The path to its configuration file, or to a folder with a 'tsconfig.json'.
--pretty Enable color and formatting in output to make errors easier to read.
--clean Remove the output directory before generating files. [default: false]
--watch, -w Watch for changes and regenerate files. [default: false]
```

## Configuration
Expand Down
24 changes: 20 additions & 4 deletions packages/codegen/bin/cmk.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
#!/usr/bin/env node
/* eslint-disable n/no-process-exit */

import { createLogger, parseCLIArgs, printHelpText, printVersion, runCMK, shouldBePretty } from '../dist/index.js';
import {
createLogger,
parseCLIArgs,
printHelpText,
printVersion,
runCMK,
runCMKInWatchMode,
shouldBePretty,
} from '../dist/index.js';

const cwd = process.cwd();
let logger = createLogger(cwd, shouldBePretty(undefined));
Expand All @@ -18,9 +26,17 @@ try {
process.exit(0);
}

const success = await runCMK(args, logger);
if (!success) {
process.exit(1);
// Normal mode and watch mode behave differently when errors occur.
// - Normal mode: Outputs errors to the terminal and exits the process with exit code 1.
// - Watch mode: Outputs errors to the terminal but does not terminate the process. Continues watching the file.
if (args.watch) {
const watcher = await runCMKInWatchMode(args, logger);
process.on('SIGINT', () => watcher.close());
} else {
const success = await runCMK(args, logger);
if (!success) {
process.exit(1);
}
}
} catch (e) {
logger.logError(e);
Expand Down
3 changes: 2 additions & 1 deletion packages/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"dist"
],
"dependencies": {
"@css-modules-kit/core": "^0.6.0"
"@css-modules-kit/core": "^0.6.0",
"chokidar": "^4.0.3"
},
"peerDependencies": {
"typescript": "^5.7.3"
Expand Down
6 changes: 6 additions & 0 deletions packages/codegen/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('parseCLIArgs', () => {
project: resolve(cwd),
pretty: undefined,
clean: false,
watch: false,
});
});
it('should parse --help option', () => {
Expand Down Expand Up @@ -41,6 +42,11 @@ describe('parseCLIArgs', () => {
expect(parseCLIArgs(['--clean'], cwd).clean).toBe(true);
expect(parseCLIArgs(['--no-clean'], cwd).clean).toBe(false);
});
it('should parse --watch option', () => {
expect(parseCLIArgs(['--watch'], cwd).watch).toBe(true);
expect(parseCLIArgs(['--no-watch'], cwd).watch).toBe(false);
expect(parseCLIArgs(['-w'], cwd).watch).toBe(true);
});
it('should throw ParseCLIArgsError for invalid options', () => {
expect(() => parseCLIArgs(['--invalid-option'], cwd)).toThrow(ParseCLIArgsError);
});
Expand Down
4 changes: 4 additions & 0 deletions packages/codegen/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Options:
--project, -p The path to its configuration file, or to a folder with a 'tsconfig.json'.
--pretty Enable color and formatting in output to make errors easier to read.
--clean Remove the output directory before generating files. [default: false]
--watch, -w Watch for changes and regenerate files. [default: false]
`;

export function printHelpText(): void {
Expand All @@ -30,6 +31,7 @@ export interface ParsedArgs {
project: string;
pretty: boolean | undefined;
clean: boolean;
watch: boolean;
}

/**
Expand All @@ -46,6 +48,7 @@ export function parseCLIArgs(args: string[], cwd: string): ParsedArgs {
project: { type: 'string', short: 'p', default: '.' },
pretty: { type: 'boolean' },
clean: { type: 'boolean', default: false },
watch: { type: 'boolean', short: 'w', default: false },
},
allowNegative: true,
});
Expand All @@ -55,6 +58,7 @@ export function parseCLIArgs(args: string[], cwd: string): ParsedArgs {
project: resolve(cwd, values.project),
pretty: values.pretty,
clean: values.clean,
watch: values.watch,
};
} catch (cause) {
throw new ParseCLIArgsError(cause);
Expand Down
6 changes: 6 additions & 0 deletions packages/codegen/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ export class ReadCSSModuleFileError extends SystemError {
super('READ_CSS_MODULE_FILE_ERROR', `Failed to read CSS Module file ${fileName}.`, cause);
}
}

export class WatchInitializationError extends SystemError {
constructor(cause: unknown) {
super('WATCH_INITIALIZATION_ERROR', `Failed to initialize file watcher.`, cause);
}
}
2 changes: 1 addition & 1 deletion packages/codegen/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { runCMK } from './runner.js';
export { runCMK, runCMKInWatchMode } from './runner.js';
export { type Logger, createLogger } from './logger/logger.js';
export { WriteDtsFileError, ReadCSSModuleFileError } from './error.js';
export { parseCLIArgs, printHelpText, printVersion } from './cli.js';
Expand Down
18 changes: 14 additions & 4 deletions packages/codegen/src/logger/formatter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dedent from 'dedent';
import ts from 'typescript';
import { describe, expect, it } from 'vitest';
import { formatDiagnostics } from './formatter';
import { describe, expect, test } from 'vitest';
import { formatDiagnostics, formatTime } from './formatter';

describe('formatDiagnostics', () => {
const file = ts.createSourceFile(
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('formatDiagnostics', () => {
},
];

it('formats diagnostics with color and context when pretty is true', () => {
test('formats diagnostics with color and context when pretty is true', () => {
const result = formatDiagnostics(diagnostics, host, true);
expect(result).toMatchInlineSnapshot(`
"test.module.css:1:2 - error: \`a_1\` is not allowed
Expand All @@ -55,7 +55,7 @@ describe('formatDiagnostics', () => {
`);
});

it('formats diagnostics without color and context when pretty is false', () => {
test('formats diagnostics without color and context when pretty is false', () => {
const result = formatDiagnostics(diagnostics, host, false);
expect(result).toMatchInlineSnapshot(`
"test.module.css(1,2): error: \`a_1\` is not allowed
Expand All @@ -66,3 +66,13 @@ describe('formatDiagnostics', () => {
`);
});
});

test('formatTime', () => {
const date = new Date('2023-01-01T00:00:00Z');
expect(formatTime(date, true)).toMatchInlineSnapshot(`
"[12:00:00 AM]"
`);
expect(formatTime(date, false)).toMatchInlineSnapshot(`
"[12:00:00 AM]"
`);
});
12 changes: 12 additions & 0 deletions packages/codegen/src/logger/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import ts from 'typescript';

const GRAY = '\u001b[90m';
const RESET = '\u001b[0m';

export function formatDiagnostics(
diagnostics: ts.Diagnostic[],
host: ts.FormatDiagnosticsHost,
Expand All @@ -12,3 +15,12 @@ export function formatDiagnostics(
}
return result;
}

export function formatTime(date: Date, pretty: boolean): string {
const text = date.toLocaleTimeString('en-US', { timeZone: 'UTC' });
if (pretty) {
return `[${GRAY}${text}${RESET}]`;
} else {
return `[${text}]`;
}
}
6 changes: 6 additions & 0 deletions packages/codegen/src/logger/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { createLogger } from './logger.js';
const stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
const stderrWriteSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);

const date = new Date('2023-01-01T00:00:00Z');
vi.useRealTimers();
vi.setSystemTime(date);

const cwd = '/app';

describe('createLogger', () => {
Expand Down Expand Up @@ -58,5 +62,7 @@ describe('createLogger', () => {
const logger = createLogger(cwd, false);
logger.logMessage('message');
expect(stdoutWriteSpy).toHaveBeenCalledWith('message\n');
logger.logMessage('message with time', { time: true });
expect(stdoutWriteSpy).toHaveBeenCalledWith('[12:00:00 AM] message with time\n');
});
});
13 changes: 9 additions & 4 deletions packages/codegen/src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { inspect } from 'node:util';
import type { DiagnosticSourceFile } from '@css-modules-kit/core';
import { convertDiagnostic, convertSystemError, type Diagnostic, SystemError } from '@css-modules-kit/core';
import ts from 'typescript';
import { formatDiagnostics } from './formatter.js';
import { formatDiagnostics, formatTime } from './formatter.js';

export interface Logger {
logDiagnostics(diagnostics: Diagnostic[]): void;
logError(error: unknown): void;
logMessage(message: string): void;
logMessage(message: string, options?: { time?: boolean }): void;
clearScreen(): void;
}

export function createLogger(cwd: string, pretty: boolean): Logger {
Expand Down Expand Up @@ -42,8 +43,12 @@ export function createLogger(cwd: string, pretty: boolean): Logger {
process.stderr.write(`${inspect(error, { colors: pretty })}\n`);
}
},
logMessage(message: string): void {
process.stdout.write(`${message}\n`);
logMessage(message: string, options?: { time?: boolean }): void {
const header = options?.time ? `${formatTime(new Date(), pretty)} ` : '';
process.stdout.write(`${header}${message}\n`);
},
clearScreen(): void {
process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
},
};
}
Loading
Loading