@najemi-software/typed-scss-modules 10.3.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/LICENSE +21 -0
- package/README.md +437 -0
- package/dist/lib/cli.d.ts +2 -0
- package/dist/lib/cli.js +123 -0
- package/dist/lib/core/alerts.d.ts +14 -0
- package/dist/lib/core/alerts.js +25 -0
- package/dist/lib/core/generate.d.ts +8 -0
- package/dist/lib/core/generate.js +18 -0
- package/dist/lib/core/index.d.ts +6 -0
- package/dist/lib/core/index.js +5 -0
- package/dist/lib/core/list-different.d.ts +3 -0
- package/dist/lib/core/list-different.js +42 -0
- package/dist/lib/core/list-files-and-perform-sanity-checks.d.ts +9 -0
- package/dist/lib/core/list-files-and-perform-sanity-checks.js +22 -0
- package/dist/lib/core/remove-file.d.ts +7 -0
- package/dist/lib/core/remove-file.js +28 -0
- package/dist/lib/core/types.d.ts +21 -0
- package/dist/lib/core/types.js +1 -0
- package/dist/lib/core/watch.d.ts +8 -0
- package/dist/lib/core/watch.js +34 -0
- package/dist/lib/core/write-file.d.ts +8 -0
- package/dist/lib/core/write-file.js +62 -0
- package/dist/lib/implementations/compilers.d.ts +14 -0
- package/dist/lib/implementations/compilers.js +49 -0
- package/dist/lib/implementations/implementations.d.ts +27 -0
- package/dist/lib/implementations/implementations.js +54 -0
- package/dist/lib/implementations/index.d.ts +2 -0
- package/dist/lib/implementations/index.js +2 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/load.d.ts +20 -0
- package/dist/lib/load.js +87 -0
- package/dist/lib/main.d.ts +2 -0
- package/dist/lib/main.js +34 -0
- package/dist/lib/prettier/can-resolve.d.ts +1 -0
- package/dist/lib/prettier/can-resolve.js +12 -0
- package/dist/lib/prettier/index.d.ts +8 -0
- package/dist/lib/prettier/index.js +44 -0
- package/dist/lib/sass/file-to-class-names.d.ts +25 -0
- package/dist/lib/sass/file-to-class-names.js +55 -0
- package/dist/lib/sass/importer.d.ts +29 -0
- package/dist/lib/sass/importer.js +37 -0
- package/dist/lib/sass/index.d.ts +1 -0
- package/dist/lib/sass/index.js +1 -0
- package/dist/lib/sass/source-to-class-names.d.ts +6 -0
- package/dist/lib/sass/source-to-class-names.js +16 -0
- package/dist/lib/typescript/class-names-to-type-definition.d.ts +20 -0
- package/dist/lib/typescript/class-names-to-type-definition.js +64 -0
- package/dist/lib/typescript/get-type-definition-path.d.ts +8 -0
- package/dist/lib/typescript/get-type-definition-path.js +25 -0
- package/dist/lib/typescript/index.d.ts +3 -0
- package/dist/lib/typescript/index.js +3 -0
- package/package.json +120 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { alerts, setAlertsLogLevel } from "./alerts.js";
|
|
2
|
+
export { generate } from "./generate.js";
|
|
3
|
+
export { listDifferent } from "./list-different.js";
|
|
4
|
+
export type { CLIOptions, ConfigOptions } from "./types.js";
|
|
5
|
+
export { watch } from "./watch.js";
|
|
6
|
+
export { writeFile } from "./write-file.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { fileToClassNames } from "../sass/file-to-class-names.js";
|
|
3
|
+
import { classNamesToTypeDefinitions, getTypeDefinitionPath, } from "../typescript/index.js";
|
|
4
|
+
import { alerts } from "./alerts.js";
|
|
5
|
+
import { listFilesAndPerformSanityChecks } from "./list-files-and-perform-sanity-checks.js";
|
|
6
|
+
export const listDifferent = async (pattern, options) => {
|
|
7
|
+
const files = listFilesAndPerformSanityChecks(pattern, options);
|
|
8
|
+
// Wait for all the files to be checked.
|
|
9
|
+
const validChecks = await Promise.all(files.map((file) => checkFile(file, options)));
|
|
10
|
+
if (validChecks.includes(false)) {
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const checkFile = async (file, options) => {
|
|
15
|
+
try {
|
|
16
|
+
const classNames = await fileToClassNames(file, options);
|
|
17
|
+
const typeDefinition = await classNamesToTypeDefinitions({
|
|
18
|
+
classNames: classNames,
|
|
19
|
+
file,
|
|
20
|
+
...options,
|
|
21
|
+
});
|
|
22
|
+
if (!typeDefinition) {
|
|
23
|
+
// Assume if no type defs are necessary it's fine
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const path = getTypeDefinitionPath(file, options);
|
|
27
|
+
if (!fs.existsSync(path)) {
|
|
28
|
+
alerts.error(`[INVALID TYPES] Type file needs to be generated for ${file} `);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const content = fs.readFileSync(path, { encoding: "utf8" });
|
|
32
|
+
if (content !== typeDefinition) {
|
|
33
|
+
alerts.error(`[INVALID TYPES] Check type definitions for ${file}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
alerts.error(`An error occurred checking ${file}:\n${JSON.stringify(error)}`);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConfigOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return the files matching the given pattern and alert the user if only 0 or 1
|
|
4
|
+
* files matched.
|
|
5
|
+
*
|
|
6
|
+
* @param pattern the file pattern to generate type definitions for
|
|
7
|
+
* @param options the CLI options
|
|
8
|
+
*/
|
|
9
|
+
export declare function listFilesAndPerformSanityChecks(pattern: string, options: ConfigOptions): string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { globSync } from "glob";
|
|
2
|
+
import { alerts } from "./alerts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Return the files matching the given pattern and alert the user if only 0 or 1
|
|
5
|
+
* files matched.
|
|
6
|
+
*
|
|
7
|
+
* @param pattern the file pattern to generate type definitions for
|
|
8
|
+
* @param options the CLI options
|
|
9
|
+
*/
|
|
10
|
+
export function listFilesAndPerformSanityChecks(pattern, options) {
|
|
11
|
+
// Find all the files that match the provided pattern.
|
|
12
|
+
const files = globSync(pattern, { ignore: options.ignore });
|
|
13
|
+
if (!files || !files.length) {
|
|
14
|
+
alerts.error("No files found.");
|
|
15
|
+
}
|
|
16
|
+
// This case still works as expected but it's easy to do on accident so
|
|
17
|
+
// provide a (hopefully) helpful warning.
|
|
18
|
+
if (files.length === 1) {
|
|
19
|
+
alerts.warn(`Only 1 file found for ${pattern}. If using a glob pattern (eg: dir/**/*.scss) make sure to wrap in quotes (eg: "dir/**/*.scss").`);
|
|
20
|
+
}
|
|
21
|
+
return files;
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConfigOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Given a single file remove the generated types if they exist
|
|
4
|
+
*
|
|
5
|
+
* @param file the SCSS file to generate types for
|
|
6
|
+
*/
|
|
7
|
+
export declare const removeSCSSTypeDefinitionFile: (file: string, options: ConfigOptions) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getTypeDefinitionPath } from "../typescript/get-type-definition-path.js";
|
|
3
|
+
import { alerts } from "./alerts.js";
|
|
4
|
+
/**
|
|
5
|
+
* Given a single file remove the file
|
|
6
|
+
*
|
|
7
|
+
* @param file any file to remove
|
|
8
|
+
*/
|
|
9
|
+
const removeFile = (file) => {
|
|
10
|
+
try {
|
|
11
|
+
if (fs.existsSync(file)) {
|
|
12
|
+
fs.unlinkSync(file);
|
|
13
|
+
alerts.success(`[REMOVED] ${file}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
alerts.error(`An error occurred removing ${file}:\n${JSON.stringify(error)}`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Given a single file remove the generated types if they exist
|
|
22
|
+
*
|
|
23
|
+
* @param file the SCSS file to generate types for
|
|
24
|
+
*/
|
|
25
|
+
export const removeSCSSTypeDefinitionFile = (file, options) => {
|
|
26
|
+
const path = getTypeDefinitionPath(file, options);
|
|
27
|
+
removeFile(path);
|
|
28
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SASSOptions } from "../sass/file-to-class-names.js";
|
|
2
|
+
import type { ExportType, LogLevel, QuoteType } from "../typescript/index.js";
|
|
3
|
+
type CLIOnlyOptions = Extract<keyof SASSOptions, "importer">;
|
|
4
|
+
export interface CLIOptions extends Exclude<SASSOptions, CLIOnlyOptions> {
|
|
5
|
+
banner: string;
|
|
6
|
+
ignore: string[];
|
|
7
|
+
ignoreInitial: boolean;
|
|
8
|
+
exportType: ExportType;
|
|
9
|
+
exportTypeName: string;
|
|
10
|
+
exportTypeInterface: string;
|
|
11
|
+
listDifferent: boolean;
|
|
12
|
+
quoteType: QuoteType;
|
|
13
|
+
updateStaleOnly: boolean;
|
|
14
|
+
watch: boolean;
|
|
15
|
+
logLevel: LogLevel;
|
|
16
|
+
outputFolder: string | null;
|
|
17
|
+
allowArbitraryExtensions: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ConfigOptions extends CLIOptions, SASSOptions {
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConfigOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Watch a file glob and generate the corresponding types.
|
|
4
|
+
*
|
|
5
|
+
* @param pattern the file pattern to watch for file changes or additions
|
|
6
|
+
* @param options the CLI options
|
|
7
|
+
*/
|
|
8
|
+
export declare const watch: (pattern: string, options: ConfigOptions) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chok from "chokidar";
|
|
2
|
+
import { alerts } from "./alerts.js";
|
|
3
|
+
import { listFilesAndPerformSanityChecks } from "./list-files-and-perform-sanity-checks.js";
|
|
4
|
+
import { removeSCSSTypeDefinitionFile } from "./remove-file.js";
|
|
5
|
+
import { writeFile } from "./write-file.js";
|
|
6
|
+
/**
|
|
7
|
+
* Watch a file glob and generate the corresponding types.
|
|
8
|
+
*
|
|
9
|
+
* @param pattern the file pattern to watch for file changes or additions
|
|
10
|
+
* @param options the CLI options
|
|
11
|
+
*/
|
|
12
|
+
export const watch = (pattern, options) => {
|
|
13
|
+
listFilesAndPerformSanityChecks(pattern, options);
|
|
14
|
+
alerts.success("Watching files...");
|
|
15
|
+
const watcher = chok.watch(pattern, {
|
|
16
|
+
ignoreInitial: options.ignoreInitial,
|
|
17
|
+
ignored: options.ignore,
|
|
18
|
+
});
|
|
19
|
+
watcher
|
|
20
|
+
.on("change", (path) => {
|
|
21
|
+
alerts.info(`[CHANGED] ${path}`);
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
23
|
+
writeFile(path, options);
|
|
24
|
+
})
|
|
25
|
+
.on("add", (path) => {
|
|
26
|
+
alerts.info(`[ADDED] ${path}`);
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
28
|
+
writeFile(path, options);
|
|
29
|
+
})
|
|
30
|
+
.on("unlink", (path) => {
|
|
31
|
+
alerts.info(`[REMOVED] ${path}`);
|
|
32
|
+
removeSCSSTypeDefinitionFile(path, options);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CLIOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Given a single file generate the proper types.
|
|
4
|
+
*
|
|
5
|
+
* @param file the SCSS file to generate types for
|
|
6
|
+
* @param options the CLI options
|
|
7
|
+
*/
|
|
8
|
+
export declare const writeFile: (file: string, options: CLIOptions) => Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileToClassNames } from "../sass/file-to-class-names.js";
|
|
4
|
+
import { classNamesToTypeDefinitions, getTypeDefinitionPath, } from "../typescript/index.js";
|
|
5
|
+
import { alerts } from "./alerts.js";
|
|
6
|
+
import { removeSCSSTypeDefinitionFile } from "./remove-file.js";
|
|
7
|
+
/**
|
|
8
|
+
* Given a single file generate the proper types.
|
|
9
|
+
*
|
|
10
|
+
* @param file the SCSS file to generate types for
|
|
11
|
+
* @param options the CLI options
|
|
12
|
+
*/
|
|
13
|
+
export const writeFile = async (file, options) => {
|
|
14
|
+
try {
|
|
15
|
+
const classNames = await fileToClassNames(file, options);
|
|
16
|
+
const typeDefinition = await classNamesToTypeDefinitions({
|
|
17
|
+
classNames,
|
|
18
|
+
file,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
const typesPath = getTypeDefinitionPath(file, options);
|
|
22
|
+
const typesExist = fs.existsSync(typesPath);
|
|
23
|
+
// Avoid outputting empty type definition files.
|
|
24
|
+
// If the file exists and the type definition is now empty, remove the file.
|
|
25
|
+
if (!typeDefinition) {
|
|
26
|
+
if (typesExist) {
|
|
27
|
+
removeSCSSTypeDefinitionFile(file, options);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
alerts.notice(`[NO GENERATED TYPES] ${file}`);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Avoid re-writing the file if it hasn't changed.
|
|
35
|
+
// First by checking the file modification time, then
|
|
36
|
+
// by comparing the file contents.
|
|
37
|
+
if (options.updateStaleOnly && typesExist) {
|
|
38
|
+
const fileModified = fs.statSync(file).mtime;
|
|
39
|
+
const typeDefinitionModified = fs.statSync(typesPath).mtime;
|
|
40
|
+
if (fileModified < typeDefinitionModified) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const existingTypeDefinition = fs.readFileSync(typesPath, "utf8");
|
|
44
|
+
if (existingTypeDefinition === typeDefinition) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Files can be written to arbitrary directories and need to
|
|
49
|
+
// be nested to match the project structure so it's possible
|
|
50
|
+
// there are multiple directories that need to be created.
|
|
51
|
+
const dirname = path.dirname(typesPath);
|
|
52
|
+
if (!fs.existsSync(dirname)) {
|
|
53
|
+
fs.mkdirSync(dirname, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
fs.writeFileSync(typesPath, typeDefinition);
|
|
56
|
+
alerts.success(`[GENERATED TYPES] ${typesPath}`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
60
|
+
alerts.error(`Write File '${file}' error: ${errorMessage}`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AsyncCompiler, Implementation, Implementations, SyncCompiler } from "./implementations.js";
|
|
2
|
+
export declare const disposeAllCompilers: () => Promise<void>;
|
|
3
|
+
type ImplementationDetails = {
|
|
4
|
+
implementation: Implementations;
|
|
5
|
+
root: Implementation;
|
|
6
|
+
};
|
|
7
|
+
export declare const getSyncCompiler: ({ root, implementation, }: ImplementationDetails) => SyncCompiler | Pick<Implementation, "compile">;
|
|
8
|
+
/**
|
|
9
|
+
* When calling the compile functions multiple times, using a compiler instance with the sass-embedded npm package
|
|
10
|
+
* is much faster than using the top-level compilation methods or the sass npm package.
|
|
11
|
+
* @see https://sass-lang.com/documentation/js-api/functions/initasynccompiler/
|
|
12
|
+
*/
|
|
13
|
+
export declare const getAsyncCompiler: ({ root, implementation, }: ImplementationDetails) => Promise<AsyncCompiler | Pick<Implementation, "compileAsync">>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { alerts } from "../core/index.js";
|
|
2
|
+
const cache = {};
|
|
3
|
+
export const disposeAllCompilers = async () => {
|
|
4
|
+
// Cleanup of any cached compilers
|
|
5
|
+
cache.syncCompiler?.dispose();
|
|
6
|
+
cache.syncCompiler = undefined;
|
|
7
|
+
if (cache.asyncCompiler) {
|
|
8
|
+
const asyncCompiler = await cache.asyncCompiler;
|
|
9
|
+
await asyncCompiler?.dispose();
|
|
10
|
+
cache.asyncCompiler = undefined;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
process.on("SIGINT", async () => {
|
|
14
|
+
await disposeAllCompilers();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
});
|
|
17
|
+
export const getSyncCompiler = ({ root, implementation, }) => {
|
|
18
|
+
/* When calling the compile functions multiple times, using a compiler instance with the sass-embedded npm package
|
|
19
|
+
is much faster than using the top-level compilation methods or the sass npm package */
|
|
20
|
+
const useCompilerInstance = implementation === "sass-embedded";
|
|
21
|
+
if (!useCompilerInstance) {
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
if (cache.syncCompiler) {
|
|
25
|
+
return cache.syncCompiler;
|
|
26
|
+
}
|
|
27
|
+
cache.syncCompiler = root.initCompiler();
|
|
28
|
+
alerts.info(`Using '${implementation}' Sync compiler instance for improved performance.`);
|
|
29
|
+
return cache.syncCompiler;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* When calling the compile functions multiple times, using a compiler instance with the sass-embedded npm package
|
|
33
|
+
* is much faster than using the top-level compilation methods or the sass npm package.
|
|
34
|
+
* @see https://sass-lang.com/documentation/js-api/functions/initasynccompiler/
|
|
35
|
+
*/
|
|
36
|
+
export const getAsyncCompiler = ({ root, implementation, }) => {
|
|
37
|
+
/* When calling the compile functions multiple times, using a compiler instance with the sass-embedded npm package
|
|
38
|
+
is much faster than using the top-level compilation methods or the sass npm package */
|
|
39
|
+
const useCompilerInstance = implementation === "sass-embedded";
|
|
40
|
+
if (!useCompilerInstance) {
|
|
41
|
+
return Promise.resolve(root);
|
|
42
|
+
}
|
|
43
|
+
if (cache.asyncCompiler) {
|
|
44
|
+
return cache.asyncCompiler;
|
|
45
|
+
}
|
|
46
|
+
cache.asyncCompiler = root.initAsyncCompiler();
|
|
47
|
+
alerts.info(`Using '${implementation}' Async compiler instance for improved performance.`);
|
|
48
|
+
return cache.asyncCompiler;
|
|
49
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sass, { AsyncCompiler as SassAsyncCompiler, Compiler as SassCompiler } from "sass";
|
|
2
|
+
import sassEmbedded, { AsyncCompiler as SassEmbeddedAsyncCompiler, Compiler as SassEmbeddedCompiler } from "sass-embedded";
|
|
3
|
+
/**
|
|
4
|
+
* A list of all possible SASS package implementations that can be used to
|
|
5
|
+
* perform the compilation and parsing of the SASS files. The expectation is
|
|
6
|
+
* that they provide a nearly identical API so they can be swapped out but
|
|
7
|
+
* all of the same logic can be reused.
|
|
8
|
+
*/
|
|
9
|
+
export declare const IMPLEMENTATIONS: readonly ["sass", "sass-embedded"];
|
|
10
|
+
export type Implementations = (typeof IMPLEMENTATIONS)[number];
|
|
11
|
+
export type Implementation = typeof sass | typeof sassEmbedded;
|
|
12
|
+
export type SyncCompiler = SassCompiler | SassEmbeddedCompiler;
|
|
13
|
+
export type AsyncCompiler = SassAsyncCompiler | SassEmbeddedAsyncCompiler;
|
|
14
|
+
/**
|
|
15
|
+
* Determine which default implementation to use by checking which packages
|
|
16
|
+
* are actually installed and available to use.
|
|
17
|
+
*
|
|
18
|
+
* @param resolver DO NOT USE - this is unfortunately necessary only for testing.
|
|
19
|
+
*/
|
|
20
|
+
export declare const getDefaultImplementation: (resolver?: RequireResolve) => Implementations;
|
|
21
|
+
/**
|
|
22
|
+
* Retrieve the desired implementation.
|
|
23
|
+
*
|
|
24
|
+
* @param implementation the desired implementation.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getImplementation: (implementation?: Implementations | string) => Promise<Implementation>;
|
|
27
|
+
export declare const getImplementationAsync: (implementation?: Implementations | string) => Promise<Implementation>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import sass, { AsyncCompiler as SassAsyncCompiler, Compiler as SassCompiler, } from "sass";
|
|
2
|
+
import sassEmbedded, { AsyncCompiler as SassEmbeddedAsyncCompiler, Compiler as SassEmbeddedCompiler, } from "sass-embedded";
|
|
3
|
+
/**
|
|
4
|
+
* A list of all possible SASS package implementations that can be used to
|
|
5
|
+
* perform the compilation and parsing of the SASS files. The expectation is
|
|
6
|
+
* that they provide a nearly identical API so they can be swapped out but
|
|
7
|
+
* all of the same logic can be reused.
|
|
8
|
+
*/
|
|
9
|
+
export const IMPLEMENTATIONS = ["sass", "sass-embedded"];
|
|
10
|
+
/**
|
|
11
|
+
* Determine which default implementation to use by checking which packages
|
|
12
|
+
* are actually installed and available to use.
|
|
13
|
+
*
|
|
14
|
+
* @param resolver DO NOT USE - this is unfortunately necessary only for testing.
|
|
15
|
+
*/
|
|
16
|
+
export const getDefaultImplementation = (resolver) => {
|
|
17
|
+
let pkg = "sass";
|
|
18
|
+
try {
|
|
19
|
+
resolver ? resolver("sass") : import("sass");
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
try {
|
|
23
|
+
resolver ? resolver("sass-embedded") : import("sass-embedded");
|
|
24
|
+
pkg = "sass-embedded";
|
|
25
|
+
}
|
|
26
|
+
catch (ignoreError) {
|
|
27
|
+
pkg = "sass";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return pkg;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve the desired implementation.
|
|
34
|
+
*
|
|
35
|
+
* @param implementation the desired implementation.
|
|
36
|
+
*/
|
|
37
|
+
export const getImplementation = async (
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
39
|
+
implementation = "sass") => {
|
|
40
|
+
return getImplementationAsync(implementation);
|
|
41
|
+
};
|
|
42
|
+
export const getImplementationAsync = (
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
44
|
+
implementation = "sass") => {
|
|
45
|
+
if (implementation === "sass") {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
47
|
+
return import("sass");
|
|
48
|
+
}
|
|
49
|
+
if (implementation === "sass-embedded") {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
51
|
+
return import("sass-embedded");
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`'${implementation}' Implementation is not supported`);
|
|
54
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CLIOptions, type ConfigOptions } from "./core/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load a custom config file in the project root directory with any options for this package.
|
|
4
|
+
*
|
|
5
|
+
* This supports config files in the following formats and order:
|
|
6
|
+
* - Named `config` export: `export const config = {}`
|
|
7
|
+
* - Default export: `export default {}`
|
|
8
|
+
* - `module.exports = {}`
|
|
9
|
+
*/
|
|
10
|
+
export declare const loadConfig: () => Promise<Record<string, never> | ConfigOptions>;
|
|
11
|
+
export declare const DEFAULT_OPTIONS: CLIOptions;
|
|
12
|
+
/**
|
|
13
|
+
* Given both the CLI and config file options merge into a single options object.
|
|
14
|
+
*
|
|
15
|
+
* When possible, CLI options will override config file options.
|
|
16
|
+
*
|
|
17
|
+
* Some options are only available in the config file. For example, a custom function can't
|
|
18
|
+
* be easily defined via the CLI so some complex options are only available in the config file.
|
|
19
|
+
*/
|
|
20
|
+
export declare const mergeOptions: (cliOptions: Partial<CLIOptions>, configOptions: Partial<ConfigOptions>) => ConfigOptions;
|
package/dist/lib/load.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { bundleRequire } from "bundle-require";
|
|
2
|
+
import JoyCon from "joycon";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { alerts } from "./core/alerts.js";
|
|
5
|
+
import {} from "./core/types.js";
|
|
6
|
+
import { getDefaultImplementation } from "./implementations/implementations.js";
|
|
7
|
+
import { nameFormatDefault } from "./sass/file-to-class-names.js";
|
|
8
|
+
import { bannerTypeDefault, exportTypeDefault, exportTypeInterfaceDefault, exportTypeNameDefault, logLevelDefault, quoteTypeDefault, } from "./typescript/index.js";
|
|
9
|
+
const VALID_CONFIG_FILES = [
|
|
10
|
+
"typed-scss-modules.config.ts",
|
|
11
|
+
"typed-scss-modules.config.mjs",
|
|
12
|
+
"typed-scss-modules.config.js",
|
|
13
|
+
];
|
|
14
|
+
const joycon = new JoyCon.default();
|
|
15
|
+
/**
|
|
16
|
+
* Load a custom config file in the project root directory with any options for this package.
|
|
17
|
+
*
|
|
18
|
+
* This supports config files in the following formats and order:
|
|
19
|
+
* - Named `config` export: `export const config = {}`
|
|
20
|
+
* - Default export: `export default {}`
|
|
21
|
+
* - `module.exports = {}`
|
|
22
|
+
*/
|
|
23
|
+
export const loadConfig = async () => {
|
|
24
|
+
const CURRENT_WORKING_DIRECTORY = process.cwd();
|
|
25
|
+
const configPath = await joycon.resolve(VALID_CONFIG_FILES, CURRENT_WORKING_DIRECTORY, path.parse(CURRENT_WORKING_DIRECTORY).root);
|
|
26
|
+
if (configPath) {
|
|
27
|
+
try {
|
|
28
|
+
const configModule = await bundleRequire({
|
|
29
|
+
filepath: configPath,
|
|
30
|
+
});
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
32
|
+
const config =
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
34
|
+
configModule.mod.config || configModule.mod.default || configModule.mod;
|
|
35
|
+
return config;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
alerts.error(
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
40
|
+
`An error occurred loading the config file "${configPath}":\n${error}`);
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
};
|
|
46
|
+
// Default values for all options that need defaults.
|
|
47
|
+
export const DEFAULT_OPTIONS = {
|
|
48
|
+
nameFormat: [nameFormatDefault],
|
|
49
|
+
implementation: getDefaultImplementation(),
|
|
50
|
+
exportType: exportTypeDefault,
|
|
51
|
+
exportTypeName: exportTypeNameDefault,
|
|
52
|
+
exportTypeInterface: exportTypeInterfaceDefault,
|
|
53
|
+
watch: false,
|
|
54
|
+
ignoreInitial: false,
|
|
55
|
+
listDifferent: false,
|
|
56
|
+
ignore: [],
|
|
57
|
+
quoteType: quoteTypeDefault,
|
|
58
|
+
updateStaleOnly: false,
|
|
59
|
+
logLevel: logLevelDefault,
|
|
60
|
+
banner: bannerTypeDefault,
|
|
61
|
+
outputFolder: null,
|
|
62
|
+
allowArbitraryExtensions: false,
|
|
63
|
+
silenceDeprecations: ["legacy-js-api"],
|
|
64
|
+
};
|
|
65
|
+
const removedUndefinedValues = (obj) => {
|
|
66
|
+
for (const key in obj) {
|
|
67
|
+
if (obj[key] === undefined) {
|
|
68
|
+
delete obj[key];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return obj;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Given both the CLI and config file options merge into a single options object.
|
|
75
|
+
*
|
|
76
|
+
* When possible, CLI options will override config file options.
|
|
77
|
+
*
|
|
78
|
+
* Some options are only available in the config file. For example, a custom function can't
|
|
79
|
+
* be easily defined via the CLI so some complex options are only available in the config file.
|
|
80
|
+
*/
|
|
81
|
+
export const mergeOptions = (cliOptions, configOptions) => {
|
|
82
|
+
return {
|
|
83
|
+
...DEFAULT_OPTIONS,
|
|
84
|
+
...configOptions,
|
|
85
|
+
...removedUndefinedValues(cliOptions),
|
|
86
|
+
};
|
|
87
|
+
};
|
package/dist/lib/main.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import slash from "slash";
|
|
4
|
+
import { generate, listDifferent, setAlertsLogLevel, watch, } from "./core/index.js";
|
|
5
|
+
import { disposeAllCompilers } from "./implementations/compilers.js";
|
|
6
|
+
import { loadConfig, mergeOptions } from "./load.js";
|
|
7
|
+
export const main = async (pattern, cliOptions) => {
|
|
8
|
+
const configOptions = await loadConfig();
|
|
9
|
+
const options = mergeOptions(cliOptions, configOptions);
|
|
10
|
+
setAlertsLogLevel(options.logLevel);
|
|
11
|
+
// When the provided pattern is a directory construct the proper glob to find
|
|
12
|
+
// all .scss files within that directory. Also, add the directory to the
|
|
13
|
+
// included paths so any imported with a path relative to the root of the
|
|
14
|
+
// project still works as expected without adding many include paths.
|
|
15
|
+
if (fs.existsSync(pattern) && fs.lstatSync(pattern).isDirectory()) {
|
|
16
|
+
if (Array.isArray(options.loadPaths)) {
|
|
17
|
+
options.loadPaths.push(pattern);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
options.loadPaths = [pattern];
|
|
21
|
+
}
|
|
22
|
+
// When the pattern provide is a directory, assume all .scss files within.
|
|
23
|
+
pattern = slash(path.resolve(pattern, "**/*.scss"));
|
|
24
|
+
}
|
|
25
|
+
if (options.listDifferent) {
|
|
26
|
+
await listDifferent(pattern, options);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (options.watch) {
|
|
30
|
+
watch(pattern, options);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await generate(pattern, options).finally(() => disposeAllCompilers());
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function canResolvePrettier(): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// this is only extracted to a module to mock in testing as require.resolve can't be mocked.
|
|
2
|
+
// https://github.com/facebook/jest/issues/9543
|
|
3
|
+
export function canResolvePrettier() {
|
|
4
|
+
try {
|
|
5
|
+
import("prettier");
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch (_error) {
|
|
9
|
+
// cannot resolve prettier
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Try to load prettier and config from project to format input,
|
|
3
|
+
* fall back to input if prettier is not found or failed
|
|
4
|
+
*
|
|
5
|
+
* @param {file} file
|
|
6
|
+
* @param {string} input
|
|
7
|
+
*/
|
|
8
|
+
export declare const attemptPrettier: (file: string, input: string) => Promise<string>;
|