@mochi-css/tsuki 2.0.1 → 3.0.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 +19 -0
- package/dist/index.js +317 -161
- package/package.json +5 -11
package/README.md
CHANGED
|
@@ -8,3 +8,22 @@ You can run it with:
|
|
|
8
8
|
```bash
|
|
9
9
|
npx @mochi-css/tsuki
|
|
10
10
|
```
|
|
11
|
+
|
|
12
|
+
`tsuki` handles installing integrations for next.js and vite frameworks.
|
|
13
|
+
To get more info, run it with `-h` or `--help` option.
|
|
14
|
+
|
|
15
|
+
## What `tsuki` sets up
|
|
16
|
+
|
|
17
|
+
During initialization, `tsuki` installs the required packages and creates a `mochi.config.ts` file in your project root.
|
|
18
|
+
This file is the single place to configure all Mochi-CSS options (`roots`, `extractors`, `plugins`, etc.) - all integrations load it automatically.
|
|
19
|
+
|
|
20
|
+
## Presets
|
|
21
|
+
|
|
22
|
+
Use the `--preset` / `-p` flag to choose a framework preset non-interactively:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @mochi-css/tsuki --preset vite
|
|
26
|
+
npx @mochi-css/tsuki --preset nextjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If `-p` is omitted, `tsuki` will prompt you to select a preset interactively.
|
package/dist/index.js
CHANGED
|
@@ -1,49 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
|
|
24
|
-
//#endregion
|
|
25
|
-
let commander = require("commander");
|
|
26
|
-
commander = __toESM(commander);
|
|
27
|
-
let __clack_prompts = require("@clack/prompts");
|
|
28
|
-
__clack_prompts = __toESM(__clack_prompts);
|
|
29
|
-
let picocolors = require("picocolors");
|
|
30
|
-
picocolors = __toESM(picocolors);
|
|
31
|
-
let node_readline = require("node:readline");
|
|
32
|
-
node_readline = __toESM(node_readline);
|
|
33
|
-
let node_stream = require("node:stream");
|
|
34
|
-
node_stream = __toESM(node_stream);
|
|
35
|
-
let package_manager_detector = require("package-manager-detector");
|
|
36
|
-
package_manager_detector = __toESM(package_manager_detector);
|
|
37
|
-
let cross_spawn = require("cross-spawn");
|
|
38
|
-
cross_spawn = __toESM(cross_spawn);
|
|
39
|
-
let fs_extra = require("fs-extra");
|
|
40
|
-
fs_extra = __toESM(fs_extra);
|
|
41
|
-
let fs_promises = require("fs/promises");
|
|
42
|
-
fs_promises = __toESM(fs_promises);
|
|
43
|
-
let path = require("path");
|
|
44
|
-
path = __toESM(path);
|
|
45
|
-
let magicast = require("magicast");
|
|
46
|
-
magicast = __toESM(magicast);
|
|
2
|
+
import { Option, program } from "commander";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { PassThrough } from "node:stream";
|
|
7
|
+
import { detect, resolveCommand } from "package-manager-detector";
|
|
8
|
+
import spawn from "cross-spawn";
|
|
9
|
+
import fsExtra from "fs-extra";
|
|
10
|
+
import fs from "fs/promises";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { generateCode, parseModule } from "magicast";
|
|
13
|
+
import dedent from "dedent";
|
|
47
14
|
|
|
48
15
|
//#region src/install.ts
|
|
49
16
|
function onceEvent(emitter, event) {
|
|
@@ -55,7 +22,7 @@ function onceEvent(emitter, event) {
|
|
|
55
22
|
}
|
|
56
23
|
function spawnProcess(command, args, options) {
|
|
57
24
|
return new Promise((resolve, reject) => {
|
|
58
|
-
const child = (
|
|
25
|
+
const child = spawn(command, args, options);
|
|
59
26
|
const onSpawn = () => {
|
|
60
27
|
child.off("error", onError);
|
|
61
28
|
resolve(child);
|
|
@@ -69,7 +36,7 @@ function spawnProcess(command, args, options) {
|
|
|
69
36
|
});
|
|
70
37
|
}
|
|
71
38
|
async function runInstall(title, command, args, packages) {
|
|
72
|
-
const log =
|
|
39
|
+
const log = p.taskLog({ title });
|
|
73
40
|
log.message(`${command} ${args.join(" ")}`);
|
|
74
41
|
try {
|
|
75
42
|
const child = await spawnProcess(command, args, { stdio: [
|
|
@@ -77,7 +44,7 @@ async function runInstall(title, command, args, packages) {
|
|
|
77
44
|
"pipe",
|
|
78
45
|
"pipe"
|
|
79
46
|
] });
|
|
80
|
-
const merged = new
|
|
47
|
+
const merged = new PassThrough();
|
|
81
48
|
let openStreams = 0;
|
|
82
49
|
for (const stream of [child.stdout, child.stderr]) {
|
|
83
50
|
if (!stream) continue;
|
|
@@ -87,7 +54,7 @@ async function runInstall(title, command, args, packages) {
|
|
|
87
54
|
if (--openStreams === 0) merged.end();
|
|
88
55
|
});
|
|
89
56
|
}
|
|
90
|
-
const rl =
|
|
57
|
+
const rl = createInterface({
|
|
91
58
|
input: merged,
|
|
92
59
|
crlfDelay: Infinity
|
|
93
60
|
});
|
|
@@ -103,9 +70,9 @@ async function runInstall(title, command, args, packages) {
|
|
|
103
70
|
throw err;
|
|
104
71
|
}
|
|
105
72
|
}
|
|
106
|
-
async function installPackages(packages) {
|
|
73
|
+
async function installPackages(packages, autoInstall = false) {
|
|
107
74
|
if (packages.length === 0) return;
|
|
108
|
-
const packageManager = await
|
|
75
|
+
const packageManager = await detect({ strategies: [
|
|
109
76
|
"packageManager-field",
|
|
110
77
|
"devEngines-field",
|
|
111
78
|
"lockfile",
|
|
@@ -117,18 +84,20 @@ async function installPackages(packages) {
|
|
|
117
84
|
const devPackages = packages.filter((pkg) => pkg.dev !== false).map((pkg) => pkg.name);
|
|
118
85
|
const prodPackages = packages.filter((pkg) => pkg.dev === false).map((pkg) => pkg.name);
|
|
119
86
|
const packageList = [...devPackages.map((name) => `${name} (dev)`), ...prodPackages.map((name) => name)].join(", ");
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
87
|
+
if (!autoInstall) {
|
|
88
|
+
const confirmed = await p.confirm({ message: `Install the following packages: ${packageList}?` });
|
|
89
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
90
|
+
p.log.info("Skipping package installation");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
124
93
|
}
|
|
125
94
|
if (devPackages.length > 0) {
|
|
126
|
-
const cmd =
|
|
95
|
+
const cmd = resolveCommand(agent, "add", [devFlag, ...devPackages]);
|
|
127
96
|
if (cmd === null) throw new Error("Could not prepare install command");
|
|
128
97
|
await runInstall(`Installing dev dependencies: ${devPackages.join(", ")}`, cmd.command, cmd.args, devPackages);
|
|
129
98
|
}
|
|
130
99
|
if (prodPackages.length > 0) {
|
|
131
|
-
const cmd =
|
|
100
|
+
const cmd = resolveCommand(agent, "add", prodPackages);
|
|
132
101
|
if (cmd === null) throw new Error("Could not prepare install command");
|
|
133
102
|
await runInstall(`Installing dependencies: ${prodPackages.join(", ")}`, cmd.command, cmd.args, prodPackages);
|
|
134
103
|
}
|
|
@@ -139,11 +108,12 @@ async function installPackages(packages) {
|
|
|
139
108
|
var ModuleRunner = class {
|
|
140
109
|
packages = [];
|
|
141
110
|
modules = [];
|
|
142
|
-
register(module
|
|
143
|
-
this.modules.push(module
|
|
111
|
+
register(module) {
|
|
112
|
+
this.modules.push(module);
|
|
144
113
|
return this;
|
|
145
114
|
}
|
|
146
|
-
async run() {
|
|
115
|
+
async run(options = {}) {
|
|
116
|
+
const { nonInteractive = false, autoInstall = false, moduleOptions = {} } = options;
|
|
147
117
|
const ctx = {
|
|
148
118
|
requirePackage: (name, dev = true) => {
|
|
149
119
|
this.packages.push({
|
|
@@ -156,13 +126,21 @@ var ModuleRunner = class {
|
|
|
156
126
|
name: pkg.name,
|
|
157
127
|
dev: pkg.dev ?? true
|
|
158
128
|
});
|
|
159
|
-
}
|
|
129
|
+
},
|
|
130
|
+
nonInteractive,
|
|
131
|
+
moduleOptions
|
|
160
132
|
};
|
|
161
|
-
for (const module
|
|
162
|
-
if (this.packages.length > 0) await installPackages(this.packages);
|
|
133
|
+
for (const module of this.modules) await module.run(ctx);
|
|
134
|
+
if (this.packages.length > 0) await installPackages(this.packages, autoInstall);
|
|
163
135
|
}
|
|
164
136
|
};
|
|
165
137
|
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/version.ts
|
|
140
|
+
function mochiPackage(name) {
|
|
141
|
+
return `${name}@^${parseInt("3.0.0".split(".")[0] ?? "0", 10)}.0.0`;
|
|
142
|
+
}
|
|
143
|
+
|
|
166
144
|
//#endregion
|
|
167
145
|
//#region src/modules/ast.ts
|
|
168
146
|
function getPropKeyName(prop) {
|
|
@@ -170,6 +148,29 @@ function getPropKeyName(prop) {
|
|
|
170
148
|
if (typeof key["name"] === "string") return key["name"];
|
|
171
149
|
if (typeof key["value"] === "string") return key["value"];
|
|
172
150
|
}
|
|
151
|
+
function getPluginsElements(obj, configPath) {
|
|
152
|
+
const existing = obj.properties.find((prop) => getPropKeyName(prop) === "plugins");
|
|
153
|
+
if (existing) {
|
|
154
|
+
const value = existing["value"];
|
|
155
|
+
if (value["type"] !== "ArrayExpression") throw new Error(`Unrecognized plugins config type in ${configPath}`);
|
|
156
|
+
return value["elements"];
|
|
157
|
+
}
|
|
158
|
+
const elements = [];
|
|
159
|
+
obj.properties.push({
|
|
160
|
+
type: "ObjectProperty",
|
|
161
|
+
key: {
|
|
162
|
+
type: "Identifier",
|
|
163
|
+
name: "plugins"
|
|
164
|
+
},
|
|
165
|
+
value: {
|
|
166
|
+
type: "ArrayExpression",
|
|
167
|
+
elements
|
|
168
|
+
},
|
|
169
|
+
computed: false,
|
|
170
|
+
shorthand: false
|
|
171
|
+
});
|
|
172
|
+
return elements;
|
|
173
|
+
}
|
|
173
174
|
function optionsToAstProperties(options) {
|
|
174
175
|
return Object.entries(options).map(([key, value]) => ({
|
|
175
176
|
type: "ObjectProperty",
|
|
@@ -195,13 +196,9 @@ function optionsToAstProperties(options) {
|
|
|
195
196
|
//#endregion
|
|
196
197
|
//#region src/modules/postcss.ts
|
|
197
198
|
const postcssConfigNames = [
|
|
198
|
-
"postcss.config.mts",
|
|
199
|
-
"postcss.config.ts",
|
|
200
199
|
"postcss.config.mjs",
|
|
201
200
|
"postcss.config.js",
|
|
202
201
|
"postcss.config.cjs",
|
|
203
|
-
".postcssrc.mts",
|
|
204
|
-
".postcssrc.ts",
|
|
205
202
|
".postcssrc.mjs",
|
|
206
203
|
".postcssrc.js",
|
|
207
204
|
".postcssrc.cjs",
|
|
@@ -211,20 +208,20 @@ const postcssConfigNames = [
|
|
|
211
208
|
".postcssrc"
|
|
212
209
|
];
|
|
213
210
|
function findPostcssConfig() {
|
|
214
|
-
return postcssConfigNames.find((name) =>
|
|
211
|
+
return postcssConfigNames.find((name) => fsExtra.existsSync(name));
|
|
215
212
|
}
|
|
216
213
|
function defaultPostcssConfig(pluginOptions = {}) {
|
|
217
214
|
const entries = Object.entries(pluginOptions);
|
|
218
215
|
return `export default {\n plugins: {\n "@mochi-css/postcss": ${entries.length === 0 ? "{}" : `{\n${entries.map(([k, v]) => ` ${k}: ${JSON.stringify(v)}`).join(",\n")}\n }`}\n }\n}\n`;
|
|
219
216
|
}
|
|
220
217
|
async function askForPath() {
|
|
221
|
-
const defaultConfig = findPostcssConfig() ?? "postcss.config.
|
|
222
|
-
const configPath = await
|
|
218
|
+
const defaultConfig = findPostcssConfig() ?? "postcss.config.mjs";
|
|
219
|
+
const configPath = await p.text({
|
|
223
220
|
message: "Path to PostCSS config",
|
|
224
221
|
placeholder: defaultConfig,
|
|
225
222
|
defaultValue: defaultConfig
|
|
226
223
|
});
|
|
227
|
-
if (
|
|
224
|
+
if (p.isCancel(configPath)) return false;
|
|
228
225
|
return configPath;
|
|
229
226
|
}
|
|
230
227
|
function isProxy(v) {
|
|
@@ -243,6 +240,7 @@ function addPluginToNamedVar(mod, varName, pluginName, pluginOptions, configPath
|
|
|
243
240
|
if (!init) throw new Error(`Failed to add postcss plugin to ${configPath}`);
|
|
244
241
|
const pluginsProp = init.properties.find((prop) => getPropKeyName(prop) === "plugins");
|
|
245
242
|
if (!pluginsProp) throw new Error(`Failed to find plugins object in ${configPath}`);
|
|
243
|
+
if (pluginsProp.value.properties.some((prop) => getPropKeyName(prop) === pluginName)) return;
|
|
246
244
|
pluginsProp.value.properties.push({
|
|
247
245
|
type: "ObjectProperty",
|
|
248
246
|
key: {
|
|
@@ -262,12 +260,12 @@ function addPluginToNamedVar(mod, varName, pluginName, pluginOptions, configPath
|
|
|
262
260
|
throw new Error(`Failed to add postcss plugin to ${configPath}`);
|
|
263
261
|
}
|
|
264
262
|
async function addPostcssPlugin(configPath, pluginName, pluginOptions = {}) {
|
|
265
|
-
const mod =
|
|
263
|
+
const mod = parseModule(await fs.readFile(configPath, "utf-8"));
|
|
266
264
|
const defaultExport = mod.exports["default"];
|
|
267
265
|
if (isProxy(defaultExport) && defaultExport.$type === "identifier") {
|
|
268
266
|
addPluginToNamedVar(mod, defaultExport.$name, pluginName, pluginOptions, configPath);
|
|
269
|
-
const { code: code$1 } =
|
|
270
|
-
await
|
|
267
|
+
const { code: code$1 } = generateCode(mod);
|
|
268
|
+
await fs.writeFile(configPath, code$1);
|
|
271
269
|
return;
|
|
272
270
|
}
|
|
273
271
|
const config = isProxy(defaultExport) && defaultExport.$type === "function-call" ? defaultExport.$args[0] : defaultExport;
|
|
@@ -275,46 +273,47 @@ async function addPostcssPlugin(configPath, pluginName, pluginOptions = {}) {
|
|
|
275
273
|
if ("plugins" in config && config["plugins"] !== void 0 && !isObject(config["plugins"])) throw new Error(`Unrecognized plugins config type in ${configPath}`);
|
|
276
274
|
config["plugins"] ??= {};
|
|
277
275
|
config["plugins"][pluginName] = pluginOptions;
|
|
278
|
-
const { code } =
|
|
279
|
-
await
|
|
276
|
+
const { code } = generateCode(mod);
|
|
277
|
+
await fs.writeFile(configPath, code);
|
|
280
278
|
}
|
|
281
279
|
async function addToConfig(configPath, pluginOptions = {}) {
|
|
282
|
-
if (!
|
|
283
|
-
await
|
|
280
|
+
if (!fsExtra.existsSync(configPath)) {
|
|
281
|
+
await fsExtra.writeFile("postcss.config.mjs", defaultPostcssConfig(pluginOptions));
|
|
284
282
|
return;
|
|
285
283
|
}
|
|
286
284
|
const ext = configPath.split(".").pop();
|
|
287
|
-
if (ext === "json" || path.
|
|
288
|
-
const config = await
|
|
285
|
+
if (ext === "json" || path.basename(configPath) === ".postcssrc") {
|
|
286
|
+
const config = await fsExtra.readJson(configPath);
|
|
289
287
|
if (!isObject(config)) throw new Error("Unrecognized config type in ${configPath}`)");
|
|
290
288
|
config["plugins"] ??= {};
|
|
291
289
|
if (!isObject(config["plugins"])) throw new Error("Unrecognized config type in ${configPath}`)");
|
|
292
290
|
config["plugins"]["@mochi-css/postcss"] = pluginOptions;
|
|
293
|
-
await
|
|
291
|
+
await fsExtra.writeJson(configPath, config, { spaces: 2 });
|
|
294
292
|
return;
|
|
295
293
|
}
|
|
296
294
|
if (ext === "yml" || ext === "yaml") throw new Error("YAML PostCSS config is not supported yet");
|
|
297
295
|
await addPostcssPlugin(configPath, "@mochi-css/postcss", pluginOptions);
|
|
298
296
|
}
|
|
299
297
|
function createPostcssModule(options = {}) {
|
|
300
|
-
const pluginOptions = {};
|
|
301
|
-
if (options.outDir !== void 0) pluginOptions["outDir"] = options.outDir;
|
|
302
298
|
return {
|
|
303
299
|
id: "postcss",
|
|
304
300
|
name: "PostCSS",
|
|
305
301
|
async run(ctx) {
|
|
306
302
|
let configPath;
|
|
307
|
-
|
|
303
|
+
const { postcss: cliOption } = ctx.moduleOptions;
|
|
304
|
+
if (cliOption !== void 0) configPath = typeof cliOption === "string" ? cliOption : findPostcssConfig() ?? "postcss.config.mts";
|
|
305
|
+
else if (options.auto) configPath = findPostcssConfig() ?? "postcss.config.mts";
|
|
306
|
+
else if (ctx.nonInteractive) return;
|
|
308
307
|
else {
|
|
309
|
-
const usePostcss = await
|
|
310
|
-
if (
|
|
308
|
+
const usePostcss = await p.confirm({ message: "Do you use PostCSS?" });
|
|
309
|
+
if (p.isCancel(usePostcss) || !usePostcss) return;
|
|
311
310
|
const selected = await askForPath();
|
|
312
311
|
if (selected === false) return;
|
|
313
312
|
configPath = selected;
|
|
314
313
|
}
|
|
315
|
-
await addToConfig(configPath
|
|
316
|
-
|
|
317
|
-
ctx.requirePackage("@mochi-css/postcss");
|
|
314
|
+
await addToConfig(configPath);
|
|
315
|
+
p.log.success("Added mochi plugin to the postcss config");
|
|
316
|
+
ctx.requirePackage(mochiPackage("@mochi-css/postcss"));
|
|
318
317
|
}
|
|
319
318
|
};
|
|
320
319
|
}
|
|
@@ -326,7 +325,7 @@ const libPreset = {
|
|
|
326
325
|
id: "lib",
|
|
327
326
|
name: "Library",
|
|
328
327
|
setup(runner) {
|
|
329
|
-
|
|
328
|
+
p.log.warn("Library preset is not fully supported yet.");
|
|
330
329
|
runner.register(createPostcssModule());
|
|
331
330
|
}
|
|
332
331
|
};
|
|
@@ -340,38 +339,16 @@ const viteConfigNames = [
|
|
|
340
339
|
"vite.config.mjs"
|
|
341
340
|
];
|
|
342
341
|
function findViteConfig() {
|
|
343
|
-
return viteConfigNames.find((name) =>
|
|
342
|
+
return viteConfigNames.find((name) => fsExtra.existsSync(name));
|
|
344
343
|
}
|
|
345
|
-
const defaultViteConfig = `
|
|
346
|
-
import {
|
|
344
|
+
const defaultViteConfig = dedent`
|
|
345
|
+
import { defineConfig } from "vite"
|
|
346
|
+
import { mochiCss } from "@mochi-css/vite"
|
|
347
347
|
|
|
348
|
-
export default defineConfig({
|
|
349
|
-
|
|
350
|
-
})
|
|
348
|
+
export default defineConfig({
|
|
349
|
+
plugins: [mochiCss()],
|
|
350
|
+
})
|
|
351
351
|
`;
|
|
352
|
-
function getPluginsElements(obj, configPath) {
|
|
353
|
-
const existing = obj.properties.find((prop) => getPropKeyName(prop) === "plugins");
|
|
354
|
-
if (existing) {
|
|
355
|
-
const value = existing["value"];
|
|
356
|
-
if (value["type"] !== "ArrayExpression") throw new Error(`Unrecognized plugins config type in ${configPath}`);
|
|
357
|
-
return value["elements"];
|
|
358
|
-
}
|
|
359
|
-
const elements = [];
|
|
360
|
-
obj.properties.push({
|
|
361
|
-
type: "ObjectProperty",
|
|
362
|
-
key: {
|
|
363
|
-
type: "Identifier",
|
|
364
|
-
name: "plugins"
|
|
365
|
-
},
|
|
366
|
-
value: {
|
|
367
|
-
type: "ArrayExpression",
|
|
368
|
-
elements
|
|
369
|
-
},
|
|
370
|
-
computed: false,
|
|
371
|
-
shorthand: false
|
|
372
|
-
});
|
|
373
|
-
return elements;
|
|
374
|
-
}
|
|
375
352
|
function addPluginCallToObj(obj, configPath) {
|
|
376
353
|
getPluginsElements(obj, configPath).push({
|
|
377
354
|
type: "CallExpression",
|
|
@@ -413,50 +390,206 @@ function addToVitePlugins(mod, configPath) {
|
|
|
413
390
|
throw new Error(`Failed to add vite plugin to ${configPath}`);
|
|
414
391
|
}
|
|
415
392
|
async function addMochiToViteConfig(configPath) {
|
|
416
|
-
const mod =
|
|
393
|
+
const mod = parseModule(await fs.readFile(configPath, "utf-8"));
|
|
417
394
|
mod.imports.$prepend({
|
|
418
395
|
from: "@mochi-css/vite",
|
|
419
396
|
imported: "mochiCss",
|
|
420
397
|
local: "mochiCss"
|
|
421
398
|
});
|
|
422
399
|
addToVitePlugins(mod, configPath);
|
|
423
|
-
const { code } =
|
|
424
|
-
await
|
|
400
|
+
const { code } = generateCode(mod);
|
|
401
|
+
await fs.writeFile(configPath, code);
|
|
425
402
|
}
|
|
426
403
|
const viteModule = {
|
|
427
404
|
id: "vite",
|
|
428
405
|
name: "Vite",
|
|
429
406
|
async run(ctx) {
|
|
430
407
|
const existingConfig = findViteConfig();
|
|
408
|
+
const { vite: cliOption } = ctx.moduleOptions;
|
|
431
409
|
let configPath;
|
|
432
|
-
if (
|
|
433
|
-
|
|
410
|
+
if (cliOption !== void 0) configPath = typeof cliOption === "string" ? cliOption : existingConfig ?? "vite.config.ts";
|
|
411
|
+
else if (existingConfig) configPath = existingConfig;
|
|
412
|
+
else if (ctx.nonInteractive) configPath = "vite.config.ts";
|
|
413
|
+
else {
|
|
414
|
+
const selected = await p.text({
|
|
434
415
|
message: "Path to Vite config",
|
|
435
416
|
placeholder: "vite.config.ts",
|
|
436
417
|
defaultValue: "vite.config.ts"
|
|
437
418
|
});
|
|
438
|
-
if (
|
|
419
|
+
if (p.isCancel(selected)) return;
|
|
439
420
|
configPath = selected;
|
|
440
|
-
}
|
|
441
|
-
if (!
|
|
442
|
-
await
|
|
443
|
-
|
|
421
|
+
}
|
|
422
|
+
if (!fsExtra.existsSync(configPath)) {
|
|
423
|
+
await fs.writeFile(configPath, defaultViteConfig);
|
|
424
|
+
p.log.success("Created vite config with mochi plugin");
|
|
444
425
|
} else {
|
|
445
426
|
await addMochiToViteConfig(configPath);
|
|
446
|
-
|
|
427
|
+
p.log.success("Added mochiCss() to vite config");
|
|
447
428
|
}
|
|
448
|
-
ctx.requirePackage("@mochi-css/vite");
|
|
429
|
+
ctx.requirePackage(mochiPackage("@mochi-css/vite"));
|
|
449
430
|
}
|
|
450
431
|
};
|
|
451
432
|
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/modules/mochiConfig.ts
|
|
435
|
+
const mochiConfigNames = [
|
|
436
|
+
"mochi.config.ts",
|
|
437
|
+
"mochi.config.mts",
|
|
438
|
+
"mochi.config.js",
|
|
439
|
+
"mochi.config.mjs"
|
|
440
|
+
];
|
|
441
|
+
function findMochiConfig() {
|
|
442
|
+
return mochiConfigNames.find((name) => fsExtra.existsSync(name));
|
|
443
|
+
}
|
|
444
|
+
const defaultMochiConfigBase = dedent`
|
|
445
|
+
import { defineConfig } from "@mochi-css/config"
|
|
446
|
+
|
|
447
|
+
export default defineConfig({})
|
|
448
|
+
`;
|
|
449
|
+
function defaultMochiConfigWithOptions(tmpDir, styledId) {
|
|
450
|
+
const lines = [];
|
|
451
|
+
if (tmpDir !== void 0) lines.push(` tmpDir: ${JSON.stringify(tmpDir)},`);
|
|
452
|
+
if (styledId) lines.push(` plugins: [styledIdPlugin()],`);
|
|
453
|
+
if (!styledId && lines.length === 0) return defaultMochiConfigBase;
|
|
454
|
+
return `${styledId ? dedent`
|
|
455
|
+
import { defineConfig } from "@mochi-css/config"
|
|
456
|
+
import { styledIdPlugin } from "@mochi-css/builder"
|
|
457
|
+
` : `import { defineConfig } from "@mochi-css/config"`}\n\nexport default defineConfig({\n${lines.join("\n")}\n})\n`;
|
|
458
|
+
}
|
|
459
|
+
function addStyledIdPluginToObj(obj, configPath) {
|
|
460
|
+
getPluginsElements(obj, configPath).push({
|
|
461
|
+
type: "CallExpression",
|
|
462
|
+
callee: {
|
|
463
|
+
type: "Identifier",
|
|
464
|
+
name: "styledIdPlugin"
|
|
465
|
+
},
|
|
466
|
+
arguments: []
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function addStyledIdToAst(mod, configPath) {
|
|
470
|
+
const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
|
|
471
|
+
if (!exportDefault) throw new Error(`No default export found in ${configPath}`);
|
|
472
|
+
const decl = exportDefault.declaration;
|
|
473
|
+
if (decl["type"] === "ObjectExpression") {
|
|
474
|
+
addStyledIdPluginToObj(decl, configPath);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (decl["type"] === "CallExpression") {
|
|
478
|
+
const firstArg = decl["arguments"][0];
|
|
479
|
+
if (firstArg?.["type"] === "ObjectExpression") {
|
|
480
|
+
addStyledIdPluginToObj(firstArg, configPath);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
throw new Error(`Failed to add styledIdPlugin to ${configPath}`);
|
|
485
|
+
}
|
|
486
|
+
async function addStyledIdToExistingConfig(configPath) {
|
|
487
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
488
|
+
if (content.includes("styledIdPlugin")) return;
|
|
489
|
+
const mod = parseModule(content);
|
|
490
|
+
mod.imports.$prepend({
|
|
491
|
+
from: "@mochi-css/builder",
|
|
492
|
+
imported: "styledIdPlugin",
|
|
493
|
+
local: "styledIdPlugin"
|
|
494
|
+
});
|
|
495
|
+
addStyledIdToAst(mod, configPath);
|
|
496
|
+
const { code } = generateCode(mod);
|
|
497
|
+
await fs.writeFile(configPath, code);
|
|
498
|
+
}
|
|
499
|
+
function getConfigObject(mod) {
|
|
500
|
+
const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
|
|
501
|
+
if (!exportDefault) return void 0;
|
|
502
|
+
const decl = exportDefault.declaration;
|
|
503
|
+
if (decl["type"] === "ObjectExpression") return decl;
|
|
504
|
+
if (decl["type"] === "CallExpression") {
|
|
505
|
+
const firstArg = decl["arguments"][0];
|
|
506
|
+
if (firstArg?.["type"] === "ObjectExpression") return firstArg;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function addTmpDirToExistingConfig(configPath, tmpDir) {
|
|
510
|
+
const mod = parseModule(await fs.readFile(configPath, "utf-8"));
|
|
511
|
+
const obj = getConfigObject(mod);
|
|
512
|
+
if (!obj) throw new Error(`Failed to add tmpDir to ${configPath}`);
|
|
513
|
+
if (obj["properties"].some((prop) => getPropKeyName(prop) === "tmpDir")) return;
|
|
514
|
+
obj["properties"] = [{
|
|
515
|
+
type: "ObjectProperty",
|
|
516
|
+
key: {
|
|
517
|
+
type: "StringLiteral",
|
|
518
|
+
value: "tmpDir"
|
|
519
|
+
},
|
|
520
|
+
value: {
|
|
521
|
+
type: "StringLiteral",
|
|
522
|
+
value: tmpDir
|
|
523
|
+
},
|
|
524
|
+
computed: false,
|
|
525
|
+
shorthand: false
|
|
526
|
+
}, ...obj["properties"]];
|
|
527
|
+
const { code } = generateCode(mod);
|
|
528
|
+
await fs.writeFile(configPath, code);
|
|
529
|
+
}
|
|
530
|
+
function createMochiConfigModule(options = {}) {
|
|
531
|
+
const { styledId = false, tmpDir } = options;
|
|
532
|
+
return {
|
|
533
|
+
id: "mochi-config",
|
|
534
|
+
name: "Mochi Config",
|
|
535
|
+
async run(ctx) {
|
|
536
|
+
const existing = findMochiConfig();
|
|
537
|
+
if (!existing) {
|
|
538
|
+
await fs.writeFile("mochi.config.ts", defaultMochiConfigWithOptions(tmpDir, styledId));
|
|
539
|
+
p.log.success("Created mochi.config.ts");
|
|
540
|
+
} else {
|
|
541
|
+
if (tmpDir !== void 0) try {
|
|
542
|
+
await addTmpDirToExistingConfig(existing, tmpDir);
|
|
543
|
+
p.log.success(`Added tmpDir to ${existing}`);
|
|
544
|
+
} catch {
|
|
545
|
+
p.log.warn(`Could not automatically add tmpDir to ${existing} — add it manually`);
|
|
546
|
+
}
|
|
547
|
+
if (styledId) try {
|
|
548
|
+
await addStyledIdToExistingConfig(existing);
|
|
549
|
+
p.log.success("Added styledIdPlugin to mochi.config.ts");
|
|
550
|
+
} catch {
|
|
551
|
+
p.log.warn(`Could not automatically add styledIdPlugin to ${existing} — add it manually`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
ctx.requirePackage(mochiPackage("@mochi-css/config"));
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/modules/uiFramework.ts
|
|
561
|
+
function createUiFrameworkModule(options = {}) {
|
|
562
|
+
return {
|
|
563
|
+
id: "ui-framework",
|
|
564
|
+
name: "UI Framework",
|
|
565
|
+
async run(ctx) {
|
|
566
|
+
const { framework: cliOption } = ctx.moduleOptions;
|
|
567
|
+
let useReact = options.auto === true || cliOption === "react";
|
|
568
|
+
if (useReact) {
|
|
569
|
+
ctx.requirePackage(mochiPackage("@mochi-css/react"), false);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (ctx.nonInteractive) return;
|
|
573
|
+
const confirmed = await p.confirm({ message: "Do you use React?" });
|
|
574
|
+
if (p.isCancel(confirmed) || !confirmed) return;
|
|
575
|
+
useReact = true;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
452
580
|
//#endregion
|
|
453
581
|
//#region src/presets/vite.ts
|
|
454
582
|
const vitePreset = {
|
|
455
583
|
id: "vite",
|
|
456
584
|
name: "Vite",
|
|
457
585
|
setup(runner) {
|
|
458
|
-
runner.register(
|
|
586
|
+
runner.register(createMochiConfigModule({
|
|
587
|
+
styledId: true,
|
|
588
|
+
tmpDir: ".mochi"
|
|
589
|
+
}));
|
|
590
|
+
runner.register(createPostcssModule());
|
|
459
591
|
runner.register(viteModule);
|
|
592
|
+
runner.register(createUiFrameworkModule());
|
|
460
593
|
}
|
|
461
594
|
};
|
|
462
595
|
|
|
@@ -469,11 +602,12 @@ const nextConfigNames = [
|
|
|
469
602
|
"next.config.mjs"
|
|
470
603
|
];
|
|
471
604
|
function findNextConfig() {
|
|
472
|
-
return nextConfigNames.find((name) =>
|
|
605
|
+
return nextConfigNames.find((name) => fsExtra.existsSync(name));
|
|
473
606
|
}
|
|
474
|
-
const defaultNextConfig = `
|
|
607
|
+
const defaultNextConfig = dedent`
|
|
608
|
+
import { withMochi } from "@mochi-css/next"
|
|
475
609
|
|
|
476
|
-
export default withMochi({})
|
|
610
|
+
export default withMochi({})
|
|
477
611
|
`;
|
|
478
612
|
function wrapExportDefault(mod, configPath) {
|
|
479
613
|
const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
|
|
@@ -488,39 +622,43 @@ function wrapExportDefault(mod, configPath) {
|
|
|
488
622
|
};
|
|
489
623
|
}
|
|
490
624
|
async function addMochiToNextConfig(configPath) {
|
|
491
|
-
const mod =
|
|
625
|
+
const mod = parseModule(await fs.readFile(configPath, "utf-8"));
|
|
492
626
|
mod.imports.$prepend({
|
|
493
627
|
from: "@mochi-css/next",
|
|
494
628
|
imported: "withMochi",
|
|
495
629
|
local: "withMochi"
|
|
496
630
|
});
|
|
497
631
|
wrapExportDefault(mod, configPath);
|
|
498
|
-
const { code } =
|
|
499
|
-
await
|
|
632
|
+
const { code } = generateCode(mod);
|
|
633
|
+
await fs.writeFile(configPath, code);
|
|
500
634
|
}
|
|
501
635
|
const nextModule = {
|
|
502
636
|
id: "next",
|
|
503
637
|
name: "Next.js",
|
|
504
638
|
async run(ctx) {
|
|
505
639
|
const existingConfig = findNextConfig();
|
|
640
|
+
const { next: cliOption } = ctx.moduleOptions;
|
|
506
641
|
let configPath;
|
|
507
|
-
if (
|
|
508
|
-
|
|
642
|
+
if (cliOption !== void 0) configPath = typeof cliOption === "string" ? cliOption : existingConfig ?? "next.config.ts";
|
|
643
|
+
else if (existingConfig) configPath = existingConfig;
|
|
644
|
+
else if (ctx.nonInteractive) configPath = "next.config.ts";
|
|
645
|
+
else {
|
|
646
|
+
const selected = await p.text({
|
|
509
647
|
message: "Path to Next.js config",
|
|
510
648
|
placeholder: "next.config.ts",
|
|
511
649
|
defaultValue: "next.config.ts"
|
|
512
650
|
});
|
|
513
|
-
if (
|
|
651
|
+
if (p.isCancel(selected)) return;
|
|
514
652
|
configPath = selected;
|
|
515
|
-
}
|
|
516
|
-
if (!
|
|
517
|
-
await
|
|
518
|
-
|
|
653
|
+
}
|
|
654
|
+
if (!fsExtra.existsSync(configPath)) {
|
|
655
|
+
await fs.writeFile(configPath, defaultNextConfig);
|
|
656
|
+
p.log.success("Created next config with mochi");
|
|
519
657
|
} else {
|
|
520
658
|
await addMochiToNextConfig(configPath);
|
|
521
|
-
|
|
659
|
+
p.log.success("Added withMochi() to next config");
|
|
522
660
|
}
|
|
523
|
-
ctx.requirePackage("@mochi-css/next");
|
|
661
|
+
ctx.requirePackage(mochiPackage("@mochi-css/next"));
|
|
524
662
|
}
|
|
525
663
|
};
|
|
526
664
|
|
|
@@ -530,11 +668,13 @@ const nextjsPreset = {
|
|
|
530
668
|
id: "nextjs",
|
|
531
669
|
name: "Next.js",
|
|
532
670
|
setup(runner) {
|
|
533
|
-
runner.register(
|
|
534
|
-
|
|
535
|
-
|
|
671
|
+
runner.register(createMochiConfigModule({
|
|
672
|
+
styledId: true,
|
|
673
|
+
tmpDir: ".mochi"
|
|
536
674
|
}));
|
|
675
|
+
runner.register(createPostcssModule({ auto: true }));
|
|
537
676
|
runner.register(nextModule);
|
|
677
|
+
runner.register(createUiFrameworkModule({ auto: true }));
|
|
538
678
|
}
|
|
539
679
|
};
|
|
540
680
|
|
|
@@ -548,25 +688,30 @@ const presets = {
|
|
|
548
688
|
|
|
549
689
|
//#endregion
|
|
550
690
|
//#region src/index.ts
|
|
551
|
-
|
|
691
|
+
program.name("tsuki").description("Add mochi-css to your project").version("3.0.0").addOption(new Option("-p, --preset <preset>", "Preset to use").choices([
|
|
552
692
|
"vite",
|
|
553
693
|
"nextjs",
|
|
554
694
|
"lib"
|
|
555
|
-
])).action(async (options) => {
|
|
556
|
-
|
|
695
|
+
])).option("-n, --no-interactive", "Non-interactive mode: skip all prompts (treat as cancelled)").option("--install", "Auto-accept package installation without prompting").option("--postcss [path]", "Enable PostCSS module; optionally specify config path").option("--vite [path]", "Use the given Vite config path instead of prompting").option("--next [path]", "Use the given Next.js config path instead of prompting").addOption(new Option("--framework <framework>", "UI framework to install support for").choices(["react"])).action(async (options) => {
|
|
696
|
+
p.intro(pc.cyan("Installing Mochi-CSS..."));
|
|
557
697
|
try {
|
|
558
698
|
const runner = new ModuleRunner();
|
|
699
|
+
const nonInteractive = options.interactive === false;
|
|
559
700
|
let presetId = options.preset;
|
|
560
701
|
if (presetId === void 0) {
|
|
561
|
-
|
|
702
|
+
if (nonInteractive) {
|
|
703
|
+
p.outro(pc.red("Cancelled"));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const selected = await p.select({
|
|
562
707
|
message: "Which framework are you using?",
|
|
563
708
|
options: Object.values(presets).map((preset$1) => ({
|
|
564
709
|
value: preset$1.id,
|
|
565
710
|
label: preset$1.name
|
|
566
711
|
}))
|
|
567
712
|
});
|
|
568
|
-
if (
|
|
569
|
-
|
|
713
|
+
if (p.isCancel(selected)) {
|
|
714
|
+
p.outro(pc.red("Cancelled"));
|
|
570
715
|
return;
|
|
571
716
|
}
|
|
572
717
|
presetId = selected;
|
|
@@ -574,12 +719,23 @@ commander.program.name("tsuki").description("Add mochi-css to your project").ver
|
|
|
574
719
|
const preset = presets[presetId];
|
|
575
720
|
if (!preset) throw new Error(`Unknown preset: ${presetId}`);
|
|
576
721
|
preset.setup(runner);
|
|
577
|
-
|
|
578
|
-
|
|
722
|
+
const moduleOptions = {};
|
|
723
|
+
if (options.postcss !== void 0) moduleOptions.postcss = options.postcss;
|
|
724
|
+
else if (presetId === "nextjs") moduleOptions.postcss = true;
|
|
725
|
+
if (options.vite !== void 0) moduleOptions.vite = options.vite;
|
|
726
|
+
if (options.next !== void 0) moduleOptions.next = options.next;
|
|
727
|
+
if (options.framework !== void 0) moduleOptions.framework = options.framework;
|
|
728
|
+
await runner.run({
|
|
729
|
+
nonInteractive,
|
|
730
|
+
autoInstall: options.install ?? false,
|
|
731
|
+
moduleOptions
|
|
732
|
+
});
|
|
733
|
+
p.outro(pc.green("Done!"));
|
|
579
734
|
} catch (e) {
|
|
580
|
-
if (e instanceof Error)
|
|
735
|
+
if (e instanceof Error) p.outro(pc.red(e.message));
|
|
581
736
|
}
|
|
582
737
|
});
|
|
583
|
-
|
|
738
|
+
program.parse();
|
|
584
739
|
|
|
585
|
-
//#endregion
|
|
740
|
+
//#endregion
|
|
741
|
+
export { };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mochi-css/tsuki",
|
|
3
3
|
"repository": "git@github.com:Niikelion/mochi-css.git",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"bin": "./dist/index.js",
|
|
7
8
|
"files": [
|
|
8
9
|
"/dist"
|
|
@@ -11,34 +12,27 @@
|
|
|
11
12
|
"build": "tsc --noEmit && tsdown",
|
|
12
13
|
"test": "vitest",
|
|
13
14
|
"coverage": "vitest run --coverage",
|
|
14
|
-
"smoke": "jiti scripts/smoke.ts",
|
|
15
15
|
"manual": "jiti scripts/manual.ts",
|
|
16
16
|
"lint": "eslint src",
|
|
17
17
|
"lint:fix": "eslint src --fix",
|
|
18
18
|
"format": "prettier --write"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@
|
|
22
|
-
"@
|
|
21
|
+
"@mochi-css/shared-config": "^2.0.0",
|
|
22
|
+
"@mochi-css/test": "^2.0.0",
|
|
23
23
|
"@types/cross-spawn": "^6.0.6",
|
|
24
24
|
"@types/fs-extra": "^11.0.4",
|
|
25
25
|
"@types/node": "^24.8.1",
|
|
26
|
-
"@vitest/coverage-v8": "^4.0.15",
|
|
27
26
|
"eslint": "^9.39.2",
|
|
28
|
-
"eslint-config-prettier": "^10.1.8",
|
|
29
|
-
"eslint-plugin-prettier": "^5.5.5",
|
|
30
|
-
"jiti": "^2.6.1",
|
|
31
27
|
"prettier": "^3.8.1",
|
|
32
|
-
"tsdown": "^0.15.7",
|
|
33
28
|
"typescript": "^5.9.3",
|
|
34
|
-
"typescript-eslint": "^8.21.0",
|
|
35
|
-
"vite-tsconfig-paths": "^5.1.4",
|
|
36
29
|
"vitest": "^4.0.15"
|
|
37
30
|
},
|
|
38
31
|
"dependencies": {
|
|
39
32
|
"@clack/prompts": "^1.0.1",
|
|
40
33
|
"commander": "^13.1.0",
|
|
41
34
|
"cross-spawn": "^7.0.6",
|
|
35
|
+
"dedent": "^1.7.2",
|
|
42
36
|
"fs-extra": "^11.3.0",
|
|
43
37
|
"magicast": "^0.5.1",
|
|
44
38
|
"package-manager-detector": "^1.6.0",
|