balm-core 5.2.0 → 6.0.0-alpha.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.
Files changed (301) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.cjs +2625 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +569 -0
  5. package/dist/index.d.ts +569 -0
  6. package/dist/index.js +2527 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +49 -123
  9. package/src/balm.ts +108 -0
  10. package/src/bundler/esbuild.ts +47 -0
  11. package/src/bundler/index.ts +3 -0
  12. package/src/bundler/rollup.ts +45 -0
  13. package/src/bundler/webpack.ts +124 -0
  14. package/src/config/constants.ts +12 -0
  15. package/src/config/defaults.ts +209 -0
  16. package/src/config/index.ts +112 -0
  17. package/src/hooks/index.ts +224 -0
  18. package/src/index.ts +16 -0
  19. package/src/pipeline/file.ts +69 -0
  20. package/src/pipeline/index.ts +10 -0
  21. package/src/pipeline/pipeline.ts +135 -0
  22. package/src/pipeline/transforms/cache.ts +146 -0
  23. package/src/pipeline/transforms/htmlmin.ts +18 -0
  24. package/src/pipeline/transforms/less.ts +75 -0
  25. package/src/pipeline/transforms/postcss.ts +43 -0
  26. package/src/pipeline/transforms/rename.ts +37 -0
  27. package/src/pipeline/transforms/replace.ts +30 -0
  28. package/src/pipeline/transforms/sass.ts +106 -0
  29. package/src/pipeline/transforms/terser.ts +20 -0
  30. package/src/runner/dag.ts +77 -0
  31. package/src/runner/task.ts +38 -0
  32. package/src/shims.d.ts +5 -0
  33. package/src/tasks/cache.ts +72 -0
  34. package/src/tasks/clean.ts +21 -0
  35. package/src/tasks/copy.ts +36 -0
  36. package/src/tasks/ftp.ts +90 -0
  37. package/src/tasks/html.ts +232 -0
  38. package/src/tasks/index.ts +14 -0
  39. package/src/tasks/publish.ts +71 -0
  40. package/src/tasks/pwa.ts +99 -0
  41. package/src/tasks/remove.ts +24 -0
  42. package/src/tasks/replace.ts +34 -0
  43. package/src/tasks/script.ts +28 -0
  44. package/src/tasks/server.ts +183 -0
  45. package/src/tasks/static.ts +104 -0
  46. package/src/tasks/style.ts +57 -0
  47. package/src/tasks/zip.ts +65 -0
  48. package/src/types/index.ts +202 -0
  49. package/src/utilities/file.ts +120 -0
  50. package/src/utilities/logger.ts +63 -0
  51. package/src/utilities/utils.ts +46 -0
  52. package/src/utilities/workspace.ts +38 -0
  53. package/src/watcher/index.ts +100 -0
  54. package/tsconfig.json +8 -0
  55. package/tsup.config.ts +12 -0
  56. package/README.md +0 -196
  57. package/index.d.ts +0 -360
  58. package/lib/balm.js +0 -49
  59. package/lib/bootstrap/check_config.js +0 -43
  60. package/lib/bootstrap/index.js +0 -87
  61. package/lib/bundler/esbuild/build.js +0 -26
  62. package/lib/bundler/esbuild/index.js +0 -7
  63. package/lib/bundler/esbuild/options.js +0 -7
  64. package/lib/bundler/esbuild/transform.js +0 -36
  65. package/lib/bundler/rollup/build.js +0 -52
  66. package/lib/bundler/rollup/entry.js +0 -28
  67. package/lib/bundler/rollup/index.js +0 -20
  68. package/lib/bundler/rollup/output.js +0 -20
  69. package/lib/bundler/rollup/plugins.js +0 -31
  70. package/lib/bundler/webpack/config/common.js +0 -135
  71. package/lib/bundler/webpack/config/dev.js +0 -12
  72. package/lib/bundler/webpack/config/env.js +0 -67
  73. package/lib/bundler/webpack/config/index.js +0 -6
  74. package/lib/bundler/webpack/config/prod.js +0 -39
  75. package/lib/bundler/webpack/config/regex.js +0 -10
  76. package/lib/bundler/webpack/entry.js +0 -65
  77. package/lib/bundler/webpack/index.js +0 -32
  78. package/lib/bundler/webpack/loaders.js +0 -50
  79. package/lib/bundler/webpack/output.js +0 -41
  80. package/lib/bundler/webpack/rules/asset.js +0 -33
  81. package/lib/bundler/webpack/rules/babel.js +0 -19
  82. package/lib/bundler/webpack/rules/css.js +0 -109
  83. package/lib/bundler/webpack/rules/html.js +0 -15
  84. package/lib/bundler/webpack/rules/index.js +0 -10
  85. package/lib/config/assets.js +0 -30
  86. package/lib/config/constants.js +0 -14
  87. package/lib/config/env.js +0 -35
  88. package/lib/config/extras.js +0 -6
  89. package/lib/config/ftp.js +0 -6
  90. package/lib/config/globals.js +0 -43
  91. package/lib/config/html.js +0 -22
  92. package/lib/config/images.js +0 -4
  93. package/lib/config/index.js +0 -63
  94. package/lib/config/logs.js +0 -11
  95. package/lib/config/paths.js +0 -46
  96. package/lib/config/pwa.js +0 -24
  97. package/lib/config/roots.js +0 -9
  98. package/lib/config/scripts.js +0 -162
  99. package/lib/config/server.js +0 -84
  100. package/lib/config/spinner.js +0 -9
  101. package/lib/config/styles.js +0 -67
  102. package/lib/hello.js +0 -19
  103. package/lib/hooks/index.js +0 -94
  104. package/lib/hooks/maker.js +0 -59
  105. package/lib/index.js +0 -5
  106. package/lib/middlewares/history.js +0 -6
  107. package/lib/middlewares/index.js +0 -11
  108. package/lib/middlewares/proxy.js +0 -28
  109. package/lib/middlewares/webpack.js +0 -26
  110. package/lib/plugins/htmlmin.js +0 -39
  111. package/lib/plugins/imagemin.js +0 -95
  112. package/lib/plugins/index.js +0 -27
  113. package/lib/plugins/jsmin.js +0 -37
  114. package/lib/plugins/less.js +0 -97
  115. package/lib/plugins/plumber.js +0 -87
  116. package/lib/plugins/postcss-plugins.js +0 -13
  117. package/lib/plugins/postcss.js +0 -117
  118. package/lib/plugins/rename.js +0 -51
  119. package/lib/plugins/replace.js +0 -91
  120. package/lib/plugins/sass-legacy.js +0 -131
  121. package/lib/plugins/sass.js +0 -134
  122. package/lib/plugins/sftp.js +0 -288
  123. package/lib/tasks/default.js +0 -46
  124. package/lib/tasks/foundation/balm.js +0 -72
  125. package/lib/tasks/foundation/balm_style.js +0 -65
  126. package/lib/tasks/foundation/index.js +0 -10
  127. package/lib/tasks/index.js +0 -48
  128. package/lib/tasks/private/build.js +0 -18
  129. package/lib/tasks/private/cache.js +0 -31
  130. package/lib/tasks/private/clean.js +0 -69
  131. package/lib/tasks/private/end.js +0 -24
  132. package/lib/tasks/private/extra.js +0 -35
  133. package/lib/tasks/private/font.js +0 -15
  134. package/lib/tasks/private/image.js +0 -37
  135. package/lib/tasks/private/index.js +0 -34
  136. package/lib/tasks/private/lint.js +0 -20
  137. package/lib/tasks/private/media.js +0 -15
  138. package/lib/tasks/private/modernizr.js +0 -59
  139. package/lib/tasks/private/pwa-cache.js +0 -32
  140. package/lib/tasks/private/script.js +0 -9
  141. package/lib/tasks/private/sprite.js +0 -105
  142. package/lib/tasks/private/start.js +0 -16
  143. package/lib/tasks/private/style.js +0 -9
  144. package/lib/tasks/private/workbox-sw.js +0 -15
  145. package/lib/tasks/public/copy.js +0 -16
  146. package/lib/tasks/public/esbuild.js +0 -18
  147. package/lib/tasks/public/ftp.js +0 -39
  148. package/lib/tasks/public/html.js +0 -65
  149. package/lib/tasks/public/index.js +0 -38
  150. package/lib/tasks/public/jsmin.js +0 -23
  151. package/lib/tasks/public/less.js +0 -21
  152. package/lib/tasks/public/postcss.js +0 -16
  153. package/lib/tasks/public/publish.js +0 -51
  154. package/lib/tasks/public/pwa.js +0 -84
  155. package/lib/tasks/public/remove.js +0 -46
  156. package/lib/tasks/public/replace.js +0 -26
  157. package/lib/tasks/public/rollup.js +0 -17
  158. package/lib/tasks/public/sass.js +0 -27
  159. package/lib/tasks/public/server.js +0 -130
  160. package/lib/tasks/public/url.js +0 -35
  161. package/lib/tasks/public/version.js +0 -23
  162. package/lib/tasks/public/webpack.js +0 -39
  163. package/lib/tasks/public/zip.js +0 -19
  164. package/lib/utilities/address.js +0 -55
  165. package/lib/utilities/cache-core.js +0 -66
  166. package/lib/utilities/color.js +0 -27
  167. package/lib/utilities/compiling.js +0 -24
  168. package/lib/utilities/detect-port.js +0 -100
  169. package/lib/utilities/escape-string-regexp.js +0 -10
  170. package/lib/utilities/file.js +0 -70
  171. package/lib/utilities/index.js +0 -8
  172. package/lib/utilities/loading.js +0 -37
  173. package/lib/utilities/logger.js +0 -99
  174. package/lib/utilities/merge-stream.js +0 -38
  175. package/lib/utilities/namespace.js +0 -11
  176. package/lib/utilities/public-url.js +0 -37
  177. package/lib/utilities/replacestream.js +0 -106
  178. package/lib/utilities/typeof.js +0 -5
  179. package/lib/utilities/utils.js +0 -43
  180. package/tslib/balm.js +0 -44
  181. package/tslib/bootstrap/check_config.js +0 -36
  182. package/tslib/bootstrap/index.js +0 -82
  183. package/tslib/bundler/esbuild/build.js +0 -30
  184. package/tslib/bundler/esbuild/index.js +0 -11
  185. package/tslib/bundler/esbuild/options.js +0 -7
  186. package/tslib/bundler/esbuild/transform.js +0 -38
  187. package/tslib/bundler/rollup/build.js +0 -17
  188. package/tslib/bundler/rollup/entry.js +0 -24
  189. package/tslib/bundler/rollup/index.js +0 -21
  190. package/tslib/bundler/rollup/output.js +0 -23
  191. package/tslib/bundler/rollup/plugins.js +0 -27
  192. package/tslib/bundler/webpack/config/common.js +0 -130
  193. package/tslib/bundler/webpack/config/dev.js +0 -14
  194. package/tslib/bundler/webpack/config/env.js +0 -44
  195. package/tslib/bundler/webpack/config/index.js +0 -8
  196. package/tslib/bundler/webpack/config/prod.js +0 -43
  197. package/tslib/bundler/webpack/config/regex.js +0 -10
  198. package/tslib/bundler/webpack/entry.js +0 -68
  199. package/tslib/bundler/webpack/index.js +0 -30
  200. package/tslib/bundler/webpack/loaders.js +0 -51
  201. package/tslib/bundler/webpack/output.js +0 -41
  202. package/tslib/bundler/webpack/rules/asset.js +0 -33
  203. package/tslib/bundler/webpack/rules/babel.js +0 -16
  204. package/tslib/bundler/webpack/rules/css.js +0 -95
  205. package/tslib/bundler/webpack/rules/html.js +0 -15
  206. package/tslib/bundler/webpack/rules/index.js +0 -10
  207. package/tslib/config/assets.js +0 -24
  208. package/tslib/config/constants.js +0 -14
  209. package/tslib/config/env.js +0 -43
  210. package/tslib/config/extras.js +0 -6
  211. package/tslib/config/ftp.js +0 -6
  212. package/tslib/config/globals.js +0 -47
  213. package/tslib/config/html.js +0 -17
  214. package/tslib/config/images.js +0 -4
  215. package/tslib/config/index.js +0 -56
  216. package/tslib/config/logs.js +0 -6
  217. package/tslib/config/paths.js +0 -30
  218. package/tslib/config/pwa.js +0 -18
  219. package/tslib/config/roots.js +0 -8
  220. package/tslib/config/scripts.js +0 -93
  221. package/tslib/config/server.js +0 -33
  222. package/tslib/config/spinner.js +0 -10
  223. package/tslib/config/styles.js +0 -37
  224. package/tslib/hello.js +0 -19
  225. package/tslib/hooks/index.js +0 -75
  226. package/tslib/hooks/maker.js +0 -66
  227. package/tslib/index.js +0 -5
  228. package/tslib/middlewares/history.js +0 -12
  229. package/tslib/middlewares/index.js +0 -18
  230. package/tslib/middlewares/proxy.js +0 -27
  231. package/tslib/middlewares/webpack.js +0 -29
  232. package/tslib/plugins/htmlmin.js +0 -38
  233. package/tslib/plugins/imagemin.js +0 -97
  234. package/tslib/plugins/index.js +0 -27
  235. package/tslib/plugins/jsmin.js +0 -39
  236. package/tslib/plugins/less.js +0 -90
  237. package/tslib/plugins/plumber.js +0 -81
  238. package/tslib/plugins/postcss-plugins.js +0 -24
  239. package/tslib/plugins/postcss.js +0 -111
  240. package/tslib/plugins/rename.js +0 -51
  241. package/tslib/plugins/replace.js +0 -72
  242. package/tslib/plugins/sass-legacy.js +0 -95
  243. package/tslib/plugins/sass.js +0 -109
  244. package/tslib/plugins/sftp.js +0 -258
  245. package/tslib/tasks/default.js +0 -59
  246. package/tslib/tasks/foundation/balm.js +0 -75
  247. package/tslib/tasks/foundation/balm_style.js +0 -68
  248. package/tslib/tasks/foundation/index.js +0 -10
  249. package/tslib/tasks/index.js +0 -43
  250. package/tslib/tasks/private/build.js +0 -15
  251. package/tslib/tasks/private/cache.js +0 -48
  252. package/tslib/tasks/private/clean.js +0 -82
  253. package/tslib/tasks/private/end.js +0 -21
  254. package/tslib/tasks/private/extra.js +0 -34
  255. package/tslib/tasks/private/font.js +0 -12
  256. package/tslib/tasks/private/image.js +0 -42
  257. package/tslib/tasks/private/index.js +0 -34
  258. package/tslib/tasks/private/lint.js +0 -17
  259. package/tslib/tasks/private/media.js +0 -12
  260. package/tslib/tasks/private/modernizr.js +0 -54
  261. package/tslib/tasks/private/pwa-cache.js +0 -32
  262. package/tslib/tasks/private/script.js +0 -9
  263. package/tslib/tasks/private/sprite.js +0 -93
  264. package/tslib/tasks/private/start.js +0 -13
  265. package/tslib/tasks/private/style.js +0 -12
  266. package/tslib/tasks/private/workbox-sw.js +0 -15
  267. package/tslib/tasks/public/copy.js +0 -18
  268. package/tslib/tasks/public/esbuild.js +0 -18
  269. package/tslib/tasks/public/ftp.js +0 -39
  270. package/tslib/tasks/public/html.js +0 -75
  271. package/tslib/tasks/public/index.js +0 -38
  272. package/tslib/tasks/public/jsmin.js +0 -24
  273. package/tslib/tasks/public/less.js +0 -21
  274. package/tslib/tasks/public/postcss.js +0 -16
  275. package/tslib/tasks/public/publish.js +0 -50
  276. package/tslib/tasks/public/pwa.js +0 -87
  277. package/tslib/tasks/public/remove.js +0 -44
  278. package/tslib/tasks/public/replace.js +0 -27
  279. package/tslib/tasks/public/rollup.js +0 -17
  280. package/tslib/tasks/public/sass.js +0 -27
  281. package/tslib/tasks/public/server.js +0 -129
  282. package/tslib/tasks/public/url.js +0 -35
  283. package/tslib/tasks/public/version.js +0 -27
  284. package/tslib/tasks/public/webpack.js +0 -41
  285. package/tslib/tasks/public/zip.js +0 -19
  286. package/tslib/utilities/address.js +0 -48
  287. package/tslib/utilities/cache-core.js +0 -57
  288. package/tslib/utilities/color.js +0 -33
  289. package/tslib/utilities/compiling.js +0 -16
  290. package/tslib/utilities/detect-port.js +0 -91
  291. package/tslib/utilities/escape-string-regexp.js +0 -7
  292. package/tslib/utilities/file.js +0 -78
  293. package/tslib/utilities/index.js +0 -8
  294. package/tslib/utilities/loading.js +0 -25
  295. package/tslib/utilities/logger.js +0 -92
  296. package/tslib/utilities/merge-stream.js +0 -34
  297. package/tslib/utilities/namespace.js +0 -12
  298. package/tslib/utilities/public-url.js +0 -31
  299. package/tslib/utilities/replacestream.js +0 -112
  300. package/tslib/utilities/typeof.js +0 -8
  301. package/tslib/utilities/utils.js +0 -38
@@ -0,0 +1,224 @@
1
+ import {
2
+ CopyTask,
3
+ RemoveTask,
4
+ ReplaceTask,
5
+ PublishTask,
6
+ PwaTask,
7
+ ZipTask,
8
+ FtpTask,
9
+ ServerTask
10
+ } from '../tasks/index.js';
11
+ import { FunctionTask } from '../runner/task.js';
12
+ import { Pipeline } from '../pipeline/pipeline.js';
13
+ import { transformSass } from '../pipeline/transforms/sass.js';
14
+ import { transformLess } from '../pipeline/transforms/less.js';
15
+ import { transformPostcss } from '../pipeline/transforms/postcss.js';
16
+ import { runWebpack } from '../bundler/webpack.js';
17
+ import { runRollup } from '../bundler/rollup.js';
18
+ import { runEsbuild } from '../bundler/esbuild.js';
19
+ import { TaskDAG } from '../runner/dag.js';
20
+ import {
21
+ RenameOptions,
22
+ ReplaceOptions,
23
+ TemplateOption,
24
+ BalmConfig
25
+ } from '../types/index.js';
26
+ import { logger } from '../utilities/logger.js';
27
+
28
+ export class BalmHooks {
29
+ private dag: TaskDAG;
30
+ private _config: BalmConfig;
31
+ private recipeIndex = 0;
32
+
33
+ constructor(dag: TaskDAG, config: BalmConfig) {
34
+ this.dag = dag;
35
+ this._config = config;
36
+ }
37
+
38
+ get config(): BalmConfig {
39
+ return this._config;
40
+ }
41
+
42
+ get env() {
43
+ return this._config.env;
44
+ }
45
+
46
+ private addRecipeTask(fn: (config: BalmConfig) => Promise<void> | void): void {
47
+ this.recipeIndex++;
48
+ const taskName = `recipe:${this.recipeIndex}`;
49
+ this.dag.registerTask(new FunctionTask(taskName, fn));
50
+ }
51
+
52
+ copy(
53
+ input: string | string[],
54
+ output: string,
55
+ options: { rename?: RenameOptions | string | Function } = {}
56
+ ): this {
57
+ this.addRecipeTask(async (config) => {
58
+ const task = new CopyTask({ input, output, rename: options.rename });
59
+ await task.run(config);
60
+ });
61
+ return this;
62
+ }
63
+
64
+ remove(target: string | string[]): this {
65
+ this.addRecipeTask(async (config) => {
66
+ const task = new RemoveTask(target);
67
+ await task.run(config);
68
+ });
69
+ return this;
70
+ }
71
+
72
+ replace(
73
+ input: string | string[],
74
+ output: string,
75
+ options: ReplaceOptions | ReplaceOptions[]
76
+ ): this {
77
+ this.addRecipeTask(async (config) => {
78
+ const task = new ReplaceTask({ input, output, options });
79
+ await task.run(config);
80
+ });
81
+ return this;
82
+ }
83
+
84
+ sass(input: string | string[], output: string, options = {}): this {
85
+ this.addRecipeTask(async (config) => {
86
+ const pipeline = Pipeline.from(input, { cwd: config.workspace });
87
+ pipeline.pipe(transformSass(options));
88
+ pipeline.pipe(transformPostcss({ minify: config.env.isProd }));
89
+ await pipeline.dest(output);
90
+ });
91
+ return this;
92
+ }
93
+
94
+ less(input: string | string[], output: string, options = {}): this {
95
+ this.addRecipeTask(async (config) => {
96
+ const pipeline = Pipeline.from(input, { cwd: config.workspace });
97
+ pipeline.pipe(transformLess(options));
98
+ pipeline.pipe(transformPostcss({ minify: config.env.isProd }));
99
+ await pipeline.dest(output);
100
+ });
101
+ return this;
102
+ }
103
+
104
+ css(input: string | string[], output: string, options = {}): this {
105
+ this.addRecipeTask(async (config) => {
106
+ const pipeline = Pipeline.from(input, { cwd: config.workspace });
107
+ pipeline.pipe(transformPostcss({ minify: config.env.isProd, ...options }));
108
+ await pipeline.dest(output);
109
+ });
110
+ return this;
111
+ }
112
+
113
+ webpack(entry?: any, output?: string, customOptions: any = {}): this {
114
+ this.addRecipeTask(async (config) => {
115
+ const mergedOptions = {
116
+ ...customOptions,
117
+ ...(entry ? { entry } : {}),
118
+ output: {
119
+ ...(output ? { path: output } : {}),
120
+ ...customOptions.output
121
+ }
122
+ };
123
+ await runWebpack(config, mergedOptions);
124
+ });
125
+ return this;
126
+ }
127
+
128
+ rollup(
129
+ input?: any,
130
+ output?: any,
131
+ customInputOptions: any = {},
132
+ customOutputOptions: any = {}
133
+ ): this {
134
+ this.addRecipeTask(async (config) => {
135
+ let inputOptions: any = {};
136
+ let outputOptions: any = {};
137
+
138
+ if (typeof input === 'object' && !Array.isArray(input) && input.input) {
139
+ inputOptions = { ...input, ...customInputOptions };
140
+ outputOptions =
141
+ typeof output === 'object'
142
+ ? { ...output, ...customOutputOptions }
143
+ : { ...(output ? { dir: output } : {}), ...customOutputOptions };
144
+ } else {
145
+ inputOptions = { ...(input ? { input } : {}), ...customInputOptions };
146
+ outputOptions =
147
+ typeof output === 'object'
148
+ ? { ...output, ...customOutputOptions }
149
+ : { ...(output ? { dir: output } : {}), ...customOutputOptions };
150
+ }
151
+
152
+ await runRollup(config, inputOptions, outputOptions);
153
+ });
154
+ return this;
155
+ }
156
+
157
+ esbuild(entryPoints?: any, outdir?: string, customOptions = {}): this {
158
+ this.addRecipeTask(async (config) => {
159
+ await runEsbuild(config, {
160
+ ...(entryPoints ? { entryPoints } : {}),
161
+ ...(outdir ? { outdir } : {}),
162
+ ...customOptions
163
+ });
164
+ });
165
+ return this;
166
+ }
167
+
168
+ js(input?: any, output?: any, options = {}): this {
169
+ logger.warn('balm api', '`mix.js` is deprecated, please use `mix.webpack`/`mix.rollup`/`mix.esbuild` instead.');
170
+ return this.webpack(input, output, options);
171
+ }
172
+
173
+ publish(
174
+ input?: string | TemplateOption[],
175
+ output?: string,
176
+ renameOptions?: string | Function | RenameOptions
177
+ ): this {
178
+ this.addRecipeTask(async (config) => {
179
+ const task = new PublishTask({ input, output, renameOptions });
180
+ await task.run(config);
181
+ });
182
+ return this;
183
+ }
184
+
185
+ generateSW(options = {}): this {
186
+ this.addRecipeTask(async (config) => {
187
+ const task = new PwaTask({ mode: 'generateSW', options });
188
+ await task.run(config);
189
+ });
190
+ return this;
191
+ }
192
+
193
+ injectManifest(options = {}): this {
194
+ this.addRecipeTask(async (config) => {
195
+ const task = new PwaTask({ mode: 'injectManifest', options });
196
+ await task.run(config);
197
+ });
198
+ return this;
199
+ }
200
+
201
+ ftp(localFiles?: string | string[], options = {}): this {
202
+ this.addRecipeTask(async (config) => {
203
+ const task = new FtpTask({ localFiles, options });
204
+ await task.run(config);
205
+ });
206
+ return this;
207
+ }
208
+
209
+ zip(input?: string | string[], output?: string, filename?: string): this {
210
+ this.addRecipeTask(async (config) => {
211
+ const task = new ZipTask({ input, output, filename });
212
+ await task.run(config);
213
+ });
214
+ return this;
215
+ }
216
+
217
+ serve(): this {
218
+ this.addRecipeTask(async (config) => {
219
+ const task = new ServerTask();
220
+ await task.run(config);
221
+ });
222
+ return this;
223
+ }
224
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { balm, Balm } from './balm.js';
2
+
3
+ export default balm;
4
+ export { Balm, balm };
5
+ export * from './types/index.js';
6
+ export * from './config/index.js';
7
+ export * from './runner/task.js';
8
+ export * from './runner/dag.js';
9
+ export * from './pipeline/index.js';
10
+ export * from './bundler/index.js';
11
+ export * from './tasks/index.js';
12
+ export * from './hooks/index.js';
13
+ export * from './utilities/logger.js';
14
+ export * from './utilities/file.js';
15
+ export * from './utilities/utils.js';
16
+ export * from './utilities/workspace.js';
@@ -0,0 +1,69 @@
1
+ import path from 'node:path';
2
+
3
+ export interface VirtualFileOptions {
4
+ cwd: string;
5
+ base: string;
6
+ path: string;
7
+ contents: Buffer | string;
8
+ }
9
+
10
+ export class VirtualFile {
11
+ cwd: string;
12
+ base: string;
13
+ path: string;
14
+ contents: Buffer;
15
+ revPathOriginal?: string;
16
+
17
+ constructor(options: VirtualFileOptions) {
18
+ this.cwd = options.cwd;
19
+ this.base = options.base;
20
+ this.path = options.path;
21
+ this.contents = typeof options.contents === 'string'
22
+ ? Buffer.from(options.contents)
23
+ : options.contents;
24
+ }
25
+
26
+ get relative(): string {
27
+ return path.relative(this.base, this.path);
28
+ }
29
+
30
+ get extname(): string {
31
+ return path.extname(this.path);
32
+ }
33
+ set extname(val: string) {
34
+ const dir = path.dirname(this.path);
35
+ const basename = path.basename(this.path, this.extname);
36
+ this.path = path.join(dir, `${basename}${val.startsWith('.') ? val : `.${val}`}`);
37
+ }
38
+
39
+ get basename(): string {
40
+ return path.basename(this.path);
41
+ }
42
+ set basename(val: string) {
43
+ const dir = path.dirname(this.path);
44
+ this.path = path.join(dir, val);
45
+ }
46
+
47
+ get stem(): string {
48
+ return path.basename(this.path, this.extname);
49
+ }
50
+ set stem(val: string) {
51
+ const dir = path.dirname(this.path);
52
+ this.path = path.join(dir, `${val}${this.extname}`);
53
+ }
54
+
55
+ toString(encoding: BufferEncoding = 'utf8'): string {
56
+ return this.contents.toString(encoding);
57
+ }
58
+
59
+ clone(): VirtualFile {
60
+ const cloned = new VirtualFile({
61
+ cwd: this.cwd,
62
+ base: this.base,
63
+ path: this.path,
64
+ contents: Buffer.from(this.contents)
65
+ });
66
+ cloned.revPathOriginal = this.revPathOriginal;
67
+ return cloned;
68
+ }
69
+ }
@@ -0,0 +1,10 @@
1
+ export * from './file.js';
2
+ export * from './pipeline.js';
3
+ export * from './transforms/sass.js';
4
+ export * from './transforms/less.js';
5
+ export * from './transforms/postcss.js';
6
+ export * from './transforms/htmlmin.js';
7
+ export * from './transforms/terser.js';
8
+ export * from './transforms/replace.js';
9
+ export * from './transforms/rename.js';
10
+ export * from './transforms/cache.js';
@@ -0,0 +1,135 @@
1
+ import path from 'node:path';
2
+ import fg from 'fast-glob';
3
+ import fs from 'node:fs/promises';
4
+ import { VirtualFile } from './file.js';
5
+ import { file as fsUtil } from '../utilities/file.js';
6
+
7
+ export type TransformFn = (
8
+ file: VirtualFile
9
+ ) => Promise<VirtualFile | VirtualFile[] | null | void> | VirtualFile | VirtualFile[] | null | void;
10
+
11
+ export interface PipelineOptions {
12
+ cwd?: string;
13
+ base?: string;
14
+ allowEmpty?: boolean;
15
+ }
16
+
17
+ export class Pipeline {
18
+ private cwd: string;
19
+ private base?: string;
20
+ private allowEmpty: boolean;
21
+ private files: VirtualFile[] = [];
22
+ private transforms: TransformFn[] = [];
23
+
24
+ constructor(options: PipelineOptions = {}) {
25
+ this.cwd = options.cwd || process.cwd();
26
+ this.base = options.base;
27
+ this.allowEmpty = options.allowEmpty ?? true;
28
+ }
29
+
30
+ static from(
31
+ patterns: string | string[],
32
+ options: PipelineOptions = {}
33
+ ): Pipeline {
34
+ const pipeline = new Pipeline(options);
35
+ return pipeline.src(patterns);
36
+ }
37
+
38
+ src(patterns: string | string[], options: PipelineOptions = {}): this {
39
+ if (options.cwd) this.cwd = options.cwd;
40
+ if (options.base) this.base = options.base;
41
+ if (options.allowEmpty !== undefined) this.allowEmpty = options.allowEmpty;
42
+
43
+ this.transforms.push(async () => {
44
+ const patternArr = Array.isArray(patterns) ? patterns : [patterns];
45
+ const posPatterns = patternArr.filter((p) => !p.startsWith('!'));
46
+ const ignorePatterns = patternArr.filter((p) => p.startsWith('!')).map((p) => p.slice(1));
47
+
48
+ const fileList: VirtualFile[] = [];
49
+ for (const p of posPatterns) {
50
+ let patternBase = this.base;
51
+ if (!patternBase) {
52
+ const globIdx = p.search(/[*?{[\\]/);
53
+ if (globIdx !== -1) {
54
+ patternBase = path.resolve(this.cwd, p.slice(0, globIdx));
55
+ } else {
56
+ patternBase = path.resolve(this.cwd, path.dirname(p));
57
+ }
58
+ }
59
+
60
+ const matched = await fg(p, {
61
+ cwd: this.cwd,
62
+ absolute: true,
63
+ dot: true,
64
+ onlyFiles: true,
65
+ ignore: ignorePatterns
66
+ });
67
+
68
+ for (const filePath of matched) {
69
+ const buffer = await fs.readFile(filePath);
70
+ fileList.push(
71
+ new VirtualFile({
72
+ cwd: this.cwd,
73
+ base: patternBase,
74
+ path: filePath,
75
+ contents: buffer
76
+ })
77
+ );
78
+ }
79
+ }
80
+
81
+ if (!fileList.length && !this.allowEmpty) {
82
+ throw new Error(`File not found with pattern: ${patterns}`);
83
+ }
84
+
85
+ this.files = fileList;
86
+ });
87
+
88
+ return this;
89
+ }
90
+
91
+ pipe(transform: TransformFn): this {
92
+ this.transforms.push(transform);
93
+ return this;
94
+ }
95
+
96
+ async process(): Promise<VirtualFile[]> {
97
+ let isSrcLoaded = false;
98
+ for (const transform of this.transforms) {
99
+ if (!isSrcLoaded) {
100
+ // Initial src loader
101
+ await transform(null as any);
102
+ isSrcLoaded = true;
103
+ } else {
104
+ if (this.files.length === 0) break;
105
+ const nextFiles: VirtualFile[] = [];
106
+ for (const file of this.files) {
107
+ const result = await transform(file);
108
+ if (result === undefined || result === file) {
109
+ nextFiles.push(file);
110
+ } else if (Array.isArray(result)) {
111
+ nextFiles.push(...result);
112
+ } else if (result !== null) {
113
+ nextFiles.push(result);
114
+ }
115
+ }
116
+ this.files = nextFiles;
117
+ }
118
+ }
119
+ return this.files;
120
+ }
121
+
122
+ async dest(outDir: string): Promise<string[]> {
123
+ const files = await this.process();
124
+ const writtenPaths: string[] = [];
125
+ const absOutDir = path.isAbsolute(outDir) ? outDir : path.resolve(this.cwd, outDir);
126
+
127
+ for (const file of files) {
128
+ const targetPath = path.resolve(absOutDir, file.relative);
129
+ await fsUtil.writeFile(targetPath, file.contents);
130
+ writtenPaths.push(targetPath);
131
+ }
132
+
133
+ return writtenPaths;
134
+ }
135
+ }
@@ -0,0 +1,146 @@
1
+ import path from 'node:path';
2
+ import picomatch from 'picomatch';
3
+ import { VirtualFile } from '../file.js';
4
+ import { file as fsUtil } from '../../utilities/file.js';
5
+
6
+ export interface CacheTransformOptions {
7
+ fileNameManifest?: string;
8
+ dontRenameFile?: (string | RegExp)[];
9
+ dontUpdateReference?: (string | RegExp)[];
10
+ hashLength?: number;
11
+ [key: string]: any;
12
+ }
13
+
14
+ function matchesAny(filePath: string, rules?: (string | RegExp)[]): boolean {
15
+ if (!rules || !rules.length) return false;
16
+ const normalized = filePath.replace(/\\/g, '/');
17
+ const baseName = path.basename(normalized);
18
+ for (const rawRule of rules) {
19
+ if (typeof rawRule === 'string') {
20
+ const rule = rawRule.replace(/\\/g, '/');
21
+ const cleanRule = rule.replace(/^dist\//, '').replace(/^\.\/dist\//, '');
22
+ if (
23
+ normalized.endsWith(rule) ||
24
+ normalized.endsWith(cleanRule) ||
25
+ baseName === rule ||
26
+ baseName === cleanRule ||
27
+ baseName.endsWith(rule) ||
28
+ picomatch.isMatch(normalized, rule) ||
29
+ picomatch.isMatch(normalized, cleanRule) ||
30
+ picomatch.isMatch(normalized, `**/${cleanRule}`) ||
31
+ picomatch.isMatch(baseName, rule) ||
32
+ picomatch.isMatch(baseName, cleanRule)
33
+ ) {
34
+ return true;
35
+ }
36
+ } else if (rawRule instanceof RegExp) {
37
+ if (rawRule.test(normalized) || rawRule.test(baseName)) return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+
43
+ export class AssetRevisioner {
44
+ private options: CacheTransformOptions;
45
+ private manifest: Record<string, string> = {};
46
+
47
+ constructor(options: CacheTransformOptions = {}) {
48
+ const baseDontRename = [
49
+ '.html',
50
+ 'favicon.ico',
51
+ 'manifest.json',
52
+ 'robots.txt',
53
+ '*.ico',
54
+ 'workbox-sw.js',
55
+ 'workbox-sw.js.map',
56
+ 'service-worker.js',
57
+ 'sw.js'
58
+ ];
59
+ this.options = {
60
+ fileNameManifest: 'rev-manifest.json',
61
+ dontRenameFile: baseDontRename,
62
+ dontUpdateReference: [],
63
+ hashLength: 8,
64
+ ...options
65
+ };
66
+ if (options.dontRenameFile) {
67
+ this.options.dontRenameFile = [
68
+ ...baseDontRename,
69
+ ...options.dontRenameFile
70
+ ];
71
+ }
72
+ }
73
+
74
+ process(files: VirtualFile[]): VirtualFile[] {
75
+ const hashLength = this.options.hashLength || 8;
76
+ const outputFiles: VirtualFile[] = [];
77
+
78
+ // Phase 1: Calculate hashes and renames
79
+ for (const file of files) {
80
+ const origRelative = file.relative;
81
+ file.revPathOriginal = file.path;
82
+
83
+ // Check if file is already hashed (e.g. app.0e94aeee.js or chunk.22810145.js)
84
+ const alreadyHashed = /\.[a-f0-9]{8}$/i.test(file.stem);
85
+
86
+ const shouldRename =
87
+ !alreadyHashed &&
88
+ !matchesAny(file.path, this.options.dontRenameFile) &&
89
+ !matchesAny(file.relative, this.options.dontRenameFile);
90
+
91
+ if (shouldRename) {
92
+ const fileHash = fsUtil.hash(file.contents, hashLength);
93
+ const dir = path.dirname(file.path);
94
+ const newFilename = `${file.stem}.${fileHash}${file.extname}`;
95
+ file.path = path.join(dir, newFilename);
96
+ }
97
+
98
+ if (alreadyHashed) {
99
+ const unhashedRelative = file.relative.replace(/\.[a-f0-9]{8}(\.[^.]+)$/i, '$1');
100
+ this.manifest[unhashedRelative] = file.relative;
101
+ }
102
+ this.manifest[origRelative] = file.relative;
103
+ outputFiles.push(file);
104
+ }
105
+
106
+ // Phase 2: Rewrite references
107
+ const manifestEntries = Object.entries(this.manifest)
108
+ .filter(([orig, reved]) => orig !== reved)
109
+ .sort((a, b) => b[0].length - a[0].length);
110
+
111
+ for (const file of outputFiles) {
112
+ const shouldUpdate =
113
+ !matchesAny(file.path, this.options.dontUpdateReference) &&
114
+ !matchesAny(file.relative, this.options.dontUpdateReference);
115
+
116
+ if (shouldUpdate && /\.(html|css|js)$/i.test(file.extname)) {
117
+ let content = file.toString();
118
+ for (const [orig, reved] of manifestEntries) {
119
+ // Replace full relative path first
120
+ content = content.replaceAll(orig, reved);
121
+
122
+ // Replace basename with boundary check to avoid substring collision (e.g. .woff matching .woff2)
123
+ const origBase = path.basename(orig);
124
+ const revedBase = path.basename(reved);
125
+ const escapedBase = origBase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
126
+ const baseRegex = new RegExp(`${escapedBase}(?![a-zA-Z0-9_])`, 'g');
127
+ content = content.replace(baseRegex, revedBase);
128
+ }
129
+ file.contents = Buffer.from(content);
130
+ }
131
+ }
132
+
133
+ // Phase 3: Add manifest file
134
+ const manifestName = this.options.fileNameManifest || 'rev-manifest.json';
135
+ const firstBase = files[0]?.base || process.cwd();
136
+ const manifestFile = new VirtualFile({
137
+ cwd: files[0]?.cwd || process.cwd(),
138
+ base: firstBase,
139
+ path: path.join(firstBase, manifestName),
140
+ contents: JSON.stringify(this.manifest, null, 2)
141
+ });
142
+ outputFiles.push(manifestFile);
143
+
144
+ return outputFiles;
145
+ }
146
+ }
@@ -0,0 +1,18 @@
1
+ import { minify } from 'html-minifier-terser';
2
+ import { TransformFn } from '../pipeline.js';
3
+ import { logger } from '../../utilities/logger.js';
4
+
5
+ export function transformHtmlmin(options: Record<string, any> = {}): TransformFn {
6
+ return async (file) => {
7
+ if (!/\.html$/i.test(file.extname)) return file;
8
+
9
+ try {
10
+ const minified = await minify(file.toString(), options);
11
+ file.contents = Buffer.from(minified);
12
+ return file;
13
+ } catch (err: any) {
14
+ logger.error('htmlmin', `Error minifying ${file.path}: ${err.message}`);
15
+ throw err;
16
+ }
17
+ };
18
+ }
@@ -0,0 +1,75 @@
1
+ import less from 'less';
2
+ import path from 'node:path';
3
+ import { TransformFn } from '../pipeline.js';
4
+ import { logger } from '../../utilities/logger.js';
5
+
6
+ export interface LessTransformOptions {
7
+ paths?: string[];
8
+ alias?: Record<string, string>;
9
+ [key: string]: any;
10
+ }
11
+
12
+ class AliasFileManager extends less.FileManager {
13
+ private alias: Record<string, string>;
14
+
15
+ constructor(alias: Record<string, string> = {}) {
16
+ super();
17
+ this.alias = alias;
18
+ }
19
+
20
+ supports(filename: string): boolean {
21
+ const clean = filename.startsWith('~') ? filename.slice(1) : filename;
22
+ return Object.keys(this.alias).some((k) => clean === k || clean.startsWith(`${k}/`));
23
+ }
24
+
25
+ loadFile(filename: string, currentDirectory: string, options: any, environment: any): Promise<any> {
26
+ const clean = filename.startsWith('~') ? filename.slice(1) : filename;
27
+ for (const [k, v] of Object.entries(this.alias)) {
28
+ if (clean === k) {
29
+ return super.loadFile(v, currentDirectory, options, environment);
30
+ } else if (clean.startsWith(`${k}/`)) {
31
+ const sub = clean.slice(k.length + 1);
32
+ return super.loadFile(path.join(v, sub), currentDirectory, options, environment);
33
+ }
34
+ }
35
+ return super.loadFile(filename, currentDirectory, options, environment);
36
+ }
37
+ }
38
+
39
+ export function transformLess(options: LessTransformOptions = {}): TransformFn {
40
+ return async (file) => {
41
+ if (!/\.less$/i.test(file.extname)) return file;
42
+ if (path.basename(file.path).startsWith('_')) return null;
43
+
44
+ try {
45
+ const paths = [
46
+ path.dirname(file.path),
47
+ path.resolve(file.cwd, 'node_modules'),
48
+ ...(options.paths || [])
49
+ ];
50
+
51
+ const plugins = [...(options.plugins || [])];
52
+ if (options.alias && Object.keys(options.alias).length > 0) {
53
+ plugins.push({
54
+ install: (lessInstance: any, pluginManager: any) => {
55
+ pluginManager.addFileManager(new AliasFileManager(options.alias));
56
+ }
57
+ });
58
+ }
59
+
60
+ const result = await less.render(file.toString(), {
61
+ filename: file.path,
62
+ paths,
63
+ ...options,
64
+ plugins
65
+ });
66
+
67
+ file.contents = Buffer.from(result.css);
68
+ file.extname = '.css';
69
+ return file;
70
+ } catch (err: any) {
71
+ logger.error('less', `Error compiling ${file.path}: ${err.message}`);
72
+ throw err;
73
+ }
74
+ };
75
+ }