milkee 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.json +3 -0
- package/README-ja.md +160 -0
- package/README.md +22 -15
- package/dist/commands/compile.js +3 -3
- package/dist/commands/plugin.js +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/lib/checks.js +58 -0
- package/dist/lib/constants.js +14 -0
- package/dist/lib/plugins.js +59 -0
- package/dist/lib/utils.js +48 -0
- package/dist/main.js +1 -1
- package/dist/options/copy.js +3 -3
- package/dist/options/refresh.js +2 -2
- package/docs/PLUGIN-ja.md +6 -6
- package/docs/PLUGIN.md +6 -6
- package/package.json +14 -4
- package/test/integration/setup.cli.test.js +36 -0
- package/test/setup.js +113 -0
- package/test/unit/checks.test.js +38 -0
- package/test/unit/copy.test.js +50 -0
- package/test/unit/plugins.test.js +50 -0
- package/test/unit/refresh.test.js +63 -0
- package/test/unit/sandbox-windows.test.js +14 -0
- package/test/unit/setup.test.js +70 -0
- package/test/unit/src.smoke.test.js +56 -0
- package/test/unit/utils.test.js +41 -0
- package/vitest.config.mjs +14 -0
package/.prettierrc.json
ADDED
package/README-ja.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Milkee
|
|
2
|
+
|
|
3
|
+
[](https://github.com/otoneko1102/coffeescript-milkee/actions)
|
|
4
|
+
[](https://www.npmjs.com/package/milkee)
|
|
5
|
+
[](https://www.npmjs.com/package/milkee)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
[English](./README.md) | **日本語**
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
`coffee.config.cjs` を使ったシンプルな CoffeeScript ビルドツール
|
|
13
|
+
|
|
14
|
+
## はじめに
|
|
15
|
+
|
|
16
|
+
### インストール
|
|
17
|
+
|
|
18
|
+
Milkee をインストールします:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# グローバルインストール
|
|
22
|
+
npm i -g milkee
|
|
23
|
+
|
|
24
|
+
# ローカルインストール
|
|
25
|
+
npm i -D milkee
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
CoffeeScript と `@babel/core` が必要です。
|
|
29
|
+
|
|
30
|
+
> [!TIP]
|
|
31
|
+
> `options.transpile` が `true` の場合は `@babel/core` が必要です。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# グローバルインストール
|
|
35
|
+
npm i -g coffeescript @babel/core
|
|
36
|
+
|
|
37
|
+
# ローカルインストール
|
|
38
|
+
npm i -D coffeescript @babel/core
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### セットアップ
|
|
42
|
+
|
|
43
|
+
`-s` (`--setup`) コマンドを実行すると、`coffee.config.cjs` が生成されます!
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# グローバル
|
|
47
|
+
milkee -s
|
|
48
|
+
|
|
49
|
+
# ローカル
|
|
50
|
+
npx milkee -s
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### `coffee.config.cjs`
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
/** @type {import('@milkee/d').Config} */
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
// コンパイルのエントリーポイント。
|
|
60
|
+
// 単一ファイルまたはディレクトリ(例: 'src/' や 'src/app.coffee')を指定できます。
|
|
61
|
+
entry: 'src',
|
|
62
|
+
// コンパイルされた JavaScript ファイルの出力先。
|
|
63
|
+
// `options.join` が true の場合は単一ファイルパス(例: 'dist/app.js')
|
|
64
|
+
// `options.join` が false の場合はディレクトリ(例: 'dist')を指定します。
|
|
65
|
+
output: 'dist',
|
|
66
|
+
// (任意) CoffeeScript コンパイラの追加オプション。
|
|
67
|
+
// 利用可能な全オプションは `coffee --help` を参照してください。
|
|
68
|
+
// Web: https://coffeescript.org/annotated-source/command.html
|
|
69
|
+
options: {
|
|
70
|
+
// 以下のオプションがサポートされています:
|
|
71
|
+
// bare: false,
|
|
72
|
+
// join: false,
|
|
73
|
+
// map: false,
|
|
74
|
+
// inlineMap: false,
|
|
75
|
+
// noHeader: false,
|
|
76
|
+
// transpile: false,
|
|
77
|
+
// literate: false,
|
|
78
|
+
// watch: false,
|
|
79
|
+
},
|
|
80
|
+
// (任意) Milkee ビルダーの追加オプション / プラグイン。
|
|
81
|
+
milkee: {
|
|
82
|
+
options: {
|
|
83
|
+
// コンパイル前に出力ディレクトリをリセットします。
|
|
84
|
+
// refresh: false,
|
|
85
|
+
// コンパイル前に「実行しますか?」と確認します。
|
|
86
|
+
// confirm: false,
|
|
87
|
+
// コンパイル後に entry から output へ非 CoffeeScript ファイルをコピーします。(`options.join` が false の場合のみ有効)
|
|
88
|
+
// copy: false,
|
|
89
|
+
},
|
|
90
|
+
plugins: []
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
##### `options` (CoffeeScript コンパイラオプション)
|
|
96
|
+
|
|
97
|
+
これらのオプションは `coffee` コンパイラに直接渡されます。
|
|
98
|
+
|
|
99
|
+
| オプション | 型 | デフォルト | 説明 |
|
|
100
|
+
| :--- | :--- | :--- | :--- |
|
|
101
|
+
| `bare` | `boolean` | `false` | トップレベルの関数ラッパーなしでコンパイルする |
|
|
102
|
+
| `join` | `boolean` | `false` | コンパイル前にソースを連結する |
|
|
103
|
+
| `map` | `boolean` | `false` | ソースマップを生成して `.js.map` として保存する |
|
|
104
|
+
| `inlineMap` | `boolean` | `false` | ソースマップを生成して出力内に直接含める |
|
|
105
|
+
| `noHeader` | `boolean` | `false` | "Generated by" ヘッダを抑制する |
|
|
106
|
+
| `transpile` | `boolean` | `false` | 生成された JavaScript を Babel に通す |
|
|
107
|
+
| `literate` | `boolean` | `false` | 標準入力をリテラート形式の CoffeeScript として扱う |
|
|
108
|
+
| `watch` | `boolean` | `false` | ファイル変更を監視してコマンドを再実行する |
|
|
109
|
+
|
|
110
|
+
[CoffeeScript - command.coffee](https://coffeescript.org/annotated-source/command.html)
|
|
111
|
+
|
|
112
|
+
##### `milkee.options` (Milkee 固有オプション)
|
|
113
|
+
|
|
114
|
+
これらのオプションは Milkee の動作を制御します。
|
|
115
|
+
|
|
116
|
+
| オプション | 型 | デフォルト | 説明 |
|
|
117
|
+
| :--- | :--- | :--- | :--- |
|
|
118
|
+
| `refresh` | `boolean` | `false` | コンパイル前に出力ディレクトリをリセットする |
|
|
119
|
+
| `confirm` | `boolean` | `false` | コンパイル前に「実行しますか?」と確認する |
|
|
120
|
+
| `copy` | `boolean` | `false` | コンパイル後に entry から output へ非 CoffeeScript ファイルをコピーする(`options.join` が false の場合のみ) |
|
|
121
|
+
|
|
122
|
+
##### `milkee.plugins` (Milkee 固有プラグイン)
|
|
123
|
+
|
|
124
|
+
Milkee の機能はプラグインで拡張できます。プラグインは、各コンパイル成功後に実行され、コンパイル済みファイルと設定にアクセスできる単純な関数です。
|
|
125
|
+
|
|
126
|
+
例:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
const myPlugin = require('./plugins/my-plugin.js');
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
// ...
|
|
133
|
+
milkee: {
|
|
134
|
+
plugins: [
|
|
135
|
+
// この呼び出しは PluginExecutor を返します
|
|
136
|
+
myPlugin({ option: 'value' }),
|
|
137
|
+
// ...
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### コンパイル
|
|
144
|
+
|
|
145
|
+
Milkee は自動で `coffee.config.cjs` を読み、`options` からコマンドを組み立ててコンパイルを開始します!
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# グローバル
|
|
149
|
+
milkee
|
|
150
|
+
|
|
151
|
+
# ローカル
|
|
152
|
+
npx milkee
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## プラグインの作成
|
|
156
|
+
|
|
157
|
+
独自の Milkee プラグインを作成したいですか?プラグインのドキュメントを確認してください:
|
|
158
|
+
|
|
159
|
+
- [English](./docs/PLUGIN.md)
|
|
160
|
+
- [日本語](./docs/PLUGIN-ja.md)
|
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Milkee
|
|
2
2
|
|
|
3
|
+
[](https://github.com/otoneko1102/coffeescript-milkee/actions)
|
|
4
|
+
[](https://www.npmjs.com/package/milkee)
|
|
5
|
+
[](https://www.npmjs.com/package/milkee)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
**English** | [日本語](./README-ja.md)
|
|
9
|
+
|
|
3
10
|

|
|
4
11
|
|
|
5
12
|
A simple CoffeeScript build tool with [coffee.config.cjs](./temp/coffee.config.cjs)
|
|
@@ -89,16 +96,16 @@ module.exports = {
|
|
|
89
96
|
|
|
90
97
|
These options are passed directly to the `coffee` compiler.
|
|
91
98
|
|
|
92
|
-
| Option
|
|
93
|
-
|
|
|
94
|
-
| `bare`
|
|
95
|
-
| `join`
|
|
96
|
-
| `map`
|
|
97
|
-
| `inlineMap` | `boolean` | `false`
|
|
98
|
-
| `noHeader`
|
|
99
|
-
| `transpile` | `boolean` | `false`
|
|
100
|
-
| `literate`
|
|
101
|
-
| `watch`
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
| :--- | :--- | :--- | :--- |
|
|
101
|
+
| `bare` | `boolean` | `false` | compile without a top-level function wrapper |
|
|
102
|
+
| `join` | `boolean` | `false` | concatenate the source CoffeeScript before compiling |
|
|
103
|
+
| `map` | `boolean` | `false` | generate source map and save as `.js.map` files |
|
|
104
|
+
| `inlineMap` | `boolean` | `false` | generate source map and include it directly in output |
|
|
105
|
+
| `noHeader` | `boolean` | `false` | suppress the "Generated by" header |
|
|
106
|
+
| `transpile` | `boolean` | `false` | pipe generated JavaScript through Babel |
|
|
107
|
+
| `literate` | `boolean` | `false` | treat stdio as literate style coffeescript |
|
|
108
|
+
| `watch` | `boolean` | `false` | watch scripts for changes and rerun commands |
|
|
102
109
|
|
|
103
110
|
[CoffeeScript - command.coffee](https://coffeescript.org/annotated-source/command.html)
|
|
104
111
|
|
|
@@ -106,11 +113,11 @@ These options are passed directly to the `coffee` compiler.
|
|
|
106
113
|
|
|
107
114
|
These options control Milkee's behavior.
|
|
108
115
|
|
|
109
|
-
| Option
|
|
110
|
-
|
|
|
111
|
-
| `refresh` | `boolean` | `false`
|
|
112
|
-
| `confirm` | `boolean` | `false`
|
|
113
|
-
| `copy`
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
| :--- | :--- | :--- | :--- |
|
|
118
|
+
| `refresh` | `boolean` | `false` | Before compiling, reset the output directory. |
|
|
119
|
+
| `confirm` | `boolean` | `false` | Before compiling, prompt "Do you want to Continue?". |
|
|
120
|
+
| `copy` | `boolean` | `false` | After compiling, copy non-coffee files from entry to output directory. (Only works when `options.join` is `false`) |
|
|
114
121
|
|
|
115
122
|
##### `milkee.plugins` (Milkee Specific Plugins)
|
|
116
123
|
|
package/dist/commands/compile.js
CHANGED
|
@@ -11,11 +11,11 @@ crypto = require('crypto');
|
|
|
11
11
|
|
|
12
12
|
consola = require('consola');
|
|
13
13
|
|
|
14
|
-
({CWD, CONFIG_PATH, CONFIG_FILE} = require('../constants'));
|
|
14
|
+
({CWD, CONFIG_PATH, CONFIG_FILE} = require('../lib/constants'));
|
|
15
15
|
|
|
16
|
-
({checkLatest, checkCoffee} = require('../checks'));
|
|
16
|
+
({checkLatest, checkCoffee} = require('../lib/checks'));
|
|
17
17
|
|
|
18
|
-
({runPlugins} = require('../plugins'));
|
|
18
|
+
({runPlugins} = require('../lib/plugins'));
|
|
19
19
|
|
|
20
20
|
confirmContinue = require('../options/confirm');
|
|
21
21
|
|
package/dist/commands/plugin.js
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -7,9 +7,9 @@ path = require('path');
|
|
|
7
7
|
|
|
8
8
|
consola = require('consola');
|
|
9
9
|
|
|
10
|
-
({CONFIG_FILE, CONFIG_PATH} = require('../constants'));
|
|
10
|
+
({CONFIG_FILE, CONFIG_PATH} = require('../lib/constants'));
|
|
11
11
|
|
|
12
|
-
({checkCoffee} = require('../checks'));
|
|
12
|
+
({checkCoffee} = require('../lib/checks'));
|
|
13
13
|
|
|
14
14
|
// async
|
|
15
15
|
setup = async function() {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var CWD, checkCoffee, checkLatest, consola, exec, fs, isPackageLatest, path, pkg;
|
|
3
|
+
|
|
4
|
+
fs = require('fs');
|
|
5
|
+
|
|
6
|
+
path = require('path');
|
|
7
|
+
|
|
8
|
+
({exec} = require('child_process'));
|
|
9
|
+
|
|
10
|
+
consola = require('consola');
|
|
11
|
+
|
|
12
|
+
({isPackageLatest} = require('is-package-latest'));
|
|
13
|
+
|
|
14
|
+
({pkg, CWD} = require('./constants'));
|
|
15
|
+
|
|
16
|
+
// async
|
|
17
|
+
checkLatest = async function() {
|
|
18
|
+
var res;
|
|
19
|
+
try {
|
|
20
|
+
res = (await isPackageLatest(pkg));
|
|
21
|
+
if (res.success && !res.isLatest) {
|
|
22
|
+
consola.box({
|
|
23
|
+
title: "A new version is now available!",
|
|
24
|
+
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\``
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
} else {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
} catch (error1) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
checkCoffee = function() {
|
|
36
|
+
var PKG_PATH, error, pkgData, pkgFile, ref, ref1;
|
|
37
|
+
PKG_PATH = path.join(CWD, 'package.json');
|
|
38
|
+
if (fs.existsSync(PKG_PATH)) {
|
|
39
|
+
try {
|
|
40
|
+
pkgFile = fs.readFileSync(PKG_PATH, 'utf-8');
|
|
41
|
+
pkgData = JSON.parse(pkgFile);
|
|
42
|
+
if (((ref = pkgData.dependencies) != null ? ref.coffeescript : void 0) || ((ref1 = pkgData.devDependencies) != null ? ref1.coffeescript : void 0)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
} catch (error1) {
|
|
46
|
+
error = error1;
|
|
47
|
+
consola.warn(`Could not parse \`package.json\`: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return exec('coffee --version', function(error) {
|
|
51
|
+
if (error) {
|
|
52
|
+
consola.warn('CoffeeScript is not found in local dependencies (`dependencies`, `devDependencies`) or globally.');
|
|
53
|
+
return consola.info('Please install it via `npm install --save-dev coffeescript` to continue.');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = {checkLatest, checkCoffee};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var CONFIG_FILE, CONFIG_PATH, CWD, path, pkg;
|
|
3
|
+
|
|
4
|
+
path = require('path');
|
|
5
|
+
|
|
6
|
+
pkg = require('../../package.json');
|
|
7
|
+
|
|
8
|
+
CWD = process.cwd();
|
|
9
|
+
|
|
10
|
+
CONFIG_FILE = 'coffee.config.cjs';
|
|
11
|
+
|
|
12
|
+
CONFIG_PATH = path.join(CWD, CONFIG_FILE);
|
|
13
|
+
|
|
14
|
+
module.exports = {pkg, CWD, CONFIG_FILE, CONFIG_PATH};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var CWD, consola, executePlugins, fs, getCompiledFiles, path, runPlugins;
|
|
3
|
+
|
|
4
|
+
path = require('path');
|
|
5
|
+
|
|
6
|
+
fs = require('fs');
|
|
7
|
+
|
|
8
|
+
consola = require('consola');
|
|
9
|
+
|
|
10
|
+
({CWD} = require('./constants'));
|
|
11
|
+
|
|
12
|
+
({getCompiledFiles} = require('./utils'));
|
|
13
|
+
|
|
14
|
+
executePlugins = function(config, compilationResult) {
|
|
15
|
+
var plugins, ref;
|
|
16
|
+
plugins = ((ref = config.milkee) != null ? ref.plugins : void 0) || [];
|
|
17
|
+
if (!(plugins.length > 0)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
consola.start(`Running ${plugins.length} plugin(s)...`);
|
|
21
|
+
return (async function() { // async
|
|
22
|
+
var error, i, len, pluginFn;
|
|
23
|
+
try {
|
|
24
|
+
for (i = 0, len = plugins.length; i < len; i++) {
|
|
25
|
+
pluginFn = plugins[i];
|
|
26
|
+
if (typeof pluginFn === 'function') {
|
|
27
|
+
await Promise.resolve(pluginFn(compilationResult));
|
|
28
|
+
} else {
|
|
29
|
+
consola.warn(`Invalid plugin definition skipped (expected a function, got ${typeof pluginFn}).`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return consola.success("Plugins executed successfully.");
|
|
33
|
+
} catch (error1) {
|
|
34
|
+
error = error1;
|
|
35
|
+
return consola.error("An error occurred during plugin execution:", error);
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
runPlugins = function(config, options, stdout = '', stderr = '') {
|
|
41
|
+
var compilationResult, compiledFiles, mapPath, outputPath;
|
|
42
|
+
outputPath = path.join(CWD, config.output);
|
|
43
|
+
compiledFiles = getCompiledFiles(outputPath);
|
|
44
|
+
if (options.join && options.map && !options.inlineMap) {
|
|
45
|
+
mapPath = `${outputPath}.map`;
|
|
46
|
+
if (fs.existsSync(mapPath && !compiledFiles.includes(mapPath))) {
|
|
47
|
+
compiledFiles = compiledFiles.concat(getCompiledFiles(mapPath));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
compilationResult = {
|
|
51
|
+
config: config,
|
|
52
|
+
compiledFiles: compiledFiles,
|
|
53
|
+
stdout: stdout,
|
|
54
|
+
stderr: stderr
|
|
55
|
+
};
|
|
56
|
+
return executePlugins(config, compilationResult);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
module.exports = {runPlugins};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var consola, fs, getCompiledFiles, path, sleep;
|
|
3
|
+
|
|
4
|
+
fs = require('fs');
|
|
5
|
+
|
|
6
|
+
path = require('path');
|
|
7
|
+
|
|
8
|
+
consola = require('consola');
|
|
9
|
+
|
|
10
|
+
sleep = function(time) {
|
|
11
|
+
return new Promise(function(resolve) {
|
|
12
|
+
return setTimeout(resolve, time);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
getCompiledFiles = function(targetPath) {
|
|
17
|
+
var error, filesList, i, item, itemPath, items, len, stat;
|
|
18
|
+
filesList = [];
|
|
19
|
+
if (!fs.existsSync(targetPath)) {
|
|
20
|
+
consola.warn(`Path does not exist, skipping scan ${targetPath}`);
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
stat = fs.statSync(targetPath);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
consola.start(`Scanning directory: ${targetPath}`);
|
|
27
|
+
items = fs.readdirSync(targetPath);
|
|
28
|
+
for (i = 0, len = items.length; i < len; i++) {
|
|
29
|
+
item = items[i];
|
|
30
|
+
itemPath = path.join(targetPath, item);
|
|
31
|
+
filesList = filesList.concat(getCompiledFiles(itemPath));
|
|
32
|
+
}
|
|
33
|
+
} else if (stat.isFile()) {
|
|
34
|
+
if (targetPath.match(/\.js(?:\.map)?$/i)) {
|
|
35
|
+
if (fs.existsSync(targetPath)) {
|
|
36
|
+
consola.info(`Found file: \`${targetPath}\``);
|
|
37
|
+
filesList.push(targetPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (error1) {
|
|
42
|
+
error = error1;
|
|
43
|
+
consola.warn(`Could not scan output path ${targetPath}: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
return filesList;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports = {sleep, getCompiledFiles};
|
package/dist/main.js
CHANGED
package/dist/options/copy.js
CHANGED
|
@@ -7,12 +7,12 @@ path = require('path');
|
|
|
7
7
|
|
|
8
8
|
consola = require('consola');
|
|
9
9
|
|
|
10
|
-
({CWD} = require('../constants'));
|
|
10
|
+
({CWD} = require('../lib/constants'));
|
|
11
11
|
|
|
12
12
|
executeCopy = function(config) {
|
|
13
13
|
var copyNonCoffeeFiles, entryPath, error, outputPath, ref, stat;
|
|
14
|
-
entryPath = path.join(CWD, config.entry);
|
|
15
|
-
outputPath = path.join(CWD, config.output);
|
|
14
|
+
entryPath = path.isAbsolute(config.entry) ? config.entry : path.join(CWD, config.entry);
|
|
15
|
+
outputPath = path.isAbsolute(config.output) ? config.output : path.join(CWD, config.output);
|
|
16
16
|
if (!fs.existsSync(entryPath)) {
|
|
17
17
|
consola.warn(`Entry path does not exist: ${config.entry}`);
|
|
18
18
|
return;
|
package/dist/options/refresh.js
CHANGED
|
@@ -9,12 +9,12 @@ crypto = require('crypto');
|
|
|
9
9
|
|
|
10
10
|
consola = require('consola');
|
|
11
11
|
|
|
12
|
-
({CWD} = require('../constants'));
|
|
12
|
+
({CWD} = require('../lib/constants'));
|
|
13
13
|
|
|
14
14
|
// Execute refresh processing
|
|
15
15
|
executeRefresh = function(config, backupFiles) {
|
|
16
16
|
var backupName, backupPath, dirName, error, fileName, hash, i, item, items, len, originalPath, stat, targetDir;
|
|
17
|
-
targetDir = path.join(CWD, config.output);
|
|
17
|
+
targetDir = path.isAbsolute(config.output) ? config.output : path.join(CWD, config.output);
|
|
18
18
|
if (fs.existsSync(targetDir)) {
|
|
19
19
|
stat = fs.statSync(targetDir);
|
|
20
20
|
hash = crypto.randomBytes(4).toString('hex');
|
package/docs/PLUGIN-ja.md
CHANGED
|
@@ -71,12 +71,12 @@ module.exports = main
|
|
|
71
71
|
|
|
72
72
|
Milkee はコンパイル後にこのオブジェクトをプラグインに渡します:
|
|
73
73
|
|
|
74
|
-
| プロパティ
|
|
75
|
-
|
|
|
76
|
-
| `config`
|
|
77
|
-
| `compiledFiles` | `string[]` | コンパイルされた `.js` と `.js.map` のパス
|
|
78
|
-
| `stdout`
|
|
79
|
-
| `stderr`
|
|
74
|
+
| プロパティ | 型 | 説明 |
|
|
75
|
+
| :--- | :--- | :--- |
|
|
76
|
+
| `config` | `object` | `coffee.config.cjs` の設定オブジェクト |
|
|
77
|
+
| `compiledFiles` | `string[]` | コンパイルされた `.js` と `.js.map` のパス |
|
|
78
|
+
| `stdout` | `string` | コンパイラの標準出力 |
|
|
79
|
+
| `stderr` | `string` | コンパイラの標準エラー |
|
|
80
80
|
|
|
81
81
|
### coffee.config.cjs での使用
|
|
82
82
|
|
package/docs/PLUGIN.md
CHANGED
|
@@ -71,12 +71,12 @@ module.exports = main
|
|
|
71
71
|
|
|
72
72
|
Milkee passes this object to your plugin after compilation:
|
|
73
73
|
|
|
74
|
-
| Property
|
|
75
|
-
|
|
|
76
|
-
| `config`
|
|
77
|
-
| `compiledFiles` | `string[]` | Paths to compiled `.js` and `.js.map` files
|
|
78
|
-
| `stdout`
|
|
79
|
-
| `stderr`
|
|
74
|
+
| Property | Type | Description |
|
|
75
|
+
| :--- | :--- | :--- |
|
|
76
|
+
| `config` | `object` | Full config from `coffee.config.cjs` |
|
|
77
|
+
| `compiledFiles` | `string[]` | Paths to compiled `.js` and `.js.map` files |
|
|
78
|
+
| `stdout` | `string` | Compiler standard output |
|
|
79
|
+
| `stderr` | `string` | Compiler standard error |
|
|
80
80
|
|
|
81
81
|
### Using in coffee.config.cjs
|
|
82
82
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "milkee",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A simple CoffeeScript build tool with coffee.config.cjs",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"milkee": "bin/milkee.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
11
|
-
"
|
|
10
|
+
"test": "prettier --write ./test && vitest",
|
|
11
|
+
"test:ci": "vitest --run",
|
|
12
|
+
"coverage": "vitest --run --coverage",
|
|
13
|
+
"build": "coffee --output dist/ --compile --bare src/",
|
|
14
|
+
"format": "node ./scripts/format.js",
|
|
15
|
+
"deprecate:dev": "node ./scripts/deprecate-dev-versions.js"
|
|
12
16
|
},
|
|
13
17
|
"repository": {
|
|
14
18
|
"type": "git",
|
|
@@ -40,6 +44,12 @@
|
|
|
40
44
|
"devDependencies": {
|
|
41
45
|
"@babel/core": "^7.28.5",
|
|
42
46
|
"@types/coffeescript": "^2.5.7",
|
|
43
|
-
"
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
48
|
+
"coffee-fmt": "^0.12.0",
|
|
49
|
+
"coffeescript": "^2.7.0",
|
|
50
|
+
"dotenv": "^17.2.3",
|
|
51
|
+
"execa": "^9.6.1",
|
|
52
|
+
"prettier": "^3.8.0",
|
|
53
|
+
"vitest": "^4.0.17"
|
|
44
54
|
}
|
|
45
55
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { execFile } = require("child_process");
|
|
5
|
+
|
|
6
|
+
describe("cli setup integration", () => {
|
|
7
|
+
it("runs `--setup` and creates coffee.config.cjs", async () => {
|
|
8
|
+
const dir = createTestDir("cli");
|
|
9
|
+
|
|
10
|
+
// run the setup command directly via CoffeeScript so we don't require the ESM-only `yargs` in `main`
|
|
11
|
+
const scriptPath = require("path").join(
|
|
12
|
+
process.cwd(),
|
|
13
|
+
"src",
|
|
14
|
+
"commands",
|
|
15
|
+
"setup.coffee",
|
|
16
|
+
);
|
|
17
|
+
const csRegister = require.resolve("coffeescript/register");
|
|
18
|
+
await new Promise((resolve, reject) => {
|
|
19
|
+
// -r <abs> -> preload CoffeeScript via absolute path so child process can find it
|
|
20
|
+
execFile(
|
|
21
|
+
"node",
|
|
22
|
+
["-r", csRegister, "-e", `require(${JSON.stringify(scriptPath)})()`],
|
|
23
|
+
{ cwd: dir },
|
|
24
|
+
(err) => {
|
|
25
|
+
if (err) return reject(err);
|
|
26
|
+
resolve();
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const cfgPath = path.join(dir, "coffee.config.cjs");
|
|
32
|
+
expect(fs.existsSync(cfgPath)).toBe(true);
|
|
33
|
+
|
|
34
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
});
|
package/test/setup.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Register CoffeeScript so tests can require .coffee files
|
|
2
|
+
require("coffeescript/register");
|
|
3
|
+
|
|
4
|
+
// Test sandbox guard: prevent accidental writes outside a temporary sandbox
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
|
|
9
|
+
const SANDBOX = fs.mkdtempSync(path.join(os.tmpdir(), "milkee-test-sandbox-"));
|
|
10
|
+
process.env.TEST_SANDBOX = SANDBOX;
|
|
11
|
+
globalThis.TEST_SANDBOX = SANDBOX;
|
|
12
|
+
|
|
13
|
+
function stripExtendedPrefix(s) {
|
|
14
|
+
if (typeof s !== "string") return s;
|
|
15
|
+
// Remove Windows extended path prefix like '\\?\\' or '\?\\'
|
|
16
|
+
let out = s.replace(/^\\+\?\\/, "");
|
|
17
|
+
// If we still have a leading backslash before a drive letter (e.g. '\C:\...'), remove it
|
|
18
|
+
out = out.replace(/^\\+([A-Za-z]:)/, "$1");
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Allowlist: sandbox + system temp + node_modules + any additional paths via TEST_WRITE_ALLOW
|
|
23
|
+
const allowed = [
|
|
24
|
+
path.resolve(stripExtendedPrefix(SANDBOX)),
|
|
25
|
+
path.resolve(stripExtendedPrefix(os.tmpdir())),
|
|
26
|
+
path.resolve(stripExtendedPrefix(process.cwd()), "node_modules"),
|
|
27
|
+
];
|
|
28
|
+
if (process.env.TEST_WRITE_ALLOW) {
|
|
29
|
+
process.env.TEST_WRITE_ALLOW.split(",").forEach((p) => {
|
|
30
|
+
if (p) allowed.push(path.resolve(stripExtendedPrefix(p)));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveSafe(p) {
|
|
35
|
+
if (!p) return p;
|
|
36
|
+
// accept Buffers as well
|
|
37
|
+
if (Buffer.isBuffer(p)) p = p.toString();
|
|
38
|
+
// strip Windows extended path prefix if present
|
|
39
|
+
if (typeof p === "string") {
|
|
40
|
+
p = stripExtendedPrefix(p);
|
|
41
|
+
}
|
|
42
|
+
if (!path.isAbsolute(p)) p = path.join(process.cwd(), String(p));
|
|
43
|
+
return path.resolve(p);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isAllowed(p) {
|
|
47
|
+
const rp = resolveSafe(p);
|
|
48
|
+
return allowed.some(
|
|
49
|
+
(prefix) => rp === prefix || rp.startsWith(prefix + path.sep),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeGuard(orig, checkIndex = 0) {
|
|
54
|
+
return function (...args) {
|
|
55
|
+
const target = args[checkIndex];
|
|
56
|
+
if (!isAllowed(target)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Test attempted to modify outside sandbox: ${target} (sandbox=${SANDBOX})`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return orig.apply(this, args);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Patch common fs methods that write or modify the FS
|
|
66
|
+
const writeSyncMethods = [
|
|
67
|
+
"writeFileSync",
|
|
68
|
+
"appendFileSync",
|
|
69
|
+
"copyFileSync",
|
|
70
|
+
"mkdirSync",
|
|
71
|
+
"rmSync",
|
|
72
|
+
"rmdirSync",
|
|
73
|
+
"renameSync",
|
|
74
|
+
"unlinkSync",
|
|
75
|
+
];
|
|
76
|
+
for (const m of writeSyncMethods) {
|
|
77
|
+
if (typeof fs[m] === "function") fs[m] = makeGuard(fs[m], 0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Async versions
|
|
81
|
+
if (typeof fs.writeFile === "function") {
|
|
82
|
+
const orig = fs.writeFile;
|
|
83
|
+
fs.writeFile = function (file, ...rest) {
|
|
84
|
+
if (!isAllowed(file)) {
|
|
85
|
+
const cb = rest.find((r) => typeof r === "function");
|
|
86
|
+
const err = new Error(
|
|
87
|
+
`Test attempted to modify outside sandbox: ${file} (sandbox=${SANDBOX})`,
|
|
88
|
+
);
|
|
89
|
+
if (cb) return process.nextTick(() => cb(err));
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
return orig.apply(this, [file, ...rest]);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (fs.promises && typeof fs.promises.writeFile === "function") {
|
|
96
|
+
const orig = fs.promises.writeFile;
|
|
97
|
+
fs.promises.writeFile = function (file, ...rest) {
|
|
98
|
+
if (!isAllowed(file))
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Test attempted to modify outside sandbox: ${file} (sandbox=${SANDBOX})`,
|
|
101
|
+
);
|
|
102
|
+
return orig.apply(this, [file, ...rest]);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper for tests to create safe temp dirs
|
|
107
|
+
globalThis.createTestDir = function (name = "") {
|
|
108
|
+
const dir = path.join(SANDBOX, name || String(Date.now()));
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
+
return dir;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
console.info(`[test setup] test sandbox: ${SANDBOX}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const consola = require("consola");
|
|
4
|
+
|
|
5
|
+
// We re-require the checks module inside tests to allow us to mock dependencies
|
|
6
|
+
// that it captures at require-time.
|
|
7
|
+
|
|
8
|
+
describe("checks", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
vi.resetModules();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("checkLatest returns true and shows box when not latest", async () => {
|
|
15
|
+
vi.mock("is-package-latest", () => ({
|
|
16
|
+
isPackageLatest: async () => ({
|
|
17
|
+
success: true,
|
|
18
|
+
isLatest: false,
|
|
19
|
+
currentVersion: "1.0.0",
|
|
20
|
+
latestVersion: "1.2.0",
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
const { checkLatest } = require("../../src/lib/checks.coffee");
|
|
24
|
+
|
|
25
|
+
vi.spyOn(consola, "box").mockImplementation(() => {});
|
|
26
|
+
|
|
27
|
+
const res = await checkLatest();
|
|
28
|
+
expect(res).toBe(true);
|
|
29
|
+
expect(consola.box).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("checkCoffee reads package.json and does not warn when coffeescript present", async () => {
|
|
33
|
+
const { checkCoffee } = require("../../src/lib/checks.coffee");
|
|
34
|
+
vi.spyOn(consola, "warn").mockImplementation(() => {});
|
|
35
|
+
await checkCoffee();
|
|
36
|
+
expect(consola.warn).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
const executeCopy = require("../../src/options/copy.coffee");
|
|
6
|
+
|
|
7
|
+
function createTree(base) {
|
|
8
|
+
fs.mkdirSync(base, { recursive: true });
|
|
9
|
+
fs.writeFileSync(path.join(base, "a.coffee"), "c");
|
|
10
|
+
fs.writeFileSync(path.join(base, "b.txt"), "b");
|
|
11
|
+
fs.mkdirSync(path.join(base, "sub"), { recursive: true });
|
|
12
|
+
fs.writeFileSync(path.join(base, "sub", "c.md"), "c");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("copy", () => {
|
|
16
|
+
it("copies non-coffee files recursively", () => {
|
|
17
|
+
const base = createTestDir("copy");
|
|
18
|
+
const entry = path.join(base, "entry");
|
|
19
|
+
const out = path.join(base, "out");
|
|
20
|
+
createTree(entry);
|
|
21
|
+
fs.mkdirSync(out, { recursive: true });
|
|
22
|
+
|
|
23
|
+
// preconditions
|
|
24
|
+
expect(fs.existsSync(entry)).toBe(true);
|
|
25
|
+
expect(fs.existsSync(path.join(entry, "b.txt"))).toBe(true);
|
|
26
|
+
|
|
27
|
+
// use absolute paths (sandboxed)
|
|
28
|
+
executeCopy({ entry, output: out });
|
|
29
|
+
|
|
30
|
+
expect(fs.existsSync(path.join(out, "b.txt"))).toBe(true);
|
|
31
|
+
expect(fs.existsSync(path.join(out, "sub", "c.md"))).toBe(true);
|
|
32
|
+
expect(fs.existsSync(path.join(out, "a.coffee"))).toBe(false);
|
|
33
|
+
|
|
34
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("skips when join option is enabled", () => {
|
|
38
|
+
const base = createTestDir("copy");
|
|
39
|
+
const entry = path.join(base, "entry");
|
|
40
|
+
const out = path.join(base, "out");
|
|
41
|
+
createTree(entry);
|
|
42
|
+
fs.mkdirSync(out, { recursive: true });
|
|
43
|
+
|
|
44
|
+
executeCopy({ entry, output: out, options: { join: true } });
|
|
45
|
+
|
|
46
|
+
expect(fs.existsSync(path.join(out, "b.txt"))).toBe(false);
|
|
47
|
+
|
|
48
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const plugins = require("../../src/lib/plugins.coffee");
|
|
6
|
+
|
|
7
|
+
describe("plugins", () => {
|
|
8
|
+
it("runPlugins executes provided plugin functions", async () => {
|
|
9
|
+
let called = false;
|
|
10
|
+
const tempDir = createTestDir("plugins");
|
|
11
|
+
// create a dummy compiled file
|
|
12
|
+
const compiled = path.join(tempDir, "out.js");
|
|
13
|
+
fs.writeFileSync(compiled, "x");
|
|
14
|
+
|
|
15
|
+
const config = {
|
|
16
|
+
output: tempDir,
|
|
17
|
+
milkee: {
|
|
18
|
+
plugins: [
|
|
19
|
+
(res) => {
|
|
20
|
+
called = true;
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
plugins.runPlugins(config, {});
|
|
27
|
+
|
|
28
|
+
// wait a tick for async IIFE to run
|
|
29
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
30
|
+
expect(called).toBe(true);
|
|
31
|
+
|
|
32
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("warns on invalid plugin entries", async () => {
|
|
36
|
+
const tempDir = fs.mkdtempSync(
|
|
37
|
+
path.join(require("os").tmpdir(), "milkee-plugins-"),
|
|
38
|
+
);
|
|
39
|
+
const consola = require("consola");
|
|
40
|
+
vi.spyOn(consola, "warn").mockImplementation(() => {});
|
|
41
|
+
|
|
42
|
+
const config = { output: tempDir, milkee: { plugins: ["not-fn"] } };
|
|
43
|
+
plugins.runPlugins(config, {});
|
|
44
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
45
|
+
|
|
46
|
+
expect(consola.warn).toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
executeRefresh,
|
|
7
|
+
restoreBackups,
|
|
8
|
+
clearBackups,
|
|
9
|
+
} = require("../../src/options/refresh.coffee");
|
|
10
|
+
|
|
11
|
+
describe("refresh", () => {
|
|
12
|
+
it("backs up directory files and restores them", () => {
|
|
13
|
+
const base = createTestDir("refresh");
|
|
14
|
+
const dir = path.join(base, "out");
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
const a = path.join(dir, "a.txt");
|
|
17
|
+
const b = path.join(dir, "b.txt");
|
|
18
|
+
fs.writeFileSync(a, "1");
|
|
19
|
+
fs.writeFileSync(b, "2");
|
|
20
|
+
|
|
21
|
+
const backupFiles = [];
|
|
22
|
+
// preconditions
|
|
23
|
+
expect(fs.existsSync(dir)).toBe(true);
|
|
24
|
+
expect(fs.existsSync(a)).toBe(true);
|
|
25
|
+
|
|
26
|
+
executeRefresh({ output: dir }, backupFiles);
|
|
27
|
+
|
|
28
|
+
expect(backupFiles.length).toBe(2);
|
|
29
|
+
for (const binfo of backupFiles) {
|
|
30
|
+
expect(fs.existsSync(binfo.backup)).toBe(true);
|
|
31
|
+
expect(fs.existsSync(binfo.original)).toBe(false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// restore
|
|
35
|
+
restoreBackups(backupFiles);
|
|
36
|
+
for (const binfo of backupFiles) {
|
|
37
|
+
expect(fs.existsSync(binfo.original)).toBe(true);
|
|
38
|
+
expect(fs.existsSync(binfo.backup)).toBe(false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// cleanup
|
|
42
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("clearBackups removes backup files", () => {
|
|
46
|
+
const base = createTestDir("refresh");
|
|
47
|
+
const dir = path.join(base, "out");
|
|
48
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
const a = path.join(dir, "a.txt");
|
|
50
|
+
fs.writeFileSync(a, "1");
|
|
51
|
+
|
|
52
|
+
const backupFiles = [];
|
|
53
|
+
executeRefresh({ output: dir }, backupFiles);
|
|
54
|
+
expect(backupFiles.length).toBeGreaterThan(0);
|
|
55
|
+
|
|
56
|
+
clearBackups(backupFiles);
|
|
57
|
+
for (const binfo of backupFiles) {
|
|
58
|
+
expect(fs.existsSync(binfo.backup)).toBe(false);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
describe("sandbox Windows extended path handling", () => {
|
|
5
|
+
it("allows writes when path uses \\?\\ prefix (Windows only)", () => {
|
|
6
|
+
if (process.platform !== "win32") return;
|
|
7
|
+
const dir = createTestDir("win-prefix");
|
|
8
|
+
// simulate Windows extended path prefix
|
|
9
|
+
// use Windows extended path prefix with doubled backslashes
|
|
10
|
+
const prefixed = "\\\\?\\\\" + path.join(dir, "file.txt");
|
|
11
|
+
expect(() => fs.writeFileSync(prefixed, "ok")).not.toThrow();
|
|
12
|
+
expect(fs.existsSync(path.join(dir, "file.txt"))).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
const setup = require("../../src/commands/setup.coffee");
|
|
6
|
+
const checks = require("../../src/lib/checks.coffee");
|
|
7
|
+
const { CONFIG_FILE } = require("../../src/lib/constants.coffee");
|
|
8
|
+
|
|
9
|
+
describe("setup", () => {
|
|
10
|
+
let cwd;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
cwd = process.cwd();
|
|
13
|
+
vi.spyOn(checks, "checkCoffee").mockImplementation(() => {});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
process.chdir(cwd);
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("creates config file when not present", async () => {
|
|
22
|
+
const dir = fs.mkdtempSync(
|
|
23
|
+
path.join(require("os").tmpdir(), "milkee-setup-"),
|
|
24
|
+
);
|
|
25
|
+
process.chdir(dir);
|
|
26
|
+
|
|
27
|
+
// re-require to ensure constants.CWD is captured from the new cwd
|
|
28
|
+
delete require.cache[require.resolve("../../src/lib/constants.coffee")];
|
|
29
|
+
delete require.cache[require.resolve("../../src/commands/setup.coffee")];
|
|
30
|
+
const setupLocal = require("../../src/commands/setup.coffee");
|
|
31
|
+
|
|
32
|
+
// run setup
|
|
33
|
+
await setupLocal();
|
|
34
|
+
|
|
35
|
+
const cfgPath = path.join(dir, CONFIG_FILE);
|
|
36
|
+
expect(fs.existsSync(cfgPath)).toBe(true);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
40
|
+
} catch (e) {
|
|
41
|
+
/* ignore */
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not overwrite when prompt says no", async () => {
|
|
46
|
+
const dir = fs.mkdtempSync(
|
|
47
|
+
path.join(require("os").tmpdir(), "milkee-setup-"),
|
|
48
|
+
);
|
|
49
|
+
process.chdir(dir);
|
|
50
|
+
|
|
51
|
+
const cfgPath = path.join(dir, CONFIG_FILE);
|
|
52
|
+
fs.writeFileSync(cfgPath, "original");
|
|
53
|
+
|
|
54
|
+
const consola = require("consola");
|
|
55
|
+
vi.spyOn(consola, "prompt").mockResolvedValue(false);
|
|
56
|
+
|
|
57
|
+
delete require.cache[require.resolve("../../src/lib/constants.coffee")];
|
|
58
|
+
delete require.cache[require.resolve("../../src/commands/setup.coffee")];
|
|
59
|
+
const setupLocal = require("../../src/commands/setup.coffee");
|
|
60
|
+
|
|
61
|
+
await setupLocal();
|
|
62
|
+
|
|
63
|
+
expect(fs.readFileSync(cfgPath, "utf-8")).toBe("original");
|
|
64
|
+
|
|
65
|
+
// try cleanup; ignore errors
|
|
66
|
+
try {
|
|
67
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
68
|
+
} catch (e) {}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function collectCoffeeFiles(dir) {
|
|
5
|
+
let out = [];
|
|
6
|
+
const items = fs.readdirSync(dir);
|
|
7
|
+
for (const item of items) {
|
|
8
|
+
const itemPath = path.join(dir, item);
|
|
9
|
+
const stat = fs.statSync(itemPath);
|
|
10
|
+
if (stat.isDirectory()) {
|
|
11
|
+
out = out.concat(collectCoffeeFiles(itemPath));
|
|
12
|
+
} else if (stat.isFile() && itemPath.endsWith(".coffee")) {
|
|
13
|
+
out.push(itemPath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SRC_DIR = path.join(__dirname, "../../src");
|
|
20
|
+
let files = [];
|
|
21
|
+
try {
|
|
22
|
+
files = collectCoffeeFiles(SRC_DIR);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// If src missing for some reason, test will fail explicitly below
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Exclude main.coffee and compile.coffee to avoid executing CLI/long-running flows on require
|
|
28
|
+
files = files.filter((f) => {
|
|
29
|
+
const excluded = [
|
|
30
|
+
path.join("src", "main.coffee"),
|
|
31
|
+
path.join("src", "commands", "compile.coffee"),
|
|
32
|
+
];
|
|
33
|
+
return !excluded.some((e) => f.endsWith(e));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (files.length === 0) {
|
|
37
|
+
it("has .coffee files under src", () => {
|
|
38
|
+
throw new Error("No .coffee files found under src");
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
describe("smoke: require all src .coffee files", () => {
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const rel = path.relative(process.cwd(), file);
|
|
44
|
+
it(`require ${rel}`, () => {
|
|
45
|
+
// clear cache to make require deterministic
|
|
46
|
+
try {
|
|
47
|
+
delete require.cache[require.resolve(file)];
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
expect(() => {
|
|
50
|
+
const mod = require(file);
|
|
51
|
+
expect(mod).not.toBeUndefined();
|
|
52
|
+
}).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
const utils = require("../../src/lib/utils.coffee");
|
|
6
|
+
|
|
7
|
+
describe("utils", () => {
|
|
8
|
+
it("sleep resolves after given time", async () => {
|
|
9
|
+
vi.useFakeTimers();
|
|
10
|
+
const p = utils.sleep(100);
|
|
11
|
+
vi.advanceTimersByTime(100);
|
|
12
|
+
await vi.runAllTimersAsync();
|
|
13
|
+
await expect(p).resolves.toBeUndefined();
|
|
14
|
+
vi.useRealTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("getCompiledFiles finds .js and .js.map files recursively", () => {
|
|
18
|
+
// create directory inside test sandbox
|
|
19
|
+
const dir = createTestDir("utils");
|
|
20
|
+
const fileA = path.join(dir, "a.js");
|
|
21
|
+
const fileB = path.join(dir, "b.js.map");
|
|
22
|
+
const fileC = path.join(dir, "c.txt");
|
|
23
|
+
const subdir = path.join(dir, "sub");
|
|
24
|
+
fs.mkdirSync(subdir);
|
|
25
|
+
const fileD = path.join(subdir, "d.js");
|
|
26
|
+
fs.writeFileSync(fileA, "console.log(1)");
|
|
27
|
+
fs.writeFileSync(fileB, "map");
|
|
28
|
+
fs.writeFileSync(fileC, "no");
|
|
29
|
+
fs.writeFileSync(fileD, "console.log(2)");
|
|
30
|
+
const res = utils.getCompiledFiles(dir);
|
|
31
|
+
expect(res).toEqual(expect.arrayContaining([fileA, fileB, fileD]));
|
|
32
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("returns empty if path does not exist", () => {
|
|
36
|
+
const res = utils.getCompiledFiles(
|
|
37
|
+
path.join(os.tmpdir(), "nonexistent-" + Date.now()),
|
|
38
|
+
);
|
|
39
|
+
expect(res).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
environment: 'node',
|
|
6
|
+
globals: true,
|
|
7
|
+
setupFiles: './test/setup.js',
|
|
8
|
+
},
|
|
9
|
+
coverage: {
|
|
10
|
+
provider: 'v8',
|
|
11
|
+
reporter: ['text', 'lcov'],
|
|
12
|
+
exclude: ['**/*.coffee']
|
|
13
|
+
}
|
|
14
|
+
})
|