pulse-js-framework 1.11.3 → 1.11.4

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 (48) hide show
  1. package/cli/analyze.js +21 -8
  2. package/cli/build.js +83 -56
  3. package/cli/dev.js +108 -94
  4. package/cli/docs-test.js +52 -33
  5. package/cli/index.js +81 -51
  6. package/cli/mobile.js +92 -40
  7. package/cli/release.js +64 -46
  8. package/cli/scaffold.js +14 -13
  9. package/compiler/lexer.js +55 -54
  10. package/compiler/parser/core.js +1 -0
  11. package/compiler/parser/state.js +6 -12
  12. package/compiler/parser/style.js +17 -20
  13. package/compiler/parser/view.js +1 -3
  14. package/compiler/preprocessor.js +124 -262
  15. package/compiler/sourcemap.js +10 -4
  16. package/compiler/transformer/expressions.js +122 -106
  17. package/compiler/transformer/index.js +2 -4
  18. package/compiler/transformer/style.js +74 -7
  19. package/compiler/transformer/view.js +86 -36
  20. package/loader/esbuild-plugin-server-components.js +209 -0
  21. package/loader/esbuild-plugin.js +41 -93
  22. package/loader/parcel-plugin.js +37 -97
  23. package/loader/rollup-plugin-server-components.js +30 -169
  24. package/loader/rollup-plugin.js +27 -78
  25. package/loader/shared.js +362 -0
  26. package/loader/swc-plugin.js +65 -82
  27. package/loader/vite-plugin-server-components.js +30 -171
  28. package/loader/vite-plugin.js +25 -10
  29. package/loader/webpack-loader-server-components.js +21 -134
  30. package/loader/webpack-loader.js +25 -80
  31. package/package.json +52 -12
  32. package/runtime/dom-selector.js +2 -1
  33. package/runtime/form.js +4 -3
  34. package/runtime/http.js +6 -1
  35. package/runtime/logger.js +44 -24
  36. package/runtime/router/utils.js +14 -7
  37. package/runtime/security.js +13 -1
  38. package/runtime/server-components/actions-server.js +23 -19
  39. package/runtime/server-components/error-sanitizer.js +18 -18
  40. package/runtime/server-components/security.js +41 -24
  41. package/runtime/ssr-preload.js +5 -3
  42. package/runtime/testing.js +759 -0
  43. package/runtime/utils.js +3 -2
  44. package/server/utils.js +15 -9
  45. package/sw/index.js +2 -0
  46. package/types/loaders.d.ts +1043 -0
  47. package/compiler/parser/_extract.js +0 -393
  48. package/loader/README.md +0 -509
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Pulse Loader - Shared Utilities
3
+ *
4
+ * Common helpers used across all build tool plugins (Vite, Webpack, Rollup,
5
+ * ESBuild, Parcel, SWC) and Server Components plugins.
6
+ *
7
+ * @module pulse-js-framework/loader/shared
8
+ */
9
+
10
+ import {
11
+ preprocessStylesSync,
12
+ isSassAvailable,
13
+ isLessAvailable,
14
+ isStylusAvailable,
15
+ getSassVersion,
16
+ getLessVersion,
17
+ getStylusVersion,
18
+ detectPreprocessor
19
+ } from '../compiler/preprocessor.js';
20
+ import { dirname, relative, posix } from 'path';
21
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
22
+ import { writeFile, mkdir } from 'fs/promises';
23
+
24
+ // ============================================================================
25
+ // Server Components Constants
26
+ // ============================================================================
27
+
28
+ /** Regex to match `__directive: "use client"` in compiled output */
29
+ export const DIRECTIVE_REGEX = /__directive:\s*["']use client["']/;
30
+
31
+ /** Regex to extract component ID from `__componentId: "Name"` */
32
+ export const COMPONENT_ID_REGEX = /__componentId:\s*["'](\w+)["']/;
33
+
34
+ /** Regex to extract component name from `export const Name = {` */
35
+ export const EXPORT_CONST_REGEX = /export const (\w+) = \{/;
36
+
37
+ /** Chunk name prefix for Client Components */
38
+ export const CLIENT_CHUNK_PREFIX = 'client-';
39
+
40
+ /** Default manifest output path */
41
+ export const DEFAULT_MANIFEST_PATH = 'dist/.pulse-manifest.json';
42
+
43
+ /** Default manifest filename */
44
+ export const DEFAULT_MANIFEST_FILENAME = '.pulse-manifest.json';
45
+
46
+ // ============================================================================
47
+ // Preprocessor Cache
48
+ // ============================================================================
49
+
50
+ let preprocessorCache = null;
51
+
52
+ /**
53
+ * Check available preprocessors (cached after first call)
54
+ * @returns {{ sass: boolean, less: boolean, stylus: boolean }}
55
+ */
56
+ export function checkPreprocessors() {
57
+ if (preprocessorCache) return preprocessorCache;
58
+
59
+ preprocessorCache = {
60
+ sass: isSassAvailable(),
61
+ less: isLessAvailable(),
62
+ stylus: isStylusAvailable()
63
+ };
64
+
65
+ return preprocessorCache;
66
+ }
67
+
68
+ /**
69
+ * Reset preprocessor cache (for testing)
70
+ */
71
+ export function resetPreprocessorCache() {
72
+ preprocessorCache = null;
73
+ }
74
+
75
+ // ============================================================================
76
+ // Preprocessor Logging
77
+ // ============================================================================
78
+
79
+ /**
80
+ * Log available preprocessors on plugin startup.
81
+ * @param {string} pluginLabel - e.g. 'Pulse Rollup'
82
+ * @param {{ sassOnly?: boolean, logFn?: Function }} [options]
83
+ */
84
+ export function logPreprocessorAvailability(pluginLabel, options = {}) {
85
+ const { sassOnly = false, logFn = console.log } = options;
86
+ const available = checkPreprocessors();
87
+ const preprocessors = [];
88
+
89
+ if (available.sass) {
90
+ preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
91
+ }
92
+ if (!sassOnly) {
93
+ if (available.less) {
94
+ preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
95
+ }
96
+ if (available.stylus) {
97
+ preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
98
+ }
99
+ }
100
+
101
+ if (preprocessors.length > 0) {
102
+ logFn(`[${pluginLabel}] Preprocessor support: ${preprocessors.join(', ')}`);
103
+ }
104
+ }
105
+
106
+ // ============================================================================
107
+ // CSS Regex Constants
108
+ // ============================================================================
109
+
110
+ /** Regex to extract CSS from compiled .pulse output */
111
+ export const STYLES_MATCH_REGEX = /const styles = `([^`]*)`;/;
112
+
113
+ /** Regex to remove inline style injection block (handles optional SCOPE_ID line) */
114
+ export const INLINE_STYLES_REMOVAL_REGEX =
115
+ /\/\/ Styles\n(?:const SCOPE_ID = '[^']*';\n)?const styles = `[^`]*`;\n\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\n(?:styleEl\.setAttribute\([^)]*\);\n)?styleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/;
116
+
117
+ // ============================================================================
118
+ // CSS Helpers
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Extract CSS from compiled .pulse output code.
123
+ * @param {string} outputCode
124
+ * @returns {{ css: string|null, found: boolean }}
125
+ */
126
+ export function extractCssFromOutput(outputCode) {
127
+ const match = outputCode.match(STYLES_MATCH_REGEX);
128
+ if (!match) return { css: null, found: false };
129
+ return { css: match[1], found: true };
130
+ }
131
+
132
+ /**
133
+ * Remove inline style injection and replace with a custom string.
134
+ * @param {string} outputCode
135
+ * @param {string} replacement
136
+ * @returns {string}
137
+ */
138
+ export function removeInlineStyles(outputCode, replacement) {
139
+ return outputCode.replace(INLINE_STYLES_REMOVAL_REGEX, replacement);
140
+ }
141
+
142
+ // ============================================================================
143
+ // Preprocessor Execution
144
+ // ============================================================================
145
+
146
+ /**
147
+ * Get preprocessor-specific options.
148
+ * @param {string} preprocessor - 'sass' | 'less' | 'stylus'
149
+ * @param {{ sassOptions?: object, lessOptions?: object, stylusOptions?: object }} allOptions
150
+ * @returns {object}
151
+ */
152
+ export function getPreprocessorOptions(preprocessor, { sassOptions = {}, lessOptions = {}, stylusOptions = {} }) {
153
+ return { sass: sassOptions, less: lessOptions, stylus: stylusOptions }[preprocessor] || {};
154
+ }
155
+
156
+ /**
157
+ * Detect preprocessor and run it synchronously if available.
158
+ * Returns the processed CSS and any warning (instead of throwing).
159
+ *
160
+ * @param {string} css - Raw CSS from compiled output
161
+ * @param {string} filePath - Source file path (for loadPaths)
162
+ * @param {{ sassOptions?: object, lessOptions?: object, stylusOptions?: object }} [allOptions]
163
+ * @returns {{ css: string, preprocessor: string, warning: string|null }}
164
+ */
165
+ export function processStyles(css, filePath, allOptions = {}) {
166
+ const available = checkPreprocessors();
167
+ const preprocessor = detectPreprocessor(css);
168
+
169
+ if (preprocessor === 'none' || !available[preprocessor]) {
170
+ return { css, preprocessor: 'none', warning: null };
171
+ }
172
+
173
+ try {
174
+ const opts = getPreprocessorOptions(preprocessor, allOptions);
175
+ const preprocessed = preprocessStylesSync(css, {
176
+ filename: filePath,
177
+ loadPaths: [dirname(filePath), ...(opts.loadPaths || [])],
178
+ compressed: opts.compressed || false,
179
+ preprocessor
180
+ });
181
+ return { css: preprocessed.css, preprocessor, warning: null };
182
+ } catch (err) {
183
+ return {
184
+ css,
185
+ preprocessor,
186
+ warning: `${preprocessor.toUpperCase()} compilation warning: ${err.message}`
187
+ };
188
+ }
189
+ }
190
+
191
+ // ============================================================================
192
+ // Server Components Helpers
193
+ // ============================================================================
194
+
195
+ /**
196
+ * Extract import statements from source code.
197
+ * Handles: static imports (single/multi-line), namespace, dynamic imports,
198
+ * side-effect imports, re-exports, and TypeScript type-only imports.
199
+ *
200
+ * @param {string} code
201
+ * @returns {string[]} Deduplicated array of import specifiers
202
+ */
203
+ export function extractImports(code) {
204
+ const imports = [];
205
+ let match;
206
+
207
+ // 1. All static imports/re-exports: ... from '...'
208
+ // Covers: import X from, import { A } from, import * as ns from,
209
+ // export { x } from, export * from, type imports, multi-line imports
210
+ const fromRegex = /\bfrom\s+['"]([^'"]+)['"]/g;
211
+ while ((match = fromRegex.exec(code)) !== null) {
212
+ imports.push(match[1]);
213
+ }
214
+
215
+ // 2. Side-effect imports: import '...'
216
+ const sideEffectRegex = /\bimport\s+['"]([^'"]+)['"]/g;
217
+ while ((match = sideEffectRegex.exec(code)) !== null) {
218
+ imports.push(match[1]);
219
+ }
220
+
221
+ // 3. Dynamic imports: import('...')
222
+ const dynamicRegex = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
223
+ while ((match = dynamicRegex.exec(code)) !== null) {
224
+ imports.push(match[1]);
225
+ }
226
+
227
+ return [...new Set(imports)];
228
+ }
229
+
230
+ /**
231
+ * Create import violation error message for Client → Server imports.
232
+ * @param {string} clientPath
233
+ * @param {string} serverPath
234
+ * @param {string} importSource
235
+ * @returns {string}
236
+ */
237
+ export function createImportViolationError(clientPath, serverPath, importSource) {
238
+ const clientRelative = relative(process.cwd(), clientPath);
239
+ const serverRelative = relative(process.cwd(), serverPath);
240
+
241
+ return `
242
+ [Pulse] Import Violation: Client Component cannot import Server Component
243
+ at ${clientRelative}
244
+ importing ${importSource}
245
+ resolved to ${serverRelative}
246
+
247
+ Client Components can only import:
248
+ \u2022 Other Client Components ('use client')
249
+ \u2022 Shared utilities (no directive)
250
+ \u2022 Third-party packages
251
+
252
+ \u2192 Move shared logic to a Client Component
253
+ \u2192 Use Server Actions for server-side operations
254
+ \u2192 Create a wrapper Client Component that calls Server Actions
255
+
256
+ See: https://pulse-js.fr/server-components#import-rules
257
+ `.trim();
258
+ }
259
+
260
+ /**
261
+ * Build the manifest data object from a client components map.
262
+ * @param {Map<string, { file: string, chunk: string|null }>} clientComponents
263
+ * @param {{ base?: string }} config
264
+ * @returns {{ version: string, components: object }}
265
+ */
266
+ export function buildManifest(clientComponents, config = {}) {
267
+ const manifest = { version: '1.0', components: {} };
268
+
269
+ for (const [componentId, info] of clientComponents.entries()) {
270
+ if (info.chunk) {
271
+ const base = config.base || '';
272
+ const chunkUrl = posix.join(base, info.chunk);
273
+
274
+ manifest.components[componentId] = {
275
+ id: componentId,
276
+ chunk: chunkUrl,
277
+ exports: ['default', componentId]
278
+ };
279
+ }
280
+ }
281
+
282
+ return manifest;
283
+ }
284
+
285
+ /**
286
+ * Write manifest JSON to disk synchronously (creates directories as needed).
287
+ * @param {object} manifest
288
+ * @param {{ manifestPath?: string, quiet?: boolean }} config
289
+ */
290
+ export function writeManifestToDisk(manifest, config = {}) {
291
+ if (!config.manifestPath) return;
292
+
293
+ try {
294
+ const manifestDir = dirname(config.manifestPath);
295
+ mkdirSync(manifestDir, { recursive: true });
296
+ writeFileSync(config.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
297
+ if (!config.quiet) {
298
+ console.log(`[Pulse Server Components] Manifest written to ${config.manifestPath}`);
299
+ }
300
+ } catch (error) {
301
+ console.warn(`[Pulse Server Components] Failed to write manifest: ${error.message}`);
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Write manifest JSON to disk asynchronously (creates directories as needed).
307
+ * Preferred for Vite/Rollup closeBundle hooks that support async.
308
+ * @param {object} manifest
309
+ * @param {{ manifestPath?: string, quiet?: boolean }} config
310
+ */
311
+ export async function writeManifestToDiskAsync(manifest, config = {}) {
312
+ if (!config.manifestPath) return;
313
+
314
+ try {
315
+ const manifestDir = dirname(config.manifestPath);
316
+ await mkdir(manifestDir, { recursive: true });
317
+ await writeFile(config.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
318
+ if (!config.quiet) {
319
+ console.log(`[Pulse Server Components] Manifest written to ${config.manifestPath}`);
320
+ }
321
+ } catch (error) {
322
+ console.warn(`[Pulse Server Components] Failed to write manifest: ${error.message}`);
323
+ }
324
+ }
325
+
326
+ // ============================================================================
327
+ // Client Manifest Helpers (shared across all SC plugins)
328
+ // ============================================================================
329
+
330
+ /**
331
+ * Load client manifest from disk (for SSR).
332
+ * @param {string} manifestPath - Path to manifest file
333
+ * @returns {{ version: string, components: object }}
334
+ */
335
+ export function loadClientManifest(manifestPath) {
336
+ try {
337
+ const content = readFileSync(manifestPath, 'utf-8');
338
+ return JSON.parse(content);
339
+ } catch (error) {
340
+ console.warn(`Failed to load client manifest from ${manifestPath}:`, error.message);
341
+ return { version: '1.0', components: {} };
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Get client component chunk URL from manifest.
347
+ * @param {{ components: object }} manifest
348
+ * @param {string} componentId
349
+ * @returns {string|null}
350
+ */
351
+ export function getComponentChunk(manifest, componentId) {
352
+ return manifest.components[componentId]?.chunk || null;
353
+ }
354
+
355
+ /**
356
+ * Get all client component IDs from manifest.
357
+ * @param {{ components: object }} manifest
358
+ * @returns {Set<string>}
359
+ */
360
+ export function getClientComponentIds(manifest) {
361
+ return new Set(Object.keys(manifest.components));
362
+ }
@@ -43,35 +43,15 @@
43
43
 
44
44
  import { compile } from '../compiler/index.js';
45
45
  import {
46
- preprocessStylesSync,
47
- isSassAvailable,
48
- isLessAvailable,
49
- isStylusAvailable,
50
- getSassVersion,
51
- getLessVersion,
52
- getStylusVersion,
53
- detectPreprocessor
54
- } from '../compiler/preprocessor.js';
55
- import { dirname, resolve } from 'path';
46
+ logPreprocessorAvailability,
47
+ extractCssFromOutput,
48
+ removeInlineStyles,
49
+ processStyles,
50
+ getPreprocessorOptions
51
+ } from './shared.js';
52
+ import { resolve, dirname } from 'path';
56
53
  import { readFileSync, writeFileSync, mkdirSync } from 'fs';
57
-
58
- // Cache for preprocessor availability checks
59
- let preprocessorCache = null;
60
-
61
- /**
62
- * Check available preprocessors once
63
- */
64
- function checkPreprocessors() {
65
- if (preprocessorCache) return preprocessorCache;
66
-
67
- preprocessorCache = {
68
- sass: isSassAvailable(),
69
- less: isLessAvailable(),
70
- stylus: isStylusAvailable()
71
- };
72
-
73
- return preprocessorCache;
74
- }
54
+ import { readFile } from 'fs/promises';
75
55
 
76
56
  /**
77
57
  * Create Pulse SWC plugin
@@ -80,6 +60,7 @@ export default function pulsePlugin(options = {}) {
80
60
  const {
81
61
  sourceMap = true,
82
62
  extractCss = null, // Path to output CSS file, or null for inline
63
+ quiet = false,
83
64
  sass: sassOptions = {},
84
65
  less: lessOptions = {},
85
66
  stylus: stylusOptions = {}
@@ -99,23 +80,9 @@ export default function pulsePlugin(options = {}) {
99
80
  accumulatedCss = '';
100
81
 
101
82
  if (!buildStarted) {
102
- const available = checkPreprocessors();
103
- const preprocessors = [];
104
-
105
- if (available.sass) {
106
- preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
107
- }
108
- if (available.less) {
109
- preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
110
- }
111
- if (available.stylus) {
112
- preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
83
+ if (!quiet) {
84
+ logPreprocessorAvailability('Pulse SWC');
113
85
  }
114
-
115
- if (preprocessors.length > 0) {
116
- console.log(`[Pulse SWC] Preprocessor support: ${preprocessors.join(', ')}`);
117
- }
118
-
119
86
  buildStarted = true;
120
87
  }
121
88
  },
@@ -152,54 +119,31 @@ export default function pulsePlugin(options = {}) {
152
119
  let extractedCss = null;
153
120
 
154
121
  // Extract CSS from compiled output
155
- const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
156
-
157
- if (stylesMatch) {
158
- let css = stylesMatch[1];
159
-
160
- // Check available preprocessors
161
- const available = checkPreprocessors();
162
- const preprocessor = detectPreprocessor(css);
163
-
164
- // Preprocess if preprocessor detected and available
165
- if (preprocessor !== 'none' && available[preprocessor]) {
166
- try {
167
- const preprocessorOptions = {
168
- sass: sassOptions,
169
- less: lessOptions,
170
- stylus: stylusOptions
171
- }[preprocessor];
122
+ const { css: rawCss, found } = extractCssFromOutput(outputCode);
172
123
 
173
- const preprocessed = preprocessStylesSync(css, {
174
- filename: filePath,
175
- loadPaths: [dirname(filePath), ...(preprocessorOptions.loadPaths || [])],
176
- compressed: preprocessorOptions.compressed || false,
177
- preprocessor // Force detected preprocessor
178
- });
124
+ if (found) {
125
+ const styleResult = processStyles(rawCss, filePath, { sassOptions, lessOptions, stylusOptions });
179
126
 
180
- css = preprocessed.css;
127
+ if (styleResult.warning) {
128
+ console.warn(`[Pulse SWC] ${styleResult.warning}`);
129
+ }
181
130
 
182
- // Log preprocessor usage in verbose mode
183
- if (preprocessorOptions.verbose) {
184
- console.log(`[Pulse] Compiled ${preprocessor.toUpperCase()} in ${filePath}`);
185
- }
186
- } catch (preprocessorError) {
187
- // Emit warning but continue with original CSS
188
- console.warn(`[Pulse SWC] ${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`);
131
+ // Log preprocessor usage in verbose mode
132
+ if (styleResult.preprocessor && styleResult.preprocessor !== 'none') {
133
+ const opts = getPreprocessorOptions(styleResult.preprocessor, { sassOptions, lessOptions, stylusOptions });
134
+ if (opts && opts.verbose) {
135
+ console.log(`[Pulse] Compiled ${styleResult.preprocessor.toUpperCase()} in ${filePath}`);
189
136
  }
190
137
  }
191
138
 
192
- extractedCss = css;
139
+ extractedCss = styleResult.css;
193
140
 
194
141
  if (extractCss) {
195
142
  // Accumulate CSS for later emission
196
- accumulatedCss += `/* ${filePath} */\n${css}\n\n`;
143
+ accumulatedCss += `/* ${filePath} */\n${styleResult.css}\n\n`;
197
144
 
198
145
  // Remove inline CSS injection from output
199
- outputCode = outputCode.replace(
200
- /\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
201
- '// Styles extracted to CSS file'
202
- );
146
+ outputCode = removeInlineStyles(outputCode, '// Styles extracted to CSS file');
203
147
  }
204
148
  // else: keep inline CSS injection
205
149
  }
@@ -231,7 +175,9 @@ export default function pulsePlugin(options = {}) {
231
175
  const cssDir = dirname(cssPath);
232
176
  mkdirSync(cssDir, { recursive: true });
233
177
  writeFileSync(cssPath, accumulatedCss, 'utf8');
234
- console.log(`[Pulse] Emitted CSS to ${extractCss}`);
178
+ if (!quiet) {
179
+ console.log(`[Pulse] Emitted CSS to ${extractCss}`);
180
+ }
235
181
  } catch (writeError) {
236
182
  console.error(`[Pulse] Failed to write CSS file: ${writeError.message}`);
237
183
  }
@@ -284,3 +230,40 @@ export function buildPulseFiles(files, options = {}) {
284
230
  plugin.buildEnd();
285
231
  return results;
286
232
  }
233
+
234
+ // ============================================================================
235
+ // Async Variants
236
+ // ============================================================================
237
+
238
+ /**
239
+ * Transform a .pulse file to JavaScript asynchronously
240
+ * @param {string} filePath - Path to the .pulse file
241
+ * @param {object} options - Plugin options
242
+ * @returns {Promise<{ code: string|null, map: object|null, css: string|null, error: string|null }>}
243
+ */
244
+ export async function transformPulseFileAsync(filePath, options = {}) {
245
+ const resolvedPath = resolve(filePath);
246
+ const source = await readFile(resolvedPath, 'utf8');
247
+ return transformPulseCode(source, { ...options, filename: resolvedPath });
248
+ }
249
+
250
+ /**
251
+ * Batch process multiple .pulse files asynchronously
252
+ * @param {string[]} files - Array of .pulse file paths
253
+ * @param {object} options - Plugin options
254
+ * @returns {Promise<Array<{ file: string, code: string|null, map: object|null, css: string|null, error: string|null }>>}
255
+ */
256
+ export async function buildPulseFilesAsync(files, options = {}) {
257
+ const plugin = pulsePlugin(options);
258
+ plugin.buildStart();
259
+
260
+ const results = await Promise.all(files.map(async (filePath) => {
261
+ const resolvedPath = resolve(filePath);
262
+ const source = await readFile(resolvedPath, 'utf8');
263
+ const result = plugin.transform(source, resolvedPath);
264
+ return { file: filePath, ...result };
265
+ }));
266
+
267
+ plugin.buildEnd();
268
+ return results;
269
+ }