@salty-css/core 0.1.0-alpha.9 → 0.1.0-refactor-add-additional-paths-to-config-cache.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/confirm-install.d.ts +34 -0
- package/bin/context.d.ts +3 -0
- package/bin/integrations/index.d.ts +11 -3
- package/bin/integrations/types.d.ts +14 -2
- package/bin/main.cjs +197 -95
- package/bin/main.js +151 -49
- package/cache/resolve-dynamic-config-cache.cjs +64 -16
- package/cache/resolve-dynamic-config-cache.d.ts +20 -1
- package/cache/resolve-dynamic-config-cache.js +65 -17
- package/{class-name-generator-YeSQe_Ik.js → class-name-generator-CMWY5KTJ.js} +1 -1
- package/{class-name-generator-B2Pb2obX.cjs → class-name-generator-DB5aQwC_.cjs} +1 -1
- package/compiler/copy-config-cache.cjs +39 -0
- package/compiler/copy-config-cache.d.ts +17 -0
- package/compiler/copy-config-cache.js +39 -0
- package/compiler/salty-compiler.cjs +22 -21
- package/compiler/salty-compiler.d.ts +11 -1
- package/compiler/salty-compiler.js +20 -19
- package/css/dynamic-styles.cjs +21 -8
- package/css/dynamic-styles.d.ts +39 -0
- package/css/dynamic-styles.js +22 -9
- package/css/index.cjs +1 -0
- package/css/index.js +2 -1
- package/css/keyframes.cjs +1 -1
- package/css/keyframes.js +1 -1
- package/generators/index.cjs +1 -1
- package/generators/index.js +2 -2
- package/instances/classname-instance.cjs +1 -1
- package/instances/classname-instance.js +1 -1
- package/logger-7xz0pyAz.cjs +12 -0
- package/logger-hHmCwThj.js +13 -0
- package/package.json +5 -1
- package/{parse-styles-CA3TP5n1.cjs → parse-styles-C54MOrPg.cjs} +106 -7
- package/{parse-styles-BTIoYnBr.js → parse-styles-CLMTHo2H.js} +107 -8
- package/parsers/index.cjs +2 -1
- package/parsers/index.d.ts +1 -0
- package/parsers/index.js +4 -3
- package/parsers/parser-regexes.d.ts +3 -0
- package/parsers/strict.d.ts +2 -0
- package/runtime/index.cjs +1 -1
- package/runtime/index.js +1 -1
- package/{salty.config-cqavVm2t.cjs → salty.config-DogY_sSQ.cjs} +1 -1
- package/salty.config-GV37Q-D2.js +4 -0
- package/types/config-types.d.ts +9 -0
- package/salty.config-DjosWdPw.js +0 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a single package spec for display. Translates the internal
|
|
3
|
+
* `'-D <pkg>@<ver>'` shorthand used by `npmInstall` into a `<pkg>@<ver> (dev)`
|
|
4
|
+
* suffix so the user-facing list reads naturally.
|
|
5
|
+
*/
|
|
6
|
+
export declare const formatPackageForDisplay: (spec: string) => string;
|
|
7
|
+
export declare const renderPackageList: (packages: string[]) => string;
|
|
8
|
+
export interface ConfirmInstallOptions {
|
|
9
|
+
/** Streams used for the prompt — defaults to process.stdin/stdout. Allows tests to inject. */
|
|
10
|
+
input?: NodeJS.ReadableStream;
|
|
11
|
+
output?: NodeJS.WritableStream;
|
|
12
|
+
/** Whether the input is a TTY — defaults to process.stdin.isTTY. Allows tests to override. */
|
|
13
|
+
isTTY?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Confirm a batched install. Resolves on success, throws on decline.
|
|
17
|
+
*
|
|
18
|
+
* - `yes=true` or empty `packages` → no prompt, resolves immediately.
|
|
19
|
+
* - Non-TTY without `yes` → throws, telling the user to pass `--yes`.
|
|
20
|
+
* - Otherwise prints the list and asks `Proceed? (y/N)`. Accepts y/yes
|
|
21
|
+
* (case-insensitive); anything else throws to abort the command.
|
|
22
|
+
*/
|
|
23
|
+
export declare const confirmInstall: (packages: string[], yes: boolean, options?: ConfirmInstallOptions) => Promise<void>;
|
|
24
|
+
export interface ConfirmYesNoOptions extends ConfirmInstallOptions {
|
|
25
|
+
/** When true, resolves true without prompting. */
|
|
26
|
+
yes?: boolean;
|
|
27
|
+
/** When true, an empty answer counts as yes. Defaults to false (empty = no). */
|
|
28
|
+
defaultYes?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generic yes/no prompt. Unlike `confirmInstall`, non-TTY without `yes`
|
|
32
|
+
* resolves `false` instead of throwing — callers can choose policy.
|
|
33
|
+
*/
|
|
34
|
+
export declare const confirmYesNo: (question: string, options?: ConfirmYesNoOptions) => Promise<boolean>;
|
package/bin/context.d.ts
CHANGED
|
@@ -8,11 +8,14 @@ export interface ProjectContext {
|
|
|
8
8
|
rcFile: RCFile;
|
|
9
9
|
cliVersion: string;
|
|
10
10
|
skipInstall: boolean;
|
|
11
|
+
yes: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare const resolveProjectDir: (dir: string, rootDir?: string) => string;
|
|
13
14
|
export interface BuildContextOptions {
|
|
14
15
|
dir: string;
|
|
15
16
|
skipInstall?: boolean;
|
|
17
|
+
/** Skip the install confirmation prompt and install without asking. */
|
|
18
|
+
yes?: boolean;
|
|
16
19
|
/** When false, build context even if package.json is missing (used by commands that should not require one). */
|
|
17
20
|
requirePackageJson?: boolean;
|
|
18
21
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { ProjectContext } from '../context';
|
|
2
|
-
import { BuildIntegrationAdapter } from './types';
|
|
2
|
+
import { BuildIntegrationAdapter, IntegrationPlan } from './types';
|
|
3
3
|
export declare const buildIntegrationRegistry: BuildIntegrationAdapter[];
|
|
4
|
-
export
|
|
4
|
+
export interface PlannedIntegration {
|
|
5
|
+
name: string;
|
|
6
|
+
configPath: string;
|
|
7
|
+
plan: IntegrationPlan;
|
|
8
|
+
}
|
|
9
|
+
/** Detect every integration that has work to do and compute its plan. */
|
|
10
|
+
export declare const planIntegrations: (ctx: ProjectContext) => Promise<PlannedIntegration[]>;
|
|
11
|
+
/** Execute each previously-planned integration (writes config files). */
|
|
12
|
+
export declare const applyIntegrationPlans: (planned: PlannedIntegration[]) => Promise<{
|
|
5
13
|
name: string;
|
|
6
14
|
configPath: string;
|
|
7
15
|
changed: boolean;
|
|
8
16
|
}[]>;
|
|
9
|
-
export type { BuildIntegrationAdapter, ConfigEdit } from './types';
|
|
17
|
+
export type { BuildIntegrationAdapter, ConfigEdit, IntegrationPlan } from './types';
|
|
10
18
|
export { viteIntegration, editViteConfig, vitePackage } from './vite';
|
|
11
19
|
export { nextIntegration, editNextConfig, nextPackage, nextConfigFiles } from './next';
|
|
12
20
|
export { astroIntegration, editAstroConfig, astroPackage } from './astro';
|
|
@@ -3,12 +3,24 @@ export interface IntegrationApplyResult {
|
|
|
3
3
|
/** Whether any file was edited or any install was performed. */
|
|
4
4
|
changed: boolean;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Pending integration work — the packages it would like installed and a
|
|
8
|
+
* closure that writes the config edits once the install (if any) has run.
|
|
9
|
+
*/
|
|
10
|
+
export interface IntegrationPlan {
|
|
11
|
+
/** Packages to install, using the same `npmInstall` shorthand (e.g. `'-D @salty-css/vite@1.2.3'`). */
|
|
12
|
+
packages: string[];
|
|
13
|
+
execute(): Promise<IntegrationApplyResult>;
|
|
14
|
+
}
|
|
6
15
|
export interface BuildIntegrationAdapter {
|
|
7
16
|
name: string;
|
|
8
17
|
/** Returns the config file path this integration targets, or null when not applicable. */
|
|
9
18
|
detect(ctx: ProjectContext): Promise<string | null> | string | null;
|
|
10
|
-
/**
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Idempotently produce the work needed to wire the integration in. Returns
|
|
21
|
+
* null when the integration is already wired (nothing to do).
|
|
22
|
+
*/
|
|
23
|
+
plan(ctx: ProjectContext, configPath: string): Promise<IntegrationPlan | null>;
|
|
12
24
|
}
|
|
13
25
|
/** Pure transform — returned by an integration when it knows how to rewrite a config file. */
|
|
14
26
|
export interface ConfigEdit {
|
package/bin/main.cjs
CHANGED
|
@@ -9,8 +9,10 @@ const path = require("path");
|
|
|
9
9
|
const promises = require("fs/promises");
|
|
10
10
|
const child_process = require("child_process");
|
|
11
11
|
const ora = require("ora");
|
|
12
|
+
const logger = require("../logger-7xz0pyAz.cjs");
|
|
12
13
|
const pascalCase = require("../pascal-case-By_l58S-.cjs");
|
|
13
14
|
const ejs = require("ejs");
|
|
15
|
+
const promises$1 = require("readline/promises");
|
|
14
16
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
15
17
|
const defaultPackageJsonPath = path.join(process.cwd(), "package.json");
|
|
16
18
|
const readPackageJson = async (filePath = defaultPackageJsonPath) => {
|
|
@@ -64,9 +66,9 @@ async function formatWithPrettier(filePath) {
|
|
|
64
66
|
const hasPrettier = hasPrettierInstalled();
|
|
65
67
|
if (!hasPrettier) return;
|
|
66
68
|
await execAsync(`./node_modules/.bin/prettier --write "${filePath}"`);
|
|
67
|
-
|
|
69
|
+
logger.logger.info(`Formatted ${filePath} with Prettier`);
|
|
68
70
|
} catch (error) {
|
|
69
|
-
|
|
71
|
+
logger.logger.error(`Error formatting ${filePath} with Prettier:`, error);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
const SALTYRC_FILENAME = ".saltyrc.json";
|
|
@@ -109,8 +111,8 @@ const writeProjectToRc = async (cwd, relativeProjectPath, framework) => {
|
|
|
109
111
|
const existing = await readRawRc(cwd);
|
|
110
112
|
const { content, changed, created } = upsertProjectInRc(existing, relativeProjectPath, framework);
|
|
111
113
|
if (!changed) return false;
|
|
112
|
-
if (created)
|
|
113
|
-
else
|
|
114
|
+
if (created) logger.logger.info("Creating file: " + path2);
|
|
115
|
+
else logger.logger.info("Edit file: " + path2);
|
|
114
116
|
await promises.writeFile(path2, content);
|
|
115
117
|
await formatWithPrettier(path2);
|
|
116
118
|
return true;
|
|
@@ -141,20 +143,24 @@ const buildContext = async (opts) => {
|
|
|
141
143
|
packageJson,
|
|
142
144
|
rcFile,
|
|
143
145
|
cliVersion: cliPackageJson.version || "0.0.0",
|
|
144
|
-
skipInstall: !!opts.skipInstall
|
|
146
|
+
skipInstall: !!opts.skipInstall,
|
|
147
|
+
yes: !!opts.yes
|
|
145
148
|
};
|
|
146
149
|
};
|
|
147
150
|
const registerBuildCommand = (program, defaultProject) => {
|
|
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) {
|
|
149
|
-
|
|
150
|
-
const { dir = _dir, watch } = this.opts();
|
|
151
|
+
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.").option("--mode <mode>", 'Build mode: "production" or "development". Defaults to NODE_ENV-based detection.').action(async function(_dir = defaultProject) {
|
|
152
|
+
logger.logger.info("Building the Salty-CSS project...");
|
|
153
|
+
const { dir = _dir, watch, mode } = this.opts();
|
|
154
|
+
if (mode !== void 0 && mode !== "production" && mode !== "development") {
|
|
155
|
+
return logger.logError(`Invalid --mode "${mode}". Expected "production" or "development".`);
|
|
156
|
+
}
|
|
151
157
|
const resolved = dir ?? await getDefaultProject();
|
|
152
|
-
if (!resolved) return
|
|
158
|
+
if (!resolved) return logger.logError("Project directory must be provided. Add it as the first argument after build command or use the --dir option.");
|
|
153
159
|
const projectDir = resolveProjectDir(resolved);
|
|
154
|
-
const compiler = new compiler_saltyCompiler.SaltyCompiler(projectDir);
|
|
160
|
+
const compiler = new compiler_saltyCompiler.SaltyCompiler(projectDir, { mode });
|
|
155
161
|
await compiler.generateCss();
|
|
156
162
|
if (watch) {
|
|
157
|
-
|
|
163
|
+
logger.logger.info("Watching for changes in the project directory...");
|
|
158
164
|
fs.watch(projectDir, { recursive: true }, async (_event, filePath) => {
|
|
159
165
|
const shouldRestart$1 = await shouldRestart.checkShouldRestart(filePath);
|
|
160
166
|
if (shouldRestart$1) {
|
|
@@ -225,7 +231,7 @@ const getFramework = (name) => {
|
|
|
225
231
|
return frameworksByName[name];
|
|
226
232
|
};
|
|
227
233
|
const templateLoaders = {
|
|
228
|
-
"salty.config.ts": () => Promise.resolve().then(() => require("../salty.config-
|
|
234
|
+
"salty.config.ts": () => Promise.resolve().then(() => require("../salty.config-DogY_sSQ.cjs")),
|
|
229
235
|
"saltygen/index.css": () => Promise.resolve().then(() => require("../index-ByR0nfaf.cjs")),
|
|
230
236
|
"react/styled-file.ts": () => Promise.resolve().then(() => require("../styled-file-CPd_rTW2.cjs")),
|
|
231
237
|
"react/vanilla-file.ts": () => Promise.resolve().then(() => require("../vanilla-file-r0fp2q_m.cjs")),
|
|
@@ -240,13 +246,13 @@ const readTemplate = async (key, options) => {
|
|
|
240
246
|
const registerGenerateCommand = (program, defaultProject) => {
|
|
241
247
|
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) {
|
|
242
248
|
const { file = _file, dir = _dir, tag, name, className, reactComponent = false } = this.opts();
|
|
243
|
-
if (!file) return
|
|
244
|
-
if (!dir) return
|
|
249
|
+
if (!file) return logger.logError("File to generate must be provided. Add it as the first argument after generate command or use the --file option.");
|
|
250
|
+
if (!dir) return logger.logError("Project directory must be provided. Add it as the second argument after generate command or use the --dir option.");
|
|
245
251
|
let ctx;
|
|
246
252
|
try {
|
|
247
253
|
ctx = await buildContext({ dir, requirePackageJson: false });
|
|
248
254
|
} catch (err) {
|
|
249
|
-
return
|
|
255
|
+
return logger.logError(err instanceof Error ? err.message : String(err));
|
|
250
256
|
}
|
|
251
257
|
const rcFramework = getFramework(getProjectFramework(ctx.rcFile, ctx.relativeProjectPath));
|
|
252
258
|
const framework = rcFramework ?? await detectFramework(ctx);
|
|
@@ -260,13 +266,13 @@ const registerGenerateCommand = (program, defaultProject) => {
|
|
|
260
266
|
const formattedStyledFilePath = path.format(parsedFilePath);
|
|
261
267
|
const alreadyExists = await promises.readFile(formattedStyledFilePath, "utf-8").catch(() => void 0);
|
|
262
268
|
if (alreadyExists !== void 0) {
|
|
263
|
-
|
|
269
|
+
logger.logger.error("File already exists: " + formattedStyledFilePath);
|
|
264
270
|
return;
|
|
265
271
|
}
|
|
266
272
|
let styledComponentName = pascalCase.pascalCase(name || parsedFilePath.base.replace(/\.css\.\w+$/, ""));
|
|
267
273
|
if (reactComponent) {
|
|
268
274
|
if (!framework.templates.component) {
|
|
269
|
-
return
|
|
275
|
+
return logger.logError(`--reactComponent is not supported for the ${framework.name} framework.`);
|
|
270
276
|
}
|
|
271
277
|
const componentName = styledComponentName + "Component";
|
|
272
278
|
styledComponentName = styledComponentName + "Wrapper";
|
|
@@ -282,17 +288,63 @@ const registerGenerateCommand = (program, defaultProject) => {
|
|
|
282
288
|
parsedFilePath.ext = framework.templates.component.wrapperExt;
|
|
283
289
|
parsedFilePath.base = parsedFilePath.name + parsedFilePath.ext;
|
|
284
290
|
const formattedWrapperPath = path.format(parsedFilePath);
|
|
285
|
-
|
|
291
|
+
logger.logger.info("Generating a new file: " + formattedWrapperPath);
|
|
286
292
|
await promises.writeFile(formattedWrapperPath, wrapperContent);
|
|
287
293
|
await formatWithPrettier(formattedWrapperPath);
|
|
288
294
|
}
|
|
289
295
|
const styledKey = reactComponent && framework.templates.component ? framework.templates.component.styled : framework.templates.styled;
|
|
290
296
|
const { content } = await readTemplate(styledKey, { tag, name: styledComponentName, className });
|
|
291
|
-
|
|
297
|
+
logger.logger.info("Generating a new file: " + formattedStyledFilePath);
|
|
292
298
|
await promises.writeFile(formattedStyledFilePath, content);
|
|
293
299
|
await formatWithPrettier(formattedStyledFilePath);
|
|
294
300
|
});
|
|
295
301
|
};
|
|
302
|
+
const formatPackageForDisplay = (spec) => {
|
|
303
|
+
const trimmed = spec.trim();
|
|
304
|
+
if (trimmed.startsWith("-D ")) return `${trimmed.slice(3).trim()} (dev)`;
|
|
305
|
+
return trimmed;
|
|
306
|
+
};
|
|
307
|
+
const renderPackageList = (packages) => {
|
|
308
|
+
return packages.map((p) => ` + ${formatPackageForDisplay(p)}`).join("\n");
|
|
309
|
+
};
|
|
310
|
+
const confirmInstall = async (packages, yes, options = {}) => {
|
|
311
|
+
if (yes) return;
|
|
312
|
+
if (packages.length === 0) return;
|
|
313
|
+
const input = options.input ?? process.stdin;
|
|
314
|
+
const output = options.output ?? process.stdout;
|
|
315
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
|
|
316
|
+
if (!isTTY) {
|
|
317
|
+
throw new Error("Cannot prompt for install confirmation: stdin is not a TTY. Re-run with --yes to install the listed packages without prompting.");
|
|
318
|
+
}
|
|
319
|
+
output.write(`The following packages will be installed:
|
|
320
|
+
${renderPackageList(packages)}
|
|
321
|
+
`);
|
|
322
|
+
const rl = promises$1.createInterface({ input, output, terminal: false });
|
|
323
|
+
try {
|
|
324
|
+
const answer = (await rl.question("Proceed? (y/N) ")).trim().toLowerCase();
|
|
325
|
+
if (answer !== "y" && answer !== "yes") {
|
|
326
|
+
throw new Error("Install cancelled by user.");
|
|
327
|
+
}
|
|
328
|
+
} finally {
|
|
329
|
+
rl.close();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const confirmYesNo = async (question, options = {}) => {
|
|
333
|
+
if (options.yes) return true;
|
|
334
|
+
const input = options.input ?? process.stdin;
|
|
335
|
+
const output = options.output ?? process.stdout;
|
|
336
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
|
|
337
|
+
if (!isTTY) return false;
|
|
338
|
+
const suffix = options.defaultYes ? "(Y/n)" : "(y/N)";
|
|
339
|
+
const rl = promises$1.createInterface({ input, output, terminal: false });
|
|
340
|
+
try {
|
|
341
|
+
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
342
|
+
if (answer === "") return !!options.defaultYes;
|
|
343
|
+
return answer === "y" || answer === "yes";
|
|
344
|
+
} finally {
|
|
345
|
+
rl.close();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
296
348
|
const CSS_FILE_FOLDERS = ["src", "public", "assets", "styles", "css", "app"];
|
|
297
349
|
const CSS_SECOND_LEVEL_FOLDERS = ["styles", "css", "app", "pages"];
|
|
298
350
|
const CSS_FILE_NAMES = ["index", "styles", "main", "app", "global", "globals"];
|
|
@@ -339,17 +391,22 @@ const editAstroConfig = (existing) => {
|
|
|
339
391
|
const astroIntegration = {
|
|
340
392
|
name: "astro",
|
|
341
393
|
detect: (ctx) => findAstroConfig(ctx.projectDir),
|
|
342
|
-
|
|
394
|
+
plan: async (ctx, configPath) => {
|
|
343
395
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
344
|
-
if (existing === void 0) return
|
|
396
|
+
if (existing === void 0) return null;
|
|
345
397
|
const result = editAstroConfig(existing);
|
|
346
|
-
if (result.warning)
|
|
347
|
-
if (result.content === null) return
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
398
|
+
if (result.warning) logger.logger.warn(result.warning);
|
|
399
|
+
if (result.content === null) return null;
|
|
400
|
+
const newContent = result.content;
|
|
401
|
+
return {
|
|
402
|
+
packages: [`-D ${astroPackage(ctx.cliVersion)}`],
|
|
403
|
+
execute: async () => {
|
|
404
|
+
logger.logger.info("Adding Salty-CSS integration to Astro config: " + configPath);
|
|
405
|
+
await promises.writeFile(configPath, newContent);
|
|
406
|
+
await formatWithPrettier(configPath);
|
|
407
|
+
return { changed: true };
|
|
408
|
+
}
|
|
409
|
+
};
|
|
353
410
|
}
|
|
354
411
|
};
|
|
355
412
|
const ESLINT_CONFIG_CANDIDATES = [
|
|
@@ -408,20 +465,25 @@ const eslintIntegration = {
|
|
|
408
465
|
const candidates = eslintConfigCandidates(ctx.projectDir, ctx.cwd);
|
|
409
466
|
return candidates.find((p) => fs.existsSync(p)) ?? null;
|
|
410
467
|
},
|
|
411
|
-
|
|
468
|
+
plan: async (ctx, configPath) => {
|
|
412
469
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
413
470
|
if (existing === void 0) {
|
|
414
|
-
|
|
415
|
-
return
|
|
471
|
+
logger.logger.error("Could not read ESLint config file.");
|
|
472
|
+
return null;
|
|
416
473
|
}
|
|
417
|
-
if (!ctx.skipInstall) await npmInstall(corePackages.eslintConfigCore(ctx.cliVersion));
|
|
418
474
|
const result = editEslintConfig(existing, configPath.endsWith("js"));
|
|
419
|
-
if (result.warning)
|
|
420
|
-
if (result.content === null) return
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
475
|
+
if (result.warning) logger.logger.warn(result.warning);
|
|
476
|
+
if (result.content === null) return null;
|
|
477
|
+
const newContent = result.content;
|
|
478
|
+
return {
|
|
479
|
+
packages: [corePackages.eslintConfigCore(ctx.cliVersion)],
|
|
480
|
+
execute: async () => {
|
|
481
|
+
logger.logger.info("Edit file: " + configPath);
|
|
482
|
+
await promises.writeFile(configPath, newContent);
|
|
483
|
+
await formatWithPrettier(configPath);
|
|
484
|
+
return { changed: true };
|
|
485
|
+
}
|
|
486
|
+
};
|
|
425
487
|
}
|
|
426
488
|
};
|
|
427
489
|
const nextConfigFiles = ["next.config.js", "next.config.cjs", "next.config.ts", "next.config.mjs"];
|
|
@@ -451,16 +513,20 @@ const nextIntegration = {
|
|
|
451
513
|
const found = nextConfigFiles.map((file) => path.join(ctx.projectDir, file)).find((p) => fs.existsSync(p));
|
|
452
514
|
return found ?? null;
|
|
453
515
|
},
|
|
454
|
-
|
|
516
|
+
plan: async (ctx, configPath) => {
|
|
455
517
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
456
|
-
if (existing === void 0) return
|
|
518
|
+
if (existing === void 0) return null;
|
|
457
519
|
const { content } = editNextConfig(existing);
|
|
458
|
-
if (content === null) return
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
520
|
+
if (content === null) return null;
|
|
521
|
+
return {
|
|
522
|
+
packages: [`-D ${nextPackage(ctx.cliVersion)}`],
|
|
523
|
+
execute: async () => {
|
|
524
|
+
logger.logger.info("Adding Salty-CSS plugin to Next.js config...");
|
|
525
|
+
await promises.writeFile(configPath, content);
|
|
526
|
+
await formatWithPrettier(configPath);
|
|
527
|
+
return { changed: true };
|
|
528
|
+
}
|
|
529
|
+
};
|
|
464
530
|
}
|
|
465
531
|
};
|
|
466
532
|
const vitePackage = (version) => `@salty-css/vite@${version}`;
|
|
@@ -478,39 +544,52 @@ const viteIntegration = {
|
|
|
478
544
|
const path$1 = path.join(ctx.projectDir, "vite.config.ts");
|
|
479
545
|
return fs.existsSync(path$1) ? path$1 : null;
|
|
480
546
|
},
|
|
481
|
-
|
|
547
|
+
plan: async (ctx, configPath) => {
|
|
482
548
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
483
|
-
if (existing === void 0) return
|
|
549
|
+
if (existing === void 0) return null;
|
|
484
550
|
const { content } = editViteConfig(existing);
|
|
485
|
-
if (content === null) return
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
551
|
+
if (content === null) return null;
|
|
552
|
+
return {
|
|
553
|
+
packages: [`-D ${vitePackage(ctx.cliVersion)}`],
|
|
554
|
+
execute: async () => {
|
|
555
|
+
logger.logger.info("Edit file: " + configPath);
|
|
556
|
+
logger.logger.info("Adding Salty-CSS plugin to Vite config...");
|
|
557
|
+
await promises.writeFile(configPath, content);
|
|
558
|
+
await formatWithPrettier(configPath);
|
|
559
|
+
return { changed: true };
|
|
560
|
+
}
|
|
561
|
+
};
|
|
492
562
|
}
|
|
493
563
|
};
|
|
494
564
|
const buildIntegrationRegistry = [eslintIntegration, viteIntegration, nextIntegration, astroIntegration];
|
|
495
|
-
const
|
|
496
|
-
const
|
|
565
|
+
const planIntegrations = async (ctx) => {
|
|
566
|
+
const planned = [];
|
|
497
567
|
for (const integration of buildIntegrationRegistry) {
|
|
498
568
|
const configPath = await integration.detect(ctx);
|
|
499
569
|
if (!configPath) continue;
|
|
500
|
-
const
|
|
501
|
-
|
|
570
|
+
const plan = await integration.plan(ctx, configPath);
|
|
571
|
+
if (!plan) continue;
|
|
572
|
+
planned.push({ name: integration.name, configPath, plan });
|
|
573
|
+
}
|
|
574
|
+
return planned;
|
|
575
|
+
};
|
|
576
|
+
const applyIntegrationPlans = async (planned) => {
|
|
577
|
+
const results = [];
|
|
578
|
+
for (const { name, configPath, plan } of planned) {
|
|
579
|
+
const result = await plan.execute();
|
|
580
|
+
results.push({ name, configPath, changed: result.changed });
|
|
502
581
|
}
|
|
503
582
|
return results;
|
|
504
583
|
};
|
|
505
584
|
const writeProjectFile = async (projectDir, fileName, content) => {
|
|
506
585
|
const filePath = path.join(projectDir, fileName);
|
|
507
586
|
if (fs.existsSync(filePath)) {
|
|
508
|
-
|
|
587
|
+
logger.logger.debug("File already exists: " + filePath);
|
|
509
588
|
return;
|
|
510
589
|
}
|
|
511
590
|
const additionalFolders = fileName.split("/").slice(0, -1).join("/");
|
|
512
591
|
if (additionalFolders) await promises.mkdir(path.join(projectDir, additionalFolders), { recursive: true });
|
|
513
|
-
|
|
592
|
+
logger.logger.info("Creating file: " + filePath);
|
|
514
593
|
await promises.writeFile(filePath, content);
|
|
515
594
|
await formatWithPrettier(filePath);
|
|
516
595
|
};
|
|
@@ -519,13 +598,13 @@ const ensureGitignoreSaltygen = async (rootDir) => {
|
|
|
519
598
|
const existing = await promises.readFile(path$1, "utf-8").catch(() => void 0);
|
|
520
599
|
if (existing === void 0) return;
|
|
521
600
|
if (existing.includes("saltygen")) return;
|
|
522
|
-
|
|
601
|
+
logger.logger.info("Edit file: " + path$1);
|
|
523
602
|
await promises.writeFile(path$1, existing + "\n\n# Salty-CSS\nsaltygen\n");
|
|
524
603
|
};
|
|
525
604
|
const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
526
605
|
const target = explicitCssFile ?? await findGlobalCssFile(projectDir);
|
|
527
606
|
if (!target) {
|
|
528
|
-
|
|
607
|
+
logger.logger.warn("Could not find a CSS file to import the generated CSS. Please add it manually.");
|
|
529
608
|
return;
|
|
530
609
|
}
|
|
531
610
|
const cssFilePath = path.join(projectDir, target);
|
|
@@ -534,14 +613,14 @@ const importSaltygenIntoCss = async (projectDir, explicitCssFile) => {
|
|
|
534
613
|
if (cssFileContent.includes("saltygen")) return;
|
|
535
614
|
const cssFileFolder = path.join(cssFilePath, "..");
|
|
536
615
|
const relPath = path.relative(cssFileFolder, path.join(projectDir, "saltygen/index.css"));
|
|
537
|
-
|
|
616
|
+
logger.logger.info("Adding global import statement to CSS file: " + cssFilePath);
|
|
538
617
|
await promises.writeFile(cssFilePath, `@import '${relPath}';
|
|
539
618
|
` + cssFileContent);
|
|
540
619
|
await formatWithPrettier(cssFilePath);
|
|
541
620
|
};
|
|
542
621
|
const wirePrepareScript = async () => {
|
|
543
622
|
const pkg = await readPackageJson().catch(() => {
|
|
544
|
-
|
|
623
|
+
logger.logError("Could not read package.json file.");
|
|
545
624
|
return void 0;
|
|
546
625
|
});
|
|
547
626
|
if (!pkg) return;
|
|
@@ -549,17 +628,24 @@ const wirePrepareScript = async () => {
|
|
|
549
628
|
await updatePackageJson(next);
|
|
550
629
|
};
|
|
551
630
|
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 = ".") {
|
|
631
|
+
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.").option("-y, --yes", "Skip the install confirmation prompt.").action(async function(_dir = ".") {
|
|
553
632
|
try {
|
|
554
633
|
const opts = this.opts();
|
|
555
634
|
const dir = opts.dir ?? _dir;
|
|
556
|
-
if (!dir) return
|
|
557
|
-
const ctx = await buildContext({ dir, skipInstall: opts.skipInstall });
|
|
558
|
-
|
|
635
|
+
if (!dir) return logger.logError("Project directory must be provided. Add it as the first argument after init command or use the --dir option.");
|
|
636
|
+
const ctx = await buildContext({ dir, skipInstall: opts.skipInstall, yes: opts.yes });
|
|
637
|
+
logger.logger.info("Initializing a new Salty-CSS project!");
|
|
559
638
|
const framework = await detectFramework(ctx);
|
|
560
|
-
|
|
639
|
+
logger.logger.info(`Detected framework: ${framework.name}`);
|
|
640
|
+
const plannedIntegrations = await planIntegrations(ctx);
|
|
561
641
|
if (!ctx.skipInstall) {
|
|
562
|
-
|
|
642
|
+
const packages = [
|
|
643
|
+
corePackages.core(ctx.cliVersion),
|
|
644
|
+
framework.runtimePackage(ctx.cliVersion),
|
|
645
|
+
...plannedIntegrations.flatMap((p) => p.plan.packages)
|
|
646
|
+
];
|
|
647
|
+
await confirmInstall(packages, ctx.yes);
|
|
648
|
+
await npmInstall(...packages);
|
|
563
649
|
}
|
|
564
650
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
565
651
|
await promises.mkdir(ctx.projectDir, { recursive: true });
|
|
@@ -567,54 +653,59 @@ const registerInitCommand = (program) => {
|
|
|
567
653
|
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
|
|
568
654
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
569
655
|
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
570
|
-
await
|
|
656
|
+
await applyIntegrationPlans(plannedIntegrations);
|
|
571
657
|
await wirePrepareScript();
|
|
572
|
-
|
|
658
|
+
logger.logger.info("Running the build to generate initial CSS...");
|
|
573
659
|
const compiler = new compiler_saltyCompiler.SaltyCompiler(ctx.projectDir);
|
|
574
660
|
await compiler.generateCss();
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
661
|
+
logger.logger.info("🎉 Salty CSS project initialized successfully!");
|
|
662
|
+
logger.logger.info("Next steps:");
|
|
663
|
+
logger.logger.info("1. Configure variables and templates in `salty.config.ts`");
|
|
664
|
+
logger.logger.info("2. Create a new component with `npx salty-css generate [component-name]`");
|
|
665
|
+
logger.logger.info("3. Run `npx salty-css build` to generate the CSS");
|
|
666
|
+
logger.logger.info("4. Read about the features in the documentation: https://salty-css.dev");
|
|
667
|
+
logger.logger.info("5. Star the project on GitHub: https://github.com/margarita-form/salty-css ⭐");
|
|
582
668
|
} catch (err) {
|
|
583
|
-
return
|
|
669
|
+
return logger.logError(err instanceof Error ? err.message : String(err));
|
|
584
670
|
}
|
|
585
671
|
});
|
|
586
672
|
};
|
|
587
673
|
const getSaltyCssPackages = async () => {
|
|
588
674
|
const packageJSONPath = path.join(process.cwd(), "package.json");
|
|
589
|
-
const packageJson = await readPackageJson(packageJSONPath).catch((err) =>
|
|
590
|
-
if (!packageJson) return
|
|
675
|
+
const packageJson = await readPackageJson(packageJSONPath).catch((err) => logger.logError(err));
|
|
676
|
+
if (!packageJson) return logger.logError("Could not read package.json file.");
|
|
591
677
|
const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
592
678
|
const saltyCssPackages = Object.entries(allDependencies).filter(([name]) => name === "salty-css" || name.startsWith("@salty-css/"));
|
|
593
679
|
if (!saltyCssPackages.length) {
|
|
594
|
-
return
|
|
680
|
+
return logger.logError(
|
|
595
681
|
"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
682
|
);
|
|
597
683
|
}
|
|
598
684
|
return saltyCssPackages;
|
|
599
685
|
};
|
|
600
686
|
const registerUpdateCommand = (program) => {
|
|
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") {
|
|
602
|
-
const { legacyPeerDeps, version = _version } = this.opts();
|
|
687
|
+
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).option("-y, --yes", "Skip confirmation prompts (install and rebuild).").option("-d, --dir <dir>", "Project directory to rebuild after updating.").action(async function(_version = "latest") {
|
|
688
|
+
const { legacyPeerDeps, version = _version, yes = false, dir } = this.opts();
|
|
603
689
|
const saltyCssPackages = await getSaltyCssPackages();
|
|
604
|
-
if (!saltyCssPackages) return
|
|
690
|
+
if (!saltyCssPackages) return logger.logError("Could not update Salty-CSS packages as any were found in package.json.");
|
|
605
691
|
const cli = await readThisPackageJson();
|
|
606
692
|
const packagesToUpdate = saltyCssPackages.map(([name]) => {
|
|
607
693
|
if (version === "@") return `${name}@${cli.version}`;
|
|
608
694
|
return `${name}@${version.replace(/^@/, "")}`;
|
|
609
695
|
});
|
|
696
|
+
try {
|
|
697
|
+
await confirmInstall(packagesToUpdate, yes);
|
|
698
|
+
} catch (err) {
|
|
699
|
+
return logger.logError(err instanceof Error ? err.message : String(err));
|
|
700
|
+
}
|
|
610
701
|
if (legacyPeerDeps) {
|
|
611
|
-
|
|
702
|
+
logger.logger.warn("Using legacy peer dependencies to update packages.");
|
|
612
703
|
await npmInstall(...packagesToUpdate, "--legacy-peer-deps");
|
|
613
704
|
} else {
|
|
614
705
|
await npmInstall(...packagesToUpdate);
|
|
615
706
|
}
|
|
616
707
|
const updatedPackages = await getSaltyCssPackages();
|
|
617
|
-
if (!updatedPackages) return
|
|
708
|
+
if (!updatedPackages) return logger.logError("Something went wrong while reading the updated packages.");
|
|
618
709
|
const mappedByVersions = updatedPackages.reduce((acc, [name, version2]) => {
|
|
619
710
|
if (!acc[version2]) acc[version2] = [];
|
|
620
711
|
acc[version2].push(name);
|
|
@@ -623,30 +714,41 @@ const registerUpdateCommand = (program) => {
|
|
|
623
714
|
const versionsCount = Object.keys(mappedByVersions).length;
|
|
624
715
|
if (versionsCount === 1) {
|
|
625
716
|
const v = Object.keys(mappedByVersions)[0];
|
|
626
|
-
|
|
717
|
+
logger.logger.info(`Updated to all Salty CSS packages successfully to ${v.replace(/^\^/, "")}`);
|
|
627
718
|
} else {
|
|
628
719
|
for (const [v, names] of Object.entries(mappedByVersions)) {
|
|
629
|
-
|
|
720
|
+
logger.logger.info(`Updated to ${v.replace(/^\^/, "")}: ${names.join(", ")}`);
|
|
630
721
|
}
|
|
631
722
|
}
|
|
723
|
+
const project = dir ?? await getDefaultProject();
|
|
724
|
+
if (!project) {
|
|
725
|
+
logger.logger.warn("Skipping rebuild: no project directory configured. Run `salty-css build [dir]` manually.");
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const shouldRebuild = await confirmYesNo("Rebuild Salty CSS now?", { yes });
|
|
729
|
+
if (!shouldRebuild) return;
|
|
730
|
+
const projectDir = resolveProjectDir(project);
|
|
731
|
+
logger.logger.info("Rebuilding Salty-CSS project...");
|
|
732
|
+
await new compiler_saltyCompiler.SaltyCompiler(projectDir).generateCss();
|
|
733
|
+
logger.logger.info("Rebuild complete.");
|
|
632
734
|
});
|
|
633
735
|
};
|
|
634
736
|
const registerVersionOption = (program) => {
|
|
635
737
|
program.option("-v, --version", "Show the current version of Salty-CSS.").action(async function() {
|
|
636
738
|
const cli = await readThisPackageJson();
|
|
637
|
-
|
|
739
|
+
logger.logger.info("CLI is running: " + cli.version);
|
|
638
740
|
const packageJSONPath = path.join(process.cwd(), "package.json");
|
|
639
|
-
const packageJson = await readPackageJson(packageJSONPath).catch((err) =>
|
|
741
|
+
const packageJson = await readPackageJson(packageJSONPath).catch((err) => logger.logError(err));
|
|
640
742
|
if (!packageJson) return;
|
|
641
743
|
const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
642
744
|
const saltyCssPackages = Object.keys(allDependencies).filter((dep) => dep === "salty-css" || dep.startsWith("@salty-css/"));
|
|
643
745
|
if (!saltyCssPackages.length) {
|
|
644
|
-
return
|
|
746
|
+
return logger.logError(
|
|
645
747
|
"No Salty-CSS packages found in package.json. Make sure you are running update command in the same directory! Used package.json path: " + packageJSONPath
|
|
646
748
|
);
|
|
647
749
|
}
|
|
648
750
|
for (const dep of saltyCssPackages) {
|
|
649
|
-
|
|
751
|
+
logger.logger.info(`${dep}: ${allDependencies[dep]}`);
|
|
650
752
|
}
|
|
651
753
|
});
|
|
652
754
|
};
|