@pulse-editor/cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/server/express.js +25 -2
- package/dist/lib/server/preview/backend/load-remote.cjs +41 -0
- package/dist/lib/server/preview/frontend/index.js +35 -0
- package/dist/lib/server/utils.d.ts +1 -0
- package/dist/lib/server/utils.js +17 -0
- package/dist/lib/webpack/configs/mf-client.js +7 -4
- package/dist/lib/webpack/configs/mf-server.js +10 -3
- package/dist/lib/webpack/configs/preview.d.ts +0 -1
- package/dist/lib/webpack/configs/preview.js +10 -131
- package/dist/lib/webpack/configs/utils.d.ts +2 -2
- package/dist/lib/webpack/configs/utils.js +12 -2
- package/dist/lib/webpack/dist/pregistered-actions.d.ts +2 -0
- package/dist/lib/webpack/dist/pulse.config.d.ts +7 -0
- package/dist/lib/webpack/dist/src/lib/agents/code-modifier-agent.d.ts +2 -0
- package/dist/lib/webpack/dist/src/lib/agents/vibe-coding-agent.d.ts +3 -0
- package/dist/lib/webpack/dist/src/lib/mcp/utils.d.ts +3 -0
- package/dist/lib/webpack/dist/src/lib/streaming/message-stream-controller.d.ts +10 -0
- package/dist/lib/webpack/dist/src/lib/types.d.ts +58 -0
- package/dist/lib/webpack/dist/src/server-function/generate-code/v1/generate.d.ts +5 -0
- package/dist/lib/webpack/dist/src/server-function/generate-code/v2/generate.d.ts +1 -0
- package/dist/lib/webpack/tsconfig.server.json +19 -0
- package/dist/lib/webpack/webpack-config.js +3 -3
- package/dist/lib/webpack/webpack.config.d.ts +2 -0
- package/dist/lib/webpack/webpack.config.js +527 -0
- package/dist/pulse-editor-cli-0.1.7.tgz +0 -0
- package/package.json +3 -2
|
@@ -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
|
|
@@ -108,8 +110,6 @@ if (isPreview) {
|
|
|
108
110
|
});
|
|
109
111
|
app.use(express.static("dist/client"));
|
|
110
112
|
// 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
113
|
app.post("/skill/:actionName", async (req, res) => {
|
|
114
114
|
const { actionName } = req.params;
|
|
115
115
|
if (skillActionNames.length > 0 && !skillActionNames.includes(actionName)) {
|
|
@@ -137,6 +137,29 @@ if (isPreview) {
|
|
|
137
137
|
else if (isDev) {
|
|
138
138
|
/* Dev mode */
|
|
139
139
|
app.use(`/${pulseConfig.id}/${pulseConfig.version}`, express.static("dist"));
|
|
140
|
+
// Expose skill actions as REST API endpoints in dev and preview modes
|
|
141
|
+
app.post(`/skill/:actionName`, async (req, res) => {
|
|
142
|
+
const { actionName } = req.params;
|
|
143
|
+
if (skillActionNames.length > 0 && !skillActionNames.includes(actionName)) {
|
|
144
|
+
res
|
|
145
|
+
.status(404)
|
|
146
|
+
.json({ error: `Skill action "${actionName}" not found.` });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const dir = path.resolve("node_modules/@pulse-editor/cli/dist/lib/server/preview/backend/load-remote.cjs");
|
|
150
|
+
const fileUrl = pathToFileURL(dir).href;
|
|
151
|
+
const { loadFunc } = await import(fileUrl);
|
|
152
|
+
try {
|
|
153
|
+
const action = await loadFunc(`skill/${actionName}`, pulseConfig.id, "http://localhost:3030", pulseConfig.version);
|
|
154
|
+
const result = await action(req.body);
|
|
155
|
+
res.json(result);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
console.error(`❌ Error running skill action "${actionName}": ${message}`);
|
|
160
|
+
res.status(500).json({ error: message });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
140
163
|
app.listen(3030, "0.0.0.0");
|
|
141
164
|
}
|
|
142
165
|
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: [
|
|
@@ -46,7 +46,42 @@ class ErrorBoundary extends React.Component {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
49
|
+
const PREVIEW_MODE_ERRORS = [
|
|
50
|
+
"Current window's ID is not defined.",
|
|
51
|
+
];
|
|
52
|
+
function isPreviewModeError(error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
+
return PREVIEW_MODE_ERRORS.some((msg) => message.includes(msg));
|
|
55
|
+
}
|
|
56
|
+
function showPreviewModeWarning() {
|
|
57
|
+
const existing = document.getElementById('__pulse_preview_warning__');
|
|
58
|
+
if (existing)
|
|
59
|
+
return;
|
|
60
|
+
const banner = document.createElement('div');
|
|
61
|
+
banner.id = '__pulse_preview_warning__';
|
|
62
|
+
Object.assign(banner.style, {
|
|
63
|
+
position: 'fixed',
|
|
64
|
+
bottom: '1rem',
|
|
65
|
+
right: '1rem',
|
|
66
|
+
background: '#2a2000',
|
|
67
|
+
border: '1px solid #665500',
|
|
68
|
+
borderRadius: '8px',
|
|
69
|
+
padding: '0.75rem 1rem',
|
|
70
|
+
fontFamily: 'monospace',
|
|
71
|
+
fontSize: '0.8rem',
|
|
72
|
+
color: '#ffcc00',
|
|
73
|
+
maxWidth: '320px',
|
|
74
|
+
zIndex: '9999',
|
|
75
|
+
lineHeight: '1.4',
|
|
76
|
+
});
|
|
77
|
+
banner.textContent = '⚠ Preview mode: some Pulse Editor platform APIs are not available and will not work.';
|
|
78
|
+
document.body.appendChild(banner);
|
|
79
|
+
}
|
|
49
80
|
function showError(error) {
|
|
81
|
+
if (isPreviewModeError(error)) {
|
|
82
|
+
showPreviewModeWarning();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
50
85
|
root.render(_jsx(ErrorPage, { error: error }));
|
|
51
86
|
}
|
|
52
87
|
// Fallback: catch errors that slip past the React error boundary
|
|
@@ -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
|
+
}
|
|
@@ -51,8 +51,6 @@ class MFClientPlugin {
|
|
|
51
51
|
else {
|
|
52
52
|
console.log("[client] ✅ Reload finished.");
|
|
53
53
|
}
|
|
54
|
-
// Write pulse config to dist
|
|
55
|
-
fs.writeFileSync(path.resolve(this.projectDirName, "dist/pulse.config.json"), JSON.stringify(this.pulseConfig, null, 2));
|
|
56
54
|
});
|
|
57
55
|
}
|
|
58
56
|
else {
|
|
@@ -63,8 +61,6 @@ class MFClientPlugin {
|
|
|
63
61
|
}
|
|
64
62
|
else {
|
|
65
63
|
console.log(`[client] ✅ Successfully built client.`);
|
|
66
|
-
// Write pulse config to dist
|
|
67
|
-
fs.writeFileSync(path.resolve(this.projectDirName, "dist/pulse.config.json"), JSON.stringify(this.pulseConfig, null, 2));
|
|
68
64
|
}
|
|
69
65
|
});
|
|
70
66
|
}
|
|
@@ -106,6 +102,13 @@ export async function makeMFClientConfig(mode) {
|
|
|
106
102
|
const pulseConfig = await loadPulseConfig();
|
|
107
103
|
const mainComponent = "./src/main.tsx";
|
|
108
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
|
+
`);
|
|
109
112
|
return {
|
|
110
113
|
mode: mode,
|
|
111
114
|
name: "client",
|
|
@@ -46,13 +46,15 @@ class MFServerPlugin {
|
|
|
46
46
|
: false;
|
|
47
47
|
if (isActionChange) {
|
|
48
48
|
console.log(`[Server] Detected changes in actions. Recompiling...`);
|
|
49
|
-
compileAppActionSkills(this.pulseConfig);
|
|
49
|
+
this.pulseConfig = compileAppActionSkills(this.pulseConfig);
|
|
50
|
+
this.saveConfig();
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
else {
|
|
53
54
|
console.log(`[Server] 🔄 Building app...`);
|
|
54
55
|
await this.compileServerFunctions(compiler);
|
|
55
|
-
compileAppActionSkills(this.pulseConfig);
|
|
56
|
+
this.pulseConfig = compileAppActionSkills(this.pulseConfig);
|
|
57
|
+
this.saveConfig();
|
|
56
58
|
console.log(`[Server] ✅ Successfully built server.`);
|
|
57
59
|
const funcs = discoverServerFunctions();
|
|
58
60
|
console.log(`\n🛜 Server functions:
|
|
@@ -83,7 +85,8 @@ ${Object.entries(funcs)
|
|
|
83
85
|
else {
|
|
84
86
|
try {
|
|
85
87
|
await this.compileServerFunctions(compiler);
|
|
86
|
-
compileAppActionSkills(this.pulseConfig);
|
|
88
|
+
this.pulseConfig = compileAppActionSkills(this.pulseConfig);
|
|
89
|
+
this.saveConfig();
|
|
87
90
|
}
|
|
88
91
|
catch (err) {
|
|
89
92
|
console.error(`[Server] ❌ Error during compilation:`, err);
|
|
@@ -104,6 +107,10 @@ ${Object.entries(funcs)
|
|
|
104
107
|
console.log("Continuing...");
|
|
105
108
|
}
|
|
106
109
|
}
|
|
110
|
+
saveConfig() {
|
|
111
|
+
const filePath = path.resolve(this.projectDirName, "dist/pulse.config.json");
|
|
112
|
+
fs.writeFileSync(filePath, JSON.stringify(this.pulseConfig, null, 2));
|
|
113
|
+
}
|
|
107
114
|
/**
|
|
108
115
|
* Programmatically call webpack to compile server functions
|
|
109
116
|
* whenever there are changes in the src/server-function directory.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { Configuration as WebpackConfig } from "webpack";
|
|
2
2
|
import { Configuration as DevServerConfig } from "webpack-dev-server";
|
|
3
|
-
export declare function makeMFPreviewConfig(): Promise<WebpackConfig>;
|
|
4
3
|
export declare function makePreviewClientConfig(mode: "development" | "production"): Promise<WebpackConfig & DevServerConfig>;
|
|
@@ -1,133 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import mfNode from "@module-federation/node";
|
|
3
2
|
import CopyWebpackPlugin from "copy-webpack-plugin";
|
|
4
3
|
import fs from "fs";
|
|
5
4
|
import HtmlWebpackPlugin from "html-webpack-plugin";
|
|
6
5
|
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|
7
6
|
import path from "path";
|
|
8
|
-
import
|
|
9
|
-
import { compileAppActionSkills, discoverAppSkillActions, getLocalNetworkIP, loadPulseConfig, } from "./utils.js";
|
|
10
|
-
const { NodeFederationPlugin } = mfNode;
|
|
11
|
-
const { webpack } = wp;
|
|
12
|
-
class MFPreviewPlugin {
|
|
13
|
-
projectDirName;
|
|
14
|
-
pulseConfig;
|
|
15
|
-
constructor(pulseConfig) {
|
|
16
|
-
this.projectDirName = process.cwd();
|
|
17
|
-
this.pulseConfig = pulseConfig;
|
|
18
|
-
}
|
|
19
|
-
apply(compiler) {
|
|
20
|
-
let isFirstRun = true;
|
|
21
|
-
compiler.hooks.environment.tap("WatchSkillPlugin", () => {
|
|
22
|
-
compiler.hooks.thisCompilation.tap("WatchActions", (compilation) => {
|
|
23
|
-
compilation.contextDependencies.add(path.resolve(this.projectDirName, "src/skill"));
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
compiler.hooks.watchRun.tap("MFPreviewPlugin", async () => {
|
|
27
|
-
if (!isFirstRun) {
|
|
28
|
-
const isActionChange = compiler.modifiedFiles
|
|
29
|
-
? Array.from(compiler.modifiedFiles).some((file) => file.includes("src/skill"))
|
|
30
|
-
: false;
|
|
31
|
-
if (isActionChange) {
|
|
32
|
-
console.log("[preview-server] Detected changes in actions. Recompiling...");
|
|
33
|
-
await this.compileActions(compiler);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
console.log("[preview-server] 🔄 Building...");
|
|
38
|
-
await this.compileActions(compiler);
|
|
39
|
-
console.log("[preview-server] ✅ Successfully built preview server.");
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
compiler.hooks.done.tap("MFPreviewPlugin", () => {
|
|
43
|
-
if (isFirstRun) {
|
|
44
|
-
isFirstRun = false;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
console.log("[preview-server] ✅ Reload finished.");
|
|
48
|
-
}
|
|
49
|
-
fs.writeFileSync(path.resolve(this.projectDirName, "dist/pulse.config.json"), JSON.stringify(this.pulseConfig, null, 2));
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
makeNodeFederationPlugin() {
|
|
53
|
-
const actions = discoverAppSkillActions();
|
|
54
|
-
return new NodeFederationPlugin({
|
|
55
|
-
name: this.pulseConfig.id + "_server",
|
|
56
|
-
remoteType: "script",
|
|
57
|
-
useRuntimePlugin: true,
|
|
58
|
-
library: { type: "commonjs-module" },
|
|
59
|
-
filename: "remoteEntry.js",
|
|
60
|
-
exposes: { ...actions },
|
|
61
|
-
}, {});
|
|
62
|
-
}
|
|
63
|
-
async compileActions(compiler) {
|
|
64
|
-
compileAppActionSkills(this.pulseConfig);
|
|
65
|
-
const options = {
|
|
66
|
-
...compiler.options,
|
|
67
|
-
watch: false,
|
|
68
|
-
plugins: [this.makeNodeFederationPlugin()],
|
|
69
|
-
};
|
|
70
|
-
const newCompiler = webpack(options);
|
|
71
|
-
return new Promise((resolve, reject) => {
|
|
72
|
-
newCompiler?.run((err, stats) => {
|
|
73
|
-
if (err) {
|
|
74
|
-
console.error("[preview-server] ❌ Error during recompilation:", err);
|
|
75
|
-
reject(err);
|
|
76
|
-
}
|
|
77
|
-
else if (stats?.hasErrors()) {
|
|
78
|
-
console.error("[preview-server] ❌ Compilation errors:", stats.toJson().errors);
|
|
79
|
-
reject(new Error("Compilation errors"));
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
console.log("[preview-server] ✅ Compiled actions successfully.");
|
|
83
|
-
resolve();
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
export async function makeMFPreviewConfig() {
|
|
90
|
-
const projectDirName = process.cwd();
|
|
91
|
-
const pulseConfig = await loadPulseConfig();
|
|
92
|
-
return {
|
|
93
|
-
mode: "development",
|
|
94
|
-
name: "preview-server",
|
|
95
|
-
entry: {},
|
|
96
|
-
target: "async-node",
|
|
97
|
-
output: {
|
|
98
|
-
publicPath: "auto",
|
|
99
|
-
path: path.resolve(projectDirName, "dist/server"),
|
|
100
|
-
},
|
|
101
|
-
resolve: {
|
|
102
|
-
extensions: [".ts", ".js"],
|
|
103
|
-
},
|
|
104
|
-
plugins: [new MFPreviewPlugin(pulseConfig)],
|
|
105
|
-
module: {
|
|
106
|
-
rules: [
|
|
107
|
-
{
|
|
108
|
-
test: /\.tsx?$/,
|
|
109
|
-
use: {
|
|
110
|
-
loader: "ts-loader",
|
|
111
|
-
options: {
|
|
112
|
-
configFile: "node_modules/.pulse/tsconfig.server.json",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
exclude: [/node_modules/, /dist/],
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
stats: {
|
|
120
|
-
all: false,
|
|
121
|
-
errors: true,
|
|
122
|
-
warnings: true,
|
|
123
|
-
logging: "warn",
|
|
124
|
-
colors: true,
|
|
125
|
-
},
|
|
126
|
-
infrastructureLogging: {
|
|
127
|
-
level: "warn",
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
7
|
+
import { getLocalNetworkIP, loadPulseConfig } from "./utils.js";
|
|
131
8
|
class PreviewClientPlugin {
|
|
132
9
|
projectDirName;
|
|
133
10
|
pulseConfig;
|
|
@@ -151,13 +28,13 @@ class PreviewClientPlugin {
|
|
|
151
28
|
// After build finishes
|
|
152
29
|
compiler.hooks.done.tap("ReloadMessagePlugin", () => {
|
|
153
30
|
if (isFirstRun) {
|
|
154
|
-
const previewStartupMessage = `
|
|
155
|
-
🎉 Your Pulse extension preview \x1b[1m${this.pulseConfig.displayName}\x1b[0m is LIVE!
|
|
156
|
-
|
|
157
|
-
⚡️ Local: http://localhost:3030
|
|
158
|
-
⚡️ Network: http://${this.origin}:3030
|
|
159
|
-
|
|
160
|
-
✨ 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! 🚀
|
|
161
38
|
`;
|
|
162
39
|
console.log("[client-preview] ✅ Successfully built preview.");
|
|
163
40
|
const skillActions = this.pulseConfig?.actions || [];
|
|
@@ -173,6 +50,8 @@ class PreviewClientPlugin {
|
|
|
173
50
|
else {
|
|
174
51
|
console.log("[client-preview] ✅ Reload finished");
|
|
175
52
|
}
|
|
53
|
+
// Write pulse config to dist
|
|
54
|
+
fs.writeFileSync(path.resolve(this.projectDirName, "dist/pulse.config.json"), JSON.stringify(this.pulseConfig, null, 2));
|
|
176
55
|
});
|
|
177
56
|
}
|
|
178
57
|
}
|
|
@@ -8,7 +8,7 @@ export declare function discoverServerFunctions(): {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function discoverAppSkillActions(): {
|
|
10
10
|
[x: string]: string;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
export declare function parseTypeDefs(jsDocs: JSDoc[]): Record<string, Record<string, {
|
|
13
13
|
type: string;
|
|
14
14
|
description: string;
|
|
@@ -17,5 +17,5 @@ export declare function normalizeJSDocPropertyName(name: string | undefined): st
|
|
|
17
17
|
export declare function isPromiseLikeType(type: import("ts-morph").Type): boolean;
|
|
18
18
|
export declare function unwrapPromiseLikeType(type: import("ts-morph").Type): import("ts-morph").Type<import("ts-morph").ts.Type>;
|
|
19
19
|
export declare function getActionType(text: string): TypedVariableType;
|
|
20
|
-
export declare function compileAppActionSkills(pulseConfig: any):
|
|
20
|
+
export declare function compileAppActionSkills(pulseConfig: any): any;
|
|
21
21
|
export declare function generateTempTsConfig(): void;
|
|
@@ -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
|
}, {});
|
|
@@ -225,6 +226,8 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
225
226
|
if (declaration.getKind() !== SyntaxKind.FunctionDeclaration)
|
|
226
227
|
return;
|
|
227
228
|
const funcDecl = declaration.asKindOrThrow(SyntaxKind.FunctionDeclaration);
|
|
229
|
+
// Get action name from path `src/skill/{actionName}/action.ts`
|
|
230
|
+
// Match `*/src/skill/{actionName}/action.ts` and extract {actionName}
|
|
228
231
|
const pattern = /src\/skill\/([^\/]+)\/action\.ts$/;
|
|
229
232
|
const match = file.replaceAll("\\", "/").match(pattern);
|
|
230
233
|
if (!match) {
|
|
@@ -300,9 +303,11 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
300
303
|
params[name] = variable;
|
|
301
304
|
});
|
|
302
305
|
}
|
|
306
|
+
/* Extract return type from JSDoc */
|
|
303
307
|
const rawReturnType = funcDecl.getReturnType();
|
|
304
308
|
const isPromiseLikeReturn = isPromiseLikeType(rawReturnType);
|
|
305
309
|
const returnType = unwrapPromiseLikeType(rawReturnType);
|
|
310
|
+
// Check if the return type is an object
|
|
306
311
|
if (!returnType.isObject()) {
|
|
307
312
|
console.warn(`[Action Registration] Function ${actionName}'s return type should be an object. Skipping...`);
|
|
308
313
|
return;
|
|
@@ -311,13 +316,17 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
311
316
|
const returnProperties = returnType.getProperties();
|
|
312
317
|
const outputTypeDef = typeDefs["output"] ?? {};
|
|
313
318
|
const hasOutputTypeDef = !!typeDefs["output"];
|
|
314
|
-
if (returnProperties.length > 0 &&
|
|
319
|
+
if (returnProperties.length > 0 &&
|
|
320
|
+
!hasOutputTypeDef &&
|
|
321
|
+
!isPromiseLikeReturn) {
|
|
315
322
|
throw new Error(`[Action Validation] Action "${actionName}" in ${file} returns properties but is missing an ` +
|
|
316
323
|
`"@typedef {Output}" JSDoc block. Please document all return values with ` +
|
|
317
324
|
`@typedef {Output} and @property tags.` +
|
|
318
325
|
`Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
|
|
319
326
|
}
|
|
320
|
-
if (returnProperties.length > 0 &&
|
|
327
|
+
if (returnProperties.length > 0 &&
|
|
328
|
+
!hasOutputTypeDef &&
|
|
329
|
+
isPromiseLikeReturn) {
|
|
321
330
|
console.warn(`[Action Validation] Action "${actionName}" in ${file} is missing an "@typedef {Object} output" JSDoc block. ` +
|
|
322
331
|
`Falling back to TypeScript-inferred return metadata because the action returns a Promise.`);
|
|
323
332
|
}
|
|
@@ -361,6 +370,7 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
361
370
|
});
|
|
362
371
|
});
|
|
363
372
|
pulseConfig.actions = actions;
|
|
373
|
+
return pulseConfig;
|
|
364
374
|
}
|
|
365
375
|
// Generate tsconfig for server functions
|
|
366
376
|
export function generateTempTsConfig() {
|
|
@@ -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 @@
|
|
|
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
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { makeMFClientConfig } from "./configs/mf-client.js";
|
|
3
3
|
import { makeMFServerConfig } from "./configs/mf-server.js";
|
|
4
|
-
import {
|
|
4
|
+
import { makePreviewClientConfig } from "./configs/preview.js";
|
|
5
5
|
export async function createWebpackConfig(isPreview, buildTarget, mode) {
|
|
6
6
|
if (isPreview) {
|
|
7
7
|
const previewClientConfig = await makePreviewClientConfig("development");
|
|
8
|
-
const
|
|
9
|
-
return [previewClientConfig,
|
|
8
|
+
const mfServerConfig = await makeMFServerConfig("development");
|
|
9
|
+
return [previewClientConfig, mfServerConfig];
|
|
10
10
|
}
|
|
11
11
|
else if (buildTarget === "server") {
|
|
12
12
|
const mfServerConfig = await makeMFServerConfig(mode);
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-editor/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pulse": "dist/cli.js"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"build": "tsx build.ts",
|
|
14
14
|
"dev": "tsx watch --include \"./src/**/*\" build.ts",
|
|
15
15
|
"test": "prettier --check . && xo && ava",
|
|
16
|
-
"link": "npm link"
|
|
16
|
+
"link": "npm link",
|
|
17
|
+
"pack": "npm run build && npm pack --pack-destination dist"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist"
|