@mochi-css/tsuki 2.1.0 → 4.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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +249 -151
  3. package/package.json +6 -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`, `plugins`, `splitCss`, 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
- //#region rolldown:runtime
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
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 = (0, cross_spawn.default)(command, args, options);
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 = __clack_prompts.taskLog({ title });
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 node_stream.PassThrough();
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 = (0, node_readline.createInterface)({
57
+ const rl = createInterface({
91
58
  input: merged,
92
59
  crlfDelay: Infinity
93
60
  });
@@ -105,7 +72,7 @@ async function runInstall(title, command, args, packages) {
105
72
  }
106
73
  async function installPackages(packages, autoInstall = false) {
107
74
  if (packages.length === 0) return;
108
- const packageManager = await (0, package_manager_detector.detect)({ strategies: [
75
+ const packageManager = await detect({ strategies: [
109
76
  "packageManager-field",
110
77
  "devEngines-field",
111
78
  "lockfile",
@@ -118,19 +85,19 @@ async function installPackages(packages, autoInstall = false) {
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
87
  if (!autoInstall) {
121
- const confirmed = await __clack_prompts.confirm({ message: `Install the following packages: ${packageList}?` });
122
- if (__clack_prompts.isCancel(confirmed) || !confirmed) {
123
- __clack_prompts.log.info("Skipping package installation");
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");
124
91
  return;
125
92
  }
126
93
  }
127
94
  if (devPackages.length > 0) {
128
- const cmd = (0, package_manager_detector.resolveCommand)(agent, "add", [devFlag, ...devPackages]);
95
+ const cmd = resolveCommand(agent, "add", [devFlag, ...devPackages]);
129
96
  if (cmd === null) throw new Error("Could not prepare install command");
130
97
  await runInstall(`Installing dev dependencies: ${devPackages.join(", ")}`, cmd.command, cmd.args, devPackages);
131
98
  }
132
99
  if (prodPackages.length > 0) {
133
- const cmd = (0, package_manager_detector.resolveCommand)(agent, "add", prodPackages);
100
+ const cmd = resolveCommand(agent, "add", prodPackages);
134
101
  if (cmd === null) throw new Error("Could not prepare install command");
135
102
  await runInstall(`Installing dependencies: ${prodPackages.join(", ")}`, cmd.command, cmd.args, prodPackages);
136
103
  }
@@ -141,8 +108,8 @@ async function installPackages(packages, autoInstall = false) {
141
108
  var ModuleRunner = class {
142
109
  packages = [];
143
110
  modules = [];
144
- register(module$1) {
145
- this.modules.push(module$1);
111
+ register(module) {
112
+ this.modules.push(module);
146
113
  return this;
147
114
  }
148
115
  async run(options = {}) {
@@ -163,11 +130,17 @@ var ModuleRunner = class {
163
130
  nonInteractive,
164
131
  moduleOptions
165
132
  };
166
- for (const module$1 of this.modules) await module$1.run(ctx);
133
+ for (const module of this.modules) await module.run(ctx);
167
134
  if (this.packages.length > 0) await installPackages(this.packages, autoInstall);
168
135
  }
169
136
  };
170
137
 
138
+ //#endregion
139
+ //#region src/version.ts
140
+ function mochiPackage(name) {
141
+ return `${name}@^${parseInt("4.0.0".split(".")[0] ?? "0", 10)}.0.0`;
142
+ }
143
+
171
144
  //#endregion
172
145
  //#region src/modules/ast.ts
173
146
  function getPropKeyName(prop) {
@@ -175,6 +148,29 @@ function getPropKeyName(prop) {
175
148
  if (typeof key["name"] === "string") return key["name"];
176
149
  if (typeof key["value"] === "string") return key["value"];
177
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
+ }
178
174
  function optionsToAstProperties(options) {
179
175
  return Object.entries(options).map(([key, value]) => ({
180
176
  type: "ObjectProperty",
@@ -200,13 +196,9 @@ function optionsToAstProperties(options) {
200
196
  //#endregion
201
197
  //#region src/modules/postcss.ts
202
198
  const postcssConfigNames = [
203
- "postcss.config.mts",
204
- "postcss.config.ts",
205
199
  "postcss.config.mjs",
206
200
  "postcss.config.js",
207
201
  "postcss.config.cjs",
208
- ".postcssrc.mts",
209
- ".postcssrc.ts",
210
202
  ".postcssrc.mjs",
211
203
  ".postcssrc.js",
212
204
  ".postcssrc.cjs",
@@ -216,20 +208,20 @@ const postcssConfigNames = [
216
208
  ".postcssrc"
217
209
  ];
218
210
  function findPostcssConfig() {
219
- return postcssConfigNames.find((name) => fs_extra.default.existsSync(name));
211
+ return postcssConfigNames.find((name) => fsExtra.existsSync(name));
220
212
  }
221
213
  function defaultPostcssConfig(pluginOptions = {}) {
222
214
  const entries = Object.entries(pluginOptions);
223
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`;
224
216
  }
225
217
  async function askForPath() {
226
- const defaultConfig = findPostcssConfig() ?? "postcss.config.js";
227
- const configPath = await __clack_prompts.text({
218
+ const defaultConfig = findPostcssConfig() ?? "postcss.config.mjs";
219
+ const configPath = await p.text({
228
220
  message: "Path to PostCSS config",
229
221
  placeholder: defaultConfig,
230
222
  defaultValue: defaultConfig
231
223
  });
232
- if (__clack_prompts.isCancel(configPath)) return false;
224
+ if (p.isCancel(configPath)) return false;
233
225
  return configPath;
234
226
  }
235
227
  function isProxy(v) {
@@ -248,6 +240,7 @@ function addPluginToNamedVar(mod, varName, pluginName, pluginOptions, configPath
248
240
  if (!init) throw new Error(`Failed to add postcss plugin to ${configPath}`);
249
241
  const pluginsProp = init.properties.find((prop) => getPropKeyName(prop) === "plugins");
250
242
  if (!pluginsProp) throw new Error(`Failed to find plugins object in ${configPath}`);
243
+ if (pluginsProp.value.properties.some((prop) => getPropKeyName(prop) === pluginName)) return;
251
244
  pluginsProp.value.properties.push({
252
245
  type: "ObjectProperty",
253
246
  key: {
@@ -267,12 +260,12 @@ function addPluginToNamedVar(mod, varName, pluginName, pluginOptions, configPath
267
260
  throw new Error(`Failed to add postcss plugin to ${configPath}`);
268
261
  }
269
262
  async function addPostcssPlugin(configPath, pluginName, pluginOptions = {}) {
270
- const mod = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
263
+ const mod = parseModule(await fs.readFile(configPath, "utf-8"));
271
264
  const defaultExport = mod.exports["default"];
272
265
  if (isProxy(defaultExport) && defaultExport.$type === "identifier") {
273
266
  addPluginToNamedVar(mod, defaultExport.$name, pluginName, pluginOptions, configPath);
274
- const { code: code$1 } = (0, magicast.generateCode)(mod);
275
- await fs_promises.default.writeFile(configPath, code$1);
267
+ const { code: code$1 } = generateCode(mod);
268
+ await fs.writeFile(configPath, code$1);
276
269
  return;
277
270
  }
278
271
  const config = isProxy(defaultExport) && defaultExport.$type === "function-call" ? defaultExport.$args[0] : defaultExport;
@@ -280,30 +273,28 @@ async function addPostcssPlugin(configPath, pluginName, pluginOptions = {}) {
280
273
  if ("plugins" in config && config["plugins"] !== void 0 && !isObject(config["plugins"])) throw new Error(`Unrecognized plugins config type in ${configPath}`);
281
274
  config["plugins"] ??= {};
282
275
  config["plugins"][pluginName] = pluginOptions;
283
- const { code } = (0, magicast.generateCode)(mod);
284
- await fs_promises.default.writeFile(configPath, code);
276
+ const { code } = generateCode(mod);
277
+ await fs.writeFile(configPath, code);
285
278
  }
286
279
  async function addToConfig(configPath, pluginOptions = {}) {
287
- if (!fs_extra.default.existsSync(configPath)) {
288
- await fs_extra.default.writeFile("postcss.config.mts", defaultPostcssConfig(pluginOptions));
280
+ if (!fsExtra.existsSync(configPath)) {
281
+ await fsExtra.writeFile("postcss.config.mjs", defaultPostcssConfig(pluginOptions));
289
282
  return;
290
283
  }
291
284
  const ext = configPath.split(".").pop();
292
- if (ext === "json" || path.default.basename(configPath) === ".postcssrc") {
293
- const config = await fs_extra.default.readJson(configPath);
285
+ if (ext === "json" || path.basename(configPath) === ".postcssrc") {
286
+ const config = await fsExtra.readJson(configPath);
294
287
  if (!isObject(config)) throw new Error("Unrecognized config type in ${configPath}`)");
295
288
  config["plugins"] ??= {};
296
289
  if (!isObject(config["plugins"])) throw new Error("Unrecognized config type in ${configPath}`)");
297
290
  config["plugins"]["@mochi-css/postcss"] = pluginOptions;
298
- await fs_extra.default.writeJson(configPath, config, { spaces: 2 });
291
+ await fsExtra.writeJson(configPath, config, { spaces: 2 });
299
292
  return;
300
293
  }
301
294
  if (ext === "yml" || ext === "yaml") throw new Error("YAML PostCSS config is not supported yet");
302
295
  await addPostcssPlugin(configPath, "@mochi-css/postcss", pluginOptions);
303
296
  }
304
297
  function createPostcssModule(options = {}) {
305
- const pluginOptions = {};
306
- if (options.outDir !== void 0) pluginOptions["outDir"] = options.outDir;
307
298
  return {
308
299
  id: "postcss",
309
300
  name: "PostCSS",
@@ -314,15 +305,15 @@ function createPostcssModule(options = {}) {
314
305
  else if (options.auto) configPath = findPostcssConfig() ?? "postcss.config.mts";
315
306
  else if (ctx.nonInteractive) return;
316
307
  else {
317
- const usePostcss = await __clack_prompts.confirm({ message: "Do you use PostCSS?" });
318
- if (__clack_prompts.isCancel(usePostcss) || !usePostcss) return;
308
+ const usePostcss = await p.confirm({ message: "Do you use PostCSS?" });
309
+ if (p.isCancel(usePostcss) || !usePostcss) return;
319
310
  const selected = await askForPath();
320
311
  if (selected === false) return;
321
312
  configPath = selected;
322
313
  }
323
- await addToConfig(configPath, pluginOptions);
324
- __clack_prompts.log.success("Added mochi plugin to the postcss config");
325
- 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"));
326
317
  }
327
318
  };
328
319
  }
@@ -334,7 +325,7 @@ const libPreset = {
334
325
  id: "lib",
335
326
  name: "Library",
336
327
  setup(runner) {
337
- __clack_prompts.log.warn("Library preset is not fully supported yet.");
328
+ p.log.warn("Library preset is not fully supported yet.");
338
329
  runner.register(createPostcssModule());
339
330
  }
340
331
  };
@@ -348,38 +339,16 @@ const viteConfigNames = [
348
339
  "vite.config.mjs"
349
340
  ];
350
341
  function findViteConfig() {
351
- return viteConfigNames.find((name) => fs_extra.default.existsSync(name));
342
+ return viteConfigNames.find((name) => fsExtra.existsSync(name));
352
343
  }
353
- const defaultViteConfig = `import { defineConfig } from "vite"
354
- import { mochiCss } from "@mochi-css/vite"
344
+ const defaultViteConfig = dedent`
345
+ import { defineConfig } from "vite"
346
+ import { mochiCss } from "@mochi-css/vite"
355
347
 
356
- export default defineConfig({
357
- plugins: [mochiCss()],
358
- })
348
+ export default defineConfig({
349
+ plugins: [mochiCss()],
350
+ })
359
351
  `;
360
- function getPluginsElements(obj, configPath) {
361
- const existing = obj.properties.find((prop) => getPropKeyName(prop) === "plugins");
362
- if (existing) {
363
- const value = existing["value"];
364
- if (value["type"] !== "ArrayExpression") throw new Error(`Unrecognized plugins config type in ${configPath}`);
365
- return value["elements"];
366
- }
367
- const elements = [];
368
- obj.properties.push({
369
- type: "ObjectProperty",
370
- key: {
371
- type: "Identifier",
372
- name: "plugins"
373
- },
374
- value: {
375
- type: "ArrayExpression",
376
- elements
377
- },
378
- computed: false,
379
- shorthand: false
380
- });
381
- return elements;
382
- }
383
352
  function addPluginCallToObj(obj, configPath) {
384
353
  getPluginsElements(obj, configPath).push({
385
354
  type: "CallExpression",
@@ -421,15 +390,15 @@ function addToVitePlugins(mod, configPath) {
421
390
  throw new Error(`Failed to add vite plugin to ${configPath}`);
422
391
  }
423
392
  async function addMochiToViteConfig(configPath) {
424
- const mod = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
393
+ const mod = parseModule(await fs.readFile(configPath, "utf-8"));
425
394
  mod.imports.$prepend({
426
395
  from: "@mochi-css/vite",
427
396
  imported: "mochiCss",
428
397
  local: "mochiCss"
429
398
  });
430
399
  addToVitePlugins(mod, configPath);
431
- const { code } = (0, magicast.generateCode)(mod);
432
- await fs_promises.default.writeFile(configPath, code);
400
+ const { code } = generateCode(mod);
401
+ await fs.writeFile(configPath, code);
433
402
  }
434
403
  const viteModule = {
435
404
  id: "vite",
@@ -442,33 +411,157 @@ const viteModule = {
442
411
  else if (existingConfig) configPath = existingConfig;
443
412
  else if (ctx.nonInteractive) configPath = "vite.config.ts";
444
413
  else {
445
- const selected = await __clack_prompts.text({
414
+ const selected = await p.text({
446
415
  message: "Path to Vite config",
447
416
  placeholder: "vite.config.ts",
448
417
  defaultValue: "vite.config.ts"
449
418
  });
450
- if (__clack_prompts.isCancel(selected)) return;
419
+ if (p.isCancel(selected)) return;
451
420
  configPath = selected;
452
421
  }
453
- if (!fs_extra.default.existsSync(configPath)) {
454
- await fs_promises.default.writeFile(configPath, defaultViteConfig);
455
- __clack_prompts.log.success("Created vite config with mochi plugin");
422
+ if (!fsExtra.existsSync(configPath)) {
423
+ await fs.writeFile(configPath, defaultViteConfig);
424
+ p.log.success("Created vite config with mochi plugin");
456
425
  } else {
457
426
  await addMochiToViteConfig(configPath);
458
- __clack_prompts.log.success("Added mochiCss() to vite config");
427
+ p.log.success("Added mochiCss() to vite config");
459
428
  }
460
- ctx.requirePackage("@mochi-css/vite");
429
+ ctx.requirePackage(mochiPackage("@mochi-css/vite"));
461
430
  }
462
431
  };
463
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/vanilla/config"
446
+
447
+ export default defineConfig({})
448
+ `;
449
+ function defaultMochiConfigWithOptions(tmpDir, styledId) {
450
+ const importPath = styledId ? "@mochi-css/vanilla-react/config" : "@mochi-css/vanilla/config";
451
+ const lines = [];
452
+ if (tmpDir !== void 0) lines.push(` tmpDir: ${JSON.stringify(tmpDir)},`);
453
+ if (!styledId && lines.length === 0) return defaultMochiConfigBase;
454
+ return `import { defineConfig } from "${importPath}"\n\nexport default defineConfig({\n${lines.join("\n")}\n})\n`;
455
+ }
456
+ function getConfigObject(mod) {
457
+ const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
458
+ if (!exportDefault) return void 0;
459
+ const decl = exportDefault.declaration;
460
+ if (decl["type"] === "ObjectExpression") return decl;
461
+ if (decl["type"] === "CallExpression") {
462
+ const firstArg = decl["arguments"][0];
463
+ if (firstArg?.["type"] === "ObjectExpression") return firstArg;
464
+ }
465
+ }
466
+ async function addTmpDirToExistingConfig(configPath, tmpDir) {
467
+ const mod = parseModule(await fs.readFile(configPath, "utf-8"));
468
+ const obj = getConfigObject(mod);
469
+ if (!obj) throw new Error(`Failed to add tmpDir to ${configPath}`);
470
+ if (obj["properties"].some((prop) => getPropKeyName(prop) === "tmpDir")) return;
471
+ obj["properties"] = [{
472
+ type: "ObjectProperty",
473
+ key: {
474
+ type: "StringLiteral",
475
+ value: "tmpDir"
476
+ },
477
+ value: {
478
+ type: "StringLiteral",
479
+ value: tmpDir
480
+ },
481
+ computed: false,
482
+ shorthand: false
483
+ }, ...obj["properties"]];
484
+ const { code } = generateCode(mod);
485
+ await fs.writeFile(configPath, code);
486
+ }
487
+ async function patchToVanillaReact(configPath) {
488
+ const content = await fs.readFile(configPath, "utf-8");
489
+ if (content.includes("@mochi-css/vanilla-react")) return;
490
+ const patched = content.replace(/"@mochi-css\/vanilla\/config"/g, "\"@mochi-css/vanilla-react/config\"");
491
+ await fs.writeFile(configPath, patched);
492
+ }
493
+ function createMochiConfigModule(options = {}) {
494
+ const { styledId = false, tmpDir } = options;
495
+ return {
496
+ id: "mochi-config",
497
+ name: "Mochi Config",
498
+ async run(ctx) {
499
+ const existing = findMochiConfig();
500
+ if (!existing) {
501
+ await fs.writeFile("mochi.config.ts", defaultMochiConfigWithOptions(tmpDir, styledId));
502
+ p.log.success("Created mochi.config.ts");
503
+ } else {
504
+ if (tmpDir !== void 0) try {
505
+ await addTmpDirToExistingConfig(existing, tmpDir);
506
+ p.log.success(`Added tmpDir to ${existing}`);
507
+ } catch {
508
+ p.log.warn(`Could not automatically add tmpDir to ${existing} — add it manually`);
509
+ }
510
+ if (styledId) try {
511
+ await patchToVanillaReact(existing);
512
+ p.log.success("Switched mochi.config to @mochi-css/vanilla-react/config");
513
+ } catch {
514
+ p.log.warn(`Could not patch ${existing} — change the import to @mochi-css/vanilla-react/config manually`);
515
+ }
516
+ }
517
+ ctx.requirePackage(mochiPackage("@mochi-css/vanilla"));
518
+ if (styledId) ctx.requirePackage(mochiPackage("@mochi-css/vanilla-react"));
519
+ }
520
+ };
521
+ }
522
+
523
+ //#endregion
524
+ //#region src/modules/uiFramework.ts
525
+ async function patchConfigForReact(configPath) {
526
+ const content = await fs.readFile(configPath, "utf-8");
527
+ if (content.includes("@mochi-css/vanilla-react")) return;
528
+ const patched = content.replace(/"@mochi-css\/vanilla\/config"/g, "\"@mochi-css/vanilla-react/config\"");
529
+ await fs.writeFile(configPath, patched);
530
+ }
531
+ function createUiFrameworkModule(options = {}) {
532
+ return {
533
+ id: "ui-framework",
534
+ name: "UI Framework",
535
+ async run(ctx) {
536
+ const { framework: cliOption } = ctx.moduleOptions;
537
+ let useReact = options.auto === true || cliOption === "react";
538
+ if (!useReact) {
539
+ if (ctx.nonInteractive) return;
540
+ const confirmed = await p.confirm({ message: "Do you use React?" });
541
+ if (p.isCancel(confirmed) || !confirmed) return;
542
+ useReact = true;
543
+ }
544
+ ctx.requirePackage(mochiPackage("@mochi-css/vanilla-react"), false);
545
+ const configPath = findMochiConfig();
546
+ if (configPath) try {
547
+ await patchConfigForReact(configPath);
548
+ } catch {
549
+ p.log.warn(`Could not patch ${configPath} — change the import to @mochi-css/vanilla-react/config manually`);
550
+ }
551
+ }
552
+ };
553
+ }
554
+
464
555
  //#endregion
465
556
  //#region src/presets/vite.ts
466
557
  const vitePreset = {
467
558
  id: "vite",
468
559
  name: "Vite",
469
560
  setup(runner) {
470
- runner.register(createPostcssModule({ outDir: ".mochi" }));
561
+ runner.register(createMochiConfigModule({ tmpDir: ".mochi" }));
562
+ runner.register(createPostcssModule());
471
563
  runner.register(viteModule);
564
+ runner.register(createUiFrameworkModule());
472
565
  }
473
566
  };
474
567
 
@@ -481,11 +574,12 @@ const nextConfigNames = [
481
574
  "next.config.mjs"
482
575
  ];
483
576
  function findNextConfig() {
484
- return nextConfigNames.find((name) => fs_extra.default.existsSync(name));
577
+ return nextConfigNames.find((name) => fsExtra.existsSync(name));
485
578
  }
486
- const defaultNextConfig = `import { withMochi } from "@mochi-css/next"
579
+ const defaultNextConfig = dedent`
580
+ import { withMochi } from "@mochi-css/next"
487
581
 
488
- export default withMochi({})
582
+ export default withMochi({})
489
583
  `;
490
584
  function wrapExportDefault(mod, configPath) {
491
585
  const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
@@ -500,15 +594,15 @@ function wrapExportDefault(mod, configPath) {
500
594
  };
501
595
  }
502
596
  async function addMochiToNextConfig(configPath) {
503
- const mod = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
597
+ const mod = parseModule(await fs.readFile(configPath, "utf-8"));
504
598
  mod.imports.$prepend({
505
599
  from: "@mochi-css/next",
506
600
  imported: "withMochi",
507
601
  local: "withMochi"
508
602
  });
509
603
  wrapExportDefault(mod, configPath);
510
- const { code } = (0, magicast.generateCode)(mod);
511
- await fs_promises.default.writeFile(configPath, code);
604
+ const { code } = generateCode(mod);
605
+ await fs.writeFile(configPath, code);
512
606
  }
513
607
  const nextModule = {
514
608
  id: "next",
@@ -521,22 +615,22 @@ const nextModule = {
521
615
  else if (existingConfig) configPath = existingConfig;
522
616
  else if (ctx.nonInteractive) configPath = "next.config.ts";
523
617
  else {
524
- const selected = await __clack_prompts.text({
618
+ const selected = await p.text({
525
619
  message: "Path to Next.js config",
526
620
  placeholder: "next.config.ts",
527
621
  defaultValue: "next.config.ts"
528
622
  });
529
- if (__clack_prompts.isCancel(selected)) return;
623
+ if (p.isCancel(selected)) return;
530
624
  configPath = selected;
531
625
  }
532
- if (!fs_extra.default.existsSync(configPath)) {
533
- await fs_promises.default.writeFile(configPath, defaultNextConfig);
534
- __clack_prompts.log.success("Created next config with mochi");
626
+ if (!fsExtra.existsSync(configPath)) {
627
+ await fs.writeFile(configPath, defaultNextConfig);
628
+ p.log.success("Created next config with mochi");
535
629
  } else {
536
630
  await addMochiToNextConfig(configPath);
537
- __clack_prompts.log.success("Added withMochi() to next config");
631
+ p.log.success("Added withMochi() to next config");
538
632
  }
539
- ctx.requirePackage("@mochi-css/next");
633
+ ctx.requirePackage(mochiPackage("@mochi-css/next"));
540
634
  }
541
635
  };
542
636
 
@@ -546,11 +640,13 @@ const nextjsPreset = {
546
640
  id: "nextjs",
547
641
  name: "Next.js",
548
642
  setup(runner) {
549
- runner.register(createPostcssModule({
550
- outDir: ".mochi",
551
- auto: true
643
+ runner.register(createMochiConfigModule({
644
+ styledId: true,
645
+ tmpDir: ".mochi"
552
646
  }));
647
+ runner.register(createPostcssModule({ auto: true }));
553
648
  runner.register(nextModule);
649
+ runner.register(createUiFrameworkModule({ auto: true }));
554
650
  }
555
651
  };
556
652
 
@@ -564,30 +660,30 @@ const presets = {
564
660
 
565
661
  //#endregion
566
662
  //#region src/index.ts
567
- commander.program.name("tsuki").description("Add mochi-css to your project").version("2.1.0").addOption(new commander.Option("-p, --preset <preset>", "Preset to use").choices([
663
+ program.name("tsuki").description("Add mochi-css to your project").version("4.0.0").addOption(new Option("-p, --preset <preset>", "Preset to use").choices([
568
664
  "vite",
569
665
  "nextjs",
570
666
  "lib"
571
- ])).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").action(async (options) => {
572
- __clack_prompts.intro(picocolors.default.cyan("Installing Mochi-CSS..."));
667
+ ])).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) => {
668
+ p.intro(pc.cyan("Installing Mochi-CSS..."));
573
669
  try {
574
670
  const runner = new ModuleRunner();
575
671
  const nonInteractive = options.interactive === false;
576
672
  let presetId = options.preset;
577
673
  if (presetId === void 0) {
578
674
  if (nonInteractive) {
579
- __clack_prompts.outro(picocolors.default.red("Cancelled"));
675
+ p.outro(pc.red("Cancelled"));
580
676
  return;
581
677
  }
582
- const selected = await __clack_prompts.select({
678
+ const selected = await p.select({
583
679
  message: "Which framework are you using?",
584
680
  options: Object.values(presets).map((preset$1) => ({
585
681
  value: preset$1.id,
586
682
  label: preset$1.name
587
683
  }))
588
684
  });
589
- if (__clack_prompts.isCancel(selected)) {
590
- __clack_prompts.outro(picocolors.default.red("Cancelled"));
685
+ if (p.isCancel(selected)) {
686
+ p.outro(pc.red("Cancelled"));
591
687
  return;
592
688
  }
593
689
  presetId = selected;
@@ -600,16 +696,18 @@ commander.program.name("tsuki").description("Add mochi-css to your project").ver
600
696
  else if (presetId === "nextjs") moduleOptions.postcss = true;
601
697
  if (options.vite !== void 0) moduleOptions.vite = options.vite;
602
698
  if (options.next !== void 0) moduleOptions.next = options.next;
699
+ if (options.framework !== void 0) moduleOptions.framework = options.framework;
603
700
  await runner.run({
604
701
  nonInteractive,
605
702
  autoInstall: options.install ?? false,
606
703
  moduleOptions
607
704
  });
608
- __clack_prompts.outro(picocolors.default.green("Done!"));
705
+ p.outro(pc.green("Done!"));
609
706
  } catch (e) {
610
- if (e instanceof Error) __clack_prompts.outro(picocolors.default.red(e.message));
707
+ if (e instanceof Error) p.outro(pc.red(e.message));
611
708
  }
612
709
  });
613
- commander.program.parse();
710
+ program.parse();
614
711
 
615
- //#endregion
712
+ //#endregion
713
+ 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": "2.1.0",
4
+ "version": "4.0.0",
5
5
  "license": "MIT",
6
+ "type": "module",
6
7
  "bin": "./dist/index.js",
7
8
  "files": [
8
9
  "/dist"
@@ -14,30 +15,24 @@
14
15
  "manual": "jiti scripts/manual.ts",
15
16
  "lint": "eslint src",
16
17
  "lint:fix": "eslint src --fix",
17
- "format": "prettier --write"
18
+ "format": "prettier --write ."
18
19
  },
19
20
  "devDependencies": {
20
- "@eslint/js": "^9.18.0",
21
- "@gamedev-sensei/ts-config": "^2.1.0",
21
+ "@mochi-css/shared-config": "^2.0.0",
22
+ "@mochi-css/test": "^2.0.0",
22
23
  "@types/cross-spawn": "^6.0.6",
23
24
  "@types/fs-extra": "^11.0.4",
24
25
  "@types/node": "^24.8.1",
25
- "@vitest/coverage-v8": "^4.0.15",
26
26
  "eslint": "^9.39.2",
27
- "eslint-config-prettier": "^10.1.8",
28
- "eslint-plugin-prettier": "^5.5.5",
29
- "jiti": "^2.6.1",
30
27
  "prettier": "^3.8.1",
31
- "tsdown": "^0.15.7",
32
28
  "typescript": "^5.9.3",
33
- "typescript-eslint": "^8.21.0",
34
- "vite-tsconfig-paths": "^5.1.4",
35
29
  "vitest": "^4.0.15"
36
30
  },
37
31
  "dependencies": {
38
32
  "@clack/prompts": "^1.0.1",
39
33
  "commander": "^13.1.0",
40
34
  "cross-spawn": "^7.0.6",
35
+ "dedent": "^1.7.2",
41
36
  "fs-extra": "^11.3.0",
42
37
  "magicast": "^0.5.1",
43
38
  "package-manager-detector": "^1.6.0",