@salty-css/core 0.2.0-alpha.2 → 0.2.0-fix-astro-fixes-06-2026.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/.saltyrc.schema.json +10 -0
- package/bin/main.cjs +31 -12
- package/bin/main.js +31 -12
- package/bin/path-defaults.d.ts +20 -0
- package/bin/saltyrc.d.ts +3 -2
- package/compiler/salty-compiler.cjs +29 -3
- package/compiler/salty-compiler.d.ts +10 -0
- package/compiler/salty-compiler.js +29 -3
- package/glob-match-C28iYp1A.js +74 -0
- package/glob-match-CT1uFlp-.cjs +73 -0
- package/package.json +1 -1
- package/types/cli-types.d.ts +12 -0
- package/util/glob-match.d.ts +35 -0
- package/util/index.cjs +5 -0
- package/util/index.d.ts +1 -0
- package/util/index.js +5 -0
package/.saltyrc.schema.json
CHANGED
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
"saltygenDir": {
|
|
39
39
|
"type": "string",
|
|
40
40
|
"description": "Directory where the salty css files will be generated to, relative to the project root."
|
|
41
|
+
},
|
|
42
|
+
"include": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"description": "Optional glob patterns (relative to the project root) that limit which files the compiler scans. When set and non-empty, only matching files are compiled. When omitted, the whole project is scanned. Supports **, * and ?.",
|
|
45
|
+
"items": { "type": "string" }
|
|
46
|
+
},
|
|
47
|
+
"exclude": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"description": "Optional glob patterns (relative to the project root) skipped by the compiler, in addition to the always-skipped node_modules and saltygen folders. Exclusions take precedence over include. Supports **, * and ?.",
|
|
50
|
+
"items": { "type": "string" }
|
|
41
51
|
}
|
|
42
52
|
},
|
|
43
53
|
"required": ["dir", "framework"]
|
package/bin/main.cjs
CHANGED
|
@@ -85,14 +85,15 @@ const getDefaultProject = async (rootDir = process.cwd()) => {
|
|
|
85
85
|
const rc = await readRc(rootDir);
|
|
86
86
|
return rc.defaultProject;
|
|
87
87
|
};
|
|
88
|
-
const upsertProjectInRc = (existingRaw, relativeProjectPath, framework) => {
|
|
88
|
+
const upsertProjectInRc = (existingRaw, relativeProjectPath, framework, pathDefaults = {}) => {
|
|
89
89
|
const projectPath = path.join(relativeProjectPath, framework.srcDirectory);
|
|
90
|
+
const newEntry = { dir: projectPath, framework: framework.name, ...pathDefaults };
|
|
90
91
|
if (existingRaw === void 0) {
|
|
91
92
|
const fresh = {
|
|
92
93
|
$schema: SALTYRC_SCHEMA,
|
|
93
94
|
info: SALTYRC_INFO,
|
|
94
95
|
defaultProject: projectPath,
|
|
95
|
-
projects: [
|
|
96
|
+
projects: [newEntry]
|
|
96
97
|
};
|
|
97
98
|
return { content: JSON.stringify(fresh, null, 2), changed: true, created: true };
|
|
98
99
|
}
|
|
@@ -100,15 +101,15 @@ const upsertProjectInRc = (existingRaw, relativeProjectPath, framework) => {
|
|
|
100
101
|
const projects = rc.projects || [];
|
|
101
102
|
const exists = projects.some((p) => p.dir === projectPath);
|
|
102
103
|
if (exists) return { content: existingRaw, changed: false, created: false };
|
|
103
|
-
projects.push(
|
|
104
|
+
projects.push(newEntry);
|
|
104
105
|
rc.projects = [...projects];
|
|
105
106
|
const next = JSON.stringify(rc, null, 2);
|
|
106
107
|
return { content: next, changed: next !== existingRaw, created: false };
|
|
107
108
|
};
|
|
108
|
-
const writeProjectToRc = async (cwd, relativeProjectPath, framework) => {
|
|
109
|
+
const writeProjectToRc = async (cwd, relativeProjectPath, framework, pathDefaults = {}) => {
|
|
109
110
|
const path2 = saltyrcPath(cwd);
|
|
110
111
|
const existing = await readRawRc(cwd);
|
|
111
|
-
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework);
|
|
112
|
+
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework, pathDefaults);
|
|
112
113
|
if (!changed) return false;
|
|
113
114
|
if (created) compiler_saltyCompiler.logger.info("Creating file: " + path2);
|
|
114
115
|
else compiler_saltyCompiler.logger.info("Edit file: " + path2);
|
|
@@ -580,6 +581,18 @@ const applyIntegrationPlans = async (planned) => {
|
|
|
580
581
|
}
|
|
581
582
|
return results;
|
|
582
583
|
};
|
|
584
|
+
const BUILD_OUTPUTS = {
|
|
585
|
+
vite: ["dist/**"],
|
|
586
|
+
next: [".next/**", "out/**"]
|
|
587
|
+
};
|
|
588
|
+
const computePathDefaults = ({ framework, integrations, hasSrcDir }) => {
|
|
589
|
+
if (framework === "astro") return {};
|
|
590
|
+
const exclude = [...new Set(integrations.flatMap((name) => BUILD_OUTPUTS[name] ?? []))];
|
|
591
|
+
const result = {};
|
|
592
|
+
if (hasSrcDir) result.include = ["src/**"];
|
|
593
|
+
if (exclude.length) result.exclude = exclude;
|
|
594
|
+
return result;
|
|
595
|
+
};
|
|
583
596
|
const writeProjectFile = async (projectDir, fileName, content) => {
|
|
584
597
|
const filePath = path.join(projectDir, fileName);
|
|
585
598
|
if (fs.existsSync(filePath)) {
|
|
@@ -600,7 +613,7 @@ const ensureGitignoreSaltygen = async (rootDir) => {
|
|
|
600
613
|
compiler_saltyCompiler.logger.info("Edit file: " + path$1);
|
|
601
614
|
await promises.writeFile(path$1, existing + "\n\n# Salty-CSS\nsaltygen\n");
|
|
602
615
|
};
|
|
603
|
-
const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
616
|
+
const importSaltygenIntoCss = async (projectDir, saltygenDir, explicitCssFile) => {
|
|
604
617
|
const target = explicitCssFile ?? await findGlobalCssFile(projectDir);
|
|
605
618
|
if (!target) {
|
|
606
619
|
compiler_saltyCompiler.logger.warn("Could not find a CSS file to import the generated CSS. Please add it manually.");
|
|
@@ -611,7 +624,7 @@ const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
|
611
624
|
if (cssFileContent === void 0) return;
|
|
612
625
|
if (cssFileContent.includes("saltygen")) return;
|
|
613
626
|
const cssFileFolder = path.join(cssFilePath, "..");
|
|
614
|
-
const relPath = path.relative(cssFileFolder, path.join(
|
|
627
|
+
const relPath = path.relative(cssFileFolder, path.join(saltygenDir, "saltygen/index.css"));
|
|
615
628
|
compiler_saltyCompiler.logger.info("Adding global import statement to CSS file: " + cssFilePath);
|
|
616
629
|
await promises.writeFile(cssFilePath, `@import '${relPath}';
|
|
617
630
|
` + cssFileContent);
|
|
@@ -636,6 +649,7 @@ const registerInitCommand = (program) => {
|
|
|
636
649
|
compiler_saltyCompiler.logger.info("Initializing a new Salty-CSS project!");
|
|
637
650
|
const framework = await detectFramework(ctx);
|
|
638
651
|
compiler_saltyCompiler.logger.info(`Detected framework: ${framework.name}`);
|
|
652
|
+
const sourceDir = path.join(ctx.projectDir, framework.srcDirectory);
|
|
639
653
|
const plannedIntegrations = await planIntegrations(ctx);
|
|
640
654
|
if (!ctx.skipInstall) {
|
|
641
655
|
const packages = [
|
|
@@ -647,15 +661,20 @@ const registerInitCommand = (program) => {
|
|
|
647
661
|
await npmInstall(...packages);
|
|
648
662
|
}
|
|
649
663
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
650
|
-
await promises.mkdir(
|
|
651
|
-
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(
|
|
652
|
-
|
|
664
|
+
await promises.mkdir(sourceDir, { recursive: true });
|
|
665
|
+
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(sourceDir, fileName, content)));
|
|
666
|
+
const pathDefaults = computePathDefaults({
|
|
667
|
+
framework: framework.name,
|
|
668
|
+
integrations: plannedIntegrations.map((p) => p.name),
|
|
669
|
+
hasSrcDir: fs.existsSync(path.join(ctx.projectDir, "src"))
|
|
670
|
+
});
|
|
671
|
+
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework, pathDefaults);
|
|
653
672
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
654
|
-
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
673
|
+
await importSaltygenIntoCss(ctx.projectDir, sourceDir, opts.cssFile);
|
|
655
674
|
await applyIntegrationPlans(plannedIntegrations);
|
|
656
675
|
await wirePrepareScript();
|
|
657
676
|
compiler_saltyCompiler.logger.info("Running the build to generate initial CSS...");
|
|
658
|
-
const compiler = new compiler_saltyCompiler.SaltyCompiler(
|
|
677
|
+
const compiler = new compiler_saltyCompiler.SaltyCompiler(sourceDir);
|
|
659
678
|
await compiler.generateCss();
|
|
660
679
|
compiler_saltyCompiler.logger.info("🎉 Salty CSS project initialized successfully!");
|
|
661
680
|
compiler_saltyCompiler.logger.info("Next steps:");
|
package/bin/main.js
CHANGED
|
@@ -82,14 +82,15 @@ const getDefaultProject = async (rootDir = process.cwd()) => {
|
|
|
82
82
|
const rc = await readRc(rootDir);
|
|
83
83
|
return rc.defaultProject;
|
|
84
84
|
};
|
|
85
|
-
const upsertProjectInRc = (existingRaw, relativeProjectPath, framework) => {
|
|
85
|
+
const upsertProjectInRc = (existingRaw, relativeProjectPath, framework, pathDefaults = {}) => {
|
|
86
86
|
const projectPath = join(relativeProjectPath, framework.srcDirectory);
|
|
87
|
+
const newEntry = { dir: projectPath, framework: framework.name, ...pathDefaults };
|
|
87
88
|
if (existingRaw === void 0) {
|
|
88
89
|
const fresh = {
|
|
89
90
|
$schema: SALTYRC_SCHEMA,
|
|
90
91
|
info: SALTYRC_INFO,
|
|
91
92
|
defaultProject: projectPath,
|
|
92
|
-
projects: [
|
|
93
|
+
projects: [newEntry]
|
|
93
94
|
};
|
|
94
95
|
return { content: JSON.stringify(fresh, null, 2), changed: true, created: true };
|
|
95
96
|
}
|
|
@@ -97,15 +98,15 @@ const upsertProjectInRc = (existingRaw, relativeProjectPath, framework) => {
|
|
|
97
98
|
const projects = rc.projects || [];
|
|
98
99
|
const exists = projects.some((p) => p.dir === projectPath);
|
|
99
100
|
if (exists) return { content: existingRaw, changed: false, created: false };
|
|
100
|
-
projects.push(
|
|
101
|
+
projects.push(newEntry);
|
|
101
102
|
rc.projects = [...projects];
|
|
102
103
|
const next = JSON.stringify(rc, null, 2);
|
|
103
104
|
return { content: next, changed: next !== existingRaw, created: false };
|
|
104
105
|
};
|
|
105
|
-
const writeProjectToRc = async (cwd, relativeProjectPath, framework) => {
|
|
106
|
+
const writeProjectToRc = async (cwd, relativeProjectPath, framework, pathDefaults = {}) => {
|
|
106
107
|
const path = saltyrcPath(cwd);
|
|
107
108
|
const existing = await readRawRc(cwd);
|
|
108
|
-
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework);
|
|
109
|
+
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework, pathDefaults);
|
|
109
110
|
if (!changed) return false;
|
|
110
111
|
if (created) logger.info("Creating file: " + path);
|
|
111
112
|
else logger.info("Edit file: " + path);
|
|
@@ -577,6 +578,18 @@ const applyIntegrationPlans = async (planned) => {
|
|
|
577
578
|
}
|
|
578
579
|
return results;
|
|
579
580
|
};
|
|
581
|
+
const BUILD_OUTPUTS = {
|
|
582
|
+
vite: ["dist/**"],
|
|
583
|
+
next: [".next/**", "out/**"]
|
|
584
|
+
};
|
|
585
|
+
const computePathDefaults = ({ framework, integrations, hasSrcDir }) => {
|
|
586
|
+
if (framework === "astro") return {};
|
|
587
|
+
const exclude = [...new Set(integrations.flatMap((name) => BUILD_OUTPUTS[name] ?? []))];
|
|
588
|
+
const result = {};
|
|
589
|
+
if (hasSrcDir) result.include = ["src/**"];
|
|
590
|
+
if (exclude.length) result.exclude = exclude;
|
|
591
|
+
return result;
|
|
592
|
+
};
|
|
580
593
|
const writeProjectFile = async (projectDir, fileName, content) => {
|
|
581
594
|
const filePath = join(projectDir, fileName);
|
|
582
595
|
if (existsSync(filePath)) {
|
|
@@ -597,7 +610,7 @@ const ensureGitignoreSaltygen = async (rootDir) => {
|
|
|
597
610
|
logger.info("Edit file: " + path);
|
|
598
611
|
await writeFile(path, existing + "\n\n# Salty-CSS\nsaltygen\n");
|
|
599
612
|
};
|
|
600
|
-
const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
613
|
+
const importSaltygenIntoCss = async (projectDir, saltygenDir, explicitCssFile) => {
|
|
601
614
|
const target = explicitCssFile ?? await findGlobalCssFile(projectDir);
|
|
602
615
|
if (!target) {
|
|
603
616
|
logger.warn("Could not find a CSS file to import the generated CSS. Please add it manually.");
|
|
@@ -608,7 +621,7 @@ const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
|
608
621
|
if (cssFileContent === void 0) return;
|
|
609
622
|
if (cssFileContent.includes("saltygen")) return;
|
|
610
623
|
const cssFileFolder = join(cssFilePath, "..");
|
|
611
|
-
const relPath = relative(cssFileFolder, join(
|
|
624
|
+
const relPath = relative(cssFileFolder, join(saltygenDir, "saltygen/index.css"));
|
|
612
625
|
logger.info("Adding global import statement to CSS file: " + cssFilePath);
|
|
613
626
|
await writeFile(cssFilePath, `@import '${relPath}';
|
|
614
627
|
` + cssFileContent);
|
|
@@ -633,6 +646,7 @@ const registerInitCommand = (program) => {
|
|
|
633
646
|
logger.info("Initializing a new Salty-CSS project!");
|
|
634
647
|
const framework = await detectFramework(ctx);
|
|
635
648
|
logger.info(`Detected framework: ${framework.name}`);
|
|
649
|
+
const sourceDir = join(ctx.projectDir, framework.srcDirectory);
|
|
636
650
|
const plannedIntegrations = await planIntegrations(ctx);
|
|
637
651
|
if (!ctx.skipInstall) {
|
|
638
652
|
const packages = [
|
|
@@ -644,15 +658,20 @@ const registerInitCommand = (program) => {
|
|
|
644
658
|
await npmInstall(...packages);
|
|
645
659
|
}
|
|
646
660
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
647
|
-
await mkdir(
|
|
648
|
-
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(
|
|
649
|
-
|
|
661
|
+
await mkdir(sourceDir, { recursive: true });
|
|
662
|
+
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(sourceDir, fileName, content)));
|
|
663
|
+
const pathDefaults = computePathDefaults({
|
|
664
|
+
framework: framework.name,
|
|
665
|
+
integrations: plannedIntegrations.map((p) => p.name),
|
|
666
|
+
hasSrcDir: existsSync(join(ctx.projectDir, "src"))
|
|
667
|
+
});
|
|
668
|
+
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework, pathDefaults);
|
|
650
669
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
651
|
-
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
670
|
+
await importSaltygenIntoCss(ctx.projectDir, sourceDir, opts.cssFile);
|
|
652
671
|
await applyIntegrationPlans(plannedIntegrations);
|
|
653
672
|
await wirePrepareScript();
|
|
654
673
|
logger.info("Running the build to generate initial CSS...");
|
|
655
|
-
const compiler = new SaltyCompiler(
|
|
674
|
+
const compiler = new SaltyCompiler(sourceDir);
|
|
656
675
|
await compiler.generateCss();
|
|
657
676
|
logger.info("🎉 Salty CSS project initialized successfully!");
|
|
658
677
|
logger.info("Next steps:");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FrameworkName } from './frameworks';
|
|
2
|
+
export interface PathDefaultsInput {
|
|
3
|
+
framework: FrameworkName;
|
|
4
|
+
/** Names of build integrations detected for the project, e.g. ['vite'] or ['next','eslint']. */
|
|
5
|
+
integrations: string[];
|
|
6
|
+
/** Whether a `src/` directory exists at the project root. */
|
|
7
|
+
hasSrcDir: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PathDefaults {
|
|
10
|
+
include?: string[];
|
|
11
|
+
exclude?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compute sensible default `include` / `exclude` globs for a freshly initialized project.
|
|
15
|
+
* - Astro is already scoped to `src/` by its plugin (it roots the compiler there), so it
|
|
16
|
+
* gets no defaults.
|
|
17
|
+
* - React / Vite / Next: exclude known build outputs, and narrow the walk to `src/**` only
|
|
18
|
+
* when a `src/` folder actually exists (projects with root-level salty files keep the full walk).
|
|
19
|
+
*/
|
|
20
|
+
export declare const computePathDefaults: ({ framework, integrations, hasSrcDir }: PathDefaultsInput) => PathDefaults;
|
package/bin/saltyrc.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RCFile } from '../types/cli-types';
|
|
2
2
|
import { FrameworkAdapter } from './frameworks';
|
|
3
|
+
import { PathDefaults } from './path-defaults';
|
|
3
4
|
export declare const SALTYRC_FILENAME = ".saltyrc.json";
|
|
4
5
|
export declare const SALTYRC_SCHEMA = "./node_modules/@salty-css/core/.saltyrc.schema.json";
|
|
5
6
|
export declare const SALTYRC_INFO = "This file is used to define projects and their configurations for Salty CSS cli. Do not delete, modify or add this file to .gitignore.";
|
|
@@ -18,7 +19,7 @@ export declare const getDefaultProject: (rootDir?: string) => Promise<string | u
|
|
|
18
19
|
* - If the project entry already exists, returns the same content (no-op).
|
|
19
20
|
* - Otherwise appends the new entry and returns updated content.
|
|
20
21
|
*/
|
|
21
|
-
export declare const upsertProjectInRc: (existingRaw: string | undefined, relativeProjectPath: string, framework: FrameworkAdapter) => {
|
|
22
|
+
export declare const upsertProjectInRc: (existingRaw: string | undefined, relativeProjectPath: string, framework: FrameworkAdapter, pathDefaults?: PathDefaults) => {
|
|
22
23
|
content: string;
|
|
23
24
|
changed: boolean;
|
|
24
25
|
created: boolean;
|
|
@@ -27,5 +28,5 @@ export declare const upsertProjectInRc: (existingRaw: string | undefined, relati
|
|
|
27
28
|
* Writes the saltyrc file, creating or updating the project entry for the given dir.
|
|
28
29
|
* Returns true when a write occurred.
|
|
29
30
|
*/
|
|
30
|
-
export declare const writeProjectToRc: (cwd: string, relativeProjectPath: string, framework: FrameworkAdapter) => Promise<boolean>;
|
|
31
|
+
export declare const writeProjectToRc: (cwd: string, relativeProjectPath: string, framework: FrameworkAdapter, pathDefaults?: PathDefaults) => Promise<boolean>;
|
|
31
32
|
export declare const getProjectFramework: (rc: RCFile, relativeProjectPath: string) => string | undefined;
|
|
@@ -11,6 +11,7 @@ const fs = require("fs");
|
|
|
11
11
|
const child_process = require("child_process");
|
|
12
12
|
const compiler_helpers = require("./helpers.cjs");
|
|
13
13
|
const toHash = require("../to-hash-DT2ImSPA.cjs");
|
|
14
|
+
const globMatch = require("../glob-match-CT1uFlp-.cjs");
|
|
14
15
|
const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
|
|
15
16
|
const module$1 = require("module");
|
|
16
17
|
const parseStyles = require("../parse-styles-DWT4tDyS.cjs");
|
|
@@ -196,6 +197,23 @@ class SaltyCompiler {
|
|
|
196
197
|
if (!projectConfig) return (_b = rcFile.projects) == null ? void 0 : _b.find((project) => project.dir === rcFile.defaultProject);
|
|
197
198
|
return projectConfig;
|
|
198
199
|
});
|
|
200
|
+
/**
|
|
201
|
+
* Read the optional `include` / `exclude` glob filters for the current project from
|
|
202
|
+
* `.saltyrc.json`. Used to scope which files the compiler discovers and watches.
|
|
203
|
+
*/
|
|
204
|
+
__publicField(this, "getPathFilter", async () => {
|
|
205
|
+
const projectConfig = await this.getRCProjectConfig(this.projectRootDir);
|
|
206
|
+
return { include: projectConfig == null ? void 0 : projectConfig.include, exclude: projectConfig == null ? void 0 : projectConfig.exclude };
|
|
207
|
+
});
|
|
208
|
+
/**
|
|
209
|
+
* Whether a file should be compiled: it must be a salty file and pass the project's
|
|
210
|
+
* `include` / `exclude` filters. `relPath` is the path relative to the project root.
|
|
211
|
+
*/
|
|
212
|
+
__publicField(this, "isAllowedSaltyFile", (file, filter) => {
|
|
213
|
+
if (!compiler_helpers.isSaltyFile(file)) return false;
|
|
214
|
+
const relPath = globMatch.normalizePath(path.relative(this.projectRootDir, file));
|
|
215
|
+
return globMatch.isPathAllowed(relPath, filter);
|
|
216
|
+
});
|
|
199
217
|
__publicField(this, "getExternalModules", (coreConfigPath) => {
|
|
200
218
|
if (this.cache.externalModules.length > 0) return this.cache.externalModules;
|
|
201
219
|
const content = fs.readFileSync(coreConfigPath, "utf8");
|
|
@@ -286,16 +304,23 @@ ${currentFile}`;
|
|
|
286
304
|
if (clean) clearDistDir();
|
|
287
305
|
const files = /* @__PURE__ */ new Set();
|
|
288
306
|
const configFiles = /* @__PURE__ */ new Set();
|
|
307
|
+
const pathFilter = await this.getPathFilter();
|
|
308
|
+
const { exclude } = pathFilter;
|
|
309
|
+
const projectRootDir = this.projectRootDir;
|
|
289
310
|
async function collectFiles(src) {
|
|
290
311
|
const foldersToSkip = ["node_modules", "saltygen"];
|
|
291
312
|
const stats = fs.statSync(src);
|
|
292
313
|
if (stats.isDirectory()) {
|
|
293
|
-
const files2 = fs.readdirSync(src);
|
|
294
314
|
const shouldSkip = foldersToSkip.some((folder) => src.includes(folder));
|
|
295
315
|
if (shouldSkip) return;
|
|
316
|
+
if (exclude == null ? void 0 : exclude.length) {
|
|
317
|
+
const relDir = globMatch.normalizePath(path.relative(projectRootDir, src));
|
|
318
|
+
if (relDir && !globMatch.isPathAllowed(relDir, { exclude })) return;
|
|
319
|
+
}
|
|
320
|
+
const files2 = fs.readdirSync(src);
|
|
296
321
|
await Promise.all(files2.map((file) => collectFiles(path.join(src, file))));
|
|
297
322
|
} else if (stats.isFile()) {
|
|
298
|
-
const validFile = compiler_helpers.isSaltyFile(src);
|
|
323
|
+
const validFile = compiler_helpers.isSaltyFile(src) && globMatch.isPathAllowed(globMatch.normalizePath(path.relative(projectRootDir, src)), pathFilter);
|
|
299
324
|
if (validFile) {
|
|
300
325
|
files.add(src);
|
|
301
326
|
const contents = fs.readFileSync(src, "utf8");
|
|
@@ -704,7 +729,8 @@ ${css}
|
|
|
704
729
|
__publicField(this, "generateFile", async (file) => {
|
|
705
730
|
try {
|
|
706
731
|
const destDir = await this.getDestDir();
|
|
707
|
-
const
|
|
732
|
+
const pathFilter = await this.getPathFilter();
|
|
733
|
+
const validFile = this.isAllowedSaltyFile(file, pathFilter);
|
|
708
734
|
if (validFile) {
|
|
709
735
|
const cssFiles = [];
|
|
710
736
|
const config = await this.getConfig();
|
|
@@ -20,6 +20,16 @@ export declare class SaltyCompiler {
|
|
|
20
20
|
* If no specific project configuration is found, it falls back to the default project.
|
|
21
21
|
*/
|
|
22
22
|
private getRCProjectConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Read the optional `include` / `exclude` glob filters for the current project from
|
|
25
|
+
* `.saltyrc.json`. Used to scope which files the compiler discovers and watches.
|
|
26
|
+
*/
|
|
27
|
+
private getPathFilter;
|
|
28
|
+
/**
|
|
29
|
+
* Whether a file should be compiled: it must be a salty file and pass the project's
|
|
30
|
+
* `include` / `exclude` filters. `relPath` is the path relative to the project root.
|
|
31
|
+
*/
|
|
32
|
+
private isAllowedSaltyFile;
|
|
23
33
|
private getExternalModules;
|
|
24
34
|
/**
|
|
25
35
|
* Get the destination directory for generated files based on the project configuration.
|
|
@@ -9,6 +9,7 @@ import { existsSync, mkdirSync, copyFileSync, readFileSync, statSync, readdirSyn
|
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
10
|
import { isSaltyFile, resolveExportValue, saltyFileExtensions } from "./helpers.js";
|
|
11
11
|
import { t as toHash, d as dashCase } from "../to-hash-DSoCPs8D.js";
|
|
12
|
+
import { n as normalizePath, i as isPathAllowed } from "../glob-match-C28iYp1A.js";
|
|
12
13
|
import { d as defineTemplates } from "../define-templates-CVhhgPnd.js";
|
|
13
14
|
import { createRequire } from "module";
|
|
14
15
|
import { p as parseAndJoinStyles, c as parseVariableTokens } from "../parse-styles-BlUpavVg.js";
|
|
@@ -176,6 +177,23 @@ class SaltyCompiler {
|
|
|
176
177
|
if (!projectConfig) return (_b = rcFile.projects) == null ? void 0 : _b.find((project) => project.dir === rcFile.defaultProject);
|
|
177
178
|
return projectConfig;
|
|
178
179
|
});
|
|
180
|
+
/**
|
|
181
|
+
* Read the optional `include` / `exclude` glob filters for the current project from
|
|
182
|
+
* `.saltyrc.json`. Used to scope which files the compiler discovers and watches.
|
|
183
|
+
*/
|
|
184
|
+
__publicField(this, "getPathFilter", async () => {
|
|
185
|
+
const projectConfig = await this.getRCProjectConfig(this.projectRootDir);
|
|
186
|
+
return { include: projectConfig == null ? void 0 : projectConfig.include, exclude: projectConfig == null ? void 0 : projectConfig.exclude };
|
|
187
|
+
});
|
|
188
|
+
/**
|
|
189
|
+
* Whether a file should be compiled: it must be a salty file and pass the project's
|
|
190
|
+
* `include` / `exclude` filters. `relPath` is the path relative to the project root.
|
|
191
|
+
*/
|
|
192
|
+
__publicField(this, "isAllowedSaltyFile", (file, filter) => {
|
|
193
|
+
if (!isSaltyFile(file)) return false;
|
|
194
|
+
const relPath = normalizePath(relative(this.projectRootDir, file));
|
|
195
|
+
return isPathAllowed(relPath, filter);
|
|
196
|
+
});
|
|
179
197
|
__publicField(this, "getExternalModules", (coreConfigPath) => {
|
|
180
198
|
if (this.cache.externalModules.length > 0) return this.cache.externalModules;
|
|
181
199
|
const content = readFileSync(coreConfigPath, "utf8");
|
|
@@ -266,16 +284,23 @@ ${currentFile}`;
|
|
|
266
284
|
if (clean) clearDistDir();
|
|
267
285
|
const files = /* @__PURE__ */ new Set();
|
|
268
286
|
const configFiles = /* @__PURE__ */ new Set();
|
|
287
|
+
const pathFilter = await this.getPathFilter();
|
|
288
|
+
const { exclude } = pathFilter;
|
|
289
|
+
const projectRootDir = this.projectRootDir;
|
|
269
290
|
async function collectFiles(src) {
|
|
270
291
|
const foldersToSkip = ["node_modules", "saltygen"];
|
|
271
292
|
const stats = statSync(src);
|
|
272
293
|
if (stats.isDirectory()) {
|
|
273
|
-
const files2 = readdirSync(src);
|
|
274
294
|
const shouldSkip = foldersToSkip.some((folder) => src.includes(folder));
|
|
275
295
|
if (shouldSkip) return;
|
|
296
|
+
if (exclude == null ? void 0 : exclude.length) {
|
|
297
|
+
const relDir = normalizePath(relative(projectRootDir, src));
|
|
298
|
+
if (relDir && !isPathAllowed(relDir, { exclude })) return;
|
|
299
|
+
}
|
|
300
|
+
const files2 = readdirSync(src);
|
|
276
301
|
await Promise.all(files2.map((file) => collectFiles(join(src, file))));
|
|
277
302
|
} else if (stats.isFile()) {
|
|
278
|
-
const validFile = isSaltyFile(src);
|
|
303
|
+
const validFile = isSaltyFile(src) && isPathAllowed(normalizePath(relative(projectRootDir, src)), pathFilter);
|
|
279
304
|
if (validFile) {
|
|
280
305
|
files.add(src);
|
|
281
306
|
const contents = readFileSync(src, "utf8");
|
|
@@ -684,7 +709,8 @@ ${css}
|
|
|
684
709
|
__publicField(this, "generateFile", async (file) => {
|
|
685
710
|
try {
|
|
686
711
|
const destDir = await this.getDestDir();
|
|
687
|
-
const
|
|
712
|
+
const pathFilter = await this.getPathFilter();
|
|
713
|
+
const validFile = this.isAllowedSaltyFile(file, pathFilter);
|
|
688
714
|
if (validFile) {
|
|
689
715
|
const cssFiles = [];
|
|
690
716
|
const config = await this.getConfig();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const normalizePath = (path) => path.replace(/\\/g, "/");
|
|
2
|
+
const tokenCache = /* @__PURE__ */ new Map();
|
|
3
|
+
const tokenize = (pattern) => {
|
|
4
|
+
const cached = tokenCache.get(pattern);
|
|
5
|
+
if (cached) return cached;
|
|
6
|
+
const normalized = normalizePath(pattern);
|
|
7
|
+
const tokens = [];
|
|
8
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
9
|
+
const char = normalized[i];
|
|
10
|
+
if (char === "*") {
|
|
11
|
+
let stars = 1;
|
|
12
|
+
while (normalized[i + 1] === "*") {
|
|
13
|
+
i++;
|
|
14
|
+
stars++;
|
|
15
|
+
}
|
|
16
|
+
tokens.push(stars >= 2 ? { type: "globstar" } : { type: "star" });
|
|
17
|
+
} else if (char === "?") {
|
|
18
|
+
tokens.push({ type: "single" });
|
|
19
|
+
} else {
|
|
20
|
+
tokens.push({ type: "lit", char });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
tokenCache.set(pattern, tokens);
|
|
24
|
+
return tokens;
|
|
25
|
+
};
|
|
26
|
+
const matchesGlob = (pattern, path) => {
|
|
27
|
+
const tokens = tokenize(pattern);
|
|
28
|
+
const normalized = normalizePath(path);
|
|
29
|
+
const P = tokens.length;
|
|
30
|
+
const S = normalized.length;
|
|
31
|
+
const memo = new Uint8Array((P + 1) * (S + 1));
|
|
32
|
+
const match = (ti, si) => {
|
|
33
|
+
const key = ti * (S + 1) + si;
|
|
34
|
+
const seen = memo[key];
|
|
35
|
+
if (seen) return seen === 2;
|
|
36
|
+
let result;
|
|
37
|
+
if (ti === P) {
|
|
38
|
+
result = si === S;
|
|
39
|
+
} else {
|
|
40
|
+
const token = tokens[ti];
|
|
41
|
+
switch (token.type) {
|
|
42
|
+
case "lit":
|
|
43
|
+
result = si < S && normalized[si] === token.char && match(ti + 1, si + 1);
|
|
44
|
+
break;
|
|
45
|
+
case "single":
|
|
46
|
+
result = si < S && normalized[si] !== "/" && match(ti + 1, si + 1);
|
|
47
|
+
break;
|
|
48
|
+
case "star":
|
|
49
|
+
result = match(ti + 1, si) || si < S && normalized[si] !== "/" && match(ti, si + 1);
|
|
50
|
+
break;
|
|
51
|
+
case "globstar":
|
|
52
|
+
result = match(ti + 1, si) || si < S && match(ti, si + 1);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
memo[key] = result ? 2 : 1;
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
return match(0, 0);
|
|
60
|
+
};
|
|
61
|
+
const matchesAnyGlob = (relPath, patterns) => {
|
|
62
|
+
return patterns.some((pattern) => matchesGlob(pattern, relPath));
|
|
63
|
+
};
|
|
64
|
+
const isPathAllowed = (relPath, { include, exclude } = {}) => {
|
|
65
|
+
if ((exclude == null ? void 0 : exclude.length) && matchesAnyGlob(relPath, exclude)) return false;
|
|
66
|
+
if (include == null ? void 0 : include.length) return matchesAnyGlob(relPath, include);
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
export {
|
|
70
|
+
matchesGlob as a,
|
|
71
|
+
isPathAllowed as i,
|
|
72
|
+
matchesAnyGlob as m,
|
|
73
|
+
normalizePath as n
|
|
74
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const normalizePath = (path) => path.replace(/\\/g, "/");
|
|
3
|
+
const tokenCache = /* @__PURE__ */ new Map();
|
|
4
|
+
const tokenize = (pattern) => {
|
|
5
|
+
const cached = tokenCache.get(pattern);
|
|
6
|
+
if (cached) return cached;
|
|
7
|
+
const normalized = normalizePath(pattern);
|
|
8
|
+
const tokens = [];
|
|
9
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
10
|
+
const char = normalized[i];
|
|
11
|
+
if (char === "*") {
|
|
12
|
+
let stars = 1;
|
|
13
|
+
while (normalized[i + 1] === "*") {
|
|
14
|
+
i++;
|
|
15
|
+
stars++;
|
|
16
|
+
}
|
|
17
|
+
tokens.push(stars >= 2 ? { type: "globstar" } : { type: "star" });
|
|
18
|
+
} else if (char === "?") {
|
|
19
|
+
tokens.push({ type: "single" });
|
|
20
|
+
} else {
|
|
21
|
+
tokens.push({ type: "lit", char });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
tokenCache.set(pattern, tokens);
|
|
25
|
+
return tokens;
|
|
26
|
+
};
|
|
27
|
+
const matchesGlob = (pattern, path) => {
|
|
28
|
+
const tokens = tokenize(pattern);
|
|
29
|
+
const normalized = normalizePath(path);
|
|
30
|
+
const P = tokens.length;
|
|
31
|
+
const S = normalized.length;
|
|
32
|
+
const memo = new Uint8Array((P + 1) * (S + 1));
|
|
33
|
+
const match = (ti, si) => {
|
|
34
|
+
const key = ti * (S + 1) + si;
|
|
35
|
+
const seen = memo[key];
|
|
36
|
+
if (seen) return seen === 2;
|
|
37
|
+
let result;
|
|
38
|
+
if (ti === P) {
|
|
39
|
+
result = si === S;
|
|
40
|
+
} else {
|
|
41
|
+
const token = tokens[ti];
|
|
42
|
+
switch (token.type) {
|
|
43
|
+
case "lit":
|
|
44
|
+
result = si < S && normalized[si] === token.char && match(ti + 1, si + 1);
|
|
45
|
+
break;
|
|
46
|
+
case "single":
|
|
47
|
+
result = si < S && normalized[si] !== "/" && match(ti + 1, si + 1);
|
|
48
|
+
break;
|
|
49
|
+
case "star":
|
|
50
|
+
result = match(ti + 1, si) || si < S && normalized[si] !== "/" && match(ti, si + 1);
|
|
51
|
+
break;
|
|
52
|
+
case "globstar":
|
|
53
|
+
result = match(ti + 1, si) || si < S && match(ti, si + 1);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
memo[key] = result ? 2 : 1;
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
return match(0, 0);
|
|
61
|
+
};
|
|
62
|
+
const matchesAnyGlob = (relPath, patterns) => {
|
|
63
|
+
return patterns.some((pattern) => matchesGlob(pattern, relPath));
|
|
64
|
+
};
|
|
65
|
+
const isPathAllowed = (relPath, { include, exclude } = {}) => {
|
|
66
|
+
if ((exclude == null ? void 0 : exclude.length) && matchesAnyGlob(relPath, exclude)) return false;
|
|
67
|
+
if (include == null ? void 0 : include.length) return matchesAnyGlob(relPath, include);
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
exports.isPathAllowed = isPathAllowed;
|
|
71
|
+
exports.matchesAnyGlob = matchesAnyGlob;
|
|
72
|
+
exports.matchesGlob = matchesGlob;
|
|
73
|
+
exports.normalizePath = normalizePath;
|
package/package.json
CHANGED
package/types/cli-types.d.ts
CHANGED
|
@@ -6,5 +6,17 @@ export interface RCFile {
|
|
|
6
6
|
components?: string;
|
|
7
7
|
configDir?: string;
|
|
8
8
|
saltygenDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional glob patterns (relative to the project root) that limit which files the
|
|
11
|
+
* compiler scans. When set and non-empty, only matching files are compiled. When
|
|
12
|
+
* omitted, the whole project is scanned. Supports `**`, `*`, and `?`.
|
|
13
|
+
*/
|
|
14
|
+
include?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Optional glob patterns (relative to the project root) that are skipped by the
|
|
17
|
+
* compiler, in addition to the always-skipped `node_modules` and `saltygen` folders.
|
|
18
|
+
* Exclusions take precedence over `include`. Supports `**`, `*`, and `?`.
|
|
19
|
+
*/
|
|
20
|
+
exclude?: string[];
|
|
9
21
|
}[];
|
|
10
22
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free glob matching for `.saltyrc.json` `include` / `exclude`.
|
|
3
|
+
*
|
|
4
|
+
* Supports the three glob tokens users expect from tsconfig-style patterns:
|
|
5
|
+
* - `**` matches any number of characters, including `/` (crosses directories).
|
|
6
|
+
* - `*` matches any number of characters except `/` (single path segment).
|
|
7
|
+
* - `?` matches a single character except `/`.
|
|
8
|
+
*
|
|
9
|
+
* Patterns and paths are matched with `/` as the separator; callers should
|
|
10
|
+
* normalize Windows separators before matching (see `normalizePath`).
|
|
11
|
+
*
|
|
12
|
+
* Matching is done with a memoized dynamic-programming walk rather than by
|
|
13
|
+
* translating to a `.*`-based RegExp. A regex translation of `**` → `.*` is
|
|
14
|
+
* vulnerable to catastrophic backtracking (ReDoS): a pattern like
|
|
15
|
+
* `**a**a**a…X` becomes `^.*a.*a.*a…X$` and can hang for seconds on a long
|
|
16
|
+
* non-matching path. The DP walk below visits each `(token, char)` pair at most
|
|
17
|
+
* once, giving a hard `O(pattern x path)` bound with no backtracking.
|
|
18
|
+
*/
|
|
19
|
+
/** Normalize a path to use `/` separators so globs match cross-platform. */
|
|
20
|
+
export declare const normalizePath: (path: string) => string;
|
|
21
|
+
/** True when `path` matches the glob `pattern` (full, anchored match). */
|
|
22
|
+
export declare const matchesGlob: (pattern: string, path: string) => boolean;
|
|
23
|
+
/** True when `relPath` matches at least one of the glob `patterns`. */
|
|
24
|
+
export declare const matchesAnyGlob: (relPath: string, patterns: string[]) => boolean;
|
|
25
|
+
export interface PathFilterOptions {
|
|
26
|
+
include?: string[];
|
|
27
|
+
exclude?: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decide whether a project-relative path is allowed by the configured filters.
|
|
31
|
+
* - Anything matching `exclude` is rejected.
|
|
32
|
+
* - When `include` is non-empty, only matching paths are allowed.
|
|
33
|
+
* - When `include` is empty/undefined, everything not excluded is allowed.
|
|
34
|
+
*/
|
|
35
|
+
export declare const isPathAllowed: (relPath: string, { include, exclude }?: PathFilterOptions) => boolean;
|
package/util/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const toHash = require("../to-hash-DT2ImSPA.cjs");
|
|
4
|
+
const globMatch = require("../glob-match-CT1uFlp-.cjs");
|
|
4
5
|
const pascalCase = require("../pascal-case-By_l58S-.cjs");
|
|
5
6
|
function camelCase(str) {
|
|
6
7
|
if (!str) return "";
|
|
@@ -9,5 +10,9 @@ function camelCase(str) {
|
|
|
9
10
|
}
|
|
10
11
|
exports.dashCase = toHash.dashCase;
|
|
11
12
|
exports.toHash = toHash.toHash;
|
|
13
|
+
exports.isPathAllowed = globMatch.isPathAllowed;
|
|
14
|
+
exports.matchesAnyGlob = globMatch.matchesAnyGlob;
|
|
15
|
+
exports.matchesGlob = globMatch.matchesGlob;
|
|
16
|
+
exports.normalizePath = globMatch.normalizePath;
|
|
12
17
|
exports.pascalCase = pascalCase.pascalCase;
|
|
13
18
|
exports.camelCase = camelCase;
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { d, t } from "../to-hash-DSoCPs8D.js";
|
|
2
|
+
import { i, m, a, n } from "../glob-match-C28iYp1A.js";
|
|
2
3
|
import { p } from "../pascal-case-F3Usi5Wf.js";
|
|
3
4
|
function camelCase(str) {
|
|
4
5
|
if (!str) return "";
|
|
@@ -8,6 +9,10 @@ function camelCase(str) {
|
|
|
8
9
|
export {
|
|
9
10
|
camelCase,
|
|
10
11
|
d as dashCase,
|
|
12
|
+
i as isPathAllowed,
|
|
13
|
+
m as matchesAnyGlob,
|
|
14
|
+
a as matchesGlob,
|
|
15
|
+
n as normalizePath,
|
|
11
16
|
p as pascalCase,
|
|
12
17
|
t as toHash
|
|
13
18
|
};
|