happy-css-modules 1.0.0 → 2.0.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 +35 -24
- package/bin/hcm.js +1 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +4 -3
- package/dist/cli.test.js.map +1 -1
- package/dist/emitter/dts.test.js +1 -1
- package/dist/emitter/dts.test.js.map +1 -1
- package/dist/emitter/file-system.test.js +1 -1
- package/dist/emitter/file-system.test.js.map +1 -1
- package/dist/emitter/index.test.js +1 -1
- package/dist/emitter/index.test.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +3 -3
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/locator/index.test.js +1 -1
- package/dist/locator/index.test.js.map +1 -1
- package/dist/locator/postcss.test.js +1 -1
- package/dist/locator/postcss.test.js.map +1 -1
- 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.d.ts +1 -0
- package/dist/regression-test/issue-168.test.js +30 -0
- package/dist/regression-test/issue-168.test.js.map +1 -0
- package/dist/resolver/index.test.js +1 -1
- package/dist/resolver/index.test.js.map +1 -1
- package/dist/resolver/node-resolver.test.js +1 -1
- package/dist/resolver/node-resolver.test.js.map +1 -1
- package/dist/resolver/relative-resolver.test.js +1 -1
- package/dist/resolver/relative-resolver.test.js.map +1 -1
- package/dist/resolver/webpack-resolver.test.js +1 -1
- package/dist/resolver/webpack-resolver.test.js.map +1 -1
- package/dist/runner.d.ts +3 -3
- package/dist/runner.js +48 -55
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +83 -28
- package/dist/runner.test.js.map +1 -1
- package/dist/test-util/jest/resolver.cjs.map +1 -0
- package/dist/test-util/tsserver.js.map +1 -0
- package/dist/test-util/util.js.map +1 -0
- package/dist/transformer/index.js +2 -2
- package/dist/transformer/index.js.map +1 -1
- package/dist/transformer/index.test.js +1 -1
- package/dist/transformer/index.test.js.map +1 -1
- package/dist/transformer/less-transformer.test.js +1 -1
- package/dist/transformer/less-transformer.test.js.map +1 -1
- package/dist/transformer/postcss-transformer.test.js +1 -1
- package/dist/transformer/postcss-transformer.test.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +1 -1
- package/dist/transformer/scss-transformer.test.js.map +1 -1
- package/dist/util.test.js +1 -1
- package/dist/util.test.js.map +1 -1
- package/package.json +7 -7
- package/src/cli.test.ts +4 -3
- package/src/cli.ts +5 -5
- package/src/emitter/dts.test.ts +1 -1
- package/src/emitter/file-system.test.ts +1 -1
- package/src/emitter/index.test.ts +1 -1
- package/src/integration-test/go-to-definition.test.ts +5 -4
- package/src/locator/index.test.ts +1 -1
- package/src/locator/postcss.test.ts +1 -1
- package/src/logger.ts +31 -0
- package/src/regression-test/issue-168.test.ts +34 -0
- package/src/resolver/index.test.ts +1 -1
- package/src/resolver/node-resolver.test.ts +1 -1
- package/src/resolver/relative-resolver.test.ts +1 -1
- package/src/resolver/webpack-resolver.test.ts +1 -1
- package/src/runner.test.ts +125 -31
- package/src/runner.ts +56 -50
- package/src/transformer/index.test.ts +1 -1
- package/src/transformer/index.ts +2 -2
- package/src/transformer/less-transformer.test.ts +1 -1
- package/src/transformer/postcss-transformer.test.ts +1 -1
- package/src/transformer/scss-transformer.test.ts +1 -1
- package/src/util.test.ts +1 -1
- package/dist/test/jest/resolver.cjs.map +0 -1
- package/dist/test/tsserver.js.map +0 -1
- package/dist/test/util.js.map +0 -1
- /package/dist/{test → test-util}/jest/resolver.cjs +0 -0
- /package/dist/{test → test-util}/jest/resolver.d.cts +0 -0
- /package/dist/{test → test-util}/tsserver.d.ts +0 -0
- /package/dist/{test → test-util}/tsserver.js +0 -0
- /package/dist/{test → test-util}/util.d.ts +0 -0
- /package/dist/{test → test-util}/util.js +0 -0
- /package/src/{test → test-util}/jest/resolver.cjs +0 -0
- /package/src/{test → test-util}/tsserver.ts +0 -0
- /package/src/{test → test-util}/util.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "happy-css-modules",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Creates .d.ts files from CSS Modules .css files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"@file-cache/core": "^1.1.3",
|
|
31
31
|
"@file-cache/npm": "^1.1.3",
|
|
32
32
|
"await-lock": "^2.2.2",
|
|
33
|
-
"camelcase": "^7.0.
|
|
33
|
+
"camelcase": "^7.0.1",
|
|
34
34
|
"chalk": "^5.0.1",
|
|
35
35
|
"chokidar": "^3.5.3",
|
|
36
36
|
"enhanced-resolve": "^5.10.0",
|
|
37
37
|
"glob": "^8.0.3",
|
|
38
38
|
"import-meta-resolve": "^2.1.0",
|
|
39
|
-
"minimatch": "^5.1.
|
|
40
|
-
"postcss": "^8.4.
|
|
39
|
+
"minimatch": "^5.1.2",
|
|
40
|
+
"postcss": "^8.4.18",
|
|
41
41
|
"postcss-load-config": "^4.0.1",
|
|
42
42
|
"postcss-modules": "^4.3.1",
|
|
43
43
|
"postcss-selector-parser": "^6.0.10",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@jest/types": "^29.0.3",
|
|
51
51
|
"@mizdra/eslint-config-mizdra": "^1.2.0",
|
|
52
52
|
"@mizdra/prettier-config-mizdra": "^1.0.0",
|
|
53
|
-
"@swc/core": "^1.3.
|
|
54
|
-
"@swc/jest": "^0.2.
|
|
53
|
+
"@swc/core": "^1.3.24",
|
|
54
|
+
"@swc/jest": "^0.2.24",
|
|
55
55
|
"@types/dedent": "^0.7.0",
|
|
56
56
|
"@types/glob": "^7.2.0",
|
|
57
57
|
"@types/jest": "^29.0.0",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"line-column": "^1.0.2",
|
|
73
73
|
"npm-run-all": "^4.1.5",
|
|
74
74
|
"postcss-import": "^15.0.0",
|
|
75
|
-
"postcss-simple-vars": "^7.0.
|
|
75
|
+
"postcss-simple-vars": "^7.0.1",
|
|
76
76
|
"prettier": "~2.7.1",
|
|
77
77
|
"sass": "^1.54.3",
|
|
78
78
|
"tsc-watch": "^5.0.3",
|
package/src/cli.test.ts
CHANGED
|
@@ -65,8 +65,9 @@ describe('parseArgv', () => {
|
|
|
65
65
|
expect(parseArgv([...baseArgs, '1.css']).cacheStrategy).toBe('content');
|
|
66
66
|
expect(parseArgv([...baseArgs, '1.css', '--cacheStrategy', 'metadata']).cacheStrategy).toBe('metadata');
|
|
67
67
|
});
|
|
68
|
-
test('--
|
|
69
|
-
expect(parseArgv([...baseArgs, '1.css'
|
|
70
|
-
expect(parseArgv([...baseArgs, '1.css', '--
|
|
68
|
+
test('--logLevel', () => {
|
|
69
|
+
expect(parseArgv([...baseArgs, '1.css']).logLevel).toBe('info');
|
|
70
|
+
expect(parseArgv([...baseArgs, '1.css', '--logLevel', 'debug']).logLevel).toBe('debug');
|
|
71
|
+
expect(parseArgv([...baseArgs, '1.css', '--logLevel', 'silent']).logLevel).toBe('silent');
|
|
71
72
|
});
|
|
72
73
|
});
|
package/src/cli.ts
CHANGED
|
@@ -67,10 +67,10 @@ export function parseArgv(argv: string[]): RunnerOptions {
|
|
|
67
67
|
default: 'content' as RunnerOptions['cacheStrategy'],
|
|
68
68
|
describe: 'Strategy for the cache to use for detecting changed files.',
|
|
69
69
|
})
|
|
70
|
-
.option('
|
|
71
|
-
|
|
72
|
-
default:
|
|
73
|
-
describe: '
|
|
70
|
+
.option('logLevel', {
|
|
71
|
+
choices: ['debug', 'info', 'silent'] as const,
|
|
72
|
+
default: 'info' as RunnerOptions['logLevel'],
|
|
73
|
+
describe: 'What level of logs to report.',
|
|
74
74
|
})
|
|
75
75
|
.alias('h', 'help')
|
|
76
76
|
.alias('v', 'version')
|
|
@@ -108,6 +108,6 @@ export function parseArgv(argv: string[]): RunnerOptions {
|
|
|
108
108
|
postcssConfig: parsedArgv.postcssConfig,
|
|
109
109
|
cache: parsedArgv.cache,
|
|
110
110
|
cacheStrategy: parsedArgv.cacheStrategy,
|
|
111
|
-
|
|
111
|
+
logLevel: parsedArgv.logLevel,
|
|
112
112
|
};
|
|
113
113
|
}
|
package/src/emitter/dts.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { SourceMapConsumer } from 'source-map';
|
|
3
3
|
import { Locator } from '../locator/index.js';
|
|
4
|
-
import { getFixturePath, createFixtures } from '../test/util.js';
|
|
4
|
+
import { getFixturePath, createFixtures } from '../test-util/util.js';
|
|
5
5
|
import { generateDtsContentWithSourceMap, getDtsFilePath } from './dts.js';
|
|
6
6
|
import { type DtsFormatOptions } from './index.js';
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, rm, stat } from 'fs/promises';
|
|
2
|
-
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
2
|
+
import { createFixtures, getFixturePath } from '../test-util/util.js';
|
|
3
3
|
import { writeFileIfChanged } from './file-system.js';
|
|
4
4
|
|
|
5
5
|
const TEST_FILE_PATH = getFixturePath('/test.txt');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile, stat } from 'fs/promises';
|
|
2
2
|
import { jest } from '@jest/globals';
|
|
3
|
-
import { createFixtures, exists, fakeToken, getFixturePath, waitForAsyncTask } from '../test/util.js';
|
|
3
|
+
import { createFixtures, exists, fakeToken, getFixturePath, waitForAsyncTask } from '../test-util/util.js';
|
|
4
4
|
import { emitGeneratedFiles, getRelativePath, isSubDirectoryFile } from './index.js';
|
|
5
5
|
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
@@ -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
|
-
import { createTSServer } from '../test/tsserver.js';
|
|
4
|
-
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
4
|
+
import { createTSServer } from '../test-util/tsserver.js';
|
|
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,
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { jest } from '@jest/globals';
|
|
4
4
|
import dedent from 'dedent';
|
|
5
5
|
import { createDefaultTransformer } from '../index.js';
|
|
6
|
-
import { createFixtures, FIXTURE_DIR_PATH, getFixturePath } from '../test/util.js';
|
|
6
|
+
import { createFixtures, FIXTURE_DIR_PATH, getFixturePath } from '../test-util/util.js';
|
|
7
7
|
import { sleepSync } from '../util.js';
|
|
8
8
|
|
|
9
9
|
const readFileSpy = jest.spyOn(fs, 'readFile');
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { symlink } from 'fs/promises';
|
|
2
|
+
import { jest } from '@jest/globals';
|
|
3
|
+
import type { RunnerOptions, Watcher } from '../runner.js';
|
|
4
|
+
import { run } from '../runner.js';
|
|
5
|
+
import { createFixtures, getFixturePath, waitForAsyncTask } from '../test-util/util.js';
|
|
6
|
+
|
|
7
|
+
const defaultOptions: RunnerOptions = {
|
|
8
|
+
pattern: 'test/**/*.css',
|
|
9
|
+
cwd: getFixturePath('/'),
|
|
10
|
+
cache: false,
|
|
11
|
+
logLevel: 'silent',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
15
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
16
|
+
|
|
17
|
+
// Exit the watcher even if the test fails
|
|
18
|
+
let watcher: Watcher | undefined;
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
if (watcher) {
|
|
21
|
+
await watcher.close();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('issue-168', async () => {
|
|
26
|
+
createFixtures({
|
|
27
|
+
'/test/css-file.css': '.a {}',
|
|
28
|
+
'/test/non-css-file.txt': 'text file',
|
|
29
|
+
});
|
|
30
|
+
await symlink(getFixturePath('/test/non-css-file.txt'), getFixturePath('/test/symlink.txt'));
|
|
31
|
+
watcher = await run({ ...defaultOptions, watch: true });
|
|
32
|
+
await waitForAsyncTask(300); // Wait for initial code generation to complete
|
|
33
|
+
expect(consoleErrorSpy).not.toBeCalled(); // If an error is output, then failed
|
|
34
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
1
|
+
import { createFixtures, getFixturePath } from '../test-util/util.js';
|
|
2
2
|
import { createWebpackResolver } from './webpack-resolver.js';
|
|
3
3
|
|
|
4
4
|
test('resolves specifier with css-loader mechanism', async () => {
|
package/src/runner.test.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { readFile, rm, writeFile } from 'fs/promises';
|
|
1
|
+
import { readFile, rm, symlink, writeFile } from 'fs/promises';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
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';
|
|
11
|
-
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test/util.js';
|
|
9
|
+
import type { RunnerOptions, Watcher } from './runner.js';
|
|
10
|
+
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
|
|
12
11
|
|
|
13
12
|
const require = createRequire(import.meta.url);
|
|
14
13
|
|
|
@@ -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);
|
|
@@ -251,3 +315,33 @@ test('postcssConfig', async () => {
|
|
|
251
315
|
});
|
|
252
316
|
await run({ ...defaultOptions, postcssConfig }); // not throw
|
|
253
317
|
});
|
|
318
|
+
|
|
319
|
+
test('support symlink', async () => {
|
|
320
|
+
createFixtures({
|
|
321
|
+
'/external/1.css': '.a {}',
|
|
322
|
+
'/external/2.css': '.b {}',
|
|
323
|
+
'/test': {}, // empty directory
|
|
324
|
+
});
|
|
325
|
+
await symlink(getFixturePath('/external/1.css'), getFixturePath('/test/1.css'));
|
|
326
|
+
await symlink(getFixturePath('/external/2.css'), getFixturePath('/test/2.txt'));
|
|
327
|
+
|
|
328
|
+
await run({ ...defaultOptions, watch: false });
|
|
329
|
+
|
|
330
|
+
// Symlinks that do not match the pattern are not processed.
|
|
331
|
+
expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
|
|
332
|
+
expect(await exists(getFixturePath('/test/2.css.d.ts'))).toBe(false);
|
|
333
|
+
expect(await exists(getFixturePath('/test/2.txt.d.ts'))).toBe(false);
|
|
334
|
+
|
|
335
|
+
// The path referred to by sourceMappingURL or sources field is the path before symlink resolution.
|
|
336
|
+
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatchInlineSnapshot(`
|
|
337
|
+
"declare const styles:
|
|
338
|
+
& Readonly<{ "a": string }>
|
|
339
|
+
;
|
|
340
|
+
export default styles;
|
|
341
|
+
//# sourceMappingURL=./1.css.d.ts.map
|
|
342
|
+
"
|
|
343
|
+
`);
|
|
344
|
+
expect(await readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).toMatchInlineSnapshot(
|
|
345
|
+
`"{"version":3,"sources":["./1.css"],"names":["a"],"mappings":"AAAA;AAAA,E,aAAAA,G,WAAA;AAAA;AAAA","file":"1.css.d.ts","sourceRoot":""}"`,
|
|
346
|
+
);
|
|
347
|
+
});
|
package/src/runner.ts
CHANGED
|
@@ -9,6 +9,7 @@ import * as chokidar from 'chokidar';
|
|
|
9
9
|
import _glob from 'glob';
|
|
10
10
|
import { isGeneratedFilesExist, emitGeneratedFiles } from './emitter/index.js';
|
|
11
11
|
import { Locator } from './locator/index.js';
|
|
12
|
+
import { Logger } from './logger.js';
|
|
12
13
|
import type { Resolver } from './resolver/index.js';
|
|
13
14
|
import { createDefaultResolver } from './resolver/index.js';
|
|
14
15
|
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
@@ -65,10 +66,10 @@ export interface RunnerOptions {
|
|
|
65
66
|
*/
|
|
66
67
|
cacheStrategy?: 'content' | 'metadata' | undefined;
|
|
67
68
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @default
|
|
69
|
+
* What level of logs to report.
|
|
70
|
+
* @default 'info'
|
|
70
71
|
*/
|
|
71
|
-
|
|
72
|
+
logLevel?: 'debug' | 'info' | 'silent' | undefined;
|
|
72
73
|
/** Working directory path. */
|
|
73
74
|
cwd?: string | undefined;
|
|
74
75
|
}
|
|
@@ -84,9 +85,9 @@ export async function run(options: OverrideProp<RunnerOptions, 'watch', true>):
|
|
|
84
85
|
export async function run(options: RunnerOptions): Promise<void>;
|
|
85
86
|
export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
86
87
|
const lock = new AwaitLock.default();
|
|
88
|
+
const logger = new Logger(options.logLevel ?? 'info');
|
|
87
89
|
|
|
88
90
|
const cwd = options.cwd ?? process.cwd();
|
|
89
|
-
const silent = options.silent ?? false;
|
|
90
91
|
const resolver =
|
|
91
92
|
options.resolver ??
|
|
92
93
|
createDefaultResolver({
|
|
@@ -115,9 +116,24 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
115
116
|
};
|
|
116
117
|
|
|
117
118
|
async function processFile(filePath: string) {
|
|
119
|
+
async function isChangedFile(filePath: string) {
|
|
120
|
+
const result = await cache.getAndUpdateCache(filePath);
|
|
121
|
+
if (result.error) throw result.error;
|
|
122
|
+
return result.changed;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Locator#load cannot be called concurrently. Therefore, it takes a lock and waits.
|
|
126
|
+
await lock.acquireAsync();
|
|
127
|
+
|
|
118
128
|
try {
|
|
119
|
-
|
|
120
|
-
await
|
|
129
|
+
const _isGeneratedFilesExist = await isGeneratedFilesExist(filePath, options.declarationMap);
|
|
130
|
+
const _isChangedFile = await isChangedFile(filePath);
|
|
131
|
+
// Generate .d.ts and .d.ts.map only when the file has been updated.
|
|
132
|
+
// However, if .d.ts or .d.ts.map has not yet been generated, always generate.
|
|
133
|
+
if (_isGeneratedFilesExist && !_isChangedFile) {
|
|
134
|
+
logger.debug(chalk.gray(`${relative(cwd, filePath)} (skipped)`));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
121
137
|
|
|
122
138
|
const result = await locator.load(filePath);
|
|
123
139
|
await emitGeneratedFiles({
|
|
@@ -129,62 +145,52 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
129
145
|
},
|
|
130
146
|
isExternalFile,
|
|
131
147
|
});
|
|
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;
|
|
148
|
+
logger.info(chalk.green(`${relative(cwd, filePath)} (generated)`));
|
|
149
|
+
|
|
150
|
+
await cache.reconcile(); // Update cache for the file
|
|
142
151
|
} finally {
|
|
143
152
|
lock.release();
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
async function
|
|
148
|
-
const result = await cache.getAndUpdateCache(filePath);
|
|
149
|
-
if (result.error) throw result.error;
|
|
150
|
-
return result.changed;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (options.watch) {
|
|
154
|
-
if (!silent) console.log('Watch ' + options.pattern + '...');
|
|
155
|
-
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
|
|
156
|
-
watcher.on('all', (eventName, filePath) => {
|
|
157
|
-
if (eventName === 'add' || eventName === 'change') {
|
|
158
|
-
processFile(resolve(cwd, filePath)).catch(() => {
|
|
159
|
-
// TODO: Emit a error by `Watcher#onerror`
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
return { close: async () => watcher.close() };
|
|
164
|
-
} else {
|
|
156
|
+
async function processAllFiles() {
|
|
165
157
|
const filePaths = (await glob(options.pattern, { dot: true, cwd }))
|
|
166
158
|
// convert relative path to absolute path
|
|
167
159
|
.map((file) => resolve(cwd, file));
|
|
168
160
|
|
|
169
161
|
const errors: unknown[] = [];
|
|
170
162
|
for (const filePath of filePaths) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!_isGeneratedFilesExist || _isChangedFile) {
|
|
177
|
-
await processFile(filePath);
|
|
178
|
-
} else {
|
|
179
|
-
if (!silent) console.log(chalk.gray(`${relative(cwd, filePath)} (skipped)`));
|
|
180
|
-
}
|
|
181
|
-
} catch (e: unknown) {
|
|
182
|
-
errors.push(e);
|
|
183
|
-
}
|
|
163
|
+
await processFile(filePath).catch((e) => errors.push(e));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (errors.length > 0) {
|
|
167
|
+
throw new AggregateError(errors, 'Failed to process files');
|
|
184
168
|
}
|
|
185
|
-
if (errors.length > 0) throw new AggregateError(errors, 'Failed to process files');
|
|
186
169
|
}
|
|
187
170
|
|
|
188
|
-
|
|
189
|
-
|
|
171
|
+
if (!options.watch) {
|
|
172
|
+
logger.info('Generate .d.ts for ' + options.pattern + '...');
|
|
173
|
+
await processAllFiles();
|
|
174
|
+
// Write cache state to file for persistence
|
|
175
|
+
} else {
|
|
176
|
+
// First, watch files.
|
|
177
|
+
logger.info('Watch ' + options.pattern + '...');
|
|
178
|
+
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
|
|
179
|
+
watcher.on('all', (eventName, relativeFilePath) => {
|
|
180
|
+
const filePath = resolve(cwd, relativeFilePath);
|
|
181
|
+
|
|
182
|
+
// There is a bug in chokidar that matches symlinks that do not match the pattern.
|
|
183
|
+
// ref: https://github.com/paulmillr/chokidar/issues/967
|
|
184
|
+
if (isExternalFile(filePath)) return;
|
|
185
|
+
|
|
186
|
+
if (eventName !== 'add' && eventName !== 'change') return;
|
|
187
|
+
|
|
188
|
+
processFile(filePath).catch((e) => {
|
|
189
|
+
logger.error(e);
|
|
190
|
+
// TODO: Emit a error by `Watcher#onerror`
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return { close: async () => watcher.close() };
|
|
195
|
+
}
|
|
190
196
|
}
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import dedent from 'dedent';
|
|
4
4
|
import { Locator } from '../locator/index.js';
|
|
5
|
-
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
5
|
+
import { createFixtures, getFixturePath } from '../test-util/util.js';
|
|
6
6
|
import { createDefaultTransformer } from './index.js';
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url);
|
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,7 +1,7 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { Locator } from '../locator/index.js';
|
|
4
|
-
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
4
|
+
import { createFixtures, getFixturePath } from '../test-util/util.js';
|
|
5
5
|
import { createLessTransformer } from './less-transformer.js';
|
|
6
6
|
|
|
7
7
|
const locator = new Locator({ transformer: createLessTransformer() });
|