@salty-css/core 0.1.0-alpha.5 → 0.1.0-alpha.7
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/astro-component-5hrNTCJ5.js +4 -0
- package/astro-component-Dj3enX6K.cjs +4 -0
- package/bin/commands/build.d.ts +2 -0
- package/bin/commands/generate.d.ts +2 -0
- package/bin/commands/init.d.ts +2 -0
- package/bin/commands/update.d.ts +2 -0
- package/bin/commands/version.d.ts +2 -0
- package/bin/context.d.ts +19 -0
- package/bin/detection/css-file.d.ts +5 -0
- package/bin/frameworks/astro.d.ts +4 -0
- package/bin/frameworks/index.d.ts +13 -0
- package/bin/frameworks/react.d.ts +2 -0
- package/bin/frameworks/types.d.ts +27 -0
- package/bin/integrations/astro.d.ts +11 -0
- package/bin/integrations/eslint.d.ts +6 -0
- package/bin/integrations/index.d.ts +13 -0
- package/bin/integrations/next.d.ts +9 -0
- package/bin/integrations/types.d.ts +17 -0
- package/bin/integrations/vite.d.ts +8 -0
- package/bin/main.cjs +548 -332
- package/bin/main.d.ts +8 -0
- package/bin/main.js +548 -332
- package/bin/package-json.d.ts +21 -0
- package/bin/saltyrc.d.ts +31 -0
- package/bin/templates.d.ts +14 -0
- package/package.json +1 -1
- package/styled-file-BzmB9_Ez.cjs +12 -0
- package/{react-styled-file-U02jek-B.cjs → styled-file-CPd_rTW2.cjs} +2 -2
- package/{react-styled-file-B99mwk0w.js → styled-file-Cda3EeR6.js} +2 -2
- package/styled-file-DLcgYmGN.js +12 -0
- package/{react-vanilla-file-D9px70iK.js → vanilla-file-1kOqbCIM.js} +2 -2
- package/{react-vanilla-file-Bj6XC8GS.cjs → vanilla-file-r0fp2q_m.cjs} +2 -2
package/bin/main.cjs
CHANGED
|
@@ -2,16 +2,45 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const commander = require("commander");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
-
const promises = require("fs/promises");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const ejs = require("ejs");
|
|
8
|
-
const pascalCase = require("../pascal-case-By_l58S-.cjs");
|
|
9
5
|
const compiler_saltyCompiler = require("../compiler/salty-compiler.cjs");
|
|
6
|
+
const compiler_helpers = require("../compiler/helpers.cjs");
|
|
7
|
+
const shouldRestart = require("../should-restart-CQsyHls3.cjs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const promises = require("fs/promises");
|
|
10
10
|
const child_process = require("child_process");
|
|
11
11
|
const ora = require("ora");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const pascalCase = require("../pascal-case-By_l58S-.cjs");
|
|
13
|
+
const ejs = require("ejs");
|
|
14
14
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
15
|
+
const defaultPackageJsonPath = path.join(process.cwd(), "package.json");
|
|
16
|
+
const readPackageJson = async (filePath = defaultPackageJsonPath) => {
|
|
17
|
+
const content = await promises.readFile(filePath, "utf-8").then(JSON.parse).catch(() => void 0);
|
|
18
|
+
if (!content) throw "Could not read package.json file!";
|
|
19
|
+
return content;
|
|
20
|
+
};
|
|
21
|
+
const updatePackageJson = async (content, filePath = defaultPackageJsonPath) => {
|
|
22
|
+
if (typeof content === "object") content = JSON.stringify(content, null, 2);
|
|
23
|
+
await promises.writeFile(filePath, content);
|
|
24
|
+
};
|
|
25
|
+
const readThisPackageJson = async () => {
|
|
26
|
+
const packageJsonPath = new URL("../package.json", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("bin/main.cjs", document.baseURI).href);
|
|
27
|
+
return readPackageJson(packageJsonPath);
|
|
28
|
+
};
|
|
29
|
+
const corePackages = {
|
|
30
|
+
core: (version) => `@salty-css/core@${version}`,
|
|
31
|
+
eslintConfigCore: (version) => `@salty-css/eslint-config-core@${version}`
|
|
32
|
+
};
|
|
33
|
+
const addPrepareScript = (pkg) => {
|
|
34
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
35
|
+
const current = pkg.scripts["prepare"];
|
|
36
|
+
if (current) {
|
|
37
|
+
if (current.includes("salty-css")) return { changed: false, pkg };
|
|
38
|
+
pkg.scripts["prepare"] = current + " && npx salty-css build";
|
|
39
|
+
} else {
|
|
40
|
+
pkg.scripts["prepare"] = "npx salty-css build";
|
|
41
|
+
}
|
|
42
|
+
return { changed: true, pkg };
|
|
43
|
+
};
|
|
15
44
|
const execAsync = (command) => {
|
|
16
45
|
return new Promise((resolve, reject) => {
|
|
17
46
|
child_process.exec(command, (error) => {
|
|
@@ -40,365 +69,542 @@ async function formatWithPrettier(filePath) {
|
|
|
40
69
|
compiler_saltyCompiler.logger.error(`Error formatting ${filePath} with Prettier:`, error);
|
|
41
70
|
}
|
|
42
71
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const packageJsonContent = await promises.readFile(filePath, "utf-8").then(JSON.parse).catch(() => void 0);
|
|
67
|
-
if (!packageJsonContent) throw "Could not read package.json file!";
|
|
68
|
-
return packageJsonContent;
|
|
69
|
-
};
|
|
70
|
-
const updatePackageJson = async (content, filePath = defaultPackageJsonPath) => {
|
|
71
|
-
if (typeof content === "object") content = JSON.stringify(content, null, 2);
|
|
72
|
-
await promises.writeFile(filePath, content);
|
|
73
|
-
};
|
|
74
|
-
const readThisPackageJson = async () => {
|
|
75
|
-
const packageJsonPath = new URL("../package.json", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("bin/main.cjs", document.baseURI).href);
|
|
76
|
-
return readPackageJson(packageJsonPath);
|
|
77
|
-
};
|
|
78
|
-
const getDefaultProject = async () => {
|
|
79
|
-
const rcContent = await readRCFile();
|
|
80
|
-
return rcContent.defaultProject;
|
|
81
|
-
};
|
|
82
|
-
const defaultProject = await getDefaultProject();
|
|
83
|
-
const currentPackageJson = await readThisPackageJson();
|
|
84
|
-
const packages = {
|
|
85
|
-
core: `@salty-css/core@${currentPackageJson.version}`,
|
|
86
|
-
react: `@salty-css/react@${currentPackageJson.version}`,
|
|
87
|
-
eslintConfigCore: `@salty-css/eslint-config-core@${currentPackageJson.version}`,
|
|
88
|
-
vite: `@salty-css/vite@${currentPackageJson.version}`,
|
|
89
|
-
next: `@salty-css/next@${currentPackageJson.version}`
|
|
90
|
-
};
|
|
91
|
-
const resolveProjectDir = (dir) => {
|
|
92
|
-
const dirName = dir === "." ? "" : dir;
|
|
93
|
-
const rootDir = process.cwd();
|
|
94
|
-
const projectDir = path.join(rootDir, dirName);
|
|
95
|
-
return projectDir;
|
|
96
|
-
};
|
|
97
|
-
program.command("init [directory]").description("Initialize a new Salty-CSS project.").option("-d, --dir <dir>", "Project directory to initialize the project in.").option("--css-file <css-file>", "Existing CSS file where to import the generated CSS. Path must be relative to the given project directory.").option("--skip-install", "Skip installing dependencies.").action(async function(_dir = ".") {
|
|
98
|
-
const packageJson = await readPackageJson().catch(() => void 0);
|
|
99
|
-
if (!packageJson) return compiler_saltyCompiler.logError("Salty CSS project must be initialized in a directory with a package.json file.");
|
|
100
|
-
compiler_saltyCompiler.logger.info("Initializing a new Salty-CSS project!");
|
|
101
|
-
const { dir = _dir, cssFile, skipInstall } = this.opts();
|
|
102
|
-
if (!dir) return compiler_saltyCompiler.logError("Project directory must be provided. Add it as the first argument after init command or use the --dir option.");
|
|
103
|
-
if (!skipInstall) await npmInstall(packages.core, packages.react);
|
|
104
|
-
const rootDir = process.cwd();
|
|
105
|
-
const projectDir = resolveProjectDir(dir);
|
|
106
|
-
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
107
|
-
const saltyCompiler = new compiler_saltyCompiler.SaltyCompiler(projectDir);
|
|
108
|
-
await promises.mkdir(projectDir, { recursive: true });
|
|
109
|
-
const writeFiles = projectFiles.map(async ({ fileName, content }) => {
|
|
110
|
-
const filePath = path.join(projectDir, fileName);
|
|
111
|
-
const existingContent = await promises.readFile(filePath, "utf-8").catch(() => void 0);
|
|
112
|
-
if (existingContent !== void 0) {
|
|
113
|
-
compiler_saltyCompiler.logger.debug("File already exists: " + filePath);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const additionalFolders = fileName.split("/").slice(0, -1).join("/");
|
|
117
|
-
if (additionalFolders) await promises.mkdir(path.join(projectDir, additionalFolders), { recursive: true });
|
|
118
|
-
compiler_saltyCompiler.logger.info("Creating file: " + filePath);
|
|
119
|
-
await promises.writeFile(filePath, content);
|
|
120
|
-
await formatWithPrettier(filePath);
|
|
121
|
-
});
|
|
122
|
-
await Promise.all(writeFiles);
|
|
123
|
-
const relativeProjectPath = path.relative(rootDir, projectDir) || ".";
|
|
124
|
-
const saltyrcPath = path.join(rootDir, ".saltyrc.json");
|
|
125
|
-
const existingSaltyrc = await promises.readFile(saltyrcPath, "utf-8").catch(() => void 0);
|
|
126
|
-
if (existingSaltyrc === void 0) {
|
|
127
|
-
compiler_saltyCompiler.logger.info("Creating file: " + saltyrcPath);
|
|
128
|
-
const rcContent = {
|
|
129
|
-
$schema: "./node_modules/@salty-css/core/.saltyrc.schema.json",
|
|
130
|
-
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.",
|
|
131
|
-
defaultProject: relativeProjectPath,
|
|
132
|
-
projects: [
|
|
133
|
-
{
|
|
134
|
-
dir: relativeProjectPath,
|
|
135
|
-
framework: "react"
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
};
|
|
139
|
-
const content = JSON.stringify(rcContent, null, 2);
|
|
140
|
-
await promises.writeFile(saltyrcPath, content);
|
|
141
|
-
await formatWithPrettier(saltyrcPath);
|
|
142
|
-
} else {
|
|
143
|
-
const rcContent = JSON.parse(existingSaltyrc);
|
|
144
|
-
const projects = (rcContent == null ? void 0 : rcContent.projects) || [];
|
|
145
|
-
const projectIndex = projects.findIndex((p) => p.dir === relativeProjectPath);
|
|
146
|
-
if (projectIndex === -1) {
|
|
147
|
-
projects.push({ dir: relativeProjectPath, framework: "react" });
|
|
148
|
-
rcContent.projects = [...projects];
|
|
149
|
-
const content = JSON.stringify(rcContent, null, 2);
|
|
150
|
-
if (content !== existingSaltyrc) {
|
|
151
|
-
compiler_saltyCompiler.logger.info("Edit file: " + saltyrcPath);
|
|
152
|
-
await promises.writeFile(saltyrcPath, content);
|
|
153
|
-
await formatWithPrettier(saltyrcPath);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const gitIgnorePath = path.join(rootDir, ".gitignore");
|
|
158
|
-
const gitIgnoreContent = await promises.readFile(gitIgnorePath, "utf-8").catch(() => void 0);
|
|
159
|
-
if (gitIgnoreContent !== void 0) {
|
|
160
|
-
const alreadyIgnoresSaltygen = gitIgnoreContent.includes("saltygen");
|
|
161
|
-
if (!alreadyIgnoresSaltygen) {
|
|
162
|
-
compiler_saltyCompiler.logger.info("Edit file: " + gitIgnorePath);
|
|
163
|
-
await promises.writeFile(gitIgnorePath, gitIgnoreContent + "\n\n# Salty-CSS\nsaltygen\n");
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const cssFileFoldersToLookFor = ["src", "public", "assets", "styles", "css", "app"];
|
|
167
|
-
const secondLevelFolders = ["styles", "css", "app", "pages"];
|
|
168
|
-
const cssFilesToLookFor = ["index", "styles", "main", "app", "global", "globals"];
|
|
169
|
-
const cssFileExtensions = [".css", ".scss", ".sass"];
|
|
170
|
-
const getTargetCssFile = async () => {
|
|
171
|
-
if (cssFile) return cssFile;
|
|
172
|
-
for (const folder of cssFileFoldersToLookFor) {
|
|
173
|
-
for (const file of cssFilesToLookFor) {
|
|
174
|
-
for (const ext of cssFileExtensions) {
|
|
175
|
-
const filePath = path.join(projectDir, folder, file + ext);
|
|
176
|
-
const fileContent = await promises.readFile(filePath, "utf-8").catch(() => void 0);
|
|
177
|
-
if (fileContent !== void 0) return path.relative(projectDir, filePath);
|
|
178
|
-
for (const secondLevelFolder of secondLevelFolders) {
|
|
179
|
-
const filePath2 = path.join(projectDir, folder, secondLevelFolder, file + ext);
|
|
180
|
-
const fileContent2 = await promises.readFile(filePath2, "utf-8").catch(() => void 0);
|
|
181
|
-
if (fileContent2 !== void 0) return path.relative(projectDir, filePath2);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return void 0;
|
|
187
|
-
};
|
|
188
|
-
const targetCSSFile = await getTargetCssFile();
|
|
189
|
-
if (targetCSSFile) {
|
|
190
|
-
const cssFilePath = path.join(projectDir, targetCSSFile);
|
|
191
|
-
const cssFileContent = await promises.readFile(cssFilePath, "utf-8").catch(() => void 0);
|
|
192
|
-
if (cssFileContent !== void 0) {
|
|
193
|
-
const alreadyImportsSaltygen = cssFileContent.includes("saltygen");
|
|
194
|
-
if (!alreadyImportsSaltygen) {
|
|
195
|
-
const cssFileFolder = path.join(cssFilePath, "..");
|
|
196
|
-
const relativePath = path.relative(cssFileFolder, path.join(projectDir, "saltygen/index.css"));
|
|
197
|
-
const importStatement = `@import '${relativePath}';`;
|
|
198
|
-
compiler_saltyCompiler.logger.info("Adding global import statement to CSS file: " + cssFilePath);
|
|
199
|
-
await promises.writeFile(cssFilePath, importStatement + "\n" + cssFileContent);
|
|
200
|
-
await formatWithPrettier(cssFilePath);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
compiler_saltyCompiler.logger.warn("Could not find a CSS file to import the generated CSS. Please add it manually.");
|
|
205
|
-
}
|
|
206
|
-
const eslintConfigs = {
|
|
207
|
-
projectJs: path.join(projectDir, "eslint.config.js"),
|
|
208
|
-
rootJs: path.join(rootDir, "eslint.config.js"),
|
|
209
|
-
projectMjs: path.join(projectDir, "eslint.config.mjs"),
|
|
210
|
-
rootMjs: path.join(rootDir, "eslint.config.mjs"),
|
|
211
|
-
projectJson: path.join(projectDir, ".eslintrc.json"),
|
|
212
|
-
rootJson: path.join(rootDir, ".eslintrc.json")
|
|
72
|
+
const SALTYRC_FILENAME = ".saltyrc.json";
|
|
73
|
+
const SALTYRC_SCHEMA = "./node_modules/@salty-css/core/.saltyrc.schema.json";
|
|
74
|
+
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.";
|
|
75
|
+
const saltyrcPath = (rootDir = process.cwd()) => path.join(rootDir, SALTYRC_FILENAME);
|
|
76
|
+
const readRc = async (rootDir = process.cwd()) => {
|
|
77
|
+
const content = await promises.readFile(saltyrcPath(rootDir), "utf-8").then(JSON.parse).catch(() => ({}));
|
|
78
|
+
return content;
|
|
79
|
+
};
|
|
80
|
+
const readRawRc = async (rootDir = process.cwd()) => {
|
|
81
|
+
return promises.readFile(saltyrcPath(rootDir), "utf-8").catch(() => void 0);
|
|
82
|
+
};
|
|
83
|
+
const getDefaultProject = async (rootDir = process.cwd()) => {
|
|
84
|
+
const rc = await readRc(rootDir);
|
|
85
|
+
return rc.defaultProject;
|
|
86
|
+
};
|
|
87
|
+
const upsertProjectInRc = (existingRaw, relativeProjectPath, framework) => {
|
|
88
|
+
const projectPath = path.join(relativeProjectPath, framework.srcDirectory);
|
|
89
|
+
if (existingRaw === void 0) {
|
|
90
|
+
const fresh = {
|
|
91
|
+
$schema: SALTYRC_SCHEMA,
|
|
92
|
+
info: SALTYRC_INFO,
|
|
93
|
+
defaultProject: projectPath,
|
|
94
|
+
projects: [{ dir: projectPath, framework: framework.name }]
|
|
213
95
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
let nextConfigContent = await promises.readFile(nextConfigPath, "utf-8").catch(() => void 0);
|
|
267
|
-
if (nextConfigContent !== void 0) {
|
|
268
|
-
const alreadyHasPlugin = nextConfigContent.includes("withSaltyCss");
|
|
269
|
-
if (!alreadyHasPlugin) {
|
|
270
|
-
let saltyCssAppended = false;
|
|
271
|
-
const hasPluginsArray = /\splugins([^=]*)=[^[]\[/.test(nextConfigContent);
|
|
272
|
-
if (hasPluginsArray && !saltyCssAppended) {
|
|
273
|
-
nextConfigContent = nextConfigContent.replace(/\splugins([^=]*)=[^[]\[/, (_, config) => {
|
|
274
|
-
return ` plugins${config}= [withSaltyCss,`;
|
|
275
|
-
});
|
|
276
|
-
saltyCssAppended = true;
|
|
277
|
-
}
|
|
278
|
-
const useRequire = nextConfigContent.includes("module.exports");
|
|
279
|
-
const pluginImport = useRequire ? "const { withSaltyCss } = require('@salty-css/next');\n" : "import { withSaltyCss } from '@salty-css/next';\n";
|
|
280
|
-
if (useRequire && !saltyCssAppended) {
|
|
281
|
-
nextConfigContent = nextConfigContent.replace(/module.exports = ([^;]+)/, (_, config) => {
|
|
282
|
-
return `module.exports = withSaltyCss(${config})`;
|
|
283
|
-
});
|
|
284
|
-
saltyCssAppended = true;
|
|
285
|
-
} else if (!saltyCssAppended) {
|
|
286
|
-
nextConfigContent = nextConfigContent.replace(/export default ([^;]+)/, (_, config) => {
|
|
287
|
-
return `export default withSaltyCss(${config})`;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (!skipInstall) await npmInstall(`-D ${packages.next}`);
|
|
291
|
-
compiler_saltyCompiler.logger.info("Adding Salty-CSS plugin to Next.js config...");
|
|
292
|
-
await promises.writeFile(nextConfigPath, pluginImport + nextConfigContent);
|
|
293
|
-
await formatWithPrettier(nextConfigPath);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const packageJsonContent = await readPackageJson().catch(() => compiler_saltyCompiler.logError("Could not read package.json file.")).then((content) => {
|
|
298
|
-
if (!content.scripts) content.scripts = {};
|
|
299
|
-
if (content.scripts.prepare) {
|
|
300
|
-
const alreadyHasSaltyCss = content.scripts.prepare.includes("salty-css");
|
|
301
|
-
if (!alreadyHasSaltyCss) {
|
|
302
|
-
compiler_saltyCompiler.logger.info("Edit file: " + defaultPackageJsonPath);
|
|
303
|
-
content.scripts.prepare = content.scripts.prepare + " && npx salty-css build";
|
|
304
|
-
}
|
|
305
|
-
} else {
|
|
306
|
-
compiler_saltyCompiler.logger.info("Edit file: " + defaultPackageJsonPath);
|
|
307
|
-
content.scripts.prepare = "npx salty-css build";
|
|
308
|
-
}
|
|
309
|
-
return content;
|
|
310
|
-
});
|
|
311
|
-
await updatePackageJson(packageJsonContent);
|
|
312
|
-
compiler_saltyCompiler.logger.info("Running the build to generate initial CSS...");
|
|
313
|
-
await saltyCompiler.generateCss();
|
|
314
|
-
compiler_saltyCompiler.logger.info("🎉 Salty CSS project initialized successfully!");
|
|
315
|
-
compiler_saltyCompiler.logger.info("Next steps:");
|
|
316
|
-
compiler_saltyCompiler.logger.info("1. Configure variables and templates in `salty.config.ts`");
|
|
317
|
-
compiler_saltyCompiler.logger.info("2. Create a new component with `npx salty-css generate [component-name]`");
|
|
318
|
-
compiler_saltyCompiler.logger.info("3. Run `npx salty-css build` to generate the CSS");
|
|
319
|
-
compiler_saltyCompiler.logger.info("4. Read about the features in the documentation: https://salty-css.dev");
|
|
320
|
-
compiler_saltyCompiler.logger.info("5. Star the project on GitHub: https://github.com/margarita-form/salty-css ⭐");
|
|
321
|
-
});
|
|
96
|
+
return { content: JSON.stringify(fresh, null, 2), changed: true, created: true };
|
|
97
|
+
}
|
|
98
|
+
const rc = JSON.parse(existingRaw);
|
|
99
|
+
const projects = rc.projects || [];
|
|
100
|
+
const exists = projects.some((p) => p.dir === projectPath);
|
|
101
|
+
if (exists) return { content: existingRaw, changed: false, created: false };
|
|
102
|
+
projects.push({ dir: projectPath, framework: framework.name });
|
|
103
|
+
rc.projects = [...projects];
|
|
104
|
+
const next = JSON.stringify(rc, null, 2);
|
|
105
|
+
return { content: next, changed: next !== existingRaw, created: false };
|
|
106
|
+
};
|
|
107
|
+
const writeProjectToRc = async (cwd, relativeProjectPath, framework) => {
|
|
108
|
+
const path2 = saltyrcPath(cwd);
|
|
109
|
+
const existing = await readRawRc(cwd);
|
|
110
|
+
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework);
|
|
111
|
+
if (!changed) return false;
|
|
112
|
+
if (created) compiler_saltyCompiler.logger.info("Creating file: " + path2);
|
|
113
|
+
else compiler_saltyCompiler.logger.info("Edit file: " + path2);
|
|
114
|
+
await promises.writeFile(path2, content);
|
|
115
|
+
await formatWithPrettier(path2);
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
const getProjectFramework = (rc, relativeProjectPath) => {
|
|
119
|
+
const projects = rc.projects || [];
|
|
120
|
+
const entry = projects.find((p) => p.dir === relativeProjectPath);
|
|
121
|
+
return entry == null ? void 0 : entry.framework;
|
|
122
|
+
};
|
|
123
|
+
const resolveProjectDir = (dir, rootDir = process.cwd()) => {
|
|
124
|
+
const dirName = dir === "." ? "" : dir;
|
|
125
|
+
return path.join(rootDir, dirName);
|
|
126
|
+
};
|
|
127
|
+
const buildContext = async (opts) => {
|
|
128
|
+
const cwd = process.cwd();
|
|
129
|
+
const projectDir = resolveProjectDir(opts.dir, cwd);
|
|
130
|
+
const relativeProjectPath = path.relative(cwd, projectDir) || ".";
|
|
131
|
+
const packageJson = await readPackageJson().catch(() => void 0);
|
|
132
|
+
if (opts.requirePackageJson !== false && !packageJson) {
|
|
133
|
+
throw new Error("Salty CSS project must be initialized in a directory with a package.json file.");
|
|
134
|
+
}
|
|
135
|
+
const rcFile = await readRc(cwd);
|
|
136
|
+
const cliPackageJson = await readThisPackageJson();
|
|
137
|
+
return {
|
|
138
|
+
cwd,
|
|
139
|
+
projectDir,
|
|
140
|
+
relativeProjectPath,
|
|
141
|
+
packageJson,
|
|
142
|
+
rcFile,
|
|
143
|
+
cliVersion: cliPackageJson.version || "0.0.0",
|
|
144
|
+
skipInstall: !!opts.skipInstall
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
const registerBuildCommand = (program, defaultProject) => {
|
|
322
148
|
program.command("build [directory]").alias("b").description("Build the Salty-CSS project.").option("-d, --dir <dir>", "Project directory to build the project in.").option("--watch", "Watch for changes and rebuild the project.").action(async function(_dir = defaultProject) {
|
|
323
149
|
compiler_saltyCompiler.logger.info("Building the Salty-CSS project...");
|
|
324
150
|
const { dir = _dir, watch } = this.opts();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
151
|
+
const resolved = dir ?? await getDefaultProject();
|
|
152
|
+
if (!resolved) return compiler_saltyCompiler.logError("Project directory must be provided. Add it as the first argument after build command or use the --dir option.");
|
|
153
|
+
const projectDir = resolveProjectDir(resolved);
|
|
154
|
+
const compiler = new compiler_saltyCompiler.SaltyCompiler(projectDir);
|
|
155
|
+
await compiler.generateCss();
|
|
329
156
|
if (watch) {
|
|
330
157
|
compiler_saltyCompiler.logger.info("Watching for changes in the project directory...");
|
|
331
|
-
fs.watch(projectDir, { recursive: true }, async (
|
|
158
|
+
fs.watch(projectDir, { recursive: true }, async (_event, filePath) => {
|
|
332
159
|
const shouldRestart$1 = await shouldRestart.checkShouldRestart(filePath);
|
|
333
160
|
if (shouldRestart$1) {
|
|
334
|
-
await
|
|
335
|
-
} else {
|
|
336
|
-
|
|
337
|
-
if (saltyFile) await saltyCompiler.generateFile(filePath);
|
|
161
|
+
await compiler.generateCss(false);
|
|
162
|
+
} else if (compiler_helpers.isSaltyFile(filePath)) {
|
|
163
|
+
await compiler.generateFile(filePath);
|
|
338
164
|
}
|
|
339
165
|
});
|
|
340
166
|
}
|
|
341
167
|
});
|
|
342
|
-
|
|
168
|
+
};
|
|
169
|
+
const astroConfigFiles = ["astro.config.mjs", "astro.config.ts", "astro.config.js", "astro.config.cjs"];
|
|
170
|
+
const findAstroConfig = (projectDir) => {
|
|
171
|
+
for (const name of astroConfigFiles) {
|
|
172
|
+
const path$1 = path.join(projectDir, name);
|
|
173
|
+
if (fs.existsSync(path$1)) return path$1;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
};
|
|
177
|
+
const hasAstroDependency = (ctx) => {
|
|
178
|
+
const pkg = ctx.packageJson;
|
|
179
|
+
if (!pkg) return false;
|
|
180
|
+
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
181
|
+
return Object.prototype.hasOwnProperty.call(all, "astro");
|
|
182
|
+
};
|
|
183
|
+
const astroFramework = {
|
|
184
|
+
name: "astro",
|
|
185
|
+
srcDirectory: "src",
|
|
186
|
+
detect: (ctx) => Boolean(findAstroConfig(ctx.projectDir)) || hasAstroDependency(ctx),
|
|
187
|
+
runtimePackage: (version) => `@salty-css/astro@${version}`,
|
|
188
|
+
templates: {
|
|
189
|
+
styled: "astro/styled-file.ts",
|
|
190
|
+
component: {
|
|
191
|
+
styled: "astro/styled-file.ts",
|
|
192
|
+
wrapper: "astro/component.astro",
|
|
193
|
+
wrapperExt: ".astro"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const reactFramework = {
|
|
198
|
+
name: "react",
|
|
199
|
+
srcDirectory: "",
|
|
200
|
+
detect: () => true,
|
|
201
|
+
// default fallback — evaluated last in the registry
|
|
202
|
+
runtimePackage: (version) => `@salty-css/react@${version}`,
|
|
203
|
+
templates: {
|
|
204
|
+
styled: "react/styled-file.ts",
|
|
205
|
+
component: {
|
|
206
|
+
styled: "react/styled-file.ts",
|
|
207
|
+
wrapper: "react/vanilla-file.ts",
|
|
208
|
+
wrapperExt: ".tsx"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const frameworkRegistry = [astroFramework, reactFramework];
|
|
213
|
+
const frameworksByName = {
|
|
214
|
+
astro: astroFramework,
|
|
215
|
+
react: reactFramework
|
|
216
|
+
};
|
|
217
|
+
const detectFramework = async (ctx) => {
|
|
218
|
+
for (const adapter of frameworkRegistry) {
|
|
219
|
+
if (await adapter.detect(ctx)) return adapter;
|
|
220
|
+
}
|
|
221
|
+
return reactFramework;
|
|
222
|
+
};
|
|
223
|
+
const getFramework = (name) => {
|
|
224
|
+
if (!name) return void 0;
|
|
225
|
+
return frameworksByName[name];
|
|
226
|
+
};
|
|
227
|
+
const templateLoaders = {
|
|
228
|
+
"salty.config.ts": () => Promise.resolve().then(() => require("../salty.config-cqavVm2t.cjs")),
|
|
229
|
+
"saltygen/index.css": () => Promise.resolve().then(() => require("../index-ByR0nfaf.cjs")),
|
|
230
|
+
"react/styled-file.ts": () => Promise.resolve().then(() => require("../styled-file-CPd_rTW2.cjs")),
|
|
231
|
+
"react/vanilla-file.ts": () => Promise.resolve().then(() => require("../vanilla-file-r0fp2q_m.cjs")),
|
|
232
|
+
"astro/styled-file.ts": () => Promise.resolve().then(() => require("../styled-file-BzmB9_Ez.cjs")),
|
|
233
|
+
"astro/component.astro": () => Promise.resolve().then(() => require("../astro-component-Dj3enX6K.cjs"))
|
|
234
|
+
};
|
|
235
|
+
const readTemplate = async (key, options) => {
|
|
236
|
+
const { default: file } = await templateLoaders[key]();
|
|
237
|
+
const content = ejs.render(file, options);
|
|
238
|
+
return { fileName: key, content };
|
|
239
|
+
};
|
|
240
|
+
const registerGenerateCommand = (program, defaultProject) => {
|
|
241
|
+
program.command("generate [file] [directory]").alias("g").description("Generate a new component file.").option("-f, --file <file>", "File to generate.").option("-d, --dir <dir>", "Project directory to generate the file in.").option("-t, --tag <tag>", "HTML tag of the component.", "div").option("-n, --name <name>", "Name of the component.").option("-c, --className <className>", "CSS class of the component.").option("-r, --reactComponent", "Generate a wrapper component file alongside the styled definition.").action(async function(_file, _dir = defaultProject) {
|
|
343
242
|
const { file = _file, dir = _dir, tag, name, className, reactComponent = false } = this.opts();
|
|
344
243
|
if (!file) return compiler_saltyCompiler.logError("File to generate must be provided. Add it as the first argument after generate command or use the --file option.");
|
|
345
244
|
if (!dir) return compiler_saltyCompiler.logError("Project directory must be provided. Add it as the second argument after generate command or use the --dir option.");
|
|
346
|
-
|
|
245
|
+
let ctx;
|
|
246
|
+
try {
|
|
247
|
+
ctx = await buildContext({ dir, requirePackageJson: false });
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return compiler_saltyCompiler.logError(err instanceof Error ? err.message : String(err));
|
|
250
|
+
}
|
|
251
|
+
const rcFramework = getFramework(getProjectFramework(ctx.rcFile, ctx.relativeProjectPath));
|
|
252
|
+
const framework = rcFramework ?? await detectFramework(ctx);
|
|
347
253
|
const additionalFolders = file.split("/").slice(0, -1).join("/");
|
|
348
|
-
if (additionalFolders) await promises.mkdir(path.join(projectDir, additionalFolders), { recursive: true });
|
|
349
|
-
const filePath = path.join(projectDir, file);
|
|
254
|
+
if (additionalFolders) await promises.mkdir(path.join(ctx.projectDir, additionalFolders), { recursive: true });
|
|
255
|
+
const filePath = path.join(ctx.projectDir, file);
|
|
350
256
|
const parsedFilePath = path.parse(filePath);
|
|
351
|
-
if (!parsedFilePath.ext)
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
if (!parsedFilePath.name.endsWith(".css")) {
|
|
355
|
-
parsedFilePath.name = parsedFilePath.name + ".css";
|
|
356
|
-
}
|
|
257
|
+
if (!parsedFilePath.ext) parsedFilePath.ext = ".ts";
|
|
258
|
+
if (!parsedFilePath.name.endsWith(".css")) parsedFilePath.name = parsedFilePath.name + ".css";
|
|
357
259
|
parsedFilePath.base = parsedFilePath.name + parsedFilePath.ext;
|
|
358
260
|
const formattedStyledFilePath = path.format(parsedFilePath);
|
|
359
261
|
const alreadyExists = await promises.readFile(formattedStyledFilePath, "utf-8").catch(() => void 0);
|
|
360
262
|
if (alreadyExists !== void 0) {
|
|
361
|
-
compiler_saltyCompiler.logger.error("File already exists:"
|
|
263
|
+
compiler_saltyCompiler.logger.error("File already exists: " + formattedStyledFilePath);
|
|
362
264
|
return;
|
|
363
265
|
}
|
|
364
266
|
let styledComponentName = pascalCase.pascalCase(name || parsedFilePath.base.replace(/\.css\.\w+$/, ""));
|
|
365
267
|
if (reactComponent) {
|
|
268
|
+
if (!framework.templates.component) {
|
|
269
|
+
return compiler_saltyCompiler.logError(`--reactComponent is not supported for the ${framework.name} framework.`);
|
|
270
|
+
}
|
|
366
271
|
const componentName = styledComponentName + "Component";
|
|
367
272
|
styledComponentName = styledComponentName + "Wrapper";
|
|
368
273
|
const fileName = parsedFilePath.base.replace(/\.css\.\w+$/, "");
|
|
369
|
-
const { content:
|
|
274
|
+
const { content: wrapperContent } = await readTemplate(framework.templates.component.wrapper, {
|
|
275
|
+
tag,
|
|
276
|
+
componentName,
|
|
277
|
+
styledComponentName,
|
|
278
|
+
className,
|
|
279
|
+
fileName
|
|
280
|
+
});
|
|
370
281
|
parsedFilePath.name = fileName.replace(/\.css$/, "");
|
|
371
|
-
parsedFilePath.ext =
|
|
282
|
+
parsedFilePath.ext = framework.templates.component.wrapperExt;
|
|
372
283
|
parsedFilePath.base = parsedFilePath.name + parsedFilePath.ext;
|
|
373
|
-
const
|
|
374
|
-
compiler_saltyCompiler.logger.info("Generating a new file: " +
|
|
375
|
-
await promises.writeFile(
|
|
376
|
-
await formatWithPrettier(
|
|
284
|
+
const formattedWrapperPath = path.format(parsedFilePath);
|
|
285
|
+
compiler_saltyCompiler.logger.info("Generating a new file: " + formattedWrapperPath);
|
|
286
|
+
await promises.writeFile(formattedWrapperPath, wrapperContent);
|
|
287
|
+
await formatWithPrettier(formattedWrapperPath);
|
|
377
288
|
}
|
|
378
|
-
const
|
|
289
|
+
const styledKey = reactComponent && framework.templates.component ? framework.templates.component.styled : framework.templates.styled;
|
|
290
|
+
const { content } = await readTemplate(styledKey, { tag, name: styledComponentName, className });
|
|
379
291
|
compiler_saltyCompiler.logger.info("Generating a new file: " + formattedStyledFilePath);
|
|
380
292
|
await promises.writeFile(formattedStyledFilePath, content);
|
|
381
293
|
await formatWithPrettier(formattedStyledFilePath);
|
|
382
294
|
});
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
295
|
+
};
|
|
296
|
+
const CSS_FILE_FOLDERS = ["src", "public", "assets", "styles", "css", "app"];
|
|
297
|
+
const CSS_SECOND_LEVEL_FOLDERS = ["styles", "css", "app", "pages"];
|
|
298
|
+
const CSS_FILE_NAMES = ["index", "styles", "main", "app", "global", "globals"];
|
|
299
|
+
const CSS_FILE_EXTENSIONS = [".css", ".scss", ".sass"];
|
|
300
|
+
const findGlobalCssFile = async (projectDir) => {
|
|
301
|
+
for (const folder of CSS_FILE_FOLDERS) {
|
|
302
|
+
for (const file of CSS_FILE_NAMES) {
|
|
303
|
+
for (const ext of CSS_FILE_EXTENSIONS) {
|
|
304
|
+
const filePath = path.join(projectDir, folder, file + ext);
|
|
305
|
+
const fileContent = await promises.readFile(filePath, "utf-8").catch(() => void 0);
|
|
306
|
+
if (fileContent !== void 0) return path.relative(projectDir, filePath);
|
|
307
|
+
for (const second of CSS_SECOND_LEVEL_FOLDERS) {
|
|
308
|
+
const nestedPath = path.join(projectDir, folder, second, file + ext);
|
|
309
|
+
const nestedContent = await promises.readFile(nestedPath, "utf-8").catch(() => void 0);
|
|
310
|
+
if (nestedContent !== void 0) return path.relative(projectDir, nestedPath);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
393
313
|
}
|
|
394
|
-
|
|
395
|
-
|
|
314
|
+
}
|
|
315
|
+
return void 0;
|
|
316
|
+
};
|
|
317
|
+
const astroPackage = (version) => `@salty-css/astro@${version}`;
|
|
318
|
+
const SALTY_ASTRO_IMPORT = "import saltyIntegration from '@salty-css/astro/integration';\n";
|
|
319
|
+
const editAstroConfig = (existing) => {
|
|
320
|
+
if (existing.includes("@salty-css/astro")) return { content: null };
|
|
321
|
+
let next = existing;
|
|
322
|
+
let inserted = false;
|
|
323
|
+
if (/integrations\s*:\s*\[/.test(next)) {
|
|
324
|
+
next = next.replace(/integrations\s*:\s*\[/, (m) => `${m}saltyIntegration(),`);
|
|
325
|
+
inserted = true;
|
|
326
|
+
} else if (/defineConfig\s*\(\s*\{/.test(next)) {
|
|
327
|
+
next = next.replace(/defineConfig\s*\(\s*\{/, (m) => `${m}
|
|
328
|
+
integrations: [saltyIntegration()],`);
|
|
329
|
+
inserted = true;
|
|
330
|
+
}
|
|
331
|
+
if (!inserted) {
|
|
332
|
+
return {
|
|
333
|
+
content: null,
|
|
334
|
+
warning: "Could not find a place to add saltyIntegration() in the Astro config. Please add it manually."
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return { content: SALTY_ASTRO_IMPORT + next };
|
|
338
|
+
};
|
|
339
|
+
const astroIntegration = {
|
|
340
|
+
name: "astro",
|
|
341
|
+
detect: (ctx) => findAstroConfig(ctx.projectDir),
|
|
342
|
+
apply: async (ctx, configPath) => {
|
|
343
|
+
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
344
|
+
if (existing === void 0) return { changed: false };
|
|
345
|
+
const result = editAstroConfig(existing);
|
|
346
|
+
if (result.warning) compiler_saltyCompiler.logger.warn(result.warning);
|
|
347
|
+
if (result.content === null) return { changed: false };
|
|
348
|
+
if (!ctx.skipInstall) await npmInstall(`-D ${astroPackage(ctx.cliVersion)}`);
|
|
349
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS integration to Astro config: " + configPath);
|
|
350
|
+
await promises.writeFile(configPath, result.content);
|
|
351
|
+
await formatWithPrettier(configPath);
|
|
352
|
+
return { changed: true };
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const ESLINT_CONFIG_CANDIDATES = [
|
|
356
|
+
// Project-local candidates first, then root-level.
|
|
357
|
+
["projectJs", "eslint.config.js"],
|
|
358
|
+
["rootJs", "eslint.config.js"],
|
|
359
|
+
["projectMjs", "eslint.config.mjs"],
|
|
360
|
+
["rootMjs", "eslint.config.mjs"],
|
|
361
|
+
["projectJson", ".eslintrc.json"],
|
|
362
|
+
["rootJson", ".eslintrc.json"]
|
|
363
|
+
];
|
|
364
|
+
const eslintConfigCandidates = (projectDir, rootDir) => {
|
|
365
|
+
return ESLINT_CONFIG_CANDIDATES.map(([scope, name]) => {
|
|
366
|
+
const base = scope.startsWith("root") ? rootDir : projectDir;
|
|
367
|
+
return path.join(base, name);
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
const editEslintConfig = (existing, isJsFlat) => {
|
|
371
|
+
if (existing.includes("salty-css")) return { content: null };
|
|
372
|
+
if (isJsFlat) {
|
|
373
|
+
const importStatement = 'import saltyCss from "@salty-css/eslint-config-core/flat";';
|
|
374
|
+
let newContent = `${importStatement}
|
|
375
|
+
${existing}`;
|
|
376
|
+
const isTsEslint = existing.includes("typescript-eslint");
|
|
377
|
+
if (isTsEslint) {
|
|
378
|
+
if (newContent.includes(".config(")) {
|
|
379
|
+
newContent = newContent.replace(".config(", ".config(saltyCss,");
|
|
380
|
+
} else {
|
|
381
|
+
return {
|
|
382
|
+
content: null,
|
|
383
|
+
warning: "Could not find the correct place to add the Salty-CSS config for ESLint. Please add it manually."
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
if (newContent.includes("export default [")) {
|
|
388
|
+
newContent = newContent.replace("export default [", "export default [ saltyCss,");
|
|
389
|
+
} else if (newContent.includes("eslintConfig = [")) {
|
|
390
|
+
newContent = newContent.replace("eslintConfig = [", "eslintConfig = [ saltyCss,");
|
|
391
|
+
} else {
|
|
392
|
+
return {
|
|
393
|
+
content: null,
|
|
394
|
+
warning: "Could not find the correct place to add the Salty-CSS config for ESLint. Please add it manually."
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return { content: newContent };
|
|
399
|
+
}
|
|
400
|
+
const json = JSON.parse(existing);
|
|
401
|
+
if (!json.extends) json.extends = [];
|
|
402
|
+
if (!json.extends.includes("@salty-css/core")) json.extends.push("@salty-css/core");
|
|
403
|
+
return { content: JSON.stringify(json, null, 2) };
|
|
404
|
+
};
|
|
405
|
+
const eslintIntegration = {
|
|
406
|
+
name: "eslint",
|
|
407
|
+
detect: (ctx) => {
|
|
408
|
+
const candidates = eslintConfigCandidates(ctx.projectDir, ctx.cwd);
|
|
409
|
+
return candidates.find((p) => fs.existsSync(p)) ?? null;
|
|
410
|
+
},
|
|
411
|
+
apply: async (ctx, configPath) => {
|
|
412
|
+
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
413
|
+
if (existing === void 0) {
|
|
414
|
+
compiler_saltyCompiler.logger.error("Could not read ESLint config file.");
|
|
415
|
+
return { changed: false };
|
|
416
|
+
}
|
|
417
|
+
if (!ctx.skipInstall) await npmInstall(corePackages.eslintConfigCore(ctx.cliVersion));
|
|
418
|
+
const result = editEslintConfig(existing, configPath.endsWith("js"));
|
|
419
|
+
if (result.warning) compiler_saltyCompiler.logger.warn(result.warning);
|
|
420
|
+
if (result.content === null) return { changed: false };
|
|
421
|
+
compiler_saltyCompiler.logger.info("Edit file: " + configPath);
|
|
422
|
+
await promises.writeFile(configPath, result.content);
|
|
423
|
+
await formatWithPrettier(configPath);
|
|
424
|
+
return { changed: true };
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const nextConfigFiles = ["next.config.js", "next.config.cjs", "next.config.ts", "next.config.mjs"];
|
|
428
|
+
const nextPackage = (version) => `@salty-css/next@${version}`;
|
|
429
|
+
const editNextConfig = (existing) => {
|
|
430
|
+
if (existing.includes("withSaltyCss")) return { content: null };
|
|
431
|
+
let next = existing;
|
|
432
|
+
let saltyCssAppended = false;
|
|
433
|
+
const hasPluginsArray = /\splugins([^=]*)=[^[]\[/.test(next);
|
|
434
|
+
if (hasPluginsArray) {
|
|
435
|
+
next = next.replace(/\splugins([^=]*)=[^[]\[/, (_, config) => ` plugins${config}= [withSaltyCss,`);
|
|
436
|
+
saltyCssAppended = true;
|
|
437
|
+
}
|
|
438
|
+
const useRequire = next.includes("module.exports");
|
|
439
|
+
const pluginImport = useRequire ? "const { withSaltyCss } = require('@salty-css/next');\n" : "import { withSaltyCss } from '@salty-css/next';\n";
|
|
440
|
+
if (useRequire && !saltyCssAppended) {
|
|
441
|
+
next = next.replace(/module.exports = ([^;]+)/, (_, config) => `module.exports = withSaltyCss(${config})`);
|
|
442
|
+
saltyCssAppended = true;
|
|
443
|
+
} else if (!saltyCssAppended) {
|
|
444
|
+
next = next.replace(/export default ([^;]+)/, (_, config) => `export default withSaltyCss(${config})`);
|
|
445
|
+
}
|
|
446
|
+
return { content: pluginImport + next };
|
|
447
|
+
};
|
|
448
|
+
const nextIntegration = {
|
|
449
|
+
name: "next",
|
|
450
|
+
detect: (ctx) => {
|
|
451
|
+
const found = nextConfigFiles.map((file) => path.join(ctx.projectDir, file)).find((p) => fs.existsSync(p));
|
|
452
|
+
return found ?? null;
|
|
453
|
+
},
|
|
454
|
+
apply: async (ctx, configPath) => {
|
|
455
|
+
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
456
|
+
if (existing === void 0) return { changed: false };
|
|
457
|
+
const { content } = editNextConfig(existing);
|
|
458
|
+
if (content === null) return { changed: false };
|
|
459
|
+
if (!ctx.skipInstall) await npmInstall(`-D ${nextPackage(ctx.cliVersion)}`);
|
|
460
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS plugin to Next.js config...");
|
|
461
|
+
await promises.writeFile(configPath, content);
|
|
462
|
+
await formatWithPrettier(configPath);
|
|
463
|
+
return { changed: true };
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const vitePackage = (version) => `@salty-css/vite@${version}`;
|
|
467
|
+
const editViteConfig = (existing) => {
|
|
468
|
+
if (existing.includes("saltyPlugin")) return { content: null };
|
|
469
|
+
const pluginImport = "import { saltyPlugin } from '@salty-css/vite';\n";
|
|
470
|
+
const pluginConfig = "saltyPlugin(__dirname),";
|
|
471
|
+
const withPlugin = existing.replace(/(plugins: \[)/, `$1
|
|
472
|
+
${pluginConfig}`);
|
|
473
|
+
return { content: pluginImport + withPlugin };
|
|
474
|
+
};
|
|
475
|
+
const viteIntegration = {
|
|
476
|
+
name: "vite",
|
|
477
|
+
detect: (ctx) => {
|
|
478
|
+
const path$1 = path.join(ctx.projectDir, "vite.config.ts");
|
|
479
|
+
return fs.existsSync(path$1) ? path$1 : null;
|
|
480
|
+
},
|
|
481
|
+
apply: async (ctx, configPath) => {
|
|
482
|
+
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
483
|
+
if (existing === void 0) return { changed: false };
|
|
484
|
+
const { content } = editViteConfig(existing);
|
|
485
|
+
if (content === null) return { changed: false };
|
|
486
|
+
compiler_saltyCompiler.logger.info("Edit file: " + configPath);
|
|
487
|
+
if (!ctx.skipInstall) await npmInstall(`-D ${vitePackage(ctx.cliVersion)}`);
|
|
488
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS plugin to Vite config...");
|
|
489
|
+
await promises.writeFile(configPath, content);
|
|
490
|
+
await formatWithPrettier(configPath);
|
|
491
|
+
return { changed: true };
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
const buildIntegrationRegistry = [eslintIntegration, viteIntegration, nextIntegration, astroIntegration];
|
|
495
|
+
const detectAndApplyIntegrations = async (ctx) => {
|
|
496
|
+
const results = [];
|
|
497
|
+
for (const integration of buildIntegrationRegistry) {
|
|
498
|
+
const configPath = await integration.detect(ctx);
|
|
499
|
+
if (!configPath) continue;
|
|
500
|
+
const result = await integration.apply(ctx, configPath);
|
|
501
|
+
results.push({ name: integration.name, configPath, changed: result.changed });
|
|
502
|
+
}
|
|
503
|
+
return results;
|
|
504
|
+
};
|
|
505
|
+
const writeProjectFile = async (projectDir, fileName, content) => {
|
|
506
|
+
const filePath = path.join(projectDir, fileName);
|
|
507
|
+
if (fs.existsSync(filePath)) {
|
|
508
|
+
compiler_saltyCompiler.logger.debug("File already exists: " + filePath);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const additionalFolders = fileName.split("/").slice(0, -1).join("/");
|
|
512
|
+
if (additionalFolders) await promises.mkdir(path.join(projectDir, additionalFolders), { recursive: true });
|
|
513
|
+
compiler_saltyCompiler.logger.info("Creating file: " + filePath);
|
|
514
|
+
await promises.writeFile(filePath, content);
|
|
515
|
+
await formatWithPrettier(filePath);
|
|
516
|
+
};
|
|
517
|
+
const ensureGitignoreSaltygen = async (rootDir) => {
|
|
518
|
+
const path$1 = path.join(rootDir, ".gitignore");
|
|
519
|
+
const existing = await promises.readFile(path$1, "utf-8").catch(() => void 0);
|
|
520
|
+
if (existing === void 0) return;
|
|
521
|
+
if (existing.includes("saltygen")) return;
|
|
522
|
+
compiler_saltyCompiler.logger.info("Edit file: " + path$1);
|
|
523
|
+
await promises.writeFile(path$1, existing + "\n\n# Salty-CSS\nsaltygen\n");
|
|
524
|
+
};
|
|
525
|
+
const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
526
|
+
const target = explicitCssFile ?? await findGlobalCssFile(projectDir);
|
|
527
|
+
if (!target) {
|
|
528
|
+
compiler_saltyCompiler.logger.warn("Could not find a CSS file to import the generated CSS. Please add it manually.");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const cssFilePath = path.join(projectDir, target);
|
|
532
|
+
const cssFileContent = await promises.readFile(cssFilePath, "utf-8").catch(() => void 0);
|
|
533
|
+
if (cssFileContent === void 0) return;
|
|
534
|
+
if (cssFileContent.includes("saltygen")) return;
|
|
535
|
+
const cssFileFolder = path.join(cssFilePath, "..");
|
|
536
|
+
const relPath = path.relative(cssFileFolder, path.join(projectDir, "saltygen/index.css"));
|
|
537
|
+
compiler_saltyCompiler.logger.info("Adding global import statement to CSS file: " + cssFilePath);
|
|
538
|
+
await promises.writeFile(cssFilePath, `@import '${relPath}';
|
|
539
|
+
` + cssFileContent);
|
|
540
|
+
await formatWithPrettier(cssFilePath);
|
|
541
|
+
};
|
|
542
|
+
const wirePrepareScript = async () => {
|
|
543
|
+
const pkg = await readPackageJson().catch(() => {
|
|
544
|
+
compiler_saltyCompiler.logError("Could not read package.json file.");
|
|
545
|
+
return void 0;
|
|
546
|
+
});
|
|
547
|
+
if (!pkg) return;
|
|
548
|
+
const { pkg: next } = addPrepareScript(pkg);
|
|
549
|
+
await updatePackageJson(next);
|
|
550
|
+
};
|
|
551
|
+
const registerInitCommand = (program) => {
|
|
552
|
+
program.command("init [directory]").description("Initialize a new Salty-CSS project.").option("-d, --dir <dir>", "Project directory to initialize the project in.").option("--css-file <css-file>", "Existing CSS file where to import the generated CSS. Path must be relative to the given project directory.").option("--skip-install", "Skip installing dependencies.").action(async function(_dir = ".") {
|
|
553
|
+
try {
|
|
554
|
+
const opts = this.opts();
|
|
555
|
+
const dir = opts.dir ?? _dir;
|
|
556
|
+
if (!dir) return compiler_saltyCompiler.logError("Project directory must be provided. Add it as the first argument after init command or use the --dir option.");
|
|
557
|
+
const ctx = await buildContext({ dir, skipInstall: opts.skipInstall });
|
|
558
|
+
compiler_saltyCompiler.logger.info("Initializing a new Salty-CSS project!");
|
|
559
|
+
const framework = await detectFramework(ctx);
|
|
560
|
+
compiler_saltyCompiler.logger.info(`Detected framework: ${framework.name}`);
|
|
561
|
+
if (!ctx.skipInstall) {
|
|
562
|
+
await npmInstall(corePackages.core(ctx.cliVersion), framework.runtimePackage(ctx.cliVersion));
|
|
563
|
+
}
|
|
564
|
+
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
565
|
+
await promises.mkdir(ctx.projectDir, { recursive: true });
|
|
566
|
+
await Promise.all(projectFiles.map(({ fileName, content }) => writeProjectFile(ctx.projectDir, fileName, content)));
|
|
567
|
+
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
|
|
568
|
+
await ensureGitignoreSaltygen(ctx.cwd);
|
|
569
|
+
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
570
|
+
await detectAndApplyIntegrations(ctx);
|
|
571
|
+
await wirePrepareScript();
|
|
572
|
+
compiler_saltyCompiler.logger.info("Running the build to generate initial CSS...");
|
|
573
|
+
const compiler = new compiler_saltyCompiler.SaltyCompiler(ctx.projectDir);
|
|
574
|
+
await compiler.generateCss();
|
|
575
|
+
compiler_saltyCompiler.logger.info("🎉 Salty CSS project initialized successfully!");
|
|
576
|
+
compiler_saltyCompiler.logger.info("Next steps:");
|
|
577
|
+
compiler_saltyCompiler.logger.info("1. Configure variables and templates in `salty.config.ts`");
|
|
578
|
+
compiler_saltyCompiler.logger.info("2. Create a new component with `npx salty-css generate [component-name]`");
|
|
579
|
+
compiler_saltyCompiler.logger.info("3. Run `npx salty-css build` to generate the CSS");
|
|
580
|
+
compiler_saltyCompiler.logger.info("4. Read about the features in the documentation: https://salty-css.dev");
|
|
581
|
+
compiler_saltyCompiler.logger.info("5. Star the project on GitHub: https://github.com/margarita-form/salty-css ⭐");
|
|
582
|
+
} catch (err) {
|
|
583
|
+
return compiler_saltyCompiler.logError(err instanceof Error ? err.message : String(err));
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
};
|
|
587
|
+
const getSaltyCssPackages = async () => {
|
|
588
|
+
const packageJSONPath = path.join(process.cwd(), "package.json");
|
|
589
|
+
const packageJson = await readPackageJson(packageJSONPath).catch((err) => compiler_saltyCompiler.logError(err));
|
|
590
|
+
if (!packageJson) return compiler_saltyCompiler.logError("Could not read package.json file.");
|
|
591
|
+
const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
592
|
+
const saltyCssPackages = Object.entries(allDependencies).filter(([name]) => name === "salty-css" || name.startsWith("@salty-css/"));
|
|
593
|
+
if (!saltyCssPackages.length) {
|
|
594
|
+
return compiler_saltyCompiler.logError(
|
|
595
|
+
"No Salty-CSS packages found in package.json. Make sure you are running update command in the same directory! Used package.json path: " + packageJSONPath
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return saltyCssPackages;
|
|
599
|
+
};
|
|
600
|
+
const registerUpdateCommand = (program) => {
|
|
396
601
|
program.command("update [version]").alias("up").description("Update Salty-CSS packages to the latest or specified version.").option("-v, --version <version>", "Version to update to.").option("--legacy-peer-deps <legacyPeerDeps>", "Use legacy peer dependencies (not recommended).", false).action(async function(_version = "latest") {
|
|
397
602
|
const { legacyPeerDeps, version = _version } = this.opts();
|
|
398
603
|
const saltyCssPackages = await getSaltyCssPackages();
|
|
399
604
|
if (!saltyCssPackages) return compiler_saltyCompiler.logError("Could not update Salty-CSS packages as any were found in package.json.");
|
|
605
|
+
const cli = await readThisPackageJson();
|
|
400
606
|
const packagesToUpdate = saltyCssPackages.map(([name]) => {
|
|
401
|
-
if (version === "@") return `${name}@${
|
|
607
|
+
if (version === "@") return `${name}@${cli.version}`;
|
|
402
608
|
return `${name}@${version.replace(/^@/, "")}`;
|
|
403
609
|
});
|
|
404
610
|
if (legacyPeerDeps) {
|
|
@@ -416,19 +622,19 @@ ${eslintConfigContent}`;
|
|
|
416
622
|
}, {});
|
|
417
623
|
const versionsCount = Object.keys(mappedByVersions).length;
|
|
418
624
|
if (versionsCount === 1) {
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
compiler_saltyCompiler.logger.info(`Updated to all Salty CSS packages successfully to ${versionString}`);
|
|
625
|
+
const v = Object.keys(mappedByVersions)[0];
|
|
626
|
+
compiler_saltyCompiler.logger.info(`Updated to all Salty CSS packages successfully to ${v.replace(/^\^/, "")}`);
|
|
422
627
|
} else {
|
|
423
|
-
for (const [
|
|
424
|
-
|
|
425
|
-
compiler_saltyCompiler.logger.info(`Updated to ${versionString}: ${names.join(", ")}`);
|
|
628
|
+
for (const [v, names] of Object.entries(mappedByVersions)) {
|
|
629
|
+
compiler_saltyCompiler.logger.info(`Updated to ${v.replace(/^\^/, "")}: ${names.join(", ")}`);
|
|
426
630
|
}
|
|
427
631
|
}
|
|
428
632
|
});
|
|
633
|
+
};
|
|
634
|
+
const registerVersionOption = (program) => {
|
|
429
635
|
program.option("-v, --version", "Show the current version of Salty-CSS.").action(async function() {
|
|
430
|
-
const
|
|
431
|
-
compiler_saltyCompiler.logger.info("CLI is running: " +
|
|
636
|
+
const cli = await readThisPackageJson();
|
|
637
|
+
compiler_saltyCompiler.logger.info("CLI is running: " + cli.version);
|
|
432
638
|
const packageJSONPath = path.join(process.cwd(), "package.json");
|
|
433
639
|
const packageJson = await readPackageJson(packageJSONPath).catch((err) => compiler_saltyCompiler.logError(err));
|
|
434
640
|
if (!packageJson) return;
|
|
@@ -443,6 +649,16 @@ ${eslintConfigContent}`;
|
|
|
443
649
|
compiler_saltyCompiler.logger.info(`${dep}: ${allDependencies[dep]}`);
|
|
444
650
|
}
|
|
445
651
|
});
|
|
652
|
+
};
|
|
653
|
+
async function main() {
|
|
654
|
+
const program = new commander.Command();
|
|
655
|
+
program.name("salty-css").description("Salty-CSS CLI tool to help with annoying configuration tasks.");
|
|
656
|
+
const defaultProject = await getDefaultProject();
|
|
657
|
+
registerInitCommand(program);
|
|
658
|
+
registerBuildCommand(program, defaultProject);
|
|
659
|
+
registerGenerateCommand(program, defaultProject);
|
|
660
|
+
registerUpdateCommand(program);
|
|
661
|
+
registerVersionOption(program);
|
|
446
662
|
program.parseAsync(process.argv);
|
|
447
663
|
}
|
|
448
664
|
exports.main = main;
|