balm-core 5.2.0 → 6.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/dist/index.cjs +2191 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +559 -0
  4. package/dist/index.d.ts +559 -0
  5. package/dist/index.js +2098 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +48 -123
  8. package/src/balm.ts +103 -0
  9. package/src/bundler/esbuild.ts +47 -0
  10. package/src/bundler/index.ts +3 -0
  11. package/src/bundler/rollup.ts +39 -0
  12. package/src/bundler/webpack.ts +82 -0
  13. package/src/config/constants.ts +12 -0
  14. package/src/config/defaults.ts +209 -0
  15. package/src/config/index.ts +112 -0
  16. package/src/hooks/index.ts +192 -0
  17. package/src/index.ts +16 -0
  18. package/src/pipeline/file.ts +69 -0
  19. package/src/pipeline/index.ts +10 -0
  20. package/src/pipeline/pipeline.ts +132 -0
  21. package/src/pipeline/transforms/cache.ts +96 -0
  22. package/src/pipeline/transforms/htmlmin.ts +18 -0
  23. package/src/pipeline/transforms/less.ts +75 -0
  24. package/src/pipeline/transforms/postcss.ts +43 -0
  25. package/src/pipeline/transforms/rename.ts +37 -0
  26. package/src/pipeline/transforms/replace.ts +30 -0
  27. package/src/pipeline/transforms/sass.ts +106 -0
  28. package/src/pipeline/transforms/terser.ts +20 -0
  29. package/src/runner/dag.ts +77 -0
  30. package/src/runner/task.ts +38 -0
  31. package/src/shims.d.ts +5 -0
  32. package/src/tasks/cache.ts +58 -0
  33. package/src/tasks/clean.ts +21 -0
  34. package/src/tasks/copy.ts +36 -0
  35. package/src/tasks/ftp.ts +90 -0
  36. package/src/tasks/html.ts +78 -0
  37. package/src/tasks/index.ts +13 -0
  38. package/src/tasks/publish.ts +71 -0
  39. package/src/tasks/pwa.ts +49 -0
  40. package/src/tasks/remove.ts +24 -0
  41. package/src/tasks/replace.ts +34 -0
  42. package/src/tasks/script.ts +28 -0
  43. package/src/tasks/server.ts +89 -0
  44. package/src/tasks/style.ts +57 -0
  45. package/src/tasks/zip.ts +65 -0
  46. package/src/types/index.ts +200 -0
  47. package/src/utilities/file.ts +120 -0
  48. package/src/utilities/logger.ts +63 -0
  49. package/src/utilities/utils.ts +46 -0
  50. package/src/utilities/workspace.ts +38 -0
  51. package/src/watcher/index.ts +100 -0
  52. package/tsconfig.json +8 -0
  53. package/tsup.config.ts +12 -0
  54. package/README.md +0 -196
  55. package/index.d.ts +0 -360
  56. package/lib/balm.js +0 -49
  57. package/lib/bootstrap/check_config.js +0 -43
  58. package/lib/bootstrap/index.js +0 -87
  59. package/lib/bundler/esbuild/build.js +0 -26
  60. package/lib/bundler/esbuild/index.js +0 -7
  61. package/lib/bundler/esbuild/options.js +0 -7
  62. package/lib/bundler/esbuild/transform.js +0 -36
  63. package/lib/bundler/rollup/build.js +0 -52
  64. package/lib/bundler/rollup/entry.js +0 -28
  65. package/lib/bundler/rollup/index.js +0 -20
  66. package/lib/bundler/rollup/output.js +0 -20
  67. package/lib/bundler/rollup/plugins.js +0 -31
  68. package/lib/bundler/webpack/config/common.js +0 -135
  69. package/lib/bundler/webpack/config/dev.js +0 -12
  70. package/lib/bundler/webpack/config/env.js +0 -67
  71. package/lib/bundler/webpack/config/index.js +0 -6
  72. package/lib/bundler/webpack/config/prod.js +0 -39
  73. package/lib/bundler/webpack/config/regex.js +0 -10
  74. package/lib/bundler/webpack/entry.js +0 -65
  75. package/lib/bundler/webpack/index.js +0 -32
  76. package/lib/bundler/webpack/loaders.js +0 -50
  77. package/lib/bundler/webpack/output.js +0 -41
  78. package/lib/bundler/webpack/rules/asset.js +0 -33
  79. package/lib/bundler/webpack/rules/babel.js +0 -19
  80. package/lib/bundler/webpack/rules/css.js +0 -109
  81. package/lib/bundler/webpack/rules/html.js +0 -15
  82. package/lib/bundler/webpack/rules/index.js +0 -10
  83. package/lib/config/assets.js +0 -30
  84. package/lib/config/constants.js +0 -14
  85. package/lib/config/env.js +0 -35
  86. package/lib/config/extras.js +0 -6
  87. package/lib/config/ftp.js +0 -6
  88. package/lib/config/globals.js +0 -43
  89. package/lib/config/html.js +0 -22
  90. package/lib/config/images.js +0 -4
  91. package/lib/config/index.js +0 -63
  92. package/lib/config/logs.js +0 -11
  93. package/lib/config/paths.js +0 -46
  94. package/lib/config/pwa.js +0 -24
  95. package/lib/config/roots.js +0 -9
  96. package/lib/config/scripts.js +0 -162
  97. package/lib/config/server.js +0 -84
  98. package/lib/config/spinner.js +0 -9
  99. package/lib/config/styles.js +0 -67
  100. package/lib/hello.js +0 -19
  101. package/lib/hooks/index.js +0 -94
  102. package/lib/hooks/maker.js +0 -59
  103. package/lib/index.js +0 -5
  104. package/lib/middlewares/history.js +0 -6
  105. package/lib/middlewares/index.js +0 -11
  106. package/lib/middlewares/proxy.js +0 -28
  107. package/lib/middlewares/webpack.js +0 -26
  108. package/lib/plugins/htmlmin.js +0 -39
  109. package/lib/plugins/imagemin.js +0 -95
  110. package/lib/plugins/index.js +0 -27
  111. package/lib/plugins/jsmin.js +0 -37
  112. package/lib/plugins/less.js +0 -97
  113. package/lib/plugins/plumber.js +0 -87
  114. package/lib/plugins/postcss-plugins.js +0 -13
  115. package/lib/plugins/postcss.js +0 -117
  116. package/lib/plugins/rename.js +0 -51
  117. package/lib/plugins/replace.js +0 -91
  118. package/lib/plugins/sass-legacy.js +0 -131
  119. package/lib/plugins/sass.js +0 -134
  120. package/lib/plugins/sftp.js +0 -288
  121. package/lib/tasks/default.js +0 -46
  122. package/lib/tasks/foundation/balm.js +0 -72
  123. package/lib/tasks/foundation/balm_style.js +0 -65
  124. package/lib/tasks/foundation/index.js +0 -10
  125. package/lib/tasks/index.js +0 -48
  126. package/lib/tasks/private/build.js +0 -18
  127. package/lib/tasks/private/cache.js +0 -31
  128. package/lib/tasks/private/clean.js +0 -69
  129. package/lib/tasks/private/end.js +0 -24
  130. package/lib/tasks/private/extra.js +0 -35
  131. package/lib/tasks/private/font.js +0 -15
  132. package/lib/tasks/private/image.js +0 -37
  133. package/lib/tasks/private/index.js +0 -34
  134. package/lib/tasks/private/lint.js +0 -20
  135. package/lib/tasks/private/media.js +0 -15
  136. package/lib/tasks/private/modernizr.js +0 -59
  137. package/lib/tasks/private/pwa-cache.js +0 -32
  138. package/lib/tasks/private/script.js +0 -9
  139. package/lib/tasks/private/sprite.js +0 -105
  140. package/lib/tasks/private/start.js +0 -16
  141. package/lib/tasks/private/style.js +0 -9
  142. package/lib/tasks/private/workbox-sw.js +0 -15
  143. package/lib/tasks/public/copy.js +0 -16
  144. package/lib/tasks/public/esbuild.js +0 -18
  145. package/lib/tasks/public/ftp.js +0 -39
  146. package/lib/tasks/public/html.js +0 -65
  147. package/lib/tasks/public/index.js +0 -38
  148. package/lib/tasks/public/jsmin.js +0 -23
  149. package/lib/tasks/public/less.js +0 -21
  150. package/lib/tasks/public/postcss.js +0 -16
  151. package/lib/tasks/public/publish.js +0 -51
  152. package/lib/tasks/public/pwa.js +0 -84
  153. package/lib/tasks/public/remove.js +0 -46
  154. package/lib/tasks/public/replace.js +0 -26
  155. package/lib/tasks/public/rollup.js +0 -17
  156. package/lib/tasks/public/sass.js +0 -27
  157. package/lib/tasks/public/server.js +0 -130
  158. package/lib/tasks/public/url.js +0 -35
  159. package/lib/tasks/public/version.js +0 -23
  160. package/lib/tasks/public/webpack.js +0 -39
  161. package/lib/tasks/public/zip.js +0 -19
  162. package/lib/utilities/address.js +0 -55
  163. package/lib/utilities/cache-core.js +0 -66
  164. package/lib/utilities/color.js +0 -27
  165. package/lib/utilities/compiling.js +0 -24
  166. package/lib/utilities/detect-port.js +0 -100
  167. package/lib/utilities/escape-string-regexp.js +0 -10
  168. package/lib/utilities/file.js +0 -70
  169. package/lib/utilities/index.js +0 -8
  170. package/lib/utilities/loading.js +0 -37
  171. package/lib/utilities/logger.js +0 -99
  172. package/lib/utilities/merge-stream.js +0 -38
  173. package/lib/utilities/namespace.js +0 -11
  174. package/lib/utilities/public-url.js +0 -37
  175. package/lib/utilities/replacestream.js +0 -106
  176. package/lib/utilities/typeof.js +0 -5
  177. package/lib/utilities/utils.js +0 -43
  178. package/tslib/balm.js +0 -44
  179. package/tslib/bootstrap/check_config.js +0 -36
  180. package/tslib/bootstrap/index.js +0 -82
  181. package/tslib/bundler/esbuild/build.js +0 -30
  182. package/tslib/bundler/esbuild/index.js +0 -11
  183. package/tslib/bundler/esbuild/options.js +0 -7
  184. package/tslib/bundler/esbuild/transform.js +0 -38
  185. package/tslib/bundler/rollup/build.js +0 -17
  186. package/tslib/bundler/rollup/entry.js +0 -24
  187. package/tslib/bundler/rollup/index.js +0 -21
  188. package/tslib/bundler/rollup/output.js +0 -23
  189. package/tslib/bundler/rollup/plugins.js +0 -27
  190. package/tslib/bundler/webpack/config/common.js +0 -130
  191. package/tslib/bundler/webpack/config/dev.js +0 -14
  192. package/tslib/bundler/webpack/config/env.js +0 -44
  193. package/tslib/bundler/webpack/config/index.js +0 -8
  194. package/tslib/bundler/webpack/config/prod.js +0 -43
  195. package/tslib/bundler/webpack/config/regex.js +0 -10
  196. package/tslib/bundler/webpack/entry.js +0 -68
  197. package/tslib/bundler/webpack/index.js +0 -30
  198. package/tslib/bundler/webpack/loaders.js +0 -51
  199. package/tslib/bundler/webpack/output.js +0 -41
  200. package/tslib/bundler/webpack/rules/asset.js +0 -33
  201. package/tslib/bundler/webpack/rules/babel.js +0 -16
  202. package/tslib/bundler/webpack/rules/css.js +0 -95
  203. package/tslib/bundler/webpack/rules/html.js +0 -15
  204. package/tslib/bundler/webpack/rules/index.js +0 -10
  205. package/tslib/config/assets.js +0 -24
  206. package/tslib/config/constants.js +0 -14
  207. package/tslib/config/env.js +0 -43
  208. package/tslib/config/extras.js +0 -6
  209. package/tslib/config/ftp.js +0 -6
  210. package/tslib/config/globals.js +0 -47
  211. package/tslib/config/html.js +0 -17
  212. package/tslib/config/images.js +0 -4
  213. package/tslib/config/index.js +0 -56
  214. package/tslib/config/logs.js +0 -6
  215. package/tslib/config/paths.js +0 -30
  216. package/tslib/config/pwa.js +0 -18
  217. package/tslib/config/roots.js +0 -8
  218. package/tslib/config/scripts.js +0 -93
  219. package/tslib/config/server.js +0 -33
  220. package/tslib/config/spinner.js +0 -10
  221. package/tslib/config/styles.js +0 -37
  222. package/tslib/hello.js +0 -19
  223. package/tslib/hooks/index.js +0 -75
  224. package/tslib/hooks/maker.js +0 -66
  225. package/tslib/index.js +0 -5
  226. package/tslib/middlewares/history.js +0 -12
  227. package/tslib/middlewares/index.js +0 -18
  228. package/tslib/middlewares/proxy.js +0 -27
  229. package/tslib/middlewares/webpack.js +0 -29
  230. package/tslib/plugins/htmlmin.js +0 -38
  231. package/tslib/plugins/imagemin.js +0 -97
  232. package/tslib/plugins/index.js +0 -27
  233. package/tslib/plugins/jsmin.js +0 -39
  234. package/tslib/plugins/less.js +0 -90
  235. package/tslib/plugins/plumber.js +0 -81
  236. package/tslib/plugins/postcss-plugins.js +0 -24
  237. package/tslib/plugins/postcss.js +0 -111
  238. package/tslib/plugins/rename.js +0 -51
  239. package/tslib/plugins/replace.js +0 -72
  240. package/tslib/plugins/sass-legacy.js +0 -95
  241. package/tslib/plugins/sass.js +0 -109
  242. package/tslib/plugins/sftp.js +0 -258
  243. package/tslib/tasks/default.js +0 -59
  244. package/tslib/tasks/foundation/balm.js +0 -75
  245. package/tslib/tasks/foundation/balm_style.js +0 -68
  246. package/tslib/tasks/foundation/index.js +0 -10
  247. package/tslib/tasks/index.js +0 -43
  248. package/tslib/tasks/private/build.js +0 -15
  249. package/tslib/tasks/private/cache.js +0 -48
  250. package/tslib/tasks/private/clean.js +0 -82
  251. package/tslib/tasks/private/end.js +0 -21
  252. package/tslib/tasks/private/extra.js +0 -34
  253. package/tslib/tasks/private/font.js +0 -12
  254. package/tslib/tasks/private/image.js +0 -42
  255. package/tslib/tasks/private/index.js +0 -34
  256. package/tslib/tasks/private/lint.js +0 -17
  257. package/tslib/tasks/private/media.js +0 -12
  258. package/tslib/tasks/private/modernizr.js +0 -54
  259. package/tslib/tasks/private/pwa-cache.js +0 -32
  260. package/tslib/tasks/private/script.js +0 -9
  261. package/tslib/tasks/private/sprite.js +0 -93
  262. package/tslib/tasks/private/start.js +0 -13
  263. package/tslib/tasks/private/style.js +0 -12
  264. package/tslib/tasks/private/workbox-sw.js +0 -15
  265. package/tslib/tasks/public/copy.js +0 -18
  266. package/tslib/tasks/public/esbuild.js +0 -18
  267. package/tslib/tasks/public/ftp.js +0 -39
  268. package/tslib/tasks/public/html.js +0 -75
  269. package/tslib/tasks/public/index.js +0 -38
  270. package/tslib/tasks/public/jsmin.js +0 -24
  271. package/tslib/tasks/public/less.js +0 -21
  272. package/tslib/tasks/public/postcss.js +0 -16
  273. package/tslib/tasks/public/publish.js +0 -50
  274. package/tslib/tasks/public/pwa.js +0 -87
  275. package/tslib/tasks/public/remove.js +0 -44
  276. package/tslib/tasks/public/replace.js +0 -27
  277. package/tslib/tasks/public/rollup.js +0 -17
  278. package/tslib/tasks/public/sass.js +0 -27
  279. package/tslib/tasks/public/server.js +0 -129
  280. package/tslib/tasks/public/url.js +0 -35
  281. package/tslib/tasks/public/version.js +0 -27
  282. package/tslib/tasks/public/webpack.js +0 -41
  283. package/tslib/tasks/public/zip.js +0 -19
  284. package/tslib/utilities/address.js +0 -48
  285. package/tslib/utilities/cache-core.js +0 -57
  286. package/tslib/utilities/color.js +0 -33
  287. package/tslib/utilities/compiling.js +0 -16
  288. package/tslib/utilities/detect-port.js +0 -91
  289. package/tslib/utilities/escape-string-regexp.js +0 -7
  290. package/tslib/utilities/file.js +0 -78
  291. package/tslib/utilities/index.js +0 -8
  292. package/tslib/utilities/loading.js +0 -25
  293. package/tslib/utilities/logger.js +0 -92
  294. package/tslib/utilities/merge-stream.js +0 -34
  295. package/tslib/utilities/namespace.js +0 -12
  296. package/tslib/utilities/public-url.js +0 -31
  297. package/tslib/utilities/replacestream.js +0 -112
  298. package/tslib/utilities/typeof.js +0 -8
  299. package/tslib/utilities/utils.js +0 -38
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,132 @@
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 matched = await fg(patterns, {
45
+ cwd: this.cwd,
46
+ absolute: true,
47
+ dot: true,
48
+ onlyFiles: true
49
+ });
50
+
51
+ if (!matched.length && !this.allowEmpty) {
52
+ throw new Error(`File not found with pattern: ${patterns}`);
53
+ }
54
+
55
+ // Calculate common base if not explicitly provided
56
+ let effectiveBase = this.base;
57
+ if (!effectiveBase) {
58
+ const patternArr = Array.isArray(patterns) ? patterns : [patterns];
59
+ const firstPos = patternArr.find((p) => !p.startsWith('!'));
60
+ if (firstPos) {
61
+ const globIdx = firstPos.indexOf('*');
62
+ if (globIdx !== -1) {
63
+ effectiveBase = path.resolve(this.cwd, firstPos.slice(0, globIdx));
64
+ } else {
65
+ effectiveBase = path.resolve(this.cwd, path.dirname(firstPos));
66
+ }
67
+ } else {
68
+ effectiveBase = this.cwd;
69
+ }
70
+ }
71
+
72
+ this.files = await Promise.all(
73
+ matched.map(async (filePath) => {
74
+ const buffer = await fs.readFile(filePath);
75
+ return new VirtualFile({
76
+ cwd: this.cwd,
77
+ base: effectiveBase!,
78
+ path: filePath,
79
+ contents: buffer
80
+ });
81
+ })
82
+ );
83
+ });
84
+
85
+ return this;
86
+ }
87
+
88
+ pipe(transform: TransformFn): this {
89
+ this.transforms.push(transform);
90
+ return this;
91
+ }
92
+
93
+ async process(): Promise<VirtualFile[]> {
94
+ let isSrcLoaded = false;
95
+ for (const transform of this.transforms) {
96
+ if (!isSrcLoaded) {
97
+ // Initial src loader
98
+ await transform(null as any);
99
+ isSrcLoaded = true;
100
+ } else {
101
+ if (this.files.length === 0) break;
102
+ const nextFiles: VirtualFile[] = [];
103
+ for (const file of this.files) {
104
+ const result = await transform(file);
105
+ if (result === undefined || result === file) {
106
+ nextFiles.push(file);
107
+ } else if (Array.isArray(result)) {
108
+ nextFiles.push(...result);
109
+ } else if (result !== null) {
110
+ nextFiles.push(result);
111
+ }
112
+ }
113
+ this.files = nextFiles;
114
+ }
115
+ }
116
+ return this.files;
117
+ }
118
+
119
+ async dest(outDir: string): Promise<string[]> {
120
+ const files = await this.process();
121
+ const writtenPaths: string[] = [];
122
+ const absOutDir = path.isAbsolute(outDir) ? outDir : path.resolve(this.cwd, outDir);
123
+
124
+ for (const file of files) {
125
+ const targetPath = path.resolve(absOutDir, file.relative);
126
+ await fsUtil.writeFile(targetPath, file.contents);
127
+ writtenPaths.push(targetPath);
128
+ }
129
+
130
+ return writtenPaths;
131
+ }
132
+ }
@@ -0,0 +1,96 @@
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
+ for (const rule of rules) {
17
+ if (typeof rule === 'string') {
18
+ if (filePath.endsWith(rule) || picomatch.isMatch(filePath, rule)) {
19
+ return true;
20
+ }
21
+ } else if (rule instanceof RegExp) {
22
+ if (rule.test(filePath)) return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+
28
+ export class AssetRevisioner {
29
+ private options: CacheTransformOptions;
30
+ private manifest: Record<string, string> = {};
31
+
32
+ constructor(options: CacheTransformOptions = {}) {
33
+ this.options = {
34
+ fileNameManifest: 'rev-manifest.json',
35
+ dontRenameFile: ['.html'],
36
+ dontUpdateReference: [],
37
+ hashLength: 8,
38
+ ...options
39
+ };
40
+ }
41
+
42
+ process(files: VirtualFile[]): VirtualFile[] {
43
+ const hashLength = this.options.hashLength || 8;
44
+ const outputFiles: VirtualFile[] = [];
45
+
46
+ // Phase 1: Calculate hashes and renames
47
+ for (const file of files) {
48
+ const origRelative = file.relative;
49
+ file.revPathOriginal = file.path;
50
+
51
+ const shouldRename = !matchesAny(file.path, this.options.dontRenameFile) &&
52
+ !matchesAny(file.relative, this.options.dontRenameFile);
53
+
54
+ if (shouldRename) {
55
+ const fileHash = fsUtil.hash(file.contents, hashLength);
56
+ const dir = path.dirname(file.path);
57
+ const newFilename = `${file.stem}.${fileHash}${file.extname}`;
58
+ file.path = path.join(dir, newFilename);
59
+ }
60
+
61
+ this.manifest[origRelative] = file.relative;
62
+ outputFiles.push(file);
63
+ }
64
+
65
+ // Phase 2: Rewrite references
66
+ for (const file of outputFiles) {
67
+ const shouldUpdate = !matchesAny(file.path, this.options.dontUpdateReference) &&
68
+ !matchesAny(file.relative, this.options.dontUpdateReference);
69
+
70
+ if (shouldUpdate && /\.(html|css|js)$/i.test(file.extname)) {
71
+ let content = file.toString();
72
+ for (const [orig, reved] of Object.entries(this.manifest)) {
73
+ if (orig !== reved) {
74
+ const origBase = path.basename(orig);
75
+ const revedBase = path.basename(reved);
76
+ content = content.replaceAll(origBase, revedBase);
77
+ }
78
+ }
79
+ file.contents = Buffer.from(content);
80
+ }
81
+ }
82
+
83
+ // Phase 3: Add manifest file
84
+ const manifestName = this.options.fileNameManifest || 'rev-manifest.json';
85
+ const firstBase = files[0]?.base || process.cwd();
86
+ const manifestFile = new VirtualFile({
87
+ cwd: files[0]?.cwd || process.cwd(),
88
+ base: firstBase,
89
+ path: path.join(firstBase, manifestName),
90
+ contents: JSON.stringify(this.manifest, null, 2)
91
+ });
92
+ outputFiles.push(manifestFile);
93
+
94
+ return outputFiles;
95
+ }
96
+ }
@@ -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
+ }
@@ -0,0 +1,43 @@
1
+ import postcss, { AcceptedPlugin } from 'postcss';
2
+ import autoprefixer from 'autoprefixer';
3
+ import postcssPresetEnv from 'postcss-preset-env';
4
+ import cssnano from 'cssnano';
5
+ import { TransformFn } from '../pipeline.js';
6
+ import { logger } from '../../utilities/logger.js';
7
+
8
+ export interface PostcssTransformOptions {
9
+ plugins?: AcceptedPlugin[];
10
+ minify?: boolean;
11
+ [key: string]: any;
12
+ }
13
+
14
+ export function transformPostcss(options: PostcssTransformOptions = {}): TransformFn {
15
+ const plugins: AcceptedPlugin[] = [
16
+ ...(options.plugins || []),
17
+ autoprefixer(),
18
+ postcssPresetEnv()
19
+ ];
20
+
21
+ if (options.minify) {
22
+ plugins.push(cssnano({ preset: 'default' }));
23
+ }
24
+
25
+ const processor = postcss(plugins);
26
+
27
+ return async (file) => {
28
+ if (!/\.css$/i.test(file.extname)) return file;
29
+
30
+ try {
31
+ const result = await processor.process(file.toString(), {
32
+ from: file.path,
33
+ to: file.path
34
+ });
35
+
36
+ file.contents = Buffer.from(result.css);
37
+ return file;
38
+ } catch (err: any) {
39
+ logger.error('postcss', `Error processing ${file.path}: ${err.message}`);
40
+ throw err;
41
+ }
42
+ };
43
+ }
@@ -0,0 +1,37 @@
1
+ import path from 'node:path';
2
+ import { TransformFn } from '../pipeline.js';
3
+ import { RenameOptions } from '../../types/index.js';
4
+
5
+ export function transformRename(
6
+ options: string | Function | RenameOptions = {}
7
+ ): TransformFn {
8
+ return async (file) => {
9
+ if (typeof options === 'string') {
10
+ file.path = path.resolve(path.dirname(file.path), options);
11
+ return file;
12
+ }
13
+
14
+ if (typeof options === 'function') {
15
+ const parsed = {
16
+ dirname: path.dirname(file.path),
17
+ basename: file.stem,
18
+ extname: file.extname
19
+ };
20
+ const result = options(parsed);
21
+ if (result) {
22
+ if (result.dirname) file.path = path.join(result.dirname, path.basename(file.path));
23
+ if (result.basename) file.stem = result.basename;
24
+ if (result.extname) file.extname = result.extname;
25
+ }
26
+ return file;
27
+ }
28
+
29
+ const { dirname, prefix = '', basename, suffix = '', extname } = options;
30
+ const currentDir = dirname ? path.resolve(file.cwd, dirname) : path.dirname(file.path);
31
+ const base = basename !== undefined ? basename : file.stem;
32
+ const ext = extname !== undefined ? extname : file.extname;
33
+
34
+ file.path = path.join(currentDir, `${prefix}${base}${suffix}${ext.startsWith('.') ? ext : `.${ext}`}`);
35
+ return file;
36
+ };
37
+ }
@@ -0,0 +1,30 @@
1
+ import { TransformFn } from '../pipeline.js';
2
+ import { ReplaceOptions } from '../../types/index.js';
3
+
4
+ export function transformReplace(
5
+ options: ReplaceOptions | ReplaceOptions[] | string | RegExp,
6
+ replacement?: string | ((substring: string, ...args: any[]) => string)
7
+ ): TransformFn {
8
+ let rules: ReplaceOptions[] = [];
9
+
10
+ if (Array.isArray(options)) {
11
+ rules = options;
12
+ } else if (typeof options === 'object' && 'substr' in options) {
13
+ rules = [options];
14
+ } else if (typeof options === 'string' || options instanceof RegExp) {
15
+ rules = [{ substr: options, replacement: replacement || '' }];
16
+ }
17
+
18
+ return async (file) => {
19
+ let content = file.toString();
20
+ for (const rule of rules) {
21
+ if (typeof rule.replacement === 'function') {
22
+ content = content.replace(rule.substr, rule.replacement as any);
23
+ } else {
24
+ content = content.replace(rule.substr, rule.replacement);
25
+ }
26
+ }
27
+ file.contents = Buffer.from(content);
28
+ return file;
29
+ };
30
+ }
@@ -0,0 +1,106 @@
1
+ import * as sass from 'sass';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { TransformFn } from '../pipeline.js';
5
+ import { logger } from '../../utilities/logger.js';
6
+
7
+ export interface SassTransformOptions {
8
+ includePaths?: string[];
9
+ alias?: Record<string, string>;
10
+ style?: 'expanded' | 'compressed';
11
+ sourceMap?: boolean;
12
+ [key: string]: any;
13
+ }
14
+
15
+ function resolveFile(baseDir: string, url: string): string | null {
16
+ const possiblePaths = [
17
+ path.resolve(baseDir, url),
18
+ path.resolve(baseDir, `${url}.scss`),
19
+ path.resolve(baseDir, `${url}.sass`),
20
+ path.resolve(baseDir, `${url}.css`),
21
+ path.resolve(baseDir, path.dirname(url), `_${path.basename(url)}.scss`),
22
+ path.resolve(baseDir, path.dirname(url), `_${path.basename(url)}.sass`),
23
+ path.resolve(baseDir, url, '_index.scss'),
24
+ path.resolve(baseDir, url, '_index.sass'),
25
+ path.resolve(baseDir, url, 'index.scss'),
26
+ path.resolve(baseDir, url, 'index.sass')
27
+ ];
28
+
29
+ for (const p of possiblePaths) {
30
+ try {
31
+ if (fs.existsSync(p) && fs.statSync(p).isFile()) {
32
+ return p;
33
+ }
34
+ } catch {
35
+ // ignore
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+
41
+ export function transformSass(options: SassTransformOptions = {}): TransformFn {
42
+ return async (file) => {
43
+ // Only process .scss / .sass files
44
+ if (!/\.(scss|sass)$/i.test(file.extname)) return file;
45
+ // Skip partials starting with '_'
46
+ if (path.basename(file.path).startsWith('_')) return null;
47
+
48
+ try {
49
+ const searchDirs = [
50
+ path.dirname(file.path),
51
+ path.resolve(file.cwd, 'node_modules'),
52
+ path.resolve(process.cwd(), 'node_modules'),
53
+ path.resolve(process.cwd(), '..', 'node_modules'),
54
+ ...(options.includePaths || [])
55
+ ];
56
+
57
+ const customImporter: sass.FileImporter<'async'> = {
58
+ findFileUrl(url) {
59
+ let cleanUrl = url;
60
+ if (cleanUrl.startsWith('~')) {
61
+ cleanUrl = cleanUrl.slice(1);
62
+ }
63
+
64
+ // 1. Check alias resolution
65
+ if (options.alias) {
66
+ for (const [aliasKey, aliasTarget] of Object.entries(options.alias)) {
67
+ if (cleanUrl === aliasKey) {
68
+ const resolved = resolveFile(path.dirname(aliasTarget), path.basename(aliasTarget));
69
+ if (resolved) return new URL(`file://${resolved}`);
70
+ } else if (cleanUrl.startsWith(`${aliasKey}/`)) {
71
+ const subPath = cleanUrl.slice(aliasKey.length + 1);
72
+ const resolved = resolveFile(aliasTarget, subPath);
73
+ if (resolved) return new URL(`file://${resolved}`);
74
+ }
75
+ }
76
+ }
77
+
78
+ // 2. Check searchDirs
79
+ for (const dir of searchDirs) {
80
+ const resolved = resolveFile(dir, cleanUrl);
81
+ if (resolved) {
82
+ return new URL(`file://${resolved}`);
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ };
88
+
89
+ const result = await sass.compileStringAsync(file.toString(), {
90
+ url: new URL(`file://${file.path}`),
91
+ syntax: file.extname === '.sass' ? 'indented' : 'scss',
92
+ loadPaths: searchDirs,
93
+ importers: [customImporter, new sass.NodePackageImporter()],
94
+ style: options.style || 'expanded',
95
+ sourceMap: options.sourceMap || false
96
+ });
97
+
98
+ file.contents = Buffer.from(result.css);
99
+ file.extname = '.css';
100
+ return file;
101
+ } catch (err: any) {
102
+ logger.error('sass', `Error compiling ${file.path}: ${err.message}`);
103
+ throw err;
104
+ }
105
+ };
106
+ }
@@ -0,0 +1,20 @@
1
+ import { minify, MinifyOptions } from 'terser';
2
+ import { TransformFn } from '../pipeline.js';
3
+ import { logger } from '../../utilities/logger.js';
4
+
5
+ export function transformTerser(options: MinifyOptions = {}): TransformFn {
6
+ return async (file) => {
7
+ if (!/\.js$/i.test(file.extname)) return file;
8
+
9
+ try {
10
+ const result = await minify(file.toString(), options);
11
+ if (result.code) {
12
+ file.contents = Buffer.from(result.code);
13
+ }
14
+ return file;
15
+ } catch (err: any) {
16
+ logger.error('terser', `Error minifying ${file.path}: ${err.message}`);
17
+ throw err;
18
+ }
19
+ };
20
+ }