@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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +317 -161
  3. 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
- //#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
  });
@@ -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 (0, package_manager_detector.detect)({ strategies: [
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
- const confirmed = await __clack_prompts.confirm({ message: `Install the following packages: ${packageList}?` });
121
- if (__clack_prompts.isCancel(confirmed) || !confirmed) {
122
- __clack_prompts.log.info("Skipping package installation");
123
- return;
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 = (0, package_manager_detector.resolveCommand)(agent, "add", [devFlag, ...devPackages]);
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 = (0, package_manager_detector.resolveCommand)(agent, "add", prodPackages);
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$1) {
143
- this.modules.push(module$1);
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$1 of this.modules) await module$1.run(ctx);
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) => fs_extra.default.existsSync(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.js";
222
- const configPath = await __clack_prompts.text({
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 (__clack_prompts.isCancel(configPath)) return false;
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 = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
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 } = (0, magicast.generateCode)(mod);
270
- await fs_promises.default.writeFile(configPath, code$1);
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 } = (0, magicast.generateCode)(mod);
279
- await fs_promises.default.writeFile(configPath, code);
276
+ const { code } = generateCode(mod);
277
+ await fs.writeFile(configPath, code);
280
278
  }
281
279
  async function addToConfig(configPath, pluginOptions = {}) {
282
- if (!fs_extra.default.existsSync(configPath)) {
283
- await fs_extra.default.writeFile("postcss.config.mts", defaultPostcssConfig(pluginOptions));
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.default.basename(configPath) === ".postcssrc") {
288
- const config = await fs_extra.default.readJson(configPath);
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 fs_extra.default.writeJson(configPath, config, { spaces: 2 });
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
- if (options.auto) configPath = findPostcssConfig() ?? "postcss.config.mts";
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 __clack_prompts.confirm({ message: "Do you use PostCSS?" });
310
- 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;
311
310
  const selected = await askForPath();
312
311
  if (selected === false) return;
313
312
  configPath = selected;
314
313
  }
315
- await addToConfig(configPath, pluginOptions);
316
- __clack_prompts.log.success("Added mochi plugin to the postcss config");
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
- __clack_prompts.log.warn("Library preset is not fully supported yet.");
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) => fs_extra.default.existsSync(name));
342
+ return viteConfigNames.find((name) => fsExtra.existsSync(name));
344
343
  }
345
- const defaultViteConfig = `import { defineConfig } from "vite"
346
- import { mochiCss } from "@mochi-css/vite"
344
+ const defaultViteConfig = dedent`
345
+ import { defineConfig } from "vite"
346
+ import { mochiCss } from "@mochi-css/vite"
347
347
 
348
- export default defineConfig({
349
- plugins: [mochiCss()],
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 = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
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 } = (0, magicast.generateCode)(mod);
424
- await fs_promises.default.writeFile(configPath, code);
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 (!existingConfig) {
433
- const selected = await __clack_prompts.text({
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 (__clack_prompts.isCancel(selected)) return;
419
+ if (p.isCancel(selected)) return;
439
420
  configPath = selected;
440
- } else configPath = existingConfig;
441
- if (!fs_extra.default.existsSync(configPath)) {
442
- await fs_promises.default.writeFile(configPath, defaultViteConfig);
443
- __clack_prompts.log.success("Created vite config with mochi plugin");
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
- __clack_prompts.log.success("Added mochiCss() to vite config");
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(createPostcssModule({ outDir: ".mochi" }));
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) => fs_extra.default.existsSync(name));
605
+ return nextConfigNames.find((name) => fsExtra.existsSync(name));
473
606
  }
474
- const defaultNextConfig = `import { withMochi } from "@mochi-css/next"
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 = (0, magicast.parseModule)(await fs_promises.default.readFile(configPath, "utf-8"));
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 } = (0, magicast.generateCode)(mod);
499
- await fs_promises.default.writeFile(configPath, code);
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 (!existingConfig) {
508
- const selected = await __clack_prompts.text({
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 (__clack_prompts.isCancel(selected)) return;
651
+ if (p.isCancel(selected)) return;
514
652
  configPath = selected;
515
- } else configPath = existingConfig;
516
- if (!fs_extra.default.existsSync(configPath)) {
517
- await fs_promises.default.writeFile(configPath, defaultNextConfig);
518
- __clack_prompts.log.success("Created next config with mochi");
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
- __clack_prompts.log.success("Added withMochi() to next config");
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(createPostcssModule({
534
- outDir: ".mochi",
535
- auto: true
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
- commander.program.name("tsuki").description("Add mochi-css to your project").version("2.0.1").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([
552
692
  "vite",
553
693
  "nextjs",
554
694
  "lib"
555
- ])).action(async (options) => {
556
- __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..."));
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
- const selected = await __clack_prompts.select({
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 (__clack_prompts.isCancel(selected)) {
569
- __clack_prompts.outro(picocolors.default.red("Cancelled"));
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
- await runner.run();
578
- __clack_prompts.outro(picocolors.default.green("Done!"));
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) __clack_prompts.outro(picocolors.default.red(e.message));
735
+ if (e instanceof Error) p.outro(pc.red(e.message));
581
736
  }
582
737
  });
583
- commander.program.parse();
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": "2.0.1",
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
- "@eslint/js": "^9.18.0",
22
- "@gamedev-sensei/ts-config": "^2.1.0",
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",