@mochi-css/tsuki 2.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +277 -151
  3. package/package.json +5 -10
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
- //#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("3.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,185 @@ 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/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
+
464
580
  //#endregion
465
581
  //#region src/presets/vite.ts
466
582
  const vitePreset = {
467
583
  id: "vite",
468
584
  name: "Vite",
469
585
  setup(runner) {
470
- runner.register(createPostcssModule({ outDir: ".mochi" }));
586
+ runner.register(createMochiConfigModule({
587
+ styledId: true,
588
+ tmpDir: ".mochi"
589
+ }));
590
+ runner.register(createPostcssModule());
471
591
  runner.register(viteModule);
592
+ runner.register(createUiFrameworkModule());
472
593
  }
473
594
  };
474
595
 
@@ -481,11 +602,12 @@ const nextConfigNames = [
481
602
  "next.config.mjs"
482
603
  ];
483
604
  function findNextConfig() {
484
- return nextConfigNames.find((name) => fs_extra.default.existsSync(name));
605
+ return nextConfigNames.find((name) => fsExtra.existsSync(name));
485
606
  }
486
- const defaultNextConfig = `import { withMochi } from "@mochi-css/next"
607
+ const defaultNextConfig = dedent`
608
+ import { withMochi } from "@mochi-css/next"
487
609
 
488
- export default withMochi({})
610
+ export default withMochi({})
489
611
  `;
490
612
  function wrapExportDefault(mod, configPath) {
491
613
  const exportDefault = mod.$ast.body.find((s) => s.type === "ExportDefaultDeclaration");
@@ -500,15 +622,15 @@ function wrapExportDefault(mod, configPath) {
500
622
  };
501
623
  }
502
624
  async function addMochiToNextConfig(configPath) {
503
- const mod = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
625
+ const mod = parseModule(await fs.readFile(configPath, "utf-8"));
504
626
  mod.imports.$prepend({
505
627
  from: "@mochi-css/next",
506
628
  imported: "withMochi",
507
629
  local: "withMochi"
508
630
  });
509
631
  wrapExportDefault(mod, configPath);
510
- const { code } = (0, magicast.generateCode)(mod);
511
- await fs_promises.default.writeFile(configPath, code);
632
+ const { code } = generateCode(mod);
633
+ await fs.writeFile(configPath, code);
512
634
  }
513
635
  const nextModule = {
514
636
  id: "next",
@@ -521,22 +643,22 @@ const nextModule = {
521
643
  else if (existingConfig) configPath = existingConfig;
522
644
  else if (ctx.nonInteractive) configPath = "next.config.ts";
523
645
  else {
524
- const selected = await __clack_prompts.text({
646
+ const selected = await p.text({
525
647
  message: "Path to Next.js config",
526
648
  placeholder: "next.config.ts",
527
649
  defaultValue: "next.config.ts"
528
650
  });
529
- if (__clack_prompts.isCancel(selected)) return;
651
+ if (p.isCancel(selected)) return;
530
652
  configPath = selected;
531
653
  }
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");
654
+ if (!fsExtra.existsSync(configPath)) {
655
+ await fs.writeFile(configPath, defaultNextConfig);
656
+ p.log.success("Created next config with mochi");
535
657
  } else {
536
658
  await addMochiToNextConfig(configPath);
537
- __clack_prompts.log.success("Added withMochi() to next config");
659
+ p.log.success("Added withMochi() to next config");
538
660
  }
539
- ctx.requirePackage("@mochi-css/next");
661
+ ctx.requirePackage(mochiPackage("@mochi-css/next"));
540
662
  }
541
663
  };
542
664
 
@@ -546,11 +668,13 @@ const nextjsPreset = {
546
668
  id: "nextjs",
547
669
  name: "Next.js",
548
670
  setup(runner) {
549
- runner.register(createPostcssModule({
550
- outDir: ".mochi",
551
- auto: true
671
+ runner.register(createMochiConfigModule({
672
+ styledId: true,
673
+ tmpDir: ".mochi"
552
674
  }));
675
+ runner.register(createPostcssModule({ auto: true }));
553
676
  runner.register(nextModule);
677
+ runner.register(createUiFrameworkModule({ auto: true }));
554
678
  }
555
679
  };
556
680
 
@@ -564,30 +688,30 @@ const presets = {
564
688
 
565
689
  //#endregion
566
690
  //#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([
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([
568
692
  "vite",
569
693
  "nextjs",
570
694
  "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..."));
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..."));
573
697
  try {
574
698
  const runner = new ModuleRunner();
575
699
  const nonInteractive = options.interactive === false;
576
700
  let presetId = options.preset;
577
701
  if (presetId === void 0) {
578
702
  if (nonInteractive) {
579
- __clack_prompts.outro(picocolors.default.red("Cancelled"));
703
+ p.outro(pc.red("Cancelled"));
580
704
  return;
581
705
  }
582
- const selected = await __clack_prompts.select({
706
+ const selected = await p.select({
583
707
  message: "Which framework are you using?",
584
708
  options: Object.values(presets).map((preset$1) => ({
585
709
  value: preset$1.id,
586
710
  label: preset$1.name
587
711
  }))
588
712
  });
589
- if (__clack_prompts.isCancel(selected)) {
590
- __clack_prompts.outro(picocolors.default.red("Cancelled"));
713
+ if (p.isCancel(selected)) {
714
+ p.outro(pc.red("Cancelled"));
591
715
  return;
592
716
  }
593
717
  presetId = selected;
@@ -600,16 +724,18 @@ commander.program.name("tsuki").description("Add mochi-css to your project").ver
600
724
  else if (presetId === "nextjs") moduleOptions.postcss = true;
601
725
  if (options.vite !== void 0) moduleOptions.vite = options.vite;
602
726
  if (options.next !== void 0) moduleOptions.next = options.next;
727
+ if (options.framework !== void 0) moduleOptions.framework = options.framework;
603
728
  await runner.run({
604
729
  nonInteractive,
605
730
  autoInstall: options.install ?? false,
606
731
  moduleOptions
607
732
  });
608
- __clack_prompts.outro(picocolors.default.green("Done!"));
733
+ p.outro(pc.green("Done!"));
609
734
  } catch (e) {
610
- if (e instanceof Error) __clack_prompts.outro(picocolors.default.red(e.message));
735
+ if (e instanceof Error) p.outro(pc.red(e.message));
611
736
  }
612
737
  });
613
- commander.program.parse();
738
+ program.parse();
614
739
 
615
- //#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": "2.1.0",
4
+ "version": "3.0.0",
5
5
  "license": "MIT",
6
+ "type": "module",
6
7
  "bin": "./dist/index.js",
7
8
  "files": [
8
9
  "/dist"
@@ -17,27 +18,21 @@
17
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",