@pulse-editor/cli 0.1.6 → 0.1.8

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.
@@ -29,6 +29,8 @@ if (isDev || isPreview) {
29
29
  console.log("✅ LiveReload connected");
30
30
  });
31
31
  }
32
+ const skillActions = pulseConfig?.actions || [];
33
+ const skillActionNames = skillActions.map((a) => a.name);
32
34
  const app = express();
33
35
  app.use(cors());
34
36
  // Inject the client-side livereload script into HTML responses
@@ -69,31 +71,36 @@ app.all(/^\/server-function\/(.*)/, async (req, res) => {
69
71
  const price = await loadPrice(func, pulseConfig.id, "http://localhost:3030", pulseConfig.version);
70
72
  if (price) {
71
73
  // Make func name and price bold in console
72
- console.log(`🏃 Running function \x1b[1m${func}\x1b[0m, credits consumed: \x1b[1m${price}\x1b[0m`);
74
+ console.log(`🏃 Running server function \x1b[1m${func}\x1b[0m, credits consumed: \x1b[1m${price}\x1b[0m`);
73
75
  }
74
76
  else {
75
- console.log(`🏃 Running function \x1b[1m${func}\x1b[0m.`);
77
+ console.log(`🏃 Running server function \x1b[1m${func}\x1b[0m.`);
76
78
  }
77
79
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
80
  const loadedFunc = await loadFunc(func, pulseConfig.id, "http://localhost:3030", pulseConfig.version);
79
- const response = await loadedFunc(request);
81
+ const funcResult = await loadedFunc(request);
80
82
  const streamPipeline = promisify(pipeline);
81
- if (response) {
83
+ if (funcResult) {
82
84
  // 1️⃣ Set status code
83
- res.status(response.status);
85
+ res.status(funcResult.status);
86
+ console.log(`✅ Server function "${func}" completed with status ${funcResult.status}.`);
84
87
  // 2️⃣ Copy headers
85
- response.headers.forEach((value, key) => {
88
+ funcResult.headers.forEach((value, key) => {
86
89
  res.setHeader(key, value);
87
90
  });
88
91
  // 3️⃣ Pipe body if present
89
- if (response.body) {
90
- const nodeStream = Readable.fromWeb(response.body);
92
+ if (funcResult.body) {
93
+ const nodeStream = Readable.fromWeb(funcResult.body);
91
94
  await streamPipeline(nodeStream, res);
92
95
  }
93
96
  else {
94
97
  res.end();
95
98
  }
96
99
  }
100
+ else {
101
+ console.log(`✅ Server function "${func}" completed with no content.`);
102
+ res.status(204).end();
103
+ }
97
104
  });
98
105
  if (isPreview) {
99
106
  /* Preview mode */
@@ -108,8 +115,6 @@ if (isPreview) {
108
115
  });
109
116
  app.use(express.static("dist/client"));
110
117
  // Expose skill actions as REST API endpoints in dev and preview modes
111
- const skillActions = pulseConfig?.actions || [];
112
- const skillActionNames = skillActions.map((a) => a.name);
113
118
  app.post("/skill/:actionName", async (req, res) => {
114
119
  const { actionName } = req.params;
115
120
  if (skillActionNames.length > 0 && !skillActionNames.includes(actionName)) {
@@ -137,6 +142,29 @@ if (isPreview) {
137
142
  else if (isDev) {
138
143
  /* Dev mode */
139
144
  app.use(`/${pulseConfig.id}/${pulseConfig.version}`, express.static("dist"));
145
+ // Expose skill actions as REST API endpoints in dev and preview modes
146
+ app.post(`/skill/:actionName`, async (req, res) => {
147
+ const { actionName } = req.params;
148
+ if (skillActionNames.length > 0 && !skillActionNames.includes(actionName)) {
149
+ res
150
+ .status(404)
151
+ .json({ error: `Skill action "${actionName}" not found.` });
152
+ return;
153
+ }
154
+ const dir = path.resolve("node_modules/@pulse-editor/cli/dist/lib/server/preview/backend/load-remote.cjs");
155
+ const fileUrl = pathToFileURL(dir).href;
156
+ const { loadFunc } = await import(fileUrl);
157
+ try {
158
+ const action = await loadFunc(`skill/${actionName}`, pulseConfig.id, "http://localhost:3030", pulseConfig.version);
159
+ const result = await action(req.body);
160
+ res.json(result);
161
+ }
162
+ catch (err) {
163
+ const message = err instanceof Error ? err.message : String(err);
164
+ console.error(`❌ Error running skill action "${actionName}": ${message}`);
165
+ res.status(500).json({ error: message });
166
+ }
167
+ });
140
168
  app.listen(3030, "0.0.0.0");
141
169
  }
142
170
  else {
@@ -1,6 +1,47 @@
1
1
  const {createInstance} = require('@module-federation/runtime');
2
2
 
3
+ const FETCH_PATCH_STATE_KEY = '__pulseFetchPatchState__';
4
+
5
+ function isAbsoluteUrl(url) {
6
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(url);
7
+ }
8
+
9
+ function ensureFetchPatched(origin) {
10
+ if (typeof globalThis.fetch !== 'function') {
11
+ return;
12
+ }
13
+
14
+ if (!globalThis[FETCH_PATCH_STATE_KEY]) {
15
+ const originalFetch = globalThis.fetch.bind(globalThis);
16
+ const state = {
17
+ baseOrigin: origin,
18
+ originalFetch,
19
+ };
20
+
21
+ globalThis.fetch = (input, init) => {
22
+ const currentBaseOrigin = state.baseOrigin;
23
+
24
+ if (
25
+ typeof input === 'string' &&
26
+ currentBaseOrigin &&
27
+ !isAbsoluteUrl(input)
28
+ ) {
29
+ return state.originalFetch(new URL(input, currentBaseOrigin).toString(), init);
30
+ }
31
+
32
+ return state.originalFetch(input, init);
33
+ };
34
+
35
+ globalThis[FETCH_PATCH_STATE_KEY] = state;
36
+ return;
37
+ }
38
+
39
+ globalThis[FETCH_PATCH_STATE_KEY].baseOrigin = origin;
40
+ }
41
+
3
42
  async function importRemoteModule(func, appId, origin, version) {
43
+ ensureFetchPatched(origin);
44
+
4
45
  const instance = createInstance({
5
46
  name: 'server_function_runner',
6
47
  remotes: [
@@ -0,0 +1 @@
1
+ export declare function readConfigFile(): Promise<any>;
@@ -0,0 +1,17 @@
1
+ import fs from 'fs/promises';
2
+ export async function readConfigFile() {
3
+ // Read pulse.config.json from dist/client
4
+ // Wait until dist/pulse.config.json exists
5
+ while (true) {
6
+ try {
7
+ await fs.access('dist/pulse.config.json');
8
+ break;
9
+ }
10
+ catch (err) {
11
+ // Wait for 100ms before trying again
12
+ await new Promise(resolve => setTimeout(resolve, 100));
13
+ }
14
+ }
15
+ const data = await fs.readFile('dist/pulse.config.json', 'utf-8');
16
+ return JSON.parse(data);
17
+ }
@@ -102,6 +102,13 @@ export async function makeMFClientConfig(mode) {
102
102
  const pulseConfig = await loadPulseConfig();
103
103
  const mainComponent = "./src/main.tsx";
104
104
  const actions = discoverAppSkillActions();
105
+ console.log(`\n🧩 App skill actions:
106
+ ${Object.entries(actions)
107
+ .map(([name, file]) => {
108
+ return ` - ${name.slice(2)} (from ${file})`;
109
+ })
110
+ .join("\n")}
111
+ `);
105
112
  return {
106
113
  mode: mode,
107
114
  name: "client",
@@ -28,13 +28,13 @@ class PreviewClientPlugin {
28
28
  // After build finishes
29
29
  compiler.hooks.done.tap("ReloadMessagePlugin", () => {
30
30
  if (isFirstRun) {
31
- const previewStartupMessage = `
32
- 🎉 Your Pulse extension preview \x1b[1m${this.pulseConfig.displayName}\x1b[0m is LIVE!
33
-
34
- ⚡️ Local: http://localhost:3030
35
- ⚡️ Network: http://${this.origin}:3030
36
-
37
- ✨ Try it out in your browser and let the magic happen! 🚀
31
+ const previewStartupMessage = `
32
+ 🎉 Your Pulse extension preview \x1b[1m${this.pulseConfig.displayName}\x1b[0m is LIVE!
33
+
34
+ ⚡️ Local: http://localhost:3030
35
+ ⚡️ Network: http://${this.origin}:3030
36
+
37
+ ✨ Try it out in your browser and let the magic happen! 🚀
38
38
  `;
39
39
  console.log("[client-preview] ✅ Successfully built preview.");
40
40
  const skillActions = this.pulseConfig?.actions || [];
@@ -8,7 +8,7 @@ export declare function discoverServerFunctions(): {
8
8
  };
9
9
  export declare function discoverAppSkillActions(): {
10
10
  [x: string]: string;
11
- } | null;
11
+ };
12
12
  export declare function parseTypeDefs(jsDocs: JSDoc[]): Record<string, Record<string, {
13
13
  type: string;
14
14
  description: string;
@@ -137,6 +137,7 @@ export function discoverAppSkillActions() {
137
137
  ["./skill/" + actionName]: "./" + file,
138
138
  };
139
139
  })
140
+ .filter((entry) => entry !== null)
140
141
  .reduce((acc, curr) => {
141
142
  return { ...acc, ...curr };
142
143
  }, {});
@@ -0,0 +1,2 @@
1
+ import { Action } from "@pulse-editor/shared-utils";
2
+ export declare const preRegisteredActions: Record<string, Action>;
@@ -0,0 +1,7 @@
1
+ import { AppConfig } from "@pulse-editor/shared-utils";
2
+ /**
3
+ * Pulse Editor Extension Config
4
+ *
5
+ */
6
+ declare const config: AppConfig;
7
+ export default config;
@@ -0,0 +1,2 @@
1
+ import { ChatPromptTemplate } from "@langchain/core/prompts";
2
+ export declare const codeModifierAgentPrompt: ChatPromptTemplate<any, any>;
@@ -0,0 +1,3 @@
1
+ import { MultiServerMCPClient } from "@langchain/mcp-adapters";
2
+ import { MessageStreamController } from "../streaming/message-stream-controller";
3
+ export declare function runVibeCoding(mcpClient: MultiServerMCPClient, userPrompt: string, controller: MessageStreamController): Promise<void>;
@@ -0,0 +1,3 @@
1
+ import { MultiServerMCPClient } from "@langchain/mcp-adapters";
2
+ export declare function writeFileToFS(mcpClient: MultiServerMCPClient, uri: string, content: string): Promise<string>;
3
+ export declare function callTerminal(mcpClient: MultiServerMCPClient, command: string): Promise<string>;
@@ -0,0 +1,10 @@
1
+ import { AgentTaskMessageData } from "../types";
2
+ export declare class MessageStreamController {
3
+ private controller;
4
+ private msgCounter;
5
+ private messages;
6
+ private timeCountMap;
7
+ constructor(controller: ReadableStreamDefaultController);
8
+ enqueueNew(data: AgentTaskMessageData, isFinal: boolean): number;
9
+ enqueueUpdate(data: AgentTaskMessageData, isFinal: boolean): void;
10
+ }
@@ -0,0 +1,58 @@
1
+ export type VibeDevFlowNode = {
2
+ id: string;
3
+ children: VibeDevFlowNode[];
4
+ };
5
+ export declare enum AgentTaskMessageType {
6
+ Creation = "creation",
7
+ Update = "update"
8
+ }
9
+ export declare enum AgentTaskMessageDataType {
10
+ Notification = "notification",
11
+ ToolCall = "toolCall",
12
+ ArtifactOutput = "artifactOutput"
13
+ }
14
+ /**
15
+ * Data associated with an AgentTaskItem.
16
+ * The fields included depend on the type of the task item.
17
+ *
18
+ * @property type - The type of the task item, defined by the AgentTaskItemType enum.
19
+ * @property title - (Optional) A brief title or summary of the task item.
20
+ * @property description - (Optional) A detailed description of the task item.
21
+ * @property toolName - (Optional) The name of the tool being called (if applicable).
22
+ * @property parameters - (Optional) A record of parameters associated with the tool call (if applicable).
23
+ * @property error - (Optional) An error message if the task item represents an error.
24
+ * @property result - (Optional) The result or output of the task item (if applicable).
25
+ */
26
+ export type AgentTaskMessageData = {
27
+ type?: AgentTaskMessageDataType;
28
+ title?: string;
29
+ description?: string;
30
+ toolName?: string;
31
+ parameters?: Record<string, unknown>;
32
+ error?: string;
33
+ result?: string;
34
+ };
35
+ /**
36
+ * Represents a single task item generated by the agent.
37
+ * Each task item can be of different types such as tool calls, notifications, or errors.
38
+ *
39
+ * @property type - The type of the task item, defined by the AgentTaskItemType enum.
40
+ * @property messageId - The unique identifier for the task item.
41
+ * This is an incremental number representing the n-th task item.
42
+ * @property data - The data associated with the task item, which varies based on the type.
43
+ * @property isFinal - (Optional) Indicates if the task item is final and no further updates are expected.
44
+ */
45
+ export type AgentTaskMessage = {
46
+ type: AgentTaskMessageType;
47
+ messageId: number;
48
+ data: AgentTaskMessageData;
49
+ isFinal?: boolean;
50
+ };
51
+ export type AgentTaskMessageUpdate = {
52
+ type: AgentTaskMessageType;
53
+ messageId: number;
54
+ updateType: "append";
55
+ delta: AgentTaskMessageData;
56
+ isFinal?: boolean;
57
+ timeUsedSec?: string;
58
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * An example function to echo the body of a POST request.
3
+ * This route is accessible at /server-function/echo
4
+ */
5
+ export default function generate(req: Request): Promise<Response>;
@@ -0,0 +1 @@
1
+ export default function handler(req: Request): Promise<Response>;
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "declaration": true,
8
+ "outDir": "dist",
9
+ },
10
+ "include": [
11
+ "../../../../../../src/server-function/**/*",
12
+ "../../../../../../pulse.config.ts",
13
+ "../../../../../../global.d.ts",
14
+ ],
15
+ "exclude": [
16
+ "../../../../../../node_modules",
17
+ "../../../../../../dist",
18
+ ]
19
+ }
@@ -0,0 +1,2 @@
1
+ import wp from 'webpack';
2
+ export declare function createWebpackConfig(isPreview: boolean, buildTarget: 'client' | 'server' | 'both', mode: 'development' | 'production'): Promise<wp.Configuration[]>;
@@ -0,0 +1,527 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
3
+ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
4
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
5
+ import { networkInterfaces } from 'os';
6
+ import path from 'path';
7
+ import { globSync } from 'glob';
8
+ import fs from 'fs';
9
+ import CopyWebpackPlugin from 'copy-webpack-plugin';
10
+ import ts from 'typescript';
11
+ import { pathToFileURL } from 'url';
12
+ import mfNode from '@module-federation/node';
13
+ const { NodeFederationPlugin } = mfNode;
14
+ import wp from 'webpack';
15
+ const { webpack } = wp;
16
+ export async function createWebpackConfig(isPreview, buildTarget, mode) {
17
+ const projectDirName = process.cwd();
18
+ async function loadPulseConfig() {
19
+ // compile to js file and import
20
+ const program = ts.createProgram({
21
+ rootNames: [path.join(projectDirName, 'pulse.config.ts')],
22
+ options: {
23
+ module: ts.ModuleKind.ESNext,
24
+ target: ts.ScriptTarget.ES2020,
25
+ outDir: path.join(projectDirName, 'node_modules/.pulse/config'),
26
+ esModuleInterop: true,
27
+ skipLibCheck: true,
28
+ forceConsistentCasingInFileNames: true,
29
+ },
30
+ });
31
+ program.emit();
32
+ // Fix imports in the generated js file for all files in node_modules/.pulse/config
33
+ globSync('node_modules/.pulse/config/**/*.js', {
34
+ cwd: projectDirName,
35
+ absolute: true,
36
+ }).forEach(jsFile => {
37
+ let content = fs.readFileSync(jsFile, 'utf-8');
38
+ content = content.replace(/(from\s+["']\.\/[^\s"']+)(["'])/g, (match, p1, p2) => {
39
+ // No change if the import already has any extension
40
+ if (p1.match(/\.(js|cjs|mjs|ts|tsx|json)$/)) {
41
+ return match; // No change needed
42
+ }
43
+ return `${p1}.js${p2}`;
44
+ });
45
+ fs.writeFileSync(jsFile, content);
46
+ });
47
+ // Copy package.json if exists
48
+ const pkgPath = path.join(projectDirName, 'package.json');
49
+ if (fs.existsSync(pkgPath)) {
50
+ const destPath = path.join(projectDirName, 'node_modules/.pulse/config/package.json');
51
+ fs.copyFileSync(pkgPath, destPath);
52
+ }
53
+ const compiledConfig = path.join(projectDirName, 'node_modules/.pulse/config/pulse.config.js');
54
+ const mod = await import(pathToFileURL(compiledConfig).href);
55
+ // delete the compiled config after importing
56
+ fs.rmSync(path.join(projectDirName, 'node_modules/.pulse/config'), {
57
+ recursive: true,
58
+ force: true,
59
+ });
60
+ return mod.default;
61
+ }
62
+ const pulseConfig = await loadPulseConfig();
63
+ function getLocalNetworkIP() {
64
+ const interfaces = networkInterfaces();
65
+ for (const iface of Object.values(interfaces)) {
66
+ if (!iface)
67
+ continue;
68
+ for (const config of iface) {
69
+ if (config.family === 'IPv4' && !config.internal) {
70
+ return config.address; // Returns the first non-internal IPv4 address
71
+ }
72
+ }
73
+ }
74
+ return 'localhost'; // Fallback
75
+ }
76
+ const origin = getLocalNetworkIP();
77
+ const previewStartupMessage = `
78
+ 🎉 Your Pulse extension preview \x1b[1m${pulseConfig.displayName}\x1b[0m is LIVE!
79
+
80
+ ⚡️ Local: http://localhost:3030
81
+ ⚡️ Network: http://${origin}:3030
82
+
83
+ ✨ Try it out in your browser and let the magic happen! 🚀
84
+ `;
85
+ const devStartupMessage = `
86
+ 🎉 Your Pulse extension \x1b[1m${pulseConfig.displayName}\x1b[0m is LIVE!
87
+
88
+ ⚡️ Local: http://localhost:3030/${pulseConfig.id}/${pulseConfig.version}/
89
+ ⚡️ Network: http://${origin}:3030/${pulseConfig.id}/${pulseConfig.version}/
90
+
91
+ ✨ Try it out in the Pulse Editor and let the magic happen! 🚀
92
+ `;
93
+ // #region Node Federation Plugin for Server Functions
94
+ function makeNodeFederationPlugin() {
95
+ function discoverServerFunctions() {
96
+ // Get all .ts files under src/server-function and read use default exports as entry points
97
+ const files = globSync('./src/server-function/**/*.ts');
98
+ const entryPoints = files
99
+ .map(file => file.replaceAll('\\', '/'))
100
+ .map(file => {
101
+ return {
102
+ ['./' +
103
+ file.replace('src/server-function/', '').replace(/\.ts$/, '')]: './' + file,
104
+ };
105
+ })
106
+ .reduce((acc, curr) => {
107
+ return { ...acc, ...curr };
108
+ }, {});
109
+ return entryPoints;
110
+ }
111
+ const funcs = discoverServerFunctions();
112
+ console.log(`Discovered server functions:
113
+ ${Object.entries(funcs)
114
+ .map(([name, file]) => {
115
+ return ` - ${name.slice(2)} (from ${file})`;
116
+ })
117
+ .join('\n')}
118
+ `);
119
+ return new NodeFederationPlugin({
120
+ name: pulseConfig.id + '_server',
121
+ remoteType: 'script',
122
+ useRuntimePlugin: true,
123
+ library: { type: 'commonjs-module' },
124
+ filename: 'remoteEntry.js',
125
+ exposes: {
126
+ ...funcs,
127
+ },
128
+ }, {});
129
+ }
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ function compileServerFunctions(compiler) {
132
+ // Remove existing entry points
133
+ try {
134
+ fs.rmSync('dist/server', { recursive: true, force: true });
135
+ }
136
+ catch (e) {
137
+ console.error('Error removing dist/server:', e);
138
+ console.log('Continuing...');
139
+ }
140
+ // Generate tsconfig for server functions
141
+ function generateTempTsConfig() {
142
+ const tempTsConfigPath = path.join(process.cwd(), 'node_modules/.pulse/tsconfig.server.json');
143
+ const tsConfig = {
144
+ compilerOptions: {
145
+ target: 'ES2020',
146
+ module: 'esnext',
147
+ moduleResolution: 'bundler',
148
+ strict: true,
149
+ declaration: true,
150
+ outDir: path.join(process.cwd(), 'dist'),
151
+ },
152
+ include: [
153
+ path.join(process.cwd(), 'src/server-function/**/*'),
154
+ path.join(process.cwd(), 'pulse.config.ts'),
155
+ path.join(process.cwd(), 'global.d.ts'),
156
+ ],
157
+ exclude: [
158
+ path.join(process.cwd(), 'node_modules'),
159
+ path.join(process.cwd(), 'dist'),
160
+ ],
161
+ };
162
+ fs.writeFileSync(tempTsConfigPath, JSON.stringify(tsConfig, null, 2));
163
+ }
164
+ generateTempTsConfig();
165
+ // Run a new webpack compilation to pick up new server functions
166
+ const options = {
167
+ ...compiler.options,
168
+ watch: false,
169
+ plugins: [
170
+ // Add a new NodeFederationPlugin with updated entry points
171
+ makeNodeFederationPlugin(),
172
+ ],
173
+ };
174
+ const newCompiler = webpack(options);
175
+ // Run the new compiler
176
+ newCompiler?.run((err, stats) => {
177
+ if (err) {
178
+ console.error(`[Server] ❌ Error during recompilation:`, err);
179
+ }
180
+ else if (stats?.hasErrors()) {
181
+ console.error(`[Server] ❌ Compilation errors:`, stats.toJson().errors);
182
+ }
183
+ else {
184
+ console.log(`[Server] ✅ Compiled server functions successfully.`);
185
+ }
186
+ });
187
+ }
188
+ // #endregion
189
+ // #region Source file parser for Pulse Config plugin
190
+ class PulseConfigPlugin {
191
+ requireFS = false;
192
+ apply(compiler) {
193
+ compiler.hooks.beforeCompile.tap('PulseConfigPlugin', () => {
194
+ this.requireFS = false;
195
+ globSync(['src/**/*.tsx', 'src/**/*.ts']).forEach(file => {
196
+ const source = fs.readFileSync(file, 'utf8');
197
+ this.scanSource(source);
198
+ });
199
+ // Persist result
200
+ pulseConfig.requireWorkspace = this.requireFS;
201
+ });
202
+ }
203
+ isWorkspaceHook(node) {
204
+ return (ts.isCallExpression(node) &&
205
+ ts.isIdentifier(node.expression) &&
206
+ [
207
+ 'useFileSystem',
208
+ 'useFile',
209
+ 'useReceiveFile',
210
+ 'useTerminal',
211
+ 'useWorkspaceInfo',
212
+ ].includes(node.expression.text));
213
+ }
214
+ scanSource(sourceText) {
215
+ const sourceFile = ts.createSourceFile('temp.tsx', sourceText, ts.ScriptTarget.Latest, true);
216
+ const visit = (node) => {
217
+ // Detect: useFileSystem(...)
218
+ if (this.isWorkspaceHook(node)) {
219
+ this.requireFS = true;
220
+ }
221
+ ts.forEachChild(node, visit);
222
+ };
223
+ visit(sourceFile);
224
+ }
225
+ }
226
+ // #endregion
227
+ // #region Webpack Configs
228
+ const previewClientConfig = {
229
+ mode: mode,
230
+ entry: {
231
+ main: './node_modules/.pulse/server/preview/frontend/index.js',
232
+ },
233
+ output: {
234
+ path: path.resolve(projectDirName, 'dist/client'),
235
+ },
236
+ resolve: {
237
+ extensions: ['.ts', '.tsx', '.js'],
238
+ },
239
+ plugins: [
240
+ new PulseConfigPlugin(),
241
+ new HtmlWebpackPlugin({
242
+ template: './node_modules/.pulse/server/preview/frontend/index.html',
243
+ }),
244
+ new MiniCssExtractPlugin({
245
+ filename: 'globals.css',
246
+ }),
247
+ new CopyWebpackPlugin({
248
+ patterns: [{ from: 'src/assets', to: 'assets' }],
249
+ }),
250
+ {
251
+ apply: compiler => {
252
+ let isFirstRun = true;
253
+ // Before build starts
254
+ compiler.hooks.watchRun.tap('ReloadMessagePlugin', () => {
255
+ if (!isFirstRun) {
256
+ console.log('[client-preview] 🔄 Reloading app...');
257
+ }
258
+ else {
259
+ console.log('[client-preview] 🔄 Building app...');
260
+ }
261
+ });
262
+ // After build finishes
263
+ compiler.hooks.done.tap('ReloadMessagePlugin', () => {
264
+ if (isFirstRun) {
265
+ console.log('[client-preview] ✅ Successfully built preview.');
266
+ console.log(previewStartupMessage);
267
+ isFirstRun = false;
268
+ }
269
+ else {
270
+ console.log('[client-preview] ✅ Reload finished');
271
+ }
272
+ // Write pulse config to dist
273
+ fs.writeFileSync(path.resolve(projectDirName, 'dist/client/pulse.config.json'), JSON.stringify(pulseConfig, null, 2));
274
+ fs.writeFileSync(path.resolve(projectDirName, 'dist/server/pulse.config.json'), JSON.stringify(pulseConfig, null, 2));
275
+ });
276
+ },
277
+ },
278
+ ],
279
+ watchOptions: {
280
+ ignored: /src\/server-function/,
281
+ },
282
+ module: {
283
+ rules: [
284
+ {
285
+ test: /\.tsx?$/,
286
+ use: 'ts-loader',
287
+ exclude: [/node_modules/, /dist/],
288
+ },
289
+ {
290
+ test: /\.css$/i,
291
+ use: [
292
+ MiniCssExtractPlugin.loader,
293
+ 'css-loader',
294
+ {
295
+ loader: 'postcss-loader',
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ },
301
+ stats: {
302
+ all: false,
303
+ errors: true,
304
+ warnings: true,
305
+ logging: 'warn',
306
+ colors: true,
307
+ },
308
+ infrastructureLogging: {
309
+ level: 'warn',
310
+ },
311
+ };
312
+ const mfClientConfig = {
313
+ mode: mode,
314
+ name: 'client',
315
+ entry: './src/main.tsx',
316
+ output: {
317
+ publicPath: 'auto',
318
+ path: path.resolve(projectDirName, 'dist/client'),
319
+ },
320
+ resolve: {
321
+ extensions: ['.ts', '.tsx', '.js'],
322
+ },
323
+ plugins: [
324
+ new PulseConfigPlugin(),
325
+ new MiniCssExtractPlugin({
326
+ filename: 'globals.css',
327
+ }),
328
+ // Copy assets to dist
329
+ new CopyWebpackPlugin({
330
+ patterns: [{ from: 'src/assets', to: 'assets' }],
331
+ }),
332
+ new ModuleFederationPlugin({
333
+ // Do not use hyphen character '-' in the name
334
+ name: pulseConfig.id,
335
+ filename: 'remoteEntry.js',
336
+ exposes: {
337
+ './main': './src/main.tsx',
338
+ },
339
+ shared: {
340
+ react: {
341
+ requiredVersion: '19.2.0',
342
+ import: 'react', // the "react" package will be used a provided and fallback module
343
+ shareKey: 'react', // under this name the shared module will be placed in the share scope
344
+ shareScope: 'default', // share scope with this name will be used
345
+ singleton: true, // only a single version of the shared module is allowed
346
+ },
347
+ 'react-dom': {
348
+ requiredVersion: '19.2.0',
349
+ singleton: true, // only a single version of the shared module is allowed
350
+ },
351
+ },
352
+ }),
353
+ {
354
+ apply: compiler => {
355
+ if (compiler.options.mode === 'development') {
356
+ let isFirstRun = true;
357
+ // Before build starts
358
+ compiler.hooks.watchRun.tap('ReloadMessagePlugin', () => {
359
+ if (!isFirstRun) {
360
+ console.log('[client] 🔄 reloading app...');
361
+ }
362
+ else {
363
+ console.log('[client] 🔄 building app...');
364
+ }
365
+ });
366
+ // Log file updates
367
+ compiler.hooks.invalid.tap('LogFileUpdates', (file, changeTime) => {
368
+ console.log(`[watch] change detected in: ${file} at ${new Date(changeTime || Date.now()).toLocaleTimeString()}`);
369
+ });
370
+ // After build finishes
371
+ compiler.hooks.done.tap('ReloadMessagePlugin', () => {
372
+ if (isFirstRun) {
373
+ console.log('[client] ✅ Successfully built client.');
374
+ console.log(devStartupMessage);
375
+ isFirstRun = false;
376
+ }
377
+ else {
378
+ console.log('[client] ✅ Reload finished.');
379
+ }
380
+ // Write pulse config to dist
381
+ fs.writeFileSync(path.resolve(projectDirName, 'dist/client/pulse.config.json'), JSON.stringify(pulseConfig, null, 2));
382
+ });
383
+ }
384
+ else {
385
+ // Print build success/failed message
386
+ compiler.hooks.done.tap('BuildMessagePlugin', stats => {
387
+ if (stats.hasErrors()) {
388
+ console.log(`[client] ❌ Failed to build client.`);
389
+ }
390
+ else {
391
+ console.log(`[client] ✅ Successfully built client.`);
392
+ // Write pulse config to dist
393
+ fs.writeFileSync(path.resolve(projectDirName, 'dist/client/pulse.config.json'), JSON.stringify(pulseConfig, null, 2));
394
+ }
395
+ });
396
+ }
397
+ },
398
+ },
399
+ ],
400
+ module: {
401
+ rules: [
402
+ {
403
+ test: /\.tsx?$/,
404
+ use: 'ts-loader',
405
+ exclude: [/node_modules/, /dist/],
406
+ },
407
+ {
408
+ test: /\.css$/i,
409
+ use: [
410
+ MiniCssExtractPlugin.loader,
411
+ 'css-loader',
412
+ {
413
+ loader: 'postcss-loader',
414
+ },
415
+ ],
416
+ exclude: [/dist/],
417
+ },
418
+ ],
419
+ },
420
+ stats: {
421
+ all: false,
422
+ errors: true,
423
+ warnings: true,
424
+ logging: 'warn',
425
+ colors: true,
426
+ assets: false,
427
+ },
428
+ infrastructureLogging: {
429
+ level: 'warn',
430
+ },
431
+ };
432
+ const mfServerConfig = {
433
+ mode: mode,
434
+ name: 'server',
435
+ entry: {},
436
+ target: 'async-node',
437
+ output: {
438
+ publicPath: 'auto',
439
+ path: path.resolve(projectDirName, 'dist/server'),
440
+ },
441
+ resolve: {
442
+ extensions: ['.ts', '.js'],
443
+ },
444
+ plugins: [
445
+ {
446
+ apply: compiler => {
447
+ if (compiler.options.mode === 'development') {
448
+ let isFirstRun = true;
449
+ // Before build starts
450
+ compiler.hooks.watchRun.tap('ReloadMessagePlugin', () => {
451
+ if (!isFirstRun) {
452
+ console.log(`[Server] 🔄 Reloading app...`);
453
+ }
454
+ else {
455
+ console.log(`[Server] 🔄 Building app...`);
456
+ }
457
+ compileServerFunctions(compiler);
458
+ });
459
+ // After build finishes
460
+ compiler.hooks.done.tap('ReloadMessagePlugin', () => {
461
+ if (isFirstRun) {
462
+ console.log(`[Server] ✅ Successfully built server.`);
463
+ isFirstRun = false;
464
+ }
465
+ else {
466
+ console.log(`[Server] ✅ Reload finished.`);
467
+ }
468
+ });
469
+ // Watch for changes in the server-function directory to trigger rebuilds
470
+ compiler.hooks.thisCompilation.tap('WatchServerFunctions', compilation => {
471
+ compilation.contextDependencies.add(path.resolve(projectDirName, 'src/server-function'));
472
+ });
473
+ }
474
+ else {
475
+ // Print build success/failed message
476
+ compiler.hooks.done.tap('BuildMessagePlugin', stats => {
477
+ if (stats.hasErrors()) {
478
+ console.log(`[Server] ❌ Failed to build server.`);
479
+ }
480
+ else {
481
+ compileServerFunctions(compiler);
482
+ console.log(`[Server] ✅ Successfully built server.`);
483
+ }
484
+ });
485
+ }
486
+ },
487
+ },
488
+ ],
489
+ module: {
490
+ rules: [
491
+ {
492
+ test: /\.tsx?$/,
493
+ use: {
494
+ loader: 'ts-loader',
495
+ options: {
496
+ configFile: 'node_modules/.pulse/tsconfig.server.json',
497
+ },
498
+ },
499
+ exclude: [/node_modules/, /dist/],
500
+ },
501
+ ],
502
+ },
503
+ stats: {
504
+ all: false,
505
+ errors: true,
506
+ warnings: true,
507
+ logging: 'warn',
508
+ colors: true,
509
+ },
510
+ infrastructureLogging: {
511
+ level: 'warn',
512
+ },
513
+ };
514
+ // #endregion
515
+ if (isPreview) {
516
+ return [previewClientConfig, mfServerConfig];
517
+ }
518
+ else if (buildTarget === 'server') {
519
+ return [mfServerConfig];
520
+ }
521
+ else if (buildTarget === 'client') {
522
+ return [mfClientConfig];
523
+ }
524
+ else {
525
+ return [mfClientConfig, mfServerConfig];
526
+ }
527
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-editor/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "pulse": "dist/cli.js"
Binary file