@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.
@@ -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: [{ dir: projectPath, framework: framework.name }]
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({ dir: projectPath, framework: framework.name });
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(projectDir, "saltygen/index.css"));
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(ctx.projectDir, { recursive: true });
651
- await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(ctx.projectDir, fileName, content)));
652
- await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
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(ctx.projectDir);
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: [{ dir: projectPath, framework: framework.name }]
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({ dir: projectPath, framework: framework.name });
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(projectDir, "saltygen/index.css"));
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(ctx.projectDir, { recursive: true });
648
- await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(ctx.projectDir, fileName, content)));
649
- await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
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(ctx.projectDir);
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 validFile = compiler_helpers.isSaltyFile(file);
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 validFile = isSaltyFile(file);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salty-css/core",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.2.0-fix-astro-fixes-06-2026.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "typings": "./dist/index.d.ts",
@@ -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
@@ -1,4 +1,5 @@
1
1
  export * from './camel-case';
2
2
  export * from './dash-case';
3
+ export * from './glob-match';
3
4
  export * from './pascal-case';
4
5
  export * from './to-hash';
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
  };