@salty-css/core 0.2.0-alpha.2 → 0.2.0-alpha.3
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 +24 -6
- package/bin/main.js +24 -6
- 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)) {
|
|
@@ -649,7 +662,12 @@ const registerInitCommand = (program) => {
|
|
|
649
662
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
650
663
|
await promises.mkdir(ctx.projectDir, { recursive: true });
|
|
651
664
|
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(ctx.projectDir, fileName, content)));
|
|
652
|
-
|
|
665
|
+
const pathDefaults = computePathDefaults({
|
|
666
|
+
framework: framework.name,
|
|
667
|
+
integrations: plannedIntegrations.map((p) => p.name),
|
|
668
|
+
hasSrcDir: fs.existsSync(path.join(ctx.projectDir, "src"))
|
|
669
|
+
});
|
|
670
|
+
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework, pathDefaults);
|
|
653
671
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
654
672
|
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
655
673
|
await applyIntegrationPlans(plannedIntegrations);
|
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)) {
|
|
@@ -646,7 +659,12 @@ const registerInitCommand = (program) => {
|
|
|
646
659
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
647
660
|
await mkdir(ctx.projectDir, { recursive: true });
|
|
648
661
|
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(ctx.projectDir, fileName, content)));
|
|
649
|
-
|
|
662
|
+
const pathDefaults = computePathDefaults({
|
|
663
|
+
framework: framework.name,
|
|
664
|
+
integrations: plannedIntegrations.map((p) => p.name),
|
|
665
|
+
hasSrcDir: existsSync(join(ctx.projectDir, "src"))
|
|
666
|
+
});
|
|
667
|
+
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework, pathDefaults);
|
|
650
668
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
651
669
|
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
652
670
|
await applyIntegrationPlans(plannedIntegrations);
|
|
@@ -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
|
};
|