From d53c366f0d7b2e902ed17063f309771a9865c1c2 Mon Sep 17 00:00:00 2001 From: otnc Date: Wed, 24 Jun 2026 03:35:37 +0900 Subject: [PATCH 1/4] feat: support alternative package managers (pnpm, yarn, bun, aube, nub, vlt, bower) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add src/lib/package-manager.coffee with detectPackageManager(), getInstallCommand(), getInitCommand(), getRunCommand(), getInstallFrozenCommand(), getCiSetupStep() - Detection order: coffee.config milkee.packageManager → package.json packageManager field → lock files (pnpm-lock.yaml, yarn.lock, bun.lockb, vlt-lock.json, bower.json) - Replace all hardcoded npm commands in setup, plugin, compile, checks with dynamic equivalents - Inject detected packageManager into generated coffee.config.cjs - Replace hardcoded npm commands in template files (README.md, publish.yml) with placeholders resolved at generation time --- dist/commands/compile.js | 19 ++-- dist/commands/plugin.js | 54 +++++++--- dist/commands/setup.js | 39 ++++--- dist/lib/checks.js | 16 ++- dist/lib/package-manager.js | 184 +++++++++++++++++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- src/commands/compile.coffee | 17 +-- src/commands/plugin.coffee | 39 ++++--- src/commands/setup.coffee | 28 +++-- src/lib/checks.coffee | 10 +- src/lib/package-manager.coffee | 130 +++++++++++++++++++++++ temp/plugin/coffee.config.cjs | 3 + temp/plugin/publish.yml | 8 +- temp/setup/README.md | 2 +- temp/setup/coffee.config.cjs | 3 + 16 files changed, 477 insertions(+), 81 deletions(-) create mode 100644 dist/lib/package-manager.js create mode 100644 src/lib/package-manager.coffee diff --git a/dist/commands/compile.js b/dist/commands/compile.js index 66389d6..ceeae66 100644 --- a/dist/commands/compile.js +++ b/dist/commands/compile.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 2.7.0 -var CONFIG_FILE, CONFIG_PATH, CWD, checkCoffee, checkLatest, checkVersion, clearBackups, compile, confirmContinue, consola, crypto, exec, executeCopy, executeRefresh, fs, path, restoreBackups, runPlugins, spawn; +var CONFIG_FILE, CONFIG_PATH, CWD, checkCoffee, checkLatest, checkVersion, clearBackups, compile, confirmContinue, consola, crypto, detectPackageManager, exec, executeCopy, executeRefresh, fs, getInstallCommand, path, restoreBackups, runPlugins, spawn; fs = require('fs'); @@ -17,6 +17,8 @@ consola = require('consola'); ({runPlugins} = require('../lib/plugins')); +({detectPackageManager, getInstallCommand} = require('../lib/package-manager')); + confirmContinue = require('../options/confirm'); ({executeRefresh, restoreBackups, clearBackups} = require('../options/refresh')); @@ -24,10 +26,13 @@ confirmContinue = require('../options/confirm'); executeCopy = require('../options/copy'); // async -checkVersion = async function() { - var action, cl, installCmd; +checkVersion = async function(config) { + var action, cl, globalCmd, installCmd, localCmd, pm; cl = (await checkLatest()); if (cl) { + pm = detectPackageManager(CWD, config); + globalCmd = getInstallCommand(pm, 'milkee@latest', false, true); + localCmd = getInstallCommand(pm, 'milkee@latest', true, false); action = (await consola.prompt('Do you want to update now?', { type: 'select', options: [ @@ -39,17 +44,17 @@ checkVersion = async function() { { label: 'Yes (Global)', value: 'global', - hint: 'npm i -g milkee@latest' + hint: globalCmd }, { label: 'Yes (Local)', value: 'local', - hint: 'npm i -D milkee@latest' + hint: localCmd } ] })); if (action && action !== 'skip') { - installCmd = action === 'global' ? 'npm i -g milkee@latest' : 'npm i -D milkee@latest'; + installCmd = action === 'global' ? globalCmd : localCmd; consola.start('Updating milkee...'); await new Promise(function(resolve) { var cp; @@ -88,7 +93,7 @@ compile = async function() { milkee = config.milkee || {}; milkeeOptions = config.milkee.options || {}; if (!milkeeOptions.ignoreUpdate) { - await checkVersion(); + await checkVersion(config); } execCommandParts = ['coffee']; if (options.join) { diff --git a/dist/commands/plugin.js b/dist/commands/plugin.js index d97f9de..25c6f95 100644 --- a/dist/commands/plugin.js +++ b/dist/commands/plugin.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 2.7.0 -var CONFIG_FILE, CWD, DOCS, DOCS_DIR, PLUGIN_KEYWORDS, TEMPLATES, TEMPLATE_DIR, confirmContinue, consola, copyDocs, copyTemplate, ensureDir, execSync, fs, generateReadme, initPackageJson, path, plugin, updatePackageJson, +var CONFIG_FILE, CWD, DOCS, DOCS_DIR, PLUGIN_KEYWORDS, TEMPLATES, TEMPLATE_DIR, confirmContinue, consola, copyDocs, copyTemplate, detectPackageManager, ensureDir, execSync, fs, generateReadme, getCiSetupStep, getInitCommand, getInstallCommand, getInstallFrozenCommand, getRunCommand, initPackageJson, path, plugin, updatePackageJson, indexOf = [].indexOf; fs = require('fs'); @@ -12,6 +12,8 @@ consola = require('consola'); ({CWD, CONFIG_FILE} = require('../lib/constants')); +({detectPackageManager, getInstallCommand, getInitCommand, getRunCommand, getInstallFrozenCommand, getCiSetupStep} = require('../lib/package-manager')); + confirmContinue = require('../options/confirm'); TEMPLATE_DIR = path.join(__dirname, '..', '..', 'temp', 'plugin'); @@ -68,8 +70,8 @@ ensureDir = function(filePath) { }; // Copy template file -copyTemplate = function(src, dest) { - var content, destPath, srcPath; +copyTemplate = function(src, dest, replacements = {}) { + var content, destPath, key, srcPath, value; srcPath = path.join(TEMPLATE_DIR, src); destPath = path.join(CWD, dest); if (!fs.existsSync(srcPath)) { @@ -78,6 +80,10 @@ copyTemplate = function(src, dest) { } ensureDir(destPath); content = fs.readFileSync(srcPath, 'utf-8'); + for (key in replacements) { + value = replacements[key]; + content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value); + } fs.writeFileSync(destPath, content); consola.success(`Created \`${dest}\``); return true; @@ -138,13 +144,13 @@ updatePackageJson = function() { }; // Initialize package.json if not exists -initPackageJson = function() { +initPackageJson = function(pm) { var error, pkgPath; pkgPath = path.join(CWD, 'package.json'); if (!fs.existsSync(pkgPath)) { consola.start('Initializing package.json...'); try { - execSync('npm init', { + execSync(getInitCommand(pm), { cwd: CWD, stdio: 'inherit' }); @@ -159,7 +165,7 @@ initPackageJson = function() { }; // Generate README.md -generateReadme = function() { +generateReadme = function(pm) { var description, error, name, pkg, pkgPath, readme, readmePath, templatePath; pkgPath = path.join(CWD, 'package.json'); readmePath = path.join(CWD, 'README.md'); @@ -187,14 +193,15 @@ generateReadme = function() { // Main plugin setup function plugin = async function() { - var confirmed, destPath, doc, error, i, j, k, l, len, len1, len2, len3, overwrite, pkgPath, readmePath, template; + var confirmed, destPath, doc, error, i, j, k, l, len, len1, len2, len3, overwrite, pkgPath, pm, readmePath, replacements, template; + pm = detectPackageManager(CWD); consola.box('Milkee Plugin Setup'); consola.info('This will set up your project as a Milkee plugin.'); consola.info(''); consola.info('The following actions will be performed:'); pkgPath = path.join(CWD, 'package.json'); if (!fs.existsSync(pkgPath)) { - consola.info(' 0. Initialize package.json (npm init)'); + consola.info(` 0. Initialize package.json (${getInitCommand(pm)})`); } consola.info(' 1. Install dependencies (consola, coffeescript, milkee)'); consola.info(' 2. Create template files:'); @@ -217,17 +224,17 @@ plugin = async function() { } consola.info(''); // Initialize package.json if not exists - if (!initPackageJson()) { + if (!initPackageJson(pm)) { return; } try { // Install dependencies consola.start('Installing dependencies...'); - execSync('npm install consola', { + execSync(getInstallCommand(pm, 'consola'), { cwd: CWD, stdio: 'inherit' }); - execSync('npm install -D coffeescript milkee', { + execSync(getInstallCommand(pm, 'coffeescript milkee', true), { cwd: CWD, stdio: 'inherit' }); @@ -252,7 +259,24 @@ plugin = async function() { continue; } } - copyTemplate(template.src, template.dest); + replacements = (function() { + switch (template.src) { + case 'coffee.config.cjs': + return { + packageManager: pm + }; + case 'publish.yml': + return { + setupPmStep: getCiSetupStep(pm), + installFrozen: getInstallFrozenCommand(pm), + runBuild: getRunCommand(pm, 'build'), + runTest: getRunCommand(pm, 'test') + }; + default: + return {}; + } + })(); + copyTemplate(template.src, template.dest, replacements); } consola.info(''); // Copy docs @@ -283,19 +307,19 @@ plugin = async function() { type: 'confirm' })); if (overwrite) { - generateReadme(); + generateReadme(pm); } else { consola.info('Skipped `README.md`'); } } else { - generateReadme(); + generateReadme(pm); } consola.info(''); consola.success('Milkee plugin setup complete!'); consola.info(''); consola.info('Next steps:'); consola.info(' 1. Edit `src/main.coffee` to implement your plugin'); - consola.info(' 2. Run `npm run build` to compile'); + consola.info(` 2. Run \`${getRunCommand(pm, 'build')}\` to compile`); return consola.info(' 3. Test your plugin locally'); }; diff --git a/dist/commands/setup.js b/dist/commands/setup.js index 6ac37f8..78b50c4 100644 --- a/dist/commands/setup.js +++ b/dist/commands/setup.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 2.7.0 -var CONFIG_FILE, CWD, SETUP_KEYWORDS, TEMPLATES, TEMPLATE_DIR, confirmContinue, consola, copyTemplate, ensureDir, execSync, fs, generateReadme, initPackageJson, path, setup, updatePackageJson, +var CONFIG_FILE, CWD, SETUP_KEYWORDS, TEMPLATES, TEMPLATE_DIR, confirmContinue, consola, copyTemplate, detectPackageManager, ensureDir, execSync, fs, generateReadme, getInitCommand, getInstallCommand, getRunCommand, initPackageJson, path, setup, updatePackageJson, indexOf = [].indexOf; fs = require('fs'); @@ -12,6 +12,8 @@ consola = require('consola'); ({CWD, CONFIG_FILE} = require('../lib/constants')); +({detectPackageManager, getInstallCommand, getInitCommand, getRunCommand} = require('../lib/package-manager')); + confirmContinue = require('../options/confirm'); TEMPLATE_DIR = path.join(__dirname, '..', '..', 'temp', 'setup'); @@ -47,8 +49,8 @@ ensureDir = function(filePath) { }; // Copy template file -copyTemplate = function(src, dest) { - var content, destPath, srcPath; +copyTemplate = function(src, dest, replacements = {}) { + var content, destPath, key, srcPath, value; srcPath = path.join(TEMPLATE_DIR, src); destPath = path.join(CWD, dest); if (!fs.existsSync(srcPath)) { @@ -57,6 +59,10 @@ copyTemplate = function(src, dest) { } ensureDir(destPath); content = fs.readFileSync(srcPath, 'utf-8'); + for (key in replacements) { + value = replacements[key]; + content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value); + } fs.writeFileSync(destPath, content); consola.success(`Created \`${dest}\``); return true; @@ -101,13 +107,13 @@ updatePackageJson = function() { }; // Initialize package.json if not exists -initPackageJson = function() { +initPackageJson = function(pm) { var error, pkgPath; pkgPath = path.join(CWD, 'package.json'); if (!fs.existsSync(pkgPath)) { consola.start('Initializing package.json...'); try { - execSync('npm init', { + execSync(getInitCommand(pm), { cwd: CWD, stdio: 'inherit' }); @@ -122,7 +128,7 @@ initPackageJson = function() { }; // Generate README.md -generateReadme = function() { +generateReadme = function(pm) { var description, error, name, pkg, pkgPath, readme, readmePath, templatePath; pkgPath = path.join(CWD, 'package.json'); readmePath = path.join(CWD, 'README.md'); @@ -138,6 +144,7 @@ generateReadme = function() { readme = fs.readFileSync(templatePath, 'utf-8'); readme = readme.replace(/\{\{name\}\}/g, name); readme = readme.replace(/\{\{description\}\}/g, description); + readme = readme.replace(/\{\{runBuild\}\}/g, getRunCommand(pm, 'build')); fs.writeFileSync(readmePath, readme); consola.success('Created `README.md`'); return true; @@ -150,14 +157,15 @@ generateReadme = function() { // Main setup function setup = async function() { - var confirmed, destPath, error, i, j, len, len1, overwrite, pkgPath, readmePath, template; + var confirmed, destPath, error, i, j, len, len1, overwrite, pkgPath, pm, readmePath, replacements, template; + pm = detectPackageManager(CWD); consola.box('Milkee Project Setup'); consola.info('This will set up your project as a Milkee project.'); consola.info(''); consola.info('The following actions will be performed:'); pkgPath = path.join(CWD, 'package.json'); if (!fs.existsSync(pkgPath)) { - consola.info(' 0. Initialize package.json (npm init)'); + consola.info(` 0. Initialize package.json (${getInitCommand(pm)})`); } consola.info(' 1. Install dependencies (coffeescript, milkee)'); consola.info(' 2. Create template files:'); @@ -179,13 +187,13 @@ setup = async function() { } consola.info(''); // Initialize package.json if not exists - if (!initPackageJson()) { + if (!initPackageJson(pm)) { return; } try { // Install dependencies consola.start('Installing dependencies...'); - execSync('npm install -D coffeescript milkee', { + execSync(getInstallCommand(pm, 'coffeescript milkee', true), { cwd: CWD, stdio: 'inherit' }); @@ -210,7 +218,10 @@ setup = async function() { continue; } } - copyTemplate(template.src, template.dest); + replacements = template.src === 'coffee.config.cjs' ? { + packageManager: pm + } : {}; + copyTemplate(template.src, template.dest, replacements); } consola.info(''); // Update package.json @@ -224,19 +235,19 @@ setup = async function() { type: 'confirm' })); if (overwrite) { - generateReadme(); + generateReadme(pm); } else { consola.info('Skipped `README.md`'); } } else { - generateReadme(); + generateReadme(pm); } consola.info(''); consola.success('Milkee project setup complete!'); consola.info(''); consola.info('Next steps:'); consola.info(' 1. Edit `src/main.coffee` to implement your code'); - consola.info(' 2. Run `npm run build` to compile'); + consola.info(` 2. Run \`${getRunCommand(pm, 'build')}\` to compile`); return consola.info(' 3. Test your project locally'); }; diff --git a/dist/lib/checks.js b/dist/lib/checks.js index 89b7a05..314d23c 100644 --- a/dist/lib/checks.js +++ b/dist/lib/checks.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 2.7.0 -var CWD, checkCoffee, checkLatest, consola, exec, fs, isPackageLatest, path, pkg; +var CWD, checkCoffee, checkLatest, consola, detectPackageManager, exec, fs, getInstallCommand, isPackageLatest, path, pkg; fs = require('fs'); @@ -13,15 +13,20 @@ consola = require('consola'); ({pkg, CWD} = require('./constants')); +({detectPackageManager, getInstallCommand} = require('./package-manager')); + // async checkLatest = async function() { - var res; + var globalCmd, localCmd, pm, res; try { res = (await isPackageLatest(pkg)); if (res.success && !res.isLatest) { + pm = detectPackageManager(CWD); + globalCmd = getInstallCommand(pm, 'milkee@latest', false, true); + localCmd = getInstallCommand(pm, 'milkee@latest', true, false); consola.box({ title: 'A new version is now available!', - message: `${res.currentVersion} --> \`${res.latestVersion}\`\n\n# global installation\n\`npm i -g milkee@latest\`\n# or local installation\n\`npm i -D milkee@latest\`` + message: `${res.currentVersion} --> \`${res.latestVersion}\`\n\n# global installation\n\`${globalCmd}\`\n# or local installation\n\`${localCmd}\`` }); return true; } else { @@ -48,9 +53,12 @@ checkCoffee = function() { } } return exec('coffee --version', function(error) { + var installCmd, pm; if (error) { + pm = detectPackageManager(CWD); + installCmd = getInstallCommand(pm, 'coffeescript', true); consola.warn('CoffeeScript is not found in local dependencies (`dependencies`, `devDependencies`) or globally.'); - return consola.info('Please install it via `npm install --save-dev coffeescript` to continue.'); + return consola.info(`Please install it via \`${installCmd}\` to continue.`); } }); }; diff --git a/dist/lib/package-manager.js b/dist/lib/package-manager.js new file mode 100644 index 0000000..e0e3561 --- /dev/null +++ b/dist/lib/package-manager.js @@ -0,0 +1,184 @@ +// Generated by CoffeeScript 2.7.0 +var SUPPORTED, detectPackageManager, fs, getCiSetupStep, getInitCommand, getInstallCommand, getInstallFrozenCommand, getRunCommand, path, + indexOf = [].indexOf; + +fs = require('fs'); + +path = require('path'); + +SUPPORTED = ['npm', 'pnpm', 'yarn', 'bun', 'aube', 'nub', 'vlt', 'bower']; + +detectPackageManager = function(cwd, config = null) { + var name, pkg, pkgPath, pm, ref; + if (config != null ? (ref = config.milkee) != null ? ref.packageManager : void 0 : void 0) { + pm = config.milkee.packageManager; + if (indexOf.call(SUPPORTED, pm) >= 0) { + return pm; + } + } + pkgPath = path.join(cwd, 'package.json'); + if (fs.existsSync(pkgPath)) { + try { + pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + if (pkg.packageManager) { + name = pkg.packageManager.split('@')[0].trim(); + if (indexOf.call(SUPPORTED, name) >= 0) { + return name; + } + } + } catch (error) { + + } + } + if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) { + return 'pnpm'; + } + if (fs.existsSync(path.join(cwd, 'yarn.lock'))) { + return 'yarn'; + } + if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock'))) { + return 'bun'; + } + if (fs.existsSync(path.join(cwd, 'vlt-lock.json'))) { + return 'vlt'; + } + if (fs.existsSync(path.join(cwd, 'bower.json'))) { + return 'bower'; + } + return 'npm'; +}; + +getInstallCommand = function(pm, packages, dev = false, global = false) { + var flag; + switch (pm) { + case 'pnpm': + flag = global ? ' -g' : dev ? ' -D' : ''; + return `pnpm add${flag} ${packages}`; + case 'yarn': + if (global) { + return `yarn global add ${packages}`; + } else { + flag = dev ? ' -D' : ''; + return `yarn add${flag} ${packages}`; + } + break; + case 'bun': + flag = global ? ' -g' : dev ? ' -D' : ''; + return `bun add${flag} ${packages}`; + case 'aube': + flag = global ? ' -g' : dev ? ' -D' : ''; + return `aube add${flag} ${packages}`; + case 'nub': + if (global) { + return `npm i -g ${packages}`; + } else { + flag = dev ? ' -D' : ''; + return `nub add${flag} ${packages}`; + } + break; + case 'vlt': + if (global) { + return `npm i -g ${packages}`; + } else { + flag = dev ? ' --save-dev' : ''; + return `vlt install${flag} ${packages}`; + } + break; + case 'bower': + if (global) { + return `npm i -g ${packages}`; + } else { + flag = dev ? ' --save-dev' : ' --save'; + return `bower install ${packages}${flag}`; + } + break; + default: + if (global) { + return `npm i -g ${packages}`; + } else { + flag = dev ? ' -D' : ''; + return `npm install${flag} ${packages}`; + } + } +}; + +getInitCommand = function(pm) { + switch (pm) { + case 'pnpm': + return 'pnpm init'; + case 'yarn': + return 'yarn init'; + case 'bun': + return 'bun init'; + case 'aube': + return 'aube init'; + case 'nub': + return 'nub install'; + case 'vlt': + return 'vlt init'; + case 'bower': + return 'bower init'; + default: + return 'npm init'; + } +}; + +getRunCommand = function(pm, script) { + switch (pm) { + case 'pnpm': + return `pnpm ${script}`; + case 'yarn': + return `yarn ${script}`; + case 'bun': + return `bun run ${script}`; + case 'aube': + return `aube run ${script}`; + case 'nub': + return `nub run ${script}`; + case 'vlt': + return `vlt run ${script}`; + case 'bower': + return `npm run ${script}`; + default: + return `npm run ${script}`; + } +}; + +getInstallFrozenCommand = function(pm) { + switch (pm) { + case 'pnpm': + return 'pnpm install --frozen-lockfile'; + case 'yarn': + return 'yarn install --frozen-lockfile'; + case 'bun': + return 'bun install'; + case 'aube': + return 'aube install --frozen-lockfile'; + case 'nub': + return 'nub ci'; + case 'vlt': + return 'vlt install --frozen-lockfile'; + default: + return 'npm ci'; + } +}; + +// Returns an extra YAML step block (with leading newline) for CI setup, or empty string. +getCiSetupStep = function(pm) { + switch (pm) { + case 'pnpm': + return ` +- name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false`; + case 'bun': + return ` +- name: Setup Bun + uses: oven-sh/setup-bun@v2`; + default: + return ''; + } +}; + +module.exports = {SUPPORTED, detectPackageManager, getInstallCommand, getInitCommand, getRunCommand, getInstallFrozenCommand, getCiSetupStep}; diff --git a/package-lock.json b/package-lock.json index cb7ec6d..81875f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "milkee", - "version": "3.3.1", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "milkee", - "version": "3.3.1", + "version": "4.0.0", "license": "MIT", "dependencies": { "@milkee/d": "^0.3.0", diff --git a/package.json b/package.json index d4b6233..ffa552b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "milkee", - "version": "3.3.1", + "version": "4.0.0", "description": "A simple CoffeeScript build tool with coffee.config.cjs ☕", "main": "dist/main.js", "bin": { diff --git a/src/commands/compile.coffee b/src/commands/compile.coffee index 1d5f508..83dfb32 100644 --- a/src/commands/compile.coffee +++ b/src/commands/compile.coffee @@ -7,29 +7,30 @@ consola = require 'consola' { CWD, CONFIG_PATH, CONFIG_FILE } = require '../lib/constants' { checkLatest, checkCoffee } = require '../lib/checks' { runPlugins } = require '../lib/plugins' +{ detectPackageManager, getInstallCommand } = require '../lib/package-manager' confirmContinue = require '../options/confirm' { executeRefresh, restoreBackups, clearBackups } = require '../options/refresh' executeCopy = require '../options/copy' # async -checkVersion = -> +checkVersion = (config) -> cl = await checkLatest() if cl + pm = detectPackageManager CWD, config + globalCmd = getInstallCommand pm, 'milkee@latest', false, true + localCmd = getInstallCommand pm, 'milkee@latest', true, false action = await consola.prompt 'Do you want to update now?', type: 'select' options: [ label: 'No (Skip)', value: 'skip', hint: 'Start compiling directly' , - label: 'Yes (Global)', value: 'global', hint: 'npm i -g milkee@latest' + label: 'Yes (Global)', value: 'global', hint: globalCmd , - label: 'Yes (Local)', value: 'local', hint: 'npm i -D milkee@latest' + label: 'Yes (Local)', value: 'local', hint: localCmd ] if action and action isnt 'skip' - installCmd = if action is 'global' - 'npm i -g milkee@latest' - else - 'npm i -D milkee@latest' + installCmd = if action is 'global' then globalCmd else localCmd consola.start 'Updating milkee...' await new Promise (resolve) -> cp = spawn installCmd, shell: true, stdio: 'inherit' @@ -64,7 +65,7 @@ compile = -> milkeeOptions = config.milkee.options or {} unless milkeeOptions.ignoreUpdate - await checkVersion() + await checkVersion(config) execCommandParts = ['coffee'] if options.join diff --git a/src/commands/plugin.coffee b/src/commands/plugin.coffee index 29f5d0d..cbfe17a 100644 --- a/src/commands/plugin.coffee +++ b/src/commands/plugin.coffee @@ -4,6 +4,7 @@ path = require 'path' consola = require 'consola' { CWD, CONFIG_FILE } = require '../lib/constants' +{ detectPackageManager, getInstallCommand, getInitCommand, getRunCommand, getInstallFrozenCommand, getCiSetupStep } = require '../lib/package-manager' confirmContinue = require '../options/confirm' TEMPLATE_DIR = path.join __dirname, '..', '..', 'temp', 'plugin' @@ -36,7 +37,7 @@ ensureDir = (filePath) -> fs.mkdirSync dir, recursive: true # Copy template file -copyTemplate = (src, dest) -> +copyTemplate = (src, dest, replacements = {}) -> srcPath = path.join TEMPLATE_DIR, src destPath = path.join CWD, dest @@ -46,6 +47,8 @@ copyTemplate = (src, dest) -> ensureDir destPath content = fs.readFileSync srcPath, 'utf-8' + for key, value of replacements + content = content.replace new RegExp("\\{\\{#{key}\\}\\}", 'g'), value fs.writeFileSync destPath, content consola.success "Created `#{dest}`" return true @@ -102,13 +105,13 @@ updatePackageJson = -> return false # Initialize package.json if not exists -initPackageJson = -> +initPackageJson = (pm) -> pkgPath = path.join CWD, 'package.json' unless fs.existsSync pkgPath consola.start 'Initializing package.json...' try - execSync 'npm init', cwd: CWD, stdio: 'inherit' + execSync getInitCommand(pm), cwd: CWD, stdio: 'inherit' consola.success 'Created `package.json`' catch error consola.error 'Failed to create package.json:', error @@ -116,7 +119,7 @@ initPackageJson = -> return true # Generate README.md -generateReadme = -> +generateReadme = (pm) -> pkgPath = path.join CWD, 'package.json' readmePath = path.join CWD, 'README.md' templatePath = path.join TEMPLATE_DIR, 'README.md' @@ -143,13 +146,14 @@ generateReadme = -> # Main plugin setup function plugin = -> + pm = detectPackageManager CWD consola.box 'Milkee Plugin Setup' consola.info 'This will set up your project as a Milkee plugin.' consola.info '' consola.info 'The following actions will be performed:' pkgPath = path.join CWD, 'package.json' unless fs.existsSync pkgPath - consola.info ' 0. Initialize package.json (npm init)' + consola.info " 0. Initialize package.json (#{getInitCommand pm})" consola.info ' 1. Install dependencies (consola, coffeescript, milkee)' consola.info ' 2. Create template files:' for template in TEMPLATES @@ -169,14 +173,14 @@ plugin = -> consola.info '' # Initialize package.json if not exists - unless initPackageJson() + unless initPackageJson(pm) return # Install dependencies try consola.start 'Installing dependencies...' - execSync 'npm install consola', cwd: CWD, stdio: 'inherit' - execSync 'npm install -D coffeescript milkee', cwd: CWD, stdio: 'inherit' + execSync getInstallCommand(pm, 'consola'), cwd: CWD, stdio: 'inherit' + execSync getInstallCommand(pm, 'coffeescript milkee', true), cwd: CWD, stdio: 'inherit' consola.success 'Dependencies installed!' catch error consola.error 'Failed to install dependencies:', error @@ -195,7 +199,18 @@ plugin = -> unless overwrite consola.info "Skipped `#{template.dest}`" continue - copyTemplate template.src, template.dest + replacements = switch template.src + when 'coffee.config.cjs' + { packageManager: pm } + when 'publish.yml' + { + setupPmStep: getCiSetupStep pm + installFrozen: getInstallFrozenCommand pm + runBuild: getRunCommand pm, 'build' + runTest: getRunCommand pm, 'test' + } + else {} + copyTemplate template.src, template.dest, replacements consola.info '' @@ -227,18 +242,18 @@ plugin = -> await consola.prompt 'README.md already exists. Overwrite?', type: 'confirm' if overwrite - generateReadme() + generateReadme(pm) else consola.info 'Skipped `README.md`' else - generateReadme() + generateReadme(pm) consola.info '' consola.success 'Milkee plugin setup complete!' consola.info '' consola.info 'Next steps:' consola.info ' 1. Edit `src/main.coffee` to implement your plugin' - consola.info ' 2. Run `npm run build` to compile' + consola.info " 2. Run `#{getRunCommand pm, 'build'}` to compile" consola.info ' 3. Test your plugin locally' module.exports = plugin diff --git a/src/commands/setup.coffee b/src/commands/setup.coffee index 55a3a77..a3ee841 100644 --- a/src/commands/setup.coffee +++ b/src/commands/setup.coffee @@ -4,6 +4,7 @@ path = require 'path' consola = require 'consola' { CWD, CONFIG_FILE } = require '../lib/constants' +{ detectPackageManager, getInstallCommand, getInitCommand, getRunCommand } = require '../lib/package-manager' confirmContinue = require '../options/confirm' TEMPLATE_DIR = path.join __dirname, '..', '..', 'temp', 'setup' @@ -25,7 +26,7 @@ ensureDir = (filePath) -> fs.mkdirSync dir, recursive: true # Copy template file -copyTemplate = (src, dest) -> +copyTemplate = (src, dest, replacements = {}) -> srcPath = path.join TEMPLATE_DIR, src destPath = path.join CWD, dest @@ -35,6 +36,8 @@ copyTemplate = (src, dest) -> ensureDir destPath content = fs.readFileSync srcPath, 'utf-8' + for key, value of replacements + content = content.replace new RegExp("\\{\\{#{key}\\}\\}", 'g'), value fs.writeFileSync destPath, content consola.success "Created `#{dest}`" return true @@ -70,13 +73,13 @@ updatePackageJson = -> return false # Initialize package.json if not exists -initPackageJson = -> +initPackageJson = (pm) -> pkgPath = path.join CWD, 'package.json' unless fs.existsSync pkgPath consola.start 'Initializing package.json...' try - execSync 'npm init', cwd: CWD, stdio: 'inherit' + execSync getInitCommand(pm), cwd: CWD, stdio: 'inherit' consola.success 'Created `package.json`' catch error consola.error 'Failed to create package.json:', error @@ -84,7 +87,7 @@ initPackageJson = -> return true # Generate README.md -generateReadme = -> +generateReadme = (pm) -> pkgPath = path.join CWD, 'package.json' readmePath = path.join CWD, 'README.md' templatePath = path.join TEMPLATE_DIR, 'README.md' @@ -101,6 +104,7 @@ generateReadme = -> readme = fs.readFileSync templatePath, 'utf-8' readme = readme.replace /\{\{name\}\}/g, name readme = readme.replace /\{\{description\}\}/g, description + readme = readme.replace /\{\{runBuild\}\}/g, getRunCommand pm, 'build' fs.writeFileSync readmePath, readme consola.success 'Created `README.md`' @@ -111,13 +115,14 @@ generateReadme = -> # Main setup function setup = -> + pm = detectPackageManager CWD consola.box 'Milkee Project Setup' consola.info 'This will set up your project as a Milkee project.' consola.info '' consola.info 'The following actions will be performed:' pkgPath = path.join CWD, 'package.json' unless fs.existsSync pkgPath - consola.info ' 0. Initialize package.json (npm init)' + consola.info " 0. Initialize package.json (#{getInitCommand pm})" consola.info ' 1. Install dependencies (coffeescript, milkee)' consola.info ' 2. Create template files:' for template in TEMPLATES @@ -137,13 +142,13 @@ setup = -> consola.info '' # Initialize package.json if not exists - unless initPackageJson() + unless initPackageJson(pm) return # Install dependencies try consola.start 'Installing dependencies...' - execSync 'npm install -D coffeescript milkee', cwd: CWD, stdio: 'inherit' + execSync getInstallCommand(pm, 'coffeescript milkee', true), cwd: CWD, stdio: 'inherit' consola.success 'Dependencies installed!' catch error consola.error 'Failed to install dependencies:', error @@ -162,7 +167,8 @@ setup = -> unless overwrite consola.info "Skipped `#{template.dest}`" continue - copyTemplate template.src, template.dest + replacements = if template.src is 'coffee.config.cjs' then { packageManager: pm } else {} + copyTemplate template.src, template.dest, replacements consola.info '' @@ -179,18 +185,18 @@ setup = -> await consola.prompt 'README.md already exists. Overwrite?', type: 'confirm' if overwrite - generateReadme() + generateReadme(pm) else consola.info 'Skipped `README.md`' else - generateReadme() + generateReadme(pm) consola.info '' consola.success 'Milkee project setup complete!' consola.info '' consola.info 'Next steps:' consola.info ' 1. Edit `src/main.coffee` to implement your code' - consola.info ' 2. Run `npm run build` to compile' + consola.info " 2. Run `#{getRunCommand pm, 'build'}` to compile" consola.info ' 3. Test your project locally' module.exports = setup diff --git a/src/lib/checks.coffee b/src/lib/checks.coffee index a670079..f04d3fb 100644 --- a/src/lib/checks.coffee +++ b/src/lib/checks.coffee @@ -5,17 +5,21 @@ consola = require 'consola' { isPackageLatest } = require 'is-package-latest' { pkg, CWD } = require './constants' +{ detectPackageManager, getInstallCommand } = require './package-manager' # async checkLatest = -> try res = await isPackageLatest pkg if res.success and not res.isLatest + pm = detectPackageManager CWD + globalCmd = getInstallCommand pm, 'milkee@latest', false, true + localCmd = getInstallCommand pm, 'milkee@latest', true, false consola.box title: 'A new version is now available!' message: "#{res.currentVersion} --> `#{ res.latestVersion - }`\n\n# global installation\n`npm i -g milkee@latest`\n# or local installation\n`npm i -D milkee@latest`" + }`\n\n# global installation\n`#{globalCmd}`\n# or local installation\n`#{localCmd}`" return true else return false @@ -38,11 +42,13 @@ checkCoffee = -> exec 'coffee --version', (error) -> if error + pm = detectPackageManager CWD + installCmd = getInstallCommand pm, 'coffeescript', true consola.warn( 'CoffeeScript is not found in local dependencies (`dependencies`, `devDependencies`) or globally.' ) consola.info( - 'Please install it via `npm install --save-dev coffeescript` to continue.' + "Please install it via `#{installCmd}` to continue." ) module.exports = { diff --git a/src/lib/package-manager.coffee b/src/lib/package-manager.coffee new file mode 100644 index 0000000..6086bbb --- /dev/null +++ b/src/lib/package-manager.coffee @@ -0,0 +1,130 @@ +fs = require 'fs' +path = require 'path' + +SUPPORTED = ['npm', 'pnpm', 'yarn', 'bun', 'aube', 'nub', 'vlt', 'bower'] + +detectPackageManager = (cwd, config = null) -> + if config?.milkee?.packageManager + pm = config.milkee.packageManager + return pm if pm in SUPPORTED + + pkgPath = path.join cwd, 'package.json' + if fs.existsSync pkgPath + try + pkg = JSON.parse fs.readFileSync pkgPath, 'utf-8' + if pkg.packageManager + name = pkg.packageManager.split('@')[0].trim() + return name if name in SUPPORTED + catch + + return 'pnpm' if fs.existsSync path.join cwd, 'pnpm-lock.yaml' + return 'yarn' if fs.existsSync path.join cwd, 'yarn.lock' + if ( + fs.existsSync(path.join cwd, 'bun.lockb') or + fs.existsSync(path.join cwd, 'bun.lock') + ) + return 'bun' + return 'vlt' if fs.existsSync path.join cwd, 'vlt-lock.json' + return 'bower' if fs.existsSync path.join cwd, 'bower.json' + 'npm' + +getInstallCommand = (pm, packages, dev = false, global = false) -> + switch pm + when 'pnpm' + flag = if global then ' -g' else if dev then ' -D' else '' + "pnpm add#{flag} #{packages}" + when 'yarn' + if global + "yarn global add #{packages}" + else + flag = if dev then ' -D' else '' + "yarn add#{flag} #{packages}" + when 'bun' + flag = if global then ' -g' else if dev then ' -D' else '' + "bun add#{flag} #{packages}" + when 'aube' + flag = if global then ' -g' else if dev then ' -D' else '' + "aube add#{flag} #{packages}" + when 'nub' + if global + "npm i -g #{packages}" + else + flag = if dev then ' -D' else '' + "nub add#{flag} #{packages}" + when 'vlt' + if global + "npm i -g #{packages}" + else + flag = if dev then ' --save-dev' else '' + "vlt install#{flag} #{packages}" + when 'bower' + if global + "npm i -g #{packages}" + else + flag = if dev then ' --save-dev' else ' --save' + "bower install #{packages}#{flag}" + else + if global + "npm i -g #{packages}" + else + flag = if dev then ' -D' else '' + "npm install#{flag} #{packages}" + +getInitCommand = (pm) -> + switch pm + when 'pnpm' then 'pnpm init' + when 'yarn' then 'yarn init' + when 'bun' then 'bun init' + when 'aube' then 'aube init' + when 'nub' then 'nub install' + when 'vlt' then 'vlt init' + when 'bower' then 'bower init' + else 'npm init' + +getRunCommand = (pm, script) -> + switch pm + when 'pnpm' then "pnpm #{script}" + when 'yarn' then "yarn #{script}" + when 'bun' then "bun run #{script}" + when 'aube' then "aube run #{script}" + when 'nub' then "nub run #{script}" + when 'vlt' then "vlt run #{script}" + when 'bower' then "npm run #{script}" + else "npm run #{script}" + +getInstallFrozenCommand = (pm) -> + switch pm + when 'pnpm' then 'pnpm install --frozen-lockfile' + when 'yarn' then 'yarn install --frozen-lockfile' + when 'bun' then 'bun install' + when 'aube' then 'aube install --frozen-lockfile' + when 'nub' then 'nub ci' + when 'vlt' then 'vlt install --frozen-lockfile' + else 'npm ci' + +# Returns an extra YAML step block (with leading newline) for CI setup, or empty string. +getCiSetupStep = (pm) -> + switch pm + when 'pnpm' + """ + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false""" + when 'bun' + """ + + - name: Setup Bun + uses: oven-sh/setup-bun@v2""" + else '' + +module.exports = { + SUPPORTED + detectPackageManager + getInstallCommand + getInitCommand + getRunCommand + getInstallFrozenCommand + getCiSetupStep +} diff --git a/temp/plugin/coffee.config.cjs b/temp/plugin/coffee.config.cjs index 850369a..d3de129 100644 --- a/temp/plugin/coffee.config.cjs +++ b/temp/plugin/coffee.config.cjs @@ -24,6 +24,9 @@ module.exports = { }, // (Optional) Additional options/plugins for the Milkee builder. milkee: { + // Package manager used in this project. Auto-detected, but can be overridden. + // Supported: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'aube' | 'nub' | 'vlt' | 'bower' + packageManager: '{{packageManager}}', options: { // Ignore update notifications. // ignoreUpdate: false, diff --git a/temp/plugin/publish.yml b/temp/plugin/publish.yml index 9850348..1bcda16 100644 --- a/temp/plugin/publish.yml +++ b/temp/plugin/publish.yml @@ -17,13 +17,13 @@ jobs: uses: actions/setup-node@v4 with: node-version: "20.x" - registry-url: "https://registry.npmjs.org" + registry-url: "https://registry.npmjs.org"{{setupPmStep}} - name: Install, Build, and Test run: | - npm ci - npm run build - npm test + {{installFrozen}} + {{runBuild}} + {{runTest}} - name: Commit dist directory (if changed) run: | diff --git a/temp/setup/README.md b/temp/setup/README.md index da0a47c..8c27981 100644 --- a/temp/setup/README.md +++ b/temp/setup/README.md @@ -5,5 +5,5 @@ ## Usage ```sh -npm run build +{{runBuild}} ``` \ No newline at end of file diff --git a/temp/setup/coffee.config.cjs b/temp/setup/coffee.config.cjs index 68d6d5b..cfb8459 100644 --- a/temp/setup/coffee.config.cjs +++ b/temp/setup/coffee.config.cjs @@ -24,6 +24,9 @@ module.exports = { }, // (Optional) Additional options/plugins for the Milkee builder. milkee: { + // Package manager used in this project. Auto-detected, but can be overridden. + // Supported: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'aube' | 'nub' | 'vlt' | 'bower' + packageManager: '{{packageManager}}', options: { // Ignore update notifications. // ignoreUpdate: false, From 3a2b9f4bfd1e562973e40b985fe92679702851f1 Mon Sep 17 00:00:00 2001 From: otnc Date: Wed, 24 Jun 2026 03:41:19 +0900 Subject: [PATCH 2/4] docs: update README and README-ja for v4.0.0 package manager support - Show npm/pnpm/yarn/bun install examples in the Install section - Add packageManager field (commented) to coffee.config.cjs example - Add milkee.packageManager section documenting detection priority and supported values --- README-ja.md | 29 ++++++++++++++++++++++++++--- README.md | 25 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/README-ja.md b/README-ja.md index e8dd74f..a24a9e6 100644 --- a/README-ja.md +++ b/README-ja.md @@ -21,11 +21,18 @@ Milkee をインストールします: ```bash -# グローバルインストール +# npm npm i -g milkee - -# ローカルインストール +# pnpm +pnpm add -g milkee +# yarn +yarn global add milkee +# bun +bun add -g milkee + +# ローカルインストールの場合 npm i -D milkee +pnpm add -D milkee ``` CoffeeScript と `@babel/core` が必要です。 @@ -82,6 +89,9 @@ module.exports = { }, // (任意) Milkee ビルダーの追加オプション / プラグイン。 milkee: { + // 使用するパッケージマネージャー。ロックファイルや package.json から自動検出されます。 + // 対応: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'aube' | 'nub' | 'vlt' | 'bower' + // packageManager: 'npm', options: { // アップデート通知を無視します。 // ignoreUpdate: false, @@ -114,6 +124,19 @@ module.exports = { [CoffeeScript - command.coffee](https://coffeescript.org/annotated-source/command.html) +##### `milkee.packageManager` (パッケージマネージャー) + +Milkee はロックファイルや `package.json` の `packageManager` フィールドからパッケージマネージャーを自動検出します。明示的に指定することもできます。 + +| 検出元 | 優先度 | +| :--- | :--- | +| `coffee.config.cjs` の `milkee.packageManager` | 1位(最高) | +| `package.json` の `packageManager` フィールド | 2位 | +| ロックファイル(`pnpm-lock.yaml`、`yarn.lock`、`bun.lockb`、`vlt-lock.json`、`bower.json`) | 3位 | +| デフォルト | `npm` | + +対応する値: `'npm'` `'pnpm'` `'yarn'` `'bun'` `'aube'` `'nub'` `'vlt'` `'bower'` + ##### `milkee.options` (Milkee 固有オプション) これらのオプションは Milkee の動作を制御します。 diff --git a/README.md b/README.md index 7ecc21b..d3a61cd 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,18 @@ Official site: https://milkee.org Install Milkee: ```bash -# global installation +# npm npm i -g milkee +# pnpm +pnpm add -g milkee +# yarn +yarn global add milkee +# bun +bun add -g milkee # or local installation npm i -D milkee +pnpm add -D milkee ``` CoffeeScript & @babel/core are required. @@ -82,6 +89,9 @@ module.exports = { }, // (Optional) Additional options/plugins for the Milkee builder. milkee: { + // Package manager used in this project. Auto-detected from lock files and package.json. + // Supported: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'aube' | 'nub' | 'vlt' | 'bower' + // packageManager: 'npm', options: { // Ignore update notifications. // ignoreUpdate: false, @@ -114,6 +124,19 @@ These options are passed directly to the `coffee` compiler. [CoffeeScript - command.coffee](https://coffeescript.org/annotated-source/command.html) +##### `milkee.packageManager` (Package Manager) + +Milkee auto-detects the package manager from lock files and the `packageManager` field in `package.json`. You can override it explicitly. + +| Detection source | Priority | +| :--- | :--- | +| `milkee.packageManager` in `coffee.config.cjs` | 1st (highest) | +| `packageManager` field in `package.json` | 2nd | +| Lock file (`pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, `vlt-lock.json`, `bower.json`) | 3rd | +| Default | `npm` | + +Supported values: `'npm'` `'pnpm'` `'yarn'` `'bun'` `'aube'` `'nub'` `'vlt'` `'bower'` + ##### `milkee.options` (Milkee Specific Options) These options control Milkee's behavior. From d89637a6e14deca3836595e4d4e247549c2f1f98 Mon Sep 17 00:00:00 2001 From: otnc Date: Wed, 24 Jun 2026 03:45:09 +0900 Subject: [PATCH 3/4] ci: drop Node 18 from test matrix vitest 4.x uses rolldown which requires node:util styleText (Node 20+). --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47cc471..6b03b01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x, 24.x] + node-version: [20.x, 22.x, 24.x] steps: - name: Checkout repository uses: actions/checkout@v4 From 953249f0f18628ba384167837c73aafd3a5674f2 Mon Sep 17 00:00:00 2001 From: otnc Date: Wed, 24 Jun 2026 03:45:55 +0900 Subject: [PATCH 4/4] chore: drop Node 18 support, require Node >= 20 vitest 4.x (rolldown) requires node:util styleText which is Node 20+. --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index ffa552b..7d9701a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,9 @@ "url": "https://github.com/otnc/coffeescript-milkee/issues" }, "homepage": "https://milkee.org", + "engines": { + "node": ">=20.0.0" + }, "dependencies": { "@milkee/d": "^0.3.0", "consola": "^3.4.2",