-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathpostcss-configuration.ts
115 lines (94 loc) · 2.98 KB
/
postcss-configuration.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { readFile, readdir } from 'node:fs/promises';
import { join } from 'node:path';
export interface PostcssConfiguration {
plugins: [name: string, options?: object | string][];
}
interface RawPostcssConfiguration {
plugins?: Record<string, object | boolean | string> | (string | [string, object])[];
}
const postcssConfigurationFiles: string[] = ['postcss.config.json', '.postcssrc.json'];
interface SearchDirectory {
root: string;
files: Set<string>;
}
async function generateSearchDirectories(roots: string[]): Promise<SearchDirectory[]> {
return await Promise.all(
roots.map((root) =>
readdir(root, { withFileTypes: true }).then((entries) => ({
root,
files: new Set(entries.filter((entry) => entry.isFile()).map((entry) => entry.name)),
})),
),
);
}
function findFile(
searchDirectories: SearchDirectory[],
potentialFiles: string[],
): string | undefined {
for (const { root, files } of searchDirectories) {
for (const potential of potentialFiles) {
if (files.has(potential)) {
return join(root, potential);
}
}
}
return undefined;
}
async function readPostcssConfiguration(
configurationFile: string,
): Promise<RawPostcssConfiguration> {
const data = await readFile(configurationFile, 'utf-8');
const config = JSON.parse(data) as RawPostcssConfiguration;
return config;
}
export async function loadPostcssConfiguration(
workspaceRoot: string,
projectRoot: string,
): Promise<PostcssConfiguration | undefined> {
// A configuration file can exist in the project or workspace root
const searchDirectories = await generateSearchDirectories([projectRoot, workspaceRoot]);
const configPath = findFile(searchDirectories, postcssConfigurationFiles);
if (!configPath) {
return undefined;
}
const raw = await readPostcssConfiguration(configPath);
// If no plugins are defined, consider it equivalent to no configuration
if (!raw.plugins || typeof raw.plugins !== 'object') {
return undefined;
}
// Normalize plugin array form
if (Array.isArray(raw.plugins)) {
if (raw.plugins.length < 1) {
return undefined;
}
const config: PostcssConfiguration = { plugins: [] };
for (const element of raw.plugins) {
if (typeof element === 'string') {
config.plugins.push([element]);
} else {
config.plugins.push(element);
}
}
return config;
}
// Normalize plugin object map form
const entries = Object.entries(raw.plugins);
if (entries.length < 1) {
return undefined;
}
const config: PostcssConfiguration = { plugins: [] };
for (const [name, options] of entries) {
if (!options || (typeof options !== 'object' && typeof options !== 'string')) {
continue;
}
config.plugins.push([name, options]);
}
return config;
}