1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const cli_utils_1 = require("@design-systems/cli-utils");
|
5 | const fs_1 = tslib_1.__importDefault(require("fs"));
|
6 | const path_1 = tslib_1.__importDefault(require("path"));
|
7 | const change_case_1 = require("change-case");
|
8 | const typescript_1 = tslib_1.__importDefault(require("typescript"));
|
9 | const postcss_1 = tslib_1.__importDefault(require("postcss"));
|
10 | const postcss_icss_selectors_1 = tslib_1.__importDefault(require("postcss-icss-selectors"));
|
11 | const icss_utils_1 = require("icss-utils");
|
12 | const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
|
13 | const postcss_2 = require("./postcss");
|
14 | const CSS_EXTENSION_REGEX = /\.css['"]$/;
|
15 | const FORMAT_HOST = {
|
16 |
|
17 | getCurrentDirectory: () => typescript_1.default.sys.getCurrentDirectory(),
|
18 |
|
19 | getNewLine: () => typescript_1.default.sys.newLine,
|
20 |
|
21 | getCanonicalFileName: (filename) => typescript_1.default.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase()
|
22 | };
|
23 |
|
24 | function resolveCssPath(cssPath, { fileName }) {
|
25 | const resolvedPath = cssPath.substring(1, cssPath.length - 1);
|
26 | if (resolvedPath.startsWith('.')) {
|
27 | const sourcePath = fileName;
|
28 | return path_1.default.resolve(path_1.default.dirname(sourcePath), resolvedPath);
|
29 | }
|
30 | return resolvedPath;
|
31 | }
|
32 |
|
33 | function relativeFile(file) {
|
34 | return Object.assign(Object.assign({}, file), { fileName: path_1.default.relative(process.env.INIT_CWD || process.cwd(), file.fileName) });
|
35 | }
|
36 |
|
37 | async function processCss(processor, project) {
|
38 | const ignore = ['node_modules', '.d.ts'];
|
39 | const files = project
|
40 | .getSourceFiles()
|
41 | .filter(f => !ignore.find(i => f.fileName.includes(i)));
|
42 | const pendingCssResults = new Map();
|
43 | const cssPromises = files.map(async (file) => {
|
44 | const styles = new Map();
|
45 | const results = [];
|
46 |
|
47 | file.forEachChild(node => {
|
48 |
|
49 | if (typescript_1.default.isImportDeclaration(node) &&
|
50 | node.importClause &&
|
51 | CSS_EXTENSION_REGEX.test(node.moduleSpecifier.getText())) {
|
52 | const { importClause } = node;
|
53 | const cssPath = resolveCssPath(node.moduleSpecifier.getText(), file);
|
54 |
|
55 | const importVar = importClause.getText();
|
56 | if (!fs_1.default.existsSync(cssPath)) {
|
57 | throw new Error(typescript_1.default.formatDiagnosticsWithColorAndContext([
|
58 | {
|
59 | category: 1,
|
60 | messageText: `Could not find file ${node.moduleSpecifier.getText()}"`,
|
61 | start: node.moduleSpecifier.getStart(),
|
62 | length: node.moduleSpecifier.getText().length,
|
63 | file: relativeFile(file),
|
64 | code: 1337
|
65 | }
|
66 | ], FORMAT_HOST));
|
67 | }
|
68 | const pending = pendingCssResults.get(cssPath);
|
69 | if (pending) {
|
70 | results.push([importVar, pending]);
|
71 | }
|
72 | else {
|
73 | const promise = processor.process(fs_1.default.readFileSync(cssPath, 'utf8'), {
|
74 | from: cssPath
|
75 | });
|
76 | pendingCssResults.set(cssPath, promise);
|
77 | results.push([importVar, promise]);
|
78 | }
|
79 | }
|
80 | });
|
81 |
|
82 | await Promise.all(results.map(async ([name, promise]) => {
|
83 | const result = await promise;
|
84 | styles.set(name, result.root ? icss_utils_1.extractICSS(result.root, false).icssExports : {});
|
85 | }));
|
86 | return [file.fileName, styles];
|
87 | });
|
88 |
|
89 | return new Map(await Promise.all(cssPromises));
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | class TypescriptCompiler {
|
96 | constructor(args) {
|
97 | this.logger = cli_utils_1.createLogger({ scope: 'build' });
|
98 |
|
99 | this.buildTypes = async (watch) => {
|
100 | const isTrace = cli_utils_1.getLogLevel() === 'trace';
|
101 | if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), 'tsconfig.json'))) {
|
102 | this.logger.debug('No tsconfig.json found, skipping type build.');
|
103 | return;
|
104 | }
|
105 | this.logger.trace('Generating Types...');
|
106 | const ignoredPatterns = [
|
107 | "**/*.snippet.*",
|
108 | ...Array.isArray(this.buildArgs.ignore)
|
109 | ? this.buildArgs.ignore
|
110 | : [this.buildArgs.ignore],
|
111 | ];
|
112 |
|
113 | const isIgnored = (file) => ignoredPatterns.some(pattern => minimatch_1.default(file, pattern));
|
114 | try {
|
115 | const diagnostics = [];
|
116 | const host = typescript_1.default.createSolutionBuilderHost(Object.assign(Object.assign({}, typescript_1.default.sys), { writeFile(fileName, content) {
|
117 | if (isIgnored(fileName)) {
|
118 | return;
|
119 | }
|
120 | fs_1.default.writeFileSync(fileName, content);
|
121 | },
|
122 | readFile(fileName, encoding = 'utf8') {
|
123 | if (fs_1.default.existsSync(fileName)) {
|
124 | let content = fs_1.default.readFileSync(fileName, encoding);
|
125 | if (isIgnored(fileName)) {
|
126 |
|
127 | content = '// @ts-nocheck';
|
128 | }
|
129 | return content;
|
130 | }
|
131 | } }), undefined, d => diagnostics.push(d), d => this.logger.trace(d.messageText));
|
132 |
|
133 | typescript_1.default.commonOptionsWithBuild.push({ name: 'emitDeclarationOnly' }, { name: 'declarationMap' }, { name: 'outDir' });
|
134 | const solution = typescript_1.default.createSolutionBuilder(host, ['./tsconfig.json'], {
|
135 | verbose: isTrace,
|
136 | listEmittedFiles: isTrace,
|
137 | outDir: this.buildArgs.outputDirectory || '',
|
138 | incremental: true,
|
139 | declarationMap: true,
|
140 | emitDeclarationOnly: true,
|
141 | });
|
142 | const postcssConfig = postcss_2.getPostCssConfigSync({
|
143 | cwd: cli_utils_1.getMonorepoRoot(),
|
144 | useModules: false,
|
145 | reportError: false
|
146 | });
|
147 | const cssProcessor = postcss_1.default([
|
148 | ...postcssConfig.plugins,
|
149 | postcss_icss_selectors_1.default({
|
150 | mode: 'local',
|
151 |
|
152 | generateScopedName: name => name
|
153 | })
|
154 | ]);
|
155 | let project = solution.getNextInvalidatedProject();
|
156 | while (project) {
|
157 | let css = new Map();
|
158 | if ('getSourceFiles' in project) {
|
159 |
|
160 | css = await processCss(cssProcessor, project);
|
161 | }
|
162 | project.done(undefined, undefined, {
|
163 | afterDeclarations: [this.findStyleUsage(css)]
|
164 | });
|
165 | project = solution.getNextInvalidatedProject();
|
166 | }
|
167 | if (diagnostics.length > 0) {
|
168 | const formattedDiagnostics = typescript_1.default.formatDiagnosticsWithColorAndContext(diagnostics
|
169 | .sort((a, b) => (a.start || 0) - (b.start || 0))
|
170 | .map(d => (Object.assign(Object.assign({}, d), { file: d.file ? relativeFile(d.file) : undefined }))), FORMAT_HOST);
|
171 | throw new Error(formattedDiagnostics);
|
172 | }
|
173 | this.logger.complete('Generated Types');
|
174 | }
|
175 | catch (e) {
|
176 | this.logger.error('\n');
|
177 |
|
178 |
|
179 | console.log(e.message);
|
180 | this.logger.debug(e.stack);
|
181 | this.logger.error('Failed to generate types');
|
182 | if (!watch) {
|
183 | process.exit(1);
|
184 | }
|
185 | }
|
186 | };
|
187 | this.buildArgs = args;
|
188 | }
|
189 | findStyleUsage(css) {
|
190 | return (ctx) => sf => {
|
191 | if (!('fileName' in sf)) {
|
192 | return sf;
|
193 | }
|
194 | const styles = css.get(sf.fileName) || new Map();
|
195 |
|
196 | const visitor = (node) => {
|
197 | if (typescript_1.default.isPropertyAccessExpression(node)) {
|
198 | const variable = node.expression.getText();
|
199 | const style = styles.get(variable);
|
200 | if (style) {
|
201 | const classes = Object.keys(style);
|
202 | const camelClasses = classes.map(s => change_case_1.camelCase(s));
|
203 | const className = node.name.getText();
|
204 | const exists = Boolean(classes.includes(className) || camelClasses.includes(className));
|
205 | if (!exists) {
|
206 |
|
207 | ctx.addDiagnostic({
|
208 | category: 1,
|
209 | messageText: `ClassName "${className}" does not exists in "${variable}"`,
|
210 | start: node.name.getStart(),
|
211 | length: className.length,
|
212 | file: sf,
|
213 | code: 1337
|
214 | });
|
215 | }
|
216 | }
|
217 | }
|
218 | return typescript_1.default.visitEachChild(node, visitor, ctx);
|
219 | };
|
220 |
|
221 | const external = sf.externalModuleIndicator;
|
222 | typescript_1.default.visitNode(external ? external.parent : sf, visitor);
|
223 | return sf;
|
224 | };
|
225 | }
|
226 | }
|
227 | exports.default = TypescriptCompiler;
|
228 |
|
\ | No newline at end of file |