@pulse-editor/cli 0.1.5 → 0.1.6
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/preview/frontend/index.js +35 -0
- package/dist/lib/webpack/configs/mf-client.js +0 -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 +3 -124
- package/dist/lib/webpack/configs/utils.d.ts +1 -1
- package/dist/lib/webpack/configs/utils.js +11 -2
- package/dist/lib/webpack/webpack-config.js +3 -3
- package/dist/pulse-editor-cli-0.1.6.tgz +0 -0
- package/package.json +3 -2
|
@@ -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
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -225,6 +225,8 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
225
225
|
if (declaration.getKind() !== SyntaxKind.FunctionDeclaration)
|
|
226
226
|
return;
|
|
227
227
|
const funcDecl = declaration.asKindOrThrow(SyntaxKind.FunctionDeclaration);
|
|
228
|
+
// Get action name from path `src/skill/{actionName}/action.ts`
|
|
229
|
+
// Match `*/src/skill/{actionName}/action.ts` and extract {actionName}
|
|
228
230
|
const pattern = /src\/skill\/([^\/]+)\/action\.ts$/;
|
|
229
231
|
const match = file.replaceAll("\\", "/").match(pattern);
|
|
230
232
|
if (!match) {
|
|
@@ -300,9 +302,11 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
300
302
|
params[name] = variable;
|
|
301
303
|
});
|
|
302
304
|
}
|
|
305
|
+
/* Extract return type from JSDoc */
|
|
303
306
|
const rawReturnType = funcDecl.getReturnType();
|
|
304
307
|
const isPromiseLikeReturn = isPromiseLikeType(rawReturnType);
|
|
305
308
|
const returnType = unwrapPromiseLikeType(rawReturnType);
|
|
309
|
+
// Check if the return type is an object
|
|
306
310
|
if (!returnType.isObject()) {
|
|
307
311
|
console.warn(`[Action Registration] Function ${actionName}'s return type should be an object. Skipping...`);
|
|
308
312
|
return;
|
|
@@ -311,13 +315,17 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
311
315
|
const returnProperties = returnType.getProperties();
|
|
312
316
|
const outputTypeDef = typeDefs["output"] ?? {};
|
|
313
317
|
const hasOutputTypeDef = !!typeDefs["output"];
|
|
314
|
-
if (returnProperties.length > 0 &&
|
|
318
|
+
if (returnProperties.length > 0 &&
|
|
319
|
+
!hasOutputTypeDef &&
|
|
320
|
+
!isPromiseLikeReturn) {
|
|
315
321
|
throw new Error(`[Action Validation] Action "${actionName}" in ${file} returns properties but is missing an ` +
|
|
316
322
|
`"@typedef {Output}" JSDoc block. Please document all return values with ` +
|
|
317
323
|
`@typedef {Output} and @property tags.` +
|
|
318
324
|
`Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
|
|
319
325
|
}
|
|
320
|
-
if (returnProperties.length > 0 &&
|
|
326
|
+
if (returnProperties.length > 0 &&
|
|
327
|
+
!hasOutputTypeDef &&
|
|
328
|
+
isPromiseLikeReturn) {
|
|
321
329
|
console.warn(`[Action Validation] Action "${actionName}" in ${file} is missing an "@typedef {Object} output" JSDoc block. ` +
|
|
322
330
|
`Falling back to TypeScript-inferred return metadata because the action returns a Promise.`);
|
|
323
331
|
}
|
|
@@ -361,6 +369,7 @@ export function compileAppActionSkills(pulseConfig) {
|
|
|
361
369
|
});
|
|
362
370
|
});
|
|
363
371
|
pulseConfig.actions = actions;
|
|
372
|
+
return pulseConfig;
|
|
364
373
|
}
|
|
365
374
|
// Generate tsconfig for server functions
|
|
366
375
|
export function generateTempTsConfig() {
|
|
@@ -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);
|
|
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.6",
|
|
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"
|