@salty-css/core 0.1.0-alpha.9 → 0.1.0-feat-define-import.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/README.md +44 -0
- 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 +149 -48
- package/bin/main.js +149 -48
- 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/resolve-import.d.ts +17 -0
- package/compiler/salty-compiler.cjs +85 -26
- package/compiler/salty-compiler.d.ts +7 -1
- package/compiler/salty-compiler.js +86 -26
- package/config/index.cjs +2 -0
- package/config/index.js +3 -1
- package/css/dynamic-styles.cjs +1 -1
- package/css/dynamic-styles.js +1 -1
- package/css/keyframes.cjs +1 -1
- package/css/keyframes.js +1 -1
- package/factories/define-import.d.ts +14 -0
- package/factories/index.cjs +19 -0
- package/factories/index.d.ts +1 -0
- package/factories/index.js +19 -0
- 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/package.json +1 -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
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ To get help with problems, [Join Salty CSS Discord server](https://discord.gg/R6
|
|
|
57
57
|
- [defineVariables](#variables) - create CSS variables (tokens) that can be used in any styling function
|
|
58
58
|
- [defineMediaQuery](#media-queries) - create CSS media queries and use them in any styling function
|
|
59
59
|
- [defineTemplates](#templates) - create reusable templates that can be applied when same styles are used over and over again
|
|
60
|
+
- [defineImport](#importing-additional-css) - pull in external CSS files (relative, public, node_modules, or URL)
|
|
60
61
|
- [keyframes](#keyframes-animations) - create CSS keyframes animation that can be used and imported in any styling function
|
|
61
62
|
|
|
62
63
|
### Styling helpers & utility
|
|
@@ -319,6 +320,49 @@ Example usage:
|
|
|
319
320
|
styled('div', { base: { textStyle: 'headline.large', card: '20px' } });
|
|
320
321
|
```
|
|
321
322
|
|
|
323
|
+
## Importing additional CSS
|
|
324
|
+
|
|
325
|
+
Use `defineImport` to pull in CSS that lives outside of Salty's authoring API — a reset stylesheet from npm, a Google Fonts URL, an asset in your app's `public/` folder, or a sibling `.css` file. The compiler turns each spec into an `@import` rule in the generated `saltygen/index.css`, so the imported stylesheets travel with the rest of your build.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
// /styles/imports.css.ts
|
|
329
|
+
import { defineImport } from '@salty-css/core/factories';
|
|
330
|
+
|
|
331
|
+
export default defineImport(
|
|
332
|
+
// Relative to this file
|
|
333
|
+
'./reset.css',
|
|
334
|
+
// From node_modules (bare specifier — same as Vite / native CSS @import)
|
|
335
|
+
'modern-normalize/modern-normalize.css',
|
|
336
|
+
// From node_modules (~ prefix — same resolver, webpack-style)
|
|
337
|
+
'~normalize.css/normalize.css',
|
|
338
|
+
// From your app's public/ folder (served at the host root)
|
|
339
|
+
'/fonts/inter.css',
|
|
340
|
+
// External URL
|
|
341
|
+
'https://fonts.googleapis.com/css2?family=Inter&display=swap',
|
|
342
|
+
// Object form — attach media or supports() conditions
|
|
343
|
+
{ url: './print.css', media: 'print' },
|
|
344
|
+
{ url: './p3.css', supports: 'color(display-p3 1 1 1)' }
|
|
345
|
+
);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Path resolution:
|
|
349
|
+
|
|
350
|
+
| Pattern | Behaviour |
|
|
351
|
+
| --------------------------- | ---------------------------------------------------------------------------------------- |
|
|
352
|
+
| `http://`, `https://`, `//` | Emitted verbatim |
|
|
353
|
+
| Starts with `/` | Public-folder URL — emitted verbatim, the browser resolves it against your host |
|
|
354
|
+
| Starts with `./` or `../` | Resolved at build time relative to the file that called `defineImport` |
|
|
355
|
+
| `~package/file.css` | Stripped of the leading `~`, then resolved from `node_modules` and copied into the build |
|
|
356
|
+
| `package/file.css` (bare) | Same `node_modules` resolution as the `~` form |
|
|
357
|
+
|
|
358
|
+
All imports are placed inside a new `imports` cascade layer that sits **before** `reset`, `global`, `templates`, and your component styles. This means your own styles always win over third-party CSS you pull in — which is what most teams expect when they drop in something like `modern-normalize`.
|
|
359
|
+
|
|
360
|
+
The full layer order in the generated `index.css` is:
|
|
361
|
+
|
|
362
|
+
```css
|
|
363
|
+
@layer imports, reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
|
|
364
|
+
```
|
|
365
|
+
|
|
322
366
|
## Keyframes animations
|
|
323
367
|
|
|
324
368
|
```ts
|
|
@@ -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
|
@@ -11,6 +11,7 @@ const child_process = require("child_process");
|
|
|
11
11
|
const ora = require("ora");
|
|
12
12
|
const pascalCase = require("../pascal-case-By_l58S-.cjs");
|
|
13
13
|
const ejs = require("ejs");
|
|
14
|
+
const promises$1 = require("readline/promises");
|
|
14
15
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
15
16
|
const defaultPackageJsonPath = path.join(process.cwd(), "package.json");
|
|
16
17
|
const readPackageJson = async (filePath = defaultPackageJsonPath) => {
|
|
@@ -141,17 +142,21 @@ const buildContext = async (opts) => {
|
|
|
141
142
|
packageJson,
|
|
142
143
|
rcFile,
|
|
143
144
|
cliVersion: cliPackageJson.version || "0.0.0",
|
|
144
|
-
skipInstall: !!opts.skipInstall
|
|
145
|
+
skipInstall: !!opts.skipInstall,
|
|
146
|
+
yes: !!opts.yes
|
|
145
147
|
};
|
|
146
148
|
};
|
|
147
149
|
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) {
|
|
150
|
+
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) {
|
|
149
151
|
compiler_saltyCompiler.logger.info("Building the Salty-CSS project...");
|
|
150
|
-
const { dir = _dir, watch } = this.opts();
|
|
152
|
+
const { dir = _dir, watch, mode } = this.opts();
|
|
153
|
+
if (mode !== void 0 && mode !== "production" && mode !== "development") {
|
|
154
|
+
return compiler_saltyCompiler.logError(`Invalid --mode "${mode}". Expected "production" or "development".`);
|
|
155
|
+
}
|
|
151
156
|
const resolved = dir ?? await getDefaultProject();
|
|
152
157
|
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
158
|
const projectDir = resolveProjectDir(resolved);
|
|
154
|
-
const compiler = new compiler_saltyCompiler.SaltyCompiler(projectDir);
|
|
159
|
+
const compiler = new compiler_saltyCompiler.SaltyCompiler(projectDir, { mode });
|
|
155
160
|
await compiler.generateCss();
|
|
156
161
|
if (watch) {
|
|
157
162
|
compiler_saltyCompiler.logger.info("Watching for changes in the project directory...");
|
|
@@ -225,7 +230,7 @@ const getFramework = (name) => {
|
|
|
225
230
|
return frameworksByName[name];
|
|
226
231
|
};
|
|
227
232
|
const templateLoaders = {
|
|
228
|
-
"salty.config.ts": () => Promise.resolve().then(() => require("../salty.config-
|
|
233
|
+
"salty.config.ts": () => Promise.resolve().then(() => require("../salty.config-DogY_sSQ.cjs")),
|
|
229
234
|
"saltygen/index.css": () => Promise.resolve().then(() => require("../index-ByR0nfaf.cjs")),
|
|
230
235
|
"react/styled-file.ts": () => Promise.resolve().then(() => require("../styled-file-CPd_rTW2.cjs")),
|
|
231
236
|
"react/vanilla-file.ts": () => Promise.resolve().then(() => require("../vanilla-file-r0fp2q_m.cjs")),
|
|
@@ -293,6 +298,52 @@ const registerGenerateCommand = (program, defaultProject) => {
|
|
|
293
298
|
await formatWithPrettier(formattedStyledFilePath);
|
|
294
299
|
});
|
|
295
300
|
};
|
|
301
|
+
const formatPackageForDisplay = (spec) => {
|
|
302
|
+
const trimmed = spec.trim();
|
|
303
|
+
if (trimmed.startsWith("-D ")) return `${trimmed.slice(3).trim()} (dev)`;
|
|
304
|
+
return trimmed;
|
|
305
|
+
};
|
|
306
|
+
const renderPackageList = (packages) => {
|
|
307
|
+
return packages.map((p) => ` + ${formatPackageForDisplay(p)}`).join("\n");
|
|
308
|
+
};
|
|
309
|
+
const confirmInstall = async (packages, yes, options = {}) => {
|
|
310
|
+
if (yes) return;
|
|
311
|
+
if (packages.length === 0) return;
|
|
312
|
+
const input = options.input ?? process.stdin;
|
|
313
|
+
const output = options.output ?? process.stdout;
|
|
314
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
|
|
315
|
+
if (!isTTY) {
|
|
316
|
+
throw new Error("Cannot prompt for install confirmation: stdin is not a TTY. Re-run with --yes to install the listed packages without prompting.");
|
|
317
|
+
}
|
|
318
|
+
output.write(`The following packages will be installed:
|
|
319
|
+
${renderPackageList(packages)}
|
|
320
|
+
`);
|
|
321
|
+
const rl = promises$1.createInterface({ input, output, terminal: false });
|
|
322
|
+
try {
|
|
323
|
+
const answer = (await rl.question("Proceed? (y/N) ")).trim().toLowerCase();
|
|
324
|
+
if (answer !== "y" && answer !== "yes") {
|
|
325
|
+
throw new Error("Install cancelled by user.");
|
|
326
|
+
}
|
|
327
|
+
} finally {
|
|
328
|
+
rl.close();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const confirmYesNo = async (question, options = {}) => {
|
|
332
|
+
if (options.yes) return true;
|
|
333
|
+
const input = options.input ?? process.stdin;
|
|
334
|
+
const output = options.output ?? process.stdout;
|
|
335
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
|
|
336
|
+
if (!isTTY) return false;
|
|
337
|
+
const suffix = options.defaultYes ? "(Y/n)" : "(y/N)";
|
|
338
|
+
const rl = promises$1.createInterface({ input, output, terminal: false });
|
|
339
|
+
try {
|
|
340
|
+
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
341
|
+
if (answer === "") return !!options.defaultYes;
|
|
342
|
+
return answer === "y" || answer === "yes";
|
|
343
|
+
} finally {
|
|
344
|
+
rl.close();
|
|
345
|
+
}
|
|
346
|
+
};
|
|
296
347
|
const CSS_FILE_FOLDERS = ["src", "public", "assets", "styles", "css", "app"];
|
|
297
348
|
const CSS_SECOND_LEVEL_FOLDERS = ["styles", "css", "app", "pages"];
|
|
298
349
|
const CSS_FILE_NAMES = ["index", "styles", "main", "app", "global", "globals"];
|
|
@@ -339,17 +390,22 @@ const editAstroConfig = (existing) => {
|
|
|
339
390
|
const astroIntegration = {
|
|
340
391
|
name: "astro",
|
|
341
392
|
detect: (ctx) => findAstroConfig(ctx.projectDir),
|
|
342
|
-
|
|
393
|
+
plan: async (ctx, configPath) => {
|
|
343
394
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
344
|
-
if (existing === void 0) return
|
|
395
|
+
if (existing === void 0) return null;
|
|
345
396
|
const result = editAstroConfig(existing);
|
|
346
397
|
if (result.warning) compiler_saltyCompiler.logger.warn(result.warning);
|
|
347
|
-
if (result.content === null) return
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
398
|
+
if (result.content === null) return null;
|
|
399
|
+
const newContent = result.content;
|
|
400
|
+
return {
|
|
401
|
+
packages: [`-D ${astroPackage(ctx.cliVersion)}`],
|
|
402
|
+
execute: async () => {
|
|
403
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS integration to Astro config: " + configPath);
|
|
404
|
+
await promises.writeFile(configPath, newContent);
|
|
405
|
+
await formatWithPrettier(configPath);
|
|
406
|
+
return { changed: true };
|
|
407
|
+
}
|
|
408
|
+
};
|
|
353
409
|
}
|
|
354
410
|
};
|
|
355
411
|
const ESLINT_CONFIG_CANDIDATES = [
|
|
@@ -408,20 +464,25 @@ const eslintIntegration = {
|
|
|
408
464
|
const candidates = eslintConfigCandidates(ctx.projectDir, ctx.cwd);
|
|
409
465
|
return candidates.find((p) => fs.existsSync(p)) ?? null;
|
|
410
466
|
},
|
|
411
|
-
|
|
467
|
+
plan: async (ctx, configPath) => {
|
|
412
468
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
413
469
|
if (existing === void 0) {
|
|
414
470
|
compiler_saltyCompiler.logger.error("Could not read ESLint config file.");
|
|
415
|
-
return
|
|
471
|
+
return null;
|
|
416
472
|
}
|
|
417
|
-
if (!ctx.skipInstall) await npmInstall(corePackages.eslintConfigCore(ctx.cliVersion));
|
|
418
473
|
const result = editEslintConfig(existing, configPath.endsWith("js"));
|
|
419
474
|
if (result.warning) compiler_saltyCompiler.logger.warn(result.warning);
|
|
420
|
-
if (result.content === null) return
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
475
|
+
if (result.content === null) return null;
|
|
476
|
+
const newContent = result.content;
|
|
477
|
+
return {
|
|
478
|
+
packages: [corePackages.eslintConfigCore(ctx.cliVersion)],
|
|
479
|
+
execute: async () => {
|
|
480
|
+
compiler_saltyCompiler.logger.info("Edit file: " + configPath);
|
|
481
|
+
await promises.writeFile(configPath, newContent);
|
|
482
|
+
await formatWithPrettier(configPath);
|
|
483
|
+
return { changed: true };
|
|
484
|
+
}
|
|
485
|
+
};
|
|
425
486
|
}
|
|
426
487
|
};
|
|
427
488
|
const nextConfigFiles = ["next.config.js", "next.config.cjs", "next.config.ts", "next.config.mjs"];
|
|
@@ -451,16 +512,20 @@ const nextIntegration = {
|
|
|
451
512
|
const found = nextConfigFiles.map((file) => path.join(ctx.projectDir, file)).find((p) => fs.existsSync(p));
|
|
452
513
|
return found ?? null;
|
|
453
514
|
},
|
|
454
|
-
|
|
515
|
+
plan: async (ctx, configPath) => {
|
|
455
516
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
456
|
-
if (existing === void 0) return
|
|
517
|
+
if (existing === void 0) return null;
|
|
457
518
|
const { content } = editNextConfig(existing);
|
|
458
|
-
if (content === null) return
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
519
|
+
if (content === null) return null;
|
|
520
|
+
return {
|
|
521
|
+
packages: [`-D ${nextPackage(ctx.cliVersion)}`],
|
|
522
|
+
execute: async () => {
|
|
523
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS plugin to Next.js config...");
|
|
524
|
+
await promises.writeFile(configPath, content);
|
|
525
|
+
await formatWithPrettier(configPath);
|
|
526
|
+
return { changed: true };
|
|
527
|
+
}
|
|
528
|
+
};
|
|
464
529
|
}
|
|
465
530
|
};
|
|
466
531
|
const vitePackage = (version) => `@salty-css/vite@${version}`;
|
|
@@ -478,27 +543,40 @@ const viteIntegration = {
|
|
|
478
543
|
const path$1 = path.join(ctx.projectDir, "vite.config.ts");
|
|
479
544
|
return fs.existsSync(path$1) ? path$1 : null;
|
|
480
545
|
},
|
|
481
|
-
|
|
546
|
+
plan: async (ctx, configPath) => {
|
|
482
547
|
const existing = await promises.readFile(configPath, "utf-8").catch(() => void 0);
|
|
483
|
-
if (existing === void 0) return
|
|
548
|
+
if (existing === void 0) return null;
|
|
484
549
|
const { content } = editViteConfig(existing);
|
|
485
|
-
if (content === null) return
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
550
|
+
if (content === null) return null;
|
|
551
|
+
return {
|
|
552
|
+
packages: [`-D ${vitePackage(ctx.cliVersion)}`],
|
|
553
|
+
execute: async () => {
|
|
554
|
+
compiler_saltyCompiler.logger.info("Edit file: " + configPath);
|
|
555
|
+
compiler_saltyCompiler.logger.info("Adding Salty-CSS plugin to Vite config...");
|
|
556
|
+
await promises.writeFile(configPath, content);
|
|
557
|
+
await formatWithPrettier(configPath);
|
|
558
|
+
return { changed: true };
|
|
559
|
+
}
|
|
560
|
+
};
|
|
492
561
|
}
|
|
493
562
|
};
|
|
494
563
|
const buildIntegrationRegistry = [eslintIntegration, viteIntegration, nextIntegration, astroIntegration];
|
|
495
|
-
const
|
|
496
|
-
const
|
|
564
|
+
const planIntegrations = async (ctx) => {
|
|
565
|
+
const planned = [];
|
|
497
566
|
for (const integration of buildIntegrationRegistry) {
|
|
498
567
|
const configPath = await integration.detect(ctx);
|
|
499
568
|
if (!configPath) continue;
|
|
500
|
-
const
|
|
501
|
-
|
|
569
|
+
const plan = await integration.plan(ctx, configPath);
|
|
570
|
+
if (!plan) continue;
|
|
571
|
+
planned.push({ name: integration.name, configPath, plan });
|
|
572
|
+
}
|
|
573
|
+
return planned;
|
|
574
|
+
};
|
|
575
|
+
const applyIntegrationPlans = async (planned) => {
|
|
576
|
+
const results = [];
|
|
577
|
+
for (const { name, configPath, plan } of planned) {
|
|
578
|
+
const result = await plan.execute();
|
|
579
|
+
results.push({ name, configPath, changed: result.changed });
|
|
502
580
|
}
|
|
503
581
|
return results;
|
|
504
582
|
};
|
|
@@ -549,17 +627,24 @@ const wirePrepareScript = async () => {
|
|
|
549
627
|
await updatePackageJson(next);
|
|
550
628
|
};
|
|
551
629
|
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 = ".") {
|
|
630
|
+
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
631
|
try {
|
|
554
632
|
const opts = this.opts();
|
|
555
633
|
const dir = opts.dir ?? _dir;
|
|
556
634
|
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 });
|
|
635
|
+
const ctx = await buildContext({ dir, skipInstall: opts.skipInstall, yes: opts.yes });
|
|
558
636
|
compiler_saltyCompiler.logger.info("Initializing a new Salty-CSS project!");
|
|
559
637
|
const framework = await detectFramework(ctx);
|
|
560
638
|
compiler_saltyCompiler.logger.info(`Detected framework: ${framework.name}`);
|
|
639
|
+
const plannedIntegrations = await planIntegrations(ctx);
|
|
561
640
|
if (!ctx.skipInstall) {
|
|
562
|
-
|
|
641
|
+
const packages = [
|
|
642
|
+
corePackages.core(ctx.cliVersion),
|
|
643
|
+
framework.runtimePackage(ctx.cliVersion),
|
|
644
|
+
...plannedIntegrations.flatMap((p) => p.plan.packages)
|
|
645
|
+
];
|
|
646
|
+
await confirmInstall(packages, ctx.yes);
|
|
647
|
+
await npmInstall(...packages);
|
|
563
648
|
}
|
|
564
649
|
const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
|
|
565
650
|
await promises.mkdir(ctx.projectDir, { recursive: true });
|
|
@@ -567,7 +652,7 @@ const registerInitCommand = (program) => {
|
|
|
567
652
|
await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
|
|
568
653
|
await ensureGitignoreSaltygen(ctx.cwd);
|
|
569
654
|
await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
|
|
570
|
-
await
|
|
655
|
+
await applyIntegrationPlans(plannedIntegrations);
|
|
571
656
|
await wirePrepareScript();
|
|
572
657
|
compiler_saltyCompiler.logger.info("Running the build to generate initial CSS...");
|
|
573
658
|
const compiler = new compiler_saltyCompiler.SaltyCompiler(ctx.projectDir);
|
|
@@ -598,8 +683,8 @@ const getSaltyCssPackages = async () => {
|
|
|
598
683
|
return saltyCssPackages;
|
|
599
684
|
};
|
|
600
685
|
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();
|
|
686
|
+
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") {
|
|
687
|
+
const { legacyPeerDeps, version = _version, yes = false, dir } = this.opts();
|
|
603
688
|
const saltyCssPackages = await getSaltyCssPackages();
|
|
604
689
|
if (!saltyCssPackages) return compiler_saltyCompiler.logError("Could not update Salty-CSS packages as any were found in package.json.");
|
|
605
690
|
const cli = await readThisPackageJson();
|
|
@@ -607,6 +692,11 @@ const registerUpdateCommand = (program) => {
|
|
|
607
692
|
if (version === "@") return `${name}@${cli.version}`;
|
|
608
693
|
return `${name}@${version.replace(/^@/, "")}`;
|
|
609
694
|
});
|
|
695
|
+
try {
|
|
696
|
+
await confirmInstall(packagesToUpdate, yes);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
return compiler_saltyCompiler.logError(err instanceof Error ? err.message : String(err));
|
|
699
|
+
}
|
|
610
700
|
if (legacyPeerDeps) {
|
|
611
701
|
compiler_saltyCompiler.logger.warn("Using legacy peer dependencies to update packages.");
|
|
612
702
|
await npmInstall(...packagesToUpdate, "--legacy-peer-deps");
|
|
@@ -629,6 +719,17 @@ const registerUpdateCommand = (program) => {
|
|
|
629
719
|
compiler_saltyCompiler.logger.info(`Updated to ${v.replace(/^\^/, "")}: ${names.join(", ")}`);
|
|
630
720
|
}
|
|
631
721
|
}
|
|
722
|
+
const project = dir ?? await getDefaultProject();
|
|
723
|
+
if (!project) {
|
|
724
|
+
compiler_saltyCompiler.logger.warn("Skipping rebuild: no project directory configured. Run `salty-css build [dir]` manually.");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const shouldRebuild = await confirmYesNo("Rebuild Salty CSS now?", { yes });
|
|
728
|
+
if (!shouldRebuild) return;
|
|
729
|
+
const projectDir = resolveProjectDir(project);
|
|
730
|
+
compiler_saltyCompiler.logger.info("Rebuilding Salty-CSS project...");
|
|
731
|
+
await new compiler_saltyCompiler.SaltyCompiler(projectDir).generateCss();
|
|
732
|
+
compiler_saltyCompiler.logger.info("Rebuild complete.");
|
|
632
733
|
});
|
|
633
734
|
};
|
|
634
735
|
const registerVersionOption = (program) => {
|