happy-css-modules 1.0.1 → 2.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/README.md +2 -1
- package/bin/hcm.js +1 -0
- package/dist/cli.js +12 -5
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +9 -3
- package/dist/cli.test.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/emitter/dts.d.ts +2 -1
- package/dist/emitter/dts.js +10 -3
- package/dist/emitter/dts.js.map +1 -1
- package/dist/emitter/dts.test.js +4 -1
- package/dist/emitter/dts.test.js.map +1 -1
- package/dist/emitter/index.d.ts +12 -3
- package/dist/emitter/index.js +7 -5
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/source-map.d.ts +2 -1
- package/dist/emitter/source-map.js +3 -2
- package/dist/emitter/source-map.js.map +1 -1
- package/dist/emitter/source-map.test.js +4 -1
- package/dist/emitter/source-map.test.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +1 -1
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/library/source-map/index.d.ts +1 -1
- package/dist/locator/index.d.ts +4 -4
- package/dist/locator/postcss.d.ts +3 -3
- package/dist/logger.d.ts +9 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/regression-test/issue-168.test.js +1 -1
- package/dist/regression-test/issue-168.test.js.map +1 -1
- package/dist/resolver/index.d.ts +3 -3
- package/dist/resolver/webpack-resolver.d.ts +1 -1
- package/dist/runner.d.ts +15 -6
- package/dist/runner.js +43 -53
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +57 -26
- package/dist/runner.test.js.map +1 -1
- package/dist/test-util/jest/resolver.d.cts +1 -15
- package/dist/test-util/tsserver.d.ts +1 -1
- package/dist/test-util/tsserver.js.map +1 -1
- package/dist/test-util/util.d.ts +3 -3
- package/dist/transformer/index.d.ts +4 -4
- package/dist/transformer/index.js +2 -2
- package/dist/transformer/index.js.map +1 -1
- package/dist/transformer/postcss-transformer.d.ts +1 -1
- package/dist/transformer/postcss-transformer.js +2 -2
- package/dist/transformer/postcss-transformer.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.test.ts +9 -3
- package/src/cli.ts +12 -5
- package/src/config.ts +1 -0
- package/src/emitter/dts.test.ts +4 -1
- package/src/emitter/dts.ts +9 -3
- package/src/emitter/index.ts +16 -4
- package/src/emitter/source-map.test.ts +4 -1
- package/src/emitter/source-map.ts +3 -2
- package/src/integration-test/go-to-definition.test.ts +3 -2
- package/src/logger.ts +31 -0
- package/src/regression-test/issue-168.test.ts +1 -1
- package/src/runner.test.ts +93 -29
- package/src/runner.ts +63 -48
- package/src/test-util/tsserver.ts +14 -13
- package/src/transformer/index.ts +2 -2
- package/src/transformer/postcss-transformer.ts +2 -2
package/src/emitter/dts.test.ts
CHANGED
|
@@ -9,7 +9,10 @@ const locator = new Locator();
|
|
|
9
9
|
const isExternalFile = () => false;
|
|
10
10
|
|
|
11
11
|
test('getDtsFilePath', () => {
|
|
12
|
-
expect(getDtsFilePath('/app/src/dir/1.css')).toBe('/app/src/dir/1.css.d.ts');
|
|
12
|
+
expect(getDtsFilePath('/app/src/dir/1.css', false)).toBe('/app/src/dir/1.css.d.ts');
|
|
13
|
+
expect(getDtsFilePath('/app/src/dir/1.scss', false)).toBe('/app/src/dir/1.scss.d.ts');
|
|
14
|
+
expect(getDtsFilePath('/app/src/dir/1.css', true)).toBe('/app/src/dir/1.d.css.ts');
|
|
15
|
+
expect(getDtsFilePath('/app/src/dir/1.scss', true)).toBe('/app/src/dir/1.d.scss.ts');
|
|
13
16
|
});
|
|
14
17
|
|
|
15
18
|
describe('generateDtsContentWithSourceMap', () => {
|
package/src/emitter/dts.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EOL } from 'os';
|
|
2
|
-
import { basename } from 'path';
|
|
2
|
+
import { basename, parse, join } from 'path';
|
|
3
3
|
import camelcase from 'camelcase';
|
|
4
4
|
import { SourceNode, type CodeWithSourceMap } from '../library/source-map/index.js';
|
|
5
5
|
import { type Token } from '../locator/index.js';
|
|
@@ -9,10 +9,16 @@ import { getRelativePath, type DtsFormatOptions } from './index.js';
|
|
|
9
9
|
/**
|
|
10
10
|
* Get .d.ts file path.
|
|
11
11
|
* @param filePath The path to the source file (i.e. `/dir/foo.css`). It is absolute.
|
|
12
|
+
* @param arbitraryExtensions Generate `.d.css.ts` instead of `.css.d.ts`.
|
|
12
13
|
* @returns The path to the .d.ts file. It is absolute.
|
|
13
14
|
*/
|
|
14
|
-
export function getDtsFilePath(filePath: string): string {
|
|
15
|
-
|
|
15
|
+
export function getDtsFilePath(filePath: string, arbitraryExtensions: boolean): string {
|
|
16
|
+
if (arbitraryExtensions) {
|
|
17
|
+
const { dir, name, ext } = parse(filePath);
|
|
18
|
+
return join(dir, name + '.d' + ext + '.ts');
|
|
19
|
+
} else {
|
|
20
|
+
return filePath + '.d.ts';
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
function dashesCamelCase(str: string): string {
|
package/src/emitter/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dirname, isAbsolute, relative } from 'path';
|
|
2
|
+
import { DEFAULT_ARBITRARY_EXTENSIONS } from '../config.js';
|
|
2
3
|
import { type Token } from '../locator/index.js';
|
|
3
4
|
import { type LocalsConvention } from '../runner.js';
|
|
4
5
|
import { exists } from '../util.js';
|
|
@@ -20,7 +21,16 @@ export function isSubDirectoryFile(fromDirectory: string, toFilePath: string): b
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export type DtsFormatOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Style of exported class names.
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
23
28
|
localsConvention?: LocalsConvention;
|
|
29
|
+
/**
|
|
30
|
+
* Generate `.d.css.ts` instead of `.css.d.ts`.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
arbitraryExtensions?: boolean | undefined;
|
|
24
34
|
};
|
|
25
35
|
|
|
26
36
|
/** The options for emitter. */
|
|
@@ -44,8 +54,9 @@ export async function emitGeneratedFiles({
|
|
|
44
54
|
dtsFormatOptions,
|
|
45
55
|
isExternalFile,
|
|
46
56
|
}: EmitterOptions): Promise<void> {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
57
|
+
const arbitraryExtensions = dtsFormatOptions?.arbitraryExtensions ?? DEFAULT_ARBITRARY_EXTENSIONS;
|
|
58
|
+
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions);
|
|
59
|
+
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions);
|
|
49
60
|
const { dtsContent, sourceMap } = generateDtsContentWithSourceMap(
|
|
50
61
|
filePath,
|
|
51
62
|
dtsFilePath,
|
|
@@ -72,9 +83,10 @@ export async function emitGeneratedFiles({
|
|
|
72
83
|
export async function isGeneratedFilesExist(
|
|
73
84
|
filePath: string,
|
|
74
85
|
emitDeclarationMap: boolean | undefined,
|
|
86
|
+
arbitraryExtensions: boolean,
|
|
75
87
|
): Promise<boolean> {
|
|
76
|
-
const dtsFilePath = getDtsFilePath(filePath);
|
|
77
|
-
const sourceMapFilePath = getSourceMapFilePath(filePath);
|
|
88
|
+
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions);
|
|
89
|
+
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions);
|
|
78
90
|
if (emitDeclarationMap && !(await exists(sourceMapFilePath))) {
|
|
79
91
|
return false;
|
|
80
92
|
}
|
|
@@ -2,7 +2,10 @@ import { EOL } from 'os';
|
|
|
2
2
|
import { getSourceMapFilePath, generateSourceMappingURLComment } from './source-map.js';
|
|
3
3
|
|
|
4
4
|
test('getSourceMapFilePath', () => {
|
|
5
|
-
expect(getSourceMapFilePath('/app/src/dir/1.css')).toBe('/app/src/dir/1.css.d.ts.map');
|
|
5
|
+
expect(getSourceMapFilePath('/app/src/dir/1.css', false)).toBe('/app/src/dir/1.css.d.ts.map');
|
|
6
|
+
expect(getSourceMapFilePath('/app/src/dir/1.scss', false)).toBe('/app/src/dir/1.scss.d.ts.map');
|
|
7
|
+
expect(getSourceMapFilePath('/app/src/dir/1.css', true)).toBe('/app/src/dir/1.d.css.ts.map');
|
|
8
|
+
expect(getSourceMapFilePath('/app/src/dir/1.scss', true)).toBe('/app/src/dir/1.d.scss.ts.map');
|
|
6
9
|
});
|
|
7
10
|
|
|
8
11
|
test('generateSourceMappingURLComment', () => {
|
|
@@ -5,10 +5,11 @@ import { getRelativePath } from './index.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Get .d.ts.map file path.
|
|
7
7
|
* @param filePath The path to the source file (i.e. `foo.css`). It is absolute.
|
|
8
|
+
* @param arbitraryExtensions Generate `.d.css.ts` instead of `.css.d.ts`.
|
|
8
9
|
* @returns The path to the .d.ts.map file. It is absolute.
|
|
9
10
|
*/
|
|
10
|
-
export function getSourceMapFilePath(filePath: string): string {
|
|
11
|
-
return getDtsFilePath(filePath) + '.map';
|
|
11
|
+
export function getSourceMapFilePath(filePath: string, arbitraryExtensions: boolean): string {
|
|
12
|
+
return getDtsFilePath(filePath, arbitraryExtensions) + '.map';
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function generateSourceMappingURLComment(dtsFilePath: string, sourceMapFilePath: string): string {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
+
import type { RunnerOptions } from '../runner.js';
|
|
2
3
|
import { run } from '../runner.js';
|
|
3
4
|
import { createTSServer } from '../test-util/tsserver.js';
|
|
4
5
|
import { createFixtures, getFixturePath } from '../test-util/util.js';
|
|
5
6
|
|
|
6
7
|
const server = await createTSServer();
|
|
7
8
|
|
|
8
|
-
const defaultOptions = {
|
|
9
|
+
const defaultOptions: RunnerOptions = {
|
|
9
10
|
pattern: 'test/**/*.{css,scss}',
|
|
10
|
-
|
|
11
|
+
logLevel: 'silent',
|
|
11
12
|
declarationMap: true,
|
|
12
13
|
cwd: getFixturePath('/'),
|
|
13
14
|
cache: false,
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
type LogLevelLabel = 'debug' | 'info' | 'silent';
|
|
3
|
+
type LogLevel = 2 | 1 | 0;
|
|
4
|
+
const LOG_LEVEL: Record<LogLevelLabel, LogLevel> = {
|
|
5
|
+
debug: 2,
|
|
6
|
+
info: 1,
|
|
7
|
+
silent: 0,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class Logger {
|
|
11
|
+
private logLevel: LogLevel;
|
|
12
|
+
constructor(logLevelLabel: LogLevelLabel) {
|
|
13
|
+
this.logLevel = LOG_LEVEL[logLevelLabel];
|
|
14
|
+
}
|
|
15
|
+
debug(message: unknown) {
|
|
16
|
+
if (this.logLevel >= LOG_LEVEL['debug']) {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.log('[debug]', message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
info(message: unknown) {
|
|
22
|
+
if (this.logLevel >= LOG_LEVEL['info']) {
|
|
23
|
+
// eslint-disable-next-line no-console
|
|
24
|
+
console.log(chalk.blue('[info]'), message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
error(message: unknown) {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.error(chalk.red('[error]'), message);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/runner.test.ts
CHANGED
|
@@ -5,9 +5,8 @@ import { dirname, join, resolve } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import * as fileCacheNpm from '@file-cache/npm';
|
|
7
7
|
import { jest } from '@jest/globals';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
8
|
import dedent from 'dedent';
|
|
10
|
-
import type { Watcher } from './runner.js';
|
|
9
|
+
import type { RunnerOptions, Watcher } from './runner.js';
|
|
11
10
|
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
|
|
12
11
|
|
|
13
12
|
const require = createRequire(import.meta.url);
|
|
@@ -24,10 +23,10 @@ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
24
23
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
25
24
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
26
25
|
|
|
27
|
-
const defaultOptions = {
|
|
26
|
+
const defaultOptions: RunnerOptions = {
|
|
28
27
|
pattern: 'test/**/*.{css,scss}',
|
|
29
28
|
declarationMap: true,
|
|
30
|
-
|
|
29
|
+
logLevel: 'silent',
|
|
31
30
|
cwd: getFixturePath('/'),
|
|
32
31
|
cache: false,
|
|
33
32
|
};
|
|
@@ -60,47 +59,119 @@ test('generates .d.ts and .d.ts.map', async () => {
|
|
|
60
59
|
expect(await readFile(getFixturePath('/test/2.css.d.ts.map'), 'utf8')).toMatchSnapshot();
|
|
61
60
|
});
|
|
62
61
|
|
|
63
|
-
test('uses cache', async () => {
|
|
62
|
+
test('uses cache in non-watch mode', async () => {
|
|
64
63
|
createFixtures({
|
|
65
64
|
'/test/1.css': '.a {}',
|
|
66
65
|
});
|
|
67
|
-
await run({ ...defaultOptions, declarationMap: true,
|
|
68
|
-
expect(consoleLogSpy).toBeCalledTimes(
|
|
69
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.stringContaining('
|
|
66
|
+
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
67
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
68
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
69
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
70
70
|
consoleLogSpy.mockClear();
|
|
71
71
|
|
|
72
72
|
// Skip generation
|
|
73
|
-
await run({ ...defaultOptions, declarationMap: true,
|
|
74
|
-
expect(consoleLogSpy).toBeCalledTimes(
|
|
75
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.stringContaining('
|
|
73
|
+
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
74
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
75
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
76
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('skipped'));
|
|
76
77
|
consoleLogSpy.mockClear();
|
|
77
78
|
|
|
78
79
|
// Generates if generated files are missing
|
|
79
80
|
await rm(getFixturePath('/test/1.css.d.ts'));
|
|
80
|
-
await run({ ...defaultOptions, declarationMap: true,
|
|
81
|
-
expect(consoleLogSpy).toBeCalledTimes(
|
|
82
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.stringContaining('
|
|
81
|
+
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
82
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
83
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
84
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
83
85
|
consoleLogSpy.mockClear();
|
|
84
86
|
|
|
85
87
|
// Generates if options are changed
|
|
86
|
-
await run({ ...defaultOptions, declarationMap: false,
|
|
88
|
+
await run({ ...defaultOptions, declarationMap: false, logLevel: 'debug', cache: true });
|
|
89
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
90
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
91
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
92
|
+
consoleLogSpy.mockClear();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('uses cache in watch mode', async () => {
|
|
96
|
+
createFixtures({
|
|
97
|
+
'/test/1.css': '.a-1 {}',
|
|
98
|
+
'/test/2.css': '.b-1 {}',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// At first, process all files
|
|
102
|
+
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
|
|
103
|
+
await waitForAsyncTask(1000); // Wait until the watcher is ready
|
|
104
|
+
expect(consoleLogSpy).toBeCalledTimes(3);
|
|
105
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
106
|
+
1,
|
|
107
|
+
expect.anything(),
|
|
108
|
+
expect.stringContaining('Watch test/**/*.{css,scss}...'),
|
|
109
|
+
);
|
|
110
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
111
|
+
2,
|
|
112
|
+
expect.anything(),
|
|
113
|
+
expect.stringContaining('test/1.css (generated)'),
|
|
114
|
+
);
|
|
115
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
116
|
+
3,
|
|
117
|
+
expect.anything(),
|
|
118
|
+
expect.stringContaining('test/2.css (generated)'),
|
|
119
|
+
);
|
|
120
|
+
consoleLogSpy.mockClear();
|
|
121
|
+
|
|
122
|
+
// Updating 1.css, it will only be processed
|
|
123
|
+
await writeFile(getFixturePath('/test/1.css'), '.a-2 {}');
|
|
124
|
+
await waitForAsyncTask(500); // Wait until the file is written
|
|
87
125
|
expect(consoleLogSpy).toBeCalledTimes(1);
|
|
88
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
126
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
127
|
+
1,
|
|
128
|
+
expect.anything(),
|
|
129
|
+
expect.stringContaining('test/1.css (generated)'),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Close the watcher
|
|
133
|
+
await watcher.close();
|
|
89
134
|
consoleLogSpy.mockClear();
|
|
135
|
+
|
|
136
|
+
// Update 1.css
|
|
137
|
+
await writeFile(getFixturePath('/test/1.css'), '.a-1 {}');
|
|
138
|
+
await waitForAsyncTask(500); // Wait until the file is written
|
|
139
|
+
|
|
140
|
+
// The updated 1.css will be processed, and the non-updated 2.css will be skipped.
|
|
141
|
+
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
|
|
142
|
+
await waitForAsyncTask(1000); // Wait until the watcher is ready
|
|
143
|
+
expect(consoleLogSpy).toBeCalledTimes(3);
|
|
144
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
145
|
+
1,
|
|
146
|
+
expect.anything(),
|
|
147
|
+
expect.stringContaining('Watch test/**/*.{css,scss}...'),
|
|
148
|
+
);
|
|
149
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
150
|
+
2,
|
|
151
|
+
expect.anything(),
|
|
152
|
+
expect.stringContaining('test/1.css (generated)'),
|
|
153
|
+
);
|
|
154
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(3, expect.anything(), expect.stringContaining('test/2.css (skipped)'));
|
|
90
155
|
});
|
|
91
156
|
|
|
92
157
|
test('outputs logs', async () => {
|
|
93
158
|
createFixtures({
|
|
94
159
|
'/test/1.css': '.a {}',
|
|
95
160
|
});
|
|
96
|
-
await run({ ...defaultOptions,
|
|
97
|
-
expect(consoleLogSpy).toBeCalledTimes(
|
|
98
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(1,
|
|
161
|
+
await run({ ...defaultOptions, logLevel: 'debug', cache: true });
|
|
162
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
163
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
164
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
165
|
+
2,
|
|
166
|
+
expect.anything(),
|
|
167
|
+
expect.stringContaining('test/1.css (generated)'),
|
|
168
|
+
);
|
|
99
169
|
consoleLogSpy.mockClear();
|
|
100
170
|
|
|
101
|
-
await run({ ...defaultOptions,
|
|
102
|
-
expect(consoleLogSpy).toBeCalledTimes(
|
|
103
|
-
expect(consoleLogSpy).toHaveBeenNthCalledWith(1,
|
|
171
|
+
await run({ ...defaultOptions, logLevel: 'debug', cache: true });
|
|
172
|
+
expect(consoleLogSpy).toBeCalledTimes(2);
|
|
173
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
174
|
+
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('test/1.css (skipped)'));
|
|
104
175
|
});
|
|
105
176
|
|
|
106
177
|
test.todo('changes dts format with localsConvention options');
|
|
@@ -156,13 +227,6 @@ test('returns an error if the file fails to process in non-watch mode', async ()
|
|
|
156
227
|
expect(error.errors[0]).toMatchInlineSnapshot(`<fixtures>/test/2.css:1:1: Unknown word`);
|
|
157
228
|
expect(error.errors[1]).toMatchInlineSnapshot(`<fixtures>/test/3.css:1:1: Unknown word`);
|
|
158
229
|
|
|
159
|
-
// The error is logged to console.error.
|
|
160
|
-
expect(consoleErrorSpy).toHaveBeenCalledTimes(2);
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
162
|
-
expect(consoleErrorSpy).toHaveBeenNthCalledWith(1, chalk.red('[Error] ' + error.errors[0]!.stack));
|
|
163
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
164
|
-
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, chalk.red('[Error] ' + error.errors[1].stack));
|
|
165
|
-
|
|
166
230
|
// The valid files are emitted.
|
|
167
231
|
expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
|
|
168
232
|
expect(await exists(getFixturePath('/test/1.css.d.ts.map'))).toBe(true);
|
package/src/runner.ts
CHANGED
|
@@ -7,8 +7,10 @@ import AwaitLock from 'await-lock';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import * as chokidar from 'chokidar';
|
|
9
9
|
import _glob from 'glob';
|
|
10
|
+
import { DEFAULT_ARBITRARY_EXTENSIONS } from './config.js';
|
|
10
11
|
import { isGeneratedFilesExist, emitGeneratedFiles } from './emitter/index.js';
|
|
11
12
|
import { Locator } from './locator/index.js';
|
|
13
|
+
import { Logger } from './logger.js';
|
|
12
14
|
import type { Resolver } from './resolver/index.js';
|
|
13
15
|
import { createDefaultResolver } from './resolver/index.js';
|
|
14
16
|
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
@@ -25,6 +27,10 @@ export type LocalsConvention = 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashe
|
|
|
25
27
|
export interface RunnerOptions {
|
|
26
28
|
pattern: string;
|
|
27
29
|
watch?: boolean | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Style of exported class names.
|
|
32
|
+
* @default undefined
|
|
33
|
+
*/
|
|
28
34
|
localsConvention?: LocalsConvention | undefined;
|
|
29
35
|
declarationMap?: boolean | undefined;
|
|
30
36
|
transformer?: Transformer | undefined;
|
|
@@ -54,6 +60,11 @@ export interface RunnerOptions {
|
|
|
54
60
|
* @example '/home/user/repository/src'
|
|
55
61
|
*/
|
|
56
62
|
postcssConfig?: string | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Generate `.d.css.ts` instead of `.css.d.ts`.
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
arbitraryExtensions?: boolean | undefined;
|
|
57
68
|
/**
|
|
58
69
|
* Only generate .d.ts and .d.ts.map for changed files.
|
|
59
70
|
* @default true
|
|
@@ -65,10 +76,10 @@ export interface RunnerOptions {
|
|
|
65
76
|
*/
|
|
66
77
|
cacheStrategy?: 'content' | 'metadata' | undefined;
|
|
67
78
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @default
|
|
79
|
+
* What level of logs to report.
|
|
80
|
+
* @default 'info'
|
|
70
81
|
*/
|
|
71
|
-
|
|
82
|
+
logLevel?: 'debug' | 'info' | 'silent' | undefined;
|
|
72
83
|
/** Working directory path. */
|
|
73
84
|
cwd?: string | undefined;
|
|
74
85
|
}
|
|
@@ -84,9 +95,9 @@ export async function run(options: OverrideProp<RunnerOptions, 'watch', true>):
|
|
|
84
95
|
export async function run(options: RunnerOptions): Promise<void>;
|
|
85
96
|
export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
86
97
|
const lock = new AwaitLock.default();
|
|
98
|
+
const logger = new Logger(options.logLevel ?? 'info');
|
|
87
99
|
|
|
88
100
|
const cwd = options.cwd ?? process.cwd();
|
|
89
|
-
const silent = options.silent ?? false;
|
|
90
101
|
const resolver =
|
|
91
102
|
options.resolver ??
|
|
92
103
|
createDefaultResolver({
|
|
@@ -115,9 +126,28 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
115
126
|
};
|
|
116
127
|
|
|
117
128
|
async function processFile(filePath: string) {
|
|
129
|
+
async function isChangedFile(filePath: string) {
|
|
130
|
+
const result = await cache.getAndUpdateCache(filePath);
|
|
131
|
+
if (result.error) throw result.error;
|
|
132
|
+
return result.changed;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Locator#load cannot be called concurrently. Therefore, it takes a lock and waits.
|
|
136
|
+
await lock.acquireAsync();
|
|
137
|
+
|
|
118
138
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
const _isGeneratedFilesExist = await isGeneratedFilesExist(
|
|
140
|
+
filePath,
|
|
141
|
+
options.declarationMap,
|
|
142
|
+
options.arbitraryExtensions ?? DEFAULT_ARBITRARY_EXTENSIONS,
|
|
143
|
+
);
|
|
144
|
+
const _isChangedFile = await isChangedFile(filePath);
|
|
145
|
+
// Generate .d.ts and .d.ts.map only when the file has been updated.
|
|
146
|
+
// However, if .d.ts or .d.ts.map has not yet been generated, always generate.
|
|
147
|
+
if (_isGeneratedFilesExist && !_isChangedFile) {
|
|
148
|
+
logger.debug(chalk.gray(`${relative(cwd, filePath)} (skipped)`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
121
151
|
|
|
122
152
|
const result = await locator.load(filePath);
|
|
123
153
|
await emitGeneratedFiles({
|
|
@@ -126,32 +156,40 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
126
156
|
emitDeclarationMap: options.declarationMap,
|
|
127
157
|
dtsFormatOptions: {
|
|
128
158
|
localsConvention: options.localsConvention,
|
|
159
|
+
arbitraryExtensions: options.arbitraryExtensions,
|
|
129
160
|
},
|
|
130
161
|
isExternalFile,
|
|
131
162
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
136
|
-
console.error(chalk.red('[Error] ' + error.stack));
|
|
137
|
-
} else {
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
139
|
-
console.error(chalk.red('[Error] ' + error));
|
|
140
|
-
}
|
|
141
|
-
throw error;
|
|
163
|
+
logger.info(chalk.green(`${relative(cwd, filePath)} (generated)`));
|
|
164
|
+
|
|
165
|
+
await cache.reconcile(); // Update cache for the file
|
|
142
166
|
} finally {
|
|
143
167
|
lock.release();
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
|
|
147
|
-
async function
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
async function processAllFiles() {
|
|
172
|
+
const filePaths = (await glob(options.pattern, { dot: true, cwd }))
|
|
173
|
+
// convert relative path to absolute path
|
|
174
|
+
.map((file) => resolve(cwd, file));
|
|
175
|
+
|
|
176
|
+
const errors: unknown[] = [];
|
|
177
|
+
for (const filePath of filePaths) {
|
|
178
|
+
await processFile(filePath).catch((e) => errors.push(e));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (errors.length > 0) {
|
|
182
|
+
throw new AggregateError(errors, 'Failed to process files');
|
|
183
|
+
}
|
|
151
184
|
}
|
|
152
185
|
|
|
153
|
-
if (options.watch) {
|
|
154
|
-
|
|
186
|
+
if (!options.watch) {
|
|
187
|
+
logger.info('Generate .d.ts for ' + options.pattern + '...');
|
|
188
|
+
await processAllFiles();
|
|
189
|
+
// Write cache state to file for persistence
|
|
190
|
+
} else {
|
|
191
|
+
// First, watch files.
|
|
192
|
+
logger.info('Watch ' + options.pattern + '...');
|
|
155
193
|
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
|
|
156
194
|
watcher.on('all', (eventName, relativeFilePath) => {
|
|
157
195
|
const filePath = resolve(cwd, relativeFilePath);
|
|
@@ -162,35 +200,12 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
162
200
|
|
|
163
201
|
if (eventName !== 'add' && eventName !== 'change') return;
|
|
164
202
|
|
|
165
|
-
processFile(filePath).catch(() => {
|
|
203
|
+
processFile(filePath).catch((e) => {
|
|
204
|
+
logger.error(e);
|
|
166
205
|
// TODO: Emit a error by `Watcher#onerror`
|
|
167
206
|
});
|
|
168
207
|
});
|
|
169
|
-
return { close: async () => watcher.close() };
|
|
170
|
-
} else {
|
|
171
|
-
const filePaths = (await glob(options.pattern, { dot: true, cwd }))
|
|
172
|
-
// convert relative path to absolute path
|
|
173
|
-
.map((file) => resolve(cwd, file));
|
|
174
208
|
|
|
175
|
-
|
|
176
|
-
for (const filePath of filePaths) {
|
|
177
|
-
try {
|
|
178
|
-
const _isGeneratedFilesExist = await isGeneratedFilesExist(filePath, options.declarationMap);
|
|
179
|
-
const _isChangedFile = await isChangedFile(filePath);
|
|
180
|
-
// Generate .d.ts and .d.ts.map only when the file has been updated.
|
|
181
|
-
// However, if .d.ts or .d.ts.map has not yet been generated, always generate.
|
|
182
|
-
if (!_isGeneratedFilesExist || _isChangedFile) {
|
|
183
|
-
await processFile(filePath);
|
|
184
|
-
} else {
|
|
185
|
-
if (!silent) console.log(chalk.gray(`${relative(cwd, filePath)} (skipped)`));
|
|
186
|
-
}
|
|
187
|
-
} catch (e: unknown) {
|
|
188
|
-
errors.push(e);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (errors.length > 0) throw new AggregateError(errors, 'Failed to process files');
|
|
209
|
+
return { close: async () => watcher.close() };
|
|
192
210
|
}
|
|
193
|
-
|
|
194
|
-
// Write cache state to file for persistence
|
|
195
|
-
await cache.reconcile();
|
|
196
211
|
}
|
|
@@ -7,9 +7,8 @@ import serverHarness from '@typescript/server-harness';
|
|
|
7
7
|
import _glob from 'glob';
|
|
8
8
|
import { resolve } from 'import-meta-resolve';
|
|
9
9
|
import lineColumn from 'line-column';
|
|
10
|
-
import type {
|
|
10
|
+
import type { server } from 'typescript/lib/tsserverlibrary.js';
|
|
11
11
|
import { getFixturePath } from './util.js';
|
|
12
|
-
|
|
13
12
|
const glob = promisify(_glob);
|
|
14
13
|
|
|
15
14
|
async function writeFile(path: string, content: string): Promise<void> {
|
|
@@ -68,7 +67,7 @@ export async function createTSServer() {
|
|
|
68
67
|
const results: { identifier: string; definitions: Definition[] }[] = [];
|
|
69
68
|
|
|
70
69
|
for (let i = 0; i < identifiers.length; i++) {
|
|
71
|
-
const response: DefinitionResponse = await server.message({
|
|
70
|
+
const response: server.protocol.DefinitionResponse = await server.message({
|
|
72
71
|
seq: 0,
|
|
73
72
|
type: 'request',
|
|
74
73
|
command: 'definition',
|
|
@@ -77,7 +76,7 @@ export async function createTSServer() {
|
|
|
77
76
|
line: i + 2, // line, 1-based
|
|
78
77
|
offset: 8, // column, 1-based
|
|
79
78
|
},
|
|
80
|
-
} as DefinitionRequest);
|
|
79
|
+
} as server.protocol.DefinitionRequest);
|
|
81
80
|
const definitions: Definition[] = response.body!.map((definition) => {
|
|
82
81
|
const { file, start, end } = definition;
|
|
83
82
|
const fileContent = readFileSync(file, 'utf-8');
|
|
@@ -100,7 +99,7 @@ export async function createTSServer() {
|
|
|
100
99
|
|
|
101
100
|
await this.refreshCache();
|
|
102
101
|
|
|
103
|
-
const response: DefinitionResponse = await server.message({
|
|
102
|
+
const response: server.protocol.DefinitionResponse = await server.message({
|
|
104
103
|
seq: 0,
|
|
105
104
|
type: 'request',
|
|
106
105
|
command: 'definition',
|
|
@@ -109,7 +108,7 @@ export async function createTSServer() {
|
|
|
109
108
|
line: 1, // line, 1-based
|
|
110
109
|
offset: 20, // column, 1-based
|
|
111
110
|
},
|
|
112
|
-
} as DefinitionRequest);
|
|
111
|
+
} as server.protocol.DefinitionRequest);
|
|
113
112
|
const definitions: Definition[] = response.body!.map((definition) => {
|
|
114
113
|
const { file, start, end } = definition;
|
|
115
114
|
const fileContent = readFileSync(file, 'utf-8');
|
|
@@ -127,12 +126,14 @@ export async function createTSServer() {
|
|
|
127
126
|
|
|
128
127
|
const fixtureFilePaths = await glob(getFixturePath('/**/*.ts'), { dot: true });
|
|
129
128
|
// latest contents
|
|
130
|
-
const openFiles: UpdateOpenRequest['arguments']['openFiles'] = fixtureFilePaths.map(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
const openFiles: server.protocol.UpdateOpenRequest['arguments']['openFiles'] = fixtureFilePaths.map(
|
|
130
|
+
(filePath) => ({
|
|
131
|
+
file: filePath,
|
|
132
|
+
fileContent: readFileSync(filePath, 'utf-8'),
|
|
133
|
+
projectRootPath: getFixturePath('/server-harness'),
|
|
134
|
+
scriptKindName: 'TS', // It's easy to get this wrong when copy-pasting
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
136
137
|
|
|
137
138
|
// override the cache
|
|
138
139
|
await server.message({
|
|
@@ -144,7 +145,7 @@ export async function createTSServer() {
|
|
|
144
145
|
closedFiles: [],
|
|
145
146
|
openFiles,
|
|
146
147
|
},
|
|
147
|
-
} as UpdateOpenRequest);
|
|
148
|
+
} as server.protocol.UpdateOpenRequest);
|
|
148
149
|
},
|
|
149
150
|
exit: async () => {
|
|
150
151
|
await server.message({ command: 'exit' });
|
package/src/transformer/index.ts
CHANGED
|
@@ -33,9 +33,9 @@ export type TransformerOptions = {
|
|
|
33
33
|
/** The function to transform source code. */
|
|
34
34
|
export type Transformer = (source: string, options: TransformerOptions) => TransformResult | Promise<TransformResult>;
|
|
35
35
|
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: pass `e` to Error's cause option
|
|
36
37
|
export const handleImportError = (packageName: string) => (e: unknown) => {
|
|
37
|
-
|
|
38
|
-
throw e;
|
|
38
|
+
throw new Error(`${packageName} import failed. Did you forget to \`npm install -D ${packageName}\`?`);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
export type DefaultTransformerOptions = PostcssTransformerOptions;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import
|
|
3
|
+
import postcss, { type Message } from 'postcss';
|
|
4
4
|
import type { Result } from 'postcss-load-config';
|
|
5
5
|
import type { Transformer } from '../index.js';
|
|
6
6
|
|
|
@@ -44,7 +44,7 @@ export const createPostcssTransformer: (postcssTransformerOptions?: PostcssTrans
|
|
|
44
44
|
});
|
|
45
45
|
if (postcssConfig === undefined) return false;
|
|
46
46
|
|
|
47
|
-
const result = await postcss(postcssConfig.plugins).process(source, {
|
|
47
|
+
const result = await postcss.default(postcssConfig.plugins).process(source, {
|
|
48
48
|
...postcssConfig.options,
|
|
49
49
|
from: options.from,
|
|
50
50
|
map: { inline: false, absolute: true },
|