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